microsoft/typespec

Public

mirrored from https://github.com/microsoft/typespecAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
65a5918bf468cd7f8685a35735b5dda7f3ec4221

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

common/scripts/install-run.js

478lines · modecode

1"use strict";
2// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
3// See the @microsoft/rush package's LICENSE file for license information.
4var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
5 if (k2 === undefined) k2 = k;
6 Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
7}) : (function(o, m, k, k2) {
8 if (k2 === undefined) k2 = k;
9 o[k2] = m[k];
10}));
11var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
12 Object.defineProperty(o, "default", { enumerable: true, value: v });
13}) : function(o, v) {
14 o["default"] = v;
15});
16var __importStar = (this && this.__importStar) || function (mod) {
17 if (mod && mod.__esModule) return mod;
18 var result = {};
19 if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
20 __setModuleDefault(result, mod);
21 return result;
22};
23Object.defineProperty(exports, "__esModule", { value: true });
24exports.runWithErrorAndStatusCode = exports.installAndRun = exports.findRushJsonFolder = exports.getNpmPath = exports.RUSH_JSON_FILENAME = void 0;
25// THIS FILE WAS GENERATED BY A TOOL. ANY MANUAL MODIFICATIONS WILL GET OVERWRITTEN WHENEVER RUSH IS UPGRADED.
26//
27// This script is intended for usage in an automated build environment where a Node tool may not have
28// been preinstalled, or may have an unpredictable version. This script will automatically install the specified
29// version of the specified tool (if not already installed), and then pass a command-line to it.
30// An example usage would be:
31//
32// node common/scripts/install-run.js qrcode@1.2.2 qrcode https://rushjs.io
33//
34// For more information, see: https://rushjs.io/pages/maintainer/setup_new_repo/
35const childProcess = __importStar(require("child_process"));
36const fs = __importStar(require("fs"));
37const os = __importStar(require("os"));
38const path = __importStar(require("path"));
39exports.RUSH_JSON_FILENAME = 'rush.json';
40const RUSH_TEMP_FOLDER_ENV_VARIABLE_NAME = 'RUSH_TEMP_FOLDER';
41const INSTALLED_FLAG_FILENAME = 'installed.flag';
42const NODE_MODULES_FOLDER_NAME = 'node_modules';
43const PACKAGE_JSON_FILENAME = 'package.json';
44/**
45 * Parse a package specifier (in the form of name\@version) into name and version parts.
46 */
47function _parsePackageSpecifier(rawPackageSpecifier) {
48 rawPackageSpecifier = (rawPackageSpecifier || '').trim();
49 const separatorIndex = rawPackageSpecifier.lastIndexOf('@');
50 let name;
51 let version = undefined;
52 if (separatorIndex === 0) {
53 // The specifier starts with a scope and doesn't have a version specified
54 name = rawPackageSpecifier;
55 }
56 else if (separatorIndex === -1) {
57 // The specifier doesn't have a version
58 name = rawPackageSpecifier;
59 }
60 else {
61 name = rawPackageSpecifier.substring(0, separatorIndex);
62 version = rawPackageSpecifier.substring(separatorIndex + 1);
63 }
64 if (!name) {
65 throw new Error(`Invalid package specifier: ${rawPackageSpecifier}`);
66 }
67 return { name, version };
68}
69/**
70 * As a workaround, copyAndTrimNpmrcFile() copies the .npmrc file to the target folder, and also trims
71 * unusable lines from the .npmrc file.
72 *
73 * Why are we trimming the .npmrc lines? NPM allows environment variables to be specified in
74 * the .npmrc file to provide different authentication tokens for different registry.
75 * However, if the environment variable is undefined, it expands to an empty string, which
76 * produces a valid-looking mapping with an invalid URL that causes an error. Instead,
77 * we'd prefer to skip that line and continue looking in other places such as the user's
78 * home directory.
79 *
80 * IMPORTANT: THIS CODE SHOULD BE KEPT UP TO DATE WITH Utilities.copyAndTrimNpmrcFile()
81 */
82function _copyAndTrimNpmrcFile(sourceNpmrcPath, targetNpmrcPath) {
83 console.log(`Transforming ${sourceNpmrcPath}`); // Verbose
84 console.log(` --> "${targetNpmrcPath}"`);
85 let npmrcFileLines = fs.readFileSync(sourceNpmrcPath).toString().split('\n');
86 npmrcFileLines = npmrcFileLines.map((line) => (line || '').trim());
87 const resultLines = [];
88 // This finds environment variable tokens that look like "${VAR_NAME}"
89 const expansionRegExp = /\$\{([^\}]+)\}/g;
90 // Comment lines start with "#" or ";"
91 const commentRegExp = /^\s*[#;]/;
92 // Trim out lines that reference environment variables that aren't defined
93 for (const line of npmrcFileLines) {
94 let lineShouldBeTrimmed = false;
95 // Ignore comment lines
96 if (!commentRegExp.test(line)) {
97 const environmentVariables = line.match(expansionRegExp);
98 if (environmentVariables) {
99 for (const token of environmentVariables) {
100 // Remove the leading "${" and the trailing "}" from the token
101 const environmentVariableName = token.substring(2, token.length - 1);
102 // Is the environment variable defined?
103 if (!process.env[environmentVariableName]) {
104 // No, so trim this line
105 lineShouldBeTrimmed = true;
106 break;
107 }
108 }
109 }
110 }
111 if (lineShouldBeTrimmed) {
112 // Example output:
113 // "; MISSING ENVIRONMENT VARIABLE: //my-registry.com/npm/:_authToken=${MY_AUTH_TOKEN}"
114 resultLines.push('; MISSING ENVIRONMENT VARIABLE: ' + line);
115 }
116 else {
117 resultLines.push(line);
118 }
119 }
120 fs.writeFileSync(targetNpmrcPath, resultLines.join(os.EOL));
121}
122/**
123 * syncNpmrc() copies the .npmrc file to the target folder, and also trims unusable lines from the .npmrc file.
124 * If the source .npmrc file not exist, then syncNpmrc() will delete an .npmrc that is found in the target folder.
125 *
126 * IMPORTANT: THIS CODE SHOULD BE KEPT UP TO DATE WITH Utilities._syncNpmrc()
127 */
128function _syncNpmrc(sourceNpmrcFolder, targetNpmrcFolder, useNpmrcPublish) {
129 const sourceNpmrcPath = path.join(sourceNpmrcFolder, !useNpmrcPublish ? '.npmrc' : '.npmrc-publish');
130 const targetNpmrcPath = path.join(targetNpmrcFolder, '.npmrc');
131 try {
132 if (fs.existsSync(sourceNpmrcPath)) {
133 _copyAndTrimNpmrcFile(sourceNpmrcPath, targetNpmrcPath);
134 }
135 else if (fs.existsSync(targetNpmrcPath)) {
136 // If the source .npmrc doesn't exist and there is one in the target, delete the one in the target
137 console.log(`Deleting ${targetNpmrcPath}`); // Verbose
138 fs.unlinkSync(targetNpmrcPath);
139 }
140 }
141 catch (e) {
142 throw new Error(`Error syncing .npmrc file: ${e}`);
143 }
144}
145let _npmPath = undefined;
146/**
147 * Get the absolute path to the npm executable
148 */
149function getNpmPath() {
150 if (!_npmPath) {
151 try {
152 if (os.platform() === 'win32') {
153 // We're on Windows
154 const whereOutput = childProcess.execSync('where npm', { stdio: [] }).toString();
155 const lines = whereOutput.split(os.EOL).filter((line) => !!line);
156 // take the last result, we are looking for a .cmd command
157 // see https://github.com/microsoft/rushstack/issues/759
158 _npmPath = lines[lines.length - 1];
159 }
160 else {
161 // We aren't on Windows - assume we're on *NIX or Darwin
162 _npmPath = childProcess.execSync('command -v npm', { stdio: [] }).toString();
163 }
164 }
165 catch (e) {
166 throw new Error(`Unable to determine the path to the NPM tool: ${e}`);
167 }
168 _npmPath = _npmPath.trim();
169 if (!fs.existsSync(_npmPath)) {
170 throw new Error('The NPM executable does not exist');
171 }
172 }
173 return _npmPath;
174}
175exports.getNpmPath = getNpmPath;
176function _ensureFolder(folderPath) {
177 if (!fs.existsSync(folderPath)) {
178 const parentDir = path.dirname(folderPath);
179 _ensureFolder(parentDir);
180 fs.mkdirSync(folderPath);
181 }
182}
183/**
184 * Create missing directories under the specified base directory, and return the resolved directory.
185 *
186 * Does not support "." or ".." path segments.
187 * Assumes the baseFolder exists.
188 */
189function _ensureAndJoinPath(baseFolder, ...pathSegments) {
190 let joinedPath = baseFolder;
191 try {
192 for (let pathSegment of pathSegments) {
193 pathSegment = pathSegment.replace(/[\\\/]/g, '+');
194 joinedPath = path.join(joinedPath, pathSegment);
195 if (!fs.existsSync(joinedPath)) {
196 fs.mkdirSync(joinedPath);
197 }
198 }
199 }
200 catch (e) {
201 throw new Error(`Error building local installation folder (${path.join(baseFolder, ...pathSegments)}): ${e}`);
202 }
203 return joinedPath;
204}
205function _getRushTempFolder(rushCommonFolder) {
206 const rushTempFolder = process.env[RUSH_TEMP_FOLDER_ENV_VARIABLE_NAME];
207 if (rushTempFolder !== undefined) {
208 _ensureFolder(rushTempFolder);
209 return rushTempFolder;
210 }
211 else {
212 return _ensureAndJoinPath(rushCommonFolder, 'temp');
213 }
214}
215/**
216 * Resolve a package specifier to a static version
217 */
218function _resolvePackageVersion(rushCommonFolder, { name, version }) {
219 if (!version) {
220 version = '*'; // If no version is specified, use the latest version
221 }
222 if (version.match(/^[a-zA-Z0-9\-\+\.]+$/)) {
223 // If the version contains only characters that we recognize to be used in static version specifiers,
224 // pass the version through
225 return version;
226 }
227 else {
228 // version resolves to
229 try {
230 const rushTempFolder = _getRushTempFolder(rushCommonFolder);
231 const sourceNpmrcFolder = path.join(rushCommonFolder, 'config', 'rush');
232 _syncNpmrc(sourceNpmrcFolder, rushTempFolder);
233 const npmPath = getNpmPath();
234 // This returns something that looks like:
235 // @microsoft/rush@3.0.0 '3.0.0'
236 // @microsoft/rush@3.0.1 '3.0.1'
237 // ...
238 // @microsoft/rush@3.0.20 '3.0.20'
239 // <blank line>
240 const npmVersionSpawnResult = childProcess.spawnSync(npmPath, ['view', `${name}@${version}`, 'version', '--no-update-notifier'], {
241 cwd: rushTempFolder,
242 stdio: []
243 });
244 if (npmVersionSpawnResult.status !== 0) {
245 throw new Error(`"npm view" returned error code ${npmVersionSpawnResult.status}`);
246 }
247 const npmViewVersionOutput = npmVersionSpawnResult.stdout.toString();
248 const versionLines = npmViewVersionOutput.split('\n').filter((line) => !!line);
249 const latestVersion = versionLines[versionLines.length - 1];
250 if (!latestVersion) {
251 throw new Error('No versions found for the specified version range.');
252 }
253 const versionMatches = latestVersion.match(/^.+\s\'(.+)\'$/);
254 if (!versionMatches) {
255 throw new Error(`Invalid npm output ${latestVersion}`);
256 }
257 return versionMatches[1];
258 }
259 catch (e) {
260 throw new Error(`Unable to resolve version ${version} of package ${name}: ${e}`);
261 }
262 }
263}
264let _rushJsonFolder;
265/**
266 * Find the absolute path to the folder containing rush.json
267 */
268function findRushJsonFolder() {
269 if (!_rushJsonFolder) {
270 let basePath = __dirname;
271 let tempPath = __dirname;
272 do {
273 const testRushJsonPath = path.join(basePath, exports.RUSH_JSON_FILENAME);
274 if (fs.existsSync(testRushJsonPath)) {
275 _rushJsonFolder = basePath;
276 break;
277 }
278 else {
279 basePath = tempPath;
280 }
281 } while (basePath !== (tempPath = path.dirname(basePath))); // Exit the loop when we hit the disk root
282 if (!_rushJsonFolder) {
283 throw new Error('Unable to find rush.json.');
284 }
285 }
286 return _rushJsonFolder;
287}
288exports.findRushJsonFolder = findRushJsonFolder;
289/**
290 * Detects if the package in the specified directory is installed
291 */
292function _isPackageAlreadyInstalled(packageInstallFolder) {
293 try {
294 const flagFilePath = path.join(packageInstallFolder, INSTALLED_FLAG_FILENAME);
295 if (!fs.existsSync(flagFilePath)) {
296 return false;
297 }
298 const fileContents = fs.readFileSync(flagFilePath).toString();
299 return fileContents.trim() === process.version;
300 }
301 catch (e) {
302 return false;
303 }
304}
305/**
306 * Removes the following files and directories under the specified folder path:
307 * - installed.flag
308 * -
309 * - node_modules
310 */
311function _cleanInstallFolder(rushTempFolder, packageInstallFolder) {
312 try {
313 const flagFile = path.resolve(packageInstallFolder, INSTALLED_FLAG_FILENAME);
314 if (fs.existsSync(flagFile)) {
315 fs.unlinkSync(flagFile);
316 }
317 const packageLockFile = path.resolve(packageInstallFolder, 'package-lock.json');
318 if (fs.existsSync(packageLockFile)) {
319 fs.unlinkSync(packageLockFile);
320 }
321 const nodeModulesFolder = path.resolve(packageInstallFolder, NODE_MODULES_FOLDER_NAME);
322 if (fs.existsSync(nodeModulesFolder)) {
323 const rushRecyclerFolder = _ensureAndJoinPath(rushTempFolder, 'rush-recycler');
324 fs.renameSync(nodeModulesFolder, path.join(rushRecyclerFolder, `install-run-${Date.now().toString()}`));
325 }
326 }
327 catch (e) {
328 throw new Error(`Error cleaning the package install folder (${packageInstallFolder}): ${e}`);
329 }
330}
331function _createPackageJson(packageInstallFolder, name, version) {
332 try {
333 const packageJsonContents = {
334 name: 'ci-rush',
335 version: '0.0.0',
336 dependencies: {
337 [name]: version
338 },
339 description: "DON'T WARN",
340 repository: "DON'T WARN",
341 license: 'MIT'
342 };
343 const packageJsonPath = path.join(packageInstallFolder, PACKAGE_JSON_FILENAME);
344 fs.writeFileSync(packageJsonPath, JSON.stringify(packageJsonContents, undefined, 2));
345 }
346 catch (e) {
347 throw new Error(`Unable to create package.json: ${e}`);
348 }
349}
350/**
351 * Run "npm install" in the package install folder.
352 */
353function _installPackage(packageInstallFolder, name, version) {
354 try {
355 console.log(`Installing ${name}...`);
356 const npmPath = getNpmPath();
357 const result = childProcess.spawnSync(npmPath, ['install'], {
358 stdio: 'inherit',
359 cwd: packageInstallFolder,
360 env: process.env
361 });
362 if (result.status !== 0) {
363 throw new Error('"npm install" encountered an error');
364 }
365 console.log(`Successfully installed ${name}@${version}`);
366 }
367 catch (e) {
368 throw new Error(`Unable to install package: ${e}`);
369 }
370}
371/**
372 * Get the ".bin" path for the package.
373 */
374function _getBinPath(packageInstallFolder, binName) {
375 const binFolderPath = path.resolve(packageInstallFolder, NODE_MODULES_FOLDER_NAME, '.bin');
376 const resolvedBinName = os.platform() === 'win32' ? `${binName}.cmd` : binName;
377 return path.resolve(binFolderPath, resolvedBinName);
378}
379/**
380 * Write a flag file to the package's install directory, signifying that the install was successful.
381 */
382function _writeFlagFile(packageInstallFolder) {
383 try {
384 const flagFilePath = path.join(packageInstallFolder, INSTALLED_FLAG_FILENAME);
385 fs.writeFileSync(flagFilePath, process.version);
386 }
387 catch (e) {
388 throw new Error(`Unable to create installed.flag file in ${packageInstallFolder}`);
389 }
390}
391function installAndRun(packageName, packageVersion, packageBinName, packageBinArgs) {
392 const rushJsonFolder = findRushJsonFolder();
393 const rushCommonFolder = path.join(rushJsonFolder, 'common');
394 const rushTempFolder = _getRushTempFolder(rushCommonFolder);
395 const packageInstallFolder = _ensureAndJoinPath(rushTempFolder, 'install-run', `${packageName}@${packageVersion}`);
396 if (!_isPackageAlreadyInstalled(packageInstallFolder)) {
397 // The package isn't already installed
398 _cleanInstallFolder(rushTempFolder, packageInstallFolder);
399 const sourceNpmrcFolder = path.join(rushCommonFolder, 'config', 'rush');
400 _syncNpmrc(sourceNpmrcFolder, packageInstallFolder);
401 _createPackageJson(packageInstallFolder, packageName, packageVersion);
402 _installPackage(packageInstallFolder, packageName, packageVersion);
403 _writeFlagFile(packageInstallFolder);
404 }
405 const statusMessage = `Invoking "${packageBinName} ${packageBinArgs.join(' ')}"`;
406 const statusMessageLine = new Array(statusMessage.length + 1).join('-');
407 console.log(os.EOL + statusMessage + os.EOL + statusMessageLine + os.EOL);
408 const binPath = _getBinPath(packageInstallFolder, packageBinName);
409 const binFolderPath = path.resolve(packageInstallFolder, NODE_MODULES_FOLDER_NAME, '.bin');
410 // Windows environment variables are case-insensitive. Instead of using SpawnSyncOptions.env, we need to
411 // assign via the process.env proxy to ensure that we append to the right PATH key.
412 const originalEnvPath = process.env.PATH || '';
413 let result;
414 try {
415 // Node.js on Windows can not spawn a file when the path has a space on it
416 // unless the path gets wrapped in a cmd friendly way and shell mode is used
417 const shouldUseShell = binPath.includes(' ') && os.platform() === 'win32';
418 const platformBinPath = shouldUseShell ? `"${binPath}"` : binPath;
419 process.env.PATH = [binFolderPath, originalEnvPath].join(path.delimiter);
420 result = childProcess.spawnSync(platformBinPath, packageBinArgs, {
421 stdio: 'inherit',
422 windowsVerbatimArguments: false,
423 shell: shouldUseShell,
424 cwd: process.cwd(),
425 env: process.env
426 });
427 }
428 finally {
429 process.env.PATH = originalEnvPath;
430 }
431 if (result.status !== null) {
432 return result.status;
433 }
434 else {
435 throw result.error || new Error('An unknown error occurred.');
436 }
437}
438exports.installAndRun = installAndRun;
439function runWithErrorAndStatusCode(fn) {
440 process.exitCode = 1;
441 try {
442 const exitCode = fn();
443 process.exitCode = exitCode;
444 }
445 catch (e) {
446 console.error(os.EOL + os.EOL + e.toString() + os.EOL + os.EOL);
447 }
448}
449exports.runWithErrorAndStatusCode = runWithErrorAndStatusCode;
450function _run() {
451 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;
452 if (!nodePath) {
453 throw new Error('Unexpected exception: could not detect node path');
454 }
455 if (path.basename(scriptPath).toLowerCase() !== 'install-run.js') {
456 // If install-run.js wasn't directly invoked, don't execute the rest of this function. Return control
457 // to the script that (presumably) imported this file
458 return;
459 }
460 if (process.argv.length < 4) {
461 console.log('Usage: install-run.js <package>@<version> <command> [args...]');
462 console.log('Example: install-run.js qrcode@1.2.2 qrcode https://rushjs.io');
463 process.exit(1);
464 }
465 runWithErrorAndStatusCode(() => {
466 const rushJsonFolder = findRushJsonFolder();
467 const rushCommonFolder = _ensureAndJoinPath(rushJsonFolder, 'common');
468 const packageSpecifier = _parsePackageSpecifier(rawPackageSpecifier);
469 const name = packageSpecifier.name;
470 const version = _resolvePackageVersion(rushCommonFolder, packageSpecifier);
471 if (packageSpecifier.version !== version) {
472 console.log(`Resolved to ${name}@${version}`);
473 }
474 return installAndRun(name, version, packageBinName, packageBinArgs);
475 });
476}
477_run();
478//# sourceMappingURL=install-run.js.map