@jsenv/humanize 1.0.0 → 1.1.1

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