@jsenv/humanize 1.1.0 → 1.1.2

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