From b887c6cabdc63bfd74c812116d5b8d4e3ab286fe Mon Sep 17 00:00:00 2001 From: Cheng Liu Date: Wed, 27 Jul 2022 11:39:22 +0800 Subject: [PATCH 1/6] feat(rush-lib): rush-pnpm accepts patch & patch-commit --- common/reviews/api/rush-lib.api.md | 1 + .../rush-lib/src/cli/RushPnpmCommandLine.ts | 268 +------------ .../src/cli/RushPnpmCommandLineParser.ts | 359 ++++++++++++++++++ libraries/rush-lib/src/logic/RushConstants.ts | 7 + .../src/logic/base/BaseInstallManager.ts | 29 +- 5 files changed, 394 insertions(+), 270 deletions(-) create mode 100644 libraries/rush-lib/src/cli/RushPnpmCommandLineParser.ts diff --git a/common/reviews/api/rush-lib.api.md b/common/reviews/api/rush-lib.api.md index dc82b2a5a73..b1dbad89f63 100644 --- a/common/reviews/api/rush-lib.api.md +++ b/common/reviews/api/rush-lib.api.md @@ -923,6 +923,7 @@ export class RushConstants { static readonly pnpmConfigFilename: string; static readonly pnpmfileV1Filename: string; static readonly pnpmfileV6Filename: string; + static readonly pnpmPatchesFolderName: string; static readonly pnpmV3ShrinkwrapFilename: string; static readonly projectRushFolderName: string; static readonly projectShrinkwrapFilename: string; diff --git a/libraries/rush-lib/src/cli/RushPnpmCommandLine.ts b/libraries/rush-lib/src/cli/RushPnpmCommandLine.ts index 52eed439976..4c11971bb6e 100644 --- a/libraries/rush-lib/src/cli/RushPnpmCommandLine.ts +++ b/libraries/rush-lib/src/cli/RushPnpmCommandLine.ts @@ -1,276 +1,14 @@ // Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. // See LICENSE in the project root for license information. -import * as path from 'path'; -import { - FileSystem, - AlreadyReportedError, - Executable, - EnvironmentMap, - ITerminal, - Colors, - Terminal, - ConsoleTerminalProvider -} from '@rushstack/node-core-library'; -import { PrintUtilities } from '@rushstack/terminal'; - -import { RushConfiguration } from '../api/RushConfiguration'; -import { NodeJsCompatibility } from '../logic/NodeJsCompatibility'; -import { SpawnSyncReturns } from 'child_process'; import { ILaunchOptions } from '../api/Rush'; +import { RushPnpmCommandLineParser } from './RushPnpmCommandLineParser'; export interface ILaunchRushPnpmInternalOptions extends ILaunchOptions {} -const RUSH_SKIP_CHECKS_PARAMETER: string = '--rush-skip-checks'; - export class RushPnpmCommandLine { public static launch(launcherVersion: string, options: ILaunchRushPnpmInternalOptions): void { - // Node.js can sometimes accidentally terminate with a zero exit code (e.g. for an uncaught - // promise exception), so we start with the assumption that the exit code is 1 - // and set it to 0 only on success. - process.exitCode = 1; - - const { terminalProvider } = options; - const terminal: ITerminal = new Terminal(terminalProvider ?? new ConsoleTerminalProvider()); - - try { - // Are we in a Rush repo? - let rushConfiguration: RushConfiguration | undefined = undefined; - if (RushConfiguration.tryFindRushJsonLocation()) { - // showVerbose is false because the logging message may break JSON output - rushConfiguration = RushConfiguration.loadFromDefaultLocation({ showVerbose: false }); - } - - NodeJsCompatibility.warnAboutCompatibilityIssues({ - isRushLib: true, - alreadyReportedNodeTooNewError: !!options.alreadyReportedNodeTooNewError, - rushConfiguration - }); - - if (!rushConfiguration) { - throw new Error( - 'The "rush-pnpm" command must be executed in a folder that is under a Rush workspace folder' - ); - } - - if (rushConfiguration.packageManager !== 'pnpm') { - throw new Error( - 'The "rush-pnpm" command requires your rush.json to be configured to use the PNPM package manager' - ); - } - - if (!rushConfiguration.pnpmOptions.useWorkspaces) { - throw new Error( - 'The "rush-pnpm" command requires the "useWorkspaces" setting to be enabled in rush.json' - ); - } - - const workspaceFolder: string = rushConfiguration.commonTempFolder; - const workspaceFilePath: string = path.join(workspaceFolder, 'pnpm-workspace.yaml'); - - if (!FileSystem.exists(workspaceFilePath)) { - terminal.writeErrorLine('Error: The PNPM workspace file has not been generated:'); - terminal.writeErrorLine(` ${workspaceFilePath}\n`); - terminal.writeLine(Colors.cyan(`Do you need to run "rush install" or "rush update"?`)); - throw new AlreadyReportedError(); - } - - if (!FileSystem.exists(rushConfiguration.packageManagerToolFilename)) { - terminal.writeErrorLine('Error: The PNPM local binary has not been installed yet.'); - terminal.writeLine('\n' + Colors.cyan(`Do you need to run "rush install" or "rush update"?`)); - throw new AlreadyReportedError(); - } - - // 0 = node.exe - // 1 = rush-pnpm - const pnpmArgs: string[] = process.argv.slice(2); - - RushPnpmCommandLine._validatePnpmUsage(pnpmArgs, terminal); - - const pnpmEnvironmentMap: EnvironmentMap = new EnvironmentMap(process.env); - pnpmEnvironmentMap.set('NPM_CONFIG_WORKSPACE_DIR', workspaceFolder); - - if (rushConfiguration.pnpmOptions.pnpmStorePath) { - pnpmEnvironmentMap.set('NPM_CONFIG_STORE_DIR', rushConfiguration.pnpmOptions.pnpmStorePath); - } - - if (rushConfiguration.pnpmOptions.environmentVariables) { - for (const [envKey, { value: envValue, override }] of Object.entries( - rushConfiguration.pnpmOptions.environmentVariables - )) { - if (override) { - pnpmEnvironmentMap.set(envKey, envValue); - } else { - if (undefined === pnpmEnvironmentMap.get(envKey)) { - pnpmEnvironmentMap.set(envKey, envValue); - } - } - } - } - - const result: SpawnSyncReturns = Executable.spawnSync( - rushConfiguration.packageManagerToolFilename, - pnpmArgs, - { - environmentMap: pnpmEnvironmentMap, - stdio: 'inherit' - } - ); - if (result.error) { - throw new Error('Failed to invoke PNPM: ' + result.error); - } - if (result.status === null) { - throw new Error('Failed to invoke PNPM: Spawn completed without an exit code'); - } - process.exitCode = result.status; - } catch (error) { - if (!(error instanceof AlreadyReportedError)) { - const prefix: string = 'ERROR: '; - terminal.writeErrorLine('\n' + PrintUtilities.wrapWords(prefix + error.message)); - } - } - } - - private static _validatePnpmUsage(pnpmArgs: string[], terminal: ITerminal): void { - if (pnpmArgs[0] === RUSH_SKIP_CHECKS_PARAMETER) { - pnpmArgs.shift(); - // Ignore other checks - return; - } - - if (pnpmArgs.length === 0) { - return; - } - const firstArg: string = pnpmArgs[0]; - - // Detect common safe invocations - if (pnpmArgs.includes('-h') || pnpmArgs.includes('--help') || pnpmArgs.includes('-?')) { - return; - } - - if (pnpmArgs.length === 1) { - if (firstArg === '-v' || firstArg === '--version') { - return; - } - } - - const BYPASS_NOTICE: string = `To bypass this check, add "${RUSH_SKIP_CHECKS_PARAMETER}" as the very first command line option.`; - - if (!/^[a-z]+([a-z0-9\-])*$/.test(firstArg)) { - // We can't parse this CLI syntax - terminal.writeErrorLine( - `Warning: The "rush-pnpm" wrapper expects a command verb before "${firstArg}"\n` - ); - terminal.writeLine(Colors.cyan(BYPASS_NOTICE)); - throw new AlreadyReportedError(); - } else { - const commandName: string = firstArg; - - // Also accept SKIP_RUSH_CHECKS_PARAMETER immediately after the command verb - if (pnpmArgs[1] === RUSH_SKIP_CHECKS_PARAMETER) { - pnpmArgs.splice(1, 1); - return; - } - - if (pnpmArgs.indexOf(RUSH_SKIP_CHECKS_PARAMETER) >= 0) { - // We do not attempt to parse PNPM's complete CLI syntax, so we cannot be sure how to interpret - // strings that appear outside of the specific patterns that this parser recognizes - terminal.writeErrorLine( - PrintUtilities.wrapWords( - `Error: The "${RUSH_SKIP_CHECKS_PARAMETER}" option must be the first parameter for the "rush-pnpm" command.` - ) - ); - throw new AlreadyReportedError(); - } - - // Warn about commands known not to work - /* eslint-disable no-fallthrough */ - switch (commandName) { - // Blocked - case 'import': { - terminal.writeErrorLine( - PrintUtilities.wrapWords( - `Error: The "pnpm ${commandName}" command is known to be incompatible with Rush's environment.` - ) + '\n' - ); - terminal.writeLine(Colors.cyan(BYPASS_NOTICE)); - throw new AlreadyReportedError(); - } - - // Show warning for install commands - case 'add': - case 'install': - /* synonym */ - case 'i': - case 'install-test': - /* synonym */ - case 'it': { - terminal.writeErrorLine( - PrintUtilities.wrapWords( - `Error: The "pnpm ${commandName}" command is incompatible with Rush's environment.` + - ` Use the "rush install" or "rush update" commands instead.` - ) + '\n' - ); - terminal.writeLine(Colors.cyan(BYPASS_NOTICE)); - throw new AlreadyReportedError(); - } - - // Show warning - case 'link': - /* synonym */ - case 'ln': - case 'remove': - /* synonym */ - case 'rm': - case 'unlink': - case 'update': - /* synonym */ - case 'up': { - terminal.writeWarningLine( - PrintUtilities.wrapWords( - `Warning: The "pnpm ${commandName}" command makes changes that may invalidate Rush's workspace state.` - ) + '\n' - ); - terminal.writeWarningLine(`==> Consider running "rush install" or "rush update" afterwards.\n`); - break; - } - - // Known safe - case 'audit': - case 'exec': - case 'list': - /* synonym */ - case 'ls': - case 'outdated': - case 'pack': - case 'prune': - case 'publish': - case 'rebuild': - /* synonym */ - case 'rb': - case 'root': - case 'run': - case 'start': - case 'store': - case 'test': - /* synonym */ - case 't': - case 'why': { - break; - } - - // Unknown - default: { - terminal.writeErrorLine( - PrintUtilities.wrapWords( - `Error: The "pnpm ${commandName}" command has not been tested with Rush's environment. It may be incompatible.` - ) + '\n' - ); - terminal.writeLine(Colors.cyan(BYPASS_NOTICE)); - throw new AlreadyReportedError(); - } - } - /* eslint-enable no-fallthrough */ - } + const rushPnpmCommandLineParser: RushPnpmCommandLineParser = new RushPnpmCommandLineParser(options); + rushPnpmCommandLineParser.execute(); } } diff --git a/libraries/rush-lib/src/cli/RushPnpmCommandLineParser.ts b/libraries/rush-lib/src/cli/RushPnpmCommandLineParser.ts new file mode 100644 index 00000000000..efa1536c1fc --- /dev/null +++ b/libraries/rush-lib/src/cli/RushPnpmCommandLineParser.ts @@ -0,0 +1,359 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. +// See LICENSE in the project root for license information. + +import * as path from 'path'; +import * as os from 'os'; +import { RushConfiguration } from '../api/RushConfiguration'; +import { NodeJsCompatibility } from '../logic/NodeJsCompatibility'; +import { + AlreadyReportedError, + Colors, + ConsoleTerminalProvider, + EnvironmentMap, + Executable, + FileSystem, + ITerminal, + ITerminalProvider, + Terminal +} from '@rushstack/node-core-library'; +import { PrintUtilities } from '@rushstack/terminal'; +import { RushConstants } from '../logic/RushConstants'; +import { Utilities } from '../utilities/Utilities'; + +import type { IBuiltInPluginConfiguration } from '../pluginFramework/PluginLoader/BuiltInPluginLoader'; +import type { SpawnSyncReturns } from 'child_process'; + +const RUSH_SKIP_CHECKS_PARAMETER: string = '--rush-skip-checks'; + +/** + * Options for RushPnpmCommandLineParser + */ +export interface IRushPnpmCommandLineParserOptions { + alreadyReportedNodeTooNewError?: boolean; + builtInPluginConfigurations?: IBuiltInPluginConfiguration[]; + terminalProvider?: ITerminalProvider; +} + +export class RushPnpmCommandLineParser { + private _terminal: ITerminal; + private _rushConfiguration!: RushConfiguration; + private _pnpmArgs!: string[]; + private _commandName: string | undefined; + + public constructor(options: IRushPnpmCommandLineParserOptions) { + const { terminalProvider } = options; + const localTerminalProvider: ITerminalProvider = + terminalProvider ?? + new ConsoleTerminalProvider({ + debugEnabled: process.argv.indexOf('--debug') >= 0, + verboseEnabled: process.argv.indexOf('--verbose') >= 0 + }); + this._terminal = new Terminal(localTerminalProvider); + + try { + // Are we in a Rush repo? + let rushConfiguration: RushConfiguration | undefined = undefined; + if (RushConfiguration.tryFindRushJsonLocation()) { + // showVerbose is false because the logging message may break JSON output + rushConfiguration = RushConfiguration.loadFromDefaultLocation({ showVerbose: false }); + } + + NodeJsCompatibility.warnAboutCompatibilityIssues({ + isRushLib: true, + alreadyReportedNodeTooNewError: !!options.alreadyReportedNodeTooNewError, + rushConfiguration + }); + + if (!rushConfiguration) { + throw new Error( + 'The "rush-pnpm" command must be executed in a folder that is under a Rush workspace folder' + ); + } + this._rushConfiguration = rushConfiguration; + + if (rushConfiguration.packageManager !== 'pnpm') { + throw new Error( + 'The "rush-pnpm" command requires your rush.json to be configured to use the PNPM package manager' + ); + } + + if (!rushConfiguration.pnpmOptions.useWorkspaces) { + throw new Error( + 'The "rush-pnpm" command requires the "useWorkspaces" setting to be enabled in rush.json' + ); + } + + const workspaceFolder: string = rushConfiguration.commonTempFolder; + const workspaceFilePath: string = path.join(workspaceFolder, 'pnpm-workspace.yaml'); + + if (!FileSystem.exists(workspaceFilePath)) { + this._terminal.writeErrorLine('Error: The PNPM workspace file has not been generated:'); + this._terminal.writeErrorLine(` ${workspaceFilePath}\n`); + this._terminal.writeLine(Colors.cyan(`Do you need to run "rush install" or "rush update"?`)); + throw new AlreadyReportedError(); + } + + if (!FileSystem.exists(rushConfiguration.packageManagerToolFilename)) { + this._terminal.writeErrorLine('Error: The PNPM local binary has not been installed yet.'); + this._terminal.writeLine('\n' + Colors.cyan(`Do you need to run "rush install" or "rush update"?`)); + throw new AlreadyReportedError(); + } + + // 0 = node.exe + // 1 = rush-pnpm + const pnpmArgs: string[] = process.argv.slice(2); + + this._validatePnpmUsage(pnpmArgs); + + this._pnpmArgs = pnpmArgs; + } catch (error) { + if (!(error instanceof AlreadyReportedError)) { + const prefix: string = 'ERROR: '; + this._terminal.writeErrorLine('\n' + PrintUtilities.wrapWords(prefix + error.message)); + } + } + } + + public execute(): void { + // Node.js can sometimes accidentally terminate with a zero exit code (e.g. for an uncaught + // promise exception), so we start with the assumption that the exit code is 1 + // and set it to 0 only on success. + process.exitCode = 1; + this._execute(); + + if (process.exitCode === 0) { + this._postExecute(); + } + } + + private _validatePnpmUsage(pnpmArgs: string[]): void { + if (pnpmArgs[0] === RUSH_SKIP_CHECKS_PARAMETER) { + pnpmArgs.shift(); + // Ignore other checks + return; + } + + if (pnpmArgs.length === 0) { + return; + } + const firstArg: string = pnpmArgs[0]; + + // Detect common safe invocations + if (pnpmArgs.includes('-h') || pnpmArgs.includes('--help') || pnpmArgs.includes('-?')) { + return; + } + + if (pnpmArgs.length === 1) { + if (firstArg === '-v' || firstArg === '--version') { + return; + } + } + + const BYPASS_NOTICE: string = `To bypass this check, add "${RUSH_SKIP_CHECKS_PARAMETER}" as the very first command line option.`; + + if (!/^[a-z]+([a-z0-9\-])*$/.test(firstArg)) { + // We can't parse this CLI syntax + this._terminal.writeErrorLine( + `Warning: The "rush-pnpm" wrapper expects a command verb before "${firstArg}"\n` + ); + this._terminal.writeLine(Colors.cyan(BYPASS_NOTICE)); + throw new AlreadyReportedError(); + } else { + const commandName: string = firstArg; + + // Also accept SKIP_RUSH_CHECKS_PARAMETER immediately after the command verb + if (pnpmArgs[1] === RUSH_SKIP_CHECKS_PARAMETER) { + pnpmArgs.splice(1, 1); + return; + } + + if (pnpmArgs.indexOf(RUSH_SKIP_CHECKS_PARAMETER) >= 0) { + // We do not attempt to parse PNPM's complete CLI syntax, so we cannot be sure how to interpret + // strings that appear outside of the specific patterns that this parser recognizes + this._terminal.writeErrorLine( + PrintUtilities.wrapWords( + `Error: The "${RUSH_SKIP_CHECKS_PARAMETER}" option must be the first parameter for the "rush-pnpm" command.` + ) + ); + throw new AlreadyReportedError(); + } + + this._commandName = commandName; + + // Warn about commands known not to work + /* eslint-disable no-fallthrough */ + switch (commandName) { + // Blocked + case 'import': { + this._terminal.writeErrorLine( + PrintUtilities.wrapWords( + `Error: The "pnpm ${commandName}" command is known to be incompatible with Rush's environment.` + ) + '\n' + ); + this._terminal.writeLine(Colors.cyan(BYPASS_NOTICE)); + throw new AlreadyReportedError(); + } + + // Show warning for install commands + case 'add': + case 'install': + /* synonym */ + case 'i': + case 'install-test': + /* synonym */ + case 'it': { + this._terminal.writeErrorLine( + PrintUtilities.wrapWords( + `Error: The "pnpm ${commandName}" command is incompatible with Rush's environment.` + + ` Use the "rush install" or "rush update" commands instead.` + ) + '\n' + ); + this._terminal.writeLine(Colors.cyan(BYPASS_NOTICE)); + throw new AlreadyReportedError(); + } + + // Show warning + case 'link': + /* synonym */ + case 'ln': + case 'remove': + /* synonym */ + case 'rm': + case 'unlink': + case 'update': + /* synonym */ + case 'up': { + this._terminal.writeWarningLine( + PrintUtilities.wrapWords( + `Warning: The "pnpm ${commandName}" command makes changes that may invalidate Rush's workspace state.` + ) + '\n' + ); + this._terminal.writeWarningLine( + `==> Consider running "rush install" or "rush update" afterwards.\n` + ); + break; + } + + // Known safe + case 'audit': + case 'exec': + case 'list': + /* synonym */ + case 'ls': + case 'outdated': + case 'pack': + case 'patch': + case 'patch-commit': + case 'prune': + case 'publish': + case 'rebuild': + /* synonym */ + case 'rb': + case 'root': + case 'run': + case 'start': + case 'store': + case 'test': + /* synonym */ + case 't': + case 'why': { + break; + } + + // Unknown + default: { + this._terminal.writeErrorLine( + PrintUtilities.wrapWords( + `Error: The "pnpm ${commandName}" command has not been tested with Rush's environment. It may be incompatible.` + ) + '\n' + ); + this._terminal.writeLine(Colors.cyan(BYPASS_NOTICE)); + throw new AlreadyReportedError(); + } + } + /* eslint-enable no-fallthrough */ + } + } + + private _execute(): void { + const rushConfiguration: RushConfiguration = this._rushConfiguration; + const workspaceFolder: string = rushConfiguration.commonTempFolder; + const pnpmEnvironmentMap: EnvironmentMap = new EnvironmentMap(process.env); + pnpmEnvironmentMap.set('NPM_CONFIG_WORKSPACE_DIR', workspaceFolder); + + if (rushConfiguration.pnpmOptions.pnpmStorePath) { + pnpmEnvironmentMap.set('NPM_CONFIG_STORE_DIR', rushConfiguration.pnpmOptions.pnpmStorePath); + } + + if (rushConfiguration.pnpmOptions.environmentVariables) { + for (const [envKey, { value: envValue, override }] of Object.entries( + rushConfiguration.pnpmOptions.environmentVariables + )) { + if (override) { + pnpmEnvironmentMap.set(envKey, envValue); + } else { + if (undefined === pnpmEnvironmentMap.get(envKey)) { + pnpmEnvironmentMap.set(envKey, envValue); + } + } + } + } + + const result: SpawnSyncReturns = Executable.spawnSync( + rushConfiguration.packageManagerToolFilename, + this._pnpmArgs, + { + environmentMap: pnpmEnvironmentMap, + stdio: 'inherit' + } + ); + if (result.error) { + throw new Error('Failed to invoke PNPM: ' + result.error); + } + if (result.status === null) { + throw new Error('Failed to invoke PNPM: Spawn completed without an exit code'); + } + process.exitCode = result.status; + } + + private _postExecute(): void { + const commandName: string | undefined = this._commandName; + if (!commandName) { + return; + } + + switch (commandName) { + case 'patch-commit': { + const commonTempPnpmPatchesFolder: string = path.join( + this._rushConfiguration.commonTempFolder, + RushConstants.pnpmPatchesFolderName + ); + const rushPnpmPatchesFolder: string = path.join( + this._rushConfiguration.commonFolder, + 'pnpm', + RushConstants.pnpmPatchesFolderName + ); + if (FileSystem.exists(commonTempPnpmPatchesFolder)) { + // Copy common\temp\patches\ to common\pnpm\patches\ + FileSystem.copyFiles({ + sourcePath: commonTempPnpmPatchesFolder, + destinationPath: rushPnpmPatchesFolder + }); + + // Copy (or delete) common\temp\pnpm-lock.yaml --> common\config\rush\pnpm-lock.yaml + Utilities.syncFile( + this._rushConfiguration.tempShrinkwrapFilename, + this._rushConfiguration.getCommittedShrinkwrapFilename() + ); + + this._terminal.writeWarningLine( + 'Rush refreshed the pnpm patch files in the "common/pnpm/patches" folder and shrinkwrap file.' + + os.EOL + + ' Please commit this change to Git.' + ); + } + break; + } + } + } +} diff --git a/libraries/rush-lib/src/logic/RushConstants.ts b/libraries/rush-lib/src/logic/RushConstants.ts index 47f59d0df3a..ad54cd1f8b6 100644 --- a/libraries/rush-lib/src/logic/RushConstants.ts +++ b/libraries/rush-lib/src/logic/RushConstants.ts @@ -91,6 +91,13 @@ export class RushConstants { */ public static readonly pnpmfileV6Filename: string = '.pnpmfile.cjs'; + /** + * The folder name used to store patch files for pnpm + * Example: `C:\MyRepo\common\config\pnpm\patches` + * Example: `C:\MyRepo\common\temp\patches` + */ + public static readonly pnpmPatchesFolderName: string = 'patches'; + /** * The filename ("shrinkwrap.yaml") used to store state for pnpm */ diff --git a/libraries/rush-lib/src/logic/base/BaseInstallManager.ts b/libraries/rush-lib/src/logic/base/BaseInstallManager.ts index 50e5b195754..e8bca8e2b2c 100644 --- a/libraries/rush-lib/src/logic/base/BaseInstallManager.ts +++ b/libraries/rush-lib/src/logic/base/BaseInstallManager.ts @@ -183,7 +183,7 @@ export abstract class BaseInstallManager { console.log( colors.red( 'Project filtering arguments can only be used when running in a workspace environment. Run the ' + - 'command again without specifying these arguments.' + 'command again without specifying these arguments.' ) ); throw new AlreadyReportedError(); @@ -195,7 +195,7 @@ export abstract class BaseInstallManager { console.log( colors.red( 'Project filtering arguments cannot be used when running "rush update". Run the command again ' + - 'without specifying these arguments.' + 'without specifying these arguments.' ) ); throw new AlreadyReportedError(); @@ -435,6 +435,25 @@ export abstract class BaseInstallManager { ? crypto.createHash('sha1').update(npmrcText).digest('hex') : undefined; + // Copy the committed patches folder if using pnpm + if (this.rushConfiguration.packageManager === 'pnpm') { + const commonTempPnpmPatchesFolder: string = path.join( + this._rushConfiguration.commonTempFolder, + RushConstants.pnpmPatchesFolderName + ); + const rushPnpmPatchesFolder: string = path.join( + this._rushConfiguration.commonFolder, + 'pnpm', + RushConstants.pnpmPatchesFolderName + ); + if (FileSystem.exists(rushPnpmPatchesFolder)) { + FileSystem.copyFiles({ + sourcePath: rushPnpmPatchesFolder, + destinationPath: commonTempPnpmPatchesFolder + }); + } + } + // Shim support for pnpmfile in. This shim will call back into the variant-specific pnpmfile. // Additionally when in workspaces, the shim implements support for common versions. if (this.rushConfiguration.packageManager === 'pnpm') { @@ -642,9 +661,9 @@ export abstract class BaseInstallManager { ) { this._terminal.writeWarningLine( 'Warning: Your rush.json specifies a pnpmVersion with a known issue ' + - 'that may cause unintended version selections.' + - " It's recommended to upgrade to PNPM >=6.34.0 or >=7.9.0. " + - 'For details see: https://rushjs.io/link/pnpm-issue-5132' + 'that may cause unintended version selections.' + + " It's recommended to upgrade to PNPM >=6.34.0 or >=7.9.0. " + + 'For details see: https://rushjs.io/link/pnpm-issue-5132' ); } if ( From 881926d292540412e5a37282cb438d8c238e8c05 Mon Sep 17 00:00:00 2001 From: Cheng Liu Date: Wed, 27 Jul 2022 11:40:05 +0800 Subject: [PATCH 2/6] chore: rush change --- .../rush/feat-pnpm-patch_2022-07-27-03-39.json | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 common/changes/@microsoft/rush/feat-pnpm-patch_2022-07-27-03-39.json diff --git a/common/changes/@microsoft/rush/feat-pnpm-patch_2022-07-27-03-39.json b/common/changes/@microsoft/rush/feat-pnpm-patch_2022-07-27-03-39.json new file mode 100644 index 00000000000..baeae9fa2b6 --- /dev/null +++ b/common/changes/@microsoft/rush/feat-pnpm-patch_2022-07-27-03-39.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@microsoft/rush", + "comment": "rush-pnpm accepts patch and patch-commit", + "type": "none" + } + ], + "packageName": "@microsoft/rush" +} \ No newline at end of file From 99a095c6e086111569c21e569cab7e17d73fc37c Mon Sep 17 00:00:00 2001 From: Cheng Liu Date: Sun, 25 Sep 2022 14:46:48 +0800 Subject: [PATCH 3/6] feat: rush-pnpm patch & patch-commit --- common/reviews/api/rush-lib.api.md | 5 + .../rush-lib/src/cli/RushPnpmCommandLine.ts | 4 +- .../src/cli/RushPnpmCommandLineParser.ts | 185 ++++++++++++++---- .../src/logic/InstallManagerFactory.ts | 3 +- .../src/logic/base/BaseInstallManager.ts | 11 +- .../logic/installManager/InstallHelpers.ts | 3 + .../logic/pnpm/PnpmOptionsConfiguration.ts | 45 ++++- .../src/schemas/pnpm-config.schema.json | 8 + 8 files changed, 214 insertions(+), 50 deletions(-) diff --git a/common/reviews/api/rush-lib.api.md b/common/reviews/api/rush-lib.api.md index b1dbad89f63..d4344c8d37c 100644 --- a/common/reviews/api/rush-lib.api.md +++ b/common/reviews/api/rush-lib.api.md @@ -462,6 +462,7 @@ export interface _IPnpmOptionsJson extends IPackageManagerOptionsJsonBase { globalOverrides?: Record; // Warning: (ae-forgotten-export) The symbol "IPnpmPackageExtension" needs to be exported by the entry point index.d.ts globalPackageExtensions?: Record; + globalPatchedDependencies?: Record; // Warning: (ae-forgotten-export) The symbol "IPnpmPeerDependencyRules" needs to be exported by the entry point index.d.ts globalPeerDependencyRules?: IPnpmPeerDependencyRules; pnpmStore?: PnpmStoreOptions; @@ -713,7 +714,10 @@ export class PnpmOptionsConfiguration extends PackageManagerOptionsConfiguration readonly globalNeverBuiltDependencies: string[] | undefined; readonly globalOverrides: Record | undefined; readonly globalPackageExtensions: Record | undefined; + get globalPatchedDependencies(): Record | undefined; readonly globalPeerDependencyRules: IPnpmPeerDependencyRules | undefined; + // (undocumented) + get jsonFilename(): string | undefined; // @internal (undocumented) static loadFromJsonFileOrThrow(jsonFilename: string, commonTempFolder: string): PnpmOptionsConfiguration; // @internal (undocumented) @@ -723,6 +727,7 @@ export class PnpmOptionsConfiguration extends PackageManagerOptionsConfiguration readonly preventManualShrinkwrapChanges: boolean; readonly strictPeerDependencies: boolean; readonly unsupportedPackageJsonSettings: unknown | undefined; + updateGlobalPatchedDependencies(patchedDependencies: Record | undefined): void; readonly useWorkspaces: boolean; } diff --git a/libraries/rush-lib/src/cli/RushPnpmCommandLine.ts b/libraries/rush-lib/src/cli/RushPnpmCommandLine.ts index 4c11971bb6e..5d755e9c4b1 100644 --- a/libraries/rush-lib/src/cli/RushPnpmCommandLine.ts +++ b/libraries/rush-lib/src/cli/RushPnpmCommandLine.ts @@ -9,6 +9,8 @@ export interface ILaunchRushPnpmInternalOptions extends ILaunchOptions {} export class RushPnpmCommandLine { public static launch(launcherVersion: string, options: ILaunchRushPnpmInternalOptions): void { const rushPnpmCommandLineParser: RushPnpmCommandLineParser = new RushPnpmCommandLineParser(options); - rushPnpmCommandLineParser.execute(); + + // RushPnpmCommandLineParser.executeAsync should never reject the promise + rushPnpmCommandLineParser.executeAsync().catch(console.error); } } diff --git a/libraries/rush-lib/src/cli/RushPnpmCommandLineParser.ts b/libraries/rush-lib/src/cli/RushPnpmCommandLineParser.ts index efa1536c1fc..6fc1a84cc0d 100644 --- a/libraries/rush-lib/src/cli/RushPnpmCommandLineParser.ts +++ b/libraries/rush-lib/src/cli/RushPnpmCommandLineParser.ts @@ -11,17 +11,30 @@ import { ConsoleTerminalProvider, EnvironmentMap, Executable, + FileConstants, FileSystem, + Import, ITerminal, ITerminalProvider, + JsonFile, + JsonObject, Terminal } from '@rushstack/node-core-library'; import { PrintUtilities } from '@rushstack/terminal'; import { RushConstants } from '../logic/RushConstants'; -import { Utilities } from '../utilities/Utilities'; +import { RushGlobalFolder } from '../api/RushGlobalFolder'; +import { PurgeManager } from '../logic/PurgeManager'; import type { IBuiltInPluginConfiguration } from '../pluginFramework/PluginLoader/BuiltInPluginLoader'; import type { SpawnSyncReturns } from 'child_process'; +import type { BaseInstallManager, IInstallManagerOptions } from '../logic/base/BaseInstallManager'; + +const lodash: typeof import('lodash') = Import.lazy('lodash', require); +const semver: typeof import('semver') = Import.lazy('semver', require); +const installManagerFactoryModule: typeof import('../logic/InstallManagerFactory') = Import.lazy( + '../logic/InstallManagerFactory', + require +); const RUSH_SKIP_CHECKS_PARAMETER: string = '--rush-skip-checks'; @@ -39,14 +52,18 @@ export class RushPnpmCommandLineParser { private _rushConfiguration!: RushConfiguration; private _pnpmArgs!: string[]; private _commandName: string | undefined; + private _debugEnabled: boolean; + private _verboseEnabled: boolean; public constructor(options: IRushPnpmCommandLineParserOptions) { const { terminalProvider } = options; + this._debugEnabled = process.argv.indexOf('--debug') >= 0; + this._verboseEnabled = process.argv.indexOf('--verbose') >= 0; const localTerminalProvider: ITerminalProvider = terminalProvider ?? new ConsoleTerminalProvider({ - debugEnabled: process.argv.indexOf('--debug') >= 0, - verboseEnabled: process.argv.indexOf('--verbose') >= 0 + debugEnabled: this._debugEnabled, + verboseEnabled: this._verboseEnabled }); this._terminal = new Terminal(localTerminalProvider); @@ -78,8 +95,9 @@ export class RushPnpmCommandLineParser { } if (!rushConfiguration.pnpmOptions.useWorkspaces) { + const pnpmConfigFilename: string = rushConfiguration.pnpmOptions.jsonFilename || 'rush.json'; throw new Error( - 'The "rush-pnpm" command requires the "useWorkspaces" setting to be enabled in rush.json' + `The "rush-pnpm" command requires the "useWorkspaces" setting to be enabled in ${pnpmConfigFilename}` ); } @@ -107,14 +125,11 @@ export class RushPnpmCommandLineParser { this._pnpmArgs = pnpmArgs; } catch (error) { - if (!(error instanceof AlreadyReportedError)) { - const prefix: string = 'ERROR: '; - this._terminal.writeErrorLine('\n' + PrintUtilities.wrapWords(prefix + error.message)); - } + this._reportErrorAndSetExitCode(error as Error); } } - public execute(): void { + public async executeAsync(): Promise { // Node.js can sometimes accidentally terminate with a zero exit code (e.g. for an uncaught // promise exception), so we start with the assumption that the exit code is 1 // and set it to 0 only on success. @@ -122,7 +137,7 @@ export class RushPnpmCommandLineParser { this._execute(); if (process.exitCode === 0) { - this._postExecute(); + await this._postExecuteAsync(); } } @@ -234,6 +249,41 @@ export class RushPnpmCommandLineParser { break; } + // Know safe after validation + case 'patch': { + /** + * If you were to accidentally attempt to use rush-pnpm patch with a pnpmVersion < 7.4.0, pnpm patch may fallback to the system patch command. + * For instance, /usr/bin/patch which may just hangs forever + * So, erroring out the command if the pnpm version is < 7.4.0 + */ + if (semver.lt(this._rushConfiguration.packageManagerToolVersion, '7.4.0')) { + this._terminal.writeErrorLine( + PrintUtilities.wrapWords( + `Error: The "pnpm patch" command is added after pnpm@7.4.0.` + + ` Please update "pnpmVersion" >= 7.4.0 in rush.json file and run "rush update" to use this command.` + ) + '\n' + ); + throw new AlreadyReportedError(); + } + break; + } + case 'patch-commit': { + const pnpmOptionsJsonFilename: string = path.join( + this._rushConfiguration.commonRushConfigFolder, + RushConstants.pnpmConfigFilename + ); + if (this._rushConfiguration.rushConfigurationJson.pnpmOptions) { + this._terminal.writeErrorLine( + PrintUtilities.wrapWords( + `Error: The "pnpm patch-commit" command is incompatible with specifying "pnpmOptions" in rush.json file.` + + ` Please move the content of "pnpmOptions" in rush.json file to ${pnpmOptionsJsonFilename}` + ) + '\n' + ); + throw new AlreadyReportedError(); + } + break; + } + // Known safe case 'audit': case 'exec': @@ -242,8 +292,6 @@ export class RushPnpmCommandLineParser { case 'ls': case 'outdated': case 'pack': - case 'patch': - case 'patch-commit': case 'prune': case 'publish': case 'rebuild': @@ -268,7 +316,6 @@ export class RushPnpmCommandLineParser { ) + '\n' ); this._terminal.writeLine(Colors.cyan(BYPASS_NOTICE)); - throw new AlreadyReportedError(); } } /* eslint-enable no-fallthrough */ @@ -316,7 +363,7 @@ export class RushPnpmCommandLineParser { process.exitCode = result.status; } - private _postExecute(): void { + private async _postExecuteAsync(): Promise { const commandName: string | undefined = this._commandName; if (!commandName) { return; @@ -324,30 +371,41 @@ export class RushPnpmCommandLineParser { switch (commandName) { case 'patch-commit': { - const commonTempPnpmPatchesFolder: string = path.join( - this._rushConfiguration.commonTempFolder, - RushConstants.pnpmPatchesFolderName - ); - const rushPnpmPatchesFolder: string = path.join( - this._rushConfiguration.commonFolder, - 'pnpm', - RushConstants.pnpmPatchesFolderName - ); - if (FileSystem.exists(commonTempPnpmPatchesFolder)) { - // Copy common\temp\patches\ to common\pnpm\patches\ - FileSystem.copyFiles({ - sourcePath: commonTempPnpmPatchesFolder, - destinationPath: rushPnpmPatchesFolder - }); - - // Copy (or delete) common\temp\pnpm-lock.yaml --> common\config\rush\pnpm-lock.yaml - Utilities.syncFile( - this._rushConfiguration.tempShrinkwrapFilename, - this._rushConfiguration.getCommittedShrinkwrapFilename() - ); + // Example: "C:\MyRepo\common\temp\package.json" + const commonPackageJsonFilename: string = `${this._rushConfiguration.commonTempFolder}/${FileConstants.PackageJson}`; + const commonPackageJson: JsonObject = JsonFile.load(commonPackageJsonFilename); + const newGlobalPatchedDependencies: Record | undefined = + commonPackageJson?.pnpm?.patchedDependencies; + const currentGlobalPatchedDependencies: Record | undefined = + this._rushConfiguration.pnpmOptions.globalPatchedDependencies; + + if (!lodash.isEqual(currentGlobalPatchedDependencies, newGlobalPatchedDependencies)) { + const commonTempPnpmPatchesFolder: string = `${this._rushConfiguration.commonTempFolder}/${RushConstants.pnpmPatchesFolderName}`; + const rushPnpmPatchesFolder: string = `${this._rushConfiguration.commonFolder}/pnpm/${RushConstants.pnpmPatchesFolderName}`; + // Copy (or delete) common\temp\patches\ --> common\pnpm\patches\ + if (FileSystem.exists(commonTempPnpmPatchesFolder)) { + FileSystem.ensureEmptyFolder(rushPnpmPatchesFolder); + console.log(`Copying ${commonTempPnpmPatchesFolder}`); + console.log(` --> ${rushPnpmPatchesFolder}`); + FileSystem.copyFiles({ + sourcePath: commonTempPnpmPatchesFolder, + destinationPath: rushPnpmPatchesFolder + }); + } else { + if (FileSystem.exists(rushPnpmPatchesFolder)) { + console.log(`Deleting ${rushPnpmPatchesFolder}`); + FileSystem.deleteFolder(rushPnpmPatchesFolder); + } + } + + // Update patchedDependencies to pnpm configuration file + this._rushConfiguration.pnpmOptions.updateGlobalPatchedDependencies(newGlobalPatchedDependencies); + + // Rerun installation to update + await this._doRushUpdateAsync(); this._terminal.writeWarningLine( - 'Rush refreshed the pnpm patch files in the "common/pnpm/patches" folder and shrinkwrap file.' + + `Rush refreshed the ${RushConstants.pnpmConfigFilename}, shrinkwrap file and patch files under the "common/pnpm/patches" folder.` + os.EOL + ' Please commit this change to Git.' ); @@ -356,4 +414,59 @@ export class RushPnpmCommandLineParser { } } } + + private async _doRushUpdateAsync(): Promise { + this._terminal.writeLine(); + this._terminal.writeLine(Colors.green('Running "rush update"')); + this._terminal.writeLine(); + + const rushGlobalFolder: RushGlobalFolder = new RushGlobalFolder(); + const purgeManager: PurgeManager = new PurgeManager(this._rushConfiguration, rushGlobalFolder); + const installManagerOptions: IInstallManagerOptions = { + debug: this._debugEnabled, + allowShrinkwrapUpdates: true, + bypassPolicy: false, + noLink: false, + fullUpgrade: false, + recheckShrinkwrap: true, + networkConcurrency: undefined, + collectLogFile: false, + variant: undefined, + maxInstallAttempts: RushConstants.defaultMaxInstallAttempts, + pnpmFilterArguments: [], + checkOnly: false + }; + + const installManager: BaseInstallManager = + installManagerFactoryModule.InstallManagerFactory.getInstallManager( + this._rushConfiguration, + rushGlobalFolder, + purgeManager, + installManagerOptions + ); + try { + await installManager.doInstallAsync(); + } finally { + purgeManager.deleteAll(); + } + } + + private _reportErrorAndSetExitCode(error: Error): void { + if (!(error instanceof AlreadyReportedError)) { + const prefix: string = 'ERROR: '; + this._terminal.writeErrorLine('\n' + PrintUtilities.wrapWords(prefix + error.message)); + } + + if (this._debugEnabled) { + // If catchSyncErrors() called this, then show a call stack similar to what Node.js + // would show for an uncaught error + this._terminal.writeErrorLine('\n' + error.stack); + } + + if (process.exitCode !== undefined) { + process.exit(process.exitCode); + } else { + process.exit(1); + } + } } diff --git a/libraries/rush-lib/src/logic/InstallManagerFactory.ts b/libraries/rush-lib/src/logic/InstallManagerFactory.ts index 4307e0dbca3..2115bcfcbc2 100644 --- a/libraries/rush-lib/src/logic/InstallManagerFactory.ts +++ b/libraries/rush-lib/src/logic/InstallManagerFactory.ts @@ -2,12 +2,13 @@ // See LICENSE in the project root for license information. import { Import } from '@rushstack/node-core-library'; -import { BaseInstallManager, IInstallManagerOptions } from './base/BaseInstallManager'; import { WorkspaceInstallManager } from './installManager/WorkspaceInstallManager'; import { PurgeManager } from './PurgeManager'; import { RushConfiguration } from '../api/RushConfiguration'; import { RushGlobalFolder } from '../api/RushGlobalFolder'; +import type { BaseInstallManager, IInstallManagerOptions } from './base/BaseInstallManager'; + const rushInstallManagerModule: typeof import('./installManager/RushInstallManager') = Import.lazy( './installManager/RushInstallManager', require diff --git a/libraries/rush-lib/src/logic/base/BaseInstallManager.ts b/libraries/rush-lib/src/logic/base/BaseInstallManager.ts index e8bca8e2b2c..fe075c19bd3 100644 --- a/libraries/rush-lib/src/logic/base/BaseInstallManager.ts +++ b/libraries/rush-lib/src/logic/base/BaseInstallManager.ts @@ -437,15 +437,8 @@ export abstract class BaseInstallManager { // Copy the committed patches folder if using pnpm if (this.rushConfiguration.packageManager === 'pnpm') { - const commonTempPnpmPatchesFolder: string = path.join( - this._rushConfiguration.commonTempFolder, - RushConstants.pnpmPatchesFolderName - ); - const rushPnpmPatchesFolder: string = path.join( - this._rushConfiguration.commonFolder, - 'pnpm', - RushConstants.pnpmPatchesFolderName - ); + const commonTempPnpmPatchesFolder: string = `${this._rushConfiguration.commonTempFolder}/${RushConstants.pnpmPatchesFolderName}`; + const rushPnpmPatchesFolder: string = `${this._rushConfiguration.commonFolder}/pnpm/${RushConstants.pnpmPatchesFolderName}`; if (FileSystem.exists(rushPnpmPatchesFolder)) { FileSystem.copyFiles({ sourcePath: rushPnpmPatchesFolder, diff --git a/libraries/rush-lib/src/logic/installManager/InstallHelpers.ts b/libraries/rush-lib/src/logic/installManager/InstallHelpers.ts index 5e3240023b2..5d7f43465b9 100644 --- a/libraries/rush-lib/src/logic/installManager/InstallHelpers.ts +++ b/libraries/rush-lib/src/logic/installManager/InstallHelpers.ts @@ -64,6 +64,9 @@ export class InstallHelpers { pnpmOptions.globalAllowedDeprecatedVersions ); } + if (pnpmOptions.globalPatchedDependencies) { + lodash.set(commonPackageJson, 'pnpm.patchedDependencies', pnpmOptions.globalPatchedDependencies); + } if (pnpmOptions.unsupportedPackageJsonSettings) { lodash.merge(commonPackageJson, pnpmOptions.unsupportedPackageJsonSettings); } diff --git a/libraries/rush-lib/src/logic/pnpm/PnpmOptionsConfiguration.ts b/libraries/rush-lib/src/logic/pnpm/PnpmOptionsConfiguration.ts index 40a65dae4a2..2424dbeaf1f 100644 --- a/libraries/rush-lib/src/logic/pnpm/PnpmOptionsConfiguration.ts +++ b/libraries/rush-lib/src/logic/pnpm/PnpmOptionsConfiguration.ts @@ -2,7 +2,7 @@ // See LICENSE in the project root for license information. import * as path from 'path'; -import { JsonFile, JsonSchema } from '@rushstack/node-core-library'; +import { JsonFile, JsonObject, JsonSchema } from '@rushstack/node-core-library'; import { IPackageManagerOptionsJsonBase, PackageManagerOptionsConfigurationBase @@ -78,6 +78,10 @@ export interface IPnpmOptionsJson extends IPackageManagerOptionsJsonBase { * {@inheritDoc PnpmOptionsConfiguration.globalAllowedDeprecatedVersions} */ globalAllowedDeprecatedVersions?: Record; + /** + * {@inheritDoc PnpmOptionsConfiguration.globalPatchedDependencies} + */ + globalPatchedDependencies?: Record; /** * {@inheritDoc PnpmOptionsConfiguration.unsupportedPackageJsonSettings} */ @@ -100,6 +104,10 @@ export class PnpmOptionsConfiguration extends PackageManagerOptionsConfiguration path.resolve(__dirname, '../../schemas/pnpm-config.schema.json') ); + private _json: JsonObject; + private _jsonFilename: string | undefined; + private _globalPatchedDependencies: Record | undefined; + /** * The method used to resolve the store used by PNPM. * @@ -241,8 +249,23 @@ export class PnpmOptionsConfiguration extends PackageManagerOptionsConfiguration */ public readonly unsupportedPackageJsonSettings: unknown | undefined; - private constructor(json: IPnpmOptionsJson, commonTempFolder: string) { + /** + * (GENERATED BY RUSH-PNPM PATCH-COMMIT) When modifying this property, make sure you know what you are doing. + * + * The `globalPatchedDependencies` is added/updated automatically when you run pnpm patch-commit + * command. It is a dictionary where the key should be the package name and exact version. The value + * should be a relative path to a patch file. + * + * PNPM documentation: https://pnpm.io/package_json#pnpmpatcheddependencies + */ + public get globalPatchedDependencies(): Record | undefined { + return this._globalPatchedDependencies; + } + + private constructor(json: IPnpmOptionsJson, commonTempFolder: string, jsonFilename?: string) { super(json); + this._json = json; + this._jsonFilename = jsonFilename; this.pnpmStore = json.pnpmStore || 'local'; if (EnvironmentConfiguration.pnpmStorePathOverride) { this.pnpmStorePath = EnvironmentConfiguration.pnpmStorePathOverride; @@ -261,6 +284,7 @@ export class PnpmOptionsConfiguration extends PackageManagerOptionsConfiguration this.globalNeverBuiltDependencies = json.globalNeverBuiltDependencies; this.globalAllowedDeprecatedVersions = json.globalAllowedDeprecatedVersions; this.unsupportedPackageJsonSettings = json.unsupportedPackageJsonSettings; + this._globalPatchedDependencies = json.globalPatchedDependencies; } /** @internal */ @@ -272,7 +296,7 @@ export class PnpmOptionsConfiguration extends PackageManagerOptionsConfiguration jsonFilename, PnpmOptionsConfiguration._jsonSchema ); - return new PnpmOptionsConfiguration(pnpmOptionJson || {}, commonTempFolder); + return new PnpmOptionsConfiguration(pnpmOptionJson || {}, commonTempFolder, jsonFilename); } /** @internal */ @@ -282,4 +306,19 @@ export class PnpmOptionsConfiguration extends PackageManagerOptionsConfiguration ): PnpmOptionsConfiguration { return new PnpmOptionsConfiguration(json, commonTempFolder); } + + /** + * Updates patchedDependencies field of the PNPM options in the common/config/rush/pnpm-config.json file. + */ + public updateGlobalPatchedDependencies(patchedDependencies: Record | undefined): void { + this._globalPatchedDependencies = patchedDependencies; + this._json.globalPatchedDependencies = patchedDependencies; + if (this._jsonFilename) { + JsonFile.save(this._json, this._jsonFilename, { updateExistingFile: true }); + } + } + + public get jsonFilename(): string | undefined { + return this._jsonFilename; + } } diff --git a/libraries/rush-lib/src/schemas/pnpm-config.schema.json b/libraries/rush-lib/src/schemas/pnpm-config.schema.json index b49e830c904..654434aace4 100644 --- a/libraries/rush-lib/src/schemas/pnpm-config.schema.json +++ b/libraries/rush-lib/src/schemas/pnpm-config.schema.json @@ -148,6 +148,14 @@ } }, + "globalPatchedDependencies": { + "description": "(GENERATED BY RUSH-PNPM PATCH-COMMIT) When modifying this property, make sure you know what you are doing.\n\nThe `globalPatchedDependencies` is added/updated automatically when you run rush-pnpm patch-commit command. It is a dictionary where the key should be the package name and exact version. The value should be a relative path to a patch file.\n\nPNPM documentation: https://pnpm.io/package_json#pnpmpatcheddependencies", + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "unsupportedPackageJsonSettings": { "description": "(USE AT YOUR OWN RISK) This is a free-form property bag that will be copied into the `common/temp/package.json` file that is generated by Rush during installation. This provides a way to experiment with new PNPM features. These settings will override any other Rush configuration associated with a given JSON field except for `.pnpmfile.cjs`.", "type": "object" From b82f42575cb1facd26662b84be362a1ae899fc43 Mon Sep 17 00:00:00 2001 From: Cheng Liu Date: Mon, 28 Nov 2022 16:36:56 +0800 Subject: [PATCH 4/6] chore: use common/pnpm-patches instead of common/pnpm/patches --- .../rush-lib/src/cli/RushPnpmCommandLineParser.ts | 4 ++-- libraries/rush-lib/src/logic/RushConstants.ts | 2 +- .../rush-lib/src/logic/base/BaseInstallManager.ts | 12 ++++++------ 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/libraries/rush-lib/src/cli/RushPnpmCommandLineParser.ts b/libraries/rush-lib/src/cli/RushPnpmCommandLineParser.ts index 6fc1a84cc0d..a818dc369d5 100644 --- a/libraries/rush-lib/src/cli/RushPnpmCommandLineParser.ts +++ b/libraries/rush-lib/src/cli/RushPnpmCommandLineParser.ts @@ -381,8 +381,8 @@ export class RushPnpmCommandLineParser { if (!lodash.isEqual(currentGlobalPatchedDependencies, newGlobalPatchedDependencies)) { const commonTempPnpmPatchesFolder: string = `${this._rushConfiguration.commonTempFolder}/${RushConstants.pnpmPatchesFolderName}`; - const rushPnpmPatchesFolder: string = `${this._rushConfiguration.commonFolder}/pnpm/${RushConstants.pnpmPatchesFolderName}`; - // Copy (or delete) common\temp\patches\ --> common\pnpm\patches\ + const rushPnpmPatchesFolder: string = `${this._rushConfiguration.commonFolder}/pnpm-${RushConstants.pnpmPatchesFolderName}`; + // Copy (or delete) common\temp\patches\ --> common\pnpm-patches\ if (FileSystem.exists(commonTempPnpmPatchesFolder)) { FileSystem.ensureEmptyFolder(rushPnpmPatchesFolder); console.log(`Copying ${commonTempPnpmPatchesFolder}`); diff --git a/libraries/rush-lib/src/logic/RushConstants.ts b/libraries/rush-lib/src/logic/RushConstants.ts index ad54cd1f8b6..539dd56d993 100644 --- a/libraries/rush-lib/src/logic/RushConstants.ts +++ b/libraries/rush-lib/src/logic/RushConstants.ts @@ -93,7 +93,7 @@ export class RushConstants { /** * The folder name used to store patch files for pnpm - * Example: `C:\MyRepo\common\config\pnpm\patches` + * Example: `C:\MyRepo\common\config\pnpm-patches` * Example: `C:\MyRepo\common\temp\patches` */ public static readonly pnpmPatchesFolderName: string = 'patches'; diff --git a/libraries/rush-lib/src/logic/base/BaseInstallManager.ts b/libraries/rush-lib/src/logic/base/BaseInstallManager.ts index fe075c19bd3..de846ed56ea 100644 --- a/libraries/rush-lib/src/logic/base/BaseInstallManager.ts +++ b/libraries/rush-lib/src/logic/base/BaseInstallManager.ts @@ -183,7 +183,7 @@ export abstract class BaseInstallManager { console.log( colors.red( 'Project filtering arguments can only be used when running in a workspace environment. Run the ' + - 'command again without specifying these arguments.' + 'command again without specifying these arguments.' ) ); throw new AlreadyReportedError(); @@ -195,7 +195,7 @@ export abstract class BaseInstallManager { console.log( colors.red( 'Project filtering arguments cannot be used when running "rush update". Run the command again ' + - 'without specifying these arguments.' + 'without specifying these arguments.' ) ); throw new AlreadyReportedError(); @@ -438,7 +438,7 @@ export abstract class BaseInstallManager { // Copy the committed patches folder if using pnpm if (this.rushConfiguration.packageManager === 'pnpm') { const commonTempPnpmPatchesFolder: string = `${this._rushConfiguration.commonTempFolder}/${RushConstants.pnpmPatchesFolderName}`; - const rushPnpmPatchesFolder: string = `${this._rushConfiguration.commonFolder}/pnpm/${RushConstants.pnpmPatchesFolderName}`; + const rushPnpmPatchesFolder: string = `${this._rushConfiguration.commonFolder}/pnpm-${RushConstants.pnpmPatchesFolderName}`; if (FileSystem.exists(rushPnpmPatchesFolder)) { FileSystem.copyFiles({ sourcePath: rushPnpmPatchesFolder, @@ -654,9 +654,9 @@ export abstract class BaseInstallManager { ) { this._terminal.writeWarningLine( 'Warning: Your rush.json specifies a pnpmVersion with a known issue ' + - 'that may cause unintended version selections.' + - " It's recommended to upgrade to PNPM >=6.34.0 or >=7.9.0. " + - 'For details see: https://rushjs.io/link/pnpm-issue-5132' + 'that may cause unintended version selections.' + + " It's recommended to upgrade to PNPM >=6.34.0 or >=7.9.0. " + + 'For details see: https://rushjs.io/link/pnpm-issue-5132' ); } if ( From b49169a35a6472daad1de2c2bb0373d4ff93c9f9 Mon Sep 17 00:00:00 2001 From: Pete Gonzalez <4673363+octogonz@users.noreply.github.com> Date: Mon, 28 Nov 2022 14:54:22 -0800 Subject: [PATCH 5/6] Update pnpm-config.json template file --- .../rush-init/common/config/rush/pnpm-config.json | 10 ++++++++++ libraries/rush-lib/src/schemas/pnpm-config.schema.json | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/libraries/rush-lib/assets/rush-init/common/config/rush/pnpm-config.json b/libraries/rush-lib/assets/rush-init/common/config/rush/pnpm-config.json index 0a6c37669e6..924e4ed6bb9 100644 --- a/libraries/rush-lib/assets/rush-init/common/config/rush/pnpm-config.json +++ b/libraries/rush-lib/assets/rush-init/common/config/rush/pnpm-config.json @@ -177,6 +177,16 @@ /*[LINE "HYPOTHETICAL"]*/ "request": "*" }, + + /** + * (THIS FIELD IS MACHINE GENERATED) The "globalPatchedDependencies" field is updated automatically + * by the `rush-pnpm patch-commit` command. It is a dictionary, where the key is an NPM package name + * and exact version, and the value is a relative path to the associated patch file. + * + * PNPM documentation: https://pnpm.io/package_json#pnpmpatcheddependencies + */ + "globalPatchedDependencies": { }, + /** * (USE AT YOUR OWN RISK) This is a free-form property bag that will be copied into * the `common/temp/package.json` file that is generated by Rush during installation. diff --git a/libraries/rush-lib/src/schemas/pnpm-config.schema.json b/libraries/rush-lib/src/schemas/pnpm-config.schema.json index 654434aace4..5adbe10e074 100644 --- a/libraries/rush-lib/src/schemas/pnpm-config.schema.json +++ b/libraries/rush-lib/src/schemas/pnpm-config.schema.json @@ -149,7 +149,7 @@ }, "globalPatchedDependencies": { - "description": "(GENERATED BY RUSH-PNPM PATCH-COMMIT) When modifying this property, make sure you know what you are doing.\n\nThe `globalPatchedDependencies` is added/updated automatically when you run rush-pnpm patch-commit command. It is a dictionary where the key should be the package name and exact version. The value should be a relative path to a patch file.\n\nPNPM documentation: https://pnpm.io/package_json#pnpmpatcheddependencies", + "description": "(THIS FIELD IS MACHINE GENERATED) The \"globalPatchedDependencies\" field is updated automatically by the `rush-pnpm patch-commit` command. It is a dictionary, where the key is an NPM package name and exact version, and the value is a relative path to the associated patch file.\n\nPNPM documentation: https://pnpm.io/package_json#pnpmpatcheddependencies", "type": "object", "additionalProperties": { "type": "string" From 673e584b755ac9bd5ff5f256664fe846842f2c12 Mon Sep 17 00:00:00 2001 From: Pete Gonzalez <4673363+octogonz@users.noreply.github.com> Date: Mon, 28 Nov 2022 15:01:11 -0800 Subject: [PATCH 6/6] Prepare for a MINOR release of Rush --- .../@microsoft/rush/feat-pnpm-patch_2022-07-27-03-39.json | 2 +- common/config/rush/version-policies.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/common/changes/@microsoft/rush/feat-pnpm-patch_2022-07-27-03-39.json b/common/changes/@microsoft/rush/feat-pnpm-patch_2022-07-27-03-39.json index baeae9fa2b6..427c8507823 100644 --- a/common/changes/@microsoft/rush/feat-pnpm-patch_2022-07-27-03-39.json +++ b/common/changes/@microsoft/rush/feat-pnpm-patch_2022-07-27-03-39.json @@ -2,7 +2,7 @@ "changes": [ { "packageName": "@microsoft/rush", - "comment": "rush-pnpm accepts patch and patch-commit", + "comment": "Add new commands \"rush-pnpm patch\" and \"rush-pnpm patch-commit\" for patching NPM packages when using the PNPM package manager (GitHub #3554)", "type": "none" } ], diff --git a/common/config/rush/version-policies.json b/common/config/rush/version-policies.json index 0ba2558f681..c6201947c8b 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.85.1", - "nextBump": "patch", + "nextBump": "minor", "mainProject": "@microsoft/rush" } ]