Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[rush] Support passing a lockfile to install-run #3671

Merged
merged 1 commit into from
Oct 4, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"changes": [
{
"packageName": "@microsoft/rush",
"comment": "Support passing a lockfile to \"install-run.js\" and \"install-run-rush.js\" to ensure stable installation on CI.",
"type": "none"
}
],
"packageName": "@microsoft/rush"
}
11 changes: 10 additions & 1 deletion libraries/rush-lib/src/scripts/install-run-rush.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ import {

const PACKAGE_NAME: string = '@microsoft/rush';
const RUSH_PREVIEW_VERSION: string = 'RUSH_PREVIEW_VERSION';
const INSTALL_RUN_RUSH_LOCKFILE_PATH_VARIABLE: 'INSTALL_RUN_RUSH_LOCKFILE_PATH' =
'INSTALL_RUN_RUSH_LOCKFILE_PATH';

function _getRushVersion(logger: ILogger): string {
const rushPreviewVersion: string | undefined = process.env[RUSH_PREVIEW_VERSION];
Expand Down Expand Up @@ -103,7 +105,14 @@ function _run(): void {
const version: string = _getRushVersion(logger);
logger.info(`The rush.json configuration requests Rush version ${version}`);

return installAndRun(logger, PACKAGE_NAME, version, bin, packageBinArgs);
const lockFilePath: string | undefined = process.env[INSTALL_RUN_RUSH_LOCKFILE_PATH_VARIABLE];
if (lockFilePath) {
logger.info(
`Found ${INSTALL_RUN_RUSH_LOCKFILE_PATH_VARIABLE}="${lockFilePath}", installing with lockfile.`
);
}

return installAndRun(logger, PACKAGE_NAME, version, bin, packageBinArgs, lockFilePath);
});
}

Expand Down
64 changes: 47 additions & 17 deletions libraries/rush-lib/src/scripts/install-run.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { IPackageJson } from '@rushstack/node-core-library';

export const RUSH_JSON_FILENAME: string = 'rush.json';
const RUSH_TEMP_FOLDER_ENV_VARIABLE_NAME: string = 'RUSH_TEMP_FOLDER';
const INSTALL_RUN_LOCKFILE_PATH_VARIABLE: 'INSTALL_RUN_LOCKFILE_PATH' = 'INSTALL_RUN_LOCKFILE_PATH';
const INSTALLED_FLAG_FILENAME: string = 'installed.flag';
const NODE_MODULES_FOLDER_NAME: string = 'node_modules';
const PACKAGE_JSON_FILENAME: string = 'package.json';
Expand Down Expand Up @@ -334,29 +335,50 @@ function _isPackageAlreadyInstalled(packageInstallFolder: string): boolean {
}
}

/**
* Delete a file. Fail silently if it does not exist.
*/
function _deleteFile(file: string): void {
try {
fs.unlinkSync(file);
} catch (err) {
if (err.code !== 'ENOENT' && err.code !== 'ENOTDIR') {
throw err;
}
}
}

/**
* Removes the following files and directories under the specified folder path:
* - installed.flag
* -
* - node_modules
*/
function _cleanInstallFolder(rushTempFolder: string, packageInstallFolder: string): void {
function _cleanInstallFolder(
rushTempFolder: string,
packageInstallFolder: string,
lockFilePath: string | undefined
): void {
try {
const flagFile: string = path.resolve(packageInstallFolder, INSTALLED_FLAG_FILENAME);
if (fs.existsSync(flagFile)) {
fs.unlinkSync(flagFile);
}
_deleteFile(flagFile);

const packageLockFile: string = path.resolve(packageInstallFolder, 'package-lock.json');
if (fs.existsSync(packageLockFile)) {
fs.unlinkSync(packageLockFile);
}
if (lockFilePath) {
fs.copyFileSync(lockFilePath, packageLockFile);
} else {
// Not running `npm ci`, so need to cleanup
_deleteFile(packageLockFile);

const nodeModulesFolder: string = path.resolve(packageInstallFolder, NODE_MODULES_FOLDER_NAME);
if (fs.existsSync(nodeModulesFolder)) {
const rushRecyclerFolder: string = _ensureAndJoinPath(rushTempFolder, 'rush-recycler');
const nodeModulesFolder: string = path.resolve(packageInstallFolder, NODE_MODULES_FOLDER_NAME);
if (fs.existsSync(nodeModulesFolder)) {
const rushRecyclerFolder: string = _ensureAndJoinPath(rushTempFolder, 'rush-recycler');

fs.renameSync(nodeModulesFolder, path.join(rushRecyclerFolder, `install-run-${Date.now().toString()}`));
fs.renameSync(
nodeModulesFolder,
path.join(rushRecyclerFolder, `install-run-${Date.now().toString()}`)
);
}
}
} catch (e) {
throw new Error(`Error cleaning the package install folder (${packageInstallFolder}): ${e}`);
Expand Down Expand Up @@ -386,18 +408,24 @@ function _createPackageJson(packageInstallFolder: string, name: string, version:
/**
* Run "npm install" in the package install folder.
*/
function _installPackage(logger: ILogger, packageInstallFolder: string, name: string, version: string): void {
function _installPackage(
logger: ILogger,
packageInstallFolder: string,
name: string,
version: string,
command: 'install' | 'ci'
): void {
try {
logger.info(`Installing ${name}...`);
const npmPath: string = getNpmPath();
const result: childProcess.SpawnSyncReturns<Buffer> = childProcess.spawnSync(npmPath, ['install'], {
const result: childProcess.SpawnSyncReturns<Buffer> = childProcess.spawnSync(npmPath, [command], {
stdio: 'inherit',
cwd: packageInstallFolder,
env: process.env
});

if (result.status !== 0) {
throw new Error('"npm install" encountered an error');
throw new Error(`"npm ${command}" encountered an error`);
}

logger.info(`Successfully installed ${name}@${version}`);
Expand Down Expand Up @@ -432,7 +460,8 @@ export function installAndRun(
packageName: string,
packageVersion: string,
packageBinName: string,
packageBinArgs: string[]
packageBinArgs: string[],
lockFilePath: string | undefined = process.env[INSTALL_RUN_LOCKFILE_PATH_VARIABLE]
): number {
const rushJsonFolder: string = findRushJsonFolder();
const rushCommonFolder: string = path.join(rushJsonFolder, 'common');
Expand All @@ -445,13 +474,14 @@ export function installAndRun(

if (!_isPackageAlreadyInstalled(packageInstallFolder)) {
// The package isn't already installed
_cleanInstallFolder(rushTempFolder, packageInstallFolder);
_cleanInstallFolder(rushTempFolder, packageInstallFolder, lockFilePath);

const sourceNpmrcFolder: string = path.join(rushCommonFolder, 'config', 'rush');
_syncNpmrc(logger, sourceNpmrcFolder, packageInstallFolder);

_createPackageJson(packageInstallFolder, packageName, packageVersion);
_installPackage(logger, packageInstallFolder, packageName, packageVersion);
const command: 'install' | 'ci' = lockFilePath ? 'ci' : 'install';
_installPackage(logger, packageInstallFolder, packageName, packageVersion, command);
_writeFlagFile(packageInstallFolder);
}

Expand Down