mirror of
https://github.com/hcengineering/platform.git
synced 2024-11-23 05:53:09 +03:00
5705281de5
Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
483 lines
20 KiB
JavaScript
483 lines
20 KiB
JavaScript
"use strict";
|
|
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
|
|
// See the @microsoft/rush package's LICENSE file for license information.
|
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
if (k2 === undefined) k2 = k;
|
|
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
}
|
|
Object.defineProperty(o, k2, desc);
|
|
}) : (function(o, m, k, k2) {
|
|
if (k2 === undefined) k2 = k;
|
|
o[k2] = m[k];
|
|
}));
|
|
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
}) : function(o, v) {
|
|
o["default"] = v;
|
|
});
|
|
var __importStar = (this && this.__importStar) || function (mod) {
|
|
if (mod && mod.__esModule) return mod;
|
|
var result = {};
|
|
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
__setModuleDefault(result, mod);
|
|
return result;
|
|
};
|
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
exports.runWithErrorAndStatusCode = exports.installAndRun = exports.findRushJsonFolder = exports.getNpmPath = exports.RUSH_JSON_FILENAME = void 0;
|
|
// THIS FILE WAS GENERATED BY A TOOL. ANY MANUAL MODIFICATIONS WILL GET OVERWRITTEN WHENEVER RUSH IS UPGRADED.
|
|
//
|
|
// This script is intended for usage in an automated build environment where a Node tool may not have
|
|
// been preinstalled, or may have an unpredictable version. This script will automatically install the specified
|
|
// version of the specified tool (if not already installed), and then pass a command-line to it.
|
|
// An example usage would be:
|
|
//
|
|
// node common/scripts/install-run.js qrcode@1.2.2 qrcode https://rushjs.io
|
|
//
|
|
// For more information, see: https://rushjs.io/pages/maintainer/setup_new_repo/
|
|
const childProcess = __importStar(require("child_process"));
|
|
const fs = __importStar(require("fs"));
|
|
const os = __importStar(require("os"));
|
|
const path = __importStar(require("path"));
|
|
exports.RUSH_JSON_FILENAME = 'rush.json';
|
|
const RUSH_TEMP_FOLDER_ENV_VARIABLE_NAME = 'RUSH_TEMP_FOLDER';
|
|
const INSTALLED_FLAG_FILENAME = 'installed.flag';
|
|
const NODE_MODULES_FOLDER_NAME = 'node_modules';
|
|
const PACKAGE_JSON_FILENAME = 'package.json';
|
|
/**
|
|
* Parse a package specifier (in the form of name\@version) into name and version parts.
|
|
*/
|
|
function _parsePackageSpecifier(rawPackageSpecifier) {
|
|
rawPackageSpecifier = (rawPackageSpecifier || '').trim();
|
|
const separatorIndex = rawPackageSpecifier.lastIndexOf('@');
|
|
let name;
|
|
let version = undefined;
|
|
if (separatorIndex === 0) {
|
|
// The specifier starts with a scope and doesn't have a version specified
|
|
name = rawPackageSpecifier;
|
|
}
|
|
else if (separatorIndex === -1) {
|
|
// The specifier doesn't have a version
|
|
name = rawPackageSpecifier;
|
|
}
|
|
else {
|
|
name = rawPackageSpecifier.substring(0, separatorIndex);
|
|
version = rawPackageSpecifier.substring(separatorIndex + 1);
|
|
}
|
|
if (!name) {
|
|
throw new Error(`Invalid package specifier: ${rawPackageSpecifier}`);
|
|
}
|
|
return { name, version };
|
|
}
|
|
/**
|
|
* As a workaround, copyAndTrimNpmrcFile() copies the .npmrc file to the target folder, and also trims
|
|
* unusable lines from the .npmrc file.
|
|
*
|
|
* Why are we trimming the .npmrc lines? NPM allows environment variables to be specified in
|
|
* the .npmrc file to provide different authentication tokens for different registry.
|
|
* However, if the environment variable is undefined, it expands to an empty string, which
|
|
* produces a valid-looking mapping with an invalid URL that causes an error. Instead,
|
|
* we'd prefer to skip that line and continue looking in other places such as the user's
|
|
* home directory.
|
|
*
|
|
* IMPORTANT: THIS CODE SHOULD BE KEPT UP TO DATE WITH Utilities.copyAndTrimNpmrcFile()
|
|
*/
|
|
function _copyAndTrimNpmrcFile(logger, sourceNpmrcPath, targetNpmrcPath) {
|
|
logger.info(`Transforming ${sourceNpmrcPath}`); // Verbose
|
|
logger.info(` --> "${targetNpmrcPath}"`);
|
|
let npmrcFileLines = fs.readFileSync(sourceNpmrcPath).toString().split('\n');
|
|
npmrcFileLines = npmrcFileLines.map((line) => (line || '').trim());
|
|
const resultLines = [];
|
|
// This finds environment variable tokens that look like "${VAR_NAME}"
|
|
const expansionRegExp = /\$\{([^\}]+)\}/g;
|
|
// Comment lines start with "#" or ";"
|
|
const commentRegExp = /^\s*[#;]/;
|
|
// Trim out lines that reference environment variables that aren't defined
|
|
for (const line of npmrcFileLines) {
|
|
let lineShouldBeTrimmed = false;
|
|
// Ignore comment lines
|
|
if (!commentRegExp.test(line)) {
|
|
const environmentVariables = line.match(expansionRegExp);
|
|
if (environmentVariables) {
|
|
for (const token of environmentVariables) {
|
|
// Remove the leading "${" and the trailing "}" from the token
|
|
const environmentVariableName = token.substring(2, token.length - 1);
|
|
// Is the environment variable defined?
|
|
if (!process.env[environmentVariableName]) {
|
|
// No, so trim this line
|
|
lineShouldBeTrimmed = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (lineShouldBeTrimmed) {
|
|
// Example output:
|
|
// "; MISSING ENVIRONMENT VARIABLE: //my-registry.com/npm/:_authToken=${MY_AUTH_TOKEN}"
|
|
resultLines.push('; MISSING ENVIRONMENT VARIABLE: ' + line);
|
|
}
|
|
else {
|
|
resultLines.push(line);
|
|
}
|
|
}
|
|
fs.writeFileSync(targetNpmrcPath, resultLines.join(os.EOL));
|
|
}
|
|
/**
|
|
* syncNpmrc() copies the .npmrc file to the target folder, and also trims unusable lines from the .npmrc file.
|
|
* If the source .npmrc file not exist, then syncNpmrc() will delete an .npmrc that is found in the target folder.
|
|
*
|
|
* IMPORTANT: THIS CODE SHOULD BE KEPT UP TO DATE WITH Utilities._syncNpmrc()
|
|
*/
|
|
function _syncNpmrc(logger, sourceNpmrcFolder, targetNpmrcFolder, useNpmrcPublish) {
|
|
const sourceNpmrcPath = path.join(sourceNpmrcFolder, !useNpmrcPublish ? '.npmrc' : '.npmrc-publish');
|
|
const targetNpmrcPath = path.join(targetNpmrcFolder, '.npmrc');
|
|
try {
|
|
if (fs.existsSync(sourceNpmrcPath)) {
|
|
_copyAndTrimNpmrcFile(logger, sourceNpmrcPath, targetNpmrcPath);
|
|
}
|
|
else if (fs.existsSync(targetNpmrcPath)) {
|
|
// If the source .npmrc doesn't exist and there is one in the target, delete the one in the target
|
|
logger.info(`Deleting ${targetNpmrcPath}`); // Verbose
|
|
fs.unlinkSync(targetNpmrcPath);
|
|
}
|
|
}
|
|
catch (e) {
|
|
throw new Error(`Error syncing .npmrc file: ${e}`);
|
|
}
|
|
}
|
|
let _npmPath = undefined;
|
|
/**
|
|
* Get the absolute path to the npm executable
|
|
*/
|
|
function getNpmPath() {
|
|
if (!_npmPath) {
|
|
try {
|
|
if (os.platform() === 'win32') {
|
|
// We're on Windows
|
|
const whereOutput = childProcess.execSync('where npm', { stdio: [] }).toString();
|
|
const lines = whereOutput.split(os.EOL).filter((line) => !!line);
|
|
// take the last result, we are looking for a .cmd command
|
|
// see https://github.com/microsoft/rushstack/issues/759
|
|
_npmPath = lines[lines.length - 1];
|
|
}
|
|
else {
|
|
// We aren't on Windows - assume we're on *NIX or Darwin
|
|
_npmPath = childProcess.execSync('command -v npm', { stdio: [] }).toString();
|
|
}
|
|
}
|
|
catch (e) {
|
|
throw new Error(`Unable to determine the path to the NPM tool: ${e}`);
|
|
}
|
|
_npmPath = _npmPath.trim();
|
|
if (!fs.existsSync(_npmPath)) {
|
|
throw new Error('The NPM executable does not exist');
|
|
}
|
|
}
|
|
return _npmPath;
|
|
}
|
|
exports.getNpmPath = getNpmPath;
|
|
function _ensureFolder(folderPath) {
|
|
if (!fs.existsSync(folderPath)) {
|
|
const parentDir = path.dirname(folderPath);
|
|
_ensureFolder(parentDir);
|
|
fs.mkdirSync(folderPath);
|
|
}
|
|
}
|
|
/**
|
|
* Create missing directories under the specified base directory, and return the resolved directory.
|
|
*
|
|
* Does not support "." or ".." path segments.
|
|
* Assumes the baseFolder exists.
|
|
*/
|
|
function _ensureAndJoinPath(baseFolder, ...pathSegments) {
|
|
let joinedPath = baseFolder;
|
|
try {
|
|
for (let pathSegment of pathSegments) {
|
|
pathSegment = pathSegment.replace(/[\\\/]/g, '+');
|
|
joinedPath = path.join(joinedPath, pathSegment);
|
|
if (!fs.existsSync(joinedPath)) {
|
|
fs.mkdirSync(joinedPath);
|
|
}
|
|
}
|
|
}
|
|
catch (e) {
|
|
throw new Error(`Error building local installation folder (${path.join(baseFolder, ...pathSegments)}): ${e}`);
|
|
}
|
|
return joinedPath;
|
|
}
|
|
function _getRushTempFolder(rushCommonFolder) {
|
|
const rushTempFolder = process.env[RUSH_TEMP_FOLDER_ENV_VARIABLE_NAME];
|
|
if (rushTempFolder !== undefined) {
|
|
_ensureFolder(rushTempFolder);
|
|
return rushTempFolder;
|
|
}
|
|
else {
|
|
return _ensureAndJoinPath(rushCommonFolder, 'temp');
|
|
}
|
|
}
|
|
/**
|
|
* Resolve a package specifier to a static version
|
|
*/
|
|
function _resolvePackageVersion(logger, rushCommonFolder, { name, version }) {
|
|
if (!version) {
|
|
version = '*'; // If no version is specified, use the latest version
|
|
}
|
|
if (version.match(/^[a-zA-Z0-9\-\+\.]+$/)) {
|
|
// If the version contains only characters that we recognize to be used in static version specifiers,
|
|
// pass the version through
|
|
return version;
|
|
}
|
|
else {
|
|
// version resolves to
|
|
try {
|
|
const rushTempFolder = _getRushTempFolder(rushCommonFolder);
|
|
const sourceNpmrcFolder = path.join(rushCommonFolder, 'config', 'rush');
|
|
_syncNpmrc(logger, sourceNpmrcFolder, rushTempFolder);
|
|
const npmPath = getNpmPath();
|
|
// This returns something that looks like:
|
|
// @microsoft/rush@3.0.0 '3.0.0'
|
|
// @microsoft/rush@3.0.1 '3.0.1'
|
|
// ...
|
|
// @microsoft/rush@3.0.20 '3.0.20'
|
|
// <blank line>
|
|
const npmVersionSpawnResult = childProcess.spawnSync(npmPath, ['view', `${name}@${version}`, 'version', '--no-update-notifier'], {
|
|
cwd: rushTempFolder,
|
|
stdio: []
|
|
});
|
|
if (npmVersionSpawnResult.status !== 0) {
|
|
throw new Error(`"npm view" returned error code ${npmVersionSpawnResult.status}`);
|
|
}
|
|
const npmViewVersionOutput = npmVersionSpawnResult.stdout.toString();
|
|
const versionLines = npmViewVersionOutput.split('\n').filter((line) => !!line);
|
|
const latestVersion = versionLines[versionLines.length - 1];
|
|
if (!latestVersion) {
|
|
throw new Error('No versions found for the specified version range.');
|
|
}
|
|
const versionMatches = latestVersion.match(/^.+\s\'(.+)\'$/);
|
|
if (!versionMatches) {
|
|
throw new Error(`Invalid npm output ${latestVersion}`);
|
|
}
|
|
return versionMatches[1];
|
|
}
|
|
catch (e) {
|
|
throw new Error(`Unable to resolve version ${version} of package ${name}: ${e}`);
|
|
}
|
|
}
|
|
}
|
|
let _rushJsonFolder;
|
|
/**
|
|
* Find the absolute path to the folder containing rush.json
|
|
*/
|
|
function findRushJsonFolder() {
|
|
if (!_rushJsonFolder) {
|
|
let basePath = __dirname;
|
|
let tempPath = __dirname;
|
|
do {
|
|
const testRushJsonPath = path.join(basePath, exports.RUSH_JSON_FILENAME);
|
|
if (fs.existsSync(testRushJsonPath)) {
|
|
_rushJsonFolder = basePath;
|
|
break;
|
|
}
|
|
else {
|
|
basePath = tempPath;
|
|
}
|
|
} while (basePath !== (tempPath = path.dirname(basePath))); // Exit the loop when we hit the disk root
|
|
if (!_rushJsonFolder) {
|
|
throw new Error('Unable to find rush.json.');
|
|
}
|
|
}
|
|
return _rushJsonFolder;
|
|
}
|
|
exports.findRushJsonFolder = findRushJsonFolder;
|
|
/**
|
|
* Detects if the package in the specified directory is installed
|
|
*/
|
|
function _isPackageAlreadyInstalled(packageInstallFolder) {
|
|
try {
|
|
const flagFilePath = path.join(packageInstallFolder, INSTALLED_FLAG_FILENAME);
|
|
if (!fs.existsSync(flagFilePath)) {
|
|
return false;
|
|
}
|
|
const fileContents = fs.readFileSync(flagFilePath).toString();
|
|
return fileContents.trim() === process.version;
|
|
}
|
|
catch (e) {
|
|
return false;
|
|
}
|
|
}
|
|
/**
|
|
* Removes the following files and directories under the specified folder path:
|
|
* - installed.flag
|
|
* -
|
|
* - node_modules
|
|
*/
|
|
function _cleanInstallFolder(rushTempFolder, packageInstallFolder) {
|
|
try {
|
|
const flagFile = path.resolve(packageInstallFolder, INSTALLED_FLAG_FILENAME);
|
|
if (fs.existsSync(flagFile)) {
|
|
fs.unlinkSync(flagFile);
|
|
}
|
|
const packageLockFile = path.resolve(packageInstallFolder, 'package-lock.json');
|
|
if (fs.existsSync(packageLockFile)) {
|
|
fs.unlinkSync(packageLockFile);
|
|
}
|
|
const nodeModulesFolder = path.resolve(packageInstallFolder, NODE_MODULES_FOLDER_NAME);
|
|
if (fs.existsSync(nodeModulesFolder)) {
|
|
const rushRecyclerFolder = _ensureAndJoinPath(rushTempFolder, 'rush-recycler');
|
|
fs.renameSync(nodeModulesFolder, path.join(rushRecyclerFolder, `install-run-${Date.now().toString()}`));
|
|
}
|
|
}
|
|
catch (e) {
|
|
throw new Error(`Error cleaning the package install folder (${packageInstallFolder}): ${e}`);
|
|
}
|
|
}
|
|
function _createPackageJson(packageInstallFolder, name, version) {
|
|
try {
|
|
const packageJsonContents = {
|
|
name: 'ci-rush',
|
|
version: '0.0.0',
|
|
dependencies: {
|
|
[name]: version
|
|
},
|
|
description: "DON'T WARN",
|
|
repository: "DON'T WARN",
|
|
license: 'MIT'
|
|
};
|
|
const packageJsonPath = path.join(packageInstallFolder, PACKAGE_JSON_FILENAME);
|
|
fs.writeFileSync(packageJsonPath, JSON.stringify(packageJsonContents, undefined, 2));
|
|
}
|
|
catch (e) {
|
|
throw new Error(`Unable to create package.json: ${e}`);
|
|
}
|
|
}
|
|
/**
|
|
* Run "npm install" in the package install folder.
|
|
*/
|
|
function _installPackage(logger, packageInstallFolder, name, version) {
|
|
try {
|
|
logger.info(`Installing ${name}...`);
|
|
const npmPath = getNpmPath();
|
|
const result = childProcess.spawnSync(npmPath, ['install'], {
|
|
stdio: 'inherit',
|
|
cwd: packageInstallFolder,
|
|
env: process.env
|
|
});
|
|
if (result.status !== 0) {
|
|
throw new Error('"npm install" encountered an error');
|
|
}
|
|
logger.info(`Successfully installed ${name}@${version}`);
|
|
}
|
|
catch (e) {
|
|
throw new Error(`Unable to install package: ${e}`);
|
|
}
|
|
}
|
|
/**
|
|
* Get the ".bin" path for the package.
|
|
*/
|
|
function _getBinPath(packageInstallFolder, binName) {
|
|
const binFolderPath = path.resolve(packageInstallFolder, NODE_MODULES_FOLDER_NAME, '.bin');
|
|
const resolvedBinName = os.platform() === 'win32' ? `${binName}.cmd` : binName;
|
|
return path.resolve(binFolderPath, resolvedBinName);
|
|
}
|
|
/**
|
|
* Write a flag file to the package's install directory, signifying that the install was successful.
|
|
*/
|
|
function _writeFlagFile(packageInstallFolder) {
|
|
try {
|
|
const flagFilePath = path.join(packageInstallFolder, INSTALLED_FLAG_FILENAME);
|
|
fs.writeFileSync(flagFilePath, process.version);
|
|
}
|
|
catch (e) {
|
|
throw new Error(`Unable to create installed.flag file in ${packageInstallFolder}`);
|
|
}
|
|
}
|
|
function installAndRun(logger, packageName, packageVersion, packageBinName, packageBinArgs) {
|
|
const rushJsonFolder = findRushJsonFolder();
|
|
const rushCommonFolder = path.join(rushJsonFolder, 'common');
|
|
const rushTempFolder = _getRushTempFolder(rushCommonFolder);
|
|
const packageInstallFolder = _ensureAndJoinPath(rushTempFolder, 'install-run', `${packageName}@${packageVersion}`);
|
|
if (!_isPackageAlreadyInstalled(packageInstallFolder)) {
|
|
// The package isn't already installed
|
|
_cleanInstallFolder(rushTempFolder, packageInstallFolder);
|
|
const sourceNpmrcFolder = path.join(rushCommonFolder, 'config', 'rush');
|
|
_syncNpmrc(logger, sourceNpmrcFolder, packageInstallFolder);
|
|
_createPackageJson(packageInstallFolder, packageName, packageVersion);
|
|
_installPackage(logger, packageInstallFolder, packageName, packageVersion);
|
|
_writeFlagFile(packageInstallFolder);
|
|
}
|
|
const statusMessage = `Invoking "${packageBinName} ${packageBinArgs.join(' ')}"`;
|
|
const statusMessageLine = new Array(statusMessage.length + 1).join('-');
|
|
logger.info(os.EOL + statusMessage + os.EOL + statusMessageLine + os.EOL);
|
|
const binPath = _getBinPath(packageInstallFolder, packageBinName);
|
|
const binFolderPath = path.resolve(packageInstallFolder, NODE_MODULES_FOLDER_NAME, '.bin');
|
|
// Windows environment variables are case-insensitive. Instead of using SpawnSyncOptions.env, we need to
|
|
// assign via the process.env proxy to ensure that we append to the right PATH key.
|
|
const originalEnvPath = process.env.PATH || '';
|
|
let result;
|
|
try {
|
|
// Node.js on Windows can not spawn a file when the path has a space on it
|
|
// unless the path gets wrapped in a cmd friendly way and shell mode is used
|
|
const shouldUseShell = binPath.includes(' ') && os.platform() === 'win32';
|
|
const platformBinPath = shouldUseShell ? `"${binPath}"` : binPath;
|
|
process.env.PATH = [binFolderPath, originalEnvPath].join(path.delimiter);
|
|
result = childProcess.spawnSync(platformBinPath, packageBinArgs, {
|
|
stdio: 'inherit',
|
|
windowsVerbatimArguments: false,
|
|
shell: shouldUseShell,
|
|
cwd: process.cwd(),
|
|
env: process.env
|
|
});
|
|
}
|
|
finally {
|
|
process.env.PATH = originalEnvPath;
|
|
}
|
|
if (result.status !== null) {
|
|
return result.status;
|
|
}
|
|
else {
|
|
throw result.error || new Error('An unknown error occurred.');
|
|
}
|
|
}
|
|
exports.installAndRun = installAndRun;
|
|
function runWithErrorAndStatusCode(logger, fn) {
|
|
process.exitCode = 1;
|
|
try {
|
|
const exitCode = fn();
|
|
process.exitCode = exitCode;
|
|
}
|
|
catch (e) {
|
|
logger.error(os.EOL + os.EOL + e.toString() + os.EOL + os.EOL);
|
|
}
|
|
}
|
|
exports.runWithErrorAndStatusCode = runWithErrorAndStatusCode;
|
|
function _run() {
|
|
const [nodePath /* Ex: /bin/node */, scriptPath /* /repo/common/scripts/install-run-rush.js */, rawPackageSpecifier /* qrcode@^1.2.0 */, packageBinName /* qrcode */, ...packageBinArgs /* [-f, myproject/lib] */] = process.argv;
|
|
if (!nodePath) {
|
|
throw new Error('Unexpected exception: could not detect node path');
|
|
}
|
|
if (path.basename(scriptPath).toLowerCase() !== 'install-run.js') {
|
|
// If install-run.js wasn't directly invoked, don't execute the rest of this function. Return control
|
|
// to the script that (presumably) imported this file
|
|
return;
|
|
}
|
|
if (process.argv.length < 4) {
|
|
console.log('Usage: install-run.js <package>@<version> <command> [args...]');
|
|
console.log('Example: install-run.js qrcode@1.2.2 qrcode https://rushjs.io');
|
|
process.exit(1);
|
|
}
|
|
const logger = { info: console.log, error: console.error };
|
|
runWithErrorAndStatusCode(logger, () => {
|
|
const rushJsonFolder = findRushJsonFolder();
|
|
const rushCommonFolder = _ensureAndJoinPath(rushJsonFolder, 'common');
|
|
const packageSpecifier = _parsePackageSpecifier(rawPackageSpecifier);
|
|
const name = packageSpecifier.name;
|
|
const version = _resolvePackageVersion(logger, rushCommonFolder, packageSpecifier);
|
|
if (packageSpecifier.version !== version) {
|
|
console.log(`Resolved to ${name}@${version}`);
|
|
}
|
|
return installAndRun(logger, name, version, packageBinName, packageBinArgs);
|
|
});
|
|
}
|
|
_run();
|
|
//# sourceMappingURL=install-run.js.map
|