Generating a release definition

A release definition lists the exact artifact versions that make up a release. Writing one by hand means looking up the current version of every package in the domain, so sfp generates it instead: sfp releasecandidate generate reads a release config at a given git reference, resolves the version of every package that config selects, and publishes the result to sfp server as a release candidate.

The candidate is what sfp release deploys. Generation sits between publishing artifacts and releasing them:

Before you generate

Three things must already be in place:

  • A release config defining the domain — which packages belong to it, and the release definition properties to apply. See Release Config.
  • Published artifacts. Generation resolves versions from git tags and then checks that each artifact actually exists in a registry the server can reach — its internal registry, or an external npm registry via a configured integration. Packages that were never built and published are left out. See How versions and artifacts are resolved.
  • A reachable sfp server, authenticated with --email or --application-token, since the candidate is published to it.

Generating a candidate

sfp releasecandidate generate \
  --gitref HEAD \
  --configfile config/release-config.yaml \
  --releasename "Release-2.0.0" \
  --branch main \
  --repository myorg/myrepo

This prints the generated definition and publishes it to the server:

release: Release-2.0.0
releaseConfigName: core
skipIfAlreadyInstalled: true
skipArtifactUpdate: false
artifacts:
  apex-logger: 1.0.20-89
  feature-management: 1.0.19-6844956242
promotePackagesBeforeDeploymentToOrg: prod
changelog:
  workItemFilters:
    - (FGK|FFK)-[0-9]{3,4}
  workItemUrl: https://flxbl.atlassian.net/browse
  limit: 30

Every attribute in that file is explained in Release Definitions. What generation decides for you:

  • Which packages appear. The project's packages, filtered through the release config's includeOnlyArtifacts / excludeArtifacts.
  • Which version each gets. The version from the package's latest git tag at --gitref, confirmed to exist in a registry before the package is included. A package with no git tag, or whose artifact is missing from every registry that is checked, is left out — see How versions and artifacts are resolved.
  • Package dependencies. Collected from the project's external package aliases, unless the release config excludes them, and merged with any explicit packageDependencies.
  • Promotion target. promotePackagesBeforeDeploymentToOrg from the release config, or the first active release environment registered for the repository when the config does not set it.

How versions and artifacts are resolved

Generation resolves each package's version from its latest git tag at --gitref — never from a registry — and then confirms the artifact for that version exists before including it. A package with no tag is left out before any registry is consulted.

Existence is checked in two registries, in order:

  1. sfp server's internal registry, under --scope (flxbl-io unless overridden).
  2. The repository's external npm registry — checked only as a fallback, and only when an npm registry integration is configured on the server for the repository. The scope, registry URL, and credentials come from that integration; the scope defaults to the repository owner unless the integration sets one.

A package is included if either registry has the artifact at the tagged version.

Migrating an existing project onto sfp server

The external-registry fallback is what lets you generate release candidates for a project whose earlier artifacts were published to an external npm registry, before it moved onto sfp server. The internal registry starts empty, but generation still resolves those historical versions from the external registry — you do not have to republish the artifacts to sfp server first. For this to work:

  • The git tags must still be in the repository. The version comes from the tag, so a package whose tag is missing at --gitref is left out regardless of what any registry holds.
  • The npm integration must be configured for the repository. Without it, only the internal registry is checked and every pre-migration package is reported missing. The fallback is best-effort: if the integration is absent or unreachable, generation logs that it is validating against the internal registry only and carries on — which then surfaces as a "no package resolved to an artifact" failure rather than an explicit configuration error.
  • The integration's scope must match where the artifacts were published. Validation looks for @<scope>/<package>_sfpowerscripts_artifact at the tagged version; if the configured scope differs from the one the artifacts were originally published under, they are not found.

Validation only confirms the artifact exists — it does not copy it into sfp server's internal registry. The artifact is fetched from the external registry at release time, so keep the integration configured for as long as any candidate still references those versions.

Releasing the candidate

The release config's releaseName becomes the candidate's domain, and --releasename becomes its release name. Together they address it as domain:releaseName — a config with releaseName: core generating Release-2.0.0 produces core:Release-2.0.0.

sfp release takes that identifier and fetches the definition from the server, so no file changes hands:

sfp release --releasecandidate core:Release-2.0.0 --targetorg production --repository myorg/myrepo

See Releasing to an environment for what happens during a release.

The same identifier addresses the candidate throughout its life:

sfp releasecandidate status   -n core:Release-2.0.0 --repository myorg/myrepo
sfp releasecandidate update   -n core:Release-2.0.0 --repository myorg/myrepo --exclude pkg-a
sfp releasecandidate finalize -n core:Release-2.0.0 --repository myorg/myrepo

Candidates are created as pending. A successful deployment moves one to in_progress, and when the project has autoReleaseFinalization enabled it is finalized automatically once it has reached every release environment for its domain — otherwise finalize marks it fully deployed. abort marks a candidate undeployable. Both finalized and aborted are terminal: a candidate in either state can no longer be updated. The full command set is in the CLI reference.

Command attributes

FlagDescriptionRequired
-c, --gitrefGit reference (commit SHA, tag, or branch) the artifact versions are resolved fromYes
-f, --configfilePath to the release config YAML fileYes
-n, --releasenameName of the release candidateYes
--repositoryRepository identifier — owner/repo for GitHub and GitLab, org/project/repo for Azure DevOps. Defaults to GITHUB_REPOSITORY / GITLAB_REPOSITORYYes, when publishing
-b, --branchBranch recorded against the release candidateNo
-m, --metadataAdditional metadata in JSON format, copied into the release definitionNo
-s, --statusInitial status. Candidates are currently always created as pending; use sfp releasecandidate status --set to change itNo
--scopeRegistry scope used when validating artifacts. Defaults to flxbl-ioNo
--include-packageRestrict the candidate to this package. Repeat for multiple packages. Available from v52.5.0No
--nopublishGenerate the release definition without publishing it to the serverNo
--outputPath to write the generated YAML. Requires --nopublishNo
-e, --emailEmail of the authenticated user. Ignored when --application-token is suppliedNo
-t, --application-tokenApplication token for CI/CD authentication. Can be set via SFP_SERVER_TOKENNo
--sfp-server-urlURL of sfp server. Can be set via SFP_SERVER_URL or sfp config:set server-urlNo

Releasing a subset of the domain

--include-package narrows the candidate to named packages, rather than everything the release config selects:

sfp releasecandidate generate \
  --gitref HEAD \
  --configfile config/release-config.yaml \
  --releasename "Hotfix-2.0.1" \
  --repository myorg/myrepo \
  --include-package src-sales-core \
  --include-package src-lead-scoring

Selection is exact: if a named package does not resolve to an artifact, the command fails rather than generating a candidate that quietly omits it.

Writing the definition to a file

--nopublish skips the server, and --output writes the YAML to disk — useful for inspecting what a config would produce, or for feeding sfp repo:patch:

mkdir -p releases

sfp releasecandidate generate \
  --gitref HEAD \
  --configfile config/release-config.yaml \
  --releasename "Release-2.0.0" \
  --repository myorg/myrepo \
  --nopublish \
  --output releases/Release-2.0.0.yml

--output depends on --nopublish, so one invocation either writes a file or publishes a candidate, never both. The parent directory is not created — it must exist first.

To publish and keep a copy on disk, generate as normal and then fetch it:

sfp releasecandidate fetch -n core:Release-2.0.0 --repository myorg/myrepo -o releases/Release-2.0.0.yml

When generation fails

  • No package resolved to an artifact. Generation fails rather than publishing an empty release. Usually the git tags exist but the artifacts were never published to the registry — check the build log for npm-validation warnings. When migrating a project onto sfp server, also confirm the npm registry integration is configured: without it the external registry is never consulted and pre-migration artifacts are reported missing. See How versions and artifacts are resolved.
  • A package named in --include-package did not resolve.
  • No repository identifier, and neither GITHUB_REPOSITORY nor GITLAB_REPOSITORY is set, while publishing.
  • No promotion target. The release config does not set promotePackagesBeforeDeploymentToOrg and no active release environment is registered for the repository.

Coming from v50: this command was sfp releasedefinition:generate, which wrote definitions to a directory. It was removed in v51, along with -d, --directory, --branchname, --nopush and --forcepush. -b, --branch also changed meaning — it records a branch against the candidate rather than naming a branch to commit the definition into. See Upgrading the CLI from v50 to v51.

On this page