@jsenv/core 38.4.10 → 38.4.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -5
- package/dist/jsenv_core.js +1076 -1063
- package/package.json +15 -15
package/dist/jsenv_core.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { readdir, chmod, stat, lstat, promises, writeFileSync as writeFileSync$1, mkdirSync, unlink, openSync, closeSync, rmdir, watch, readdirSync, statSync, createReadStream, readFile, existsSync, readFileSync, realpathSync } from "node:fs";
|
|
2
|
-
import stringWidth from "string-width";
|
|
3
2
|
import process$1 from "node:process";
|
|
4
3
|
import os, { networkInterfaces } from "node:os";
|
|
5
4
|
import tty from "node:tty";
|
|
5
|
+
import stringWidth from "string-width";
|
|
6
6
|
import { pathToFileURL, fileURLToPath } from "node:url";
|
|
7
7
|
import { extname } from "node:path";
|
|
8
8
|
import crypto, { createHash } from "node:crypto";
|
|
@@ -588,1220 +588,1233 @@ const DATA_URL = {
|
|
|
588
588
|
},
|
|
589
589
|
};
|
|
590
590
|
|
|
591
|
-
const
|
|
592
|
-
|
|
593
|
-
const LOG_LEVEL_DEBUG = "debug";
|
|
594
|
-
|
|
595
|
-
const LOG_LEVEL_INFO = "info";
|
|
596
|
-
|
|
597
|
-
const LOG_LEVEL_WARN = "warn";
|
|
591
|
+
const createDetailedMessage$1 = (message, details = {}) => {
|
|
592
|
+
let string = `${message}`;
|
|
598
593
|
|
|
599
|
-
|
|
594
|
+
Object.keys(details).forEach((key) => {
|
|
595
|
+
const value = details[key];
|
|
596
|
+
string += `
|
|
597
|
+
--- ${key} ---
|
|
598
|
+
${
|
|
599
|
+
Array.isArray(value)
|
|
600
|
+
? value.join(`
|
|
601
|
+
`)
|
|
602
|
+
: value
|
|
603
|
+
}`;
|
|
604
|
+
});
|
|
600
605
|
|
|
601
|
-
|
|
602
|
-
if (logLevel === LOG_LEVEL_DEBUG) {
|
|
603
|
-
return {
|
|
604
|
-
level: "debug",
|
|
605
|
-
levels: { debug: true, info: true, warn: true, error: true },
|
|
606
|
-
debug,
|
|
607
|
-
info,
|
|
608
|
-
warn,
|
|
609
|
-
error,
|
|
610
|
-
};
|
|
611
|
-
}
|
|
612
|
-
if (logLevel === LOG_LEVEL_INFO) {
|
|
613
|
-
return {
|
|
614
|
-
level: "info",
|
|
615
|
-
levels: { debug: false, info: true, warn: true, error: true },
|
|
616
|
-
debug: debugDisabled,
|
|
617
|
-
info,
|
|
618
|
-
warn,
|
|
619
|
-
error,
|
|
620
|
-
};
|
|
621
|
-
}
|
|
622
|
-
if (logLevel === LOG_LEVEL_WARN) {
|
|
623
|
-
return {
|
|
624
|
-
level: "warn",
|
|
625
|
-
levels: { debug: false, info: false, warn: true, error: true },
|
|
626
|
-
debug: debugDisabled,
|
|
627
|
-
info: infoDisabled,
|
|
628
|
-
warn,
|
|
629
|
-
error,
|
|
630
|
-
};
|
|
631
|
-
}
|
|
632
|
-
if (logLevel === LOG_LEVEL_ERROR) {
|
|
633
|
-
return {
|
|
634
|
-
level: "error",
|
|
635
|
-
levels: { debug: false, info: false, warn: false, error: true },
|
|
636
|
-
debug: debugDisabled,
|
|
637
|
-
info: infoDisabled,
|
|
638
|
-
warn: warnDisabled,
|
|
639
|
-
error,
|
|
640
|
-
};
|
|
641
|
-
}
|
|
642
|
-
if (logLevel === LOG_LEVEL_OFF) {
|
|
643
|
-
return {
|
|
644
|
-
level: "off",
|
|
645
|
-
levels: { debug: false, info: false, warn: false, error: false },
|
|
646
|
-
debug: debugDisabled,
|
|
647
|
-
info: infoDisabled,
|
|
648
|
-
warn: warnDisabled,
|
|
649
|
-
error: errorDisabled,
|
|
650
|
-
};
|
|
651
|
-
}
|
|
652
|
-
throw new Error(`unexpected logLevel.
|
|
653
|
-
--- logLevel ---
|
|
654
|
-
${logLevel}
|
|
655
|
-
--- allowed log levels ---
|
|
656
|
-
${LOG_LEVEL_OFF}
|
|
657
|
-
${LOG_LEVEL_ERROR}
|
|
658
|
-
${LOG_LEVEL_WARN}
|
|
659
|
-
${LOG_LEVEL_INFO}
|
|
660
|
-
${LOG_LEVEL_DEBUG}`);
|
|
606
|
+
return string;
|
|
661
607
|
};
|
|
662
608
|
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
const
|
|
609
|
+
// From: https://github.com/sindresorhus/has-flag/blob/main/index.js
|
|
610
|
+
/// function hasFlag(flag, argv = globalThis.Deno?.args ?? process.argv) {
|
|
611
|
+
function hasFlag(flag, argv = globalThis.Deno ? globalThis.Deno.args : process$1.argv) {
|
|
612
|
+
const prefix = flag.startsWith('-') ? '' : (flag.length === 1 ? '-' : '--');
|
|
613
|
+
const position = argv.indexOf(prefix + flag);
|
|
614
|
+
const terminatorPosition = argv.indexOf('--');
|
|
615
|
+
return position !== -1 && (terminatorPosition === -1 || position < terminatorPosition);
|
|
616
|
+
}
|
|
668
617
|
|
|
669
|
-
const
|
|
618
|
+
const {env} = process$1;
|
|
670
619
|
|
|
671
|
-
|
|
620
|
+
let flagForceColor;
|
|
621
|
+
if (
|
|
622
|
+
hasFlag('no-color')
|
|
623
|
+
|| hasFlag('no-colors')
|
|
624
|
+
|| hasFlag('color=false')
|
|
625
|
+
|| hasFlag('color=never')
|
|
626
|
+
) {
|
|
627
|
+
flagForceColor = 0;
|
|
628
|
+
} else if (
|
|
629
|
+
hasFlag('color')
|
|
630
|
+
|| hasFlag('colors')
|
|
631
|
+
|| hasFlag('color=true')
|
|
632
|
+
|| hasFlag('color=always')
|
|
633
|
+
) {
|
|
634
|
+
flagForceColor = 1;
|
|
635
|
+
}
|
|
672
636
|
|
|
673
|
-
|
|
637
|
+
function envForceColor() {
|
|
638
|
+
if ('FORCE_COLOR' in env) {
|
|
639
|
+
if (env.FORCE_COLOR === 'true') {
|
|
640
|
+
return 1;
|
|
641
|
+
}
|
|
674
642
|
|
|
675
|
-
|
|
643
|
+
if (env.FORCE_COLOR === 'false') {
|
|
644
|
+
return 0;
|
|
645
|
+
}
|
|
676
646
|
|
|
677
|
-
|
|
647
|
+
return env.FORCE_COLOR.length === 0 ? 1 : Math.min(Number.parseInt(env.FORCE_COLOR, 10), 3);
|
|
648
|
+
}
|
|
649
|
+
}
|
|
678
650
|
|
|
679
|
-
|
|
651
|
+
function translateLevel(level) {
|
|
652
|
+
if (level === 0) {
|
|
653
|
+
return false;
|
|
654
|
+
}
|
|
680
655
|
|
|
681
|
-
|
|
656
|
+
return {
|
|
657
|
+
level,
|
|
658
|
+
hasBasic: true,
|
|
659
|
+
has256: level >= 2,
|
|
660
|
+
has16m: level >= 3,
|
|
661
|
+
};
|
|
662
|
+
}
|
|
682
663
|
|
|
683
|
-
|
|
664
|
+
function _supportsColor(haveStream, {streamIsTTY, sniffFlags = true} = {}) {
|
|
665
|
+
const noFlagForceColor = envForceColor();
|
|
666
|
+
if (noFlagForceColor !== undefined) {
|
|
667
|
+
flagForceColor = noFlagForceColor;
|
|
668
|
+
}
|
|
684
669
|
|
|
685
|
-
|
|
670
|
+
const forceColor = sniffFlags ? flagForceColor : noFlagForceColor;
|
|
686
671
|
|
|
687
|
-
|
|
672
|
+
if (forceColor === 0) {
|
|
673
|
+
return 0;
|
|
674
|
+
}
|
|
688
675
|
|
|
689
|
-
|
|
676
|
+
if (sniffFlags) {
|
|
677
|
+
if (hasFlag('color=16m')
|
|
678
|
+
|| hasFlag('color=full')
|
|
679
|
+
|| hasFlag('color=truecolor')) {
|
|
680
|
+
return 3;
|
|
681
|
+
}
|
|
690
682
|
|
|
691
|
-
|
|
683
|
+
if (hasFlag('color=256')) {
|
|
684
|
+
return 2;
|
|
685
|
+
}
|
|
686
|
+
}
|
|
692
687
|
|
|
693
|
-
|
|
688
|
+
// Check for Azure DevOps pipelines.
|
|
689
|
+
// Has to be above the `!streamIsTTY` check.
|
|
690
|
+
if ('TF_BUILD' in env && 'AGENT_NAME' in env) {
|
|
691
|
+
return 1;
|
|
692
|
+
}
|
|
694
693
|
|
|
695
|
-
|
|
694
|
+
if (haveStream && !streamIsTTY && forceColor === undefined) {
|
|
695
|
+
return 0;
|
|
696
|
+
}
|
|
696
697
|
|
|
697
|
-
|
|
698
|
+
const min = forceColor || 0;
|
|
698
699
|
|
|
699
|
-
|
|
700
|
+
if (env.TERM === 'dumb') {
|
|
701
|
+
return min;
|
|
702
|
+
}
|
|
700
703
|
|
|
701
|
-
|
|
702
|
-
|
|
704
|
+
if (process$1.platform === 'win32') {
|
|
705
|
+
// Windows 10 build 10586 is the first Windows release that supports 256 colors.
|
|
706
|
+
// Windows 10 build 14931 is the first release that supports 16m/TrueColor.
|
|
707
|
+
const osRelease = os.release().split('.');
|
|
708
|
+
if (
|
|
709
|
+
Number(osRelease[0]) >= 10
|
|
710
|
+
&& Number(osRelease[2]) >= 10_586
|
|
711
|
+
) {
|
|
712
|
+
return Number(osRelease[2]) >= 14_931 ? 3 : 2;
|
|
713
|
+
}
|
|
703
714
|
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|| globalThis.navigator?.userAgent?.includes(' Mac ') === true
|
|
707
|
-
|| globalThis.process?.platform === 'darwin';
|
|
715
|
+
return 1;
|
|
716
|
+
}
|
|
708
717
|
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
718
|
+
if ('CI' in env) {
|
|
719
|
+
if ('GITHUB_ACTIONS' in env || 'GITEA_ACTIONS' in env) {
|
|
720
|
+
return 3;
|
|
721
|
+
}
|
|
712
722
|
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|| globalThis.process?.platform === 'linux';
|
|
723
|
+
if (['TRAVIS', 'CIRCLECI', 'APPVEYOR', 'GITLAB_CI', 'BUILDKITE', 'DRONE'].some(sign => sign in env) || env.CI_NAME === 'codeship') {
|
|
724
|
+
return 1;
|
|
725
|
+
}
|
|
717
726
|
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|| globalThis.navigator?.userAgent?.includes(' Android ') === true
|
|
721
|
-
|| globalThis.process?.platform === 'android';
|
|
727
|
+
return min;
|
|
728
|
+
}
|
|
722
729
|
|
|
723
|
-
|
|
730
|
+
if ('TEAMCITY_VERSION' in env) {
|
|
731
|
+
return /^(9\.(0*[1-9]\d*)\.|\d{2,}\.)/.test(env.TEAMCITY_VERSION) ? 1 : 0;
|
|
732
|
+
}
|
|
724
733
|
|
|
725
|
-
|
|
726
|
-
|
|
734
|
+
if (env.COLORTERM === 'truecolor') {
|
|
735
|
+
return 3;
|
|
736
|
+
}
|
|
727
737
|
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
}
|
|
738
|
+
if (env.TERM === 'xterm-kitty') {
|
|
739
|
+
return 3;
|
|
740
|
+
}
|
|
731
741
|
|
|
732
|
-
|
|
742
|
+
if ('TERM_PROGRAM' in env) {
|
|
743
|
+
const version = Number.parseInt((env.TERM_PROGRAM_VERSION || '').split('.')[0], 10);
|
|
733
744
|
|
|
734
|
-
|
|
745
|
+
switch (env.TERM_PROGRAM) {
|
|
746
|
+
case 'iTerm.app': {
|
|
747
|
+
return version >= 3 ? 3 : 2;
|
|
748
|
+
}
|
|
735
749
|
|
|
736
|
-
|
|
737
|
-
|
|
750
|
+
case 'Apple_Terminal': {
|
|
751
|
+
return 2;
|
|
752
|
+
}
|
|
753
|
+
// No default
|
|
754
|
+
}
|
|
755
|
+
}
|
|
738
756
|
|
|
739
|
-
|
|
740
|
-
|
|
757
|
+
if (/-256(color)?$/i.test(env.TERM)) {
|
|
758
|
+
return 2;
|
|
741
759
|
}
|
|
742
760
|
|
|
743
|
-
if (
|
|
744
|
-
|
|
761
|
+
if (/^screen|^xterm|^vt100|^vt220|^rxvt|color|ansi|cygwin|linux/i.test(env.TERM)) {
|
|
762
|
+
return 1;
|
|
745
763
|
}
|
|
746
764
|
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
const eraseScreen = ESC + '2J';
|
|
751
|
-
|
|
752
|
-
const clearTerminal = isWindows$3
|
|
753
|
-
? `${eraseScreen}${ESC}0f`
|
|
754
|
-
// 1. Erases the screen (Only done in case `2` is not supported)
|
|
755
|
-
// 2. Erases the whole screen including scrollback buffer
|
|
756
|
-
// 3. Moves cursor to the top-left position
|
|
757
|
-
// More info: https://www.real-world-systems.com/docs/ANSIcode.html
|
|
758
|
-
: `${eraseScreen}${ESC}3J${ESC}H`;
|
|
759
|
-
|
|
760
|
-
/*
|
|
761
|
-
* see also https://github.com/vadimdemedes/ink
|
|
762
|
-
*/
|
|
765
|
+
if ('COLORTERM' in env) {
|
|
766
|
+
return 1;
|
|
767
|
+
}
|
|
763
768
|
|
|
769
|
+
return min;
|
|
770
|
+
}
|
|
764
771
|
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
} = {}) => {
|
|
771
|
-
const { columns = 80, rows = 24 } = stream;
|
|
772
|
-
const dynamicLog = {
|
|
773
|
-
destroyed: false,
|
|
774
|
-
onVerticalOverflow,
|
|
775
|
-
onWriteFromOutside,
|
|
776
|
-
};
|
|
772
|
+
function createSupportsColor(stream, options = {}) {
|
|
773
|
+
const level = _supportsColor(stream, {
|
|
774
|
+
streamIsTTY: stream && stream.isTTY,
|
|
775
|
+
...options,
|
|
776
|
+
});
|
|
777
777
|
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
let clearAttemptResult;
|
|
781
|
-
let writing = false;
|
|
778
|
+
return translateLevel(level);
|
|
779
|
+
}
|
|
782
780
|
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
}
|
|
788
|
-
if (clearAttemptResult !== undefined) {
|
|
789
|
-
return "";
|
|
790
|
-
}
|
|
781
|
+
({
|
|
782
|
+
stdout: createSupportsColor({isTTY: tty.isatty(1)}),
|
|
783
|
+
stderr: createSupportsColor({isTTY: tty.isatty(2)}),
|
|
784
|
+
});
|
|
791
785
|
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
const width = stringWidth(logLine);
|
|
796
|
-
if (width === 0) {
|
|
797
|
-
visualLineCount++;
|
|
798
|
-
} else {
|
|
799
|
-
visualLineCount += Math.ceil(width / columns);
|
|
800
|
-
}
|
|
801
|
-
}
|
|
786
|
+
// https://github.com/Marak/colors.js/blob/master/lib/styles.js
|
|
787
|
+
// https://stackoverflow.com/a/75985833/2634179
|
|
788
|
+
const RESET = "\x1b[0m";
|
|
802
789
|
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
return
|
|
816
|
-
|
|
790
|
+
const createAnsi = ({ supported }) => {
|
|
791
|
+
const ANSI = {
|
|
792
|
+
supported,
|
|
793
|
+
|
|
794
|
+
RED: "\x1b[31m",
|
|
795
|
+
GREEN: "\x1b[32m",
|
|
796
|
+
YELLOW: "\x1b[33m",
|
|
797
|
+
BLUE: "\x1b[34m",
|
|
798
|
+
MAGENTA: "\x1b[35m",
|
|
799
|
+
CYAN: "\x1b[36m",
|
|
800
|
+
GREY: "\x1b[90m",
|
|
801
|
+
color: (text, ANSI_COLOR) => {
|
|
802
|
+
return ANSI.supported && ANSI_COLOR
|
|
803
|
+
? `${ANSI_COLOR}${text}${RESET}`
|
|
804
|
+
: text;
|
|
805
|
+
},
|
|
817
806
|
|
|
818
|
-
|
|
819
|
-
|
|
807
|
+
BOLD: "\x1b[1m",
|
|
808
|
+
UNDERLINE: "\x1b[4m",
|
|
809
|
+
STRIKE: "\x1b[9m",
|
|
810
|
+
effect: (text, ANSI_EFFECT) => {
|
|
811
|
+
return ANSI.supported && ANSI_EFFECT
|
|
812
|
+
? `${ANSI_EFFECT}${text}${RESET}`
|
|
813
|
+
: text;
|
|
814
|
+
},
|
|
820
815
|
};
|
|
821
816
|
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
throw new Error("Cannot write log after destroy");
|
|
825
|
-
}
|
|
826
|
-
let stringToWrite = string;
|
|
827
|
-
if (lastOutput) {
|
|
828
|
-
if (lastOutputFromOutside) {
|
|
829
|
-
// We don't want to clear logs written by other code,
|
|
830
|
-
// it makes output unreadable and might erase precious information
|
|
831
|
-
// To detect this we put a spy on the stream.
|
|
832
|
-
// The spy is required only if we actually wrote something in the stream
|
|
833
|
-
// something else than this code has written in the stream
|
|
834
|
-
// so we just write without clearing (append instead of replacing)
|
|
835
|
-
lastOutput = "";
|
|
836
|
-
lastOutputFromOutside = "";
|
|
837
|
-
} else {
|
|
838
|
-
stringToWrite = `${getErasePreviousOutput()}${string}`;
|
|
839
|
-
}
|
|
840
|
-
}
|
|
841
|
-
writing = true;
|
|
842
|
-
stream.write(stringToWrite);
|
|
843
|
-
lastOutput = string;
|
|
844
|
-
writing = false;
|
|
845
|
-
clearAttemptResult = undefined;
|
|
846
|
-
};
|
|
817
|
+
return ANSI;
|
|
818
|
+
};
|
|
847
819
|
|
|
848
|
-
|
|
849
|
-
callback,
|
|
850
|
-
ouputAfterCallback = lastOutput,
|
|
851
|
-
) => {
|
|
852
|
-
// 1. Erase the current log
|
|
853
|
-
// 2. Call callback (expect to write something on stdout)
|
|
854
|
-
// 3. Restore the current log
|
|
855
|
-
// During step 2. we expect a "write from outside" so we uninstall
|
|
856
|
-
// the stream spy during function call
|
|
857
|
-
update("");
|
|
820
|
+
const processSupportsBasicColor = createSupportsColor(process.stdout).hasBasic;
|
|
858
821
|
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
822
|
+
const ANSI = createAnsi({
|
|
823
|
+
supported:
|
|
824
|
+
processSupportsBasicColor ||
|
|
825
|
+
// GitHub workflow does support ANSI but "supports-color" returns false
|
|
826
|
+
// because stream.isTTY returns false, see https://github.com/actions/runner/issues/241
|
|
827
|
+
(process.env.GITHUB_WORKFLOW &&
|
|
828
|
+
// Check on FORCE_COLOR is to ensure it is prio over GitHub workflow check
|
|
829
|
+
// in unit test we use process.env.FORCE_COLOR = 'false' to fake
|
|
830
|
+
// that colors are not supported. Let it have priority
|
|
831
|
+
process.env.FORCE_COLOR !== "false"),
|
|
832
|
+
});
|
|
862
833
|
|
|
863
|
-
|
|
864
|
-
|
|
834
|
+
function isUnicodeSupported() {
|
|
835
|
+
if (process$1.platform !== 'win32') {
|
|
836
|
+
return process$1.env.TERM !== 'linux'; // Linux console (kernel)
|
|
837
|
+
}
|
|
865
838
|
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
lastOutputFromOutside = value;
|
|
876
|
-
dynamicLog.onWriteFromOutside(value);
|
|
877
|
-
};
|
|
839
|
+
return Boolean(process$1.env.WT_SESSION) // Windows Terminal
|
|
840
|
+
|| Boolean(process$1.env.TERMINUS_SUBLIME) // Terminus (<0.2.27)
|
|
841
|
+
|| process$1.env.ConEmuTask === '{cmd::Cmder}' // ConEmu and cmder
|
|
842
|
+
|| process$1.env.TERM_PROGRAM === 'Terminus-Sublime'
|
|
843
|
+
|| process$1.env.TERM_PROGRAM === 'vscode'
|
|
844
|
+
|| process$1.env.TERM === 'xterm-256color'
|
|
845
|
+
|| process$1.env.TERM === 'alacritty'
|
|
846
|
+
|| process$1.env.TERMINAL_EMULATOR === 'JetBrains-JediTerm';
|
|
847
|
+
}
|
|
878
848
|
|
|
879
|
-
|
|
880
|
-
if (stream === process.stdout) {
|
|
881
|
-
const removeStdoutSpy = spyStreamOutput(
|
|
882
|
-
process.stdout,
|
|
883
|
-
writeFromOutsideEffect,
|
|
884
|
-
);
|
|
885
|
-
const removeStderrSpy = spyStreamOutput(
|
|
886
|
-
process.stderr,
|
|
887
|
-
writeFromOutsideEffect,
|
|
888
|
-
);
|
|
889
|
-
removeStreamSpy = () => {
|
|
890
|
-
removeStdoutSpy();
|
|
891
|
-
removeStderrSpy();
|
|
892
|
-
};
|
|
893
|
-
} else {
|
|
894
|
-
removeStreamSpy = spyStreamOutput(stream, writeFromOutsideEffect);
|
|
895
|
-
}
|
|
849
|
+
// see also https://github.com/sindresorhus/figures
|
|
896
850
|
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
851
|
+
const createUnicode = ({ supported, ANSI }) => {
|
|
852
|
+
const UNICODE = {
|
|
853
|
+
supported,
|
|
854
|
+
get COMMAND_RAW() {
|
|
855
|
+
return UNICODE.supported ? `❯` : `>`;
|
|
856
|
+
},
|
|
857
|
+
get OK_RAW() {
|
|
858
|
+
return UNICODE.supported ? `✔` : `√`;
|
|
859
|
+
},
|
|
860
|
+
get FAILURE_RAW() {
|
|
861
|
+
return UNICODE.supported ? `✖` : `×`;
|
|
862
|
+
},
|
|
863
|
+
get DEBUG_RAW() {
|
|
864
|
+
return UNICODE.supported ? `◆` : `♦`;
|
|
865
|
+
},
|
|
866
|
+
get INFO_RAW() {
|
|
867
|
+
return UNICODE.supported ? `ℹ` : `i`;
|
|
868
|
+
},
|
|
869
|
+
get WARNING_RAW() {
|
|
870
|
+
return UNICODE.supported ? `⚠` : `‼`;
|
|
871
|
+
},
|
|
872
|
+
get CIRCLE_CROSS_RAW() {
|
|
873
|
+
return UNICODE.supported ? `ⓧ` : `(×)`;
|
|
874
|
+
},
|
|
875
|
+
get COMMAND() {
|
|
876
|
+
return ANSI.color(UNICODE.COMMAND_RAW, ANSI.GREY); // ANSI_MAGENTA)
|
|
877
|
+
},
|
|
878
|
+
get OK() {
|
|
879
|
+
return ANSI.color(UNICODE.OK_RAW, ANSI.GREEN);
|
|
880
|
+
},
|
|
881
|
+
get FAILURE() {
|
|
882
|
+
return ANSI.color(UNICODE.FAILURE_RAW, ANSI.RED);
|
|
883
|
+
},
|
|
884
|
+
get DEBUG() {
|
|
885
|
+
return ANSI.color(UNICODE.DEBUG_RAW, ANSI.GREY);
|
|
886
|
+
},
|
|
887
|
+
get INFO() {
|
|
888
|
+
return ANSI.color(UNICODE.INFO_RAW, ANSI.BLUE);
|
|
889
|
+
},
|
|
890
|
+
get WARNING() {
|
|
891
|
+
return ANSI.color(UNICODE.WARNING_RAW, ANSI.YELLOW);
|
|
892
|
+
},
|
|
893
|
+
get CIRCLE_CROSS() {
|
|
894
|
+
return ANSI.color(UNICODE.CIRCLE_CROSS_RAW, ANSI.RED);
|
|
895
|
+
},
|
|
896
|
+
get ELLIPSIS() {
|
|
897
|
+
return UNICODE.supported ? `…` : `...`;
|
|
898
|
+
},
|
|
905
899
|
};
|
|
906
|
-
|
|
907
|
-
Object.assign(dynamicLog, {
|
|
908
|
-
update,
|
|
909
|
-
destroy,
|
|
910
|
-
stream,
|
|
911
|
-
clearDuringFunctionCall,
|
|
912
|
-
});
|
|
913
|
-
return dynamicLog;
|
|
900
|
+
return UNICODE;
|
|
914
901
|
};
|
|
915
902
|
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
const spyStreamOutput = (stream, callback) => {
|
|
921
|
-
const originalWrite = stream.write;
|
|
922
|
-
|
|
923
|
-
let output = "";
|
|
924
|
-
let installed = true;
|
|
925
|
-
|
|
926
|
-
stream.write = function (...args /* chunk, encoding, callback */) {
|
|
927
|
-
output += args;
|
|
928
|
-
callback(output);
|
|
929
|
-
return originalWrite.call(stream, ...args);
|
|
930
|
-
};
|
|
903
|
+
const UNICODE = createUnicode({
|
|
904
|
+
supported: isUnicodeSupported(),
|
|
905
|
+
ANSI,
|
|
906
|
+
});
|
|
931
907
|
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
installed = false;
|
|
938
|
-
};
|
|
908
|
+
const getPrecision = (number) => {
|
|
909
|
+
if (Math.floor(number) === number) return 0;
|
|
910
|
+
const [, decimals] = number.toString().split(".");
|
|
911
|
+
return decimals.length || 0;
|
|
912
|
+
};
|
|
939
913
|
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
914
|
+
const setRoundedPrecision = (
|
|
915
|
+
number,
|
|
916
|
+
{ decimals = 1, decimalsWhenSmall = decimals } = {},
|
|
917
|
+
) => {
|
|
918
|
+
return setDecimalsPrecision(number, {
|
|
919
|
+
decimals,
|
|
920
|
+
decimalsWhenSmall,
|
|
921
|
+
transform: Math.round,
|
|
922
|
+
});
|
|
944
923
|
};
|
|
945
924
|
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
925
|
+
const setPrecision = (
|
|
926
|
+
number,
|
|
927
|
+
{ decimals = 1, decimalsWhenSmall = decimals } = {},
|
|
928
|
+
) => {
|
|
929
|
+
return setDecimalsPrecision(number, {
|
|
930
|
+
decimals,
|
|
931
|
+
decimalsWhenSmall,
|
|
932
|
+
transform: parseInt,
|
|
933
|
+
});
|
|
934
|
+
};
|
|
954
935
|
|
|
955
|
-
const
|
|
936
|
+
const setDecimalsPrecision = (
|
|
937
|
+
number,
|
|
938
|
+
{
|
|
939
|
+
transform,
|
|
940
|
+
decimals, // max decimals for number in [-Infinity, -1[]1, Infinity]
|
|
941
|
+
decimalsWhenSmall, // max decimals for number in [-1,1]
|
|
942
|
+
} = {},
|
|
943
|
+
) => {
|
|
944
|
+
if (number === 0) {
|
|
945
|
+
return 0;
|
|
946
|
+
}
|
|
947
|
+
let numberCandidate = Math.abs(number);
|
|
948
|
+
if (numberCandidate < 1) {
|
|
949
|
+
const integerGoal = Math.pow(10, decimalsWhenSmall - 1);
|
|
950
|
+
let i = 1;
|
|
951
|
+
while (numberCandidate < integerGoal) {
|
|
952
|
+
numberCandidate *= 10;
|
|
953
|
+
i *= 10;
|
|
954
|
+
}
|
|
955
|
+
const asInteger = transform(numberCandidate);
|
|
956
|
+
const asFloat = asInteger / i;
|
|
957
|
+
return number < 0 ? -asFloat : asFloat;
|
|
958
|
+
}
|
|
959
|
+
const coef = Math.pow(10, decimals);
|
|
960
|
+
const numberMultiplied = (number + Number.EPSILON) * coef;
|
|
961
|
+
const asInteger = transform(numberMultiplied);
|
|
962
|
+
const asFloat = asInteger / coef;
|
|
963
|
+
return number < 0 ? -asFloat : asFloat;
|
|
964
|
+
};
|
|
956
965
|
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
flagForceColor = 0;
|
|
965
|
-
} else if (
|
|
966
|
-
hasFlag('color')
|
|
967
|
-
|| hasFlag('colors')
|
|
968
|
-
|| hasFlag('color=true')
|
|
969
|
-
|| hasFlag('color=always')
|
|
970
|
-
) {
|
|
971
|
-
flagForceColor = 1;
|
|
972
|
-
}
|
|
966
|
+
// https://www.codingem.com/javascript-how-to-limit-decimal-places/
|
|
967
|
+
// export const roundNumber = (number, maxDecimals) => {
|
|
968
|
+
// const decimalsExp = Math.pow(10, maxDecimals)
|
|
969
|
+
// const numberRoundInt = Math.round(decimalsExp * (number + Number.EPSILON))
|
|
970
|
+
// const numberRoundFloat = numberRoundInt / decimalsExp
|
|
971
|
+
// return numberRoundFloat
|
|
972
|
+
// }
|
|
973
973
|
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
974
|
+
// export const setPrecision = (number, precision) => {
|
|
975
|
+
// if (Math.floor(number) === number) return number
|
|
976
|
+
// const [int, decimals] = number.toString().split(".")
|
|
977
|
+
// if (precision <= 0) return int
|
|
978
|
+
// const numberTruncated = `${int}.${decimals.slice(0, precision)}`
|
|
979
|
+
// return numberTruncated
|
|
980
|
+
// }
|
|
979
981
|
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
982
|
+
const unitShort = {
|
|
983
|
+
year: "y",
|
|
984
|
+
month: "m",
|
|
985
|
+
week: "w",
|
|
986
|
+
day: "d",
|
|
987
|
+
hour: "h",
|
|
988
|
+
minute: "m",
|
|
989
|
+
second: "s",
|
|
990
|
+
};
|
|
983
991
|
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
}
|
|
992
|
+
const humanizeDuration = (
|
|
993
|
+
ms,
|
|
994
|
+
{ short, rounded = true, decimals } = {},
|
|
995
|
+
) => {
|
|
996
|
+
// ignore ms below meaningfulMs so that:
|
|
997
|
+
// humanizeDuration(0.5) -> "0 second"
|
|
998
|
+
// humanizeDuration(1.1) -> "0.001 second" (and not "0.0011 second")
|
|
999
|
+
// This tool is meant to be read by humans and it would be barely readable to see
|
|
1000
|
+
// "0.0001 second" (stands for 0.1 millisecond)
|
|
1001
|
+
// yes we could return "0.1 millisecond" but we choosed consistency over precision
|
|
1002
|
+
// so that the prefered unit is "second" (and does not become millisecond when ms is super small)
|
|
1003
|
+
if (ms < 1) {
|
|
1004
|
+
return short ? "0s" : "0 second";
|
|
1005
|
+
}
|
|
1006
|
+
const { primary, remaining } = parseMs(ms);
|
|
1007
|
+
if (!remaining) {
|
|
1008
|
+
return humanizeDurationUnit(primary, {
|
|
1009
|
+
decimals:
|
|
1010
|
+
decimals === undefined ? (primary.name === "second" ? 1 : 0) : decimals,
|
|
1011
|
+
short,
|
|
1012
|
+
rounded,
|
|
1013
|
+
});
|
|
1014
|
+
}
|
|
1015
|
+
return `${humanizeDurationUnit(primary, {
|
|
1016
|
+
decimals: decimals === undefined ? 0 : decimals,
|
|
1017
|
+
short,
|
|
1018
|
+
rounded,
|
|
1019
|
+
})} and ${humanizeDurationUnit(remaining, {
|
|
1020
|
+
decimals: decimals === undefined ? 0 : decimals,
|
|
1021
|
+
short,
|
|
1022
|
+
rounded,
|
|
1023
|
+
})}`;
|
|
1024
|
+
};
|
|
1025
|
+
const humanizeDurationUnit = (unit, { decimals, short, rounded }) => {
|
|
1026
|
+
const count = rounded
|
|
1027
|
+
? setRoundedPrecision(unit.count, { decimals })
|
|
1028
|
+
: setPrecision(unit.count, { decimals });
|
|
1029
|
+
let name = unit.name;
|
|
1030
|
+
if (short) {
|
|
1031
|
+
name = unitShort[name];
|
|
1032
|
+
return `${count}${name}`;
|
|
1033
|
+
}
|
|
1034
|
+
if (count <= 1) {
|
|
1035
|
+
return `${count} ${name}`;
|
|
1036
|
+
}
|
|
1037
|
+
return `${count} ${name}s`;
|
|
1038
|
+
};
|
|
1039
|
+
const MS_PER_UNITS = {
|
|
1040
|
+
year: 31_557_600_000,
|
|
1041
|
+
month: 2_629_000_000,
|
|
1042
|
+
week: 604_800_000,
|
|
1043
|
+
day: 86_400_000,
|
|
1044
|
+
hour: 3_600_000,
|
|
1045
|
+
minute: 60_000,
|
|
1046
|
+
second: 1000,
|
|
1047
|
+
};
|
|
987
1048
|
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
1049
|
+
const parseMs = (ms) => {
|
|
1050
|
+
const unitNames = Object.keys(MS_PER_UNITS);
|
|
1051
|
+
const smallestUnitName = unitNames[unitNames.length - 1];
|
|
1052
|
+
let firstUnitName = smallestUnitName;
|
|
1053
|
+
let firstUnitCount = ms / MS_PER_UNITS[smallestUnitName];
|
|
1054
|
+
const firstUnitIndex = unitNames.findIndex((unitName) => {
|
|
1055
|
+
if (unitName === smallestUnitName) {
|
|
1056
|
+
return false;
|
|
1057
|
+
}
|
|
1058
|
+
const msPerUnit = MS_PER_UNITS[unitName];
|
|
1059
|
+
const unitCount = Math.floor(ms / msPerUnit);
|
|
1060
|
+
if (unitCount) {
|
|
1061
|
+
firstUnitName = unitName;
|
|
1062
|
+
firstUnitCount = unitCount;
|
|
1063
|
+
return true;
|
|
1064
|
+
}
|
|
1065
|
+
return false;
|
|
1066
|
+
});
|
|
1067
|
+
if (firstUnitName === smallestUnitName) {
|
|
1068
|
+
return {
|
|
1069
|
+
primary: {
|
|
1070
|
+
name: firstUnitName,
|
|
1071
|
+
count: firstUnitCount,
|
|
1072
|
+
},
|
|
1073
|
+
};
|
|
1074
|
+
}
|
|
1075
|
+
const remainingMs = ms - firstUnitCount * MS_PER_UNITS[firstUnitName];
|
|
1076
|
+
const remainingUnitName = unitNames[firstUnitIndex + 1];
|
|
1077
|
+
const remainingUnitCount = remainingMs / MS_PER_UNITS[remainingUnitName];
|
|
1078
|
+
// - 1 year and 1 second is too much information
|
|
1079
|
+
// so we don't check the remaining units
|
|
1080
|
+
// - 1 year and 0.0001 week is awful
|
|
1081
|
+
// hence the if below
|
|
1082
|
+
if (Math.round(remainingUnitCount) < 1) {
|
|
1083
|
+
return {
|
|
1084
|
+
primary: {
|
|
1085
|
+
name: firstUnitName,
|
|
1086
|
+
count: firstUnitCount,
|
|
1087
|
+
},
|
|
1088
|
+
};
|
|
1089
|
+
}
|
|
1090
|
+
// - 1 year and 1 month is great
|
|
1091
|
+
return {
|
|
1092
|
+
primary: {
|
|
1093
|
+
name: firstUnitName,
|
|
1094
|
+
count: firstUnitCount,
|
|
1095
|
+
},
|
|
1096
|
+
remaining: {
|
|
1097
|
+
name: remainingUnitName,
|
|
1098
|
+
count: remainingUnitCount,
|
|
1099
|
+
},
|
|
1100
|
+
};
|
|
1101
|
+
};
|
|
992
1102
|
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
has256: level >= 2,
|
|
997
|
-
has16m: level >= 3,
|
|
998
|
-
};
|
|
999
|
-
}
|
|
1103
|
+
const humanizeFileSize = (numberOfBytes, { decimals, short } = {}) => {
|
|
1104
|
+
return inspectBytes(numberOfBytes, { decimals, short });
|
|
1105
|
+
};
|
|
1000
1106
|
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1107
|
+
const inspectBytes = (
|
|
1108
|
+
number,
|
|
1109
|
+
{ fixedDecimals = false, decimals, short } = {},
|
|
1110
|
+
) => {
|
|
1111
|
+
if (number === 0) {
|
|
1112
|
+
return `0 B`;
|
|
1113
|
+
}
|
|
1114
|
+
const exponent = Math.min(
|
|
1115
|
+
Math.floor(Math.log10(number) / 3),
|
|
1116
|
+
BYTE_UNITS.length - 1,
|
|
1117
|
+
);
|
|
1118
|
+
const unitNumber = number / Math.pow(1000, exponent);
|
|
1119
|
+
const unitName = BYTE_UNITS[exponent];
|
|
1120
|
+
if (decimals === undefined) {
|
|
1121
|
+
if (unitNumber < 100) {
|
|
1122
|
+
decimals = 1;
|
|
1123
|
+
} else {
|
|
1124
|
+
decimals = 0;
|
|
1125
|
+
}
|
|
1126
|
+
}
|
|
1127
|
+
const unitNumberRounded = setRoundedPrecision(unitNumber, {
|
|
1128
|
+
decimals,
|
|
1129
|
+
decimalsWhenSmall: 1,
|
|
1130
|
+
});
|
|
1131
|
+
const value = fixedDecimals
|
|
1132
|
+
? unitNumberRounded.toFixed(decimals)
|
|
1133
|
+
: unitNumberRounded;
|
|
1134
|
+
if (short) {
|
|
1135
|
+
return `${value}${unitName}`;
|
|
1136
|
+
}
|
|
1137
|
+
return `${value} ${unitName}`;
|
|
1138
|
+
};
|
|
1006
1139
|
|
|
1007
|
-
|
|
1140
|
+
const BYTE_UNITS = ["B", "kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"];
|
|
1008
1141
|
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1142
|
+
const distributePercentages = (
|
|
1143
|
+
namedNumbers,
|
|
1144
|
+
{ maxPrecisionHint = 2 } = {},
|
|
1145
|
+
) => {
|
|
1146
|
+
const numberNames = Object.keys(namedNumbers);
|
|
1147
|
+
if (numberNames.length === 0) {
|
|
1148
|
+
return {};
|
|
1149
|
+
}
|
|
1150
|
+
if (numberNames.length === 1) {
|
|
1151
|
+
const firstNumberName = numberNames[0];
|
|
1152
|
+
return { [firstNumberName]: "100 %" };
|
|
1153
|
+
}
|
|
1154
|
+
const numbers = numberNames.map((name) => namedNumbers[name]);
|
|
1155
|
+
const total = numbers.reduce((sum, value) => sum + value, 0);
|
|
1156
|
+
const ratios = numbers.map((number) => number / total);
|
|
1157
|
+
const percentages = {};
|
|
1158
|
+
ratios.pop();
|
|
1159
|
+
ratios.forEach((ratio, index) => {
|
|
1160
|
+
const percentage = ratio * 100;
|
|
1161
|
+
percentages[numberNames[index]] = percentage;
|
|
1162
|
+
});
|
|
1163
|
+
const lowestPercentage = (1 / Math.pow(10, maxPrecisionHint)) * 100;
|
|
1164
|
+
let precision = 0;
|
|
1165
|
+
Object.keys(percentages).forEach((name) => {
|
|
1166
|
+
const percentage = percentages[name];
|
|
1167
|
+
if (percentage < lowestPercentage) {
|
|
1168
|
+
// check the amout of meaningful decimals
|
|
1169
|
+
// and that what we will use
|
|
1170
|
+
const percentageRounded = setRoundedPrecision(percentage);
|
|
1171
|
+
const percentagePrecision = getPrecision(percentageRounded);
|
|
1172
|
+
if (percentagePrecision > precision) {
|
|
1173
|
+
precision = percentagePrecision;
|
|
1174
|
+
}
|
|
1175
|
+
}
|
|
1176
|
+
});
|
|
1177
|
+
let remainingPercentage = 100;
|
|
1012
1178
|
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1179
|
+
Object.keys(percentages).forEach((name) => {
|
|
1180
|
+
const percentage = percentages[name];
|
|
1181
|
+
const percentageAllocated = setRoundedPrecision(percentage, {
|
|
1182
|
+
decimals: precision,
|
|
1183
|
+
});
|
|
1184
|
+
remainingPercentage -= percentageAllocated;
|
|
1185
|
+
percentages[name] = percentageAllocated;
|
|
1186
|
+
});
|
|
1187
|
+
const lastName = numberNames[numberNames.length - 1];
|
|
1188
|
+
percentages[lastName] = setRoundedPrecision(remainingPercentage, {
|
|
1189
|
+
decimals: precision,
|
|
1190
|
+
});
|
|
1191
|
+
return percentages;
|
|
1192
|
+
};
|
|
1019
1193
|
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1194
|
+
const formatDefault = (v) => v;
|
|
1195
|
+
|
|
1196
|
+
const generateContentFrame = ({
|
|
1197
|
+
content,
|
|
1198
|
+
line,
|
|
1199
|
+
column,
|
|
1200
|
+
|
|
1201
|
+
linesAbove = 3,
|
|
1202
|
+
linesBelow = 0,
|
|
1203
|
+
lineMaxWidth = 120,
|
|
1204
|
+
lineNumbersOnTheLeft = true,
|
|
1205
|
+
lineMarker = true,
|
|
1206
|
+
columnMarker = true,
|
|
1207
|
+
format = formatDefault,
|
|
1208
|
+
} = {}) => {
|
|
1209
|
+
const lineStrings = content.split(/\r?\n/);
|
|
1210
|
+
if (line === 0) line = 1;
|
|
1211
|
+
if (column === undefined) {
|
|
1212
|
+
columnMarker = false;
|
|
1213
|
+
column = 1;
|
|
1214
|
+
}
|
|
1215
|
+
if (column === 0) column = 1;
|
|
1216
|
+
|
|
1217
|
+
let lineStartIndex = line - 1 - linesAbove;
|
|
1218
|
+
if (lineStartIndex < 0) {
|
|
1219
|
+
lineStartIndex = 0;
|
|
1220
|
+
}
|
|
1221
|
+
let lineEndIndex = line - 1 + linesBelow;
|
|
1222
|
+
if (lineEndIndex > lineStrings.length - 1) {
|
|
1223
|
+
lineEndIndex = lineStrings.length - 1;
|
|
1224
|
+
}
|
|
1225
|
+
if (columnMarker) {
|
|
1226
|
+
// human reader deduce the line when there is a column marker
|
|
1227
|
+
lineMarker = false;
|
|
1228
|
+
}
|
|
1229
|
+
if (line - 1 === lineEndIndex) {
|
|
1230
|
+
lineMarker = false; // useless because last line
|
|
1231
|
+
}
|
|
1232
|
+
let lineIndex = lineStartIndex;
|
|
1233
|
+
|
|
1234
|
+
let columnsBefore;
|
|
1235
|
+
let columnsAfter;
|
|
1236
|
+
if (column > lineMaxWidth) {
|
|
1237
|
+
columnsBefore = column - Math.ceil(lineMaxWidth / 2);
|
|
1238
|
+
columnsAfter = column + Math.floor(lineMaxWidth / 2);
|
|
1239
|
+
} else {
|
|
1240
|
+
columnsBefore = 0;
|
|
1241
|
+
columnsAfter = lineMaxWidth;
|
|
1242
|
+
}
|
|
1243
|
+
let columnMarkerIndex = column - 1 - columnsBefore;
|
|
1244
|
+
|
|
1245
|
+
let source = "";
|
|
1246
|
+
while (lineIndex <= lineEndIndex) {
|
|
1247
|
+
const lineString = lineStrings[lineIndex];
|
|
1248
|
+
const lineNumber = lineIndex + 1;
|
|
1249
|
+
const isLastLine = lineIndex === lineEndIndex;
|
|
1250
|
+
const isMainLine = lineNumber === line;
|
|
1251
|
+
lineIndex++;
|
|
1252
|
+
|
|
1253
|
+
{
|
|
1254
|
+
if (lineMarker) {
|
|
1255
|
+
if (isMainLine) {
|
|
1256
|
+
source += `${format(">", "marker_line")} `;
|
|
1257
|
+
} else {
|
|
1258
|
+
source += " ";
|
|
1259
|
+
}
|
|
1260
|
+
}
|
|
1261
|
+
if (lineNumbersOnTheLeft) {
|
|
1262
|
+
// fill with spaces to ensure if line moves from 7,8,9 to 10 the display is still great
|
|
1263
|
+
const asideSource = `${fillLeft(lineNumber, lineEndIndex + 1)} |`;
|
|
1264
|
+
source += `${format(asideSource, "line_number_aside")} `;
|
|
1265
|
+
}
|
|
1266
|
+
}
|
|
1267
|
+
{
|
|
1268
|
+
source += truncateLine(lineString, {
|
|
1269
|
+
start: columnsBefore,
|
|
1270
|
+
end: columnsAfter,
|
|
1271
|
+
prefix: "…",
|
|
1272
|
+
suffix: "…",
|
|
1273
|
+
format,
|
|
1274
|
+
});
|
|
1275
|
+
}
|
|
1276
|
+
{
|
|
1277
|
+
if (columnMarker && isMainLine) {
|
|
1278
|
+
source += `\n`;
|
|
1279
|
+
if (lineMarker) {
|
|
1280
|
+
source += " ";
|
|
1281
|
+
}
|
|
1282
|
+
if (lineNumbersOnTheLeft) {
|
|
1283
|
+
const asideSpaces = `${fillLeft(lineNumber, lineEndIndex + 1)} | `
|
|
1284
|
+
.length;
|
|
1285
|
+
source += " ".repeat(asideSpaces);
|
|
1286
|
+
}
|
|
1287
|
+
source += " ".repeat(columnMarkerIndex);
|
|
1288
|
+
source += format("^", "marker_column");
|
|
1289
|
+
}
|
|
1290
|
+
}
|
|
1291
|
+
if (!isLastLine) {
|
|
1292
|
+
source += "\n";
|
|
1293
|
+
}
|
|
1294
|
+
}
|
|
1295
|
+
return source;
|
|
1296
|
+
};
|
|
1297
|
+
|
|
1298
|
+
const truncateLine = (line, { start, end, prefix, suffix, format }) => {
|
|
1299
|
+
const lastIndex = line.length;
|
|
1300
|
+
|
|
1301
|
+
if (line.length === 0) {
|
|
1302
|
+
// don't show any ellipsis if the line is empty
|
|
1303
|
+
// because it's not truncated in that case
|
|
1304
|
+
return "";
|
|
1305
|
+
}
|
|
1306
|
+
|
|
1307
|
+
const startTruncated = start > 0;
|
|
1308
|
+
const endTruncated = lastIndex > end;
|
|
1309
|
+
|
|
1310
|
+
let from = startTruncated ? start + prefix.length : start;
|
|
1311
|
+
let to = endTruncated ? end - suffix.length : end;
|
|
1312
|
+
if (to > lastIndex) to = lastIndex;
|
|
1313
|
+
|
|
1314
|
+
if (start >= lastIndex || from === to) {
|
|
1315
|
+
return "";
|
|
1316
|
+
}
|
|
1317
|
+
let result = "";
|
|
1318
|
+
while (from < to) {
|
|
1319
|
+
result += format(line[from], "char");
|
|
1320
|
+
from++;
|
|
1321
|
+
}
|
|
1322
|
+
if (result.length === 0) {
|
|
1323
|
+
return "";
|
|
1324
|
+
}
|
|
1325
|
+
if (startTruncated && endTruncated) {
|
|
1326
|
+
return `${format(prefix, "marker_overflow_left")}${result}${format(
|
|
1327
|
+
suffix,
|
|
1328
|
+
"marker_overflow_right",
|
|
1329
|
+
)}`;
|
|
1330
|
+
}
|
|
1331
|
+
if (startTruncated) {
|
|
1332
|
+
return `${format(prefix, "marker_overflow_left")}${result}`;
|
|
1333
|
+
}
|
|
1334
|
+
if (endTruncated) {
|
|
1335
|
+
return `${result}${format(suffix, "marker_overflow_right")}`;
|
|
1336
|
+
}
|
|
1337
|
+
return result;
|
|
1338
|
+
};
|
|
1339
|
+
|
|
1340
|
+
const fillLeft = (value, biggestValue, char = " ") => {
|
|
1341
|
+
const width = String(value).length;
|
|
1342
|
+
const biggestWidth = String(biggestValue).length;
|
|
1343
|
+
let missingWidth = biggestWidth - width;
|
|
1344
|
+
let padded = "";
|
|
1345
|
+
while (missingWidth--) {
|
|
1346
|
+
padded += char;
|
|
1347
|
+
}
|
|
1348
|
+
padded += value;
|
|
1349
|
+
return padded;
|
|
1350
|
+
};
|
|
1351
|
+
|
|
1352
|
+
const LOG_LEVEL_OFF = "off";
|
|
1024
1353
|
|
|
1025
|
-
|
|
1026
|
-
// Has to be above the `!streamIsTTY` check.
|
|
1027
|
-
if ('TF_BUILD' in env && 'AGENT_NAME' in env) {
|
|
1028
|
-
return 1;
|
|
1029
|
-
}
|
|
1354
|
+
const LOG_LEVEL_DEBUG = "debug";
|
|
1030
1355
|
|
|
1031
|
-
|
|
1032
|
-
return 0;
|
|
1033
|
-
}
|
|
1356
|
+
const LOG_LEVEL_INFO = "info";
|
|
1034
1357
|
|
|
1035
|
-
|
|
1358
|
+
const LOG_LEVEL_WARN = "warn";
|
|
1036
1359
|
|
|
1037
|
-
|
|
1038
|
-
return min;
|
|
1039
|
-
}
|
|
1360
|
+
const LOG_LEVEL_ERROR = "error";
|
|
1040
1361
|
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1362
|
+
const createLogger = ({ logLevel = LOG_LEVEL_INFO } = {}) => {
|
|
1363
|
+
if (logLevel === LOG_LEVEL_DEBUG) {
|
|
1364
|
+
return {
|
|
1365
|
+
level: "debug",
|
|
1366
|
+
levels: { debug: true, info: true, warn: true, error: true },
|
|
1367
|
+
debug,
|
|
1368
|
+
info,
|
|
1369
|
+
warn,
|
|
1370
|
+
error,
|
|
1371
|
+
};
|
|
1372
|
+
}
|
|
1373
|
+
if (logLevel === LOG_LEVEL_INFO) {
|
|
1374
|
+
return {
|
|
1375
|
+
level: "info",
|
|
1376
|
+
levels: { debug: false, info: true, warn: true, error: true },
|
|
1377
|
+
debug: debugDisabled,
|
|
1378
|
+
info,
|
|
1379
|
+
warn,
|
|
1380
|
+
error,
|
|
1381
|
+
};
|
|
1382
|
+
}
|
|
1383
|
+
if (logLevel === LOG_LEVEL_WARN) {
|
|
1384
|
+
return {
|
|
1385
|
+
level: "warn",
|
|
1386
|
+
levels: { debug: false, info: false, warn: true, error: true },
|
|
1387
|
+
debug: debugDisabled,
|
|
1388
|
+
info: infoDisabled,
|
|
1389
|
+
warn,
|
|
1390
|
+
error,
|
|
1391
|
+
};
|
|
1392
|
+
}
|
|
1393
|
+
if (logLevel === LOG_LEVEL_ERROR) {
|
|
1394
|
+
return {
|
|
1395
|
+
level: "error",
|
|
1396
|
+
levels: { debug: false, info: false, warn: false, error: true },
|
|
1397
|
+
debug: debugDisabled,
|
|
1398
|
+
info: infoDisabled,
|
|
1399
|
+
warn: warnDisabled,
|
|
1400
|
+
error,
|
|
1401
|
+
};
|
|
1402
|
+
}
|
|
1403
|
+
if (logLevel === LOG_LEVEL_OFF) {
|
|
1404
|
+
return {
|
|
1405
|
+
level: "off",
|
|
1406
|
+
levels: { debug: false, info: false, warn: false, error: false },
|
|
1407
|
+
debug: debugDisabled,
|
|
1408
|
+
info: infoDisabled,
|
|
1409
|
+
warn: warnDisabled,
|
|
1410
|
+
error: errorDisabled,
|
|
1411
|
+
};
|
|
1412
|
+
}
|
|
1413
|
+
throw new Error(`unexpected logLevel.
|
|
1414
|
+
--- logLevel ---
|
|
1415
|
+
${logLevel}
|
|
1416
|
+
--- allowed log levels ---
|
|
1417
|
+
${LOG_LEVEL_OFF}
|
|
1418
|
+
${LOG_LEVEL_ERROR}
|
|
1419
|
+
${LOG_LEVEL_WARN}
|
|
1420
|
+
${LOG_LEVEL_INFO}
|
|
1421
|
+
${LOG_LEVEL_DEBUG}`);
|
|
1422
|
+
};
|
|
1051
1423
|
|
|
1052
|
-
|
|
1053
|
-
}
|
|
1424
|
+
const debug = (...args) => console.debug(...args);
|
|
1054
1425
|
|
|
1055
|
-
|
|
1056
|
-
if ('GITHUB_ACTIONS' in env || 'GITEA_ACTIONS' in env) {
|
|
1057
|
-
return 3;
|
|
1058
|
-
}
|
|
1426
|
+
const debugDisabled = () => {};
|
|
1059
1427
|
|
|
1060
|
-
|
|
1061
|
-
return 1;
|
|
1062
|
-
}
|
|
1428
|
+
const info = (...args) => console.info(...args);
|
|
1063
1429
|
|
|
1064
|
-
|
|
1065
|
-
}
|
|
1430
|
+
const infoDisabled = () => {};
|
|
1066
1431
|
|
|
1067
|
-
|
|
1068
|
-
return /^(9\.(0*[1-9]\d*)\.|\d{2,}\.)/.test(env.TEAMCITY_VERSION) ? 1 : 0;
|
|
1069
|
-
}
|
|
1432
|
+
const warn = (...args) => console.warn(...args);
|
|
1070
1433
|
|
|
1071
|
-
|
|
1072
|
-
return 3;
|
|
1073
|
-
}
|
|
1434
|
+
const warnDisabled = () => {};
|
|
1074
1435
|
|
|
1075
|
-
|
|
1076
|
-
return 3;
|
|
1077
|
-
}
|
|
1436
|
+
const error = (...args) => console.error(...args);
|
|
1078
1437
|
|
|
1079
|
-
|
|
1080
|
-
const version = Number.parseInt((env.TERM_PROGRAM_VERSION || '').split('.')[0], 10);
|
|
1438
|
+
const errorDisabled = () => {};
|
|
1081
1439
|
|
|
1082
|
-
|
|
1083
|
-
case 'iTerm.app': {
|
|
1084
|
-
return version >= 3 ? 3 : 2;
|
|
1085
|
-
}
|
|
1440
|
+
/* globals WorkerGlobalScope, DedicatedWorkerGlobalScope, SharedWorkerGlobalScope, ServiceWorkerGlobalScope */
|
|
1086
1441
|
|
|
1087
|
-
|
|
1088
|
-
return 2;
|
|
1089
|
-
}
|
|
1090
|
-
// No default
|
|
1091
|
-
}
|
|
1092
|
-
}
|
|
1442
|
+
const isBrowser = globalThis.window?.document !== undefined;
|
|
1093
1443
|
|
|
1094
|
-
|
|
1095
|
-
return 2;
|
|
1096
|
-
}
|
|
1444
|
+
globalThis.process?.versions?.node !== undefined;
|
|
1097
1445
|
|
|
1098
|
-
|
|
1099
|
-
return 1;
|
|
1100
|
-
}
|
|
1446
|
+
globalThis.process?.versions?.bun !== undefined;
|
|
1101
1447
|
|
|
1102
|
-
|
|
1103
|
-
return 1;
|
|
1104
|
-
}
|
|
1448
|
+
globalThis.Deno?.version?.deno !== undefined;
|
|
1105
1449
|
|
|
1106
|
-
|
|
1107
|
-
}
|
|
1450
|
+
globalThis.process?.versions?.electron !== undefined;
|
|
1108
1451
|
|
|
1109
|
-
|
|
1110
|
-
const level = _supportsColor(stream, {
|
|
1111
|
-
streamIsTTY: stream && stream.isTTY,
|
|
1112
|
-
...options,
|
|
1113
|
-
});
|
|
1452
|
+
globalThis.navigator?.userAgent?.includes('jsdom') === true;
|
|
1114
1453
|
|
|
1115
|
-
|
|
1116
|
-
}
|
|
1454
|
+
typeof WorkerGlobalScope !== 'undefined' && globalThis instanceof WorkerGlobalScope;
|
|
1117
1455
|
|
|
1118
|
-
|
|
1119
|
-
stdout: createSupportsColor({isTTY: tty.isatty(1)}),
|
|
1120
|
-
stderr: createSupportsColor({isTTY: tty.isatty(2)}),
|
|
1121
|
-
});
|
|
1456
|
+
typeof DedicatedWorkerGlobalScope !== 'undefined' && globalThis instanceof DedicatedWorkerGlobalScope;
|
|
1122
1457
|
|
|
1123
|
-
|
|
1124
|
-
// https://github.com/Marak/colors.js/blob/master/lib/styles.js
|
|
1125
|
-
// https://stackoverflow.com/a/75985833/2634179
|
|
1126
|
-
const RESET = "\x1b[0m";
|
|
1458
|
+
typeof SharedWorkerGlobalScope !== 'undefined' && globalThis instanceof SharedWorkerGlobalScope;
|
|
1127
1459
|
|
|
1128
|
-
|
|
1129
|
-
supported: processSupportsBasicColor,
|
|
1130
|
-
|
|
1131
|
-
RED: "\x1b[31m",
|
|
1132
|
-
GREEN: "\x1b[32m",
|
|
1133
|
-
YELLOW: "\x1b[33m",
|
|
1134
|
-
BLUE: "\x1b[34m",
|
|
1135
|
-
MAGENTA: "\x1b[35m",
|
|
1136
|
-
CYAN: "\x1b[36m",
|
|
1137
|
-
GREY: "\x1b[90m",
|
|
1138
|
-
color: (text, ANSI_COLOR) => {
|
|
1139
|
-
return ANSI.supported && ANSI_COLOR ? `${ANSI_COLOR}${text}${RESET}` : text;
|
|
1140
|
-
},
|
|
1460
|
+
typeof ServiceWorkerGlobalScope !== 'undefined' && globalThis instanceof ServiceWorkerGlobalScope;
|
|
1141
1461
|
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
STRIKE: "\x1b[9m",
|
|
1145
|
-
effect: (text, ANSI_EFFECT) => {
|
|
1146
|
-
return ANSI.supported && ANSI_EFFECT
|
|
1147
|
-
? `${ANSI_EFFECT}${text}${RESET}`
|
|
1148
|
-
: text;
|
|
1149
|
-
},
|
|
1150
|
-
};
|
|
1462
|
+
// Note: I'm intentionally not DRYing up the other variables to keep them "lazy".
|
|
1463
|
+
const platform = globalThis.navigator?.userAgentData?.platform;
|
|
1151
1464
|
|
|
1152
|
-
|
|
1153
|
-
//
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
// Check on FORCE_COLOR is to ensure it is prio over GitHub workflow check
|
|
1157
|
-
// in unit test we use process.env.FORCE_COLOR = 'false' to fake
|
|
1158
|
-
// that colors are not supported. Let it have priority
|
|
1159
|
-
process.env.FORCE_COLOR !== "false"
|
|
1160
|
-
) {
|
|
1161
|
-
ANSI.supported = true;
|
|
1162
|
-
}
|
|
1465
|
+
platform === 'macOS'
|
|
1466
|
+
|| globalThis.navigator?.platform === 'MacIntel' // Even on Apple silicon Macs.
|
|
1467
|
+
|| globalThis.navigator?.userAgent?.includes(' Mac ') === true
|
|
1468
|
+
|| globalThis.process?.platform === 'darwin';
|
|
1163
1469
|
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
fps = 20,
|
|
1168
|
-
keepProcessAlive = false,
|
|
1169
|
-
stopOnWriteFromOutside = true,
|
|
1170
|
-
stopOnVerticalOverflow = true,
|
|
1171
|
-
render = () => "",
|
|
1172
|
-
effect = () => {},
|
|
1173
|
-
animated = dynamicLog.stream.isTTY,
|
|
1174
|
-
}) => {
|
|
1175
|
-
let frameIndex = 0;
|
|
1176
|
-
let interval;
|
|
1177
|
-
let running = true;
|
|
1470
|
+
platform === 'Windows'
|
|
1471
|
+
|| globalThis.navigator?.platform === 'Win32'
|
|
1472
|
+
|| globalThis.process?.platform === 'win32';
|
|
1178
1473
|
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1474
|
+
platform === 'Linux'
|
|
1475
|
+
|| globalThis.navigator?.platform?.startsWith('Linux') === true
|
|
1476
|
+
|| globalThis.navigator?.userAgent?.includes(' Linux ') === true
|
|
1477
|
+
|| globalThis.process?.platform === 'linux';
|
|
1182
1478
|
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
return spinner.message;
|
|
1188
|
-
};
|
|
1189
|
-
spinner.update = update;
|
|
1479
|
+
platform === 'Android'
|
|
1480
|
+
|| globalThis.navigator?.platform === 'Android'
|
|
1481
|
+
|| globalThis.navigator?.userAgent?.includes(' Android ') === true
|
|
1482
|
+
|| globalThis.process?.platform === 'android';
|
|
1190
1483
|
|
|
1191
|
-
|
|
1192
|
-
if (animated && ANSI.supported) {
|
|
1193
|
-
running = true;
|
|
1194
|
-
cleanup = effect();
|
|
1195
|
-
dynamicLog.update(update(render()));
|
|
1484
|
+
const ESC = '\u001B[';
|
|
1196
1485
|
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
dynamicLog.update(update(render()));
|
|
1200
|
-
}, 1000 / fps);
|
|
1201
|
-
if (!keepProcessAlive) {
|
|
1202
|
-
interval.unref();
|
|
1203
|
-
}
|
|
1204
|
-
} else {
|
|
1205
|
-
dynamicLog.update(update(render()));
|
|
1206
|
-
}
|
|
1486
|
+
!isBrowser && process$1.env.TERM_PROGRAM === 'Apple_Terminal';
|
|
1487
|
+
const isWindows$3 = !isBrowser && process$1.platform === 'win32';
|
|
1207
1488
|
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
clearInterval(interval);
|
|
1212
|
-
interval = null;
|
|
1213
|
-
}
|
|
1214
|
-
if (cleanup) {
|
|
1215
|
-
cleanup();
|
|
1216
|
-
cleanup = null;
|
|
1217
|
-
}
|
|
1218
|
-
if (dynamicLog && message) {
|
|
1219
|
-
dynamicLog.update(update(message));
|
|
1220
|
-
dynamicLog = null;
|
|
1221
|
-
}
|
|
1222
|
-
};
|
|
1223
|
-
spinner.stop = stop;
|
|
1489
|
+
isBrowser ? () => {
|
|
1490
|
+
throw new Error('`process.cwd()` only works in Node.js, not the browser.');
|
|
1491
|
+
} : process$1.cwd;
|
|
1224
1492
|
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
if (stopOnWriteFromOutside) {
|
|
1229
|
-
dynamicLog.onWriteFromOutside = stop;
|
|
1230
|
-
}
|
|
1493
|
+
const cursorUp = (count = 1) => ESC + count + 'A';
|
|
1494
|
+
|
|
1495
|
+
const cursorLeft = ESC + 'G';
|
|
1231
1496
|
|
|
1232
|
-
|
|
1233
|
-
|
|
1497
|
+
const eraseLines = count => {
|
|
1498
|
+
let clear = '';
|
|
1234
1499
|
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
return decimals.length || 0;
|
|
1239
|
-
};
|
|
1500
|
+
for (let i = 0; i < count; i++) {
|
|
1501
|
+
clear += eraseLine + (i < count - 1 ? cursorUp() : '');
|
|
1502
|
+
}
|
|
1240
1503
|
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
) => {
|
|
1245
|
-
return setDecimalsPrecision(number, {
|
|
1246
|
-
decimals,
|
|
1247
|
-
decimalsWhenSmall,
|
|
1248
|
-
transform: Math.round,
|
|
1249
|
-
});
|
|
1250
|
-
};
|
|
1504
|
+
if (count) {
|
|
1505
|
+
clear += cursorLeft;
|
|
1506
|
+
}
|
|
1251
1507
|
|
|
1252
|
-
|
|
1253
|
-
number,
|
|
1254
|
-
{ decimals = 1, decimalsWhenSmall = decimals } = {},
|
|
1255
|
-
) => {
|
|
1256
|
-
return setDecimalsPrecision(number, {
|
|
1257
|
-
decimals,
|
|
1258
|
-
decimalsWhenSmall,
|
|
1259
|
-
transform: parseInt,
|
|
1260
|
-
});
|
|
1508
|
+
return clear;
|
|
1261
1509
|
};
|
|
1510
|
+
const eraseLine = ESC + '2K';
|
|
1511
|
+
const eraseScreen = ESC + '2J';
|
|
1262
1512
|
|
|
1263
|
-
const
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
) => {
|
|
1271
|
-
if (number === 0) {
|
|
1272
|
-
return 0;
|
|
1273
|
-
}
|
|
1274
|
-
let numberCandidate = Math.abs(number);
|
|
1275
|
-
if (numberCandidate < 1) {
|
|
1276
|
-
const integerGoal = Math.pow(10, decimalsWhenSmall - 1);
|
|
1277
|
-
let i = 1;
|
|
1278
|
-
while (numberCandidate < integerGoal) {
|
|
1279
|
-
numberCandidate *= 10;
|
|
1280
|
-
i *= 10;
|
|
1281
|
-
}
|
|
1282
|
-
const asInteger = transform(numberCandidate);
|
|
1283
|
-
const asFloat = asInteger / i;
|
|
1284
|
-
return number < 0 ? -asFloat : asFloat;
|
|
1285
|
-
}
|
|
1286
|
-
const coef = Math.pow(10, decimals);
|
|
1287
|
-
const numberMultiplied = (number + Number.EPSILON) * coef;
|
|
1288
|
-
const asInteger = transform(numberMultiplied);
|
|
1289
|
-
const asFloat = asInteger / coef;
|
|
1290
|
-
return number < 0 ? -asFloat : asFloat;
|
|
1291
|
-
};
|
|
1513
|
+
const clearTerminal = isWindows$3
|
|
1514
|
+
? `${eraseScreen}${ESC}0f`
|
|
1515
|
+
// 1. Erases the screen (Only done in case `2` is not supported)
|
|
1516
|
+
// 2. Erases the whole screen including scrollback buffer
|
|
1517
|
+
// 3. Moves cursor to the top-left position
|
|
1518
|
+
// More info: https://www.real-world-systems.com/docs/ANSIcode.html
|
|
1519
|
+
: `${eraseScreen}${ESC}3J${ESC}H`;
|
|
1292
1520
|
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
// const numberRoundInt = Math.round(decimalsExp * (number + Number.EPSILON))
|
|
1297
|
-
// const numberRoundFloat = numberRoundInt / decimalsExp
|
|
1298
|
-
// return numberRoundFloat
|
|
1299
|
-
// }
|
|
1521
|
+
/*
|
|
1522
|
+
* see also https://github.com/vadimdemedes/ink
|
|
1523
|
+
*/
|
|
1300
1524
|
|
|
1301
|
-
// export const setPrecision = (number, precision) => {
|
|
1302
|
-
// if (Math.floor(number) === number) return number
|
|
1303
|
-
// const [int, decimals] = number.toString().split(".")
|
|
1304
|
-
// if (precision <= 0) return int
|
|
1305
|
-
// const numberTruncated = `${int}.${decimals.slice(0, precision)}`
|
|
1306
|
-
// return numberTruncated
|
|
1307
|
-
// }
|
|
1308
1525
|
|
|
1309
|
-
const
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1526
|
+
const createDynamicLog = ({
|
|
1527
|
+
stream = process.stdout,
|
|
1528
|
+
clearTerminalAllowed,
|
|
1529
|
+
onVerticalOverflow = () => {},
|
|
1530
|
+
onWriteFromOutside = () => {},
|
|
1531
|
+
} = {}) => {
|
|
1532
|
+
const { columns = 80, rows = 24 } = stream;
|
|
1533
|
+
const dynamicLog = {
|
|
1534
|
+
destroyed: false,
|
|
1535
|
+
onVerticalOverflow,
|
|
1536
|
+
onWriteFromOutside,
|
|
1537
|
+
};
|
|
1318
1538
|
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
// ignore ms below meaningfulMs so that:
|
|
1324
|
-
// humanizeDuration(0.5) -> "0 second"
|
|
1325
|
-
// humanizeDuration(1.1) -> "0.001 second" (and not "0.0011 second")
|
|
1326
|
-
// This tool is meant to be read by humans and it would be barely readable to see
|
|
1327
|
-
// "0.0001 second" (stands for 0.1 millisecond)
|
|
1328
|
-
// yes we could return "0.1 millisecond" but we choosed consistency over precision
|
|
1329
|
-
// so that the prefered unit is "second" (and does not become millisecond when ms is super small)
|
|
1330
|
-
if (ms < 1) {
|
|
1331
|
-
return short ? "0s" : "0 second";
|
|
1332
|
-
}
|
|
1333
|
-
const { primary, remaining } = parseMs(ms);
|
|
1334
|
-
if (!remaining) {
|
|
1335
|
-
return humanizeDurationUnit(primary, {
|
|
1336
|
-
decimals:
|
|
1337
|
-
decimals === undefined ? (primary.name === "second" ? 1 : 0) : decimals,
|
|
1338
|
-
short,
|
|
1339
|
-
rounded,
|
|
1340
|
-
});
|
|
1341
|
-
}
|
|
1342
|
-
return `${humanizeDurationUnit(primary, {
|
|
1343
|
-
decimals: decimals === undefined ? 0 : decimals,
|
|
1344
|
-
short,
|
|
1345
|
-
rounded,
|
|
1346
|
-
})} and ${humanizeDurationUnit(remaining, {
|
|
1347
|
-
decimals: decimals === undefined ? 0 : decimals,
|
|
1348
|
-
short,
|
|
1349
|
-
rounded,
|
|
1350
|
-
})}`;
|
|
1351
|
-
};
|
|
1352
|
-
const humanizeDurationUnit = (unit, { decimals, short, rounded }) => {
|
|
1353
|
-
const count = rounded
|
|
1354
|
-
? setRoundedPrecision(unit.count, { decimals })
|
|
1355
|
-
: setPrecision(unit.count, { decimals });
|
|
1356
|
-
let name = unit.name;
|
|
1357
|
-
if (short) {
|
|
1358
|
-
name = unitShort[name];
|
|
1359
|
-
return `${count}${name}`;
|
|
1360
|
-
}
|
|
1361
|
-
if (count <= 1) {
|
|
1362
|
-
return `${count} ${name}`;
|
|
1363
|
-
}
|
|
1364
|
-
return `${count} ${name}s`;
|
|
1365
|
-
};
|
|
1366
|
-
const MS_PER_UNITS = {
|
|
1367
|
-
year: 31_557_600_000,
|
|
1368
|
-
month: 2_629_000_000,
|
|
1369
|
-
week: 604_800_000,
|
|
1370
|
-
day: 86_400_000,
|
|
1371
|
-
hour: 3_600_000,
|
|
1372
|
-
minute: 60_000,
|
|
1373
|
-
second: 1000,
|
|
1374
|
-
};
|
|
1539
|
+
let lastOutput = "";
|
|
1540
|
+
let lastOutputFromOutside = "";
|
|
1541
|
+
let clearAttemptResult;
|
|
1542
|
+
let writing = false;
|
|
1375
1543
|
|
|
1376
|
-
const
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
let firstUnitCount = ms / MS_PER_UNITS[smallestUnitName];
|
|
1381
|
-
const firstUnitIndex = unitNames.findIndex((unitName) => {
|
|
1382
|
-
if (unitName === smallestUnitName) {
|
|
1383
|
-
return false;
|
|
1544
|
+
const getErasePreviousOutput = () => {
|
|
1545
|
+
// nothing to clear
|
|
1546
|
+
if (!lastOutput) {
|
|
1547
|
+
return "";
|
|
1384
1548
|
}
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
if (unitCount) {
|
|
1388
|
-
firstUnitName = unitName;
|
|
1389
|
-
firstUnitCount = unitCount;
|
|
1390
|
-
return true;
|
|
1549
|
+
if (clearAttemptResult !== undefined) {
|
|
1550
|
+
return "";
|
|
1391
1551
|
}
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
}
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
count: firstUnitCount,
|
|
1422
|
-
},
|
|
1423
|
-
remaining: {
|
|
1424
|
-
name: remainingUnitName,
|
|
1425
|
-
count: remainingUnitCount,
|
|
1426
|
-
},
|
|
1552
|
+
|
|
1553
|
+
const logLines = lastOutput.split(/\r\n|\r|\n/);
|
|
1554
|
+
let visualLineCount = 0;
|
|
1555
|
+
for (const logLine of logLines) {
|
|
1556
|
+
const width = stringWidth(logLine);
|
|
1557
|
+
if (width === 0) {
|
|
1558
|
+
visualLineCount++;
|
|
1559
|
+
} else {
|
|
1560
|
+
visualLineCount += Math.ceil(width / columns);
|
|
1561
|
+
}
|
|
1562
|
+
}
|
|
1563
|
+
|
|
1564
|
+
if (visualLineCount > rows) {
|
|
1565
|
+
if (clearTerminalAllowed) {
|
|
1566
|
+
clearAttemptResult = true;
|
|
1567
|
+
return clearTerminal;
|
|
1568
|
+
}
|
|
1569
|
+
// the whole log cannot be cleared because it's vertically to long
|
|
1570
|
+
// (longer than terminal height)
|
|
1571
|
+
// readline.moveCursor cannot move cursor higher than screen height
|
|
1572
|
+
// it means we would only clear the visible part of the log
|
|
1573
|
+
// better keep the log untouched
|
|
1574
|
+
clearAttemptResult = false;
|
|
1575
|
+
dynamicLog.onVerticalOverflow();
|
|
1576
|
+
return "";
|
|
1577
|
+
}
|
|
1578
|
+
|
|
1579
|
+
clearAttemptResult = true;
|
|
1580
|
+
return eraseLines(visualLineCount);
|
|
1427
1581
|
};
|
|
1428
|
-
};
|
|
1429
|
-
|
|
1430
|
-
function isUnicodeSupported() {
|
|
1431
|
-
if (process$1.platform !== 'win32') {
|
|
1432
|
-
return process$1.env.TERM !== 'linux'; // Linux console (kernel)
|
|
1433
|
-
}
|
|
1434
1582
|
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1583
|
+
const update = (string) => {
|
|
1584
|
+
if (dynamicLog.destroyed) {
|
|
1585
|
+
throw new Error("Cannot write log after destroy");
|
|
1586
|
+
}
|
|
1587
|
+
let stringToWrite = string;
|
|
1588
|
+
if (lastOutput) {
|
|
1589
|
+
if (lastOutputFromOutside) {
|
|
1590
|
+
// We don't want to clear logs written by other code,
|
|
1591
|
+
// it makes output unreadable and might erase precious information
|
|
1592
|
+
// To detect this we put a spy on the stream.
|
|
1593
|
+
// The spy is required only if we actually wrote something in the stream
|
|
1594
|
+
// something else than this code has written in the stream
|
|
1595
|
+
// so we just write without clearing (append instead of replacing)
|
|
1596
|
+
lastOutput = "";
|
|
1597
|
+
lastOutputFromOutside = "";
|
|
1598
|
+
} else {
|
|
1599
|
+
stringToWrite = `${getErasePreviousOutput()}${string}`;
|
|
1600
|
+
}
|
|
1601
|
+
}
|
|
1602
|
+
writing = true;
|
|
1603
|
+
stream.write(stringToWrite);
|
|
1604
|
+
lastOutput = string;
|
|
1605
|
+
writing = false;
|
|
1606
|
+
clearAttemptResult = undefined;
|
|
1607
|
+
};
|
|
1444
1608
|
|
|
1445
|
-
|
|
1609
|
+
const clearDuringFunctionCall = (
|
|
1610
|
+
callback,
|
|
1611
|
+
ouputAfterCallback = lastOutput,
|
|
1612
|
+
) => {
|
|
1613
|
+
// 1. Erase the current log
|
|
1614
|
+
// 2. Call callback (expect to write something on stdout)
|
|
1615
|
+
// 3. Restore the current log
|
|
1616
|
+
// During step 2. we expect a "write from outside" so we uninstall
|
|
1617
|
+
// the stream spy during function call
|
|
1618
|
+
update("");
|
|
1446
1619
|
|
|
1620
|
+
writing = true;
|
|
1621
|
+
callback();
|
|
1622
|
+
writing = false;
|
|
1447
1623
|
|
|
1448
|
-
|
|
1449
|
-
|
|
1624
|
+
update(ouputAfterCallback);
|
|
1625
|
+
};
|
|
1450
1626
|
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
}
|
|
1463
|
-
get INFO_RAW() {
|
|
1464
|
-
return UNICODE.supported ? `ℹ` : `i`;
|
|
1465
|
-
},
|
|
1466
|
-
get WARNING_RAW() {
|
|
1467
|
-
return UNICODE.supported ? `⚠` : `‼`;
|
|
1468
|
-
},
|
|
1469
|
-
get CIRCLE_CROSS_RAW() {
|
|
1470
|
-
return UNICODE.supported ? `ⓧ` : `(×)`;
|
|
1471
|
-
},
|
|
1472
|
-
get COMMAND() {
|
|
1473
|
-
return ANSI.color(UNICODE.COMMAND_RAW, ANSI.GREY); // ANSI_MAGENTA)
|
|
1474
|
-
},
|
|
1475
|
-
get OK() {
|
|
1476
|
-
return ANSI.color(UNICODE.OK_RAW, ANSI.GREEN);
|
|
1477
|
-
},
|
|
1478
|
-
get FAILURE() {
|
|
1479
|
-
return ANSI.color(UNICODE.FAILURE_RAW, ANSI.RED);
|
|
1480
|
-
},
|
|
1481
|
-
get DEBUG() {
|
|
1482
|
-
return ANSI.color(UNICODE.DEBUG_RAW, ANSI.GREY);
|
|
1483
|
-
},
|
|
1484
|
-
get INFO() {
|
|
1485
|
-
return ANSI.color(UNICODE.INFO_RAW, ANSI.BLUE);
|
|
1486
|
-
},
|
|
1487
|
-
get WARNING() {
|
|
1488
|
-
return ANSI.color(UNICODE.WARNING_RAW, ANSI.YELLOW);
|
|
1489
|
-
},
|
|
1490
|
-
get CIRCLE_CROSS() {
|
|
1491
|
-
return ANSI.color(UNICODE.CIRCLE_CROSS_RAW, ANSI.RED);
|
|
1492
|
-
},
|
|
1493
|
-
get ELLIPSIS() {
|
|
1494
|
-
return UNICODE.supported ? `…` : `...`;
|
|
1495
|
-
},
|
|
1496
|
-
};
|
|
1627
|
+
const writeFromOutsideEffect = (value) => {
|
|
1628
|
+
if (!lastOutput) {
|
|
1629
|
+
// we don't care if the log never wrote anything
|
|
1630
|
+
// or if last update() wrote an empty string
|
|
1631
|
+
return;
|
|
1632
|
+
}
|
|
1633
|
+
if (writing) {
|
|
1634
|
+
return;
|
|
1635
|
+
}
|
|
1636
|
+
lastOutputFromOutside = value;
|
|
1637
|
+
dynamicLog.onWriteFromOutside(value);
|
|
1638
|
+
};
|
|
1497
1639
|
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1640
|
+
let removeStreamSpy;
|
|
1641
|
+
if (stream === process.stdout) {
|
|
1642
|
+
const removeStdoutSpy = spyStreamOutput(
|
|
1643
|
+
process.stdout,
|
|
1644
|
+
writeFromOutsideEffect,
|
|
1645
|
+
);
|
|
1646
|
+
const removeStderrSpy = spyStreamOutput(
|
|
1647
|
+
process.stderr,
|
|
1648
|
+
writeFromOutsideEffect,
|
|
1649
|
+
);
|
|
1650
|
+
removeStreamSpy = () => {
|
|
1651
|
+
removeStdoutSpy();
|
|
1652
|
+
removeStderrSpy();
|
|
1508
1653
|
};
|
|
1654
|
+
} else {
|
|
1655
|
+
removeStreamSpy = spyStreamOutput(stream, writeFromOutsideEffect);
|
|
1509
1656
|
}
|
|
1510
|
-
const startMs = Date.now();
|
|
1511
|
-
const dynamicLog = createDynamicLog();
|
|
1512
|
-
let message = label;
|
|
1513
|
-
const taskSpinner = startSpinner({
|
|
1514
|
-
dynamicLog,
|
|
1515
|
-
render: () => message,
|
|
1516
|
-
stopOnWriteFromOutside,
|
|
1517
|
-
animated,
|
|
1518
|
-
});
|
|
1519
|
-
return {
|
|
1520
|
-
setRightText: (value) => {
|
|
1521
|
-
message = `${label} ${value}`;
|
|
1522
|
-
},
|
|
1523
|
-
done: () => {
|
|
1524
|
-
const msEllapsed = Date.now() - startMs;
|
|
1525
|
-
taskSpinner.stop(
|
|
1526
|
-
`${UNICODE.OK} ${label} (done in ${humanizeDuration(msEllapsed)})`,
|
|
1527
|
-
);
|
|
1528
|
-
},
|
|
1529
|
-
happen: (message) => {
|
|
1530
|
-
taskSpinner.stop(
|
|
1531
|
-
`${UNICODE.INFO} ${message} (at ${new Date().toLocaleTimeString()})`,
|
|
1532
|
-
);
|
|
1533
|
-
},
|
|
1534
|
-
fail: (message = `failed to ${label}`) => {
|
|
1535
|
-
taskSpinner.stop(`${UNICODE.FAILURE} ${message}`);
|
|
1536
|
-
},
|
|
1537
|
-
};
|
|
1538
|
-
};
|
|
1539
1657
|
|
|
1540
|
-
const
|
|
1541
|
-
|
|
1658
|
+
const destroy = () => {
|
|
1659
|
+
dynamicLog.destroyed = true;
|
|
1660
|
+
if (removeStreamSpy) {
|
|
1661
|
+
removeStreamSpy();
|
|
1662
|
+
removeStreamSpy = null;
|
|
1663
|
+
lastOutput = "";
|
|
1664
|
+
lastOutputFromOutside = "";
|
|
1665
|
+
}
|
|
1666
|
+
};
|
|
1542
1667
|
|
|
1543
|
-
Object.
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
Array.isArray(value)
|
|
1549
|
-
? value.join(`
|
|
1550
|
-
`)
|
|
1551
|
-
: value
|
|
1552
|
-
}`;
|
|
1668
|
+
Object.assign(dynamicLog, {
|
|
1669
|
+
update,
|
|
1670
|
+
destroy,
|
|
1671
|
+
stream,
|
|
1672
|
+
clearDuringFunctionCall,
|
|
1553
1673
|
});
|
|
1554
|
-
|
|
1555
|
-
return string;
|
|
1674
|
+
return dynamicLog;
|
|
1556
1675
|
};
|
|
1557
1676
|
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1677
|
+
// maybe https://github.com/gajus/output-interceptor/tree/v3.0.0 ?
|
|
1678
|
+
// the problem with listening data on stdout
|
|
1679
|
+
// is that node.js will later throw error if stream gets closed
|
|
1680
|
+
// while something listening data on it
|
|
1681
|
+
const spyStreamOutput = (stream, callback) => {
|
|
1682
|
+
const originalWrite = stream.write;
|
|
1561
1683
|
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
{ fixedDecimals = false, decimals, short } = {},
|
|
1565
|
-
) => {
|
|
1566
|
-
if (number === 0) {
|
|
1567
|
-
return `0 B`;
|
|
1568
|
-
}
|
|
1569
|
-
const exponent = Math.min(
|
|
1570
|
-
Math.floor(Math.log10(number) / 3),
|
|
1571
|
-
BYTE_UNITS.length - 1,
|
|
1572
|
-
);
|
|
1573
|
-
const unitNumber = number / Math.pow(1000, exponent);
|
|
1574
|
-
const unitName = BYTE_UNITS[exponent];
|
|
1575
|
-
if (decimals === undefined) {
|
|
1576
|
-
if (unitNumber < 100) {
|
|
1577
|
-
decimals = 1;
|
|
1578
|
-
} else {
|
|
1579
|
-
decimals = 0;
|
|
1580
|
-
}
|
|
1581
|
-
}
|
|
1582
|
-
const unitNumberRounded = setRoundedPrecision(unitNumber, {
|
|
1583
|
-
decimals,
|
|
1584
|
-
decimalsWhenSmall: 1,
|
|
1585
|
-
});
|
|
1586
|
-
const value = fixedDecimals
|
|
1587
|
-
? unitNumberRounded.toFixed(decimals)
|
|
1588
|
-
: unitNumberRounded;
|
|
1589
|
-
if (short) {
|
|
1590
|
-
return `${value}${unitName}`;
|
|
1591
|
-
}
|
|
1592
|
-
return `${value} ${unitName}`;
|
|
1593
|
-
};
|
|
1684
|
+
let output = "";
|
|
1685
|
+
let installed = true;
|
|
1594
1686
|
|
|
1595
|
-
|
|
1687
|
+
stream.write = function (...args /* chunk, encoding, callback */) {
|
|
1688
|
+
output += args;
|
|
1689
|
+
callback(output);
|
|
1690
|
+
return originalWrite.call(stream, ...args);
|
|
1691
|
+
};
|
|
1596
1692
|
|
|
1597
|
-
const
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
}
|
|
1605
|
-
if (numberNames.length === 1) {
|
|
1606
|
-
const firstNumberName = numberNames[0];
|
|
1607
|
-
return { [firstNumberName]: "100 %" };
|
|
1608
|
-
}
|
|
1609
|
-
const numbers = numberNames.map((name) => namedNumbers[name]);
|
|
1610
|
-
const total = numbers.reduce((sum, value) => sum + value, 0);
|
|
1611
|
-
const ratios = numbers.map((number) => number / total);
|
|
1612
|
-
const percentages = {};
|
|
1613
|
-
ratios.pop();
|
|
1614
|
-
ratios.forEach((ratio, index) => {
|
|
1615
|
-
const percentage = ratio * 100;
|
|
1616
|
-
percentages[numberNames[index]] = percentage;
|
|
1617
|
-
});
|
|
1618
|
-
const lowestPercentage = (1 / Math.pow(10, maxPrecisionHint)) * 100;
|
|
1619
|
-
let precision = 0;
|
|
1620
|
-
Object.keys(percentages).forEach((name) => {
|
|
1621
|
-
const percentage = percentages[name];
|
|
1622
|
-
if (percentage < lowestPercentage) {
|
|
1623
|
-
// check the amout of meaningful decimals
|
|
1624
|
-
// and that what we will use
|
|
1625
|
-
const percentageRounded = setRoundedPrecision(percentage);
|
|
1626
|
-
const percentagePrecision = getPrecision(percentageRounded);
|
|
1627
|
-
if (percentagePrecision > precision) {
|
|
1628
|
-
precision = percentagePrecision;
|
|
1629
|
-
}
|
|
1630
|
-
}
|
|
1631
|
-
});
|
|
1632
|
-
let remainingPercentage = 100;
|
|
1693
|
+
const uninstall = () => {
|
|
1694
|
+
if (!installed) {
|
|
1695
|
+
return;
|
|
1696
|
+
}
|
|
1697
|
+
stream.write = originalWrite;
|
|
1698
|
+
installed = false;
|
|
1699
|
+
};
|
|
1633
1700
|
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
});
|
|
1639
|
-
remainingPercentage -= percentageAllocated;
|
|
1640
|
-
percentages[name] = percentageAllocated;
|
|
1641
|
-
});
|
|
1642
|
-
const lastName = numberNames[numberNames.length - 1];
|
|
1643
|
-
percentages[lastName] = setRoundedPrecision(remainingPercentage, {
|
|
1644
|
-
decimals: precision,
|
|
1645
|
-
});
|
|
1646
|
-
return percentages;
|
|
1701
|
+
return () => {
|
|
1702
|
+
uninstall();
|
|
1703
|
+
return output;
|
|
1704
|
+
};
|
|
1647
1705
|
};
|
|
1648
1706
|
|
|
1649
|
-
const
|
|
1707
|
+
const startSpinner = ({
|
|
1708
|
+
dynamicLog,
|
|
1709
|
+
frames = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"],
|
|
1710
|
+
fps = 20,
|
|
1711
|
+
keepProcessAlive = false,
|
|
1712
|
+
stopOnWriteFromOutside = true,
|
|
1713
|
+
stopOnVerticalOverflow = true,
|
|
1714
|
+
render = () => "",
|
|
1715
|
+
effect = () => {},
|
|
1716
|
+
animated = dynamicLog.stream.isTTY,
|
|
1717
|
+
}) => {
|
|
1718
|
+
let frameIndex = 0;
|
|
1719
|
+
let interval;
|
|
1720
|
+
let running = true;
|
|
1650
1721
|
|
|
1651
|
-
const
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
column,
|
|
1722
|
+
const spinner = {
|
|
1723
|
+
message: undefined,
|
|
1724
|
+
};
|
|
1655
1725
|
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
} = {}) => {
|
|
1664
|
-
const lineStrings = content.split(/\r?\n/);
|
|
1665
|
-
if (line === 0) line = 1;
|
|
1666
|
-
if (column === undefined) {
|
|
1667
|
-
columnMarker = false;
|
|
1668
|
-
column = 1;
|
|
1669
|
-
}
|
|
1670
|
-
if (column === 0) column = 1;
|
|
1726
|
+
const update = (message) => {
|
|
1727
|
+
spinner.message = running
|
|
1728
|
+
? `${frames[frameIndex]} ${message}\n`
|
|
1729
|
+
: `${message}\n`;
|
|
1730
|
+
return spinner.message;
|
|
1731
|
+
};
|
|
1732
|
+
spinner.update = update;
|
|
1671
1733
|
|
|
1672
|
-
let
|
|
1673
|
-
if (
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
if (lineEndIndex > lineStrings.length - 1) {
|
|
1678
|
-
lineEndIndex = lineStrings.length - 1;
|
|
1679
|
-
}
|
|
1680
|
-
if (columnMarker) {
|
|
1681
|
-
// human reader deduce the line when there is a column marker
|
|
1682
|
-
lineMarker = false;
|
|
1683
|
-
}
|
|
1684
|
-
if (line - 1 === lineEndIndex) {
|
|
1685
|
-
lineMarker = false; // useless because last line
|
|
1686
|
-
}
|
|
1687
|
-
let lineIndex = lineStartIndex;
|
|
1734
|
+
let cleanup;
|
|
1735
|
+
if (animated && ANSI.supported) {
|
|
1736
|
+
running = true;
|
|
1737
|
+
cleanup = effect();
|
|
1738
|
+
dynamicLog.update(update(render()));
|
|
1688
1739
|
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1740
|
+
interval = setInterval(() => {
|
|
1741
|
+
frameIndex = frameIndex === frames.length - 1 ? 0 : frameIndex + 1;
|
|
1742
|
+
dynamicLog.update(update(render()));
|
|
1743
|
+
}, 1000 / fps);
|
|
1744
|
+
if (!keepProcessAlive) {
|
|
1745
|
+
interval.unref();
|
|
1746
|
+
}
|
|
1694
1747
|
} else {
|
|
1695
|
-
|
|
1696
|
-
columnsAfter = lineMaxWidth;
|
|
1748
|
+
dynamicLog.update(update(render()));
|
|
1697
1749
|
}
|
|
1698
|
-
let columnMarkerIndex = column - 1 - columnsBefore;
|
|
1699
|
-
|
|
1700
|
-
let source = "";
|
|
1701
|
-
while (lineIndex <= lineEndIndex) {
|
|
1702
|
-
const lineString = lineStrings[lineIndex];
|
|
1703
|
-
const lineNumber = lineIndex + 1;
|
|
1704
|
-
const isLastLine = lineIndex === lineEndIndex;
|
|
1705
|
-
const isMainLine = lineNumber === line;
|
|
1706
|
-
lineIndex++;
|
|
1707
1750
|
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
source += " ";
|
|
1714
|
-
}
|
|
1715
|
-
}
|
|
1716
|
-
if (lineNumbersOnTheLeft) {
|
|
1717
|
-
// fill with spaces to ensure if line moves from 7,8,9 to 10 the display is still great
|
|
1718
|
-
const asideSource = `${fillLeft(lineNumber, lineEndIndex + 1)} |`;
|
|
1719
|
-
source += `${format(asideSource, "line_number_aside")} `;
|
|
1720
|
-
}
|
|
1721
|
-
}
|
|
1722
|
-
{
|
|
1723
|
-
source += truncateLine(lineString, {
|
|
1724
|
-
start: columnsBefore,
|
|
1725
|
-
end: columnsAfter,
|
|
1726
|
-
prefix: "…",
|
|
1727
|
-
suffix: "…",
|
|
1728
|
-
format,
|
|
1729
|
-
});
|
|
1751
|
+
const stop = (message) => {
|
|
1752
|
+
running = false;
|
|
1753
|
+
if (interval) {
|
|
1754
|
+
clearInterval(interval);
|
|
1755
|
+
interval = null;
|
|
1730
1756
|
}
|
|
1731
|
-
{
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
if (lineMarker) {
|
|
1735
|
-
source += " ";
|
|
1736
|
-
}
|
|
1737
|
-
if (lineNumbersOnTheLeft) {
|
|
1738
|
-
const asideSpaces = `${fillLeft(lineNumber, lineEndIndex + 1)} | `
|
|
1739
|
-
.length;
|
|
1740
|
-
source += " ".repeat(asideSpaces);
|
|
1741
|
-
}
|
|
1742
|
-
source += " ".repeat(columnMarkerIndex);
|
|
1743
|
-
source += format("^", "marker_column");
|
|
1744
|
-
}
|
|
1757
|
+
if (cleanup) {
|
|
1758
|
+
cleanup();
|
|
1759
|
+
cleanup = null;
|
|
1745
1760
|
}
|
|
1746
|
-
if (
|
|
1747
|
-
|
|
1761
|
+
if (dynamicLog && message) {
|
|
1762
|
+
dynamicLog.update(update(message));
|
|
1763
|
+
dynamicLog = null;
|
|
1748
1764
|
}
|
|
1749
|
-
}
|
|
1750
|
-
|
|
1751
|
-
};
|
|
1752
|
-
|
|
1753
|
-
const truncateLine = (line, { start, end, prefix, suffix, format }) => {
|
|
1754
|
-
const lastIndex = line.length;
|
|
1755
|
-
|
|
1756
|
-
if (line.length === 0) {
|
|
1757
|
-
// don't show any ellipsis if the line is empty
|
|
1758
|
-
// because it's not truncated in that case
|
|
1759
|
-
return "";
|
|
1760
|
-
}
|
|
1761
|
-
|
|
1762
|
-
const startTruncated = start > 0;
|
|
1763
|
-
const endTruncated = lastIndex > end;
|
|
1764
|
-
|
|
1765
|
-
let from = startTruncated ? start + prefix.length : start;
|
|
1766
|
-
let to = endTruncated ? end - suffix.length : end;
|
|
1767
|
-
if (to > lastIndex) to = lastIndex;
|
|
1765
|
+
};
|
|
1766
|
+
spinner.stop = stop;
|
|
1768
1767
|
|
|
1769
|
-
if (
|
|
1770
|
-
|
|
1771
|
-
}
|
|
1772
|
-
let result = "";
|
|
1773
|
-
while (from < to) {
|
|
1774
|
-
result += format(line[from], "char");
|
|
1775
|
-
from++;
|
|
1776
|
-
}
|
|
1777
|
-
if (result.length === 0) {
|
|
1778
|
-
return "";
|
|
1779
|
-
}
|
|
1780
|
-
if (startTruncated && endTruncated) {
|
|
1781
|
-
return `${format(prefix, "marker_overflow_left")}${result}${format(
|
|
1782
|
-
suffix,
|
|
1783
|
-
"marker_overflow_right",
|
|
1784
|
-
)}`;
|
|
1785
|
-
}
|
|
1786
|
-
if (startTruncated) {
|
|
1787
|
-
return `${format(prefix, "marker_overflow_left")}${result}`;
|
|
1768
|
+
if (stopOnVerticalOverflow) {
|
|
1769
|
+
dynamicLog.onVerticalOverflow = stop;
|
|
1788
1770
|
}
|
|
1789
|
-
if (
|
|
1790
|
-
|
|
1771
|
+
if (stopOnWriteFromOutside) {
|
|
1772
|
+
dynamicLog.onWriteFromOutside = stop;
|
|
1791
1773
|
}
|
|
1792
|
-
|
|
1774
|
+
|
|
1775
|
+
return spinner;
|
|
1793
1776
|
};
|
|
1794
1777
|
|
|
1795
|
-
const
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1778
|
+
const createTaskLog = (
|
|
1779
|
+
label,
|
|
1780
|
+
{ disabled = false, animated = true, stopOnWriteFromOutside } = {},
|
|
1781
|
+
) => {
|
|
1782
|
+
if (disabled) {
|
|
1783
|
+
return {
|
|
1784
|
+
setRightText: () => {},
|
|
1785
|
+
done: () => {},
|
|
1786
|
+
happen: () => {},
|
|
1787
|
+
fail: () => {},
|
|
1788
|
+
};
|
|
1802
1789
|
}
|
|
1803
|
-
|
|
1804
|
-
|
|
1790
|
+
const startMs = Date.now();
|
|
1791
|
+
const dynamicLog = createDynamicLog();
|
|
1792
|
+
let message = label;
|
|
1793
|
+
const taskSpinner = startSpinner({
|
|
1794
|
+
dynamicLog,
|
|
1795
|
+
render: () => message,
|
|
1796
|
+
stopOnWriteFromOutside,
|
|
1797
|
+
animated,
|
|
1798
|
+
});
|
|
1799
|
+
return {
|
|
1800
|
+
setRightText: (value) => {
|
|
1801
|
+
message = `${label} ${value}`;
|
|
1802
|
+
},
|
|
1803
|
+
done: () => {
|
|
1804
|
+
const msEllapsed = Date.now() - startMs;
|
|
1805
|
+
taskSpinner.stop(
|
|
1806
|
+
`${UNICODE.OK} ${label} (done in ${humanizeDuration(msEllapsed)})`,
|
|
1807
|
+
);
|
|
1808
|
+
},
|
|
1809
|
+
happen: (message) => {
|
|
1810
|
+
taskSpinner.stop(
|
|
1811
|
+
`${UNICODE.INFO} ${message} (at ${new Date().toLocaleTimeString()})`,
|
|
1812
|
+
);
|
|
1813
|
+
},
|
|
1814
|
+
fail: (message = `failed to ${label}`) => {
|
|
1815
|
+
taskSpinner.stop(`${UNICODE.FAILURE} ${message}`);
|
|
1816
|
+
},
|
|
1817
|
+
};
|
|
1805
1818
|
};
|
|
1806
1819
|
|
|
1807
1820
|
// consider switching to https://babeljs.io/docs/en/babel-code-frame
|