diff --git a/e2e/release/jest.config.ts b/e2e/release/jest.config.ts index ddd7f44eb6036..4239406548f41 100644 --- a/e2e/release/jest.config.ts +++ b/e2e/release/jest.config.ts @@ -1,5 +1,6 @@ /* eslint-disable */ export default { + testTimeout: 120000, transform: { '^.+\\.[tj]sx?$': ['ts-jest', { tsconfig: '/tsconfig.spec.json' }], }, diff --git a/e2e/release/src/conventional-commits-config.test.ts b/e2e/release/src/conventional-commits-config.test.ts index 40f183a91f6d4..7365cfced6115 100644 --- a/e2e/release/src/conventional-commits-config.test.ts +++ b/e2e/release/src/conventional-commits-config.test.ts @@ -1,6 +1,8 @@ import { NxJsonConfiguration } from '@nx/devkit'; import { cleanupProject, + detectPackageManager, + getPackageManagerCommand, newProject, readFile, runCLI, @@ -8,6 +10,7 @@ import { uniq, updateJson, } from '@nx/e2e-utils'; +import { setupWorkspaces } from './utils'; expect.addSnapshotSerializer({ serialize(str: string) { @@ -86,6 +89,10 @@ describe('nx release conventional commits config', () => { return nxJson; }); + setupWorkspaces(detectPackageManager(), pkg1, pkg2, pkg3, pkg4, pkg5, pkg6); + const pmc = getPackageManagerCommand(); + await runCommandAsync(pmc.install); + await runCommandAsync(`git add .`); await runCommandAsync(`git commit -m "chore: initial commit"`); await runCommandAsync(`git tag -a ${pkg1}@0.0.1 -m "${pkg1}@0.0.1"`); diff --git a/e2e/release/src/independent-projects.test.ts b/e2e/release/src/independent-projects.test.ts index 299693556016d..bfc4afaa077b4 100644 --- a/e2e/release/src/independent-projects.test.ts +++ b/e2e/release/src/independent-projects.test.ts @@ -3,15 +3,19 @@ import { cleanupProject, exists, getSelectedPackageManager, + getPackageManagerCommand, newProject, readFile, runCLI, runCommand, + runCommandAsync, tmpProjPath, uniq, updateJson, + removeFile, } from '@nx/e2e-utils'; import { execSync } from 'child_process'; +import { setupWorkspaces, prepareAndInstallDependencies } from './utils'; expect.addSnapshotSerializer({ serialize(str: string) { @@ -57,13 +61,14 @@ expect.addSnapshotSerializer({ }, }); -describe('nx release - independent projects', () => { +describe('debug nx release - independent projects', () => { let pkg1: string; let pkg2: string; let pkg3: string; let e2eRegistryUrl: string; + const packageManager = getSelectedPackageManager(); - beforeAll(() => { + beforeAll(async () => { newProject({ packages: ['@nx/js'], }); @@ -82,10 +87,15 @@ describe('nx release - independent projects', () => { */ updateJson(`${pkg2}/package.json`, (json) => { json.dependencies ??= {}; - json.dependencies[`@proj/${pkg3}`] = '0.0.0'; + json.dependencies[`@proj/${pkg3}`] = + packageManager === 'pnpm' ? 'workspace:*' : '0.0.0'; return json; }); + setupWorkspaces(packageManager, pkg1, pkg2, pkg3); + const pmc = getPackageManagerCommand({ packageManager }); + await prepareAndInstallDependencies(packageManager, pmc.install); + // Normalize git committer information so it is deterministic in snapshots runCommand(`git config user.email "test@test.com"`); runCommand(`git config user.name "Test"`); @@ -119,105 +129,108 @@ describe('nx release - independent projects', () => { ); expect(versionPkg1Output).toMatchInlineSnapshot(` - NX Your filter "{project-name}" matched the following projects: + NX Your filter "{project-name}" matched the following projects: + + - {project-name} + - - {project-name} + 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 explicit semver value "999.9.9-package.1", from the given specifier, to get new version 999.9.9-package.1 + {project-name} ✍️ New version 999.9.9-package.1 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 explicit semver value "999.9.9-package.1", from the given specifier, to get new version 999.9.9-package.1 - {project-name} ✍️ New version 999.9.9-package.1 written to manifest: {project-name}/package.json + "name": "@proj/{project-name}", + - "version": "0.0.0", + + "version": "999.9.9-package.1", + "exports": { - "name": "@proj/{project-name}", - - "version": "0.0.0", - + "version": "999.9.9-package.1", - "exports": { + NX Updating {package-manager} lock file - NX Staging changed files with git + NX Staging changed files with git - `); + `); const versionPkg2Output = runCLI( `release version 999.9.9-package.2 -p ${pkg2}` ); expect(versionPkg2Output).toMatchInlineSnapshot(` - NX Your filter "{project-name}" matched the following projects: + NX Your filter "{project-name}" matched the following projects: + + - {project-name} + - - {project-name} + 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 explicit semver value "999.9.9-package.2", from the given specifier, to get new version 999.9.9-package.2 + {project-name} ✍️ New version 999.9.9-package.2 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 explicit semver value "999.9.9-package.2", from the given specifier, to get new version 999.9.9-package.2 - {project-name} ✍️ New version 999.9.9-package.2 written to manifest: {project-name}/package.json + "name": "@proj/{project-name}", + - "version": "0.0.0", + + "version": "999.9.9-package.2", + "exports": { + } + + - "name": "@proj/{project-name}", - - "version": "0.0.0", - + "version": "999.9.9-package.2", - "exports": { - } - + + NX Updating {package-manager} lock file - NX Staging changed files with git + NX Staging changed files with git - `); + `); const versionPkg3Output = runCLI( `release version 999.9.9-package.3 -p ${pkg3}` ); expect(versionPkg3Output).toMatchInlineSnapshot(` - NX Your filter "{project-name}" matched the following projects: + NX Your filter "{project-name}" matched the following projects: + + - {project-name} - - {project-name} + NX Running release version for project: {project-name} - NX Running release version for project: {project-name} + {project-name} 📄 Resolved the current version as 999.9.9-package.2 from manifest: {project-name}/package.json + {project-name} ❓ Applied semver relative bump "patch", because a dependency was bumped, to get new version 999.9.9 + {project-name} ✍️ New version 999.9.9 written to manifest: {project-name}/package.json - {project-name} 📄 Resolved the current version as 999.9.9-package.2 from manifest: {project-name}/package.json - {project-name} ✍️ Updated 1 dependency in manifest: {project-name}/package.json - {project-name} ❓ Applied semver relative bump "patch", because a dependency was bumped, to get new version 999.9.9 - {project-name} ✍️ New version 999.9.9 written to manifest: {project-name}/package.json + NX Running release version for project: {project-name} - 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 explicit semver value "999.9.9-package.3", from the given specifier, to get new version 999.9.9-package.3 + {project-name} ✍️ New version 999.9.9-package.3 written to manifest: {project-name}/package.json - {project-name} 📄 Resolved the current version as 0.0.0 from manifest: {project-name}/package.json - {project-name} ❓ Applied explicit semver value "999.9.9-package.3", from the given specifier, to get new version 999.9.9-package.3 - {project-name} ✍️ New version 999.9.9-package.3 written to manifest: {project-name}/package.json + "name": "@proj/{project-name}", + - "version": "0.0.0", + + "version": "999.9.9-package.3", + "exports": { - "name": "@proj/{project-name}", - - "version": "0.0.0", - + "version": "999.9.9-package.3", - "exports": { + "name": "@proj/{project-name}", + - "version": "999.9.9-package.2", + + "version": "999.9.9", + "exports": { - "name": "@proj/{project-name}", - - "version": "999.9.9-package.2", - + "version": "999.9.9", - "exports": { - "dependencies": { - - "@proj/{project-name}": "0.0.0" - + "@proj/{project-name}": "999.9.9-package.3" - } + NX Updating {package-manager} lock file - NX Staging changed files with git + NX Staging changed files with git - `); + `); }, 500000); it('should support automated git operations after versioning when configured', async () => { @@ -234,55 +247,58 @@ describe('nx release - independent projects', () => { ); expect(versionWithGitActionsCLIOutput).toMatchInlineSnapshot(` - NX Your filter "{project-name}" matched the following projects: + NX Your filter "{project-name}" matched the following projects: + + - {project-name} - - {project-name} + NX Running release version for project: {project-name} - NX Running release version for project: {project-name} + {project-name} 📄 Resolved the current version as 999.9.9-version-git-operations-test.1 from manifest: {project-name}/package.json + {project-name} ❓ Applied explicit semver value "999.9.9-version-git-operations-test.2", from the given specifier, to get new version 999.9.9-version-git-operations-test.2 + {project-name} ✍️ New version 999.9.9-version-git-operations-test.2 written to manifest: {project-name}/package.json - {project-name} 📄 Resolved the current version as 999.9.9-version-git-operations-test.1 from manifest: {project-name}/package.json - {project-name} ❓ Applied explicit semver value "999.9.9-version-git-operations-test.2", from the given specifier, to get new version 999.9.9-version-git-operations-test.2 - {project-name} ✍️ New version 999.9.9-version-git-operations-test.2 written to manifest: {project-name}/package.json + "name": "@proj/{project-name}", + - "version": "999.9.9-version-git-operations-test.1", + + "version": "999.9.9-version-git-operations-test.2", + "exports": { - "name": "@proj/{project-name}", - - "version": "999.9.9-version-git-operations-test.1", - + "version": "999.9.9-version-git-operations-test.2", - "exports": { + NX Updating {package-manager} lock file - Skipped lock file update because {package-manager} workspaces are not enabled. + Updating {lock-file} with the following command: + {lock-file-command} - NX Committing changes with git + NX Committing changes with git - Staging files in git with the following command: - git add {project-name}/package.json + Staging files in git with the following command: + git add {project-name}/package.json {lock-file} - Committing files in git with the following command: - git commit --message chore(release): publish --message - project: {project-name} 999.9.9-version-git-operations-test.2 + Committing files in git with the following command: + git commit --message chore(release): publish --message - project: {project-name} 999.9.9-version-git-operations-test.2 - NX Tagging commit with git + NX Tagging commit with git - Tagging the current commit in git with the following command: - git tag --annotate {project-name}@999.9.9-version-git-operations-test.2 --message {project-name}@999.9.9-version-git-operations-test.2 + Tagging the current commit in git with the following command: + git tag --annotate {project-name}@999.9.9-version-git-operations-test.2 --message {project-name}@999.9.9-version-git-operations-test.2 - `); + `); // Ensure the git operations were performed expect(runCommand(`git rev-parse HEAD`).trim()).not.toEqual(headSHA); // Commit expect(runCommand(`git --no-pager log -1 --pretty=format:%B`).trim()) .toMatchInlineSnapshot(` - chore(release): publish + chore(release): publish - - project: {project-name} 999.9.9-version-git-operations-test.2 - `); + - project: {project-name} 999.9.9-version-git-operations-test.2 + `); // Tags expect(runCommand('git tag --points-at HEAD')).toMatchInlineSnapshot(` - {project-name}@999.9.9-version-git-operations-test.2 + {project-name}@999.9.9-version-git-operations-test.2 - `); + `); // Enable git commit and tag operations for the version command via config updateJson('nx.json', (json) => { @@ -317,90 +333,87 @@ describe('nx release - independent projects', () => { ); expect(versionWithGitActionsConfigOutput).toMatchInlineSnapshot(` - NX Running release version for project: {project-name} + NX Running release version for project: {project-name} - {project-name} 📄 Resolved the current version as 999.9.9-package.3 from manifest: {project-name}/package.json - {project-name} ❓ Applied explicit semver value "999.9.9-version-git-operations-test.3", from the given specifier, to get new version 999.9.9-version-git-operations-test.3 - {project-name} ✍️ New version 999.9.9-version-git-operations-test.3 written to manifest: {project-name}/package.json + {project-name} 📄 Resolved the current version as 999.9.9-package.3 from manifest: {project-name}/package.json + {project-name} ❓ Applied explicit semver value "999.9.9-version-git-operations-test.3", from the given specifier, to get new version 999.9.9-version-git-operations-test.3 + {project-name} ✍️ New version 999.9.9-version-git-operations-test.3 written to manifest: {project-name}/package.json - NX Running release version for project: {project-name} + NX Running release version for project: {project-name} - {project-name} 📄 Resolved the current version as 999.9.9-version-git-operations-test.2 from manifest: {project-name}/package.json - {project-name} ❓ Applied explicit semver value "999.9.9-version-git-operations-test.3", from the given specifier, to get new version 999.9.9-version-git-operations-test.3 - {project-name} ✍️ New version 999.9.9-version-git-operations-test.3 written to manifest: {project-name}/package.json + {project-name} 📄 Resolved the current version as 999.9.9-version-git-operations-test.2 from manifest: {project-name}/package.json + {project-name} ❓ Applied explicit semver value "999.9.9-version-git-operations-test.3", from the given specifier, to get new version 999.9.9-version-git-operations-test.3 + {project-name} ✍️ New version 999.9.9-version-git-operations-test.3 written to manifest: {project-name}/package.json - NX Running release version for project: {project-name} + NX Running release version for project: {project-name} - {project-name} 📄 Resolved the current version as 999.9.9 from manifest: {project-name}/package.json - {project-name} ❓ Applied explicit semver value "999.9.9-version-git-operations-test.3", from the given specifier, to get new version 999.9.9-version-git-operations-test.3 - {project-name} ✍️ New version 999.9.9-version-git-operations-test.3 written to manifest: {project-name}/package.json - {project-name} ✍️ Updated 1 dependency in manifest: {project-name}/package.json + {project-name} 📄 Resolved the current version as 999.9.9 from manifest: {project-name}/package.json + {project-name} ❓ Applied explicit semver value "999.9.9-version-git-operations-test.3", from the given specifier, to get new version 999.9.9-version-git-operations-test.3 + {project-name} ✍️ New version 999.9.9-version-git-operations-test.3 written to manifest: {project-name}/package.json - "name": "@proj/{project-name}", - - "version": "999.9.9-package.3", - + "version": "999.9.9-version-git-operations-test.3", - "exports": { + "name": "@proj/{project-name}", + - "version": "999.9.9-package.3", + + "version": "999.9.9-version-git-operations-test.3", + "exports": { - "name": "@proj/{project-name}", - - "version": "999.9.9-version-git-operations-test.2", - + "version": "999.9.9-version-git-operations-test.3", - "exports": { + "name": "@proj/{project-name}", + - "version": "999.9.9-version-git-operations-test.2", + + "version": "999.9.9-version-git-operations-test.3", + "exports": { - "name": "@proj/{project-name}", - - "version": "999.9.9", - + "version": "999.9.9-version-git-operations-test.3", - "exports": { + "name": "@proj/{project-name}", + - "version": "999.9.9", + + "version": "999.9.9-version-git-operations-test.3", + "exports": { - "dependencies": { - - "@proj/{project-name}": "999.9.9-package.3" - + "@proj/{project-name}": "999.9.9-version-git-operations-test.3" - } + NX Updating {package-manager} lock file - Skipped lock file update because {package-manager} workspaces are not enabled. + Updating {lock-file} with the following command: + {lock-file-command} - NX Committing changes with git + NX Committing changes with git - Staging files in git with the following command: - git add {project-name}/package.json {project-name}/package.json {project-name}/package.json + Staging files in git with the following command: + git add {project-name}/package.json {project-name}/package.json {project-name}/package.json {lock-file} - Committing files in git with the following command: - git commit --message chore(release): publish --message - project: {project-name} 999.9.9-version-git-operations-test.3 --message - project: {project-name} 999.9.9-version-git-operations-test.3 --message - release-group: fixed 999.9.9-version-git-operations-test.3 + Committing files in git with the following command: + git commit --message chore(release): publish --message - project: {project-name} 999.9.9-version-git-operations-test.3 --message - project: {project-name} 999.9.9-version-git-operations-test.3 --message - release-group: fixed 999.9.9-version-git-operations-test.3 - NX Tagging commit with git + NX Tagging commit with git - Tagging the current commit in git with the following command: - git tag --annotate {project-name}@999.9.9-version-git-operations-test.3 --message {project-name}@999.9.9-version-git-operations-test.3 - Tagging the current commit in git with the following command: - git tag --annotate {project-name}@999.9.9-version-git-operations-test.3 --message {project-name}@999.9.9-version-git-operations-test.3 - Tagging the current commit in git with the following command: - git tag --annotate v999.9.9-version-git-operations-test.3 --message v999.9.9-version-git-operations-test.3 + Tagging the current commit in git with the following command: + git tag --annotate {project-name}@999.9.9-version-git-operations-test.3 --message {project-name}@999.9.9-version-git-operations-test.3 + Tagging the current commit in git with the following command: + git tag --annotate {project-name}@999.9.9-version-git-operations-test.3 --message {project-name}@999.9.9-version-git-operations-test.3 + Tagging the current commit in git with the following command: + git tag --annotate v999.9.9-version-git-operations-test.3 --message v999.9.9-version-git-operations-test.3 - `); + `); // Ensure the git operations were performed expect(runCommand(`git rev-parse HEAD`).trim()).not.toEqual(headSHA); // Commit expect(runCommand(`git --no-pager log -1 --pretty=format:%B`).trim()) .toMatchInlineSnapshot(` - chore(release): publish + chore(release): publish - - project: {project-name} 999.9.9-version-git-operations-test.3 + - project: {project-name} 999.9.9-version-git-operations-test.3 - - project: {project-name} 999.9.9-version-git-operations-test.3 + - project: {project-name} 999.9.9-version-git-operations-test.3 - - release-group: fixed 999.9.9-version-git-operations-test.3 - `); + - release-group: fixed 999.9.9-version-git-operations-test.3 + `); // Tags expect(runCommand('git tag --points-at HEAD')).toMatchInlineSnapshot(` - {project-name}@999.9.9-version-git-operations-test.3 - {project-name}@999.9.9-version-git-operations-test.3 - v999.9.9-version-git-operations-test.3 + {project-name}@999.9.9-version-git-operations-test.3 + {project-name}@999.9.9-version-git-operations-test.3 + v999.9.9-version-git-operations-test.3 - `); + `); }); }); @@ -426,26 +439,26 @@ describe('nx release - independent projects', () => { ); expect(changelogPkg1Output).toMatchInlineSnapshot(` - NX Your filter "{project-name}" matched the following projects: + NX Your filter "{project-name}" matched the following projects: - - {project-name} + - {project-name} - NX Previewing an entry in {project-name}/CHANGELOG.md for {project-name}@999.9.9-package.1 + NX Previewing an entry in {project-name}/CHANGELOG.md for {project-name}@999.9.9-package.1 - + ## 999.9.9-package.1 (YYYY-MM-DD) - + - + This was a version bump only for {project-name} to align it with other projects, there were no code changes. + + ## 999.9.9-package.1 (YYYY-MM-DD) + + + + 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 Committing changes with git - NX Tagging commit with git + NX Tagging commit with git - `); + `); // pkg2 const changelogPkg2Output = runCLI( @@ -453,26 +466,26 @@ describe('nx release - independent projects', () => { ); expect(changelogPkg2Output).toMatchInlineSnapshot(` - NX Your filter "{project-name}" matched the following projects: + NX Your filter "{project-name}" matched the following projects: - - {project-name} + - {project-name} - NX Previewing an entry in {project-name}/CHANGELOG.md for {project-name}@999.9.9-package.2 + NX Previewing an entry in {project-name}/CHANGELOG.md for {project-name}@999.9.9-package.2 - + ## 999.9.9-package.2 (YYYY-MM-DD) - + - + This was a version bump only for {project-name} to align it with other projects, there were no code changes. + + ## 999.9.9-package.2 (YYYY-MM-DD) + + + + 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 Committing changes with git - NX Tagging commit with git + NX Tagging commit with git - `); + `); // pkg3 const changelogPkg3Output = runCLI( @@ -480,26 +493,26 @@ describe('nx release - independent projects', () => { ); expect(changelogPkg3Output).toMatchInlineSnapshot(` - NX Your filter "{project-name}" matched the following projects: + NX Your filter "{project-name}" matched the following projects: - - {project-name} + - {project-name} - NX Previewing an entry in {project-name}/CHANGELOG.md for {project-name}@999.9.9-package.3 + NX Previewing an entry in {project-name}/CHANGELOG.md for {project-name}@999.9.9-package.3 - + ## 999.9.9-package.3 (YYYY-MM-DD) - + - + This was a version bump only for {project-name} to align it with other projects, there were no code changes. + + ## 999.9.9-package.3 (YYYY-MM-DD) + + + + 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 Committing changes with git - NX Tagging commit with git + NX Tagging commit with git - `); + `); }, 500000); it('should support automated git operations after changelog by default', async () => { @@ -513,55 +526,55 @@ describe('nx release - independent projects', () => { ); expect(versionWithGitActionsCLIOutput).toMatchInlineSnapshot(` - NX Your filter "{project-name}" matched the following projects: + NX Your filter "{project-name}" matched the following projects: - - {project-name} + - {project-name} - NX Generating an entry in {project-name}/CHANGELOG.md for {project-name}@999.9.9-changelog-git-operations-test.1 + NX Generating an entry in {project-name}/CHANGELOG.md for {project-name}@999.9.9-changelog-git-operations-test.1 - + ## 999.9.9-changelog-git-operations-test.1 (YYYY-MM-DD) - + - + This was a version bump only for {project-name} to align it with other projects, there were no code changes. + + ## 999.9.9-changelog-git-operations-test.1 (YYYY-MM-DD) + + + + 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 Committing changes with git - Staging files in git with the following command: - git add {project-name}/CHANGELOG.md + Staging files in git with the following command: + git add {project-name}/CHANGELOG.md - Committing files in git with the following command: - git commit --message chore(release): publish --message - project: {project-name} 999.9.9-changelog-git-operations-test.1 + Committing files in git with the following command: + git commit --message chore(release): publish --message - project: {project-name} 999.9.9-changelog-git-operations-test.1 - NX Tagging commit with git + NX Tagging commit with git - Tagging the current commit in git with the following command: - git tag --annotate {project-name}@999.9.9-changelog-git-operations-test.1 --message {project-name}@999.9.9-changelog-git-operations-test.1 + Tagging the current commit in git with the following command: + git tag --annotate {project-name}@999.9.9-changelog-git-operations-test.1 --message {project-name}@999.9.9-changelog-git-operations-test.1 - `); + `); // Ensure the git operations were performed expect(runCommand(`git rev-parse HEAD`).trim()).not.toEqual(headSHA); // Commit expect(runCommand(`git --no-pager log -1 --pretty=format:%B`).trim()) .toMatchInlineSnapshot(` - chore(release): publish + chore(release): publish - - project: {project-name} 999.9.9-changelog-git-operations-test.1 - `); + - project: {project-name} 999.9.9-changelog-git-operations-test.1 + `); // Tags expect(runCommand('git tag --points-at HEAD')).toMatchInlineSnapshot(` - {project-name}@999.9.9-changelog-git-operations-test.1 + {project-name}@999.9.9-changelog-git-operations-test.1 - `); + `); expect(readFile(joinPathFragments(pkg1, 'CHANGELOG.md'))) .toMatchInlineSnapshot(` - ## 999.9.9-changelog-git-operations-test.1 (YYYY-MM-DD) + ## 999.9.9-changelog-git-operations-test.1 (YYYY-MM-DD) - This was a version bump only for {project-name} to align it with other projects, there were no code changes. - `); + This was a version bump only for {project-name} to align it with other projects, there were no code changes. + `); const updatedHeadSHA = runCommand(`git rev-parse HEAD`).trim(); // Disable git commit and tag operations via CLI flags @@ -631,244 +644,244 @@ describe('nx release - independent projects', () => { // Should only contain 1 project expect(runCLI(`release publish -p ${pkg1} -d`)).toMatchInlineSnapshot(` - NX Your filter "{project-name}" matched the following projects: + NX Your filter "{project-name}" matched the following projects: - - {project-name} (release group "group1") + - {project-name} (release group "group1") - NX Running target nx-release-publish for project {project-name}: + NX Running target nx-release-publish for project {project-name}: - - {project-name} + - {project-name} - With additional flags: - --dryRun=true + With additional flags: + --dryRun=true - > nx run {project-name}:nx-release-publish + > nx run {project-name}:nx-release-publish - 📦 @proj/{project-name}@X.X.X-dry-run - === Tarball Contents === + 📦 @proj/{project-name}@X.X.X-dry-run + === Tarball Contents === - XXXB CHANGELOG.md - 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: 4 + XXXB CHANGELOG.md + 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: 4 - Would publish to ${e2eRegistryUrl} with tag "latest", but [dry-run] was set + Would publish to http://localhost:4873 with tag "latest", but [dry-run] was set - NX Successfully ran target nx-release-publish for project {project-name} + NX Successfully ran target nx-release-publish for project {project-name} - `); + `); // Should only contain 2 projects expect(runCLI(`release publish -p ${pkg1} -p ${pkg3} -d`)) .toMatchInlineSnapshot(` - NX Your filter "{project-name},{project-name}" matched the following projects: + NX Your filter "{project-name},{project-name}" matched the following projects: - - {project-name} (release group "group1") - - {project-name} (release group "group2") + - {project-name} (release group "group1") + - {project-name} (release group "group2") - NX Running target nx-release-publish for project {project-name}: + NX Running target nx-release-publish for project {project-name}: - - {project-name} + - {project-name} - With additional flags: - --dryRun=true + With additional flags: + --dryRun=true - > nx run {project-name}:nx-release-publish + > nx run {project-name}:nx-release-publish - 📦 @proj/{project-name}@X.X.X-dry-run - === Tarball Contents === + 📦 @proj/{project-name}@X.X.X-dry-run + === Tarball Contents === - XXXB CHANGELOG.md - 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: 4 + XXXB CHANGELOG.md + 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: 4 - Would publish to ${e2eRegistryUrl} with tag "latest", but [dry-run] was set + Would publish to http://localhost:4873 with tag "latest", but [dry-run] was set - NX Successfully ran target nx-release-publish for project {project-name} + NX Successfully ran target nx-release-publish for project {project-name} - NX Running target nx-release-publish for project {project-name}: + NX Running target nx-release-publish for project {project-name}: - - {project-name} + - {project-name} - With additional flags: - --dryRun=true + With additional flags: + --dryRun=true - > nx run {project-name}:nx-release-publish + > nx run {project-name}:nx-release-publish - 📦 @proj/{project-name}@X.X.X-dry-run - === Tarball Contents === + 📦 @proj/{project-name}@X.X.X-dry-run + === Tarball Contents === - XXXB CHANGELOG.md - 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: 4 + XXXB CHANGELOG.md + 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: 4 - Would publish to ${e2eRegistryUrl} with tag "latest", but [dry-run] was set + Would publish to http://localhost:4873 with tag "latest", but [dry-run] was set - NX Successfully ran target nx-release-publish for project {project-name} + NX Successfully ran target nx-release-publish for project {project-name} - `); + `); }); it('should only run the publish task for the filtered projects', async () => { // Should only contain the 2 projects from group1 expect(runCLI(`release publish -g group1 -d`)).toMatchInlineSnapshot(` - NX Running target nx-release-publish for 2 projects: + NX Running target nx-release-publish for 2 projects: - - {project-name} - - {project-name} + - {project-name} + - {project-name} - With additional flags: - --dryRun=true + With additional flags: + --dryRun=true - > nx run {project-name}:nx-release-publish + > nx run {project-name}:nx-release-publish - 📦 @proj/{project-name}@X.X.X-dry-run - === Tarball Contents === + 📦 @proj/{project-name}@X.X.X-dry-run + === Tarball Contents === - XXXB CHANGELOG.md - 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: 4 + XXXB CHANGELOG.md + 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: 4 - Would publish to ${e2eRegistryUrl} with tag "latest", but [dry-run] was set + Would publish to http://localhost:4873 with tag "latest", but [dry-run] was set - > nx run {project-name}:nx-release-publish + > nx run {project-name}:nx-release-publish - 📦 @proj/{project-name}@X.X.X-dry-run - === Tarball Contents === + 📦 @proj/{project-name}@X.X.X-dry-run + === Tarball Contents === - XXXB CHANGELOG.md - 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: 4 + XXXB CHANGELOG.md + 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: 4 - Would publish to ${e2eRegistryUrl} with tag "latest", but [dry-run] was set + Would publish to http://localhost:4873 with tag "latest", but [dry-run] was set - NX Successfully ran target nx-release-publish for 2 projects + NX Successfully ran target nx-release-publish for 2 projects - `); + `); // Should only contain the 1 project from group2 expect(runCLI(`release publish -g group2 -d`)).toMatchInlineSnapshot(` - NX Running target nx-release-publish for project {project-name}: + NX Running target nx-release-publish for project {project-name}: - - {project-name} + - {project-name} - With additional flags: - --dryRun=true + With additional flags: + --dryRun=true - > nx run {project-name}:nx-release-publish + > nx run {project-name}:nx-release-publish - 📦 @proj/{project-name}@X.X.X-dry-run - === Tarball Contents === + 📦 @proj/{project-name}@X.X.X-dry-run + === Tarball Contents === - XXXB CHANGELOG.md - 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: 4 + XXXB CHANGELOG.md + 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: 4 - Would publish to ${e2eRegistryUrl} with tag "latest", but [dry-run] was set + Would publish to http://localhost:4873 with tag "latest", but [dry-run] was set - NX Successfully ran target nx-release-publish for project {project-name} + NX Successfully ran target nx-release-publish for project {project-name} - `); + `); }); }); diff --git a/e2e/release/src/preserve-matching-dependency-ranges.test.ts b/e2e/release/src/preserve-matching-dependency-ranges.test.ts index 7135ed7fbe51c..4ab68a2c37dcb 100644 --- a/e2e/release/src/preserve-matching-dependency-ranges.test.ts +++ b/e2e/release/src/preserve-matching-dependency-ranges.test.ts @@ -2,14 +2,17 @@ import { NxJsonConfiguration } from '@nx/devkit'; import { cleanupProject, newProject, - readJson, + runCommandAsync, runCLI, tmpProjPath, uniq, updateJson, + getPackageManagerCommand, + detectPackageManager, } from '@nx/e2e-utils'; import { execSync } from 'node:child_process'; import { join } from 'node:path'; +import { setupWorkspaces } from './utils'; expect.addSnapshotSerializer({ serialize(str: string) { @@ -33,6 +36,7 @@ expect.addSnapshotSerializer({ .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') + .replaceAll('NX Updating PM lock file\n', '') // 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 @@ -62,7 +66,7 @@ describe('nx release preserve matching dependency ranges', () => { /** * Initialize each test with a fresh workspace */ - const initializeProject = () => { + const initializeProject = async () => { newProject({ packages: ['@nx/js'], }); @@ -74,6 +78,8 @@ describe('nx release preserve matching dependency ranges', () => { const pkg3 = uniq('my-pkg-3'); runCLI(`generate @nx/workspace:npm-package ${pkg3}`); + setupWorkspaces(detectPackageManager(), pkg1, pkg2, pkg3); + // Set up dependencies with various range types updateJson(join(pkg1, 'package.json'), (packageJson) => { packageJson.version = '1.0.0'; @@ -103,6 +109,9 @@ describe('nx release preserve matching dependency ranges', () => { return packageJson; }); + const pmc = getPackageManagerCommand(); + await runCommandAsync(pmc.install); + // workaround for NXC-143 runCLI('reset'); @@ -110,8 +119,8 @@ describe('nx release preserve matching dependency ranges', () => { }; describe('when preserveMatchingDependencyRanges is set to false', () => { - it('should update all dependency ranges', () => { - const { workspacePath } = initializeProject(); + it('should update all dependency ranges', async () => { + const { workspacePath } = await initializeProject(); updateJson('nx.json', (nxJson) => { nxJson.release = { @@ -184,8 +193,8 @@ describe('nx release preserve matching dependency ranges', () => { }); describe('when preserveMatchingDependencyRanges is set to true', () => { - it('should preserve dependency ranges when new version satisfies them', () => { - const { workspacePath, pkg1, pkg3 } = initializeProject(); + it('should preserve dependency ranges when new version satisfies them', async () => { + const { workspacePath, pkg1, pkg3 } = await initializeProject(); updateJson(join(pkg1, 'package.json'), (packageJson) => { packageJson.dependencies[`@proj/${pkg3}`] = '^1.0.0'; return packageJson; @@ -244,8 +253,8 @@ describe('nx release preserve matching dependency ranges', () => { }); describe('when preserveMatchingDependencyRanges is set to specific dependency types', () => { - it('should only preserve ranges for specified dependency types', () => { - const { workspacePath } = initializeProject(); + it('should only preserve ranges for specified dependency types', async () => { + const { workspacePath } = await initializeProject(); updateJson('nx.json', (nxJson) => { nxJson.release = { @@ -311,8 +320,8 @@ describe('nx release preserve matching dependency ranges', () => { `); }); - it('should handle empty array (no preservation)', () => { - const { workspacePath } = initializeProject(); + it('should handle empty array (no preservation)', async () => { + const { workspacePath } = await initializeProject(); updateJson('nx.json', (nxJson) => { nxJson.release = { @@ -386,8 +395,8 @@ describe('nx release preserve matching dependency ranges', () => { }); describe('with patch versions', () => { - it('should preserve ranges when patch version satisfies them', () => { - const { workspacePath } = initializeProject(); + it('should preserve ranges when patch version satisfies them', async () => { + const { workspacePath } = await initializeProject(); updateJson('nx.json', (nxJson) => { nxJson.release = { @@ -443,8 +452,8 @@ describe('nx release preserve matching dependency ranges', () => { }); describe('with exact version dependencies', () => { - it('should always update exact version dependencies', () => { - const { workspacePath, pkg1, pkg2 } = initializeProject(); + it('should always update exact version dependencies', async () => { + const { workspacePath, pkg1, pkg2 } = await initializeProject(); // Add exact version dependency updateJson(join(pkg1, 'package.json'), (packageJson) => { @@ -512,8 +521,8 @@ describe('nx release preserve matching dependency ranges', () => { }); describe('with wildcard ranges', () => { - it('should preserve wildcard ranges', () => { - const { workspacePath, pkg1, pkg2, pkg3 } = initializeProject(); + it('should preserve wildcard ranges', async () => { + const { workspacePath, pkg1, pkg2, pkg3 } = await initializeProject(); // Add wildcard dependency updateJson(join(pkg1, 'package.json'), (packageJson) => { diff --git a/e2e/release/src/utils.ts b/e2e/release/src/utils.ts new file mode 100644 index 0000000000000..350a3eb257f4e --- /dev/null +++ b/e2e/release/src/utils.ts @@ -0,0 +1,49 @@ +import { + runCommandAsync, + createFile, + updateJson, + removeFile, +} from '@nx/e2e-utils'; + +export function setupWorkspaces( + packageManager: 'npm' | 'yarn' | 'pnpm' | 'bun', + ...packages: string[] +) { + if (packageManager === 'npm' || packageManager === 'yarn') { + updateJson('package.json', (packageJson) => { + packageJson.workspaces = packages; + return packageJson; + }); + } else if (packageManager === 'pnpm') { + createFile( + `pnpm-workspace.yaml`, + `packages: + ${packages.map((p) => `- ${p}`).join('\n ')} + ` + ); + } +} + +export async function prepareAndInstallDependencies( + packageManager: 'npm' | 'yarn' | 'pnpm' | 'bun', + installCommand: string +) { + if (packageManager === 'npm') { + removeFile('yarn.lock'); + removeFile('pnpm-lock.yaml'); + removeFile('pnpm-workspace.yaml'); + } else if (packageManager === 'yarn') { + removeFile('package-lock.json'); + removeFile('pnpm-lock.yaml'); + removeFile('pnpm-workspace.yaml'); + updateJson('package.json', (pkgJson) => { + delete pkgJson.packageManager; + return pkgJson; + }); + await runCommandAsync(`yarn config set enableImmutableInstalls false`); + } else if (packageManager === 'pnpm') { + removeFile('package-lock.json'); + removeFile('yarn.lock'); + } + await runCommandAsync(installCommand); +} diff --git a/e2e/release/tsconfig.spec.json b/e2e/release/tsconfig.spec.json index 3a6cc7d023dbc..43707e3f659fb 100644 --- a/e2e/release/tsconfig.spec.json +++ b/e2e/release/tsconfig.spec.json @@ -5,6 +5,7 @@ "types": ["jest", "node"] }, "include": [ + "src/utils.ts", "**/*.test.ts", "**/*.spec.ts", "**/*.spec.tsx", diff --git a/packages/nx/src/config/workspace-json-project-json.ts b/packages/nx/src/config/workspace-json-project-json.ts index 70429baff73ab..913b13cefcf83 100644 --- a/packages/nx/src/config/workspace-json-project-json.ts +++ b/packages/nx/src/config/workspace-json-project-json.ts @@ -157,6 +157,7 @@ export interface ProjectMetadata { }; js?: { packageName: string; + packageVersion?: string; packageExports?: PackageJson['exports']; packageMain?: string; isInPackageManagerWorkspaces?: boolean; diff --git a/packages/nx/src/plugins/js/lock-file/npm-parser.ts b/packages/nx/src/plugins/js/lock-file/npm-parser.ts index 7be674fd7a731..f9b8817cc6aaf 100644 --- a/packages/nx/src/plugins/js/lock-file/npm-parser.ts +++ b/packages/nx/src/plugins/js/lock-file/npm-parser.ts @@ -116,7 +116,10 @@ function getNodes( const packageName = path.split('node_modules/').pop(); const version = findV3Version(snapshot, packageName); - createNode(packageName, version, path, nodes, keyMap, snapshot); + // symlinked packages in workspaces do not have versions + if (version) { + createNode(packageName, version, path, nodes, keyMap, snapshot); + } }); } else { Object.entries(data.dependencies).forEach(([packageName, snapshot]) => { diff --git a/packages/nx/src/plugins/js/project-graph/build-dependencies/explicit-package-json-dependencies.spec.ts b/packages/nx/src/plugins/js/project-graph/build-dependencies/explicit-package-json-dependencies.spec.ts index ce0b37048f039..9d51433d71b58 100644 --- a/packages/nx/src/plugins/js/project-graph/build-dependencies/explicit-package-json-dependencies.spec.ts +++ b/packages/nx/src/plugins/js/project-graph/build-dependencies/explicit-package-json-dependencies.spec.ts @@ -1,4 +1,5 @@ import { TempFs } from '../../../../internal-testing-utils/temp-fs'; + const tempFs = new TempFs('explicit-package-json'); import { ProjectGraphProjectNode } from '../../../../config/project-graph'; @@ -33,6 +34,14 @@ describe('explicit package json dependencies', () => { root: 'libs/proj4', name: 'proj4', }, + proj5: { + root: 'libs/proj5', + name: 'proj5', + }, + proj6: { + root: 'libs/proj6', + name: 'proj6', + }, }, }; @@ -54,15 +63,44 @@ describe('explicit package json dependencies', () => { './libs/proj2/package.json': JSON.stringify({ name: 'proj2' }), './libs/proj3/package.json': JSON.stringify({ name: 'proj3', - dependencies: { lodash: '3.0.0' }, + dependencies: { + lodash: '3.0.0', + proj4: '2.0.0', // references the local source version in the workspace + }, + }), + './libs/proj4/index.js': '', + './libs/proj4/package.json': JSON.stringify({ + name: 'proj4', + version: '2.0.0', // the source version in the workspace + }), + './libs/proj5/index.js': '', + './libs/proj5/package.json': JSON.stringify({ + name: 'proj5', + version: '1.1.0', // the source version in the workspace + }), + './libs/proj6/index.js': '', + './libs/proj6/package.json': JSON.stringify({ + name: 'proj6', + version: '1.1.0', // the source version in the workspace }), './libs/proj3/node_modules/lodash/index.js': '', './libs/proj3/node_modules/lodash/package.json': JSON.stringify({ name: 'lodash', version: '3.0.0', }), + './libs/proj/node_modules/proj4/index.js': '', + './libs/proj/node_modules/proj4/package.json': JSON.stringify({ + name: 'proj4', + version: '1.0.0', + }), './libs/proj/package.json': JSON.stringify({ - dependencies: { proj2: '*', lodash: '4.0.0' }, + dependencies: { + proj2: '*', + proj4: '1.0.0', // references an installed older version from package manager + proj5: '^1.0.0', + proj6: 'file:../proj6', + lodash: '4.0.0', + }, devDependencies: { proj3: '*' }, }), }); @@ -100,7 +138,7 @@ describe('explicit package json dependencies', () => { name: 'proj3', type: 'lib', data: { - root: 'libs/proj4', + root: 'libs/proj3', metadata: { js: { packageName: 'proj3', @@ -110,6 +148,51 @@ describe('explicit package json dependencies', () => { }, }, }, + proj4: { + name: 'proj4', + type: 'lib', + data: { + root: 'libs/proj4', + metadata: { + js: { + packageName: 'proj4', + packageVersion: '2.0.0', + packageExports: undefined, + isInPackageManagerWorkspaces: true, + }, + }, + }, + }, + proj5: { + name: 'proj5', + type: 'lib', + data: { + root: 'libs/proj5', + metadata: { + js: { + packageName: 'proj5', + packageVersion: '1.1.0', + packageExports: undefined, + isInPackageManagerWorkspaces: true, + }, + }, + }, + }, + proj6: { + name: 'proj6', + type: 'lib', + data: { + root: 'libs/proj6', + metadata: { + js: { + packageName: 'proj6', + packageVersion: '1.1.0', + packageExports: undefined, + isInPackageManagerWorkspaces: true, + }, + }, + }, + }, }; const fileMap = createFileMap( @@ -129,6 +212,14 @@ describe('explicit package json dependencies', () => { packageName: 'lodash', }, }); + builder.addExternalNode({ + type: 'npm', + name: 'npm:proj4', + data: { + version: '1.0.0', + packageName: 'proj4', + }, + }); builder.addExternalNode({ type: 'npm', name: 'npm:lodash@3.0.0', @@ -161,34 +252,37 @@ describe('explicit package json dependencies', () => { ); const res = buildExplicitPackageJsonDependencies(ctx, targetProjectLocator); - expect(res).toEqual([ - { - source: 'proj', - target: 'proj3', - sourceFile: 'libs/proj/package.json', - type: 'static', - }, - { - source: 'proj', - target: 'proj2', - sourceFile: 'libs/proj/package.json', - type: 'static', - }, - { - sourceFile: 'libs/proj/package.json', - source: 'proj', - target: 'npm:lodash', - type: 'static', - }, - { - sourceFile: 'libs/proj3/package.json', - source: 'proj3', - target: 'npm:lodash@3.0.0', - type: 'static', - }, - ]); + expect(res).toEqual( + expect.arrayContaining([ + { + source: 'proj', + target: 'proj3', + sourceFile: 'libs/proj/package.json', + type: 'static', + }, + { + source: 'proj', + target: 'proj2', + sourceFile: 'libs/proj/package.json', + type: 'static', + }, + { + sourceFile: 'libs/proj/package.json', + source: 'proj', + target: 'npm:lodash', + type: 'static', + }, + { + sourceFile: 'libs/proj3/package.json', + source: 'proj3', + target: 'npm:lodash@3.0.0', + type: 'static', + }, + ]) + ); expect(npmResolutionCache).toMatchInlineSnapshot(` Map { + "proj4__libs/proj" => "npm:proj4", "lodash__libs/proj" => "npm:lodash", "lodash__libs/proj3" => "npm:lodash@3.0.0", } @@ -208,33 +302,82 @@ describe('explicit package json dependencies', () => { npmResolutionCache.set('lodash__libs/proj3', 'npm:lodash@999.9.9'); const res = buildExplicitPackageJsonDependencies(ctx, targetProjectLocator); - expect(res).toEqual([ - { - source: 'proj', - target: 'proj3', - sourceFile: 'libs/proj/package.json', - type: 'static', - }, - { - source: 'proj', - target: 'proj2', - sourceFile: 'libs/proj/package.json', - type: 'static', - }, - { - sourceFile: 'libs/proj/package.json', - source: 'proj', - // Preferred the cached version of lodash, instead of 4.0.0 resolved from tempFs node_modules - target: 'npm:lodash@999.9.9', - type: 'static', - }, - { - sourceFile: 'libs/proj3/package.json', - source: 'proj3', - // Preferred the cached version of lodash, instead of 3.0.0 resolved from tempFs node_modules - target: 'npm:lodash@999.9.9', - type: 'static', - }, - ]); + expect(res).toContainEqual({ + sourceFile: 'libs/proj/package.json', + source: 'proj', + // Preferred the cached version of lodash, instead of 4.0.0 resolved from tempFs node_modules + target: 'npm:lodash@999.9.9', + type: 'static', + }); + expect(res).toContainEqual({ + sourceFile: 'libs/proj3/package.json', + source: 'proj3', + // Preferred the cached version of lodash, instead of 3.0.0 resolved from tempFs node_modules + target: 'npm:lodash@999.9.9', + type: 'static', + }); + }); + + it('should add correct npm dependencies for projects that use an older installed version of a package that exists in the workspace', async () => { + const npmResolutionCache = new Map(); + const targetProjectLocator = new TargetProjectLocator( + projects, + ctx.externalNodes, + npmResolutionCache + ); + + const res = buildExplicitPackageJsonDependencies(ctx, targetProjectLocator); + expect(res).toEqual( + expect.arrayContaining([ + { + source: 'proj', + sourceFile: 'libs/proj/package.json', + target: 'npm:proj4', // correctly resolves to the npm package of the project rather than the workspace project + type: 'static', + }, + ]) + ); + }); + + it('should add correct workspace dependencies for projects that use a range for packages which exist in the workspace', async () => { + const npmResolutionCache = new Map(); + const targetProjectLocator = new TargetProjectLocator( + projects, + ctx.externalNodes, + npmResolutionCache + ); + + const res = buildExplicitPackageJsonDependencies(ctx, targetProjectLocator); + expect(res).toEqual( + expect.arrayContaining([ + { + source: 'proj', + sourceFile: 'libs/proj/package.json', + target: 'proj5', // correctly resolves to the npm package of the project rather than the workspace project + type: 'static', + }, + ]) + ); + }); + + it('should add correct workspace dependencies for projects that use a file reference of a package that exists in the workspace', async () => { + const npmResolutionCache = new Map(); + const targetProjectLocator = new TargetProjectLocator( + projects, + ctx.externalNodes, + npmResolutionCache + ); + + const res = buildExplicitPackageJsonDependencies(ctx, targetProjectLocator); + expect(res).toEqual( + expect.arrayContaining([ + { + source: 'proj', + sourceFile: 'libs/proj/package.json', + target: 'proj6', // correctly resolves to the npm package of the project rather than the workspace project + type: 'static', + }, + ]) + ); }); }); diff --git a/packages/nx/src/plugins/js/project-graph/build-dependencies/explicit-package-json-dependencies.ts b/packages/nx/src/plugins/js/project-graph/build-dependencies/explicit-package-json-dependencies.ts index 5d5bfd040c8b7..3a80cf08588e5 100644 --- a/packages/nx/src/plugins/js/project-graph/build-dependencies/explicit-package-json-dependencies.ts +++ b/packages/nx/src/plugins/js/project-graph/build-dependencies/explicit-package-json-dependencies.ts @@ -42,23 +42,27 @@ function isPackageJsonAtProjectRoot( function processPackageJson( sourceProject: string, - fileName: string, + packageJsonPath: string, ctx: CreateDependenciesContext, targetProjectLocator: TargetProjectLocator, collectedDeps: RawProjectGraphDependency[] ) { try { - const deps = readDeps(parseJson(defaultFileRead(fileName))); + const deps = readDeps(parseJson(defaultFileRead(packageJsonPath))); - for (const d of Object.keys(deps)) { + for (const [packageName, packageVersion] of Object.entries(deps)) { const localProject = - targetProjectLocator.findDependencyInWorkspaceProjects(d); + targetProjectLocator.findDependencyInWorkspaceProjects( + packageJsonPath, + packageName, + packageVersion + ); if (localProject) { // package.json refers to another project in the monorepo const dependency: RawProjectGraphDependency = { source: sourceProject, target: localProject, - sourceFile: fileName, + sourceFile: packageJsonPath, type: DependencyType.static, }; validateDependency(dependency, ctx); @@ -67,8 +71,8 @@ function processPackageJson( } const externalNodeName = targetProjectLocator.findNpmProjectFromImport( - d, - fileName + packageName, + packageJsonPath ); if (!externalNodeName) { continue; @@ -77,7 +81,7 @@ function processPackageJson( const dependency: RawProjectGraphDependency = { source: sourceProject, target: externalNodeName, - sourceFile: fileName, + sourceFile: packageJsonPath, type: DependencyType.static, }; validateDependency(dependency, ctx); diff --git a/packages/nx/src/plugins/js/project-graph/build-dependencies/target-project-locator.spec.ts b/packages/nx/src/plugins/js/project-graph/build-dependencies/target-project-locator.spec.ts index 0b8e24ff76e08..79b0c375ee5e6 100644 --- a/packages/nx/src/plugins/js/project-graph/build-dependencies/target-project-locator.spec.ts +++ b/packages/nx/src/plugins/js/project-graph/build-dependencies/target-project-locator.spec.ts @@ -1116,8 +1116,11 @@ describe('TargetProjectLocator', () => { {}, new Map() ); - const result = - targetProjectLocator.findDependencyInWorkspaceProjects('@org/pkg1'); + const result = targetProjectLocator.findDependencyInWorkspaceProjects( + '', + '@org/pkg1', + '*' + ); expect(result).toEqual('pkg1'); } @@ -1145,8 +1148,11 @@ describe('TargetProjectLocator', () => { {}, new Map() ); - const result = - targetProjectLocator.findDependencyInWorkspaceProjects('@org/pkg2'); + const result = targetProjectLocator.findDependencyInWorkspaceProjects( + '', + '@org/pkg2', + '*' + ); expect(result).toBeFalsy(); }); diff --git a/packages/nx/src/plugins/js/project-graph/build-dependencies/target-project-locator.ts b/packages/nx/src/plugins/js/project-graph/build-dependencies/target-project-locator.ts index 4dca92b1e9b44..77131cd7fab33 100644 --- a/packages/nx/src/plugins/js/project-graph/build-dependencies/target-project-locator.ts +++ b/packages/nx/src/plugins/js/project-graph/build-dependencies/target-project-locator.ts @@ -1,6 +1,6 @@ import { isBuiltin } from 'node:module'; -import { dirname, join, posix, relative } from 'node:path'; -import { clean } from 'semver'; +import { dirname, join, posix, relative, resolve } from 'node:path'; +import { clean, satisfies } from 'semver'; import type { ProjectGraphExternalNode, ProjectGraphProjectNode, @@ -323,10 +323,56 @@ export class TargetProjectLocator { return project?.name; } - findDependencyInWorkspaceProjects(dep: string): string | null { + findDependencyInWorkspaceProjects( + packageJsonPath: string, + dep: string, + packageVersion: string + ): string | null { this.packagesMetadata ??= getWorkspacePackagesMetadata(this.nodes); - return this.packagesMetadata.packageToProjectMap[dep]?.name; + const maybeDep = this.packagesMetadata.packageToProjectMap[dep]; + + const maybeDepMetadata = maybeDep?.data.metadata.js; + + if (!maybeDepMetadata?.isInPackageManagerWorkspaces) { + return null; + } + + const workspaceRegex = /^workspace:/; + const hasWorkspaceProtocol = workspaceRegex.test(packageVersion); + const normalizedRange = packageVersion.replace(workspaceRegex, ''); + + /** + * Regex is needed to test for workspace: protocol because following options are all valid: + * - workspace:* + * - workspace:^ + * - workspace:~ + * - workspace:foo@* + */ + if (hasWorkspaceProtocol || normalizedRange === '*') { + return maybeDep?.name; + } + + if (normalizedRange.startsWith('file:')) { + const targetPath = maybeDep?.data.root; + + const normalizedPath = normalizedRange.replace('file:', ''); + const resolvedPath = join(dirname(packageJsonPath), normalizedPath); + + if (targetPath === resolvedPath) { + return maybeDep?.name; + } + } + + if ( + satisfies(maybeDepMetadata.packageVersion, normalizedRange, { + includePrerelease: true, + }) + ) { + return maybeDep?.name; + } + + return null; } private isPatternMatch( diff --git a/packages/nx/src/plugins/package-json/create-nodes.spec.ts b/packages/nx/src/plugins/package-json/create-nodes.spec.ts index 28571a7103038..69eac16f173fd 100644 --- a/packages/nx/src/plugins/package-json/create-nodes.spec.ts +++ b/packages/nx/src/plugins/package-json/create-nodes.spec.ts @@ -60,6 +60,7 @@ describe('nx package.json workspaces plugin', () => { "packageExports": undefined, "packageMain": undefined, "packageName": "root", + "packageVersion": undefined, }, "targetGroups": { "NPM Scripts": [ @@ -113,6 +114,7 @@ describe('nx package.json workspaces plugin', () => { "packageExports": undefined, "packageMain": undefined, "packageName": "lib-a", + "packageVersion": undefined, }, "targetGroups": { "NPM Scripts": [ @@ -173,6 +175,7 @@ describe('nx package.json workspaces plugin', () => { "packageExports": undefined, "packageMain": undefined, "packageName": "lib-b", + "packageVersion": undefined, }, "targetGroups": { "NPM Scripts": [ @@ -282,6 +285,7 @@ describe('nx package.json workspaces plugin', () => { "packageExports": undefined, "packageMain": undefined, "packageName": "vite", + "packageVersion": undefined, }, "targetGroups": {}, }, @@ -385,6 +389,7 @@ describe('nx package.json workspaces plugin', () => { "packageExports": undefined, "packageMain": undefined, "packageName": "vite", + "packageVersion": undefined, }, "targetGroups": {}, }, @@ -484,6 +489,7 @@ describe('nx package.json workspaces plugin', () => { "packageExports": undefined, "packageMain": undefined, "packageName": "vite", + "packageVersion": undefined, }, "targetGroups": {}, }, @@ -567,6 +573,7 @@ describe('nx package.json workspaces plugin', () => { "packageExports": undefined, "packageMain": undefined, "packageName": "root", + "packageVersion": undefined, }, "targetGroups": { "NPM Scripts": [ @@ -650,6 +657,7 @@ describe('nx package.json workspaces plugin', () => { "packageExports": undefined, "packageMain": undefined, "packageName": "root", + "packageVersion": undefined, }, "targetGroups": { "NPM Scripts": [ @@ -740,6 +748,7 @@ describe('nx package.json workspaces plugin', () => { "packageExports": undefined, "packageMain": undefined, "packageName": "root", + "packageVersion": undefined, }, "targetGroups": {}, }, @@ -921,6 +930,7 @@ describe('nx package.json workspaces plugin', () => { }, "packageMain": undefined, "packageName": "lib-a", + "packageVersion": undefined, }, "targetGroups": { "NPM Scripts": [ @@ -971,6 +981,7 @@ describe('nx package.json workspaces plugin', () => { }, "packageMain": undefined, "packageName": "lib-b", + "packageVersion": undefined, }, "targetGroups": { "NPM Scripts": [ diff --git a/packages/nx/src/utils/package-json.ts b/packages/nx/src/utils/package-json.ts index ed3d6f7acbba7..8c3f775bfd4c3 100644 --- a/packages/nx/src/utils/package-json.ts +++ b/packages/nx/src/utils/package-json.ts @@ -163,7 +163,8 @@ export function getMetadataFromPackageJson( packageJson: PackageJson, isInPackageManagerWorkspaces: boolean ): ProjectMetadata { - const { scripts, nx, description, name, exports, main } = packageJson; + const { scripts, nx, description, name, exports, main, version } = + packageJson; const includedScripts = nx?.includedScripts || Object.keys(scripts ?? {}); return { targetGroups: { @@ -172,6 +173,7 @@ export function getMetadataFromPackageJson( description, js: { packageName: name, + packageVersion: version, packageExports: exports, packageMain: main, isInPackageManagerWorkspaces,