This commit is contained in:
2024-02-07 01:33:07 -05:00
commit c1af19d441
4088 changed files with 1260170 additions and 0 deletions

View File

@ -0,0 +1,36 @@
/**
* This exception can be thrown to indicate that an operation failed and an error message has already
* been reported appropriately. Thus, the catch handler does not have responsibility for reporting
* the error.
*
* @remarks
* For example, suppose a tool writes interactive output to `console.log()`. When an exception is thrown,
* the `catch` handler will typically provide simplistic reporting such as this:
*
* ```ts
* catch (error) {
* console.log("ERROR: " + error.message);
* }
* ```
*
* Suppose that the code performing the operation normally prints rich output to the console. It may be able to
* present an error message more nicely (for example, as part of a table, or structured log format). Throwing
* `AlreadyReportedError` provides a way to use exception handling to abort the operation, but instruct the `catch`
* handler not to print an error a second time:
*
* ```ts
* catch (error) {
* if (error instanceof AlreadyReportedError) {
* return;
* }
* console.log("ERROR: " + error.message);
* }
* ```
*
* @public
*/
export declare class AlreadyReportedError extends Error {
constructor();
static [Symbol.hasInstance](instance: object): boolean;
}
//# sourceMappingURL=AlreadyReportedError.d.ts.map

View File

@ -0,0 +1 @@
{"version":3,"file":"AlreadyReportedError.d.ts","sourceRoot":"","sources":["../src/AlreadyReportedError.ts"],"names":[],"mappings":"AAOA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,qBAAa,oBAAqB,SAAQ,KAAK;;WAW/B,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO;CAG9D"}

View File

@ -0,0 +1,54 @@
"use strict";
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root for license information.
Object.defineProperty(exports, "__esModule", { value: true });
exports.AlreadyReportedError = void 0;
const TypeUuid_1 = require("./TypeUuid");
const uuidAlreadyReportedError = 'f26b0640-a49b-49d1-9ead-1a516d5920c7';
/**
* This exception can be thrown to indicate that an operation failed and an error message has already
* been reported appropriately. Thus, the catch handler does not have responsibility for reporting
* the error.
*
* @remarks
* For example, suppose a tool writes interactive output to `console.log()`. When an exception is thrown,
* the `catch` handler will typically provide simplistic reporting such as this:
*
* ```ts
* catch (error) {
* console.log("ERROR: " + error.message);
* }
* ```
*
* Suppose that the code performing the operation normally prints rich output to the console. It may be able to
* present an error message more nicely (for example, as part of a table, or structured log format). Throwing
* `AlreadyReportedError` provides a way to use exception handling to abort the operation, but instruct the `catch`
* handler not to print an error a second time:
*
* ```ts
* catch (error) {
* if (error instanceof AlreadyReportedError) {
* return;
* }
* console.log("ERROR: " + error.message);
* }
* ```
*
* @public
*/
class AlreadyReportedError extends Error {
constructor() {
super('An error occurred.');
// Manually set the prototype, as we can no longer extend built-in classes like Error, Array, Map, etc
// [https://github.com/microsoft/TypeScript-wiki/blob/main/Breaking-Changes.md#extending-built-ins-like-error-array-and-map-may-no-longer-work](https://github.com/microsoft/TypeScript-wiki/blob/main/Breaking-Changes.md#extending-built-ins-like-error-array-and-map-may-no-longer-work)
//
// Note: the prototype must also be set on any classes which extend this one
this.__proto__ = AlreadyReportedError.prototype; // eslint-disable-line @typescript-eslint/no-explicit-any
}
static [Symbol.hasInstance](instance) {
return TypeUuid_1.TypeUuid.isInstanceOf(instance, uuidAlreadyReportedError);
}
}
exports.AlreadyReportedError = AlreadyReportedError;
TypeUuid_1.TypeUuid.registerClass(AlreadyReportedError, uuidAlreadyReportedError);
//# sourceMappingURL=AlreadyReportedError.js.map

View File

@ -0,0 +1 @@
{"version":3,"file":"AlreadyReportedError.js","sourceRoot":"","sources":["../src/AlreadyReportedError.ts"],"names":[],"mappings":";AAAA,4FAA4F;AAC5F,2DAA2D;;;AAE3D,yCAAsC;AAEtC,MAAM,wBAAwB,GAAW,sCAAsC,CAAC;AAEhF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,MAAa,oBAAqB,SAAQ,KAAK;IAC7C;QACE,KAAK,CAAC,oBAAoB,CAAC,CAAC;QAE5B,sGAAsG;QACtG,2RAA2R;QAC3R,EAAE;QACF,4EAA4E;QAC3E,IAAY,CAAC,SAAS,GAAG,oBAAoB,CAAC,SAAS,CAAC,CAAC,yDAAyD;IACrH,CAAC;IAEM,MAAM,CAAC,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC,QAAgB;QACjD,OAAO,mBAAQ,CAAC,YAAY,CAAC,QAAQ,EAAE,wBAAwB,CAAC,CAAC;IACnE,CAAC;CACF;AAdD,oDAcC;AAED,mBAAQ,CAAC,aAAa,CAAC,oBAAoB,EAAE,wBAAwB,CAAC,CAAC","sourcesContent":["// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.\n// See LICENSE in the project root for license information.\n\nimport { TypeUuid } from './TypeUuid';\n\nconst uuidAlreadyReportedError: string = 'f26b0640-a49b-49d1-9ead-1a516d5920c7';\n\n/**\n * This exception can be thrown to indicate that an operation failed and an error message has already\n * been reported appropriately. Thus, the catch handler does not have responsibility for reporting\n * the error.\n *\n * @remarks\n * For example, suppose a tool writes interactive output to `console.log()`. When an exception is thrown,\n * the `catch` handler will typically provide simplistic reporting such as this:\n *\n * ```ts\n * catch (error) {\n * console.log(\"ERROR: \" + error.message);\n * }\n * ```\n *\n * Suppose that the code performing the operation normally prints rich output to the console. It may be able to\n * present an error message more nicely (for example, as part of a table, or structured log format). Throwing\n * `AlreadyReportedError` provides a way to use exception handling to abort the operation, but instruct the `catch`\n * handler not to print an error a second time:\n *\n * ```ts\n * catch (error) {\n * if (error instanceof AlreadyReportedError) {\n * return;\n * }\n * console.log(\"ERROR: \" + error.message);\n * }\n * ```\n *\n * @public\n */\nexport class AlreadyReportedError extends Error {\n public constructor() {\n super('An error occurred.');\n\n // Manually set the prototype, as we can no longer extend built-in classes like Error, Array, Map, etc\n // [https://github.com/microsoft/TypeScript-wiki/blob/main/Breaking-Changes.md#extending-built-ins-like-error-array-and-map-may-no-longer-work](https://github.com/microsoft/TypeScript-wiki/blob/main/Breaking-Changes.md#extending-built-ins-like-error-array-and-map-may-no-longer-work)\n //\n // Note: the prototype must also be set on any classes which extend this one\n (this as any).__proto__ = AlreadyReportedError.prototype; // eslint-disable-line @typescript-eslint/no-explicit-any\n }\n\n public static [Symbol.hasInstance](instance: object): boolean {\n return TypeUuid.isInstanceOf(instance, uuidAlreadyReportedError);\n }\n}\n\nTypeUuid.registerClass(AlreadyReportedError, uuidAlreadyReportedError);\n"]}

View File

@ -0,0 +1,106 @@
/**
* Options for controlling the parallelism of asynchronous operations.
*
* @remarks
* Used with {@link Async.mapAsync} and {@link Async.forEachAsync}.
*
* @beta
*/
export interface IAsyncParallelismOptions {
/**
* Optionally used with the {@link Async.mapAsync} and {@link Async.forEachAsync}
* to limit the maximum number of concurrent promises to the specified number.
*/
concurrency?: number;
}
/**
* @remarks
* Used with {@link Async.runWithRetriesAsync}.
*
* @beta
*/
export interface IRunWithRetriesOptions<TResult> {
action: () => Promise<TResult> | TResult;
maxRetries: number;
retryDelayMs?: number;
}
/**
* Utilities for parallel asynchronous operations, for use with the system `Promise` APIs.
*
* @beta
*/
export declare class Async {
/**
* Given an input array and a `callback` function, invoke the callback to start a
* promise for each element in the array. Returns an array containing the results.
*
* @remarks
* This API is similar to the system `Array#map`, except that the loop is asynchronous,
* and the maximum number of concurrent promises can be throttled
* using {@link IAsyncParallelismOptions.concurrency}.
*
* If `callback` throws a synchronous exception, or if it returns a promise that rejects,
* then the loop stops immediately. Any remaining array items will be skipped, and
* overall operation will reject with the first error that was encountered.
*
* @param iterable - the array of inputs for the callback function
* @param callback - a function that starts an asynchronous promise for an element
* from the array
* @param options - options for customizing the control flow
* @returns an array containing the result for each callback, in the same order
* as the original input `array`
*/
static mapAsync<TEntry, TRetVal>(iterable: Iterable<TEntry> | AsyncIterable<TEntry>, callback: (entry: TEntry, arrayIndex: number) => Promise<TRetVal>, options?: IAsyncParallelismOptions | undefined): Promise<TRetVal[]>;
/**
* Given an input array and a `callback` function, invoke the callback to start a
* promise for each element in the array.
*
* @remarks
* This API is similar to the system `Array#forEach`, except that the loop is asynchronous,
* and the maximum number of concurrent promises can be throttled
* using {@link IAsyncParallelismOptions.concurrency}.
*
* If `callback` throws a synchronous exception, or if it returns a promise that rejects,
* then the loop stops immediately. Any remaining array items will be skipped, and
* overall operation will reject with the first error that was encountered.
*
* @param iterable - the array of inputs for the callback function
* @param callback - a function that starts an asynchronous promise for an element
* from the array
* @param options - options for customizing the control flow
*/
static forEachAsync<TEntry>(iterable: Iterable<TEntry> | AsyncIterable<TEntry>, callback: (entry: TEntry, arrayIndex: number) => Promise<void>, options?: IAsyncParallelismOptions | undefined): Promise<void>;
/**
* Return a promise that resolves after the specified number of milliseconds.
*/
static sleep(ms: number): Promise<void>;
/**
* Executes an async function and optionally retries it if it fails.
*/
static runWithRetriesAsync<TResult>({ action, maxRetries, retryDelayMs }: IRunWithRetriesOptions<TResult>): Promise<TResult>;
/**
* Returns a Signal, a.k.a. a "deferred promise".
*/
static getSignal(): [Promise<void>, () => void, (err: Error) => void];
}
/**
* A queue that allows for asynchronous iteration. During iteration, the queue will wait until
* the next item is pushed into the queue before yielding. If instead all queue items are consumed
* and all callbacks have been called, the queue will return.
*
* @public
*/
export declare class AsyncQueue<T> implements AsyncIterable<[T, () => void]> {
private _queue;
private _onPushSignal;
private _onPushResolve;
constructor(iterable?: Iterable<T>);
[Symbol.asyncIterator](): AsyncIterableIterator<[T, () => void]>;
/**
* Adds an item to the queue.
*
* @param item - The item to push into the queue.
*/
push(item: T): void;
}
//# sourceMappingURL=Async.d.ts.map

View File

@ -0,0 +1 @@
{"version":3,"file":"Async.d.ts","sourceRoot":"","sources":["../src/Async.ts"],"names":[],"mappings":"AAGA;;;;;;;GAOG;AACH,MAAM,WAAW,wBAAwB;IACvC;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;;;;GAKG;AACH,MAAM,WAAW,sBAAsB,CAAC,OAAO;IAC7C,MAAM,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC;IACzC,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED;;;;GAIG;AACH,qBAAa,KAAK;IAChB;;;;;;;;;;;;;;;;;;;OAmBG;WACiB,QAAQ,CAAC,MAAM,EAAE,OAAO,EAC1C,QAAQ,EAAE,QAAQ,CAAC,MAAM,CAAC,GAAG,aAAa,CAAC,MAAM,CAAC,EAClD,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,EACjE,OAAO,CAAC,EAAE,wBAAwB,GAAG,SAAS,GAC7C,OAAO,CAAC,OAAO,EAAE,CAAC;IAcrB;;;;;;;;;;;;;;;;;OAiBG;WACiB,YAAY,CAAC,MAAM,EACrC,QAAQ,EAAE,QAAQ,CAAC,MAAM,CAAC,GAAG,aAAa,CAAC,MAAM,CAAC,EAClD,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,EAC9D,OAAO,CAAC,EAAE,wBAAwB,GAAG,SAAS,GAC7C,OAAO,CAAC,IAAI,CAAC;IA+DhB;;OAEG;WACiB,KAAK,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAMpD;;OAEG;WACiB,mBAAmB,CAAC,OAAO,EAAE,EAC/C,MAAM,EACN,UAAU,EACV,YAAgB,EACjB,EAAE,sBAAsB,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC;IAgBrD;;OAEG;WACW,SAAS,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,MAAM,IAAI,EAAE,CAAC,GAAG,EAAE,KAAK,KAAK,IAAI,CAAC;CAG7E;AAeD;;;;;;GAMG;AACH,qBAAa,UAAU,CAAC,CAAC,CAAE,YAAW,aAAa,CAAC,CAAC,CAAC,EAAE,MAAM,IAAI,CAAC,CAAC;IAClE,OAAO,CAAC,MAAM,CAAM;IACpB,OAAO,CAAC,aAAa,CAAgB;IACrC,OAAO,CAAC,cAAc,CAAa;gBAEhB,QAAQ,CAAC,EAAE,QAAQ,CAAC,CAAC,CAAC;IAO3B,CAAC,MAAM,CAAC,aAAa,CAAC,IAAI,qBAAqB,CAAC,CAAC,CAAC,EAAE,MAAM,IAAI,CAAC,CAAC;IA4B9E;;;;OAIG;IACI,IAAI,CAAC,IAAI,EAAE,CAAC,GAAG,IAAI;CAO3B"}

228
node_modules/@rushstack/node-core-library/lib/Async.js generated vendored Normal file
View File

@ -0,0 +1,228 @@
"use strict";
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root for license information.
var __await = (this && this.__await) || function (v) { return this instanceof __await ? (this.v = v, this) : new __await(v); }
var __asyncGenerator = (this && this.__asyncGenerator) || function (thisArg, _arguments, generator) {
if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
var g = generator.apply(thisArg, _arguments || []), i, q = [];
return i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i;
function verb(n) { if (g[n]) i[n] = function (v) { return new Promise(function (a, b) { q.push([n, v, a, b]) > 1 || resume(n, v); }); }; }
function resume(n, v) { try { step(g[n](v)); } catch (e) { settle(q[0][3], e); } }
function step(r) { r.value instanceof __await ? Promise.resolve(r.value.v).then(fulfill, reject) : settle(q[0][2], r); }
function fulfill(value) { resume("next", value); }
function reject(value) { resume("throw", value); }
function settle(f, v) { if (f(v), q.shift(), q.length) resume(q[0][0], q[0][1]); }
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.AsyncQueue = exports.Async = void 0;
/**
* Utilities for parallel asynchronous operations, for use with the system `Promise` APIs.
*
* @beta
*/
class Async {
/**
* Given an input array and a `callback` function, invoke the callback to start a
* promise for each element in the array. Returns an array containing the results.
*
* @remarks
* This API is similar to the system `Array#map`, except that the loop is asynchronous,
* and the maximum number of concurrent promises can be throttled
* using {@link IAsyncParallelismOptions.concurrency}.
*
* If `callback` throws a synchronous exception, or if it returns a promise that rejects,
* then the loop stops immediately. Any remaining array items will be skipped, and
* overall operation will reject with the first error that was encountered.
*
* @param iterable - the array of inputs for the callback function
* @param callback - a function that starts an asynchronous promise for an element
* from the array
* @param options - options for customizing the control flow
* @returns an array containing the result for each callback, in the same order
* as the original input `array`
*/
static async mapAsync(iterable, callback, options) {
const result = [];
await Async.forEachAsync(iterable, async (item, arrayIndex) => {
result[arrayIndex] = await callback(item, arrayIndex);
}, options);
return result;
}
/**
* Given an input array and a `callback` function, invoke the callback to start a
* promise for each element in the array.
*
* @remarks
* This API is similar to the system `Array#forEach`, except that the loop is asynchronous,
* and the maximum number of concurrent promises can be throttled
* using {@link IAsyncParallelismOptions.concurrency}.
*
* If `callback` throws a synchronous exception, or if it returns a promise that rejects,
* then the loop stops immediately. Any remaining array items will be skipped, and
* overall operation will reject with the first error that was encountered.
*
* @param iterable - the array of inputs for the callback function
* @param callback - a function that starts an asynchronous promise for an element
* from the array
* @param options - options for customizing the control flow
*/
static async forEachAsync(iterable, callback, options) {
await new Promise((resolve, reject) => {
const concurrency = (options === null || options === void 0 ? void 0 : options.concurrency) && options.concurrency > 0 ? options.concurrency : Infinity;
let operationsInProgress = 0;
const iterator = (iterable[Symbol.iterator] ||
iterable[Symbol.asyncIterator]).call(iterable);
let arrayIndex = 0;
let iteratorIsComplete = false;
let promiseHasResolvedOrRejected = false;
async function queueOperationsAsync() {
while (operationsInProgress < concurrency && !iteratorIsComplete && !promiseHasResolvedOrRejected) {
// Increment the concurrency while waiting for the iterator.
// This function is reentrant, so this ensures that at most `concurrency` executions are waiting
operationsInProgress++;
const currentIteratorResult = await iterator.next();
// eslint-disable-next-line require-atomic-updates
iteratorIsComplete = !!currentIteratorResult.done;
if (!iteratorIsComplete) {
Promise.resolve(callback(currentIteratorResult.value, arrayIndex++))
.then(async () => {
operationsInProgress--;
await onOperationCompletionAsync();
})
.catch((error) => {
promiseHasResolvedOrRejected = true;
reject(error);
});
}
else {
// The iterator is complete and there wasn't a value, so untrack the waiting state.
operationsInProgress--;
}
}
if (iteratorIsComplete) {
await onOperationCompletionAsync();
}
}
async function onOperationCompletionAsync() {
if (!promiseHasResolvedOrRejected) {
if (operationsInProgress === 0 && iteratorIsComplete) {
promiseHasResolvedOrRejected = true;
resolve();
}
else if (!iteratorIsComplete) {
await queueOperationsAsync();
}
}
}
queueOperationsAsync().catch((error) => {
promiseHasResolvedOrRejected = true;
reject(error);
});
});
}
/**
* Return a promise that resolves after the specified number of milliseconds.
*/
static async sleep(ms) {
await new Promise((resolve) => {
setTimeout(resolve, ms);
});
}
/**
* Executes an async function and optionally retries it if it fails.
*/
static async runWithRetriesAsync({ action, maxRetries, retryDelayMs = 0 }) {
let retryCounter = 0;
// eslint-disable-next-line no-constant-condition
while (true) {
try {
return await action();
}
catch (e) {
if (++retryCounter > maxRetries) {
throw e;
}
else if (retryDelayMs > 0) {
await Async.sleep(retryDelayMs);
}
}
}
}
/**
* Returns a Signal, a.k.a. a "deferred promise".
*/
static getSignal() {
return getSignal();
}
}
exports.Async = Async;
/**
* Returns an unwrapped promise.
*/
function getSignal() {
let resolver;
let rejecter;
const promise = new Promise((resolve, reject) => {
resolver = resolve;
rejecter = reject;
});
return [promise, resolver, rejecter];
}
/**
* A queue that allows for asynchronous iteration. During iteration, the queue will wait until
* the next item is pushed into the queue before yielding. If instead all queue items are consumed
* and all callbacks have been called, the queue will return.
*
* @public
*/
class AsyncQueue {
constructor(iterable) {
this._queue = iterable ? Array.from(iterable) : [];
const [promise, resolver] = getSignal();
this._onPushSignal = promise;
this._onPushResolve = resolver;
}
[Symbol.asyncIterator]() {
return __asyncGenerator(this, arguments, function* _a() {
let activeIterations = 0;
let [callbackSignal, callbackResolve] = getSignal();
const callback = () => {
if (--activeIterations === 0) {
// Resolve whatever the latest callback promise is and create a new one
callbackResolve();
const [newCallbackSignal, newCallbackResolve] = getSignal();
callbackSignal = newCallbackSignal;
callbackResolve = newCallbackResolve;
}
};
let position = 0;
while (this._queue.length > position || activeIterations > 0) {
if (this._queue.length > position) {
activeIterations++;
yield yield __await([this._queue[position++], callback]);
}
else {
// On push, the item will be added to the queue and the onPushSignal will be resolved.
// On calling the callback, active iterations will be decremented by the callback and the
// callbackSignal will be resolved. This means that the loop will continue if there are
// active iterations or if there are items in the queue that haven't been yielded yet.
yield __await(Promise.race([this._onPushSignal, callbackSignal]));
}
}
});
}
/**
* Adds an item to the queue.
*
* @param item - The item to push into the queue.
*/
push(item) {
this._queue.push(item);
this._onPushResolve();
const [onPushSignal, onPushResolve] = getSignal();
this._onPushSignal = onPushSignal;
this._onPushResolve = onPushResolve;
}
}
exports.AsyncQueue = AsyncQueue;
//# sourceMappingURL=Async.js.map

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,27 @@
/**
* String constants for common filenames and parts of filenames.
*
* @public
*/
export declare enum FileConstants {
/**
* "package.json" - the configuration file that defines an NPM package
*/
PackageJson = "package.json"
}
/**
* String constants for common folder names.
*
* @public
*/
export declare enum FolderConstants {
/**
* ".git" - the data storage for a Git working folder
*/
Git = ".git",
/**
* "node_modules" - the folder where package managers install their files
*/
NodeModules = "node_modules"
}
//# sourceMappingURL=Constants.d.ts.map

View File

@ -0,0 +1 @@
{"version":3,"file":"Constants.d.ts","sourceRoot":"","sources":["../src/Constants.ts"],"names":[],"mappings":"AAGA;;;;GAIG;AACH,oBAAY,aAAa;IACvB;;OAEG;IACH,WAAW,iBAAiB;CAC7B;AAED;;;;GAIG;AACH,oBAAY,eAAe;IACzB;;OAEG;IACH,GAAG,SAAS;IAEZ;;OAEG;IACH,WAAW,iBAAiB;CAC7B"}

View File

@ -0,0 +1,34 @@
"use strict";
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root for license information.
Object.defineProperty(exports, "__esModule", { value: true });
exports.FolderConstants = exports.FileConstants = void 0;
/**
* String constants for common filenames and parts of filenames.
*
* @public
*/
var FileConstants;
(function (FileConstants) {
/**
* "package.json" - the configuration file that defines an NPM package
*/
FileConstants["PackageJson"] = "package.json";
})(FileConstants = exports.FileConstants || (exports.FileConstants = {}));
/**
* String constants for common folder names.
*
* @public
*/
var FolderConstants;
(function (FolderConstants) {
/**
* ".git" - the data storage for a Git working folder
*/
FolderConstants["Git"] = ".git";
/**
* "node_modules" - the folder where package managers install their files
*/
FolderConstants["NodeModules"] = "node_modules";
})(FolderConstants = exports.FolderConstants || (exports.FolderConstants = {}));
//# sourceMappingURL=Constants.js.map

View File

@ -0,0 +1 @@
{"version":3,"file":"Constants.js","sourceRoot":"","sources":["../src/Constants.ts"],"names":[],"mappings":";AAAA,4FAA4F;AAC5F,2DAA2D;;;AAE3D;;;;GAIG;AACH,IAAY,aAKX;AALD,WAAY,aAAa;IACvB;;OAEG;IACH,6CAA4B,CAAA;AAC9B,CAAC,EALW,aAAa,GAAb,qBAAa,KAAb,qBAAa,QAKxB;AAED;;;;GAIG;AACH,IAAY,eAUX;AAVD,WAAY,eAAe;IACzB;;OAEG;IACH,+BAAY,CAAA;IAEZ;;OAEG;IACH,+CAA4B,CAAA;AAC9B,CAAC,EAVW,eAAe,GAAf,uBAAe,KAAf,uBAAe,QAU1B","sourcesContent":["// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.\n// See LICENSE in the project root for license information.\n\n/**\n * String constants for common filenames and parts of filenames.\n *\n * @public\n */\nexport enum FileConstants {\n /**\n * \"package.json\" - the configuration file that defines an NPM package\n */\n PackageJson = 'package.json'\n}\n\n/**\n * String constants for common folder names.\n *\n * @public\n */\nexport enum FolderConstants {\n /**\n * \".git\" - the data storage for a Git working folder\n */\n Git = '.git',\n\n /**\n * \"node_modules\" - the folder where package managers install their files\n */\n NodeModules = 'node_modules'\n}\n"]}

120
node_modules/@rushstack/node-core-library/lib/Enum.d.ts generated vendored Normal file
View File

@ -0,0 +1,120 @@
/**
* A helper for looking up TypeScript `enum` keys/values.
*
* @remarks
* TypeScript enums implement a lookup table for mapping between their keys and values:
*
* ```ts
* enum Colors {
* Red = 1
* }
*
* // Prints "Red"
* console.log(Colors[1]);
*
* // Prints "1"
* console.log(Colors["Red]);
* ```
*
* However the compiler's "noImplicitAny" validation has trouble with these mappings, because
* there are so many possible types for the map elements:
*
* ```ts
* function f(s: string): Colors | undefined {
* // (TS 7015) Element implicitly has an 'any' type because
* // index expression is not of type 'number'.
* return Colors[s];
* }
* ```
*
* The `Enum` helper provides a more specific, strongly typed way to access members:
*
* ```ts
* function f(s: string): Colors | undefined {
* return Enum.tryGetValueByKey(Colors, s);
* }
* ```
*
* @public
*/
export declare class Enum {
private constructor();
/**
* Returns an enum value, given its key. Returns `undefined` if no matching key is found.
*
* @example
*
* Example usage:
* ```ts
* enum Colors {
* Red = 1
* }
*
* // Prints "1"
* console.log(Enum.tryGetValueByKey(Colors, "Red"));
*
* // Prints "undefined"
* console.log(Enum.tryGetValueByKey(Colors, "Black"));
* ```
*/
static tryGetValueByKey<TEnumValue>(enumObject: {
[key: string]: TEnumValue | string;
[key: number]: TEnumValue | string;
}, key: string): TEnumValue | undefined;
/**
* This API is similar to {@link Enum.tryGetValueByKey}, except that it throws an exception
* if the key is undefined.
*/
static getValueByKey<TEnumValue>(enumObject: {
[key: string]: TEnumValue | string;
[key: number]: TEnumValue | string;
}, key: string): TEnumValue;
/**
* Returns an enum string key, given its numeric value. Returns `undefined` if no matching value
* is found.
*
* @remarks
* The TypeScript compiler only creates a reverse mapping for enum members whose value is numeric.
* For example:
*
* ```ts
* enum E {
* A = 1,
* B = 'c'
* }
*
* // Prints "A"
* console.log(E[1]);
*
* // Prints "undefined"
* console.log(E["c"]);
* ```
*
* @example
*
* Example usage:
* ```ts
* enum Colors {
* Red = 1,
* Blue = 'blue'
* }
*
* // Prints "Red"
* console.log(Enum.tryGetKeyByNumber(Colors, 1));
*
* // Prints "undefined"
* console.log(Enum.tryGetKeyByNumber(Colors, -1));
* ```
*/
static tryGetKeyByNumber<TEnumValue, TEnumObject extends {
[key: string]: TEnumValue;
}>(enumObject: TEnumObject, value: number): keyof typeof enumObject | undefined;
/**
* This API is similar to {@link Enum.tryGetKeyByNumber}, except that it throws an exception
* if the key is undefined.
*/
static getKeyByNumber<TEnumValue, TEnumObject extends {
[key: string]: TEnumValue;
}>(enumObject: TEnumObject, value: number): keyof typeof enumObject;
}
//# sourceMappingURL=Enum.d.ts.map

View File

@ -0,0 +1 @@
{"version":3,"file":"Enum.d.ts","sourceRoot":"","sources":["../src/Enum.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AACH,qBAAa,IAAI;IACf,OAAO;IAEP;;;;;;;;;;;;;;;;;OAiBG;WACW,gBAAgB,CAAC,UAAU,EACvC,UAAU,EAAE;QACV,CAAC,GAAG,EAAE,MAAM,GAAG,UAAU,GAAG,MAAM,CAAC;QACnC,CAAC,GAAG,EAAE,MAAM,GAAG,UAAU,GAAG,MAAM,CAAC;KACpC,EACD,GAAG,EAAE,MAAM,GACV,UAAU,GAAG,SAAS;IAKzB;;;OAGG;WACW,aAAa,CAAC,UAAU,EACpC,UAAU,EAAE;QACV,CAAC,GAAG,EAAE,MAAM,GAAG,UAAU,GAAG,MAAM,CAAC;QACnC,CAAC,GAAG,EAAE,MAAM,GAAG,UAAU,GAAG,MAAM,CAAC;KACpC,EACD,GAAG,EAAE,MAAM,GACV,UAAU;IASb;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAoCG;WACW,iBAAiB,CAAC,UAAU,EAAE,WAAW,SAAS;QAAE,CAAC,GAAG,EAAE,MAAM,GAAG,UAAU,CAAA;KAAE,EAC3F,UAAU,EAAE,WAAW,EACvB,KAAK,EAAE,MAAM,GACZ,MAAM,OAAO,UAAU,GAAG,SAAS;IAKtC;;;OAGG;WACW,cAAc,CAAC,UAAU,EAAE,WAAW,SAAS;QAAE,CAAC,GAAG,EAAE,MAAM,GAAG,UAAU,CAAA;KAAE,EACxF,UAAU,EAAE,WAAW,EACvB,KAAK,EAAE,MAAM,GACZ,MAAM,OAAO,UAAU;CAQ3B"}

136
node_modules/@rushstack/node-core-library/lib/Enum.js generated vendored Normal file
View File

@ -0,0 +1,136 @@
"use strict";
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root for license information.
Object.defineProperty(exports, "__esModule", { value: true });
exports.Enum = void 0;
/**
* A helper for looking up TypeScript `enum` keys/values.
*
* @remarks
* TypeScript enums implement a lookup table for mapping between their keys and values:
*
* ```ts
* enum Colors {
* Red = 1
* }
*
* // Prints "Red"
* console.log(Colors[1]);
*
* // Prints "1"
* console.log(Colors["Red]);
* ```
*
* However the compiler's "noImplicitAny" validation has trouble with these mappings, because
* there are so many possible types for the map elements:
*
* ```ts
* function f(s: string): Colors | undefined {
* // (TS 7015) Element implicitly has an 'any' type because
* // index expression is not of type 'number'.
* return Colors[s];
* }
* ```
*
* The `Enum` helper provides a more specific, strongly typed way to access members:
*
* ```ts
* function f(s: string): Colors | undefined {
* return Enum.tryGetValueByKey(Colors, s);
* }
* ```
*
* @public
*/
class Enum {
constructor() { }
/**
* Returns an enum value, given its key. Returns `undefined` if no matching key is found.
*
* @example
*
* Example usage:
* ```ts
* enum Colors {
* Red = 1
* }
*
* // Prints "1"
* console.log(Enum.tryGetValueByKey(Colors, "Red"));
*
* // Prints "undefined"
* console.log(Enum.tryGetValueByKey(Colors, "Black"));
* ```
*/
static tryGetValueByKey(enumObject, key) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return enumObject[key];
}
/**
* This API is similar to {@link Enum.tryGetValueByKey}, except that it throws an exception
* if the key is undefined.
*/
static getValueByKey(enumObject, key) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const result = enumObject[key];
if (result === undefined) {
throw new Error(`The lookup key ${JSON.stringify(key)} is not defined`);
}
return result;
}
/**
* Returns an enum string key, given its numeric value. Returns `undefined` if no matching value
* is found.
*
* @remarks
* The TypeScript compiler only creates a reverse mapping for enum members whose value is numeric.
* For example:
*
* ```ts
* enum E {
* A = 1,
* B = 'c'
* }
*
* // Prints "A"
* console.log(E[1]);
*
* // Prints "undefined"
* console.log(E["c"]);
* ```
*
* @example
*
* Example usage:
* ```ts
* enum Colors {
* Red = 1,
* Blue = 'blue'
* }
*
* // Prints "Red"
* console.log(Enum.tryGetKeyByNumber(Colors, 1));
*
* // Prints "undefined"
* console.log(Enum.tryGetKeyByNumber(Colors, -1));
* ```
*/
static tryGetKeyByNumber(enumObject, value) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return enumObject[value];
}
/**
* This API is similar to {@link Enum.tryGetKeyByNumber}, except that it throws an exception
* if the key is undefined.
*/
static getKeyByNumber(enumObject, value) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const result = enumObject[value];
if (result === undefined) {
throw new Error(`The value ${value} does not exist in the mapping`);
}
return result;
}
}
exports.Enum = Enum;
//# sourceMappingURL=Enum.js.map

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,72 @@
/**
* A process environment variable name and its value. Used by {@link EnvironmentMap}.
* @public
*/
export interface IEnvironmentEntry {
/**
* The name of the environment variable.
*/
name: string;
/**
* The value of the environment variable.
*/
value: string;
}
/**
* A map data structure that stores process environment variables. On Windows
* operating system, the variable names are case-insensitive.
* @public
*/
export declare class EnvironmentMap {
private readonly _map;
/**
* Whether the environment variable names are case-sensitive.
*
* @remarks
* On Windows operating system, environment variables are case-insensitive.
* The map will preserve the variable name casing from the most recent assignment operation.
*/
readonly caseSensitive: boolean;
constructor(environmentObject?: Record<string, string | undefined>);
/**
* Clears all entries, resulting in an empty map.
*/
clear(): void;
/**
* Assigns the variable to the specified value. A previous value will be overwritten.
*
* @remarks
* The value can be an empty string. To completely remove the entry, use
* {@link EnvironmentMap.unset} instead.
*/
set(name: string, value: string): void;
/**
* Removes the key from the map, if present.
*/
unset(name: string): void;
/**
* Returns the value of the specified variable, or `undefined` if the map does not contain that name.
*/
get(name: string): string | undefined;
/**
* Returns the map keys, which are environment variable names.
*/
names(): IterableIterator<string>;
/**
* Returns the map entries.
*/
entries(): IterableIterator<IEnvironmentEntry>;
/**
* Adds each entry from `environmentMap` to this map.
*/
mergeFrom(environmentMap: EnvironmentMap): void;
/**
* Merges entries from a plain JavaScript object, such as would be used with the `process.env` API.
*/
mergeFromObject(environmentObject?: Record<string, string | undefined>): void;
/**
* Returns the keys as a plain JavaScript object similar to the object returned by the `process.env` API.
*/
toObject(): Record<string, string>;
}
//# sourceMappingURL=EnvironmentMap.d.ts.map

View File

@ -0,0 +1 @@
{"version":3,"file":"EnvironmentMap.d.ts","sourceRoot":"","sources":["../src/EnvironmentMap.ts"],"names":[],"mappings":"AAMA;;;GAGG;AACH,MAAM,WAAW,iBAAiB;IAChC;;OAEG;IACH,IAAI,EAAE,MAAM,CAAC;IAEb;;OAEG;IACH,KAAK,EAAE,MAAM,CAAC;CACf;AAED;;;;GAIG;AACH,qBAAa,cAAc;IACzB,OAAO,CAAC,QAAQ,CAAC,IAAI,CAA6C;IAElE;;;;;;OAMG;IACH,SAAgB,aAAa,EAAE,OAAO,CAAC;gBAEpB,iBAAiB,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAM;IAe7E;;OAEG;IACI,KAAK,IAAI,IAAI;IAIpB;;;;;;OAMG;IACI,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI;IAK7C;;OAEG;IACI,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAKhC;;OAEG;IACI,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAS5C;;OAEG;IACI,KAAK,IAAI,gBAAgB,CAAC,MAAM,CAAC;IAIxC;;OAEG;IACI,OAAO,IAAI,gBAAgB,CAAC,iBAAiB,CAAC;IAIrD;;OAEG;IACI,SAAS,CAAC,cAAc,EAAE,cAAc,GAAG,IAAI;IAMtD;;OAEG;IACI,eAAe,CAAC,iBAAiB,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAM,GAAG,IAAI;IAQxF;;OAEG;IACI,QAAQ,IAAI,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC;CAO1C"}

View File

@ -0,0 +1,108 @@
"use strict";
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root for license information.
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.EnvironmentMap = void 0;
const process_1 = __importDefault(require("process"));
const InternalError_1 = require("./InternalError");
/**
* A map data structure that stores process environment variables. On Windows
* operating system, the variable names are case-insensitive.
* @public
*/
class EnvironmentMap {
constructor(environmentObject = {}) {
this._map = new Map();
// This property helps catch a mistake where an instance of `EnvironmentMap` is accidentally passed to
// a function that expects a `Record<string, string>` (as would be used with the `process.env` API).
// The property getter will throw an exception if that function tries to enumerate the object values.
Object.defineProperty(this, '_sanityCheck', {
enumerable: true,
get: function () {
throw new InternalError_1.InternalError('Attempt to read EnvironmentMap class as an object');
}
});
this.caseSensitive = process_1.default.platform !== 'win32';
this.mergeFromObject(environmentObject);
}
/**
* Clears all entries, resulting in an empty map.
*/
clear() {
this._map.clear();
}
/**
* Assigns the variable to the specified value. A previous value will be overwritten.
*
* @remarks
* The value can be an empty string. To completely remove the entry, use
* {@link EnvironmentMap.unset} instead.
*/
set(name, value) {
const key = this.caseSensitive ? name : name.toUpperCase();
this._map.set(key, { name: name, value });
}
/**
* Removes the key from the map, if present.
*/
unset(name) {
const key = this.caseSensitive ? name : name.toUpperCase();
this._map.delete(key);
}
/**
* Returns the value of the specified variable, or `undefined` if the map does not contain that name.
*/
get(name) {
const key = this.caseSensitive ? name : name.toUpperCase();
const entry = this._map.get(key);
if (entry === undefined) {
return undefined;
}
return entry.value;
}
/**
* Returns the map keys, which are environment variable names.
*/
names() {
return this._map.keys();
}
/**
* Returns the map entries.
*/
entries() {
return this._map.values();
}
/**
* Adds each entry from `environmentMap` to this map.
*/
mergeFrom(environmentMap) {
for (const entry of environmentMap.entries()) {
this.set(entry.name, entry.value);
}
}
/**
* Merges entries from a plain JavaScript object, such as would be used with the `process.env` API.
*/
mergeFromObject(environmentObject = {}) {
for (const [name, value] of Object.entries(environmentObject)) {
if (value !== undefined) {
this.set(name, value);
}
}
}
/**
* Returns the keys as a plain JavaScript object similar to the object returned by the `process.env` API.
*/
toObject() {
const result = {};
for (const entry of this.entries()) {
result[entry.name] = entry.value;
}
return result;
}
}
exports.EnvironmentMap = EnvironmentMap;
//# sourceMappingURL=EnvironmentMap.js.map

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,341 @@
/// <reference types="node" />
/// <reference types="node" />
/// <reference types="node" />
/// <reference types="node" />
import * as child_process from 'child_process';
import { EnvironmentMap } from './EnvironmentMap';
/**
* Typings for one of the streams inside IExecutableSpawnSyncOptions.stdio.
* @public
*/
export type ExecutableStdioStreamMapping = 'pipe' | 'ignore' | 'inherit' | NodeJS.WritableStream | NodeJS.ReadableStream | number | undefined;
/**
* Types for {@link IExecutableSpawnSyncOptions.stdio}
* and {@link IExecutableSpawnOptions.stdio}
* @public
*/
export type ExecutableStdioMapping = 'pipe' | 'ignore' | 'inherit' | ExecutableStdioStreamMapping[];
/**
* Options for Executable.tryResolve().
* @public
*/
export interface IExecutableResolveOptions {
/**
* The current working directory. If omitted, process.cwd() will be used.
*/
currentWorkingDirectory?: string;
/**
* 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 {@link Executable.spawnSync}
* @public
*/
export interface IExecutableSpawnSyncOptions extends IExecutableResolveOptions {
/**
* The content to be passed to the child process's stdin.
*
* NOTE: If specified, this content replaces any IExecutableSpawnSyncOptions.stdio[0]
* mapping for stdin.
*/
input?: string;
/**
* 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 maximum time the process is allowed to run before it will be terminated.
*/
timeoutMs?: number;
/**
* 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.
*/
maxBuffer?: number;
}
/**
* 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 options for running a process to completion using {@link Executable.(waitForExitAsync:3)}.
*
* @public
*/
export interface IWaitForExitOptions {
/**
* Whether or not to throw when the process completes with a non-zero exit code. Defaults to false.
*/
throwOnNonZeroExitCode?: boolean;
/**
* The encoding of the output. If not provided, the output will not be collected.
*/
encoding?: BufferEncoding | 'buffer';
}
/**
* {@inheritDoc IWaitForExitOptions}
*
* @public
*/
export interface IWaitForExitWithStringOptions extends IWaitForExitOptions {
/**
* {@inheritDoc IWaitForExitOptions.encoding}
*/
encoding: BufferEncoding;
}
/**
* {@inheritDoc IWaitForExitOptions}
*
* @public
*/
export interface IWaitForExitWithBufferOptions extends IWaitForExitOptions {
/**
* {@inheritDoc IWaitForExitOptions.encoding}
*/
encoding: 'buffer';
}
/**
* The result of running a process to completion using {@link Executable.(waitForExitAsync:3)}.
*
* @public
*/
export interface IWaitForExitResult<T extends Buffer | string | never = never> {
/**
* The process stdout output, if encoding was specified.
*/
stdout: T;
/**
* The process stderr output, if encoding was specified.
*/
stderr: T;
/**
* The process exit code. If the process was terminated, this will be null.
*/
exitCode: number | null;
}
/**
* Process information sourced from the system. This process info is sourced differently depending
* on the operating system:
* - On Windows, this uses the `wmic.exe` utility.
* - On Unix, this uses the `ps` utility.
*
* @public
*/
export interface IProcessInfo {
/**
* The name of the process.
*
* @remarks On Windows, the process name will be empty if the process is a kernel process.
* On Unix, the process name will be empty if the process is the root process.
*/
processName: string;
/**
* The process ID.
*/
processId: number;
/**
* The parent process info.
*
* @remarks On Windows, the parent process info will be undefined if the process is a kernel process.
* On Unix, the parent process info will be undefined if the process is the root process.
*/
parentProcessInfo: IProcessInfo | undefined;
/**
* The child process infos.
*/
childProcessInfos: IProcessInfo[];
}
export declare function parseProcessListOutputAsync(stream: NodeJS.ReadableStream): Promise<Map<number, IProcessInfo>>;
export declare function parseProcessListOutput(output: Iterable<string | null>): Map<number, IProcessInfo>;
/**
* The Executable class provides a safe, portable, recommended solution for tools that need
* to launch child processes.
*
* @remarks
* The NodeJS child_process API provides a solution for launching child processes, however
* its design encourages reliance on the operating system shell for certain features.
* Invoking the OS shell is not safe, not portable, and generally not recommended:
*
* - Different shells have different behavior and command-line syntax, and which shell you
* will get with NodeJS is unpredictable. There is no universal shell guaranteed to be
* available on all platforms.
*
* - If a command parameter contains symbol characters, a shell may interpret them, which
* can introduce a security vulnerability
*
* - Each shell has different rules for escaping these symbols. On Windows, the default
* shell is incapable of escaping certain character sequences.
*
* The Executable API provides a pure JavaScript implementation of primitive shell-like
* functionality for searching the default PATH, appending default file extensions on Windows,
* and executing a file that may contain a POSIX shebang. This primitive functionality
* is sufficient (and recommended) for most tooling scenarios.
*
* If you need additional shell features such as wildcard globbing, environment variable
* expansion, piping, or built-in commands, then we recommend to use the `@microsoft/rushell`
* library instead. Rushell is a pure JavaScript shell with a standard syntax that is
* guaranteed to work consistently across all platforms.
*
* @public
*/
export declare class Executable {
/**
* Synchronously create a child process and optionally capture its output.
*
* @remarks
* This function is similar to child_process.spawnSync(). 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.
*
* @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
*
* @privateRemarks
*
* NOTE: The NodeJS spawnSync() returns SpawnSyncReturns<string> or SpawnSyncReturns<Buffer>
* polymorphically based on the options.encoding parameter value. This is a fairly confusing
* design. In most cases, developers want string with the default encoding. If/when someone
* wants binary output or a non-default text encoding, we will introduce a separate API function
* with a name like "spawnWithBufferSync".
*/
static spawnSync(filename: string, args: string[], options?: IExecutableSpawnSyncOptions): child_process.SpawnSyncReturns<string>;
/**
* 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;
/** {@inheritDoc Executable.(waitForExitAsync:3)} */
static waitForExitAsync(childProcess: child_process.ChildProcess, options: IWaitForExitWithStringOptions): Promise<IWaitForExitResult<string>>;
/** {@inheritDoc Executable.(waitForExitAsync:3)} */
static waitForExitAsync(childProcess: child_process.ChildProcess, options: IWaitForExitWithBufferOptions): Promise<IWaitForExitResult<Buffer>>;
/**
* Wait for a child process to exit and return the result.
*
* @param childProcess - The child process to wait for.
* @param options - Options for waiting for the process to exit.
*/
static waitForExitAsync(childProcess: child_process.ChildProcess, options?: IWaitForExitOptions): Promise<IWaitForExitResult<never>>;
/**
* Get the list of processes currently running on the system, keyed by the process ID.
*
* @remarks The underlying implementation depends on the operating system:
* - On Windows, this uses the `wmic.exe` utility.
* - On Unix, this uses the `ps` utility.
*/
static getProcessInfoByIdAsync(): Promise<Map<number, IProcessInfo>>;
/**
* {@inheritDoc Executable.getProcessInfoByIdAsync}
*/
static getProcessInfoById(): Map<number, IProcessInfo>;
/**
* Get the list of processes currently running on the system, keyed by the process name. All processes
* with the same name will be grouped.
*
* @remarks The underlying implementation depends on the operating system:
* - On Windows, this uses the `wmic.exe` utility.
* - On Unix, this uses the `ps` utility.
*/
static getProcessInfoByNameAsync(): Promise<Map<string, IProcessInfo[]>>;
/**
* {@inheritDoc Executable.getProcessInfoByNameAsync}
*/
static getProcessInfoByName(): Map<string, IProcessInfo[]>;
private static _buildCommandLineFixup;
/**
* Given a filename, this determines the absolute path of the executable file that would
* be executed by a shell:
*
* - 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.
*
* @remarks
*
* @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 options - optional other parameters
* @returns the absolute path of the executable, or undefined if it was not found
*/
static tryResolve(filename: string, options?: IExecutableResolveOptions): string | undefined;
private static _tryResolve;
private static _tryResolveFileExtension;
private static _buildEnvironmentMap;
/**
* This is used when searching the shell PATH for an executable, to determine
* whether a match should be skipped or not. If it returns true, this does not
* guarantee that the file can be successfully executed.
*/
private static _canExecute;
/**
* Returns the list of folders where we will search for an executable,
* based on the PATH environment variable.
*/
private static _getSearchFolders;
private static _getExecutableContext;
/**
* Given an input string containing special symbol characters, this inserts the "^" escape
* character to ensure the symbols are interpreted literally by the Windows shell.
*/
private static _getEscapedForWindowsShell;
/**
* Checks for characters that are unsafe to pass to a Windows batch file
* due to the way that cmd.exe implements escaping.
*/
private static _validateArgsForWindowsShell;
}
//# sourceMappingURL=Executable.d.ts.map

View File

@ -0,0 +1 @@
{"version":3,"file":"Executable.d.ts","sourceRoot":"","sources":["../src/Executable.ts"],"names":[],"mappings":";;;;AAGA,OAAO,KAAK,aAAa,MAAM,eAAe,CAAC;AAG/C,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAOlD;;;GAGG;AACH,MAAM,MAAM,4BAA4B,GACpC,MAAM,GACN,QAAQ,GACR,SAAS,GACT,MAAM,CAAC,cAAc,GACrB,MAAM,CAAC,cAAc,GACrB,MAAM,GACN,SAAS,CAAC;AAEd;;;;GAIG;AACH,MAAM,MAAM,sBAAsB,GAAG,MAAM,GAAG,QAAQ,GAAG,SAAS,GAAG,4BAA4B,EAAE,CAAC;AAEpG;;;GAGG;AACH,MAAM,WAAW,yBAAyB;IACxC;;OAEG;IACH,uBAAuB,CAAC,EAAE,MAAM,CAAC;IAEjC;;;;;;OAMG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IAEhC;;;;;;OAMG;IACH,cAAc,CAAC,EAAE,cAAc,CAAC;CACjC;AAED;;;GAGG;AACH,MAAM,WAAW,2BAA4B,SAAQ,yBAAyB;IAC5E;;;;;OAKG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IAEf;;;;;OAKG;IACH,KAAK,CAAC,EAAE,sBAAsB,CAAC;IAE/B;;OAEG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;GAGG;AACH,MAAM,WAAW,uBAAwB,SAAQ,yBAAyB;IACxE;;;;;OAKG;IACH,KAAK,CAAC,EAAE,sBAAsB,CAAC;CAChC;AAED;;;;GAIG;AACH,MAAM,WAAW,mBAAmB;IAClC;;OAEG;IACH,sBAAsB,CAAC,EAAE,OAAO,CAAC;IAEjC;;OAEG;IACH,QAAQ,CAAC,EAAE,cAAc,GAAG,QAAQ,CAAC;CACtC;AAED;;;;GAIG;AACH,MAAM,WAAW,6BAA8B,SAAQ,mBAAmB;IACxE;;OAEG;IACH,QAAQ,EAAE,cAAc,CAAC;CAC1B;AAED;;;;GAIG;AACH,MAAM,WAAW,6BAA8B,SAAQ,mBAAmB;IACxE;;OAEG;IACH,QAAQ,EAAE,QAAQ,CAAC;CACpB;AAED;;;;GAIG;AACH,MAAM,WAAW,kBAAkB,CAAC,CAAC,SAAS,MAAM,GAAG,MAAM,GAAG,KAAK,GAAG,KAAK;IAC3E;;OAEG;IACH,MAAM,EAAE,CAAC,CAAC;IAEV;;OAEG;IACH,MAAM,EAAE,CAAC,CAAC;IAEV;;OAEG;IAEH,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;CACzB;AAeD;;;;;;;GAOG;AACH,MAAM,WAAW,YAAY;IAC3B;;;;;OAKG;IACH,WAAW,EAAE,MAAM,CAAC;IAEpB;;OAEG;IACH,SAAS,EAAE,MAAM,CAAC;IAElB;;;;;OAKG;IACH,iBAAiB,EAAE,YAAY,GAAG,SAAS,CAAC;IAE5C;;OAEG;IACH,iBAAiB,EAAE,YAAY,EAAE,CAAC;CACnC;AAED,wBAAsB,2BAA2B,CAC/C,MAAM,EAAE,MAAM,CAAC,cAAc,GAC5B,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC,CAWpC;AAGD,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,QAAQ,CAAC,MAAM,GAAG,IAAI,CAAC,GAAG,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC,CAWjG;AAmGD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,qBAAa,UAAU;IACrB;;;;;;;;;;;;;;;;;;;;;;;;;;;OA2BG;WACW,SAAS,CACrB,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,EAAE,EACd,OAAO,CAAC,EAAE,2BAA2B,GACpC,aAAa,CAAC,gBAAgB,CAAC,MAAM,CAAC;IAqCzC;;;;;;;;;;;;;;;;;;;;;;OAsBG;WACW,KAAK,CACjB,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,EAAE,EACd,OAAO,CAAC,EAAE,uBAAuB,GAChC,aAAa,CAAC,YAAY;IA+B7B,oDAAoD;WAChC,gBAAgB,CAClC,YAAY,EAAE,aAAa,CAAC,YAAY,EACxC,OAAO,EAAE,6BAA6B,GACrC,OAAO,CAAC,kBAAkB,CAAC,MAAM,CAAC,CAAC;IAEtC,oDAAoD;WAChC,gBAAgB,CAClC,YAAY,EAAE,aAAa,CAAC,YAAY,EACxC,OAAO,EAAE,6BAA6B,GACrC,OAAO,CAAC,kBAAkB,CAAC,MAAM,CAAC,CAAC;IAEtC;;;;;OAKG;WACiB,gBAAgB,CAClC,YAAY,EAAE,aAAa,CAAC,YAAY,EACxC,OAAO,CAAC,EAAE,mBAAmB,GAC5B,OAAO,CAAC,kBAAkB,CAAC,KAAK,CAAC,CAAC;IAsErC;;;;;;OAMG;WACiB,uBAAuB,IAAI,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;IAgBjF;;OAEG;WACW,kBAAkB,IAAI,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC;IAY7D;;;;;;;OAOG;WACiB,yBAAyB,IAAI,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,YAAY,EAAE,CAAC,CAAC;IAKrF;;OAEG;WACW,oBAAoB,IAAI,GAAG,CAAC,MAAM,EAAE,YAAY,EAAE,CAAC;IAoBjE,OAAO,CAAC,MAAM,CAAC,sBAAsB;IA0DrC;;;;;;;;;;;;;;;OAeG;WACW,UAAU,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,yBAAyB,GAAG,MAAM,GAAG,SAAS;IAInG,OAAO,CAAC,MAAM,CAAC,WAAW;IAgC1B,OAAO,CAAC,MAAM,CAAC,wBAAwB;IAoBvC,OAAO,CAAC,MAAM,CAAC,oBAAoB;IAkBnC;;;;OAIG;IACH,OAAO,CAAC,MAAM,CAAC,WAAW;IAgC1B;;;OAGG;IACH,OAAO,CAAC,MAAM,CAAC,iBAAiB;IAsChC,OAAO,CAAC,MAAM,CAAC,qBAAqB;IAqCpC;;;OAGG;IACH,OAAO,CAAC,MAAM,CAAC,0BAA0B;IAKzC;;;OAGG;IACH,OAAO,CAAC,MAAM,CAAC,4BAA4B;CAwB5C"}

View File

@ -0,0 +1,680 @@
"use strict";
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root 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;
};
var __asyncValues = (this && this.__asyncValues) || function (o) {
if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
var m = o[Symbol.asyncIterator], i;
return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i);
function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }
function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Executable = exports.parseProcessListOutput = exports.parseProcessListOutputAsync = void 0;
const child_process = __importStar(require("child_process"));
const os = __importStar(require("os"));
const path = __importStar(require("path"));
const EnvironmentMap_1 = require("./EnvironmentMap");
const FileSystem_1 = require("./FileSystem");
const PosixModeBits_1 = require("./PosixModeBits");
const Text_1 = require("./Text");
const InternalError_1 = require("./InternalError");
async function parseProcessListOutputAsync(stream) {
var _a, e_1, _b, _c;
const processInfoById = new Map();
let seenHeaders = false;
try {
for (var _d = true, _e = __asyncValues(Text_1.Text.readLinesFromIterableAsync(stream, { ignoreEmptyLines: true })), _f; _f = await _e.next(), _a = _f.done, !_a;) {
_c = _f.value;
_d = false;
try {
const line = _c;
if (!seenHeaders) {
seenHeaders = true;
}
else {
parseProcessInfoEntry(line, processInfoById);
}
}
finally {
_d = true;
}
}
}
catch (e_1_1) { e_1 = { error: e_1_1 }; }
finally {
try {
if (!_d && !_a && (_b = _e.return)) await _b.call(_e);
}
finally { if (e_1) throw e_1.error; }
}
return processInfoById;
}
exports.parseProcessListOutputAsync = parseProcessListOutputAsync;
// eslint-disable-next-line @rushstack/no-new-null
function parseProcessListOutput(output) {
const processInfoById = new Map();
let seenHeaders = false;
for (const line of Text_1.Text.readLinesFromIterable(output, { ignoreEmptyLines: true })) {
if (!seenHeaders) {
seenHeaders = true;
}
else {
parseProcessInfoEntry(line, processInfoById);
}
}
return processInfoById;
}
exports.parseProcessListOutput = parseProcessListOutput;
// win32 format:
// Name ParentProcessId ProcessId
// process name 1234 5678
// unix format:
// COMMAND PPID PID
// process name 51234 56784
const NAME_GROUP = 'name';
const PROCESS_ID_GROUP = 'pid';
const PARENT_PROCESS_ID_GROUP = 'ppid';
// eslint-disable-next-line @rushstack/security/no-unsafe-regexp
const PROCESS_LIST_ENTRY_REGEX = new RegExp(`^(?<${NAME_GROUP}>.+?)\\s+(?<${PARENT_PROCESS_ID_GROUP}>\\d+)\\s+(?<${PROCESS_ID_GROUP}>\\d+)\\s*$`);
function parseProcessInfoEntry(line, existingProcessInfoById) {
const match = line.match(PROCESS_LIST_ENTRY_REGEX);
if (!(match === null || match === void 0 ? void 0 : match.groups)) {
throw new InternalError_1.InternalError(`Invalid process list entry: ${line}`);
}
const processName = match.groups[NAME_GROUP];
const processId = parseInt(match.groups[PROCESS_ID_GROUP], 10);
const parentProcessId = parseInt(match.groups[PARENT_PROCESS_ID_GROUP], 10);
// Only care about the parent process if it is not the same as the current process.
let parentProcessInfo;
if (parentProcessId !== processId) {
parentProcessInfo = existingProcessInfoById.get(parentProcessId);
if (!parentProcessInfo) {
// Create a new placeholder entry for the parent with the information we have so far
parentProcessInfo = {
processName: '',
processId: parentProcessId,
parentProcessInfo: undefined,
childProcessInfos: []
};
existingProcessInfoById.set(parentProcessId, parentProcessInfo);
}
}
let processInfo = existingProcessInfoById.get(processId);
if (!processInfo) {
// Create a new entry
processInfo = {
processName,
processId,
parentProcessInfo,
childProcessInfos: []
};
existingProcessInfoById.set(processId, processInfo);
}
else {
// Update placeholder entry
processInfo.processName = processName;
processInfo.parentProcessInfo = parentProcessInfo;
}
// Add the process as a child of the parent process
parentProcessInfo === null || parentProcessInfo === void 0 ? void 0 : parentProcessInfo.childProcessInfos.push(processInfo);
}
function convertToProcessInfoByNameMap(processInfoById) {
const processInfoByNameMap = new Map();
for (const processInfo of processInfoById.values()) {
let processInfoNameEntries = processInfoByNameMap.get(processInfo.processName);
if (!processInfoNameEntries) {
processInfoNameEntries = [];
processInfoByNameMap.set(processInfo.processName, processInfoNameEntries);
}
processInfoNameEntries.push(processInfo);
}
return processInfoByNameMap;
}
const OS_PLATFORM = os.platform();
function getProcessListProcessOptions() {
let command;
let args;
if (OS_PLATFORM === 'win32') {
command = 'wmic.exe';
// Order of declared properties does not impact the order of the output
args = ['process', 'get', 'Name,ParentProcessId,ProcessId'];
}
else {
command = 'ps';
// -A: Select all processes
// -o: User-defined format
// Order of declared properties impacts the order of the output, so match
// the order of wmic.exe output
args = ['-Ao', 'comm,ppid,pid'];
}
return { path: command, args };
}
/**
* The Executable class provides a safe, portable, recommended solution for tools that need
* to launch child processes.
*
* @remarks
* The NodeJS child_process API provides a solution for launching child processes, however
* its design encourages reliance on the operating system shell for certain features.
* Invoking the OS shell is not safe, not portable, and generally not recommended:
*
* - Different shells have different behavior and command-line syntax, and which shell you
* will get with NodeJS is unpredictable. There is no universal shell guaranteed to be
* available on all platforms.
*
* - If a command parameter contains symbol characters, a shell may interpret them, which
* can introduce a security vulnerability
*
* - Each shell has different rules for escaping these symbols. On Windows, the default
* shell is incapable of escaping certain character sequences.
*
* The Executable API provides a pure JavaScript implementation of primitive shell-like
* functionality for searching the default PATH, appending default file extensions on Windows,
* and executing a file that may contain a POSIX shebang. This primitive functionality
* is sufficient (and recommended) for most tooling scenarios.
*
* If you need additional shell features such as wildcard globbing, environment variable
* expansion, piping, or built-in commands, then we recommend to use the `@microsoft/rushell`
* library instead. Rushell is a pure JavaScript shell with a standard syntax that is
* guaranteed to work consistently across all platforms.
*
* @public
*/
class Executable {
/**
* Synchronously create a child process and optionally capture its output.
*
* @remarks
* This function is similar to child_process.spawnSync(). 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.
*
* @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
*
* @privateRemarks
*
* NOTE: The NodeJS spawnSync() returns SpawnSyncReturns<string> or SpawnSyncReturns<Buffer>
* polymorphically based on the options.encoding parameter value. This is a fairly confusing
* design. In most cases, developers want string with the default encoding. If/when someone
* wants binary output or a non-default text encoding, we will introduce a separate API function
* with a name like "spawnWithBufferSync".
*/
static spawnSync(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(),
input: options.input,
stdio: options.stdio,
timeout: options.timeoutMs,
maxBuffer: options.maxBuffer,
// Contrary to what the NodeJS typings imply, we must explicitly specify "utf8" here
// if we want the result to be SpawnSyncReturns<string> instead of SpawnSyncReturns<Buffer>.
encoding: 'utf8',
// 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.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);
}
static async waitForExitAsync(childProcess, options = {}) {
const { throwOnNonZeroExitCode = false, encoding } = options;
if (encoding && (!childProcess.stdout || !childProcess.stderr)) {
throw new Error('An encoding was specified, but stdout and/or stderr on the child process are not defined');
}
const collectedStdout = [];
const collectedStderr = [];
const useBufferEncoding = encoding === 'buffer';
function normalizeChunk(chunk) {
if (typeof chunk === 'string') {
return (useBufferEncoding ? Buffer.from(chunk) : chunk);
}
else {
return (useBufferEncoding ? chunk : chunk.toString(encoding));
}
}
let errorThrown = false;
const exitCode = await new Promise((resolve, reject) => {
if (encoding) {
childProcess.stdout.on('data', (chunk) => {
collectedStdout.push(normalizeChunk(chunk));
});
childProcess.stderr.on('data', (chunk) => {
collectedStderr.push(normalizeChunk(chunk));
});
}
childProcess.on('error', (error) => {
errorThrown = true;
reject(error);
});
childProcess.on('exit', (code) => {
if (errorThrown) {
// We've already rejected the promise
return;
}
if (code !== 0 && throwOnNonZeroExitCode) {
reject(new Error(`Process exited with code ${code}`));
}
else {
resolve(code);
}
});
});
const result = {
exitCode
};
if (encoding === 'buffer') {
result.stdout = Buffer.concat(collectedStdout);
result.stderr = Buffer.concat(collectedStderr);
}
else if (encoding) {
result.stdout = collectedStdout.join('');
result.stderr = collectedStderr.join('');
}
return result;
}
/* eslint-enable @rushstack/no-new-null */
/**
* Get the list of processes currently running on the system, keyed by the process ID.
*
* @remarks The underlying implementation depends on the operating system:
* - On Windows, this uses the `wmic.exe` utility.
* - On Unix, this uses the `ps` utility.
*/
static async getProcessInfoByIdAsync() {
const { path: command, args } = getProcessListProcessOptions();
const process = Executable.spawn(command, args, {
stdio: ['ignore', 'pipe', 'ignore']
});
if (process.stdout === null) {
throw new InternalError_1.InternalError('Child process did not provide stdout');
}
const [processInfoByIdMap] = await Promise.all([
parseProcessListOutputAsync(process.stdout),
// Don't collect output in the result since we process it directly
Executable.waitForExitAsync(process, { throwOnNonZeroExitCode: true })
]);
return processInfoByIdMap;
}
/**
* {@inheritDoc Executable.getProcessInfoByIdAsync}
*/
static getProcessInfoById() {
const { path: command, args } = getProcessListProcessOptions();
const processOutput = Executable.spawnSync(command, args);
if (processOutput.error) {
throw new Error(`Unable to list processes: ${command} failed with error ${processOutput.error}`);
}
if (processOutput.status !== 0) {
throw new Error(`Unable to list processes: ${command} exited with code ${processOutput.status}`);
}
return parseProcessListOutput(processOutput.output);
}
/**
* Get the list of processes currently running on the system, keyed by the process name. All processes
* with the same name will be grouped.
*
* @remarks The underlying implementation depends on the operating system:
* - On Windows, this uses the `wmic.exe` utility.
* - On Unix, this uses the `ps` utility.
*/
static async getProcessInfoByNameAsync() {
const processInfoById = await Executable.getProcessInfoByIdAsync();
return convertToProcessInfoByNameMap(processInfoById);
}
/**
* {@inheritDoc Executable.getProcessInfoByNameAsync}
*/
static getProcessInfoByName() {
const processInfoByIdMap = Executable.getProcessInfoById();
return convertToProcessInfoByNameMap(processInfoByIdMap);
}
// 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);
if (OS_PLATFORM === 'win32') {
// Do we need a custom handler for this file type?
switch (fileExtension.toUpperCase()) {
case '.EXE':
case '.COM':
// okay to execute directly
break;
case '.BAT':
case '.CMD': {
Executable._validateArgsForWindowsShell(args);
// These file types must be invoked via the Windows shell
let shellPath = context.environmentMap.get('COMSPEC');
if (!shellPath || !Executable._canExecute(shellPath, context)) {
shellPath = Executable.tryResolve('cmd.exe');
}
if (!shellPath) {
throw new Error(`Unable to execute "${path.basename(resolvedPath)}" ` +
`because CMD.exe was not found in the PATH`);
}
const shellArgs = [];
// /D: Disable execution of AutoRun commands when starting the new shell context
shellArgs.push('/d');
// /S: Disable Cmd.exe's parsing of double-quote characters inside the command-line
shellArgs.push('/s');
// /C: Execute the following command and then exit immediately
shellArgs.push('/c');
// If the path contains special charactrers (e.g. spaces), escape them so that
// they don't get interpreted by the shell
shellArgs.push(Executable._getEscapedForWindowsShell(resolvedPath));
shellArgs.push(...args);
return { path: shellPath, args: shellArgs };
}
default:
throw new Error(`Cannot execute "${path.basename(resolvedPath)}" because the file type is not supported`);
}
}
return {
path: resolvedPath,
args: args
};
}
/**
* Given a filename, this determines the absolute path of the executable file that would
* be executed by a shell:
*
* - 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.
*
* @remarks
*
* @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 options - optional other parameters
* @returns the absolute path of the executable, or undefined if it was not found
*/
static tryResolve(filename, options) {
return Executable._tryResolve(filename, options || {}, Executable._getExecutableContext(options));
}
static _tryResolve(filename, options, context) {
// NOTE: Since "filename" cannot contain command-line arguments, the "/" here
// must be interpreted as a path delimiter
const hasPathSeparators = filename.indexOf('/') >= 0 || (OS_PLATFORM === 'win32' && filename.indexOf('\\') >= 0);
// Are there any path separators?
if (hasPathSeparators) {
// If so, then don't search the PATH. Just resolve relative to the current working directory
const resolvedPath = path.resolve(context.currentWorkingDirectory, filename);
return Executable._tryResolveFileExtension(resolvedPath, context);
}
else {
// Otherwise if it's a bare name, then try everything in the shell PATH
const pathsToSearch = Executable._getSearchFolders(context);
for (const pathToSearch of pathsToSearch) {
const resolvedPath = path.join(pathToSearch, filename);
const result = Executable._tryResolveFileExtension(resolvedPath, context);
if (result) {
return result;
}
}
// No match was found
return undefined;
}
}
static _tryResolveFileExtension(resolvedPath, context) {
if (Executable._canExecute(resolvedPath, context)) {
return resolvedPath;
}
// Try the default file extensions
for (const shellExtension of context.windowsExecutableExtensions) {
const resolvedNameWithExtension = resolvedPath + shellExtension;
if (Executable._canExecute(resolvedNameWithExtension, context)) {
return resolvedNameWithExtension;
}
}
return undefined;
}
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;
}
/**
* This is used when searching the shell PATH for an executable, to determine
* whether a match should be skipped or not. If it returns true, this does not
* guarantee that the file can be successfully executed.
*/
static _canExecute(filePath, context) {
if (!FileSystem_1.FileSystem.exists(filePath)) {
return false;
}
if (OS_PLATFORM === 'win32') {
// NOTE: For Windows, we don't validate that the file extension appears in PATHEXT.
// That environment variable determines which extensions can be appended if the
// extension is missing, but it does not affect whether a file may be executed or not.
// Windows does have a (seldom used) ACL that can be used to deny execution permissions
// for a file, but NodeJS doesn't expose that API, so we don't bother checking it.
// However, Windows *does* require that the file has some kind of file extension
if (path.extname(filePath) === '') {
return false;
}
}
else {
// For Unix, check whether any of the POSIX execute bits are set
try {
// eslint-disable-next-line no-bitwise
if ((FileSystem_1.FileSystem.getPosixModeBits(filePath) & PosixModeBits_1.PosixModeBits.AllExecute) === 0) {
return false; // not executable
}
}
catch (error) {
// If we have trouble accessing the file, ignore the error and consider it "not executable"
// since that's what a shell would do
}
}
return true;
}
/**
* Returns the list of folders where we will search for an executable,
* based on the PATH environment variable.
*/
static _getSearchFolders(context) {
const pathList = context.environmentMap.get('PATH') || '';
const folders = [];
// Avoid processing duplicates
const seenPaths = new Set();
// NOTE: Cmd.exe on Windows always searches the current working directory first.
// PowerShell and Unix shells do NOT do that, because it's a security concern.
// We follow their behavior.
for (const splitPath of pathList.split(path.delimiter)) {
const trimmedPath = splitPath.trim();
if (trimmedPath !== '') {
if (!seenPaths.has(trimmedPath)) {
// Fun fact: If you put relative paths in your PATH environment variable,
// all shells will dynamically match them against the current working directory.
// This is a terrible design, and in practice nobody does that, but it is supported...
// so we allow it here.
const resolvedPath = path.resolve(context.currentWorkingDirectory, trimmedPath);
if (!seenPaths.has(resolvedPath)) {
if (FileSystem_1.FileSystem.exists(resolvedPath)) {
folders.push(resolvedPath);
}
seenPaths.add(resolvedPath);
}
seenPaths.add(trimmedPath);
}
}
}
return folders;
}
static _getExecutableContext(options) {
if (!options) {
options = {};
}
const environment = Executable._buildEnvironmentMap(options);
let currentWorkingDirectory;
if (options.currentWorkingDirectory) {
currentWorkingDirectory = path.resolve(options.currentWorkingDirectory);
}
else {
currentWorkingDirectory = process.cwd();
}
const windowsExecutableExtensions = [];
if (OS_PLATFORM === 'win32') {
const pathExtVariable = environment.get('PATHEXT') || '';
for (const splitValue of pathExtVariable.split(';')) {
const trimmed = splitValue.trim().toLowerCase();
// Ignore malformed extensions
if (/^\.[a-z0-9\.]*[a-z0-9]$/i.test(trimmed)) {
// Don't add the same extension twice
if (windowsExecutableExtensions.indexOf(trimmed) < 0) {
windowsExecutableExtensions.push(trimmed);
}
}
}
}
return {
environmentMap: environment,
currentWorkingDirectory,
windowsExecutableExtensions
};
}
/**
* Given an input string containing special symbol characters, this inserts the "^" escape
* character to ensure the symbols are interpreted literally by the Windows shell.
*/
static _getEscapedForWindowsShell(text) {
const escapableCharRegExp = /[%\^&|<> ]/g;
return text.replace(escapableCharRegExp, (value) => '^' + value);
}
/**
* Checks for characters that are unsafe to pass to a Windows batch file
* due to the way that cmd.exe implements escaping.
*/
static _validateArgsForWindowsShell(args) {
const specialCharRegExp = /[%\^&|<>\r\n]/g;
for (const arg of args) {
const match = arg.match(specialCharRegExp);
if (match) {
// NOTE: It is possible to escape some of these characters by prefixing them
// with a caret (^), which allows these characters to be successfully passed
// through to the batch file %1 variables. But they will be expanded again
// whenever they are used. For example, NPM's binary wrapper batch files
// use "%*" to pass their arguments to Node.exe, which causes them to be expanded
// again. Unfortunately the Cmd.exe batch language provides native escaping
// function (that could be used to insert the carets again).
//
// We could work around that by adding double carets, but in general there
// is no way to predict how many times the variable will get expanded.
// Thus, there is no generally reliable way to pass these characters.
throw new Error(`The command line argument ${JSON.stringify(arg)} contains a` +
` special character ${JSON.stringify(match[0])} that cannot be escaped for the Windows shell`);
}
}
}
}
exports.Executable = Executable;
//# sourceMappingURL=Executable.js.map

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,81 @@
import { type FileLocationStyle } from './Path';
/**
* Provides options for the creation of a FileError.
*
* @public
*/
export interface IFileErrorOptions {
/**
* The absolute path to the file that contains the error.
*/
absolutePath: string;
/**
* The root folder for the project that the error is in relation to.
*/
projectFolder: string;
/**
* The line number of the error in the target file. Minimum value is 1.
*/
line?: number;
/**
* The column number of the error in the target file. Minimum value is 1.
*/
column?: number;
}
/**
* Provides options for the output message of a file error.
*
* @public
*/
export interface IFileErrorFormattingOptions {
/**
* The format for the error message. If no format is provided, format 'Unix' is used by default.
*/
format?: FileLocationStyle;
}
/**
* An `Error` subclass that should be thrown to report an unexpected state that specifically references
* a location in a file.
*
* @remarks The file path provided to the FileError constructor is expected to exist on disk. FileError
* should not be used for reporting errors that are not in reference to an existing file.
*
* @public
*/
export declare class FileError extends Error {
/** @internal */
static _sanitizedEnvironmentVariable: string | undefined;
/** @internal */
static _environmentVariableIsAbsolutePath: boolean;
private static _environmentVariableBasePathFnMap;
/** {@inheritdoc IFileErrorOptions.absolutePath} */
readonly absolutePath: string;
/** {@inheritdoc IFileErrorOptions.projectFolder} */
readonly projectFolder: string;
/** {@inheritdoc IFileErrorOptions.line} */
readonly line: number | undefined;
/** {@inheritdoc IFileErrorOptions.column} */
readonly column: number | undefined;
/**
* Constructs a new instance of the {@link FileError} class.
*
* @param message - A message describing the error.
* @param options - Options for the error.
*/
constructor(message: string, options: IFileErrorOptions);
/**
* Get the Unix-formatted the error message.
*
* @override
*/
toString(): string;
/**
* Get the formatted error message.
*
* @param options - Options for the error message format.
*/
getFormattedErrorMessage(options?: IFileErrorFormattingOptions): string;
private _evaluateBaseFolder;
static [Symbol.hasInstance](instance: object): boolean;
}
//# sourceMappingURL=FileError.d.ts.map

View File

@ -0,0 +1 @@
{"version":3,"file":"FileError.d.ts","sourceRoot":"","sources":["../src/FileError.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,KAAK,iBAAiB,EAAQ,MAAM,QAAQ,CAAC;AAGtD;;;;GAIG;AACH,MAAM,WAAW,iBAAiB;IAChC;;OAEG;IACH,YAAY,EAAE,MAAM,CAAC;IAErB;;OAEG;IACH,aAAa,EAAE,MAAM,CAAC;IAEtB;;OAEG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IAEd;;OAEG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;;;GAIG;AACH,MAAM,WAAW,2BAA2B;IAC1C;;OAEG;IACH,MAAM,CAAC,EAAE,iBAAiB,CAAC;CAC5B;AAMD;;;;;;;;GAQG;AACH,qBAAa,SAAU,SAAQ,KAAK;IAClC,gBAAgB;IAChB,OAAc,6BAA6B,EAAE,MAAM,GAAG,SAAS,CAAC;IAChE,gBAAgB;IAChB,OAAc,kCAAkC,EAAE,OAAO,CAAS;IAElE,OAAO,CAAC,MAAM,CAAC,iCAAiC,CAO7C;IAEH,mDAAmD;IACnD,SAAgB,YAAY,EAAE,MAAM,CAAC;IACrC,oDAAoD;IACpD,SAAgB,aAAa,EAAE,MAAM,CAAC;IACtC,2CAA2C;IAC3C,SAAgB,IAAI,EAAE,MAAM,GAAG,SAAS,CAAC;IACzC,6CAA6C;IAC7C,SAAgB,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC;IAE3C;;;;;OAKG;gBACgB,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,iBAAiB;IAe9D;;;;OAIG;IACI,QAAQ,IAAI,MAAM;IAKzB;;;;OAIG;IACI,wBAAwB,CAAC,OAAO,CAAC,EAAE,2BAA2B,GAAG,MAAM;IAW9E,OAAO,CAAC,mBAAmB;WAgDb,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO;CAG9D"}

View File

@ -0,0 +1,113 @@
"use strict";
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root for license information.
Object.defineProperty(exports, "__esModule", { value: true });
exports.FileError = void 0;
const Path_1 = require("./Path");
const TypeUuid_1 = require("./TypeUuid");
const uuidFileError = '37a4c772-2dc8-4c66-89ae-262f8cc1f0c1';
const baseFolderEnvVar = 'RUSHSTACK_FILE_ERROR_BASE_FOLDER';
/**
* An `Error` subclass that should be thrown to report an unexpected state that specifically references
* a location in a file.
*
* @remarks The file path provided to the FileError constructor is expected to exist on disk. FileError
* should not be used for reporting errors that are not in reference to an existing file.
*
* @public
*/
class FileError extends Error {
/**
* Constructs a new instance of the {@link FileError} class.
*
* @param message - A message describing the error.
* @param options - Options for the error.
*/
constructor(message, options) {
super(message);
this.absolutePath = options.absolutePath;
this.projectFolder = options.projectFolder;
this.line = options.line;
this.column = options.column;
// Manually set the prototype, as we can no longer extend built-in classes like Error, Array, Map, etc.
// https://github.com/microsoft/TypeScript-wiki/blob/main/Breaking-Changes.md#extending-built-ins-like-error-array-and-map-may-no-longer-work
//
// Note: the prototype must also be set on any classes which extend this one
this.__proto__ = FileError.prototype; // eslint-disable-line @typescript-eslint/no-explicit-any
}
/**
* Get the Unix-formatted the error message.
*
* @override
*/
toString() {
// Default to formatting in 'Unix' format, for consistency.
return this.getFormattedErrorMessage();
}
/**
* Get the formatted error message.
*
* @param options - Options for the error message format.
*/
getFormattedErrorMessage(options) {
return Path_1.Path.formatFileLocation({
format: (options === null || options === void 0 ? void 0 : options.format) || 'Unix',
baseFolder: this._evaluateBaseFolder(),
pathToFormat: this.absolutePath,
message: this.message,
line: this.line,
column: this.column
});
}
_evaluateBaseFolder() {
// Cache the sanitized environment variable. This means that we don't support changing
// the environment variable mid-execution. This is a reasonable tradeoff for the benefit
// of being able to cache absolute paths, since that is only able to be determined after
// running the regex, which is expensive. Since this would be a common execution path for
// tools like Rush, we should optimize for that.
if (!FileError._sanitizedEnvironmentVariable && process.env[baseFolderEnvVar]) {
// Strip leading and trailing quotes, if present.
FileError._sanitizedEnvironmentVariable = process.env[baseFolderEnvVar].replace(/^("|')|("|')$/g, '');
}
if (FileError._environmentVariableIsAbsolutePath) {
return FileError._sanitizedEnvironmentVariable;
}
// undefined environment variable has a mapping to the project folder
const baseFolderFn = FileError._environmentVariableBasePathFnMap.get(FileError._sanitizedEnvironmentVariable);
if (baseFolderFn) {
return baseFolderFn(this);
}
const baseFolderTokenRegex = /{([^}]+)}/g;
const result = baseFolderTokenRegex.exec(FileError._sanitizedEnvironmentVariable);
if (!result) {
// No tokens, assume absolute path
FileError._environmentVariableIsAbsolutePath = true;
return FileError._sanitizedEnvironmentVariable;
}
else if (result.index !== 0) {
// Currently only support the token being first in the string.
throw new Error(`The ${baseFolderEnvVar} environment variable contains text before the token "${result[0]}".`);
}
else if (result[0].length !== FileError._sanitizedEnvironmentVariable.length) {
// Currently only support the token being the entire string.
throw new Error(`The ${baseFolderEnvVar} environment variable contains text after the token "${result[0]}".`);
}
else {
throw new Error(`The ${baseFolderEnvVar} environment variable contains a token "${result[0]}", which is not ` +
'supported.');
}
}
static [Symbol.hasInstance](instance) {
return TypeUuid_1.TypeUuid.isInstanceOf(instance, uuidFileError);
}
}
/** @internal */
FileError._environmentVariableIsAbsolutePath = false;
FileError._environmentVariableBasePathFnMap = new Map([
[undefined, (fileError) => fileError.projectFolder],
['{PROJECT_FOLDER}', (fileError) => fileError.projectFolder],
['{ABSOLUTE_PATH}', (fileError) => undefined]
]);
exports.FileError = FileError;
TypeUuid_1.TypeUuid.registerClass(FileError, uuidFileError);
//# sourceMappingURL=FileError.js.map

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,676 @@
/// <reference types="node" />
/// <reference types="node" />
/// <reference types="node" />
import * as fs from 'fs';
import { type NewlineKind, Encoding } from './Text';
import { PosixModeBits } from './PosixModeBits';
/**
* An alias for the Node.js `fs.Stats` object.
*
* @remarks
* This avoids the need to import the `fs` package when using the {@link FileSystem} API.
* @public
*/
export type FileSystemStats = fs.Stats;
/**
* An alias for the Node.js `fs.Dirent` object.
*
* @remarks
* This avoids the need to import the `fs` package when using the {@link FileSystem} API.
* @public
*/
export type FolderItem = fs.Dirent;
/**
* The options for {@link FileSystem.readFolder}
* @public
*/
export interface IFileSystemReadFolderOptions {
/**
* If true, returns the absolute paths of the files in the folder.
* @defaultValue false
*/
absolutePaths?: boolean;
}
/**
* The options for {@link FileSystem.writeFile}
* @public
*/
export interface IFileSystemWriteFileOptions {
/**
* If true, will ensure the folder is created before writing the file.
* @defaultValue false
*/
ensureFolderExists?: boolean;
/**
* If specified, will normalize line endings to the specified style of newline.
* @defaultValue `undefined` which means no conversion will be performed
*/
convertLineEndings?: NewlineKind;
/**
* If specified, will change the encoding of the file that will be written.
* @defaultValue "utf8"
*/
encoding?: Encoding;
}
/**
* The options for {@link FileSystem.readFile}
* @public
*/
export interface IFileSystemReadFileOptions {
/**
* If specified, will change the encoding of the file that will be written.
* @defaultValue Encoding.Utf8
*/
encoding?: Encoding;
/**
* If specified, will normalize line endings to the specified style of newline.
* @defaultValue `undefined` which means no conversion will be performed
*/
convertLineEndings?: NewlineKind;
}
/**
* The options for {@link FileSystem.move}
* @public
*/
export interface IFileSystemMoveOptions {
/**
* The path of the existing object to be moved.
* The path may be absolute or relative.
*/
sourcePath: string;
/**
* The new path for the object.
* The path may be absolute or relative.
*/
destinationPath: string;
/**
* If true, will overwrite the file if it already exists.
* @defaultValue true
*/
overwrite?: boolean;
/**
* If true, will ensure the folder is created before writing the file.
* @defaultValue false
*/
ensureFolderExists?: boolean;
}
/**
* @public
*/
export interface IFileSystemCopyFileBaseOptions {
/**
* The path of the existing object to be copied.
* The path may be absolute or relative.
*/
sourcePath: string;
/**
* Specifies what to do if the destination path already exists.
* @defaultValue {@link AlreadyExistsBehavior.Overwrite}
*/
alreadyExistsBehavior?: AlreadyExistsBehavior;
}
/**
* The options for {@link FileSystem.copyFile}
* @public
*/
export interface IFileSystemCopyFileOptions extends IFileSystemCopyFileBaseOptions {
/**
* The path that the object will be copied to.
* The path may be absolute or relative.
*/
destinationPath: string;
}
/**
* Specifies the behavior of APIs such as {@link FileSystem.copyFile} or
* {@link FileSystem.createSymbolicLinkFile} when the output file path already exists.
*
* @remarks
* For {@link FileSystem.copyFile} and related APIs, the "output file path" is
* {@link IFileSystemCopyFileOptions.destinationPath}.
*
* For {@link FileSystem.createSymbolicLinkFile} and related APIs, the "output file path" is
* {@link IFileSystemCreateLinkOptions.newLinkPath}.
*
* @public
*/
export declare enum AlreadyExistsBehavior {
/**
* If the output file path already exists, try to overwrite the existing object.
*
* @remarks
* If overwriting the object would require recursively deleting a folder tree,
* then the operation will fail. As an example, suppose {@link FileSystem.copyFile}
* is copying a single file `/a/b/c` to the destination path `/d/e`, and `/d/e` is a
* nonempty folder. In this situation, an error will be reported; specifying
* `AlreadyExistsBehavior.Overwrite` does not help. Empty folders can be overwritten
* depending on the details of the implementation.
*/
Overwrite = "overwrite",
/**
* If the output file path already exists, the operation will fail, and an error
* will be reported.
*/
Error = "error",
/**
* If the output file path already exists, skip this item, and continue the operation.
*/
Ignore = "ignore"
}
/**
* Callback function type for {@link IFileSystemCopyFilesAsyncOptions.filter}
* @public
*/
export type FileSystemCopyFilesAsyncFilter = (sourcePath: string, destinationPath: string) => Promise<boolean>;
/**
* Callback function type for {@link IFileSystemCopyFilesOptions.filter}
* @public
*/
export type FileSystemCopyFilesFilter = (sourcePath: string, destinationPath: string) => boolean;
/**
* The options for {@link FileSystem.copyFilesAsync}
* @public
*/
export interface IFileSystemCopyFilesAsyncOptions {
/**
* The starting path of the file or folder to be copied.
* The path may be absolute or relative.
*/
sourcePath: string;
/**
* The path that the files will be copied to.
* The path may be absolute or relative.
*/
destinationPath: string;
/**
* If true, then when copying symlinks, copy the target object instead of copying the link.
*/
dereferenceSymlinks?: boolean;
/**
* Specifies what to do if a destination path already exists.
*
* @remarks
* This setting is applied individually for each file being copied.
* For example, `AlreadyExistsBehavior.Overwrite` will not recursively delete a folder
* whose path corresponds to an individual file that is being copied to that location.
*/
alreadyExistsBehavior?: AlreadyExistsBehavior;
/**
* If true, then the target object will be assigned "last modification" and "last access" timestamps
* that are the same as the source. Otherwise, the OS default timestamps are assigned.
*/
preserveTimestamps?: boolean;
/**
* A callback that will be invoked for each path that is copied. The callback can return `false`
* to cause the object to be excluded from the operation.
*/
filter?: FileSystemCopyFilesAsyncFilter | FileSystemCopyFilesFilter;
}
/**
* The options for {@link FileSystem.copyFiles}
* @public
*/
export interface IFileSystemCopyFilesOptions extends IFileSystemCopyFilesAsyncOptions {
/** {@inheritdoc IFileSystemCopyFilesAsyncOptions.filter} */
filter?: FileSystemCopyFilesFilter;
}
/**
* The options for {@link FileSystem.deleteFile}
* @public
*/
export interface IFileSystemDeleteFileOptions {
/**
* If true, will throw an exception if the file did not exist before `deleteFile()` was called.
* @defaultValue false
*/
throwIfNotExists?: boolean;
}
/**
* The options for {@link FileSystem.updateTimes}
* Both times must be specified.
* @public
*/
export interface IFileSystemUpdateTimeParameters {
/**
* The POSIX epoch time or Date when this was last accessed.
*/
accessedTime: number | Date;
/**
* The POSIX epoch time or Date when this was last modified
*/
modifiedTime: number | Date;
}
/**
* The options for {@link FileSystem.createSymbolicLinkJunction}, {@link FileSystem.createSymbolicLinkFile},
* {@link FileSystem.createSymbolicLinkFolder}, and {@link FileSystem.createHardLink}.
*
* @public
*/
export interface IFileSystemCreateLinkOptions {
/**
* The newly created symbolic link will point to `linkTargetPath` as its target.
*/
linkTargetPath: string;
/**
* The newly created symbolic link will have this path.
*/
newLinkPath: string;
/**
* Specifies what to do if the path to create already exists.
* The default is `AlreadyExistsBehavior.Error`.
*/
alreadyExistsBehavior?: AlreadyExistsBehavior;
}
/**
* The FileSystem API provides a complete set of recommended operations for interacting with the file system.
*
* @remarks
* We recommend to use this instead of the native `fs` API, because `fs` is a minimal set of low-level
* primitives that must be mapped for each supported operating system. The FileSystem API takes a
* philosophical approach of providing "one obvious way" to do each operation. We also prefer synchronous
* operations except in cases where there would be a clear performance benefit for using async, since synchronous
* code is much easier to read and debug. Also, indiscriminate parallelism has been seen to actually worsen
* performance, versus improving it.
*
* Note that in the documentation, we refer to "filesystem objects", this can be a
* file, folder, symbolic link, hard link, directory junction, etc.
*
* @public
*/
export declare class FileSystem {
/**
* Returns true if the path exists on disk.
* Behind the scenes it uses `fs.existsSync()`.
* @remarks
* There is a debate about the fact that after `fs.existsSync()` returns true,
* the file might be deleted before fs.readSync() is called, which would imply that everybody
* should catch a `readSync()` exception, and nobody should ever use `fs.existsSync()`.
* We find this to be unpersuasive, since "unexceptional exceptions" really hinder the
* break-on-exception debugging experience. Also, throwing/catching is generally slow.
* @param path - The absolute or relative path to the filesystem object.
*/
static exists(path: string): boolean;
/**
* An async version of {@link FileSystem.exists}.
*/
static existsAsync(path: string): Promise<boolean>;
/**
* Gets the statistics for a particular filesystem object.
* If the path is a link, this function follows the link and returns statistics about the link target.
* Behind the scenes it uses `fs.statSync()`.
* @param path - The absolute or relative path to the filesystem object.
*/
static getStatistics(path: string): FileSystemStats;
/**
* An async version of {@link FileSystem.getStatistics}.
*/
static getStatisticsAsync(path: string): Promise<FileSystemStats>;
/**
* Updates the accessed and modified timestamps of the filesystem object referenced by path.
* Behind the scenes it uses `fs.utimesSync()`.
* The caller should specify both times in the `times` parameter.
* @param path - The path of the file that should be modified.
* @param times - The times that the object should be updated to reflect.
*/
static updateTimes(path: string, times: IFileSystemUpdateTimeParameters): void;
/**
* An async version of {@link FileSystem.updateTimes}.
*/
static updateTimesAsync(path: string, times: IFileSystemUpdateTimeParameters): Promise<void>;
/**
* Changes the permissions (i.e. file mode bits) for a filesystem object.
* Behind the scenes it uses `fs.chmodSync()`.
* @param path - The absolute or relative path to the object that should be updated.
* @param modeBits - POSIX-style file mode bits specified using the {@link PosixModeBits} enum
*/
static changePosixModeBits(path: string, modeBits: PosixModeBits): void;
/**
* An async version of {@link FileSystem.changePosixModeBits}.
*/
static changePosixModeBitsAsync(path: string, mode: PosixModeBits): Promise<void>;
/**
* Retrieves the permissions (i.e. file mode bits) for a filesystem object.
* Behind the scenes it uses `fs.chmodSync()`.
* @param path - The absolute or relative path to the object that should be updated.
*
* @remarks
* This calls {@link FileSystem.getStatistics} to get the POSIX mode bits.
* If statistics in addition to the mode bits are needed, it is more efficient
* to call {@link FileSystem.getStatistics} directly instead.
*/
static getPosixModeBits(path: string): PosixModeBits;
/**
* An async version of {@link FileSystem.getPosixModeBits}.
*/
static getPosixModeBitsAsync(path: string): Promise<PosixModeBits>;
/**
* Returns a 10-character string representation of a PosixModeBits value similar to what
* would be displayed by a command such as "ls -l" on a POSIX-like operating system.
* @remarks
* For example, `PosixModeBits.AllRead | PosixModeBits.AllWrite` would be formatted as "-rw-rw-rw-".
* @param modeBits - POSIX-style file mode bits specified using the {@link PosixModeBits} enum
*/
static formatPosixModeBits(modeBits: PosixModeBits): string;
/**
* Moves a file. The folder must exist, unless the `ensureFolderExists` option is provided.
* Behind the scenes it uses `fs-extra.moveSync()`
*/
static move(options: IFileSystemMoveOptions): void;
/**
* An async version of {@link FileSystem.move}.
*/
static moveAsync(options: IFileSystemMoveOptions): Promise<void>;
/**
* Recursively creates a folder at a given path.
* Behind the scenes is uses `fs-extra.ensureDirSync()`.
* @remarks
* Throws an exception if anything in the folderPath is not a folder.
* @param folderPath - The absolute or relative path of the folder which should be created.
*/
static ensureFolder(folderPath: string): void;
/**
* An async version of {@link FileSystem.ensureFolder}.
*/
static ensureFolderAsync(folderPath: string): Promise<void>;
/**
* @deprecated
* Use {@link FileSystem.readFolderItemNames} instead.
*/
static readFolder(folderPath: string, options?: IFileSystemReadFolderOptions): string[];
/**
* @deprecated
* Use {@link FileSystem.readFolderItemNamesAsync} instead.
*/
static readFolderAsync(folderPath: string, options?: IFileSystemReadFolderOptions): Promise<string[]>;
/**
* Reads the names of folder entries, not including "." or "..".
* Behind the scenes it uses `fs.readdirSync()`.
* @param folderPath - The absolute or relative path to the folder which should be read.
* @param options - Optional settings that can change the behavior. Type: `IReadFolderOptions`
*/
static readFolderItemNames(folderPath: string, options?: IFileSystemReadFolderOptions): string[];
/**
* An async version of {@link FileSystem.readFolderItemNames}.
*/
static readFolderItemNamesAsync(folderPath: string, options?: IFileSystemReadFolderOptions): Promise<string[]>;
/**
* Reads the contents of the folder, not including "." or "..", returning objects including the
* entry names and types.
* Behind the scenes it uses `fs.readdirSync()`.
* @param folderPath - The absolute or relative path to the folder which should be read.
* @param options - Optional settings that can change the behavior. Type: `IReadFolderOptions`
*/
static readFolderItems(folderPath: string, options?: IFileSystemReadFolderOptions): FolderItem[];
/**
* An async version of {@link FileSystem.readFolderItems}.
*/
static readFolderItemsAsync(folderPath: string, options?: IFileSystemReadFolderOptions): Promise<FolderItem[]>;
/**
* Deletes a folder, including all of its contents.
* Behind the scenes is uses `fs-extra.removeSync()`.
* @remarks
* Does not throw if the folderPath does not exist.
* @param folderPath - The absolute or relative path to the folder which should be deleted.
*/
static deleteFolder(folderPath: string): void;
/**
* An async version of {@link FileSystem.deleteFolder}.
*/
static deleteFolderAsync(folderPath: string): Promise<void>;
/**
* Deletes the content of a folder, but not the folder itself. Also ensures the folder exists.
* Behind the scenes it uses `fs-extra.emptyDirSync()`.
* @remarks
* This is a workaround for a common race condition, where the virus scanner holds a lock on the folder
* for a brief period after it was deleted, causing EBUSY errors for any code that tries to recreate the folder.
* @param folderPath - The absolute or relative path to the folder which should have its contents deleted.
*/
static ensureEmptyFolder(folderPath: string): void;
/**
* An async version of {@link FileSystem.ensureEmptyFolder}.
*/
static ensureEmptyFolderAsync(folderPath: string): Promise<void>;
/**
* Writes a text string to a file on disk, overwriting the file if it already exists.
* Behind the scenes it uses `fs.writeFileSync()`.
* @remarks
* Throws an error if the folder doesn't exist, unless ensureFolder=true.
* @param filePath - The absolute or relative path of the file.
* @param contents - The text that should be written to the file.
* @param options - Optional settings that can change the behavior. Type: `IWriteFileOptions`
*/
static writeFile(filePath: string, contents: string | Buffer, options?: IFileSystemWriteFileOptions): void;
/**
* An async version of {@link FileSystem.writeFile}.
*/
static writeFileAsync(filePath: string, contents: string | Buffer, options?: IFileSystemWriteFileOptions): Promise<void>;
/**
* Writes a text string to a file on disk, appending to the file if it already exists.
* Behind the scenes it uses `fs.appendFileSync()`.
* @remarks
* Throws an error if the folder doesn't exist, unless ensureFolder=true.
* @param filePath - The absolute or relative path of the file.
* @param contents - The text that should be written to the file.
* @param options - Optional settings that can change the behavior. Type: `IWriteFileOptions`
*/
static appendToFile(filePath: string, contents: string | Buffer, options?: IFileSystemWriteFileOptions): void;
/**
* An async version of {@link FileSystem.appendToFile}.
*/
static appendToFileAsync(filePath: string, contents: string | Buffer, options?: IFileSystemWriteFileOptions): Promise<void>;
/**
* Reads the contents of a file into a string.
* Behind the scenes it uses `fs.readFileSync()`.
* @param filePath - The relative or absolute path to the file whose contents should be read.
* @param options - Optional settings that can change the behavior. Type: `IReadFileOptions`
*/
static readFile(filePath: string, options?: IFileSystemReadFileOptions): string;
/**
* An async version of {@link FileSystem.readFile}.
*/
static readFileAsync(filePath: string, options?: IFileSystemReadFileOptions): Promise<string>;
/**
* Reads the contents of a file into a buffer.
* Behind the scenes is uses `fs.readFileSync()`.
* @param filePath - The relative or absolute path to the file whose contents should be read.
*/
static readFileToBuffer(filePath: string): Buffer;
/**
* An async version of {@link FileSystem.readFileToBuffer}.
*/
static readFileToBufferAsync(filePath: string): Promise<Buffer>;
/**
* Copies a single file from one location to another.
* By default, destinationPath is overwritten if it already exists.
*
* @remarks
* The `copyFile()` API cannot be used to copy folders. It copies at most one file.
* Use {@link FileSystem.copyFiles} if you need to recursively copy a tree of folders.
*
* The implementation is based on `copySync()` from the `fs-extra` package.
*/
static copyFile(options: IFileSystemCopyFileOptions): void;
/**
* An async version of {@link FileSystem.copyFile}.
*/
static copyFileAsync(options: IFileSystemCopyFileOptions): Promise<void>;
/**
* Copies a file or folder from one location to another, recursively copying any folder contents.
* By default, destinationPath is overwritten if it already exists.
*
* @remarks
* If you only intend to copy a single file, it is recommended to use {@link FileSystem.copyFile}
* instead to more clearly communicate the intended operation.
*
* The implementation is based on `copySync()` from the `fs-extra` package.
*/
static copyFiles(options: IFileSystemCopyFilesOptions): void;
/**
* An async version of {@link FileSystem.copyFiles}.
*/
static copyFilesAsync(options: IFileSystemCopyFilesAsyncOptions): Promise<void>;
/**
* Deletes a file. Can optionally throw if the file doesn't exist.
* Behind the scenes it uses `fs.unlinkSync()`.
* @param filePath - The absolute or relative path to the file that should be deleted.
* @param options - Optional settings that can change the behavior. Type: `IDeleteFileOptions`
*/
static deleteFile(filePath: string, options?: IFileSystemDeleteFileOptions): void;
/**
* An async version of {@link FileSystem.deleteFile}.
*/
static deleteFileAsync(filePath: string, options?: IFileSystemDeleteFileOptions): Promise<void>;
/**
* Gets the statistics of a filesystem object. Does NOT follow the link to its target.
* Behind the scenes it uses `fs.lstatSync()`.
* @param path - The absolute or relative path to the filesystem object.
*/
static getLinkStatistics(path: string): FileSystemStats;
/**
* An async version of {@link FileSystem.getLinkStatistics}.
*/
static getLinkStatisticsAsync(path: string): Promise<FileSystemStats>;
/**
* If `path` refers to a symbolic link, this returns the path of the link target, which may be
* an absolute or relative path.
*
* @remarks
* If `path` refers to a filesystem object that is not a symbolic link, then an `ErrnoException` is thrown
* with code 'UNKNOWN'. If `path` does not exist, then an `ErrnoException` is thrown with code `ENOENT`.
*
* @param path - The absolute or relative path to the symbolic link.
* @returns the path of the link target
*/
static readLink(path: string): string;
/**
* An async version of {@link FileSystem.readLink}.
*/
static readLinkAsync(path: string): Promise<string>;
/**
* Creates an NTFS "directory junction" on Windows operating systems; for other operating systems, it
* creates a regular symbolic link. The link target must be a folder, not a file.
* Behind the scenes it uses `fs.symlinkSync()`.
*
* @remarks
* For security reasons, Windows operating systems by default require administrator elevation to create
* symbolic links. As a result, on Windows it's generally recommended for Node.js tools to use hard links
* (for files) or NTFS directory junctions (for folders), since regular users are allowed to create them.
* Hard links and junctions are less vulnerable to symlink attacks because they cannot reference a network share,
* and their target must exist at the time of link creation. Non-Windows operating systems generally don't
* restrict symlink creation, and as such are more vulnerable to symlink attacks. Note that Windows can be
* configured to permit regular users to create symlinks, for example by enabling Windows 10 "developer mode."
*
* A directory junction requires the link source and target to both be located on local disk volumes;
* if not, use a symbolic link instead.
*/
static createSymbolicLinkJunction(options: IFileSystemCreateLinkOptions): void;
/**
* An async version of {@link FileSystem.createSymbolicLinkJunction}.
*/
static createSymbolicLinkJunctionAsync(options: IFileSystemCreateLinkOptions): Promise<void>;
/**
* Creates a symbolic link to a file. On Windows operating systems, this may require administrator elevation.
* Behind the scenes it uses `fs.symlinkSync()`.
*
* @remarks
* To avoid administrator elevation on Windows, use {@link FileSystem.createHardLink} instead.
*
* On Windows operating systems, the NTFS file system distinguishes file symlinks versus directory symlinks:
* If the target is not the correct type, the symlink will be created successfully, but will fail to resolve.
* Other operating systems do not make this distinction, in which case {@link FileSystem.createSymbolicLinkFile}
* and {@link FileSystem.createSymbolicLinkFolder} can be used interchangeably, but doing so will make your
* tool incompatible with Windows.
*/
static createSymbolicLinkFile(options: IFileSystemCreateLinkOptions): void;
/**
* An async version of {@link FileSystem.createSymbolicLinkFile}.
*/
static createSymbolicLinkFileAsync(options: IFileSystemCreateLinkOptions): Promise<void>;
/**
* Creates a symbolic link to a folder. On Windows operating systems, this may require administrator elevation.
* Behind the scenes it uses `fs.symlinkSync()`.
*
* @remarks
* To avoid administrator elevation on Windows, use {@link FileSystem.createSymbolicLinkJunction} instead.
*
* On Windows operating systems, the NTFS file system distinguishes file symlinks versus directory symlinks:
* If the target is not the correct type, the symlink will be created successfully, but will fail to resolve.
* Other operating systems do not make this distinction, in which case {@link FileSystem.createSymbolicLinkFile}
* and {@link FileSystem.createSymbolicLinkFolder} can be used interchangeably, but doing so will make your
* tool incompatible with Windows.
*/
static createSymbolicLinkFolder(options: IFileSystemCreateLinkOptions): void;
/**
* An async version of {@link FileSystem.createSymbolicLinkFolder}.
*/
static createSymbolicLinkFolderAsync(options: IFileSystemCreateLinkOptions): Promise<void>;
/**
* Creates a hard link. The link target must be a file, not a folder.
* Behind the scenes it uses `fs.linkSync()`.
*
* @remarks
* For security reasons, Windows operating systems by default require administrator elevation to create
* symbolic links. As a result, on Windows it's generally recommended for Node.js tools to use hard links
* (for files) or NTFS directory junctions (for folders), since regular users are allowed to create them.
* Hard links and junctions are less vulnerable to symlink attacks because they cannot reference a network share,
* and their target must exist at the time of link creation. Non-Windows operating systems generally don't
* restrict symlink creation, and as such are more vulnerable to symlink attacks. Note that Windows can be
* configured to permit regular users to create symlinks, for example by enabling Windows 10 "developer mode."
*
* A hard link requires the link source and target to both be located on same disk volume;
* if not, use a symbolic link instead.
*/
static createHardLink(options: IFileSystemCreateLinkOptions): void;
/**
* An async version of {@link FileSystem.createHardLink}.
*/
static createHardLinkAsync(options: IFileSystemCreateLinkOptions): Promise<void>;
/**
* Follows a link to its destination and returns the absolute path to the final target of the link.
* Behind the scenes it uses `fs.realpathSync()`.
* @param linkPath - The path to the link.
*/
static getRealPath(linkPath: string): string;
/**
* An async version of {@link FileSystem.getRealPath}.
*/
static getRealPathAsync(linkPath: string): Promise<string>;
/**
* Returns true if the error object indicates the file or folder already exists (`EEXIST`).
*/
static isExistError(error: Error): boolean;
/**
* Returns true if the error object indicates the file or folder does not exist (`ENOENT` or `ENOTDIR`)
*/
static isNotExistError(error: Error): boolean;
/**
* Returns true if the error object indicates the file does not exist (`ENOENT`).
*/
static isFileDoesNotExistError(error: Error): boolean;
/**
* Returns true if the error object indicates the folder does not exist (`ENOTDIR`).
*/
static isFolderDoesNotExistError(error: Error): boolean;
/**
* Returns true if the error object indicates the target is a directory (`EISDIR`).
*/
static isDirectoryError(error: Error): boolean;
/**
* Returns true if the error object indicates the target is not a directory (`ENOTDIR`).
*/
static isNotDirectoryError(error: Error): boolean;
/**
* Returns true if the error object indicates that the `unlink` system call failed
* due to a permissions issue (`EPERM`).
*/
static isUnlinkNotPermittedError(error: Error): boolean;
/**
* Detects if the provided error object is a `NodeJS.ErrnoException`
*/
static isErrnoException(error: Error): error is NodeJS.ErrnoException;
private static _handleLink;
private static _handleLinkAsync;
private static _wrapException;
private static _wrapExceptionAsync;
private static _updateErrorMessage;
}
//# sourceMappingURL=FileSystem.d.ts.map

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,61 @@
/**
* Interface which represents the flags about which mode the file should be opened in.
* @public
*/
export interface IFileWriterFlags {
/**
* Open file for appending.
*/
append?: boolean;
/**
* Fails if path exists. The exclusive flag ensures that path is newly created.
*
* @remarks
* On POSIX-like operating systems, path is considered to exist even if it is a symlink to a
* non-existent file. The exclusive flag may or may not work with network file systems.
*
* POSIX is a registered trademark of the Institute of Electrical and Electronic Engineers, Inc.
*/
exclusive?: boolean;
}
/**
* API for interacting with file handles.
* @public
*/
export declare class FileWriter {
/**
* The `filePath` that was passed to {@link FileWriter.open}.
*/
readonly filePath: string;
private _fileDescriptor;
private constructor();
/**
* Opens a new file handle to the file at the specified path and given mode.
* Behind the scenes it uses `fs.openSync()`.
* The behaviour of this function is platform specific.
* See: https://nodejs.org/docs/latest-v8.x/api/fs.html#fs_fs_open_path_flags_mode_callback
* @param filePath - The absolute or relative path to the file handle that should be opened.
* @param flags - The flags for opening the handle
*/
static open(filePath: string, flags?: IFileWriterFlags): FileWriter;
/**
* Helper function to convert the file writer array to a Node.js style string (e.g. "wx" or "a").
* @param flags - The flags that should be converted.
*/
private static _convertFlagsForNode;
/**
* Writes some text to the given file handle. Throws if the file handle has been closed.
* Behind the scenes it uses `fs.writeSync()`.
* @param text - The text to write to the file.
*/
write(text: string): void;
/**
* Closes the file handle permanently. No operations can be made on this file handle after calling this.
* Behind the scenes it uses `fs.closeSync()` and releases the file descriptor to be re-used.
*
* @remarks
* The `close()` method can be called more than once; additional calls are ignored.
*/
close(): void;
}
//# sourceMappingURL=FileWriter.d.ts.map

View File

@ -0,0 +1 @@
{"version":3,"file":"FileWriter.d.ts","sourceRoot":"","sources":["../src/FileWriter.ts"],"names":[],"mappings":"AAaA;;;GAGG;AACH,MAAM,WAAW,gBAAgB;IAC/B;;OAEG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;IAEjB;;;;;;;;OAQG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAED;;;GAGG;AACH,qBAAa,UAAU;IACrB;;OAEG;IACH,SAAgB,QAAQ,EAAE,MAAM,CAAC;IAEjC,OAAO,CAAC,eAAe,CAAqB;IAE5C,OAAO;IAKP;;;;;;;OAOG;WACW,IAAI,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,gBAAgB,GAAG,UAAU;IAI1E;;;OAGG;IACH,OAAO,CAAC,MAAM,CAAC,oBAAoB;IASnC;;;;OAIG;IACI,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAQhC;;;;;;OAMG;IACI,KAAK,IAAI,IAAI;CAOrB"}

View File

@ -0,0 +1,63 @@
"use strict";
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root for license information.
Object.defineProperty(exports, "__esModule", { value: true });
exports.FileWriter = void 0;
const Import_1 = require("./Import");
const fsx = Import_1.Import.lazy('fs-extra', require);
/**
* API for interacting with file handles.
* @public
*/
class FileWriter {
constructor(fileDescriptor, filePath) {
this._fileDescriptor = fileDescriptor;
this.filePath = filePath;
}
/**
* Opens a new file handle to the file at the specified path and given mode.
* Behind the scenes it uses `fs.openSync()`.
* The behaviour of this function is platform specific.
* See: https://nodejs.org/docs/latest-v8.x/api/fs.html#fs_fs_open_path_flags_mode_callback
* @param filePath - The absolute or relative path to the file handle that should be opened.
* @param flags - The flags for opening the handle
*/
static open(filePath, flags) {
return new FileWriter(fsx.openSync(filePath, FileWriter._convertFlagsForNode(flags)), filePath);
}
/**
* Helper function to convert the file writer array to a Node.js style string (e.g. "wx" or "a").
* @param flags - The flags that should be converted.
*/
static _convertFlagsForNode(flags) {
flags = Object.assign({ append: false, exclusive: false }, flags);
return [flags.append ? 'a' : 'w', flags.exclusive ? 'x' : ''].join('');
}
/**
* Writes some text to the given file handle. Throws if the file handle has been closed.
* Behind the scenes it uses `fs.writeSync()`.
* @param text - The text to write to the file.
*/
write(text) {
if (!this._fileDescriptor) {
throw new Error(`Cannot write to file, file descriptor has already been released.`);
}
fsx.writeSync(this._fileDescriptor, text);
}
/**
* Closes the file handle permanently. No operations can be made on this file handle after calling this.
* Behind the scenes it uses `fs.closeSync()` and releases the file descriptor to be re-used.
*
* @remarks
* The `close()` method can be called more than once; additional calls are ignored.
*/
close() {
const fd = this._fileDescriptor;
if (fd) {
this._fileDescriptor = undefined;
fsx.closeSync(fd);
}
}
}
exports.FileWriter = FileWriter;
//# sourceMappingURL=FileWriter.js.map

View File

@ -0,0 +1 @@
{"version":3,"file":"FileWriter.js","sourceRoot":"","sources":["../src/FileWriter.ts"],"names":[],"mappings":";AAAA,4FAA4F;AAC5F,2DAA2D;;;AAE3D,qCAAkC;AAElC,MAAM,GAAG,GAA8B,eAAM,CAAC,IAAI,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;AA8BxE;;;GAGG;AACH,MAAa,UAAU;IAQrB,YAAoB,cAAsB,EAAE,QAAgB;QAC1D,IAAI,CAAC,eAAe,GAAG,cAAc,CAAC;QACtC,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;IAC3B,CAAC;IAED;;;;;;;OAOG;IACI,MAAM,CAAC,IAAI,CAAC,QAAgB,EAAE,KAAwB;QAC3D,OAAO,IAAI,UAAU,CAAC,GAAG,CAAC,QAAQ,CAAC,QAAQ,EAAE,UAAU,CAAC,oBAAoB,CAAC,KAAK,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC;IAClG,CAAC;IAED;;;OAGG;IACK,MAAM,CAAC,oBAAoB,CAAC,KAAmC;QACrE,KAAK,mBACH,MAAM,EAAE,KAAK,EACb,SAAS,EAAE,KAAK,IACb,KAAK,CACT,CAAC;QACF,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,EAAE,CAAkB,CAAC;IAC1F,CAAC;IAED;;;;OAIG;IACI,KAAK,CAAC,IAAY;QACvB,IAAI,CAAC,IAAI,CAAC,eAAe,EAAE;YACzB,MAAM,IAAI,KAAK,CAAC,kEAAkE,CAAC,CAAC;SACrF;QAED,GAAG,CAAC,SAAS,CAAC,IAAI,CAAC,eAAe,EAAE,IAAI,CAAC,CAAC;IAC5C,CAAC;IAED;;;;;;OAMG;IACI,KAAK;QACV,MAAM,EAAE,GAAuB,IAAI,CAAC,eAAe,CAAC;QACpD,IAAI,EAAE,EAAE;YACN,IAAI,CAAC,eAAe,GAAG,SAAS,CAAC;YACjC,GAAG,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC;SACnB;IACH,CAAC;CACF;AAjED,gCAiEC","sourcesContent":["// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.\n// See LICENSE in the project root for license information.\n\nimport { Import } from './Import';\n\nconst fsx: typeof import('fs-extra') = Import.lazy('fs-extra', require);\n\n/**\n * Available file handle opening flags.\n * @public\n */\ntype NodeFileFlags = 'r' | 'r+' | 'rs+' | 'w' | 'wx' | 'w+' | 'wx+' | 'a' | 'ax' | 'a+' | 'ax+';\n\n/**\n * Interface which represents the flags about which mode the file should be opened in.\n * @public\n */\nexport interface IFileWriterFlags {\n /**\n * Open file for appending.\n */\n append?: boolean;\n\n /**\n * Fails if path exists. The exclusive flag ensures that path is newly created.\n *\n * @remarks\n * On POSIX-like operating systems, path is considered to exist even if it is a symlink to a\n * non-existent file. The exclusive flag may or may not work with network file systems.\n *\n * POSIX is a registered trademark of the Institute of Electrical and Electronic Engineers, Inc.\n */\n exclusive?: boolean;\n}\n\n/**\n * API for interacting with file handles.\n * @public\n */\nexport class FileWriter {\n /**\n * The `filePath` that was passed to {@link FileWriter.open}.\n */\n public readonly filePath: string;\n\n private _fileDescriptor: number | undefined;\n\n private constructor(fileDescriptor: number, filePath: string) {\n this._fileDescriptor = fileDescriptor;\n this.filePath = filePath;\n }\n\n /**\n * Opens a new file handle to the file at the specified path and given mode.\n * Behind the scenes it uses `fs.openSync()`.\n * The behaviour of this function is platform specific.\n * See: https://nodejs.org/docs/latest-v8.x/api/fs.html#fs_fs_open_path_flags_mode_callback\n * @param filePath - The absolute or relative path to the file handle that should be opened.\n * @param flags - The flags for opening the handle\n */\n public static open(filePath: string, flags?: IFileWriterFlags): FileWriter {\n return new FileWriter(fsx.openSync(filePath, FileWriter._convertFlagsForNode(flags)), filePath);\n }\n\n /**\n * Helper function to convert the file writer array to a Node.js style string (e.g. \"wx\" or \"a\").\n * @param flags - The flags that should be converted.\n */\n private static _convertFlagsForNode(flags: IFileWriterFlags | undefined): NodeFileFlags {\n flags = {\n append: false,\n exclusive: false,\n ...flags\n };\n return [flags.append ? 'a' : 'w', flags.exclusive ? 'x' : ''].join('') as NodeFileFlags;\n }\n\n /**\n * Writes some text to the given file handle. Throws if the file handle has been closed.\n * Behind the scenes it uses `fs.writeSync()`.\n * @param text - The text to write to the file.\n */\n public write(text: string): void {\n if (!this._fileDescriptor) {\n throw new Error(`Cannot write to file, file descriptor has already been released.`);\n }\n\n fsx.writeSync(this._fileDescriptor, text);\n }\n\n /**\n * Closes the file handle permanently. No operations can be made on this file handle after calling this.\n * Behind the scenes it uses `fs.closeSync()` and releases the file descriptor to be re-used.\n *\n * @remarks\n * The `close()` method can be called more than once; additional calls are ignored.\n */\n public close(): void {\n const fd: number | undefined = this._fileDescriptor;\n if (fd) {\n this._fileDescriptor = undefined;\n fsx.closeSync(fd);\n }\n }\n}\n"]}

View File

@ -0,0 +1,179 @@
/**
* This interface is part of the {@link IPackageJson} file format. It is used for the
* "dependencies", "optionalDependencies", and "devDependencies" fields.
* @public
*/
export interface IPackageJsonDependencyTable {
/**
* The key is the name of a dependency. The value is a Semantic Versioning (SemVer)
* range specifier.
*/
[dependencyName: string]: string;
}
/**
* This interface is part of the {@link IPackageJson} file format. It is used for the
* "scripts" field.
* @public
*/
export interface IPackageJsonScriptTable {
/**
* The key is the name of the script hook. The value is the script body which may
* be a file path or shell script command.
*/
[scriptName: string]: string;
}
/**
* This interface is part of the {@link IPackageJson} file format. It is used for the
* "repository" field.
* @public
*/
export interface IPackageJsonRepository {
/**
* The source control type for the repository that hosts the project. This is typically "git".
*/
type: string;
/**
* The URL of the repository that hosts the project.
*/
url: string;
/**
* If the project does not exist at the root of the repository, its path is specified here.
*/
directory?: string;
}
/**
* This interface is part of the {@link IPackageJson} file format. It is used for the
* "peerDependenciesMeta" field.
* @public
*/
export interface IPeerDependenciesMetaTable {
[dependencyName: string]: {
optional?: boolean;
};
}
/**
* An interface for accessing common fields from a package.json file whose version field may be missing.
*
* @remarks
* This interface is the same as {@link IPackageJson}, except that the `version` field is optional.
* According to the {@link https://docs.npmjs.com/files/package.json | NPM documentation}
* and {@link http://wiki.commonjs.org/wiki/Packages/1.0 | CommonJS Packages specification}, the `version` field
* is normally a required field for package.json files.
*
* However, NodeJS relaxes this requirement for its `require()` API. The
* {@link https://nodejs.org/dist/latest-v10.x/docs/api/modules.html#modules_folders_as_modules
* | "Folders as Modules" section} from the NodeJS documentation gives an example of a package.json file
* that has only the `name` and `main` fields. NodeJS does not consider the `version` field during resolution,
* so it can be omitted. Some libraries do this.
*
* Use the `INodePackageJson` interface when loading such files. Use `IPackageJson` for package.json files
* that are installed from an NPM registry, or are otherwise known to have a `version` field.
*
* @public
*/
export interface INodePackageJson {
/**
* The name of the package.
*/
name: string;
/**
* A version number conforming to the Semantic Versioning (SemVer) standard.
*/
version?: string;
/**
* Indicates whether this package is allowed to be published or not.
*/
private?: boolean;
/**
* A brief description of the package.
*/
description?: string;
/**
* The URL of the project's repository.
*/
repository?: string | IPackageJsonRepository;
/**
* The URL to the project's web page.
*/
homepage?: string;
/**
* The name of the license.
*/
license?: string;
/**
* The path to the module file that will act as the main entry point.
*/
main?: string;
/**
* The path to the TypeScript *.d.ts file describing the module file
* that will act as the main entry point.
*/
types?: string;
/**
* Alias for `types`
*/
typings?: string;
/**
* The path to the TSDoc metadata file.
* This is still being standardized: https://github.com/microsoft/tsdoc/issues/7#issuecomment-442271815
* @beta
*/
tsdocMetadata?: string;
/**
* The main entry point for the package.
*/
bin?: string;
/**
* An array of dependencies that must always be installed for this package.
*/
dependencies?: IPackageJsonDependencyTable;
/**
* An array of optional dependencies that may be installed for this package.
*/
optionalDependencies?: IPackageJsonDependencyTable;
/**
* An array of dependencies that must only be installed for developers who will
* build this package.
*/
devDependencies?: IPackageJsonDependencyTable;
/**
* An array of dependencies that must be installed by a consumer of this package,
* but which will not be automatically installed by this package.
*/
peerDependencies?: IPackageJsonDependencyTable;
/**
* An array of metadata about peer dependencies.
*/
peerDependenciesMeta?: IPeerDependenciesMetaTable;
/**
* A table of script hooks that a package manager or build tool may invoke.
*/
scripts?: IPackageJsonScriptTable;
/**
* A table of package version resolutions. This feature is only implemented by the Yarn package manager.
*
* @remarks
* See the {@link https://github.com/yarnpkg/rfcs/blob/master/implemented/0000-selective-versions-resolutions.md
* | 0000-selective-versions-resolutions.md RFC} for details.
*/
resolutions?: Record<string, string>;
}
/**
* An interface for accessing common fields from a package.json file.
*
* @remarks
* This interface describes a package.json file format whose `name` and `version` field are required.
* In some situations, the `version` field is optional; in that case, use the {@link INodePackageJson}
* interface instead.
*
* More fields may be added to this interface in the future. For documentation about the package.json file format,
* see the {@link http://wiki.commonjs.org/wiki/Packages/1.0 | CommonJS Packages specification}
* and the {@link https://docs.npmjs.com/files/package.json | NPM manual page}.
*
* @public
*/
export interface IPackageJson extends INodePackageJson {
/** {@inheritDoc INodePackageJson.version} */
version: string;
}
//# sourceMappingURL=IPackageJson.d.ts.map

View File

@ -0,0 +1 @@
{"version":3,"file":"IPackageJson.d.ts","sourceRoot":"","sources":["../src/IPackageJson.ts"],"names":[],"mappings":"AAGA;;;;GAIG;AACH,MAAM,WAAW,2BAA2B;IAC1C;;;OAGG;IACH,CAAC,cAAc,EAAE,MAAM,GAAG,MAAM,CAAC;CAClC;AAED;;;;GAIG;AACH,MAAM,WAAW,uBAAuB;IACtC;;;OAGG;IACH,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAAC;CAC9B;AAED;;;;GAIG;AACH,MAAM,WAAW,sBAAsB;IACrC;;OAEG;IACH,IAAI,EAAE,MAAM,CAAC;IAEb;;OAEG;IACH,GAAG,EAAE,MAAM,CAAC;IAEZ;;OAEG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;;GAIG;AACH,MAAM,WAAW,0BAA0B;IACzC,CAAC,cAAc,EAAE,MAAM,GAAG;QACxB,QAAQ,CAAC,EAAE,OAAO,CAAC;KACpB,CAAC;CACH;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,WAAW,gBAAgB;IAC/B;;OAEG;IACH,IAAI,EAAE,MAAM,CAAC;IAEb;;OAEG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB;;OAEG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;IAElB;;OAEG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB;;OAEG;IACH,UAAU,CAAC,EAAE,MAAM,GAAG,sBAAsB,CAAC;IAE7C;;OAEG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB;;OAEG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB;;OAEG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IAEd;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IAEf;;OAEG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB;;;;OAIG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IAEvB;;OAEG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC;IAEb;;OAEG;IACH,YAAY,CAAC,EAAE,2BAA2B,CAAC;IAE3C;;OAEG;IACH,oBAAoB,CAAC,EAAE,2BAA2B,CAAC;IAEnD;;;OAGG;IACH,eAAe,CAAC,EAAE,2BAA2B,CAAC;IAE9C;;;OAGG;IACH,gBAAgB,CAAC,EAAE,2BAA2B,CAAC;IAE/C;;OAEG;IACH,oBAAoB,CAAC,EAAE,0BAA0B,CAAC;IAElD;;OAEG;IACH,OAAO,CAAC,EAAE,uBAAuB,CAAC;IAElC;;;;;;OAMG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACtC;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,WAAW,YAAa,SAAQ,gBAAgB;IAEpD,6CAA6C;IAC7C,OAAO,EAAE,MAAM,CAAC;CACjB"}

View File

@ -0,0 +1,5 @@
"use strict";
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root for license information.
Object.defineProperty(exports, "__esModule", { value: true });
//# sourceMappingURL=IPackageJson.js.map

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,247 @@
/**
* Common options shared by {@link IImportResolveModuleOptions} and {@link IImportResolvePackageOptions}
* @public
*/
export interface IImportResolveOptions {
/**
* The path from which {@link IImportResolveModuleOptions.modulePath} or
* {@link IImportResolvePackageOptions.packageName} should be resolved.
*/
baseFolderPath: string;
/**
* If true, if the package name matches a Node.js system module, then the return
* value will be the package name without any path.
*
* @remarks
* This will take precedence over an installed NPM package of the same name.
*
* Example:
* ```ts
* // Returns the string "fs" indicating the Node.js system module
* Import.resolveModulePath({
* resolvePath: "fs",
* basePath: process.cwd()
* })
* ```
*/
includeSystemModules?: boolean;
/**
* If true, then resolvePath is allowed to refer to the package.json of the active project.
*
* @remarks
* This will take precedence over any installed dependency with the same name.
* Note that this requires an additional PackageJsonLookup calculation.
*
* Example:
* ```ts
* // Returns an absolute path to the current package
* Import.resolveModulePath({
* resolvePath: "current-project",
* basePath: process.cwd(),
* allowSelfReference: true
* })
* ```
*/
allowSelfReference?: boolean;
/**
* A function used to resolve the realpath of a provided file path.
*
* @remarks
* This is used to resolve symlinks and other non-standard file paths. By default, this uses the
* {@link FileSystem.getRealPath} function. However, it can be overridden to use a custom implementation
* which may be faster, more accurate, or provide support for additional non-standard file paths.
*/
getRealPath?: (filePath: string) => string;
}
/**
* Common options shared by {@link IImportResolveModuleAsyncOptions} and {@link IImportResolvePackageAsyncOptions}
* @public
*/
export interface IImportResolveAsyncOptions extends IImportResolveOptions {
/**
* A function used to resolve the realpath of a provided file path.
*
* @remarks
* This is used to resolve symlinks and other non-standard file paths. By default, this uses the
* {@link FileSystem.getRealPath} function. However, it can be overridden to use a custom implementation
* which may be faster, more accurate, or provide support for additional non-standard file paths.
*/
getRealPathAsync?: (filePath: string) => Promise<string>;
}
/**
* Options for {@link Import.resolveModule}
* @public
*/
export interface IImportResolveModuleOptions extends IImportResolveOptions {
/**
* The module identifier to resolve. For example "\@rushstack/node-core-library" or
* "\@rushstack/node-core-library/lib/index.js"
*/
modulePath: string;
}
/**
* Options for {@link Import.resolveModuleAsync}
* @public
*/
export interface IImportResolveModuleAsyncOptions extends IImportResolveAsyncOptions {
/**
* The module identifier to resolve. For example "\@rushstack/node-core-library" or
* "\@rushstack/node-core-library/lib/index.js"
*/
modulePath: string;
}
/**
* Options for {@link Import.resolvePackage}
* @public
*/
export interface IImportResolvePackageOptions extends IImportResolveOptions {
/**
* The package name to resolve. For example "\@rushstack/node-core-library"
*/
packageName: string;
}
/**
* Options for {@link Import.resolvePackageAsync}
* @public
*/
export interface IImportResolvePackageAsyncOptions extends IImportResolveAsyncOptions {
/**
* The package name to resolve. For example "\@rushstack/node-core-library"
*/
packageName: string;
}
/**
* Helpers for resolving and importing Node.js modules.
* @public
*/
export declare class Import {
private static __builtInModules;
private static get _builtInModules();
/**
* Provides a way to improve process startup times by lazy-loading imported modules.
*
* @remarks
* This is a more structured wrapper for the {@link https://www.npmjs.com/package/import-lazy|import-lazy}
* package. It enables you to replace an import like this:
*
* ```ts
* import * as example from 'example'; // <-- 100ms load time
*
* if (condition) {
* example.doSomething();
* }
* ```
*
* ...with a pattern like this:
*
* ```ts
* const example: typeof import('example') = Import.lazy('example', require);
*
* if (condition) {
* example.doSomething(); // <-- 100ms load time occurs here, only if needed
* }
* ```
*
* The implementation relies on JavaScript's `Proxy` feature to intercept access to object members. Thus
* it will only work correctly with certain types of module exports. If a particular export isn't well behaved,
* you may need to find (or introduce) some other module in your dependency graph to apply the optimization to.
*
* Usage guidelines:
*
* - Always specify types using `typeof` as shown above.
*
* - Never apply lazy-loading in a way that would convert the module's type to `any`. Losing type safety
* seriously impacts the maintainability of the code base.
*
* - In cases where the non-runtime types are needed, import them separately using the `Types` suffix:
*
* ```ts
* const example: typeof import('example') = Import.lazy('example', require);
* import type * as exampleTypes from 'example';
* ```
*
* - If the imported module confusingly has the same name as its export, then use the Module suffix:
*
* ```ts
* const exampleModule: typeof import('../../logic/Example') = Import.lazy(
* '../../logic/Example', require);
* import type * as exampleTypes from '../../logic/Example';
* ```
*
* - If the exports cause a lot of awkwardness (e.g. too many expressions need to have `exampleModule.` inserted
* into them), or if some exports cannot be proxied (e.g. `Import.lazy('example', require)` returns a function
* signature), then do not lazy-load that module. Instead, apply lazy-loading to some other module which is
* better behaved.
*
* - It's recommended to sort imports in a standard ordering:
*
* ```ts
* // 1. external imports
* import * as path from 'path';
* import { Import, JsonFile, JsonObject } from '@rushstack/node-core-library';
*
* // 2. local imports
* import { LocalFile } from './path/LocalFile';
*
* // 3. lazy-imports (which are technically variables, not imports)
* const semver: typeof import('semver') = Import.lazy('semver', require);
* ```
*/
static lazy(moduleName: string, require: (id: string) => unknown): any;
/**
* This resolves a module path using similar logic as the Node.js `require.resolve()` API,
* but supporting extra features such as specifying the base folder.
*
* @remarks
* A module path is a text string that might appear in a statement such as
* `import { X } from "____";` or `const x = require("___");`. The implementation is based
* on the popular `resolve` NPM package.
*
* Suppose `example` is an NPM package whose entry point is `lib/index.js`:
* ```ts
* // Returns "/path/to/project/node_modules/example/lib/index.js"
* Import.resolveModule({ modulePath: 'example' });
*
* // Returns "/path/to/project/node_modules/example/lib/other.js"
* Import.resolveModule({ modulePath: 'example/lib/other' });
* ```
* If you need to determine the containing package folder
* (`/path/to/project/node_modules/example`), use {@link Import.resolvePackage} instead.
*
* @returns the absolute path of the resolved module.
* If {@link IImportResolveOptions.includeSystemModules} is specified
* and a system module is found, then its name is returned without any file path.
*/
static resolveModule(options: IImportResolveModuleOptions): string;
/**
* Async version of {@link Import.resolveModule}.
*/
static resolveModuleAsync(options: IImportResolveModuleAsyncOptions): Promise<string>;
/**
* Performs module resolution to determine the folder where a package is installed.
*
* @remarks
* Suppose `example` is an NPM package whose entry point is `lib/index.js`:
* ```ts
* // Returns "/path/to/project/node_modules/example"
* Import.resolvePackage({ packageName: 'example' });
* ```
*
* If you need to resolve a module path, use {@link Import.resolveModule} instead:
* ```ts
* // Returns "/path/to/project/node_modules/example/lib/index.js"
* Import.resolveModule({ modulePath: 'example' });
* ```
*
* @returns the absolute path of the package folder.
* If {@link IImportResolveOptions.includeSystemModules} is specified
* and a system module is found, then its name is returned without any file path.
*/
static resolvePackage(options: IImportResolvePackageOptions): string;
/**
* Async version of {@link Import.resolvePackage}.
*/
static resolvePackageAsync(options: IImportResolvePackageAsyncOptions): Promise<string>;
private static _getPackageName;
}
//# sourceMappingURL=Import.d.ts.map

View File

@ -0,0 +1 @@
{"version":3,"file":"Import.d.ts","sourceRoot":"","sources":["../src/Import.ts"],"names":[],"mappings":"AAeA;;;GAGG;AACH,MAAM,WAAW,qBAAqB;IACpC;;;OAGG;IACH,cAAc,EAAE,MAAM,CAAC;IAEvB;;;;;;;;;;;;;;;OAeG;IACH,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAE/B;;;;;;;;;;;;;;;;OAgBG;IACH,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAE7B;;;;;;;OAOG;IACH,WAAW,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,MAAM,CAAC;CAC5C;AAED;;;GAGG;AACH,MAAM,WAAW,0BAA2B,SAAQ,qBAAqB;IACvE;;;;;;;OAOG;IACH,gBAAgB,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;CAC1D;AAED;;;GAGG;AACH,MAAM,WAAW,2BAA4B,SAAQ,qBAAqB;IACxE;;;OAGG;IACH,UAAU,EAAE,MAAM,CAAC;CACpB;AAED;;;GAGG;AACH,MAAM,WAAW,gCAAiC,SAAQ,0BAA0B;IAClF;;;OAGG;IACH,UAAU,EAAE,MAAM,CAAC;CACpB;AAED;;;GAGG;AACH,MAAM,WAAW,4BAA6B,SAAQ,qBAAqB;IACzE;;OAEG;IACH,WAAW,EAAE,MAAM,CAAC;CACrB;AAED;;;GAGG;AACH,MAAM,WAAW,iCAAkC,SAAQ,0BAA0B;IACnF;;OAEG;IACH,WAAW,EAAE,MAAM,CAAC;CACrB;AAOD;;;GAGG;AACH,qBAAa,MAAM;IACjB,OAAO,CAAC,MAAM,CAAC,gBAAgB,CAA0B;IACzD,OAAO,CAAC,MAAM,KAAK,eAAe,GAMjC;IAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAqEG;WAEW,IAAI,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,GAAG,GAAG;IAK7E;;;;;;;;;;;;;;;;;;;;;;;OAuBG;WACW,aAAa,CAAC,OAAO,EAAE,2BAA2B,GAAG,MAAM;IA4CzE;;OAEG;WACiB,kBAAkB,CAAC,OAAO,EAAE,gCAAgC,GAAG,OAAO,CAAC,MAAM,CAAC;IAwFlG;;;;;;;;;;;;;;;;;;;OAmBG;WACW,cAAc,CAAC,OAAO,EAAE,4BAA4B,GAAG,MAAM;IAyC3E;;OAEG;WACiB,mBAAmB,CAAC,OAAO,EAAE,iCAAiC,GAAG,OAAO,CAAC,MAAM,CAAC;IAyFpG,OAAO,CAAC,MAAM,CAAC,eAAe;CAa/B"}

390
node_modules/@rushstack/node-core-library/lib/Import.js generated vendored Normal file
View File

@ -0,0 +1,390 @@
"use strict";
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root 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.Import = void 0;
const path = __importStar(require("path"));
const importLazy = require("import-lazy");
const Resolve = __importStar(require("resolve"));
const nodeModule = require("module");
const PackageJsonLookup_1 = require("./PackageJsonLookup");
const FileSystem_1 = require("./FileSystem");
const PackageName_1 = require("./PackageName");
/**
* Helpers for resolving and importing Node.js modules.
* @public
*/
class Import {
static get _builtInModules() {
if (!Import.__builtInModules) {
Import.__builtInModules = new Set(nodeModule.builtinModules);
}
return Import.__builtInModules;
}
/**
* Provides a way to improve process startup times by lazy-loading imported modules.
*
* @remarks
* This is a more structured wrapper for the {@link https://www.npmjs.com/package/import-lazy|import-lazy}
* package. It enables you to replace an import like this:
*
* ```ts
* import * as example from 'example'; // <-- 100ms load time
*
* if (condition) {
* example.doSomething();
* }
* ```
*
* ...with a pattern like this:
*
* ```ts
* const example: typeof import('example') = Import.lazy('example', require);
*
* if (condition) {
* example.doSomething(); // <-- 100ms load time occurs here, only if needed
* }
* ```
*
* The implementation relies on JavaScript's `Proxy` feature to intercept access to object members. Thus
* it will only work correctly with certain types of module exports. If a particular export isn't well behaved,
* you may need to find (or introduce) some other module in your dependency graph to apply the optimization to.
*
* Usage guidelines:
*
* - Always specify types using `typeof` as shown above.
*
* - Never apply lazy-loading in a way that would convert the module's type to `any`. Losing type safety
* seriously impacts the maintainability of the code base.
*
* - In cases where the non-runtime types are needed, import them separately using the `Types` suffix:
*
* ```ts
* const example: typeof import('example') = Import.lazy('example', require);
* import type * as exampleTypes from 'example';
* ```
*
* - If the imported module confusingly has the same name as its export, then use the Module suffix:
*
* ```ts
* const exampleModule: typeof import('../../logic/Example') = Import.lazy(
* '../../logic/Example', require);
* import type * as exampleTypes from '../../logic/Example';
* ```
*
* - If the exports cause a lot of awkwardness (e.g. too many expressions need to have `exampleModule.` inserted
* into them), or if some exports cannot be proxied (e.g. `Import.lazy('example', require)` returns a function
* signature), then do not lazy-load that module. Instead, apply lazy-loading to some other module which is
* better behaved.
*
* - It's recommended to sort imports in a standard ordering:
*
* ```ts
* // 1. external imports
* import * as path from 'path';
* import { Import, JsonFile, JsonObject } from '@rushstack/node-core-library';
*
* // 2. local imports
* import { LocalFile } from './path/LocalFile';
*
* // 3. lazy-imports (which are technically variables, not imports)
* const semver: typeof import('semver') = Import.lazy('semver', require);
* ```
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
static lazy(moduleName, require) {
const importLazyLocal = importLazy(require);
return importLazyLocal(moduleName);
}
/**
* This resolves a module path using similar logic as the Node.js `require.resolve()` API,
* but supporting extra features such as specifying the base folder.
*
* @remarks
* A module path is a text string that might appear in a statement such as
* `import { X } from "____";` or `const x = require("___");`. The implementation is based
* on the popular `resolve` NPM package.
*
* Suppose `example` is an NPM package whose entry point is `lib/index.js`:
* ```ts
* // Returns "/path/to/project/node_modules/example/lib/index.js"
* Import.resolveModule({ modulePath: 'example' });
*
* // Returns "/path/to/project/node_modules/example/lib/other.js"
* Import.resolveModule({ modulePath: 'example/lib/other' });
* ```
* If you need to determine the containing package folder
* (`/path/to/project/node_modules/example`), use {@link Import.resolvePackage} instead.
*
* @returns the absolute path of the resolved module.
* If {@link IImportResolveOptions.includeSystemModules} is specified
* and a system module is found, then its name is returned without any file path.
*/
static resolveModule(options) {
const { modulePath, baseFolderPath, includeSystemModules, allowSelfReference, getRealPath } = options;
if (path.isAbsolute(modulePath)) {
return modulePath;
}
const normalizedRootPath = (getRealPath || FileSystem_1.FileSystem.getRealPath)(baseFolderPath);
if (modulePath.startsWith('.')) {
// This looks like a conventional relative path
return path.resolve(normalizedRootPath, modulePath);
}
// Built-in modules do not have a scope, so if there is a slash, then we need to check
// against the first path segment
const slashIndex = modulePath.indexOf('/');
const moduleName = slashIndex === -1 ? modulePath : modulePath.slice(0, slashIndex);
if (!includeSystemModules && Import._builtInModules.has(moduleName)) {
throw new Error(`Cannot find module "${modulePath}" from "${options.baseFolderPath}".`);
}
if (allowSelfReference === true) {
const ownPackage = Import._getPackageName(normalizedRootPath);
if (ownPackage &&
(modulePath === ownPackage.packageName || modulePath.startsWith(`${ownPackage.packageName}/`))) {
const packagePath = modulePath.slice(ownPackage.packageName.length + 1);
return path.resolve(ownPackage.packageRootPath, packagePath);
}
}
try {
return Resolve.sync(modulePath, {
basedir: normalizedRootPath,
preserveSymlinks: false,
realpathSync: getRealPath
});
}
catch (e) {
throw new Error(`Cannot find module "${modulePath}" from "${options.baseFolderPath}": ${e}`);
}
}
/**
* Async version of {@link Import.resolveModule}.
*/
static async resolveModuleAsync(options) {
const { modulePath, baseFolderPath, includeSystemModules, allowSelfReference, getRealPath, getRealPathAsync } = options;
if (path.isAbsolute(modulePath)) {
return modulePath;
}
const normalizedRootPath = await (getRealPathAsync || getRealPath || FileSystem_1.FileSystem.getRealPathAsync)(baseFolderPath);
if (modulePath.startsWith('.')) {
// This looks like a conventional relative path
return path.resolve(normalizedRootPath, modulePath);
}
// Built-in modules do not have a scope, so if there is a slash, then we need to check
// against the first path segment
const slashIndex = modulePath.indexOf('/');
const moduleName = slashIndex === -1 ? modulePath : modulePath.slice(0, slashIndex);
if (!includeSystemModules && Import._builtInModules.has(moduleName)) {
throw new Error(`Cannot find module "${modulePath}" from "${options.baseFolderPath}".`);
}
if (allowSelfReference === true) {
const ownPackage = Import._getPackageName(normalizedRootPath);
if (ownPackage &&
(modulePath === ownPackage.packageName || modulePath.startsWith(`${ownPackage.packageName}/`))) {
const packagePath = modulePath.slice(ownPackage.packageName.length + 1);
return path.resolve(ownPackage.packageRootPath, packagePath);
}
}
try {
const resolvePromise = new Promise((resolve, reject) => {
const realPathFn = getRealPathAsync || getRealPath
? (filePath, callback) => {
if (getRealPathAsync) {
getRealPathAsync(filePath)
.then((resolvedPath) => callback(null, resolvedPath))
.catch((error) => callback(error));
}
else {
try {
const resolvedPath = getRealPath(filePath);
callback(null, resolvedPath);
}
catch (error) {
callback(error);
}
}
}
: undefined;
Resolve.default(modulePath, {
basedir: normalizedRootPath,
preserveSymlinks: false,
realpath: realPathFn
}, (error, resolvedPath) => {
if (error) {
reject(error);
}
else {
// Resolve docs state that either an error will be returned, or the resolved path.
// In this case, the resolved path should always be populated.
resolve(resolvedPath);
}
});
});
return await resolvePromise;
}
catch (e) {
throw new Error(`Cannot find module "${modulePath}" from "${options.baseFolderPath}": ${e}`);
}
}
/**
* Performs module resolution to determine the folder where a package is installed.
*
* @remarks
* Suppose `example` is an NPM package whose entry point is `lib/index.js`:
* ```ts
* // Returns "/path/to/project/node_modules/example"
* Import.resolvePackage({ packageName: 'example' });
* ```
*
* If you need to resolve a module path, use {@link Import.resolveModule} instead:
* ```ts
* // Returns "/path/to/project/node_modules/example/lib/index.js"
* Import.resolveModule({ modulePath: 'example' });
* ```
*
* @returns the absolute path of the package folder.
* If {@link IImportResolveOptions.includeSystemModules} is specified
* and a system module is found, then its name is returned without any file path.
*/
static resolvePackage(options) {
const { packageName, includeSystemModules, baseFolderPath, allowSelfReference, getRealPath } = options;
if (includeSystemModules && Import._builtInModules.has(packageName)) {
return packageName;
}
const normalizedRootPath = (getRealPath || FileSystem_1.FileSystem.getRealPath)(baseFolderPath);
if (allowSelfReference) {
const ownPackage = Import._getPackageName(normalizedRootPath);
if (ownPackage && ownPackage.packageName === packageName) {
return ownPackage.packageRootPath;
}
}
PackageName_1.PackageName.parse(packageName); // Ensure the package name is valid and doesn't contain a path
try {
// Append a slash to the package name to ensure `resolve.sync` doesn't attempt to return a system package
const resolvedPath = Resolve.sync(`${packageName}/`, {
basedir: normalizedRootPath,
preserveSymlinks: false,
packageFilter: (pkg, pkgFile, dir) => {
// Hardwire "main" to point to a file that is guaranteed to exist.
// This helps resolve packages such as @types/node that have no entry point.
// And then we can use path.dirname() below to locate the package folder,
// even if the real entry point was in an subfolder with arbitrary nesting.
pkg.main = 'package.json';
return pkg;
},
realpathSync: getRealPath
});
const packagePath = path.dirname(resolvedPath);
return packagePath;
}
catch (e) {
throw new Error(`Cannot find package "${packageName}" from "${baseFolderPath}": ${e}.`);
}
}
/**
* Async version of {@link Import.resolvePackage}.
*/
static async resolvePackageAsync(options) {
const { packageName, includeSystemModules, baseFolderPath, allowSelfReference, getRealPath, getRealPathAsync } = options;
if (includeSystemModules && Import._builtInModules.has(packageName)) {
return packageName;
}
const normalizedRootPath = await (getRealPathAsync || getRealPath || FileSystem_1.FileSystem.getRealPathAsync)(baseFolderPath);
if (allowSelfReference) {
const ownPackage = Import._getPackageName(normalizedRootPath);
if (ownPackage && ownPackage.packageName === packageName) {
return ownPackage.packageRootPath;
}
}
PackageName_1.PackageName.parse(packageName); // Ensure the package name is valid and doesn't contain a path
try {
const resolvePromise = new Promise((resolve, reject) => {
const realPathFn = getRealPathAsync || getRealPath
? (filePath, callback) => {
if (getRealPathAsync) {
getRealPathAsync(filePath)
.then((resolvedPath) => callback(null, resolvedPath))
.catch((error) => callback(error));
}
else {
try {
const resolvedPath = getRealPath(filePath);
callback(null, resolvedPath);
}
catch (error) {
callback(error);
}
}
}
: undefined;
Resolve.default(
// Append a slash to the package name to ensure `resolve` doesn't attempt to return a system package
`${packageName}/`, {
basedir: normalizedRootPath,
preserveSymlinks: false,
packageFilter: (pkg, pkgFile, dir) => {
// Hardwire "main" to point to a file that is guaranteed to exist.
// This helps resolve packages such as @types/node that have no entry point.
// And then we can use path.dirname() below to locate the package folder,
// even if the real entry point was in an subfolder with arbitrary nesting.
pkg.main = 'package.json';
return pkg;
},
realpath: realPathFn
}, (error, resolvedPath) => {
if (error) {
reject(error);
}
else {
// Resolve docs state that either an error will be returned, or the resolved path.
// In this case, the resolved path should always be populated.
resolve(resolvedPath);
}
});
});
const resolvedPath = await resolvePromise;
const packagePath = path.dirname(resolvedPath);
return packagePath;
}
catch (e) {
throw new Error(`Cannot find package "${packageName}" from "${baseFolderPath}": ${e}`);
}
}
static _getPackageName(rootPath) {
const packageJsonPath = PackageJsonLookup_1.PackageJsonLookup.instance.tryGetPackageJsonFilePathFor(rootPath);
if (packageJsonPath) {
const packageJson = PackageJsonLookup_1.PackageJsonLookup.instance.loadPackageJson(packageJsonPath);
return {
packageRootPath: path.dirname(packageJsonPath),
packageName: packageJson.name
};
}
else {
return undefined;
}
}
}
exports.Import = Import;
//# sourceMappingURL=Import.js.map

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,36 @@
/**
* An `Error` subclass that should be thrown to report an unexpected state that may indicate a software defect.
* An application may handle this error by instructing the end user to report an issue to the application maintainers.
*
* @remarks
* Do not use this class unless you intend to solicit bug reports from end users.
*
* @public
*/
export declare class InternalError extends Error {
/**
* If true, a JavScript `debugger;` statement will be invoked whenever the `InternalError` constructor is called.
*
* @remarks
* Generally applications should not be catching and ignoring an `InternalError`. Instead, the error should
* be reported and typically the application will terminate. Thus, if `InternalError` is constructed, it's
* almost always something we want to examine in a debugger.
*/
static breakInDebugger: boolean;
/**
* The underlying error message, without the additional boilerplate for an `InternalError`.
*/
readonly unformattedMessage: string;
/**
* Constructs a new instance of the {@link InternalError} class.
*
* @param message - A message describing the error. This will be assigned to
* {@link InternalError.unformattedMessage}. The `Error.message` field will have additional boilerplate
* explaining that the user has encountered a software defect.
*/
constructor(message: string);
private static _formatMessage;
/** @override */
toString(): string;
}
//# sourceMappingURL=InternalError.d.ts.map

View File

@ -0,0 +1 @@
{"version":3,"file":"InternalError.d.ts","sourceRoot":"","sources":["../src/InternalError.ts"],"names":[],"mappings":"AAGA;;;;;;;;GAQG;AACH,qBAAa,aAAc,SAAQ,KAAK;IACtC;;;;;;;OAOG;IACH,OAAc,eAAe,EAAE,OAAO,CAAQ;IAE9C;;OAEG;IACH,SAAgB,kBAAkB,EAAE,MAAM,CAAC;IAE3C;;;;;;OAMG;gBACgB,OAAO,EAAE,MAAM;IAiBlC,OAAO,CAAC,MAAM,CAAC,cAAc;IAO7B,gBAAgB;IACT,QAAQ,IAAI,MAAM;CAG1B"}

View File

@ -0,0 +1,55 @@
"use strict";
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root for license information.
Object.defineProperty(exports, "__esModule", { value: true });
exports.InternalError = void 0;
/**
* An `Error` subclass that should be thrown to report an unexpected state that may indicate a software defect.
* An application may handle this error by instructing the end user to report an issue to the application maintainers.
*
* @remarks
* Do not use this class unless you intend to solicit bug reports from end users.
*
* @public
*/
class InternalError extends Error {
/**
* Constructs a new instance of the {@link InternalError} class.
*
* @param message - A message describing the error. This will be assigned to
* {@link InternalError.unformattedMessage}. The `Error.message` field will have additional boilerplate
* explaining that the user has encountered a software defect.
*/
constructor(message) {
super(InternalError._formatMessage(message));
// Manually set the prototype, as we can no longer extend built-in classes like Error, Array, Map, etc.
// https://github.com/microsoft/TypeScript-wiki/blob/main/Breaking-Changes.md#extending-built-ins-like-error-array-and-map-may-no-longer-work
//
// Note: the prototype must also be set on any classes which extend this one
this.__proto__ = InternalError.prototype; // eslint-disable-line @typescript-eslint/no-explicit-any
this.unformattedMessage = message;
if (InternalError.breakInDebugger) {
// eslint-disable-next-line no-debugger
debugger;
}
}
static _formatMessage(unformattedMessage) {
return (`Internal Error: ${unformattedMessage}\n\nYou have encountered a software defect. Please consider` +
` reporting the issue to the maintainers of this application.`);
}
/** @override */
toString() {
return this.message; // Avoid adding the "Error:" prefix
}
}
/**
* If true, a JavScript `debugger;` statement will be invoked whenever the `InternalError` constructor is called.
*
* @remarks
* Generally applications should not be catching and ignoring an `InternalError`. Instead, the error should
* be reported and typically the application will terminate. Thus, if `InternalError` is constructed, it's
* almost always something we want to examine in a debugger.
*/
InternalError.breakInDebugger = true;
exports.InternalError = InternalError;
//# sourceMappingURL=InternalError.js.map

View File

@ -0,0 +1 @@
{"version":3,"file":"InternalError.js","sourceRoot":"","sources":["../src/InternalError.ts"],"names":[],"mappings":";AAAA,4FAA4F;AAC5F,2DAA2D;;;AAE3D;;;;;;;;GAQG;AACH,MAAa,aAAc,SAAQ,KAAK;IAgBtC;;;;;;OAMG;IACH,YAAmB,OAAe;QAChC,KAAK,CAAC,aAAa,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC,CAAC;QAE7C,uGAAuG;QACvG,6IAA6I;QAC7I,EAAE;QACF,4EAA4E;QAC3E,IAAY,CAAC,SAAS,GAAG,aAAa,CAAC,SAAS,CAAC,CAAC,yDAAyD;QAE5G,IAAI,CAAC,kBAAkB,GAAG,OAAO,CAAC;QAElC,IAAI,aAAa,CAAC,eAAe,EAAE;YACjC,uCAAuC;YACvC,QAAQ,CAAC;SACV;IACH,CAAC;IAEO,MAAM,CAAC,cAAc,CAAC,kBAA0B;QACtD,OAAO,CACL,mBAAmB,kBAAkB,6DAA6D;YAClG,8DAA8D,CAC/D,CAAC;IACJ,CAAC;IAED,gBAAgB;IACT,QAAQ;QACb,OAAO,IAAI,CAAC,OAAO,CAAC,CAAC,mCAAmC;IAC1D,CAAC;;AAjDD;;;;;;;GAOG;AACW,6BAAe,GAAY,IAAI,CAAC;AATnC,sCAAa","sourcesContent":["// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.\n// See LICENSE in the project root for license information.\n\n/**\n * An `Error` subclass that should be thrown to report an unexpected state that may indicate a software defect.\n * An application may handle this error by instructing the end user to report an issue to the application maintainers.\n *\n * @remarks\n * Do not use this class unless you intend to solicit bug reports from end users.\n *\n * @public\n */\nexport class InternalError extends Error {\n /**\n * If true, a JavScript `debugger;` statement will be invoked whenever the `InternalError` constructor is called.\n *\n * @remarks\n * Generally applications should not be catching and ignoring an `InternalError`. Instead, the error should\n * be reported and typically the application will terminate. Thus, if `InternalError` is constructed, it's\n * almost always something we want to examine in a debugger.\n */\n public static breakInDebugger: boolean = true;\n\n /**\n * The underlying error message, without the additional boilerplate for an `InternalError`.\n */\n public readonly unformattedMessage: string;\n\n /**\n * Constructs a new instance of the {@link InternalError} class.\n *\n * @param message - A message describing the error. This will be assigned to\n * {@link InternalError.unformattedMessage}. The `Error.message` field will have additional boilerplate\n * explaining that the user has encountered a software defect.\n */\n public constructor(message: string) {\n super(InternalError._formatMessage(message));\n\n // Manually set the prototype, as we can no longer extend built-in classes like Error, Array, Map, etc.\n // https://github.com/microsoft/TypeScript-wiki/blob/main/Breaking-Changes.md#extending-built-ins-like-error-array-and-map-may-no-longer-work\n //\n // Note: the prototype must also be set on any classes which extend this one\n (this as any).__proto__ = InternalError.prototype; // eslint-disable-line @typescript-eslint/no-explicit-any\n\n this.unformattedMessage = message;\n\n if (InternalError.breakInDebugger) {\n // eslint-disable-next-line no-debugger\n debugger;\n }\n }\n\n private static _formatMessage(unformattedMessage: string): string {\n return (\n `Internal Error: ${unformattedMessage}\\n\\nYou have encountered a software defect. Please consider` +\n ` reporting the issue to the maintainers of this application.`\n );\n }\n\n /** @override */\n public toString(): string {\n return this.message; // Avoid adding the \"Error:\" prefix\n }\n}\n"]}

View File

@ -0,0 +1,250 @@
import type { JsonSchema, IJsonSchemaErrorInfo, IJsonSchemaValidateOptions } from './JsonSchema';
import { type NewlineKind } from './Text';
/**
* Represents a JSON-serializable object whose type has not been determined yet.
*
* @remarks
*
* This type is similar to `any`, except that it communicates that the object is serializable JSON.
*
* @public
*/
export type JsonObject = any;
/**
* The Rush Stack lint rules discourage usage of `null`. However, JSON parsers always return JavaScript's
* `null` to keep the two syntaxes consistent. When creating interfaces that describe JSON structures,
* use `JsonNull` to avoid triggering the lint rule. Do not use `JsonNull` for any other purpose.
*
* @remarks
* If you are designing a new JSON file format, it's a good idea to avoid `null` entirely. In most cases
* there are better representations that convey more information about an item that is unknown, omitted, or disabled.
*
* To understand why `null` is deprecated, please see the `@rushstack/eslint-plugin` documentation here:
*
* {@link https://www.npmjs.com/package/@rushstack/eslint-plugin#rushstackno-null}
*
* @public
*/
export type JsonNull = null;
/**
* Specifies the variant of JSON syntax to be used.
*
* @public
*/
export declare enum JsonSyntax {
/**
* Specifies the exact RFC 8259 format as implemented by the `JSON.parse()` system API.
* This format was designed for machine generated inputs such as an HTTP payload.
* It is not a recommend choice for human-authored files, because it does not support
* code comments.
*
* @remarks
*
* A well-known quote from Douglas Crockford, the inventor of JSON:
*
* "I removed comments from JSON because I saw people were using them to hold parsing directives,
* a practice which would have destroyed interoperability. I know that the lack of comments makes
* some people sad, but it shouldn't. Suppose you are using JSON to keep configuration files,
* which you would like to annotate. Go ahead and insert all the comments you like.
* Then pipe it through JSMin before handing it to your JSON parser."
*
* @see {@link https://datatracker.ietf.org/doc/html/rfc8259 | RFC 8259}
*/
Strict = "strict",
/**
* `JsonSyntax.JsonWithComments` is the recommended format for human-authored config files.
* It is a minimal extension to `JsonSyntax.Strict` adding support for code comments
* using `//` and `/*`.
*
* @remarks
*
* VS Code calls this format `jsonc`, but it should not be confused with unrelated file formats
* and libraries that also use the name "JSONC".
*
* To fix VS Code syntax highlighting, add this setting:
* `"files.associations": { "*.json": "jsonc" }`
*
* To fix GitHub syntax highlighting, add this to your `.gitattributes`:
* `*.json linguist-language=JSON-with-Comments`
*/
JsonWithComments = "jsonWithComments",
/**
* JSON5 is a project that proposes a JSON-like format supplemented with ECMAScript 5.1
* notations for objects, numbers, comments, and more.
*
* @remarks
* Files using this format should use the `.json5` file extension instead of `.json`.
*
* JSON5 has substantial differences from JSON: object keys may be unquoted, trailing commas
* are allowed, and strings may span multiple lines. Whereas `JsonSyntax.JsonWithComments` can
* be cheaply converted to standard JSON by stripping comments, parsing JSON5 requires a
* nontrivial algorithm that may not be easily available in some contexts or programming languages.
*
* @see {@link https://json5.org/ | JSON5 project website}
*/
Json5 = "json5"
}
/**
* Options for {@link JsonFile.parseString}, {@link JsonFile.load}, and {@link JsonFile.loadAsync}.
*
* @public
*/
export interface IJsonFileParseOptions {
/**
* Specifies the variant of JSON syntax to be used.
*
* @defaultValue
* `JsonSyntax.Json5`
*
* NOTE: This default will be changed to `JsonSyntax.JsonWithComments` in a future release.
*/
jsonSyntax?: JsonSyntax;
}
/**
* Options for {@link JsonFile.loadAndValidate} and {@link JsonFile.loadAndValidateAsync}
*
* @public
*/
export interface IJsonFileLoadAndValidateOptions extends IJsonFileParseOptions, IJsonSchemaValidateOptions {
}
/**
* Options for {@link JsonFile.stringify}
*
* @public
*/
export interface IJsonFileStringifyOptions {
/**
* If provided, the specified newline type will be used instead of the default `\r\n`.
*/
newlineConversion?: NewlineKind;
/**
* By default, `JsonFile.stringify()` validates that the object does not contain any
* keys whose value is `undefined`. To disable this validation, set `ignoreUndefinedValues=true`
* which causes such keys to be silently discarded, consistent with the system `JSON.stringify()`.
*
* @remarks
*
* The JSON file format can represent `null` values ({@link JsonNull}) but not `undefined` values.
* In ECMAScript code however, we generally avoid `null` and always represent empty states
* as `undefined`, because it is the default value of missing/uninitialized variables.
* (In practice, distinguishing "null" versus "uninitialized" has more drawbacks than benefits.)
* This poses a problem when serializing ECMAScript objects that contain `undefined` members.
* As a safeguard, `JsonFile` will report an error if any `undefined` values are encountered
* during serialization. Set `ignoreUndefinedValues=true` to disable this safeguard.
*/
ignoreUndefinedValues?: boolean;
/**
* If true, then the "jju" library will be used to improve the text formatting.
* Note that this is slightly slower than the native JSON.stringify() implementation.
*/
prettyFormatting?: boolean;
/**
* If specified, this header will be prepended to the start of the file. The header must consist
* of lines prefixed by "//" characters.
* @remarks
* When used with {@link IJsonFileSaveOptions.updateExistingFile}
* or {@link JsonFile.updateString}, the header will ONLY be added for a newly created file.
*/
headerComment?: string;
}
/**
* Options for {@link JsonFile.save} and {@link JsonFile.saveAsync}.
*
* @public
*/
export interface IJsonFileSaveOptions extends IJsonFileStringifyOptions {
/**
* If there is an existing file, and the contents have not changed, then
* don't write anything; this preserves the old timestamp.
*/
onlyIfChanged?: boolean;
/**
* Creates the folder recursively using FileSystem.ensureFolder()
* Defaults to false.
*/
ensureFolderExists?: boolean;
/**
* If true, use the "jju" library to preserve the existing JSON formatting: The file will be loaded
* from the target filename, the new content will be merged in (preserving whitespace and comments),
* and then the file will be overwritten with the merged contents. If the target file does not exist,
* then the file is saved normally.
*/
updateExistingFile?: boolean;
}
/**
* Utilities for reading/writing JSON files.
* @public
*/
export declare class JsonFile {
/**
* @internal
*/
static _formatPathForError: (path: string) => string;
/**
* Loads a JSON file.
*/
static load(jsonFilename: string, options?: IJsonFileParseOptions): JsonObject;
/**
* An async version of {@link JsonFile.load}.
*/
static loadAsync(jsonFilename: string, options?: IJsonFileParseOptions): Promise<JsonObject>;
/**
* Parses a JSON file's contents.
*/
static parseString(jsonContents: string, options?: IJsonFileParseOptions): JsonObject;
/**
* Loads a JSON file and validate its schema.
*/
static loadAndValidate(jsonFilename: string, jsonSchema: JsonSchema, options?: IJsonFileLoadAndValidateOptions): JsonObject;
/**
* An async version of {@link JsonFile.loadAndValidate}.
*/
static loadAndValidateAsync(jsonFilename: string, jsonSchema: JsonSchema, options?: IJsonFileLoadAndValidateOptions): Promise<JsonObject>;
/**
* Loads a JSON file and validate its schema, reporting errors using a callback
* @remarks
* See JsonSchema.validateObjectWithCallback() for more info.
*/
static loadAndValidateWithCallback(jsonFilename: string, jsonSchema: JsonSchema, errorCallback: (errorInfo: IJsonSchemaErrorInfo) => void, options?: IJsonFileLoadAndValidateOptions): JsonObject;
/**
* An async version of {@link JsonFile.loadAndValidateWithCallback}.
*/
static loadAndValidateWithCallbackAsync(jsonFilename: string, jsonSchema: JsonSchema, errorCallback: (errorInfo: IJsonSchemaErrorInfo) => void, options?: IJsonFileLoadAndValidateOptions): Promise<JsonObject>;
/**
* Serializes the specified JSON object to a string buffer.
* @param jsonObject - the object to be serialized
* @param options - other settings that control serialization
* @returns a JSON string, with newlines, and indented with two spaces
*/
static stringify(jsonObject: JsonObject, options?: IJsonFileStringifyOptions): string;
/**
* Serializes the specified JSON object to a string buffer.
* @param previousJson - the previous JSON string, which will be updated
* @param newJsonObject - the object to be serialized
* @param options - other settings that control serialization
* @returns a JSON string, with newlines, and indented with two spaces
*/
static updateString(previousJson: string, newJsonObject: JsonObject, options?: IJsonFileStringifyOptions): string;
/**
* Saves the file to disk. Returns false if nothing was written due to options.onlyIfChanged.
* @param jsonObject - the object to be saved
* @param jsonFilename - the file path to write
* @param options - other settings that control how the file is saved
* @returns false if ISaveJsonFileOptions.onlyIfChanged didn't save anything; true otherwise
*/
static save(jsonObject: JsonObject, jsonFilename: string, options?: IJsonFileSaveOptions): boolean;
/**
* An async version of {@link JsonFile.save}.
*/
static saveAsync(jsonObject: JsonObject, jsonFilename: string, options?: IJsonFileSaveOptions): Promise<boolean>;
/**
* Used to validate a data structure before writing. Reports an error if there
* are any undefined members.
*/
static validateNoUndefinedMembers(jsonObject: JsonObject): void;
private static _validateNoUndefinedMembers;
private static _formatKeyPath;
private static _formatJsonHeaderComment;
private static _buildJjuParseOptions;
}
//# sourceMappingURL=JsonFile.d.ts.map

View File

@ -0,0 +1 @@
{"version":3,"file":"JsonFile.d.ts","sourceRoot":"","sources":["../src/JsonFile.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,UAAU,EAAE,oBAAoB,EAAE,0BAA0B,EAAE,MAAM,cAAc,CAAC;AACjG,OAAO,EAAQ,KAAK,WAAW,EAAE,MAAM,QAAQ,CAAC;AAGhD;;;;;;;;GAQG;AAEH,MAAM,MAAM,UAAU,GAAG,GAAG,CAAC;AAE7B;;;;;;;;;;;;;;GAcG;AAEH,MAAM,MAAM,QAAQ,GAAG,IAAI,CAAC;AAE5B;;;;GAIG;AACH,oBAAY,UAAU;IACpB;;;;;;;;;;;;;;;;;OAiBG;IACH,MAAM,WAAW;IAEjB;;;;;;;;;;;;;;;OAeG;IACH,gBAAgB,qBAAqB;IAErC;;;;;;;;;;;;;OAaG;IACH,KAAK,UAAU;CAChB;AAED;;;;GAIG;AACH,MAAM,WAAW,qBAAqB;IACpC;;;;;;;OAOG;IACH,UAAU,CAAC,EAAE,UAAU,CAAC;CACzB;AAED;;;;GAIG;AACH,MAAM,WAAW,+BAAgC,SAAQ,qBAAqB,EAAE,0BAA0B;CAAG;AAE7G;;;;GAIG;AACH,MAAM,WAAW,yBAAyB;IACxC;;OAEG;IACH,iBAAiB,CAAC,EAAE,WAAW,CAAC;IAEhC;;;;;;;;;;;;;;OAcG;IACH,qBAAqB,CAAC,EAAE,OAAO,CAAC;IAEhC;;;OAGG;IACH,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAE3B;;;;;;OAMG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED;;;;GAIG;AACH,MAAM,WAAW,oBAAqB,SAAQ,yBAAyB;IACrE;;;OAGG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;IAExB;;;OAGG;IACH,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAE7B;;;;;OAKG;IACH,kBAAkB,CAAC,EAAE,OAAO,CAAC;CAC9B;AAID;;;GAGG;AACH,qBAAa,QAAQ;IACnB;;OAEG;IACH,OAAc,mBAAmB,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAA0B;IAErF;;OAEG;WACW,IAAI,CAAC,YAAY,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,qBAAqB,GAAG,UAAU;IAkBrF;;OAEG;WACiB,SAAS,CAAC,YAAY,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,qBAAqB,GAAG,OAAO,CAAC,UAAU,CAAC;IAkBzG;;OAEG;WACW,WAAW,CAAC,YAAY,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,qBAAqB,GAAG,UAAU;IAK5F;;OAEG;WACW,eAAe,CAC3B,YAAY,EAAE,MAAM,EACpB,UAAU,EAAE,UAAU,EACtB,OAAO,CAAC,EAAE,+BAA+B,GACxC,UAAU;IAOb;;OAEG;WACiB,oBAAoB,CACtC,YAAY,EAAE,MAAM,EACpB,UAAU,EAAE,UAAU,EACtB,OAAO,CAAC,EAAE,+BAA+B,GACxC,OAAO,CAAC,UAAU,CAAC;IAOtB;;;;OAIG;WACW,2BAA2B,CACvC,YAAY,EAAE,MAAM,EACpB,UAAU,EAAE,UAAU,EACtB,aAAa,EAAE,CAAC,SAAS,EAAE,oBAAoB,KAAK,IAAI,EACxD,OAAO,CAAC,EAAE,+BAA+B,GACxC,UAAU;IAOb;;OAEG;WACiB,gCAAgC,CAClD,YAAY,EAAE,MAAM,EACpB,UAAU,EAAE,UAAU,EACtB,aAAa,EAAE,CAAC,SAAS,EAAE,oBAAoB,KAAK,IAAI,EACxD,OAAO,CAAC,EAAE,+BAA+B,GACxC,OAAO,CAAC,UAAU,CAAC;IAOtB;;;;;OAKG;WACW,SAAS,CAAC,UAAU,EAAE,UAAU,EAAE,OAAO,CAAC,EAAE,yBAAyB,GAAG,MAAM;IAI5F;;;;;;OAMG;WACW,YAAY,CACxB,YAAY,EAAE,MAAM,EACpB,aAAa,EAAE,UAAU,EACzB,OAAO,CAAC,EAAE,yBAAyB,GAClC,MAAM;IA6CT;;;;;;OAMG;WACW,IAAI,CAAC,UAAU,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,oBAAoB,GAAG,OAAO;IAmDzG;;OAEG;WACiB,SAAS,CAC3B,UAAU,EAAE,UAAU,EACtB,YAAY,EAAE,MAAM,EACpB,OAAO,CAAC,EAAE,oBAAoB,GAC7B,OAAO,CAAC,OAAO,CAAC;IAmDnB;;;OAGG;WACW,0BAA0B,CAAC,UAAU,EAAE,UAAU,GAAG,IAAI;IAKtE,OAAO,CAAC,MAAM,CAAC,2BAA2B;IAuB1C,OAAO,CAAC,MAAM,CAAC,cAAc;IA2B7B,OAAO,CAAC,MAAM,CAAC,wBAAwB;IAmBvC,OAAO,CAAC,MAAM,CAAC,qBAAqB;CAoBrC"}

View File

@ -0,0 +1,426 @@
"use strict";
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root 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.JsonFile = exports.JsonSyntax = void 0;
const os = __importStar(require("os"));
const jju = __importStar(require("jju"));
const Text_1 = require("./Text");
const FileSystem_1 = require("./FileSystem");
/**
* Specifies the variant of JSON syntax to be used.
*
* @public
*/
var JsonSyntax;
(function (JsonSyntax) {
/**
* Specifies the exact RFC 8259 format as implemented by the `JSON.parse()` system API.
* This format was designed for machine generated inputs such as an HTTP payload.
* It is not a recommend choice for human-authored files, because it does not support
* code comments.
*
* @remarks
*
* A well-known quote from Douglas Crockford, the inventor of JSON:
*
* "I removed comments from JSON because I saw people were using them to hold parsing directives,
* a practice which would have destroyed interoperability. I know that the lack of comments makes
* some people sad, but it shouldn't. Suppose you are using JSON to keep configuration files,
* which you would like to annotate. Go ahead and insert all the comments you like.
* Then pipe it through JSMin before handing it to your JSON parser."
*
* @see {@link https://datatracker.ietf.org/doc/html/rfc8259 | RFC 8259}
*/
JsonSyntax["Strict"] = "strict";
/**
* `JsonSyntax.JsonWithComments` is the recommended format for human-authored config files.
* It is a minimal extension to `JsonSyntax.Strict` adding support for code comments
* using `//` and `/*`.
*
* @remarks
*
* VS Code calls this format `jsonc`, but it should not be confused with unrelated file formats
* and libraries that also use the name "JSONC".
*
* To fix VS Code syntax highlighting, add this setting:
* `"files.associations": { "*.json": "jsonc" }`
*
* To fix GitHub syntax highlighting, add this to your `.gitattributes`:
* `*.json linguist-language=JSON-with-Comments`
*/
JsonSyntax["JsonWithComments"] = "jsonWithComments";
/**
* JSON5 is a project that proposes a JSON-like format supplemented with ECMAScript 5.1
* notations for objects, numbers, comments, and more.
*
* @remarks
* Files using this format should use the `.json5` file extension instead of `.json`.
*
* JSON5 has substantial differences from JSON: object keys may be unquoted, trailing commas
* are allowed, and strings may span multiple lines. Whereas `JsonSyntax.JsonWithComments` can
* be cheaply converted to standard JSON by stripping comments, parsing JSON5 requires a
* nontrivial algorithm that may not be easily available in some contexts or programming languages.
*
* @see {@link https://json5.org/ | JSON5 project website}
*/
JsonSyntax["Json5"] = "json5";
})(JsonSyntax = exports.JsonSyntax || (exports.JsonSyntax = {}));
const DEFAULT_ENCODING = 'utf8';
/**
* Utilities for reading/writing JSON files.
* @public
*/
class JsonFile {
/**
* Loads a JSON file.
*/
static load(jsonFilename, options) {
try {
const contents = FileSystem_1.FileSystem.readFile(jsonFilename);
const parseOptions = JsonFile._buildJjuParseOptions(options);
return jju.parse(contents, parseOptions);
}
catch (error) {
if (FileSystem_1.FileSystem.isNotExistError(error)) {
throw error;
}
else {
throw new Error(`Error reading "${JsonFile._formatPathForError(jsonFilename)}":` +
os.EOL +
` ${error.message}`);
}
}
}
/**
* An async version of {@link JsonFile.load}.
*/
static async loadAsync(jsonFilename, options) {
try {
const contents = await FileSystem_1.FileSystem.readFileAsync(jsonFilename);
const parseOptions = JsonFile._buildJjuParseOptions(options);
return jju.parse(contents, parseOptions);
}
catch (error) {
if (FileSystem_1.FileSystem.isNotExistError(error)) {
throw error;
}
else {
throw new Error(`Error reading "${JsonFile._formatPathForError(jsonFilename)}":` +
os.EOL +
` ${error.message}`);
}
}
}
/**
* Parses a JSON file's contents.
*/
static parseString(jsonContents, options) {
const parseOptions = JsonFile._buildJjuParseOptions(options);
return jju.parse(jsonContents, parseOptions);
}
/**
* Loads a JSON file and validate its schema.
*/
static loadAndValidate(jsonFilename, jsonSchema, options) {
const jsonObject = JsonFile.load(jsonFilename, options);
jsonSchema.validateObject(jsonObject, jsonFilename, options);
return jsonObject;
}
/**
* An async version of {@link JsonFile.loadAndValidate}.
*/
static async loadAndValidateAsync(jsonFilename, jsonSchema, options) {
const jsonObject = await JsonFile.loadAsync(jsonFilename, options);
jsonSchema.validateObject(jsonObject, jsonFilename, options);
return jsonObject;
}
/**
* Loads a JSON file and validate its schema, reporting errors using a callback
* @remarks
* See JsonSchema.validateObjectWithCallback() for more info.
*/
static loadAndValidateWithCallback(jsonFilename, jsonSchema, errorCallback, options) {
const jsonObject = JsonFile.load(jsonFilename, options);
jsonSchema.validateObjectWithCallback(jsonObject, errorCallback);
return jsonObject;
}
/**
* An async version of {@link JsonFile.loadAndValidateWithCallback}.
*/
static async loadAndValidateWithCallbackAsync(jsonFilename, jsonSchema, errorCallback, options) {
const jsonObject = await JsonFile.loadAsync(jsonFilename, options);
jsonSchema.validateObjectWithCallback(jsonObject, errorCallback);
return jsonObject;
}
/**
* Serializes the specified JSON object to a string buffer.
* @param jsonObject - the object to be serialized
* @param options - other settings that control serialization
* @returns a JSON string, with newlines, and indented with two spaces
*/
static stringify(jsonObject, options) {
return JsonFile.updateString('', jsonObject, options);
}
/**
* Serializes the specified JSON object to a string buffer.
* @param previousJson - the previous JSON string, which will be updated
* @param newJsonObject - the object to be serialized
* @param options - other settings that control serialization
* @returns a JSON string, with newlines, and indented with two spaces
*/
static updateString(previousJson, newJsonObject, options) {
if (!options) {
options = {};
}
if (!options.ignoreUndefinedValues) {
// Standard handling of `undefined` in JSON stringification is to discard the key.
JsonFile.validateNoUndefinedMembers(newJsonObject);
}
let stringified;
if (previousJson !== '') {
// NOTE: We don't use mode=json here because comments aren't allowed by strict JSON
stringified = jju.update(previousJson, newJsonObject, {
mode: 'cjson',
indent: 2
});
}
else if (options.prettyFormatting) {
stringified = jju.stringify(newJsonObject, {
mode: 'json',
indent: 2
});
if (options.headerComment !== undefined) {
stringified = JsonFile._formatJsonHeaderComment(options.headerComment) + stringified;
}
}
else {
stringified = JSON.stringify(newJsonObject, undefined, 2);
if (options.headerComment !== undefined) {
stringified = JsonFile._formatJsonHeaderComment(options.headerComment) + stringified;
}
}
// Add the trailing newline
stringified = Text_1.Text.ensureTrailingNewline(stringified);
if (options && options.newlineConversion) {
stringified = Text_1.Text.convertTo(stringified, options.newlineConversion);
}
return stringified;
}
/**
* Saves the file to disk. Returns false if nothing was written due to options.onlyIfChanged.
* @param jsonObject - the object to be saved
* @param jsonFilename - the file path to write
* @param options - other settings that control how the file is saved
* @returns false if ISaveJsonFileOptions.onlyIfChanged didn't save anything; true otherwise
*/
static save(jsonObject, jsonFilename, options) {
if (!options) {
options = {};
}
// Do we need to read the previous file contents?
let oldBuffer = undefined;
if (options.updateExistingFile || options.onlyIfChanged) {
try {
oldBuffer = FileSystem_1.FileSystem.readFileToBuffer(jsonFilename);
}
catch (error) {
if (!FileSystem_1.FileSystem.isNotExistError(error)) {
throw error;
}
}
}
let jsonToUpdate = '';
if (options.updateExistingFile && oldBuffer) {
jsonToUpdate = oldBuffer.toString(DEFAULT_ENCODING);
}
const newJson = JsonFile.updateString(jsonToUpdate, jsonObject, options);
const newBuffer = Buffer.from(newJson, DEFAULT_ENCODING);
if (options.onlyIfChanged) {
// Has the file changed?
if (oldBuffer && Buffer.compare(newBuffer, oldBuffer) === 0) {
// Nothing has changed, so don't touch the file
return false;
}
}
FileSystem_1.FileSystem.writeFile(jsonFilename, newBuffer, {
ensureFolderExists: options.ensureFolderExists
});
// TEST CODE: Used to verify that onlyIfChanged isn't broken by a hidden transformation during saving.
/*
const oldBuffer2: Buffer = FileSystem.readFileToBuffer(jsonFilename);
if (Buffer.compare(buffer, oldBuffer2) !== 0) {
console.log('new:' + buffer.toString('hex'));
console.log('old:' + oldBuffer2.toString('hex'));
throw new Error('onlyIfChanged logic is broken');
}
*/
return true;
}
/**
* An async version of {@link JsonFile.save}.
*/
static async saveAsync(jsonObject, jsonFilename, options) {
if (!options) {
options = {};
}
// Do we need to read the previous file contents?
let oldBuffer = undefined;
if (options.updateExistingFile || options.onlyIfChanged) {
try {
oldBuffer = await FileSystem_1.FileSystem.readFileToBufferAsync(jsonFilename);
}
catch (error) {
if (!FileSystem_1.FileSystem.isNotExistError(error)) {
throw error;
}
}
}
let jsonToUpdate = '';
if (options.updateExistingFile && oldBuffer) {
jsonToUpdate = oldBuffer.toString(DEFAULT_ENCODING);
}
const newJson = JsonFile.updateString(jsonToUpdate, jsonObject, options);
const newBuffer = Buffer.from(newJson, DEFAULT_ENCODING);
if (options.onlyIfChanged) {
// Has the file changed?
if (oldBuffer && Buffer.compare(newBuffer, oldBuffer) === 0) {
// Nothing has changed, so don't touch the file
return false;
}
}
await FileSystem_1.FileSystem.writeFileAsync(jsonFilename, newBuffer, {
ensureFolderExists: options.ensureFolderExists
});
// TEST CODE: Used to verify that onlyIfChanged isn't broken by a hidden transformation during saving.
/*
const oldBuffer2: Buffer = await FileSystem.readFileToBufferAsync(jsonFilename);
if (Buffer.compare(buffer, oldBuffer2) !== 0) {
console.log('new:' + buffer.toString('hex'));
console.log('old:' + oldBuffer2.toString('hex'));
throw new Error('onlyIfChanged logic is broken');
}
*/
return true;
}
/**
* Used to validate a data structure before writing. Reports an error if there
* are any undefined members.
*/
static validateNoUndefinedMembers(jsonObject) {
return JsonFile._validateNoUndefinedMembers(jsonObject, []);
}
// Private implementation of validateNoUndefinedMembers()
static _validateNoUndefinedMembers(jsonObject, keyPath) {
if (!jsonObject) {
return;
}
if (typeof jsonObject === 'object') {
for (const key of Object.keys(jsonObject)) {
keyPath.push(key);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const value = jsonObject[key];
if (value === undefined) {
const fullPath = JsonFile._formatKeyPath(keyPath);
throw new Error(`The value for ${fullPath} is "undefined" and cannot be serialized as JSON`);
}
JsonFile._validateNoUndefinedMembers(value, keyPath);
keyPath.pop();
}
}
}
// Given this input: ['items', '4', 'syntax', 'parameters', 'string "with" symbols", 'type']
// Return this string: items[4].syntax.parameters["string \"with\" symbols"].type
static _formatKeyPath(keyPath) {
let result = '';
for (const key of keyPath) {
if (/^[0-9]+$/.test(key)) {
// It's an integer, so display like this: parent[123]
result += `[${key}]`;
}
else if (/^[a-z_][a-z_0-9]*$/i.test(key)) {
// It's an alphanumeric identifier, so display like this: parent.name
if (result) {
result += '.';
}
result += `${key}`;
}
else {
// It's a freeform string, so display like this: parent["A path: \"C:\\file\""]
// Convert this: A path: "C:\file"
// To this: A path: \"C:\\file\"
const escapedKey = key
.replace(/[\\]/g, '\\\\') // escape backslashes
.replace(/["]/g, '\\'); // escape quotes
result += `["${escapedKey}"]`;
}
}
return result;
}
static _formatJsonHeaderComment(headerComment) {
if (headerComment === '') {
return '';
}
const lines = headerComment.split('\n');
const result = [];
for (const line of lines) {
if (!/^\s*$/.test(line) && !/^\s*\/\//.test(line)) {
throw new Error('The headerComment lines must be blank or start with the "//" prefix.\n' +
'Invalid line' +
JSON.stringify(line));
}
result.push(Text_1.Text.replaceAll(line, '\r', ''));
}
return lines.join('\n') + '\n';
}
static _buildJjuParseOptions(options) {
if (!options) {
options = {};
}
const parseOptions = {};
switch (options.jsonSyntax) {
case JsonSyntax.Strict:
parseOptions.mode = 'json';
break;
case JsonSyntax.JsonWithComments:
parseOptions.mode = 'cjson';
break;
case JsonSyntax.Json5:
default:
parseOptions.mode = 'json5';
break;
}
return parseOptions;
}
}
/**
* @internal
*/
JsonFile._formatPathForError = (path) => path;
exports.JsonFile = JsonFile;
//# sourceMappingURL=JsonFile.js.map

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,113 @@
import { type JsonObject } from './JsonFile';
/**
* Callback function arguments for JsonSchema.validateObjectWithCallback();
* @public
*/
export interface IJsonSchemaErrorInfo {
/**
* The z-schema error tree, formatted as an indented text string.
*/
details: string;
}
/**
* Options for JsonSchema.validateObject()
* @public
*/
export interface IJsonSchemaValidateOptions {
/**
* A custom header that will be used to report schema errors.
* @remarks
* If omitted, the default header is "JSON validation failed:". The error message starts with
* the header, followed by the full input filename, followed by the z-schema error tree.
* If you wish to customize all aspects of the error message, use JsonFile.loadAndValidateWithCallback()
* or JsonSchema.validateObjectWithCallback().
*/
customErrorHeader?: string;
}
/**
* Options for JsonSchema.fromFile()
* @public
*/
export interface IJsonSchemaFromFileOptions {
/**
* Other schemas that this schema references, e.g. via the "$ref" directive.
* @remarks
* The tree of dependent schemas may reference the same schema more than once.
* However, if the same schema "id" is used by two different JsonSchema instances,
* an error will be reported. This means you cannot load the same filename twice
* and use them both together, and you cannot have diamond dependencies on different
* versions of the same schema. Although technically this would be possible to support,
* it normally indicates an error or design problem.
*
* JsonSchema also does not allow circular references between schema dependencies.
*/
dependentSchemas?: JsonSchema[];
}
/**
* Represents a JSON schema that can be used to validate JSON data files loaded by the JsonFile class.
* @remarks
* The schema itself is normally loaded and compiled later, only if it is actually required to validate
* an input. To avoid schema errors at runtime, it's recommended to create a unit test that calls
* JsonSchema.ensureCompiled() for each of your schema objects.
*
* @public
*/
export declare class JsonSchema {
private _dependentSchemas;
private _filename;
private _validator;
private _schemaObject;
private constructor();
/**
* Registers a JsonSchema that will be loaded from a file on disk.
* @remarks
* NOTE: An error occurs if the file does not exist; however, the file itself is not loaded or validated
* until it the schema is actually used.
*/
static fromFile(filename: string, options?: IJsonSchemaFromFileOptions): JsonSchema;
/**
* Registers a JsonSchema that will be loaded from a file on disk.
* @remarks
* NOTE: An error occurs if the file does not exist; however, the file itself is not loaded or validated
* until it the schema is actually used.
*/
static fromLoadedObject(schemaObject: JsonObject): JsonSchema;
private static _collectDependentSchemas;
/**
* Used to nicely format the ZSchema error tree.
*/
private static _formatErrorDetails;
/**
* Used by _formatErrorDetails.
*/
private static _formatErrorDetailsHelper;
/**
* Returns a short name for this schema, for use in error messages.
* @remarks
* If the schema was loaded from a file, then the base filename is used. Otherwise, the "id"
* field is used if available.
*/
get shortName(): string;
/**
* If not already done, this loads the schema from disk and compiles it.
* @remarks
* Any dependencies will be compiled as well.
*/
ensureCompiled(): void;
/**
* Validates the specified JSON object against this JSON schema. If the validation fails,
* an exception will be thrown.
* @param jsonObject - The JSON data to be validated
* @param filenameForErrors - The filename that the JSON data was available, or an empty string
* if not applicable
* @param options - Other options that control the validation
*/
validateObject(jsonObject: JsonObject, filenameForErrors: string, options?: IJsonSchemaValidateOptions): void;
/**
* Validates the specified JSON object against this JSON schema. If the validation fails,
* a callback is called for each validation error.
*/
validateObjectWithCallback(jsonObject: JsonObject, errorCallback: (errorInfo: IJsonSchemaErrorInfo) => void): void;
private _ensureLoaded;
}
//# sourceMappingURL=JsonSchema.d.ts.map

View File

@ -0,0 +1 @@
{"version":3,"file":"JsonSchema.d.ts","sourceRoot":"","sources":["../src/JsonSchema.ts"],"names":[],"mappings":"AAMA,OAAO,EAAY,KAAK,UAAU,EAAE,MAAM,YAAY,CAAC;AAUvD;;;GAGG;AACH,MAAM,WAAW,oBAAoB;IACnC;;OAEG;IACH,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;;GAGG;AACH,MAAM,WAAW,0BAA0B;IACzC;;;;;;;OAOG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED;;;GAGG;AACH,MAAM,WAAW,0BAA0B;IACzC;;;;;;;;;;;OAWG;IACH,gBAAgB,CAAC,EAAE,UAAU,EAAE,CAAC;CACjC;AAED;;;;;;;;GAQG;AACH,qBAAa,UAAU;IACrB,OAAO,CAAC,iBAAiB,CAAoB;IAC7C,OAAO,CAAC,SAAS,CAAc;IAC/B,OAAO,CAAC,UAAU,CAAwC;IAC1D,OAAO,CAAC,aAAa,CAAqC;IAE1D,OAAO;IAEP;;;;;OAKG;WACW,QAAQ,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,0BAA0B,GAAG,UAAU;IAiB1F;;;;;OAKG;WACW,gBAAgB,CAAC,YAAY,EAAE,UAAU,GAAG,UAAU;IAMpE,OAAO,CAAC,MAAM,CAAC,wBAAwB;IAuCvC;;OAEG;IACH,OAAO,CAAC,MAAM,CAAC,mBAAmB;IAIlC;;OAEG;IACH,OAAO,CAAC,MAAM,CAAC,yBAAyB;IA4BxC;;;;;OAKG;IACH,IAAW,SAAS,IAAI,MAAM,CAY7B;IAED;;;;OAIG;IACI,cAAc,IAAI,IAAI;IAwC7B;;;;;;;OAOG;IACI,cAAc,CACnB,UAAU,EAAE,UAAU,EACtB,iBAAiB,EAAE,MAAM,EACzB,OAAO,CAAC,EAAE,0BAA0B,GACnC,IAAI;IASP;;;OAGG;IACI,0BAA0B,CAC/B,UAAU,EAAE,UAAU,EACtB,aAAa,EAAE,CAAC,SAAS,EAAE,oBAAoB,KAAK,IAAI,GACvD,IAAI;IAaP,OAAO,CAAC,aAAa;CAMtB"}

View File

@ -0,0 +1,218 @@
"use strict";
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root 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.JsonSchema = void 0;
const os = __importStar(require("os"));
const path = __importStar(require("path"));
const JsonFile_1 = require("./JsonFile");
const FileSystem_1 = require("./FileSystem");
const Validator = require('z-schema/dist/ZSchema-browser-min');
/**
* Represents a JSON schema that can be used to validate JSON data files loaded by the JsonFile class.
* @remarks
* The schema itself is normally loaded and compiled later, only if it is actually required to validate
* an input. To avoid schema errors at runtime, it's recommended to create a unit test that calls
* JsonSchema.ensureCompiled() for each of your schema objects.
*
* @public
*/
class JsonSchema {
constructor() {
this._dependentSchemas = [];
this._filename = '';
this._validator = undefined;
this._schemaObject = undefined;
}
/**
* Registers a JsonSchema that will be loaded from a file on disk.
* @remarks
* NOTE: An error occurs if the file does not exist; however, the file itself is not loaded or validated
* until it the schema is actually used.
*/
static fromFile(filename, options) {
// This is a quick and inexpensive test to avoid the catch the most common errors early.
// Full validation will happen later in JsonSchema.compile().
if (!FileSystem_1.FileSystem.exists(filename)) {
throw new Error('Schema file not found: ' + filename);
}
const schema = new JsonSchema();
schema._filename = filename;
if (options) {
schema._dependentSchemas = options.dependentSchemas || [];
}
return schema;
}
/**
* Registers a JsonSchema that will be loaded from a file on disk.
* @remarks
* NOTE: An error occurs if the file does not exist; however, the file itself is not loaded or validated
* until it the schema is actually used.
*/
static fromLoadedObject(schemaObject) {
const schema = new JsonSchema();
schema._schemaObject = schemaObject;
return schema;
}
static _collectDependentSchemas(collectedSchemas, dependentSchemas, seenObjects, seenIds) {
for (const dependentSchema of dependentSchemas) {
// It's okay for the same schema to appear multiple times in the tree, but we only process it once
if (seenObjects.has(dependentSchema)) {
continue;
}
seenObjects.add(dependentSchema);
const schemaId = dependentSchema._ensureLoaded();
if (schemaId === '') {
throw new Error(`This schema ${dependentSchema.shortName} cannot be referenced` +
' because is missing the "id" field');
}
if (seenIds.has(schemaId)) {
throw new Error(`This schema ${dependentSchema.shortName} has the same "id" as another schema in this set`);
}
seenIds.add(schemaId);
collectedSchemas.push(dependentSchema);
JsonSchema._collectDependentSchemas(collectedSchemas, dependentSchema._dependentSchemas, seenObjects, seenIds);
}
}
/**
* Used to nicely format the ZSchema error tree.
*/
static _formatErrorDetails(errorDetails) {
return JsonSchema._formatErrorDetailsHelper(errorDetails, '', '');
}
/**
* Used by _formatErrorDetails.
*/
static _formatErrorDetailsHelper(errorDetails, indent, buffer) {
for (const errorDetail of errorDetails) {
buffer += os.EOL + indent + `Error: ${errorDetail.path}`;
if (errorDetail.description) {
const MAX_LENGTH = 40;
let truncatedDescription = errorDetail.description.trim();
if (truncatedDescription.length > MAX_LENGTH) {
truncatedDescription = truncatedDescription.substr(0, MAX_LENGTH - 3) + '...';
}
buffer += ` (${truncatedDescription})`;
}
buffer += os.EOL + indent + ` ${errorDetail.message}`;
if (errorDetail.inner) {
buffer = JsonSchema._formatErrorDetailsHelper(errorDetail.inner, indent + ' ', buffer);
}
}
return buffer;
}
/**
* Returns a short name for this schema, for use in error messages.
* @remarks
* If the schema was loaded from a file, then the base filename is used. Otherwise, the "id"
* field is used if available.
*/
get shortName() {
if (!this._filename) {
if (this._schemaObject) {
const schemaWithId = this._schemaObject;
if (schemaWithId.id) {
return schemaWithId.id;
}
}
return '(anonymous schema)';
}
else {
return path.basename(this._filename);
}
}
/**
* If not already done, this loads the schema from disk and compiles it.
* @remarks
* Any dependencies will be compiled as well.
*/
ensureCompiled() {
this._ensureLoaded();
if (!this._validator) {
// Don't assign this to _validator until we're sure everything was successful
const newValidator = new Validator({
breakOnFirstError: false,
noTypeless: true,
noExtraKeywords: true
});
const anythingSchema = {
type: ['array', 'boolean', 'integer', 'number', 'object', 'string']
};
// eslint-disable-next-line @typescript-eslint/no-explicit-any
newValidator.setRemoteReference('http://json-schema.org/draft-04/schema', anythingSchema);
const collectedSchemas = [];
const seenObjects = new Set();
const seenIds = new Set();
JsonSchema._collectDependentSchemas(collectedSchemas, this._dependentSchemas, seenObjects, seenIds);
// Validate each schema in order. We specifically do not supply them all together, because we want
// to make sure that circular references will fail to validate.
for (const collectedSchema of collectedSchemas) {
if (!newValidator.validateSchema(collectedSchema._schemaObject)) {
throw new Error(`Failed to validate schema "${collectedSchema.shortName}":` +
os.EOL +
JsonSchema._formatErrorDetails(newValidator.getLastErrors()));
}
}
this._validator = newValidator;
}
}
/**
* Validates the specified JSON object against this JSON schema. If the validation fails,
* an exception will be thrown.
* @param jsonObject - The JSON data to be validated
* @param filenameForErrors - The filename that the JSON data was available, or an empty string
* if not applicable
* @param options - Other options that control the validation
*/
validateObject(jsonObject, filenameForErrors, options) {
this.validateObjectWithCallback(jsonObject, (errorInfo) => {
const prefix = options && options.customErrorHeader ? options.customErrorHeader : 'JSON validation failed:';
throw new Error(prefix + os.EOL + filenameForErrors + os.EOL + errorInfo.details);
});
}
/**
* Validates the specified JSON object against this JSON schema. If the validation fails,
* a callback is called for each validation error.
*/
validateObjectWithCallback(jsonObject, errorCallback) {
this.ensureCompiled();
if (!this._validator.validate(jsonObject, this._schemaObject)) {
const errorDetails = JsonSchema._formatErrorDetails(this._validator.getLastErrors());
const args = {
details: errorDetails
};
errorCallback(args);
}
}
_ensureLoaded() {
if (!this._schemaObject) {
this._schemaObject = JsonFile_1.JsonFile.load(this._filename);
}
return this._schemaObject.id || '';
}
}
exports.JsonSchema = JsonSchema;
//# sourceMappingURL=JsonSchema.js.map

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,34 @@
/**
* Callback used by {@link LegacyAdapters}.
* @public
*/
export type LegacyCallback<TResult, TError> = (error: TError | null | undefined, result: TResult) => void;
/**
* Helper functions used when interacting with APIs that do not follow modern coding practices.
* @public
*/
export declare class LegacyAdapters {
/**
* This function wraps a function with a callback in a promise.
*/
static convertCallbackToPromise<TResult, TError>(fn: (cb: LegacyCallback<TResult, TError>) => void): Promise<TResult>;
static convertCallbackToPromise<TResult, TError, TArg1>(fn: (arg1: TArg1, cb: LegacyCallback<TResult, TError>) => void, arg1: TArg1): Promise<TResult>;
static convertCallbackToPromise<TResult, TError, TArg1, TArg2>(fn: (arg1: TArg1, arg2: TArg2, cb: LegacyCallback<TResult, TError>) => void, arg1: TArg1, arg2: TArg2): Promise<TResult>;
static convertCallbackToPromise<TResult, TError, TArg1, TArg2, TArg3>(fn: (arg1: TArg1, arg2: TArg2, arg3: TArg3, cb: LegacyCallback<TResult, TError>) => void, arg1: TArg1, arg2: TArg2, arg3: TArg3): Promise<TResult>;
static convertCallbackToPromise<TResult, TError, TArg1, TArg2, TArg3, TArg4>(fn: (arg1: TArg1, arg2: TArg2, arg3: TArg3, arg4: TArg4, cb: LegacyCallback<TResult, TError>) => void, arg1: TArg1, arg2: TArg2, arg3: TArg3, arg4: TArg4): Promise<TResult>;
/**
* Normalizes an object into an `Error` object.
*/
static scrubError(error: Error | string | any): Error;
/**
* Prior to Node 11.x, the `Array.sort()` algorithm is not guaranteed to be stable.
* If you need a stable sort, you can use `sortStable()` as a workaround.
*
* @deprecated
* Use native Array.sort(), since Node &lt; 14 is no longer supported
* @remarks
* On NodeJS 11.x and later, this method simply calls the native `Array.sort()`.
*/
static sortStable<T>(array: T[], compare?: (a: T, b: T) => number): void;
}
//# sourceMappingURL=LegacyAdapters.d.ts.map

View File

@ -0,0 +1 @@
{"version":3,"file":"LegacyAdapters.d.ts","sourceRoot":"","sources":["../src/LegacyAdapters.ts"],"names":[],"mappings":"AAGA;;;GAGG;AAEH,MAAM,MAAM,cAAc,CAAC,OAAO,EAAE,MAAM,IAAI,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EAAE,MAAM,EAAE,OAAO,KAAK,IAAI,CAAC;AAE1G;;;GAGG;AACH,qBAAa,cAAc;IACzB;;OAEG;WACW,wBAAwB,CAAC,OAAO,EAAE,MAAM,EACpD,EAAE,EAAE,CAAC,EAAE,EAAE,cAAc,CAAC,OAAO,EAAE,MAAM,CAAC,KAAK,IAAI,GAChD,OAAO,CAAC,OAAO,CAAC;WACL,wBAAwB,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAC3D,EAAE,EAAE,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,EAAE,cAAc,CAAC,OAAO,EAAE,MAAM,CAAC,KAAK,IAAI,EAC9D,IAAI,EAAE,KAAK,GACV,OAAO,CAAC,OAAO,CAAC;WACL,wBAAwB,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,EAClE,EAAE,EAAE,CAAC,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,EAAE,cAAc,CAAC,OAAO,EAAE,MAAM,CAAC,KAAK,IAAI,EAC3E,IAAI,EAAE,KAAK,EACX,IAAI,EAAE,KAAK,GACV,OAAO,CAAC,OAAO,CAAC;WACL,wBAAwB,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EACzE,EAAE,EAAE,CAAC,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,EAAE,cAAc,CAAC,OAAO,EAAE,MAAM,CAAC,KAAK,IAAI,EACxF,IAAI,EAAE,KAAK,EACX,IAAI,EAAE,KAAK,EACX,IAAI,EAAE,KAAK,GACV,OAAO,CAAC,OAAO,CAAC;WACL,wBAAwB,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAChF,EAAE,EAAE,CAAC,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,EAAE,cAAc,CAAC,OAAO,EAAE,MAAM,CAAC,KAAK,IAAI,EACrG,IAAI,EAAE,KAAK,EACX,IAAI,EAAE,KAAK,EACX,IAAI,EAAE,KAAK,EACX,IAAI,EAAE,KAAK,GACV,OAAO,CAAC,OAAO,CAAC;IAyCnB;;OAEG;WAEW,UAAU,CAAC,KAAK,EAAE,KAAK,GAAG,MAAM,GAAG,GAAG,GAAG,KAAK;IAY5D;;;;;;;;OAQG;WACW,UAAU,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE,OAAO,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,KAAK,MAAM,GAAG,IAAI;CAGhF"}

View File

@ -0,0 +1,74 @@
"use strict";
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root for license information.
Object.defineProperty(exports, "__esModule", { value: true });
exports.LegacyAdapters = void 0;
/**
* Helper functions used when interacting with APIs that do not follow modern coding practices.
* @public
*/
class LegacyAdapters {
static convertCallbackToPromise(fn, arg1, arg2, arg3, arg4) {
return new Promise((resolve, reject) => {
const cb = (error, result) => {
if (error) {
reject(LegacyAdapters.scrubError(error));
}
else {
resolve(result);
}
};
try {
if (arg1 !== undefined && arg2 !== undefined && arg3 !== undefined && arg4 !== undefined) {
fn(arg1, arg2, arg3, arg4, cb);
}
else if (arg1 !== undefined && arg2 !== undefined && arg3 !== undefined) {
fn(arg1, arg2, arg3, cb);
}
else if (arg1 !== undefined && arg2 !== undefined) {
fn(arg1, arg2, cb);
}
else if (arg1 !== undefined) {
fn(arg1, cb);
}
else {
fn(cb);
}
}
catch (e) {
reject(e);
}
});
}
/**
* Normalizes an object into an `Error` object.
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
static scrubError(error) {
if (error instanceof Error) {
return error;
}
else if (typeof error === 'string') {
return new Error(error);
}
else {
const errorObject = new Error('An error occurred.');
errorObject.errorData = error; // eslint-disable-line @typescript-eslint/no-explicit-any
return errorObject;
}
}
/**
* Prior to Node 11.x, the `Array.sort()` algorithm is not guaranteed to be stable.
* If you need a stable sort, you can use `sortStable()` as a workaround.
*
* @deprecated
* Use native Array.sort(), since Node &lt; 14 is no longer supported
* @remarks
* On NodeJS 11.x and later, this method simply calls the native `Array.sort()`.
*/
static sortStable(array, compare) {
Array.prototype.sort.call(array, compare);
}
}
exports.LegacyAdapters = LegacyAdapters;
//# sourceMappingURL=LegacyAdapters.js.map

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,89 @@
/**
* Parses the process start time from the contents of a linux /proc/[pid]/stat file.
* @param stat - The contents of a linux /proc/[pid]/stat file.
* @returns The process start time in jiffies, or undefined if stat has an unexpected format.
*/
export declare function getProcessStartTimeFromProcStat(stat: string): string | undefined;
/**
* Helper function that is exported for unit tests only.
* Returns undefined if the process doesn't exist with that pid.
*/
export declare function getProcessStartTime(pid: number): string | undefined;
/**
* The `LockFile` implements a file-based mutex for synchronizing access to a shared resource
* between multiple Node.js processes. It is not recommended for synchronization solely within
* a single Node.js process.
* @remarks
* The implementation works on Windows, Mac, and Linux without requiring any native helpers.
* On non-Windows systems, the algorithm requires access to the `ps` shell command. On Linux,
* it requires access the `/proc/${pidString}/stat` filesystem.
* @public
*/
export declare class LockFile {
private static _getStartTime;
private _fileWriter;
private _filePath;
private _dirtyWhenAcquired;
private constructor();
/**
* Returns the path of the lockfile that will be created when a lock is successfully acquired.
* @param resourceFolder - The folder where the lock file will be created
* @param resourceName - An alphanumeric name that describes the resource being locked. This will become
* the filename of the temporary file created to manage the lock.
* @param pid - The PID for the current Node.js process (`process.pid`), which is used by the locking algorithm.
*/
static getLockFilePath(resourceFolder: string, resourceName: string, pid?: number): string;
/**
* Attempts to create a lockfile with the given filePath.
* @param resourceFolder - The folder where the lock file will be created
* @param resourceName - An alphanumeric name that describes the resource being locked. This will become
* the filename of the temporary file created to manage the lock.
* @returns If successful, returns a `LockFile` instance. If unable to get a lock, returns `undefined`.
*/
static tryAcquire(resourceFolder: string, resourceName: string): LockFile | undefined;
/**
* Attempts to create the lockfile. Will continue to loop at every 100ms until the lock becomes available
* or the maxWaitMs is surpassed.
*
* @remarks
* This function is subject to starvation, whereby it does not ensure that the process that has been
* waiting the longest to acquire the lock will get it first. This means that a process could theoretically
* wait for the lock forever, while other processes skipped it in line and acquired the lock first.
*
* @param resourceFolder - The folder where the lock file will be created
* @param resourceName - An alphanumeric name that describes the resource being locked. This will become
* the filename of the temporary file created to manage the lock.
* @param maxWaitMs - The maximum number of milliseconds to wait for the lock before reporting an error
*/
static acquire(resourceFolder: string, resourceName: string, maxWaitMs?: number): Promise<LockFile>;
/**
* Attempts to acquire the lock on a Linux or OSX machine
*/
private static _tryAcquireMacOrLinux;
/**
* Attempts to acquire the lock using Windows
* This algorithm is much simpler since we can rely on the operating system
*/
private static _tryAcquireWindows;
/**
* Unlocks a file and optionally removes it from disk.
* This can only be called once.
*
* @param deleteFile - Whether to delete the lockfile from disk. Defaults to true.
*/
release(deleteFile?: boolean): void;
/**
* Returns the initial state of the lock.
* This can be used to detect if the previous process was terminated before releasing the resource.
*/
get dirtyWhenAcquired(): boolean;
/**
* Returns the absolute path to the lockfile
*/
get filePath(): string;
/**
* Returns true if this lock is currently being held.
*/
get isReleased(): boolean;
}
//# sourceMappingURL=LockFile.d.ts.map

View File

@ -0,0 +1 @@
{"version":3,"file":"LockFile.d.ts","sourceRoot":"","sources":["../src/LockFile.ts"],"names":[],"mappings":"AAmBA;;;;GAIG;AACH,wBAAgB,+BAA+B,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAsChF;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAoEnE;AAED;;;;;;;;;GASG;AACH,qBAAa,QAAQ;IACnB,OAAO,CAAC,MAAM,CAAC,aAAa,CAA4D;IAExF,OAAO,CAAC,WAAW,CAAyB;IAC5C,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,kBAAkB,CAAU;IAEpC,OAAO;IAMP;;;;;;OAMG;WACW,eAAe,CAC3B,cAAc,EAAE,MAAM,EACtB,YAAY,EAAE,MAAM,EACpB,GAAG,GAAE,MAAoB,GACxB,MAAM;IAiBT;;;;;;OAMG;WACW,UAAU,CAAC,cAAc,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,QAAQ,GAAG,SAAS;IAU5F;;;;;;;;;;;;;OAaG;WACW,OAAO,CAAC,cAAc,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC;IAoB1G;;OAEG;IACH,OAAO,CAAC,MAAM,CAAC,qBAAqB;IAuIpC;;;OAGG;IACH,OAAO,CAAC,MAAM,CAAC,kBAAkB;IAwCjC;;;;;OAKG;IACI,OAAO,CAAC,UAAU,GAAE,OAAc,GAAG,IAAI;IAYhD;;;OAGG;IACH,IAAW,iBAAiB,IAAI,OAAO,CAEtC;IAED;;OAEG;IACH,IAAW,QAAQ,IAAI,MAAM,CAE5B;IAED;;OAEG;IACH,IAAW,UAAU,IAAI,OAAO,CAE/B;CACF"}

View File

@ -0,0 +1,424 @@
"use strict";
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root 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.LockFile = exports.getProcessStartTime = exports.getProcessStartTimeFromProcStat = void 0;
const path = __importStar(require("path"));
const child_process = __importStar(require("child_process"));
const FileSystem_1 = require("./FileSystem");
const FileWriter_1 = require("./FileWriter");
const Async_1 = require("./Async");
/**
* http://man7.org/linux/man-pages/man5/proc.5.html
* (22) starttime %llu
* The time the process started after system boot. In kernels before Linux 2.6, this value was
* expressed in jiffies. Since Linux 2.6, the value is expressed in clock ticks (divide by
* sysconf(_SC_CLK_TCK)).
* The format for this field was %lu before Linux 2.6.
*/
const procStatStartTimePos = 22;
/**
* Parses the process start time from the contents of a linux /proc/[pid]/stat file.
* @param stat - The contents of a linux /proc/[pid]/stat file.
* @returns The process start time in jiffies, or undefined if stat has an unexpected format.
*/
function getProcessStartTimeFromProcStat(stat) {
// Parse the value at position procStatStartTimePos.
// We cannot just split stat on spaces, because value 2 may contain spaces.
// For example, when running the following Shell commands:
// > cp "$(which bash)" ./'bash 2)('
// > ./'bash 2)(' -c 'OWNPID=$BASHPID;cat /proc/$OWNPID/stat'
// 59389 (bash 2)() S 59358 59389 59358 34818 59389 4202496 329 0 0 0 0 0 0 0 20 0 1 0
// > rm -rf ./'bash 2)('
// The output shows a stat file such that value 2 contains spaces.
// To still umambiguously parse such output we assume no values after the second ends with a right parenthesis...
// trimRight to remove the trailing line terminator.
let values = stat.trimRight().split(' ');
let i = values.length - 1;
while (i >= 0 &&
// charAt returns an empty string if the index is out of bounds.
values[i].charAt(values[i].length - 1) !== ')') {
i -= 1;
}
// i is the index of the last part of the second value (but i need not be 1).
if (i < 1) {
// Format of stat has changed.
return undefined;
}
const value2 = values.slice(1, i + 1).join(' ');
values = [values[0], value2].concat(values.slice(i + 1));
if (values.length < procStatStartTimePos) {
// Older version of linux, or non-standard configuration of linux.
return undefined;
}
const startTimeJiffies = values[procStatStartTimePos - 1];
// In theory, the representations of start time returned by `cat /proc/[pid]/stat` and `ps -o lstart` can change
// while the system is running, but we assume this does not happen.
// So the caller can safely use this value as part of a unique process id (on the machine, without comparing
// accross reboots).
return startTimeJiffies;
}
exports.getProcessStartTimeFromProcStat = getProcessStartTimeFromProcStat;
/**
* Helper function that is exported for unit tests only.
* Returns undefined if the process doesn't exist with that pid.
*/
function getProcessStartTime(pid) {
const pidString = pid.toString();
if (pid < 0 || pidString.indexOf('e') >= 0 || pidString.indexOf('E') >= 0) {
throw new Error(`"pid" is negative or too large`);
}
let args;
if (process.platform === 'darwin') {
args = [`-p ${pidString}`, '-o lstart'];
}
else if (process.platform === 'linux') {
args = ['-p', pidString, '-o', 'lstart'];
}
else {
throw new Error(`Unsupported system: ${process.platform}`);
}
const psResult = child_process.spawnSync('ps', args, {
encoding: 'utf8'
});
const psStdout = psResult.stdout;
// If no process with PID pid exists then the exit code is non-zero on linux but stdout is not empty.
// But if no process exists we do not want to fall back on /proc/*/stat to determine the process
// start time, so we we additionally test for !psStdout. NOTE: !psStdout evaluates to true if
// zero bytes are written to stdout.
if (psResult.status !== 0 && !psStdout && process.platform === 'linux') {
// Try to read /proc/[pid]/stat and get the value at position procStatStartTimePos.
let stat;
try {
stat = FileSystem_1.FileSystem.readFile(`/proc/${pidString}/stat`);
}
catch (error) {
if (error.code !== 'ENOENT') {
throw error;
}
// Either no process with PID pid exists, or this version/configuration of linux is non-standard.
// We assume the former.
return undefined;
}
if (stat !== undefined) {
const startTimeJiffies = getProcessStartTimeFromProcStat(stat);
if (startTimeJiffies === undefined) {
throw new Error(`Could not retrieve the start time of process ${pidString} from the OS because the ` +
`contents of /proc/${pidString}/stat have an unexpected format`);
}
return startTimeJiffies;
}
}
// there was an error executing ps (zero bytes were written to stdout).
if (!psStdout) {
throw new Error(`Unexpected output from "ps" command`);
}
const psSplit = psStdout.split('\n');
// successfuly able to run "ps", but no process was found
if (psSplit[1] === '') {
return undefined;
}
if (psSplit[1]) {
const trimmed = psSplit[1].trim();
if (trimmed.length > 10) {
return trimmed;
}
}
throw new Error(`Unexpected output from the "ps" command`);
}
exports.getProcessStartTime = getProcessStartTime;
/**
* The `LockFile` implements a file-based mutex for synchronizing access to a shared resource
* between multiple Node.js processes. It is not recommended for synchronization solely within
* a single Node.js process.
* @remarks
* The implementation works on Windows, Mac, and Linux without requiring any native helpers.
* On non-Windows systems, the algorithm requires access to the `ps` shell command. On Linux,
* it requires access the `/proc/${pidString}/stat` filesystem.
* @public
*/
class LockFile {
constructor(fileWriter, filePath, dirtyWhenAcquired) {
this._fileWriter = fileWriter;
this._filePath = filePath;
this._dirtyWhenAcquired = dirtyWhenAcquired;
}
/**
* Returns the path of the lockfile that will be created when a lock is successfully acquired.
* @param resourceFolder - The folder where the lock file will be created
* @param resourceName - An alphanumeric name that describes the resource being locked. This will become
* the filename of the temporary file created to manage the lock.
* @param pid - The PID for the current Node.js process (`process.pid`), which is used by the locking algorithm.
*/
static getLockFilePath(resourceFolder, resourceName, pid = process.pid) {
if (!resourceName.match(/^[a-zA-Z0-9][a-zA-Z0-9-.]+[a-zA-Z0-9]$/)) {
throw new Error(`The resource name "${resourceName}" is invalid.` +
` It must be an alphanumberic string with only "-" or "." It must start with an alphanumeric character.`);
}
if (process.platform === 'win32') {
return path.join(path.resolve(resourceFolder), `${resourceName}.lock`);
}
else if (process.platform === 'linux' || process.platform === 'darwin') {
return path.join(path.resolve(resourceFolder), `${resourceName}#${pid}.lock`);
}
throw new Error(`File locking not implemented for platform: "${process.platform}"`);
}
/**
* Attempts to create a lockfile with the given filePath.
* @param resourceFolder - The folder where the lock file will be created
* @param resourceName - An alphanumeric name that describes the resource being locked. This will become
* the filename of the temporary file created to manage the lock.
* @returns If successful, returns a `LockFile` instance. If unable to get a lock, returns `undefined`.
*/
static tryAcquire(resourceFolder, resourceName) {
FileSystem_1.FileSystem.ensureFolder(resourceFolder);
if (process.platform === 'win32') {
return LockFile._tryAcquireWindows(resourceFolder, resourceName);
}
else if (process.platform === 'linux' || process.platform === 'darwin') {
return LockFile._tryAcquireMacOrLinux(resourceFolder, resourceName);
}
throw new Error(`File locking not implemented for platform: "${process.platform}"`);
}
/**
* Attempts to create the lockfile. Will continue to loop at every 100ms until the lock becomes available
* or the maxWaitMs is surpassed.
*
* @remarks
* This function is subject to starvation, whereby it does not ensure that the process that has been
* waiting the longest to acquire the lock will get it first. This means that a process could theoretically
* wait for the lock forever, while other processes skipped it in line and acquired the lock first.
*
* @param resourceFolder - The folder where the lock file will be created
* @param resourceName - An alphanumeric name that describes the resource being locked. This will become
* the filename of the temporary file created to manage the lock.
* @param maxWaitMs - The maximum number of milliseconds to wait for the lock before reporting an error
*/
static acquire(resourceFolder, resourceName, maxWaitMs) {
const interval = 100;
const startTime = Date.now();
const retryLoop = async () => {
const lock = LockFile.tryAcquire(resourceFolder, resourceName);
if (lock) {
return lock;
}
if (maxWaitMs && Date.now() > startTime + maxWaitMs) {
throw new Error(`Exceeded maximum wait time to acquire lock for resource "${resourceName}"`);
}
await Async_1.Async.sleep(interval);
return retryLoop();
};
return retryLoop();
}
/**
* Attempts to acquire the lock on a Linux or OSX machine
*/
static _tryAcquireMacOrLinux(resourceFolder, resourceName) {
let dirtyWhenAcquired = false;
// get the current process' pid
const pid = process.pid;
const startTime = LockFile._getStartTime(pid);
if (!startTime) {
throw new Error(`Unable to calculate start time for current process.`);
}
const pidLockFilePath = LockFile.getLockFilePath(resourceFolder, resourceName);
let lockFileHandle;
let lockFile;
try {
// open in write mode since if this file exists, it cannot be from the current process
// TODO: This will malfunction if the same process tries to acquire two locks on the same file.
// We should ideally maintain a dictionary of normalized acquired filenames
lockFileHandle = FileWriter_1.FileWriter.open(pidLockFilePath);
lockFileHandle.write(startTime);
const currentBirthTimeMs = FileSystem_1.FileSystem.getStatistics(pidLockFilePath).birthtime.getTime();
let smallestBirthTimeMs = currentBirthTimeMs;
let smallestBirthTimePid = pid.toString();
// now, scan the directory for all lockfiles
const files = FileSystem_1.FileSystem.readFolderItemNames(resourceFolder);
// look for anything ending with # then numbers and ".lock"
const lockFileRegExp = /^(.+)#([0-9]+)\.lock$/;
let match;
let otherPid;
for (const fileInFolder of files) {
if ((match = fileInFolder.match(lockFileRegExp)) &&
match[1] === resourceName &&
(otherPid = match[2]) !== pid.toString()) {
// we found at least one lockfile hanging around that isn't ours
const fileInFolderPath = path.join(resourceFolder, fileInFolder);
dirtyWhenAcquired = true;
// console.log(`FOUND OTHER LOCKFILE: ${otherPid}`);
const otherPidCurrentStartTime = LockFile._getStartTime(parseInt(otherPid, 10));
let otherPidOldStartTime;
let otherBirthtimeMs;
try {
otherPidOldStartTime = FileSystem_1.FileSystem.readFile(fileInFolderPath);
// check the timestamp of the file
otherBirthtimeMs = FileSystem_1.FileSystem.getStatistics(fileInFolderPath).birthtime.getTime();
}
catch (err) {
// this means the file is probably deleted already
}
// if the otherPidOldStartTime is invalid, then we should look at the timestamp,
// if this file was created after us, ignore it
// if it was created within 1 second before us, then it could be good, so we
// will conservatively fail
// otherwise it is an old lock file and will be deleted
if (otherPidOldStartTime === '' && otherBirthtimeMs !== undefined) {
if (otherBirthtimeMs > currentBirthTimeMs) {
// ignore this file, he will be unable to get the lock since this process
// will hold it
// console.log(`Ignoring lock for pid ${otherPid} because its lockfile is newer than ours.`);
continue;
}
else if (otherBirthtimeMs - currentBirthTimeMs < 0 && // it was created before us AND
otherBirthtimeMs - currentBirthTimeMs > -1000) {
// it was created less than a second before
// conservatively be unable to keep the lock
return undefined;
}
}
// console.log(`Other pid ${otherPid} lockfile has start time: "${otherPidOldStartTime}"`);
// console.log(`Other pid ${otherPid} actually has start time: "${otherPidCurrentStartTime}"`);
// this means the process is no longer executing, delete the file
if (!otherPidCurrentStartTime || otherPidOldStartTime !== otherPidCurrentStartTime) {
// console.log(`Other pid ${otherPid} is no longer executing!`);
FileSystem_1.FileSystem.deleteFile(fileInFolderPath);
continue;
}
// console.log(`Pid ${otherPid} lockfile has birth time: ${otherBirthtimeMs}`);
// console.log(`Pid ${pid} lockfile has birth time: ${currentBirthTimeMs}`);
// this is a lockfile pointing at something valid
if (otherBirthtimeMs !== undefined) {
// the other lock file was created before the current earliest lock file
// or the other lock file was created at the same exact time, but has earlier pid
// note that it is acceptable to do a direct comparison of the PIDs in this case
// since we are establishing a consistent order to apply to the lock files in all
// execution instances.
// it doesn't matter that the PIDs roll over, we've already
// established that these processes all started at the same time, so we just
// need to get all instances of the lock test to agree which one won.
if (otherBirthtimeMs < smallestBirthTimeMs ||
(otherBirthtimeMs === smallestBirthTimeMs && otherPid < smallestBirthTimePid)) {
smallestBirthTimeMs = otherBirthtimeMs;
smallestBirthTimePid = otherPid;
}
}
}
}
if (smallestBirthTimePid !== pid.toString()) {
// we do not have the lock
return undefined;
}
// we have the lock!
lockFile = new LockFile(lockFileHandle, pidLockFilePath, dirtyWhenAcquired);
lockFileHandle = undefined; // we have handed the descriptor off to the instance
}
finally {
if (lockFileHandle) {
// ensure our lock is closed
lockFileHandle.close();
FileSystem_1.FileSystem.deleteFile(pidLockFilePath);
}
}
return lockFile;
}
/**
* Attempts to acquire the lock using Windows
* This algorithm is much simpler since we can rely on the operating system
*/
static _tryAcquireWindows(resourceFolder, resourceName) {
const lockFilePath = LockFile.getLockFilePath(resourceFolder, resourceName);
let dirtyWhenAcquired = false;
let fileHandle;
let lockFile;
try {
if (FileSystem_1.FileSystem.exists(lockFilePath)) {
dirtyWhenAcquired = true;
// If the lockfile is held by an process with an exclusive lock, then removing it will
// silently fail. OpenSync() below will then fail and we will be unable to create a lock.
// Otherwise, the lockfile is sitting on disk, but nothing is holding it, implying that
// the last process to hold it died.
FileSystem_1.FileSystem.deleteFile(lockFilePath);
}
try {
// Attempt to open an exclusive lockfile
fileHandle = FileWriter_1.FileWriter.open(lockFilePath, { exclusive: true });
}
catch (error) {
// we tried to delete the lock, but something else is holding it,
// (probably an active process), therefore we are unable to create a lock
return undefined;
}
// Ensure we can hand off the file descriptor to the lockfile
lockFile = new LockFile(fileHandle, lockFilePath, dirtyWhenAcquired);
fileHandle = undefined;
}
finally {
if (fileHandle) {
fileHandle.close();
}
}
return lockFile;
}
/**
* Unlocks a file and optionally removes it from disk.
* This can only be called once.
*
* @param deleteFile - Whether to delete the lockfile from disk. Defaults to true.
*/
release(deleteFile = true) {
if (this.isReleased) {
throw new Error(`The lock for file "${path.basename(this._filePath)}" has already been released.`);
}
this._fileWriter.close();
if (deleteFile) {
FileSystem_1.FileSystem.deleteFile(this._filePath);
}
this._fileWriter = undefined;
}
/**
* Returns the initial state of the lock.
* This can be used to detect if the previous process was terminated before releasing the resource.
*/
get dirtyWhenAcquired() {
return this._dirtyWhenAcquired;
}
/**
* Returns the absolute path to the lockfile
*/
get filePath() {
return this._filePath;
}
/**
* Returns true if this lock is currently being held.
*/
get isReleased() {
return this._fileWriter === undefined;
}
}
LockFile._getStartTime = getProcessStartTime;
exports.LockFile = LockFile;
//# sourceMappingURL=LockFile.js.map

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,26 @@
/**
* Helper functions for working with the `Map<K, V>` data type.
*
* @public
*/
export declare class MapExtensions {
/**
* Adds all the (key, value) pairs from the source map into the target map.
* @remarks
* This function modifies targetMap. Any existing keys will be overwritten.
* @param targetMap - The map that entries will be added to
* @param sourceMap - The map containing the entries to be added
*/
static mergeFromMap<K, V>(targetMap: Map<K, V>, sourceMap: ReadonlyMap<K, V>): void;
/**
* Converts a string-keyed map to an object.
* @remarks
* This function has the same effect as Object.fromEntries(map.entries())
* in supported versions of Node (\>= 12.0.0).
* @param map - The map that the object properties will be sourced from
*/
static toObject<TValue>(map: Map<string, TValue>): {
[key: string]: TValue;
};
}
//# sourceMappingURL=MapExtensions.d.ts.map

View File

@ -0,0 +1 @@
{"version":3,"file":"MapExtensions.d.ts","sourceRoot":"","sources":["../src/MapExtensions.ts"],"names":[],"mappings":"AAGA;;;;GAIG;AACH,qBAAa,aAAa;IACxB;;;;;;OAMG;WACW,YAAY,CAAC,CAAC,EAAE,CAAC,EAAE,SAAS,EAAE,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,SAAS,EAAE,WAAW,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,IAAI;IAM1F;;;;;;OAMG;WACW,QAAQ,CAAC,MAAM,EAAE,GAAG,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG;QAAE,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAAA;KAAE;CAOpF"}

View File

@ -0,0 +1,40 @@
"use strict";
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root for license information.
Object.defineProperty(exports, "__esModule", { value: true });
exports.MapExtensions = void 0;
/**
* Helper functions for working with the `Map<K, V>` data type.
*
* @public
*/
class MapExtensions {
/**
* Adds all the (key, value) pairs from the source map into the target map.
* @remarks
* This function modifies targetMap. Any existing keys will be overwritten.
* @param targetMap - The map that entries will be added to
* @param sourceMap - The map containing the entries to be added
*/
static mergeFromMap(targetMap, sourceMap) {
for (const pair of sourceMap.entries()) {
targetMap.set(pair[0], pair[1]);
}
}
/**
* Converts a string-keyed map to an object.
* @remarks
* This function has the same effect as Object.fromEntries(map.entries())
* in supported versions of Node (\>= 12.0.0).
* @param map - The map that the object properties will be sourced from
*/
static toObject(map) {
const object = {};
for (const [key, value] of map.entries()) {
object[key] = value;
}
return object;
}
}
exports.MapExtensions = MapExtensions;
//# sourceMappingURL=MapExtensions.js.map

View File

@ -0,0 +1 @@
{"version":3,"file":"MapExtensions.js","sourceRoot":"","sources":["../src/MapExtensions.ts"],"names":[],"mappings":";AAAA,4FAA4F;AAC5F,2DAA2D;;;AAE3D;;;;GAIG;AACH,MAAa,aAAa;IACxB;;;;;;OAMG;IACI,MAAM,CAAC,YAAY,CAAO,SAAoB,EAAE,SAA4B;QACjF,KAAK,MAAM,IAAI,IAAI,SAAS,CAAC,OAAO,EAAE,EAAE;YACtC,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;SACjC;IACH,CAAC;IAED;;;;;;OAMG;IACI,MAAM,CAAC,QAAQ,CAAS,GAAwB;QACrD,MAAM,MAAM,GAA8B,EAAE,CAAC;QAC7C,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,GAAG,CAAC,OAAO,EAAE,EAAE;YACxC,MAAM,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;SACrB;QACD,OAAO,MAAM,CAAC;IAChB,CAAC;CACF;AA5BD,sCA4BC","sourcesContent":["// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.\n// See LICENSE in the project root for license information.\n\n/**\n * Helper functions for working with the `Map<K, V>` data type.\n *\n * @public\n */\nexport class MapExtensions {\n /**\n * Adds all the (key, value) pairs from the source map into the target map.\n * @remarks\n * This function modifies targetMap. Any existing keys will be overwritten.\n * @param targetMap - The map that entries will be added to\n * @param sourceMap - The map containing the entries to be added\n */\n public static mergeFromMap<K, V>(targetMap: Map<K, V>, sourceMap: ReadonlyMap<K, V>): void {\n for (const pair of sourceMap.entries()) {\n targetMap.set(pair[0], pair[1]);\n }\n }\n\n /**\n * Converts a string-keyed map to an object.\n * @remarks\n * This function has the same effect as Object.fromEntries(map.entries())\n * in supported versions of Node (\\>= 12.0.0).\n * @param map - The map that the object properties will be sourced from\n */\n public static toObject<TValue>(map: Map<string, TValue>): { [key: string]: TValue } {\n const object: { [key: string]: TValue } = {};\n for (const [key, value] of map.entries()) {\n object[key] = value;\n }\n return object;\n }\n}\n"]}

View File

@ -0,0 +1,39 @@
/**
* Implements a standard heap data structure for items of type T and a custom comparator.
* The root will always be the minimum value as determined by the comparator.
*
* @beta
*/
export declare class MinimumHeap<T> {
private readonly _items;
private readonly _comparator;
/**
* Constructs a new MinimumHeap instance.
* @param comparator - a comparator function that determines the order of the items in the heap.
* If the comparator returns a value less than zero, then `a` will be considered less than `b`.
* If the comparator returns zero, then `a` and `b` are considered equal.
* Otherwise, `a` will be considered greater than `b`.
*/
constructor(comparator: (a: T, b: T) => number);
/**
* Returns the number of items in the heap.
* @returns the number of items in the heap.
*/
get size(): number;
/**
* Retrieves the root item from the heap without removing it.
* @returns the root item, or `undefined` if the heap is empty
*/
peek(): T | undefined;
/**
* Retrieves and removes the root item from the heap. The next smallest item will become the new root.
* @returns the root item, or `undefined` if the heap is empty
*/
poll(): T | undefined;
/**
* Pushes an item into the heap.
* @param item - the item to push
*/
push(item: T): void;
}
//# sourceMappingURL=MinimumHeap.d.ts.map

View File

@ -0,0 +1 @@
{"version":3,"file":"MinimumHeap.d.ts","sourceRoot":"","sources":["../src/MinimumHeap.ts"],"names":[],"mappings":"AAGA;;;;;GAKG;AACH,qBAAa,WAAW,CAAC,CAAC;IACxB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAW;IAClC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAyB;IAErD;;;;;;OAMG;gBACgB,UAAU,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,KAAK,MAAM;IAIrD;;;OAGG;IACH,IAAW,IAAI,IAAI,MAAM,CAExB;IAED;;;OAGG;IACI,IAAI,IAAI,CAAC,GAAG,SAAS;IAI5B;;;OAGG;IACI,IAAI,IAAI,CAAC,GAAG,SAAS;IA4C5B;;;OAGG;IACI,IAAI,CAAC,IAAI,EAAE,CAAC,GAAG,IAAI;CAe3B"}

View File

@ -0,0 +1,99 @@
"use strict";
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root for license information.
Object.defineProperty(exports, "__esModule", { value: true });
exports.MinimumHeap = void 0;
/**
* Implements a standard heap data structure for items of type T and a custom comparator.
* The root will always be the minimum value as determined by the comparator.
*
* @beta
*/
class MinimumHeap {
/**
* Constructs a new MinimumHeap instance.
* @param comparator - a comparator function that determines the order of the items in the heap.
* If the comparator returns a value less than zero, then `a` will be considered less than `b`.
* If the comparator returns zero, then `a` and `b` are considered equal.
* Otherwise, `a` will be considered greater than `b`.
*/
constructor(comparator) {
this._items = [];
this._comparator = comparator;
}
/**
* Returns the number of items in the heap.
* @returns the number of items in the heap.
*/
get size() {
return this._items.length;
}
/**
* Retrieves the root item from the heap without removing it.
* @returns the root item, or `undefined` if the heap is empty
*/
peek() {
return this._items[0];
}
/**
* Retrieves and removes the root item from the heap. The next smallest item will become the new root.
* @returns the root item, or `undefined` if the heap is empty
*/
poll() {
if (this.size > 0) {
const result = this._items[0];
const item = this._items.pop();
const size = this.size;
if (size === 0) {
// Short circuit in the trivial case
return result;
}
let index = 0;
let smallerChildIndex = 1;
while (smallerChildIndex < size) {
let smallerChild = this._items[smallerChildIndex];
const rightChildIndex = smallerChildIndex + 1;
if (rightChildIndex < size) {
const rightChild = this._items[rightChildIndex];
if (this._comparator(rightChild, smallerChild) < 0) {
smallerChildIndex = rightChildIndex;
smallerChild = rightChild;
}
}
if (this._comparator(smallerChild, item) < 0) {
this._items[index] = smallerChild;
index = smallerChildIndex;
smallerChildIndex = index * 2 + 1;
}
else {
break;
}
}
// Place the item in its final location satisfying the heap property
this._items[index] = item;
return result;
}
}
/**
* Pushes an item into the heap.
* @param item - the item to push
*/
push(item) {
let index = this.size;
while (index > 0) {
// Due to zero-based indexing the parent is not exactly a bit shift
const parentIndex = ((index + 1) >> 1) - 1;
const parent = this._items[parentIndex];
if (this._comparator(item, parent) < 0) {
this._items[index] = parent;
index = parentIndex;
}
else {
break;
}
}
this._items[index] = item;
}
}
exports.MinimumHeap = MinimumHeap;
//# sourceMappingURL=MinimumHeap.js.map

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,142 @@
import type { IPackageJson, INodePackageJson } from './IPackageJson';
/**
* Constructor parameters for {@link PackageJsonLookup}
*
* @public
*/
export interface IPackageJsonLookupParameters {
/**
* Certain package.json fields such as "contributors" can be very large, and may
* significantly increase the memory footprint for the PackageJsonLookup cache.
* By default, PackageJsonLookup only loads a subset of standard commonly used
* fields names. Set loadExtraFields=true to always return all fields.
*/
loadExtraFields?: boolean;
}
/**
* This class provides methods for finding the nearest "package.json" for a folder
* and retrieving the name of the package. The results are cached.
*
* @public
*/
export declare class PackageJsonLookup {
private static _instance;
/**
* A singleton instance of `PackageJsonLookup`, which is useful for short-lived processes
* that can reasonably assume that the file system will not be modified after the cache
* is populated.
*
* @remarks
* For long-running processes that need to clear the cache at appropriate times,
* it is recommended to create your own instance of `PackageJsonLookup` instead
* of relying on this instance.
*/
static get instance(): PackageJsonLookup;
private _loadExtraFields;
private _packageFolderCache;
private _packageJsonCache;
constructor(parameters?: IPackageJsonLookupParameters);
/**
* A helper for loading the caller's own package.json file.
*
* @remarks
*
* This function provides a concise and efficient way for an NPM package to report metadata about itself.
* For example, a tool might want to report its version.
*
* The `loadOwnPackageJson()` probes upwards from the caller's folder, expecting to find a package.json file,
* which is assumed to be the caller's package. The result is cached, under the assumption that a tool's
* own package.json (and intermediary folders) will never change during the lifetime of the process.
*
* @example
* ```ts
* // Report the version of our NPM package
* const myPackageVersion: string = PackageJsonLookup.loadOwnPackageJson(__dirname).version;
* console.log(`Cool Tool - Version ${myPackageVersion}`);
* ```
*
* @param dirnameOfCaller - The NodeJS `__dirname` macro for the caller.
* @returns This function always returns a valid `IPackageJson` object. If any problems are encountered during
* loading, an exception will be thrown instead.
*/
static loadOwnPackageJson(dirnameOfCaller: string): IPackageJson;
/**
* Clears the internal file cache.
* @remarks
* Call this method if changes have been made to the package.json files on disk.
*/
clearCache(): void;
/**
* Returns the absolute path of a folder containing a package.json file, by looking
* upwards from the specified fileOrFolderPath. If no package.json can be found,
* undefined is returned.
*
* @remarks
* The fileOrFolderPath is not required to actually exist on disk.
* The fileOrFolderPath itself can be the return value, if it is a folder containing
* a package.json file.
* Both positive and negative lookup results are cached.
*
* @param fileOrFolderPath - a relative or absolute path to a source file or folder
* that may be part of a package
* @returns an absolute path to a folder containing a package.json file
*/
tryGetPackageFolderFor(fileOrFolderPath: string): string | undefined;
/**
* If the specified file or folder is part of a package, this returns the absolute path
* to the associated package.json file.
*
* @remarks
* The package folder is determined using the same algorithm
* as {@link PackageJsonLookup.tryGetPackageFolderFor}.
*
* @param fileOrFolderPath - a relative or absolute path to a source file or folder
* that may be part of a package
* @returns an absolute path to * package.json file
*/
tryGetPackageJsonFilePathFor(fileOrFolderPath: string): string | undefined;
/**
* If the specified file or folder is part of a package, this loads and returns the
* associated package.json file.
*
* @remarks
* The package folder is determined using the same algorithm
* as {@link PackageJsonLookup.tryGetPackageFolderFor}.
*
* @param fileOrFolderPath - a relative or absolute path to a source file or folder
* that may be part of a package
* @returns an IPackageJson object, or undefined if the fileOrFolderPath does not
* belong to a package
*/
tryLoadPackageJsonFor(fileOrFolderPath: string): IPackageJson | undefined;
/**
* This function is similar to {@link PackageJsonLookup.tryLoadPackageJsonFor}, except that it does not report
* an error if the `version` field is missing from the package.json file.
*/
tryLoadNodePackageJsonFor(fileOrFolderPath: string): INodePackageJson | undefined;
/**
* Loads the specified package.json file, if it is not already present in the cache.
*
* @remarks
* Unless {@link IPackageJsonLookupParameters.loadExtraFields} was specified,
* the returned IPackageJson object will contain a subset of essential fields.
* The returned object should be considered to be immutable; the caller must never
* modify it.
*
* @param jsonFilename - a relative or absolute path to a package.json file
*/
loadPackageJson(jsonFilename: string): IPackageJson;
/**
* This function is similar to {@link PackageJsonLookup.loadPackageJson}, except that it does not report an error
* if the `version` field is missing from the package.json file.
*/
loadNodePackageJson(jsonFilename: string): INodePackageJson;
private _loadPackageJsonInner;
/**
* Try to load a package.json file as an INodePackageJson,
* returning undefined if the found file does not contain a `name` field.
*/
private _tryLoadNodePackageJsonInner;
private _tryGetPackageFolderFor;
}
//# sourceMappingURL=PackageJsonLookup.d.ts.map

View File

@ -0,0 +1 @@
{"version":3,"file":"PackageJsonLookup.d.ts","sourceRoot":"","sources":["../src/PackageJsonLookup.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAIrE;;;;GAIG;AACH,MAAM,WAAW,4BAA4B;IAC3C;;;;;OAKG;IACH,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B;AA8BD;;;;;GAKG;AACH,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,MAAM,CAAC,SAAS,CAAgC;IAExD;;;;;;;;;OASG;IACH,WAAkB,QAAQ,IAAI,iBAAiB,CAM9C;IAED,OAAO,CAAC,gBAAgB,CAAkB;IAI1C,OAAO,CAAC,mBAAmB,CAAmC;IAI9D,OAAO,CAAC,iBAAiB,CAA6B;gBAEnC,UAAU,CAAC,EAAE,4BAA4B;IAS5D;;;;;;;;;;;;;;;;;;;;;;OAsBG;WACW,kBAAkB,CAAC,eAAe,EAAE,MAAM,GAAG,YAAY;IAuBvE;;;;OAIG;IACI,UAAU,IAAI,IAAI;IAKzB;;;;;;;;;;;;;;OAcG;IACI,sBAAsB,CAAC,gBAAgB,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAiB3E;;;;;;;;;;;OAWG;IACI,4BAA4B,CAAC,gBAAgB,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAQjF;;;;;;;;;;;;OAYG;IACI,qBAAqB,CAAC,gBAAgB,EAAE,MAAM,GAAG,YAAY,GAAG,SAAS;IAQhF;;;OAGG;IACI,yBAAyB,CAAC,gBAAgB,EAAE,MAAM,GAAG,gBAAgB,GAAG,SAAS;IAQxF;;;;;;;;;;OAUG;IACI,eAAe,CAAC,YAAY,EAAE,MAAM,GAAG,YAAY;IAU1D;;;OAGG;IACI,mBAAmB,CAAC,YAAY,EAAE,MAAM,GAAG,gBAAgB;IAIlE,OAAO,CAAC,qBAAqB;IAkC7B;;;OAGG;IACH,OAAO,CAAC,4BAA4B;IAiEpC,OAAO,CAAC,uBAAuB;CAkChC"}

View File

@ -0,0 +1,327 @@
"use strict";
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root 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.PackageJsonLookup = void 0;
const path = __importStar(require("path"));
const JsonFile_1 = require("./JsonFile");
const Constants_1 = require("./Constants");
const FileSystem_1 = require("./FileSystem");
/**
* This class provides methods for finding the nearest "package.json" for a folder
* and retrieving the name of the package. The results are cached.
*
* @public
*/
class PackageJsonLookup {
/**
* A singleton instance of `PackageJsonLookup`, which is useful for short-lived processes
* that can reasonably assume that the file system will not be modified after the cache
* is populated.
*
* @remarks
* For long-running processes that need to clear the cache at appropriate times,
* it is recommended to create your own instance of `PackageJsonLookup` instead
* of relying on this instance.
*/
static get instance() {
if (!PackageJsonLookup._instance) {
PackageJsonLookup._instance = new PackageJsonLookup({ loadExtraFields: true });
}
return PackageJsonLookup._instance;
}
constructor(parameters) {
this._loadExtraFields = false;
if (parameters) {
if (parameters.loadExtraFields) {
this._loadExtraFields = parameters.loadExtraFields;
}
}
this.clearCache();
}
/**
* A helper for loading the caller's own package.json file.
*
* @remarks
*
* This function provides a concise and efficient way for an NPM package to report metadata about itself.
* For example, a tool might want to report its version.
*
* The `loadOwnPackageJson()` probes upwards from the caller's folder, expecting to find a package.json file,
* which is assumed to be the caller's package. The result is cached, under the assumption that a tool's
* own package.json (and intermediary folders) will never change during the lifetime of the process.
*
* @example
* ```ts
* // Report the version of our NPM package
* const myPackageVersion: string = PackageJsonLookup.loadOwnPackageJson(__dirname).version;
* console.log(`Cool Tool - Version ${myPackageVersion}`);
* ```
*
* @param dirnameOfCaller - The NodeJS `__dirname` macro for the caller.
* @returns This function always returns a valid `IPackageJson` object. If any problems are encountered during
* loading, an exception will be thrown instead.
*/
static loadOwnPackageJson(dirnameOfCaller) {
const packageJson = PackageJsonLookup.instance.tryLoadPackageJsonFor(dirnameOfCaller);
if (packageJson === undefined) {
throw new Error(`PackageJsonLookup.loadOwnPackageJson() failed to find the caller's package.json.` +
` The __dirname was: ${dirnameOfCaller}`);
}
if (packageJson.version !== undefined) {
return packageJson;
}
const errorPath = PackageJsonLookup.instance.tryGetPackageJsonFilePathFor(dirnameOfCaller) || 'package.json';
throw new Error(`PackageJsonLookup.loadOwnPackageJson() failed because the "version" field is missing in` +
` ${errorPath}`);
}
/**
* Clears the internal file cache.
* @remarks
* Call this method if changes have been made to the package.json files on disk.
*/
clearCache() {
this._packageFolderCache = new Map();
this._packageJsonCache = new Map();
}
/**
* Returns the absolute path of a folder containing a package.json file, by looking
* upwards from the specified fileOrFolderPath. If no package.json can be found,
* undefined is returned.
*
* @remarks
* The fileOrFolderPath is not required to actually exist on disk.
* The fileOrFolderPath itself can be the return value, if it is a folder containing
* a package.json file.
* Both positive and negative lookup results are cached.
*
* @param fileOrFolderPath - a relative or absolute path to a source file or folder
* that may be part of a package
* @returns an absolute path to a folder containing a package.json file
*/
tryGetPackageFolderFor(fileOrFolderPath) {
// Convert it to an absolute path
const resolvedFileOrFolderPath = path.resolve(fileOrFolderPath);
// Optimistically hope that the starting string is already in the cache,
// in which case we can avoid disk access entirely.
//
// (Two lookups are required, because get() cannot distinguish the undefined value
// versus a missing key.)
if (this._packageFolderCache.has(resolvedFileOrFolderPath)) {
return this._packageFolderCache.get(resolvedFileOrFolderPath);
}
// Now call the recursive part of the algorithm
return this._tryGetPackageFolderFor(resolvedFileOrFolderPath);
}
/**
* If the specified file or folder is part of a package, this returns the absolute path
* to the associated package.json file.
*
* @remarks
* The package folder is determined using the same algorithm
* as {@link PackageJsonLookup.tryGetPackageFolderFor}.
*
* @param fileOrFolderPath - a relative or absolute path to a source file or folder
* that may be part of a package
* @returns an absolute path to * package.json file
*/
tryGetPackageJsonFilePathFor(fileOrFolderPath) {
const packageJsonFolder = this.tryGetPackageFolderFor(fileOrFolderPath);
if (!packageJsonFolder) {
return undefined;
}
return path.join(packageJsonFolder, Constants_1.FileConstants.PackageJson);
}
/**
* If the specified file or folder is part of a package, this loads and returns the
* associated package.json file.
*
* @remarks
* The package folder is determined using the same algorithm
* as {@link PackageJsonLookup.tryGetPackageFolderFor}.
*
* @param fileOrFolderPath - a relative or absolute path to a source file or folder
* that may be part of a package
* @returns an IPackageJson object, or undefined if the fileOrFolderPath does not
* belong to a package
*/
tryLoadPackageJsonFor(fileOrFolderPath) {
const packageJsonFilePath = this.tryGetPackageJsonFilePathFor(fileOrFolderPath);
if (!packageJsonFilePath) {
return undefined;
}
return this.loadPackageJson(packageJsonFilePath);
}
/**
* This function is similar to {@link PackageJsonLookup.tryLoadPackageJsonFor}, except that it does not report
* an error if the `version` field is missing from the package.json file.
*/
tryLoadNodePackageJsonFor(fileOrFolderPath) {
const packageJsonFilePath = this.tryGetPackageJsonFilePathFor(fileOrFolderPath);
if (!packageJsonFilePath) {
return undefined;
}
return this.loadNodePackageJson(packageJsonFilePath);
}
/**
* Loads the specified package.json file, if it is not already present in the cache.
*
* @remarks
* Unless {@link IPackageJsonLookupParameters.loadExtraFields} was specified,
* the returned IPackageJson object will contain a subset of essential fields.
* The returned object should be considered to be immutable; the caller must never
* modify it.
*
* @param jsonFilename - a relative or absolute path to a package.json file
*/
loadPackageJson(jsonFilename) {
const packageJson = this.loadNodePackageJson(jsonFilename);
if (!packageJson.version) {
throw new Error(`Error reading "${jsonFilename}":\n The required field "version" was not found`);
}
return packageJson;
}
/**
* This function is similar to {@link PackageJsonLookup.loadPackageJson}, except that it does not report an error
* if the `version` field is missing from the package.json file.
*/
loadNodePackageJson(jsonFilename) {
return this._loadPackageJsonInner(jsonFilename);
}
_loadPackageJsonInner(jsonFilename, errorsToIgnore) {
const loadResult = this._tryLoadNodePackageJsonInner(jsonFilename);
if (loadResult.error && (errorsToIgnore === null || errorsToIgnore === void 0 ? void 0 : errorsToIgnore.has(loadResult.error))) {
return undefined;
}
switch (loadResult.error) {
case 'FILE_NOT_FOUND': {
throw new Error(`Input file not found: ${jsonFilename}`);
}
case 'MISSING_NAME_FIELD': {
throw new Error(`Error reading "${jsonFilename}":\n The required field "name" was not found`);
}
case 'OTHER_ERROR': {
throw loadResult.errorObject;
}
default: {
return loadResult.packageJson;
}
}
}
/**
* Try to load a package.json file as an INodePackageJson,
* returning undefined if the found file does not contain a `name` field.
*/
_tryLoadNodePackageJsonInner(jsonFilename) {
// Since this will be a cache key, follow any symlinks and get an absolute path
// to minimize duplication. (Note that duplication can still occur due to e.g. character case.)
let normalizedFilePath;
try {
normalizedFilePath = FileSystem_1.FileSystem.getRealPath(jsonFilename);
}
catch (e) {
if (FileSystem_1.FileSystem.isNotExistError(e)) {
return {
error: 'FILE_NOT_FOUND'
};
}
else {
return {
error: 'OTHER_ERROR',
errorObject: e
};
}
}
let packageJson = this._packageJsonCache.get(normalizedFilePath);
if (!packageJson) {
const loadedPackageJson = JsonFile_1.JsonFile.load(normalizedFilePath);
// Make sure this is really a package.json file. CommonJS has fairly strict requirements,
// but NPM only requires "name" and "version"
if (!loadedPackageJson.name) {
return {
error: 'MISSING_NAME_FIELD'
};
}
if (this._loadExtraFields) {
packageJson = loadedPackageJson;
}
else {
packageJson = {};
// Unless "loadExtraFields" was requested, copy over the essential fields only
packageJson.bin = loadedPackageJson.bin;
packageJson.dependencies = loadedPackageJson.dependencies;
packageJson.description = loadedPackageJson.description;
packageJson.devDependencies = loadedPackageJson.devDependencies;
packageJson.homepage = loadedPackageJson.homepage;
packageJson.license = loadedPackageJson.license;
packageJson.main = loadedPackageJson.main;
packageJson.name = loadedPackageJson.name;
packageJson.optionalDependencies = loadedPackageJson.optionalDependencies;
packageJson.peerDependencies = loadedPackageJson.peerDependencies;
packageJson.private = loadedPackageJson.private;
packageJson.scripts = loadedPackageJson.scripts;
packageJson.typings = loadedPackageJson.typings || loadedPackageJson.types;
packageJson.tsdocMetadata = loadedPackageJson.tsdocMetadata;
packageJson.version = loadedPackageJson.version;
}
Object.freeze(packageJson);
this._packageJsonCache.set(normalizedFilePath, packageJson);
}
return {
packageJson
};
}
// Recursive part of the algorithm from tryGetPackageFolderFor()
_tryGetPackageFolderFor(resolvedFileOrFolderPath) {
// Two lookups are required, because get() cannot distinguish the undefined value
// versus a missing key.
if (this._packageFolderCache.has(resolvedFileOrFolderPath)) {
return this._packageFolderCache.get(resolvedFileOrFolderPath);
}
// Is resolvedFileOrFolderPath itself a folder with a valid package.json file? If so, return it.
const packageJsonFilePath = `${resolvedFileOrFolderPath}/${Constants_1.FileConstants.PackageJson}`;
const packageJson = this._loadPackageJsonInner(packageJsonFilePath, new Set(['FILE_NOT_FOUND', 'MISSING_NAME_FIELD']));
if (packageJson) {
this._packageFolderCache.set(resolvedFileOrFolderPath, resolvedFileOrFolderPath);
return resolvedFileOrFolderPath;
}
// Otherwise go up one level
const parentFolder = path.dirname(resolvedFileOrFolderPath);
if (!parentFolder || parentFolder === resolvedFileOrFolderPath) {
// We reached the root directory without finding a package.json file,
// so cache the negative result
this._packageFolderCache.set(resolvedFileOrFolderPath, undefined);
return undefined; // no match
}
// Recurse upwards, caching every step along the way
const parentResult = this._tryGetPackageFolderFor(parentFolder);
// Cache the parent's answer as well
this._packageFolderCache.set(resolvedFileOrFolderPath, parentResult);
return parentResult;
}
}
exports.PackageJsonLookup = PackageJsonLookup;
//# sourceMappingURL=PackageJsonLookup.js.map

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,142 @@
/**
* A package name that has been separated into its scope and unscoped name.
*
* @public
*/
export interface IParsedPackageName {
/**
* The parsed NPM scope, or an empty string if there was no scope. The scope value will
* always include the at-sign.
* @remarks
* For example, if the parsed input was "\@scope/example", then scope would be "\@scope".
*/
scope: string;
/**
* The parsed NPM package name without the scope.
* @remarks
* For example, if the parsed input was "\@scope/example", then the name would be "example".
*/
unscopedName: string;
}
/**
* Result object returned by {@link PackageName.tryParse}
*
* @public
*/
export interface IParsedPackageNameOrError extends IParsedPackageName {
/**
* If the input string could not be parsed, then this string will contain a nonempty
* error message. Otherwise it will be an empty string.
*/
error: string;
}
/**
* Options that configure the validation rules used by a {@link PackageNameParser} instance.
*
* @remarks
* The default validation is based on the npmjs.com registry's policy for published packages, and includes these
* restrictions:
*
* - The package name cannot be longer than 214 characters.
*
* - The package name must not be empty.
*
* - Other than the `@` and `/` delimiters used for scopes, the only allowed characters
* are letters, numbers, `-`, `_`, and `.`.
*
* - The name must not start with a `.` or `_`.
*
* @public
*/
export interface IPackageNameParserOptions {
/**
* If true, allows upper-case letters in package names.
* This improves compatibility with some legacy private registries that still allow that.
*/
allowUpperCase?: boolean;
}
/**
* A configurable parser for validating and manipulating NPM package names such as `my-package` or `@scope/my-package`.
*
* @remarks
* If you do not need to customize the parser configuration, it is recommended to use {@link PackageName}
* which exposes these operations as a simple static class.
*
* @public
*/
export declare class PackageNameParser {
private static readonly _invalidNameCharactersRegExp;
private readonly _options;
constructor(options?: IPackageNameParserOptions);
/**
* This attempts to parse a package name that may include a scope component.
* The packageName must not be an empty string.
* @remarks
* This function will not throw an exception.
*
* @returns an {@link IParsedPackageNameOrError} structure whose `error` property will be
* nonempty if the string could not be parsed.
*/
tryParse(packageName: string): IParsedPackageNameOrError;
/**
* Same as {@link PackageName.tryParse}, except this throws an exception if the input
* cannot be parsed.
* @remarks
* The packageName must not be an empty string.
*/
parse(packageName: string): IParsedPackageName;
/**
* {@inheritDoc IParsedPackageName.scope}
*/
getScope(packageName: string): string;
/**
* {@inheritDoc IParsedPackageName.unscopedName}
*/
getUnscopedName(packageName: string): string;
/**
* Returns true if the specified package name is valid, or false otherwise.
* @remarks
* This function will not throw an exception.
*/
isValidName(packageName: string): boolean;
/**
* Throws an exception if the specified name is not a valid package name.
* The packageName must not be an empty string.
*/
validate(packageName: string): void;
/**
* Combines an optional package scope with an unscoped root name.
* @param scope - Must be either an empty string, or a scope name such as "\@example"
* @param unscopedName - Must be a nonempty package name that does not contain a scope
* @returns A full package name such as "\@example/some-library".
*/
combineParts(scope: string, unscopedName: string): string;
}
/**
* Provides basic operations for validating and manipulating NPM package names such as `my-package`
* or `@scope/my-package`.
*
* @remarks
* This is the default implementation of {@link PackageNameParser}, exposed as a convenient static class.
* If you need to configure the parsing rules, use `PackageNameParser` instead.
*
* @public
*/
export declare class PackageName {
private static readonly _parser;
/** {@inheritDoc PackageNameParser.tryParse} */
static tryParse(packageName: string): IParsedPackageNameOrError;
/** {@inheritDoc PackageNameParser.parse} */
static parse(packageName: string): IParsedPackageName;
/** {@inheritDoc PackageNameParser.getScope} */
static getScope(packageName: string): string;
/** {@inheritDoc PackageNameParser.getUnscopedName} */
static getUnscopedName(packageName: string): string;
/** {@inheritDoc PackageNameParser.isValidName} */
static isValidName(packageName: string): boolean;
/** {@inheritDoc PackageNameParser.validate} */
static validate(packageName: string): void;
/** {@inheritDoc PackageNameParser.combineParts} */
static combineParts(scope: string, unscopedName: string): string;
}
//# sourceMappingURL=PackageName.d.ts.map

View File

@ -0,0 +1 @@
{"version":3,"file":"PackageName.d.ts","sourceRoot":"","sources":["../src/PackageName.ts"],"names":[],"mappings":"AAGA;;;;GAIG;AACH,MAAM,WAAW,kBAAkB;IACjC;;;;;OAKG;IACH,KAAK,EAAE,MAAM,CAAC;IAEd;;;;OAIG;IACH,YAAY,EAAE,MAAM,CAAC;CACtB;AAED;;;;GAIG;AACH,MAAM,WAAW,yBAA0B,SAAQ,kBAAkB;IACnE;;;OAGG;IACH,KAAK,EAAE,MAAM,CAAC;CACf;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,WAAW,yBAAyB;IACxC;;;OAGG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED;;;;;;;;GAQG;AACH,qBAAa,iBAAiB;IAG5B,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,4BAA4B,CAA+B;IAEnF,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAA4B;gBAElC,OAAO,GAAE,yBAA8B;IAI1D;;;;;;;;OAQG;IACI,QAAQ,CAAC,WAAW,EAAE,MAAM,GAAG,yBAAyB;IAmF/D;;;;;OAKG;IACI,KAAK,CAAC,WAAW,EAAE,MAAM,GAAG,kBAAkB;IAQrD;;OAEG;IACI,QAAQ,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM;IAI5C;;OAEG;IACI,eAAe,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM;IAInD;;;;OAIG;IACI,WAAW,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO;IAKhD;;;OAGG;IACI,QAAQ,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI;IAI1C;;;;;OAKG;IACI,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,MAAM;CA6BjE;AAED;;;;;;;;;GASG;AACH,qBAAa,WAAW;IACtB,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,OAAO,CAA8C;IAE7E,+CAA+C;WACjC,QAAQ,CAAC,WAAW,EAAE,MAAM,GAAG,yBAAyB;IAItE,4CAA4C;WAC9B,KAAK,CAAC,WAAW,EAAE,MAAM,GAAG,kBAAkB;IAI5D,+CAA+C;WACjC,QAAQ,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM;IAInD,sDAAsD;WACxC,eAAe,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM;IAI1D,kDAAkD;WACpC,WAAW,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO;IAIvD,+CAA+C;WACjC,QAAQ,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI;IAIjD,mDAAmD;WACrC,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,MAAM;CAGxE"}

View File

@ -0,0 +1,213 @@
"use strict";
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root for license information.
Object.defineProperty(exports, "__esModule", { value: true });
exports.PackageName = exports.PackageNameParser = void 0;
/**
* A configurable parser for validating and manipulating NPM package names such as `my-package` or `@scope/my-package`.
*
* @remarks
* If you do not need to customize the parser configuration, it is recommended to use {@link PackageName}
* which exposes these operations as a simple static class.
*
* @public
*/
class PackageNameParser {
constructor(options = {}) {
this._options = Object.assign({}, options);
}
/**
* This attempts to parse a package name that may include a scope component.
* The packageName must not be an empty string.
* @remarks
* This function will not throw an exception.
*
* @returns an {@link IParsedPackageNameOrError} structure whose `error` property will be
* nonempty if the string could not be parsed.
*/
tryParse(packageName) {
const result = {
scope: '',
unscopedName: '',
error: ''
};
let input = packageName;
if (input === null || input === undefined) {
result.error = 'The package name must not be null or undefined';
return result;
}
// Rule from npmjs.com:
// "The name must be less than or equal to 214 characters. This includes the scope for scoped packages."
if (packageName.length > 214) {
// Don't attempt to parse a ridiculously long input
result.error = 'The package name cannot be longer than 214 characters';
return result;
}
if (input[0] === '@') {
const indexOfScopeSlash = input.indexOf('/');
if (indexOfScopeSlash <= 0) {
result.scope = input;
result.error = `Error parsing "${packageName}": The scope must be followed by a slash`;
return result;
}
// Extract the scope substring
result.scope = input.substr(0, indexOfScopeSlash);
input = input.substr(indexOfScopeSlash + 1);
}
result.unscopedName = input;
if (result.scope === '@') {
result.error = `Error parsing "${packageName}": The scope name cannot be empty`;
return result;
}
if (result.unscopedName === '') {
result.error = 'The package name must not be empty';
return result;
}
// Rule from npmjs.com:
// "The name can't start with a dot or an underscore."
if (result.unscopedName[0] === '.' || result.unscopedName[0] === '_') {
result.error = `The package name "${packageName}" starts with an invalid character`;
return result;
}
// Convert "@scope/unscoped-name" --> "scopeunscoped-name"
const nameWithoutScopeSymbols = (result.scope ? result.scope.slice(1, -1) : '') + result.unscopedName;
if (!this._options.allowUpperCase) {
// "New packages must not have uppercase letters in the name."
// This can't be enforced because "old" packages are still actively maintained.
// Example: https://www.npmjs.com/package/Base64
// However it's pretty reasonable to require the scope to be lower case
if (result.scope !== result.scope.toLowerCase()) {
result.error = `The package scope "${result.scope}" must not contain upper case characters`;
return result;
}
}
// "The name ends up being part of a URL, an argument on the command line, and a folder name.
// Therefore, the name can't contain any non-URL-safe characters"
const match = nameWithoutScopeSymbols.match(PackageNameParser._invalidNameCharactersRegExp);
if (match) {
result.error = `The package name "${packageName}" contains an invalid character: "${match[0]}"`;
return result;
}
return result;
}
/**
* Same as {@link PackageName.tryParse}, except this throws an exception if the input
* cannot be parsed.
* @remarks
* The packageName must not be an empty string.
*/
parse(packageName) {
const result = this.tryParse(packageName);
if (result.error) {
throw new Error(result.error);
}
return result;
}
/**
* {@inheritDoc IParsedPackageName.scope}
*/
getScope(packageName) {
return this.parse(packageName).scope;
}
/**
* {@inheritDoc IParsedPackageName.unscopedName}
*/
getUnscopedName(packageName) {
return this.parse(packageName).unscopedName;
}
/**
* Returns true if the specified package name is valid, or false otherwise.
* @remarks
* This function will not throw an exception.
*/
isValidName(packageName) {
const result = this.tryParse(packageName);
return !result.error;
}
/**
* Throws an exception if the specified name is not a valid package name.
* The packageName must not be an empty string.
*/
validate(packageName) {
this.parse(packageName);
}
/**
* Combines an optional package scope with an unscoped root name.
* @param scope - Must be either an empty string, or a scope name such as "\@example"
* @param unscopedName - Must be a nonempty package name that does not contain a scope
* @returns A full package name such as "\@example/some-library".
*/
combineParts(scope, unscopedName) {
if (scope !== '') {
if (scope[0] !== '@') {
throw new Error('The scope must start with an "@" character');
}
}
if (scope.indexOf('/') >= 0) {
throw new Error('The scope must not contain a "/" character');
}
if (unscopedName[0] === '@') {
throw new Error('The unscopedName cannot start with an "@" character');
}
if (unscopedName.indexOf('/') >= 0) {
throw new Error('The unscopedName must not contain a "/" character');
}
let result;
if (scope === '') {
result = unscopedName;
}
else {
result = scope + '/' + unscopedName;
}
// Make sure the result is a valid package name
this.validate(result);
return result;
}
}
// encodeURIComponent() escapes all characters except: A-Z a-z 0-9 - _ . ! ~ * ' ( )
// However, these are disallowed because they are shell characters: ! ~ * ' ( )
PackageNameParser._invalidNameCharactersRegExp = /[^A-Za-z0-9\-_\.]/;
exports.PackageNameParser = PackageNameParser;
/**
* Provides basic operations for validating and manipulating NPM package names such as `my-package`
* or `@scope/my-package`.
*
* @remarks
* This is the default implementation of {@link PackageNameParser}, exposed as a convenient static class.
* If you need to configure the parsing rules, use `PackageNameParser` instead.
*
* @public
*/
class PackageName {
/** {@inheritDoc PackageNameParser.tryParse} */
static tryParse(packageName) {
return PackageName._parser.tryParse(packageName);
}
/** {@inheritDoc PackageNameParser.parse} */
static parse(packageName) {
return this._parser.parse(packageName);
}
/** {@inheritDoc PackageNameParser.getScope} */
static getScope(packageName) {
return this._parser.getScope(packageName);
}
/** {@inheritDoc PackageNameParser.getUnscopedName} */
static getUnscopedName(packageName) {
return this._parser.getUnscopedName(packageName);
}
/** {@inheritDoc PackageNameParser.isValidName} */
static isValidName(packageName) {
return this._parser.isValidName(packageName);
}
/** {@inheritDoc PackageNameParser.validate} */
static validate(packageName) {
return this._parser.validate(packageName);
}
/** {@inheritDoc PackageNameParser.combineParts} */
static combineParts(scope, unscopedName) {
return this._parser.combineParts(scope, unscopedName);
}
}
PackageName._parser = new PackageNameParser();
exports.PackageName = PackageName;
//# sourceMappingURL=PackageName.js.map

File diff suppressed because one or more lines are too long

155
node_modules/@rushstack/node-core-library/lib/Path.d.ts generated vendored Normal file
View File

@ -0,0 +1,155 @@
/**
* The format that the FileError message should conform to. The supported formats are:
* - Unix: `<path>:<line>:<column> - <message>`
* - VisualStudio: `<path>(<line>,<column>) - <message>`
*
* @public
*/
export type FileLocationStyle = 'Unix' | 'VisualStudio';
/**
* Options for {@link Path.formatFileLocation}.
* @public
*/
export interface IPathFormatFileLocationOptions {
/**
* The base path to use when converting `pathToFormat` to a relative path. If not specified,
* `pathToFormat` will be used as-is.
*/
baseFolder?: string;
/**
* The path that will be used to specify the file location.
*/
pathToFormat: string;
/**
* The message related to the file location.
*/
message: string;
/**
* The style of file location formatting to use.
*/
format: FileLocationStyle;
/**
* The optional line number. If not specified, the line number will not be included
* in the formatted string.
*/
line?: number;
/**
* The optional column number. If not specified, the column number will not be included
* in the formatted string.
*/
column?: number;
}
/**
* Options for {@link Path.formatConcisely}.
* @public
*/
export interface IPathFormatConciselyOptions {
/**
* The path to be converted.
*/
pathToConvert: string;
/**
* The base path to use when converting `pathToConvert` to a relative path.
*/
baseFolder: string;
/**
* If set to true, don't include the leading `./` if the path is under the base folder.
*/
trimLeadingDotSlash?: boolean;
}
/**
* Common operations for manipulating file and directory paths.
* @remarks
* This API is intended to eventually be a complete replacement for the NodeJS "path" API.
* @public
*/
export declare class Path {
private static _relativePathRegex;
private static _upwardPathSegmentRegex;
/**
* Returns true if "childPath" is located inside the "parentFolderPath" folder
* or one of its child folders. Note that "parentFolderPath" is not considered to be
* under itself. The "childPath" can refer to any type of file system object.
*
* @remarks
* The indicated file/folder objects are not required to actually exist on disk.
* For example, "parentFolderPath" is interpreted as a folder name even if it refers to a file.
* If the paths are relative, they will first be resolved using path.resolve().
*/
static isUnder(childPath: string, parentFolderPath: string): boolean;
/**
* Returns true if "childPath" is equal to "parentFolderPath", or if it is inside that folder
* or one of its children. The "childPath" can refer to any type of file system object.
*
* @remarks
* The indicated file/folder objects are not required to actually exist on disk.
* For example, "parentFolderPath" is interpreted as a folder name even if it refers to a file.
* If the paths are relative, they will first be resolved using path.resolve().
*/
static isUnderOrEqual(childPath: string, parentFolderPath: string): boolean;
/**
* Returns true if `path1` and `path2` refer to the same underlying path.
*
* @remarks
*
* The comparison is performed using `path.relative()`.
*/
static isEqual(path1: string, path2: string): boolean;
/**
* Formats a path to look nice for reporting purposes.
* @remarks
* If `pathToConvert` is under the `baseFolder`, then it will be converted to a relative with the `./` prefix
* unless the {@link IPathFormatConciselyOptions.trimLeadingDotSlash} option is set to `true`.
* Otherwise, it will be converted to an absolute path.
*
* Backslashes will be converted to slashes, unless the path starts with an OS-specific string like `C:\`.
*/
static formatConcisely(options: IPathFormatConciselyOptions): string;
/**
* Formats a file location to look nice for reporting purposes.
* @remarks
* If `pathToFormat` is under the `baseFolder`, then it will be converted to a relative with the `./` prefix.
* Otherwise, it will be converted to an absolute path.
*
* Backslashes will be converted to slashes, unless the path starts with an OS-specific string like `C:\`.
*/
static formatFileLocation(options: IPathFormatFileLocationOptions): string;
/**
* Replaces Windows-style backslashes with POSIX-style slashes.
*
* @remarks
* POSIX is a registered trademark of the Institute of Electrical and Electronic Engineers, Inc.
*/
static convertToSlashes(inputPath: string): string;
/**
* Replaces POSIX-style slashes with Windows-style backslashes
*
* @remarks
* POSIX is a registered trademark of the Institute of Electrical and Electronic Engineers, Inc.
*/
static convertToBackslashes(inputPath: string): string;
/**
* Replaces slashes or backslashes with the appropriate slash for the current operating system.
*/
static convertToPlatformDefault(inputPath: string): string;
/**
* Returns true if the specified path is a relative path and does not use `..` to walk upwards.
*
* @example
* ```ts
* // These evaluate to true
* isDownwardRelative('folder');
* isDownwardRelative('file');
* isDownwardRelative('folder/');
* isDownwardRelative('./folder/');
* isDownwardRelative('./folder/file');
*
* // These evaluate to false
* isDownwardRelative('../folder');
* isDownwardRelative('folder/../file');
* isDownwardRelative('/folder/file');
* ```
*/
static isDownwardRelative(inputPath: string): boolean;
}
//# sourceMappingURL=Path.d.ts.map

View File

@ -0,0 +1 @@
{"version":3,"file":"Path.d.ts","sourceRoot":"","sources":["../src/Path.ts"],"names":[],"mappings":"AAKA;;;;;;GAMG;AACH,MAAM,MAAM,iBAAiB,GAAG,MAAM,GAAG,cAAc,CAAC;AAExD;;;GAGG;AACH,MAAM,WAAW,8BAA8B;IAC7C;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;OAEG;IACH,YAAY,EAAE,MAAM,CAAC;IACrB;;OAEG;IACH,OAAO,EAAE,MAAM,CAAC;IAChB;;OAEG;IACH,MAAM,EAAE,iBAAiB,CAAC;IAC1B;;;OAGG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;;GAGG;AACH,MAAM,WAAW,2BAA2B;IAC1C;;OAEG;IACH,aAAa,EAAE,MAAM,CAAC;IAEtB;;OAEG;IACH,UAAU,EAAE,MAAM,CAAC;IAEnB;;OAEG;IACH,mBAAmB,CAAC,EAAE,OAAO,CAAC;CAC/B;AAED;;;;;GAKG;AACH,qBAAa,IAAI;IAGf,OAAO,CAAC,MAAM,CAAC,kBAAkB,CAAwB;IAIzD,OAAO,CAAC,MAAM,CAAC,uBAAuB,CAAsC;IAE5E;;;;;;;;;OASG;WACW,OAAO,CAAC,SAAS,EAAE,MAAM,EAAE,gBAAgB,EAAE,MAAM,GAAG,OAAO;IAQ3E;;;;;;;;OAQG;WACW,cAAc,CAAC,SAAS,EAAE,MAAM,EAAE,gBAAgB,EAAE,MAAM,GAAG,OAAO;IAKlF;;;;;;OAMG;WACW,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO;IAI5D;;;;;;;;OAQG;WACW,eAAe,CAAC,OAAO,EAAE,2BAA2B,GAAG,MAAM;IAqB3E;;;;;;;OAOG;WACW,kBAAkB,CAAC,OAAO,EAAE,8BAA8B,GAAG,MAAM;IA+CjF;;;;;OAKG;WACW,gBAAgB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM;IAIzD;;;;;OAKG;WACW,oBAAoB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM;IAG7D;;OAEG;WACW,wBAAwB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM;IAIjE;;;;;;;;;;;;;;;;;OAiBG;WACW,kBAAkB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO;CAU7D"}

214
node_modules/@rushstack/node-core-library/lib/Path.js generated vendored Normal file
View File

@ -0,0 +1,214 @@
"use strict";
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root 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.Path = void 0;
const path = __importStar(require("path"));
/**
* Common operations for manipulating file and directory paths.
* @remarks
* This API is intended to eventually be a complete replacement for the NodeJS "path" API.
* @public
*/
class Path {
/**
* Returns true if "childPath" is located inside the "parentFolderPath" folder
* or one of its child folders. Note that "parentFolderPath" is not considered to be
* under itself. The "childPath" can refer to any type of file system object.
*
* @remarks
* The indicated file/folder objects are not required to actually exist on disk.
* For example, "parentFolderPath" is interpreted as a folder name even if it refers to a file.
* If the paths are relative, they will first be resolved using path.resolve().
*/
static isUnder(childPath, parentFolderPath) {
// If childPath is under parentPath, then relativePath will be something like
// "../.." or "..\\..", which consists entirely of periods and slashes.
// (Note that something like "....t" is actually a valid filename, but "...." is not.)
const relativePath = path.relative(childPath, parentFolderPath);
return Path._relativePathRegex.test(relativePath);
}
/**
* Returns true if "childPath" is equal to "parentFolderPath", or if it is inside that folder
* or one of its children. The "childPath" can refer to any type of file system object.
*
* @remarks
* The indicated file/folder objects are not required to actually exist on disk.
* For example, "parentFolderPath" is interpreted as a folder name even if it refers to a file.
* If the paths are relative, they will first be resolved using path.resolve().
*/
static isUnderOrEqual(childPath, parentFolderPath) {
const relativePath = path.relative(childPath, parentFolderPath);
return relativePath === '' || Path._relativePathRegex.test(relativePath);
}
/**
* Returns true if `path1` and `path2` refer to the same underlying path.
*
* @remarks
*
* The comparison is performed using `path.relative()`.
*/
static isEqual(path1, path2) {
return path.relative(path1, path2) === '';
}
/**
* Formats a path to look nice for reporting purposes.
* @remarks
* If `pathToConvert` is under the `baseFolder`, then it will be converted to a relative with the `./` prefix
* unless the {@link IPathFormatConciselyOptions.trimLeadingDotSlash} option is set to `true`.
* Otherwise, it will be converted to an absolute path.
*
* Backslashes will be converted to slashes, unless the path starts with an OS-specific string like `C:\`.
*/
static formatConcisely(options) {
// Same logic as Path.isUnderOrEqual()
const relativePath = path.relative(options.pathToConvert, options.baseFolder);
const isUnderOrEqual = relativePath === '' || Path._relativePathRegex.test(relativePath);
if (isUnderOrEqual) {
// Note that isUnderOrEqual()'s relativePath is the reverse direction
const convertedPath = Path.convertToSlashes(path.relative(options.baseFolder, options.pathToConvert));
if (options.trimLeadingDotSlash) {
return convertedPath;
}
else {
return `./${convertedPath}`;
}
}
const absolutePath = path.resolve(options.pathToConvert);
return absolutePath;
}
/**
* Formats a file location to look nice for reporting purposes.
* @remarks
* If `pathToFormat` is under the `baseFolder`, then it will be converted to a relative with the `./` prefix.
* Otherwise, it will be converted to an absolute path.
*
* Backslashes will be converted to slashes, unless the path starts with an OS-specific string like `C:\`.
*/
static formatFileLocation(options) {
const { message, format, pathToFormat, baseFolder, line, column } = options;
// Convert the path to be relative to the base folder, if specified. Otherwise, use
// the path as-is.
const filePath = baseFolder
? Path.formatConcisely({
pathToConvert: pathToFormat,
baseFolder,
trimLeadingDotSlash: true
})
: path.resolve(pathToFormat);
let formattedFileLocation;
switch (format) {
case 'Unix': {
if (line !== undefined && column !== undefined) {
formattedFileLocation = `:${line}:${column}`;
}
else if (line !== undefined) {
formattedFileLocation = `:${line}`;
}
else {
formattedFileLocation = '';
}
break;
}
case 'VisualStudio': {
if (line !== undefined && column !== undefined) {
formattedFileLocation = `(${line},${column})`;
}
else if (line !== undefined) {
formattedFileLocation = `(${line})`;
}
else {
formattedFileLocation = '';
}
break;
}
default: {
throw new Error(`Unknown format: ${format}`);
}
}
return `${filePath}${formattedFileLocation} - ${message}`;
}
/**
* Replaces Windows-style backslashes with POSIX-style slashes.
*
* @remarks
* POSIX is a registered trademark of the Institute of Electrical and Electronic Engineers, Inc.
*/
static convertToSlashes(inputPath) {
return inputPath.replace(/\\/g, '/');
}
/**
* Replaces POSIX-style slashes with Windows-style backslashes
*
* @remarks
* POSIX is a registered trademark of the Institute of Electrical and Electronic Engineers, Inc.
*/
static convertToBackslashes(inputPath) {
return inputPath.replace(/\//g, '\\');
}
/**
* Replaces slashes or backslashes with the appropriate slash for the current operating system.
*/
static convertToPlatformDefault(inputPath) {
return path.sep === '/' ? Path.convertToSlashes(inputPath) : Path.convertToBackslashes(inputPath);
}
/**
* Returns true if the specified path is a relative path and does not use `..` to walk upwards.
*
* @example
* ```ts
* // These evaluate to true
* isDownwardRelative('folder');
* isDownwardRelative('file');
* isDownwardRelative('folder/');
* isDownwardRelative('./folder/');
* isDownwardRelative('./folder/file');
*
* // These evaluate to false
* isDownwardRelative('../folder');
* isDownwardRelative('folder/../file');
* isDownwardRelative('/folder/file');
* ```
*/
static isDownwardRelative(inputPath) {
if (path.isAbsolute(inputPath)) {
return false;
}
// Does it contain ".."
if (Path._upwardPathSegmentRegex.test(inputPath)) {
return false;
}
return true;
}
}
// Matches a relative path consisting entirely of periods and slashes
// Example: ".", "..", "../..", etc
Path._relativePathRegex = /^[.\/\\]+$/;
// Matches a relative path segment that traverses upwards
// Example: "a/../b"
Path._upwardPathSegmentRegex = /([\/\\]|^)\.\.([\/\\]|$)/;
exports.Path = Path;
//# sourceMappingURL=Path.js.map

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,73 @@
/**
* An integer value used to specify file permissions for POSIX-like operating systems.
*
* @remarks
*
* This bitfield corresponds to the "mode_t" structure described in this document:
* http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/sys_stat.h.html
*
* It is used with NodeJS APIs such as fs.Stat.mode and fs.chmodSync(). These values
* represent a set of permissions and can be combined using bitwise arithmetic.
*
* POSIX is a registered trademark of the Institute of Electrical and Electronic Engineers, Inc.
*
* @public
*/
export declare enum PosixModeBits {
/**
* Indicates that the item's owner can read the item.
*/
UserRead = 256,
/**
* Indicates that the item's owner can modify the item.
*/
UserWrite = 128,
/**
* Indicates that the item's owner can execute the item (if it is a file)
* or search the item (if it is a directory).
*/
UserExecute = 64,
/**
* Indicates that users belonging to the item's group can read the item.
*/
GroupRead = 32,
/**
* Indicates that users belonging to the item's group can modify the item.
*/
GroupWrite = 16,
/**
* Indicates that users belonging to the item's group can execute the item (if it is a file)
* or search the item (if it is a directory).
*/
GroupExecute = 8,
/**
* Indicates that other users (besides the item's owner user or group) can read the item.
*/
OthersRead = 4,
/**
* Indicates that other users (besides the item's owner user or group) can modify the item.
*/
OthersWrite = 2,
/**
* Indicates that other users (besides the item's owner user or group) can execute the item (if it is a file)
* or search the item (if it is a directory).
*/
OthersExecute = 1,
/**
* A zero value where no permissions bits are set.
*/
None = 0,
/**
* An alias combining OthersRead, GroupRead, and UserRead permission bits.
*/
AllRead = 292,
/**
* An alias combining OthersWrite, GroupWrite, and UserWrite permission bits.
*/
AllWrite = 146,
/**
* An alias combining OthersExecute, GroupExecute, and UserExecute permission bits.
*/
AllExecute = 73
}
//# sourceMappingURL=PosixModeBits.d.ts.map

View File

@ -0,0 +1 @@
{"version":3,"file":"PosixModeBits.d.ts","sourceRoot":"","sources":["../src/PosixModeBits.ts"],"names":[],"mappings":"AAMA;;;;;;;;;;;;;;GAcG;AACH,oBAAY,aAAa;IAGvB;;OAEG;IACH,QAAQ,MAAS;IAEjB;;OAEG;IACH,SAAS,MAAS;IAElB;;;OAGG;IACH,WAAW,KAAS;IAEpB;;OAEG;IACH,SAAS,KAAS;IAElB;;OAEG;IACH,UAAU,KAAS;IAEnB;;;OAGG;IACH,YAAY,IAAS;IAErB;;OAEG;IACH,UAAU,IAAS;IAEnB;;OAEG;IACH,WAAW,IAAS;IAEpB;;;OAGG;IACH,aAAa,IAAS;IAItB;;OAEG;IACH,IAAI,IAAI;IAER;;OAEG;IACH,OAAO,MAAoC;IAE3C;;OAEG;IACH,QAAQ,MAAuC;IAE/C;;OAEG;IACH,UAAU,KAA6C;CACxD"}

View File

@ -0,0 +1,83 @@
"use strict";
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root for license information.
Object.defineProperty(exports, "__esModule", { value: true });
exports.PosixModeBits = void 0;
// The PosixModeBits are intended to be used with bitwise operations.
/* eslint-disable no-bitwise */
/**
* An integer value used to specify file permissions for POSIX-like operating systems.
*
* @remarks
*
* This bitfield corresponds to the "mode_t" structure described in this document:
* http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/sys_stat.h.html
*
* It is used with NodeJS APIs such as fs.Stat.mode and fs.chmodSync(). These values
* represent a set of permissions and can be combined using bitwise arithmetic.
*
* POSIX is a registered trademark of the Institute of Electrical and Electronic Engineers, Inc.
*
* @public
*/
var PosixModeBits;
(function (PosixModeBits) {
// The bits
/**
* Indicates that the item's owner can read the item.
*/
PosixModeBits[PosixModeBits["UserRead"] = 256] = "UserRead";
/**
* Indicates that the item's owner can modify the item.
*/
PosixModeBits[PosixModeBits["UserWrite"] = 128] = "UserWrite";
/**
* Indicates that the item's owner can execute the item (if it is a file)
* or search the item (if it is a directory).
*/
PosixModeBits[PosixModeBits["UserExecute"] = 64] = "UserExecute";
/**
* Indicates that users belonging to the item's group can read the item.
*/
PosixModeBits[PosixModeBits["GroupRead"] = 32] = "GroupRead";
/**
* Indicates that users belonging to the item's group can modify the item.
*/
PosixModeBits[PosixModeBits["GroupWrite"] = 16] = "GroupWrite";
/**
* Indicates that users belonging to the item's group can execute the item (if it is a file)
* or search the item (if it is a directory).
*/
PosixModeBits[PosixModeBits["GroupExecute"] = 8] = "GroupExecute";
/**
* Indicates that other users (besides the item's owner user or group) can read the item.
*/
PosixModeBits[PosixModeBits["OthersRead"] = 4] = "OthersRead";
/**
* Indicates that other users (besides the item's owner user or group) can modify the item.
*/
PosixModeBits[PosixModeBits["OthersWrite"] = 2] = "OthersWrite";
/**
* Indicates that other users (besides the item's owner user or group) can execute the item (if it is a file)
* or search the item (if it is a directory).
*/
PosixModeBits[PosixModeBits["OthersExecute"] = 1] = "OthersExecute";
// Helpful aliases
/**
* A zero value where no permissions bits are set.
*/
PosixModeBits[PosixModeBits["None"] = 0] = "None";
/**
* An alias combining OthersRead, GroupRead, and UserRead permission bits.
*/
PosixModeBits[PosixModeBits["AllRead"] = 292] = "AllRead";
/**
* An alias combining OthersWrite, GroupWrite, and UserWrite permission bits.
*/
PosixModeBits[PosixModeBits["AllWrite"] = 146] = "AllWrite";
/**
* An alias combining OthersExecute, GroupExecute, and UserExecute permission bits.
*/
PosixModeBits[PosixModeBits["AllExecute"] = 73] = "AllExecute";
})(PosixModeBits = exports.PosixModeBits || (exports.PosixModeBits = {}));
//# sourceMappingURL=PosixModeBits.js.map

View File

@ -0,0 +1 @@
{"version":3,"file":"PosixModeBits.js","sourceRoot":"","sources":["../src/PosixModeBits.ts"],"names":[],"mappings":";AAAA,4FAA4F;AAC5F,2DAA2D;;;AAE3D,qEAAqE;AACrE,+BAA+B;AAE/B;;;;;;;;;;;;;;GAcG;AACH,IAAY,aAwEX;AAxED,WAAY,aAAa;IACvB,WAAW;IAEX;;OAEG;IACH,2DAAiB,CAAA;IAEjB;;OAEG;IACH,6DAAkB,CAAA;IAElB;;;OAGG;IACH,gEAAoB,CAAA;IAEpB;;OAEG;IACH,4DAAkB,CAAA;IAElB;;OAEG;IACH,8DAAmB,CAAA;IAEnB;;;OAGG;IACH,iEAAqB,CAAA;IAErB;;OAEG;IACH,6DAAmB,CAAA;IAEnB;;OAEG;IACH,+DAAoB,CAAA;IAEpB;;;OAGG;IACH,mEAAsB,CAAA;IAEtB,kBAAkB;IAElB;;OAEG;IACH,iDAAQ,CAAA;IAER;;OAEG;IACH,yDAA2C,CAAA;IAE3C;;OAEG;IACH,2DAA+C,CAAA;IAE/C;;OAEG;IACH,8DAAuD,CAAA;AACzD,CAAC,EAxEW,aAAa,GAAb,qBAAa,KAAb,qBAAa,QAwExB","sourcesContent":["// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.\n// See LICENSE in the project root for license information.\n\n// The PosixModeBits are intended to be used with bitwise operations.\n/* eslint-disable no-bitwise */\n\n/**\n * An integer value used to specify file permissions for POSIX-like operating systems.\n *\n * @remarks\n *\n * This bitfield corresponds to the \"mode_t\" structure described in this document:\n * http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/sys_stat.h.html\n *\n * It is used with NodeJS APIs such as fs.Stat.mode and fs.chmodSync(). These values\n * represent a set of permissions and can be combined using bitwise arithmetic.\n *\n * POSIX is a registered trademark of the Institute of Electrical and Electronic Engineers, Inc.\n *\n * @public\n */\nexport enum PosixModeBits {\n // The bits\n\n /**\n * Indicates that the item's owner can read the item.\n */\n UserRead = 1 << 8,\n\n /**\n * Indicates that the item's owner can modify the item.\n */\n UserWrite = 1 << 7,\n\n /**\n * Indicates that the item's owner can execute the item (if it is a file)\n * or search the item (if it is a directory).\n */\n UserExecute = 1 << 6,\n\n /**\n * Indicates that users belonging to the item's group can read the item.\n */\n GroupRead = 1 << 5,\n\n /**\n * Indicates that users belonging to the item's group can modify the item.\n */\n GroupWrite = 1 << 4,\n\n /**\n * Indicates that users belonging to the item's group can execute the item (if it is a file)\n * or search the item (if it is a directory).\n */\n GroupExecute = 1 << 3,\n\n /**\n * Indicates that other users (besides the item's owner user or group) can read the item.\n */\n OthersRead = 1 << 2,\n\n /**\n * Indicates that other users (besides the item's owner user or group) can modify the item.\n */\n OthersWrite = 1 << 1,\n\n /**\n * Indicates that other users (besides the item's owner user or group) can execute the item (if it is a file)\n * or search the item (if it is a directory).\n */\n OthersExecute = 1 << 0,\n\n // Helpful aliases\n\n /**\n * A zero value where no permissions bits are set.\n */\n None = 0,\n\n /**\n * An alias combining OthersRead, GroupRead, and UserRead permission bits.\n */\n AllRead = OthersRead | GroupRead | UserRead,\n\n /**\n * An alias combining OthersWrite, GroupWrite, and UserWrite permission bits.\n */\n AllWrite = OthersWrite | GroupWrite | UserWrite,\n\n /**\n * An alias combining OthersExecute, GroupExecute, and UserExecute permission bits.\n */\n AllExecute = OthersExecute | GroupExecute | UserExecute\n}\n"]}

View File

@ -0,0 +1,41 @@
/**
* A "branded type" is a primitive type with a compile-type key that makes it incompatible with other
* aliases for the primitive type.
*
* @remarks
*
* Example usage:
*
* ```ts
* // PhoneNumber is a branded type based on the "string" primitive.
* type PhoneNumber = Brand<string, 'PhoneNumber'>;
*
* function createPhoneNumber(input: string): PhoneNumber {
* if (!/\d+(\-\d+)+/.test(input)) {
* throw new Error('Invalid phone number: ' + JSON.stringify(input));
* }
* return input as PhoneNumber;
* }
*
* const p1: PhoneNumber = createPhoneNumber('123-456-7890');
*
* // PhoneNumber is a string and can be used as one:
* const p2: string = p1;
*
* // But an arbitrary string cannot be implicitly type cast as PhoneNumber.
* // ERROR: Type 'string' is not assignable to type 'PhoneNumber'
* const p3: PhoneNumber = '123-456-7890';
* ```
*
* For more information about this pattern, see {@link
* https://github.com/Microsoft/TypeScript/blob/7b48a182c05ea4dea81bab73ecbbe9e013a79e99/src/compiler/types.ts#L693-L698
* | this comment} explaining the TypeScript compiler's introduction of this pattern, and
* {@link https://spin.atomicobject.com/2018/01/15/typescript-flexible-nominal-typing/ | this article}
* explaining the technique in depth.
*
* @public
*/
export type Brand<T, BrandTag extends string> = T & {
__brand: BrandTag;
};
//# sourceMappingURL=PrimitiveTypes.d.ts.map

View File

@ -0,0 +1 @@
{"version":3,"file":"PrimitiveTypes.d.ts","sourceRoot":"","sources":["../src/PrimitiveTypes.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AACH,MAAM,MAAM,KAAK,CAAC,CAAC,EAAE,QAAQ,SAAS,MAAM,IAAI,CAAC,GAAG;IAAE,OAAO,EAAE,QAAQ,CAAA;CAAE,CAAC"}

View File

@ -0,0 +1,5 @@
"use strict";
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root for license information.
Object.defineProperty(exports, "__esModule", { value: true });
//# sourceMappingURL=PrimitiveTypes.js.map

View File

@ -0,0 +1 @@
{"version":3,"file":"PrimitiveTypes.js","sourceRoot":"","sources":["../src/PrimitiveTypes.ts"],"names":[],"mappings":";AAAA,4FAA4F;AAC5F,2DAA2D","sourcesContent":["// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.\n// See LICENSE in the project root for license information.\n\n/**\n * A \"branded type\" is a primitive type with a compile-type key that makes it incompatible with other\n * aliases for the primitive type.\n *\n * @remarks\n *\n * Example usage:\n *\n * ```ts\n * // PhoneNumber is a branded type based on the \"string\" primitive.\n * type PhoneNumber = Brand<string, 'PhoneNumber'>;\n *\n * function createPhoneNumber(input: string): PhoneNumber {\n * if (!/\\d+(\\-\\d+)+/.test(input)) {\n * throw new Error('Invalid phone number: ' + JSON.stringify(input));\n * }\n * return input as PhoneNumber;\n * }\n *\n * const p1: PhoneNumber = createPhoneNumber('123-456-7890');\n *\n * // PhoneNumber is a string and can be used as one:\n * const p2: string = p1;\n *\n * // But an arbitrary string cannot be implicitly type cast as PhoneNumber.\n * // ERROR: Type 'string' is not assignable to type 'PhoneNumber'\n * const p3: PhoneNumber = '123-456-7890';\n * ```\n *\n * For more information about this pattern, see {@link\n * https://github.com/Microsoft/TypeScript/blob/7b48a182c05ea4dea81bab73ecbbe9e013a79e99/src/compiler/types.ts#L693-L698\n * | this comment} explaining the TypeScript compiler's introduction of this pattern, and\n * {@link https://spin.atomicobject.com/2018/01/15/typescript-flexible-nominal-typing/ | this article}\n * explaining the technique in depth.\n *\n * @public\n */\nexport type Brand<T, BrandTag extends string> = T & { __brand: BrandTag };\n"]}

View File

@ -0,0 +1,83 @@
/**
* Constructor parameters for {@link ProtectableMap}
*
* @public
*/
export interface IProtectableMapParameters<K, V> {
/**
* An optional hook that will be invoked before Map.clear() is performed.
*/
onClear?: (source: ProtectableMap<K, V>) => void;
/**
* An optional hook that will be invoked before Map.delete() is performed.
*/
onDelete?: (source: ProtectableMap<K, V>, key: K) => void;
/**
* An optional hook that will be invoked before Map.set() is performed.
* @remarks
* If this hook is provided, the function MUST return the `value` parameter.
* This provides the opportunity to modify the value before it is added
* to the map.
*/
onSet?: (source: ProtectableMap<K, V>, key: K, value: V) => V;
}
/**
* The ProtectableMap provides an easy way for an API to expose a `Map<K, V>` property
* while intercepting and validating any write operations that are performed by
* consumers of the API.
*
* @remarks
* The ProtectableMap itself is intended to be a private object that only its owner
* can access directly. Any operations performed directly on the ProtectableMap will
* bypass the hooks and any validation they perform. The public property that is exposed
* to API consumers should return {@link ProtectableMap.protectedView} instead.
*
* For example, suppose you want to share your `Map<string, number>` data structure,
* but you want to enforce that the key must always be an upper case string:
* You could use the onSet() hook to validate the keys and throw an exception
* if the key is not uppercase.
*
* @public
*/
export declare class ProtectableMap<K, V> {
private readonly _protectedView;
constructor(parameters: IProtectableMapParameters<K, V>);
/**
* The owner of the protectable map should return this object via its public API.
*/
get protectedView(): Map<K, V>;
/**
* Removes all entries from the map.
* This operation does NOT invoke the ProtectableMap onClear() hook.
*/
clear(): void;
/**
* Removes the specified key from the map.
* This operation does NOT invoke the ProtectableMap onDelete() hook.
*/
delete(key: K): boolean;
/**
* Sets a value for the specified key.
* This operation does NOT invoke the ProtectableMap onSet() hook.
*/
set(key: K, value: V): this;
/**
* Performs an operation for each (key, value) entries in the map.
*/
forEach(callbackfn: (value: V, key: K, map: Map<K, V>) => void, thisArg?: any): void;
/**
* Retrieves the value for the specified key.
* @returns undefined if the value is undefined OR if the key is missing;
* otherwise returns the value associated with the key.
*/
get(key: K): V | undefined;
/**
* Returns true if the specified key belongs to the map.
*/
has(key: K): boolean;
/**
* Returns the number of (key, value) entries in the map.
*/
get size(): number;
}
//# sourceMappingURL=ProtectableMap.d.ts.map

View File

@ -0,0 +1 @@
{"version":3,"file":"ProtectableMap.d.ts","sourceRoot":"","sources":["../src/ProtectableMap.ts"],"names":[],"mappings":"AAKA;;;;GAIG;AACH,MAAM,WAAW,yBAAyB,CAAC,CAAC,EAAE,CAAC;IAC7C;;OAEG;IACH,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC,EAAE,CAAC,CAAC,KAAK,IAAI,CAAC;IAEjD;;OAEG;IACH,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC,KAAK,IAAI,CAAC;IAE1D;;;;;;OAMG;IACH,KAAK,CAAC,EAAE,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,KAAK,CAAC,CAAC;CAC/D;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,qBAAa,cAAc,CAAC,CAAC,EAAE,CAAC;IAC9B,OAAO,CAAC,QAAQ,CAAC,cAAc,CAA2B;gBAEvC,UAAU,EAAE,yBAAyB,CAAC,CAAC,EAAE,CAAC,CAAC;IAI9D;;OAEG;IACH,IAAW,aAAa,IAAI,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,CAEpC;IAKD;;;OAGG;IACI,KAAK,IAAI,IAAI;IAIpB;;;OAGG;IACI,MAAM,CAAC,GAAG,EAAE,CAAC,GAAG,OAAO;IAI9B;;;OAGG;IACI,GAAG,CAAC,GAAG,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,GAAG,IAAI;IAQlC;;OAEG;IAEI,OAAO,CAAC,UAAU,EAAE,CAAC,KAAK,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,KAAK,IAAI,EAAE,OAAO,CAAC,EAAE,GAAG,GAAG,IAAI;IAI3F;;;;OAIG;IACI,GAAG,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,GAAG,SAAS;IAIjC;;OAEG;IACI,GAAG,CAAC,GAAG,EAAE,CAAC,GAAG,OAAO;IAI3B;;OAEG;IACH,IAAW,IAAI,IAAI,MAAM,CAExB;CACF"}

View File

@ -0,0 +1,90 @@
"use strict";
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root for license information.
Object.defineProperty(exports, "__esModule", { value: true });
exports.ProtectableMap = void 0;
const ProtectableMapView_1 = require("./ProtectableMapView");
/**
* The ProtectableMap provides an easy way for an API to expose a `Map<K, V>` property
* while intercepting and validating any write operations that are performed by
* consumers of the API.
*
* @remarks
* The ProtectableMap itself is intended to be a private object that only its owner
* can access directly. Any operations performed directly on the ProtectableMap will
* bypass the hooks and any validation they perform. The public property that is exposed
* to API consumers should return {@link ProtectableMap.protectedView} instead.
*
* For example, suppose you want to share your `Map<string, number>` data structure,
* but you want to enforce that the key must always be an upper case string:
* You could use the onSet() hook to validate the keys and throw an exception
* if the key is not uppercase.
*
* @public
*/
class ProtectableMap {
constructor(parameters) {
this._protectedView = new ProtectableMapView_1.ProtectableMapView(this, parameters);
}
/**
* The owner of the protectable map should return this object via its public API.
*/
get protectedView() {
return this._protectedView;
}
// ---------------------------------------------------------------------------
// lib.es2015.collections contract - write operations
/**
* Removes all entries from the map.
* This operation does NOT invoke the ProtectableMap onClear() hook.
*/
clear() {
this._protectedView._clearUnprotected();
}
/**
* Removes the specified key from the map.
* This operation does NOT invoke the ProtectableMap onDelete() hook.
*/
delete(key) {
return this._protectedView._deleteUnprotected(key);
}
/**
* Sets a value for the specified key.
* This operation does NOT invoke the ProtectableMap onSet() hook.
*/
set(key, value) {
this._protectedView._setUnprotected(key, value);
return this;
}
// ---------------------------------------------------------------------------
// lib.es2015.collections contract - read operations
/**
* Performs an operation for each (key, value) entries in the map.
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
forEach(callbackfn, thisArg) {
this._protectedView.forEach(callbackfn);
}
/**
* Retrieves the value for the specified key.
* @returns undefined if the value is undefined OR if the key is missing;
* otherwise returns the value associated with the key.
*/
get(key) {
return this._protectedView.get(key);
}
/**
* Returns true if the specified key belongs to the map.
*/
has(key) {
return this._protectedView.has(key);
}
/**
* Returns the number of (key, value) entries in the map.
*/
get size() {
return this._protectedView.size;
}
}
exports.ProtectableMap = ProtectableMap;
//# sourceMappingURL=ProtectableMap.js.map

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,20 @@
import type { ProtectableMap, IProtectableMapParameters } from './ProtectableMap';
/**
* The internal wrapper used by ProtectableMap. It extends the real `Map<K, V>` base class,
* but hooks the destructive operations (clear/delete/set) to give the owner a chance
* to block them.
*
* NOTE: This is not a public API.
*/
export declare class ProtectableMapView<K, V> extends Map<K, V> {
private readonly _owner;
private readonly _parameters;
constructor(owner: ProtectableMap<K, V>, parameters: IProtectableMapParameters<K, V>);
clear(): void;
delete(key: K): boolean;
set(key: K, value: V): this;
_clearUnprotected(): void;
_deleteUnprotected(key: K): boolean;
_setUnprotected(key: K, value: V): void;
}
//# sourceMappingURL=ProtectableMapView.d.ts.map

View File

@ -0,0 +1 @@
{"version":3,"file":"ProtectableMapView.d.ts","sourceRoot":"","sources":["../src/ProtectableMapView.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,cAAc,EAAE,yBAAyB,EAAE,MAAM,kBAAkB,CAAC;AAElF;;;;;;GAMG;AACH,qBAAa,kBAAkB,CAAC,CAAC,EAAE,CAAC,CAAE,SAAQ,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC;IACrD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAuB;IAC9C,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAkC;gBAE3C,KAAK,EAAE,cAAc,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,UAAU,EAAE,yBAAyB,CAAC,CAAC,EAAE,CAAC,CAAC;IAOpF,KAAK,IAAI,IAAI;IAQb,MAAM,CAAC,GAAG,EAAE,CAAC,GAAG,OAAO;IAQvB,GAAG,CAAC,GAAG,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,GAAG,IAAI;IAW3B,iBAAiB,IAAI,IAAI;IAKzB,kBAAkB,CAAC,GAAG,EAAE,CAAC,GAAG,OAAO;IAKnC,eAAe,CAAC,GAAG,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,GAAG,IAAI;CAG/C"}

View File

@ -0,0 +1,56 @@
"use strict";
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root for license information.
Object.defineProperty(exports, "__esModule", { value: true });
exports.ProtectableMapView = void 0;
/**
* The internal wrapper used by ProtectableMap. It extends the real `Map<K, V>` base class,
* but hooks the destructive operations (clear/delete/set) to give the owner a chance
* to block them.
*
* NOTE: This is not a public API.
*/
class ProtectableMapView extends Map {
constructor(owner, parameters) {
super();
this._owner = owner;
this._parameters = parameters;
}
clear() {
// override
if (this._parameters.onClear) {
this._parameters.onClear(this._owner);
}
super.clear();
}
delete(key) {
// override
if (this._parameters.onDelete) {
this._parameters.onDelete(this._owner, key);
}
return super.delete(key);
}
set(key, value) {
// override
let modifiedValue = value;
if (this._parameters.onSet) {
modifiedValue = this._parameters.onSet(this._owner, key, modifiedValue);
}
super.set(key, modifiedValue);
return this;
}
// INTERNAL USAGE ONLY
_clearUnprotected() {
super.clear();
}
// INTERNAL USAGE ONLY
_deleteUnprotected(key) {
return super.delete(key);
}
// INTERNAL USAGE ONLY
_setUnprotected(key, value) {
super.set(key, value);
}
}
exports.ProtectableMapView = ProtectableMapView;
//# sourceMappingURL=ProtectableMapView.js.map

View File

@ -0,0 +1 @@
{"version":3,"file":"ProtectableMapView.js","sourceRoot":"","sources":["../src/ProtectableMapView.ts"],"names":[],"mappings":";AAAA,4FAA4F;AAC5F,2DAA2D;;;AAI3D;;;;;;GAMG;AACH,MAAa,kBAAyB,SAAQ,GAAS;IAIrD,YAAmB,KAA2B,EAAE,UAA2C;QACzF,KAAK,EAAE,CAAC;QAER,IAAI,CAAC,MAAM,GAAG,KAAK,CAAC;QACpB,IAAI,CAAC,WAAW,GAAG,UAAU,CAAC;IAChC,CAAC;IAEM,KAAK;QACV,WAAW;QACX,IAAI,IAAI,CAAC,WAAW,CAAC,OAAO,EAAE;YAC5B,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;SACvC;QACD,KAAK,CAAC,KAAK,EAAE,CAAC;IAChB,CAAC;IAEM,MAAM,CAAC,GAAM;QAClB,WAAW;QACX,IAAI,IAAI,CAAC,WAAW,CAAC,QAAQ,EAAE;YAC7B,IAAI,CAAC,WAAW,CAAC,QAAQ,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;SAC7C;QACD,OAAO,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IAC3B,CAAC;IAEM,GAAG,CAAC,GAAM,EAAE,KAAQ;QACzB,WAAW;QACX,IAAI,aAAa,GAAM,KAAK,CAAC;QAC7B,IAAI,IAAI,CAAC,WAAW,CAAC,KAAK,EAAE;YAC1B,aAAa,GAAG,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,EAAE,aAAa,CAAC,CAAC;SACzE;QACD,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,aAAa,CAAC,CAAC;QAC9B,OAAO,IAAI,CAAC;IACd,CAAC;IAED,sBAAsB;IACf,iBAAiB;QACtB,KAAK,CAAC,KAAK,EAAE,CAAC;IAChB,CAAC;IAED,sBAAsB;IACf,kBAAkB,CAAC,GAAM;QAC9B,OAAO,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IAC3B,CAAC;IAED,sBAAsB;IACf,eAAe,CAAC,GAAM,EAAE,KAAQ;QACrC,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;IACxB,CAAC;CACF;AAnDD,gDAmDC","sourcesContent":["// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.\n// See LICENSE in the project root for license information.\n\nimport type { ProtectableMap, IProtectableMapParameters } from './ProtectableMap';\n\n/**\n * The internal wrapper used by ProtectableMap. It extends the real `Map<K, V>` base class,\n * but hooks the destructive operations (clear/delete/set) to give the owner a chance\n * to block them.\n *\n * NOTE: This is not a public API.\n */\nexport class ProtectableMapView<K, V> extends Map<K, V> {\n private readonly _owner: ProtectableMap<K, V>;\n private readonly _parameters: IProtectableMapParameters<K, V>;\n\n public constructor(owner: ProtectableMap<K, V>, parameters: IProtectableMapParameters<K, V>) {\n super();\n\n this._owner = owner;\n this._parameters = parameters;\n }\n\n public clear(): void {\n // override\n if (this._parameters.onClear) {\n this._parameters.onClear(this._owner);\n }\n super.clear();\n }\n\n public delete(key: K): boolean {\n // override\n if (this._parameters.onDelete) {\n this._parameters.onDelete(this._owner, key);\n }\n return super.delete(key);\n }\n\n public set(key: K, value: V): this {\n // override\n let modifiedValue: V = value;\n if (this._parameters.onSet) {\n modifiedValue = this._parameters.onSet(this._owner, key, modifiedValue);\n }\n super.set(key, modifiedValue);\n return this;\n }\n\n // INTERNAL USAGE ONLY\n public _clearUnprotected(): void {\n super.clear();\n }\n\n // INTERNAL USAGE ONLY\n public _deleteUnprotected(key: K): boolean {\n return super.delete(key);\n }\n\n // INTERNAL USAGE ONLY\n public _setUnprotected(key: K, value: V): void {\n super.set(key, value);\n }\n}\n"]}

Some files were not shown because too many files have changed in this diff Show More