From bce3791ea9596d43f9b91450bfbee96086172fb4 Mon Sep 17 00:00:00 2001 From: LPegasus Date: Tue, 24 Mar 2026 19:42:34 +0800 Subject: [PATCH 1/4] fix(package-extractor): preventing duplicate-copy conflicts across npm-packlist versions --- .../package-extractor-test-05/package.json | 16 ++++++++++++++++ .../package-extractor-test-05/src/index.js | 1 + .../rush-issue-5719_2026-03-24-11-42.json | 10 ++++++++++ common/config/subspaces/default/pnpm-lock.yaml | 6 ++++++ .../package-extractor/src/PackageExtractor.ts | 9 ++++++++- .../src/test/PackageExtractor.test.ts | 11 +++++++++++ rush.json | 6 ++++++ 7 files changed, 58 insertions(+), 1 deletion(-) create mode 100644 build-tests/package-extractor-test-05/package.json create mode 100644 build-tests/package-extractor-test-05/src/index.js create mode 100644 common/changes/@rushstack/package-extractor/rush-issue-5719_2026-03-24-11-42.json diff --git a/build-tests/package-extractor-test-05/package.json b/build-tests/package-extractor-test-05/package.json new file mode 100644 index 00000000000..a168568c337 --- /dev/null +++ b/build-tests/package-extractor-test-05/package.json @@ -0,0 +1,16 @@ +{ + "name": "package-extractor-test-05", + "description": "This project is used by tests in the @rushstack/package-extractor package.", + "version": "1.0.0", + "private": true, + "main": "./dist/index.js", + "files": [ + "dist" + ], + "scripts": { + "_phase:build": "" + }, + "dependencies": { + "@rushstack/node-core-library": "workspace:*" + } +} diff --git a/build-tests/package-extractor-test-05/src/index.js b/build-tests/package-extractor-test-05/src/index.js new file mode 100644 index 00000000000..cb0ff5c3b54 --- /dev/null +++ b/build-tests/package-extractor-test-05/src/index.js @@ -0,0 +1 @@ +export {}; diff --git a/common/changes/@rushstack/package-extractor/rush-issue-5719_2026-03-24-11-42.json b/common/changes/@rushstack/package-extractor/rush-issue-5719_2026-03-24-11-42.json new file mode 100644 index 00000000000..cecc850cc2f --- /dev/null +++ b/common/changes/@rushstack/package-extractor/rush-issue-5719_2026-03-24-11-42.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@rushstack/package-extractor", + "comment": "preventing duplicate-copy conflicts across npm-packlist versions", + "type": "patch" + } + ], + "packageName": "@rushstack/package-extractor" +} \ No newline at end of file diff --git a/common/config/subspaces/default/pnpm-lock.yaml b/common/config/subspaces/default/pnpm-lock.yaml index 9c00b0b7596..1e5fecda4f7 100644 --- a/common/config/subspaces/default/pnpm-lock.yaml +++ b/common/config/subspaces/default/pnpm-lock.yaml @@ -2702,6 +2702,12 @@ importers: specifier: workspace:* version: link:../../libraries/node-core-library + ../../../build-tests/package-extractor-test-05: + dependencies: + '@rushstack/node-core-library': + specifier: workspace:* + version: link:../../libraries/node-core-library + ../../../build-tests/run-scenarios-helpers: dependencies: '@microsoft/api-extractor': diff --git a/libraries/package-extractor/src/PackageExtractor.ts b/libraries/package-extractor/src/PackageExtractor.ts index 1170c315034..d1262e2f100 100644 --- a/libraries/package-extractor/src/PackageExtractor.ts +++ b/libraries/package-extractor/src/PackageExtractor.ts @@ -334,7 +334,14 @@ export class PackageExtractor { } ); const npmPackFiles: string[] = await walkerPromise; - return npmPackFiles; + + // npm-packlist@v5 (uses glob@v8) returns "./dist/index.js" for pattern: "./dist/index.js", + // whereas npm-packlist@v2 (uses glob@v7) returns an empty array. + // This may cause duplicate files in the result list, leading to copying conflicts + // in the AssetHandler.ts includeAssetAsync method. + // To avoid this issue, we will normalize the file paths to be relative to the package root and remove duplicates. + const normalizedFiles: string[] = Array.from(new Set(npmPackFiles.map((file) => path.normalize(file)))); + return normalizedFiles; } /** diff --git a/libraries/package-extractor/src/test/PackageExtractor.test.ts b/libraries/package-extractor/src/test/PackageExtractor.test.ts index 3a9f14e5928..16b15e63b79 100644 --- a/libraries/package-extractor/src/test/PackageExtractor.test.ts +++ b/libraries/package-extractor/src/test/PackageExtractor.test.ts @@ -19,14 +19,17 @@ const project1PackageName: string = 'package-extractor-test-01'; const project2PackageName: string = 'package-extractor-test-02'; const project3PackageName: string = 'package-extractor-test-03'; const project4PackageName: string = 'package-extractor-test-04'; +const project5PackageName: string = 'package-extractor-test-05'; const project1RelativePath: string = path.join('build-tests', project1PackageName); const project2RelativePath: string = path.join('build-tests', project2PackageName); const project3RelativePath: string = path.join('build-tests', project3PackageName); const project4RelativePath: string = path.join('build-tests', project4PackageName); +const project5RelativePath: string = path.join('build-tests', project5PackageName); const project1Path: string = path.join(repoRoot, project1RelativePath); const project2Path: string = path.resolve(repoRoot, project2RelativePath); const project3Path: string = path.resolve(repoRoot, project3RelativePath); const project4Path: string = path.resolve(repoRoot, project4RelativePath); +const project5Path: string = path.resolve(repoRoot, project5RelativePath); function getDefaultProjectConfigurations(): IExtractorProjectConfiguration[] { return [ @@ -619,4 +622,12 @@ describe(PackageExtractor.name, () => { Sort.sortBy(metadata.projects, (x) => x.path); expect(metadata).toMatchSnapshot(); }); + + it('should normalize and remove duplicate file paths', async () => { + await FileSystem.writeFileAsync(path.join(project5Path, 'dist', 'index.js'), '', { + ensureFolderExists: true + }); + const result = await PackageExtractor.getPackageIncludedFilesAsync(project5Path); + expect(result).toEqual(['dist/index.js', 'package.json']); + }); }); diff --git a/rush.json b/rush.json index be44a877901..ac42209d7f9 100644 --- a/rush.json +++ b/rush.json @@ -690,6 +690,12 @@ "reviewCategory": "tests", "shouldPublish": false }, + { + "packageName": "package-extractor-test-05", + "projectFolder": "build-tests/package-extractor-test-05", + "reviewCategory": "tests", + "shouldPublish": false + }, { "packageName": "rush-mcp-example-plugin", "projectFolder": "build-tests/rush-mcp-example-plugin", From 316cd186a676e8a33a69e915d1a97038c3ed093b Mon Sep 17 00:00:00 2001 From: LPegasus Date: Tue, 24 Mar 2026 20:53:36 +0800 Subject: [PATCH 2/4] docs(README.md): update readme --- README.md | 1 + libraries/package-extractor/src/test/PackageExtractor.test.ts | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index e49d4c1c729..2f7ba775a30 100644 --- a/README.md +++ b/README.md @@ -216,6 +216,7 @@ These GitHub repositories provide supplementary resources for Rush Stack: | [/build-tests/package-extractor-test-02](./build-tests/package-extractor-test-02/) | This project is used by tests in the @rushstack/package-extractor package. | | [/build-tests/package-extractor-test-03](./build-tests/package-extractor-test-03/) | This project is used by tests in the @rushstack/package-extractor package. | | [/build-tests/package-extractor-test-04](./build-tests/package-extractor-test-04/) | This project is used by tests in the @rushstack/package-extractor package. | +| [/build-tests/package-extractor-test-05](./build-tests/package-extractor-test-05/) | This project is used by tests in the @rushstack/package-extractor package. | | [/build-tests/run-scenarios-helpers](./build-tests/run-scenarios-helpers/) | Helpers for the *-scenarios test projects. | | [/build-tests/rush-amazon-s3-build-cache-plugin-integration-test](./build-tests/rush-amazon-s3-build-cache-plugin-integration-test/) | Tests connecting to an amazon S3 endpoint | | [/build-tests/rush-lib-declaration-paths-test](./build-tests/rush-lib-declaration-paths-test/) | This project ensures all of the paths in rush-lib/lib/... have imports that resolve correctly. If this project builds, all `lib/**/*.d.ts` files in the `@microsoft/rush-lib` package are valid. | diff --git a/libraries/package-extractor/src/test/PackageExtractor.test.ts b/libraries/package-extractor/src/test/PackageExtractor.test.ts index 16b15e63b79..3f07a56594f 100644 --- a/libraries/package-extractor/src/test/PackageExtractor.test.ts +++ b/libraries/package-extractor/src/test/PackageExtractor.test.ts @@ -628,6 +628,7 @@ describe(PackageExtractor.name, () => { ensureFolderExists: true }); const result = await PackageExtractor.getPackageIncludedFilesAsync(project5Path); - expect(result).toEqual(['dist/index.js', 'package.json']); + // To make the test work on both Windows and *nix, need to normalize the paths to posix style + expect(result.map(path.posix.normalize)).toEqual(['dist/index.js', 'package.json']); }); }); From fa6d3ed12008e3f7914c4642d63e8d137c69d0be Mon Sep 17 00:00:00 2001 From: Pete Gonzalez <4673363+octogonz@users.noreply.github.com> Date: Tue, 24 Mar 2026 16:57:02 -0700 Subject: [PATCH 3/4] Fix different behavior on Windows that was breaking the unit test --- libraries/package-extractor/src/PackageExtractor.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/libraries/package-extractor/src/PackageExtractor.ts b/libraries/package-extractor/src/PackageExtractor.ts index d1262e2f100..1e7c3602111 100644 --- a/libraries/package-extractor/src/PackageExtractor.ts +++ b/libraries/package-extractor/src/PackageExtractor.ts @@ -339,8 +339,14 @@ export class PackageExtractor { // whereas npm-packlist@v2 (uses glob@v7) returns an empty array. // This may cause duplicate files in the result list, leading to copying conflicts // in the AssetHandler.ts includeAssetAsync method. - // To avoid this issue, we will normalize the file paths to be relative to the package root and remove duplicates. - const normalizedFiles: string[] = Array.from(new Set(npmPackFiles.map((file) => path.normalize(file)))); + // + // Temporary fix: normalize the file paths to be relative to the package root, and remove duplicates. + // + // TODO: A better long-term fix is to replace "npm-packlist" with "@pnpm/fs.packlist", notes here: + // https://github.com/microsoft/rushstack/pull/5720/changes#r2984873283 + const normalizedFiles: string[] = Array.from( + new Set(npmPackFiles.map((file) => path.posix.normalize(file))) + ); return normalizedFiles; } From 0ba8c2cfad75f87159a64fd32a6206eb88f4800d Mon Sep 17 00:00:00 2001 From: Pete Gonzalez <4673363+octogonz@users.noreply.github.com> Date: Tue, 24 Mar 2026 17:06:24 -0700 Subject: [PATCH 4/4] Prepare to publish a PATCH version of Rush --- .../rush/rush-issue-5719_2026-03-25-00-05.json | 10 ++++++++++ common/config/rush/version-policies.json | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) create mode 100644 common/changes/@microsoft/rush/rush-issue-5719_2026-03-25-00-05.json diff --git a/common/changes/@microsoft/rush/rush-issue-5719_2026-03-25-00-05.json b/common/changes/@microsoft/rush/rush-issue-5719_2026-03-25-00-05.json new file mode 100644 index 00000000000..a8f8da23dfd --- /dev/null +++ b/common/changes/@microsoft/rush/rush-issue-5719_2026-03-25-00-05.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@microsoft/rush", + "comment": "Fix an issue where \"rush deploy\" could fail with EEXIST due to a \"npm-packlist\" regression (GitHub #5720)", + "type": "none" + } + ], + "packageName": "@microsoft/rush" +} \ No newline at end of file diff --git a/common/config/rush/version-policies.json b/common/config/rush/version-policies.json index 93899ccf6e3..a182a6eb329 100644 --- a/common/config/rush/version-policies.json +++ b/common/config/rush/version-policies.json @@ -103,7 +103,7 @@ "policyName": "rush", "definitionName": "lockStepVersion", "version": "5.171.0", - "nextBump": "minor", + "nextBump": "patch", "mainProject": "@microsoft/rush" } ]