@jsenv/humanize 1.1.0 → 1.1.1

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.
@@ -1,752 +1,1099 @@
1
- import stringWidth from "string-width";
2
1
  import process$1 from "node:process";
3
2
  import os from "node:os";
4
3
  import tty from "node:tty";
4
+ import stringWidth from "string-width";
5
5
 
6
- const LOG_LEVEL_OFF = "off";
7
-
8
- const LOG_LEVEL_DEBUG = "debug";
9
-
10
- const LOG_LEVEL_INFO = "info";
11
-
12
- const LOG_LEVEL_WARN = "warn";
6
+ const createDetailedMessage = (message, details = {}) => {
7
+ let string = `${message}`;
13
8
 
14
- const LOG_LEVEL_ERROR = "error";
9
+ Object.keys(details).forEach((key) => {
10
+ const value = details[key];
11
+ string += `
12
+ --- ${key} ---
13
+ ${
14
+ Array.isArray(value)
15
+ ? value.join(`
16
+ `)
17
+ : value
18
+ }`;
19
+ });
15
20
 
16
- const createLogger = ({ logLevel = LOG_LEVEL_INFO } = {}) => {
17
- if (logLevel === LOG_LEVEL_DEBUG) {
18
- return {
19
- level: "debug",
20
- levels: { debug: true, info: true, warn: true, error: true },
21
- debug,
22
- info,
23
- warn,
24
- error,
25
- };
26
- }
27
- if (logLevel === LOG_LEVEL_INFO) {
28
- return {
29
- level: "info",
30
- levels: { debug: false, info: true, warn: true, error: true },
31
- debug: debugDisabled,
32
- info,
33
- warn,
34
- error,
35
- };
36
- }
37
- if (logLevel === LOG_LEVEL_WARN) {
38
- return {
39
- level: "warn",
40
- levels: { debug: false, info: false, warn: true, error: true },
41
- debug: debugDisabled,
42
- info: infoDisabled,
43
- warn,
44
- error,
45
- };
46
- }
47
- if (logLevel === LOG_LEVEL_ERROR) {
48
- return {
49
- level: "error",
50
- levels: { debug: false, info: false, warn: false, error: true },
51
- debug: debugDisabled,
52
- info: infoDisabled,
53
- warn: warnDisabled,
54
- error,
55
- };
56
- }
57
- if (logLevel === LOG_LEVEL_OFF) {
58
- return {
59
- level: "off",
60
- levels: { debug: false, info: false, warn: false, error: false },
61
- debug: debugDisabled,
62
- info: infoDisabled,
63
- warn: warnDisabled,
64
- error: errorDisabled,
65
- };
66
- }
67
- throw new Error(`unexpected logLevel.
68
- --- logLevel ---
69
- ${logLevel}
70
- --- allowed log levels ---
71
- ${LOG_LEVEL_OFF}
72
- ${LOG_LEVEL_ERROR}
73
- ${LOG_LEVEL_WARN}
74
- ${LOG_LEVEL_INFO}
75
- ${LOG_LEVEL_DEBUG}`);
21
+ return string;
76
22
  };
77
23
 
78
- const debug = (...args) => console.debug(...args);
79
-
80
- const debugDisabled = () => {};
81
-
82
- const info = (...args) => console.info(...args);
24
+ // From: https://github.com/sindresorhus/has-flag/blob/main/index.js
25
+ /// function hasFlag(flag, argv = globalThis.Deno?.args ?? process.argv) {
26
+ function hasFlag(flag, argv = globalThis.Deno ? globalThis.Deno.args : process$1.argv) {
27
+ const prefix = flag.startsWith('-') ? '' : (flag.length === 1 ? '-' : '--');
28
+ const position = argv.indexOf(prefix + flag);
29
+ const terminatorPosition = argv.indexOf('--');
30
+ return position !== -1 && (terminatorPosition === -1 || position < terminatorPosition);
31
+ }
83
32
 
84
- const infoDisabled = () => {};
33
+ const {env} = process$1;
85
34
 
86
- const warn = (...args) => console.warn(...args);
35
+ let flagForceColor;
36
+ if (
37
+ hasFlag('no-color')
38
+ || hasFlag('no-colors')
39
+ || hasFlag('color=false')
40
+ || hasFlag('color=never')
41
+ ) {
42
+ flagForceColor = 0;
43
+ } else if (
44
+ hasFlag('color')
45
+ || hasFlag('colors')
46
+ || hasFlag('color=true')
47
+ || hasFlag('color=always')
48
+ ) {
49
+ flagForceColor = 1;
50
+ }
87
51
 
88
- const warnDisabled = () => {};
52
+ function envForceColor() {
53
+ if ('FORCE_COLOR' in env) {
54
+ if (env.FORCE_COLOR === 'true') {
55
+ return 1;
56
+ }
89
57
 
90
- const error = (...args) => console.error(...args);
58
+ if (env.FORCE_COLOR === 'false') {
59
+ return 0;
60
+ }
91
61
 
92
- const errorDisabled = () => {};
62
+ return env.FORCE_COLOR.length === 0 ? 1 : Math.min(Number.parseInt(env.FORCE_COLOR, 10), 3);
63
+ }
64
+ }
93
65
 
94
- /* globals WorkerGlobalScope, DedicatedWorkerGlobalScope, SharedWorkerGlobalScope, ServiceWorkerGlobalScope */
66
+ function translateLevel(level) {
67
+ if (level === 0) {
68
+ return false;
69
+ }
95
70
 
96
- const isBrowser = globalThis.window?.document !== undefined;
71
+ return {
72
+ level,
73
+ hasBasic: true,
74
+ has256: level >= 2,
75
+ has16m: level >= 3,
76
+ };
77
+ }
97
78
 
98
- globalThis.process?.versions?.node !== undefined;
79
+ function _supportsColor(haveStream, {streamIsTTY, sniffFlags = true} = {}) {
80
+ const noFlagForceColor = envForceColor();
81
+ if (noFlagForceColor !== undefined) {
82
+ flagForceColor = noFlagForceColor;
83
+ }
99
84
 
100
- globalThis.process?.versions?.bun !== undefined;
85
+ const forceColor = sniffFlags ? flagForceColor : noFlagForceColor;
101
86
 
102
- globalThis.Deno?.version?.deno !== undefined;
87
+ if (forceColor === 0) {
88
+ return 0;
89
+ }
103
90
 
104
- globalThis.process?.versions?.electron !== undefined;
91
+ if (sniffFlags) {
92
+ if (hasFlag('color=16m')
93
+ || hasFlag('color=full')
94
+ || hasFlag('color=truecolor')) {
95
+ return 3;
96
+ }
105
97
 
106
- globalThis.navigator?.userAgent?.includes('jsdom') === true;
98
+ if (hasFlag('color=256')) {
99
+ return 2;
100
+ }
101
+ }
107
102
 
108
- typeof WorkerGlobalScope !== 'undefined' && globalThis instanceof WorkerGlobalScope;
103
+ // Check for Azure DevOps pipelines.
104
+ // Has to be above the `!streamIsTTY` check.
105
+ if ('TF_BUILD' in env && 'AGENT_NAME' in env) {
106
+ return 1;
107
+ }
109
108
 
110
- typeof DedicatedWorkerGlobalScope !== 'undefined' && globalThis instanceof DedicatedWorkerGlobalScope;
109
+ if (haveStream && !streamIsTTY && forceColor === undefined) {
110
+ return 0;
111
+ }
111
112
 
112
- typeof SharedWorkerGlobalScope !== 'undefined' && globalThis instanceof SharedWorkerGlobalScope;
113
+ const min = forceColor || 0;
113
114
 
114
- typeof ServiceWorkerGlobalScope !== 'undefined' && globalThis instanceof ServiceWorkerGlobalScope;
115
+ if (env.TERM === 'dumb') {
116
+ return min;
117
+ }
115
118
 
116
- // Note: I'm intentionally not DRYing up the other variables to keep them "lazy".
117
- const platform = globalThis.navigator?.userAgentData?.platform;
119
+ if (process$1.platform === 'win32') {
120
+ // Windows 10 build 10586 is the first Windows release that supports 256 colors.
121
+ // Windows 10 build 14931 is the first release that supports 16m/TrueColor.
122
+ const osRelease = os.release().split('.');
123
+ if (
124
+ Number(osRelease[0]) >= 10
125
+ && Number(osRelease[2]) >= 10_586
126
+ ) {
127
+ return Number(osRelease[2]) >= 14_931 ? 3 : 2;
128
+ }
118
129
 
119
- platform === 'macOS'
120
- || globalThis.navigator?.platform === 'MacIntel' // Even on Apple silicon Macs.
121
- || globalThis.navigator?.userAgent?.includes(' Mac ') === true
122
- || globalThis.process?.platform === 'darwin';
130
+ return 1;
131
+ }
123
132
 
124
- platform === 'Windows'
125
- || globalThis.navigator?.platform === 'Win32'
126
- || globalThis.process?.platform === 'win32';
133
+ if ('CI' in env) {
134
+ if ('GITHUB_ACTIONS' in env || 'GITEA_ACTIONS' in env) {
135
+ return 3;
136
+ }
127
137
 
128
- platform === 'Linux'
129
- || globalThis.navigator?.platform?.startsWith('Linux') === true
130
- || globalThis.navigator?.userAgent?.includes(' Linux ') === true
131
- || globalThis.process?.platform === 'linux';
138
+ if (['TRAVIS', 'CIRCLECI', 'APPVEYOR', 'GITLAB_CI', 'BUILDKITE', 'DRONE'].some(sign => sign in env) || env.CI_NAME === 'codeship') {
139
+ return 1;
140
+ }
132
141
 
133
- platform === 'Android'
134
- || globalThis.navigator?.platform === 'Android'
135
- || globalThis.navigator?.userAgent?.includes(' Android ') === true
136
- || globalThis.process?.platform === 'android';
142
+ return min;
143
+ }
137
144
 
138
- const ESC = '\u001B[';
145
+ if ('TEAMCITY_VERSION' in env) {
146
+ return /^(9\.(0*[1-9]\d*)\.|\d{2,}\.)/.test(env.TEAMCITY_VERSION) ? 1 : 0;
147
+ }
139
148
 
140
- !isBrowser && process$1.env.TERM_PROGRAM === 'Apple_Terminal';
141
- const isWindows = !isBrowser && process$1.platform === 'win32';
149
+ if (env.COLORTERM === 'truecolor') {
150
+ return 3;
151
+ }
142
152
 
143
- isBrowser ? () => {
144
- throw new Error('`process.cwd()` only works in Node.js, not the browser.');
145
- } : process$1.cwd;
153
+ if (env.TERM === 'xterm-kitty') {
154
+ return 3;
155
+ }
146
156
 
147
- const cursorUp = (count = 1) => ESC + count + 'A';
157
+ if ('TERM_PROGRAM' in env) {
158
+ const version = Number.parseInt((env.TERM_PROGRAM_VERSION || '').split('.')[0], 10);
148
159
 
149
- const cursorLeft = ESC + 'G';
160
+ switch (env.TERM_PROGRAM) {
161
+ case 'iTerm.app': {
162
+ return version >= 3 ? 3 : 2;
163
+ }
150
164
 
151
- const eraseLines = count => {
152
- let clear = '';
165
+ case 'Apple_Terminal': {
166
+ return 2;
167
+ }
168
+ // No default
169
+ }
170
+ }
153
171
 
154
- for (let i = 0; i < count; i++) {
155
- clear += eraseLine + (i < count - 1 ? cursorUp() : '');
172
+ if (/-256(color)?$/i.test(env.TERM)) {
173
+ return 2;
156
174
  }
157
175
 
158
- if (count) {
159
- clear += cursorLeft;
176
+ if (/^screen|^xterm|^vt100|^vt220|^rxvt|color|ansi|cygwin|linux/i.test(env.TERM)) {
177
+ return 1;
160
178
  }
161
179
 
162
- return clear;
163
- };
164
- const eraseLine = ESC + '2K';
165
- const eraseScreen = ESC + '2J';
166
-
167
- const clearTerminal = isWindows
168
- ? `${eraseScreen}${ESC}0f`
169
- // 1. Erases the screen (Only done in case `2` is not supported)
170
- // 2. Erases the whole screen including scrollback buffer
171
- // 3. Moves cursor to the top-left position
172
- // More info: https://www.real-world-systems.com/docs/ANSIcode.html
173
- : `${eraseScreen}${ESC}3J${ESC}H`;
174
-
175
- /*
176
- * see also https://github.com/vadimdemedes/ink
177
- */
180
+ if ('COLORTERM' in env) {
181
+ return 1;
182
+ }
178
183
 
184
+ return min;
185
+ }
179
186
 
180
- const createDynamicLog = ({
181
- stream = process.stdout,
182
- clearTerminalAllowed,
183
- onVerticalOverflow = () => {},
184
- onWriteFromOutside = () => {},
185
- } = {}) => {
186
- const { columns = 80, rows = 24 } = stream;
187
- const dynamicLog = {
188
- destroyed: false,
189
- onVerticalOverflow,
190
- onWriteFromOutside,
191
- };
187
+ function createSupportsColor(stream, options = {}) {
188
+ const level = _supportsColor(stream, {
189
+ streamIsTTY: stream && stream.isTTY,
190
+ ...options,
191
+ });
192
192
 
193
- let lastOutput = "";
194
- let lastOutputFromOutside = "";
195
- let clearAttemptResult;
196
- let writing = false;
193
+ return translateLevel(level);
194
+ }
197
195
 
198
- const getErasePreviousOutput = () => {
199
- // nothing to clear
200
- if (!lastOutput) {
201
- return "";
202
- }
203
- if (clearAttemptResult !== undefined) {
204
- return "";
205
- }
196
+ ({
197
+ stdout: createSupportsColor({isTTY: tty.isatty(1)}),
198
+ stderr: createSupportsColor({isTTY: tty.isatty(2)}),
199
+ });
206
200
 
207
- const logLines = lastOutput.split(/\r\n|\r|\n/);
208
- let visualLineCount = 0;
209
- for (const logLine of logLines) {
210
- const width = stringWidth(logLine);
211
- if (width === 0) {
212
- visualLineCount++;
213
- } else {
214
- visualLineCount += Math.ceil(width / columns);
215
- }
216
- }
201
+ // https://github.com/Marak/colors.js/blob/master/lib/styles.js
202
+ // https://stackoverflow.com/a/75985833/2634179
203
+ const RESET = "\x1b[0m";
217
204
 
218
- if (visualLineCount > rows) {
219
- if (clearTerminalAllowed) {
220
- clearAttemptResult = true;
221
- return clearTerminal;
222
- }
223
- // the whole log cannot be cleared because it's vertically to long
224
- // (longer than terminal height)
225
- // readline.moveCursor cannot move cursor higher than screen height
226
- // it means we would only clear the visible part of the log
227
- // better keep the log untouched
228
- clearAttemptResult = false;
229
- dynamicLog.onVerticalOverflow();
230
- return "";
231
- }
205
+ const createAnsi = ({ supported }) => {
206
+ const ANSI = {
207
+ supported,
208
+
209
+ RED: "\x1b[31m",
210
+ GREEN: "\x1b[32m",
211
+ YELLOW: "\x1b[33m",
212
+ BLUE: "\x1b[34m",
213
+ MAGENTA: "\x1b[35m",
214
+ CYAN: "\x1b[36m",
215
+ GREY: "\x1b[90m",
216
+ color: (text, ANSI_COLOR) => {
217
+ return ANSI.supported && ANSI_COLOR
218
+ ? `${ANSI_COLOR}${text}${RESET}`
219
+ : text;
220
+ },
232
221
 
233
- clearAttemptResult = true;
234
- return eraseLines(visualLineCount);
222
+ BOLD: "\x1b[1m",
223
+ UNDERLINE: "\x1b[4m",
224
+ STRIKE: "\x1b[9m",
225
+ effect: (text, ANSI_EFFECT) => {
226
+ return ANSI.supported && ANSI_EFFECT
227
+ ? `${ANSI_EFFECT}${text}${RESET}`
228
+ : text;
229
+ },
235
230
  };
236
231
 
237
- const update = (string) => {
238
- if (dynamicLog.destroyed) {
239
- throw new Error("Cannot write log after destroy");
240
- }
241
- let stringToWrite = string;
242
- if (lastOutput) {
243
- if (lastOutputFromOutside) {
244
- // We don't want to clear logs written by other code,
245
- // it makes output unreadable and might erase precious information
246
- // To detect this we put a spy on the stream.
247
- // The spy is required only if we actually wrote something in the stream
248
- // something else than this code has written in the stream
249
- // so we just write without clearing (append instead of replacing)
250
- lastOutput = "";
251
- lastOutputFromOutside = "";
252
- } else {
253
- stringToWrite = `${getErasePreviousOutput()}${string}`;
254
- }
255
- }
256
- writing = true;
257
- stream.write(stringToWrite);
258
- lastOutput = string;
259
- writing = false;
260
- clearAttemptResult = undefined;
261
- };
232
+ return ANSI;
233
+ };
262
234
 
263
- const clearDuringFunctionCall = (
264
- callback,
265
- ouputAfterCallback = lastOutput,
266
- ) => {
267
- // 1. Erase the current log
268
- // 2. Call callback (expect to write something on stdout)
269
- // 3. Restore the current log
270
- // During step 2. we expect a "write from outside" so we uninstall
271
- // the stream spy during function call
272
- update("");
235
+ const processSupportsBasicColor = createSupportsColor(process.stdout).hasBasic;
273
236
 
274
- writing = true;
275
- callback();
276
- writing = false;
237
+ const ANSI = createAnsi({
238
+ supported:
239
+ processSupportsBasicColor ||
240
+ // GitHub workflow does support ANSI but "supports-color" returns false
241
+ // because stream.isTTY returns false, see https://github.com/actions/runner/issues/241
242
+ (process.env.GITHUB_WORKFLOW &&
243
+ // Check on FORCE_COLOR is to ensure it is prio over GitHub workflow check
244
+ // in unit test we use process.env.FORCE_COLOR = 'false' to fake
245
+ // that colors are not supported. Let it have priority
246
+ process.env.FORCE_COLOR !== "false"),
247
+ });
277
248
 
278
- update(ouputAfterCallback);
279
- };
249
+ function isUnicodeSupported() {
250
+ if (process$1.platform !== 'win32') {
251
+ return process$1.env.TERM !== 'linux'; // Linux console (kernel)
252
+ }
280
253
 
281
- const writeFromOutsideEffect = (value) => {
282
- if (!lastOutput) {
283
- // we don't care if the log never wrote anything
284
- // or if last update() wrote an empty string
285
- return;
286
- }
287
- if (writing) {
288
- return;
289
- }
290
- lastOutputFromOutside = value;
291
- dynamicLog.onWriteFromOutside(value);
292
- };
254
+ return Boolean(process$1.env.WT_SESSION) // Windows Terminal
255
+ || Boolean(process$1.env.TERMINUS_SUBLIME) // Terminus (<0.2.27)
256
+ || process$1.env.ConEmuTask === '{cmd::Cmder}' // ConEmu and cmder
257
+ || process$1.env.TERM_PROGRAM === 'Terminus-Sublime'
258
+ || process$1.env.TERM_PROGRAM === 'vscode'
259
+ || process$1.env.TERM === 'xterm-256color'
260
+ || process$1.env.TERM === 'alacritty'
261
+ || process$1.env.TERMINAL_EMULATOR === 'JetBrains-JediTerm';
262
+ }
293
263
 
294
- let removeStreamSpy;
295
- if (stream === process.stdout) {
296
- const removeStdoutSpy = spyStreamOutput(
297
- process.stdout,
298
- writeFromOutsideEffect,
299
- );
300
- const removeStderrSpy = spyStreamOutput(
301
- process.stderr,
302
- writeFromOutsideEffect,
303
- );
304
- removeStreamSpy = () => {
305
- removeStdoutSpy();
306
- removeStderrSpy();
307
- };
308
- } else {
309
- removeStreamSpy = spyStreamOutput(stream, writeFromOutsideEffect);
310
- }
264
+ // see also https://github.com/sindresorhus/figures
311
265
 
312
- const destroy = () => {
313
- dynamicLog.destroyed = true;
314
- if (removeStreamSpy) {
315
- removeStreamSpy();
316
- removeStreamSpy = null;
317
- lastOutput = "";
318
- lastOutputFromOutside = "";
319
- }
266
+ const createUnicode = ({ supported, ANSI }) => {
267
+ const UNICODE = {
268
+ supported,
269
+ get COMMAND_RAW() {
270
+ return UNICODE.supported ? `❯` : `>`;
271
+ },
272
+ get OK_RAW() {
273
+ return UNICODE.supported ? `✔` : `√`;
274
+ },
275
+ get FAILURE_RAW() {
276
+ return UNICODE.supported ? `✖` : `×`;
277
+ },
278
+ get DEBUG_RAW() {
279
+ return UNICODE.supported ? `◆` : `♦`;
280
+ },
281
+ get INFO_RAW() {
282
+ return UNICODE.supported ? `ℹ` : `i`;
283
+ },
284
+ get WARNING_RAW() {
285
+ return UNICODE.supported ? `⚠` : `‼`;
286
+ },
287
+ get CIRCLE_CROSS_RAW() {
288
+ return UNICODE.supported ? `ⓧ` : `(×)`;
289
+ },
290
+ get COMMAND() {
291
+ return ANSI.color(UNICODE.COMMAND_RAW, ANSI.GREY); // ANSI_MAGENTA)
292
+ },
293
+ get OK() {
294
+ return ANSI.color(UNICODE.OK_RAW, ANSI.GREEN);
295
+ },
296
+ get FAILURE() {
297
+ return ANSI.color(UNICODE.FAILURE_RAW, ANSI.RED);
298
+ },
299
+ get DEBUG() {
300
+ return ANSI.color(UNICODE.DEBUG_RAW, ANSI.GREY);
301
+ },
302
+ get INFO() {
303
+ return ANSI.color(UNICODE.INFO_RAW, ANSI.BLUE);
304
+ },
305
+ get WARNING() {
306
+ return ANSI.color(UNICODE.WARNING_RAW, ANSI.YELLOW);
307
+ },
308
+ get CIRCLE_CROSS() {
309
+ return ANSI.color(UNICODE.CIRCLE_CROSS_RAW, ANSI.RED);
310
+ },
311
+ get ELLIPSIS() {
312
+ return UNICODE.supported ? `…` : `...`;
313
+ },
320
314
  };
321
-
322
- Object.assign(dynamicLog, {
323
- update,
324
- destroy,
325
- stream,
326
- clearDuringFunctionCall,
327
- });
328
- return dynamicLog;
315
+ return UNICODE;
329
316
  };
330
317
 
331
- // maybe https://github.com/gajus/output-interceptor/tree/v3.0.0 ?
332
- // the problem with listening data on stdout
333
- // is that node.js will later throw error if stream gets closed
334
- // while something listening data on it
335
- const spyStreamOutput = (stream, callback) => {
336
- const originalWrite = stream.write;
337
-
338
- let output = "";
339
- let installed = true;
340
-
341
- stream.write = function (...args /* chunk, encoding, callback */) {
342
- output += args;
343
- callback(output);
344
- return originalWrite.call(stream, ...args);
345
- };
346
-
347
- const uninstall = () => {
348
- if (!installed) {
349
- return;
350
- }
351
- stream.write = originalWrite;
352
- installed = false;
353
- };
318
+ const UNICODE = createUnicode({
319
+ supported: isUnicodeSupported(),
320
+ ANSI,
321
+ });
354
322
 
355
- return () => {
356
- uninstall();
357
- return output;
358
- };
359
- };
323
+ const inspectBoolean = (value) => value.toString();
360
324
 
361
- // From: https://github.com/sindresorhus/has-flag/blob/main/index.js
362
- /// function hasFlag(flag, argv = globalThis.Deno?.args ?? process.argv) {
363
- function hasFlag(flag, argv = globalThis.Deno ? globalThis.Deno.args : process$1.argv) {
364
- const prefix = flag.startsWith('-') ? '' : (flag.length === 1 ? '-' : '--');
365
- const position = argv.indexOf(prefix + flag);
366
- const terminatorPosition = argv.indexOf('--');
367
- return position !== -1 && (terminatorPosition === -1 || position < terminatorPosition);
368
- }
325
+ const inspectNull = () => "null";
369
326
 
370
- const {env} = process$1;
327
+ // https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/rules/numeric-separators-style.js
371
328
 
372
- let flagForceColor;
373
- if (
374
- hasFlag('no-color')
375
- || hasFlag('no-colors')
376
- || hasFlag('color=false')
377
- || hasFlag('color=never')
378
- ) {
379
- flagForceColor = 0;
380
- } else if (
381
- hasFlag('color')
382
- || hasFlag('colors')
383
- || hasFlag('color=true')
384
- || hasFlag('color=always')
385
- ) {
386
- flagForceColor = 1;
387
- }
388
-
389
- function envForceColor() {
390
- if ('FORCE_COLOR' in env) {
391
- if (env.FORCE_COLOR === 'true') {
392
- return 1;
393
- }
329
+ const inspectNumber = (value, { numericSeparator }) => {
330
+ if (isNegativeZero(value)) {
331
+ return "-0";
332
+ }
333
+ // isNaN
334
+ // eslint-disable-next-line no-self-compare
335
+ if (value !== value) {
336
+ return "NaN";
337
+ }
338
+ if (value === Infinity) {
339
+ return "Infinity";
340
+ }
341
+ if (value === -Infinity) {
342
+ return "-Infinity";
343
+ }
344
+ const numberString = String(value);
345
+ if (!numericSeparator) {
346
+ return numberString;
347
+ }
348
+ const {
349
+ number,
350
+ mark = "",
351
+ sign = "",
352
+ power = "",
353
+ } = numberString.match(
354
+ /^(?<number>.*?)(?:(?<mark>e)(?<sign>[+-])?(?<power>\d+))?$/i,
355
+ ).groups;
356
+ const numberWithSeparators = formatNumber(number);
357
+ const powerWithSeparators = addSeparator(power, {
358
+ minimumDigits: 5,
359
+ groupLength: 3,
360
+ });
361
+ return `${numberWithSeparators}${mark}${sign}${powerWithSeparators}`;
362
+ };
394
363
 
395
- if (env.FORCE_COLOR === 'false') {
396
- return 0;
397
- }
364
+ // Use this and instead of Object.is(value, -0)
365
+ // because in some corner cases firefox returns false
366
+ // for Object.is(-0, -0)
367
+ const isNegativeZero = (value) => {
368
+ return value === 0 && 1 / value === -Infinity;
369
+ };
398
370
 
399
- return env.FORCE_COLOR.length === 0 ? 1 : Math.min(Number.parseInt(env.FORCE_COLOR, 10), 3);
400
- }
401
- }
371
+ const formatNumber = (numberString) => {
372
+ const parts = numberString.split(".");
373
+ const [integer, fractional] = parts;
402
374
 
403
- function translateLevel(level) {
404
- if (level === 0) {
405
- return false;
406
- }
375
+ if (parts.length === 2) {
376
+ const integerWithSeparators = addSeparator(integer, {
377
+ minimumDigits: 5,
378
+ groupLength: 3,
379
+ });
380
+ return `${integerWithSeparators}.${fractional}`;
381
+ }
407
382
 
408
- return {
409
- level,
410
- hasBasic: true,
411
- has256: level >= 2,
412
- has16m: level >= 3,
413
- };
414
- }
383
+ return addSeparator(integer, {
384
+ minimumDigits: 5,
385
+ groupLength: 3,
386
+ });
387
+ };
415
388
 
416
- function _supportsColor(haveStream, {streamIsTTY, sniffFlags = true} = {}) {
417
- const noFlagForceColor = envForceColor();
418
- if (noFlagForceColor !== undefined) {
419
- flagForceColor = noFlagForceColor;
420
- }
389
+ const addSeparator = (numberString, { minimumDigits, groupLength }) => {
390
+ if (numberString[0] === "-") {
391
+ return `-${groupDigits(numberString.slice(1), {
392
+ minimumDigits,
393
+ groupLength,
394
+ })}`;
395
+ }
396
+ return groupDigits(numberString, { minimumDigits, groupLength });
397
+ };
421
398
 
422
- const forceColor = sniffFlags ? flagForceColor : noFlagForceColor;
399
+ const groupDigits = (digits, { minimumDigits, groupLength }) => {
400
+ const digitCount = digits.length;
401
+ if (digitCount < minimumDigits) {
402
+ return digits;
403
+ }
423
404
 
424
- if (forceColor === 0) {
425
- return 0;
426
- }
405
+ let digitsWithSeparator = digits.slice(-groupLength);
406
+ let remainingDigits = digits.slice(0, -groupLength);
407
+ while (remainingDigits.length) {
408
+ const group = remainingDigits.slice(-groupLength);
409
+ remainingDigits = remainingDigits.slice(0, -groupLength);
410
+ digitsWithSeparator = `${group}_${digitsWithSeparator}`;
411
+ }
412
+ return digitsWithSeparator;
413
+ };
427
414
 
428
- if (sniffFlags) {
429
- if (hasFlag('color=16m')
430
- || hasFlag('color=full')
431
- || hasFlag('color=truecolor')) {
432
- return 3;
433
- }
415
+ // const addSeparatorFromLeft = (value, { minimumDigits, groupLength }) => {
416
+ // const { length } = value;
417
+ // if (length < minimumDigits) {
418
+ // return value;
419
+ // }
434
420
 
435
- if (hasFlag('color=256')) {
436
- return 2;
437
- }
438
- }
421
+ // const parts = [];
422
+ // for (let start = 0; start < length; start += groupLength) {
423
+ // const end = Math.min(start + groupLength, length);
424
+ // parts.push(value.slice(start, end));
425
+ // }
426
+ // return parts.join("_");
427
+ // };
439
428
 
440
- // Check for Azure DevOps pipelines.
441
- // Has to be above the `!streamIsTTY` check.
442
- if ('TF_BUILD' in env && 'AGENT_NAME' in env) {
443
- return 1;
444
- }
429
+ const DOUBLE_QUOTE = `"`;
430
+ const SINGLE_QUOTE = `'`;
431
+ const BACKTICK = "`";
445
432
 
446
- if (haveStream && !streamIsTTY && forceColor === undefined) {
447
- return 0;
448
- }
433
+ const inspectString = (
434
+ value,
435
+ {
436
+ quote = "auto",
437
+ canUseTemplateString = false,
438
+ preserveLineBreaks = false,
439
+ quoteDefault = DOUBLE_QUOTE,
440
+ } = {},
441
+ ) => {
442
+ quote =
443
+ quote === "auto"
444
+ ? determineQuote(value, { canUseTemplateString, quoteDefault })
445
+ : quote;
446
+ if (quote === BACKTICK) {
447
+ return `\`${escapeTemplateStringSpecialCharacters(value)}\``;
448
+ }
449
+ return surroundStringWith(value, { quote, preserveLineBreaks });
450
+ };
449
451
 
450
- const min = forceColor || 0;
452
+ // https://github.com/mgenware/string-to-template-literal/blob/main/src/main.ts#L1
453
+ const escapeTemplateStringSpecialCharacters = (string) => {
454
+ string = String(string);
455
+ let i = 0;
456
+ let escapedString = "";
457
+ while (i < string.length) {
458
+ const char = string[i];
459
+ i++;
460
+ escapedString += isTemplateStringSpecialChar(char) ? `\\${char}` : char;
461
+ }
462
+ return escapedString;
463
+ };
451
464
 
452
- if (env.TERM === 'dumb') {
453
- return min;
454
- }
465
+ const isTemplateStringSpecialChar = (char) =>
466
+ templateStringSpecialChars.indexOf(char) > -1;
467
+ const templateStringSpecialChars = ["\\", "`", "$"];
455
468
 
456
- if (process$1.platform === 'win32') {
457
- // Windows 10 build 10586 is the first Windows release that supports 256 colors.
458
- // Windows 10 build 14931 is the first release that supports 16m/TrueColor.
459
- const osRelease = os.release().split('.');
460
- if (
461
- Number(osRelease[0]) >= 10
462
- && Number(osRelease[2]) >= 10_586
463
- ) {
464
- return Number(osRelease[2]) >= 14_931 ? 3 : 2;
465
- }
469
+ const determineQuote = (
470
+ string,
471
+ { canUseTemplateString, quoteDefault = DOUBLE_QUOTE } = {},
472
+ ) => {
473
+ const containsDoubleQuote = string.includes(DOUBLE_QUOTE);
474
+ if (!containsDoubleQuote) {
475
+ return DOUBLE_QUOTE;
476
+ }
477
+ const containsSimpleQuote = string.includes(SINGLE_QUOTE);
478
+ if (!containsSimpleQuote) {
479
+ return SINGLE_QUOTE;
480
+ }
481
+ if (canUseTemplateString) {
482
+ const containsBackTick = string.includes(BACKTICK);
483
+ if (!containsBackTick) {
484
+ return BACKTICK;
485
+ }
486
+ }
487
+ const doubleQuoteCount = string.split(DOUBLE_QUOTE).length - 1;
488
+ const singleQuoteCount = string.split(SINGLE_QUOTE).length - 1;
489
+ if (singleQuoteCount > doubleQuoteCount) {
490
+ return DOUBLE_QUOTE;
491
+ }
492
+ if (doubleQuoteCount > singleQuoteCount) {
493
+ return SINGLE_QUOTE;
494
+ }
495
+ return quoteDefault;
496
+ };
466
497
 
467
- return 1;
468
- }
498
+ const inspectChar = (char, { quote, preserveLineBreaks }) => {
499
+ const point = char.charCodeAt(0);
500
+ if (preserveLineBreaks && (char === "\n" || char === "\r")) {
501
+ return char;
502
+ }
503
+ if (
504
+ char === quote ||
505
+ point === 92 ||
506
+ point < 32 ||
507
+ (point > 126 && point < 160) ||
508
+ // line separators
509
+ point === 8232 ||
510
+ point === 8233
511
+ ) {
512
+ const replacement =
513
+ char === quote
514
+ ? `\\${quote}`
515
+ : point === 8232
516
+ ? "\\u2028"
517
+ : point === 8233
518
+ ? "\\u2029"
519
+ : meta[point];
520
+ return replacement;
521
+ }
522
+ return char;
523
+ };
469
524
 
470
- if ('CI' in env) {
471
- if ('GITHUB_ACTIONS' in env || 'GITEA_ACTIONS' in env) {
472
- return 3;
473
- }
474
-
475
- if (['TRAVIS', 'CIRCLECI', 'APPVEYOR', 'GITLAB_CI', 'BUILDKITE', 'DRONE'].some(sign => sign in env) || env.CI_NAME === 'codeship') {
476
- return 1;
477
- }
525
+ // https://github.com/jsenv/jsenv-uneval/blob/6c97ef9d8f2e9425a66f2c88347e0a118d427f3a/src/internal/escapeString.js#L3
526
+ // https://github.com/jsenv/jsenv-inspect/blob/bb11de3adf262b68f71ed82b0a37d4528dd42229/src/internal/string.js#L3
527
+ // https://github.com/joliss/js-string-escape/blob/master/index.js
528
+ // http://javascript.crockford.com/remedial.html
529
+ const surroundStringWith = (string, { quote, preserveLineBreaks }) => {
530
+ let result = "";
531
+ let last = 0;
532
+ const lastIndex = string.length;
533
+ let i = 0;
534
+ while (i < lastIndex) {
535
+ const char = string[i];
536
+ const replacement = inspectChar(char, { quote, preserveLineBreaks });
537
+ if (char !== replacement) {
538
+ if (last === i) {
539
+ result += replacement;
540
+ } else {
541
+ result += `${string.slice(last, i)}${replacement}`;
542
+ }
543
+ last = i + 1;
544
+ }
545
+ i++;
546
+ }
547
+ if (last !== lastIndex) {
548
+ result += string.slice(last);
549
+ }
550
+ return `${quote}${result}${quote}`;
551
+ };
478
552
 
479
- return min;
480
- }
553
+ // prettier-ignore
554
+ const meta = [
555
+ '\\x00', '\\x01', '\\x02', '\\x03', '\\x04', '\\x05', '\\x06', '\\x07', // x07
556
+ '\\b', '\\t', '\\n', '\\x0B', '\\f', '\\r', '\\x0E', '\\x0F', // x0F
557
+ '\\x10', '\\x11', '\\x12', '\\x13', '\\x14', '\\x15', '\\x16', '\\x17', // x17
558
+ '\\x18', '\\x19', '\\x1A', '\\x1B', '\\x1C', '\\x1D', '\\x1E', '\\x1F', // x1F
559
+ '', '', '', '', '', '', '', "\\'", '', '', '', '', '', '', '', '', // x2F
560
+ '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', // x3F
561
+ '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', // x4F
562
+ '', '', '', '', '', '', '', '', '', '', '', '', '\\\\', '', '', '', // x5F
563
+ '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', // x6F
564
+ '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '\\x7F', // x7F
565
+ '\\x80', '\\x81', '\\x82', '\\x83', '\\x84', '\\x85', '\\x86', '\\x87', // x87
566
+ '\\x88', '\\x89', '\\x8A', '\\x8B', '\\x8C', '\\x8D', '\\x8E', '\\x8F', // x8F
567
+ '\\x90', '\\x91', '\\x92', '\\x93', '\\x94', '\\x95', '\\x96', '\\x97', // x97
568
+ '\\x98', '\\x99', '\\x9A', '\\x9B', '\\x9C', '\\x9D', '\\x9E', '\\x9F', // x9F
569
+ ];
481
570
 
482
- if ('TEAMCITY_VERSION' in env) {
483
- return /^(9\.(0*[1-9]\d*)\.|\d{2,}\.)/.test(env.TEAMCITY_VERSION) ? 1 : 0;
484
- }
571
+ const inspectSymbol = (value, { nestedHumanize, parenthesis }) => {
572
+ const symbolDescription = symbolToDescription(value);
573
+ const symbolDescriptionSource = symbolDescription
574
+ ? nestedHumanize(symbolDescription)
575
+ : "";
576
+ const symbolSource = `Symbol(${symbolDescriptionSource})`;
485
577
 
486
- if (env.COLORTERM === 'truecolor') {
487
- return 3;
488
- }
578
+ if (parenthesis) return `${symbolSource}`;
579
+ return symbolSource;
580
+ };
489
581
 
490
- if (env.TERM === 'xterm-kitty') {
491
- return 3;
492
- }
582
+ const symbolToDescription =
583
+ "description" in Symbol.prototype
584
+ ? (symbol) => symbol.description
585
+ : (symbol) => {
586
+ const toStringResult = symbol.toString();
587
+ const openingParenthesisIndex = toStringResult.indexOf("(");
588
+ const closingParenthesisIndex = toStringResult.indexOf(")");
589
+ const symbolDescription = toStringResult.slice(
590
+ openingParenthesisIndex + 1,
591
+ closingParenthesisIndex,
592
+ );
593
+ return symbolDescription;
594
+ };
493
595
 
494
- if ('TERM_PROGRAM' in env) {
495
- const version = Number.parseInt((env.TERM_PROGRAM_VERSION || '').split('.')[0], 10);
596
+ const inspectUndefined = () => "undefined";
496
597
 
497
- switch (env.TERM_PROGRAM) {
498
- case 'iTerm.app': {
499
- return version >= 3 ? 3 : 2;
500
- }
598
+ const inspectBigInt = (value) => {
599
+ return `${value}n`;
600
+ };
501
601
 
502
- case 'Apple_Terminal': {
503
- return 2;
504
- }
505
- // No default
506
- }
507
- }
602
+ const preNewLineAndIndentation = (
603
+ value,
604
+ { depth, indentUsingTab, indentSize },
605
+ ) => {
606
+ return `${newLineAndIndent({
607
+ count: depth + 1,
608
+ useTabs: indentUsingTab,
609
+ size: indentSize,
610
+ })}${value}`;
611
+ };
508
612
 
509
- if (/-256(color)?$/i.test(env.TERM)) {
510
- return 2;
511
- }
613
+ const postNewLineAndIndentation = ({ depth, indentUsingTab, indentSize }) => {
614
+ return newLineAndIndent({
615
+ count: depth,
616
+ useTabs: indentUsingTab,
617
+ size: indentSize,
618
+ });
619
+ };
512
620
 
513
- if (/^screen|^xterm|^vt100|^vt220|^rxvt|color|ansi|cygwin|linux/i.test(env.TERM)) {
514
- return 1;
515
- }
621
+ const newLineAndIndent = ({ count, useTabs, size }) => {
622
+ if (useTabs) {
623
+ // eslint-disable-next-line prefer-template
624
+ return "\n" + "\t".repeat(count);
625
+ }
626
+ // eslint-disable-next-line prefer-template
627
+ return "\n" + " ".repeat(count * size);
628
+ };
516
629
 
517
- if ('COLORTERM' in env) {
518
- return 1;
519
- }
630
+ const wrapNewLineAndIndentation = (
631
+ value,
632
+ { depth, indentUsingTab, indentSize },
633
+ ) => {
634
+ return `${preNewLineAndIndentation(value, {
635
+ depth,
636
+ indentUsingTab,
637
+ indentSize,
638
+ })}${postNewLineAndIndentation({ depth, indentUsingTab, indentSize })}`;
639
+ };
520
640
 
521
- return min;
522
- }
641
+ const inspectConstructor = (value, { parenthesis, useNew }) => {
642
+ let formattedString = value;
643
+ if (parenthesis) {
644
+ formattedString = `(${value})`;
645
+ }
646
+ if (useNew) {
647
+ formattedString = `new ${formattedString}`;
648
+ }
649
+ return formattedString;
650
+ };
523
651
 
524
- function createSupportsColor(stream, options = {}) {
525
- const level = _supportsColor(stream, {
526
- streamIsTTY: stream && stream.isTTY,
527
- ...options,
528
- });
652
+ const inspectArray = (
653
+ value,
654
+ {
655
+ seen = [],
656
+ nestedHumanize,
657
+ depth,
658
+ indentUsingTab,
659
+ indentSize,
660
+ parenthesis,
661
+ useNew,
662
+ },
663
+ ) => {
664
+ if (seen.indexOf(value) > -1) {
665
+ return "Symbol.for('circular')";
666
+ }
667
+ seen.push(value);
529
668
 
530
- return translateLevel(level);
531
- }
669
+ let valuesSource = "";
670
+ let i = 0;
671
+ const j = value.length;
532
672
 
533
- ({
534
- stdout: createSupportsColor({isTTY: tty.isatty(1)}),
535
- stderr: createSupportsColor({isTTY: tty.isatty(2)}),
536
- });
673
+ while (i < j) {
674
+ const valueSource = value.hasOwnProperty(i)
675
+ ? nestedHumanize(value[i], { seen })
676
+ : "";
677
+ if (i === 0) {
678
+ valuesSource += valueSource;
679
+ } else {
680
+ valuesSource += `,${preNewLineAndIndentation(valueSource, {
681
+ depth,
682
+ indentUsingTab,
683
+ indentSize,
684
+ })}`;
685
+ }
686
+ i++;
687
+ }
537
688
 
538
- const processSupportsBasicColor = createSupportsColor(process.stdout).hasBasic;
539
- // https://github.com/Marak/colors.js/blob/master/lib/styles.js
540
- // https://stackoverflow.com/a/75985833/2634179
541
- const RESET = "\x1b[0m";
689
+ let arraySource;
690
+ if (valuesSource.length) {
691
+ arraySource = wrapNewLineAndIndentation(valuesSource, {
692
+ depth,
693
+ indentUsingTab,
694
+ indentSize,
695
+ });
696
+ } else {
697
+ arraySource = "";
698
+ }
542
699
 
543
- const ANSI = {
544
- supported: processSupportsBasicColor,
545
-
546
- RED: "\x1b[31m",
547
- GREEN: "\x1b[32m",
548
- YELLOW: "\x1b[33m",
549
- BLUE: "\x1b[34m",
550
- MAGENTA: "\x1b[35m",
551
- CYAN: "\x1b[36m",
552
- GREY: "\x1b[90m",
553
- color: (text, ANSI_COLOR) => {
554
- return ANSI.supported && ANSI_COLOR ? `${ANSI_COLOR}${text}${RESET}` : text;
555
- },
700
+ arraySource = `[${arraySource}]`;
556
701
 
557
- BOLD: "\x1b[1m",
558
- UNDERLINE: "\x1b[4m",
559
- STRIKE: "\x1b[9m",
560
- effect: (text, ANSI_EFFECT) => {
561
- return ANSI.supported && ANSI_EFFECT
562
- ? `${ANSI_EFFECT}${text}${RESET}`
563
- : text;
564
- },
702
+ return inspectConstructor(arraySource, { parenthesis, useNew });
565
703
  };
566
704
 
567
- // GitHub workflow does support ANSI but "supports-color" returns false
568
- // because stream.isTTY returns false, see https://github.com/actions/runner/issues/241
569
- if (
570
- process.env.GITHUB_WORKFLOW &&
571
- // Check on FORCE_COLOR is to ensure it is prio over GitHub workflow check
572
- // in unit test we use process.env.FORCE_COLOR = 'false' to fake
573
- // that colors are not supported. Let it have priority
574
- process.env.FORCE_COLOR !== "false"
575
- ) {
576
- ANSI.supported = true;
577
- }
705
+ const inspectBigIntObject = (value, { nestedHumanize }) => {
706
+ const bigIntSource = nestedHumanize(value.valueOf());
578
707
 
579
- const startSpinner = ({
580
- dynamicLog,
581
- frames = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"],
582
- fps = 20,
583
- keepProcessAlive = false,
584
- stopOnWriteFromOutside = true,
585
- stopOnVerticalOverflow = true,
586
- render = () => "",
587
- effect = () => {},
588
- animated = dynamicLog.stream.isTTY,
589
- }) => {
590
- let frameIndex = 0;
591
- let interval;
592
- let running = true;
708
+ return `BigInt(${bigIntSource})`;
709
+ };
593
710
 
594
- const spinner = {
595
- message: undefined,
596
- };
711
+ const inspectBooleanObject = (
712
+ value,
713
+ { nestedHumanize, useNew, parenthesis },
714
+ ) => {
715
+ const booleanSource = nestedHumanize(value.valueOf());
716
+ return inspectConstructor(`Boolean(${booleanSource})`, {
717
+ useNew,
718
+ parenthesis,
719
+ });
720
+ };
597
721
 
598
- const update = (message) => {
599
- spinner.message = running
600
- ? `${frames[frameIndex]} ${message}\n`
601
- : `${message}\n`;
602
- return spinner.message;
603
- };
604
- spinner.update = update;
722
+ const inspectError = (
723
+ error,
724
+ { nestedHumanize, useNew, parenthesis },
725
+ ) => {
726
+ const messageSource = nestedHumanize(error.message);
605
727
 
606
- let cleanup;
607
- if (animated && ANSI.supported) {
608
- running = true;
609
- cleanup = effect();
610
- dynamicLog.update(update(render()));
728
+ const errorSource = inspectConstructor(
729
+ `${errorToConstructorName(error)}(${messageSource})`,
730
+ {
731
+ useNew,
732
+ parenthesis,
733
+ },
734
+ );
735
+ return errorSource;
736
+ };
611
737
 
612
- interval = setInterval(() => {
613
- frameIndex = frameIndex === frames.length - 1 ? 0 : frameIndex + 1;
614
- dynamicLog.update(update(render()));
615
- }, 1000 / fps);
616
- if (!keepProcessAlive) {
617
- interval.unref();
618
- }
619
- } else {
620
- dynamicLog.update(update(render()));
738
+ const errorToConstructorName = ({ name }) => {
739
+ if (derivedErrorNameArray.includes(name)) {
740
+ return name;
621
741
  }
742
+ return "Error";
743
+ };
622
744
 
623
- const stop = (message) => {
624
- running = false;
625
- if (interval) {
626
- clearInterval(interval);
627
- interval = null;
628
- }
629
- if (cleanup) {
630
- cleanup();
631
- cleanup = null;
632
- }
633
- if (dynamicLog && message) {
634
- dynamicLog.update(update(message));
635
- dynamicLog = null;
636
- }
637
- };
638
- spinner.stop = stop;
745
+ // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error#Error_types
746
+ const derivedErrorNameArray = [
747
+ "EvalError",
748
+ "RangeError",
749
+ "ReferenceError",
750
+ "SyntaxError",
751
+ "TypeError",
752
+ "URIError",
753
+ ];
639
754
 
640
- if (stopOnVerticalOverflow) {
641
- dynamicLog.onVerticalOverflow = stop;
642
- }
643
- if (stopOnWriteFromOutside) {
644
- dynamicLog.onWriteFromOutside = stop;
755
+ const inspectDate = (value, { nestedHumanize, useNew, parenthesis }) => {
756
+ const dateSource = nestedHumanize(value.valueOf(), {
757
+ numericSeparator: false,
758
+ });
759
+ return inspectConstructor(`Date(${dateSource})`, { useNew, parenthesis });
760
+ };
761
+
762
+ const inspectFunction = (
763
+ value,
764
+ { showFunctionBody, parenthesis, depth },
765
+ ) => {
766
+ let functionSource;
767
+ if (showFunctionBody) {
768
+ functionSource = value.toString();
769
+ } else {
770
+ const isArrowFunction = value.prototype === undefined;
771
+ const head = isArrowFunction
772
+ ? "() =>"
773
+ : `function ${depth === 0 ? value.name : ""}()`;
774
+ functionSource = `${head} {/* hidden */}`;
645
775
  }
646
776
 
647
- return spinner;
777
+ if (parenthesis) {
778
+ return `(${functionSource})`;
779
+ }
780
+ return functionSource;
648
781
  };
649
782
 
650
- const getPrecision = (number) => {
651
- if (Math.floor(number) === number) return 0;
652
- const [, decimals] = number.toString().split(".");
653
- return decimals.length || 0;
783
+ const inspectNumberObject = (
784
+ value,
785
+ { nestedHumanize, useNew, parenthesis },
786
+ ) => {
787
+ const numberSource = nestedHumanize(value.valueOf());
788
+ return inspectConstructor(`Number(${numberSource})`, { useNew, parenthesis });
654
789
  };
655
790
 
656
- const setRoundedPrecision = (
657
- number,
658
- { decimals = 1, decimalsWhenSmall = decimals } = {},
791
+ const inspectObject = (
792
+ value,
793
+ {
794
+ nestedHumanize,
795
+ seen = [],
796
+ depth,
797
+ indentUsingTab,
798
+ indentSize,
799
+ objectConstructor,
800
+ parenthesis,
801
+ useNew,
802
+ },
659
803
  ) => {
660
- return setDecimalsPrecision(number, {
661
- decimals,
662
- decimalsWhenSmall,
663
- transform: Math.round,
804
+ if (seen.indexOf(value) > -1) return "Symbol.for('circular')";
805
+
806
+ seen.push(value);
807
+
808
+ const propertySourceArray = [];
809
+ Object.getOwnPropertyNames(value).forEach((propertyName) => {
810
+ const propertyNameAsNumber = parseInt(propertyName, 10);
811
+ const propertyNameSource = nestedHumanize(
812
+ Number.isInteger(propertyNameAsNumber)
813
+ ? propertyNameAsNumber
814
+ : propertyName,
815
+ );
816
+ propertySourceArray.push({
817
+ nameOrSymbolSource: propertyNameSource,
818
+ valueSource: nestedHumanize(value[propertyName], { seen }),
819
+ });
820
+ });
821
+ Object.getOwnPropertySymbols(value).forEach((symbol) => {
822
+ propertySourceArray.push({
823
+ nameOrSymbolSource: `[${nestedHumanize(symbol)}]`,
824
+ valueSource: nestedHumanize(value[symbol], { seen }),
825
+ });
664
826
  });
665
- };
666
827
 
667
- const setPrecision = (
668
- number,
669
- { decimals = 1, decimalsWhenSmall = decimals } = {},
670
- ) => {
671
- return setDecimalsPrecision(number, {
672
- decimals,
673
- decimalsWhenSmall,
674
- transform: parseInt,
828
+ let propertiesSource = "";
829
+ propertySourceArray.forEach(({ nameOrSymbolSource, valueSource }, index) => {
830
+ if (index === 0) {
831
+ propertiesSource += `${nameOrSymbolSource}: ${valueSource}`;
832
+ } else {
833
+ propertiesSource += `,${preNewLineAndIndentation(
834
+ `${nameOrSymbolSource}: ${valueSource}`,
835
+ {
836
+ depth,
837
+ indentUsingTab,
838
+ indentSize,
839
+ },
840
+ )}`;
841
+ }
675
842
  });
676
- };
677
843
 
678
- const setDecimalsPrecision = (
679
- number,
680
- {
681
- transform,
682
- decimals, // max decimals for number in [-Infinity, -1[]1, Infinity]
683
- decimalsWhenSmall, // max decimals for number in [-1,1]
684
- } = {},
685
- ) => {
686
- if (number === 0) {
687
- return 0;
844
+ let objectSource;
845
+ if (propertiesSource.length) {
846
+ objectSource = `${wrapNewLineAndIndentation(propertiesSource, {
847
+ depth,
848
+ indentUsingTab,
849
+ indentSize,
850
+ })}`;
851
+ } else {
852
+ objectSource = "";
688
853
  }
689
- let numberCandidate = Math.abs(number);
690
- if (numberCandidate < 1) {
691
- const integerGoal = Math.pow(10, decimalsWhenSmall - 1);
692
- let i = 1;
693
- while (numberCandidate < integerGoal) {
694
- numberCandidate *= 10;
695
- i *= 10;
696
- }
697
- const asInteger = transform(numberCandidate);
698
- const asFloat = asInteger / i;
699
- return number < 0 ? -asFloat : asFloat;
854
+
855
+ if (objectConstructor) {
856
+ objectSource = `Object({${objectSource}})`;
857
+ } else {
858
+ objectSource = `{${objectSource}}`;
700
859
  }
701
- const coef = Math.pow(10, decimals);
702
- const numberMultiplied = (number + Number.EPSILON) * coef;
703
- const asInteger = transform(numberMultiplied);
704
- const asFloat = asInteger / coef;
705
- return number < 0 ? -asFloat : asFloat;
860
+
861
+ return inspectConstructor(objectSource, { parenthesis, useNew });
706
862
  };
707
863
 
708
- // https://www.codingem.com/javascript-how-to-limit-decimal-places/
709
- // export const roundNumber = (number, maxDecimals) => {
710
- // const decimalsExp = Math.pow(10, maxDecimals)
711
- // const numberRoundInt = Math.round(decimalsExp * (number + Number.EPSILON))
712
- // const numberRoundFloat = numberRoundInt / decimalsExp
713
- // return numberRoundFloat
714
- // }
864
+ const inspectRegExp = (value) => value.toString();
715
865
 
716
- // export const setPrecision = (number, precision) => {
717
- // if (Math.floor(number) === number) return number
718
- // const [int, decimals] = number.toString().split(".")
719
- // if (precision <= 0) return int
720
- // const numberTruncated = `${int}.${decimals.slice(0, precision)}`
721
- // return numberTruncated
722
- // }
866
+ const inspectStringObject = (
867
+ value,
868
+ { nestedHumanize, useNew, parenthesis },
869
+ ) => {
870
+ const stringSource = nestedHumanize(value.valueOf());
723
871
 
724
- const humanizeEllapsedTime = (ms, { short } = {}) => {
725
- if (ms < 1000) {
726
- return short ? "0s" : "0 second";
727
- }
728
- const { primary, remaining } = parseMs(ms);
729
- if (!remaining) {
730
- return inspectEllapsedUnit(primary, short);
731
- }
732
- return `${inspectEllapsedUnit(primary, short)} and ${inspectEllapsedUnit(
733
- remaining,
734
- short,
735
- )}`;
872
+ return inspectConstructor(`String(${stringSource})`, { useNew, parenthesis });
736
873
  };
737
- const inspectEllapsedUnit = (unit, short) => {
738
- const count =
739
- unit.name === "second" ? Math.floor(unit.count) : Math.round(unit.count);
740
- let name = unit.name;
741
- if (short) {
742
- name = unitShort[name];
743
- if (count <= 1) {
744
- return `${count}${name}`;
745
- }
746
- return `${count}${name}s`;
747
- }
748
- if (count <= 1) {
749
- return `${count} ${name}`;
874
+
875
+ // primitives
876
+
877
+ const humanize = (
878
+ value,
879
+ {
880
+ parenthesis = false,
881
+ quote = "auto",
882
+ canUseTemplateString = true,
883
+ useNew = false,
884
+ objectConstructor = false,
885
+ showFunctionBody = false,
886
+ indentUsingTab = false,
887
+ indentSize = 2,
888
+ numericSeparator = true,
889
+ preserveLineBreaks = false,
890
+ } = {},
891
+ ) => {
892
+ const scopedHumanize = (scopedValue, scopedOptions) => {
893
+ const options = {
894
+ ...scopedOptions,
895
+ nestedHumanize: (nestedValue, nestedOptions = {}) => {
896
+ return scopedHumanize(nestedValue, {
897
+ ...scopedOptions,
898
+ depth: scopedOptions.depth + 1,
899
+ ...nestedOptions,
900
+ });
901
+ },
902
+ };
903
+ return humanizeValue(scopedValue, options);
904
+ };
905
+
906
+ return scopedHumanize(value, {
907
+ parenthesis,
908
+ quote,
909
+ canUseTemplateString,
910
+ useNew,
911
+ objectConstructor,
912
+ showFunctionBody,
913
+ indentUsingTab,
914
+ indentSize,
915
+ numericSeparator,
916
+ preserveLineBreaks,
917
+ depth: 0,
918
+ });
919
+ };
920
+
921
+ const humanizeMethodSymbol = Symbol.for("inspect");
922
+
923
+ const humanizeValue = (value, options) => {
924
+ const customHumanize = value && value[humanizeMethodSymbol];
925
+ if (customHumanize) {
926
+ return customHumanize(options);
927
+ }
928
+ const primitiveType = primitiveTypeFromValue(value);
929
+ const primitiveStringifier = primitiveStringifiers[primitiveType];
930
+ if (primitiveStringifier) {
931
+ return primitiveStringifier(value, options);
932
+ }
933
+ const compositeType = compositeTypeFromObject(value);
934
+ const compositeStringifier = compositeStringifiers[compositeType];
935
+ if (compositeStringifier) {
936
+ return compositeStringifier(value, options);
937
+ }
938
+ return inspectConstructor(
939
+ `${compositeType}(${inspectObject(value, options)})`,
940
+ {
941
+ ...options,
942
+ parenthesis: false,
943
+ },
944
+ );
945
+ };
946
+
947
+ const primitiveTypeFromValue = (value) => {
948
+ if (value === null) {
949
+ return "null";
950
+ }
951
+ if (value === undefined) {
952
+ return "undefined";
953
+ }
954
+ return typeof value;
955
+ };
956
+ const primitiveStringifiers = {
957
+ boolean: inspectBoolean,
958
+ function: inspectFunction,
959
+ null: inspectNull,
960
+ number: inspectNumber,
961
+ string: inspectString,
962
+ symbol: inspectSymbol,
963
+ undefined: inspectUndefined,
964
+ bigint: inspectBigInt,
965
+ };
966
+ const compositeTypeFromObject = (object) => {
967
+ if (typeof object === "object" && Object.getPrototypeOf(object) === null) {
968
+ return "Object";
969
+ }
970
+ const toStringResult = toString.call(object);
971
+ // returns format is '[object ${tagName}]';
972
+ // and we want ${tagName}
973
+ const tagName = toStringResult.slice("[object ".length, -1);
974
+ if (tagName === "Object") {
975
+ const objectConstructorName = object.constructor.name;
976
+ if (objectConstructorName !== "Object") {
977
+ return objectConstructorName;
978
+ }
979
+ }
980
+ return tagName;
981
+ };
982
+ const { toString } = Object.prototype;
983
+
984
+ const compositeStringifiers = {
985
+ Array: inspectArray,
986
+ BigInt: inspectBigIntObject,
987
+ Boolean: inspectBooleanObject,
988
+ Error: inspectError,
989
+ Date: inspectDate,
990
+ Function: inspectFunction,
991
+ Number: inspectNumberObject,
992
+ Object: inspectObject,
993
+ RegExp: inspectRegExp,
994
+ String: inspectStringObject,
995
+ };
996
+
997
+ const getPrecision = (number) => {
998
+ if (Math.floor(number) === number) return 0;
999
+ const [, decimals] = number.toString().split(".");
1000
+ return decimals.length || 0;
1001
+ };
1002
+
1003
+ const setRoundedPrecision = (
1004
+ number,
1005
+ { decimals = 1, decimalsWhenSmall = decimals } = {},
1006
+ ) => {
1007
+ return setDecimalsPrecision(number, {
1008
+ decimals,
1009
+ decimalsWhenSmall,
1010
+ transform: Math.round,
1011
+ });
1012
+ };
1013
+
1014
+ const setPrecision = (
1015
+ number,
1016
+ { decimals = 1, decimalsWhenSmall = decimals } = {},
1017
+ ) => {
1018
+ return setDecimalsPrecision(number, {
1019
+ decimals,
1020
+ decimalsWhenSmall,
1021
+ transform: parseInt,
1022
+ });
1023
+ };
1024
+
1025
+ const setDecimalsPrecision = (
1026
+ number,
1027
+ {
1028
+ transform,
1029
+ decimals, // max decimals for number in [-Infinity, -1[]1, Infinity]
1030
+ decimalsWhenSmall, // max decimals for number in [-1,1]
1031
+ } = {},
1032
+ ) => {
1033
+ if (number === 0) {
1034
+ return 0;
1035
+ }
1036
+ let numberCandidate = Math.abs(number);
1037
+ if (numberCandidate < 1) {
1038
+ const integerGoal = Math.pow(10, decimalsWhenSmall - 1);
1039
+ let i = 1;
1040
+ while (numberCandidate < integerGoal) {
1041
+ numberCandidate *= 10;
1042
+ i *= 10;
1043
+ }
1044
+ const asInteger = transform(numberCandidate);
1045
+ const asFloat = asInteger / i;
1046
+ return number < 0 ? -asFloat : asFloat;
1047
+ }
1048
+ const coef = Math.pow(10, decimals);
1049
+ const numberMultiplied = (number + Number.EPSILON) * coef;
1050
+ const asInteger = transform(numberMultiplied);
1051
+ const asFloat = asInteger / coef;
1052
+ return number < 0 ? -asFloat : asFloat;
1053
+ };
1054
+
1055
+ // https://www.codingem.com/javascript-how-to-limit-decimal-places/
1056
+ // export const roundNumber = (number, maxDecimals) => {
1057
+ // const decimalsExp = Math.pow(10, maxDecimals)
1058
+ // const numberRoundInt = Math.round(decimalsExp * (number + Number.EPSILON))
1059
+ // const numberRoundFloat = numberRoundInt / decimalsExp
1060
+ // return numberRoundFloat
1061
+ // }
1062
+
1063
+ // export const setPrecision = (number, precision) => {
1064
+ // if (Math.floor(number) === number) return number
1065
+ // const [int, decimals] = number.toString().split(".")
1066
+ // if (precision <= 0) return int
1067
+ // const numberTruncated = `${int}.${decimals.slice(0, precision)}`
1068
+ // return numberTruncated
1069
+ // }
1070
+
1071
+ const humanizeEllapsedTime = (ms, { short } = {}) => {
1072
+ if (ms < 1000) {
1073
+ return short ? "0s" : "0 second";
1074
+ }
1075
+ const { primary, remaining } = parseMs(ms);
1076
+ if (!remaining) {
1077
+ return inspectEllapsedUnit(primary, short);
1078
+ }
1079
+ return `${inspectEllapsedUnit(primary, short)} and ${inspectEllapsedUnit(
1080
+ remaining,
1081
+ short,
1082
+ )}`;
1083
+ };
1084
+ const inspectEllapsedUnit = (unit, short) => {
1085
+ const count =
1086
+ unit.name === "second" ? Math.floor(unit.count) : Math.round(unit.count);
1087
+ let name = unit.name;
1088
+ if (short) {
1089
+ name = unitShort[name];
1090
+ if (count <= 1) {
1091
+ return `${count}${name}`;
1092
+ }
1093
+ return `${count}${name}s`;
1094
+ }
1095
+ if (count <= 1) {
1096
+ return `${count} ${name}`;
750
1097
  }
751
1098
  return `${count} ${name}s`;
752
1099
  };
@@ -871,1059 +1218,725 @@ const parseMs = (ms) => {
871
1218
  };
872
1219
  };
873
1220
 
874
- function isUnicodeSupported() {
875
- if (process$1.platform !== 'win32') {
876
- return process$1.env.TERM !== 'linux'; // Linux console (kernel)
877
- }
878
-
879
- return Boolean(process$1.env.WT_SESSION) // Windows Terminal
880
- || Boolean(process$1.env.TERMINUS_SUBLIME) // Terminus (<0.2.27)
881
- || process$1.env.ConEmuTask === '{cmd::Cmder}' // ConEmu and cmder
882
- || process$1.env.TERM_PROGRAM === 'Terminus-Sublime'
883
- || process$1.env.TERM_PROGRAM === 'vscode'
884
- || process$1.env.TERM === 'xterm-256color'
885
- || process$1.env.TERM === 'alacritty'
886
- || process$1.env.TERMINAL_EMULATOR === 'JetBrains-JediTerm';
887
- }
888
-
889
- // see also https://github.com/sindresorhus/figures
890
-
1221
+ const humanizeFileSize = (numberOfBytes, { decimals, short } = {}) => {
1222
+ return inspectBytes(numberOfBytes, { decimals, short });
1223
+ };
891
1224
 
892
- const UNICODE = {
893
- supported: isUnicodeSupported(),
1225
+ const humanizeMemory = (metricValue, { decimals, short } = {}) => {
1226
+ return inspectBytes(metricValue, { decimals, fixedDecimals: true, short });
1227
+ };
894
1228
 
895
- get COMMAND_RAW() {
896
- return UNICODE.supported ? `❯` : `>`;
897
- },
898
- get OK_RAW() {
899
- return UNICODE.supported ? `✔` : `√`;
900
- },
901
- get FAILURE_RAW() {
902
- return UNICODE.supported ? `✖` : `×`;
903
- },
904
- get DEBUG_RAW() {
905
- return UNICODE.supported ? `◆` : `♦`;
906
- },
907
- get INFO_RAW() {
908
- return UNICODE.supported ? `ℹ` : `i`;
909
- },
910
- get WARNING_RAW() {
911
- return UNICODE.supported ? `⚠` : `‼`;
912
- },
913
- get CIRCLE_CROSS_RAW() {
914
- return UNICODE.supported ? `ⓧ` : `(×)`;
915
- },
916
- get COMMAND() {
917
- return ANSI.color(UNICODE.COMMAND_RAW, ANSI.GREY); // ANSI_MAGENTA)
918
- },
919
- get OK() {
920
- return ANSI.color(UNICODE.OK_RAW, ANSI.GREEN);
921
- },
922
- get FAILURE() {
923
- return ANSI.color(UNICODE.FAILURE_RAW, ANSI.RED);
924
- },
925
- get DEBUG() {
926
- return ANSI.color(UNICODE.DEBUG_RAW, ANSI.GREY);
927
- },
928
- get INFO() {
929
- return ANSI.color(UNICODE.INFO_RAW, ANSI.BLUE);
930
- },
931
- get WARNING() {
932
- return ANSI.color(UNICODE.WARNING_RAW, ANSI.YELLOW);
933
- },
934
- get CIRCLE_CROSS() {
935
- return ANSI.color(UNICODE.CIRCLE_CROSS_RAW, ANSI.RED);
936
- },
937
- get ELLIPSIS() {
938
- return UNICODE.supported ? `…` : `...`;
939
- },
940
- };
941
-
942
- const createTaskLog = (
943
- label,
944
- { disabled = false, animated = true, stopOnWriteFromOutside } = {},
1229
+ const inspectBytes = (
1230
+ number,
1231
+ { fixedDecimals = false, decimals, short } = {},
945
1232
  ) => {
946
- if (disabled) {
947
- return {
948
- setRightText: () => {},
949
- done: () => {},
950
- happen: () => {},
951
- fail: () => {},
952
- };
1233
+ if (number === 0) {
1234
+ return `0 B`;
953
1235
  }
954
- const startMs = Date.now();
955
- const dynamicLog = createDynamicLog();
956
- let message = label;
957
- const taskSpinner = startSpinner({
958
- dynamicLog,
959
- render: () => message,
960
- stopOnWriteFromOutside,
961
- animated,
1236
+ const exponent = Math.min(
1237
+ Math.floor(Math.log10(number) / 3),
1238
+ BYTE_UNITS.length - 1,
1239
+ );
1240
+ const unitNumber = number / Math.pow(1000, exponent);
1241
+ const unitName = BYTE_UNITS[exponent];
1242
+ if (decimals === undefined) {
1243
+ if (unitNumber < 100) {
1244
+ decimals = 1;
1245
+ } else {
1246
+ decimals = 0;
1247
+ }
1248
+ }
1249
+ const unitNumberRounded = setRoundedPrecision(unitNumber, {
1250
+ decimals,
1251
+ decimalsWhenSmall: 1,
962
1252
  });
963
- return {
964
- setRightText: (value) => {
965
- message = `${label} ${value}`;
966
- },
967
- done: () => {
968
- const msEllapsed = Date.now() - startMs;
969
- taskSpinner.stop(
970
- `${UNICODE.OK} ${label} (done in ${humanizeDuration(msEllapsed)})`,
971
- );
972
- },
973
- happen: (message) => {
974
- taskSpinner.stop(
975
- `${UNICODE.INFO} ${message} (at ${new Date().toLocaleTimeString()})`,
976
- );
977
- },
978
- fail: (message = `failed to ${label}`) => {
979
- taskSpinner.stop(`${UNICODE.FAILURE} ${message}`);
980
- },
981
- };
1253
+ const value = fixedDecimals
1254
+ ? unitNumberRounded.toFixed(decimals)
1255
+ : unitNumberRounded;
1256
+ if (short) {
1257
+ return `${value}${unitName}`;
1258
+ }
1259
+ return `${value} ${unitName}`;
982
1260
  };
983
1261
 
984
- const createDetailedMessage = (message, details = {}) => {
985
- let string = `${message}`;
1262
+ const BYTE_UNITS = ["B", "kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"];
986
1263
 
987
- Object.keys(details).forEach((key) => {
988
- const value = details[key];
989
- string += `
990
- --- ${key} ---
991
- ${
992
- Array.isArray(value)
993
- ? value.join(`
994
- `)
995
- : value
996
- }`;
1264
+ const distributePercentages = (
1265
+ namedNumbers,
1266
+ { maxPrecisionHint = 2 } = {},
1267
+ ) => {
1268
+ const numberNames = Object.keys(namedNumbers);
1269
+ if (numberNames.length === 0) {
1270
+ return {};
1271
+ }
1272
+ if (numberNames.length === 1) {
1273
+ const firstNumberName = numberNames[0];
1274
+ return { [firstNumberName]: "100 %" };
1275
+ }
1276
+ const numbers = numberNames.map((name) => namedNumbers[name]);
1277
+ const total = numbers.reduce((sum, value) => sum + value, 0);
1278
+ const ratios = numbers.map((number) => number / total);
1279
+ const percentages = {};
1280
+ ratios.pop();
1281
+ ratios.forEach((ratio, index) => {
1282
+ const percentage = ratio * 100;
1283
+ percentages[numberNames[index]] = percentage;
1284
+ });
1285
+ const lowestPercentage = (1 / Math.pow(10, maxPrecisionHint)) * 100;
1286
+ let precision = 0;
1287
+ Object.keys(percentages).forEach((name) => {
1288
+ const percentage = percentages[name];
1289
+ if (percentage < lowestPercentage) {
1290
+ // check the amout of meaningful decimals
1291
+ // and that what we will use
1292
+ const percentageRounded = setRoundedPrecision(percentage);
1293
+ const percentagePrecision = getPrecision(percentageRounded);
1294
+ if (percentagePrecision > precision) {
1295
+ precision = percentagePrecision;
1296
+ }
1297
+ }
997
1298
  });
1299
+ let remainingPercentage = 100;
998
1300
 
999
- return string;
1301
+ Object.keys(percentages).forEach((name) => {
1302
+ const percentage = percentages[name];
1303
+ const percentageAllocated = setRoundedPrecision(percentage, {
1304
+ decimals: precision,
1305
+ });
1306
+ remainingPercentage -= percentageAllocated;
1307
+ percentages[name] = percentageAllocated;
1308
+ });
1309
+ const lastName = numberNames[numberNames.length - 1];
1310
+ percentages[lastName] = setRoundedPrecision(remainingPercentage, {
1311
+ decimals: precision,
1312
+ });
1313
+ return percentages;
1000
1314
  };
1001
1315
 
1002
- const inspectBoolean = (value) => value.toString();
1003
-
1004
- const inspectNull = () => "null";
1316
+ const formatDefault = (v) => v;
1005
1317
 
1006
- // https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/rules/numeric-separators-style.js
1318
+ const generateContentFrame = ({
1319
+ content,
1320
+ line,
1321
+ column,
1007
1322
 
1008
- const inspectNumber = (value, { numericSeparator }) => {
1009
- if (isNegativeZero(value)) {
1010
- return "-0";
1011
- }
1012
- // isNaN
1013
- // eslint-disable-next-line no-self-compare
1014
- if (value !== value) {
1015
- return "NaN";
1323
+ linesAbove = 3,
1324
+ linesBelow = 0,
1325
+ lineMaxWidth = 120,
1326
+ lineNumbersOnTheLeft = true,
1327
+ lineMarker = true,
1328
+ columnMarker = true,
1329
+ format = formatDefault,
1330
+ } = {}) => {
1331
+ const lineStrings = content.split(/\r?\n/);
1332
+ if (line === 0) line = 1;
1333
+ if (column === undefined) {
1334
+ columnMarker = false;
1335
+ column = 1;
1016
1336
  }
1017
- if (value === Infinity) {
1018
- return "Infinity";
1337
+ if (column === 0) column = 1;
1338
+
1339
+ let lineStartIndex = line - 1 - linesAbove;
1340
+ if (lineStartIndex < 0) {
1341
+ lineStartIndex = 0;
1019
1342
  }
1020
- if (value === -Infinity) {
1021
- return "-Infinity";
1343
+ let lineEndIndex = line - 1 + linesBelow;
1344
+ if (lineEndIndex > lineStrings.length - 1) {
1345
+ lineEndIndex = lineStrings.length - 1;
1022
1346
  }
1023
- const numberString = String(value);
1024
- if (!numericSeparator) {
1025
- return numberString;
1347
+ if (columnMarker) {
1348
+ // human reader deduce the line when there is a column marker
1349
+ lineMarker = false;
1026
1350
  }
1027
- const {
1028
- number,
1029
- mark = "",
1030
- sign = "",
1031
- power = "",
1032
- } = numberString.match(
1033
- /^(?<number>.*?)(?:(?<mark>e)(?<sign>[+-])?(?<power>\d+))?$/i,
1034
- ).groups;
1035
- const numberWithSeparators = formatNumber(number);
1036
- const powerWithSeparators = addSeparator(power, {
1037
- minimumDigits: 5,
1038
- groupLength: 3,
1039
- });
1040
- return `${numberWithSeparators}${mark}${sign}${powerWithSeparators}`;
1041
- };
1042
-
1043
- // Use this and instead of Object.is(value, -0)
1044
- // because in some corner cases firefox returns false
1045
- // for Object.is(-0, -0)
1046
- const isNegativeZero = (value) => {
1047
- return value === 0 && 1 / value === -Infinity;
1048
- };
1049
-
1050
- const formatNumber = (numberString) => {
1051
- const parts = numberString.split(".");
1052
- const [integer, fractional] = parts;
1053
-
1054
- if (parts.length === 2) {
1055
- const integerWithSeparators = addSeparator(integer, {
1056
- minimumDigits: 5,
1057
- groupLength: 3,
1058
- });
1059
- return `${integerWithSeparators}.${fractional}`;
1351
+ if (line - 1 === lineEndIndex) {
1352
+ lineMarker = false; // useless because last line
1060
1353
  }
1354
+ let lineIndex = lineStartIndex;
1061
1355
 
1062
- return addSeparator(integer, {
1063
- minimumDigits: 5,
1064
- groupLength: 3,
1065
- });
1066
- };
1067
-
1068
- const addSeparator = (numberString, { minimumDigits, groupLength }) => {
1069
- if (numberString[0] === "-") {
1070
- return `-${groupDigits(numberString.slice(1), {
1071
- minimumDigits,
1072
- groupLength,
1073
- })}`;
1356
+ let columnsBefore;
1357
+ let columnsAfter;
1358
+ if (column > lineMaxWidth) {
1359
+ columnsBefore = column - Math.ceil(lineMaxWidth / 2);
1360
+ columnsAfter = column + Math.floor(lineMaxWidth / 2);
1361
+ } else {
1362
+ columnsBefore = 0;
1363
+ columnsAfter = lineMaxWidth;
1074
1364
  }
1075
- return groupDigits(numberString, { minimumDigits, groupLength });
1076
- };
1365
+ let columnMarkerIndex = column - 1 - columnsBefore;
1077
1366
 
1078
- const groupDigits = (digits, { minimumDigits, groupLength }) => {
1079
- const digitCount = digits.length;
1080
- if (digitCount < minimumDigits) {
1081
- return digits;
1082
- }
1367
+ let source = "";
1368
+ while (lineIndex <= lineEndIndex) {
1369
+ const lineString = lineStrings[lineIndex];
1370
+ const lineNumber = lineIndex + 1;
1371
+ const isLastLine = lineIndex === lineEndIndex;
1372
+ const isMainLine = lineNumber === line;
1373
+ lineIndex++;
1083
1374
 
1084
- let digitsWithSeparator = digits.slice(-groupLength);
1085
- let remainingDigits = digits.slice(0, -groupLength);
1086
- while (remainingDigits.length) {
1087
- const group = remainingDigits.slice(-groupLength);
1088
- remainingDigits = remainingDigits.slice(0, -groupLength);
1089
- digitsWithSeparator = `${group}_${digitsWithSeparator}`;
1375
+ {
1376
+ if (lineMarker) {
1377
+ if (isMainLine) {
1378
+ source += `${format(">", "marker_line")} `;
1379
+ } else {
1380
+ source += " ";
1381
+ }
1382
+ }
1383
+ if (lineNumbersOnTheLeft) {
1384
+ // fill with spaces to ensure if line moves from 7,8,9 to 10 the display is still great
1385
+ const asideSource = `${fillLeft(lineNumber, lineEndIndex + 1)} |`;
1386
+ source += `${format(asideSource, "line_number_aside")} `;
1387
+ }
1388
+ }
1389
+ {
1390
+ source += truncateLine(lineString, {
1391
+ start: columnsBefore,
1392
+ end: columnsAfter,
1393
+ prefix: "…",
1394
+ suffix: "…",
1395
+ format,
1396
+ });
1397
+ }
1398
+ {
1399
+ if (columnMarker && isMainLine) {
1400
+ source += `\n`;
1401
+ if (lineMarker) {
1402
+ source += " ";
1403
+ }
1404
+ if (lineNumbersOnTheLeft) {
1405
+ const asideSpaces = `${fillLeft(lineNumber, lineEndIndex + 1)} | `
1406
+ .length;
1407
+ source += " ".repeat(asideSpaces);
1408
+ }
1409
+ source += " ".repeat(columnMarkerIndex);
1410
+ source += format("^", "marker_column");
1411
+ }
1412
+ }
1413
+ if (!isLastLine) {
1414
+ source += "\n";
1415
+ }
1090
1416
  }
1091
- return digitsWithSeparator;
1417
+ return source;
1092
1418
  };
1093
1419
 
1094
- // const addSeparatorFromLeft = (value, { minimumDigits, groupLength }) => {
1095
- // const { length } = value;
1096
- // if (length < minimumDigits) {
1097
- // return value;
1098
- // }
1099
-
1100
- // const parts = [];
1101
- // for (let start = 0; start < length; start += groupLength) {
1102
- // const end = Math.min(start + groupLength, length);
1103
- // parts.push(value.slice(start, end));
1104
- // }
1105
- // return parts.join("_");
1106
- // };
1107
-
1108
- const DOUBLE_QUOTE = `"`;
1109
- const SINGLE_QUOTE = `'`;
1110
- const BACKTICK = "`";
1420
+ const truncateLine = (line, { start, end, prefix, suffix, format }) => {
1421
+ const lastIndex = line.length;
1111
1422
 
1112
- const inspectString = (
1113
- value,
1114
- {
1115
- quote = "auto",
1116
- canUseTemplateString = false,
1117
- preserveLineBreaks = false,
1118
- quoteDefault = DOUBLE_QUOTE,
1119
- } = {},
1120
- ) => {
1121
- quote =
1122
- quote === "auto"
1123
- ? determineQuote(value, { canUseTemplateString, quoteDefault })
1124
- : quote;
1125
- if (quote === BACKTICK) {
1126
- return `\`${escapeTemplateStringSpecialCharacters(value)}\``;
1423
+ if (line.length === 0) {
1424
+ // don't show any ellipsis if the line is empty
1425
+ // because it's not truncated in that case
1426
+ return "";
1127
1427
  }
1128
- return surroundStringWith(value, { quote, preserveLineBreaks });
1129
- };
1130
1428
 
1131
- // https://github.com/mgenware/string-to-template-literal/blob/main/src/main.ts#L1
1132
- const escapeTemplateStringSpecialCharacters = (string) => {
1133
- string = String(string);
1134
- let i = 0;
1135
- let escapedString = "";
1136
- while (i < string.length) {
1137
- const char = string[i];
1138
- i++;
1139
- escapedString += isTemplateStringSpecialChar(char) ? `\\${char}` : char;
1140
- }
1141
- return escapedString;
1142
- };
1429
+ const startTruncated = start > 0;
1430
+ const endTruncated = lastIndex > end;
1143
1431
 
1144
- const isTemplateStringSpecialChar = (char) =>
1145
- templateStringSpecialChars.indexOf(char) > -1;
1146
- const templateStringSpecialChars = ["\\", "`", "$"];
1432
+ let from = startTruncated ? start + prefix.length : start;
1433
+ let to = endTruncated ? end - suffix.length : end;
1434
+ if (to > lastIndex) to = lastIndex;
1147
1435
 
1148
- const determineQuote = (
1149
- string,
1150
- { canUseTemplateString, quoteDefault = DOUBLE_QUOTE } = {},
1151
- ) => {
1152
- const containsDoubleQuote = string.includes(DOUBLE_QUOTE);
1153
- if (!containsDoubleQuote) {
1154
- return DOUBLE_QUOTE;
1155
- }
1156
- const containsSimpleQuote = string.includes(SINGLE_QUOTE);
1157
- if (!containsSimpleQuote) {
1158
- return SINGLE_QUOTE;
1436
+ if (start >= lastIndex || from === to) {
1437
+ return "";
1159
1438
  }
1160
- if (canUseTemplateString) {
1161
- const containsBackTick = string.includes(BACKTICK);
1162
- if (!containsBackTick) {
1163
- return BACKTICK;
1164
- }
1439
+ let result = "";
1440
+ while (from < to) {
1441
+ result += format(line[from], "char");
1442
+ from++;
1165
1443
  }
1166
- const doubleQuoteCount = string.split(DOUBLE_QUOTE).length - 1;
1167
- const singleQuoteCount = string.split(SINGLE_QUOTE).length - 1;
1168
- if (singleQuoteCount > doubleQuoteCount) {
1169
- return DOUBLE_QUOTE;
1444
+ if (result.length === 0) {
1445
+ return "";
1170
1446
  }
1171
- if (doubleQuoteCount > singleQuoteCount) {
1172
- return SINGLE_QUOTE;
1447
+ if (startTruncated && endTruncated) {
1448
+ return `${format(prefix, "marker_overflow_left")}${result}${format(
1449
+ suffix,
1450
+ "marker_overflow_right",
1451
+ )}`;
1173
1452
  }
1174
- return quoteDefault;
1175
- };
1176
-
1177
- const inspectChar = (char, { quote, preserveLineBreaks }) => {
1178
- const point = char.charCodeAt(0);
1179
- if (preserveLineBreaks && (char === "\n" || char === "\r")) {
1180
- return char;
1453
+ if (startTruncated) {
1454
+ return `${format(prefix, "marker_overflow_left")}${result}`;
1181
1455
  }
1182
- if (
1183
- char === quote ||
1184
- point === 92 ||
1185
- point < 32 ||
1186
- (point > 126 && point < 160) ||
1187
- // line separators
1188
- point === 8232 ||
1189
- point === 8233
1190
- ) {
1191
- const replacement =
1192
- char === quote
1193
- ? `\\${quote}`
1194
- : point === 8232
1195
- ? "\\u2028"
1196
- : point === 8233
1197
- ? "\\u2029"
1198
- : meta[point];
1199
- return replacement;
1456
+ if (endTruncated) {
1457
+ return `${result}${format(suffix, "marker_overflow_right")}`;
1200
1458
  }
1201
- return char;
1459
+ return result;
1202
1460
  };
1203
1461
 
1204
- // https://github.com/jsenv/jsenv-uneval/blob/6c97ef9d8f2e9425a66f2c88347e0a118d427f3a/src/internal/escapeString.js#L3
1205
- // https://github.com/jsenv/jsenv-inspect/blob/bb11de3adf262b68f71ed82b0a37d4528dd42229/src/internal/string.js#L3
1206
- // https://github.com/joliss/js-string-escape/blob/master/index.js
1207
- // http://javascript.crockford.com/remedial.html
1208
- const surroundStringWith = (string, { quote, preserveLineBreaks }) => {
1209
- let result = "";
1210
- let last = 0;
1211
- const lastIndex = string.length;
1212
- let i = 0;
1213
- while (i < lastIndex) {
1214
- const char = string[i];
1215
- const replacement = inspectChar(char, { quote, preserveLineBreaks });
1216
- if (char !== replacement) {
1217
- if (last === i) {
1218
- result += replacement;
1219
- } else {
1220
- result += `${string.slice(last, i)}${replacement}`;
1221
- }
1222
- last = i + 1;
1223
- }
1224
- i++;
1225
- }
1226
- if (last !== lastIndex) {
1227
- result += string.slice(last);
1462
+ const fillLeft = (value, biggestValue, char = " ") => {
1463
+ const width = String(value).length;
1464
+ const biggestWidth = String(biggestValue).length;
1465
+ let missingWidth = biggestWidth - width;
1466
+ let padded = "";
1467
+ while (missingWidth--) {
1468
+ padded += char;
1228
1469
  }
1229
- return `${quote}${result}${quote}`;
1470
+ padded += value;
1471
+ return padded;
1230
1472
  };
1231
1473
 
1232
- // prettier-ignore
1233
- const meta = [
1234
- '\\x00', '\\x01', '\\x02', '\\x03', '\\x04', '\\x05', '\\x06', '\\x07', // x07
1235
- '\\b', '\\t', '\\n', '\\x0B', '\\f', '\\r', '\\x0E', '\\x0F', // x0F
1236
- '\\x10', '\\x11', '\\x12', '\\x13', '\\x14', '\\x15', '\\x16', '\\x17', // x17
1237
- '\\x18', '\\x19', '\\x1A', '\\x1B', '\\x1C', '\\x1D', '\\x1E', '\\x1F', // x1F
1238
- '', '', '', '', '', '', '', "\\'", '', '', '', '', '', '', '', '', // x2F
1239
- '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', // x3F
1240
- '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', // x4F
1241
- '', '', '', '', '', '', '', '', '', '', '', '', '\\\\', '', '', '', // x5F
1242
- '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', // x6F
1243
- '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '\\x7F', // x7F
1244
- '\\x80', '\\x81', '\\x82', '\\x83', '\\x84', '\\x85', '\\x86', '\\x87', // x87
1245
- '\\x88', '\\x89', '\\x8A', '\\x8B', '\\x8C', '\\x8D', '\\x8E', '\\x8F', // x8F
1246
- '\\x90', '\\x91', '\\x92', '\\x93', '\\x94', '\\x95', '\\x96', '\\x97', // x97
1247
- '\\x98', '\\x99', '\\x9A', '\\x9B', '\\x9C', '\\x9D', '\\x9E', '\\x9F', // x9F
1248
- ];
1249
-
1250
- const inspectSymbol = (value, { nestedHumanize, parenthesis }) => {
1251
- const symbolDescription = symbolToDescription(value);
1252
- const symbolDescriptionSource = symbolDescription
1253
- ? nestedHumanize(symbolDescription)
1254
- : "";
1255
- const symbolSource = `Symbol(${symbolDescriptionSource})`;
1474
+ const LOG_LEVEL_OFF = "off";
1256
1475
 
1257
- if (parenthesis) return `${symbolSource}`;
1258
- return symbolSource;
1259
- };
1476
+ const LOG_LEVEL_DEBUG = "debug";
1260
1477
 
1261
- const symbolToDescription =
1262
- "description" in Symbol.prototype
1263
- ? (symbol) => symbol.description
1264
- : (symbol) => {
1265
- const toStringResult = symbol.toString();
1266
- const openingParenthesisIndex = toStringResult.indexOf("(");
1267
- const closingParenthesisIndex = toStringResult.indexOf(")");
1268
- const symbolDescription = toStringResult.slice(
1269
- openingParenthesisIndex + 1,
1270
- closingParenthesisIndex,
1271
- );
1272
- return symbolDescription;
1273
- };
1478
+ const LOG_LEVEL_INFO = "info";
1274
1479
 
1275
- const inspectUndefined = () => "undefined";
1480
+ const LOG_LEVEL_WARN = "warn";
1276
1481
 
1277
- const inspectBigInt = (value) => {
1278
- return `${value}n`;
1279
- };
1482
+ const LOG_LEVEL_ERROR = "error";
1280
1483
 
1281
- const preNewLineAndIndentation = (
1282
- value,
1283
- { depth, indentUsingTab, indentSize },
1284
- ) => {
1285
- return `${newLineAndIndent({
1286
- count: depth + 1,
1287
- useTabs: indentUsingTab,
1288
- size: indentSize,
1289
- })}${value}`;
1290
- };
1291
-
1292
- const postNewLineAndIndentation = ({ depth, indentUsingTab, indentSize }) => {
1293
- return newLineAndIndent({
1294
- count: depth,
1295
- useTabs: indentUsingTab,
1296
- size: indentSize,
1297
- });
1298
- };
1299
-
1300
- const newLineAndIndent = ({ count, useTabs, size }) => {
1301
- if (useTabs) {
1302
- // eslint-disable-next-line prefer-template
1303
- return "\n" + "\t".repeat(count);
1484
+ const createLogger = ({ logLevel = LOG_LEVEL_INFO } = {}) => {
1485
+ if (logLevel === LOG_LEVEL_DEBUG) {
1486
+ return {
1487
+ level: "debug",
1488
+ levels: { debug: true, info: true, warn: true, error: true },
1489
+ debug,
1490
+ info,
1491
+ warn,
1492
+ error,
1493
+ };
1304
1494
  }
1305
- // eslint-disable-next-line prefer-template
1306
- return "\n" + " ".repeat(count * size);
1307
- };
1308
-
1309
- const wrapNewLineAndIndentation = (
1310
- value,
1311
- { depth, indentUsingTab, indentSize },
1312
- ) => {
1313
- return `${preNewLineAndIndentation(value, {
1314
- depth,
1315
- indentUsingTab,
1316
- indentSize,
1317
- })}${postNewLineAndIndentation({ depth, indentUsingTab, indentSize })}`;
1318
- };
1319
-
1320
- const inspectConstructor = (value, { parenthesis, useNew }) => {
1321
- let formattedString = value;
1322
- if (parenthesis) {
1323
- formattedString = `(${value})`;
1495
+ if (logLevel === LOG_LEVEL_INFO) {
1496
+ return {
1497
+ level: "info",
1498
+ levels: { debug: false, info: true, warn: true, error: true },
1499
+ debug: debugDisabled,
1500
+ info,
1501
+ warn,
1502
+ error,
1503
+ };
1324
1504
  }
1325
- if (useNew) {
1326
- formattedString = `new ${formattedString}`;
1505
+ if (logLevel === LOG_LEVEL_WARN) {
1506
+ return {
1507
+ level: "warn",
1508
+ levels: { debug: false, info: false, warn: true, error: true },
1509
+ debug: debugDisabled,
1510
+ info: infoDisabled,
1511
+ warn,
1512
+ error,
1513
+ };
1327
1514
  }
1328
- return formattedString;
1515
+ if (logLevel === LOG_LEVEL_ERROR) {
1516
+ return {
1517
+ level: "error",
1518
+ levels: { debug: false, info: false, warn: false, error: true },
1519
+ debug: debugDisabled,
1520
+ info: infoDisabled,
1521
+ warn: warnDisabled,
1522
+ error,
1523
+ };
1524
+ }
1525
+ if (logLevel === LOG_LEVEL_OFF) {
1526
+ return {
1527
+ level: "off",
1528
+ levels: { debug: false, info: false, warn: false, error: false },
1529
+ debug: debugDisabled,
1530
+ info: infoDisabled,
1531
+ warn: warnDisabled,
1532
+ error: errorDisabled,
1533
+ };
1534
+ }
1535
+ throw new Error(`unexpected logLevel.
1536
+ --- logLevel ---
1537
+ ${logLevel}
1538
+ --- allowed log levels ---
1539
+ ${LOG_LEVEL_OFF}
1540
+ ${LOG_LEVEL_ERROR}
1541
+ ${LOG_LEVEL_WARN}
1542
+ ${LOG_LEVEL_INFO}
1543
+ ${LOG_LEVEL_DEBUG}`);
1329
1544
  };
1330
1545
 
1331
- const inspectArray = (
1332
- value,
1333
- {
1334
- seen = [],
1335
- nestedHumanize,
1336
- depth,
1337
- indentUsingTab,
1338
- indentSize,
1339
- parenthesis,
1340
- useNew,
1341
- },
1342
- ) => {
1343
- if (seen.indexOf(value) > -1) {
1344
- return "Symbol.for('circular')";
1345
- }
1346
- seen.push(value);
1546
+ const debug = (...args) => console.debug(...args);
1347
1547
 
1348
- let valuesSource = "";
1349
- let i = 0;
1350
- const j = value.length;
1548
+ const debugDisabled = () => {};
1351
1549
 
1352
- while (i < j) {
1353
- const valueSource = value.hasOwnProperty(i)
1354
- ? nestedHumanize(value[i], { seen })
1355
- : "";
1356
- if (i === 0) {
1357
- valuesSource += valueSource;
1358
- } else {
1359
- valuesSource += `,${preNewLineAndIndentation(valueSource, {
1360
- depth,
1361
- indentUsingTab,
1362
- indentSize,
1363
- })}`;
1364
- }
1365
- i++;
1366
- }
1550
+ const info = (...args) => console.info(...args);
1367
1551
 
1368
- let arraySource;
1369
- if (valuesSource.length) {
1370
- arraySource = wrapNewLineAndIndentation(valuesSource, {
1371
- depth,
1372
- indentUsingTab,
1373
- indentSize,
1374
- });
1375
- } else {
1376
- arraySource = "";
1377
- }
1552
+ const infoDisabled = () => {};
1378
1553
 
1379
- arraySource = `[${arraySource}]`;
1554
+ const warn = (...args) => console.warn(...args);
1380
1555
 
1381
- return inspectConstructor(arraySource, { parenthesis, useNew });
1382
- };
1556
+ const warnDisabled = () => {};
1383
1557
 
1384
- const inspectBigIntObject = (value, { nestedHumanize }) => {
1385
- const bigIntSource = nestedHumanize(value.valueOf());
1558
+ const error = (...args) => console.error(...args);
1386
1559
 
1387
- return `BigInt(${bigIntSource})`;
1388
- };
1560
+ const errorDisabled = () => {};
1389
1561
 
1390
- const inspectBooleanObject = (
1391
- value,
1392
- { nestedHumanize, useNew, parenthesis },
1393
- ) => {
1394
- const booleanSource = nestedHumanize(value.valueOf());
1395
- return inspectConstructor(`Boolean(${booleanSource})`, {
1396
- useNew,
1397
- parenthesis,
1398
- });
1399
- };
1562
+ /* globals WorkerGlobalScope, DedicatedWorkerGlobalScope, SharedWorkerGlobalScope, ServiceWorkerGlobalScope */
1400
1563
 
1401
- const inspectError = (
1402
- error,
1403
- { nestedHumanize, useNew, parenthesis },
1404
- ) => {
1405
- const messageSource = nestedHumanize(error.message);
1564
+ const isBrowser = globalThis.window?.document !== undefined;
1406
1565
 
1407
- const errorSource = inspectConstructor(
1408
- `${errorToConstructorName(error)}(${messageSource})`,
1409
- {
1410
- useNew,
1411
- parenthesis,
1412
- },
1413
- );
1414
- return errorSource;
1415
- };
1566
+ globalThis.process?.versions?.node !== undefined;
1416
1567
 
1417
- const errorToConstructorName = ({ name }) => {
1418
- if (derivedErrorNameArray.includes(name)) {
1419
- return name;
1420
- }
1421
- return "Error";
1422
- };
1568
+ globalThis.process?.versions?.bun !== undefined;
1423
1569
 
1424
- // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error#Error_types
1425
- const derivedErrorNameArray = [
1426
- "EvalError",
1427
- "RangeError",
1428
- "ReferenceError",
1429
- "SyntaxError",
1430
- "TypeError",
1431
- "URIError",
1432
- ];
1570
+ globalThis.Deno?.version?.deno !== undefined;
1433
1571
 
1434
- const inspectDate = (value, { nestedHumanize, useNew, parenthesis }) => {
1435
- const dateSource = nestedHumanize(value.valueOf(), {
1436
- numericSeparator: false,
1437
- });
1438
- return inspectConstructor(`Date(${dateSource})`, { useNew, parenthesis });
1439
- };
1572
+ globalThis.process?.versions?.electron !== undefined;
1440
1573
 
1441
- const inspectFunction = (
1442
- value,
1443
- { showFunctionBody, parenthesis, depth },
1444
- ) => {
1445
- let functionSource;
1446
- if (showFunctionBody) {
1447
- functionSource = value.toString();
1448
- } else {
1449
- const isArrowFunction = value.prototype === undefined;
1450
- const head = isArrowFunction
1451
- ? "() =>"
1452
- : `function ${depth === 0 ? value.name : ""}()`;
1453
- functionSource = `${head} {/* hidden */}`;
1454
- }
1574
+ globalThis.navigator?.userAgent?.includes('jsdom') === true;
1455
1575
 
1456
- if (parenthesis) {
1457
- return `(${functionSource})`;
1458
- }
1459
- return functionSource;
1460
- };
1576
+ typeof WorkerGlobalScope !== 'undefined' && globalThis instanceof WorkerGlobalScope;
1461
1577
 
1462
- const inspectNumberObject = (
1463
- value,
1464
- { nestedHumanize, useNew, parenthesis },
1465
- ) => {
1466
- const numberSource = nestedHumanize(value.valueOf());
1467
- return inspectConstructor(`Number(${numberSource})`, { useNew, parenthesis });
1468
- };
1578
+ typeof DedicatedWorkerGlobalScope !== 'undefined' && globalThis instanceof DedicatedWorkerGlobalScope;
1469
1579
 
1470
- const inspectObject = (
1471
- value,
1472
- {
1473
- nestedHumanize,
1474
- seen = [],
1475
- depth,
1476
- indentUsingTab,
1477
- indentSize,
1478
- objectConstructor,
1479
- parenthesis,
1480
- useNew,
1481
- },
1482
- ) => {
1483
- if (seen.indexOf(value) > -1) return "Symbol.for('circular')";
1580
+ typeof SharedWorkerGlobalScope !== 'undefined' && globalThis instanceof SharedWorkerGlobalScope;
1484
1581
 
1485
- seen.push(value);
1582
+ typeof ServiceWorkerGlobalScope !== 'undefined' && globalThis instanceof ServiceWorkerGlobalScope;
1486
1583
 
1487
- const propertySourceArray = [];
1488
- Object.getOwnPropertyNames(value).forEach((propertyName) => {
1489
- const propertyNameAsNumber = parseInt(propertyName, 10);
1490
- const propertyNameSource = nestedHumanize(
1491
- Number.isInteger(propertyNameAsNumber)
1492
- ? propertyNameAsNumber
1493
- : propertyName,
1494
- );
1495
- propertySourceArray.push({
1496
- nameOrSymbolSource: propertyNameSource,
1497
- valueSource: nestedHumanize(value[propertyName], { seen }),
1498
- });
1499
- });
1500
- Object.getOwnPropertySymbols(value).forEach((symbol) => {
1501
- propertySourceArray.push({
1502
- nameOrSymbolSource: `[${nestedHumanize(symbol)}]`,
1503
- valueSource: nestedHumanize(value[symbol], { seen }),
1504
- });
1505
- });
1584
+ // Note: I'm intentionally not DRYing up the other variables to keep them "lazy".
1585
+ const platform = globalThis.navigator?.userAgentData?.platform;
1506
1586
 
1507
- let propertiesSource = "";
1508
- propertySourceArray.forEach(({ nameOrSymbolSource, valueSource }, index) => {
1509
- if (index === 0) {
1510
- propertiesSource += `${nameOrSymbolSource}: ${valueSource}`;
1511
- } else {
1512
- propertiesSource += `,${preNewLineAndIndentation(
1513
- `${nameOrSymbolSource}: ${valueSource}`,
1514
- {
1515
- depth,
1516
- indentUsingTab,
1517
- indentSize,
1518
- },
1519
- )}`;
1520
- }
1521
- });
1587
+ platform === 'macOS'
1588
+ || globalThis.navigator?.platform === 'MacIntel' // Even on Apple silicon Macs.
1589
+ || globalThis.navigator?.userAgent?.includes(' Mac ') === true
1590
+ || globalThis.process?.platform === 'darwin';
1522
1591
 
1523
- let objectSource;
1524
- if (propertiesSource.length) {
1525
- objectSource = `${wrapNewLineAndIndentation(propertiesSource, {
1526
- depth,
1527
- indentUsingTab,
1528
- indentSize,
1529
- })}`;
1530
- } else {
1531
- objectSource = "";
1532
- }
1592
+ platform === 'Windows'
1593
+ || globalThis.navigator?.platform === 'Win32'
1594
+ || globalThis.process?.platform === 'win32';
1533
1595
 
1534
- if (objectConstructor) {
1535
- objectSource = `Object({${objectSource}})`;
1536
- } else {
1537
- objectSource = `{${objectSource}}`;
1538
- }
1596
+ platform === 'Linux'
1597
+ || globalThis.navigator?.platform?.startsWith('Linux') === true
1598
+ || globalThis.navigator?.userAgent?.includes(' Linux ') === true
1599
+ || globalThis.process?.platform === 'linux';
1539
1600
 
1540
- return inspectConstructor(objectSource, { parenthesis, useNew });
1541
- };
1601
+ platform === 'Android'
1602
+ || globalThis.navigator?.platform === 'Android'
1603
+ || globalThis.navigator?.userAgent?.includes(' Android ') === true
1604
+ || globalThis.process?.platform === 'android';
1542
1605
 
1543
- const inspectRegExp = (value) => value.toString();
1606
+ const ESC = '\u001B[';
1544
1607
 
1545
- const inspectStringObject = (
1546
- value,
1547
- { nestedHumanize, useNew, parenthesis },
1548
- ) => {
1549
- const stringSource = nestedHumanize(value.valueOf());
1608
+ !isBrowser && process$1.env.TERM_PROGRAM === 'Apple_Terminal';
1609
+ const isWindows = !isBrowser && process$1.platform === 'win32';
1550
1610
 
1551
- return inspectConstructor(`String(${stringSource})`, { useNew, parenthesis });
1611
+ isBrowser ? () => {
1612
+ throw new Error('`process.cwd()` only works in Node.js, not the browser.');
1613
+ } : process$1.cwd;
1614
+
1615
+ const cursorUp = (count = 1) => ESC + count + 'A';
1616
+
1617
+ const cursorLeft = ESC + 'G';
1618
+
1619
+ const eraseLines = count => {
1620
+ let clear = '';
1621
+
1622
+ for (let i = 0; i < count; i++) {
1623
+ clear += eraseLine + (i < count - 1 ? cursorUp() : '');
1624
+ }
1625
+
1626
+ if (count) {
1627
+ clear += cursorLeft;
1628
+ }
1629
+
1630
+ return clear;
1552
1631
  };
1632
+ const eraseLine = ESC + '2K';
1633
+ const eraseScreen = ESC + '2J';
1553
1634
 
1554
- // primitives
1635
+ const clearTerminal = isWindows
1636
+ ? `${eraseScreen}${ESC}0f`
1637
+ // 1. Erases the screen (Only done in case `2` is not supported)
1638
+ // 2. Erases the whole screen including scrollback buffer
1639
+ // 3. Moves cursor to the top-left position
1640
+ // More info: https://www.real-world-systems.com/docs/ANSIcode.html
1641
+ : `${eraseScreen}${ESC}3J${ESC}H`;
1555
1642
 
1556
- const humanize = (
1557
- value,
1558
- {
1559
- parenthesis = false,
1560
- quote = "auto",
1561
- canUseTemplateString = true,
1562
- useNew = false,
1563
- objectConstructor = false,
1564
- showFunctionBody = false,
1565
- indentUsingTab = false,
1566
- indentSize = 2,
1567
- numericSeparator = true,
1568
- preserveLineBreaks = false,
1569
- } = {},
1570
- ) => {
1571
- const scopedHumanize = (scopedValue, scopedOptions) => {
1572
- const options = {
1573
- ...scopedOptions,
1574
- nestedHumanize: (nestedValue, nestedOptions = {}) => {
1575
- return scopedHumanize(nestedValue, {
1576
- ...scopedOptions,
1577
- depth: scopedOptions.depth + 1,
1578
- ...nestedOptions,
1579
- });
1580
- },
1581
- };
1582
- return humanizeValue(scopedValue, options);
1643
+ /*
1644
+ * see also https://github.com/vadimdemedes/ink
1645
+ */
1646
+
1647
+
1648
+ const createDynamicLog = ({
1649
+ stream = process.stdout,
1650
+ clearTerminalAllowed,
1651
+ onVerticalOverflow = () => {},
1652
+ onWriteFromOutside = () => {},
1653
+ } = {}) => {
1654
+ const { columns = 80, rows = 24 } = stream;
1655
+ const dynamicLog = {
1656
+ destroyed: false,
1657
+ onVerticalOverflow,
1658
+ onWriteFromOutside,
1583
1659
  };
1584
1660
 
1585
- return scopedHumanize(value, {
1586
- parenthesis,
1587
- quote,
1588
- canUseTemplateString,
1589
- useNew,
1590
- objectConstructor,
1591
- showFunctionBody,
1592
- indentUsingTab,
1593
- indentSize,
1594
- numericSeparator,
1595
- preserveLineBreaks,
1596
- depth: 0,
1597
- });
1598
- };
1661
+ let lastOutput = "";
1662
+ let lastOutputFromOutside = "";
1663
+ let clearAttemptResult;
1664
+ let writing = false;
1599
1665
 
1600
- const humanizeMethodSymbol = Symbol.for("inspect");
1666
+ const getErasePreviousOutput = () => {
1667
+ // nothing to clear
1668
+ if (!lastOutput) {
1669
+ return "";
1670
+ }
1671
+ if (clearAttemptResult !== undefined) {
1672
+ return "";
1673
+ }
1601
1674
 
1602
- const humanizeValue = (value, options) => {
1603
- const customHumanize = value && value[humanizeMethodSymbol];
1604
- if (customHumanize) {
1605
- return customHumanize(options);
1606
- }
1607
- const primitiveType = primitiveTypeFromValue(value);
1608
- const primitiveStringifier = primitiveStringifiers[primitiveType];
1609
- if (primitiveStringifier) {
1610
- return primitiveStringifier(value, options);
1611
- }
1612
- const compositeType = compositeTypeFromObject(value);
1613
- const compositeStringifier = compositeStringifiers[compositeType];
1614
- if (compositeStringifier) {
1615
- return compositeStringifier(value, options);
1616
- }
1617
- return inspectConstructor(
1618
- `${compositeType}(${inspectObject(value, options)})`,
1619
- {
1620
- ...options,
1621
- parenthesis: false,
1622
- },
1623
- );
1624
- };
1675
+ const logLines = lastOutput.split(/\r\n|\r|\n/);
1676
+ let visualLineCount = 0;
1677
+ for (const logLine of logLines) {
1678
+ const width = stringWidth(logLine);
1679
+ if (width === 0) {
1680
+ visualLineCount++;
1681
+ } else {
1682
+ visualLineCount += Math.ceil(width / columns);
1683
+ }
1684
+ }
1625
1685
 
1626
- const primitiveTypeFromValue = (value) => {
1627
- if (value === null) {
1628
- return "null";
1629
- }
1630
- if (value === undefined) {
1631
- return "undefined";
1632
- }
1633
- return typeof value;
1634
- };
1635
- const primitiveStringifiers = {
1636
- boolean: inspectBoolean,
1637
- function: inspectFunction,
1638
- null: inspectNull,
1639
- number: inspectNumber,
1640
- string: inspectString,
1641
- symbol: inspectSymbol,
1642
- undefined: inspectUndefined,
1643
- bigint: inspectBigInt,
1644
- };
1645
- const compositeTypeFromObject = (object) => {
1646
- if (typeof object === "object" && Object.getPrototypeOf(object) === null) {
1647
- return "Object";
1648
- }
1649
- const toStringResult = toString.call(object);
1650
- // returns format is '[object ${tagName}]';
1651
- // and we want ${tagName}
1652
- const tagName = toStringResult.slice("[object ".length, -1);
1653
- if (tagName === "Object") {
1654
- const objectConstructorName = object.constructor.name;
1655
- if (objectConstructorName !== "Object") {
1656
- return objectConstructorName;
1686
+ if (visualLineCount > rows) {
1687
+ if (clearTerminalAllowed) {
1688
+ clearAttemptResult = true;
1689
+ return clearTerminal;
1690
+ }
1691
+ // the whole log cannot be cleared because it's vertically to long
1692
+ // (longer than terminal height)
1693
+ // readline.moveCursor cannot move cursor higher than screen height
1694
+ // it means we would only clear the visible part of the log
1695
+ // better keep the log untouched
1696
+ clearAttemptResult = false;
1697
+ dynamicLog.onVerticalOverflow();
1698
+ return "";
1657
1699
  }
1658
- }
1659
- return tagName;
1660
- };
1661
- const { toString } = Object.prototype;
1662
1700
 
1663
- const compositeStringifiers = {
1664
- Array: inspectArray,
1665
- BigInt: inspectBigIntObject,
1666
- Boolean: inspectBooleanObject,
1667
- Error: inspectError,
1668
- Date: inspectDate,
1669
- Function: inspectFunction,
1670
- Number: inspectNumberObject,
1671
- Object: inspectObject,
1672
- RegExp: inspectRegExp,
1673
- String: inspectStringObject,
1674
- };
1701
+ clearAttemptResult = true;
1702
+ return eraseLines(visualLineCount);
1703
+ };
1675
1704
 
1676
- const humanizeFileSize = (numberOfBytes, { decimals, short } = {}) => {
1677
- return inspectBytes(numberOfBytes, { decimals, short });
1678
- };
1705
+ const update = (string) => {
1706
+ if (dynamicLog.destroyed) {
1707
+ throw new Error("Cannot write log after destroy");
1708
+ }
1709
+ let stringToWrite = string;
1710
+ if (lastOutput) {
1711
+ if (lastOutputFromOutside) {
1712
+ // We don't want to clear logs written by other code,
1713
+ // it makes output unreadable and might erase precious information
1714
+ // To detect this we put a spy on the stream.
1715
+ // The spy is required only if we actually wrote something in the stream
1716
+ // something else than this code has written in the stream
1717
+ // so we just write without clearing (append instead of replacing)
1718
+ lastOutput = "";
1719
+ lastOutputFromOutside = "";
1720
+ } else {
1721
+ stringToWrite = `${getErasePreviousOutput()}${string}`;
1722
+ }
1723
+ }
1724
+ writing = true;
1725
+ stream.write(stringToWrite);
1726
+ lastOutput = string;
1727
+ writing = false;
1728
+ clearAttemptResult = undefined;
1729
+ };
1679
1730
 
1680
- const humanizeMemory = (metricValue, { decimals, short } = {}) => {
1681
- return inspectBytes(metricValue, { decimals, fixedDecimals: true, short });
1682
- };
1731
+ const clearDuringFunctionCall = (
1732
+ callback,
1733
+ ouputAfterCallback = lastOutput,
1734
+ ) => {
1735
+ // 1. Erase the current log
1736
+ // 2. Call callback (expect to write something on stdout)
1737
+ // 3. Restore the current log
1738
+ // During step 2. we expect a "write from outside" so we uninstall
1739
+ // the stream spy during function call
1740
+ update("");
1683
1741
 
1684
- const inspectBytes = (
1685
- number,
1686
- { fixedDecimals = false, decimals, short } = {},
1687
- ) => {
1688
- if (number === 0) {
1689
- return `0 B`;
1690
- }
1691
- const exponent = Math.min(
1692
- Math.floor(Math.log10(number) / 3),
1693
- BYTE_UNITS.length - 1,
1694
- );
1695
- const unitNumber = number / Math.pow(1000, exponent);
1696
- const unitName = BYTE_UNITS[exponent];
1697
- if (decimals === undefined) {
1698
- if (unitNumber < 100) {
1699
- decimals = 1;
1700
- } else {
1701
- decimals = 0;
1742
+ writing = true;
1743
+ callback();
1744
+ writing = false;
1745
+
1746
+ update(ouputAfterCallback);
1747
+ };
1748
+
1749
+ const writeFromOutsideEffect = (value) => {
1750
+ if (!lastOutput) {
1751
+ // we don't care if the log never wrote anything
1752
+ // or if last update() wrote an empty string
1753
+ return;
1702
1754
  }
1755
+ if (writing) {
1756
+ return;
1757
+ }
1758
+ lastOutputFromOutside = value;
1759
+ dynamicLog.onWriteFromOutside(value);
1760
+ };
1761
+
1762
+ let removeStreamSpy;
1763
+ if (stream === process.stdout) {
1764
+ const removeStdoutSpy = spyStreamOutput(
1765
+ process.stdout,
1766
+ writeFromOutsideEffect,
1767
+ );
1768
+ const removeStderrSpy = spyStreamOutput(
1769
+ process.stderr,
1770
+ writeFromOutsideEffect,
1771
+ );
1772
+ removeStreamSpy = () => {
1773
+ removeStdoutSpy();
1774
+ removeStderrSpy();
1775
+ };
1776
+ } else {
1777
+ removeStreamSpy = spyStreamOutput(stream, writeFromOutsideEffect);
1703
1778
  }
1704
- const unitNumberRounded = setRoundedPrecision(unitNumber, {
1705
- decimals,
1706
- decimalsWhenSmall: 1,
1779
+
1780
+ const destroy = () => {
1781
+ dynamicLog.destroyed = true;
1782
+ if (removeStreamSpy) {
1783
+ removeStreamSpy();
1784
+ removeStreamSpy = null;
1785
+ lastOutput = "";
1786
+ lastOutputFromOutside = "";
1787
+ }
1788
+ };
1789
+
1790
+ Object.assign(dynamicLog, {
1791
+ update,
1792
+ destroy,
1793
+ stream,
1794
+ clearDuringFunctionCall,
1707
1795
  });
1708
- const value = fixedDecimals
1709
- ? unitNumberRounded.toFixed(decimals)
1710
- : unitNumberRounded;
1711
- if (short) {
1712
- return `${value}${unitName}`;
1713
- }
1714
- return `${value} ${unitName}`;
1796
+ return dynamicLog;
1715
1797
  };
1716
1798
 
1717
- const BYTE_UNITS = ["B", "kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"];
1799
+ // maybe https://github.com/gajus/output-interceptor/tree/v3.0.0 ?
1800
+ // the problem with listening data on stdout
1801
+ // is that node.js will later throw error if stream gets closed
1802
+ // while something listening data on it
1803
+ const spyStreamOutput = (stream, callback) => {
1804
+ const originalWrite = stream.write;
1718
1805
 
1719
- const distributePercentages = (
1720
- namedNumbers,
1721
- { maxPrecisionHint = 2 } = {},
1722
- ) => {
1723
- const numberNames = Object.keys(namedNumbers);
1724
- if (numberNames.length === 0) {
1725
- return {};
1726
- }
1727
- if (numberNames.length === 1) {
1728
- const firstNumberName = numberNames[0];
1729
- return { [firstNumberName]: "100 %" };
1730
- }
1731
- const numbers = numberNames.map((name) => namedNumbers[name]);
1732
- const total = numbers.reduce((sum, value) => sum + value, 0);
1733
- const ratios = numbers.map((number) => number / total);
1734
- const percentages = {};
1735
- ratios.pop();
1736
- ratios.forEach((ratio, index) => {
1737
- const percentage = ratio * 100;
1738
- percentages[numberNames[index]] = percentage;
1739
- });
1740
- const lowestPercentage = (1 / Math.pow(10, maxPrecisionHint)) * 100;
1741
- let precision = 0;
1742
- Object.keys(percentages).forEach((name) => {
1743
- const percentage = percentages[name];
1744
- if (percentage < lowestPercentage) {
1745
- // check the amout of meaningful decimals
1746
- // and that what we will use
1747
- const percentageRounded = setRoundedPrecision(percentage);
1748
- const percentagePrecision = getPrecision(percentageRounded);
1749
- if (percentagePrecision > precision) {
1750
- precision = percentagePrecision;
1751
- }
1752
- }
1753
- });
1754
- let remainingPercentage = 100;
1806
+ let output = "";
1807
+ let installed = true;
1808
+
1809
+ stream.write = function (...args /* chunk, encoding, callback */) {
1810
+ output += args;
1811
+ callback(output);
1812
+ return originalWrite.call(stream, ...args);
1813
+ };
1814
+
1815
+ const uninstall = () => {
1816
+ if (!installed) {
1817
+ return;
1818
+ }
1819
+ stream.write = originalWrite;
1820
+ installed = false;
1821
+ };
1755
1822
 
1756
- Object.keys(percentages).forEach((name) => {
1757
- const percentage = percentages[name];
1758
- const percentageAllocated = setRoundedPrecision(percentage, {
1759
- decimals: precision,
1760
- });
1761
- remainingPercentage -= percentageAllocated;
1762
- percentages[name] = percentageAllocated;
1763
- });
1764
- const lastName = numberNames[numberNames.length - 1];
1765
- percentages[lastName] = setRoundedPrecision(remainingPercentage, {
1766
- decimals: precision,
1767
- });
1768
- return percentages;
1823
+ return () => {
1824
+ uninstall();
1825
+ return output;
1826
+ };
1769
1827
  };
1770
1828
 
1771
- const formatDefault = (v) => v;
1829
+ const startSpinner = ({
1830
+ dynamicLog,
1831
+ frames = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"],
1832
+ fps = 20,
1833
+ keepProcessAlive = false,
1834
+ stopOnWriteFromOutside = true,
1835
+ stopOnVerticalOverflow = true,
1836
+ render = () => "",
1837
+ effect = () => {},
1838
+ animated = dynamicLog.stream.isTTY,
1839
+ }) => {
1840
+ let frameIndex = 0;
1841
+ let interval;
1842
+ let running = true;
1772
1843
 
1773
- const generateContentFrame = ({
1774
- content,
1775
- line,
1776
- column,
1844
+ const spinner = {
1845
+ message: undefined,
1846
+ };
1777
1847
 
1778
- linesAbove = 3,
1779
- linesBelow = 0,
1780
- lineMaxWidth = 120,
1781
- lineNumbersOnTheLeft = true,
1782
- lineMarker = true,
1783
- columnMarker = true,
1784
- format = formatDefault,
1785
- } = {}) => {
1786
- const lineStrings = content.split(/\r?\n/);
1787
- if (line === 0) line = 1;
1788
- if (column === undefined) {
1789
- columnMarker = false;
1790
- column = 1;
1791
- }
1792
- if (column === 0) column = 1;
1848
+ const update = (message) => {
1849
+ spinner.message = running
1850
+ ? `${frames[frameIndex]} ${message}\n`
1851
+ : `${message}\n`;
1852
+ return spinner.message;
1853
+ };
1854
+ spinner.update = update;
1793
1855
 
1794
- let lineStartIndex = line - 1 - linesAbove;
1795
- if (lineStartIndex < 0) {
1796
- lineStartIndex = 0;
1797
- }
1798
- let lineEndIndex = line - 1 + linesBelow;
1799
- if (lineEndIndex > lineStrings.length - 1) {
1800
- lineEndIndex = lineStrings.length - 1;
1801
- }
1802
- if (columnMarker) {
1803
- // human reader deduce the line when there is a column marker
1804
- lineMarker = false;
1805
- }
1806
- if (line - 1 === lineEndIndex) {
1807
- lineMarker = false; // useless because last line
1808
- }
1809
- let lineIndex = lineStartIndex;
1856
+ let cleanup;
1857
+ if (animated && ANSI.supported) {
1858
+ running = true;
1859
+ cleanup = effect();
1860
+ dynamicLog.update(update(render()));
1810
1861
 
1811
- let columnsBefore;
1812
- let columnsAfter;
1813
- if (column > lineMaxWidth) {
1814
- columnsBefore = column - Math.ceil(lineMaxWidth / 2);
1815
- columnsAfter = column + Math.floor(lineMaxWidth / 2);
1862
+ interval = setInterval(() => {
1863
+ frameIndex = frameIndex === frames.length - 1 ? 0 : frameIndex + 1;
1864
+ dynamicLog.update(update(render()));
1865
+ }, 1000 / fps);
1866
+ if (!keepProcessAlive) {
1867
+ interval.unref();
1868
+ }
1816
1869
  } else {
1817
- columnsBefore = 0;
1818
- columnsAfter = lineMaxWidth;
1870
+ dynamicLog.update(update(render()));
1819
1871
  }
1820
- let columnMarkerIndex = column - 1 - columnsBefore;
1821
-
1822
- let source = "";
1823
- while (lineIndex <= lineEndIndex) {
1824
- const lineString = lineStrings[lineIndex];
1825
- const lineNumber = lineIndex + 1;
1826
- const isLastLine = lineIndex === lineEndIndex;
1827
- const isMainLine = lineNumber === line;
1828
- lineIndex++;
1829
1872
 
1830
- {
1831
- if (lineMarker) {
1832
- if (isMainLine) {
1833
- source += `${format(">", "marker_line")} `;
1834
- } else {
1835
- source += " ";
1836
- }
1837
- }
1838
- if (lineNumbersOnTheLeft) {
1839
- // fill with spaces to ensure if line moves from 7,8,9 to 10 the display is still great
1840
- const asideSource = `${fillLeft(lineNumber, lineEndIndex + 1)} |`;
1841
- source += `${format(asideSource, "line_number_aside")} `;
1842
- }
1843
- }
1844
- {
1845
- source += truncateLine(lineString, {
1846
- start: columnsBefore,
1847
- end: columnsAfter,
1848
- prefix: "…",
1849
- suffix: "…",
1850
- format,
1851
- });
1873
+ const stop = (message) => {
1874
+ running = false;
1875
+ if (interval) {
1876
+ clearInterval(interval);
1877
+ interval = null;
1852
1878
  }
1853
- {
1854
- if (columnMarker && isMainLine) {
1855
- source += `\n`;
1856
- if (lineMarker) {
1857
- source += " ";
1858
- }
1859
- if (lineNumbersOnTheLeft) {
1860
- const asideSpaces = `${fillLeft(lineNumber, lineEndIndex + 1)} | `
1861
- .length;
1862
- source += " ".repeat(asideSpaces);
1863
- }
1864
- source += " ".repeat(columnMarkerIndex);
1865
- source += format("^", "marker_column");
1866
- }
1879
+ if (cleanup) {
1880
+ cleanup();
1881
+ cleanup = null;
1867
1882
  }
1868
- if (!isLastLine) {
1869
- source += "\n";
1883
+ if (dynamicLog && message) {
1884
+ dynamicLog.update(update(message));
1885
+ dynamicLog = null;
1870
1886
  }
1871
- }
1872
- return source;
1873
- };
1874
-
1875
- const truncateLine = (line, { start, end, prefix, suffix, format }) => {
1876
- const lastIndex = line.length;
1877
-
1878
- if (line.length === 0) {
1879
- // don't show any ellipsis if the line is empty
1880
- // because it's not truncated in that case
1881
- return "";
1882
- }
1883
-
1884
- const startTruncated = start > 0;
1885
- const endTruncated = lastIndex > end;
1886
-
1887
- let from = startTruncated ? start + prefix.length : start;
1888
- let to = endTruncated ? end - suffix.length : end;
1889
- if (to > lastIndex) to = lastIndex;
1887
+ };
1888
+ spinner.stop = stop;
1890
1889
 
1891
- if (start >= lastIndex || from === to) {
1892
- return "";
1893
- }
1894
- let result = "";
1895
- while (from < to) {
1896
- result += format(line[from], "char");
1897
- from++;
1898
- }
1899
- if (result.length === 0) {
1900
- return "";
1901
- }
1902
- if (startTruncated && endTruncated) {
1903
- return `${format(prefix, "marker_overflow_left")}${result}${format(
1904
- suffix,
1905
- "marker_overflow_right",
1906
- )}`;
1907
- }
1908
- if (startTruncated) {
1909
- return `${format(prefix, "marker_overflow_left")}${result}`;
1890
+ if (stopOnVerticalOverflow) {
1891
+ dynamicLog.onVerticalOverflow = stop;
1910
1892
  }
1911
- if (endTruncated) {
1912
- return `${result}${format(suffix, "marker_overflow_right")}`;
1893
+ if (stopOnWriteFromOutside) {
1894
+ dynamicLog.onWriteFromOutside = stop;
1913
1895
  }
1914
- return result;
1896
+
1897
+ return spinner;
1915
1898
  };
1916
1899
 
1917
- const fillLeft = (value, biggestValue, char = " ") => {
1918
- const width = String(value).length;
1919
- const biggestWidth = String(biggestValue).length;
1920
- let missingWidth = biggestWidth - width;
1921
- let padded = "";
1922
- while (missingWidth--) {
1923
- padded += char;
1900
+ const createTaskLog = (
1901
+ label,
1902
+ { disabled = false, animated = true, stopOnWriteFromOutside } = {},
1903
+ ) => {
1904
+ if (disabled) {
1905
+ return {
1906
+ setRightText: () => {},
1907
+ done: () => {},
1908
+ happen: () => {},
1909
+ fail: () => {},
1910
+ };
1924
1911
  }
1925
- padded += value;
1926
- return padded;
1912
+ const startMs = Date.now();
1913
+ const dynamicLog = createDynamicLog();
1914
+ let message = label;
1915
+ const taskSpinner = startSpinner({
1916
+ dynamicLog,
1917
+ render: () => message,
1918
+ stopOnWriteFromOutside,
1919
+ animated,
1920
+ });
1921
+ return {
1922
+ setRightText: (value) => {
1923
+ message = `${label} ${value}`;
1924
+ },
1925
+ done: () => {
1926
+ const msEllapsed = Date.now() - startMs;
1927
+ taskSpinner.stop(
1928
+ `${UNICODE.OK} ${label} (done in ${humanizeDuration(msEllapsed)})`,
1929
+ );
1930
+ },
1931
+ happen: (message) => {
1932
+ taskSpinner.stop(
1933
+ `${UNICODE.INFO} ${message} (at ${new Date().toLocaleTimeString()})`,
1934
+ );
1935
+ },
1936
+ fail: (message = `failed to ${label}`) => {
1937
+ taskSpinner.stop(`${UNICODE.FAILURE} ${message}`);
1938
+ },
1939
+ };
1927
1940
  };
1928
1941
 
1929
- export { ANSI, UNICODE, createDetailedMessage, createDynamicLog, createLogger, createTaskLog, determineQuote, distributePercentages, generateContentFrame, humanize, humanizeDuration, humanizeEllapsedTime, humanizeFileSize, humanizeMemory, humanizeMethodSymbol, inspectChar, startSpinner };
1942
+ export { ANSI, UNICODE, createDetailedMessage, createDynamicLog, createLogger, createTaskLog, distributePercentages, generateContentFrame, humanize, humanizeDuration, humanizeEllapsedTime, humanizeFileSize, humanizeMemory, humanizeMethodSymbol, startSpinner };