@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.
Files changed (3) hide show
  1. package/README.md +5 -5
  2. package/dist/jsenv_core.js +1076 -1063
  3. package/package.json +15 -15
@@ -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 LOG_LEVEL_OFF = "off";
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
- const LOG_LEVEL_ERROR = "error";
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
- const createLogger = ({ logLevel = LOG_LEVEL_INFO } = {}) => {
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
- const debug = (...args) => console.debug(...args);
664
-
665
- const debugDisabled = () => {};
666
-
667
- const info = (...args) => console.info(...args);
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 infoDisabled = () => {};
618
+ const {env} = process$1;
670
619
 
671
- const warn = (...args) => console.warn(...args);
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
- const warnDisabled = () => {};
637
+ function envForceColor() {
638
+ if ('FORCE_COLOR' in env) {
639
+ if (env.FORCE_COLOR === 'true') {
640
+ return 1;
641
+ }
674
642
 
675
- const error = (...args) => console.error(...args);
643
+ if (env.FORCE_COLOR === 'false') {
644
+ return 0;
645
+ }
676
646
 
677
- const errorDisabled = () => {};
647
+ return env.FORCE_COLOR.length === 0 ? 1 : Math.min(Number.parseInt(env.FORCE_COLOR, 10), 3);
648
+ }
649
+ }
678
650
 
679
- /* globals WorkerGlobalScope, DedicatedWorkerGlobalScope, SharedWorkerGlobalScope, ServiceWorkerGlobalScope */
651
+ function translateLevel(level) {
652
+ if (level === 0) {
653
+ return false;
654
+ }
680
655
 
681
- const isBrowser = globalThis.window?.document !== undefined;
656
+ return {
657
+ level,
658
+ hasBasic: true,
659
+ has256: level >= 2,
660
+ has16m: level >= 3,
661
+ };
662
+ }
682
663
 
683
- globalThis.process?.versions?.node !== undefined;
664
+ function _supportsColor(haveStream, {streamIsTTY, sniffFlags = true} = {}) {
665
+ const noFlagForceColor = envForceColor();
666
+ if (noFlagForceColor !== undefined) {
667
+ flagForceColor = noFlagForceColor;
668
+ }
684
669
 
685
- globalThis.process?.versions?.bun !== undefined;
670
+ const forceColor = sniffFlags ? flagForceColor : noFlagForceColor;
686
671
 
687
- globalThis.Deno?.version?.deno !== undefined;
672
+ if (forceColor === 0) {
673
+ return 0;
674
+ }
688
675
 
689
- globalThis.process?.versions?.electron !== undefined;
676
+ if (sniffFlags) {
677
+ if (hasFlag('color=16m')
678
+ || hasFlag('color=full')
679
+ || hasFlag('color=truecolor')) {
680
+ return 3;
681
+ }
690
682
 
691
- globalThis.navigator?.userAgent?.includes('jsdom') === true;
683
+ if (hasFlag('color=256')) {
684
+ return 2;
685
+ }
686
+ }
692
687
 
693
- typeof WorkerGlobalScope !== 'undefined' && globalThis instanceof WorkerGlobalScope;
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
- typeof DedicatedWorkerGlobalScope !== 'undefined' && globalThis instanceof DedicatedWorkerGlobalScope;
694
+ if (haveStream && !streamIsTTY && forceColor === undefined) {
695
+ return 0;
696
+ }
696
697
 
697
- typeof SharedWorkerGlobalScope !== 'undefined' && globalThis instanceof SharedWorkerGlobalScope;
698
+ const min = forceColor || 0;
698
699
 
699
- typeof ServiceWorkerGlobalScope !== 'undefined' && globalThis instanceof ServiceWorkerGlobalScope;
700
+ if (env.TERM === 'dumb') {
701
+ return min;
702
+ }
700
703
 
701
- // Note: I'm intentionally not DRYing up the other variables to keep them "lazy".
702
- const platform = globalThis.navigator?.userAgentData?.platform;
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
- platform === 'macOS'
705
- || globalThis.navigator?.platform === 'MacIntel' // Even on Apple silicon Macs.
706
- || globalThis.navigator?.userAgent?.includes(' Mac ') === true
707
- || globalThis.process?.platform === 'darwin';
715
+ return 1;
716
+ }
708
717
 
709
- platform === 'Windows'
710
- || globalThis.navigator?.platform === 'Win32'
711
- || globalThis.process?.platform === 'win32';
718
+ if ('CI' in env) {
719
+ if ('GITHUB_ACTIONS' in env || 'GITEA_ACTIONS' in env) {
720
+ return 3;
721
+ }
712
722
 
713
- platform === 'Linux'
714
- || globalThis.navigator?.platform?.startsWith('Linux') === true
715
- || globalThis.navigator?.userAgent?.includes(' Linux ') === true
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
- platform === 'Android'
719
- || globalThis.navigator?.platform === 'Android'
720
- || globalThis.navigator?.userAgent?.includes(' Android ') === true
721
- || globalThis.process?.platform === 'android';
727
+ return min;
728
+ }
722
729
 
723
- const ESC = '\u001B[';
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
- !isBrowser && process$1.env.TERM_PROGRAM === 'Apple_Terminal';
726
- const isWindows$3 = !isBrowser && process$1.platform === 'win32';
734
+ if (env.COLORTERM === 'truecolor') {
735
+ return 3;
736
+ }
727
737
 
728
- isBrowser ? () => {
729
- throw new Error('`process.cwd()` only works in Node.js, not the browser.');
730
- } : process$1.cwd;
738
+ if (env.TERM === 'xterm-kitty') {
739
+ return 3;
740
+ }
731
741
 
732
- const cursorUp = (count = 1) => ESC + count + 'A';
742
+ if ('TERM_PROGRAM' in env) {
743
+ const version = Number.parseInt((env.TERM_PROGRAM_VERSION || '').split('.')[0], 10);
733
744
 
734
- const cursorLeft = ESC + 'G';
745
+ switch (env.TERM_PROGRAM) {
746
+ case 'iTerm.app': {
747
+ return version >= 3 ? 3 : 2;
748
+ }
735
749
 
736
- const eraseLines = count => {
737
- let clear = '';
750
+ case 'Apple_Terminal': {
751
+ return 2;
752
+ }
753
+ // No default
754
+ }
755
+ }
738
756
 
739
- for (let i = 0; i < count; i++) {
740
- clear += eraseLine + (i < count - 1 ? cursorUp() : '');
757
+ if (/-256(color)?$/i.test(env.TERM)) {
758
+ return 2;
741
759
  }
742
760
 
743
- if (count) {
744
- clear += cursorLeft;
761
+ if (/^screen|^xterm|^vt100|^vt220|^rxvt|color|ansi|cygwin|linux/i.test(env.TERM)) {
762
+ return 1;
745
763
  }
746
764
 
747
- return clear;
748
- };
749
- const eraseLine = ESC + '2K';
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
- const createDynamicLog = ({
766
- stream = process.stdout,
767
- clearTerminalAllowed,
768
- onVerticalOverflow = () => {},
769
- onWriteFromOutside = () => {},
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
- let lastOutput = "";
779
- let lastOutputFromOutside = "";
780
- let clearAttemptResult;
781
- let writing = false;
778
+ return translateLevel(level);
779
+ }
782
780
 
783
- const getErasePreviousOutput = () => {
784
- // nothing to clear
785
- if (!lastOutput) {
786
- return "";
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
- const logLines = lastOutput.split(/\r\n|\r|\n/);
793
- let visualLineCount = 0;
794
- for (const logLine of logLines) {
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
- if (visualLineCount > rows) {
804
- if (clearTerminalAllowed) {
805
- clearAttemptResult = true;
806
- return clearTerminal;
807
- }
808
- // the whole log cannot be cleared because it's vertically to long
809
- // (longer than terminal height)
810
- // readline.moveCursor cannot move cursor higher than screen height
811
- // it means we would only clear the visible part of the log
812
- // better keep the log untouched
813
- clearAttemptResult = false;
814
- dynamicLog.onVerticalOverflow();
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
- clearAttemptResult = true;
819
- return eraseLines(visualLineCount);
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
- const update = (string) => {
823
- if (dynamicLog.destroyed) {
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
- const clearDuringFunctionCall = (
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
- writing = true;
860
- callback();
861
- writing = false;
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
- update(ouputAfterCallback);
864
- };
834
+ function isUnicodeSupported() {
835
+ if (process$1.platform !== 'win32') {
836
+ return process$1.env.TERM !== 'linux'; // Linux console (kernel)
837
+ }
865
838
 
866
- const writeFromOutsideEffect = (value) => {
867
- if (!lastOutput) {
868
- // we don't care if the log never wrote anything
869
- // or if last update() wrote an empty string
870
- return;
871
- }
872
- if (writing) {
873
- return;
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
- let removeStreamSpy;
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
- const destroy = () => {
898
- dynamicLog.destroyed = true;
899
- if (removeStreamSpy) {
900
- removeStreamSpy();
901
- removeStreamSpy = null;
902
- lastOutput = "";
903
- lastOutputFromOutside = "";
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
- // maybe https://github.com/gajus/output-interceptor/tree/v3.0.0 ?
917
- // the problem with listening data on stdout
918
- // is that node.js will later throw error if stream gets closed
919
- // while something listening data on it
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
- const uninstall = () => {
933
- if (!installed) {
934
- return;
935
- }
936
- stream.write = originalWrite;
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
- return () => {
941
- uninstall();
942
- return output;
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
- // From: https://github.com/sindresorhus/has-flag/blob/main/index.js
947
- /// function hasFlag(flag, argv = globalThis.Deno?.args ?? process.argv) {
948
- function hasFlag(flag, argv = globalThis.Deno ? globalThis.Deno.args : process$1.argv) {
949
- const prefix = flag.startsWith('-') ? '' : (flag.length === 1 ? '-' : '--');
950
- const position = argv.indexOf(prefix + flag);
951
- const terminatorPosition = argv.indexOf('--');
952
- return position !== -1 && (terminatorPosition === -1 || position < terminatorPosition);
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 {env} = process$1;
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
- let flagForceColor;
958
- if (
959
- hasFlag('no-color')
960
- || hasFlag('no-colors')
961
- || hasFlag('color=false')
962
- || hasFlag('color=never')
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
- function envForceColor() {
975
- if ('FORCE_COLOR' in env) {
976
- if (env.FORCE_COLOR === 'true') {
977
- return 1;
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
- if (env.FORCE_COLOR === 'false') {
981
- return 0;
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
- return env.FORCE_COLOR.length === 0 ? 1 : Math.min(Number.parseInt(env.FORCE_COLOR, 10), 3);
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
- function translateLevel(level) {
989
- if (level === 0) {
990
- return false;
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
- return {
994
- level,
995
- hasBasic: true,
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
- function _supportsColor(haveStream, {streamIsTTY, sniffFlags = true} = {}) {
1002
- const noFlagForceColor = envForceColor();
1003
- if (noFlagForceColor !== undefined) {
1004
- flagForceColor = noFlagForceColor;
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
- const forceColor = sniffFlags ? flagForceColor : noFlagForceColor;
1140
+ const BYTE_UNITS = ["B", "kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"];
1008
1141
 
1009
- if (forceColor === 0) {
1010
- return 0;
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
- if (sniffFlags) {
1014
- if (hasFlag('color=16m')
1015
- || hasFlag('color=full')
1016
- || hasFlag('color=truecolor')) {
1017
- return 3;
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
- if (hasFlag('color=256')) {
1021
- return 2;
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
- // Check for Azure DevOps pipelines.
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
- if (haveStream && !streamIsTTY && forceColor === undefined) {
1032
- return 0;
1033
- }
1356
+ const LOG_LEVEL_INFO = "info";
1034
1357
 
1035
- const min = forceColor || 0;
1358
+ const LOG_LEVEL_WARN = "warn";
1036
1359
 
1037
- if (env.TERM === 'dumb') {
1038
- return min;
1039
- }
1360
+ const LOG_LEVEL_ERROR = "error";
1040
1361
 
1041
- if (process$1.platform === 'win32') {
1042
- // Windows 10 build 10586 is the first Windows release that supports 256 colors.
1043
- // Windows 10 build 14931 is the first release that supports 16m/TrueColor.
1044
- const osRelease = os.release().split('.');
1045
- if (
1046
- Number(osRelease[0]) >= 10
1047
- && Number(osRelease[2]) >= 10_586
1048
- ) {
1049
- return Number(osRelease[2]) >= 14_931 ? 3 : 2;
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
- return 1;
1053
- }
1424
+ const debug = (...args) => console.debug(...args);
1054
1425
 
1055
- if ('CI' in env) {
1056
- if ('GITHUB_ACTIONS' in env || 'GITEA_ACTIONS' in env) {
1057
- return 3;
1058
- }
1426
+ const debugDisabled = () => {};
1059
1427
 
1060
- if (['TRAVIS', 'CIRCLECI', 'APPVEYOR', 'GITLAB_CI', 'BUILDKITE', 'DRONE'].some(sign => sign in env) || env.CI_NAME === 'codeship') {
1061
- return 1;
1062
- }
1428
+ const info = (...args) => console.info(...args);
1063
1429
 
1064
- return min;
1065
- }
1430
+ const infoDisabled = () => {};
1066
1431
 
1067
- if ('TEAMCITY_VERSION' in env) {
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
- if (env.COLORTERM === 'truecolor') {
1072
- return 3;
1073
- }
1434
+ const warnDisabled = () => {};
1074
1435
 
1075
- if (env.TERM === 'xterm-kitty') {
1076
- return 3;
1077
- }
1436
+ const error = (...args) => console.error(...args);
1078
1437
 
1079
- if ('TERM_PROGRAM' in env) {
1080
- const version = Number.parseInt((env.TERM_PROGRAM_VERSION || '').split('.')[0], 10);
1438
+ const errorDisabled = () => {};
1081
1439
 
1082
- switch (env.TERM_PROGRAM) {
1083
- case 'iTerm.app': {
1084
- return version >= 3 ? 3 : 2;
1085
- }
1440
+ /* globals WorkerGlobalScope, DedicatedWorkerGlobalScope, SharedWorkerGlobalScope, ServiceWorkerGlobalScope */
1086
1441
 
1087
- case 'Apple_Terminal': {
1088
- return 2;
1089
- }
1090
- // No default
1091
- }
1092
- }
1442
+ const isBrowser = globalThis.window?.document !== undefined;
1093
1443
 
1094
- if (/-256(color)?$/i.test(env.TERM)) {
1095
- return 2;
1096
- }
1444
+ globalThis.process?.versions?.node !== undefined;
1097
1445
 
1098
- if (/^screen|^xterm|^vt100|^vt220|^rxvt|color|ansi|cygwin|linux/i.test(env.TERM)) {
1099
- return 1;
1100
- }
1446
+ globalThis.process?.versions?.bun !== undefined;
1101
1447
 
1102
- if ('COLORTERM' in env) {
1103
- return 1;
1104
- }
1448
+ globalThis.Deno?.version?.deno !== undefined;
1105
1449
 
1106
- return min;
1107
- }
1450
+ globalThis.process?.versions?.electron !== undefined;
1108
1451
 
1109
- function createSupportsColor(stream, options = {}) {
1110
- const level = _supportsColor(stream, {
1111
- streamIsTTY: stream && stream.isTTY,
1112
- ...options,
1113
- });
1452
+ globalThis.navigator?.userAgent?.includes('jsdom') === true;
1114
1453
 
1115
- return translateLevel(level);
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
- const processSupportsBasicColor = createSupportsColor(process.stdout).hasBasic;
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
- const ANSI = {
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
- BOLD: "\x1b[1m",
1143
- UNDERLINE: "\x1b[4m",
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
- // GitHub workflow does support ANSI but "supports-color" returns false
1153
- // because stream.isTTY returns false, see https://github.com/actions/runner/issues/241
1154
- if (
1155
- process.env.GITHUB_WORKFLOW &&
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
- const startSpinner = ({
1165
- dynamicLog,
1166
- frames = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"],
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
- const spinner = {
1180
- message: undefined,
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
- const update = (message) => {
1184
- spinner.message = running
1185
- ? `${frames[frameIndex]} ${message}\n`
1186
- : `${message}\n`;
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
- let cleanup;
1192
- if (animated && ANSI.supported) {
1193
- running = true;
1194
- cleanup = effect();
1195
- dynamicLog.update(update(render()));
1484
+ const ESC = '\u001B[';
1196
1485
 
1197
- interval = setInterval(() => {
1198
- frameIndex = frameIndex === frames.length - 1 ? 0 : frameIndex + 1;
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
- const stop = (message) => {
1209
- running = false;
1210
- if (interval) {
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
- if (stopOnVerticalOverflow) {
1226
- dynamicLog.onVerticalOverflow = stop;
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
- return spinner;
1233
- };
1497
+ const eraseLines = count => {
1498
+ let clear = '';
1234
1499
 
1235
- const getPrecision = (number) => {
1236
- if (Math.floor(number) === number) return 0;
1237
- const [, decimals] = number.toString().split(".");
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
- const setRoundedPrecision = (
1242
- number,
1243
- { decimals = 1, decimalsWhenSmall = decimals } = {},
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
- const setPrecision = (
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 setDecimalsPrecision = (
1264
- number,
1265
- {
1266
- transform,
1267
- decimals, // max decimals for number in [-Infinity, -1[]1, Infinity]
1268
- decimalsWhenSmall, // max decimals for number in [-1,1]
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
- // https://www.codingem.com/javascript-how-to-limit-decimal-places/
1294
- // export const roundNumber = (number, maxDecimals) => {
1295
- // const decimalsExp = Math.pow(10, maxDecimals)
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 unitShort = {
1310
- year: "y",
1311
- month: "m",
1312
- week: "w",
1313
- day: "d",
1314
- hour: "h",
1315
- minute: "m",
1316
- second: "s",
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
- const humanizeDuration = (
1320
- ms,
1321
- { short, rounded = true, decimals } = {},
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 parseMs = (ms) => {
1377
- const unitNames = Object.keys(MS_PER_UNITS);
1378
- const smallestUnitName = unitNames[unitNames.length - 1];
1379
- let firstUnitName = smallestUnitName;
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
- const msPerUnit = MS_PER_UNITS[unitName];
1386
- const unitCount = Math.floor(ms / msPerUnit);
1387
- if (unitCount) {
1388
- firstUnitName = unitName;
1389
- firstUnitCount = unitCount;
1390
- return true;
1549
+ if (clearAttemptResult !== undefined) {
1550
+ return "";
1391
1551
  }
1392
- return false;
1393
- });
1394
- if (firstUnitName === smallestUnitName) {
1395
- return {
1396
- primary: {
1397
- name: firstUnitName,
1398
- count: firstUnitCount,
1399
- },
1400
- };
1401
- }
1402
- const remainingMs = ms - firstUnitCount * MS_PER_UNITS[firstUnitName];
1403
- const remainingUnitName = unitNames[firstUnitIndex + 1];
1404
- const remainingUnitCount = remainingMs / MS_PER_UNITS[remainingUnitName];
1405
- // - 1 year and 1 second is too much information
1406
- // so we don't check the remaining units
1407
- // - 1 year and 0.0001 week is awful
1408
- // hence the if below
1409
- if (Math.round(remainingUnitCount) < 1) {
1410
- return {
1411
- primary: {
1412
- name: firstUnitName,
1413
- count: firstUnitCount,
1414
- },
1415
- };
1416
- }
1417
- // - 1 year and 1 month is great
1418
- return {
1419
- primary: {
1420
- name: firstUnitName,
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
- return Boolean(process$1.env.WT_SESSION) // Windows Terminal
1436
- || Boolean(process$1.env.TERMINUS_SUBLIME) // Terminus (<0.2.27)
1437
- || process$1.env.ConEmuTask === '{cmd::Cmder}' // ConEmu and cmder
1438
- || process$1.env.TERM_PROGRAM === 'Terminus-Sublime'
1439
- || process$1.env.TERM_PROGRAM === 'vscode'
1440
- || process$1.env.TERM === 'xterm-256color'
1441
- || process$1.env.TERM === 'alacritty'
1442
- || process$1.env.TERMINAL_EMULATOR === 'JetBrains-JediTerm';
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
- // see also https://github.com/sindresorhus/figures
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
- const UNICODE = {
1449
- supported: isUnicodeSupported(),
1624
+ update(ouputAfterCallback);
1625
+ };
1450
1626
 
1451
- get COMMAND_RAW() {
1452
- return UNICODE.supported ? `❯` : `>`;
1453
- },
1454
- get OK_RAW() {
1455
- return UNICODE.supported ? `✔` : `√`;
1456
- },
1457
- get FAILURE_RAW() {
1458
- return UNICODE.supported ? `✖` : `×`;
1459
- },
1460
- get DEBUG_RAW() {
1461
- return UNICODE.supported ? `◆` : `♦`;
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
- const createTaskLog = (
1499
- label,
1500
- { disabled = false, animated = true, stopOnWriteFromOutside } = {},
1501
- ) => {
1502
- if (disabled) {
1503
- return {
1504
- setRightText: () => {},
1505
- done: () => {},
1506
- happen: () => {},
1507
- fail: () => {},
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 createDetailedMessage$1 = (message, details = {}) => {
1541
- let string = `${message}`;
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.keys(details).forEach((key) => {
1544
- const value = details[key];
1545
- string += `
1546
- --- ${key} ---
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
- const humanizeFileSize = (numberOfBytes, { decimals, short } = {}) => {
1559
- return inspectBytes(numberOfBytes, { decimals, short });
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
- const inspectBytes = (
1563
- number,
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
- const BYTE_UNITS = ["B", "kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"];
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 distributePercentages = (
1598
- namedNumbers,
1599
- { maxPrecisionHint = 2 } = {},
1600
- ) => {
1601
- const numberNames = Object.keys(namedNumbers);
1602
- if (numberNames.length === 0) {
1603
- return {};
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
- Object.keys(percentages).forEach((name) => {
1635
- const percentage = percentages[name];
1636
- const percentageAllocated = setRoundedPrecision(percentage, {
1637
- decimals: precision,
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 formatDefault = (v) => v;
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 generateContentFrame = ({
1652
- content,
1653
- line,
1654
- column,
1722
+ const spinner = {
1723
+ message: undefined,
1724
+ };
1655
1725
 
1656
- linesAbove = 3,
1657
- linesBelow = 0,
1658
- lineMaxWidth = 120,
1659
- lineNumbersOnTheLeft = true,
1660
- lineMarker = true,
1661
- columnMarker = true,
1662
- format = formatDefault,
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 lineStartIndex = line - 1 - linesAbove;
1673
- if (lineStartIndex < 0) {
1674
- lineStartIndex = 0;
1675
- }
1676
- let lineEndIndex = line - 1 + linesBelow;
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
- let columnsBefore;
1690
- let columnsAfter;
1691
- if (column > lineMaxWidth) {
1692
- columnsBefore = column - Math.ceil(lineMaxWidth / 2);
1693
- columnsAfter = column + Math.floor(lineMaxWidth / 2);
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
- columnsBefore = 0;
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
- if (lineMarker) {
1710
- if (isMainLine) {
1711
- source += `${format(">", "marker_line")} `;
1712
- } else {
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
- if (columnMarker && isMainLine) {
1733
- source += `\n`;
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 (!isLastLine) {
1747
- source += "\n";
1761
+ if (dynamicLog && message) {
1762
+ dynamicLog.update(update(message));
1763
+ dynamicLog = null;
1748
1764
  }
1749
- }
1750
- return source;
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 (start >= lastIndex || from === to) {
1770
- return "";
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 (endTruncated) {
1790
- return `${result}${format(suffix, "marker_overflow_right")}`;
1771
+ if (stopOnWriteFromOutside) {
1772
+ dynamicLog.onWriteFromOutside = stop;
1791
1773
  }
1792
- return result;
1774
+
1775
+ return spinner;
1793
1776
  };
1794
1777
 
1795
- const fillLeft = (value, biggestValue, char = " ") => {
1796
- const width = String(value).length;
1797
- const biggestWidth = String(biggestValue).length;
1798
- let missingWidth = biggestWidth - width;
1799
- let padded = "";
1800
- while (missingWidth--) {
1801
- padded += char;
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
- padded += value;
1804
- return padded;
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