import { NxJsonConfiguration } from '@nx/devkit'; import { cleanupProject, getPackageManagerCommand, newProject, readJson, runCLI, runCommandAsync, tmpProjPath, uniq, updateFile, updateJson, } from '@nx/e2e/utils'; import { execSync } from 'node:child_process'; import { join } from 'node:path'; 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\s+project\.json/g, 'XXB project.json') .replaceAll(/\d*B\s+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(/\d*B\s+src\//g, 'XXB src/') .replaceAll(/\d*B\s+index/g, 'XXB index') .replaceAll(/total files:\s+\d*/g, 'total files: X') .replaceAll(/\d*B\s+README.md/g, 'XXB README.md') .replaceAll(/Test @[\w\d]+/g, 'Test @{COMMIT_AUTHOR}') .replaceAll(/(\w+) lock file/g, 'PM lock file') // 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 // Slightly different handling needed for bun (length can be 8) .replaceAll(/[a-fA-F0-9]{7,8}/g, '{COMMIT_SHA}') .replaceAll(/bun publish v\d+\.\d+\.\d+/g, 'bun publish vX.X.X') .replaceAll( /Integrity:\s*.*/g, 'Integrity: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' ) .split('\n') .map((r) => r.trim()) .filter(Boolean) .join('\n') ); }, test(val: string) { return val != null && typeof val === 'string'; }, }); describe('nx release preserve local dependency protocols', () => { let previousPackageManager: string; let e2eRegistryUrl: string; beforeAll(() => { previousPackageManager = process.env.SELECTED_PM; // This is the verdaccio instance that the e2e tests themselves are working from e2eRegistryUrl = execSync('npm config get registry').toString().trim(); }); afterEach(() => cleanupProject()); afterAll(() => { process.env.SELECTED_PM = previousPackageManager; }); /** * Initialize each test with a fresh workspace using the specified * package manager. */ const initializeProject = async (packageManager: 'pnpm' | 'bun') => { process.env.SELECTED_PM = packageManager; console.log(`Creating workspace with package manager: ${packageManager}`); newProject({ packages: ['@nx/js'], packageManager, }); const pkg1 = uniq('my-pkg-1'); runCLI(`generate @nx/workspace:npm-package ${pkg1}`); const pkg2 = uniq('my-pkg-2'); runCLI(`generate @nx/workspace:npm-package ${pkg2}`); // Set up a workspace dependency using the workspace protocol updateJson(join(pkg1, 'package.json'), (packageJson) => { packageJson.dependencies = { [`@proj/${pkg2}`]: 'workspace:*', }; return packageJson; }); // Add workspaces config if (packageManager === 'pnpm') { updateFile('pnpm-workspace.yaml', `packages:\n - ${pkg1}\n - ${pkg2}\n`); } else { updateJson('package.json', (packageJson) => { packageJson.workspaces = [pkg1, pkg2]; return packageJson; }); } // workaround for NXC-143 runCLI('reset'); await runCommandAsync(getPackageManagerCommand({ packageManager }).install); return { workspacePath: tmpProjPath(), pkg1, pkg2 }; }; it('should replace local dependency protocols with the actual version number when version.preserveLocalDependencyProtocols is set to false', async () => { // The package manager currently does not matter for the versioning behavior, it's imperatively controlled by the user const { workspacePath } = await initializeProject('pnpm'); updateJson('nx.json', (nxJson) => { nxJson.release = { version: { preserveLocalDependencyProtocols: false, }, }; return nxJson; }); // Show the dependency being updated expect(runCLI(`release version minor -d --verbose`, { cwd: workspacePath })) .toMatchInlineSnapshot(` NX Running release version for project: {project-name} {project-name} 📄 Resolved the current version as 0.0.0 from manifest: {project-name}/package.json {project-name} ❓ Applied semver relative bump "minor", from the given specifier, to get new version 0.1.0 {project-name} ✍️ New version 0.1.0 written to manifest: {project-name}/package.json {project-name} ✍️ Updated 1 dependency in manifest: {project-name}/package.json NX Running release version for project: {project-name} {project-name} 📄 Resolved the current version as 0.0.0 from manifest: {project-name}/package.json {project-name} ❓ Applied version 0.1.0 directly, because the project is a member of a fixed release group containing {project-name} {project-name} ✍️ New version 0.1.0 written to manifest: {project-name}/package.json "name": "@proj/{project-name}", - "version": "0.0.0", + "version": "0.1.0", "scripts": { "dependencies": { - "@proj/{project-name}": "workspace:*" + "@proj/{project-name}": "0.1.0" } } + "name": "@proj/{project-name}", - "version": "0.0.0", + "version": "0.1.0", "scripts": { NX Updating PM lock file Would update pnpm-lock.yaml with the following command, but --dry-run was set: pnpm install --lockfile-only NX Staging changed files with git Would stage files in git with the following command, but --dry-run was set: git add {project-name}/package.json {project-name}/package.json `); }); it('should preserve local dependency protocols when version.preserveLocalDependencyProtocols is not set to false', async () => { // The package manager currently does not matter for the versioning behavior, it's imperatively controlled by the user const { workspacePath } = await initializeProject('pnpm'); updateJson('nx.json', (nxJson) => { nxJson.release = { version: {}, }; return nxJson; }); // Show that the dependency has not been updated expect(runCLI(`release version minor -d --verbose`, { cwd: workspacePath })) .toMatchInlineSnapshot(` NX Running release version for project: {project-name} {project-name} 📄 Resolved the current version as 0.0.0 from manifest: {project-name}/package.json {project-name} ❓ Applied semver relative bump "minor", from the given specifier, to get new version 0.1.0 {project-name} ✍️ New version 0.1.0 written to manifest: {project-name}/package.json NX Running release version for project: {project-name} {project-name} 📄 Resolved the current version as 0.0.0 from manifest: {project-name}/package.json {project-name} ❓ Applied version 0.1.0 directly, because the project is a member of a fixed release group containing {project-name} {project-name} ✍️ New version 0.1.0 written to manifest: {project-name}/package.json "name": "@proj/{project-name}", - "version": "0.0.0", + "version": "0.1.0", "scripts": { } + "name": "@proj/{project-name}", - "version": "0.0.0", + "version": "0.1.0", "scripts": { NX Updating PM lock file Would update pnpm-lock.yaml with the following command, but --dry-run was set: pnpm install --lockfile-only NX Staging changed files with git Would stage files in git with the following command, but --dry-run was set: git add {project-name}/package.json {project-name}/package.json `); }); describe('pnpm publish', () => { it('should replace local dependency protocols dynamically during publishing', async () => { const { workspacePath, pkg1 } = await initializeProject('pnpm'); // Prove that the local dependency protocol is present in the pkg1 package.json expect(readJson(join(workspacePath, pkg1, 'package.json'))) .toMatchInlineSnapshot(` { dependencies: { @proj/{project-name}: workspace:*, }, name: @proj/{project-name}, scripts: { test: node index.js, }, version: 0.0.0, } `); // Publish the packages expect( runCLI(`release publish`, { silenceError: true, cwd: workspacePath }) ).toMatchInlineSnapshot(` NX Running target nx-release-publish for 2 projects: - {project-name} - {project-name} > nx run {project-name}:nx-release-publish 📦 @proj/{project-name}@0.0.0 === Tarball Contents === XXXXB index.js XXXB package.json XXB project.json === Tarball Details === name: @proj/{project-name} version: 0.0.0 filename: proj-{project-name}-0.0.0.tgz package size: XXXB unpacked size: XXXB shasum: {SHASUM} integrity: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX total files: X Published to ${e2eRegistryUrl} with tag "latest" > nx run {project-name}:nx-release-publish 📦 @proj/{project-name}@0.0.0 === Tarball Contents === XXXXB index.js XXXB package.json XXB project.json === Tarball Details === name: @proj/{project-name} version: 0.0.0 filename: proj-{project-name}-0.0.0.tgz package size: XXXB unpacked size: XXXB shasum: {SHASUM} integrity: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX total files: X Published to ${e2eRegistryUrl} with tag "latest" NX Successfully ran target nx-release-publish for 2 projects `); // Ensure that the dependency on pkg2 specified on the registry was replaced with the actual version number during publishing expect( (await runCommandAsync(`npm view @proj/${pkg1} dependencies`)) .combinedOutput ).toMatchInlineSnapshot(`{ '@proj/{project-name}': '0.0.0' }`); }); }); describe('bun publish', () => { it('should replace local dependency protocols dynamically during publishing', async () => { const { workspacePath, pkg1 } = await initializeProject('bun'); // Prove that the local dependency protocol is present in the pkg1 package.json expect(readJson(join(workspacePath, pkg1, 'package.json'))) .toMatchInlineSnapshot(` { dependencies: { @proj/{project-name}: workspace:*, }, name: @proj/{project-name}, scripts: { test: node index.js, }, version: 0.0.0, } `); // Publish the packages expect( runCLI(`release publish`, { silenceError: true, cwd: workspacePath }) ).toMatchInlineSnapshot(` NX Running target nx-release-publish for 2 projects: - {project-name} - {project-name} > nx run {project-name}:nx-release-publish bun publish vX.X.X ({COMMIT_SHA}) packed XXXB package.json packed XXB index.js packed XXB project.json Total files: 3 Shasum: {SHASUM} Integrity: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX Unpacked size: XXXB Packed size: XXXB Tag: latest Access: default Registry: ${e2eRegistryUrl} + @proj/{project-name}@0.0.0 Published to ${e2eRegistryUrl} with tag "latest" > nx run {project-name}:nx-release-publish bun publish vX.X.X ({COMMIT_SHA}) packed XXXB package.json packed XXB index.js packed XXB project.json Total files: 3 Shasum: {SHASUM} Integrity: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX Unpacked size: XXXB Packed size: XXXB Tag: latest Access: default Registry: ${e2eRegistryUrl} + @proj/{project-name}@0.0.0 Published to ${e2eRegistryUrl} with tag "latest" NX Successfully ran target nx-release-publish for 2 projects `); // Ensure that the dependency on pkg2 specified on the registry was replaced with the actual version number during publishing expect( (await runCommandAsync(`npm view @proj/${pkg1} dependencies`)) .combinedOutput ).toMatchInlineSnapshot(`{ '@proj/{project-name}': '0.0.0' }`); }); }); });