protected async prepareCommonTempAsync()

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 };
  }