@ix-xs/node-comfort 2.1.0 → 2.2.0

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 (71) hide show
  1. package/CHANGELOG.md +153 -0
  2. package/README.md +2 -2
  3. package/package.json +2 -2
  4. package/src/Arr.js +13 -5
  5. package/src/Checker.js +56 -8
  6. package/src/Cli.js +60 -18
  7. package/src/Color.js +32 -6
  8. package/src/Crypto.js +429 -78
  9. package/src/Emitter.js +9 -9
  10. package/src/Env.js +90 -8
  11. package/src/FS.js +369 -59
  12. package/src/Func.js +1 -1
  13. package/src/Http.js +266 -75
  14. package/src/Id.js +53 -15
  15. package/src/Logger.js +100 -22
  16. package/src/Num.js +3 -2
  17. package/src/Obj.js +225 -36
  18. package/src/SQLite.js +155 -98
  19. package/src/Schema.js +281 -40
  20. package/src/Str.js +77 -29
  21. package/src/Sys.js +124 -23
  22. package/src/Time.js +98 -24
  23. package/src/Utils.js +18 -3
  24. package/src/errors.js +75 -4
  25. package/src/internal/shared.js +68 -14
  26. package/src/internal/terminal.js +49 -0
  27. package/types/index.d.ts +28 -5
  28. package/types/src/Arr.d.ts +4 -2
  29. package/types/src/Arr.d.ts.map +1 -1
  30. package/types/src/Checker.d.ts +29 -3
  31. package/types/src/Checker.d.ts.map +1 -1
  32. package/types/src/Cli.d.ts +6 -4
  33. package/types/src/Cli.d.ts.map +1 -1
  34. package/types/src/Color.d.ts +2 -2
  35. package/types/src/Color.d.ts.map +1 -1
  36. package/types/src/Crypto.d.ts +126 -28
  37. package/types/src/Crypto.d.ts.map +1 -1
  38. package/types/src/Emitter.d.ts +6 -6
  39. package/types/src/Emitter.d.ts.map +1 -1
  40. package/types/src/Env.d.ts +19 -1
  41. package/types/src/Env.d.ts.map +1 -1
  42. package/types/src/FS.d.ts +30 -23
  43. package/types/src/FS.d.ts.map +1 -1
  44. package/types/src/Func.d.ts +1 -1
  45. package/types/src/Http.d.ts +45 -8
  46. package/types/src/Http.d.ts.map +1 -1
  47. package/types/src/Id.d.ts.map +1 -1
  48. package/types/src/Logger.d.ts +11 -3
  49. package/types/src/Logger.d.ts.map +1 -1
  50. package/types/src/Num.d.ts +1 -1
  51. package/types/src/Num.d.ts.map +1 -1
  52. package/types/src/Obj.d.ts +15 -2
  53. package/types/src/Obj.d.ts.map +1 -1
  54. package/types/src/SQLite.d.ts +105 -13
  55. package/types/src/SQLite.d.ts.map +1 -1
  56. package/types/src/Schema.d.ts +61 -5
  57. package/types/src/Schema.d.ts.map +1 -1
  58. package/types/src/Str.d.ts +32 -7
  59. package/types/src/Str.d.ts.map +1 -1
  60. package/types/src/Sys.d.ts +43 -10
  61. package/types/src/Sys.d.ts.map +1 -1
  62. package/types/src/Time.d.ts +12 -7
  63. package/types/src/Time.d.ts.map +1 -1
  64. package/types/src/Utils.d.ts +16 -6
  65. package/types/src/Utils.d.ts.map +1 -1
  66. package/types/src/errors.d.ts +6 -2
  67. package/types/src/errors.d.ts.map +1 -1
  68. package/types/src/internal/shared.d.ts +7 -0
  69. package/types/src/internal/shared.d.ts.map +1 -1
  70. package/types/src/internal/terminal.d.ts +13 -0
  71. package/types/src/internal/terminal.d.ts.map +1 -0
package/src/Color.js CHANGED
@@ -114,10 +114,10 @@ const { segmenter, graphemes } = require("./internal/shared.js");
114
114
  * @property {boolean} enabled Whether colors are being output.
115
115
  * @property {(level?: ColorLevel) => ColorModule} create A separate instance with its own level, for another stream or for tests.
116
116
  * @property {(stream?: NodeJS.WriteStream | { isTTY?: boolean }) => ColorLevel} detect The color level a stream supports, taking `NO_COLOR` and `FORCE_COLOR` into account.
117
- * @property {(text: string) => string} strip Removes colors and other terminal escape codes.
117
+ * @property {(text: string) => string} strip Removes colors, every other terminal escape code and every control character but tabs and new lines (so `\r` and `\b` go too).
118
118
  * @property {(text: string) => number} width How many columns the text takes, ignoring colors and counting emoji and CJK as 2.
119
119
  * @property {(text: string, columns: number, options?: { ellipsis?: string }) => string} truncate Cuts text to a number of columns and adds `…`. Colors are kept and still closed properly.
120
- * @property {(text: string, url: string) => string} link A clickable link in terminals that support it, `"text (url)"` elsewhere.
120
+ * @property {(text: string, url: string) => string} link A clickable link in terminals that support it, `"text (url)"` elsewhere. Control characters are removed from both, so they can't end the link early; the text keeps its colors.
121
121
  * @property {(text: string, colors: string[]) => string} gradient Colors the text along a gradient of hex colors.
122
122
  */
123
123
 
@@ -142,7 +142,16 @@ for (const [name, [open, close]] of Object.entries(_CODES)) _STYLES[name] = { op
142
142
  * @typedef {{ open: string, close: string } | { rgb: [number, number, number], bg: boolean } | { ansi256: number, bg: boolean }} StyleEntry
143
143
  */
144
144
 
145
- const _ANSI_REGEX = /[\u001b\u009b](?:\][^\u0007\u001b]*(?:\u0007|\u001b\\)|[[\]()#;?]*(?:\d{1,4}(?:[;:]\d{0,4})*)?[\dA-PR-TZcf-nq-uy=><~])/g;
145
+ // Terminal sequences, by kind. The string ones (OSC, DCS, APC, PM, SOS) end
146
+ // at their terminator, or at the next ESC, as a terminal reads them. Their
147
+ // body also stops at a string introducer, so the scan stays linear.
148
+ const _STRING_SEQUENCES = /(?:\x1b[\]P^_X]|[\x90\x98\x9d\x9e\x9f])[^\x07\x1b\x90\x98\x9c-\x9f]*(?:\x07|\x1b\\|\x9c)/g;
149
+ const _CSI_SEQUENCES = /(?:\x1b\[|\x9b)[0-?]*[ -/]*[@-~]/g;
150
+ const _SHORT_SEQUENCES = /\x1b[ -/]*[0-~]/g;
151
+ const _ANSI_REGEX = /(?:\x1b[\]P^_X]|[\x90\x98\x9d\x9e\x9f])[^\x07\x1b\x90\x98\x9c-\x9f]*(?:\x07|\x1b\\|\x9c)|(?:\x1b\[|\x9b)[0-?]*[ -/]*[@-~]|\x1b[ -/]*[0-~]/g;
152
+ // Every control character but tab and new line: C0, DEL and C1.
153
+ const _CONTROLS = /[\x00-\x08\x0b-\x1f\x7f-\x9f]/g;
154
+ const _HAS_CONTROL = /[\x00-\x08\x0b-\x1f\x7f-\x9f]/;
146
155
 
147
156
  /**
148
157
  * @param {string} hex
@@ -302,7 +311,10 @@ const _charWidth = (grapheme) => {
302
311
  */
303
312
  const strip = (text) => {
304
313
  const value = typeof text === "string" ? text : String(text);
305
- return value.indexOf("\u001b") === -1 && value.indexOf("\u009b") === -1 ? value : value.replace(_ANSI_REGEX, "");
314
+ if (!_HAS_CONTROL.test(value)) return value;
315
+ // One kind at a time, then every control character left: no ESC remains to
316
+ // rebuild a sequence from the pieces around it.
317
+ return value.replace(_STRING_SEQUENCES, "").replace(_CSI_SEQUENCES, "").replace(_SHORT_SEQUENCES, "").replace(_CONTROLS, "");
306
318
  };
307
319
 
308
320
  /**
@@ -384,6 +396,16 @@ const _supportsHyperlinks = () => {
384
396
  return false;
385
397
  };
386
398
 
399
+ const _LINK_CONTROLS = /[\x00-\x1f\x7f-\x9f]/g;
400
+ const _HAS_LINK_CONTROL = /[\x00-\x1f\x7f-\x9f]/;
401
+ const _LINK_TEXT = /(\x1b\[[\d;:]*m)|(?:\x1b[\]P^_X]|[\x90\x98\x9d\x9e\x9f])[^\x07\x1b\x90\x98\x9c-\x9f]*(?:\x07|\x1b\\|\x9c)|(?:\x1b\[|\x9b)[0-?]*[ -/]*[@-~]|\x1b[ -/]*[0-~]|[\x00-\x1f\x7f-\x9f]/g;
402
+
403
+ /**
404
+ * The text of a link: colors stay, any other sequence or control character goes.
405
+ * @param {string} text
406
+ */
407
+ const _linkText = (text) => (_HAS_LINK_CONTROL.test(text) ? text.replace(_LINK_TEXT, (match, style) => style ?? "") : text);
408
+
387
409
  /** @param {number} value */
388
410
  const _byte = (value) => (value > 0 ? (value < 255 ? Math.round(value) : 255) : 0);
389
411
 
@@ -542,8 +564,12 @@ const _createModule = (forcedLevel) => {
542
564
  truncate: { enumerable: true, value: truncate },
543
565
  link: {
544
566
  enumerable: true,
545
- value: (/** @type {string} */ text, /** @type {string} */ url) =>
546
- currentLevel() > 0 && _supportsHyperlinks() ? `\u001b]8;;${url}\u0007${text}\u001b]8;;\u0007` : `${text} (${url})`,
567
+ value: (/** @type {string} */ text, /** @type {string} */ url) => {
568
+ // A control character would end the link early and start something else.
569
+ const label = _linkText(String(text));
570
+ const target = String(url).replace(_LINK_CONTROLS, "");
571
+ return currentLevel() > 0 && _supportsHyperlinks() ? `\u001b]8;;${target}\u0007${label}\u001b]8;;\u0007` : `${label} (${target})`;
572
+ },
547
573
  },
548
574
  gradient: { enumerable: true, value: gradient },
549
575
  });