Automated Package Versioning

This feature is in limited preview — it is available to selected customers only, and behaviour and commands may change based on feedback. Reach out to the flxbl team if you would like early access.

This feature is available in sfp-pro and requires an sfp server, as the server keeps track of the last published version of each package per branch.

Version numbers in sfdx-project.json are a constant source of friction in team development. Every pull request that touches a package potentially fights over the versionNumber field, developers forget to bump versions (or bump them incorrectly), and there is no structured way to express whether a change is a patch, a minor or a major.

Automated package versioning takes the version number out of the day-to-day workflow. Packages you opt in are marked with the sentinel version 0.0.0.NEXT in sfdx-project.json, and the version of every artifact is computed at build time from:

  • The last published version of the package, tracked by the sfp server per branch
  • The intent of the changes since then — derived from your conventional commit messages, explicit version: commit footers, or /version comments on pull requests
// sfdx-project.json — a server-managed package
{
  "path": "src/sales",
  "package": "sales",
  "versionNumber": "0.0.0.NEXT"
}

With this in place, merging a pull request titled feat(sales): add discount calculation and building produces the next minor version of sales automatically — if the last published version was 2.4.1, the new artifact is 2.5.0.<build>. The version field stays constant in source control, so pull requests no longer conflict on it.

How a version is computed

For every package that is part of a build, the version pipeline runs the following stages:

  1. User override — if the package's versionNumber is anything other than 0.0.0.NEXT, that version is used verbatim (with the build number substituted into the fourth segment) and all other stages are skipped. This is what keeps packages you have not opted in behaving exactly as before.
  2. Explicit intent — a /version comment on a merged pull request, or a version: footer in a commit message, declares the bump explicitly. Explicit intent always wins over inference.
  3. Smart defaults — the conventional commit prefixes of the commits since the last published version decide the bump: feat: is a minor, fix: (and everything else) is a patch, and feat!: or a BREAKING CHANGE: footer is a major. Across multiple commits, the highest intent wins.
  4. Dependents expansion — when a change declares dependents:<intent>, the direct dependents of the changed packages are also given a version bump and added to the build.
  5. Promoted version guard — for unlocked packages, if the current version series has already been promoted, a patch is automatically escalated to a minor, since Salesforce does not allow patching a promoted series.
  6. Compute — the intent is applied on top of the last published version.

A few examples, assuming sales was last published as 2.4.1:

Changes since last publishComputed version
fix(sales): correct rounding on totals2.4.2.<build>
feat(sales): add discount calculation2.5.0.<build>
feat(sales)!: rework pricing API3.0.0.<build>
fix(sales): small fix and feat(sales): new feature2.5.0.<build> (highest wins)
chore: update labels (no recognised prefix)2.4.2.<build>
Merged PR carrying the comment /version sales:major3.0.0.<build>

The computed versions are displayed during every build:

Computed Versions:
┌──────────────┬───────────┬─────────────┬────────┬─────────────────────┐
│ Package      │ Previous  │ New Version │ Intent │ Source              │
├──────────────┼───────────┼─────────────┼────────┼─────────────────────┤
│ sales        │ 2.4.1.45  │ 2.5.0.46    │ minor  │ conventional-commit │
├──────────────┼───────────┼─────────────┼────────┼─────────────────────┤
│ sales-ui     │ 1.2.0.45  │ 1.2.1.46    │ patch  │ default             │
└──────────────┴───────────┴─────────────┴────────┴─────────────────────┘

After a successful publish, the new version is recorded on the server and becomes the base for the next build's computation.

What does not change

  • Packages you have not opted in are untouched. Any package with an explicit version (2.4.1.NEXT) keeps its classic behaviour — its version is used as-is, and a manual version bump still triggers a rebuild. You can run a project with a mix of managed and manual packages indefinitely.
  • Version computation never fails a build. Invalid intents are skipped with a warning, and if the server is temporarily unreachable, the build falls back to the versions recorded in your git tags. A 0.0.0.LATEST dependency is the exception, see Depending on the latest published version.

How dependencies are treated

  • A bump never propagates on its own. Changing core-crm does not bump the packages that depend on it. Dependent packages are only bumped when a change explicitly asks for it with dependents:<intent> (in a commit footer or a /version comment).
  • Expansion is single-level. dependents:patch on core-crm bumps the packages that directly depend on core-crm — not their dependents in turn. A package reachable through several dependency paths is bumped once, from its own last published version.
  • Bumped dependents are built. A dependent that receives a version bump is added to the build so an artifact exists for the new version, even though its own source did not change.
  • Dependency version references are not rewritten. Entries in a package's dependencies array stay as written in sfdx-project.json. They are resolved at build time, and the exact version used is recorded in the dependent's artifact.

Dependency version references

ReferenceDependency built in the same runDependency not in the run
X.Y.Z.LATESTThe version just createdThe newest validated version on the X.Y.Z line that is tagged on the branch
0.0.0.LATESTThe version just createdThe version last published on the branch, as recorded on the server

X.Y.Z.LATEST selects a major.minor.patch line. It is not a minimum version. When a server-managed dependency moves to a new patch or minor line, a reference such as 1.4.0.LATEST keeps resolving the 1.4.0 line until you change it.

Depending on the latest published version

For a dependency on a server-managed package of the same project, use 0.0.0.LATEST. The dependent then builds against the version of the dependency last published on the branch, so the reference does not need to follow every change.

// sfdx-project.json
{
  "path": "src/core",
  "package": "core",
  "versionNumber": "0.0.0.NEXT"
},
{
  "path": "src/sales",
  "package": "sales",
  "versionNumber": "0.0.0.NEXT",
  "dependencies": [
    { "package": "core", "versionNumber": "0.0.0.LATEST" }
  ]
}
  • Where it is accepted. The dependency must be a package directory of this project and must itself be server-managed (0.0.0.NEXT). A 0.0.0.LATEST reference to any other package fails the build with an error naming the dependent and the dependency.
  • Exact version. The build looks up the exact version last published on the branch in the DevHub. If that version is not found as a validated, non-deprecated version (for example, it was created with skip-validation or later deprecated), the build fails and names the version. It does not fall back to an older version.
  • Nothing published on the branch yet. When the dependency has no published version on the branch, for example on a new feature branch, the build uses the newest validated version whose git tag is reachable from the branch and logs a warning. If there is none, the build fails.
  • Server unavailable. If the published versions cannot be read from the sfp server, a build with a 0.0.0.LATEST dependency fails instead of falling back.
  • Transitive dependencies. When transitive dependencies are resolved, 0.0.0.LATEST takes precedence over any other reference to the same package, so sales → core → common resolves common the same way core does.
  • Quick builds. sfp quickbuild resolves 0.0.0.LATEST to the version just built or the latest published version, without requiring it to be validated.

Every dependency other than a branch dependency needs a versionNumber. A dependency without one fails the build with an error naming the dependent and the dependency.

Where to go next

On this page