in apps/rush-lib/src/logic/installManager/WorkspaceInstallManager.ts [55:237]
protected async prepareCommonTempAsync(
shrinkwrapFile: BaseShrinkwrapFile | undefined
): Promise<{ shrinkwrapIsUpToDate: boolean; shrinkwrapWarnings: string[] }> {
// Block use of the RUSH_TEMP_FOLDER environment variable
if (EnvironmentConfiguration.rushTempFolderOverride !== undefined) {
throw new Error(
'The RUSH_TEMP_FOLDER environment variable is not compatible with workspace installs. If attempting ' +
'to move the PNPM store path, see the `RUSH_PNPM_STORE_PATH` environment variable.'
);
}
console.log(
os.EOL + colors.bold('Updating workspace files in ' + this.rushConfiguration.commonTempFolder)
);
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 has not been updated to support workspaces. Run "rush update --full" to update ' +
'the shrinkwrap file.'
)
);
throw new AlreadyReportedError();
}
// 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;
}
}
// If preferred versions have been updated, or if the repo-state.json is invalid,
// we can't be certain of the state of the shrinkwrap
const repoState: RepoStateFile = this.rushConfiguration.getRepoState(this.options.variant);
if (!repoState.isValid) {
shrinkwrapWarnings.push(
`The ${RushConstants.repoStateFilename} file is invalid. There may be a merge conflict marker in the file.`
);
shrinkwrapIsUpToDate = false;
} else {
const commonVersions: CommonVersionsConfiguration = this.rushConfiguration.getCommonVersions(
this.options.variant
);
if (repoState.preferredVersionsHash !== commonVersions.getPreferredVersionsHash()) {
shrinkwrapWarnings.push(
`Preferred versions from ${RushConstants.commonVersionsFilename} have been modified.`
);
shrinkwrapIsUpToDate = false;
}
}
// To generate the workspace file, we will add each project to the file as we loop through and validate
const workspaceFile: PnpmWorkspaceFile = new PnpmWorkspaceFile(
path.join(this.rushConfiguration.commonTempFolder, 'pnpm-workspace.yaml')
);
// Loop through the projects and add them to the workspace file. While we're at it, also validate that
// referenced workspace projects are valid, and check if the shrinkwrap file is already up-to-date.
for (const rushProject of this.rushConfiguration.projects) {
const packageJson: PackageJsonEditor = rushProject.packageJsonEditor;
workspaceFile.addPackage(rushProject.projectFolder);
for (const { name, version, dependencyType } of [
...packageJson.dependencyList,
...packageJson.devDependencyList
]) {
// Allow the package manager to handle peer dependency resolution, since this is simply a constraint
// enforced by the package manager. Additionally, peer dependencies are simply a version constraint
// and do not need to be converted to workspaces protocol.
if (dependencyType === DependencyType.Peer) {
continue;
}
const dependencySpecifier: DependencySpecifier = new DependencySpecifier(name, version);
// Is there a locally built Rush project that could satisfy this dependency?
const referencedLocalProject: RushConfigurationProject | undefined =
this.rushConfiguration.getProjectByName(name);
// Validate that local projects are referenced with workspace notation. If not, and it is not a
// cyclic dependency, then it needs to be updated to specify `workspace:*` explicitly. Currently only
// supporting versions and version ranges for specifying a local project.
if (
(dependencySpecifier.specifierType === DependencySpecifierType.Version ||
dependencySpecifier.specifierType === DependencySpecifierType.Range) &&
referencedLocalProject &&
!rushProject.cyclicDependencyProjects.has(name)
) {
// Make sure that this version is intended to target a local package. If not, then we will fail since it
// is not explicitly specified as a cyclic dependency.
if (
!semver.satisfies(
referencedLocalProject.packageJsonEditor.version,
dependencySpecifier.versionSpecifier
)
) {
console.log();
console.log(
colors.red(
`"${rushProject.packageName}" depends on package "${name}" (${version}) which exists ` +
'within the workspace but cannot be fulfilled with the specified version range. Either ' +
'specify a valid version range, or add the package as a cyclic dependency.'
)
);
throw new AlreadyReportedError();
}
if (!this.options.allowShrinkwrapUpdates) {
console.log();
console.log(
colors.red(
`"${rushProject.packageName}" depends on package "${name}" (${version}) which exists within ` +
'the workspace. Run "rush update" to update workspace references for this package.'
)
);
throw new AlreadyReportedError();
}
if (this.options.fullUpgrade) {
// We will update to `workspace` notation. If the version specified is a range, then use the provided range.
// Otherwise, use `workspace:*` to ensure we're always using the workspace package.
const workspaceRange: string =
!!semver.validRange(dependencySpecifier.versionSpecifier) &&
!semver.valid(dependencySpecifier.versionSpecifier)
? dependencySpecifier.versionSpecifier
: '*';
packageJson.addOrUpdateDependency(name, `workspace:${workspaceRange}`, dependencyType);
shrinkwrapIsUpToDate = false;
continue;
}
} else if (dependencySpecifier.specifierType === DependencySpecifierType.Workspace) {
// Already specified as a local project. Allow the package manager to validate this
continue;
}
}
// 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 workspace packages which did not use "workspace:" ` +
'notation. The package.json has been modified and must be committed to source control.'
)
);
}
// Now validate that the shrinkwrap file matches what is in the package.json
if (shrinkwrapFile?.isWorkspaceProjectModified(rushProject, this.options.variant)) {
shrinkwrapWarnings.push(
`Dependencies of project "${rushProject.packageName}" do not match the current shrinkwrap.`
);
shrinkwrapIsUpToDate = false;
}
}
// Write the common package.json
InstallHelpers.generateCommonPackageJson(this.rushConfiguration);
// Save the generated workspace file. Don't update the file timestamp unless the content has changed,
// since "rush install" will consider this timestamp
workspaceFile.save(workspaceFile.workspaceFilename, { onlyIfChanged: true });
return { shrinkwrapIsUpToDate, shrinkwrapWarnings };
}