From aa655e1394c3bd1e88152468690dde31108b7b8e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 26 Jan 2026 19:42:27 +0000 Subject: [PATCH 01/15] Initial plan From b9e75f49be930778f191ded55404e04929838ba2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 26 Jan 2026 19:53:12 +0000 Subject: [PATCH 02/15] Add filtering for npm-incompatible properties in .npmrc syncing Co-authored-by: iclanton <5010588+iclanton@users.noreply.github.com> --- .../rush-lib/src/cli/actions/PublishAction.ts | 4 +- libraries/rush-lib/src/scripts/install-run.ts | 8 +- libraries/rush-lib/src/utilities/Utilities.ts | 5 +- .../rush-lib/src/utilities/npmrcUtilities.ts | 129 +++++++++++++----- .../src/utilities/test/npmrcUtilities.test.ts | 96 +++++++++++++ 5 files changed, 201 insertions(+), 41 deletions(-) diff --git a/libraries/rush-lib/src/cli/actions/PublishAction.ts b/libraries/rush-lib/src/cli/actions/PublishAction.ts index cb05204508..157d73dd41 100644 --- a/libraries/rush-lib/src/cli/actions/PublishAction.ts +++ b/libraries/rush-lib/src/cli/actions/PublishAction.ts @@ -593,7 +593,9 @@ export class PublishAction extends BaseRushAction { sourceNpmrcFolder: this.rushConfiguration.commonRushConfigFolder, targetNpmrcFolder: this._targetNpmrcPublishFolder, useNpmrcPublish: true, - supportEnvVarFallbackSyntax + supportEnvVarFallbackSyntax, + // Filter out npm-incompatible properties when using npm to publish + filterNpmIncompatibleProperties: true }); } diff --git a/libraries/rush-lib/src/scripts/install-run.ts b/libraries/rush-lib/src/scripts/install-run.ts index 2aec0b67d1..7584ec01c8 100644 --- a/libraries/rush-lib/src/scripts/install-run.ts +++ b/libraries/rush-lib/src/scripts/install-run.ts @@ -173,7 +173,9 @@ function _resolvePackageVersion( sourceNpmrcFolder, targetNpmrcFolder: rushTempFolder, logger, - supportEnvVarFallbackSyntax: false + supportEnvVarFallbackSyntax: false, + // Filter out npm-incompatible properties when using npm + filterNpmIncompatibleProperties: true }); // This returns something that looks like: @@ -459,7 +461,9 @@ export function installAndRun( sourceNpmrcFolder, targetNpmrcFolder: packageInstallFolder, logger, - supportEnvVarFallbackSyntax: false + supportEnvVarFallbackSyntax: false, + // Filter out npm-incompatible properties when using npm + filterNpmIncompatibleProperties: true }); _createPackageJson(packageInstallFolder, packageName, packageVersion); diff --git a/libraries/rush-lib/src/utilities/Utilities.ts b/libraries/rush-lib/src/utilities/Utilities.ts index c5fe8df92d..5b369286e7 100644 --- a/libraries/rush-lib/src/utilities/Utilities.ts +++ b/libraries/rush-lib/src/utilities/Utilities.ts @@ -531,7 +531,10 @@ export class Utilities { Utilities.syncNpmrc({ sourceNpmrcFolder: commonRushConfigFolder, targetNpmrcFolder: directory, - supportEnvVarFallbackSyntax: false + supportEnvVarFallbackSyntax: false, + // Filter out npm-incompatible properties when using npm to install packages + // to avoid warnings about unknown config properties + filterNpmIncompatibleProperties: true }); } diff --git a/libraries/rush-lib/src/utilities/npmrcUtilities.ts b/libraries/rush-lib/src/utilities/npmrcUtilities.ts index b5a61e45af..102d3deea8 100644 --- a/libraries/rush-lib/src/utilities/npmrcUtilities.ts +++ b/libraries/rush-lib/src/utilities/npmrcUtilities.ts @@ -25,10 +25,20 @@ const _combinedNpmrcMap: Map = new Map(); function _trimNpmrcFile( options: Pick< INpmrcTrimOptions, - 'sourceNpmrcPath' | 'linesToAppend' | 'linesToPrepend' | 'supportEnvVarFallbackSyntax' + | 'sourceNpmrcPath' + | 'linesToAppend' + | 'linesToPrepend' + | 'supportEnvVarFallbackSyntax' + | 'filterNpmIncompatibleProperties' > ): string { - const { sourceNpmrcPath, linesToPrepend, linesToAppend, supportEnvVarFallbackSyntax } = options; + const { + sourceNpmrcPath, + linesToPrepend, + linesToAppend, + supportEnvVarFallbackSyntax, + filterNpmIncompatibleProperties + } = options; const combinedNpmrcFromCache: string | undefined = _combinedNpmrcMap.get(sourceNpmrcPath); if (combinedNpmrcFromCache !== undefined) { return combinedNpmrcFromCache; @@ -49,7 +59,12 @@ function _trimNpmrcFile( npmrcFileLines = npmrcFileLines.map((line) => (line || '').trim()); - const resultLines: string[] = trimNpmrcFileLines(npmrcFileLines, process.env, supportEnvVarFallbackSyntax); + const resultLines: string[] = trimNpmrcFileLines( + npmrcFileLines, + process.env, + supportEnvVarFallbackSyntax, + filterNpmIncompatibleProperties || false + ); const combinedNpmrc: string = resultLines.join('\n'); @@ -59,17 +74,35 @@ function _trimNpmrcFile( return combinedNpmrc; } +/** + * List of npmrc properties that are not supported by npm but may be present in the config. + * These include pnpm-specific properties and deprecated npm properties. + * Registry-scoped properties (starting with "//") are never filtered as they contain auth tokens. + */ +const NPM_INCOMPATIBLE_PROPERTIES: Set = new Set([ + // pnpm-specific hoisting configuration + 'hoist', + 'hoist-pattern', + 'public-hoist-pattern', + 'shamefully-hoist', + // Deprecated or unknown npm properties that cause warnings + 'email', + 'publish-branch' +]); + /** * * @param npmrcFileLines The npmrc file's lines * @param env The environment variables object * @param supportEnvVarFallbackSyntax Whether to support fallback values in the form of `${VAR_NAME:-fallback}` + * @param filterNpmIncompatibleProperties Whether to filter out properties that npm doesn't understand * @returns */ export function trimNpmrcFileLines( npmrcFileLines: string[], env: NodeJS.ProcessEnv, - supportEnvVarFallbackSyntax: boolean + supportEnvVarFallbackSyntax: boolean, + filterNpmIncompatibleProperties: boolean = false ): string[] { const resultLines: string[] = []; @@ -91,42 +124,62 @@ export function trimNpmrcFileLines( // Ignore comment lines if (!commentRegExp.test(line)) { - const environmentVariables: string[] | null = line.match(expansionRegExp); - if (environmentVariables) { - for (const token of environmentVariables) { - /** - * Remove the leading "${" and the trailing "}" from the token - * - * ${nameString} -> nameString - * ${nameString-fallbackString} -> name-fallbackString - * ${nameString:-fallbackString} -> name:-fallbackString - */ - const nameWithFallback: string = token.substring(2, token.length - 1); - - let environmentVariableName: string; - let fallback: string | undefined; - if (supportEnvVarFallbackSyntax) { + // Check if this is a property that npm doesn't understand + if (filterNpmIncompatibleProperties) { + // Extract the property name (everything before the '=' or '[') + const match: RegExpMatchArray | null = line.match(/^([^=\[]+)/); + if (match) { + const propertyName: string = match[1].trim(); + + // Never filter registry-scoped properties (auth tokens, etc.) + // These start with "//" like "//registry.npmjs.org/:_authToken" + const isRegistryScoped: boolean = propertyName.startsWith('//'); + + if (!isRegistryScoped && NPM_INCOMPATIBLE_PROPERTIES.has(propertyName)) { + lineShouldBeTrimmed = true; + } + } + } + + // Check for undefined environment variables + if (!lineShouldBeTrimmed) { + const environmentVariables: string[] | null = line.match(expansionRegExp); + if (environmentVariables) { + for (const token of environmentVariables) { /** - * Get the environment variable name and fallback value. + * Remove the leading "${" and the trailing "}" from the token * - * name fallback - * nameString -> nameString undefined - * nameString-fallbackString -> nameString fallbackString - * nameString:-fallbackString -> nameString fallbackString + * ${nameString} -> nameString + * ${nameString-fallbackString} -> name-fallbackString + * ${nameString:-fallbackString} -> name:-fallbackString */ - const matched: string[] | null = nameWithFallback.match(/^([^:-]+)(?:\:?-(.+))?$/); - // matched: [originStr, variableName, fallback] - environmentVariableName = matched?.[1] ?? nameWithFallback; - fallback = matched?.[2]; - } else { - environmentVariableName = nameWithFallback; - } - - // Is the environment variable and fallback value defined. - if (!env[environmentVariableName] && !fallback) { - // No, so trim this line - lineShouldBeTrimmed = true; - break; + const nameWithFallback: string = token.substring(2, token.length - 1); + + let environmentVariableName: string; + let fallback: string | undefined; + if (supportEnvVarFallbackSyntax) { + /** + * Get the environment variable name and fallback value. + * + * name fallback + * nameString -> nameString undefined + * nameString-fallbackString -> nameString fallbackString + * nameString:-fallbackString -> nameString fallbackString + */ + const matched: string[] | null = nameWithFallback.match(/^([^:-]+)(?:\:?-(.+))?$/); + // matched: [originStr, variableName, fallback] + environmentVariableName = matched?.[1] ?? nameWithFallback; + fallback = matched?.[2]; + } else { + environmentVariableName = nameWithFallback; + } + + // Is the environment variable and fallback value defined. + if (!env[environmentVariableName] && !fallback) { + // No, so trim this line + lineShouldBeTrimmed = true; + break; + } } } } @@ -165,6 +218,7 @@ interface INpmrcTrimOptions { linesToPrepend?: string[]; linesToAppend?: string[]; supportEnvVarFallbackSyntax: boolean; + filterNpmIncompatibleProperties?: boolean; } function _copyAndTrimNpmrcFile(options: INpmrcTrimOptions): string { @@ -197,6 +251,7 @@ export interface ISyncNpmrcOptions { linesToPrepend?: string[]; linesToAppend?: string[]; createIfMissing?: boolean; + filterNpmIncompatibleProperties?: boolean; } export function syncNpmrc(options: ISyncNpmrcOptions): string | undefined { diff --git a/libraries/rush-lib/src/utilities/test/npmrcUtilities.test.ts b/libraries/rush-lib/src/utilities/test/npmrcUtilities.test.ts index b889435a0f..33ccbf1944 100644 --- a/libraries/rush-lib/src/utilities/test/npmrcUtilities.test.ts +++ b/libraries/rush-lib/src/utilities/test/npmrcUtilities.test.ts @@ -92,5 +92,101 @@ describe('npmrcUtilities', () => { describe(trimNpmrcFileLines.name, () => { describe('With support for env var fallback syntax', () => runTests(true)); describe('Without support for env var fallback syntax', () => runTests(false)); + + describe('With npm-incompatible properties filtering', () => { + const supportEnvVarFallbackSyntax = false; + const filterNpmIncompatibleProperties = true; + + it('filters out pnpm-specific hoisting properties', () => { + expect( + trimNpmrcFileLines( + [ + 'registry=https://registry.npmjs.org/', + 'hoist=false', + 'hoist-pattern[]=*eslint*', + 'public-hoist-pattern[]=', + 'shamefully-hoist=true', + 'always-auth=false' + ], + {}, + supportEnvVarFallbackSyntax, + filterNpmIncompatibleProperties + ) + ).toMatchSnapshot(); + }); + + it('filters out deprecated npm properties', () => { + expect( + trimNpmrcFileLines( + ['registry=https://registry.npmjs.org/', 'email=test@example.com', 'publish-branch=main'], + {}, + supportEnvVarFallbackSyntax, + filterNpmIncompatibleProperties + ) + ).toMatchSnapshot(); + }); + + it('preserves registry-scoped auth tokens', () => { + expect( + trimNpmrcFileLines( + [ + 'registry=https://registry.npmjs.org/', + '//registry.npmjs.org/:_authToken=${NPM_TOKEN}', + '//my-registry.com/:_authToken=${MY_TOKEN}', + 'email=test@example.com' + ], + { NPM_TOKEN: 'abc123', MY_TOKEN: 'xyz789' }, + supportEnvVarFallbackSyntax, + filterNpmIncompatibleProperties + ) + ).toMatchSnapshot(); + }); + + it('preserves registry-scoped configurations', () => { + expect( + trimNpmrcFileLines( + [ + 'registry=https://registry.npmjs.org/', + '//registry.npmjs.org/:always-auth=true', + '//my-registry.com/:_authToken=${MY_TOKEN}', + 'hoist=false' + ], + { MY_TOKEN: 'xyz789' }, + supportEnvVarFallbackSyntax, + filterNpmIncompatibleProperties + ) + ).toMatchSnapshot(); + }); + + it('does not filter when filterNpmIncompatibleProperties is false', () => { + expect( + trimNpmrcFileLines( + ['registry=https://registry.npmjs.org/', 'email=test@example.com', 'hoist=false'], + {}, + supportEnvVarFallbackSyntax, + false + ) + ).toMatchSnapshot(); + }); + + it('preserves standard npm properties', () => { + expect( + trimNpmrcFileLines( + [ + 'registry=https://registry.npmjs.org/', + 'always-auth=false', + 'strict-ssl=true', + 'save-exact=true', + 'package-lock=true', + 'hoist=false', + 'email=test@example.com' + ], + {}, + supportEnvVarFallbackSyntax, + filterNpmIncompatibleProperties + ) + ).toMatchSnapshot(); + }); + }); }); }); From f9e7b87ce2f70e22bb69a7051c9fd7d478078a2e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 26 Jan 2026 20:01:04 +0000 Subject: [PATCH 03/15] Add test snapshots for npm-incompatible properties filtering Co-authored-by: iclanton <5010588+iclanton@users.noreply.github.com> --- .../__snapshots__/npmrcUtilities.test.ts.snap | 57 +++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/libraries/rush-lib/src/utilities/test/__snapshots__/npmrcUtilities.test.ts.snap b/libraries/rush-lib/src/utilities/test/__snapshots__/npmrcUtilities.test.ts.snap index 691864176a..e73c24d31d 100644 --- a/libraries/rush-lib/src/utilities/test/__snapshots__/npmrcUtilities.test.ts.snap +++ b/libraries/rush-lib/src/utilities/test/__snapshots__/npmrcUtilities.test.ts.snap @@ -1,5 +1,62 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`npmrcUtilities trimNpmrcFileLines With npm-incompatible properties filtering does not filter when filterNpmIncompatibleProperties is false 1`] = ` +Array [ + "registry=https://registry.npmjs.org/", + "email=test@example.com", + "hoist=false", +] +`; + +exports[`npmrcUtilities trimNpmrcFileLines With npm-incompatible properties filtering filters out deprecated npm properties 1`] = ` +Array [ + "registry=https://registry.npmjs.org/", + "; MISSING ENVIRONMENT VARIABLE: email=test@example.com", + "; MISSING ENVIRONMENT VARIABLE: publish-branch=main", +] +`; + +exports[`npmrcUtilities trimNpmrcFileLines With npm-incompatible properties filtering filters out pnpm-specific hoisting properties 1`] = ` +Array [ + "registry=https://registry.npmjs.org/", + "; MISSING ENVIRONMENT VARIABLE: hoist=false", + "; MISSING ENVIRONMENT VARIABLE: hoist-pattern[]=*eslint*", + "; MISSING ENVIRONMENT VARIABLE: public-hoist-pattern[]=", + "; MISSING ENVIRONMENT VARIABLE: shamefully-hoist=true", + "always-auth=false", +] +`; + +exports[`npmrcUtilities trimNpmrcFileLines With npm-incompatible properties filtering preserves registry-scoped auth tokens 1`] = ` +Array [ + "registry=https://registry.npmjs.org/", + "//registry.npmjs.org/:_authToken=\${NPM_TOKEN}", + "//my-registry.com/:_authToken=\${MY_TOKEN}", + "; MISSING ENVIRONMENT VARIABLE: email=test@example.com", +] +`; + +exports[`npmrcUtilities trimNpmrcFileLines With npm-incompatible properties filtering preserves registry-scoped configurations 1`] = ` +Array [ + "registry=https://registry.npmjs.org/", + "//registry.npmjs.org/:always-auth=true", + "//my-registry.com/:_authToken=\${MY_TOKEN}", + "; MISSING ENVIRONMENT VARIABLE: hoist=false", +] +`; + +exports[`npmrcUtilities trimNpmrcFileLines With npm-incompatible properties filtering preserves standard npm properties 1`] = ` +Array [ + "registry=https://registry.npmjs.org/", + "always-auth=false", + "strict-ssl=true", + "save-exact=true", + "package-lock=true", + "; MISSING ENVIRONMENT VARIABLE: hoist=false", + "; MISSING ENVIRONMENT VARIABLE: email=test@example.com", +] +`; + exports[`npmrcUtilities trimNpmrcFileLines With support for env var fallback syntax supports a a variable without a fallback 1`] = ` Array [ "; MISSING ENVIRONMENT VARIABLE: var1=\${foo}", From 81f63c20e287fa1c3c37f3553c468e8bb73b1e5e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 26 Jan 2026 20:03:35 +0000 Subject: [PATCH 04/15] Address code review feedback: improve regex pattern and add JSDoc return description Co-authored-by: iclanton <5010588+iclanton@users.noreply.github.com> --- libraries/rush-lib/src/utilities/npmrcUtilities.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/libraries/rush-lib/src/utilities/npmrcUtilities.ts b/libraries/rush-lib/src/utilities/npmrcUtilities.ts index 102d3deea8..a3dc9ad61b 100644 --- a/libraries/rush-lib/src/utilities/npmrcUtilities.ts +++ b/libraries/rush-lib/src/utilities/npmrcUtilities.ts @@ -96,7 +96,7 @@ const NPM_INCOMPATIBLE_PROPERTIES: Set = new Set([ * @param env The environment variables object * @param supportEnvVarFallbackSyntax Whether to support fallback values in the form of `${VAR_NAME:-fallback}` * @param filterNpmIncompatibleProperties Whether to filter out properties that npm doesn't understand - * @returns + * @returns An array of processed npmrc file lines with undefined environment variables and npm-incompatible properties commented out */ export function trimNpmrcFileLines( npmrcFileLines: string[], @@ -127,14 +127,14 @@ export function trimNpmrcFileLines( // Check if this is a property that npm doesn't understand if (filterNpmIncompatibleProperties) { // Extract the property name (everything before the '=' or '[') - const match: RegExpMatchArray | null = line.match(/^([^=\[]+)/); + const match: RegExpMatchArray | null = line.match(/^([^=\[\s]+)/); if (match) { - const propertyName: string = match[1].trim(); - + const propertyName: string = match[1]; + // Never filter registry-scoped properties (auth tokens, etc.) // These start with "//" like "//registry.npmjs.org/:_authToken" const isRegistryScoped: boolean = propertyName.startsWith('//'); - + if (!isRegistryScoped && NPM_INCOMPATIBLE_PROPERTIES.has(propertyName)) { lineShouldBeTrimmed = true; } From 6df696b0eb9cc3abed9e2a2db2dc1e17de1cba0c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 26 Jan 2026 20:05:59 +0000 Subject: [PATCH 05/15] Address code review nitpicks: extract regex constant and simplify boolean expression Co-authored-by: iclanton <5010588+iclanton@users.noreply.github.com> --- libraries/rush-lib/src/utilities/npmrcUtilities.ts | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/libraries/rush-lib/src/utilities/npmrcUtilities.ts b/libraries/rush-lib/src/utilities/npmrcUtilities.ts index a3dc9ad61b..1d1551e535 100644 --- a/libraries/rush-lib/src/utilities/npmrcUtilities.ts +++ b/libraries/rush-lib/src/utilities/npmrcUtilities.ts @@ -63,7 +63,7 @@ function _trimNpmrcFile( npmrcFileLines, process.env, supportEnvVarFallbackSyntax, - filterNpmIncompatibleProperties || false + filterNpmIncompatibleProperties ); const combinedNpmrc: string = resultLines.join('\n'); @@ -90,6 +90,15 @@ const NPM_INCOMPATIBLE_PROPERTIES: Set = new Set([ 'publish-branch' ]); +/** + * Regular expression to extract property names from .npmrc lines. + * Matches everything before '=', '[', or whitespace to capture the property name. + * Examples: + * "registry=https://..." -> matches "registry" + * "hoist-pattern[]=..." -> matches "hoist-pattern" + */ +const PROPERTY_NAME_REGEX: RegExp = /^([^=\[\s]+)/; + /** * * @param npmrcFileLines The npmrc file's lines @@ -127,7 +136,7 @@ export function trimNpmrcFileLines( // Check if this is a property that npm doesn't understand if (filterNpmIncompatibleProperties) { // Extract the property name (everything before the '=' or '[') - const match: RegExpMatchArray | null = line.match(/^([^=\[\s]+)/); + const match: RegExpMatchArray | null = line.match(PROPERTY_NAME_REGEX); if (match) { const propertyName: string = match[1]; From 254a598dc721baa36cf39e28ba2bcda989dc1550 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 26 Jan 2026 20:08:46 +0000 Subject: [PATCH 06/15] Improve error messages to distinguish npm-incompatible properties from missing env vars Co-authored-by: iclanton <5010588+iclanton@users.noreply.github.com> --- .../rush-lib/src/utilities/npmrcUtilities.ts | 16 ++++++++++++--- .../__snapshots__/npmrcUtilities.test.ts.snap | 20 +++++++++---------- 2 files changed, 23 insertions(+), 13 deletions(-) diff --git a/libraries/rush-lib/src/utilities/npmrcUtilities.ts b/libraries/rush-lib/src/utilities/npmrcUtilities.ts index 1d1551e535..49088a2997 100644 --- a/libraries/rush-lib/src/utilities/npmrcUtilities.ts +++ b/libraries/rush-lib/src/utilities/npmrcUtilities.ts @@ -124,6 +124,7 @@ export function trimNpmrcFileLines( // Trim out lines that reference environment variables that aren't defined for (let line of npmrcFileLines) { let lineShouldBeTrimmed: boolean = false; + let trimReason: string = ''; //remove spaces before or after key and value line = line @@ -146,6 +147,7 @@ export function trimNpmrcFileLines( if (!isRegistryScoped && NPM_INCOMPATIBLE_PROPERTIES.has(propertyName)) { lineShouldBeTrimmed = true; + trimReason = 'NPM_INCOMPATIBLE_PROPERTY'; } } } @@ -187,6 +189,7 @@ export function trimNpmrcFileLines( if (!env[environmentVariableName] && !fallback) { // No, so trim this line lineShouldBeTrimmed = true; + trimReason = 'MISSING_ENVIRONMENT_VARIABLE'; break; } } @@ -195,9 +198,16 @@ export function trimNpmrcFileLines( } if (lineShouldBeTrimmed) { - // Example output: - // "; MISSING ENVIRONMENT VARIABLE: //my-registry.com/npm/:_authToken=${MY_AUTH_TOKEN}" - resultLines.push('; MISSING ENVIRONMENT VARIABLE: ' + line); + // Comment out the line with appropriate reason + if (trimReason === 'NPM_INCOMPATIBLE_PROPERTY') { + // Example output: + // "; UNSUPPORTED BY NPM: email=test@example.com" + resultLines.push('; UNSUPPORTED BY NPM: ' + line); + } else { + // Example output: + // "; MISSING ENVIRONMENT VARIABLE: //my-registry.com/npm/:_authToken=${MY_AUTH_TOKEN}" + resultLines.push('; MISSING ENVIRONMENT VARIABLE: ' + line); + } } else { resultLines.push(line); } diff --git a/libraries/rush-lib/src/utilities/test/__snapshots__/npmrcUtilities.test.ts.snap b/libraries/rush-lib/src/utilities/test/__snapshots__/npmrcUtilities.test.ts.snap index e73c24d31d..5e4739c16f 100644 --- a/libraries/rush-lib/src/utilities/test/__snapshots__/npmrcUtilities.test.ts.snap +++ b/libraries/rush-lib/src/utilities/test/__snapshots__/npmrcUtilities.test.ts.snap @@ -11,18 +11,18 @@ Array [ exports[`npmrcUtilities trimNpmrcFileLines With npm-incompatible properties filtering filters out deprecated npm properties 1`] = ` Array [ "registry=https://registry.npmjs.org/", - "; MISSING ENVIRONMENT VARIABLE: email=test@example.com", - "; MISSING ENVIRONMENT VARIABLE: publish-branch=main", + "; UNSUPPORTED BY NPM: email=test@example.com", + "; UNSUPPORTED BY NPM: publish-branch=main", ] `; exports[`npmrcUtilities trimNpmrcFileLines With npm-incompatible properties filtering filters out pnpm-specific hoisting properties 1`] = ` Array [ "registry=https://registry.npmjs.org/", - "; MISSING ENVIRONMENT VARIABLE: hoist=false", - "; MISSING ENVIRONMENT VARIABLE: hoist-pattern[]=*eslint*", - "; MISSING ENVIRONMENT VARIABLE: public-hoist-pattern[]=", - "; MISSING ENVIRONMENT VARIABLE: shamefully-hoist=true", + "; UNSUPPORTED BY NPM: hoist=false", + "; UNSUPPORTED BY NPM: hoist-pattern[]=*eslint*", + "; UNSUPPORTED BY NPM: public-hoist-pattern[]=", + "; UNSUPPORTED BY NPM: shamefully-hoist=true", "always-auth=false", ] `; @@ -32,7 +32,7 @@ Array [ "registry=https://registry.npmjs.org/", "//registry.npmjs.org/:_authToken=\${NPM_TOKEN}", "//my-registry.com/:_authToken=\${MY_TOKEN}", - "; MISSING ENVIRONMENT VARIABLE: email=test@example.com", + "; UNSUPPORTED BY NPM: email=test@example.com", ] `; @@ -41,7 +41,7 @@ Array [ "registry=https://registry.npmjs.org/", "//registry.npmjs.org/:always-auth=true", "//my-registry.com/:_authToken=\${MY_TOKEN}", - "; MISSING ENVIRONMENT VARIABLE: hoist=false", + "; UNSUPPORTED BY NPM: hoist=false", ] `; @@ -52,8 +52,8 @@ Array [ "strict-ssl=true", "save-exact=true", "package-lock=true", - "; MISSING ENVIRONMENT VARIABLE: hoist=false", - "; MISSING ENVIRONMENT VARIABLE: email=test@example.com", + "; UNSUPPORTED BY NPM: hoist=false", + "; UNSUPPORTED BY NPM: email=test@example.com", ] `; From 1d0acaa8193b090e4a5f13611ed99f0bb467cd0b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 26 Jan 2026 20:15:27 +0000 Subject: [PATCH 07/15] Fix TypeScript error in isVariableSetInNpmrcFile and update test typo Co-authored-by: iclanton <5010588+iclanton@users.noreply.github.com> --- .../rush-lib/src/utilities/npmrcUtilities.ts | 6 ++- .../__snapshots__/npmrcUtilities.test.ts.snap | 48 +++++++++---------- .../src/utilities/test/npmrcUtilities.test.ts | 2 +- 3 files changed, 30 insertions(+), 26 deletions(-) diff --git a/libraries/rush-lib/src/utilities/npmrcUtilities.ts b/libraries/rush-lib/src/utilities/npmrcUtilities.ts index 49088a2997..4501a98853 100644 --- a/libraries/rush-lib/src/utilities/npmrcUtilities.ts +++ b/libraries/rush-lib/src/utilities/npmrcUtilities.ts @@ -326,7 +326,11 @@ export function isVariableSetInNpmrcFile( return false; } - const trimmedNpmrcFile: string = _trimNpmrcFile({ sourceNpmrcPath, supportEnvVarFallbackSyntax }); + const trimmedNpmrcFile: string = _trimNpmrcFile({ + sourceNpmrcPath, + supportEnvVarFallbackSyntax, + filterNpmIncompatibleProperties: false + }); const variableKeyRegExp: RegExp = new RegExp(`^${variableKey}=`, 'm'); return trimmedNpmrcFile.match(variableKeyRegExp) !== null; diff --git a/libraries/rush-lib/src/utilities/test/__snapshots__/npmrcUtilities.test.ts.snap b/libraries/rush-lib/src/utilities/test/__snapshots__/npmrcUtilities.test.ts.snap index 5e4739c16f..4df9d2ea68 100644 --- a/libraries/rush-lib/src/utilities/test/__snapshots__/npmrcUtilities.test.ts.snap +++ b/libraries/rush-lib/src/utilities/test/__snapshots__/npmrcUtilities.test.ts.snap @@ -57,18 +57,6 @@ Array [ ] `; -exports[`npmrcUtilities trimNpmrcFileLines With support for env var fallback syntax supports a a variable without a fallback 1`] = ` -Array [ - "; MISSING ENVIRONMENT VARIABLE: var1=\${foo}", -] -`; - -exports[`npmrcUtilities trimNpmrcFileLines With support for env var fallback syntax supports a a variable without a fallback 2`] = ` -Array [ - "var1=\${foo}", -] -`; - exports[`npmrcUtilities trimNpmrcFileLines With support for env var fallback syntax supports a variable with a fallback 1`] = ` Array [ "var1=\${foo-fallback_value}", @@ -117,6 +105,18 @@ Array [ ] `; +exports[`npmrcUtilities trimNpmrcFileLines With support for env var fallback syntax supports a variable without a fallback 1`] = ` +Array [ + "; MISSING ENVIRONMENT VARIABLE: var1=\${foo}", +] +`; + +exports[`npmrcUtilities trimNpmrcFileLines With support for env var fallback syntax supports a variable without a fallback 2`] = ` +Array [ + "var1=\${foo}", +] +`; + exports[`npmrcUtilities trimNpmrcFileLines With support for env var fallback syntax supports malformed lines 1`] = ` Array [ "; MISSING ENVIRONMENT VARIABLE: var1=\${foo_fallback_value}", @@ -169,18 +169,6 @@ Array [ ] `; -exports[`npmrcUtilities trimNpmrcFileLines Without support for env var fallback syntax supports a a variable without a fallback 1`] = ` -Array [ - "; MISSING ENVIRONMENT VARIABLE: var1=\${foo}", -] -`; - -exports[`npmrcUtilities trimNpmrcFileLines Without support for env var fallback syntax supports a a variable without a fallback 2`] = ` -Array [ - "var1=\${foo}", -] -`; - exports[`npmrcUtilities trimNpmrcFileLines Without support for env var fallback syntax supports a variable with a fallback 1`] = ` Array [ "; MISSING ENVIRONMENT VARIABLE: var1=\${foo-fallback_value}", @@ -229,6 +217,18 @@ Array [ ] `; +exports[`npmrcUtilities trimNpmrcFileLines Without support for env var fallback syntax supports a variable without a fallback 1`] = ` +Array [ + "; MISSING ENVIRONMENT VARIABLE: var1=\${foo}", +] +`; + +exports[`npmrcUtilities trimNpmrcFileLines Without support for env var fallback syntax supports a variable without a fallback 2`] = ` +Array [ + "var1=\${foo}", +] +`; + exports[`npmrcUtilities trimNpmrcFileLines Without support for env var fallback syntax supports malformed lines 1`] = ` Array [ "; MISSING ENVIRONMENT VARIABLE: var1=\${foo_fallback_value}", diff --git a/libraries/rush-lib/src/utilities/test/npmrcUtilities.test.ts b/libraries/rush-lib/src/utilities/test/npmrcUtilities.test.ts index 33ccbf1944..9db4f00102 100644 --- a/libraries/rush-lib/src/utilities/test/npmrcUtilities.test.ts +++ b/libraries/rush-lib/src/utilities/test/npmrcUtilities.test.ts @@ -9,7 +9,7 @@ describe('npmrcUtilities', () => { expect(trimNpmrcFileLines([], {}, supportEnvVarFallbackSyntax)).toEqual([]); }); - it('supports a a variable without a fallback', () => { + it('supports a variable without a fallback', () => { expect(trimNpmrcFileLines(['var1=${foo}'], {}, supportEnvVarFallbackSyntax)).toMatchSnapshot(); expect( trimNpmrcFileLines(['var1=${foo}'], { foo: 'test' }, supportEnvVarFallbackSyntax) From 86ce5b5a181895674d0334772e64940c7ddd0519 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 26 Jan 2026 20:17:32 +0000 Subject: [PATCH 08/15] Add documentation comment explaining why regex 'g' flag is omitted Co-authored-by: iclanton <5010588+iclanton@users.noreply.github.com> --- libraries/rush-lib/src/utilities/npmrcUtilities.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/libraries/rush-lib/src/utilities/npmrcUtilities.ts b/libraries/rush-lib/src/utilities/npmrcUtilities.ts index 4501a98853..aaa4088d3b 100644 --- a/libraries/rush-lib/src/utilities/npmrcUtilities.ts +++ b/libraries/rush-lib/src/utilities/npmrcUtilities.ts @@ -93,6 +93,7 @@ const NPM_INCOMPATIBLE_PROPERTIES: Set = new Set([ /** * Regular expression to extract property names from .npmrc lines. * Matches everything before '=', '[', or whitespace to capture the property name. + * Note: The 'g' flag is intentionally omitted since we only need the first match. * Examples: * "registry=https://..." -> matches "registry" * "hoist-pattern[]=..." -> matches "hoist-pattern" From 13cf88d99ab5235a14ca257337375483ff3a1225 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 26 Jan 2026 21:31:19 +0000 Subject: [PATCH 09/15] Only filter npm-incompatible properties when not using pnpm for publishing Co-authored-by: iclanton <5010588+iclanton@users.noreply.github.com> --- libraries/rush-lib/src/cli/actions/PublishAction.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/libraries/rush-lib/src/cli/actions/PublishAction.ts b/libraries/rush-lib/src/cli/actions/PublishAction.ts index 157d73dd41..0e250e1963 100644 --- a/libraries/rush-lib/src/cli/actions/PublishAction.ts +++ b/libraries/rush-lib/src/cli/actions/PublishAction.ts @@ -594,8 +594,9 @@ export class PublishAction extends BaseRushAction { targetNpmrcFolder: this._targetNpmrcPublishFolder, useNpmrcPublish: true, supportEnvVarFallbackSyntax, - // Filter out npm-incompatible properties when using npm to publish - filterNpmIncompatibleProperties: true + // Filter out npm-incompatible properties when using npm or yarn to publish. + // Don't filter for pnpm since it understands pnpm-specific properties. + filterNpmIncompatibleProperties: !this.rushConfiguration.isPnpm }); } From 2e01c52118db430dd9b2b79cec5b21df762d220e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 26 Jan 2026 21:47:53 +0000 Subject: [PATCH 10/15] Only filter npmrc properties when repo uses pnpm/yarn, not when repo uses npm Co-authored-by: iclanton <5010588+iclanton@users.noreply.github.com> --- .../logic/installManager/InstallHelpers.ts | 5 +++- libraries/rush-lib/src/scripts/install-run.ts | 28 ++++++++++++++++--- libraries/rush-lib/src/utilities/Utilities.ts | 15 +++++++--- 3 files changed, 39 insertions(+), 9 deletions(-) diff --git a/libraries/rush-lib/src/logic/installManager/InstallHelpers.ts b/libraries/rush-lib/src/logic/installManager/InstallHelpers.ts index 35a1998c6a..515f9e5bec 100644 --- a/libraries/rush-lib/src/logic/installManager/InstallHelpers.ts +++ b/libraries/rush-lib/src/logic/installManager/InstallHelpers.ts @@ -257,7 +257,10 @@ export class InstallHelpers { // In particular, we'll assume that two different NPM registries cannot have two // different implementations of the same version of the same package. // This was needed for: https://github.com/microsoft/rushstack/issues/691 - commonRushConfigFolder: rushConfiguration.commonRushConfigFolder + commonRushConfigFolder: rushConfiguration.commonRushConfigFolder, + // Only filter npm-incompatible properties when the repo uses pnpm or yarn. + // If the repo uses npm, the .npmrc is already configured for npm, so don't filter. + filterNpmIncompatibleProperties: rushConfiguration.packageManager !== 'npm' }); logIfConsoleOutputIsNotRestricted( diff --git a/libraries/rush-lib/src/scripts/install-run.ts b/libraries/rush-lib/src/scripts/install-run.ts index 7584ec01c8..adaa5fcde3 100644 --- a/libraries/rush-lib/src/scripts/install-run.ts +++ b/libraries/rush-lib/src/scripts/install-run.ts @@ -124,6 +124,23 @@ function _getRushTempFolder(rushCommonFolder: string): string { } } +/** + * Check if the Rush repository is configured to use npm as the package manager. + * Returns true if the repo uses npm, false for pnpm or yarn. + */ +function _isRepoUsingNpm(rushJsonFolder: string): boolean { + try { + const rushJsonPath: string = path.join(rushJsonFolder, RUSH_JSON_FILENAME); + const rushJsonContent: string = fs.readFileSync(rushJsonPath, 'utf-8'); + const rushJson: { packageManager?: string } = JSON.parse(rushJsonContent); + // If packageManager is not specified or is 'npm', return true + return !rushJson.packageManager || rushJson.packageManager === 'npm'; + } catch (e) { + // If we can't read rush.json, assume npm (conservative default) + return true; + } +} + export interface IPackageSpecifier { name: string; version: string | undefined; @@ -168,14 +185,16 @@ function _resolvePackageVersion( try { const rushTempFolder: string = _getRushTempFolder(rushCommonFolder); const sourceNpmrcFolder: string = path.join(rushCommonFolder, 'config', 'rush'); + const rushJsonFolder: string = findRushJsonFolder(); syncNpmrc({ sourceNpmrcFolder, targetNpmrcFolder: rushTempFolder, logger, supportEnvVarFallbackSyntax: false, - // Filter out npm-incompatible properties when using npm - filterNpmIncompatibleProperties: true + // Only filter npm-incompatible properties when the repo uses pnpm or yarn. + // If the repo uses npm, the .npmrc is already configured for npm, so don't filter. + filterNpmIncompatibleProperties: !_isRepoUsingNpm(rushJsonFolder) }); // This returns something that looks like: @@ -462,8 +481,9 @@ export function installAndRun( targetNpmrcFolder: packageInstallFolder, logger, supportEnvVarFallbackSyntax: false, - // Filter out npm-incompatible properties when using npm - filterNpmIncompatibleProperties: true + // Only filter npm-incompatible properties when the repo uses pnpm or yarn. + // If the repo uses npm, the .npmrc is already configured for npm, so don't filter. + filterNpmIncompatibleProperties: !_isRepoUsingNpm(rushJsonFolder) }); _createPackageJson(packageInstallFolder, packageName, packageVersion); diff --git a/libraries/rush-lib/src/utilities/Utilities.ts b/libraries/rush-lib/src/utilities/Utilities.ts index 5b369286e7..a3ab86e1a2 100644 --- a/libraries/rush-lib/src/utilities/Utilities.ts +++ b/libraries/rush-lib/src/utilities/Utilities.ts @@ -65,6 +65,12 @@ export interface IInstallPackageInDirectoryOptions { maxInstallAttempts: number; commonRushConfigFolder: string | undefined; suppressOutput?: boolean; + /** + * Whether to filter npm-incompatible properties from .npmrc. + * This should be true when the .npmrc is configured for a different package manager (pnpm/yarn) + * but npm is being used to install packages. + */ + filterNpmIncompatibleProperties?: boolean; } export interface ILifecycleCommandOptions { @@ -505,7 +511,8 @@ export class Utilities { commonRushConfigFolder, maxInstallAttempts, suppressOutput, - directory + directory, + filterNpmIncompatibleProperties }: IInstallPackageInDirectoryOptions): Promise { directory = path.resolve(directory); const directoryExists: boolean = await FileSystem.existsAsync(directory); @@ -532,9 +539,9 @@ export class Utilities { sourceNpmrcFolder: commonRushConfigFolder, targetNpmrcFolder: directory, supportEnvVarFallbackSyntax: false, - // Filter out npm-incompatible properties when using npm to install packages - // to avoid warnings about unknown config properties - filterNpmIncompatibleProperties: true + // Filter out npm-incompatible properties only when the .npmrc is configured for + // a different package manager (pnpm/yarn) but npm is being used to install. + filterNpmIncompatibleProperties: filterNpmIncompatibleProperties || false }); } From 44381bb138413b8b119998382b473a8b6284efa4 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 26 Jan 2026 23:12:40 +0000 Subject: [PATCH 11/15] Address code review feedback: simplify filtering logic and add changelog Co-authored-by: iclanton <5010588+iclanton@users.noreply.github.com> --- ...ter-npmrc-properties_2026-01-26-23-12.json | 11 +++++++ .../rush-lib/src/cli/actions/PublishAction.ts | 2 +- libraries/rush-lib/src/scripts/install-run.ts | 30 ++++--------------- libraries/rush-lib/src/utilities/Utilities.ts | 4 +-- 4 files changed, 20 insertions(+), 27 deletions(-) create mode 100644 common/changes/@microsoft/rush-lib/filter-npmrc-properties_2026-01-26-23-12.json diff --git a/common/changes/@microsoft/rush-lib/filter-npmrc-properties_2026-01-26-23-12.json b/common/changes/@microsoft/rush-lib/filter-npmrc-properties_2026-01-26-23-12.json new file mode 100644 index 0000000000..c4c68f25c6 --- /dev/null +++ b/common/changes/@microsoft/rush-lib/filter-npmrc-properties_2026-01-26-23-12.json @@ -0,0 +1,11 @@ +{ + "changes": [ + { + "comment": "Filter npm-incompatible properties from .npmrc when npm is used with a configuration intended for pnpm or yarn, to eliminate spurious warnings during package manager installation.", + "type": "patch", + "packageName": "@microsoft/rush-lib" + } + ], + "packageName": "@microsoft/rush-lib", + "email": "copilot@github.com" +} diff --git a/libraries/rush-lib/src/cli/actions/PublishAction.ts b/libraries/rush-lib/src/cli/actions/PublishAction.ts index 0e250e1963..6e36e2f180 100644 --- a/libraries/rush-lib/src/cli/actions/PublishAction.ts +++ b/libraries/rush-lib/src/cli/actions/PublishAction.ts @@ -596,7 +596,7 @@ export class PublishAction extends BaseRushAction { supportEnvVarFallbackSyntax, // Filter out npm-incompatible properties when using npm or yarn to publish. // Don't filter for pnpm since it understands pnpm-specific properties. - filterNpmIncompatibleProperties: !this.rushConfiguration.isPnpm + filterNpmIncompatibleProperties: this.rushConfiguration.packageManager !== 'pnpm' }); } diff --git a/libraries/rush-lib/src/scripts/install-run.ts b/libraries/rush-lib/src/scripts/install-run.ts index adaa5fcde3..7f56856648 100644 --- a/libraries/rush-lib/src/scripts/install-run.ts +++ b/libraries/rush-lib/src/scripts/install-run.ts @@ -124,23 +124,6 @@ function _getRushTempFolder(rushCommonFolder: string): string { } } -/** - * Check if the Rush repository is configured to use npm as the package manager. - * Returns true if the repo uses npm, false for pnpm or yarn. - */ -function _isRepoUsingNpm(rushJsonFolder: string): boolean { - try { - const rushJsonPath: string = path.join(rushJsonFolder, RUSH_JSON_FILENAME); - const rushJsonContent: string = fs.readFileSync(rushJsonPath, 'utf-8'); - const rushJson: { packageManager?: string } = JSON.parse(rushJsonContent); - // If packageManager is not specified or is 'npm', return true - return !rushJson.packageManager || rushJson.packageManager === 'npm'; - } catch (e) { - // If we can't read rush.json, assume npm (conservative default) - return true; - } -} - export interface IPackageSpecifier { name: string; version: string | undefined; @@ -185,16 +168,15 @@ function _resolvePackageVersion( try { const rushTempFolder: string = _getRushTempFolder(rushCommonFolder); const sourceNpmrcFolder: string = path.join(rushCommonFolder, 'config', 'rush'); - const rushJsonFolder: string = findRushJsonFolder(); syncNpmrc({ sourceNpmrcFolder, targetNpmrcFolder: rushTempFolder, logger, supportEnvVarFallbackSyntax: false, - // Only filter npm-incompatible properties when the repo uses pnpm or yarn. - // If the repo uses npm, the .npmrc is already configured for npm, so don't filter. - filterNpmIncompatibleProperties: !_isRepoUsingNpm(rushJsonFolder) + // Always filter npm-incompatible properties in install-run scripts. + // Any warnings will be shown when running Rush commands directly. + filterNpmIncompatibleProperties: true }); // This returns something that looks like: @@ -481,9 +463,9 @@ export function installAndRun( targetNpmrcFolder: packageInstallFolder, logger, supportEnvVarFallbackSyntax: false, - // Only filter npm-incompatible properties when the repo uses pnpm or yarn. - // If the repo uses npm, the .npmrc is already configured for npm, so don't filter. - filterNpmIncompatibleProperties: !_isRepoUsingNpm(rushJsonFolder) + // Always filter npm-incompatible properties in install-run scripts. + // Any warnings will be shown when running Rush commands directly. + filterNpmIncompatibleProperties: true }); _createPackageJson(packageInstallFolder, packageName, packageVersion); diff --git a/libraries/rush-lib/src/utilities/Utilities.ts b/libraries/rush-lib/src/utilities/Utilities.ts index a3ab86e1a2..6c502224ef 100644 --- a/libraries/rush-lib/src/utilities/Utilities.ts +++ b/libraries/rush-lib/src/utilities/Utilities.ts @@ -512,7 +512,7 @@ export class Utilities { maxInstallAttempts, suppressOutput, directory, - filterNpmIncompatibleProperties + filterNpmIncompatibleProperties = false }: IInstallPackageInDirectoryOptions): Promise { directory = path.resolve(directory); const directoryExists: boolean = await FileSystem.existsAsync(directory); @@ -541,7 +541,7 @@ export class Utilities { supportEnvVarFallbackSyntax: false, // Filter out npm-incompatible properties only when the .npmrc is configured for // a different package manager (pnpm/yarn) but npm is being used to install. - filterNpmIncompatibleProperties: filterNpmIncompatibleProperties || false + filterNpmIncompatibleProperties }); } From bbc2d666808d8e2729f05ab2da73943c8c05a964 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 26 Jan 2026 23:31:54 +0000 Subject: [PATCH 12/15] Always filter npmrc in publish and fix changelog for lockstepped versioning Co-authored-by: iclanton <5010588+iclanton@users.noreply.github.com> --- .../rush-lib/filter-npmrc-properties_2026-01-26-23-12.json | 6 +++--- libraries/rush-lib/src/cli/actions/PublishAction.ts | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/common/changes/@microsoft/rush-lib/filter-npmrc-properties_2026-01-26-23-12.json b/common/changes/@microsoft/rush-lib/filter-npmrc-properties_2026-01-26-23-12.json index c4c68f25c6..54bb3a29b1 100644 --- a/common/changes/@microsoft/rush-lib/filter-npmrc-properties_2026-01-26-23-12.json +++ b/common/changes/@microsoft/rush-lib/filter-npmrc-properties_2026-01-26-23-12.json @@ -2,10 +2,10 @@ "changes": [ { "comment": "Filter npm-incompatible properties from .npmrc when npm is used with a configuration intended for pnpm or yarn, to eliminate spurious warnings during package manager installation.", - "type": "patch", - "packageName": "@microsoft/rush-lib" + "type": "none", + "packageName": "@microsoft/rush" } ], - "packageName": "@microsoft/rush-lib", + "packageName": "@microsoft/rush", "email": "copilot@github.com" } diff --git a/libraries/rush-lib/src/cli/actions/PublishAction.ts b/libraries/rush-lib/src/cli/actions/PublishAction.ts index 6e36e2f180..796d0c21b3 100644 --- a/libraries/rush-lib/src/cli/actions/PublishAction.ts +++ b/libraries/rush-lib/src/cli/actions/PublishAction.ts @@ -594,9 +594,9 @@ export class PublishAction extends BaseRushAction { targetNpmrcFolder: this._targetNpmrcPublishFolder, useNpmrcPublish: true, supportEnvVarFallbackSyntax, - // Filter out npm-incompatible properties when using npm or yarn to publish. - // Don't filter for pnpm since it understands pnpm-specific properties. - filterNpmIncompatibleProperties: this.rushConfiguration.packageManager !== 'pnpm' + // Always filter npm-incompatible properties when publishing. + // The .npmrc-publish file may contain pnpm-specific properties even in npm repos. + filterNpmIncompatibleProperties: true }); } From b92cf31ab90e0548e3ccdd06d7b29f87dd53f755 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 26 Jan 2026 23:48:16 +0000 Subject: [PATCH 13/15] Remove filtering from PublishAction - only filter in install-run and installPackageInDirectoryAsync Co-authored-by: iclanton <5010588+iclanton@users.noreply.github.com> --- libraries/rush-lib/src/cli/actions/PublishAction.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/libraries/rush-lib/src/cli/actions/PublishAction.ts b/libraries/rush-lib/src/cli/actions/PublishAction.ts index 796d0c21b3..cb05204508 100644 --- a/libraries/rush-lib/src/cli/actions/PublishAction.ts +++ b/libraries/rush-lib/src/cli/actions/PublishAction.ts @@ -593,10 +593,7 @@ export class PublishAction extends BaseRushAction { sourceNpmrcFolder: this.rushConfiguration.commonRushConfigFolder, targetNpmrcFolder: this._targetNpmrcPublishFolder, useNpmrcPublish: true, - supportEnvVarFallbackSyntax, - // Always filter npm-incompatible properties when publishing. - // The .npmrc-publish file may contain pnpm-specific properties even in npm repos. - filterNpmIncompatibleProperties: true + supportEnvVarFallbackSyntax }); } From 383d111f30fd55f0102b974686681f5a38a0471b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 27 Jan 2026 00:12:38 +0000 Subject: [PATCH 14/15] Filter pnpm-specific registry-scoped properties (tokenHelper, urlTokenHelper) Co-authored-by: iclanton <5010588+iclanton@users.noreply.github.com> --- .../rush-lib/src/utilities/npmrcUtilities.ts | 35 +++++++++++++++---- .../__snapshots__/npmrcUtilities.test.ts.snap | 10 ++++++ .../src/utilities/test/npmrcUtilities.test.ts | 17 +++++++++ 3 files changed, 56 insertions(+), 6 deletions(-) diff --git a/libraries/rush-lib/src/utilities/npmrcUtilities.ts b/libraries/rush-lib/src/utilities/npmrcUtilities.ts index aaa4088d3b..adf1f851d1 100644 --- a/libraries/rush-lib/src/utilities/npmrcUtilities.ts +++ b/libraries/rush-lib/src/utilities/npmrcUtilities.ts @@ -77,7 +77,6 @@ function _trimNpmrcFile( /** * List of npmrc properties that are not supported by npm but may be present in the config. * These include pnpm-specific properties and deprecated npm properties. - * Registry-scoped properties (starting with "//") are never filtered as they contain auth tokens. */ const NPM_INCOMPATIBLE_PROPERTIES: Set = new Set([ // pnpm-specific hoisting configuration @@ -90,6 +89,17 @@ const NPM_INCOMPATIBLE_PROPERTIES: Set = new Set([ 'publish-branch' ]); +/** + * List of registry-scoped npmrc property suffixes that are pnpm-specific. + * These are properties like "//registry.example.com/:tokenHelper" where "tokenHelper" + * is the suffix after the last colon. + */ +const NPM_INCOMPATIBLE_REGISTRY_SCOPED_PROPERTIES: Set = new Set([ + // pnpm-specific token helper properties + 'tokenHelper', + 'urlTokenHelper' +]); + /** * Regular expression to extract property names from .npmrc lines. * Matches everything before '=', '[', or whitespace to capture the property name. @@ -142,13 +152,26 @@ export function trimNpmrcFileLines( if (match) { const propertyName: string = match[1]; - // Never filter registry-scoped properties (auth tokens, etc.) - // These start with "//" like "//registry.npmjs.org/:_authToken" + // Check if this is a registry-scoped property (starts with "//" like "//registry.npmjs.org/:_authToken") const isRegistryScoped: boolean = propertyName.startsWith('//'); - if (!isRegistryScoped && NPM_INCOMPATIBLE_PROPERTIES.has(propertyName)) { - lineShouldBeTrimmed = true; - trimReason = 'NPM_INCOMPATIBLE_PROPERTY'; + if (isRegistryScoped) { + // For registry-scoped properties, check if the suffix (after the last colon) is npm-incompatible + // Example: "//registry.example.com/:tokenHelper" -> suffix is "tokenHelper" + const lastColonIndex: number = propertyName.lastIndexOf(':'); + if (lastColonIndex !== -1) { + const registryPropertySuffix: string = propertyName.substring(lastColonIndex + 1); + if (NPM_INCOMPATIBLE_REGISTRY_SCOPED_PROPERTIES.has(registryPropertySuffix)) { + lineShouldBeTrimmed = true; + trimReason = 'NPM_INCOMPATIBLE_PROPERTY'; + } + } + } else { + // For non-registry-scoped properties, check the full property name + if (NPM_INCOMPATIBLE_PROPERTIES.has(propertyName)) { + lineShouldBeTrimmed = true; + trimReason = 'NPM_INCOMPATIBLE_PROPERTY'; + } } } } diff --git a/libraries/rush-lib/src/utilities/test/__snapshots__/npmrcUtilities.test.ts.snap b/libraries/rush-lib/src/utilities/test/__snapshots__/npmrcUtilities.test.ts.snap index 4df9d2ea68..0484645555 100644 --- a/libraries/rush-lib/src/utilities/test/__snapshots__/npmrcUtilities.test.ts.snap +++ b/libraries/rush-lib/src/utilities/test/__snapshots__/npmrcUtilities.test.ts.snap @@ -27,6 +27,16 @@ Array [ ] `; +exports[`npmrcUtilities trimNpmrcFileLines With npm-incompatible properties filtering filters out pnpm-specific registry-scoped properties 1`] = ` +Array [ + "registry=https://registry.npmjs.org/", + "//registry.npmjs.org/:_authToken=\${NPM_TOKEN}", + "; UNSUPPORTED BY NPM: //my-registry.com/:tokenHelper=/path/to/helper", + "; UNSUPPORTED BY NPM: //other-registry.com/:urlTokenHelper=/path/to/url-helper", + "//registry.npmjs.org/:always-auth=true", +] +`; + exports[`npmrcUtilities trimNpmrcFileLines With npm-incompatible properties filtering preserves registry-scoped auth tokens 1`] = ` Array [ "registry=https://registry.npmjs.org/", diff --git a/libraries/rush-lib/src/utilities/test/npmrcUtilities.test.ts b/libraries/rush-lib/src/utilities/test/npmrcUtilities.test.ts index 9db4f00102..3c84a54cfc 100644 --- a/libraries/rush-lib/src/utilities/test/npmrcUtilities.test.ts +++ b/libraries/rush-lib/src/utilities/test/npmrcUtilities.test.ts @@ -187,6 +187,23 @@ describe('npmrcUtilities', () => { ) ).toMatchSnapshot(); }); + + it('filters out pnpm-specific registry-scoped properties', () => { + expect( + trimNpmrcFileLines( + [ + 'registry=https://registry.npmjs.org/', + '//registry.npmjs.org/:_authToken=${NPM_TOKEN}', + '//my-registry.com/:tokenHelper=/path/to/helper', + '//other-registry.com/:urlTokenHelper=/path/to/url-helper', + '//registry.npmjs.org/:always-auth=true' + ], + { NPM_TOKEN: 'abc123' }, + supportEnvVarFallbackSyntax, + filterNpmIncompatibleProperties + ) + ).toMatchSnapshot(); + }); }); }); }); From 1300bc85c4feb02b5132d82413bfd3de506aa185 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 27 Jan 2026 00:23:32 +0000 Subject: [PATCH 15/15] Address @dmichon-msft feedback: make env configurable, use slice, and named regex groups Co-authored-by: iclanton <5010588+iclanton@users.noreply.github.com> --- .../rush-lib/src/utilities/npmrcUtilities.ts | 26 ++++++++++++++----- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/libraries/rush-lib/src/utilities/npmrcUtilities.ts b/libraries/rush-lib/src/utilities/npmrcUtilities.ts index adf1f851d1..6e5b1da615 100644 --- a/libraries/rush-lib/src/utilities/npmrcUtilities.ts +++ b/libraries/rush-lib/src/utilities/npmrcUtilities.ts @@ -30,6 +30,7 @@ function _trimNpmrcFile( | 'linesToPrepend' | 'supportEnvVarFallbackSyntax' | 'filterNpmIncompatibleProperties' + | 'env' > ): string { const { @@ -37,7 +38,8 @@ function _trimNpmrcFile( linesToPrepend, linesToAppend, supportEnvVarFallbackSyntax, - filterNpmIncompatibleProperties + filterNpmIncompatibleProperties, + env = process.env } = options; const combinedNpmrcFromCache: string | undefined = _combinedNpmrcMap.get(sourceNpmrcPath); if (combinedNpmrcFromCache !== undefined) { @@ -61,7 +63,7 @@ function _trimNpmrcFile( const resultLines: string[] = trimNpmrcFileLines( npmrcFileLines, - process.env, + env, supportEnvVarFallbackSyntax, filterNpmIncompatibleProperties ); @@ -110,6 +112,15 @@ const NPM_INCOMPATIBLE_REGISTRY_SCOPED_PROPERTIES: Set = new Set([ */ const PROPERTY_NAME_REGEX: RegExp = /^([^=\[\s]+)/; +/** + * Regular expression to extract environment variable names and optional fallback values. + * Matches patterns like: + * nameString -> group 1: nameString, group 2: undefined + * nameString-fallbackString -> group 1: nameString, group 2: fallbackString + * nameString:-fallbackString -> group 1: nameString, group 2: fallbackString + */ +const ENV_VAR_WITH_FALLBACK_REGEX: RegExp = /^(?[^:-]+)(?::?-(?.+))?$/; + /** * * @param npmrcFileLines The npmrc file's lines @@ -188,7 +199,7 @@ export function trimNpmrcFileLines( * ${nameString-fallbackString} -> name-fallbackString * ${nameString:-fallbackString} -> name:-fallbackString */ - const nameWithFallback: string = token.substring(2, token.length - 1); + const nameWithFallback: string = token.slice(2, -1); let environmentVariableName: string; let fallback: string | undefined; @@ -201,10 +212,9 @@ export function trimNpmrcFileLines( * nameString-fallbackString -> nameString fallbackString * nameString:-fallbackString -> nameString fallbackString */ - const matched: string[] | null = nameWithFallback.match(/^([^:-]+)(?:\:?-(.+))?$/); - // matched: [originStr, variableName, fallback] - environmentVariableName = matched?.[1] ?? nameWithFallback; - fallback = matched?.[2]; + const matched: RegExpMatchArray | null = nameWithFallback.match(ENV_VAR_WITH_FALLBACK_REGEX); + environmentVariableName = matched?.groups?.name ?? nameWithFallback; + fallback = matched?.groups?.fallback; } else { environmentVariableName = nameWithFallback; } @@ -262,6 +272,7 @@ interface INpmrcTrimOptions { linesToAppend?: string[]; supportEnvVarFallbackSyntax: boolean; filterNpmIncompatibleProperties?: boolean; + env?: NodeJS.ProcessEnv; } function _copyAndTrimNpmrcFile(options: INpmrcTrimOptions): string { @@ -295,6 +306,7 @@ export interface ISyncNpmrcOptions { linesToAppend?: string[]; createIfMissing?: boolean; filterNpmIncompatibleProperties?: boolean; + env?: NodeJS.ProcessEnv; } export function syncNpmrc(options: ISyncNpmrcOptions): string | undefined {