From 36fae081a218adef603d4612bbb7533e1067c7ea Mon Sep 17 00:00:00 2001 From: Daniel Nadeau <3473356+D4N14L@users.noreply.github.com> Date: Wed, 14 Apr 2021 10:40:10 -0700 Subject: [PATCH 1/9] Remove rewriting of integrity hash when using frozen lockfile for legacy Rush pnpm installs, and instead verify the hash --- .../src/logic/base/BaseInstallManager.ts | 18 +-- .../src/logic/base/BaseShrinkwrapFile.ts | 8 -- .../installManager/RushInstallManager.ts | 106 +++++++----------- 3 files changed, 51 insertions(+), 81 deletions(-) diff --git a/apps/rush-lib/src/logic/base/BaseInstallManager.ts b/apps/rush-lib/src/logic/base/BaseInstallManager.ts index 834431e0a5..f346dc4759 100644 --- a/apps/rush-lib/src/logic/base/BaseInstallManager.ts +++ b/apps/rush-lib/src/logic/base/BaseInstallManager.ts @@ -220,12 +220,7 @@ export abstract class BaseInstallManager { // Perform the actual install await this.installAsync(cleanInstall); - const usePnpmFrozenLockfile: boolean = - this._rushConfiguration.packageManager === 'pnpm' && - this._rushConfiguration.experimentsConfiguration.configuration.usePnpmFrozenLockfileForRushInstall === - true; - - if (this.options.allowShrinkwrapUpdates && (usePnpmFrozenLockfile || !shrinkwrapIsUpToDate)) { + if (this.options.allowShrinkwrapUpdates && !shrinkwrapIsUpToDate) { // Copy (or delete) common\temp\pnpm-lock.yaml --> common\config\rush\pnpm-lock.yaml Utilities.syncFile( this._rushConfiguration.tempShrinkwrapFilename, @@ -649,9 +644,14 @@ export abstract class BaseInstallManager { private _syncTempShrinkwrap(shrinkwrapFile: BaseShrinkwrapFile | undefined): void { if (shrinkwrapFile) { - // If we have a (possibly incomplete) shrinkwrap file, save it as the temporary file. - shrinkwrapFile.save(this.rushConfiguration.tempShrinkwrapFilename); - shrinkwrapFile.save(this.rushConfiguration.tempShrinkwrapPreinstallFilename); + Utilities.syncFile( + this._rushConfiguration.getCommittedShrinkwrapFilename(this.options.variant), + this.rushConfiguration.tempShrinkwrapFilename + ); + Utilities.syncFile( + this._rushConfiguration.getCommittedShrinkwrapFilename(this.options.variant), + this.rushConfiguration.tempShrinkwrapPreinstallFilename + ); } else { // Otherwise delete the temporary file FileSystem.deleteFile(this.rushConfiguration.tempShrinkwrapFilename); diff --git a/apps/rush-lib/src/logic/base/BaseShrinkwrapFile.ts b/apps/rush-lib/src/logic/base/BaseShrinkwrapFile.ts index 78875a976b..8e96831606 100644 --- a/apps/rush-lib/src/logic/base/BaseShrinkwrapFile.ts +++ b/apps/rush-lib/src/logic/base/BaseShrinkwrapFile.ts @@ -3,7 +3,6 @@ import colors from 'colors/safe'; import * as semver from 'semver'; -import { FileSystem } from '@rushstack/node-core-library'; import { RushConstants } from '../../logic/RushConstants'; import { DependencySpecifier, DependencySpecifierType } from '../DependencySpecifier'; @@ -25,13 +24,6 @@ export abstract class BaseShrinkwrapFile { return undefined; } - /** - * Serializes and saves the shrinkwrap file to specified location - */ - public save(filePath: string): void { - FileSystem.writeFile(filePath, this.serialize()); - } - /** * Validate the shrinkwrap using the provided policy options. * diff --git a/apps/rush-lib/src/logic/installManager/RushInstallManager.ts b/apps/rush-lib/src/logic/installManager/RushInstallManager.ts index 338648b691..04b74987e9 100644 --- a/apps/rush-lib/src/logic/installManager/RushInstallManager.ts +++ b/apps/rush-lib/src/logic/installManager/RushInstallManager.ts @@ -69,28 +69,6 @@ export class RushInstallManager extends BaseInstallManager { this._tempProjectHelper = new TempProjectHelper(this.rushConfiguration); } - protected async prepareAsync(): Promise<{ variantIsUpToDate: boolean; shrinkwrapIsUpToDate: boolean }> { - const result: { variantIsUpToDate: boolean; shrinkwrapIsUpToDate: boolean } = await super.prepareAsync(); - - // We have already done prep work to ensure that the package.json files are "up to date". Some changes - // (such as local package version bumps, or adding a reference to another existing local package) do - // not need a "rush update" to be run, and as such can be changed manually in the temp shrinkwrap. These - // changes will eventually be picked up during a "rush update". - if ( - this.rushConfiguration.packageManager === 'pnpm' && - !this.options.allowShrinkwrapUpdates && - this.rushConfiguration.experimentsConfiguration.configuration.usePnpmFrozenLockfileForRushInstall - ) { - const tempShrinkwrap: PnpmShrinkwrapFile | undefined = PnpmShrinkwrapFile.loadFromFile( - this.rushConfiguration.tempShrinkwrapFilename, - this.rushConfiguration.pnpmOptions - ); - await this._updatePnpmShrinkwrapTarballIntegritiesAsync(tempShrinkwrap); - } - - return result; - } - /** * Regenerates the common/package.json and all temp_modules projects. * If shrinkwrapFile is provided, this function also validates whether it contains @@ -344,14 +322,23 @@ export class RushInstallManager extends BaseInstallManager { } } - // Remove the workspace file if it exists - if (this.rushConfiguration.packageManager === 'pnpm') { - const workspaceFilePath: string = path.join( - this.rushConfiguration.commonTempFolder, - 'pnpm-workspace.yaml' + // When using frozen shrinkwrap, we need to validate that the tarball integrities are up-to-date + // with the shrinkwrap file, since these will cause install to fail. + if ( + shrinkwrapFile && + this.rushConfiguration.packageManager === 'pnpm' && + this.rushConfiguration.experimentsConfiguration.configuration.usePnpmFrozenLockfileForRushInstall + ) { + const pnpmShrinkwrapFile: PnpmShrinkwrapFile = shrinkwrapFile as PnpmShrinkwrapFile; + const tarballIntegrityValid: boolean = await this._validateRushProjectTarballIntegrityAsync( + pnpmShrinkwrapFile, + rushProject ); - if (FileSystem.exists(workspaceFilePath)) { - FileSystem.deleteFile(workspaceFilePath); + if (!tarballIntegrityValid) { + shrinkwrapIsUpToDate = false; + shrinkwrapWarnings.push( + `Invalid or missing tarball integrity hash in shrinkwrap for "${rushProject.packageName}"` + ); } } @@ -366,6 +353,17 @@ export class RushInstallManager extends BaseInstallManager { } } + // Remove the workspace file if it exists + if (this.rushConfiguration.packageManager === 'pnpm') { + const workspaceFilePath: string = path.join( + this.rushConfiguration.commonTempFolder, + 'pnpm-workspace.yaml' + ); + if (FileSystem.exists(workspaceFilePath)) { + FileSystem.deleteFile(workspaceFilePath); + } + } + // Write the common package.json InstallHelpers.generateCommonPackageJson(this.rushConfiguration, commonDependencies); @@ -396,54 +394,34 @@ export class RushInstallManager extends BaseInstallManager { return true; } - private async _updatePnpmShrinkwrapTarballIntegritiesAsync( - tempShrinkwrapFile: PnpmShrinkwrapFile | undefined - ): Promise { - if (!tempShrinkwrapFile) { - return; - } - - const tempProjectHelper: TempProjectHelper = new TempProjectHelper(this.rushConfiguration); - - console.log( - `Checking shrinkwrap local dependency tarball hashes in ${tempShrinkwrapFile.shrinkwrapFilename}` - ); + private async _validateRushProjectTarballIntegrityAsync( + shrinkwrapFile: PnpmShrinkwrapFile | undefined, + rushProject: RushConfigurationProject + ): Promise { + if (shrinkwrapFile) { + console.log( + `Checking shrinkwrap local dependency tarball hashes in ${shrinkwrapFile.shrinkwrapFilename}` + ); - let shrinkwrapFileUpdated: boolean = false; - for (const rushProject of this.rushConfiguration.projects) { - const tempProjectDependencyKey: string | undefined = tempShrinkwrapFile.getTempProjectDependencyKey( + const tempProjectDependencyKey: string | undefined = shrinkwrapFile.getTempProjectDependencyKey( rushProject.tempProjectName ); - if (!tempProjectDependencyKey) { - throw new Error(`Cannot get dependency key for temp project: ${rushProject.tempProjectName}`); + return false; } - const parentShrinkwrapEntry: - | IPnpmShrinkwrapDependencyYaml - | undefined = tempShrinkwrapFile.getShrinkwrapEntryFromTempProjectDependencyKey( + const parentShrinkwrapEntry: IPnpmShrinkwrapDependencyYaml = shrinkwrapFile.getShrinkwrapEntryFromTempProjectDependencyKey( tempProjectDependencyKey - ); - if (!parentShrinkwrapEntry) { - throw new InternalError( - `Cannot find shrinkwrap entry using dependency key for temp project: ${rushProject.tempProjectName}` - ); - } - + )!; const newIntegrity: string = ( - await ssri.fromStream(fs.createReadStream(tempProjectHelper.getTarballFilePath(rushProject))) + await ssri.fromStream(fs.createReadStream(this._tempProjectHelper.getTarballFilePath(rushProject))) ).toString(); if (parentShrinkwrapEntry.resolution.integrity !== newIntegrity) { - shrinkwrapFileUpdated = true; - parentShrinkwrapEntry.resolution.integrity = newIntegrity; + return false; } } - - tempShrinkwrapFile.save(tempShrinkwrapFile.shrinkwrapFilename); - if (shrinkwrapFileUpdated) { - console.log('Shrinkwrap local dependency tarball hashes were updated.'); - } + return true; } /** From 8afd9630cf529119e0ce8d439d89a1e970c63ecf Mon Sep 17 00:00:00 2001 From: Daniel Nadeau <3473356+D4N14L@users.noreply.github.com> Date: Wed, 14 Apr 2021 11:33:20 -0700 Subject: [PATCH 2/9] Fix linking stage in PNPM 6 --- .../src/logic/pnpm/PnpmLinkManager.ts | 23 +++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/apps/rush-lib/src/logic/pnpm/PnpmLinkManager.ts b/apps/rush-lib/src/logic/pnpm/PnpmLinkManager.ts index 7780529a48..5b74e9b48a 100644 --- a/apps/rush-lib/src/logic/pnpm/PnpmLinkManager.ts +++ b/apps/rush-lib/src/logic/pnpm/PnpmLinkManager.ts @@ -222,11 +222,17 @@ export class PnpmLinkManager extends BaseLinkManager { // C%3A%2Fwbt%2Fcommon%2Ftemp%2Fprojects%2Fapi-documenter.tgz // C%3A%2Fdev%2Fimodeljs%2Fimodeljs%2Fcommon%2Ftemp%2Fprojects%2Fpresentation-integration-tests.tgz_jsdom@11.12.0 // C%3A%2Fdev%2Fimodeljs%2Fimodeljs%2Fcommon%2Ftemp%2Fprojects%2Fbuild-tools.tgz_2a665c89609864b4e75bc5365d7f8f56 - const folderNameInLocalInstallationRoot: string = + let folderNameInLocalInstallationRoot: string = uriEncode(Text.replaceAll(absolutePathToTgzFile, path.sep, '/')) + folderNameSuffix; - // e.g.: C:\wbt\common\temp\node_modules\.local\C%3A%2Fwbt%2Fcommon%2Ftemp%2Fprojects%2Fapi-documenter.tgz\node_modules + // PNPM 6 changed formatting to replace all special chars with '+' + // e.g.: C++dev+imodeljs+imodeljs+common+temp+projects+presentation-integration-tests.tgz_jsdom@11.12.0 + if (this._pnpmVersion.major >= 6) { + const specialCharRegex: RegExp = /%[a-fA-FA-F0-9]{2}/g; + folderNameInLocalInstallationRoot = folderNameInLocalInstallationRoot.replace(specialCharRegex, '+'); + } + // e.g.: C:\wbt\common\temp\node_modules\.local\C%3A%2Fwbt%2Fcommon%2Ftemp%2Fprojects%2Fapi-documenter.tgz\node_modules const pathToLocalInstallation: string = this._getPathToLocalInstallation( folderNameInLocalInstallationRoot ); @@ -300,8 +306,17 @@ export class PnpmLinkManager extends BaseLinkManager { } private _getPathToLocalInstallation(folderNameInLocalInstallationRoot: string): string { - // See https://github.com/pnpm/pnpm/releases/tag/v4.0.0 - if (this._pnpmVersion.major >= 4) { + if (this._pnpmVersion.major >= 6) { + // See https://github.com/pnpm/pnpm/releases/tag/v6.0.0 + return path.join( + this._rushConfiguration.commonTempFolder, + RushConstants.nodeModulesFolderName, + '.pnpm', + `local+${folderNameInLocalInstallationRoot}`, + RushConstants.nodeModulesFolderName + ); + } else if (this._pnpmVersion.major >= 4) { + // See https://github.com/pnpm/pnpm/releases/tag/v4.0.0 return path.join( this._rushConfiguration.commonTempFolder, RushConstants.nodeModulesFolderName, From 02f5f404c22f7e01c7bc04f9bc84eab55dc186f0 Mon Sep 17 00:00:00 2001 From: Daniel Nadeau <3473356+D4N14L@users.noreply.github.com> Date: Wed, 14 Apr 2021 12:12:20 -0700 Subject: [PATCH 3/9] Move shrinkwrap copy to before warnings get logged --- apps/rush-lib/src/logic/base/BaseInstallManager.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/rush-lib/src/logic/base/BaseInstallManager.ts b/apps/rush-lib/src/logic/base/BaseInstallManager.ts index f346dc4759..a8198b91bb 100644 --- a/apps/rush-lib/src/logic/base/BaseInstallManager.ts +++ b/apps/rush-lib/src/logic/base/BaseInstallManager.ts @@ -398,6 +398,8 @@ export abstract class BaseInstallManager { let { shrinkwrapIsUpToDate, shrinkwrapWarnings } = await this.prepareCommonTempAsync(shrinkwrapFile); shrinkwrapIsUpToDate = shrinkwrapIsUpToDate && !this.options.recheckShrinkwrap; + this._syncTempShrinkwrap(shrinkwrapFile); + // Write out the reported warnings if (shrinkwrapWarnings.length > 0) { console.log(); @@ -415,8 +417,6 @@ export abstract class BaseInstallManager { console.log(); } - this._syncTempShrinkwrap(shrinkwrapFile); - // Force update if the shrinkwrap is out of date if (!shrinkwrapIsUpToDate) { if (!this.options.allowShrinkwrapUpdates) { From 2cdee2bcdc54fabf9cbc22896c8ef450abe71d6e Mon Sep 17 00:00:00 2001 From: Daniel Nadeau <3473356+D4N14L@users.noreply.github.com> Date: Wed, 14 Apr 2021 14:17:45 -0700 Subject: [PATCH 4/9] Remove shrinkwrap churn optimization --- .../src/logic/base/BaseShrinkwrapFile.ts | 9 ++-- .../installManager/RushInstallManager.ts | 29 ++++-------- .../src/logic/pnpm/PnpmShrinkwrapFile.ts | 46 +++---------------- .../src/logic/test/ShrinkwrapFile.test.ts | 9 ---- 4 files changed, 17 insertions(+), 76 deletions(-) diff --git a/apps/rush-lib/src/logic/base/BaseShrinkwrapFile.ts b/apps/rush-lib/src/logic/base/BaseShrinkwrapFile.ts index 8e96831606..2e7c58fe3c 100644 --- a/apps/rush-lib/src/logic/base/BaseShrinkwrapFile.ts +++ b/apps/rush-lib/src/logic/base/BaseShrinkwrapFile.ts @@ -73,13 +73,11 @@ export abstract class BaseShrinkwrapFile { */ public tryEnsureCompatibleDependency( dependencySpecifier: DependencySpecifier, - tempProjectName: string, - tryReusingPackageVersionsFromShrinkwrap: boolean = true + tempProjectName: string ): boolean { const shrinkwrapDependency: DependencySpecifier | undefined = this.tryEnsureDependencyVersion( dependencySpecifier, - tempProjectName, - tryReusingPackageVersionsFromShrinkwrap + tempProjectName ); if (!shrinkwrapDependency) { return false; @@ -99,8 +97,7 @@ export abstract class BaseShrinkwrapFile { /** @virtual */ protected abstract tryEnsureDependencyVersion( dependencySpecifier: DependencySpecifier, - tempProjectName: string, - tryReusingPackageVersionsFromShrinkwrap: boolean + tempProjectName: string ): DependencySpecifier | undefined; /** @virtual */ diff --git a/apps/rush-lib/src/logic/installManager/RushInstallManager.ts b/apps/rush-lib/src/logic/installManager/RushInstallManager.ts index 04b74987e9..51f2dcd75e 100644 --- a/apps/rush-lib/src/logic/installManager/RushInstallManager.ts +++ b/apps/rush-lib/src/logic/installManager/RushInstallManager.ts @@ -249,27 +249,14 @@ export class RushInstallManager extends BaseInstallManager { // We will NOT locally link this package; add it as a regular dependency. tempPackageJson.dependencies![packageName] = packageVersion; - let tryReusingPackageVersionsFromShrinkwrap: boolean = true; - - if (this.rushConfiguration.packageManager === 'pnpm') { - // Shrinkwrap churn optimization doesn't make sense when --frozen-lockfile is true - tryReusingPackageVersionsFromShrinkwrap = !this.rushConfiguration.experimentsConfiguration - .configuration.usePnpmFrozenLockfileForRushInstall; - } - - if (shrinkwrapFile) { - if ( - !shrinkwrapFile.tryEnsureCompatibleDependency( - dependencySpecifier, - rushProject.tempProjectName, - tryReusingPackageVersionsFromShrinkwrap - ) - ) { - shrinkwrapWarnings.push( - `Missing dependency "${packageName}" (${packageVersion}) required by "${rushProject.packageName}"` - ); - shrinkwrapIsUpToDate = false; - } + if ( + shrinkwrapFile && + !shrinkwrapFile.tryEnsureCompatibleDependency(dependencySpecifier, rushProject.tempProjectName) + ) { + shrinkwrapWarnings.push( + `Missing dependency "${packageName}" (${packageVersion}) required by "${rushProject.packageName}"` + ); + shrinkwrapIsUpToDate = false; } } diff --git a/apps/rush-lib/src/logic/pnpm/PnpmShrinkwrapFile.ts b/apps/rush-lib/src/logic/pnpm/PnpmShrinkwrapFile.ts index 15381df288..71eb1ae9a9 100644 --- a/apps/rush-lib/src/logic/pnpm/PnpmShrinkwrapFile.ts +++ b/apps/rush-lib/src/logic/pnpm/PnpmShrinkwrapFile.ts @@ -458,8 +458,7 @@ export class PnpmShrinkwrapFile extends BaseShrinkwrapFile { */ protected tryEnsureDependencyVersion( dependencySpecifier: DependencySpecifier, - tempProjectName: string, - tryReusingPackageVersionsFromShrinkwrap: boolean + tempProjectName: string ): DependencySpecifier | undefined { // PNPM doesn't have the same advantage of NPM, where we can skip generate as long as the // shrinkwrap file puts our dependency in either the top of the node_modules folder @@ -479,44 +478,11 @@ export class PnpmShrinkwrapFile extends BaseShrinkwrapFile { const packageDescription: IPnpmShrinkwrapDependencyYaml | undefined = this._getPackageDescription( tempProjectDependencyKey ); - if (!packageDescription || !packageDescription.dependencies) { - return undefined; - } - - if (!packageDescription.dependencies.hasOwnProperty(packageName)) { - if (tryReusingPackageVersionsFromShrinkwrap && dependencySpecifier.versionSpecifier) { - // this means the current temp project doesn't provide this dependency, - // however, we may be able to use a different version. we prefer the latest version - let latestVersion: string | undefined = undefined; - - for (const otherTempProject of this.getTempProjectNames()) { - const otherVersionSpecifier: DependencySpecifier | undefined = this._getDependencyVersion( - dependencySpecifier.packageName, - otherTempProject - ); - - if (otherVersionSpecifier) { - const otherVersion: string = otherVersionSpecifier.versionSpecifier; - - if (semver.satisfies(otherVersion, dependencySpecifier.versionSpecifier)) { - if (!latestVersion || semver.gt(otherVersion, latestVersion)) { - latestVersion = otherVersion; - } - } - } - } - - if (latestVersion) { - // go ahead and fixup the shrinkwrap file to point at this - const dependencies: { [key: string]: string } | undefined = - this._shrinkwrapJson.packages[tempProjectDependencyKey].dependencies || {}; - dependencies[packageName] = latestVersion; - this._shrinkwrapJson.packages[tempProjectDependencyKey].dependencies = dependencies; - - return new DependencySpecifier(dependencySpecifier.packageName, latestVersion); - } - } - + if ( + !packageDescription || + !packageDescription.dependencies || + !packageDescription.dependencies.hasOwnProperty(packageName) + ) { return undefined; } diff --git a/apps/rush-lib/src/logic/test/ShrinkwrapFile.test.ts b/apps/rush-lib/src/logic/test/ShrinkwrapFile.test.ts index 5ac880b372..86a511b27c 100644 --- a/apps/rush-lib/src/logic/test/ShrinkwrapFile.test.ts +++ b/apps/rush-lib/src/logic/test/ShrinkwrapFile.test.ts @@ -86,15 +86,6 @@ describe('pnpm ShrinkwrapFile', () => { expect(tempProjectNames).toEqual(['@rush-temp/project1', '@rush-temp/project2', '@rush-temp/project3']); }); - - it('can reuse the latest version that another temp package is providing', () => { - expect( - shrinkwrapFile.tryEnsureCompatibleDependency( - new DependencySpecifier('jquery', '>=2.0.0 <3.0.0'), - '@rush-temp/project3' - ) - ).toEqual(true); - }); }); function testParsePnpmDependencyKey(packageName: string, key: string): string | undefined { From 88065b0a6857298250740a4ecf8eef376e861a76 Mon Sep 17 00:00:00 2001 From: Daniel Nadeau <3473356+D4N14L@users.noreply.github.com> Date: Wed, 14 Apr 2021 14:22:12 -0700 Subject: [PATCH 5/9] Rush change --- .../rush/user-danade-pnpm6_2021-04-14-21-22.json | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 common/changes/@microsoft/rush/user-danade-pnpm6_2021-04-14-21-22.json diff --git a/common/changes/@microsoft/rush/user-danade-pnpm6_2021-04-14-21-22.json b/common/changes/@microsoft/rush/user-danade-pnpm6_2021-04-14-21-22.json new file mode 100644 index 0000000000..830d9b2c35 --- /dev/null +++ b/common/changes/@microsoft/rush/user-danade-pnpm6_2021-04-14-21-22.json @@ -0,0 +1,11 @@ +{ + "changes": [ + { + "packageName": "@microsoft/rush", + "comment": "Add support for PNPM 6", + "type": "none" + } + ], + "packageName": "@microsoft/rush", + "email": "3473356+D4N14L@users.noreply.github.com" +} \ No newline at end of file From b1fe619e0988dcd706ae078aee236aea59c4291b Mon Sep 17 00:00:00 2001 From: Daniel <3473356+D4N14L@users.noreply.github.com> Date: Fri, 16 Apr 2021 17:36:55 -0700 Subject: [PATCH 6/9] Optimize workspace file delete Co-authored-by: Ian Clanton-Thuon --- .../src/logic/installManager/RushInstallManager.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/apps/rush-lib/src/logic/installManager/RushInstallManager.ts b/apps/rush-lib/src/logic/installManager/RushInstallManager.ts index 51f2dcd75e..9e6b6bb9fc 100644 --- a/apps/rush-lib/src/logic/installManager/RushInstallManager.ts +++ b/apps/rush-lib/src/logic/installManager/RushInstallManager.ts @@ -346,8 +346,12 @@ export class RushInstallManager extends BaseInstallManager { this.rushConfiguration.commonTempFolder, 'pnpm-workspace.yaml' ); - if (FileSystem.exists(workspaceFilePath)) { - FileSystem.deleteFile(workspaceFilePath); + try { + await FileSystem.deleteFileAsync(workspaceFilePath); + } catch (e) { + if (!FileSystem.isNotExistError(e)) { + throw e; + } } } From 533c8b16fabad2ca84123f4c9a3c9e3963c7fa88 Mon Sep 17 00:00:00 2001 From: Daniel Nadeau <3473356+D4N14L@users.noreply.github.com> Date: Fri, 16 Apr 2021 17:41:20 -0700 Subject: [PATCH 7/9] Use path method to do slash conversion --- apps/rush-lib/src/logic/pnpm/PnpmLinkManager.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/rush-lib/src/logic/pnpm/PnpmLinkManager.ts b/apps/rush-lib/src/logic/pnpm/PnpmLinkManager.ts index 5b74e9b48a..0f40505326 100644 --- a/apps/rush-lib/src/logic/pnpm/PnpmLinkManager.ts +++ b/apps/rush-lib/src/logic/pnpm/PnpmLinkManager.ts @@ -9,11 +9,11 @@ import * as semver from 'semver'; import colors from 'colors/safe'; import { - Text, + AlreadyReportedError, FileSystem, FileConstants, InternalError, - AlreadyReportedError + Path } from '@rushstack/node-core-library'; import { BaseLinkManager } from '../base/BaseLinkManager'; @@ -223,7 +223,7 @@ export class PnpmLinkManager extends BaseLinkManager { // C%3A%2Fdev%2Fimodeljs%2Fimodeljs%2Fcommon%2Ftemp%2Fprojects%2Fpresentation-integration-tests.tgz_jsdom@11.12.0 // C%3A%2Fdev%2Fimodeljs%2Fimodeljs%2Fcommon%2Ftemp%2Fprojects%2Fbuild-tools.tgz_2a665c89609864b4e75bc5365d7f8f56 let folderNameInLocalInstallationRoot: string = - uriEncode(Text.replaceAll(absolutePathToTgzFile, path.sep, '/')) + folderNameSuffix; + uriEncode(Path.convertToSlashes(absolutePathToTgzFile)) + folderNameSuffix; // PNPM 6 changed formatting to replace all special chars with '+' // e.g.: C++dev+imodeljs+imodeljs+common+temp+projects+presentation-integration-tests.tgz_jsdom@11.12.0 From 702106d54bcffeaf4584a118ed65220fc96c119f Mon Sep 17 00:00:00 2001 From: Daniel Nadeau <3473356+D4N14L@users.noreply.github.com> Date: Mon, 19 Apr 2021 20:45:12 -0700 Subject: [PATCH 8/9] Use new local installation root folder path format logic --- .../src/logic/pnpm/PnpmLinkManager.ts | 83 +++++++++++-------- 1 file changed, 49 insertions(+), 34 deletions(-) diff --git a/apps/rush-lib/src/logic/pnpm/PnpmLinkManager.ts b/apps/rush-lib/src/logic/pnpm/PnpmLinkManager.ts index 0f40505326..e1523353bb 100644 --- a/apps/rush-lib/src/logic/pnpm/PnpmLinkManager.ts +++ b/apps/rush-lib/src/logic/pnpm/PnpmLinkManager.ts @@ -3,6 +3,7 @@ import * as os from 'os'; import * as path from 'path'; +import * as crypto from 'crypto'; import uriEncode = require('strict-uri-encode'); import pnpmLinkBins from '@pnpm/link-bins'; import * as semver from 'semver'; @@ -218,23 +219,10 @@ export class PnpmLinkManager extends BaseLinkManager { ? tempProjectDependencyKey.slice(tarballEntry.length) : ''; - // e.g.: - // C%3A%2Fwbt%2Fcommon%2Ftemp%2Fprojects%2Fapi-documenter.tgz - // C%3A%2Fdev%2Fimodeljs%2Fimodeljs%2Fcommon%2Ftemp%2Fprojects%2Fpresentation-integration-tests.tgz_jsdom@11.12.0 - // C%3A%2Fdev%2Fimodeljs%2Fimodeljs%2Fcommon%2Ftemp%2Fprojects%2Fbuild-tools.tgz_2a665c89609864b4e75bc5365d7f8f56 - let folderNameInLocalInstallationRoot: string = - uriEncode(Path.convertToSlashes(absolutePathToTgzFile)) + folderNameSuffix; - - // PNPM 6 changed formatting to replace all special chars with '+' - // e.g.: C++dev+imodeljs+imodeljs+common+temp+projects+presentation-integration-tests.tgz_jsdom@11.12.0 - if (this._pnpmVersion.major >= 6) { - const specialCharRegex: RegExp = /%[a-fA-FA-F0-9]{2}/g; - folderNameInLocalInstallationRoot = folderNameInLocalInstallationRoot.replace(specialCharRegex, '+'); - } - // e.g.: C:\wbt\common\temp\node_modules\.local\C%3A%2Fwbt%2Fcommon%2Ftemp%2Fprojects%2Fapi-documenter.tgz\node_modules const pathToLocalInstallation: string = this._getPathToLocalInstallation( - folderNameInLocalInstallationRoot + absolutePathToTgzFile, + folderNameSuffix ); const parentShrinkwrapEntry: @@ -305,34 +293,61 @@ export class PnpmLinkManager extends BaseLinkManager { }); } - private _getPathToLocalInstallation(folderNameInLocalInstallationRoot: string): string { + private _getPathToLocalInstallation(absolutePathToTgzFile: string, folderSuffix: string): string { if (this._pnpmVersion.major >= 6) { + // PNPM 6 changed formatting to replace all ':' and '/' chars with '+'. Additionally, folder names > 120 + // are trimmed and hashed. NOTE: PNPM internally uses fs.realpath.native, which will cause additional + // issues in environments that do not support long paths. // See https://github.com/pnpm/pnpm/releases/tag/v6.0.0 + // e.g.: + // C++dev+imodeljs+imodeljs+common+temp+projects+presentation-integration-tests.tgz_jsdom@11.12.0 + // C++dev+imodeljs+imodeljs+common+temp+projects+presentation-integrat_089eb799caf0f998ab34e4e1e9254956 + const specialCharRegex: RegExp = /\/|:/g; + let folderName: string = `local+${Path.convertToSlashes(absolutePathToTgzFile).replace( + specialCharRegex, + '+' + )}${folderSuffix}`; + if (folderName.length > 120) { + folderName = `${folderName.substring(0, 50)}_${crypto + .createHash('md5') + .update(folderName) + .digest('hex')}`; + } + return path.join( this._rushConfiguration.commonTempFolder, RushConstants.nodeModulesFolderName, '.pnpm', - `local+${folderNameInLocalInstallationRoot}`, - RushConstants.nodeModulesFolderName - ); - } else if (this._pnpmVersion.major >= 4) { - // See https://github.com/pnpm/pnpm/releases/tag/v4.0.0 - return path.join( - this._rushConfiguration.commonTempFolder, - RushConstants.nodeModulesFolderName, - '.pnpm', - 'local', - folderNameInLocalInstallationRoot, + folderName, RushConstants.nodeModulesFolderName ); } else { - return path.join( - this._rushConfiguration.commonTempFolder, - RushConstants.nodeModulesFolderName, - '.local', - folderNameInLocalInstallationRoot, - RushConstants.nodeModulesFolderName - ); + // e.g.: + // C%3A%2Fwbt%2Fcommon%2Ftemp%2Fprojects%2Fapi-documenter.tgz + // C%3A%2Fdev%2Fimodeljs%2Fimodeljs%2Fcommon%2Ftemp%2Fprojects%2Fpresentation-integration-tests.tgz_jsdom@11.12.0 + // C%3A%2Fdev%2Fimodeljs%2Fimodeljs%2Fcommon%2Ftemp%2Fprojects%2Fbuild-tools.tgz_2a665c89609864b4e75bc5365d7f8f56 + const folderNameInLocalInstallationRoot: string = + uriEncode(Path.convertToSlashes(absolutePathToTgzFile)) + folderSuffix; + + if (this._pnpmVersion.major >= 4) { + // See https://github.com/pnpm/pnpm/releases/tag/v4.0.0 + return path.join( + this._rushConfiguration.commonTempFolder, + RushConstants.nodeModulesFolderName, + '.pnpm', + 'local', + folderNameInLocalInstallationRoot, + RushConstants.nodeModulesFolderName + ); + } else { + return path.join( + this._rushConfiguration.commonTempFolder, + RushConstants.nodeModulesFolderName, + '.local', + folderNameInLocalInstallationRoot, + RushConstants.nodeModulesFolderName + ); + } } } private _createLocalPackageForDependency( From c8f72c366bc8b805178783e719ec6274524fa155 Mon Sep 17 00:00:00 2001 From: Daniel Nadeau <3473356+D4N14L@users.noreply.github.com> Date: Mon, 19 Apr 2021 21:36:16 -0700 Subject: [PATCH 9/9] Logging updates --- apps/rush-lib/src/logic/base/BaseInstallManager.ts | 6 ++++++ apps/rush-lib/src/logic/base/BaseLinkManager.ts | 2 +- .../src/logic/installManager/InstallHelpers.ts | 4 ++-- .../src/logic/installManager/RushInstallManager.ts | 12 ------------ .../installManager/WorkspaceInstallManager.ts | 14 -------------- apps/rush-lib/src/scripts/install-run.ts | 3 ++- apps/rush-lib/src/utilities/Utilities.ts | 6 ++++-- 7 files changed, 15 insertions(+), 32 deletions(-) diff --git a/apps/rush-lib/src/logic/base/BaseInstallManager.ts b/apps/rush-lib/src/logic/base/BaseInstallManager.ts index a8198b91bb..f39b8ced3d 100644 --- a/apps/rush-lib/src/logic/base/BaseInstallManager.ts +++ b/apps/rush-lib/src/logic/base/BaseInstallManager.ts @@ -178,6 +178,10 @@ export abstract class BaseInstallManager { const { shrinkwrapIsUpToDate, variantIsUpToDate } = await this.prepareAsync(); + console.log( + os.EOL + colors.bold(`Checking installation in "${this.rushConfiguration.commonTempFolder}"`) + ); + // This marker file indicates that the last "rush install" completed successfully. // Always perform a clean install if filter flags were provided. Additionally, if // "--purge" was specified, or if the last install was interrupted, then we will @@ -245,6 +249,8 @@ export abstract class BaseInstallManager { if (!isFilteredInstall) { this._commonTempInstallFlag.create(); } + } else { + console.log('Installation is already up-to-date.'); } // Perform any post-install work the install manager requires diff --git a/apps/rush-lib/src/logic/base/BaseLinkManager.ts b/apps/rush-lib/src/logic/base/BaseLinkManager.ts index ace4465b22..7f264fae7b 100644 --- a/apps/rush-lib/src/logic/base/BaseLinkManager.ts +++ b/apps/rush-lib/src/logic/base/BaseLinkManager.ts @@ -186,7 +186,7 @@ export abstract class BaseLinkManager { * if true, this option forces the links to be recreated. */ public async createSymlinksForProjects(force: boolean): Promise { - console.log('Linking projects together...'); + console.log(os.EOL + colors.bold('Linking local projects')); const stopwatch: Stopwatch = Stopwatch.start(); await this._linkProjects(); diff --git a/apps/rush-lib/src/logic/installManager/InstallHelpers.ts b/apps/rush-lib/src/logic/installManager/InstallHelpers.ts index 0358208dbb..eceed5c7da 100644 --- a/apps/rush-lib/src/logic/installManager/InstallHelpers.ts +++ b/apps/rush-lib/src/logic/installManager/InstallHelpers.ts @@ -236,8 +236,8 @@ export class InstallHelpers { `${packageManager}-local` ); - console.log(os.EOL + 'Symlinking "' + localPackageManagerToolFolder + '"'); - console.log(' --> "' + packageManagerToolFolder + '"'); + console.log(os.EOL + `Symlinking "${localPackageManagerToolFolder}"`); + console.log(` --> "${packageManagerToolFolder}"`); // We cannot use FileSystem.exists() to test the existence of a symlink, because it will // return false for broken symlinks. There is no way to test without catching an exception. diff --git a/apps/rush-lib/src/logic/installManager/RushInstallManager.ts b/apps/rush-lib/src/logic/installManager/RushInstallManager.ts index 9e6b6bb9fc..f7d7cea627 100644 --- a/apps/rush-lib/src/logic/installManager/RushInstallManager.ts +++ b/apps/rush-lib/src/logic/installManager/RushInstallManager.ts @@ -390,10 +390,6 @@ export class RushInstallManager extends BaseInstallManager { rushProject: RushConfigurationProject ): Promise { if (shrinkwrapFile) { - console.log( - `Checking shrinkwrap local dependency tarball hashes in ${shrinkwrapFile.shrinkwrapFilename}` - ); - const tempProjectDependencyKey: string | undefined = shrinkwrapFile.getTempProjectDependencyKey( rushProject.tempProjectName ); @@ -421,14 +417,6 @@ export class RushInstallManager extends BaseInstallManager { * @override */ protected canSkipInstall(lastModifiedDate: Date): boolean { - console.log( - os.EOL + - colors.bold( - `Checking ${RushConstants.nodeModulesFolderName} in ${this.rushConfiguration.commonTempFolder}` - ) + - os.EOL - ); - // Based on timestamps, can we skip this install entirely? const potentiallyChangedFiles: string[] = []; diff --git a/apps/rush-lib/src/logic/installManager/WorkspaceInstallManager.ts b/apps/rush-lib/src/logic/installManager/WorkspaceInstallManager.ts index c75028e8b6..c9ab4bd9e1 100644 --- a/apps/rush-lib/src/logic/installManager/WorkspaceInstallManager.ts +++ b/apps/rush-lib/src/logic/installManager/WorkspaceInstallManager.ts @@ -21,7 +21,6 @@ import { PackageJsonEditor, DependencyType, PackageJsonDependency } from '../../ import { PnpmWorkspaceFile } from '../pnpm/PnpmWorkspaceFile'; import { RushConfigurationProject } from '../../api/RushConfigurationProject'; import { RushConstants } from '../../logic/RushConstants'; -import { Stopwatch } from '../../utilities/Stopwatch'; import { Utilities } from '../../utilities/Utilities'; import { InstallHelpers } from './InstallHelpers'; import { CommonVersionsConfiguration } from '../../api/CommonVersionsConfiguration'; @@ -65,8 +64,6 @@ export class WorkspaceInstallManager extends BaseInstallManager { protected async prepareCommonTempAsync( shrinkwrapFile: BaseShrinkwrapFile | undefined ): Promise<{ shrinkwrapIsUpToDate: boolean; shrinkwrapWarnings: string[] }> { - const stopwatch: Stopwatch = Stopwatch.start(); - // Block use of the RUSH_TEMP_FOLDER environment variable if (EnvironmentConfiguration.rushTempFolderOverride !== undefined) { throw new Error( @@ -264,21 +261,10 @@ export class WorkspaceInstallManager extends BaseInstallManager { // since "rush install" will consider this timestamp workspaceFile.save(workspaceFile.workspaceFilename, { onlyIfChanged: true }); - stopwatch.stop(); - console.log(`Finished creating workspace (${stopwatch.toString()})`); - return { shrinkwrapIsUpToDate, shrinkwrapWarnings }; } protected canSkipInstall(lastModifiedDate: Date): boolean { - console.log( - os.EOL + - colors.bold( - `Checking ${RushConstants.nodeModulesFolderName} in ${this.rushConfiguration.commonTempFolder}` - ) + - os.EOL - ); - // Based on timestamps, can we skip this install entirely? const potentiallyChangedFiles: string[] = []; diff --git a/apps/rush-lib/src/scripts/install-run.ts b/apps/rush-lib/src/scripts/install-run.ts index f70a1fc2e3..e2890ab1aa 100644 --- a/apps/rush-lib/src/scripts/install-run.ts +++ b/apps/rush-lib/src/scripts/install-run.ts @@ -65,7 +65,8 @@ function _parsePackageSpecifier(rawPackageSpecifier: string): IPackageSpecifier * IMPORTANT: THIS CODE SHOULD BE KEPT UP TO DATE WITH Utilities.copyAndTrimNpmrcFile() */ function _copyAndTrimNpmrcFile(sourceNpmrcPath: string, targetNpmrcPath: string): void { - console.log(`Copying ${sourceNpmrcPath} --> ${targetNpmrcPath}`); // Verbose + console.log(`Transforming ${sourceNpmrcPath}`); // Verbose + console.log(` --> "${targetNpmrcPath}"`); let npmrcFileLines: string[] = fs.readFileSync(sourceNpmrcPath).toString().split('\n'); npmrcFileLines = npmrcFileLines.map((line) => (line || '').trim()); const resultLines: string[] = []; diff --git a/apps/rush-lib/src/utilities/Utilities.ts b/apps/rush-lib/src/utilities/Utilities.ts index 0946031de1..dfab1d13bb 100644 --- a/apps/rush-lib/src/utilities/Utilities.ts +++ b/apps/rush-lib/src/utilities/Utilities.ts @@ -522,7 +522,8 @@ export class Utilities { * IMPORTANT: THIS CODE SHOULD BE KEPT UP TO DATE WITH _copyAndTrimNpmrcFile() FROM scripts/install-run.ts */ public static copyAndTrimNpmrcFile(sourceNpmrcPath: string, targetNpmrcPath: string): void { - console.log(`Copying ${sourceNpmrcPath} --> ${targetNpmrcPath}`); // Verbose + console.log(`Transforming ${sourceNpmrcPath}`); // Verbose + console.log(` --> "${targetNpmrcPath}"`); let npmrcFileLines: string[] = FileSystem.readFile(sourceNpmrcPath).split('\n'); npmrcFileLines = npmrcFileLines.map((line) => (line || '').trim()); const resultLines: string[] = []; @@ -573,7 +574,8 @@ export class Utilities { */ public static syncFile(sourcePath: string, destinationPath: string): void { if (FileSystem.exists(sourcePath)) { - console.log(`Updating ${destinationPath}`); + console.log(`Copying "${sourcePath}"`); + console.log(` --> "${destinationPath}"`); FileSystem.copyFile({ sourcePath, destinationPath }); } else { if (FileSystem.exists(destinationPath)) {