nx/e2e/release/src/release.test.ts
James Henry ee097a8e10
feat(release)!: useLegacyVersioning is false by default, migrate config (#30838)
This PR updates `nx release` to use the revamped versioning
implementation by default. It also updates and adds relevant
documentation, and provides an automated migration for the new
configuration structure.

For anyone exclusively versioning TS/JS packages, there should be no
real difference to your experience (although a number of bugs have been
fixed and new features around updating multiple `package.json` files at
once are now available to you with this change).

For the lifecycle of Nx v21, `release.version.useLegacyVersioning` will
remain as a option that can be set to `true` to revert to the old
behavior and configuration structure.

NOTE: This should only be a temporary solution, for example if one of
the plugins you use does provide a `VersionActions` based versioning
implementation yet. The option and legacy implementation will be removed
entirely in Nx v22 (in ~6 months).

BREAKING CHANGE:

**⚠️ For any early adopters of `VersionActions` in Nx 20.8 when it was
opt-in, there are breaking changes to the abstract class here as well.**

`manifestRootsToUpdate` has gone from `string[]` to `manifestsToUpdate:
{ manifestPath: string; preserveLocalDependencyProtocols: boolean; }[]`
to support controlling the local dependency updates per manifest in
order to support advanced source vs dist scenarios, and correspondingly
`isLocalDependencyProtocol` has been removed from the abstract class and
the method will no longer be called from the core logic. It should be
thought of as an implementation detail of `updateProjectDependencies`
instead.
2025-04-24 22:09:38 -04:00

1222 lines
36 KiB
TypeScript

import {
type NxJsonConfiguration,
detectPackageManager,
getPackageManagerCommand,
} from '@nx/devkit';
import {
cleanupProject,
createFile,
exists,
killProcessAndPorts,
newProject,
readFile,
readJson,
runCLI,
runCommandAsync,
runCommandUntil,
tmpProjPath,
uniq,
updateJson,
} from '@nx/e2e/utils';
import { execSync } from 'node:child_process';
import type { NxReleaseVersionConfiguration } from 'nx/src/config/nx-json';
expect.addSnapshotSerializer({
serialize(str: string) {
return (
str
// Remove all output unique to specific projects to ensure deterministic snapshots
.replaceAll(/my-pkg-\d+/g, '{project-name}')
.replaceAll(
/integrity:\s*.*/g,
'integrity: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'
)
.replaceAll(/\b[0-9a-f]{40}\b/g, '{SHASUM}')
.replaceAll(/\d*B index\.js/g, 'XXB index.js')
.replaceAll(/\d*B project\.json/g, 'XXB project.json')
.replaceAll(/\d*B package\.json/g, 'XXXB package.json')
.replaceAll(/size:\s*\d*\s?B/g, 'size: XXXB')
.replaceAll(/\d*\.\d*\s?kB/g, 'XXX.XXX kb')
.replaceAll(/[a-fA-F0-9]{7}/g, '{COMMIT_SHA}')
.replaceAll(/Test @[\w\d]+/g, 'Test @{COMMIT_AUTHOR}')
// Normalize the version title date.
.replaceAll(/\(\d{4}-\d{2}-\d{2}\)/g, '(YYYY-MM-DD)')
// We trim each line to reduce the chances of snapshot flakiness
.split('\n')
.map((r) => r.trim())
.join('\n')
);
},
test(val: string) {
return val != null && typeof val === 'string';
},
});
describe('nx release', () => {
let pkg1: string;
let pkg2: string;
let pkg3: string;
let previousPackageManager: string;
beforeAll(() => {
previousPackageManager = process.env.SELECTED_PM;
// Ensure consistent package manager usage in all environments for this file
process.env.SELECTED_PM = 'npm';
newProject({
packages: ['@nx/js'],
});
pkg1 = uniq('my-pkg-1');
runCLI(`generate @nx/workspace:npm-package ${pkg1}`);
pkg2 = uniq('my-pkg-2');
runCLI(`generate @nx/workspace:npm-package ${pkg2}`);
pkg3 = uniq('my-pkg-3');
runCLI(`generate @nx/workspace:npm-package ${pkg3}`);
// Update pkg2 to depend on pkg1
updateJson(`${pkg2}/package.json`, (json) => {
json.dependencies ??= {};
json.dependencies[`@proj/${pkg1}`] = '0.0.0';
return json;
});
});
afterAll(() => {
cleanupProject();
process.env.SELECTED_PM = previousPackageManager;
});
it('should version and publish multiple related npm packages with zero config', async () => {
// Normalize git committer information so it is deterministic in snapshots
await runCommandAsync(`git config user.email "test@test.com"`);
await runCommandAsync(`git config user.name "Test"`);
// Create a baseline version tag
await runCommandAsync(`git tag v0.0.0`);
// Add an example feature so that we can generate a CHANGELOG.md for it
createFile('an-awesome-new-thing.js', 'console.log("Hello world!");');
await runCommandAsync(
`git add --all && git commit -m "feat: an awesome new feature"`
);
// We need a valid git origin to exist for the commit references to work (and later the test for createRelease)
await runCommandAsync(
`git remote add origin https://github.com/nrwl/fake-repo.git`
);
const packageManager = detectPackageManager(tmpProjPath());
const updateLockFileCommand =
getPackageManagerCommand(packageManager).updateLockFile;
const versionOutput = runCLI(`release version 999.9.9`);
/**
* We can't just assert on the whole version output as a snapshot because the order of the projects
* is non-deterministic, and not every project has the same number of log lines (because of the
* dependency relationship)
*/
expect(
versionOutput.match(/Running release version for project: my-pkg-\d*/g)
.length
).toEqual(3);
expect(
versionOutput.match(
/Resolved the current version as 0\.0\.0 from manifest: my-pkg-\d*\/package\.json/g
).length
).toEqual(3);
// First project
expect(
versionOutput.match(
/Applied explicit semver value "999\.9\.9", from the given specifier, to get new version 999\.9\.9/g
).length
).toEqual(1);
// Other projects
expect(
versionOutput.match(
/Applied version 999\.9\.9 directly, because the project is a member of a fixed release group containing my-pkg-\d*/g
).length
).toEqual(2);
expect(
versionOutput.match(
/New version 999\.9\.9 written to manifest: my-pkg-\d*\/package\.json/g
).length
).toEqual(3);
// Generate a changelog for the new version
expect(exists('CHANGELOG.md')).toEqual(false);
const changelogOutput = runCLI(`release changelog 999.9.9`);
expect(changelogOutput).toMatchInlineSnapshot(`
NX Generating an entry in CHANGELOG.md for v999.9.9
+ ## 999.9.9 (YYYY-MM-DD)
+
+ ### 🚀 Features
+
+ - an awesome new feature ([{COMMIT_SHA}](https://github.com/nrwl/fake-repo/commit/{COMMIT_SHA}))
+
+ ### ❤️ Thank You
+
+ - Test @{COMMIT_AUTHOR}
NX Committing changes with git
NX Tagging commit with git
`);
expect(readFile('CHANGELOG.md')).toMatchInlineSnapshot(`
## 999.9.9 (YYYY-MM-DD)
### 🚀 Features
- an awesome new feature ([{COMMIT_SHA}](https://github.com/nrwl/fake-repo/commit/{COMMIT_SHA}))
### ❤️ Thank You
- Test @{COMMIT_AUTHOR}
`);
// This is the verdaccio instance that the e2e tests themselves are working from
const e2eRegistryUrl = execSync('npm config get registry')
.toString()
.trim();
// Thanks to the custom serializer above, the publish output should be deterministic
const publishOutput = runCLI(`release publish`);
expect(publishOutput).toMatchInlineSnapshot(`
NX Running target nx-release-publish for 3 projects:
- {project-name}
- {project-name}
- {project-name}
> nx run {project-name}:nx-release-publish
📦 @proj/{project-name}@999.9.9
=== Tarball Contents ===
XXB index.js
XXXB package.json
XXB project.json
=== Tarball Details ===
name: @proj/{project-name}
version: 999.9.9
filename: proj-{project-name}-999.9.9.tgz
package size: XXXB
unpacked size: XXXB
shasum: {SHASUM}
integrity: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
total files: 3
Published to ${e2eRegistryUrl} with tag "latest"
> nx run {project-name}:nx-release-publish
📦 @proj/{project-name}@999.9.9
=== Tarball Contents ===
XXB index.js
XXXB package.json
XXB project.json
=== Tarball Details ===
name: @proj/{project-name}
version: 999.9.9
filename: proj-{project-name}-999.9.9.tgz
package size: XXXB
unpacked size: XXXB
shasum: {SHASUM}
integrity: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
total files: 3
Published to ${e2eRegistryUrl} with tag "latest"
> nx run {project-name}:nx-release-publish
📦 @proj/{project-name}@999.9.9
=== Tarball Contents ===
XXB index.js
XXXB package.json
XXB project.json
=== Tarball Details ===
name: @proj/{project-name}
version: 999.9.9
filename: proj-{project-name}-999.9.9.tgz
package size: XXXB
unpacked size: XXXB
shasum: {SHASUM}
integrity: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
total files: 3
Published to ${e2eRegistryUrl} with tag "latest"
NX Successfully ran target nx-release-publish for 3 projects
`);
expect(
execSync(`npm view @proj/${pkg1} version`).toString().trim()
).toEqual('999.9.9');
expect(
execSync(`npm view @proj/${pkg2} version`).toString().trim()
).toEqual('999.9.9');
expect(
execSync(`npm view @proj/${pkg3} version`).toString().trim()
).toEqual('999.9.9');
// Add custom nx release config to control version resolution
updateJson<NxJsonConfiguration>('nx.json', (nxJson) => {
nxJson.release = {
groups: {
default: {
// @proj/source will be added as a project by the verdaccio setup, but we aren't versioning or publishing it, so we exclude it here
projects: ['*', '!@proj/source'],
version: {
generator: '@nx/js:release-version',
// Resolve the latest version from the custom registry instance, therefore finding the previously published versions
currentVersionResolver: 'registry',
currentVersionResolverMetadata: {
registry: e2eRegistryUrl,
tag: 'latest',
},
},
},
},
};
return nxJson;
});
// Run additional custom verdaccio instance to publish the packages to
runCLI(`generate setup-verdaccio`);
const verdaccioPort = 7190;
const customRegistryUrl = `http://localhost:${verdaccioPort}`;
const process = await runCommandUntil(
`local-registry @proj/source --port=${verdaccioPort}`,
(output) => output.includes(`warn --- http address`)
);
const versionOutput2 = runCLI(`release version premajor --preid next`); // version using semver keyword this time (and custom preid)
expect(
versionOutput2.match(/Running release version for project: my-pkg-\d*/g)
.length
).toEqual(3);
// It should resolve the current version from the registry once because it is a fixed release group...
expect(
versionOutput2.match(
new RegExp(
`Resolved the current version as 999.9.9 from the remote registry: "@proj:registry=${e2eRegistryUrl}" tag=latest`,
'g'
)
).length
).toEqual(1);
// ...and then reuse it twice
expect(
versionOutput2.match(
new RegExp(
`Reusing the current version 999.9.9 already resolved for my-pkg-\\d* from the registry: "@proj:registry=${e2eRegistryUrl}" tag=latest`,
'g'
)
).length
).toEqual(2);
// First project
expect(
versionOutput2.match(
/Applied semver relative bump "premajor", from the given specifier, to get new version 1000\.0\.0-next\.0/g
).length
).toEqual(1);
// Other projects
expect(
versionOutput2.match(
/Applied version 1000\.0\.0-next\.0 directly, because the project is a member of a fixed release group containing my-pkg-\d*/g
).length
).toEqual(2);
expect(
versionOutput2.match(
/New version 1000\.0\.0-next\.0 written to manifest: my-pkg-\d*\/package\.json/g
).length
).toEqual(3);
// Perform an initial dry-run of the publish to the custom registry (not e2e registry), and a custom dist tag of "next"
const publishToNext = `release publish --registry=${customRegistryUrl} --tag=next`;
const publishOutput2 = runCLI(`${publishToNext} --dry-run`);
expect(publishOutput2).toMatchInlineSnapshot(`
NX Running target nx-release-publish for 3 projects:
- {project-name}
- {project-name}
- {project-name}
With additional flags:
--registry=${customRegistryUrl}
--tag=next
--dryRun=true
> nx run {project-name}:nx-release-publish
📦 @proj/{project-name}@X.X.X-dry-run
=== Tarball Contents ===
XXB index.js
XXXB package.json
XXB project.json
=== Tarball Details ===
name: @proj/{project-name}
version: X.X.X-dry-run
filename: proj-{project-name}-X.X.X-dry-run.tgz
package size: XXXB
unpacked size: XXXB
shasum: {SHASUM}
integrity: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
total files: 3
Would publish to ${customRegistryUrl} with tag "next", but [dry-run] was set
> nx run {project-name}:nx-release-publish
📦 @proj/{project-name}@X.X.X-dry-run
=== Tarball Contents ===
XXB index.js
XXXB package.json
XXB project.json
=== Tarball Details ===
name: @proj/{project-name}
version: X.X.X-dry-run
filename: proj-{project-name}-X.X.X-dry-run.tgz
package size: XXXB
unpacked size: XXXB
shasum: {SHASUM}
integrity: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
total files: 3
Would publish to ${customRegistryUrl} with tag "next", but [dry-run] was set
> nx run {project-name}:nx-release-publish
📦 @proj/{project-name}@X.X.X-dry-run
=== Tarball Contents ===
XXB index.js
XXXB package.json
XXB project.json
=== Tarball Details ===
name: @proj/{project-name}
version: X.X.X-dry-run
filename: proj-{project-name}-X.X.X-dry-run.tgz
package size: XXXB
unpacked size: XXXB
shasum: {SHASUM}
integrity: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
total files: 3
Would publish to ${customRegistryUrl} with tag "next", but [dry-run] was set
NX Successfully ran target nx-release-publish for 3 projects
`);
// Versions are still unpublished on the next tag in the custom registry, because it was only a dry-run
expect(() =>
execSync(
`npm view @proj/${pkg1}@next version --registry=${customRegistryUrl}`
)
).toThrow(/npm (ERR!|error) code E404/);
expect(() =>
execSync(
`npm view @proj/${pkg2}@next version --registry=${customRegistryUrl}`
)
).toThrow(/npm (ERR!|error) code E404/);
expect(() =>
execSync(
`npm view @proj/${pkg3}@next version --registry=${customRegistryUrl}`
)
).toThrow(/npm (ERR!|error) code E404/);
// Actually publish to the custom registry (not e2e registry), and a custom dist tag of "next"
const publishOutput3 = runCLI(publishToNext);
expect(publishOutput3).toMatchInlineSnapshot(`
NX Running target nx-release-publish for 3 projects:
- {project-name}
- {project-name}
- {project-name}
With additional flags:
--registry=${customRegistryUrl}
--tag=next
> nx run {project-name}:nx-release-publish
📦 @proj/{project-name}@1000.0.0-next.0
=== Tarball Contents ===
XXB index.js
XXXB package.json
XXB project.json
=== Tarball Details ===
name: @proj/{project-name}
version: 1000.0.0-next.0
filename: proj-{project-name}-1000.0.0-next.0.tgz
package size: XXXB
unpacked size: XXXB
shasum: {SHASUM}
integrity: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
total files: 3
Published to ${customRegistryUrl} with tag "next"
> nx run {project-name}:nx-release-publish
📦 @proj/{project-name}@1000.0.0-next.0
=== Tarball Contents ===
XXB index.js
XXXB package.json
XXB project.json
=== Tarball Details ===
name: @proj/{project-name}
version: 1000.0.0-next.0
filename: proj-{project-name}-1000.0.0-next.0.tgz
package size: XXXB
unpacked size: XXXB
shasum: {SHASUM}
integrity: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
total files: 3
Published to ${customRegistryUrl} with tag "next"
> nx run {project-name}:nx-release-publish
📦 @proj/{project-name}@1000.0.0-next.0
=== Tarball Contents ===
XXB index.js
XXXB package.json
XXB project.json
=== Tarball Details ===
name: @proj/{project-name}
version: 1000.0.0-next.0
filename: proj-{project-name}-1000.0.0-next.0.tgz
package size: XXXB
unpacked size: XXXB
shasum: {SHASUM}
integrity: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
total files: 3
Published to ${customRegistryUrl} with tag "next"
NX Successfully ran target nx-release-publish for 3 projects
`);
// All packages should be skipped when the same publish is performed again
const publishOutput3Repeat = runCLI(publishToNext);
expect(publishOutput3Repeat).toMatchInlineSnapshot(`
NX Running target nx-release-publish for 3 projects:
- {project-name}
- {project-name}
- {project-name}
With additional flags:
--registry=${customRegistryUrl}
--tag=next
> nx run {project-name}:nx-release-publish
Skipped package "@proj/{project-name}" from project "{project-name}" because v1000.0.0-next.0 already exists in ${customRegistryUrl} with tag "next"
> nx run {project-name}:nx-release-publish
Skipped package "@proj/{project-name}" from project "{project-name}" because v1000.0.0-next.0 already exists in ${customRegistryUrl} with tag "next"
> nx run {project-name}:nx-release-publish
Skipped package "@proj/{project-name}" from project "{project-name}" because v1000.0.0-next.0 already exists in ${customRegistryUrl} with tag "next"
NX Successfully ran target nx-release-publish for 3 projects
`);
// All packages should have dist-tags updated when they were already published to a different dist-tag
const publishOutput3NewDistTags = runCLI(
`release publish --registry=${customRegistryUrl} --tag=next2`
);
expect(publishOutput3NewDistTags).toMatchInlineSnapshot(`
NX Running target nx-release-publish for 3 projects:
- {project-name}
- {project-name}
- {project-name}
With additional flags:
--registry=${customRegistryUrl}
--tag=next2
> nx run {project-name}:nx-release-publish
Added the dist-tag next2 to v1000.0.0-next.0 for registry ${customRegistryUrl}.
> nx run {project-name}:nx-release-publish
Added the dist-tag next2 to v1000.0.0-next.0 for registry ${customRegistryUrl}.
> nx run {project-name}:nx-release-publish
Added the dist-tag next2 to v1000.0.0-next.0 for registry ${customRegistryUrl}.
NX Successfully ran target nx-release-publish for 3 projects
`);
// The versions now exist on the next tag in the custom registry
expect(
execSync(
`npm view @proj/${pkg1}@next version --registry=${customRegistryUrl}`
)
.toString()
.trim()
).toEqual('1000.0.0-next.0');
expect(
execSync(
`npm view @proj/${pkg2}@next version --registry=${customRegistryUrl}`
)
.toString()
.trim()
).toEqual('1000.0.0-next.0');
expect(
execSync(
`npm view @proj/${pkg3}@next version --registry=${customRegistryUrl}`
)
.toString()
.trim()
).toEqual('1000.0.0-next.0');
// Update custom nx release config to demonstrate project level changelogs
updateJson<NxJsonConfiguration>('nx.json', (nxJson) => {
nxJson.release = {
groups: {
default: {
// @proj/source will be added as a project by the verdaccio setup, but we aren't versioning or publishing it, so we exclude it here
projects: ['*', '!@proj/source'],
changelog: {
// This should be merged with and take priority over the projectChangelogs config at the root of the config
createRelease: 'github',
},
},
},
changelog: {
projectChangelogs: {
createRelease: false, // will be overridden by the group
renderOptions: {
// Customize the changelog renderer to not print the Thank You or commit references section for project changelogs (not overridden by the group)
authors: false,
commitReferences: false, // commit reference will still be printed in workspace changelog
versionTitleDate: false, // version title date will still be printed in the workspace changelog
},
},
},
};
return nxJson;
});
// Perform a dry-run this time to show that it works but also prevent making any requests to github within the test
const changelogDryRunOutput = runCLI(
`release changelog 1000.0.0-next.0 --dry-run`
);
expect(changelogDryRunOutput).toMatchInlineSnapshot(`
NX Previewing an entry in CHANGELOG.md for v1000.0.0-next.0
+ ## 1000.0.0-next.0 (YYYY-MM-DD)
+
+ This was a version bump only, there were no code changes.
+
## 999.9.9 (YYYY-MM-DD)
### 🚀 Features
NX Previewing an entry in {project-name}/CHANGELOG.md for v1000.0.0-next.0
+ ## 1000.0.0-next.0
+
+ This was a version bump only for {project-name} to align it with other projects, there were no code changes.
NX Previewing an entry in {project-name}/CHANGELOG.md for v1000.0.0-next.0
+ ## 1000.0.0-next.0
+
+ This was a version bump only for {project-name} to align it with other projects, there were no code changes.
NX Previewing an entry in {project-name}/CHANGELOG.md for v1000.0.0-next.0
+ ## 1000.0.0-next.0
+
+ This was a version bump only for {project-name} to align it with other projects, there were no code changes.
NX Committing changes with git
NX Tagging commit with git
NX Pushing to git remote "origin"
NX Creating GitHub Release
+ ## 1000.0.0-next.0
+
+ This was a version bump only for {project-name} to align it with other projects, there were no code changes.
NX Creating GitHub Release
+ ## 1000.0.0-next.0
+
+ This was a version bump only for {project-name} to align it with other projects, there were no code changes.
NX Creating GitHub Release
+ ## 1000.0.0-next.0
+
+ This was a version bump only for {project-name} to align it with other projects, there were no code changes.
`);
// port and process cleanup
await killProcessAndPorts(process.pid, verdaccioPort);
// Add custom nx release config to control version resolution
updateJson<NxJsonConfiguration>('nx.json', (nxJson) => {
nxJson.release = {
groups: {
default: {
// @proj/source will be added as a project by the verdaccio setup, but we aren't versioning or publishing it, so we exclude it here
projects: ['*', '!@proj/source'],
releaseTagPattern: 'xx{version}',
version: {
generator: '@nx/js:release-version',
// Resolve the latest version from the git tag
currentVersionResolver: 'git-tag',
},
},
},
};
return nxJson;
});
// Add a git tag to the repo
await runCommandAsync(`git tag xx1100.0.0`);
const versionOutput3 = runCLI(`release version minor`);
expect(
versionOutput3.match(/Running release version for project: my-pkg-\d*/g)
.length
).toEqual(3);
// It should resolve the current version from the git tag once...
expect(
versionOutput3.match(
new RegExp(
`Resolved the current version as 1100.0.0 from git tag "xx1100.0.0"`,
'g'
)
).length
).toEqual(1);
// ...and then reuse it twice
expect(
versionOutput3.match(
new RegExp(
`Reusing the current version 1100.0.0 already resolved for my-pkg-\\d* from git tag "xx1100.0.0"`,
'g'
)
).length
).toEqual(2);
// First project
expect(
versionOutput3.match(
/Applied semver relative bump "minor", from the given specifier, to get new version 1100\.1\.0/g
).length
).toEqual(1);
// Other projects
expect(
versionOutput3.match(
/Applied version 1100\.1\.0 directly, because the project is a member of a fixed release group containing my-pkg-\d*/g
).length
).toEqual(2);
expect(
versionOutput3.match(
/New version 1100\.1\.0 written to manifest: my-pkg-\d*\/package\.json/g
).length
).toEqual(3);
createFile(
`${pkg1}/my-file.txt`,
'update for conventional-commits testing'
);
// Add custom nx release config to control version resolution
updateJson<NxJsonConfiguration>('nx.json', (nxJson) => {
nxJson.release = {
groups: {
default: {
// @proj/source will be added as a project by the verdaccio setup, but we aren't versioning or publishing it, so we exclude it here
projects: ['*', '!@proj/source'],
releaseTagPattern: 'xx{version}',
version: {
specifierSource: 'conventional-commits',
currentVersionResolver: 'git-tag',
},
},
},
};
return nxJson;
});
const versionOutput4 = runCLI(`release version`);
expect(
versionOutput4.match(/Running release version for project: my-pkg-\d*/g)
.length
).toEqual(3);
// It should resolve the current version from the git tag once...
expect(
versionOutput4.match(
new RegExp(
`Resolved the current version as 1100.0.0 from git tag "xx1100.0.0"`,
'g'
)
).length
).toEqual(1);
// ...and then reuse it twice
expect(
versionOutput4.match(
new RegExp(
`Reusing the current version 1100.0.0 already resolved for my-pkg-\\d* from git tag "xx1100.0.0"`,
'g'
)
).length
).toEqual(2);
// First project
expect(
versionOutput4.match(
/No changes were detected using git history and the conventional commits standard/g
).length
).toEqual(1);
// Other projects
expect(
versionOutput4.match(
/Skipping versioning for my-pkg-\d* as it is a part of a fixed release group with my-pkg-\d* and no dependency bumps were detected/g
).length
).toEqual(2);
await runCommandAsync(
`git add ${pkg1}/my-file.txt && git commit -m "feat!: add new file"`
);
const versionOutput5 = runCLI(`release version`);
expect(
versionOutput5.match(
/New version 1101\.0\.0 written to manifest: my-pkg-\d*\/package\.json/g
).length
).toEqual(3);
// Reset the nx release config to something basic for testing the release command
updateJson<NxJsonConfiguration>('nx.json', (nxJson) => {
nxJson.release = {
groups: {
default: {
// @proj/source will be added as a project by the verdaccio setup, but we aren't versioning or publishing it, so we exclude it here
projects: ['*', '!@proj/source'],
releaseTagPattern: 'xx{version}',
},
},
};
return nxJson;
});
const releaseOutput = runCLI(`release 1200.0.0 -y`);
expect(
releaseOutput.match(
new RegExp(`Running release version for project: `, 'g')
).length
).toEqual(3);
expect(
releaseOutput.match(
new RegExp(`Generating an entry in CHANGELOG\.md for v1200\.0\.0`, 'g')
).length
).toEqual(1);
expect(
releaseOutput.match(
new RegExp(
`Successfully ran target nx-release-publish for 3 projects`,
'g'
)
).length
).toEqual(1);
// define two release groups that are released separately to ensure the --from ref is selected correctly
updateJson<NxJsonConfiguration>('nx.json', (nxJson) => {
nxJson.release = {
groups: {
group1: {
projects: [pkg1],
releaseTagPattern: 'xx-{version}',
},
group2: {
projects: [pkg2, pkg3],
releaseTagPattern: 'zz-{version}',
},
},
git: {
commit: true,
commitMessage: 'chore(release): release {version}',
tag: true,
},
version: {
specifierSource: 'conventional-commits',
currentVersionResolver: 'git-tag',
},
changelog: {
projectChangelogs: {},
},
};
return nxJson;
});
// Remove the dependency from pkg2 on pkg1, it is not applicable to this group configuration where pkg1 and pkg2 are in separate groups
updateJson(`${pkg2}/package.json`, (json) => {
json.dependencies = {};
return json;
});
// Update lockfile
await runCommandAsync(updateLockFileCommand);
await runCommandAsync(`git tag zz-1300.0.0`);
await runCommandAsync(`git tag xx-1400.0.0`);
// update my-pkg-1 with a feature commit
updateJson(`${pkg1}/package.json`, (json) => ({
...json,
license: 'MIT',
}));
await runCommandAsync(`git add ${pkg1}/package.json`);
await runCommandAsync(`git commit -m "feat(${pkg1}): new feature 1"`);
// update my-pkg-3 with a feature commit
updateJson(`${pkg3}/package.json`, (json) => ({
...json,
license: 'GNU GPLv3',
}));
await runCommandAsync(`git add ${pkg3}/package.json`);
await runCommandAsync(`git commit -m "feat(${pkg3}): new feat 3"`);
// set 1300.1.0 as the latest version for group2
const releaseOutput2 = runCLI(
`release version --group=group2 --stage-changes --git-commit --git-tag`
);
expect(
releaseOutput2.match(
new RegExp(
`Resolved the specifier as "minor" using git history and the conventional commits standard`,
'g'
)
).length
).toEqual(1);
expect(
releaseOutput2.match(new RegExp(`New version 1300\.1\.0 written to`, 'g'))
.length
).toEqual(2);
// update my-pkg-3 with a fix commit
updateJson(`${pkg3}/package.json`, (json) => ({
...json,
license: 'MIT',
}));
await runCommandAsync(`git add ${pkg3}/package.json`);
await runCommandAsync(`git commit -m "fix(${pkg3}): new fix 2"`);
const releaseOutput3 = runCLI(`release -y`);
expect(
releaseOutput3.match(
new RegExp(
`Resolved the specifier as "minor" using git history and the conventional commits standard`,
'g'
)
).length
).toEqual(1);
expect(
releaseOutput3.match(
new RegExp(`New version 1400\\.1\\.0 written to`, 'g')
).length
).toEqual(1);
expect(
releaseOutput3.match(
new RegExp(`- \\*\\*${pkg1}:\\*\\* new feature 1`, 'g')
).length
).toEqual(1);
expect(
releaseOutput3.match(
new RegExp(
`Resolved the specifier as "patch" using git history and the conventional commits standard`,
'g'
)
).length
).toEqual(1);
expect(
releaseOutput3.match(
new RegExp(`New version 1300\\.1\\.1 written to`, 'g')
).length
).toEqual(2);
expect(
releaseOutput3.match(new RegExp(`- \\*\\*${pkg3}:\\*\\* new fix 2`, 'g'))
.length
).toEqual(1);
expect(
releaseOutput3.match(
new RegExp(`Successfully ran target nx-release-publish for`, 'g')
).length
).toEqual(2);
// change the releaseTagPattern to something that doesn't exist in order to test fallbackCurrentVersionResolver
updateJson<NxJsonConfiguration>('nx.json', (nxJson) => {
nxJson.release = {
groups: {
group1: {
projects: [pkg1, pkg2, pkg3],
releaseTagPattern: '>{version}',
},
},
git: {
commit: false,
tag: false,
},
version: {
currentVersionResolver: 'git-tag',
},
};
return nxJson;
});
// Add back the dependency on pkg1 for pkg2
updateJson(`${pkg2}/package.json`, (json) => {
json.dependencies = {};
json.dependencies[`@proj/${pkg1}`] = readJson(
`${pkg1}/package.json`
).version;
return json;
});
// Update lockfile
await runCommandAsync(updateLockFileCommand);
const releaseOutput4a = runCLI(`release patch --skip-publish`, {
silenceError: true,
});
expect(releaseOutput4a).toMatchInlineSnapshot(`
NX No git tags matching pattern ">{version}" for project "{project-name}" were found. You will need to create an initial matching tag to use as a base for determining the next version. Alternatively, you can use the --first-release option or set "release.version.fallbackCurrentVersionResolver" to "disk" in order to fallback to the version on disk when no matching git tags are found.
Pass --verbose to see the stacktrace.
`);
const releaseOutput4b = runCLI(
`release patch --skip-publish --first-release`,
{
silenceError: true,
}
);
expect(releaseOutput4b).toMatch(
/Unable to resolve the current version from git tags using pattern ">{version}". Falling back to the version 1400\.1\.0 in manifest: my-pkg-\d*\/package\.json/g
);
expect(
releaseOutput4b.match(
new RegExp(
`Reusing the current version 1400\\.1\\.0 already resolved for my-pkg-\\d* from the disk fallback`,
'g'
)
).length
).toEqual(2);
updateJson<NxJsonConfiguration>('nx.json', (nxJson) => {
(
nxJson.release.version as NxReleaseVersionConfiguration
).fallbackCurrentVersionResolver = 'disk';
return nxJson;
});
const releaseOutput5 = runCLI(`release patch --skip-publish`);
expect(releaseOutput5).toMatch(
/Unable to resolve the current version from git tags using pattern ">{version}". Falling back to the version 1400\.1\.1 in manifest: my-pkg-\d*\/package\.json/g
);
expect(
releaseOutput5.match(
new RegExp(
`Reusing the current version 1400\\.1\\.1 already resolved for my-pkg-\\d* from the disk fallback`,
'g'
)
).length
).toEqual(2);
updateJson<NxJsonConfiguration>('nx.json', (nxJson) => {
(
nxJson.release.version as NxReleaseVersionConfiguration
).currentVersionResolver = 'registry';
(
nxJson.release.version as NxReleaseVersionConfiguration
).currentVersionResolverMetadata = {
tag: 'other',
};
delete (nxJson.release.version as NxReleaseVersionConfiguration)
.fallbackCurrentVersionResolver;
return nxJson;
});
const releaseOutput6a = runCLI(`release patch --skip-publish`, {
silenceError: true,
});
expect(
releaseOutput6a.match(
new RegExp(
`NX Unable to resolve the current version from the registry: "@proj:registry=${e2eRegistryUrl}" tag=other. Please ensure that the package exists in the registry in order to use the "registry" currentVersionResolver. Alternatively, you can use the --first-release option or set "release.version.fallbackCurrentVersionResolver" to "disk" in order to fallback to the version on disk when the registry lookup fails.`,
'g'
)
).length
).toEqual(1);
const releaseOutput6b = runCLI(
`release patch --skip-publish --first-release`,
{
silenceError: true,
}
);
expect(releaseOutput6b).toMatch(
new RegExp(
`Unable to resolve the current version from the registry: "@proj:registry=${e2eRegistryUrl}" tag=other. Falling back to the version 1400\\.1\\.2 in manifest: my-pkg-\\d*\\/package\\.json`
)
);
expect(
releaseOutput6b.match(
new RegExp(
`Reusing the current version 1400\\.1\\.2 already resolved for my-pkg-\\d* from the disk fallback`,
'g'
)
).length
).toEqual(2);
updateJson<NxJsonConfiguration>('nx.json', (nxJson) => {
(
nxJson.release.version as NxReleaseVersionConfiguration
).fallbackCurrentVersionResolver = 'disk';
return nxJson;
});
const releaseOutput7 = runCLI(`release patch --skip-publish --verbose`);
expect(releaseOutput7).toMatch(
new RegExp(
`Unable to resolve the current version from the registry: "@proj:registry=${e2eRegistryUrl}" tag=other. Falling back to the version 1400\\.1\\.3 in manifest: my-pkg-\\d*\\/package\\.json`
)
);
expect(
releaseOutput7.match(
new RegExp(
`Reusing the current version 1400\\.1\\.3 already resolved for my-pkg-\\d* from the disk fallback`,
'g'
)
).length
).toEqual(2);
}, 500000);
});