For the complete documentation index, see llms.txt. This page is also available as Markdown.

Automated Package Versioning (Limited Preview)

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 publish
Computed version

fix(sales): correct rounding on totals

2.4.2.<build>

feat(sales): add discount calculation

2.5.0.<build>

feat(sales)!: rework pricing API

3.0.0.<build>

fix(sales): small fix and feat(sales): new feature

2.5.0.<build> (highest wins)

chore: update labels (no recognised prefix)

2.4.2.<build>

Merged PR carrying the comment /version sales:major

3.0.0.<build>

The computed versions are displayed during every build:

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.

  • Builds never fail because of versioning. 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.

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 (for example "versionNumber": "1.0.0.LATEST") are left for you to manage; unlocked package dependencies use minimum-version matching, so a bump in a dependency does not invalidate them. Raising a reference after a major change in a dependency remains a deliberate, manual decision.

Where to go next

Last updated

Was this helpful?