in apps/rush-lib/src/logic/installManager/RushInstallManager.ts [81:368]
public async prepareCommonTempAsync(
shrinkwrapFile: BaseShrinkwrapFile | undefined
): Promise<{ shrinkwrapIsUpToDate: boolean; shrinkwrapWarnings: string[] }> {
const stopwatch: Stopwatch = Stopwatch.start();
// Example: "C:\MyRepo\common\temp\projects"
const tempProjectsFolder: string = path.join(
this.rushConfiguration.commonTempFolder,
RushConstants.rushTempProjectsFolderName
);
console.log(os.EOL + colors.bold('Updating temp projects in ' + tempProjectsFolder));
Utilities.createFolderWithRetry(tempProjectsFolder);
const shrinkwrapWarnings: string[] = [];
// We will start with the assumption that it's valid, and then set it to false if
// any of the checks fail
let shrinkwrapIsUpToDate: boolean = true;
if (!shrinkwrapFile) {
shrinkwrapIsUpToDate = false;
} else if (shrinkwrapFile.isWorkspaceCompatible && !this.options.fullUpgrade) {
console.log();
console.log(
colors.red(
'The shrinkwrap file had previously been updated to support workspaces. Run "rush update --full" ' +
'to update the shrinkwrap file.'
)
);
throw new AlreadyReportedError();
}
// dependency name --> version specifier
const allExplicitPreferredVersions: Map<string, string> = this.rushConfiguration
.getCommonVersions(this.options.variant)
.getAllPreferredVersions();
if (shrinkwrapFile) {
// Check any (explicitly) preferred dependencies first
allExplicitPreferredVersions.forEach((version: string, dependency: string) => {
const dependencySpecifier: DependencySpecifier = new DependencySpecifier(dependency, version);
if (!shrinkwrapFile.hasCompatibleTopLevelDependency(dependencySpecifier)) {
shrinkwrapWarnings.push(
`Missing dependency "${dependency}" (${version}) required by the preferred versions from ` +
RushConstants.commonVersionsFilename
);
shrinkwrapIsUpToDate = false;
}
});
if (this._findMissingTempProjects(shrinkwrapFile)) {
// If any Rush project's tarball is missing from the shrinkwrap file, then we need to update
// the shrinkwrap file.
shrinkwrapIsUpToDate = false;
}
// If there are orphaned projects, we need to update
const orphanedProjects: ReadonlyArray<string> = shrinkwrapFile.findOrphanedProjects(
this.rushConfiguration
);
if (orphanedProjects.length > 0) {
for (const orhpanedProject of orphanedProjects) {
shrinkwrapWarnings.push(
`Your ${this.rushConfiguration.shrinkwrapFilePhrase} references "${orhpanedProject}" ` +
'which was not found in rush.json'
);
}
shrinkwrapIsUpToDate = false;
}
}
// dependency name --> version specifier
const commonDependencies: Map<string, string> = new Map([
...allExplicitPreferredVersions,
...this.rushConfiguration.getImplicitlyPreferredVersions(this.options.variant)
]);
// To make the common/package.json file more readable, sort alphabetically
// according to rushProject.tempProjectName instead of packageName.
const sortedRushProjects: RushConfigurationProject[] = this.rushConfiguration.projects.slice(0);
Sort.sortBy(sortedRushProjects, (x) => x.tempProjectName);
for (const rushProject of sortedRushProjects) {
const packageJson: PackageJsonEditor = rushProject.packageJsonEditor;
// Example: "C:\MyRepo\common\temp\projects\my-project-2.tgz"
const tarballFile: string = this._tempProjectHelper.getTarballFilePath(rushProject);
// Example: dependencies["@rush-temp/my-project-2"] = "file:./projects/my-project-2.tgz"
commonDependencies.set(
rushProject.tempProjectName,
`file:./${RushConstants.rushTempProjectsFolderName}/${rushProject.unscopedTempProjectName}.tgz`
);
const tempPackageJson: IRushTempPackageJson = {
name: rushProject.tempProjectName,
version: '0.0.0',
private: true,
dependencies: {}
};
// Collect pairs of (packageName, packageVersion) to be added as dependencies of the @rush-temp package.json
const tempDependencies: Map<string, string> = new Map<string, string>();
// These can be regular, optional, or peer dependencies (but NOT dev dependencies).
// (A given packageName will never appear more than once in this list.)
for (const dependency of packageJson.dependencyList) {
if (this.options.fullUpgrade && this._revertWorkspaceNotation(dependency)) {
shrinkwrapIsUpToDate = false;
}
// If there are any optional dependencies, copy directly into the optionalDependencies field.
if (dependency.dependencyType === DependencyType.Optional) {
if (!tempPackageJson.optionalDependencies) {
tempPackageJson.optionalDependencies = {};
}
tempPackageJson.optionalDependencies[dependency.name] = dependency.version;
} else {
tempDependencies.set(dependency.name, dependency.version);
}
}
for (const dependency of packageJson.devDependencyList) {
if (this.options.fullUpgrade && this._revertWorkspaceNotation(dependency)) {
shrinkwrapIsUpToDate = false;
}
// If there are devDependencies, we need to merge them with the regular dependencies. If the same
// library appears in both places, then the dev dependency wins (because presumably it's saying what you
// want right now for development, not the range that you support for consumers).
tempDependencies.set(dependency.name, dependency.version);
}
Sort.sortMapKeys(tempDependencies);
for (const [packageName, packageVersion] of tempDependencies.entries()) {
const dependencySpecifier: DependencySpecifier = new DependencySpecifier(packageName, packageVersion);
// Is there a locally built Rush project that could satisfy this dependency?
// If so, then we will symlink to the project folder rather than to common/temp/node_modules.
// In this case, we don't want "npm install" to process this package, but we do need
// to record this decision for linking later, so we add it to a special 'rushDependencies' field.
const localProject: RushConfigurationProject | undefined =
this.rushConfiguration.getProjectByName(packageName);
if (localProject) {
// Don't locally link if it's listed in the cyclicDependencyProjects
if (!rushProject.cyclicDependencyProjects.has(packageName)) {
// Also, don't locally link if the SemVer doesn't match
const localProjectVersion: string = localProject.packageJsonEditor.version;
if (semver.satisfies(localProjectVersion, packageVersion)) {
// We will locally link this package, so instead add it to our special "rushDependencies"
// field in the package.json file.
if (!tempPackageJson.rushDependencies) {
tempPackageJson.rushDependencies = {};
}
tempPackageJson.rushDependencies[packageName] = packageVersion;
continue;
}
}
}
// We will NOT locally link this package; add it as a regular dependency.
tempPackageJson.dependencies![packageName] = packageVersion;
if (
shrinkwrapFile &&
!shrinkwrapFile.tryEnsureCompatibleDependency(dependencySpecifier, rushProject.tempProjectName)
) {
shrinkwrapWarnings.push(
`Missing dependency "${packageName}" (${packageVersion}) required by "${rushProject.packageName}"`
);
shrinkwrapIsUpToDate = false;
}
}
if (this.rushConfiguration.packageManager === 'yarn') {
// This feature is only implemented by the Yarn package manager
if (packageJson.resolutionsList.length > 0) {
tempPackageJson.resolutions = packageJson.saveToObject().resolutions;
}
}
// Example: "C:\MyRepo\common\temp\projects\my-project-2"
const tempProjectFolder: string = this._tempProjectHelper.getTempProjectFolder(rushProject);
// Example: "C:\MyRepo\common\temp\projects\my-project-2\package.json"
const tempPackageJsonFilename: string = path.join(tempProjectFolder, FileConstants.PackageJson);
// we only want to overwrite the package if the existing tarball's package.json is different from tempPackageJson
let shouldOverwrite: boolean = true;
try {
// if the tarball and the temp file still exist, then compare the contents
if (FileSystem.exists(tarballFile) && FileSystem.exists(tempPackageJsonFilename)) {
// compare the extracted package.json with the one we are about to write
const oldBuffer: Buffer = FileSystem.readFileToBuffer(tempPackageJsonFilename);
const newBuffer: Buffer = Buffer.from(JsonFile.stringify(tempPackageJson));
if (Buffer.compare(oldBuffer, newBuffer) === 0) {
shouldOverwrite = false;
}
}
} catch (error) {
// ignore the error, we will go ahead and create a new tarball
}
if (shouldOverwrite) {
try {
// ensure the folder we are about to zip exists
Utilities.createFolderWithRetry(tempProjectFolder);
// remove the old tarball & old temp package json, this is for any cases where new tarball creation
// fails, and the shouldOverwrite logic is messed up because the my-project-2\package.json
// exists and is updated, but the tarball is not accurate
FileSystem.deleteFile(tarballFile);
FileSystem.deleteFile(tempPackageJsonFilename);
// write the expected package.json file into the zip staging folder
JsonFile.save(tempPackageJson, tempPackageJsonFilename);
// Delete the existing tarball and create a new one
this._tempProjectHelper.createTempProjectTarball(rushProject);
console.log(`Updating ${tarballFile}`);
} catch (error) {
console.log(colors.yellow(error as string));
// delete everything in case of any error
FileSystem.deleteFile(tarballFile);
FileSystem.deleteFile(tempPackageJsonFilename);
}
}
// 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 (!tarballIntegrityValid) {
shrinkwrapIsUpToDate = false;
shrinkwrapWarnings.push(
`Invalid or missing tarball integrity hash in shrinkwrap for "${rushProject.packageName}"`
);
}
}
// Save the package.json if we modified the version references and warn that the package.json was modified
if (packageJson.saveIfModified()) {
console.log(
colors.yellow(
`"${rushProject.packageName}" depends on one or more local packages which used "workspace:" ` +
'notation. The package.json has been modified and must be committed to source control.'
)
);
}
}
// Remove the workspace file if it exists
if (this.rushConfiguration.packageManager === 'pnpm') {
const workspaceFilePath: string = path.join(
this.rushConfiguration.commonTempFolder,
'pnpm-workspace.yaml'
);
try {
await FileSystem.deleteFileAsync(workspaceFilePath);
} catch (e) {
if (!FileSystem.isNotExistError(e as Error)) {
throw e;
}
}
}
// Write the common package.json
InstallHelpers.generateCommonPackageJson(this.rushConfiguration, commonDependencies);
stopwatch.stop();
console.log(`Finished creating temporary modules (${stopwatch.toString()})`);
return { shrinkwrapIsUpToDate, shrinkwrapWarnings };
}