@node-core/utils 7.2.0 → 7.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (182) hide show
  1. package/lib/ci/build-types/test_build.js +3 -3
  2. package/lib/ci/failure_aggregator.js +2 -2
  3. package/lib/ci/run_ci.js +3 -2
  4. package/lib/cli.js +6 -6
  5. package/lib/figures.js +6 -6
  6. package/lib/update-v8/backport.js +3 -4
  7. package/lib/verbosity.js +2 -4
  8. package/node_modules/@listr2/{prompt-adapter-enquirer → prompt-adapter-inquirer}/README.md +4 -4
  9. package/node_modules/@listr2/prompt-adapter-inquirer/dist/index.d.mts +21 -0
  10. package/node_modules/@listr2/prompt-adapter-inquirer/dist/index.mjs +46 -0
  11. package/node_modules/@listr2/prompt-adapter-inquirer/node_modules/@inquirer/type/LICENSE +22 -0
  12. package/node_modules/@listr2/prompt-adapter-inquirer/node_modules/@inquirer/type/dist/index.d.ts +2 -0
  13. package/node_modules/@listr2/prompt-adapter-inquirer/node_modules/@inquirer/type/dist/index.js +2 -0
  14. package/node_modules/@listr2/prompt-adapter-inquirer/node_modules/@inquirer/type/dist/inquirer.d.ts +35 -0
  15. package/node_modules/@listr2/prompt-adapter-inquirer/node_modules/@inquirer/type/dist/inquirer.js +1 -0
  16. package/node_modules/@listr2/prompt-adapter-inquirer/node_modules/@inquirer/type/dist/utils.d.ts +34 -0
  17. package/node_modules/@listr2/prompt-adapter-inquirer/node_modules/@inquirer/type/dist/utils.js +2 -0
  18. package/node_modules/@listr2/prompt-adapter-inquirer/node_modules/@inquirer/type/package.json +88 -0
  19. package/node_modules/@listr2/{prompt-adapter-enquirer → prompt-adapter-inquirer}/package.json +25 -20
  20. package/node_modules/{log-update/node_modules/ansi-escapes → ansi-escapes}/base.d.ts +48 -3
  21. package/node_modules/{log-update/node_modules/ansi-escapes → ansi-escapes}/base.js +58 -19
  22. package/node_modules/{log-update/node_modules/ansi-escapes → ansi-escapes}/package.json +1 -1
  23. package/node_modules/{log-update/node_modules/ansi-escapes → ansi-escapes}/readme.md +36 -1
  24. package/node_modules/ansi-regex/index.js +9 -4
  25. package/node_modules/ansi-regex/package.json +1 -1
  26. package/node_modules/ansi-regex/readme.md +6 -0
  27. package/node_modules/ansi-styles/index.d.ts +12 -12
  28. package/node_modules/ansi-styles/index.js +3 -3
  29. package/node_modules/ansi-styles/package.json +4 -4
  30. package/node_modules/cli-truncate/index.d.ts +3 -3
  31. package/node_modules/cli-truncate/index.js +40 -14
  32. package/node_modules/cli-truncate/node_modules/string-width/index.js +133 -15
  33. package/node_modules/cli-truncate/node_modules/string-width/package.json +4 -4
  34. package/node_modules/cli-truncate/node_modules/string-width/readme.md +1 -1
  35. package/node_modules/cli-truncate/package.json +7 -7
  36. package/node_modules/cli-truncate/readme.md +2 -3
  37. package/node_modules/ghauth/.github/dependabot.yml +11 -3
  38. package/node_modules/ghauth/.github/workflows/depsound.yml +33 -0
  39. package/node_modules/ghauth/.github/workflows/test-and-release.yml +4 -4
  40. package/node_modules/ghauth/CHANGELOG.md +60 -0
  41. package/node_modules/ghauth/package.json +30 -6
  42. package/node_modules/js-yaml/README.md +20 -187
  43. package/node_modules/js-yaml/dist/browser/js-yaml.esm.min.mjs +1008 -946
  44. package/node_modules/js-yaml/dist/browser/js-yaml.esm.min.mjs.map +1 -1
  45. package/node_modules/js-yaml/dist/browser/js-yaml.umd.min.js +29 -23
  46. package/node_modules/js-yaml/dist/browser/js-yaml.umd.min.js.map +1 -1
  47. package/node_modules/js-yaml/dist/js-yaml.cjs.js +1043 -583
  48. package/node_modules/js-yaml/dist/js-yaml.cjs.js.map +1 -1
  49. package/node_modules/js-yaml/dist/js-yaml.d.ts +877 -133
  50. package/node_modules/js-yaml/dist/js-yaml.mjs +995 -556
  51. package/node_modules/js-yaml/dist/js-yaml.mjs.map +1 -1
  52. package/node_modules/js-yaml/package.json +8 -7
  53. package/node_modules/listr2/dist/{index.d.cts → index.d.mts} +90 -60
  54. package/node_modules/listr2/dist/{index.js → index.mjs} +496 -373
  55. package/node_modules/listr2/node_modules/string-width/index.d.ts +39 -0
  56. package/node_modules/listr2/node_modules/string-width/index.js +207 -0
  57. package/node_modules/listr2/node_modules/string-width/package.json +65 -0
  58. package/node_modules/listr2/node_modules/string-width/readme.md +66 -0
  59. package/node_modules/listr2/node_modules/wrap-ansi/index.d.ts +1 -1
  60. package/node_modules/listr2/node_modules/wrap-ansi/index.js +544 -116
  61. package/node_modules/listr2/node_modules/wrap-ansi/package.json +10 -13
  62. package/node_modules/listr2/node_modules/wrap-ansi/readme.md +2 -0
  63. package/node_modules/listr2/package.json +16 -19
  64. package/node_modules/log-update/index.d.ts +64 -1
  65. package/node_modules/log-update/index.js +268 -18
  66. package/node_modules/log-update/node_modules/string-width/index.d.ts +39 -0
  67. package/node_modules/log-update/node_modules/string-width/index.js +207 -0
  68. package/node_modules/log-update/node_modules/string-width/package.json +65 -0
  69. package/node_modules/log-update/node_modules/string-width/readme.md +66 -0
  70. package/node_modules/log-update/node_modules/wrap-ansi/index.d.ts +1 -1
  71. package/node_modules/log-update/node_modules/wrap-ansi/index.js +544 -116
  72. package/node_modules/log-update/node_modules/wrap-ansi/package.json +10 -13
  73. package/node_modules/log-update/node_modules/wrap-ansi/readme.md +2 -0
  74. package/node_modules/log-update/package.json +14 -12
  75. package/node_modules/log-update/readme.md +61 -0
  76. package/node_modules/slice-ansi/index.d.ts +2 -2
  77. package/node_modules/slice-ansi/index.js +260 -112
  78. package/node_modules/slice-ansi/package.json +10 -9
  79. package/node_modules/slice-ansi/readme.md +3 -2
  80. package/node_modules/slice-ansi/tokenize-ansi.js +752 -0
  81. package/node_modules/smol-toml/README.md +205 -191
  82. package/node_modules/smol-toml/dist/date.d.ts +0 -1
  83. package/node_modules/smol-toml/dist/date.js +0 -1
  84. package/node_modules/smol-toml/dist/error.d.ts +0 -1
  85. package/node_modules/smol-toml/dist/error.js +0 -1
  86. package/node_modules/smol-toml/dist/extract.js +32 -65
  87. package/node_modules/smol-toml/dist/index.cjs +302 -288
  88. package/node_modules/smol-toml/dist/index.d.ts +0 -1
  89. package/node_modules/smol-toml/dist/index.js +0 -1
  90. package/node_modules/smol-toml/dist/parse.d.ts +1 -3
  91. package/node_modules/smol-toml/dist/parse.js +22 -21
  92. package/node_modules/smol-toml/dist/primitive.js +75 -76
  93. package/node_modules/smol-toml/dist/stringify.d.ts +0 -1
  94. package/node_modules/smol-toml/dist/stringify.js +43 -30
  95. package/node_modules/smol-toml/dist/struct.js +90 -93
  96. package/node_modules/smol-toml/dist/util.d.ts +1 -1
  97. package/node_modules/smol-toml/dist/util.js +36 -39
  98. package/node_modules/smol-toml/package.json +6 -6
  99. package/node_modules/strip-ansi/index.js +5 -0
  100. package/node_modules/strip-ansi/package.json +5 -5
  101. package/package.json +3 -5
  102. package/node_modules/@listr2/prompt-adapter-enquirer/dist/index.cjs +0 -127
  103. package/node_modules/@listr2/prompt-adapter-enquirer/dist/index.d.cts +0 -152
  104. package/node_modules/@listr2/prompt-adapter-enquirer/dist/index.d.ts +0 -152
  105. package/node_modules/@listr2/prompt-adapter-enquirer/dist/index.js +0 -92
  106. package/node_modules/chalk/package.json +0 -92
  107. package/node_modules/chalk/readme.md +0 -331
  108. package/node_modules/chalk/source/index.d.ts +0 -437
  109. package/node_modules/chalk/source/index.js +0 -261
  110. package/node_modules/chalk/source/utilities.js +0 -33
  111. package/node_modules/chalk/source/vendor/ansi-styles/index.d.ts +0 -299
  112. package/node_modules/chalk/source/vendor/ansi-styles/index.js +0 -261
  113. package/node_modules/chalk/source/vendor/supports-color/browser.d.ts +0 -1
  114. package/node_modules/chalk/source/vendor/supports-color/browser.js +0 -32
  115. package/node_modules/chalk/source/vendor/supports-color/index.d.ts +0 -55
  116. package/node_modules/chalk/source/vendor/supports-color/index.js +0 -206
  117. package/node_modules/colorette/LICENSE.md +0 -7
  118. package/node_modules/colorette/README.md +0 -134
  119. package/node_modules/colorette/index.cjs +0 -218
  120. package/node_modules/colorette/index.d.ts +0 -93
  121. package/node_modules/colorette/index.js +0 -150
  122. package/node_modules/colorette/package.json +0 -40
  123. package/node_modules/eventemitter3/LICENSE +0 -21
  124. package/node_modules/eventemitter3/README.md +0 -94
  125. package/node_modules/eventemitter3/dist/eventemitter3.esm.js +0 -347
  126. package/node_modules/eventemitter3/dist/eventemitter3.esm.min.js +0 -1
  127. package/node_modules/eventemitter3/dist/eventemitter3.esm.min.js.map +0 -1
  128. package/node_modules/eventemitter3/dist/eventemitter3.umd.js +0 -355
  129. package/node_modules/eventemitter3/dist/eventemitter3.umd.min.js +0 -1
  130. package/node_modules/eventemitter3/dist/eventemitter3.umd.min.js.map +0 -1
  131. package/node_modules/eventemitter3/index.d.ts +0 -135
  132. package/node_modules/eventemitter3/index.js +0 -336
  133. package/node_modules/eventemitter3/index.mjs +0 -4
  134. package/node_modules/eventemitter3/package.json +0 -67
  135. package/node_modules/figures/index.d.ts +0 -279
  136. package/node_modules/figures/index.js +0 -292
  137. package/node_modules/figures/package.json +0 -49
  138. package/node_modules/figures/readme.md +0 -337
  139. package/node_modules/listr2/dist/index.cjs +0 -2189
  140. package/node_modules/listr2/dist/index.d.ts +0 -1528
  141. package/node_modules/rfdc/.github/workflows/ci.yml +0 -21
  142. package/node_modules/rfdc/LICENSE +0 -15
  143. package/node_modules/rfdc/default.js +0 -3
  144. package/node_modules/rfdc/index.d.ts +0 -13
  145. package/node_modules/rfdc/index.js +0 -198
  146. package/node_modules/rfdc/index.test-d.ts +0 -13
  147. package/node_modules/rfdc/package.json +0 -73
  148. package/node_modules/rfdc/readme.md +0 -181
  149. package/node_modules/rfdc/test/index.js +0 -306
  150. package/node_modules/smol-toml/dist/date.d.ts.map +0 -1
  151. package/node_modules/smol-toml/dist/date.js.map +0 -1
  152. package/node_modules/smol-toml/dist/error.d.ts.map +0 -1
  153. package/node_modules/smol-toml/dist/error.js.map +0 -1
  154. package/node_modules/smol-toml/dist/extract.js.map +0 -1
  155. package/node_modules/smol-toml/dist/index.cjs.map +0 -1
  156. package/node_modules/smol-toml/dist/index.d.ts.map +0 -1
  157. package/node_modules/smol-toml/dist/index.js.map +0 -1
  158. package/node_modules/smol-toml/dist/parse.d.ts.map +0 -1
  159. package/node_modules/smol-toml/dist/parse.js.map +0 -1
  160. package/node_modules/smol-toml/dist/primitive.d.ts +0 -29
  161. package/node_modules/smol-toml/dist/primitive.d.ts.map +0 -1
  162. package/node_modules/smol-toml/dist/primitive.js.map +0 -1
  163. package/node_modules/smol-toml/dist/stringify.d.ts.map +0 -1
  164. package/node_modules/smol-toml/dist/stringify.js.map +0 -1
  165. package/node_modules/smol-toml/dist/struct.js.map +0 -1
  166. package/node_modules/smol-toml/dist/util.d.ts.map +0 -1
  167. package/node_modules/smol-toml/dist/util.js.map +0 -1
  168. package/node_modules/smol-toml/src/date.ts +0 -152
  169. package/node_modules/smol-toml/src/error.ts +0 -77
  170. package/node_modules/smol-toml/src/extract.ts +0 -113
  171. package/node_modules/smol-toml/src/index.ts +0 -42
  172. package/node_modules/smol-toml/src/parse.ts +0 -184
  173. package/node_modules/smol-toml/src/primitive.ts +0 -249
  174. package/node_modules/smol-toml/src/stringify.ts +0 -191
  175. package/node_modules/smol-toml/src/struct.ts +0 -202
  176. package/node_modules/smol-toml/src/util.ts +0 -106
  177. /package/node_modules/@listr2/{prompt-adapter-enquirer → prompt-adapter-inquirer}/LICENSE +0 -0
  178. /package/node_modules/{log-update/node_modules/ansi-escapes → ansi-escapes}/index.d.ts +0 -0
  179. /package/node_modules/{log-update/node_modules/ansi-escapes → ansi-escapes}/index.js +0 -0
  180. /package/node_modules/{chalk → ansi-escapes}/license +0 -0
  181. /package/node_modules/{figures → listr2/node_modules/string-width}/license +0 -0
  182. /package/node_modules/log-update/node_modules/{ansi-escapes → string-width}/license +0 -0
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wrap-ansi",
3
- "version": "9.0.0",
3
+ "version": "10.0.1",
4
4
  "description": "Wordwrap a string with ANSI escape codes",
5
5
  "license": "MIT",
6
6
  "repository": "chalk/wrap-ansi",
@@ -16,10 +16,10 @@
16
16
  "default": "./index.js"
17
17
  },
18
18
  "engines": {
19
- "node": ">=18"
19
+ "node": ">=20"
20
20
  },
21
21
  "scripts": {
22
- "test": "xo && nyc ava && tsd"
22
+ "test": "xo && node --test && tsd"
23
23
  },
24
24
  "files": [
25
25
  "index.js",
@@ -53,17 +53,14 @@
53
53
  "text"
54
54
  ],
55
55
  "dependencies": {
56
- "ansi-styles": "^6.2.1",
57
- "string-width": "^7.0.0",
58
- "strip-ansi": "^7.1.0"
56
+ "ansi-styles": "^6.2.3",
57
+ "string-width": "^8.2.0"
59
58
  },
60
59
  "devDependencies": {
61
- "ava": "^5.3.1",
62
- "chalk": "^5.3.0",
63
- "coveralls": "^3.1.1",
64
- "has-ansi": "^5.0.1",
65
- "nyc": "^15.1.0",
66
- "tsd": "^0.29.0",
67
- "xo": "^0.56.0"
60
+ "chalk": "^5.6.2",
61
+ "has-ansi": "^6.0.2",
62
+ "strip-ansi": "^7.1.2",
63
+ "tsd": "^0.33.0",
64
+ "xo": "^1.2.3"
68
65
  }
69
66
  }
@@ -36,6 +36,8 @@ A string with ANSI escape codes, like one styled by [`chalk`](https://github.com
36
36
 
37
37
  Newline characters will be normalized to `\n`.
38
38
 
39
+ Tab characters are expanded to spaces using 8-column tab stops before wrapping.
40
+
39
41
  #### columns
40
42
 
41
43
  Type: `number`
@@ -1,22 +1,22 @@
1
1
  {
2
2
  "name": "listr2",
3
- "version": "9.0.5",
3
+ "version": "11.1.0",
4
4
  "description": "Terminal task list reborn! Create beautiful CLI interfaces via easy and logical to implement task lists that feel alive and interactive.",
5
5
  "license": "MIT",
6
6
  "repository": "https://github.com/listr2/listr2",
7
7
  "type": "module",
8
- "main": "./dist/index.cjs",
9
- "module": "./dist/index.js",
10
- "types": "./dist/index.d.ts",
8
+ "main": "./dist/index.mjs",
9
+ "module": "./dist/index.mjs",
10
+ "types": "./dist/index.d.mts",
11
11
  "exports": {
12
12
  ".": {
13
13
  "require": {
14
- "types": "./dist/index.d.cts",
15
- "default": "./dist/index.cjs"
14
+ "types": "./dist/index.d.mts",
15
+ "default": "./dist/index.mjs"
16
16
  },
17
17
  "import": {
18
- "types": "./dist/index.d.ts",
19
- "default": "./dist/index.js"
18
+ "types": "./dist/index.d.mts",
19
+ "default": "./dist/index.mjs"
20
20
  }
21
21
  },
22
22
  "./package.json": "./package.json"
@@ -30,21 +30,21 @@
30
30
  "access": "public"
31
31
  },
32
32
  "engines": {
33
- "node": ">=20.0.0"
33
+ "node": ">=22.13.0"
34
34
  },
35
35
  "scripts": {
36
36
  "build": "tsdown",
37
37
  "dev:start": "tsdown --watch",
38
38
  "format": "prettier --log-level warn --write src/ tests/ && pnpm run lint --fix",
39
- "lint": "eslint --flag v10_config_lookup_from_file src/ tests/",
40
- "test": "NO_COLOR=1 TS_NODE_PROJECT=tests/tsconfig.json NODE_OPTIONS='--no-warnings --experimental-specifier-resolution=node --experimental-vm-modules' jest --config tests/jest.config.ts",
39
+ "lint": "eslint src/ tests/",
40
+ "test": "NODE_OPTIONS='--no-warnings --experimental-vm-modules' jest --config tests/jest.config.ts",
41
41
  "test:cov": "pnpm run test --coverage",
42
- "test:dev": "NODE_OPTIONS='--no-warnings --experimental-specifier-resolution=node --experimental-vm-modules --inspect=0.0.0.0:9229' pnpm run test --verbose --watchAll"
42
+ "test:dev": "NODE_OPTIONS='--no-warnings --experimental-vm-modules --inspect=0.0.0.0:9229' pnpm run test --verbose --watchAll"
43
43
  },
44
44
  "lint-staged": {
45
45
  "{src,tests}/**/*.{ts,js,tsx,jsx,spec.ts}": [
46
46
  "prettier --log-level warn --write",
47
- "eslint --flag v10_config_lookup_from_file --fix"
47
+ "eslint --fix"
48
48
  ],
49
49
  "*.{json,md}": [
50
50
  "prettier --log-level warn --write"
@@ -70,12 +70,9 @@
70
70
  "idle"
71
71
  ],
72
72
  "dependencies": {
73
- "cli-truncate": "^5.0.0",
74
- "colorette": "^2.0.20",
75
- "eventemitter3": "^5.0.1",
76
- "log-update": "^6.1.0",
77
- "rfdc": "^1.4.1",
78
- "wrap-ansi": "^9.0.0"
73
+ "cli-truncate": "^6.1.1",
74
+ "log-update": "^8.0.0",
75
+ "wrap-ansi": "^10.0.1"
79
76
  },
80
77
  "devDependencies": {
81
78
  "@types/zen-observable": "^0.8.7",
@@ -13,6 +13,44 @@ export type Options = {
13
13
  ```
14
14
  */
15
15
  readonly showCursor?: boolean;
16
+
17
+ /**
18
+ The width to use when the stream doesn't provide a `columns` property. Defaults to `80`.
19
+
20
+ This is useful when the output is piped, redirected, or in environments where the terminal size is not available.
21
+
22
+ @default 80
23
+
24
+ @example
25
+ ```
26
+ import {createLogUpdate} from 'log-update';
27
+
28
+ // Use custom width when the stream doesn't provide columns
29
+ const log = createLogUpdate(process.stdout, {
30
+ defaultWidth: 120
31
+ });
32
+ ```
33
+ */
34
+ readonly defaultWidth?: number;
35
+
36
+ /**
37
+ The height to use when the stream doesn't provide a `rows` property. Defaults to `24`.
38
+
39
+ This is useful when the output is piped, redirected, or in environments where the terminal size is not available.
40
+
41
+ @default 24
42
+
43
+ @example
44
+ ```
45
+ import {createLogUpdate} from 'log-update';
46
+
47
+ // Use custom height when the stream doesn't provide rows
48
+ const log = createLogUpdate(process.stdout, {
49
+ defaultHeight: 50
50
+ });
51
+ ```
52
+ */
53
+ readonly defaultHeight?: number;
16
54
  };
17
55
 
18
56
  type LogUpdateMethods = {
@@ -25,6 +63,31 @@ type LogUpdateMethods = {
25
63
  Persist the logged output. Useful if you want to start a new log session below the current one.
26
64
  */
27
65
  done(): void;
66
+
67
+ /**
68
+ Write text to the terminal that persists in the scrollback buffer.
69
+
70
+ Unlike the main log function which updates in place, this method writes output that remains in the terminal history, similar to `console.log()`. This is useful for displaying permanent results, status messages, or logs that users need to scroll back and review.
71
+
72
+ @param text - The text to persist to the terminal.
73
+
74
+ @example
75
+ ```
76
+ import logUpdate from 'log-update';
77
+
78
+ // Update in place
79
+ logUpdate('Processing...');
80
+ logUpdate('Still processing...');
81
+
82
+ // Write permanent output
83
+ logUpdate.persist('✓ Task complete');
84
+ logUpdate.persist('✓ All tests passed');
85
+
86
+ // Continue with updates
87
+ logUpdate('Starting next task...');
88
+ ```
89
+ */
90
+ persist(...text: string[]): void;
28
91
  };
29
92
 
30
93
  /**
@@ -100,5 +163,5 @@ const log = createLogUpdate(process.stdout);
100
163
  */
101
164
  export function createLogUpdate(
102
165
  stream: NodeJS.WritableStream,
103
- options?: Options
166
+ options?: Options,
104
167
  ): typeof logUpdate;
@@ -4,26 +4,200 @@ import cliCursor from 'cli-cursor';
4
4
  import wrapAnsi from 'wrap-ansi';
5
5
  import sliceAnsi from 'slice-ansi';
6
6
  import stripAnsi from 'strip-ansi';
7
+ import stringWidth from 'string-width';
7
8
 
8
- const defaultTerminalHeight = 24;
9
+ const getWidth = (stream, defaultWidth) => stream.columns ?? defaultWidth ?? 80;
10
+ const SYNCHRONIZED_OUTPUT_ENABLE = '\u001B[?2026h';
11
+ const SYNCHRONIZED_OUTPUT_DISABLE = '\u001B[?2026l';
12
+ const countLines = text => text.split('\n').length;
13
+ const getFrameHeight = text => {
14
+ const unstyledText = stripAnsi(text);
15
+ return unstyledText === '' ? 0 : unstyledText.split('\n').length;
16
+ };
17
+
18
+ const fitToTerminalHeight = (stream, wrappedText, defaultHeight) => {
19
+ const terminalHeight = stream.rows ?? defaultHeight ?? 24;
9
20
 
10
- const getWidth = ({columns = 80}) => columns;
21
+ // Zero height: intentionally produce no output
22
+ if (terminalHeight === 0) {
23
+ return {text: '', wasClipped: wrappedText !== ''};
24
+ }
11
25
 
12
- const fitToTerminalHeight = (stream, text) => {
13
- const terminalHeight = stream.rows ?? defaultTerminalHeight;
14
- const lines = text.split('\n');
26
+ const unstyled = stripAnsi(wrappedText);
27
+ const lines = unstyled.split('\n');
15
28
  const toRemove = Math.max(0, lines.length - terminalHeight);
16
- return toRemove ? sliceAnsi(text, stripAnsi(lines.slice(0, toRemove).join('\n')).length + 1) : text;
29
+ if (toRemove === 0) {
30
+ return {text: wrappedText, wasClipped: false};
31
+ }
32
+
33
+ // Compute visible-column cut to match sliceAnsi's position tracking.
34
+ // sliceAnsi counts each \n as 1 column, but stringWidth returns 0 for \n.
35
+ let cut = 0;
36
+ for (let index = 0; index < toRemove; index++) {
37
+ cut += stringWidth(lines[index]) + 1;
38
+ }
39
+
40
+ let clippedText = sliceAnsi(wrappedText, cut);
41
+ let clippedLineCount = getFrameHeight(clippedText);
42
+
43
+ // Normalize the estimated cut so it matches sliceAnsi's width model.
44
+ while (clippedLineCount > terminalHeight) {
45
+ cut++;
46
+ clippedText = sliceAnsi(wrappedText, cut);
47
+ clippedLineCount = getFrameHeight(clippedText);
48
+ }
49
+
50
+ while (cut > 0) {
51
+ const previousClippedText = sliceAnsi(wrappedText, cut - 1);
52
+ if (getFrameHeight(previousClippedText) > terminalHeight) {
53
+ break;
54
+ }
55
+
56
+ cut--;
57
+ clippedText = previousClippedText;
58
+ }
59
+
60
+ return {text: clippedText, wasClipped: cut > 0};
17
61
  };
18
62
 
19
- export function createLogUpdate(stream, {showCursor = false} = {}) {
63
+ /**
64
+ Find common prefix and suffix between frames.
65
+ */
66
+ const diffFrames = (previousLines, nextLines) => {
67
+ let start = 0;
68
+ for (; start < previousLines.length && start < nextLines.length; start++) {
69
+ if (previousLines[start] !== nextLines[start]) {
70
+ break;
71
+ }
72
+ }
73
+
74
+ let endPrevious = previousLines.length - 1;
75
+ let endNext = nextLines.length - 1;
76
+ while (endPrevious >= start && endNext >= start && previousLines[endPrevious] === nextLines[endNext]) {
77
+ endPrevious--;
78
+ endNext--;
79
+ }
80
+
81
+ return {start, endPrevious, endNext};
82
+ };
83
+
84
+ /**
85
+ Build a single escape sequence patch to transform previous -> next.
86
+ */
87
+ const buildPatch = ({
88
+ prevCount,
89
+ start,
90
+ endPrevious,
91
+ endNext,
92
+ nextLines,
93
+ nextWrappedEndsWithNewline,
94
+ }) => {
95
+ let sequence = '';
96
+
97
+ // Move cursor from the trailing blank line to the first changed line.
98
+ const cursorLine = prevCount - 1;
99
+ const cursorLineDelta = start - cursorLine;
100
+ if (cursorLineDelta > 0) {
101
+ sequence += ansiEscapes.cursorDown(cursorLineDelta);
102
+ }
103
+
104
+ if (cursorLineDelta < 0) {
105
+ sequence += ansiEscapes.cursorUp(-cursorLineDelta);
106
+ }
107
+
108
+ sequence += ansiEscapes.cursorLeft;
109
+
110
+ // Clear the changed block from the previous frame.
111
+ const linesToClear = Math.max(0, endPrevious - start + 1);
112
+ for (let index = 0; index < linesToClear; index++) {
113
+ sequence += ansiEscapes.eraseLine;
114
+
115
+ if (index < linesToClear - 1) {
116
+ sequence += ansiEscapes.cursorDown();
117
+ }
118
+ }
119
+
120
+ if (linesToClear > 1) {
121
+ sequence += ansiEscapes.cursorUp(linesToClear - 1);
122
+ }
123
+
124
+ sequence += ansiEscapes.cursorLeft;
125
+
126
+ // Write the new changed block.
127
+ const wroteSlice = nextLines.slice(start, endNext + 1);
128
+ let writtenLineBreakCount = 0;
129
+ if (wroteSlice.length > 0) {
130
+ const chunk = wroteSlice.join('\n');
131
+ const shouldWriteTrailingNewline = nextWrappedEndsWithNewline
132
+ && endNext < nextLines.length - 1
133
+ && !chunk.endsWith('\n');
134
+
135
+ sequence += chunk;
136
+ writtenLineBreakCount = countLines(chunk) - 1;
137
+
138
+ // Ensure we do not leave trailing characters on the last written line
139
+ sequence += ansiEscapes.eraseEndLine;
140
+
141
+ if (shouldWriteTrailingNewline) {
142
+ sequence += '\n';
143
+ writtenLineBreakCount++;
144
+ }
145
+ }
146
+
147
+ // Reposition cursor to the final trailing blank line for the next call
148
+ const currentLine = start + writtenLineBreakCount;
149
+ const finalLine = nextLines.length - 1;
150
+ const lineDelta = finalLine - currentLine;
151
+ if (lineDelta > 0) {
152
+ sequence += ansiEscapes.cursorDown(lineDelta);
153
+ }
154
+
155
+ if (lineDelta < 0) {
156
+ sequence += ansiEscapes.cursorUp(-lineDelta);
157
+ }
158
+
159
+ return sequence;
160
+ };
161
+
162
+ export function createLogUpdate(stream, {showCursor = false, defaultWidth, defaultHeight} = {}) {
20
163
  let previousLineCount = 0;
21
- let previousWidth = getWidth(stream);
164
+ let previousWidth = getWidth(stream, defaultWidth);
22
165
  let previousOutput = '';
166
+ const useSynchronizedOutput = stream.isTTY === true;
167
+
168
+ const write = output => {
169
+ if (output === '') {
170
+ return;
171
+ }
172
+
173
+ if (useSynchronizedOutput) {
174
+ stream.write(SYNCHRONIZED_OUTPUT_ENABLE + output + SYNCHRONIZED_OUTPUT_DISABLE);
175
+ return;
176
+ }
177
+
178
+ stream.write(output);
179
+ };
180
+
181
+ /**
182
+ Normalize, wrap, and height-clip into a concrete frame.
183
+
184
+ Returns both the wrapped string and an array of lines, where an empty frame (for `rows === 0`) is represented by `lines.length === 0`.
185
+ */
186
+ const computeFrame = (text, width, clipToHeight = true) => {
187
+ // Preserve user's trailing newlines, ensure at least one
188
+ const textString = String(text);
189
+ const raw = textString.endsWith('\n') ? textString : `${textString}\n`;
190
+ const wrapped = wrapAnsi(raw, width, {trim: false, hard: true, wordWrap: false});
191
+ const {text: frameText, wasClipped} = clipToHeight ? fitToTerminalHeight(stream, wrapped, defaultHeight) : {text: wrapped, wasClipped: false};
192
+
193
+ // Derive lines. Special-case empty string to represent 0 lines.
194
+ const lines = frameText === '' ? [] : frameText.split('\n');
195
+ return {wrapped: frameText, lines, wasClipped};
196
+ };
23
197
 
24
198
  const reset = () => {
25
199
  previousOutput = '';
26
- previousWidth = getWidth(stream);
200
+ previousWidth = getWidth(stream, defaultWidth);
27
201
  previousLineCount = 0;
28
202
  };
29
203
 
@@ -32,23 +206,89 @@ export function createLogUpdate(stream, {showCursor = false} = {}) {
32
206
  cliCursor.hide();
33
207
  }
34
208
 
35
- let output = fitToTerminalHeight(stream, arguments_.join(' ') + '\n');
36
- const width = getWidth(stream);
209
+ const width = getWidth(stream, defaultWidth);
210
+ const {wrapped, lines, wasClipped} = computeFrame(arguments_.join(' '), width);
211
+
212
+ // If nothing would be written (rows === 0 after clipping), erase previous output and update state.
213
+ if (lines.length === 0) {
214
+ if (previousLineCount > 0) {
215
+ write(ansiEscapes.eraseLines(previousLineCount));
216
+ }
37
217
 
38
- if (output === previousOutput && previousWidth === width) {
218
+ previousOutput = wrapped;
219
+ previousWidth = width;
220
+ previousLineCount = 0;
39
221
  return;
40
222
  }
41
223
 
42
- previousOutput = output;
43
- previousWidth = width;
44
- output = wrapAnsi(output, width, {trim: false, hard: true, wordWrap: false});
224
+ // Fast no-op path
225
+ if (wrapped === previousOutput && previousWidth === width) {
226
+ return;
227
+ }
228
+
229
+ // First frame: just write
230
+ if (previousLineCount === 0) {
231
+ write(wrapped);
232
+
233
+ previousOutput = wrapped;
234
+ previousWidth = width;
235
+ previousLineCount = lines.length;
236
+ return;
237
+ }
238
+
239
+ // Width changed or content clipped: full erase + write (diffing is invalid across wraps/clips)
240
+ if (previousWidth !== width || wasClipped) {
241
+ write(ansiEscapes.eraseLines(previousLineCount) + wrapped);
242
+
243
+ previousOutput = wrapped;
244
+ previousWidth = width;
245
+ previousLineCount = lines.length;
246
+ return;
247
+ }
45
248
 
46
- stream.write(ansiEscapes.eraseLines(previousLineCount) + output);
47
- previousLineCount = output.split('\n').length;
249
+ const previousLines = previousOutput === '' ? [] : previousOutput.split('\n');
250
+ let {start, endPrevious, endNext} = diffFrames(previousLines, lines);
251
+
252
+ // When lines are inserted or removed, the suffix shifts position.
253
+ // Include it in the rewrite so it renders at the correct row.
254
+ if (previousLines.length !== lines.length) {
255
+ endPrevious = previousLines.length - 1;
256
+ endNext = lines.length - 1;
257
+ }
258
+
259
+ // If nothing changed (including trailing blank logic), bail
260
+ if (start === lines.length && previousLineCount === lines.length) {
261
+ return;
262
+ }
263
+
264
+ // If common prefix length is zero, simpler and correct to full erase.
265
+ if (start === 0) {
266
+ write(ansiEscapes.eraseLines(previousLineCount) + wrapped);
267
+
268
+ previousOutput = wrapped;
269
+ previousWidth = width;
270
+ previousLineCount = lines.length;
271
+ return;
272
+ }
273
+
274
+ const patch = buildPatch({
275
+ prevCount: previousLineCount,
276
+ start,
277
+ endPrevious,
278
+ endNext,
279
+ nextLines: lines,
280
+ nextWrappedEndsWithNewline: wrapped.endsWith('\n'),
281
+ });
282
+
283
+ write(patch);
284
+
285
+ previousOutput = wrapped;
286
+ previousWidth = width;
287
+ previousLineCount = lines.length;
48
288
  };
49
289
 
50
290
  render.clear = () => {
51
- stream.write(ansiEscapes.eraseLines(previousLineCount));
291
+ write(ansiEscapes.eraseLines(previousLineCount));
52
292
  reset();
53
293
  };
54
294
 
@@ -59,10 +299,20 @@ export function createLogUpdate(stream, {showCursor = false} = {}) {
59
299
  }
60
300
  };
61
301
 
302
+ render.persist = (...arguments_) => {
303
+ const erasePrevious = previousLineCount > 0 ? ansiEscapes.eraseLines(previousLineCount) : '';
304
+ const width = getWidth(stream, defaultWidth);
305
+ const {wrapped: wrappedText} = computeFrame(arguments_.join(' '), width, false);
306
+ write(erasePrevious + wrappedText);
307
+
308
+ reset();
309
+ };
310
+
62
311
  return render;
63
312
  }
64
313
 
65
314
  const logUpdate = createLogUpdate(process.stdout);
315
+
66
316
  export default logUpdate;
67
317
 
68
318
  export const logUpdateStderr = createLogUpdate(process.stderr);
@@ -0,0 +1,39 @@
1
+ export type Options = {
2
+ /**
3
+ Count [ambiguous width characters](https://www.unicode.org/reports/tr11/#Ambiguous) as having narrow width (count of 1) instead of wide width (count of 2).
4
+
5
+ @default true
6
+
7
+ > Ambiguous characters behave like wide or narrow characters depending on the context (language tag, script identification, associated font, source of data, or explicit markup; all can provide the context). __If the context cannot be established reliably, they should be treated as narrow characters by default.__
8
+ > - http://www.unicode.org/reports/tr11/
9
+ */
10
+ readonly ambiguousIsNarrow?: boolean;
11
+
12
+ /**
13
+ Whether [ANSI escape codes](https://en.wikipedia.org/wiki/ANSI_escape_code) should be counted.
14
+
15
+ @default false
16
+ */
17
+ readonly countAnsiEscapeCodes?: boolean;
18
+ };
19
+
20
+ /**
21
+ Get the visual width of a string - the number of columns required to display it.
22
+
23
+ Some Unicode characters are [fullwidth](https://en.wikipedia.org/wiki/Halfwidth_and_fullwidth_forms) and use double the normal width. [ANSI escape codes](https://en.wikipedia.org/wiki/ANSI_escape_code) are stripped and doesn't affect the width.
24
+
25
+ @example
26
+ ```
27
+ import stringWidth from 'string-width';
28
+
29
+ stringWidth('a');
30
+ //=> 1
31
+
32
+ stringWidth('古');
33
+ //=> 2
34
+
35
+ stringWidth('\u001B[1m古\u001B[22m');
36
+ //=> 2
37
+ ```
38
+ */
39
+ export default function stringWidth(string: string, options?: Options): number;