@rushstack/node-core-library
Advanced tools
Comparing version 3.35.2 to 3.36.0
@@ -5,2 +5,17 @@ { | ||
{ | ||
"version": "3.36.0", | ||
"tag": "@rushstack/node-core-library_v3.36.0", | ||
"date": "Fri, 05 Feb 2021 16:10:42 GMT", | ||
"comments": { | ||
"minor": [ | ||
{ | ||
"comment": "Add EnvironmentMap API" | ||
}, | ||
{ | ||
"comment": "Add Executable.spawn() API" | ||
} | ||
] | ||
} | ||
}, | ||
{ | ||
"version": "3.35.2", | ||
@@ -7,0 +22,0 @@ "tag": "@rushstack/node-core-library_v3.35.2", |
# Change Log - @rushstack/node-core-library | ||
This log was last generated on Thu, 10 Dec 2020 23:25:49 GMT and should not be manually modified. | ||
This log was last generated on Fri, 05 Feb 2021 16:10:42 GMT and should not be manually modified. | ||
## 3.36.0 | ||
Fri, 05 Feb 2021 16:10:42 GMT | ||
### Minor changes | ||
- Add EnvironmentMap API | ||
- Add Executable.spawn() API | ||
## 3.35.2 | ||
@@ -6,0 +14,0 @@ Thu, 10 Dec 2020 23:25:49 GMT |
@@ -8,5 +8,5 @@ // This file is read by tools that parse documentation comments conforming to the TSDoc standard. | ||
"packageName": "@microsoft/api-extractor", | ||
"packageVersion": "7.12.0" | ||
"packageVersion": "7.12.1" | ||
} | ||
] | ||
} |
/// <reference types="node" /> | ||
import * as child_process from 'child_process'; | ||
import { EnvironmentMap } from './EnvironmentMap'; | ||
/** | ||
@@ -9,3 +10,4 @@ * Typings for one of the streams inside IExecutableSpawnSyncOptions.stdio. | ||
/** | ||
* Typings for IExecutableSpawnSyncOptions.stdio. | ||
* Types for {@link IExecutableSpawnSyncOptions.stdio} | ||
* and {@link IExecutableSpawnOptions.stdio} | ||
* @public | ||
@@ -24,8 +26,20 @@ */ | ||
/** | ||
* The environment variables for the child process. If omitted, process.env will be used. | ||
* The environment variables for the child process. | ||
* | ||
* @remarks | ||
* If `environment` and `environmentMap` are both omitted, then `process.env` will be used. | ||
* If `environment` and `environmentMap` cannot both be specified. | ||
*/ | ||
environment?: NodeJS.ProcessEnv; | ||
/** | ||
* The environment variables for the child process. | ||
* | ||
* @remarks | ||
* If `environment` and `environmentMap` are both omitted, then `process.env` will be used. | ||
* If `environment` and `environmentMap` cannot both be specified. | ||
*/ | ||
environmentMap?: EnvironmentMap; | ||
} | ||
/** | ||
* Options for Executable.execute(). | ||
* Options for {@link Executable.spawnSync} | ||
* @public | ||
@@ -53,3 +67,3 @@ */ | ||
/** | ||
* The largest amount of bytes allowed on stdout or stderr for this synchonous operation. | ||
* The largest amount of bytes allowed on stdout or stderr for this synchronous operation. | ||
* If exceeded, the child process will be terminated. The default is 200 * 1024. | ||
@@ -60,2 +74,15 @@ */ | ||
/** | ||
* Options for {@link Executable.spawn} | ||
* @public | ||
*/ | ||
export interface IExecutableSpawnOptions extends IExecutableResolveOptions { | ||
/** | ||
* The stdio mappings for the child process. | ||
* | ||
* NOTE: If IExecutableSpawnSyncOptions.input is provided, it will take precedence | ||
* over the stdin mapping (stdio[0]). | ||
*/ | ||
stdio?: ExecutableStdioMapping; | ||
} | ||
/** | ||
* The Executable class provides a safe, portable, recommended solution for tools that need | ||
@@ -122,2 +149,27 @@ * to launch child processes. | ||
/** | ||
* Start a child process. | ||
* | ||
* @remarks | ||
* This function is similar to child_process.spawn(). The main differences are: | ||
* | ||
* - It does not invoke the OS shell unless the executable file is a shell script. | ||
* - Command-line arguments containing special characters are more accurately passed | ||
* through to the child process. | ||
* - If the filename is missing a path, then the shell's default PATH will be searched. | ||
* - If the filename is missing a file extension, then Windows default file extensions | ||
* will be searched. | ||
* | ||
* This command is asynchronous, but it does not return a `Promise`. Instead it returns | ||
* a Node.js `ChildProcess` supporting event notifications. | ||
* | ||
* @param filename - The name of the executable file. This string must not contain any | ||
* command-line arguments. If the name contains any path delimiters, then the shell's | ||
* default PATH will not be searched. | ||
* @param args - The command-line arguments to be passed to the process. | ||
* @param options - Additional options | ||
* @returns the same data type as returned by the NodeJS child_process.spawnSync() API | ||
*/ | ||
static spawn(filename: string, args: string[], options?: IExecutableSpawnOptions): child_process.ChildProcess; | ||
private static _buildCommandLineFixup; | ||
/** | ||
* Given a filename, this determines the absolute path of the executable file that would | ||
@@ -141,2 +193,3 @@ * be executed by a shell: | ||
private static _tryResolveFileExtension; | ||
private static _buildEnvironmentMap; | ||
/** | ||
@@ -143,0 +196,0 @@ * This is used when searching the shell PATH for an executable, to determine |
@@ -28,2 +28,3 @@ "use strict"; | ||
const path = __importStar(require("path")); | ||
const EnvironmentMap_1 = require("./EnvironmentMap"); | ||
const FileSystem_1 = require("./FileSystem"); | ||
@@ -101,3 +102,3 @@ /** | ||
cwd: context.currentWorkingDirectory, | ||
env: context.environment, | ||
env: context.environmentMap.toObject(), | ||
input: options.input, | ||
@@ -110,21 +111,66 @@ stdio: options.stdio, | ||
encoding: 'utf8', | ||
// NOTE: This is always false, because Rushell is recommended instead of relying on the OS shell. | ||
// NOTE: This is always false, because Rushell will be recommended instead of relying on the OS shell. | ||
shell: false | ||
}; | ||
// PROBLEM: Given an "args" array of strings that may contain special characters (e.g. spaces, | ||
// backslashes, quotes), ensure that these strings pass through to the child process's ARGV array | ||
// without anything getting corrupted along the way. | ||
// | ||
// On Unix you just pass the array to spawnSync(). But on Windows, this is a very complex problem: | ||
// - The Win32 CreateProcess() API expects the args to be encoded as a single text string | ||
// - The decoding of this string is up to the application (not the OS), and there are 3 different | ||
// algorithms in common usage: the cmd.exe shell, the Microsoft CRT library init code, and | ||
// the Win32 CommandLineToArgvW() | ||
// - The encodings are counterintuitive and have lots of special cases | ||
// - NodeJS spawnSync() tries do the encoding without knowing which decoder will be used | ||
// | ||
// See these articles for a full analysis: | ||
// http://www.windowsinspired.com/understanding-the-command-line-string-and-arguments-received-by-a-windows-program/ | ||
// http://www.windowsinspired.com/how-a-windows-programs-splits-its-command-line-into-individual-arguments/ | ||
const environment = (options && options.environment) || process.env; | ||
const normalizedCommandLine = Executable._buildCommandLineFixup(resolvedPath, args, context); | ||
return child_process.spawnSync(normalizedCommandLine.path, normalizedCommandLine.args, spawnOptions); | ||
} | ||
/** | ||
* Start a child process. | ||
* | ||
* @remarks | ||
* This function is similar to child_process.spawn(). The main differences are: | ||
* | ||
* - It does not invoke the OS shell unless the executable file is a shell script. | ||
* - Command-line arguments containing special characters are more accurately passed | ||
* through to the child process. | ||
* - If the filename is missing a path, then the shell's default PATH will be searched. | ||
* - If the filename is missing a file extension, then Windows default file extensions | ||
* will be searched. | ||
* | ||
* This command is asynchronous, but it does not return a `Promise`. Instead it returns | ||
* a Node.js `ChildProcess` supporting event notifications. | ||
* | ||
* @param filename - The name of the executable file. This string must not contain any | ||
* command-line arguments. If the name contains any path delimiters, then the shell's | ||
* default PATH will not be searched. | ||
* @param args - The command-line arguments to be passed to the process. | ||
* @param options - Additional options | ||
* @returns the same data type as returned by the NodeJS child_process.spawnSync() API | ||
*/ | ||
static spawn(filename, args, options) { | ||
if (!options) { | ||
options = {}; | ||
} | ||
const context = Executable._getExecutableContext(options); | ||
const resolvedPath = Executable._tryResolve(filename, options, context); | ||
if (!resolvedPath) { | ||
throw new Error(`The executable file was not found: "${filename}"`); | ||
} | ||
const spawnOptions = { | ||
cwd: context.currentWorkingDirectory, | ||
env: context.environmentMap.toObject(), | ||
stdio: options.stdio, | ||
// NOTE: This is always false, because Rushell will be recommended instead of relying on the OS shell. | ||
shell: false | ||
}; | ||
const normalizedCommandLine = Executable._buildCommandLineFixup(resolvedPath, args, context); | ||
return child_process.spawn(normalizedCommandLine.path, normalizedCommandLine.args, spawnOptions); | ||
} | ||
// PROBLEM: Given an "args" array of strings that may contain special characters (e.g. spaces, | ||
// backslashes, quotes), ensure that these strings pass through to the child process's ARGV array | ||
// without anything getting corrupted along the way. | ||
// | ||
// On Unix you just pass the array to spawnSync(). But on Windows, this is a very complex problem: | ||
// - The Win32 CreateProcess() API expects the args to be encoded as a single text string | ||
// - The decoding of this string is up to the application (not the OS), and there are 3 different | ||
// algorithms in common usage: the cmd.exe shell, the Microsoft CRT library init code, and | ||
// the Win32 CommandLineToArgvW() | ||
// - The encodings are counterintuitive and have lots of special cases | ||
// - NodeJS spawnSync() tries do the encoding without knowing which decoder will be used | ||
// | ||
// See these articles for a full analysis: | ||
// http://www.windowsinspired.com/understanding-the-command-line-string-and-arguments-received-by-a-windows-program/ | ||
// http://www.windowsinspired.com/how-a-windows-programs-splits-its-command-line-into-individual-arguments/ | ||
static _buildCommandLineFixup(resolvedPath, args, context) { | ||
const fileExtension = path.extname(resolvedPath); | ||
@@ -142,3 +188,3 @@ if (os.platform() === 'win32') { | ||
// These file types must be invoked via the Windows shell | ||
let shellPath = environment.COMSPEC; | ||
let shellPath = context.environmentMap.get('COMSPEC'); | ||
if (!shellPath || !Executable._canExecute(shellPath, context)) { | ||
@@ -162,3 +208,3 @@ shellPath = Executable.tryResolve('cmd.exe'); | ||
shellArgs.push(...args); | ||
return child_process.spawnSync(shellPath, shellArgs, spawnOptions); | ||
return { path: shellPath, args: shellArgs }; | ||
} | ||
@@ -169,3 +215,6 @@ default: | ||
} | ||
return child_process.spawnSync(resolvedPath, args, spawnOptions); | ||
return { | ||
path: resolvedPath, | ||
args: args | ||
}; | ||
} | ||
@@ -228,2 +277,19 @@ /** | ||
} | ||
static _buildEnvironmentMap(options) { | ||
const environmentMap = new EnvironmentMap_1.EnvironmentMap(); | ||
if (options.environment !== undefined && options.environmentMap !== undefined) { | ||
throw new Error('IExecutableResolveOptions.environment and IExecutableResolveOptions.environmentMap' + | ||
' cannot both be specified'); | ||
} | ||
if (options.environment !== undefined) { | ||
environmentMap.mergeFromObject(options.environment); | ||
} | ||
else if (options.environmentMap !== undefined) { | ||
environmentMap.mergeFrom(options.environmentMap); | ||
} | ||
else { | ||
environmentMap.mergeFromObject(process.env); | ||
} | ||
return environmentMap; | ||
} | ||
/** | ||
@@ -269,3 +335,3 @@ * This is used when searching the shell PATH for an executable, to determine | ||
static _getSearchFolders(context) { | ||
const pathList = context.environment.PATH || ''; | ||
const pathList = context.environmentMap.get('PATH') || ''; | ||
const folders = []; | ||
@@ -302,3 +368,3 @@ // Avoid processing duplicates | ||
} | ||
const environment = options.environment || process.env; | ||
const environment = Executable._buildEnvironmentMap(options); | ||
let currentWorkingDirectory; | ||
@@ -313,3 +379,3 @@ if (options.currentWorkingDirectory) { | ||
if (os.platform() === 'win32') { | ||
const pathExtVariable = environment.PATHEXT || ''; | ||
const pathExtVariable = environment.get('PATHEXT') || ''; | ||
for (const splitValue of pathExtVariable.split(';')) { | ||
@@ -327,3 +393,3 @@ const trimmed = splitValue.trim().toLowerCase(); | ||
return { | ||
environment, | ||
environmentMap: environment, | ||
currentWorkingDirectory, | ||
@@ -330,0 +396,0 @@ windowsExecutableExtensions |
@@ -11,3 +11,4 @@ /** | ||
export { Enum } from './Enum'; | ||
export { ExecutableStdioStreamMapping, ExecutableStdioMapping, IExecutableResolveOptions, IExecutableSpawnSyncOptions, Executable } from './Executable'; | ||
export { EnvironmentMap, IEnvironmentEntry } from './EnvironmentMap'; | ||
export { ExecutableStdioStreamMapping, ExecutableStdioMapping, IExecutableResolveOptions, IExecutableSpawnSyncOptions, IExecutableSpawnOptions, Executable } from './Executable'; | ||
export { INodePackageJson, IPackageJson, IPackageJsonDependencyTable, IPackageJsonScriptTable } from './IPackageJson'; | ||
@@ -14,0 +15,0 @@ export { Import, IImportResolveOptions, IImportResolveModuleOptions, IImportResolvePackageOptions } from './Import'; |
@@ -16,2 +16,4 @@ "use strict"; | ||
Object.defineProperty(exports, "Enum", { enumerable: true, get: function () { return Enum_1.Enum; } }); | ||
var EnvironmentMap_1 = require("./EnvironmentMap"); | ||
Object.defineProperty(exports, "EnvironmentMap", { enumerable: true, get: function () { return EnvironmentMap_1.EnvironmentMap; } }); | ||
var Executable_1 = require("./Executable"); | ||
@@ -18,0 +20,0 @@ Object.defineProperty(exports, "Executable", { enumerable: true, get: function () { return Executable_1.Executable; } }); |
@@ -219,13 +219,12 @@ "use strict"; | ||
const startTime = Date.now(); | ||
const retryLoop = () => { | ||
const retryLoop = async () => { | ||
const lock = LockFile.tryAcquire(resourceFolder, resourceName); | ||
if (lock) { | ||
return Promise.resolve(lock); | ||
return lock; | ||
} | ||
if (maxWaitMs && Date.now() > startTime + maxWaitMs) { | ||
return Promise.reject(new Error(`Exceeded maximum wait time to acquire lock for resource "${resourceName}"`)); | ||
throw new Error(`Exceeded maximum wait time to acquire lock for resource "${resourceName}"`); | ||
} | ||
return LockFile._sleepForMs(interval).then(() => { | ||
return retryLoop(); | ||
}); | ||
await LockFile._sleepForMs(interval); | ||
return retryLoop(); | ||
}; | ||
@@ -232,0 +231,0 @@ return retryLoop(); |
{ | ||
"name": "@rushstack/node-core-library", | ||
"version": "3.35.2", | ||
"version": "3.36.0", | ||
"description": "Core libraries that every NodeJS toolchain project should use", | ||
@@ -27,4 +27,4 @@ "main": "lib/index.js", | ||
"@rushstack/eslint-config": "2.3.2", | ||
"@rushstack/heft": "0.22.3", | ||
"@rushstack/heft-node-rig": "0.1.28", | ||
"@rushstack/heft": "0.23.1", | ||
"@rushstack/heft-node-rig": "0.2.0", | ||
"@types/fs-extra": "7.0.0", | ||
@@ -31,0 +31,0 @@ "@types/heft-jest": "1.0.1", |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
947854
139
13234
5
0