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.
1222 lines
36 KiB
TypeScript
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);
|
|
});
|