@jsenv/core 40.0.0 → 40.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/js/build.js +17639 -0
- package/dist/js/directory_listing.js +4 -4
- package/dist/js/main.js +994 -0
- package/dist/js/new_stylesheet.js +1 -1
- package/dist/js/process_teardown_events.js +1848 -0
- package/dist/js/regenerator_runtime.js +1 -1
- package/dist/js/server_events_client.js +0 -1
- package/dist/js/start_build_server.js +206 -0
- package/dist/js/start_dev_server.js +846 -0
- package/dist/jsenv_core.js +2 -21359
- package/package.json +18 -14
- package/src/build/build_specifier_manager.js +2 -1
- package/src/main.js +13 -3
- package/src/plugins/resolution_node_esm/node_esm_resolver.js +17 -1
|
@@ -0,0 +1,1848 @@
|
|
|
1
|
+
import process$1 from "node:process";
|
|
2
|
+
import os from "node:os";
|
|
3
|
+
import tty from "node:tty";
|
|
4
|
+
import stringWidth from "string-width";
|
|
5
|
+
import { pathToFileURL } from "node:url";
|
|
6
|
+
import { ensurePathnameTrailingSlash } from "./main.js";
|
|
7
|
+
|
|
8
|
+
// From: https://github.com/sindresorhus/has-flag/blob/main/index.js
|
|
9
|
+
/// function hasFlag(flag, argv = globalThis.Deno?.args ?? process.argv) {
|
|
10
|
+
function hasFlag(flag, argv = globalThis.Deno ? globalThis.Deno.args : process$1.argv) {
|
|
11
|
+
const prefix = flag.startsWith('-') ? '' : (flag.length === 1 ? '-' : '--');
|
|
12
|
+
const position = argv.indexOf(prefix + flag);
|
|
13
|
+
const terminatorPosition = argv.indexOf('--');
|
|
14
|
+
return position !== -1 && (terminatorPosition === -1 || position < terminatorPosition);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const {env} = process$1;
|
|
18
|
+
|
|
19
|
+
let flagForceColor;
|
|
20
|
+
if (
|
|
21
|
+
hasFlag('no-color')
|
|
22
|
+
|| hasFlag('no-colors')
|
|
23
|
+
|| hasFlag('color=false')
|
|
24
|
+
|| hasFlag('color=never')
|
|
25
|
+
) {
|
|
26
|
+
flagForceColor = 0;
|
|
27
|
+
} else if (
|
|
28
|
+
hasFlag('color')
|
|
29
|
+
|| hasFlag('colors')
|
|
30
|
+
|| hasFlag('color=true')
|
|
31
|
+
|| hasFlag('color=always')
|
|
32
|
+
) {
|
|
33
|
+
flagForceColor = 1;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function envForceColor() {
|
|
37
|
+
if (!('FORCE_COLOR' in env)) {
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (env.FORCE_COLOR === 'true') {
|
|
42
|
+
return 1;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (env.FORCE_COLOR === 'false') {
|
|
46
|
+
return 0;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (env.FORCE_COLOR.length === 0) {
|
|
50
|
+
return 1;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const level = Math.min(Number.parseInt(env.FORCE_COLOR, 10), 3);
|
|
54
|
+
|
|
55
|
+
if (![0, 1, 2, 3].includes(level)) {
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return level;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function translateLevel(level) {
|
|
63
|
+
if (level === 0) {
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return {
|
|
68
|
+
level,
|
|
69
|
+
hasBasic: true,
|
|
70
|
+
has256: level >= 2,
|
|
71
|
+
has16m: level >= 3,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function _supportsColor(haveStream, {streamIsTTY, sniffFlags = true} = {}) {
|
|
76
|
+
const noFlagForceColor = envForceColor();
|
|
77
|
+
if (noFlagForceColor !== undefined) {
|
|
78
|
+
flagForceColor = noFlagForceColor;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const forceColor = sniffFlags ? flagForceColor : noFlagForceColor;
|
|
82
|
+
|
|
83
|
+
if (forceColor === 0) {
|
|
84
|
+
return 0;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (sniffFlags) {
|
|
88
|
+
if (hasFlag('color=16m')
|
|
89
|
+
|| hasFlag('color=full')
|
|
90
|
+
|| hasFlag('color=truecolor')) {
|
|
91
|
+
return 3;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (hasFlag('color=256')) {
|
|
95
|
+
return 2;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Check for Azure DevOps pipelines.
|
|
100
|
+
// Has to be above the `!streamIsTTY` check.
|
|
101
|
+
if ('TF_BUILD' in env && 'AGENT_NAME' in env) {
|
|
102
|
+
return 1;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (haveStream && !streamIsTTY && forceColor === undefined) {
|
|
106
|
+
return 0;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const min = forceColor || 0;
|
|
110
|
+
|
|
111
|
+
if (env.TERM === 'dumb') {
|
|
112
|
+
return min;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (process$1.platform === 'win32') {
|
|
116
|
+
// Windows 10 build 10586 is the first Windows release that supports 256 colors.
|
|
117
|
+
// Windows 10 build 14931 is the first release that supports 16m/TrueColor.
|
|
118
|
+
const osRelease = os.release().split('.');
|
|
119
|
+
if (
|
|
120
|
+
Number(osRelease[0]) >= 10
|
|
121
|
+
&& Number(osRelease[2]) >= 10_586
|
|
122
|
+
) {
|
|
123
|
+
return Number(osRelease[2]) >= 14_931 ? 3 : 2;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return 1;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if ('CI' in env) {
|
|
130
|
+
if (['GITHUB_ACTIONS', 'GITEA_ACTIONS', 'CIRCLECI'].some(key => key in env)) {
|
|
131
|
+
return 3;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (['TRAVIS', 'APPVEYOR', 'GITLAB_CI', 'BUILDKITE', 'DRONE'].some(sign => sign in env) || env.CI_NAME === 'codeship') {
|
|
135
|
+
return 1;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return min;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if ('TEAMCITY_VERSION' in env) {
|
|
142
|
+
return /^(9\.(0*[1-9]\d*)\.|\d{2,}\.)/.test(env.TEAMCITY_VERSION) ? 1 : 0;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (env.COLORTERM === 'truecolor') {
|
|
146
|
+
return 3;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (env.TERM === 'xterm-kitty') {
|
|
150
|
+
return 3;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if ('TERM_PROGRAM' in env) {
|
|
154
|
+
const version = Number.parseInt((env.TERM_PROGRAM_VERSION || '').split('.')[0], 10);
|
|
155
|
+
|
|
156
|
+
switch (env.TERM_PROGRAM) {
|
|
157
|
+
case 'iTerm.app': {
|
|
158
|
+
return version >= 3 ? 3 : 2;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
case 'Apple_Terminal': {
|
|
162
|
+
return 2;
|
|
163
|
+
}
|
|
164
|
+
// No default
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
if (/-256(color)?$/i.test(env.TERM)) {
|
|
169
|
+
return 2;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (/^screen|^xterm|^vt100|^vt220|^rxvt|color|ansi|cygwin|linux/i.test(env.TERM)) {
|
|
173
|
+
return 1;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if ('COLORTERM' in env) {
|
|
177
|
+
return 1;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return min;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function createSupportsColor(stream, options = {}) {
|
|
184
|
+
const level = _supportsColor(stream, {
|
|
185
|
+
streamIsTTY: stream && stream.isTTY,
|
|
186
|
+
...options,
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
return translateLevel(level);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
({
|
|
193
|
+
stdout: createSupportsColor({isTTY: tty.isatty(1)}),
|
|
194
|
+
stderr: createSupportsColor({isTTY: tty.isatty(2)}),
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
function isUnicodeSupported() {
|
|
198
|
+
const {env} = process$1;
|
|
199
|
+
const {TERM, TERM_PROGRAM} = env;
|
|
200
|
+
|
|
201
|
+
if (process$1.platform !== 'win32') {
|
|
202
|
+
return TERM !== 'linux'; // Linux console (kernel)
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
return Boolean(env.WT_SESSION) // Windows Terminal
|
|
206
|
+
|| Boolean(env.TERMINUS_SUBLIME) // Terminus (<0.2.27)
|
|
207
|
+
|| env.ConEmuTask === '{cmd::Cmder}' // ConEmu and cmder
|
|
208
|
+
|| TERM_PROGRAM === 'Terminus-Sublime'
|
|
209
|
+
|| TERM_PROGRAM === 'vscode'
|
|
210
|
+
|| TERM === 'xterm-256color'
|
|
211
|
+
|| TERM === 'alacritty'
|
|
212
|
+
|| TERM === 'rxvt-unicode'
|
|
213
|
+
|| TERM === 'rxvt-unicode-256color'
|
|
214
|
+
|| env.TERMINAL_EMULATOR === 'JetBrains-JediTerm';
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/* globals WorkerGlobalScope, DedicatedWorkerGlobalScope, SharedWorkerGlobalScope, ServiceWorkerGlobalScope */
|
|
218
|
+
|
|
219
|
+
const isBrowser = globalThis.window?.document !== undefined;
|
|
220
|
+
|
|
221
|
+
globalThis.process?.versions?.node !== undefined;
|
|
222
|
+
|
|
223
|
+
globalThis.process?.versions?.bun !== undefined;
|
|
224
|
+
|
|
225
|
+
globalThis.Deno?.version?.deno !== undefined;
|
|
226
|
+
|
|
227
|
+
globalThis.process?.versions?.electron !== undefined;
|
|
228
|
+
|
|
229
|
+
globalThis.navigator?.userAgent?.includes('jsdom') === true;
|
|
230
|
+
|
|
231
|
+
typeof WorkerGlobalScope !== 'undefined' && globalThis instanceof WorkerGlobalScope;
|
|
232
|
+
|
|
233
|
+
typeof DedicatedWorkerGlobalScope !== 'undefined' && globalThis instanceof DedicatedWorkerGlobalScope;
|
|
234
|
+
|
|
235
|
+
typeof SharedWorkerGlobalScope !== 'undefined' && globalThis instanceof SharedWorkerGlobalScope;
|
|
236
|
+
|
|
237
|
+
typeof ServiceWorkerGlobalScope !== 'undefined' && globalThis instanceof ServiceWorkerGlobalScope;
|
|
238
|
+
|
|
239
|
+
// Note: I'm intentionally not DRYing up the other variables to keep them "lazy".
|
|
240
|
+
const platform = globalThis.navigator?.userAgentData?.platform;
|
|
241
|
+
|
|
242
|
+
platform === 'macOS'
|
|
243
|
+
|| globalThis.navigator?.platform === 'MacIntel' // Even on Apple silicon Macs.
|
|
244
|
+
|| globalThis.navigator?.userAgent?.includes(' Mac ') === true
|
|
245
|
+
|| globalThis.process?.platform === 'darwin';
|
|
246
|
+
|
|
247
|
+
platform === 'Windows'
|
|
248
|
+
|| globalThis.navigator?.platform === 'Win32'
|
|
249
|
+
|| globalThis.process?.platform === 'win32';
|
|
250
|
+
|
|
251
|
+
platform === 'Linux'
|
|
252
|
+
|| globalThis.navigator?.platform?.startsWith('Linux') === true
|
|
253
|
+
|| globalThis.navigator?.userAgent?.includes(' Linux ') === true
|
|
254
|
+
|| globalThis.process?.platform === 'linux';
|
|
255
|
+
|
|
256
|
+
platform === 'Android'
|
|
257
|
+
|| globalThis.navigator?.platform === 'Android'
|
|
258
|
+
|| globalThis.navigator?.userAgent?.includes(' Android ') === true
|
|
259
|
+
|| globalThis.process?.platform === 'android';
|
|
260
|
+
|
|
261
|
+
const ESC = '\u001B[';
|
|
262
|
+
|
|
263
|
+
!isBrowser && process$1.env.TERM_PROGRAM === 'Apple_Terminal';
|
|
264
|
+
const isWindows = !isBrowser && process$1.platform === 'win32';
|
|
265
|
+
|
|
266
|
+
isBrowser ? () => {
|
|
267
|
+
throw new Error('`process.cwd()` only works in Node.js, not the browser.');
|
|
268
|
+
} : process$1.cwd;
|
|
269
|
+
|
|
270
|
+
const cursorUp = (count = 1) => ESC + count + 'A';
|
|
271
|
+
|
|
272
|
+
const cursorLeft = ESC + 'G';
|
|
273
|
+
|
|
274
|
+
const eraseLines = count => {
|
|
275
|
+
let clear = '';
|
|
276
|
+
|
|
277
|
+
for (let i = 0; i < count; i++) {
|
|
278
|
+
clear += eraseLine + (i < count - 1 ? cursorUp() : '');
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
if (count) {
|
|
282
|
+
clear += cursorLeft;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
return clear;
|
|
286
|
+
};
|
|
287
|
+
const eraseLine = ESC + '2K';
|
|
288
|
+
const eraseScreen = ESC + '2J';
|
|
289
|
+
|
|
290
|
+
const clearTerminal = isWindows
|
|
291
|
+
? `${eraseScreen}${ESC}0f`
|
|
292
|
+
// 1. Erases the screen (Only done in case `2` is not supported)
|
|
293
|
+
// 2. Erases the whole screen including scrollback buffer
|
|
294
|
+
// 3. Moves cursor to the top-left position
|
|
295
|
+
// More info: https://www.real-world-systems.com/docs/ANSIcode.html
|
|
296
|
+
: `${eraseScreen}${ESC}3J${ESC}H`;
|
|
297
|
+
|
|
298
|
+
const createDetailedMessage = (message, details = {}) => {
|
|
299
|
+
let string = `${message}`;
|
|
300
|
+
|
|
301
|
+
Object.keys(details).forEach((key) => {
|
|
302
|
+
const value = details[key];
|
|
303
|
+
string += `
|
|
304
|
+
--- ${key} ---
|
|
305
|
+
${
|
|
306
|
+
Array.isArray(value)
|
|
307
|
+
? value.join(`
|
|
308
|
+
`)
|
|
309
|
+
: value
|
|
310
|
+
}`;
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
return string;
|
|
314
|
+
};
|
|
315
|
+
|
|
316
|
+
// https://github.com/Marak/colors.js/blob/master/lib/styles.js
|
|
317
|
+
// https://stackoverflow.com/a/75985833/2634179
|
|
318
|
+
const RESET = "\x1b[0m";
|
|
319
|
+
|
|
320
|
+
const createAnsi = ({ supported }) => {
|
|
321
|
+
const ANSI = {
|
|
322
|
+
supported,
|
|
323
|
+
|
|
324
|
+
RED: "\x1b[31m",
|
|
325
|
+
GREEN: "\x1b[32m",
|
|
326
|
+
YELLOW: "\x1b[33m",
|
|
327
|
+
BLUE: "\x1b[34m",
|
|
328
|
+
MAGENTA: "\x1b[35m",
|
|
329
|
+
CYAN: "\x1b[36m",
|
|
330
|
+
GREY: "\x1b[90m",
|
|
331
|
+
color: (text, color) => {
|
|
332
|
+
if (!ANSI.supported) {
|
|
333
|
+
return text;
|
|
334
|
+
}
|
|
335
|
+
if (!color) {
|
|
336
|
+
return text;
|
|
337
|
+
}
|
|
338
|
+
if (typeof text === "string" && text.trim() === "") {
|
|
339
|
+
// cannot set color of blank chars
|
|
340
|
+
return text;
|
|
341
|
+
}
|
|
342
|
+
return `${color}${text}${RESET}`;
|
|
343
|
+
},
|
|
344
|
+
|
|
345
|
+
BOLD: "\x1b[1m",
|
|
346
|
+
UNDERLINE: "\x1b[4m",
|
|
347
|
+
STRIKE: "\x1b[9m",
|
|
348
|
+
effect: (text, effect) => {
|
|
349
|
+
if (!ANSI.supported) {
|
|
350
|
+
return text;
|
|
351
|
+
}
|
|
352
|
+
if (!effect) {
|
|
353
|
+
return text;
|
|
354
|
+
}
|
|
355
|
+
// cannot add effect to empty string
|
|
356
|
+
if (text === "") {
|
|
357
|
+
return text;
|
|
358
|
+
}
|
|
359
|
+
return `${effect}${text}${RESET}`;
|
|
360
|
+
},
|
|
361
|
+
};
|
|
362
|
+
|
|
363
|
+
return ANSI;
|
|
364
|
+
};
|
|
365
|
+
|
|
366
|
+
const processSupportsBasicColor = createSupportsColor(process.stdout).hasBasic;
|
|
367
|
+
|
|
368
|
+
const ANSI = createAnsi({
|
|
369
|
+
supported:
|
|
370
|
+
process.env.FORCE_COLOR === "1" ||
|
|
371
|
+
processSupportsBasicColor ||
|
|
372
|
+
// GitHub workflow does support ANSI but "supports-color" returns false
|
|
373
|
+
// because stream.isTTY returns false, see https://github.com/actions/runner/issues/241
|
|
374
|
+
process.env.GITHUB_WORKFLOW,
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
// see also https://github.com/sindresorhus/figures
|
|
378
|
+
|
|
379
|
+
const createUnicode = ({ supported, ANSI }) => {
|
|
380
|
+
const UNICODE = {
|
|
381
|
+
supported,
|
|
382
|
+
get COMMAND_RAW() {
|
|
383
|
+
return UNICODE.supported ? `❯` : `>`;
|
|
384
|
+
},
|
|
385
|
+
get OK_RAW() {
|
|
386
|
+
return UNICODE.supported ? `✔` : `√`;
|
|
387
|
+
},
|
|
388
|
+
get FAILURE_RAW() {
|
|
389
|
+
return UNICODE.supported ? `✖` : `×`;
|
|
390
|
+
},
|
|
391
|
+
get DEBUG_RAW() {
|
|
392
|
+
return UNICODE.supported ? `◆` : `♦`;
|
|
393
|
+
},
|
|
394
|
+
get INFO_RAW() {
|
|
395
|
+
return UNICODE.supported ? `ℹ` : `i`;
|
|
396
|
+
},
|
|
397
|
+
get WARNING_RAW() {
|
|
398
|
+
return UNICODE.supported ? `⚠` : `‼`;
|
|
399
|
+
},
|
|
400
|
+
get CIRCLE_CROSS_RAW() {
|
|
401
|
+
return UNICODE.supported ? `ⓧ` : `(×)`;
|
|
402
|
+
},
|
|
403
|
+
get CIRCLE_DOTTED_RAW() {
|
|
404
|
+
return UNICODE.supported ? `◌` : `*`;
|
|
405
|
+
},
|
|
406
|
+
get COMMAND() {
|
|
407
|
+
return ANSI.color(UNICODE.COMMAND_RAW, ANSI.GREY); // ANSI_MAGENTA)
|
|
408
|
+
},
|
|
409
|
+
get OK() {
|
|
410
|
+
return ANSI.color(UNICODE.OK_RAW, ANSI.GREEN);
|
|
411
|
+
},
|
|
412
|
+
get FAILURE() {
|
|
413
|
+
return ANSI.color(UNICODE.FAILURE_RAW, ANSI.RED);
|
|
414
|
+
},
|
|
415
|
+
get DEBUG() {
|
|
416
|
+
return ANSI.color(UNICODE.DEBUG_RAW, ANSI.GREY);
|
|
417
|
+
},
|
|
418
|
+
get INFO() {
|
|
419
|
+
return ANSI.color(UNICODE.INFO_RAW, ANSI.BLUE);
|
|
420
|
+
},
|
|
421
|
+
get WARNING() {
|
|
422
|
+
return ANSI.color(UNICODE.WARNING_RAW, ANSI.YELLOW);
|
|
423
|
+
},
|
|
424
|
+
get CIRCLE_CROSS() {
|
|
425
|
+
return ANSI.color(UNICODE.CIRCLE_CROSS_RAW, ANSI.RED);
|
|
426
|
+
},
|
|
427
|
+
get ELLIPSIS() {
|
|
428
|
+
return UNICODE.supported ? `…` : `...`;
|
|
429
|
+
},
|
|
430
|
+
};
|
|
431
|
+
return UNICODE;
|
|
432
|
+
};
|
|
433
|
+
|
|
434
|
+
const UNICODE = createUnicode({
|
|
435
|
+
supported: process.env.FORCE_UNICODE === "1" || isUnicodeSupported(),
|
|
436
|
+
ANSI,
|
|
437
|
+
});
|
|
438
|
+
|
|
439
|
+
const getPrecision = (number) => {
|
|
440
|
+
if (Math.floor(number) === number) return 0;
|
|
441
|
+
const [, decimals] = number.toString().split(".");
|
|
442
|
+
return decimals.length || 0;
|
|
443
|
+
};
|
|
444
|
+
|
|
445
|
+
const setRoundedPrecision = (
|
|
446
|
+
number,
|
|
447
|
+
{ decimals = 1, decimalsWhenSmall = decimals } = {},
|
|
448
|
+
) => {
|
|
449
|
+
return setDecimalsPrecision(number, {
|
|
450
|
+
decimals,
|
|
451
|
+
decimalsWhenSmall,
|
|
452
|
+
transform: Math.round,
|
|
453
|
+
});
|
|
454
|
+
};
|
|
455
|
+
|
|
456
|
+
const setPrecision = (
|
|
457
|
+
number,
|
|
458
|
+
{ decimals = 1, decimalsWhenSmall = decimals } = {},
|
|
459
|
+
) => {
|
|
460
|
+
return setDecimalsPrecision(number, {
|
|
461
|
+
decimals,
|
|
462
|
+
decimalsWhenSmall,
|
|
463
|
+
transform: parseInt,
|
|
464
|
+
});
|
|
465
|
+
};
|
|
466
|
+
|
|
467
|
+
const setDecimalsPrecision = (
|
|
468
|
+
number,
|
|
469
|
+
{
|
|
470
|
+
transform,
|
|
471
|
+
decimals, // max decimals for number in [-Infinity, -1[]1, Infinity]
|
|
472
|
+
decimalsWhenSmall, // max decimals for number in [-1,1]
|
|
473
|
+
} = {},
|
|
474
|
+
) => {
|
|
475
|
+
if (number === 0) {
|
|
476
|
+
return 0;
|
|
477
|
+
}
|
|
478
|
+
let numberCandidate = Math.abs(number);
|
|
479
|
+
if (numberCandidate < 1) {
|
|
480
|
+
const integerGoal = Math.pow(10, decimalsWhenSmall - 1);
|
|
481
|
+
let i = 1;
|
|
482
|
+
while (numberCandidate < integerGoal) {
|
|
483
|
+
numberCandidate *= 10;
|
|
484
|
+
i *= 10;
|
|
485
|
+
}
|
|
486
|
+
const asInteger = transform(numberCandidate);
|
|
487
|
+
const asFloat = asInteger / i;
|
|
488
|
+
return number < 0 ? -asFloat : asFloat;
|
|
489
|
+
}
|
|
490
|
+
const coef = Math.pow(10, decimals);
|
|
491
|
+
const numberMultiplied = (number + Number.EPSILON) * coef;
|
|
492
|
+
const asInteger = transform(numberMultiplied);
|
|
493
|
+
const asFloat = asInteger / coef;
|
|
494
|
+
return number < 0 ? -asFloat : asFloat;
|
|
495
|
+
};
|
|
496
|
+
const unitShort = {
|
|
497
|
+
year: "y",
|
|
498
|
+
month: "m",
|
|
499
|
+
week: "w",
|
|
500
|
+
day: "d",
|
|
501
|
+
hour: "h",
|
|
502
|
+
minute: "m",
|
|
503
|
+
second: "s",
|
|
504
|
+
};
|
|
505
|
+
|
|
506
|
+
const humanizeDuration = (
|
|
507
|
+
ms,
|
|
508
|
+
{ short, rounded = true, decimals } = {},
|
|
509
|
+
) => {
|
|
510
|
+
// ignore ms below meaningfulMs so that:
|
|
511
|
+
// humanizeDuration(0.5) -> "0 second"
|
|
512
|
+
// humanizeDuration(1.1) -> "0.001 second" (and not "0.0011 second")
|
|
513
|
+
// This tool is meant to be read by humans and it would be barely readable to see
|
|
514
|
+
// "0.0001 second" (stands for 0.1 millisecond)
|
|
515
|
+
// yes we could return "0.1 millisecond" but we choosed consistency over precision
|
|
516
|
+
// so that the prefered unit is "second" (and does not become millisecond when ms is super small)
|
|
517
|
+
if (ms < 1) {
|
|
518
|
+
return short ? "0s" : "0 second";
|
|
519
|
+
}
|
|
520
|
+
const { primary, remaining } = parseMs(ms);
|
|
521
|
+
if (!remaining) {
|
|
522
|
+
return humanizeDurationUnit(primary, {
|
|
523
|
+
decimals:
|
|
524
|
+
decimals === undefined ? (primary.name === "second" ? 1 : 0) : decimals,
|
|
525
|
+
short,
|
|
526
|
+
rounded,
|
|
527
|
+
});
|
|
528
|
+
}
|
|
529
|
+
return `${humanizeDurationUnit(primary, {
|
|
530
|
+
decimals: decimals === undefined ? 0 : decimals,
|
|
531
|
+
short,
|
|
532
|
+
rounded,
|
|
533
|
+
})} and ${humanizeDurationUnit(remaining, {
|
|
534
|
+
decimals: decimals === undefined ? 0 : decimals,
|
|
535
|
+
short,
|
|
536
|
+
rounded,
|
|
537
|
+
})}`;
|
|
538
|
+
};
|
|
539
|
+
const humanizeDurationUnit = (unit, { decimals, short, rounded }) => {
|
|
540
|
+
const count = rounded
|
|
541
|
+
? setRoundedPrecision(unit.count, { decimals })
|
|
542
|
+
: setPrecision(unit.count, { decimals });
|
|
543
|
+
let name = unit.name;
|
|
544
|
+
if (short) {
|
|
545
|
+
name = unitShort[name];
|
|
546
|
+
return `${count}${name}`;
|
|
547
|
+
}
|
|
548
|
+
if (count <= 1) {
|
|
549
|
+
return `${count} ${name}`;
|
|
550
|
+
}
|
|
551
|
+
return `${count} ${name}s`;
|
|
552
|
+
};
|
|
553
|
+
const MS_PER_UNITS = {
|
|
554
|
+
year: 31_557_600_000,
|
|
555
|
+
month: 2_629_000_000,
|
|
556
|
+
week: 604_800_000,
|
|
557
|
+
day: 86_400_000,
|
|
558
|
+
hour: 3_600_000,
|
|
559
|
+
minute: 60_000,
|
|
560
|
+
second: 1000,
|
|
561
|
+
};
|
|
562
|
+
|
|
563
|
+
const parseMs = (ms) => {
|
|
564
|
+
const unitNames = Object.keys(MS_PER_UNITS);
|
|
565
|
+
const smallestUnitName = unitNames[unitNames.length - 1];
|
|
566
|
+
let firstUnitName = smallestUnitName;
|
|
567
|
+
let firstUnitCount = ms / MS_PER_UNITS[smallestUnitName];
|
|
568
|
+
const firstUnitIndex = unitNames.findIndex((unitName) => {
|
|
569
|
+
if (unitName === smallestUnitName) {
|
|
570
|
+
return false;
|
|
571
|
+
}
|
|
572
|
+
const msPerUnit = MS_PER_UNITS[unitName];
|
|
573
|
+
const unitCount = Math.floor(ms / msPerUnit);
|
|
574
|
+
if (unitCount) {
|
|
575
|
+
firstUnitName = unitName;
|
|
576
|
+
firstUnitCount = unitCount;
|
|
577
|
+
return true;
|
|
578
|
+
}
|
|
579
|
+
return false;
|
|
580
|
+
});
|
|
581
|
+
if (firstUnitName === smallestUnitName) {
|
|
582
|
+
return {
|
|
583
|
+
primary: {
|
|
584
|
+
name: firstUnitName,
|
|
585
|
+
count: firstUnitCount,
|
|
586
|
+
},
|
|
587
|
+
};
|
|
588
|
+
}
|
|
589
|
+
const remainingMs = ms - firstUnitCount * MS_PER_UNITS[firstUnitName];
|
|
590
|
+
const remainingUnitName = unitNames[firstUnitIndex + 1];
|
|
591
|
+
const remainingUnitCount = remainingMs / MS_PER_UNITS[remainingUnitName];
|
|
592
|
+
// - 1 year and 1 second is too much information
|
|
593
|
+
// so we don't check the remaining units
|
|
594
|
+
// - 1 year and 0.0001 week is awful
|
|
595
|
+
// hence the if below
|
|
596
|
+
if (Math.round(remainingUnitCount) < 1) {
|
|
597
|
+
return {
|
|
598
|
+
primary: {
|
|
599
|
+
name: firstUnitName,
|
|
600
|
+
count: firstUnitCount,
|
|
601
|
+
},
|
|
602
|
+
};
|
|
603
|
+
}
|
|
604
|
+
// - 1 year and 1 month is great
|
|
605
|
+
return {
|
|
606
|
+
primary: {
|
|
607
|
+
name: firstUnitName,
|
|
608
|
+
count: firstUnitCount,
|
|
609
|
+
},
|
|
610
|
+
remaining: {
|
|
611
|
+
name: remainingUnitName,
|
|
612
|
+
count: remainingUnitCount,
|
|
613
|
+
},
|
|
614
|
+
};
|
|
615
|
+
};
|
|
616
|
+
|
|
617
|
+
const humanizeFileSize = (numberOfBytes, { decimals, short } = {}) => {
|
|
618
|
+
return inspectBytes(numberOfBytes, { decimals, short });
|
|
619
|
+
};
|
|
620
|
+
|
|
621
|
+
const inspectBytes = (
|
|
622
|
+
number,
|
|
623
|
+
{ fixedDecimals = false, decimals, short } = {},
|
|
624
|
+
) => {
|
|
625
|
+
if (number === 0) {
|
|
626
|
+
return `0 B`;
|
|
627
|
+
}
|
|
628
|
+
const exponent = Math.min(
|
|
629
|
+
Math.floor(Math.log10(number) / 3),
|
|
630
|
+
BYTE_UNITS.length - 1,
|
|
631
|
+
);
|
|
632
|
+
const unitNumber = number / Math.pow(1000, exponent);
|
|
633
|
+
const unitName = BYTE_UNITS[exponent];
|
|
634
|
+
if (decimals === undefined) {
|
|
635
|
+
if (unitNumber < 100) {
|
|
636
|
+
decimals = 1;
|
|
637
|
+
} else {
|
|
638
|
+
decimals = 0;
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
const unitNumberRounded = setRoundedPrecision(unitNumber, {
|
|
642
|
+
decimals,
|
|
643
|
+
decimalsWhenSmall: 1,
|
|
644
|
+
});
|
|
645
|
+
const value = fixedDecimals
|
|
646
|
+
? unitNumberRounded.toFixed(decimals)
|
|
647
|
+
: unitNumberRounded;
|
|
648
|
+
if (short) {
|
|
649
|
+
return `${value}${unitName}`;
|
|
650
|
+
}
|
|
651
|
+
return `${value} ${unitName}`;
|
|
652
|
+
};
|
|
653
|
+
|
|
654
|
+
const BYTE_UNITS = ["B", "kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"];
|
|
655
|
+
|
|
656
|
+
const distributePercentages = (
|
|
657
|
+
namedNumbers,
|
|
658
|
+
{ maxPrecisionHint = 2 } = {},
|
|
659
|
+
) => {
|
|
660
|
+
const numberNames = Object.keys(namedNumbers);
|
|
661
|
+
if (numberNames.length === 0) {
|
|
662
|
+
return {};
|
|
663
|
+
}
|
|
664
|
+
if (numberNames.length === 1) {
|
|
665
|
+
const firstNumberName = numberNames[0];
|
|
666
|
+
return { [firstNumberName]: "100 %" };
|
|
667
|
+
}
|
|
668
|
+
const numbers = numberNames.map((name) => namedNumbers[name]);
|
|
669
|
+
const total = numbers.reduce((sum, value) => sum + value, 0);
|
|
670
|
+
const ratios = numbers.map((number) => number / total);
|
|
671
|
+
const percentages = {};
|
|
672
|
+
ratios.pop();
|
|
673
|
+
ratios.forEach((ratio, index) => {
|
|
674
|
+
const percentage = ratio * 100;
|
|
675
|
+
percentages[numberNames[index]] = percentage;
|
|
676
|
+
});
|
|
677
|
+
const lowestPercentage = (1 / Math.pow(10, maxPrecisionHint)) * 100;
|
|
678
|
+
let precision = 0;
|
|
679
|
+
Object.keys(percentages).forEach((name) => {
|
|
680
|
+
const percentage = percentages[name];
|
|
681
|
+
if (percentage < lowestPercentage) {
|
|
682
|
+
// check the amout of meaningful decimals
|
|
683
|
+
// and that what we will use
|
|
684
|
+
const percentageRounded = setRoundedPrecision(percentage);
|
|
685
|
+
const percentagePrecision = getPrecision(percentageRounded);
|
|
686
|
+
if (percentagePrecision > precision) {
|
|
687
|
+
precision = percentagePrecision;
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
});
|
|
691
|
+
let remainingPercentage = 100;
|
|
692
|
+
|
|
693
|
+
Object.keys(percentages).forEach((name) => {
|
|
694
|
+
const percentage = percentages[name];
|
|
695
|
+
const percentageAllocated = setRoundedPrecision(percentage, {
|
|
696
|
+
decimals: precision,
|
|
697
|
+
});
|
|
698
|
+
remainingPercentage -= percentageAllocated;
|
|
699
|
+
percentages[name] = percentageAllocated;
|
|
700
|
+
});
|
|
701
|
+
const lastName = numberNames[numberNames.length - 1];
|
|
702
|
+
percentages[lastName] = setRoundedPrecision(remainingPercentage, {
|
|
703
|
+
decimals: precision,
|
|
704
|
+
});
|
|
705
|
+
return percentages;
|
|
706
|
+
};
|
|
707
|
+
|
|
708
|
+
const formatDefault = (v) => v;
|
|
709
|
+
|
|
710
|
+
const generateContentFrame = ({
|
|
711
|
+
content,
|
|
712
|
+
line,
|
|
713
|
+
column,
|
|
714
|
+
|
|
715
|
+
linesAbove = 3,
|
|
716
|
+
linesBelow = 0,
|
|
717
|
+
lineMaxWidth = 120,
|
|
718
|
+
lineNumbersOnTheLeft = true,
|
|
719
|
+
lineMarker = true,
|
|
720
|
+
columnMarker = true,
|
|
721
|
+
format = formatDefault,
|
|
722
|
+
} = {}) => {
|
|
723
|
+
const lineStrings = content.split(/\r?\n/);
|
|
724
|
+
if (line === 0) line = 1;
|
|
725
|
+
if (column === undefined) {
|
|
726
|
+
columnMarker = false;
|
|
727
|
+
column = 1;
|
|
728
|
+
}
|
|
729
|
+
if (column === 0) column = 1;
|
|
730
|
+
|
|
731
|
+
let lineStartIndex = line - 1 - linesAbove;
|
|
732
|
+
if (lineStartIndex < 0) {
|
|
733
|
+
lineStartIndex = 0;
|
|
734
|
+
}
|
|
735
|
+
let lineEndIndex = line - 1 + linesBelow;
|
|
736
|
+
if (lineEndIndex > lineStrings.length - 1) {
|
|
737
|
+
lineEndIndex = lineStrings.length - 1;
|
|
738
|
+
}
|
|
739
|
+
if (columnMarker) {
|
|
740
|
+
// human reader deduce the line when there is a column marker
|
|
741
|
+
lineMarker = false;
|
|
742
|
+
}
|
|
743
|
+
if (line - 1 === lineEndIndex) {
|
|
744
|
+
lineMarker = false; // useless because last line
|
|
745
|
+
}
|
|
746
|
+
let lineIndex = lineStartIndex;
|
|
747
|
+
|
|
748
|
+
let columnsBefore;
|
|
749
|
+
let columnsAfter;
|
|
750
|
+
if (column > lineMaxWidth) {
|
|
751
|
+
columnsBefore = column - Math.ceil(lineMaxWidth / 2);
|
|
752
|
+
columnsAfter = column + Math.floor(lineMaxWidth / 2);
|
|
753
|
+
} else {
|
|
754
|
+
columnsBefore = 0;
|
|
755
|
+
columnsAfter = lineMaxWidth;
|
|
756
|
+
}
|
|
757
|
+
let columnMarkerIndex = column - 1 - columnsBefore;
|
|
758
|
+
|
|
759
|
+
let source = "";
|
|
760
|
+
while (lineIndex <= lineEndIndex) {
|
|
761
|
+
const lineString = lineStrings[lineIndex];
|
|
762
|
+
const lineNumber = lineIndex + 1;
|
|
763
|
+
const isLastLine = lineIndex === lineEndIndex;
|
|
764
|
+
const isMainLine = lineNumber === line;
|
|
765
|
+
lineIndex++;
|
|
766
|
+
|
|
767
|
+
{
|
|
768
|
+
if (lineMarker) {
|
|
769
|
+
if (isMainLine) {
|
|
770
|
+
source += `${format(">", "marker_line")} `;
|
|
771
|
+
} else {
|
|
772
|
+
source += " ";
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
if (lineNumbersOnTheLeft) {
|
|
776
|
+
// fill with spaces to ensure if line moves from 7,8,9 to 10 the display is still great
|
|
777
|
+
const asideSource = `${fillLeft(lineNumber, lineEndIndex + 1)} |`;
|
|
778
|
+
source += `${format(asideSource, "line_number_aside")} `;
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
{
|
|
782
|
+
source += truncateLine(lineString, {
|
|
783
|
+
start: columnsBefore,
|
|
784
|
+
end: columnsAfter,
|
|
785
|
+
prefix: "…",
|
|
786
|
+
suffix: "…",
|
|
787
|
+
format,
|
|
788
|
+
});
|
|
789
|
+
}
|
|
790
|
+
{
|
|
791
|
+
if (columnMarker && isMainLine) {
|
|
792
|
+
source += `\n`;
|
|
793
|
+
if (lineMarker) {
|
|
794
|
+
source += " ";
|
|
795
|
+
}
|
|
796
|
+
if (lineNumbersOnTheLeft) {
|
|
797
|
+
const asideSpaces = `${fillLeft(lineNumber, lineEndIndex + 1)} | `
|
|
798
|
+
.length;
|
|
799
|
+
source += " ".repeat(asideSpaces);
|
|
800
|
+
}
|
|
801
|
+
source += " ".repeat(columnMarkerIndex);
|
|
802
|
+
source += format("^", "marker_column");
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
if (!isLastLine) {
|
|
806
|
+
source += "\n";
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
return source;
|
|
810
|
+
};
|
|
811
|
+
|
|
812
|
+
const truncateLine = (line, { start, end, prefix, suffix, format }) => {
|
|
813
|
+
const lastIndex = line.length;
|
|
814
|
+
|
|
815
|
+
if (line.length === 0) {
|
|
816
|
+
// don't show any ellipsis if the line is empty
|
|
817
|
+
// because it's not truncated in that case
|
|
818
|
+
return "";
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
const startTruncated = start > 0;
|
|
822
|
+
const endTruncated = lastIndex > end;
|
|
823
|
+
|
|
824
|
+
let from = startTruncated ? start + prefix.length : start;
|
|
825
|
+
let to = endTruncated ? end - suffix.length : end;
|
|
826
|
+
if (to > lastIndex) to = lastIndex;
|
|
827
|
+
|
|
828
|
+
if (start >= lastIndex || from === to) {
|
|
829
|
+
return "";
|
|
830
|
+
}
|
|
831
|
+
let result = "";
|
|
832
|
+
while (from < to) {
|
|
833
|
+
result += format(line[from], "char");
|
|
834
|
+
from++;
|
|
835
|
+
}
|
|
836
|
+
if (result.length === 0) {
|
|
837
|
+
return "";
|
|
838
|
+
}
|
|
839
|
+
if (startTruncated && endTruncated) {
|
|
840
|
+
return `${format(prefix, "marker_overflow_left")}${result}${format(
|
|
841
|
+
suffix,
|
|
842
|
+
"marker_overflow_right",
|
|
843
|
+
)}`;
|
|
844
|
+
}
|
|
845
|
+
if (startTruncated) {
|
|
846
|
+
return `${format(prefix, "marker_overflow_left")}${result}`;
|
|
847
|
+
}
|
|
848
|
+
if (endTruncated) {
|
|
849
|
+
return `${result}${format(suffix, "marker_overflow_right")}`;
|
|
850
|
+
}
|
|
851
|
+
return result;
|
|
852
|
+
};
|
|
853
|
+
|
|
854
|
+
const fillLeft = (value, biggestValue, char = " ") => {
|
|
855
|
+
const width = String(value).length;
|
|
856
|
+
const biggestWidth = String(biggestValue).length;
|
|
857
|
+
let missingWidth = biggestWidth - width;
|
|
858
|
+
let padded = "";
|
|
859
|
+
while (missingWidth--) {
|
|
860
|
+
padded += char;
|
|
861
|
+
}
|
|
862
|
+
padded += value;
|
|
863
|
+
return padded;
|
|
864
|
+
};
|
|
865
|
+
|
|
866
|
+
const LOG_LEVEL_OFF = "off";
|
|
867
|
+
|
|
868
|
+
const LOG_LEVEL_DEBUG = "debug";
|
|
869
|
+
|
|
870
|
+
const LOG_LEVEL_INFO = "info";
|
|
871
|
+
|
|
872
|
+
const LOG_LEVEL_WARN = "warn";
|
|
873
|
+
|
|
874
|
+
const LOG_LEVEL_ERROR = "error";
|
|
875
|
+
|
|
876
|
+
const createLogger = ({ logLevel = LOG_LEVEL_INFO } = {}) => {
|
|
877
|
+
if (logLevel === LOG_LEVEL_DEBUG) {
|
|
878
|
+
return {
|
|
879
|
+
level: "debug",
|
|
880
|
+
levels: { debug: true, info: true, warn: true, error: true },
|
|
881
|
+
debug,
|
|
882
|
+
info,
|
|
883
|
+
warn,
|
|
884
|
+
error,
|
|
885
|
+
};
|
|
886
|
+
}
|
|
887
|
+
if (logLevel === LOG_LEVEL_INFO) {
|
|
888
|
+
return {
|
|
889
|
+
level: "info",
|
|
890
|
+
levels: { debug: false, info: true, warn: true, error: true },
|
|
891
|
+
debug: debugDisabled,
|
|
892
|
+
info,
|
|
893
|
+
warn,
|
|
894
|
+
error,
|
|
895
|
+
};
|
|
896
|
+
}
|
|
897
|
+
if (logLevel === LOG_LEVEL_WARN) {
|
|
898
|
+
return {
|
|
899
|
+
level: "warn",
|
|
900
|
+
levels: { debug: false, info: false, warn: true, error: true },
|
|
901
|
+
debug: debugDisabled,
|
|
902
|
+
info: infoDisabled,
|
|
903
|
+
warn,
|
|
904
|
+
error,
|
|
905
|
+
};
|
|
906
|
+
}
|
|
907
|
+
if (logLevel === LOG_LEVEL_ERROR) {
|
|
908
|
+
return {
|
|
909
|
+
level: "error",
|
|
910
|
+
levels: { debug: false, info: false, warn: false, error: true },
|
|
911
|
+
debug: debugDisabled,
|
|
912
|
+
info: infoDisabled,
|
|
913
|
+
warn: warnDisabled,
|
|
914
|
+
error,
|
|
915
|
+
};
|
|
916
|
+
}
|
|
917
|
+
if (logLevel === LOG_LEVEL_OFF) {
|
|
918
|
+
return {
|
|
919
|
+
level: "off",
|
|
920
|
+
levels: { debug: false, info: false, warn: false, error: false },
|
|
921
|
+
debug: debugDisabled,
|
|
922
|
+
info: infoDisabled,
|
|
923
|
+
warn: warnDisabled,
|
|
924
|
+
error: errorDisabled,
|
|
925
|
+
};
|
|
926
|
+
}
|
|
927
|
+
throw new Error(`unexpected logLevel.
|
|
928
|
+
--- logLevel ---
|
|
929
|
+
${logLevel}
|
|
930
|
+
--- allowed log levels ---
|
|
931
|
+
${LOG_LEVEL_OFF}
|
|
932
|
+
${LOG_LEVEL_ERROR}
|
|
933
|
+
${LOG_LEVEL_WARN}
|
|
934
|
+
${LOG_LEVEL_INFO}
|
|
935
|
+
${LOG_LEVEL_DEBUG}`);
|
|
936
|
+
};
|
|
937
|
+
|
|
938
|
+
const debug = (...args) => console.debug(...args);
|
|
939
|
+
|
|
940
|
+
const debugDisabled = () => {};
|
|
941
|
+
|
|
942
|
+
const info = (...args) => console.info(...args);
|
|
943
|
+
|
|
944
|
+
const infoDisabled = () => {};
|
|
945
|
+
|
|
946
|
+
const warn = (...args) => console.warn(...args);
|
|
947
|
+
|
|
948
|
+
const warnDisabled = () => {};
|
|
949
|
+
|
|
950
|
+
const error = (...args) => console.error(...args);
|
|
951
|
+
|
|
952
|
+
const errorDisabled = () => {};
|
|
953
|
+
|
|
954
|
+
/*
|
|
955
|
+
* see also https://github.com/vadimdemedes/ink
|
|
956
|
+
*/
|
|
957
|
+
|
|
958
|
+
|
|
959
|
+
const createDynamicLog = ({
|
|
960
|
+
stream = process.stdout,
|
|
961
|
+
clearTerminalAllowed,
|
|
962
|
+
onVerticalOverflow = () => {},
|
|
963
|
+
onWriteFromOutside = () => {},
|
|
964
|
+
} = {}) => {
|
|
965
|
+
const { columns = 80, rows = 24 } = stream;
|
|
966
|
+
const dynamicLog = {
|
|
967
|
+
destroyed: false,
|
|
968
|
+
onVerticalOverflow,
|
|
969
|
+
onWriteFromOutside,
|
|
970
|
+
};
|
|
971
|
+
|
|
972
|
+
let lastOutput = "";
|
|
973
|
+
let lastOutputFromOutside = "";
|
|
974
|
+
let clearAttemptResult;
|
|
975
|
+
let writing = false;
|
|
976
|
+
|
|
977
|
+
const getErasePreviousOutput = () => {
|
|
978
|
+
// nothing to clear
|
|
979
|
+
if (!lastOutput) {
|
|
980
|
+
return "";
|
|
981
|
+
}
|
|
982
|
+
if (clearAttemptResult !== undefined) {
|
|
983
|
+
return "";
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
const logLines = lastOutput.split(/\r\n|\r|\n/);
|
|
987
|
+
let visualLineCount = 0;
|
|
988
|
+
for (const logLine of logLines) {
|
|
989
|
+
const width = stringWidth(logLine);
|
|
990
|
+
if (width === 0) {
|
|
991
|
+
visualLineCount++;
|
|
992
|
+
} else {
|
|
993
|
+
visualLineCount += Math.ceil(width / columns);
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
if (visualLineCount > rows) {
|
|
998
|
+
if (clearTerminalAllowed) {
|
|
999
|
+
clearAttemptResult = true;
|
|
1000
|
+
return clearTerminal;
|
|
1001
|
+
}
|
|
1002
|
+
// the whole log cannot be cleared because it's vertically to long
|
|
1003
|
+
// (longer than terminal height)
|
|
1004
|
+
// readline.moveCursor cannot move cursor higher than screen height
|
|
1005
|
+
// it means we would only clear the visible part of the log
|
|
1006
|
+
// better keep the log untouched
|
|
1007
|
+
clearAttemptResult = false;
|
|
1008
|
+
dynamicLog.onVerticalOverflow();
|
|
1009
|
+
return "";
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
clearAttemptResult = true;
|
|
1013
|
+
return eraseLines(visualLineCount);
|
|
1014
|
+
};
|
|
1015
|
+
|
|
1016
|
+
const update = (string) => {
|
|
1017
|
+
if (dynamicLog.destroyed) {
|
|
1018
|
+
throw new Error("Cannot write log after destroy");
|
|
1019
|
+
}
|
|
1020
|
+
let stringToWrite = string;
|
|
1021
|
+
if (lastOutput) {
|
|
1022
|
+
if (lastOutputFromOutside) {
|
|
1023
|
+
// We don't want to clear logs written by other code,
|
|
1024
|
+
// it makes output unreadable and might erase precious information
|
|
1025
|
+
// To detect this we put a spy on the stream.
|
|
1026
|
+
// The spy is required only if we actually wrote something in the stream
|
|
1027
|
+
// something else than this code has written in the stream
|
|
1028
|
+
// so we just write without clearing (append instead of replacing)
|
|
1029
|
+
lastOutput = "";
|
|
1030
|
+
lastOutputFromOutside = "";
|
|
1031
|
+
} else {
|
|
1032
|
+
stringToWrite = `${getErasePreviousOutput()}${string}`;
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
1035
|
+
writing = true;
|
|
1036
|
+
stream.write(stringToWrite);
|
|
1037
|
+
lastOutput = string;
|
|
1038
|
+
writing = false;
|
|
1039
|
+
clearAttemptResult = undefined;
|
|
1040
|
+
};
|
|
1041
|
+
|
|
1042
|
+
const clearDuringFunctionCall = (
|
|
1043
|
+
callback,
|
|
1044
|
+
ouputAfterCallback = lastOutput,
|
|
1045
|
+
) => {
|
|
1046
|
+
// 1. Erase the current log
|
|
1047
|
+
// 2. Call callback (expect to write something on stdout)
|
|
1048
|
+
// 3. Restore the current log
|
|
1049
|
+
// During step 2. we expect a "write from outside" so we uninstall
|
|
1050
|
+
// the stream spy during function call
|
|
1051
|
+
update("");
|
|
1052
|
+
|
|
1053
|
+
writing = true;
|
|
1054
|
+
callback();
|
|
1055
|
+
writing = false;
|
|
1056
|
+
|
|
1057
|
+
update(ouputAfterCallback);
|
|
1058
|
+
};
|
|
1059
|
+
|
|
1060
|
+
const writeFromOutsideEffect = (value) => {
|
|
1061
|
+
if (!lastOutput) {
|
|
1062
|
+
// we don't care if the log never wrote anything
|
|
1063
|
+
// or if last update() wrote an empty string
|
|
1064
|
+
return;
|
|
1065
|
+
}
|
|
1066
|
+
if (writing) {
|
|
1067
|
+
return;
|
|
1068
|
+
}
|
|
1069
|
+
lastOutputFromOutside = value;
|
|
1070
|
+
dynamicLog.onWriteFromOutside(value);
|
|
1071
|
+
};
|
|
1072
|
+
|
|
1073
|
+
let removeStreamSpy;
|
|
1074
|
+
if (stream === process.stdout) {
|
|
1075
|
+
const removeStdoutSpy = spyStreamOutput(
|
|
1076
|
+
process.stdout,
|
|
1077
|
+
writeFromOutsideEffect,
|
|
1078
|
+
);
|
|
1079
|
+
const removeStderrSpy = spyStreamOutput(
|
|
1080
|
+
process.stderr,
|
|
1081
|
+
writeFromOutsideEffect,
|
|
1082
|
+
);
|
|
1083
|
+
removeStreamSpy = () => {
|
|
1084
|
+
removeStdoutSpy();
|
|
1085
|
+
removeStderrSpy();
|
|
1086
|
+
};
|
|
1087
|
+
} else {
|
|
1088
|
+
removeStreamSpy = spyStreamOutput(stream, writeFromOutsideEffect);
|
|
1089
|
+
}
|
|
1090
|
+
|
|
1091
|
+
const destroy = () => {
|
|
1092
|
+
dynamicLog.destroyed = true;
|
|
1093
|
+
if (removeStreamSpy) {
|
|
1094
|
+
removeStreamSpy();
|
|
1095
|
+
removeStreamSpy = null;
|
|
1096
|
+
lastOutput = "";
|
|
1097
|
+
lastOutputFromOutside = "";
|
|
1098
|
+
}
|
|
1099
|
+
};
|
|
1100
|
+
|
|
1101
|
+
Object.assign(dynamicLog, {
|
|
1102
|
+
update,
|
|
1103
|
+
destroy,
|
|
1104
|
+
stream,
|
|
1105
|
+
clearDuringFunctionCall,
|
|
1106
|
+
});
|
|
1107
|
+
return dynamicLog;
|
|
1108
|
+
};
|
|
1109
|
+
|
|
1110
|
+
// maybe https://github.com/gajus/output-interceptor/tree/v3.0.0 ?
|
|
1111
|
+
// the problem with listening data on stdout
|
|
1112
|
+
// is that node.js will later throw error if stream gets closed
|
|
1113
|
+
// while something listening data on it
|
|
1114
|
+
const spyStreamOutput = (stream, callback) => {
|
|
1115
|
+
let output = "";
|
|
1116
|
+
let installed = true;
|
|
1117
|
+
const originalWrite = stream.write;
|
|
1118
|
+
stream.write = function (...args /* chunk, encoding, callback */) {
|
|
1119
|
+
output += args;
|
|
1120
|
+
callback(output);
|
|
1121
|
+
return originalWrite.call(this, ...args);
|
|
1122
|
+
};
|
|
1123
|
+
|
|
1124
|
+
const uninstall = () => {
|
|
1125
|
+
if (!installed) {
|
|
1126
|
+
return;
|
|
1127
|
+
}
|
|
1128
|
+
stream.write = originalWrite;
|
|
1129
|
+
installed = false;
|
|
1130
|
+
};
|
|
1131
|
+
|
|
1132
|
+
return () => {
|
|
1133
|
+
uninstall();
|
|
1134
|
+
return output;
|
|
1135
|
+
};
|
|
1136
|
+
};
|
|
1137
|
+
|
|
1138
|
+
const startSpinner = ({
|
|
1139
|
+
dynamicLog,
|
|
1140
|
+
frames = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"],
|
|
1141
|
+
fps = 20,
|
|
1142
|
+
keepProcessAlive = false,
|
|
1143
|
+
stopOnWriteFromOutside = true,
|
|
1144
|
+
stopOnVerticalOverflow = true,
|
|
1145
|
+
render = () => "",
|
|
1146
|
+
effect = () => {},
|
|
1147
|
+
animated = dynamicLog.stream.isTTY,
|
|
1148
|
+
}) => {
|
|
1149
|
+
let frameIndex = 0;
|
|
1150
|
+
let interval;
|
|
1151
|
+
let running = true;
|
|
1152
|
+
|
|
1153
|
+
const spinner = {
|
|
1154
|
+
message: undefined,
|
|
1155
|
+
};
|
|
1156
|
+
|
|
1157
|
+
const update = (message) => {
|
|
1158
|
+
spinner.message = running
|
|
1159
|
+
? `${frames[frameIndex]} ${message}\n`
|
|
1160
|
+
: `${message}\n`;
|
|
1161
|
+
return spinner.message;
|
|
1162
|
+
};
|
|
1163
|
+
spinner.update = update;
|
|
1164
|
+
|
|
1165
|
+
let cleanup;
|
|
1166
|
+
if (animated && ANSI.supported) {
|
|
1167
|
+
running = true;
|
|
1168
|
+
cleanup = effect();
|
|
1169
|
+
dynamicLog.update(update(render()));
|
|
1170
|
+
|
|
1171
|
+
interval = setInterval(() => {
|
|
1172
|
+
frameIndex = frameIndex === frames.length - 1 ? 0 : frameIndex + 1;
|
|
1173
|
+
dynamicLog.update(update(render()));
|
|
1174
|
+
}, 1000 / fps);
|
|
1175
|
+
if (!keepProcessAlive) {
|
|
1176
|
+
interval.unref();
|
|
1177
|
+
}
|
|
1178
|
+
} else {
|
|
1179
|
+
dynamicLog.update(update(render()));
|
|
1180
|
+
}
|
|
1181
|
+
|
|
1182
|
+
const stop = (message) => {
|
|
1183
|
+
running = false;
|
|
1184
|
+
if (interval) {
|
|
1185
|
+
clearInterval(interval);
|
|
1186
|
+
interval = null;
|
|
1187
|
+
}
|
|
1188
|
+
if (cleanup) {
|
|
1189
|
+
cleanup();
|
|
1190
|
+
cleanup = null;
|
|
1191
|
+
}
|
|
1192
|
+
if (dynamicLog && message) {
|
|
1193
|
+
dynamicLog.update(update(message));
|
|
1194
|
+
dynamicLog = null;
|
|
1195
|
+
}
|
|
1196
|
+
};
|
|
1197
|
+
spinner.stop = stop;
|
|
1198
|
+
|
|
1199
|
+
if (stopOnVerticalOverflow) {
|
|
1200
|
+
dynamicLog.onVerticalOverflow = stop;
|
|
1201
|
+
}
|
|
1202
|
+
if (stopOnWriteFromOutside) {
|
|
1203
|
+
dynamicLog.onWriteFromOutside = stop;
|
|
1204
|
+
}
|
|
1205
|
+
|
|
1206
|
+
return spinner;
|
|
1207
|
+
};
|
|
1208
|
+
|
|
1209
|
+
const createTaskLog = (
|
|
1210
|
+
label,
|
|
1211
|
+
{ disabled = false, animated = true, stopOnWriteFromOutside } = {},
|
|
1212
|
+
) => {
|
|
1213
|
+
if (disabled) {
|
|
1214
|
+
return {
|
|
1215
|
+
setRightText: () => {},
|
|
1216
|
+
done: () => {},
|
|
1217
|
+
happen: () => {},
|
|
1218
|
+
fail: () => {},
|
|
1219
|
+
};
|
|
1220
|
+
}
|
|
1221
|
+
if (animated && process.env.CAPTURING_SIDE_EFFECTS) {
|
|
1222
|
+
animated = false;
|
|
1223
|
+
}
|
|
1224
|
+
const startMs = Date.now();
|
|
1225
|
+
const dynamicLog = createDynamicLog();
|
|
1226
|
+
let message = label;
|
|
1227
|
+
const taskSpinner = startSpinner({
|
|
1228
|
+
dynamicLog,
|
|
1229
|
+
render: () => message,
|
|
1230
|
+
stopOnWriteFromOutside,
|
|
1231
|
+
animated,
|
|
1232
|
+
});
|
|
1233
|
+
return {
|
|
1234
|
+
setRightText: (value) => {
|
|
1235
|
+
message = `${label} ${value}`;
|
|
1236
|
+
},
|
|
1237
|
+
done: () => {
|
|
1238
|
+
const msEllapsed = Date.now() - startMs;
|
|
1239
|
+
taskSpinner.stop(
|
|
1240
|
+
`${UNICODE.OK} ${label} (done in ${humanizeDuration(msEllapsed)})`,
|
|
1241
|
+
);
|
|
1242
|
+
},
|
|
1243
|
+
happen: (message) => {
|
|
1244
|
+
taskSpinner.stop(
|
|
1245
|
+
`${UNICODE.INFO} ${message} (at ${new Date().toLocaleTimeString()})`,
|
|
1246
|
+
);
|
|
1247
|
+
},
|
|
1248
|
+
fail: (message = `failed to ${label}`) => {
|
|
1249
|
+
taskSpinner.stop(`${UNICODE.FAILURE} ${message}`);
|
|
1250
|
+
},
|
|
1251
|
+
};
|
|
1252
|
+
};
|
|
1253
|
+
|
|
1254
|
+
const isFileSystemPath = (value) => {
|
|
1255
|
+
if (typeof value !== "string") {
|
|
1256
|
+
throw new TypeError(
|
|
1257
|
+
`isFileSystemPath first arg must be a string, got ${value}`,
|
|
1258
|
+
);
|
|
1259
|
+
}
|
|
1260
|
+
if (value[0] === "/") {
|
|
1261
|
+
return true;
|
|
1262
|
+
}
|
|
1263
|
+
return startsWithWindowsDriveLetter(value);
|
|
1264
|
+
};
|
|
1265
|
+
|
|
1266
|
+
const startsWithWindowsDriveLetter = (string) => {
|
|
1267
|
+
const firstChar = string[0];
|
|
1268
|
+
if (!/[a-zA-Z]/.test(firstChar)) return false;
|
|
1269
|
+
|
|
1270
|
+
const secondChar = string[1];
|
|
1271
|
+
if (secondChar !== ":") return false;
|
|
1272
|
+
|
|
1273
|
+
return true;
|
|
1274
|
+
};
|
|
1275
|
+
|
|
1276
|
+
const fileSystemPathToUrl = (value) => {
|
|
1277
|
+
if (!isFileSystemPath(value)) {
|
|
1278
|
+
throw new Error(`value must be a filesystem path, got ${value}`);
|
|
1279
|
+
}
|
|
1280
|
+
return String(pathToFileURL(value));
|
|
1281
|
+
};
|
|
1282
|
+
|
|
1283
|
+
const validateDirectoryUrl = (value) => {
|
|
1284
|
+
let urlString;
|
|
1285
|
+
|
|
1286
|
+
if (value instanceof URL) {
|
|
1287
|
+
urlString = value.href;
|
|
1288
|
+
} else if (typeof value === "string") {
|
|
1289
|
+
if (isFileSystemPath(value)) {
|
|
1290
|
+
urlString = fileSystemPathToUrl(value);
|
|
1291
|
+
} else {
|
|
1292
|
+
try {
|
|
1293
|
+
urlString = String(new URL(value));
|
|
1294
|
+
} catch {
|
|
1295
|
+
return {
|
|
1296
|
+
valid: false,
|
|
1297
|
+
value,
|
|
1298
|
+
message: `must be a valid url`,
|
|
1299
|
+
};
|
|
1300
|
+
}
|
|
1301
|
+
}
|
|
1302
|
+
} else if (
|
|
1303
|
+
value &&
|
|
1304
|
+
typeof value === "object" &&
|
|
1305
|
+
typeof value.href === "string"
|
|
1306
|
+
) {
|
|
1307
|
+
value = value.href;
|
|
1308
|
+
} else {
|
|
1309
|
+
return {
|
|
1310
|
+
valid: false,
|
|
1311
|
+
value,
|
|
1312
|
+
message: `must be a string or an url`,
|
|
1313
|
+
};
|
|
1314
|
+
}
|
|
1315
|
+
if (!urlString.startsWith("file://")) {
|
|
1316
|
+
return {
|
|
1317
|
+
valid: false,
|
|
1318
|
+
value,
|
|
1319
|
+
message: 'must start with "file://"',
|
|
1320
|
+
};
|
|
1321
|
+
}
|
|
1322
|
+
return {
|
|
1323
|
+
valid: true,
|
|
1324
|
+
value: ensurePathnameTrailingSlash(urlString),
|
|
1325
|
+
};
|
|
1326
|
+
};
|
|
1327
|
+
|
|
1328
|
+
const assertAndNormalizeDirectoryUrl = (
|
|
1329
|
+
directoryUrl,
|
|
1330
|
+
name = "directoryUrl",
|
|
1331
|
+
) => {
|
|
1332
|
+
const { valid, message, value } = validateDirectoryUrl(directoryUrl);
|
|
1333
|
+
if (!valid) {
|
|
1334
|
+
throw new TypeError(`${name} ${message}, got ${value}`);
|
|
1335
|
+
}
|
|
1336
|
+
return value;
|
|
1337
|
+
};
|
|
1338
|
+
|
|
1339
|
+
const createCallbackListNotifiedOnce = () => {
|
|
1340
|
+
let callbacks = [];
|
|
1341
|
+
let status = "waiting";
|
|
1342
|
+
let currentCallbackIndex = -1;
|
|
1343
|
+
|
|
1344
|
+
const callbackListOnce = {};
|
|
1345
|
+
|
|
1346
|
+
const add = (callback) => {
|
|
1347
|
+
if (status !== "waiting") {
|
|
1348
|
+
emitUnexpectedActionWarning({ action: "add", status });
|
|
1349
|
+
return removeNoop;
|
|
1350
|
+
}
|
|
1351
|
+
|
|
1352
|
+
if (typeof callback !== "function") {
|
|
1353
|
+
throw new Error(`callback must be a function, got ${callback}`);
|
|
1354
|
+
}
|
|
1355
|
+
|
|
1356
|
+
// don't register twice
|
|
1357
|
+
const existingCallback = callbacks.find((callbackCandidate) => {
|
|
1358
|
+
return callbackCandidate === callback;
|
|
1359
|
+
});
|
|
1360
|
+
if (existingCallback) {
|
|
1361
|
+
emitCallbackDuplicationWarning();
|
|
1362
|
+
return removeNoop;
|
|
1363
|
+
}
|
|
1364
|
+
|
|
1365
|
+
callbacks.push(callback);
|
|
1366
|
+
return () => {
|
|
1367
|
+
if (status === "notified") {
|
|
1368
|
+
// once called removing does nothing
|
|
1369
|
+
// as the callbacks array is frozen to null
|
|
1370
|
+
return;
|
|
1371
|
+
}
|
|
1372
|
+
|
|
1373
|
+
const index = callbacks.indexOf(callback);
|
|
1374
|
+
if (index === -1) {
|
|
1375
|
+
return;
|
|
1376
|
+
}
|
|
1377
|
+
|
|
1378
|
+
if (status === "looping") {
|
|
1379
|
+
if (index <= currentCallbackIndex) {
|
|
1380
|
+
// The callback was already called (or is the current callback)
|
|
1381
|
+
// We don't want to mutate the callbacks array
|
|
1382
|
+
// or it would alter the looping done in "call" and the next callback
|
|
1383
|
+
// would be skipped
|
|
1384
|
+
return;
|
|
1385
|
+
}
|
|
1386
|
+
|
|
1387
|
+
// Callback is part of the next callback to call,
|
|
1388
|
+
// we mutate the callbacks array to prevent this callback to be called
|
|
1389
|
+
}
|
|
1390
|
+
|
|
1391
|
+
callbacks.splice(index, 1);
|
|
1392
|
+
};
|
|
1393
|
+
};
|
|
1394
|
+
|
|
1395
|
+
const notify = (param) => {
|
|
1396
|
+
if (status !== "waiting") {
|
|
1397
|
+
emitUnexpectedActionWarning({ action: "call", status });
|
|
1398
|
+
return [];
|
|
1399
|
+
}
|
|
1400
|
+
status = "looping";
|
|
1401
|
+
const values = callbacks.map((callback, index) => {
|
|
1402
|
+
currentCallbackIndex = index;
|
|
1403
|
+
return callback(param);
|
|
1404
|
+
});
|
|
1405
|
+
callbackListOnce.notified = true;
|
|
1406
|
+
status = "notified";
|
|
1407
|
+
// we reset callbacks to null after looping
|
|
1408
|
+
// so that it's possible to remove during the loop
|
|
1409
|
+
callbacks = null;
|
|
1410
|
+
currentCallbackIndex = -1;
|
|
1411
|
+
|
|
1412
|
+
return values;
|
|
1413
|
+
};
|
|
1414
|
+
|
|
1415
|
+
callbackListOnce.notified = false;
|
|
1416
|
+
callbackListOnce.add = add;
|
|
1417
|
+
callbackListOnce.notify = notify;
|
|
1418
|
+
|
|
1419
|
+
return callbackListOnce;
|
|
1420
|
+
};
|
|
1421
|
+
|
|
1422
|
+
const emitUnexpectedActionWarning = ({ action, status }) => {
|
|
1423
|
+
if (typeof process.emitWarning === "function") {
|
|
1424
|
+
process.emitWarning(
|
|
1425
|
+
`"${action}" should not happen when callback list is ${status}`,
|
|
1426
|
+
{
|
|
1427
|
+
CODE: "UNEXPECTED_ACTION_ON_CALLBACK_LIST",
|
|
1428
|
+
detail: `Code is potentially executed when it should not`,
|
|
1429
|
+
},
|
|
1430
|
+
);
|
|
1431
|
+
} else {
|
|
1432
|
+
console.warn(
|
|
1433
|
+
`"${action}" should not happen when callback list is ${status}`,
|
|
1434
|
+
);
|
|
1435
|
+
}
|
|
1436
|
+
};
|
|
1437
|
+
|
|
1438
|
+
const emitCallbackDuplicationWarning = () => {
|
|
1439
|
+
if (typeof process.emitWarning === "function") {
|
|
1440
|
+
process.emitWarning(`Trying to add a callback already in the list`, {
|
|
1441
|
+
CODE: "CALLBACK_DUPLICATION",
|
|
1442
|
+
detail: `Code is potentially executed more than it should`,
|
|
1443
|
+
});
|
|
1444
|
+
} else {
|
|
1445
|
+
console.warn(`Trying to add same callback twice`);
|
|
1446
|
+
}
|
|
1447
|
+
};
|
|
1448
|
+
|
|
1449
|
+
const removeNoop = () => {};
|
|
1450
|
+
|
|
1451
|
+
/*
|
|
1452
|
+
* See callback_race.md
|
|
1453
|
+
*/
|
|
1454
|
+
|
|
1455
|
+
const raceCallbacks = (raceDescription, winnerCallback) => {
|
|
1456
|
+
let cleanCallbacks = [];
|
|
1457
|
+
let status = "racing";
|
|
1458
|
+
|
|
1459
|
+
const clean = () => {
|
|
1460
|
+
cleanCallbacks.forEach((clean) => {
|
|
1461
|
+
clean();
|
|
1462
|
+
});
|
|
1463
|
+
cleanCallbacks = null;
|
|
1464
|
+
};
|
|
1465
|
+
|
|
1466
|
+
const cancel = () => {
|
|
1467
|
+
if (status !== "racing") {
|
|
1468
|
+
return;
|
|
1469
|
+
}
|
|
1470
|
+
status = "cancelled";
|
|
1471
|
+
clean();
|
|
1472
|
+
};
|
|
1473
|
+
|
|
1474
|
+
Object.keys(raceDescription).forEach((candidateName) => {
|
|
1475
|
+
const register = raceDescription[candidateName];
|
|
1476
|
+
const returnValue = register((data) => {
|
|
1477
|
+
if (status !== "racing") {
|
|
1478
|
+
return;
|
|
1479
|
+
}
|
|
1480
|
+
status = "done";
|
|
1481
|
+
clean();
|
|
1482
|
+
winnerCallback({
|
|
1483
|
+
name: candidateName,
|
|
1484
|
+
data,
|
|
1485
|
+
});
|
|
1486
|
+
});
|
|
1487
|
+
if (typeof returnValue === "function") {
|
|
1488
|
+
cleanCallbacks.push(returnValue);
|
|
1489
|
+
}
|
|
1490
|
+
});
|
|
1491
|
+
|
|
1492
|
+
return cancel;
|
|
1493
|
+
};
|
|
1494
|
+
|
|
1495
|
+
/*
|
|
1496
|
+
* https://github.com/whatwg/dom/issues/920
|
|
1497
|
+
*/
|
|
1498
|
+
|
|
1499
|
+
|
|
1500
|
+
const Abort = {
|
|
1501
|
+
isAbortError: (error) => {
|
|
1502
|
+
return error && error.name === "AbortError";
|
|
1503
|
+
},
|
|
1504
|
+
|
|
1505
|
+
startOperation: () => {
|
|
1506
|
+
return createOperation();
|
|
1507
|
+
},
|
|
1508
|
+
|
|
1509
|
+
throwIfAborted: (signal) => {
|
|
1510
|
+
if (signal.aborted) {
|
|
1511
|
+
const error = new Error(`The operation was aborted`);
|
|
1512
|
+
error.name = "AbortError";
|
|
1513
|
+
error.type = "aborted";
|
|
1514
|
+
throw error;
|
|
1515
|
+
}
|
|
1516
|
+
},
|
|
1517
|
+
};
|
|
1518
|
+
|
|
1519
|
+
const createOperation = () => {
|
|
1520
|
+
const operationAbortController = new AbortController();
|
|
1521
|
+
// const abortOperation = (value) => abortController.abort(value)
|
|
1522
|
+
const operationSignal = operationAbortController.signal;
|
|
1523
|
+
|
|
1524
|
+
// abortCallbackList is used to ignore the max listeners warning from Node.js
|
|
1525
|
+
// this warning is useful but becomes problematic when it's expect
|
|
1526
|
+
// (a function doing 20 http call in parallel)
|
|
1527
|
+
// To be 100% sure we don't have memory leak, only Abortable.asyncCallback
|
|
1528
|
+
// uses abortCallbackList to know when something is aborted
|
|
1529
|
+
const abortCallbackList = createCallbackListNotifiedOnce();
|
|
1530
|
+
const endCallbackList = createCallbackListNotifiedOnce();
|
|
1531
|
+
|
|
1532
|
+
let isAbortAfterEnd = false;
|
|
1533
|
+
|
|
1534
|
+
operationSignal.onabort = () => {
|
|
1535
|
+
operationSignal.onabort = null;
|
|
1536
|
+
|
|
1537
|
+
const allAbortCallbacksPromise = Promise.all(abortCallbackList.notify());
|
|
1538
|
+
if (!isAbortAfterEnd) {
|
|
1539
|
+
addEndCallback(async () => {
|
|
1540
|
+
await allAbortCallbacksPromise;
|
|
1541
|
+
});
|
|
1542
|
+
}
|
|
1543
|
+
};
|
|
1544
|
+
|
|
1545
|
+
const throwIfAborted = () => {
|
|
1546
|
+
Abort.throwIfAborted(operationSignal);
|
|
1547
|
+
};
|
|
1548
|
+
|
|
1549
|
+
// add a callback called on abort
|
|
1550
|
+
// differences with signal.addEventListener('abort')
|
|
1551
|
+
// - operation.end awaits the return value of this callback
|
|
1552
|
+
// - It won't increase the count of listeners for "abort" that would
|
|
1553
|
+
// trigger max listeners warning when count > 10
|
|
1554
|
+
const addAbortCallback = (callback) => {
|
|
1555
|
+
// It would be painful and not super redable to check if signal is aborted
|
|
1556
|
+
// before deciding if it's an abort or end callback
|
|
1557
|
+
// with pseudo-code below where we want to stop server either
|
|
1558
|
+
// on abort or when ended because signal is aborted
|
|
1559
|
+
// operation[operation.signal.aborted ? 'addAbortCallback': 'addEndCallback'](async () => {
|
|
1560
|
+
// await server.stop()
|
|
1561
|
+
// })
|
|
1562
|
+
if (operationSignal.aborted) {
|
|
1563
|
+
return addEndCallback(callback);
|
|
1564
|
+
}
|
|
1565
|
+
return abortCallbackList.add(callback);
|
|
1566
|
+
};
|
|
1567
|
+
|
|
1568
|
+
const addEndCallback = (callback) => {
|
|
1569
|
+
return endCallbackList.add(callback);
|
|
1570
|
+
};
|
|
1571
|
+
|
|
1572
|
+
const end = async ({ abortAfterEnd = false } = {}) => {
|
|
1573
|
+
await Promise.all(endCallbackList.notify());
|
|
1574
|
+
|
|
1575
|
+
// "abortAfterEnd" can be handy to ensure "abort" callbacks
|
|
1576
|
+
// added with { once: true } are removed
|
|
1577
|
+
// It might also help garbage collection because
|
|
1578
|
+
// runtime implementing AbortSignal (Node.js, browsers) can consider abortSignal
|
|
1579
|
+
// as settled and clean up things
|
|
1580
|
+
if (abortAfterEnd) {
|
|
1581
|
+
// because of operationSignal.onabort = null
|
|
1582
|
+
// + abortCallbackList.clear() this won't re-call
|
|
1583
|
+
// callbacks
|
|
1584
|
+
if (!operationSignal.aborted) {
|
|
1585
|
+
isAbortAfterEnd = true;
|
|
1586
|
+
operationAbortController.abort();
|
|
1587
|
+
}
|
|
1588
|
+
}
|
|
1589
|
+
};
|
|
1590
|
+
|
|
1591
|
+
const addAbortSignal = (
|
|
1592
|
+
signal,
|
|
1593
|
+
{ onAbort = callbackNoop, onRemove = callbackNoop } = {},
|
|
1594
|
+
) => {
|
|
1595
|
+
const applyAbortEffects = () => {
|
|
1596
|
+
const onAbortCallback = onAbort;
|
|
1597
|
+
onAbort = callbackNoop;
|
|
1598
|
+
onAbortCallback();
|
|
1599
|
+
};
|
|
1600
|
+
const applyRemoveEffects = () => {
|
|
1601
|
+
const onRemoveCallback = onRemove;
|
|
1602
|
+
onRemove = callbackNoop;
|
|
1603
|
+
onAbort = callbackNoop;
|
|
1604
|
+
onRemoveCallback();
|
|
1605
|
+
};
|
|
1606
|
+
|
|
1607
|
+
if (operationSignal.aborted) {
|
|
1608
|
+
applyAbortEffects();
|
|
1609
|
+
applyRemoveEffects();
|
|
1610
|
+
return callbackNoop;
|
|
1611
|
+
}
|
|
1612
|
+
|
|
1613
|
+
if (signal.aborted) {
|
|
1614
|
+
operationAbortController.abort();
|
|
1615
|
+
applyAbortEffects();
|
|
1616
|
+
applyRemoveEffects();
|
|
1617
|
+
return callbackNoop;
|
|
1618
|
+
}
|
|
1619
|
+
|
|
1620
|
+
const cancelRace = raceCallbacks(
|
|
1621
|
+
{
|
|
1622
|
+
operation_abort: (cb) => {
|
|
1623
|
+
return addAbortCallback(cb);
|
|
1624
|
+
},
|
|
1625
|
+
operation_end: (cb) => {
|
|
1626
|
+
return addEndCallback(cb);
|
|
1627
|
+
},
|
|
1628
|
+
child_abort: (cb) => {
|
|
1629
|
+
return addEventListener(signal, "abort", cb);
|
|
1630
|
+
},
|
|
1631
|
+
},
|
|
1632
|
+
(winner) => {
|
|
1633
|
+
const raceEffects = {
|
|
1634
|
+
// Both "operation_abort" and "operation_end"
|
|
1635
|
+
// means we don't care anymore if the child aborts.
|
|
1636
|
+
// So we can:
|
|
1637
|
+
// - remove "abort" event listener on child (done by raceCallback)
|
|
1638
|
+
// - remove abort callback on operation (done by raceCallback)
|
|
1639
|
+
// - remove end callback on operation (done by raceCallback)
|
|
1640
|
+
// - call any custom cancel function
|
|
1641
|
+
operation_abort: () => {
|
|
1642
|
+
applyAbortEffects();
|
|
1643
|
+
applyRemoveEffects();
|
|
1644
|
+
},
|
|
1645
|
+
operation_end: () => {
|
|
1646
|
+
// Exists to
|
|
1647
|
+
// - remove abort callback on operation
|
|
1648
|
+
// - remove "abort" event listener on child
|
|
1649
|
+
// - call any custom cancel function
|
|
1650
|
+
applyRemoveEffects();
|
|
1651
|
+
},
|
|
1652
|
+
child_abort: () => {
|
|
1653
|
+
applyAbortEffects();
|
|
1654
|
+
operationAbortController.abort();
|
|
1655
|
+
},
|
|
1656
|
+
};
|
|
1657
|
+
raceEffects[winner.name](winner.value);
|
|
1658
|
+
},
|
|
1659
|
+
);
|
|
1660
|
+
|
|
1661
|
+
return () => {
|
|
1662
|
+
cancelRace();
|
|
1663
|
+
applyRemoveEffects();
|
|
1664
|
+
};
|
|
1665
|
+
};
|
|
1666
|
+
|
|
1667
|
+
const addAbortSource = (abortSourceCallback) => {
|
|
1668
|
+
const abortSource = {
|
|
1669
|
+
cleaned: false,
|
|
1670
|
+
signal: null,
|
|
1671
|
+
remove: callbackNoop,
|
|
1672
|
+
};
|
|
1673
|
+
const abortSourceController = new AbortController();
|
|
1674
|
+
const abortSourceSignal = abortSourceController.signal;
|
|
1675
|
+
abortSource.signal = abortSourceSignal;
|
|
1676
|
+
if (operationSignal.aborted) {
|
|
1677
|
+
return abortSource;
|
|
1678
|
+
}
|
|
1679
|
+
const returnValue = abortSourceCallback((value) => {
|
|
1680
|
+
abortSourceController.abort(value);
|
|
1681
|
+
});
|
|
1682
|
+
const removeAbortSignal = addAbortSignal(abortSourceSignal, {
|
|
1683
|
+
onRemove: () => {
|
|
1684
|
+
if (typeof returnValue === "function") {
|
|
1685
|
+
returnValue();
|
|
1686
|
+
}
|
|
1687
|
+
abortSource.cleaned = true;
|
|
1688
|
+
},
|
|
1689
|
+
});
|
|
1690
|
+
abortSource.remove = removeAbortSignal;
|
|
1691
|
+
return abortSource;
|
|
1692
|
+
};
|
|
1693
|
+
|
|
1694
|
+
const timeout = (ms) => {
|
|
1695
|
+
return addAbortSource((abort) => {
|
|
1696
|
+
const timeoutId = setTimeout(abort, ms);
|
|
1697
|
+
// an abort source return value is called when:
|
|
1698
|
+
// - operation is aborted (by an other source)
|
|
1699
|
+
// - operation ends
|
|
1700
|
+
return () => {
|
|
1701
|
+
clearTimeout(timeoutId);
|
|
1702
|
+
};
|
|
1703
|
+
});
|
|
1704
|
+
};
|
|
1705
|
+
|
|
1706
|
+
const wait = (ms) => {
|
|
1707
|
+
return new Promise((resolve) => {
|
|
1708
|
+
const timeoutId = setTimeout(() => {
|
|
1709
|
+
removeAbortCallback();
|
|
1710
|
+
resolve();
|
|
1711
|
+
}, ms);
|
|
1712
|
+
const removeAbortCallback = addAbortCallback(() => {
|
|
1713
|
+
clearTimeout(timeoutId);
|
|
1714
|
+
});
|
|
1715
|
+
});
|
|
1716
|
+
};
|
|
1717
|
+
|
|
1718
|
+
const withSignal = async (asyncCallback) => {
|
|
1719
|
+
const abortController = new AbortController();
|
|
1720
|
+
const signal = abortController.signal;
|
|
1721
|
+
const removeAbortSignal = addAbortSignal(signal, {
|
|
1722
|
+
onAbort: () => {
|
|
1723
|
+
abortController.abort();
|
|
1724
|
+
},
|
|
1725
|
+
});
|
|
1726
|
+
try {
|
|
1727
|
+
const value = await asyncCallback(signal);
|
|
1728
|
+
removeAbortSignal();
|
|
1729
|
+
return value;
|
|
1730
|
+
} catch (e) {
|
|
1731
|
+
removeAbortSignal();
|
|
1732
|
+
throw e;
|
|
1733
|
+
}
|
|
1734
|
+
};
|
|
1735
|
+
|
|
1736
|
+
const withSignalSync = (callback) => {
|
|
1737
|
+
const abortController = new AbortController();
|
|
1738
|
+
const signal = abortController.signal;
|
|
1739
|
+
const removeAbortSignal = addAbortSignal(signal, {
|
|
1740
|
+
onAbort: () => {
|
|
1741
|
+
abortController.abort();
|
|
1742
|
+
},
|
|
1743
|
+
});
|
|
1744
|
+
try {
|
|
1745
|
+
const value = callback(signal);
|
|
1746
|
+
removeAbortSignal();
|
|
1747
|
+
return value;
|
|
1748
|
+
} catch (e) {
|
|
1749
|
+
removeAbortSignal();
|
|
1750
|
+
throw e;
|
|
1751
|
+
}
|
|
1752
|
+
};
|
|
1753
|
+
|
|
1754
|
+
const fork = () => {
|
|
1755
|
+
const forkedOperation = createOperation();
|
|
1756
|
+
forkedOperation.addAbortSignal(operationSignal);
|
|
1757
|
+
return forkedOperation;
|
|
1758
|
+
};
|
|
1759
|
+
|
|
1760
|
+
return {
|
|
1761
|
+
// We could almost hide the operationSignal
|
|
1762
|
+
// But it can be handy for 2 things:
|
|
1763
|
+
// - know if operation is aborted (operation.signal.aborted)
|
|
1764
|
+
// - forward the operation.signal directly (not using "withSignal" or "withSignalSync")
|
|
1765
|
+
signal: operationSignal,
|
|
1766
|
+
|
|
1767
|
+
throwIfAborted,
|
|
1768
|
+
addAbortCallback,
|
|
1769
|
+
addAbortSignal,
|
|
1770
|
+
addAbortSource,
|
|
1771
|
+
fork,
|
|
1772
|
+
timeout,
|
|
1773
|
+
wait,
|
|
1774
|
+
withSignal,
|
|
1775
|
+
withSignalSync,
|
|
1776
|
+
addEndCallback,
|
|
1777
|
+
end,
|
|
1778
|
+
};
|
|
1779
|
+
};
|
|
1780
|
+
|
|
1781
|
+
const callbackNoop = () => {};
|
|
1782
|
+
|
|
1783
|
+
const addEventListener = (target, eventName, cb) => {
|
|
1784
|
+
target.addEventListener(eventName, cb);
|
|
1785
|
+
return () => {
|
|
1786
|
+
target.removeEventListener(eventName, cb);
|
|
1787
|
+
};
|
|
1788
|
+
};
|
|
1789
|
+
|
|
1790
|
+
const raceProcessTeardownEvents = (processTeardownEvents, callback) => {
|
|
1791
|
+
return raceCallbacks(
|
|
1792
|
+
{
|
|
1793
|
+
...(processTeardownEvents.SIGHUP ? SIGHUP_CALLBACK : {}),
|
|
1794
|
+
...(processTeardownEvents.SIGTERM ? SIGTERM_CALLBACK : {}),
|
|
1795
|
+
...(processTeardownEvents.SIGINT ? SIGINT_CALLBACK : {}),
|
|
1796
|
+
...(processTeardownEvents.beforeExit ? BEFORE_EXIT_CALLBACK : {}),
|
|
1797
|
+
...(processTeardownEvents.exit ? EXIT_CALLBACK : {}),
|
|
1798
|
+
},
|
|
1799
|
+
callback,
|
|
1800
|
+
);
|
|
1801
|
+
};
|
|
1802
|
+
|
|
1803
|
+
const SIGHUP_CALLBACK = {
|
|
1804
|
+
SIGHUP: (cb) => {
|
|
1805
|
+
process.on("SIGHUP", cb);
|
|
1806
|
+
return () => {
|
|
1807
|
+
process.removeListener("SIGHUP", cb);
|
|
1808
|
+
};
|
|
1809
|
+
},
|
|
1810
|
+
};
|
|
1811
|
+
|
|
1812
|
+
const SIGTERM_CALLBACK = {
|
|
1813
|
+
SIGTERM: (cb) => {
|
|
1814
|
+
process.on("SIGTERM", cb);
|
|
1815
|
+
return () => {
|
|
1816
|
+
process.removeListener("SIGTERM", cb);
|
|
1817
|
+
};
|
|
1818
|
+
},
|
|
1819
|
+
};
|
|
1820
|
+
|
|
1821
|
+
const BEFORE_EXIT_CALLBACK = {
|
|
1822
|
+
beforeExit: (cb) => {
|
|
1823
|
+
process.on("beforeExit", cb);
|
|
1824
|
+
return () => {
|
|
1825
|
+
process.removeListener("beforeExit", cb);
|
|
1826
|
+
};
|
|
1827
|
+
},
|
|
1828
|
+
};
|
|
1829
|
+
|
|
1830
|
+
const EXIT_CALLBACK = {
|
|
1831
|
+
exit: (cb) => {
|
|
1832
|
+
process.on("exit", cb);
|
|
1833
|
+
return () => {
|
|
1834
|
+
process.removeListener("exit", cb);
|
|
1835
|
+
};
|
|
1836
|
+
},
|
|
1837
|
+
};
|
|
1838
|
+
|
|
1839
|
+
const SIGINT_CALLBACK = {
|
|
1840
|
+
SIGINT: (cb) => {
|
|
1841
|
+
process.on("SIGINT", cb);
|
|
1842
|
+
return () => {
|
|
1843
|
+
process.removeListener("SIGINT", cb);
|
|
1844
|
+
};
|
|
1845
|
+
},
|
|
1846
|
+
};
|
|
1847
|
+
|
|
1848
|
+
export { ANSI, Abort, UNICODE, assertAndNormalizeDirectoryUrl, createDetailedMessage, createLogger, createTaskLog, distributePercentages, fileSystemPathToUrl, generateContentFrame, humanizeFileSize, isFileSystemPath, raceProcessTeardownEvents };
|