2022-04-07 12:26:50 -08:00
|
|
|
/**
|
|
|
|
* Copyright (c) Microsoft Corporation.
|
|
|
|
*
|
|
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
* you may not use this file except in compliance with the License.
|
|
|
|
* You may obtain a copy of the License at
|
|
|
|
*
|
|
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
*
|
|
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
* See the License for the specific language governing permissions and
|
|
|
|
* limitations under the License.
|
|
|
|
*/
|
|
|
|
|
2022-04-07 12:55:44 -08:00
|
|
|
import * as crypto from 'crypto';
|
|
|
|
import type stream from 'stream';
|
|
|
|
import * as URL from 'url';
|
2022-04-28 15:49:36 -08:00
|
|
|
import v8 from 'v8';
|
2022-04-07 12:55:44 -08:00
|
|
|
|
2022-04-07 19:18:22 -08:00
|
|
|
type NameValue = {
|
|
|
|
name: string,
|
|
|
|
value: string,
|
|
|
|
};
|
2022-04-07 12:55:44 -08:00
|
|
|
|
|
|
|
// See https://joel.tools/microtasks/
|
|
|
|
export function makeWaitForNextTask() {
|
|
|
|
// As of Mar 2021, Electron v12 doesn't create new task with `setImmediate` despite
|
|
|
|
// using Node 14 internally, so we fallback to `setTimeout(0)` instead.
|
|
|
|
// @see https://github.com/electron/electron/issues/28261
|
|
|
|
if ((process.versions as any).electron)
|
|
|
|
return (callback: () => void) => setTimeout(callback, 0);
|
|
|
|
if (parseInt(process.versions.node, 10) >= 11)
|
|
|
|
return setImmediate;
|
|
|
|
|
|
|
|
// Unlike Node 11, Node 10 and less have a bug with Task and MicroTask execution order:
|
|
|
|
// - https://github.com/nodejs/node/issues/22257
|
|
|
|
//
|
|
|
|
// So we can't simply run setImmediate to dispatch code in a following task.
|
|
|
|
// However, we can run setImmediate from-inside setImmediate to make sure we're getting
|
|
|
|
// in the following task.
|
|
|
|
|
|
|
|
let spinning = false;
|
|
|
|
const callbacks: (() => void)[] = [];
|
|
|
|
const loop = () => {
|
|
|
|
const callback = callbacks.shift();
|
|
|
|
if (!callback) {
|
|
|
|
spinning = false;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
setImmediate(loop);
|
|
|
|
// Make sure to call callback() as the last thing since it's
|
|
|
|
// untrusted code that might throw.
|
|
|
|
callback();
|
|
|
|
};
|
|
|
|
|
|
|
|
return (callback: () => void) => {
|
|
|
|
callbacks.push(callback);
|
|
|
|
if (!spinning) {
|
|
|
|
spinning = true;
|
|
|
|
setImmediate(loop);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
export function assert(value: any, message?: string): asserts value {
|
|
|
|
if (!value)
|
|
|
|
throw new Error(message || 'Assertion error');
|
|
|
|
}
|
|
|
|
|
|
|
|
export function debugAssert(value: any, message?: string): asserts value {
|
|
|
|
if (isUnderTest() && !value)
|
|
|
|
throw new Error(message);
|
|
|
|
}
|
|
|
|
|
|
|
|
export function isString(obj: any): obj is string {
|
|
|
|
return typeof obj === 'string' || obj instanceof String;
|
|
|
|
}
|
|
|
|
|
|
|
|
export function isRegExp(obj: any): obj is RegExp {
|
|
|
|
return obj instanceof RegExp || Object.prototype.toString.call(obj) === '[object RegExp]';
|
|
|
|
}
|
|
|
|
|
|
|
|
export function isObject(obj: any): obj is NonNullable<object> {
|
|
|
|
return typeof obj === 'object' && obj !== null;
|
|
|
|
}
|
|
|
|
|
|
|
|
export function isError(obj: any): obj is Error {
|
2022-09-21 04:01:12 +02:00
|
|
|
return obj instanceof Error || (obj && Object.getPrototypeOf(obj)?.name === 'Error');
|
2022-04-07 12:55:44 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
const debugEnv = getFromENV('PWDEBUG') || '';
|
|
|
|
export function debugMode() {
|
|
|
|
if (debugEnv === 'console')
|
|
|
|
return 'console';
|
|
|
|
if (debugEnv === '0' || debugEnv === 'false')
|
|
|
|
return '';
|
|
|
|
return debugEnv ? 'inspector' : '';
|
|
|
|
}
|
|
|
|
|
|
|
|
let _isUnderTest = false;
|
|
|
|
export function setUnderTest() {
|
|
|
|
_isUnderTest = true;
|
|
|
|
}
|
|
|
|
export function isUnderTest(): boolean {
|
|
|
|
return _isUnderTest;
|
|
|
|
}
|
|
|
|
|
|
|
|
export function getFromENV(name: string): string | undefined {
|
|
|
|
let value = process.env[name];
|
|
|
|
value = value === undefined ? process.env[`npm_config_${name.toLowerCase()}`] : value;
|
|
|
|
value = value === undefined ? process.env[`npm_package_config_${name.toLowerCase()}`] : value;
|
|
|
|
return value;
|
|
|
|
}
|
|
|
|
|
|
|
|
export function getAsBooleanFromENV(name: string): boolean {
|
|
|
|
const value = getFromENV(name);
|
|
|
|
return !!value && value !== 'false' && value !== '0';
|
|
|
|
}
|
|
|
|
|
|
|
|
type HeadersArray = { name: string, value: string }[];
|
|
|
|
type HeadersObject = { [key: string]: string };
|
|
|
|
|
|
|
|
export function headersObjectToArray(headers: HeadersObject, separator?: string, setCookieSeparator?: string): HeadersArray {
|
|
|
|
if (!setCookieSeparator)
|
|
|
|
setCookieSeparator = separator;
|
|
|
|
const result: HeadersArray = [];
|
|
|
|
for (const name in headers) {
|
|
|
|
const values = headers[name];
|
2022-05-02 12:38:57 -07:00
|
|
|
if (values === undefined)
|
|
|
|
continue;
|
2022-04-07 12:55:44 -08:00
|
|
|
if (separator) {
|
|
|
|
const sep = name.toLowerCase() === 'set-cookie' ? setCookieSeparator : separator;
|
|
|
|
for (const value of values.split(sep!))
|
|
|
|
result.push({ name, value: value.trim() });
|
|
|
|
} else {
|
|
|
|
result.push({ name, value: values });
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
export function headersArrayToObject(headers: HeadersArray, lowerCase: boolean): HeadersObject {
|
|
|
|
const result: HeadersObject = {};
|
|
|
|
for (const { name, value } of headers)
|
|
|
|
result[lowerCase ? name.toLowerCase() : name] = value;
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
export function monotonicTime(): number {
|
|
|
|
const [seconds, nanoseconds] = process.hrtime();
|
|
|
|
return seconds * 1000 + (nanoseconds / 1000 | 0) / 1000;
|
|
|
|
}
|
|
|
|
|
|
|
|
export function objectToArray(map?: { [key: string]: any }): NameValue[] | undefined {
|
|
|
|
if (!map)
|
|
|
|
return undefined;
|
|
|
|
const result = [];
|
|
|
|
for (const [name, value] of Object.entries(map))
|
|
|
|
result.push({ name, value: String(value) });
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
export function arrayToObject(array?: NameValue[]): { [key: string]: string } | undefined {
|
|
|
|
if (!array)
|
|
|
|
return undefined;
|
|
|
|
const result: { [key: string]: string } = {};
|
|
|
|
for (const { name, value } of array)
|
|
|
|
result[name] = value;
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
export function calculateSha1(buffer: Buffer | string): string {
|
|
|
|
const hash = crypto.createHash('sha1');
|
|
|
|
hash.update(buffer);
|
|
|
|
return hash.digest('hex');
|
|
|
|
}
|
|
|
|
|
|
|
|
export function createGuid(): string {
|
|
|
|
return crypto.randomBytes(16).toString('hex');
|
|
|
|
}
|
|
|
|
|
|
|
|
export function constructURLBasedOnBaseURL(baseURL: string | undefined, givenURL: string): string {
|
|
|
|
try {
|
|
|
|
return (new URL.URL(givenURL, baseURL)).toString();
|
|
|
|
} catch (e) {
|
|
|
|
return givenURL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export function wrapInASCIIBox(text: string, padding = 0): string {
|
|
|
|
const lines = text.split('\n');
|
2022-11-14 08:45:39 -08:00
|
|
|
const maxLength = Math.max(...lines.map(line => line.length));
|
|
|
|
return [
|
|
|
|
'╔' + '═'.repeat(maxLength + padding * 2) + '╗',
|
|
|
|
...lines.map(line => '║' + ' '.repeat(padding) + line + ' '.repeat(maxLength - line.length + padding) + '║'),
|
|
|
|
'╚' + '═'.repeat(maxLength + padding * 2) + '╝',
|
|
|
|
].join('\n');
|
2022-04-07 12:55:44 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
export function isFilePayload(value: any): boolean {
|
|
|
|
return typeof value === 'object' && value['name'] && value['mimeType'] && value['buffer'];
|
|
|
|
}
|
|
|
|
|
|
|
|
export function streamToString(stream: stream.Readable): Promise<string> {
|
|
|
|
return new Promise<string>((resolve, reject) => {
|
|
|
|
const chunks: Buffer[] = [];
|
|
|
|
stream.on('data', chunk => chunks.push(Buffer.from(chunk)));
|
|
|
|
stream.on('error', reject);
|
|
|
|
stream.on('end', () => resolve(Buffer.concat(chunks).toString('utf8')));
|
|
|
|
});
|
|
|
|
}
|
2022-04-08 10:46:24 -07:00
|
|
|
|
|
|
|
export const isLikelyNpxGlobal = () => process.argv.length >= 2 && process.argv[1].includes('_npx');
|
2022-04-28 15:49:36 -08:00
|
|
|
|
|
|
|
export function deepCopy<T>(obj: T): T {
|
|
|
|
return v8.deserialize(v8.serialize(obj));
|
|
|
|
}
|