From 639510367efeebf15af9ba55f780b1dda032e6b4 Mon Sep 17 00:00:00 2001 From: zxfan Date: Wed, 4 Dec 2024 20:40:13 +0800 Subject: [PATCH] Support fallback syntax in .npmrc file --- ...back_syntax_in_npmrc_2024-12-04-13-22.json | 10 +++ .../rush-lib/src/utilities/npmrcUtilities.ts | 83 +++++++++++++------ .../src/utilities/test/npmrcUtilities.test.ts | 37 +++++++++ 3 files changed, 103 insertions(+), 27 deletions(-) create mode 100644 common/changes/@microsoft/rush/feature-support_fallback_syntax_in_npmrc_2024-12-04-13-22.json create mode 100644 libraries/rush-lib/src/utilities/test/npmrcUtilities.test.ts diff --git a/common/changes/@microsoft/rush/feature-support_fallback_syntax_in_npmrc_2024-12-04-13-22.json b/common/changes/@microsoft/rush/feature-support_fallback_syntax_in_npmrc_2024-12-04-13-22.json new file mode 100644 index 00000000000..312c8fbab59 --- /dev/null +++ b/common/changes/@microsoft/rush/feature-support_fallback_syntax_in_npmrc_2024-12-04-13-22.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@microsoft/rush", + "comment": "Support fallback syntax in .npmrc file", + "type": "none" + } + ], + "packageName": "@microsoft/rush" +} \ No newline at end of file diff --git a/libraries/rush-lib/src/utilities/npmrcUtilities.ts b/libraries/rush-lib/src/utilities/npmrcUtilities.ts index e3bd9026e21..07c6731776d 100644 --- a/libraries/rush-lib/src/utilities/npmrcUtilities.ts +++ b/libraries/rush-lib/src/utilities/npmrcUtilities.ts @@ -22,27 +22,11 @@ export interface ILogger { // create a global _combinedNpmrc for cache purpose const _combinedNpmrcMap: Map = new Map(); -function _trimNpmrcFile(options: { - sourceNpmrcPath: string; - linesToPrepend?: string[]; - linesToAppend?: string[]; -}): string { - const { sourceNpmrcPath, linesToPrepend, linesToAppend } = options; - const combinedNpmrcFromCache: string | undefined = _combinedNpmrcMap.get(sourceNpmrcPath); - if (combinedNpmrcFromCache !== undefined) { - return combinedNpmrcFromCache; - } - let npmrcFileLines: string[] = []; - if (linesToPrepend) { - npmrcFileLines.push(...linesToPrepend); - } - if (fs.existsSync(sourceNpmrcPath)) { - npmrcFileLines.push(...fs.readFileSync(sourceNpmrcPath).toString().split('\n')); - } - if (linesToAppend) { - npmrcFileLines.push(...linesToAppend); - } - npmrcFileLines = npmrcFileLines.map((line) => (line || '').trim()); +export function addMissingEnvPrefix(line: string): string { + return '; MISSING ENVIRONMENT VARIABLE: ' + line +} + +export function trimNpmrcFileLines(npmrcFileLines: string[], env: NodeJS.ProcessEnv): string[] { const resultLines: string[] = []; // This finds environment variable tokens that look like "${VAR_NAME}" @@ -66,11 +50,30 @@ function _trimNpmrcFile(options: { const environmentVariables: string[] | null = line.match(expansionRegExp); if (environmentVariables) { for (const token of environmentVariables) { - // Remove the leading "${" and the trailing "}" from the token - const environmentVariableName: string = token.substring(2, token.length - 1); - - // Is the environment variable defined? - if (!process.env[environmentVariableName]) { + /** + * 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); + + /** + * 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] + const name: string = matched?.[1] ?? nameWithFallback; + const fallback: string | undefined = matched?.[2]; + + // Is the environment variable and fallback value defined. + if (!env[name] && !fallback) { // No, so trim this line lineShouldBeTrimmed = true; break; @@ -82,11 +85,37 @@ function _trimNpmrcFile(options: { if (lineShouldBeTrimmed) { // Example output: // "; MISSING ENVIRONMENT VARIABLE: //my-registry.com/npm/:_authToken=${MY_AUTH_TOKEN}" - resultLines.push('; MISSING ENVIRONMENT VARIABLE: ' + line); + resultLines.push(addMissingEnvPrefix(line)); } else { resultLines.push(line); } } + return resultLines; +} + +function _trimNpmrcFile(options: { + sourceNpmrcPath: string; + linesToPrepend?: string[]; + linesToAppend?: string[]; +}): string { + const { sourceNpmrcPath, linesToPrepend, linesToAppend } = options; + const combinedNpmrcFromCache: string | undefined = _combinedNpmrcMap.get(sourceNpmrcPath); + if (combinedNpmrcFromCache !== undefined) { + return combinedNpmrcFromCache; + } + let npmrcFileLines: string[] = []; + if (linesToPrepend) { + npmrcFileLines.push(...linesToPrepend); + } + if (fs.existsSync(sourceNpmrcPath)) { + npmrcFileLines.push(...fs.readFileSync(sourceNpmrcPath).toString().split('\n')); + } + if (linesToAppend) { + npmrcFileLines.push(...linesToAppend); + } + npmrcFileLines = npmrcFileLines.map((line) => (line || '').trim()); + + const resultLines: string[] = trimNpmrcFileLines(npmrcFileLines, process.env); const combinedNpmrc: string = resultLines.join('\n'); diff --git a/libraries/rush-lib/src/utilities/test/npmrcUtilities.test.ts b/libraries/rush-lib/src/utilities/test/npmrcUtilities.test.ts new file mode 100644 index 00000000000..2cd89d3aeb9 --- /dev/null +++ b/libraries/rush-lib/src/utilities/test/npmrcUtilities.test.ts @@ -0,0 +1,37 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. +// See LICENSE in the project root for license information. + +import { addMissingEnvPrefix, trimNpmrcFileLines } from '../npmrcUtilities'; + +describe('npmrcUtilities', () => { + it(trimNpmrcFileLines.name, () => { + // Normal + expect(trimNpmrcFileLines(['var1=${foo}'], {})).toEqual([addMissingEnvPrefix('var1=${foo}')]); + expect(trimNpmrcFileLines(['var1=${foo}'], { foo: 'test' })).toEqual(['var1=${foo}']); + expect(trimNpmrcFileLines(['var1=${foo-fallback_value}'], {})).toEqual(['var1=${foo-fallback_value}']); + expect(trimNpmrcFileLines(['var1=${foo-fallback_value}'], { foo: 'test' })).toEqual([ + 'var1=${foo-fallback_value}' + ]); + expect(trimNpmrcFileLines(['var1=${foo:-fallback_value}'], {})).toEqual(['var1=${foo:-fallback_value}']); + expect(trimNpmrcFileLines(['var1=${foo:-fallback_value}'], { foo: 'test' })).toEqual([ + 'var1=${foo:-fallback_value}' + ]); + + // Multiple environment variables + expect(trimNpmrcFileLines(['var1=${foo}-${bar}'], { foo: 'test' })).toEqual([addMissingEnvPrefix('var1=${foo}-${bar}')]); + expect(trimNpmrcFileLines(['var1=${foo}-${bar}'], { bar: 'test' })).toEqual([addMissingEnvPrefix('var1=${foo}-${bar}')]); + expect(trimNpmrcFileLines(['var1=${foo}-${bar}'], { foo: 'test', bar: 'test' })).toEqual(['var1=${foo}-${bar}']); + expect(trimNpmrcFileLines(['var1=${foo:-fallback_value}-${bar-fallback_value}'], {})).toEqual(['var1=${foo:-fallback_value}-${bar-fallback_value}']); + + // Multiline + expect(trimNpmrcFileLines(['var1=${foo}', 'var2=${bar}'], { foo: 'test' })).toEqual(['var1=${foo}', addMissingEnvPrefix('var2=${bar}')]); + expect(trimNpmrcFileLines(['var1=${foo}', 'var2=${bar}'], { foo: 'test', bar: 'test' })).toEqual(['var1=${foo}', 'var2=${bar}']); + expect(trimNpmrcFileLines(['var1=${foo}', 'var2=${bar-fallback_value}'], { foo: 'test' })).toEqual(['var1=${foo}', 'var2=${bar-fallback_value}']); + expect(trimNpmrcFileLines(['var1=${foo:-fallback_value}', 'var2=${bar-fallback_value}'], {})).toEqual(['var1=${foo:-fallback_value}', 'var2=${bar-fallback_value}']); + + // Malformed + expect(trimNpmrcFileLines(['var1=${foo_fallback_value}'], {})).toEqual([addMissingEnvPrefix('var1=${foo_fallback_value}')]); + expect(trimNpmrcFileLines(['var1=${foo:fallback_value}'], {})).toEqual([addMissingEnvPrefix('var1=${foo:fallback_value}')]); + expect(trimNpmrcFileLines(['var1=${foo:_fallback_value}'], {})).toEqual([addMissingEnvPrefix('var1=${foo:_fallback_value}')]); + }); +});