@jsenv/core 38.4.9 → 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 +741 -685
  3. package/package.json +15 -15
@@ -1,9 +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
- import { isBrowser } from "environment";
5
3
  import os, { networkInterfaces } from "node:os";
6
4
  import tty from "node:tty";
5
+ import stringWidth from "string-width";
7
6
  import { pathToFileURL, fileURLToPath } from "node:url";
8
7
  import { extname } from "node:path";
9
8
  import crypto, { createHash } from "node:crypto";
@@ -589,611 +588,328 @@ const DATA_URL = {
589
588
  },
590
589
  };
591
590
 
592
- const LOG_LEVEL_OFF = "off";
593
-
594
- const LOG_LEVEL_DEBUG = "debug";
591
+ const createDetailedMessage$1 = (message, details = {}) => {
592
+ let string = `${message}`;
595
593
 
596
- const LOG_LEVEL_INFO = "info";
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
+ });
597
605
 
598
- const LOG_LEVEL_WARN = "warn";
606
+ return string;
607
+ };
599
608
 
600
- const LOG_LEVEL_ERROR = "error";
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
+ }
601
617
 
602
- const createLogger = ({ logLevel = LOG_LEVEL_INFO } = {}) => {
603
- if (logLevel === LOG_LEVEL_DEBUG) {
604
- return {
605
- level: "debug",
606
- levels: { debug: true, info: true, warn: true, error: true },
607
- debug,
608
- info,
609
- warn,
610
- error,
611
- };
612
- }
613
- if (logLevel === LOG_LEVEL_INFO) {
614
- return {
615
- level: "info",
616
- levels: { debug: false, info: true, warn: true, error: true },
617
- debug: debugDisabled,
618
- info,
619
- warn,
620
- error,
621
- };
622
- }
623
- if (logLevel === LOG_LEVEL_WARN) {
624
- return {
625
- level: "warn",
626
- levels: { debug: false, info: false, warn: true, error: true },
627
- debug: debugDisabled,
628
- info: infoDisabled,
629
- warn,
630
- error,
631
- };
632
- }
633
- if (logLevel === LOG_LEVEL_ERROR) {
634
- return {
635
- level: "error",
636
- levels: { debug: false, info: false, warn: false, error: true },
637
- debug: debugDisabled,
638
- info: infoDisabled,
639
- warn: warnDisabled,
640
- error,
641
- };
642
- }
643
- if (logLevel === LOG_LEVEL_OFF) {
644
- return {
645
- level: "off",
646
- levels: { debug: false, info: false, warn: false, error: false },
647
- debug: debugDisabled,
648
- info: infoDisabled,
649
- warn: warnDisabled,
650
- error: errorDisabled,
651
- };
652
- }
653
- throw new Error(`unexpected logLevel.
654
- --- logLevel ---
655
- ${logLevel}
656
- --- allowed log levels ---
657
- ${LOG_LEVEL_OFF}
658
- ${LOG_LEVEL_ERROR}
659
- ${LOG_LEVEL_WARN}
660
- ${LOG_LEVEL_INFO}
661
- ${LOG_LEVEL_DEBUG}`);
662
- };
618
+ const {env} = process$1;
663
619
 
664
- const debug = (...args) => console.debug(...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
+ }
665
636
 
666
- const debugDisabled = () => {};
637
+ function envForceColor() {
638
+ if ('FORCE_COLOR' in env) {
639
+ if (env.FORCE_COLOR === 'true') {
640
+ return 1;
641
+ }
667
642
 
668
- const info = (...args) => console.info(...args);
643
+ if (env.FORCE_COLOR === 'false') {
644
+ return 0;
645
+ }
669
646
 
670
- const infoDisabled = () => {};
647
+ return env.FORCE_COLOR.length === 0 ? 1 : Math.min(Number.parseInt(env.FORCE_COLOR, 10), 3);
648
+ }
649
+ }
671
650
 
672
- const warn = (...args) => console.warn(...args);
651
+ function translateLevel(level) {
652
+ if (level === 0) {
653
+ return false;
654
+ }
673
655
 
674
- const warnDisabled = () => {};
656
+ return {
657
+ level,
658
+ hasBasic: true,
659
+ has256: level >= 2,
660
+ has16m: level >= 3,
661
+ };
662
+ }
675
663
 
676
- const error = (...args) => console.error(...args);
664
+ function _supportsColor(haveStream, {streamIsTTY, sniffFlags = true} = {}) {
665
+ const noFlagForceColor = envForceColor();
666
+ if (noFlagForceColor !== undefined) {
667
+ flagForceColor = noFlagForceColor;
668
+ }
677
669
 
678
- const errorDisabled = () => {};
670
+ const forceColor = sniffFlags ? flagForceColor : noFlagForceColor;
679
671
 
680
- const ESC = '\u001B[';
672
+ if (forceColor === 0) {
673
+ return 0;
674
+ }
681
675
 
682
- !isBrowser && process$1.env.TERM_PROGRAM === 'Apple_Terminal';
683
- const isWindows$3 = !isBrowser && process$1.platform === 'win32';
676
+ if (sniffFlags) {
677
+ if (hasFlag('color=16m')
678
+ || hasFlag('color=full')
679
+ || hasFlag('color=truecolor')) {
680
+ return 3;
681
+ }
684
682
 
685
- isBrowser ? () => {
686
- throw new Error('`process.cwd()` only works in Node.js, not the browser.');
687
- } : process$1.cwd;
683
+ if (hasFlag('color=256')) {
684
+ return 2;
685
+ }
686
+ }
688
687
 
689
- const cursorUp = (count = 1) => ESC + count + 'A';
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
+ }
690
693
 
691
- const cursorLeft = ESC + 'G';
694
+ if (haveStream && !streamIsTTY && forceColor === undefined) {
695
+ return 0;
696
+ }
692
697
 
693
- const eraseLines = count => {
694
- let clear = '';
698
+ const min = forceColor || 0;
695
699
 
696
- for (let i = 0; i < count; i++) {
697
- clear += eraseLine + (i < count - 1 ? cursorUp() : '');
700
+ if (env.TERM === 'dumb') {
701
+ return min;
698
702
  }
699
703
 
700
- if (count) {
701
- clear += cursorLeft;
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
+ }
714
+
715
+ return 1;
702
716
  }
703
717
 
704
- return clear;
705
- };
706
- const eraseLine = ESC + '2K';
707
- const eraseScreen = ESC + '2J';
718
+ if ('CI' in env) {
719
+ if ('GITHUB_ACTIONS' in env || 'GITEA_ACTIONS' in env) {
720
+ return 3;
721
+ }
708
722
 
709
- const clearTerminal = isWindows$3
710
- ? `${eraseScreen}${ESC}0f`
711
- // 1. Erases the screen (Only done in case `2` is not supported)
712
- // 2. Erases the whole screen including scrollback buffer
713
- // 3. Moves cursor to the top-left position
714
- // More info: https://www.real-world-systems.com/docs/ANSIcode.html
715
- : `${eraseScreen}${ESC}3J${ESC}H`;
723
+ if (['TRAVIS', 'CIRCLECI', 'APPVEYOR', 'GITLAB_CI', 'BUILDKITE', 'DRONE'].some(sign => sign in env) || env.CI_NAME === 'codeship') {
724
+ return 1;
725
+ }
716
726
 
717
- /*
718
- * see also https://github.com/vadimdemedes/ink
719
- */
727
+ return min;
728
+ }
720
729
 
730
+ if ('TEAMCITY_VERSION' in env) {
731
+ return /^(9\.(0*[1-9]\d*)\.|\d{2,}\.)/.test(env.TEAMCITY_VERSION) ? 1 : 0;
732
+ }
721
733
 
722
- const createDynamicLog = ({
723
- stream = process.stdout,
724
- clearTerminalAllowed,
725
- onVerticalOverflow = () => {},
726
- onWriteFromOutside = () => {},
727
- } = {}) => {
728
- const { columns = 80, rows = 24 } = stream;
729
- const dynamicLog = {
730
- destroyed: false,
731
- onVerticalOverflow,
732
- onWriteFromOutside,
733
- };
734
+ if (env.COLORTERM === 'truecolor') {
735
+ return 3;
736
+ }
734
737
 
735
- let lastOutput = "";
736
- let lastOutputFromOutside = "";
737
- let clearAttemptResult;
738
- let writing = false;
738
+ if (env.TERM === 'xterm-kitty') {
739
+ return 3;
740
+ }
739
741
 
740
- const getErasePreviousOutput = () => {
741
- // nothing to clear
742
- if (!lastOutput) {
743
- return "";
744
- }
745
- if (clearAttemptResult !== undefined) {
746
- return "";
747
- }
742
+ if ('TERM_PROGRAM' in env) {
743
+ const version = Number.parseInt((env.TERM_PROGRAM_VERSION || '').split('.')[0], 10);
748
744
 
749
- const logLines = lastOutput.split(/\r\n|\r|\n/);
750
- let visualLineCount = 0;
751
- for (const logLine of logLines) {
752
- const width = stringWidth(logLine);
753
- if (width === 0) {
754
- visualLineCount++;
755
- } else {
756
- visualLineCount += Math.ceil(width / columns);
757
- }
758
- }
759
-
760
- if (visualLineCount > rows) {
761
- if (clearTerminalAllowed) {
762
- clearAttemptResult = true;
763
- return clearTerminal;
764
- }
765
- // the whole log cannot be cleared because it's vertically to long
766
- // (longer than terminal height)
767
- // readline.moveCursor cannot move cursor higher than screen height
768
- // it means we would only clear the visible part of the log
769
- // better keep the log untouched
770
- clearAttemptResult = false;
771
- dynamicLog.onVerticalOverflow();
772
- return "";
773
- }
774
-
775
- clearAttemptResult = true;
776
- return eraseLines(visualLineCount);
777
- };
778
-
779
- const update = (string) => {
780
- if (dynamicLog.destroyed) {
781
- throw new Error("Cannot write log after destroy");
782
- }
783
- let stringToWrite = string;
784
- if (lastOutput) {
785
- if (lastOutputFromOutside) {
786
- // We don't want to clear logs written by other code,
787
- // it makes output unreadable and might erase precious information
788
- // To detect this we put a spy on the stream.
789
- // The spy is required only if we actually wrote something in the stream
790
- // something else than this code has written in the stream
791
- // so we just write without clearing (append instead of replacing)
792
- lastOutput = "";
793
- lastOutputFromOutside = "";
794
- } else {
795
- stringToWrite = `${getErasePreviousOutput()}${string}`;
796
- }
797
- }
798
- writing = true;
799
- stream.write(stringToWrite);
800
- lastOutput = string;
801
- writing = false;
802
- clearAttemptResult = undefined;
803
- };
745
+ switch (env.TERM_PROGRAM) {
746
+ case 'iTerm.app': {
747
+ return version >= 3 ? 3 : 2;
748
+ }
804
749
 
805
- const clearDuringFunctionCall = (
806
- callback,
807
- ouputAfterCallback = lastOutput,
808
- ) => {
809
- // 1. Erase the current log
810
- // 2. Call callback (expect to write something on stdout)
811
- // 3. Restore the current log
812
- // During step 2. we expect a "write from outside" so we uninstall
813
- // the stream spy during function call
814
- update("");
750
+ case 'Apple_Terminal': {
751
+ return 2;
752
+ }
753
+ // No default
754
+ }
755
+ }
815
756
 
816
- writing = true;
817
- callback();
818
- writing = false;
757
+ if (/-256(color)?$/i.test(env.TERM)) {
758
+ return 2;
759
+ }
819
760
 
820
- update(ouputAfterCallback);
821
- };
761
+ if (/^screen|^xterm|^vt100|^vt220|^rxvt|color|ansi|cygwin|linux/i.test(env.TERM)) {
762
+ return 1;
763
+ }
822
764
 
823
- const writeFromOutsideEffect = (value) => {
824
- if (!lastOutput) {
825
- // we don't care if the log never wrote anything
826
- // or if last update() wrote an empty string
827
- return;
828
- }
829
- if (writing) {
830
- return;
831
- }
832
- lastOutputFromOutside = value;
833
- dynamicLog.onWriteFromOutside(value);
834
- };
765
+ if ('COLORTERM' in env) {
766
+ return 1;
767
+ }
835
768
 
836
- let removeStreamSpy;
837
- if (stream === process.stdout) {
838
- const removeStdoutSpy = spyStreamOutput(
839
- process.stdout,
840
- writeFromOutsideEffect,
841
- );
842
- const removeStderrSpy = spyStreamOutput(
843
- process.stderr,
844
- writeFromOutsideEffect,
845
- );
846
- removeStreamSpy = () => {
847
- removeStdoutSpy();
848
- removeStderrSpy();
849
- };
850
- } else {
851
- removeStreamSpy = spyStreamOutput(stream, writeFromOutsideEffect);
852
- }
769
+ return min;
770
+ }
853
771
 
854
- const destroy = () => {
855
- dynamicLog.destroyed = true;
856
- if (removeStreamSpy) {
857
- removeStreamSpy();
858
- removeStreamSpy = null;
859
- lastOutput = "";
860
- lastOutputFromOutside = "";
861
- }
862
- };
772
+ function createSupportsColor(stream, options = {}) {
773
+ const level = _supportsColor(stream, {
774
+ streamIsTTY: stream && stream.isTTY,
775
+ ...options,
776
+ });
863
777
 
864
- Object.assign(dynamicLog, {
865
- update,
866
- destroy,
867
- stream,
868
- clearDuringFunctionCall,
869
- });
870
- return dynamicLog;
871
- };
778
+ return translateLevel(level);
779
+ }
872
780
 
873
- // maybe https://github.com/gajus/output-interceptor/tree/v3.0.0 ?
874
- // the problem with listening data on stdout
875
- // is that node.js will later throw error if stream gets closed
876
- // while something listening data on it
877
- const spyStreamOutput = (stream, callback) => {
878
- const originalWrite = stream.write;
781
+ ({
782
+ stdout: createSupportsColor({isTTY: tty.isatty(1)}),
783
+ stderr: createSupportsColor({isTTY: tty.isatty(2)}),
784
+ });
879
785
 
880
- let output = "";
881
- let installed = true;
786
+ // https://github.com/Marak/colors.js/blob/master/lib/styles.js
787
+ // https://stackoverflow.com/a/75985833/2634179
788
+ const RESET = "\x1b[0m";
882
789
 
883
- stream.write = function (...args /* chunk, encoding, callback */) {
884
- output += args;
885
- callback(output);
886
- return originalWrite.call(stream, ...args);
887
- };
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
+ },
888
806
 
889
- const uninstall = () => {
890
- if (!installed) {
891
- return;
892
- }
893
- stream.write = originalWrite;
894
- installed = false;
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
+ },
895
815
  };
896
816
 
897
- return () => {
898
- uninstall();
899
- return output;
900
- };
817
+ return ANSI;
901
818
  };
902
819
 
903
- // From: https://github.com/sindresorhus/has-flag/blob/main/index.js
904
- /// function hasFlag(flag, argv = globalThis.Deno?.args ?? process.argv) {
905
- function hasFlag(flag, argv = globalThis.Deno ? globalThis.Deno.args : process$1.argv) {
906
- const prefix = flag.startsWith('-') ? '' : (flag.length === 1 ? '-' : '--');
907
- const position = argv.indexOf(prefix + flag);
908
- const terminatorPosition = argv.indexOf('--');
909
- return position !== -1 && (terminatorPosition === -1 || position < terminatorPosition);
910
- }
820
+ const processSupportsBasicColor = createSupportsColor(process.stdout).hasBasic;
911
821
 
912
- const {env} = process$1;
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
+ });
913
833
 
914
- let flagForceColor;
915
- if (
916
- hasFlag('no-color')
917
- || hasFlag('no-colors')
918
- || hasFlag('color=false')
919
- || hasFlag('color=never')
920
- ) {
921
- flagForceColor = 0;
922
- } else if (
923
- hasFlag('color')
924
- || hasFlag('colors')
925
- || hasFlag('color=true')
926
- || hasFlag('color=always')
927
- ) {
928
- flagForceColor = 1;
834
+ function isUnicodeSupported() {
835
+ if (process$1.platform !== 'win32') {
836
+ return process$1.env.TERM !== 'linux'; // Linux console (kernel)
837
+ }
838
+
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';
929
847
  }
930
848
 
931
- function envForceColor() {
932
- if ('FORCE_COLOR' in env) {
933
- if (env.FORCE_COLOR === 'true') {
934
- return 1;
935
- }
849
+ // see also https://github.com/sindresorhus/figures
936
850
 
937
- if (env.FORCE_COLOR === 'false') {
938
- return 0;
939
- }
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
+ },
899
+ };
900
+ return UNICODE;
901
+ };
940
902
 
941
- return env.FORCE_COLOR.length === 0 ? 1 : Math.min(Number.parseInt(env.FORCE_COLOR, 10), 3);
942
- }
943
- }
903
+ const UNICODE = createUnicode({
904
+ supported: isUnicodeSupported(),
905
+ ANSI,
906
+ });
944
907
 
945
- function translateLevel(level) {
946
- if (level === 0) {
947
- return false;
948
- }
949
-
950
- return {
951
- level,
952
- hasBasic: true,
953
- has256: level >= 2,
954
- has16m: level >= 3,
955
- };
956
- }
957
-
958
- function _supportsColor(haveStream, {streamIsTTY, sniffFlags = true} = {}) {
959
- const noFlagForceColor = envForceColor();
960
- if (noFlagForceColor !== undefined) {
961
- flagForceColor = noFlagForceColor;
962
- }
963
-
964
- const forceColor = sniffFlags ? flagForceColor : noFlagForceColor;
965
-
966
- if (forceColor === 0) {
967
- return 0;
968
- }
969
-
970
- if (sniffFlags) {
971
- if (hasFlag('color=16m')
972
- || hasFlag('color=full')
973
- || hasFlag('color=truecolor')) {
974
- return 3;
975
- }
976
-
977
- if (hasFlag('color=256')) {
978
- return 2;
979
- }
980
- }
981
-
982
- // Check for Azure DevOps pipelines.
983
- // Has to be above the `!streamIsTTY` check.
984
- if ('TF_BUILD' in env && 'AGENT_NAME' in env) {
985
- return 1;
986
- }
987
-
988
- if (haveStream && !streamIsTTY && forceColor === undefined) {
989
- return 0;
990
- }
991
-
992
- const min = forceColor || 0;
993
-
994
- if (env.TERM === 'dumb') {
995
- return min;
996
- }
997
-
998
- if (process$1.platform === 'win32') {
999
- // Windows 10 build 10586 is the first Windows release that supports 256 colors.
1000
- // Windows 10 build 14931 is the first release that supports 16m/TrueColor.
1001
- const osRelease = os.release().split('.');
1002
- if (
1003
- Number(osRelease[0]) >= 10
1004
- && Number(osRelease[2]) >= 10_586
1005
- ) {
1006
- return Number(osRelease[2]) >= 14_931 ? 3 : 2;
1007
- }
1008
-
1009
- return 1;
1010
- }
1011
-
1012
- if ('CI' in env) {
1013
- if ('GITHUB_ACTIONS' in env || 'GITEA_ACTIONS' in env) {
1014
- return 3;
1015
- }
1016
-
1017
- if (['TRAVIS', 'CIRCLECI', 'APPVEYOR', 'GITLAB_CI', 'BUILDKITE', 'DRONE'].some(sign => sign in env) || env.CI_NAME === 'codeship') {
1018
- return 1;
1019
- }
1020
-
1021
- return min;
1022
- }
1023
-
1024
- if ('TEAMCITY_VERSION' in env) {
1025
- return /^(9\.(0*[1-9]\d*)\.|\d{2,}\.)/.test(env.TEAMCITY_VERSION) ? 1 : 0;
1026
- }
1027
-
1028
- if (env.COLORTERM === 'truecolor') {
1029
- return 3;
1030
- }
1031
-
1032
- if (env.TERM === 'xterm-kitty') {
1033
- return 3;
1034
- }
1035
-
1036
- if ('TERM_PROGRAM' in env) {
1037
- const version = Number.parseInt((env.TERM_PROGRAM_VERSION || '').split('.')[0], 10);
1038
-
1039
- switch (env.TERM_PROGRAM) {
1040
- case 'iTerm.app': {
1041
- return version >= 3 ? 3 : 2;
1042
- }
1043
-
1044
- case 'Apple_Terminal': {
1045
- return 2;
1046
- }
1047
- // No default
1048
- }
1049
- }
1050
-
1051
- if (/-256(color)?$/i.test(env.TERM)) {
1052
- return 2;
1053
- }
1054
-
1055
- if (/^screen|^xterm|^vt100|^vt220|^rxvt|color|ansi|cygwin|linux/i.test(env.TERM)) {
1056
- return 1;
1057
- }
1058
-
1059
- if ('COLORTERM' in env) {
1060
- return 1;
1061
- }
1062
-
1063
- return min;
1064
- }
1065
-
1066
- function createSupportsColor(stream, options = {}) {
1067
- const level = _supportsColor(stream, {
1068
- streamIsTTY: stream && stream.isTTY,
1069
- ...options,
1070
- });
1071
-
1072
- return translateLevel(level);
1073
- }
1074
-
1075
- ({
1076
- stdout: createSupportsColor({isTTY: tty.isatty(1)}),
1077
- stderr: createSupportsColor({isTTY: tty.isatty(2)}),
1078
- });
1079
-
1080
- const processSupportsBasicColor = createSupportsColor(process.stdout).hasBasic;
1081
- // https://github.com/Marak/colors.js/blob/master/lib/styles.js
1082
- // https://stackoverflow.com/a/75985833/2634179
1083
- const RESET = "\x1b[0m";
1084
-
1085
- const ANSI = {
1086
- supported: processSupportsBasicColor,
1087
-
1088
- RED: "\x1b[31m",
1089
- GREEN: "\x1b[32m",
1090
- YELLOW: "\x1b[33m",
1091
- BLUE: "\x1b[34m",
1092
- MAGENTA: "\x1b[35m",
1093
- CYAN: "\x1b[36m",
1094
- GREY: "\x1b[90m",
1095
- color: (text, ANSI_COLOR) => {
1096
- return ANSI.supported && ANSI_COLOR ? `${ANSI_COLOR}${text}${RESET}` : text;
1097
- },
1098
-
1099
- BOLD: "\x1b[1m",
1100
- UNDERLINE: "\x1b[4m",
1101
- STRIKE: "\x1b[9m",
1102
- effect: (text, ANSI_EFFECT) => {
1103
- return ANSI.supported && ANSI_EFFECT
1104
- ? `${ANSI_EFFECT}${text}${RESET}`
1105
- : text;
1106
- },
1107
- };
1108
-
1109
- // GitHub workflow does support ANSI but "supports-color" returns false
1110
- // because stream.isTTY returns false, see https://github.com/actions/runner/issues/241
1111
- if (
1112
- process.env.GITHUB_WORKFLOW &&
1113
- // Check on FORCE_COLOR is to ensure it is prio over GitHub workflow check
1114
- // in unit test we use process.env.FORCE_COLOR = 'false' to fake
1115
- // that colors are not supported. Let it have priority
1116
- process.env.FORCE_COLOR !== "false"
1117
- ) {
1118
- ANSI.supported = true;
1119
- }
1120
-
1121
- const startSpinner = ({
1122
- dynamicLog,
1123
- frames = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"],
1124
- fps = 20,
1125
- keepProcessAlive = false,
1126
- stopOnWriteFromOutside = true,
1127
- stopOnVerticalOverflow = true,
1128
- render = () => "",
1129
- effect = () => {},
1130
- animated = dynamicLog.stream.isTTY,
1131
- }) => {
1132
- let frameIndex = 0;
1133
- let interval;
1134
- let running = true;
1135
-
1136
- const spinner = {
1137
- message: undefined,
1138
- };
1139
-
1140
- const update = (message) => {
1141
- spinner.message = running
1142
- ? `${frames[frameIndex]} ${message}\n`
1143
- : `${message}\n`;
1144
- return spinner.message;
1145
- };
1146
- spinner.update = update;
1147
-
1148
- let cleanup;
1149
- if (animated && ANSI.supported) {
1150
- running = true;
1151
- cleanup = effect();
1152
- dynamicLog.update(update(render()));
1153
-
1154
- interval = setInterval(() => {
1155
- frameIndex = frameIndex === frames.length - 1 ? 0 : frameIndex + 1;
1156
- dynamicLog.update(update(render()));
1157
- }, 1000 / fps);
1158
- if (!keepProcessAlive) {
1159
- interval.unref();
1160
- }
1161
- } else {
1162
- dynamicLog.update(update(render()));
1163
- }
1164
-
1165
- const stop = (message) => {
1166
- running = false;
1167
- if (interval) {
1168
- clearInterval(interval);
1169
- interval = null;
1170
- }
1171
- if (cleanup) {
1172
- cleanup();
1173
- cleanup = null;
1174
- }
1175
- if (dynamicLog && message) {
1176
- dynamicLog.update(update(message));
1177
- dynamicLog = null;
1178
- }
1179
- };
1180
- spinner.stop = stop;
1181
-
1182
- if (stopOnVerticalOverflow) {
1183
- dynamicLog.onVerticalOverflow = stop;
1184
- }
1185
- if (stopOnWriteFromOutside) {
1186
- dynamicLog.onWriteFromOutside = stop;
1187
- }
1188
-
1189
- return spinner;
1190
- };
1191
-
1192
- const getPrecision = (number) => {
1193
- if (Math.floor(number) === number) return 0;
1194
- const [, decimals] = number.toString().split(".");
1195
- return decimals.length || 0;
1196
- };
908
+ const getPrecision = (number) => {
909
+ if (Math.floor(number) === number) return 0;
910
+ const [, decimals] = number.toString().split(".");
911
+ return decimals.length || 0;
912
+ };
1197
913
 
1198
914
  const setRoundedPrecision = (
1199
915
  number,
@@ -1384,136 +1100,8 @@ const parseMs = (ms) => {
1384
1100
  };
1385
1101
  };
1386
1102
 
1387
- function isUnicodeSupported() {
1388
- if (process$1.platform !== 'win32') {
1389
- return process$1.env.TERM !== 'linux'; // Linux console (kernel)
1390
- }
1391
-
1392
- return Boolean(process$1.env.WT_SESSION) // Windows Terminal
1393
- || Boolean(process$1.env.TERMINUS_SUBLIME) // Terminus (<0.2.27)
1394
- || process$1.env.ConEmuTask === '{cmd::Cmder}' // ConEmu and cmder
1395
- || process$1.env.TERM_PROGRAM === 'Terminus-Sublime'
1396
- || process$1.env.TERM_PROGRAM === 'vscode'
1397
- || process$1.env.TERM === 'xterm-256color'
1398
- || process$1.env.TERM === 'alacritty'
1399
- || process$1.env.TERMINAL_EMULATOR === 'JetBrains-JediTerm';
1400
- }
1401
-
1402
- // see also https://github.com/sindresorhus/figures
1403
-
1404
-
1405
- const UNICODE = {
1406
- supported: isUnicodeSupported(),
1407
-
1408
- get COMMAND_RAW() {
1409
- return UNICODE.supported ? `❯` : `>`;
1410
- },
1411
- get OK_RAW() {
1412
- return UNICODE.supported ? `✔` : `√`;
1413
- },
1414
- get FAILURE_RAW() {
1415
- return UNICODE.supported ? `✖` : `×`;
1416
- },
1417
- get DEBUG_RAW() {
1418
- return UNICODE.supported ? `◆` : `♦`;
1419
- },
1420
- get INFO_RAW() {
1421
- return UNICODE.supported ? `ℹ` : `i`;
1422
- },
1423
- get WARNING_RAW() {
1424
- return UNICODE.supported ? `⚠` : `‼`;
1425
- },
1426
- get CIRCLE_CROSS_RAW() {
1427
- return UNICODE.supported ? `ⓧ` : `(×)`;
1428
- },
1429
- get COMMAND() {
1430
- return ANSI.color(UNICODE.COMMAND_RAW, ANSI.GREY); // ANSI_MAGENTA)
1431
- },
1432
- get OK() {
1433
- return ANSI.color(UNICODE.OK_RAW, ANSI.GREEN);
1434
- },
1435
- get FAILURE() {
1436
- return ANSI.color(UNICODE.FAILURE_RAW, ANSI.RED);
1437
- },
1438
- get DEBUG() {
1439
- return ANSI.color(UNICODE.DEBUG_RAW, ANSI.GREY);
1440
- },
1441
- get INFO() {
1442
- return ANSI.color(UNICODE.INFO_RAW, ANSI.BLUE);
1443
- },
1444
- get WARNING() {
1445
- return ANSI.color(UNICODE.WARNING_RAW, ANSI.YELLOW);
1446
- },
1447
- get CIRCLE_CROSS() {
1448
- return ANSI.color(UNICODE.CIRCLE_CROSS_RAW, ANSI.RED);
1449
- },
1450
- get ELLIPSIS() {
1451
- return UNICODE.supported ? `…` : `...`;
1452
- },
1453
- };
1454
-
1455
- const createTaskLog = (
1456
- label,
1457
- { disabled = false, animated = true, stopOnWriteFromOutside } = {},
1458
- ) => {
1459
- if (disabled) {
1460
- return {
1461
- setRightText: () => {},
1462
- done: () => {},
1463
- happen: () => {},
1464
- fail: () => {},
1465
- };
1466
- }
1467
- const startMs = Date.now();
1468
- const dynamicLog = createDynamicLog();
1469
- let message = label;
1470
- const taskSpinner = startSpinner({
1471
- dynamicLog,
1472
- render: () => message,
1473
- stopOnWriteFromOutside,
1474
- animated,
1475
- });
1476
- return {
1477
- setRightText: (value) => {
1478
- message = `${label} ${value}`;
1479
- },
1480
- done: () => {
1481
- const msEllapsed = Date.now() - startMs;
1482
- taskSpinner.stop(
1483
- `${UNICODE.OK} ${label} (done in ${humanizeDuration(msEllapsed)})`,
1484
- );
1485
- },
1486
- happen: (message) => {
1487
- taskSpinner.stop(
1488
- `${UNICODE.INFO} ${message} (at ${new Date().toLocaleTimeString()})`,
1489
- );
1490
- },
1491
- fail: (message = `failed to ${label}`) => {
1492
- taskSpinner.stop(`${UNICODE.FAILURE} ${message}`);
1493
- },
1494
- };
1495
- };
1496
-
1497
- const createDetailedMessage$1 = (message, details = {}) => {
1498
- let string = `${message}`;
1499
-
1500
- Object.keys(details).forEach((key) => {
1501
- const value = details[key];
1502
- string += `
1503
- --- ${key} ---
1504
- ${
1505
- Array.isArray(value)
1506
- ? value.join(`
1507
- `)
1508
- : value
1509
- }`;
1510
- });
1511
-
1512
- return string;
1513
- };
1514
-
1515
- const humanizeFileSize = (numberOfBytes, { decimals, short } = {}) => {
1516
- return inspectBytes(numberOfBytes, { decimals, short });
1103
+ const humanizeFileSize = (numberOfBytes, { decimals, short } = {}) => {
1104
+ return inspectBytes(numberOfBytes, { decimals, short });
1517
1105
  };
1518
1106
 
1519
1107
  const inspectBytes = (
@@ -1761,6 +1349,474 @@ const fillLeft = (value, biggestValue, char = " ") => {
1761
1349
  return padded;
1762
1350
  };
1763
1351
 
1352
+ const LOG_LEVEL_OFF = "off";
1353
+
1354
+ const LOG_LEVEL_DEBUG = "debug";
1355
+
1356
+ const LOG_LEVEL_INFO = "info";
1357
+
1358
+ const LOG_LEVEL_WARN = "warn";
1359
+
1360
+ const LOG_LEVEL_ERROR = "error";
1361
+
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
+ };
1423
+
1424
+ const debug = (...args) => console.debug(...args);
1425
+
1426
+ const debugDisabled = () => {};
1427
+
1428
+ const info = (...args) => console.info(...args);
1429
+
1430
+ const infoDisabled = () => {};
1431
+
1432
+ const warn = (...args) => console.warn(...args);
1433
+
1434
+ const warnDisabled = () => {};
1435
+
1436
+ const error = (...args) => console.error(...args);
1437
+
1438
+ const errorDisabled = () => {};
1439
+
1440
+ /* globals WorkerGlobalScope, DedicatedWorkerGlobalScope, SharedWorkerGlobalScope, ServiceWorkerGlobalScope */
1441
+
1442
+ const isBrowser = globalThis.window?.document !== undefined;
1443
+
1444
+ globalThis.process?.versions?.node !== undefined;
1445
+
1446
+ globalThis.process?.versions?.bun !== undefined;
1447
+
1448
+ globalThis.Deno?.version?.deno !== undefined;
1449
+
1450
+ globalThis.process?.versions?.electron !== undefined;
1451
+
1452
+ globalThis.navigator?.userAgent?.includes('jsdom') === true;
1453
+
1454
+ typeof WorkerGlobalScope !== 'undefined' && globalThis instanceof WorkerGlobalScope;
1455
+
1456
+ typeof DedicatedWorkerGlobalScope !== 'undefined' && globalThis instanceof DedicatedWorkerGlobalScope;
1457
+
1458
+ typeof SharedWorkerGlobalScope !== 'undefined' && globalThis instanceof SharedWorkerGlobalScope;
1459
+
1460
+ typeof ServiceWorkerGlobalScope !== 'undefined' && globalThis instanceof ServiceWorkerGlobalScope;
1461
+
1462
+ // Note: I'm intentionally not DRYing up the other variables to keep them "lazy".
1463
+ const platform = globalThis.navigator?.userAgentData?.platform;
1464
+
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';
1469
+
1470
+ platform === 'Windows'
1471
+ || globalThis.navigator?.platform === 'Win32'
1472
+ || globalThis.process?.platform === 'win32';
1473
+
1474
+ platform === 'Linux'
1475
+ || globalThis.navigator?.platform?.startsWith('Linux') === true
1476
+ || globalThis.navigator?.userAgent?.includes(' Linux ') === true
1477
+ || globalThis.process?.platform === 'linux';
1478
+
1479
+ platform === 'Android'
1480
+ || globalThis.navigator?.platform === 'Android'
1481
+ || globalThis.navigator?.userAgent?.includes(' Android ') === true
1482
+ || globalThis.process?.platform === 'android';
1483
+
1484
+ const ESC = '\u001B[';
1485
+
1486
+ !isBrowser && process$1.env.TERM_PROGRAM === 'Apple_Terminal';
1487
+ const isWindows$3 = !isBrowser && process$1.platform === 'win32';
1488
+
1489
+ isBrowser ? () => {
1490
+ throw new Error('`process.cwd()` only works in Node.js, not the browser.');
1491
+ } : process$1.cwd;
1492
+
1493
+ const cursorUp = (count = 1) => ESC + count + 'A';
1494
+
1495
+ const cursorLeft = ESC + 'G';
1496
+
1497
+ const eraseLines = count => {
1498
+ let clear = '';
1499
+
1500
+ for (let i = 0; i < count; i++) {
1501
+ clear += eraseLine + (i < count - 1 ? cursorUp() : '');
1502
+ }
1503
+
1504
+ if (count) {
1505
+ clear += cursorLeft;
1506
+ }
1507
+
1508
+ return clear;
1509
+ };
1510
+ const eraseLine = ESC + '2K';
1511
+ const eraseScreen = ESC + '2J';
1512
+
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`;
1520
+
1521
+ /*
1522
+ * see also https://github.com/vadimdemedes/ink
1523
+ */
1524
+
1525
+
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
+ };
1538
+
1539
+ let lastOutput = "";
1540
+ let lastOutputFromOutside = "";
1541
+ let clearAttemptResult;
1542
+ let writing = false;
1543
+
1544
+ const getErasePreviousOutput = () => {
1545
+ // nothing to clear
1546
+ if (!lastOutput) {
1547
+ return "";
1548
+ }
1549
+ if (clearAttemptResult !== undefined) {
1550
+ return "";
1551
+ }
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);
1581
+ };
1582
+
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
+ };
1608
+
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("");
1619
+
1620
+ writing = true;
1621
+ callback();
1622
+ writing = false;
1623
+
1624
+ update(ouputAfterCallback);
1625
+ };
1626
+
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
+ };
1639
+
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();
1653
+ };
1654
+ } else {
1655
+ removeStreamSpy = spyStreamOutput(stream, writeFromOutsideEffect);
1656
+ }
1657
+
1658
+ const destroy = () => {
1659
+ dynamicLog.destroyed = true;
1660
+ if (removeStreamSpy) {
1661
+ removeStreamSpy();
1662
+ removeStreamSpy = null;
1663
+ lastOutput = "";
1664
+ lastOutputFromOutside = "";
1665
+ }
1666
+ };
1667
+
1668
+ Object.assign(dynamicLog, {
1669
+ update,
1670
+ destroy,
1671
+ stream,
1672
+ clearDuringFunctionCall,
1673
+ });
1674
+ return dynamicLog;
1675
+ };
1676
+
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;
1683
+
1684
+ let output = "";
1685
+ let installed = true;
1686
+
1687
+ stream.write = function (...args /* chunk, encoding, callback */) {
1688
+ output += args;
1689
+ callback(output);
1690
+ return originalWrite.call(stream, ...args);
1691
+ };
1692
+
1693
+ const uninstall = () => {
1694
+ if (!installed) {
1695
+ return;
1696
+ }
1697
+ stream.write = originalWrite;
1698
+ installed = false;
1699
+ };
1700
+
1701
+ return () => {
1702
+ uninstall();
1703
+ return output;
1704
+ };
1705
+ };
1706
+
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;
1721
+
1722
+ const spinner = {
1723
+ message: undefined,
1724
+ };
1725
+
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;
1733
+
1734
+ let cleanup;
1735
+ if (animated && ANSI.supported) {
1736
+ running = true;
1737
+ cleanup = effect();
1738
+ dynamicLog.update(update(render()));
1739
+
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
+ }
1747
+ } else {
1748
+ dynamicLog.update(update(render()));
1749
+ }
1750
+
1751
+ const stop = (message) => {
1752
+ running = false;
1753
+ if (interval) {
1754
+ clearInterval(interval);
1755
+ interval = null;
1756
+ }
1757
+ if (cleanup) {
1758
+ cleanup();
1759
+ cleanup = null;
1760
+ }
1761
+ if (dynamicLog && message) {
1762
+ dynamicLog.update(update(message));
1763
+ dynamicLog = null;
1764
+ }
1765
+ };
1766
+ spinner.stop = stop;
1767
+
1768
+ if (stopOnVerticalOverflow) {
1769
+ dynamicLog.onVerticalOverflow = stop;
1770
+ }
1771
+ if (stopOnWriteFromOutside) {
1772
+ dynamicLog.onWriteFromOutside = stop;
1773
+ }
1774
+
1775
+ return spinner;
1776
+ };
1777
+
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
+ };
1789
+ }
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
+ };
1818
+ };
1819
+
1764
1820
  // consider switching to https://babeljs.io/docs/en/babel-code-frame
1765
1821
  // https://github.com/postcss/postcss/blob/fd30d3df5abc0954a0ec642a3cdc644ab2aacf9c/lib/css-syntax-error.js#L43
1766
1822
  // https://github.com/postcss/postcss/blob/fd30d3df5abc0954a0ec642a3cdc644ab2aacf9c/lib/terminal-highlight.js#L50