@jsenv/core 38.4.10 → 38.4.12

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