Versioning & Releases
SUDIGITAL follows Semantic Versioning (MAJOR.MINOR.PATCH). The root package.json version is the single source of truth for the whole monorepo, and every app shows the running version in its sidebar footer.
Bump rules
The project is still on 0.y.z, so the pre-1.0 rule applies: 0.y behaves as the major number and z is the patch number. The table below is what release-please produces today; the standard SemVer table resumes at 1.0.0.
| Change | Bump while 0.y.z | Bump from 1.0.0 |
|---|---|---|
| Breaking change to a shipped surface | MINOR (0.9.2 → 0.10.0) | MAJOR |
| Backwards-compatible feature | PATCH (0.9.2 → 0.9.3) | MINOR |
| Backwards-compatible fix | PATCH | PATCH |
Pre-1.0 bumps are release-please's bump-minor-pre-major and bump-patch-for-minor-pre-major options, both enabled in release-please-config.json. Never hand-edit the version to work around them.
Pre-1.0 policy
Semantic Versioning reserves 0.y.z for initial development: anything MAY change at any time and the public API SHOULD NOT be considered stable. The same specification's FAQ adds that software already used in production "should probably already be 1.0.0".
SUDIGITAL ships to production, but its API, on-chain contracts and payment surfaces are still moving, so it stays pre-1.0 deliberately — and follows the convention the wider 0.x ecosystem uses (Rust crates, cargo, and release-please's own pre-major mode): while the version is below 1.0.0, 0.y acts as the major number, so a backwards-compatible feature is a PATCH and only a breaking change moves y.
That preserves three properties:
- A
MINORbump always means "a breaking change happened", the same discipline a1.xproject applies toMAJOR. - Features no longer inflate the minor number, so the project stops drifting toward
1.0.0by accident —1.0.0stays an explicit stability declaration. - The mapping only affects releases cut after the change lands; past tags and changelog entries are untouched.
Rejected alternative: declare 1.0.0 now and keep feat → MINOR. It signals a stable API, which the contract and payment surfaces are not yet ready to promise, so it is deferred until the ecosystem is frozen.
Commit types drive the bump
release-please only reads Conventional Commit prefixes, so the commit type and the Notion scope matter:
| Commit prefix | Bump while 0.y.z | Bump from 1.0.0 |
|---|---|---|
feat(SUDI-xxx): | PATCH | MINOR |
fix(SUDI-xxx): | PATCH | PATCH |
feat(SUDI-xxx)!: / BREAKING CHANGE: footer | MINOR | MAJOR |
perf, refactor, chore, docs, test, ci, build, style, revert | none | none |
Nothing is enforced automatically — write the Conventional Commit type by hand. The hidden types (chore, ci, docs, test, build, style) never produce a release on their own, so an infra-only promotion cuts no new version. That is expected, not a broken pipeline: release-please is idle until a feat or fix reaches dev.
Branch names keep their human prefixes (feature/, bugfix/, hotfix/, improvement/, chore/) — only the commit type follows Conventional Commits. The workflow triggers on push: dev, so it is active on the integration branch; dev → main only promotes a version that has already been released.
Release flow
Versioning needs no label — the commit types decide the bump. Deploying the development environment is what the deploy label gates. release-please runs on every push to dev and opens a chore(release): X.Y.Z PR automatically when the Conventional Commits since the last release include a feat or fix:
- Feature work merges into
dev. A PR merged intodevdeploys the development environment (deploy.yml) when it carries thedeploylabel, or when its head branch is arelease-please--branch. - When a
feat/fixhas landed, release-please opens the version PR ondev; the weekly release train merges it (or you merge it out of band), which bumps the rootpackage.jsonversion, updatesCHANGELOG.md, records the version in.release-please-manifest.json, and tagsvX.Y.Z. Achore/ci/docs-only merge cuts no release (standard). Merging the version PR also redeploys dev — that merge carries nodeploylabel, it is recognised by itsrelease-please--head branch. devis promoted tomainthrough a manually approved PR, which deploys production (deploy-prod.yml).
Versioning itself has no label — release-please derives the bump from the commit types. Deploy has one: deploy for a feature PR, release-please-- for a version PR. There is no main -> dev back-merge. The PR Check version-monotonic job blocks any PR that would lower its target branch's version.
Release cadence
A version bump is not the same event as a deploy, so releases are cut on a schedule rather than on every merge. Left unmanaged, release-please opens a version PR per merge and the team merges it immediately — which is what the history shows (22 tags across 7 days, 2026-10-07 → 2026-10-13, three of them on 2026-10-13, including five-release days on 2026-10-08 and 2026-10-11), leaving CHANGELOG.md as a per-merge log instead of release notes.
Industry practice splits two ways:
- Continuous delivery — one version per deploy. Normal for internal services with no external consumers, where the changelog is a build log nobody tracks.
- Release train — a fixed cut (weekly, biweekly) that batches whatever is ready since the last one. The norm for anything with external consumers; Node.js, Angular, Kubernetes and React all release on a schedule rather than on every merge, so a version number means "these changes shipped together".
SUDIGITAL ships to production with public contracts, payments and an API, so it uses a release train: .github/workflows/release-train.yml merges the pending version PR every Monday at 09:00 UTC, then dispatches the release finalisation (the vX.Y.Z tag and GitHub Release) and the development deploy.
- Feature PRs still merge into
devcontinuously — only the version cut is scheduled. - An out-of-band release runs the same workflow manually (
workflow_dispatch), so a hotfix never has to wait for Monday. - If no version PR is pending, the train exits cleanly and ships nothing.
scheduleonly fires from the repository default branch (main), so the train becomes active oncerelease-train.ymlis promoteddev → main; until then the version PR is merged by hand exactly as before.
Where the shown version comes from
The released version is owned by release-please. release-please runs on dev and its chore(release): X.Y.Z PR (see Release flow) bumps the root package.json, updates CHANGELOG.md, tags vX.Y.Z, and writes the released version to .release-please-manifest.json; the dev → main promotion carries it to production.
tooling/build-info.ts reads that manifest (inlined as __APP_VERSION__); it never computes a version itself, so the footer always shows the released SemVer:
- Local (
vite, development) → the released version, e.g.v0.1.0 - Development (
dev) → the released version - Production (
main) → the released version
The commit is shown separately as __APP_COMMIT__. If the manifest is unavailable the build falls back to the root package.json version, then 0.0.0.
Non-Conventional commit types (bugfix:, improvement:, …) make release-please skip a bump. Nothing enforces the convention automatically — write Conventional Commits by hand.
Environments
| Environment | Branch | Host | Footer version |
|---|---|---|---|
| Local | — | localhost | vX.Y.Z |
| Development | dev | dev.*.sudigital.com | vX.Y.Z |
| Production | main | *.sudigital.com | vX.Y.Z |
The sidebar footer renders v<version> · <environment> (<commit>) so it is always clear which version of which environment is in front of you.