diff --git a/README.md b/README.md index 001b4c7d696..8a54e1dea4e 100644 --- a/README.md +++ b/README.md @@ -217,6 +217,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/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/@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/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/rush/version-policies.json b/common/config/rush/version-policies.json index ba1787c4308..a7ed6086251 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.172.0", - "nextBump": "minor", + "nextBump": "patch", "mainProject": "@microsoft/rush" } ] diff --git a/common/config/subspaces/default/pnpm-lock.yaml b/common/config/subspaces/default/pnpm-lock.yaml index 365bd4234e1..689bd6e5086 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..1e7c3602111 100644 --- a/libraries/package-extractor/src/PackageExtractor.ts +++ b/libraries/package-extractor/src/PackageExtractor.ts @@ -334,7 +334,20 @@ 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. + // + // 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; } /** diff --git a/libraries/package-extractor/src/test/PackageExtractor.test.ts b/libraries/package-extractor/src/test/PackageExtractor.test.ts index 3a9f14e5928..3f07a56594f 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,13 @@ 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); + // 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']); + }); }); diff --git a/rush.json b/rush.json index fb69074c677..cdc907387f1 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",