Skip to content

Commit fb2ec44

Browse files
committed
Refactoring install-run* scripts.
1 parent 8788357 commit fb2ec44

File tree

4 files changed

+115
-68
lines changed

4 files changed

+115
-68
lines changed

.travis.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,14 @@ script:
55
- set -e
66
- echo 'Checking change file...' && echo -en 'travis_fold:start:change\\r'
77
- git fetch origin master:refs/remotes/origin/master -a
8-
- node common/scripts/install-run-rush.js rush change -v
8+
- node common/scripts/install-run-rush.js change -v
99
- echo -en 'travis_fold:end:change\\r'
1010
- echo 'Running rush check' && echo -en 'travis_fold:start:check\\r'
11-
- node common/scripts/install-run-rush.js rush check
11+
- node common/scripts/install-run-rush.js check
1212
- echo -en 'travis_fold:end:check\\r'
1313
- echo 'Installing...' && echo -en 'travis_fold:start:install\\r'
14-
- node common/scripts/install-run-rush.js rush install --bypass-policy
14+
- node common/scripts/install-run-rush.js install --bypass-policy
1515
- echo -en 'travis_fold:end:install\\r'
1616
- echo 'Building...' && echo -en 'travis_fold:start:build\\r'
17-
- node common/scripts/install-run-rush.js rush rebuild --verbose --production
17+
- node common/scripts/install-run-rush.js rebuild --verbose --production
1818
- echo -en 'travis_fold:end:build\\r'

apps/rush-lib/src/scripts/install-run-common.ts

Lines changed: 48 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ export function findRushJsonFolder(): string {
6868
} else {
6969
basePath = tempPath;
7070
}
71-
} while (basePath !== (tempPath = path.resolve(basePath, '..'))); // Exit the loop when we hit the disk root
71+
} while (basePath !== (tempPath = path.dirname(basePath))); // Exit the loop when we hit the disk root
7272

7373
if (!_rushJsonFolder) {
7474
throw new Error('Unable to find rush.json.');
@@ -88,7 +88,7 @@ function ensureAndResolveFolder(baseFolder: string, ...pathSegments: string[]):
8888
let resolvedDirectory: string = baseFolder;
8989
try {
9090
for (let pathSegment of pathSegments) {
91-
pathSegment = pathSegment.replace(/[\\\/]/g, '_');
91+
pathSegment = pathSegment.replace(/[\\\/]/g, '+');
9292
resolvedDirectory = path.resolve(resolvedDirectory, pathSegment);
9393
if (!fs.existsSync(resolvedDirectory)) {
9494
fs.mkdirSync(resolvedDirectory);
@@ -155,7 +155,10 @@ function isPackageAlreadyInstalled(packageInstallFolder: string): boolean {
155155
}
156156

157157
/**
158-
* Removes the installed.flag file and the node_modules folder under the specified folder path.
158+
* Removes the following files and directories under the specified folder path:
159+
* - installed.flag
160+
* -
161+
* - node_modules
159162
*/
160163
function cleanInstallFolder(rushCommonFolder: string, packageInstallFolder: string): void {
161164
try {
@@ -164,13 +167,18 @@ function cleanInstallFolder(rushCommonFolder: string, packageInstallFolder: stri
164167
fs.unlinkSync(flagFile);
165168
}
166169

170+
const packageLockFile: string = path.resolve(packageInstallFolder, 'package-lock.json');
171+
if (fs.existsSync(packageLockFile)) {
172+
fs.unlinkSync(packageLockFile);
173+
}
174+
167175
const nodeModulesFolder: string = path.resolve(packageInstallFolder, NODE_MODULES_FOLDER_NAME);
168176
if (fs.existsSync(nodeModulesFolder)) {
169177
const rushRecyclerFolder: string = ensureAndResolveFolder(
170178
rushCommonFolder,
171179
'temp',
172180
'rush-recycler',
173-
Date.now().toString()
181+
`install-run-${Date.now().toString()}`
174182
);
175183
fs.renameSync(nodeModulesFolder, rushRecyclerFolder);
176184
}
@@ -206,39 +214,33 @@ function installPackage(packageInstallFolder: string, name: string, version: str
206214
try {
207215
console.log(`Installing ${name}...`);
208216
const npmPath: string = getNpmPath();
209-
childProcess.execSync(`"${npmPath}" install`, { cwd: packageInstallFolder });
217+
const result: childProcess.SpawnSyncReturns<Buffer> = childProcess.spawnSync(
218+
npmPath,
219+
['install'],
220+
{
221+
stdio: 'inherit',
222+
cwd: packageInstallFolder,
223+
env: process.env
224+
}
225+
);
226+
227+
if (result.status !== 0) {
228+
throw new Error('"npm install" encountered an error');
229+
}
230+
210231
console.log(`Successfully installed ${name}@${version}`);
211232
} catch (e) {
212233
throw new Error(`Unable to install package: ${e}`);
213234
}
214235
}
215236

216237
/**
217-
* Try to resolve the specified binary in an installed package.
238+
* Get the ".bin" path for the package.
218239
*/
219-
function findBinPath(packageInstallFolder: string, name: string, binName: string): string {
220-
try {
221-
const packagePath: string = path.resolve(packageInstallFolder, NODE_MODULES_FOLDER_NAME, name);
222-
const packageJsonPath: string = path.resolve(packagePath, PACKAGE_JSON_FILENAME);
223-
const packageJson: IPackageJson = require(packageJsonPath);
224-
if (!packageJson.bin) {
225-
throw new Error('No binaries are specified for package.');
226-
} else {
227-
const binValue: string = packageJson.bin[binName];
228-
if (!binValue) {
229-
throw new Error(`Binary ${binName} is not specified in the package's package.json`);
230-
} else {
231-
const resolvedBinPath: string = path.resolve(packagePath, binValue);
232-
if (!fs.existsSync(resolvedBinPath)) {
233-
throw new Error('The specified binary points to a path that does not exist');
234-
} else {
235-
return resolvedBinPath;
236-
}
237-
}
238-
}
239-
} catch (e) {
240-
throw new Error(`Unable to find specified binary "${binName}": ${e}`);
241-
}
240+
function getBinPath(packageInstallFolder: string, binName: string): string {
241+
const binFolderPath: string = path.resolve(packageInstallFolder, NODE_MODULES_FOLDER_NAME, '.bin');
242+
const resolvedBinName: string = (os.platform() === 'win32') ? `${binName}.cmd` : binName;
243+
return path.resolve(binFolderPath, resolvedBinName);
242244
}
243245

244246
/**
@@ -249,17 +251,16 @@ function writeFlagFile(packageInstallFolder: string): void {
249251
const flagFilePath: string = path.join(packageInstallFolder, INSTALLED_FLAG_FILENAME);
250252
fs.writeFileSync(flagFilePath, process.version);
251253
} catch (e) {
252-
// Ignore
254+
throw new Error(`Unable to create installed.flag file in ${packageInstallFolder}`);
253255
}
254256
}
255257

256258
export function installAndRun(
257-
nodePath: string,
258259
packageName: string,
259260
packageVersion: string,
260261
packageBinName: string,
261262
packageBinArgs: string[]
262-
): void {
263+
): number {
263264
const rushJsonFolder: string = findRushJsonFolder();
264265
const rushCommonFolder: string = path.join(rushJsonFolder, 'common');
265266
const packageInstallFolder: string = ensureAndResolveFolder(
@@ -278,21 +279,30 @@ export function installAndRun(
278279
writeFlagFile(packageInstallFolder);
279280
}
280281

281-
const binPath: string = findBinPath(packageInstallFolder, packageName, packageBinName);
282-
childProcess.spawnSync(
283-
nodePath,
284-
[binPath, ...packageBinArgs],
282+
const statusMessage: string = `Invoking "${packageBinName} ${packageBinArgs.join(' ')}"`;
283+
const statusMessageLine: string = new Array(statusMessage.length).join('-');
284+
console.log(os.EOL + statusMessage + os.EOL + statusMessageLine + os.EOL);
285+
286+
const binPath: string = getBinPath(packageInstallFolder, packageBinName);
287+
const result: childProcess.SpawnSyncReturns<Buffer> = childProcess.spawnSync(
288+
binPath,
289+
packageBinArgs,
285290
{
286291
stdio: 'inherit',
287292
cwd: process.cwd(),
288293
env: process.env
289294
}
290295
);
296+
297+
return result.status;
291298
}
292299

293-
export function runWithErrorPrinting(fn: () => void): void {
300+
export function runWithErrorAndStatusCode(fn: () => number): void {
301+
process.exitCode = 1;
302+
294303
try {
295-
fn();
304+
const exitCode: number = fn();
305+
process.exitCode = exitCode;
296306
} catch (e) {
297307
console.error(os.EOL + os.EOL + e.toString() + os.EOL + os.EOL);
298308
}

apps/rush-lib/src/scripts/install-run-rush.ts

Lines changed: 25 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
11
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
22
// See LICENSE in the project root for license information.
33

4-
// This file was generated by a tool. Modifying this file will produce unexpected behavior.
4+
// THIS FILE WAS GENERATED BY A TOOL. MODIFYING THIS FILE WILL PRODUCE UNEXPECTED BEHAVIOR.
55
//
6-
// This script is used to install and invoke Rush from a CI definition.
6+
// This script is intended for usage in an automated build environment where the Rush command may not have
7+
// been preinstalled, or may have an unpredictable version. This script will automatically install the version of Rush
8+
// specified in the rush.json configuration file (if not already installed), and then pass a command-line to it.
9+
// An example usage would be: "node common/scripts/install-run-rush.js install"
10+
// For more information, see:
11+
// [https://rushjs.io/pages/maintainer/setup_new_repo/](https://rushjs.io/pages/maintainer/setup_new_repo/)
712

813
import * as path from 'path';
914
import * as fs from 'fs';
@@ -12,15 +17,14 @@ import {
1217
installAndRun,
1318
findRushJsonFolder,
1419
RUSH_JSON_FILENAME,
15-
runWithErrorPrinting
20+
runWithErrorAndStatusCode
1621
} from './install-run-common';
1722

1823
const PACKAGE_NAME: string = '@microsoft/rush';
1924

2025
function getRushVersion(): string {
21-
const rushJsonDirectory: string = findRushJsonFolder();
22-
23-
const rushJsonPath: string = path.join(rushJsonDirectory, RUSH_JSON_FILENAME);
26+
const rushJsonFolder: string = findRushJsonFolder();
27+
const rushJsonPath: string = path.join(rushJsonFolder, RUSH_JSON_FILENAME);
2428
try {
2529
const rushJsonContents: string = fs.readFileSync(rushJsonPath, 'UTF-8');
2630
// Use a regular expression to parse out the rushVersion value because rush.json supports comments,
@@ -29,22 +33,31 @@ function getRushVersion(): string {
2933
return rushJsonMatches[1];
3034
} catch (e) {
3135
throw new Error(
32-
`Unable to determine the required version of Rush from rush.json (${rushJsonDirectory}). ` +
36+
`Unable to determine the required version of Rush from rush.json (${rushJsonFolder}). ` +
3337
'The \'rushVersion\' field is either not assigned in rush.json or was specified ' +
3438
'using an unexpected syntax.'
3539
);
3640
}
3741
}
3842

3943
function run(): void {
40-
runWithErrorPrinting(() => {
41-
// tslint:disable-next-line:no-unused-variable
42-
const [ nodePath, scriptPath, packageBinName, ...packageBinArgs ]: string[] = process.argv;
44+
if (process.argv.length < 3) {
45+
console.log('Usage: install-run-rush.js <command> [args...]');
46+
console.log('Example: install-run-rush.js build --to myproject');
47+
process.exit(1);
48+
}
49+
50+
runWithErrorAndStatusCode(() => {
51+
const [
52+
nodePath, /* Ex: /bin/node */ // tslint:disable-line:no-unused-variable
53+
scriptPath, /* /repo/common/scripts/install-run-rush.js */ // tslint:disable-line:no-unused-variable
54+
...packageBinArgs /* [build, --to, myproject] */
55+
]: string[] = process.argv;
4356

4457
const version: string = getRushVersion();
45-
console.log(`Expected Rush version is ${version}`);
58+
console.log(`The rush.json configuration requests Rush version ${version}`);
4659

47-
installAndRun(nodePath, PACKAGE_NAME, version, packageBinName, packageBinArgs);
60+
return installAndRun(PACKAGE_NAME, version, 'rush', packageBinArgs);
4861
});
4962
}
5063

apps/rush-lib/src/scripts/install-run.ts

Lines changed: 38 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,22 @@
11
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
22
// See LICENSE in the project root for license information.
33

4-
// This file was generated by a tool. Modifying this file will produce unexpected behavior.
4+
// THIS FILE WAS GENERATED BY A TOOL. MODIFYING THIS FILE WILL PRODUCE UNEXPECTED BEHAVIOR.
55
//
6-
// This script is used to install and invoke a tool from a CI definition.
6+
// This script is intended for usage in an automated build environment where a Node tool may not have
7+
// been preinstalled, or may have an unpredictable version. This script will automatically install the specified
8+
// version of the specified tool (if not already installed), and then pass a command-line to it.
9+
// An example usage would be: "node common/scripts/install-run.js rimraf@2.6.2 rimraf -f project1/lib"
10+
// For more information, see:
11+
// [https://rushjs.io/pages/maintainer/setup_new_repo/](https://rushjs.io/pages/maintainer/setup_new_repo/)
712

813
import * as childProcess from 'child_process';
914

1015
import {
1116
installAndRun,
1217
IPackageSpecifier,
1318
getNpmPath,
14-
runWithErrorPrinting
19+
runWithErrorAndStatusCode
1520
} from './install-run-common';
1621

1722
/**
@@ -48,8 +53,11 @@ function resolvePackageVersion({ name, version }: IPackageSpecifier): string {
4853
version = '*'; // If no version is specified, use the latest version
4954
}
5055

51-
if (version.match(/[\*\^\~]/ig)) {
52-
// If the version contains the characters "*", "^", or "~", we need to figure out what the
56+
if (version.match(/^[a-zA-Z0-9\-\+\.]+$/)) {
57+
// If the version contains only characters that we recognize to be used in static version specifiers,
58+
// pass the version through
59+
return version;
60+
} else {
5361
// version resolves to
5462
try {
5563
const npmPath: string = getNpmPath();
@@ -60,10 +68,17 @@ function resolvePackageVersion({ name, version }: IPackageSpecifier): string {
6068
// ...
6169
// @microsoft/rush@3.0.20 '3.0.20'
6270
// <blank line>
63-
const npmViewVersionOutput: string = childProcess.execSync(
64-
`${npmPath} view ${name}@${version} version --no-update-notifier`,
71+
const npmVersionSpawnResult: childProcess.SpawnSyncReturns<Buffer> = childProcess.spawnSync(
72+
npmPath,
73+
['view', `${name}@${version}`, 'version', '--no-update-notifier'],
6574
{ stdio: [] }
66-
).toString();
75+
);
76+
77+
if (npmVersionSpawnResult.status !== 0) {
78+
throw new Error(`"npm view" returned error code ${npmVersionSpawnResult.status}`);
79+
}
80+
81+
const npmViewVersionOutput: string = npmVersionSpawnResult.stdout.toString();
6782
const versionLines: string[] = npmViewVersionOutput.split('\n').filter((line) => !!line);
6883
const latestVersion: string | undefined = versionLines[versionLines.length - 1];
6984
if (!latestVersion) {
@@ -79,15 +94,24 @@ function resolvePackageVersion({ name, version }: IPackageSpecifier): string {
7994
} catch (e) {
8095
throw new Error(`Unable to resolve version ${version} of package ${name}: ${e}`);
8196
}
82-
} else {
83-
return version;
8497
}
8598
}
8699

87100
function run(): void {
88-
runWithErrorPrinting(() => {
89-
// tslint:disable-next-line:no-unused-variable
90-
const [ nodePath, scriptPath, rawPackageSpecifier, packageBinName, ...packageBinArgs ]: string[] = process.argv;
101+
if (process.argv.length < 4) {
102+
console.log('Usage: install-run.js <package>@<version> <command> [args...]');
103+
console.log('Example: install-run.js rimraf@2.6.2 rimraf -f project1/lib');
104+
process.exit(1);
105+
}
106+
107+
runWithErrorAndStatusCode(() => {
108+
const [
109+
nodePath, /* Ex: /bin/node */ // tslint:disable-line:no-unused-variable
110+
scriptPath, /* /repo/common/scripts/install-run-rush.js */ // tslint:disable-line:no-unused-variable
111+
rawPackageSpecifier, /* rimraf@^2.0.0 */
112+
packageBinName, /* rimraf */
113+
...packageBinArgs /* [-f, myproject/lib] */
114+
]: string[] = process.argv;
91115

92116
const packageSpecifier: IPackageSpecifier = parsePackageSpecifier(rawPackageSpecifier);
93117
const name: string = packageSpecifier.name;
@@ -97,7 +121,7 @@ function run(): void {
97121
console.log(`Resolved to ${name}@${version}`);
98122
}
99123

100-
installAndRun(nodePath, name, version, packageBinName, packageBinArgs);
124+
return installAndRun(name, version, packageBinName, packageBinArgs);
101125
});
102126
}
103127

0 commit comments

Comments
 (0)