@node-core/utils 7.1.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 (232) hide show
  1. package/components/metadata.js +2 -1
  2. package/lib/ci/build-types/test_build.js +3 -3
  3. package/lib/ci/failure_aggregator.js +2 -2
  4. package/lib/ci/run_ci.js +3 -2
  5. package/lib/cli.js +6 -6
  6. package/lib/figures.js +6 -6
  7. package/lib/prepare_security.js +20 -21
  8. package/lib/request.js +1 -1
  9. package/lib/update-v8/backport.js +3 -4
  10. package/lib/utils.js +4 -2
  11. package/lib/verbosity.js +2 -4
  12. package/node_modules/@listr2/{prompt-adapter-enquirer → prompt-adapter-inquirer}/README.md +4 -4
  13. package/node_modules/@listr2/prompt-adapter-inquirer/dist/index.d.mts +21 -0
  14. package/node_modules/@listr2/prompt-adapter-inquirer/dist/index.mjs +46 -0
  15. package/node_modules/@listr2/prompt-adapter-inquirer/node_modules/@inquirer/type/LICENSE +22 -0
  16. package/node_modules/@listr2/prompt-adapter-inquirer/node_modules/@inquirer/type/dist/index.d.ts +2 -0
  17. package/node_modules/@listr2/prompt-adapter-inquirer/node_modules/@inquirer/type/dist/index.js +2 -0
  18. package/node_modules/@listr2/prompt-adapter-inquirer/node_modules/@inquirer/type/dist/inquirer.d.ts +35 -0
  19. package/node_modules/@listr2/prompt-adapter-inquirer/node_modules/@inquirer/type/dist/inquirer.js +1 -0
  20. package/node_modules/@listr2/prompt-adapter-inquirer/node_modules/@inquirer/type/dist/utils.d.ts +34 -0
  21. package/node_modules/@listr2/prompt-adapter-inquirer/node_modules/@inquirer/type/dist/utils.js +2 -0
  22. package/node_modules/@listr2/prompt-adapter-inquirer/node_modules/@inquirer/type/package.json +88 -0
  23. package/node_modules/@listr2/{prompt-adapter-enquirer → prompt-adapter-inquirer}/package.json +25 -20
  24. package/node_modules/{log-update/node_modules/ansi-escapes → ansi-escapes}/base.d.ts +48 -3
  25. package/node_modules/{log-update/node_modules/ansi-escapes → ansi-escapes}/base.js +58 -19
  26. package/node_modules/{log-update/node_modules/ansi-escapes → ansi-escapes}/package.json +1 -1
  27. package/node_modules/{log-update/node_modules/ansi-escapes → ansi-escapes}/readme.md +36 -1
  28. package/node_modules/ansi-regex/index.js +9 -4
  29. package/node_modules/ansi-regex/package.json +1 -1
  30. package/node_modules/ansi-regex/readme.md +6 -0
  31. package/node_modules/ansi-styles/index.d.ts +12 -12
  32. package/node_modules/ansi-styles/index.js +3 -3
  33. package/node_modules/ansi-styles/package.json +4 -4
  34. package/node_modules/cli-truncate/index.d.ts +3 -3
  35. package/node_modules/cli-truncate/index.js +40 -14
  36. package/node_modules/cli-truncate/node_modules/string-width/index.js +133 -15
  37. package/node_modules/cli-truncate/node_modules/string-width/package.json +4 -4
  38. package/node_modules/cli-truncate/node_modules/string-width/readme.md +1 -1
  39. package/node_modules/cli-truncate/package.json +7 -7
  40. package/node_modules/cli-truncate/readme.md +2 -3
  41. package/node_modules/ghauth/.github/dependabot.yml +11 -3
  42. package/node_modules/ghauth/.github/workflows/depsound.yml +33 -0
  43. package/node_modules/ghauth/.github/workflows/test-and-release.yml +4 -4
  44. package/node_modules/ghauth/CHANGELOG.md +60 -0
  45. package/node_modules/ghauth/package.json +30 -6
  46. package/node_modules/js-yaml/README.md +20 -187
  47. package/node_modules/js-yaml/dist/browser/js-yaml.esm.min.mjs +1008 -946
  48. package/node_modules/js-yaml/dist/browser/js-yaml.esm.min.mjs.map +1 -1
  49. package/node_modules/js-yaml/dist/browser/js-yaml.umd.min.js +29 -23
  50. package/node_modules/js-yaml/dist/browser/js-yaml.umd.min.js.map +1 -1
  51. package/node_modules/js-yaml/dist/js-yaml.cjs.js +1043 -583
  52. package/node_modules/js-yaml/dist/js-yaml.cjs.js.map +1 -1
  53. package/node_modules/js-yaml/dist/js-yaml.d.ts +877 -133
  54. package/node_modules/js-yaml/dist/js-yaml.mjs +995 -556
  55. package/node_modules/js-yaml/dist/js-yaml.mjs.map +1 -1
  56. package/node_modules/js-yaml/package.json +8 -7
  57. package/node_modules/listr2/dist/{index.d.cts → index.d.mts} +90 -60
  58. package/node_modules/listr2/dist/{index.js → index.mjs} +496 -373
  59. package/node_modules/listr2/node_modules/string-width/index.d.ts +39 -0
  60. package/node_modules/listr2/node_modules/string-width/index.js +207 -0
  61. package/node_modules/listr2/node_modules/string-width/package.json +65 -0
  62. package/node_modules/listr2/node_modules/string-width/readme.md +66 -0
  63. package/node_modules/listr2/node_modules/wrap-ansi/index.d.ts +1 -1
  64. package/node_modules/listr2/node_modules/wrap-ansi/index.js +544 -116
  65. package/node_modules/listr2/node_modules/wrap-ansi/package.json +10 -13
  66. package/node_modules/listr2/node_modules/wrap-ansi/readme.md +2 -0
  67. package/node_modules/listr2/package.json +16 -19
  68. package/node_modules/log-update/index.d.ts +64 -1
  69. package/node_modules/log-update/index.js +268 -18
  70. package/node_modules/log-update/node_modules/string-width/index.d.ts +39 -0
  71. package/node_modules/log-update/node_modules/string-width/index.js +207 -0
  72. package/node_modules/log-update/node_modules/string-width/package.json +65 -0
  73. package/node_modules/log-update/node_modules/string-width/readme.md +66 -0
  74. package/node_modules/log-update/node_modules/wrap-ansi/index.d.ts +1 -1
  75. package/node_modules/log-update/node_modules/wrap-ansi/index.js +544 -116
  76. package/node_modules/log-update/node_modules/wrap-ansi/package.json +10 -13
  77. package/node_modules/log-update/node_modules/wrap-ansi/readme.md +2 -0
  78. package/node_modules/log-update/package.json +14 -12
  79. package/node_modules/log-update/readme.md +61 -0
  80. package/node_modules/slice-ansi/index.d.ts +2 -2
  81. package/node_modules/slice-ansi/index.js +260 -112
  82. package/node_modules/slice-ansi/package.json +10 -9
  83. package/node_modules/slice-ansi/readme.md +3 -2
  84. package/node_modules/slice-ansi/tokenize-ansi.js +752 -0
  85. package/node_modules/smol-toml/README.md +205 -191
  86. package/node_modules/smol-toml/dist/date.d.ts +0 -1
  87. package/node_modules/smol-toml/dist/date.js +0 -1
  88. package/node_modules/smol-toml/dist/error.d.ts +0 -1
  89. package/node_modules/smol-toml/dist/error.js +0 -1
  90. package/node_modules/smol-toml/dist/extract.js +32 -65
  91. package/node_modules/smol-toml/dist/index.cjs +302 -288
  92. package/node_modules/smol-toml/dist/index.d.ts +0 -1
  93. package/node_modules/smol-toml/dist/index.js +0 -1
  94. package/node_modules/smol-toml/dist/parse.d.ts +1 -3
  95. package/node_modules/smol-toml/dist/parse.js +22 -21
  96. package/node_modules/smol-toml/dist/primitive.js +75 -76
  97. package/node_modules/smol-toml/dist/stringify.d.ts +0 -1
  98. package/node_modules/smol-toml/dist/stringify.js +43 -30
  99. package/node_modules/smol-toml/dist/struct.js +90 -93
  100. package/node_modules/smol-toml/dist/util.d.ts +1 -1
  101. package/node_modules/smol-toml/dist/util.js +36 -39
  102. package/node_modules/smol-toml/package.json +6 -6
  103. package/node_modules/strip-ansi/index.js +5 -0
  104. package/node_modules/strip-ansi/package.json +5 -5
  105. package/package.json +3 -9
  106. package/node_modules/@listr2/prompt-adapter-enquirer/dist/index.cjs +0 -127
  107. package/node_modules/@listr2/prompt-adapter-enquirer/dist/index.d.cts +0 -152
  108. package/node_modules/@listr2/prompt-adapter-enquirer/dist/index.d.ts +0 -152
  109. package/node_modules/@listr2/prompt-adapter-enquirer/dist/index.js +0 -92
  110. package/node_modules/chalk/package.json +0 -92
  111. package/node_modules/chalk/readme.md +0 -331
  112. package/node_modules/chalk/source/index.d.ts +0 -437
  113. package/node_modules/chalk/source/index.js +0 -261
  114. package/node_modules/chalk/source/utilities.js +0 -33
  115. package/node_modules/chalk/source/vendor/ansi-styles/index.d.ts +0 -299
  116. package/node_modules/chalk/source/vendor/ansi-styles/index.js +0 -261
  117. package/node_modules/chalk/source/vendor/supports-color/browser.d.ts +0 -1
  118. package/node_modules/chalk/source/vendor/supports-color/browser.js +0 -32
  119. package/node_modules/chalk/source/vendor/supports-color/index.d.ts +0 -55
  120. package/node_modules/chalk/source/vendor/supports-color/index.js +0 -206
  121. package/node_modules/colorette/LICENSE.md +0 -7
  122. package/node_modules/colorette/README.md +0 -134
  123. package/node_modules/colorette/index.cjs +0 -218
  124. package/node_modules/colorette/index.d.ts +0 -93
  125. package/node_modules/colorette/index.js +0 -150
  126. package/node_modules/colorette/package.json +0 -40
  127. package/node_modules/eventemitter3/LICENSE +0 -21
  128. package/node_modules/eventemitter3/README.md +0 -94
  129. package/node_modules/eventemitter3/dist/eventemitter3.esm.js +0 -347
  130. package/node_modules/eventemitter3/dist/eventemitter3.esm.min.js +0 -1
  131. package/node_modules/eventemitter3/dist/eventemitter3.esm.min.js.map +0 -1
  132. package/node_modules/eventemitter3/dist/eventemitter3.umd.js +0 -355
  133. package/node_modules/eventemitter3/dist/eventemitter3.umd.min.js +0 -1
  134. package/node_modules/eventemitter3/dist/eventemitter3.umd.min.js.map +0 -1
  135. package/node_modules/eventemitter3/index.d.ts +0 -135
  136. package/node_modules/eventemitter3/index.js +0 -336
  137. package/node_modules/eventemitter3/index.mjs +0 -4
  138. package/node_modules/eventemitter3/package.json +0 -67
  139. package/node_modules/figures/index.d.ts +0 -279
  140. package/node_modules/figures/index.js +0 -292
  141. package/node_modules/figures/package.json +0 -49
  142. package/node_modules/figures/readme.md +0 -337
  143. package/node_modules/isexe/LICENSE.md +0 -55
  144. package/node_modules/isexe/README.md +0 -80
  145. package/node_modules/isexe/dist/commonjs/index.d.ts +0 -14
  146. package/node_modules/isexe/dist/commonjs/index.d.ts.map +0 -1
  147. package/node_modules/isexe/dist/commonjs/index.js +0 -56
  148. package/node_modules/isexe/dist/commonjs/index.js.map +0 -1
  149. package/node_modules/isexe/dist/commonjs/index.min.js +0 -2
  150. package/node_modules/isexe/dist/commonjs/index.min.js.map +0 -7
  151. package/node_modules/isexe/dist/commonjs/options.d.ts +0 -32
  152. package/node_modules/isexe/dist/commonjs/options.d.ts.map +0 -1
  153. package/node_modules/isexe/dist/commonjs/options.js +0 -3
  154. package/node_modules/isexe/dist/commonjs/options.js.map +0 -1
  155. package/node_modules/isexe/dist/commonjs/package.json +0 -3
  156. package/node_modules/isexe/dist/commonjs/posix.d.ts +0 -18
  157. package/node_modules/isexe/dist/commonjs/posix.d.ts.map +0 -1
  158. package/node_modules/isexe/dist/commonjs/posix.js +0 -67
  159. package/node_modules/isexe/dist/commonjs/posix.js.map +0 -1
  160. package/node_modules/isexe/dist/commonjs/win32.d.ts +0 -18
  161. package/node_modules/isexe/dist/commonjs/win32.d.ts.map +0 -1
  162. package/node_modules/isexe/dist/commonjs/win32.js +0 -63
  163. package/node_modules/isexe/dist/commonjs/win32.js.map +0 -1
  164. package/node_modules/isexe/dist/esm/index.d.ts +0 -14
  165. package/node_modules/isexe/dist/esm/index.d.ts.map +0 -1
  166. package/node_modules/isexe/dist/esm/index.js +0 -16
  167. package/node_modules/isexe/dist/esm/index.js.map +0 -1
  168. package/node_modules/isexe/dist/esm/index.min.js +0 -2
  169. package/node_modules/isexe/dist/esm/index.min.js.map +0 -7
  170. package/node_modules/isexe/dist/esm/options.d.ts +0 -32
  171. package/node_modules/isexe/dist/esm/options.d.ts.map +0 -1
  172. package/node_modules/isexe/dist/esm/options.js +0 -2
  173. package/node_modules/isexe/dist/esm/options.js.map +0 -1
  174. package/node_modules/isexe/dist/esm/package.json +0 -3
  175. package/node_modules/isexe/dist/esm/posix.d.ts +0 -18
  176. package/node_modules/isexe/dist/esm/posix.d.ts.map +0 -1
  177. package/node_modules/isexe/dist/esm/posix.js +0 -62
  178. package/node_modules/isexe/dist/esm/posix.js.map +0 -1
  179. package/node_modules/isexe/dist/esm/win32.d.ts +0 -18
  180. package/node_modules/isexe/dist/esm/win32.d.ts.map +0 -1
  181. package/node_modules/isexe/dist/esm/win32.js +0 -58
  182. package/node_modules/isexe/dist/esm/win32.js.map +0 -1
  183. package/node_modules/isexe/package.json +0 -78
  184. package/node_modules/listr2/dist/index.cjs +0 -2189
  185. package/node_modules/listr2/dist/index.d.ts +0 -1528
  186. package/node_modules/rfdc/.github/workflows/ci.yml +0 -21
  187. package/node_modules/rfdc/LICENSE +0 -15
  188. package/node_modules/rfdc/default.js +0 -3
  189. package/node_modules/rfdc/index.d.ts +0 -13
  190. package/node_modules/rfdc/index.js +0 -198
  191. package/node_modules/rfdc/index.test-d.ts +0 -13
  192. package/node_modules/rfdc/package.json +0 -73
  193. package/node_modules/rfdc/readme.md +0 -181
  194. package/node_modules/rfdc/test/index.js +0 -306
  195. package/node_modules/smol-toml/dist/date.d.ts.map +0 -1
  196. package/node_modules/smol-toml/dist/date.js.map +0 -1
  197. package/node_modules/smol-toml/dist/error.d.ts.map +0 -1
  198. package/node_modules/smol-toml/dist/error.js.map +0 -1
  199. package/node_modules/smol-toml/dist/extract.js.map +0 -1
  200. package/node_modules/smol-toml/dist/index.cjs.map +0 -1
  201. package/node_modules/smol-toml/dist/index.d.ts.map +0 -1
  202. package/node_modules/smol-toml/dist/index.js.map +0 -1
  203. package/node_modules/smol-toml/dist/parse.d.ts.map +0 -1
  204. package/node_modules/smol-toml/dist/parse.js.map +0 -1
  205. package/node_modules/smol-toml/dist/primitive.d.ts +0 -29
  206. package/node_modules/smol-toml/dist/primitive.d.ts.map +0 -1
  207. package/node_modules/smol-toml/dist/primitive.js.map +0 -1
  208. package/node_modules/smol-toml/dist/stringify.d.ts.map +0 -1
  209. package/node_modules/smol-toml/dist/stringify.js.map +0 -1
  210. package/node_modules/smol-toml/dist/struct.js.map +0 -1
  211. package/node_modules/smol-toml/dist/util.d.ts.map +0 -1
  212. package/node_modules/smol-toml/dist/util.js.map +0 -1
  213. package/node_modules/smol-toml/src/date.ts +0 -152
  214. package/node_modules/smol-toml/src/error.ts +0 -77
  215. package/node_modules/smol-toml/src/extract.ts +0 -113
  216. package/node_modules/smol-toml/src/index.ts +0 -42
  217. package/node_modules/smol-toml/src/parse.ts +0 -184
  218. package/node_modules/smol-toml/src/primitive.ts +0 -249
  219. package/node_modules/smol-toml/src/stringify.ts +0 -191
  220. package/node_modules/smol-toml/src/struct.ts +0 -202
  221. package/node_modules/smol-toml/src/util.ts +0 -106
  222. package/node_modules/which/LICENSE +0 -15
  223. package/node_modules/which/README.md +0 -51
  224. package/node_modules/which/bin/which.js +0 -52
  225. package/node_modules/which/lib/index.js +0 -111
  226. package/node_modules/which/package.json +0 -52
  227. /package/node_modules/@listr2/{prompt-adapter-enquirer → prompt-adapter-inquirer}/LICENSE +0 -0
  228. /package/node_modules/{log-update/node_modules/ansi-escapes → ansi-escapes}/index.d.ts +0 -0
  229. /package/node_modules/{log-update/node_modules/ansi-escapes → ansi-escapes}/index.js +0 -0
  230. /package/node_modules/{chalk → ansi-escapes}/license +0 -0
  231. /package/node_modules/{figures → listr2/node_modules/string-width}/license +0 -0
  232. /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;