A powerful feature of Nx Release is the fact that it is designed to be used via a Node.js programmatic API in addition to the nx release
CLI.
Releases are a hugely complex and nuanced process, filled with many special cases and idiosyncratic preferences, and it is impossible for a CLI to be able to support all of them out of the box. By having a first-class programmatic API, you can go beyond the CLI and create custom release workflows that are highly dynamic and tailored to your specific needs.
Just as with the CLI, the programmatic API is broken up into the distinct phases of a release: versioning, changelog generation, and publishing. These are available via the releaseChangelog
, releasePublish
, and releaseVersion
functions, which are importable from the nx/release
entrypoint.
These functions are the exact functions used behind the scenes by the CLI, and so they will read from the "release" config in nx.json
in just the same way. If you need even more fine grained control over configuration via the programmatic API, see the section on using the ReleaseClient
class below.
Using the Programmatic API
Section titled “Using the Programmatic API”import { releaseChangelog, releasePublish, releaseVersion } from 'nx/release';
The functions are all asynchronous and return data relevant to their specific phase of the release process. They all support dryRun
and verbose
boolean options to control the level of detail of the output and allow you to preview changes before they are applied. You can inspect their types to see what each one supports in terms of additional config options.
releaseVersion
Section titled “releaseVersion”releaseVersion
will return a NxReleaseVersionResult
object containing the following properties:
workspaceVersion
: The new overall version of the workspace. This is only applicable in cases where all projects are versioned together in a single release group. In all other cases, this will benull
.projectsVersionData
: A map of project names to their version data. The version data is aVersionData
object, which contains the following properties:currentVersion
: The current version of the project. This is the version that the project was at before the versioning process began.newVersion
: The new version of the project.dockerVersion
: The new version of the project if it is a docker project.dependentProjects
: A list of projects that depend on the current project.
releaseGraph
: The release graph that was generated for the nx release config and workspace data. This can be passed to subsequent operations (changelog, publish) to avoid recomputing and improve performance.
const { workspaceVersion, projectsVersionData, releaseGraph } = await releaseVersion({ // E.g. if releasing a specific known version // otherwise if using e.g. conventional-commits this is not needed specifier: '1.0.0', dryRun: true, verbose: true, // ... other options });
console.log(workspaceVersion);console.log(projectsVersionData);console.log(releaseGraph);
releaseChangelog
Section titled “releaseChangelog”releaseChangelog
will return a NxReleaseChangelogResult
object containing the following properties:
workspaceChangelog
: The changelog data for the workspace, if applicable based on the nx release config.releaseVersion
: Relevant version data for the new changelog entry.contents
: The changelog entry contents.
projectChangelogs
: A map of project names to their changelog data, if applicable based on the nx release config.releaseVersion
: Relevant version data for the new changelog entry.contents
: The changelog entry contents.
const { workspaceChangelog, projectChangelogs } = await releaseChangelog({ // Re-use the existing release graph from the releaseVersion // call (if applicable) to avoid recomputing in each subcommand releaseGraph,
// NOTE: One of either version or versionData must be provided versionData: projectsVersionData, // Pass the detailed project version data from the releaseVersion call version: workspaceVersion, // Pass the new workspace version from the releaseVersion call
dryRun: true, verbose: true,
// ... other options});
releasePublish
Section titled “releasePublish”releasePublish
will return a PublishProjectsResult
object, which is a map of project names to their publish result which is a simple object with a code
property representing the exit code of the publish operation for the project.
const publishResults = await releasePublish({ // Re-use the existing release graph from the releaseVersion // call (if applicable) to avoid recomputing in each subcommand releaseGraph,
dryRun: true, verbose: true,
// ... other options});
You can optionally pass through the version data (e.g. if you are using a custom publish executor that needs to be aware of versions). It will then be provided to the publish executor options as nxReleaseVersionData
and can be accessed in the publish executor options like any other option.
const publishResults = await releasePublish({ releaseGraph, versionData: projectsVersionData, dryRun: true, verbose: true,});
NOTE: Passing versionData
to releasePublish
is not required for the default @nx/js publish executor.
It is recommended to use the publishResults to determine the overall success or failure of the release process, for example:
process.exit( Object.values(publishResults).every((result) => result.code === 0) ? 0 : 1);
Example Release Script
Section titled “Example Release Script”How you compose these functions in your release script is of course entirely up to you, and you may even want to break them up into multiple files depending on your use-case.
The below is purely an example of you might compose them together into one holistic release script which uses yargs
to parse script options from the command line (nx release does not require yargs
, it is simply a common choice for this use-case, you can parse arguments however you wish).
import { releaseChangelog, releasePublish, releaseVersion } from 'nx/release';import * as yargs from 'yargs';
(async () => { const options = await yargs .version(false) // don't use the default meaning of version in yargs .option('version', { description: 'Explicit version specifier to use, if overriding conventional commits', type: 'string', }) .option('dryRun', { alias: 'd', description: 'Whether or not to perform a dry-run of the release process, defaults to true', type: 'boolean', default: true, }) .option('verbose', { description: 'Whether or not to enable verbose logging, defaults to false', type: 'boolean', default: false, }) .parseAsync();
const { workspaceVersion, projectsVersionData, releaseGraph } = await releaseVersion({ specifier: options.version, dryRun: options.dryRun, verbose: options.verbose, });
await releaseChangelog({ releaseGraph, // Re-use the existing release graph to avoid recomputing in each subcommand versionData: projectsVersionData, version: workspaceVersion, dryRun: options.dryRun, verbose: options.verbose, });
// publishResults contains a map of project names and their exit codes const publishResults = await releasePublish({ releaseGraph, // Re-use the existing release graph to avoid recomputing in each subcommand dryRun: options.dryRun, verbose: options.verbose, });
process.exit( Object.values(publishResults).every((result) => result.code === 0) ? 0 : 1 );})();
To perform a dry-run of version 1.0.0
, you would therefore run the script like so:
npx tsx scripts/release.ts --version 1.0.0
(Or by using ts-node
or any other tool you prefer to run TS scripts.)
Using the ReleaseClient
class
Section titled “Using the ReleaseClient class”The standalone functions that we covered in the previous sections are actually just bound methods of the ReleaseClient
class that has been pre-instantiated for you.
For an extra layer of control, you can import the ReleaseClient
directly instead of the standalone functions and use your instance's methods for versioning, changelog generation, and publishing.
The reason you might want to do this is configuration. The ReleaseClient
constructor allows you to either override release configuration found in nx.json
, or completely replace it.
import { ReleaseClient } from 'nx/release';
const releaseClientWithMergedConfig = new ReleaseClient( { projects: ['project-1', 'project-2'], // ... more nx release config options }, false // Do NOT ignore nx.json config, merge whatever was given in the first parameter with it);
const releaseClientWithIsolatedConfig = new ReleaseClient( { projects: ['project-1', 'project-2'], // ... more nx release config options }, true // Ignore nx.json config, only the configuration given in the first parameter will be used);
Ignoring the Nx Release configurations in nx.json
can be useful for cases where you have a large, complex workspace and your script only needs to focus on a specific subset in a granular way. One such example would be a script that only focuses on changelog generation for a specific project or projects and does not want to invoke any versioning or publishing logic.
Using the ReleaseClient
Section titled “Using the ReleaseClient”Once you have the instantiated ReleaseClient
class, the usage pattern is the same as with the standalone functions.
import { ReleaseClient } from 'nx/release';
const releaseClient = new ReleaseClient({});
const { workspaceVersion, projectsVersionData, releaseGraph } = await releaseClient.releaseVersion({ // ... options });
const { workspaceChangelog, projectChangelogs } = await releaseClient.releaseChangelog({ releaseGraph, // ... other options });
const publishResults = await releaseClient.releasePublish({ releaseGraph, // ... other options});