@n8n/utils 1.19.0 → 1.21.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.
@@ -0,0 +1,3 @@
1
+ const require_sanitize = require('../sanitize.cjs');
2
+
3
+ exports.sanitizeFilename = require_sanitize.sanitizeFilename;
@@ -0,0 +1,2 @@
1
+ import { t as sanitizeFilename } from "../sanitize.cjs";
2
+ export { sanitizeFilename };
@@ -0,0 +1,2 @@
1
+ import { t as sanitizeFilename } from "../sanitize.mjs";
2
+ export { sanitizeFilename };
@@ -0,0 +1,3 @@
1
+ import { t as sanitizeFilename } from "../sanitize.mjs";
2
+
3
+ export { sanitizeFilename };
package/dist/index.cjs CHANGED
@@ -7,6 +7,7 @@ const require_reRankSearchResults = require('./reRankSearchResults.cjs');
7
7
  const require_sublimeSearch = require('./sublimeSearch.cjs');
8
8
  const require_sortByProperty = require('./sortByProperty.cjs');
9
9
  const require_truncate = require('./truncate.cjs');
10
+ const require_sanitize = require('./sanitize.cjs');
10
11
 
11
12
  exports.DEFAULT_KEYS = require_sublimeSearch.DEFAULT_KEYS;
12
13
  exports.assert = require_assert.assert;
@@ -14,6 +15,7 @@ exports.createEventBus = require_event_bus.createEventBus;
14
15
  exports.createEventQueue = require_event_queue.createEventQueue;
15
16
  exports.reRankSearchResults = require_reRankSearchResults.reRankSearchResults;
16
17
  exports.retry = require_retry.retry;
18
+ exports.sanitizeFilename = require_sanitize.sanitizeFilename;
17
19
  exports.smartDecimal = require_smartDecimal.smartDecimal;
18
20
  exports.sortByProperty = require_sortByProperty.sortByProperty;
19
21
  exports.sublimeSearch = require_sublimeSearch.sublimeSearch;
package/dist/index.d.cts CHANGED
@@ -1,10 +1,11 @@
1
1
  import { t as assert } from "./assert2.cjs";
2
2
  import { n as EventBus, r as createEventBus, t as CallbackFn } from "./event-bus2.cjs";
3
3
  import { t as createEventQueue } from "./event-queue2.cjs";
4
+ import { t as sanitizeFilename } from "./sanitize.cjs";
4
5
  import { t as retry } from "./retry2.cjs";
5
6
  import { t as smartDecimal } from "./smartDecimal.cjs";
6
7
  import { t as reRankSearchResults } from "./reRankSearchResults.cjs";
7
8
  import { n as sublimeSearch, t as DEFAULT_KEYS } from "./sublimeSearch.cjs";
8
9
  import { t as sortByProperty } from "./sortByProperty.cjs";
9
10
  import { n as truncateBeforeLast, t as truncate } from "./truncate.cjs";
10
- export { CallbackFn, DEFAULT_KEYS, EventBus, assert, createEventBus, createEventQueue, reRankSearchResults, retry, smartDecimal, sortByProperty, sublimeSearch, truncate, truncateBeforeLast };
11
+ export { CallbackFn, DEFAULT_KEYS, EventBus, assert, createEventBus, createEventQueue, reRankSearchResults, retry, sanitizeFilename, smartDecimal, sortByProperty, sublimeSearch, truncate, truncateBeforeLast };
package/dist/index.d.mts CHANGED
@@ -1,10 +1,11 @@
1
1
  import { t as assert } from "./assert2.mjs";
2
2
  import { n as EventBus, r as createEventBus, t as CallbackFn } from "./event-bus2.mjs";
3
3
  import { t as createEventQueue } from "./event-queue2.mjs";
4
+ import { t as sanitizeFilename } from "./sanitize.mjs";
4
5
  import { t as retry } from "./retry2.mjs";
5
6
  import { t as smartDecimal } from "./smartDecimal.mjs";
6
7
  import { t as reRankSearchResults } from "./reRankSearchResults.mjs";
7
8
  import { n as sublimeSearch, t as DEFAULT_KEYS } from "./sublimeSearch.mjs";
8
9
  import { t as sortByProperty } from "./sortByProperty.mjs";
9
10
  import { n as truncateBeforeLast, t as truncate } from "./truncate.mjs";
10
- export { CallbackFn, DEFAULT_KEYS, EventBus, assert, createEventBus, createEventQueue, reRankSearchResults, retry, smartDecimal, sortByProperty, sublimeSearch, truncate, truncateBeforeLast };
11
+ export { CallbackFn, DEFAULT_KEYS, EventBus, assert, createEventBus, createEventQueue, reRankSearchResults, retry, sanitizeFilename, smartDecimal, sortByProperty, sublimeSearch, truncate, truncateBeforeLast };
package/dist/index.mjs CHANGED
@@ -7,5 +7,6 @@ import { t as reRankSearchResults } from "./reRankSearchResults.mjs";
7
7
  import { n as sublimeSearch, t as DEFAULT_KEYS } from "./sublimeSearch.mjs";
8
8
  import { t as sortByProperty } from "./sortByProperty.mjs";
9
9
  import { n as truncateBeforeLast, t as truncate } from "./truncate.mjs";
10
+ import { t as sanitizeFilename } from "./sanitize.mjs";
10
11
 
11
- export { DEFAULT_KEYS, assert, createEventBus, createEventQueue, reRankSearchResults, retry, smartDecimal, sortByProperty, sublimeSearch, truncate, truncateBeforeLast };
12
+ export { DEFAULT_KEYS, assert, createEventBus, createEventQueue, reRankSearchResults, retry, sanitizeFilename, smartDecimal, sortByProperty, sublimeSearch, truncate, truncateBeforeLast };
@@ -0,0 +1,67 @@
1
+
2
+ //#region src/files/sanitize.ts
3
+ const INVALID_CHARS_REGEX = /[<>:"/\\|?*\u0000-\u001F\u007F-\u009F]/g;
4
+ const ZERO_WIDTH_CHARS_REGEX = /[\u200B-\u200D\u2060\uFEFF]/g;
5
+ const UNICODE_SPACES_REGEX = /[\u00A0\u2000-\u200A]/g;
6
+ const LEADING_TRAILING_DOTS_SPACES_REGEX = /^[\s.]+|[\s.]+$/g;
7
+ const WINDOWS_RESERVED_NAMES = new Set([
8
+ "CON",
9
+ "PRN",
10
+ "AUX",
11
+ "NUL",
12
+ "COM1",
13
+ "COM2",
14
+ "COM3",
15
+ "COM4",
16
+ "COM5",
17
+ "COM6",
18
+ "COM7",
19
+ "COM8",
20
+ "COM9",
21
+ "LPT1",
22
+ "LPT2",
23
+ "LPT3",
24
+ "LPT4",
25
+ "LPT5",
26
+ "LPT6",
27
+ "LPT7",
28
+ "LPT8",
29
+ "LPT9"
30
+ ]);
31
+ const DEFAULT_FALLBACK_NAME = "untitled";
32
+ const MAX_FILENAME_LENGTH = 200;
33
+ /**
34
+ * Sanitizes a filename to be compatible with Mac, Linux, and Windows file systems
35
+ *
36
+ * Main features:
37
+ * - Replace invalid characters (e.g. ":" in hello:world)
38
+ * - Handle Windows reserved names
39
+ * - Limit filename length
40
+ * - Normalize Unicode characters
41
+ *
42
+ * @param filename - The filename to sanitize (without extension)
43
+ * @param maxLength - Maximum filename length (default: 200)
44
+ * @returns A sanitized filename (without extension)
45
+ *
46
+ * @example
47
+ * sanitizeFilename('hello:world') // returns 'hello_world'
48
+ * sanitizeFilename('CON') // returns '_CON'
49
+ * sanitizeFilename('') // returns 'untitled'
50
+ */
51
+ const sanitizeFilename = (filename, maxLength = MAX_FILENAME_LENGTH) => {
52
+ if (!filename) return DEFAULT_FALLBACK_NAME;
53
+ let baseName = filename.trim().replace(INVALID_CHARS_REGEX, "_").replace(ZERO_WIDTH_CHARS_REGEX, "").replace(UNICODE_SPACES_REGEX, " ").replace(LEADING_TRAILING_DOTS_SPACES_REGEX, "");
54
+ if (!baseName) baseName = DEFAULT_FALLBACK_NAME;
55
+ if (WINDOWS_RESERVED_NAMES.has(baseName.toUpperCase())) baseName = `_${baseName}`;
56
+ if (baseName.length > maxLength) baseName = baseName.slice(0, maxLength);
57
+ return baseName;
58
+ };
59
+
60
+ //#endregion
61
+ Object.defineProperty(exports, 'sanitizeFilename', {
62
+ enumerable: true,
63
+ get: function () {
64
+ return sanitizeFilename;
65
+ }
66
+ });
67
+ //# sourceMappingURL=sanitize.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sanitize.cjs","names":[],"sources":["../src/files/sanitize.ts"],"sourcesContent":["// Constants definition\n/* eslint-disable no-control-regex */\nconst INVALID_CHARS_REGEX = /[<>:\"/\\\\|?*\\u0000-\\u001F\\u007F-\\u009F]/g;\nconst ZERO_WIDTH_CHARS_REGEX = /[\\u200B-\\u200D\\u2060\\uFEFF]/g;\nconst UNICODE_SPACES_REGEX = /[\\u00A0\\u2000-\\u200A]/g;\nconst LEADING_TRAILING_DOTS_SPACES_REGEX = /^[\\s.]+|[\\s.]+$/g;\n/* eslint-enable no-control-regex */\n\nconst WINDOWS_RESERVED_NAMES = new Set([\n\t'CON',\n\t'PRN',\n\t'AUX',\n\t'NUL',\n\t'COM1',\n\t'COM2',\n\t'COM3',\n\t'COM4',\n\t'COM5',\n\t'COM6',\n\t'COM7',\n\t'COM8',\n\t'COM9',\n\t'LPT1',\n\t'LPT2',\n\t'LPT3',\n\t'LPT4',\n\t'LPT5',\n\t'LPT6',\n\t'LPT7',\n\t'LPT8',\n\t'LPT9',\n]);\n\nconst DEFAULT_FALLBACK_NAME = 'untitled';\nconst MAX_FILENAME_LENGTH = 200;\n\n/**\n * Sanitizes a filename to be compatible with Mac, Linux, and Windows file systems\n *\n * Main features:\n * - Replace invalid characters (e.g. \":\" in hello:world)\n * - Handle Windows reserved names\n * - Limit filename length\n * - Normalize Unicode characters\n *\n * @param filename - The filename to sanitize (without extension)\n * @param maxLength - Maximum filename length (default: 200)\n * @returns A sanitized filename (without extension)\n *\n * @example\n * sanitizeFilename('hello:world') // returns 'hello_world'\n * sanitizeFilename('CON') // returns '_CON'\n * sanitizeFilename('') // returns 'untitled'\n */\nexport const sanitizeFilename = (\n\tfilename: string,\n\tmaxLength: number = MAX_FILENAME_LENGTH,\n): string => {\n\t// Input validation\n\tif (!filename) {\n\t\treturn DEFAULT_FALLBACK_NAME;\n\t}\n\n\tlet baseName = filename\n\t\t.trim()\n\t\t.replace(INVALID_CHARS_REGEX, '_')\n\t\t.replace(ZERO_WIDTH_CHARS_REGEX, '')\n\t\t.replace(UNICODE_SPACES_REGEX, ' ')\n\t\t.replace(LEADING_TRAILING_DOTS_SPACES_REGEX, '');\n\n\t// Handle empty or invalid filenames after cleaning\n\tif (!baseName) {\n\t\tbaseName = DEFAULT_FALLBACK_NAME;\n\t}\n\n\t// Handle Windows reserved names\n\tif (WINDOWS_RESERVED_NAMES.has(baseName.toUpperCase())) {\n\t\tbaseName = `_${baseName}`;\n\t}\n\n\t// Truncate if too long\n\tif (baseName.length > maxLength) {\n\t\tbaseName = baseName.slice(0, maxLength);\n\t}\n\n\treturn baseName;\n};\n"],"mappings":";;AAEA,MAAM,sBAAsB;AAC5B,MAAM,yBAAyB;AAC/B,MAAM,uBAAuB;AAC7B,MAAM,qCAAqC;AAG3C,MAAM,yBAAyB,IAAI,IAAI;CACtC;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA,CAAC;AAEF,MAAM,wBAAwB;AAC9B,MAAM,sBAAsB;;;;;;;;;;;;;;;;;;;AAoB5B,MAAa,oBACZ,UACA,YAAoB,wBACR;AAEZ,KAAI,CAAC,SACJ,QAAO;CAGR,IAAI,WAAW,SACb,MAAM,CACN,QAAQ,qBAAqB,IAAI,CACjC,QAAQ,wBAAwB,GAAG,CACnC,QAAQ,sBAAsB,IAAI,CAClC,QAAQ,oCAAoC,GAAG;AAGjD,KAAI,CAAC,SACJ,YAAW;AAIZ,KAAI,uBAAuB,IAAI,SAAS,aAAa,CAAC,CACrD,YAAW,IAAI;AAIhB,KAAI,SAAS,SAAS,UACrB,YAAW,SAAS,MAAM,GAAG,UAAU;AAGxC,QAAO"}
@@ -0,0 +1,5 @@
1
+ //#region src/files/sanitize.d.ts
2
+ declare const sanitizeFilename: (filename: string, maxLength?: number) => string;
3
+ //#endregion
4
+ export { sanitizeFilename as t };
5
+ //# sourceMappingURL=sanitize.d.cts.map
@@ -0,0 +1,5 @@
1
+ //#region src/files/sanitize.d.ts
2
+ declare const sanitizeFilename: (filename: string, maxLength?: number) => string;
3
+ //#endregion
4
+ export { sanitizeFilename as t };
5
+ //# sourceMappingURL=sanitize.d.mts.map
@@ -0,0 +1,61 @@
1
+ //#region src/files/sanitize.ts
2
+ const INVALID_CHARS_REGEX = /[<>:"/\\|?*\u0000-\u001F\u007F-\u009F]/g;
3
+ const ZERO_WIDTH_CHARS_REGEX = /[\u200B-\u200D\u2060\uFEFF]/g;
4
+ const UNICODE_SPACES_REGEX = /[\u00A0\u2000-\u200A]/g;
5
+ const LEADING_TRAILING_DOTS_SPACES_REGEX = /^[\s.]+|[\s.]+$/g;
6
+ const WINDOWS_RESERVED_NAMES = new Set([
7
+ "CON",
8
+ "PRN",
9
+ "AUX",
10
+ "NUL",
11
+ "COM1",
12
+ "COM2",
13
+ "COM3",
14
+ "COM4",
15
+ "COM5",
16
+ "COM6",
17
+ "COM7",
18
+ "COM8",
19
+ "COM9",
20
+ "LPT1",
21
+ "LPT2",
22
+ "LPT3",
23
+ "LPT4",
24
+ "LPT5",
25
+ "LPT6",
26
+ "LPT7",
27
+ "LPT8",
28
+ "LPT9"
29
+ ]);
30
+ const DEFAULT_FALLBACK_NAME = "untitled";
31
+ const MAX_FILENAME_LENGTH = 200;
32
+ /**
33
+ * Sanitizes a filename to be compatible with Mac, Linux, and Windows file systems
34
+ *
35
+ * Main features:
36
+ * - Replace invalid characters (e.g. ":" in hello:world)
37
+ * - Handle Windows reserved names
38
+ * - Limit filename length
39
+ * - Normalize Unicode characters
40
+ *
41
+ * @param filename - The filename to sanitize (without extension)
42
+ * @param maxLength - Maximum filename length (default: 200)
43
+ * @returns A sanitized filename (without extension)
44
+ *
45
+ * @example
46
+ * sanitizeFilename('hello:world') // returns 'hello_world'
47
+ * sanitizeFilename('CON') // returns '_CON'
48
+ * sanitizeFilename('') // returns 'untitled'
49
+ */
50
+ const sanitizeFilename = (filename, maxLength = MAX_FILENAME_LENGTH) => {
51
+ if (!filename) return DEFAULT_FALLBACK_NAME;
52
+ let baseName = filename.trim().replace(INVALID_CHARS_REGEX, "_").replace(ZERO_WIDTH_CHARS_REGEX, "").replace(UNICODE_SPACES_REGEX, " ").replace(LEADING_TRAILING_DOTS_SPACES_REGEX, "");
53
+ if (!baseName) baseName = DEFAULT_FALLBACK_NAME;
54
+ if (WINDOWS_RESERVED_NAMES.has(baseName.toUpperCase())) baseName = `_${baseName}`;
55
+ if (baseName.length > maxLength) baseName = baseName.slice(0, maxLength);
56
+ return baseName;
57
+ };
58
+
59
+ //#endregion
60
+ export { sanitizeFilename as t };
61
+ //# sourceMappingURL=sanitize.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sanitize.mjs","names":[],"sources":["../src/files/sanitize.ts"],"sourcesContent":["// Constants definition\n/* eslint-disable no-control-regex */\nconst INVALID_CHARS_REGEX = /[<>:\"/\\\\|?*\\u0000-\\u001F\\u007F-\\u009F]/g;\nconst ZERO_WIDTH_CHARS_REGEX = /[\\u200B-\\u200D\\u2060\\uFEFF]/g;\nconst UNICODE_SPACES_REGEX = /[\\u00A0\\u2000-\\u200A]/g;\nconst LEADING_TRAILING_DOTS_SPACES_REGEX = /^[\\s.]+|[\\s.]+$/g;\n/* eslint-enable no-control-regex */\n\nconst WINDOWS_RESERVED_NAMES = new Set([\n\t'CON',\n\t'PRN',\n\t'AUX',\n\t'NUL',\n\t'COM1',\n\t'COM2',\n\t'COM3',\n\t'COM4',\n\t'COM5',\n\t'COM6',\n\t'COM7',\n\t'COM8',\n\t'COM9',\n\t'LPT1',\n\t'LPT2',\n\t'LPT3',\n\t'LPT4',\n\t'LPT5',\n\t'LPT6',\n\t'LPT7',\n\t'LPT8',\n\t'LPT9',\n]);\n\nconst DEFAULT_FALLBACK_NAME = 'untitled';\nconst MAX_FILENAME_LENGTH = 200;\n\n/**\n * Sanitizes a filename to be compatible with Mac, Linux, and Windows file systems\n *\n * Main features:\n * - Replace invalid characters (e.g. \":\" in hello:world)\n * - Handle Windows reserved names\n * - Limit filename length\n * - Normalize Unicode characters\n *\n * @param filename - The filename to sanitize (without extension)\n * @param maxLength - Maximum filename length (default: 200)\n * @returns A sanitized filename (without extension)\n *\n * @example\n * sanitizeFilename('hello:world') // returns 'hello_world'\n * sanitizeFilename('CON') // returns '_CON'\n * sanitizeFilename('') // returns 'untitled'\n */\nexport const sanitizeFilename = (\n\tfilename: string,\n\tmaxLength: number = MAX_FILENAME_LENGTH,\n): string => {\n\t// Input validation\n\tif (!filename) {\n\t\treturn DEFAULT_FALLBACK_NAME;\n\t}\n\n\tlet baseName = filename\n\t\t.trim()\n\t\t.replace(INVALID_CHARS_REGEX, '_')\n\t\t.replace(ZERO_WIDTH_CHARS_REGEX, '')\n\t\t.replace(UNICODE_SPACES_REGEX, ' ')\n\t\t.replace(LEADING_TRAILING_DOTS_SPACES_REGEX, '');\n\n\t// Handle empty or invalid filenames after cleaning\n\tif (!baseName) {\n\t\tbaseName = DEFAULT_FALLBACK_NAME;\n\t}\n\n\t// Handle Windows reserved names\n\tif (WINDOWS_RESERVED_NAMES.has(baseName.toUpperCase())) {\n\t\tbaseName = `_${baseName}`;\n\t}\n\n\t// Truncate if too long\n\tif (baseName.length > maxLength) {\n\t\tbaseName = baseName.slice(0, maxLength);\n\t}\n\n\treturn baseName;\n};\n"],"mappings":";AAEA,MAAM,sBAAsB;AAC5B,MAAM,yBAAyB;AAC/B,MAAM,uBAAuB;AAC7B,MAAM,qCAAqC;AAG3C,MAAM,yBAAyB,IAAI,IAAI;CACtC;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA,CAAC;AAEF,MAAM,wBAAwB;AAC9B,MAAM,sBAAsB;;;;;;;;;;;;;;;;;;;AAoB5B,MAAa,oBACZ,UACA,YAAoB,wBACR;AAEZ,KAAI,CAAC,SACJ,QAAO;CAGR,IAAI,WAAW,SACb,MAAM,CACN,QAAQ,qBAAqB,IAAI,CACjC,QAAQ,wBAAwB,GAAG,CACnC,QAAQ,sBAAsB,IAAI,CAClC,QAAQ,oCAAoC,GAAG;AAGjD,KAAI,CAAC,SACJ,YAAW;AAIZ,KAAI,uBAAuB,IAAI,SAAS,aAAa,CAAC,CACrD,YAAW,IAAI;AAIhB,KAAI,SAAS,SAAS,UACrB,YAAW,SAAS,MAAM,GAAG,UAAU;AAGxC,QAAO"}
package/dist/truncate.cjs CHANGED
@@ -7,7 +7,7 @@ const truncate = (text, length = 30) => text.length > length ? text.slice(0, len
7
7
  * - Remove chars just before the last word, as long as the last word is under 15 chars
8
8
  * - Otherwise preserve the last 5 chars of the name and remove chars before that
9
9
  */
10
- function truncateBeforeLast(text, maxLength) {
10
+ function truncateBeforeLast(text, maxLength, lastCharsLength = 5) {
11
11
  const chars = [];
12
12
  const segmenter = new Intl.Segmenter(void 0, { granularity: "grapheme" });
13
13
  for (const { segment } of segmenter.segment(text)) chars.push(segment);
@@ -22,7 +22,8 @@ function truncateBeforeLast(text, maxLength) {
22
22
  const keepLength = indexBeforeLastWord - charsToRemove;
23
23
  if (keepLength > 0) return chars.slice(0, keepLength).join("") + ellipsis + chars.slice(indexBeforeLastWord).join("");
24
24
  }
25
- return chars.slice(0, maxLength - 5 - ellipsisLength).join("") + ellipsis + chars.slice(-5).join("");
25
+ if (lastCharsLength < 1) return chars.slice(0, maxLength).join("") + ellipsis;
26
+ return chars.slice(0, maxLength - lastCharsLength - ellipsisLength).join("") + ellipsis + chars.slice(-lastCharsLength).join("");
26
27
  }
27
28
 
28
29
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"truncate.cjs","names":["chars: string[]"],"sources":["../src/string/truncate.ts"],"sourcesContent":["export const truncate = (text: string, length = 30): string =>\n\ttext.length > length ? text.slice(0, length) + '...' : text;\n\n/**\n * Replace part of given text with ellipsis following the rules below:\n *\n * - Remove chars just before the last word, as long as the last word is under 15 chars\n * - Otherwise preserve the last 5 chars of the name and remove chars before that\n */\nexport function truncateBeforeLast(text: string, maxLength: number): string {\n\tconst chars: string[] = [];\n\n\tconst segmenter = new Intl.Segmenter(undefined, { granularity: 'grapheme' });\n\n\tfor (const { segment } of segmenter.segment(text)) {\n\t\tchars.push(segment);\n\t}\n\n\tif (chars.length <= maxLength) {\n\t\treturn text;\n\t}\n\n\tconst lastWhitespaceIndex = chars.findLastIndex((ch) => ch.match(/^\\s+$/));\n\tconst lastWordIndex = lastWhitespaceIndex + 1;\n\tconst lastWord = chars.slice(lastWordIndex);\n\tconst ellipsis = '…';\n\tconst ellipsisLength = ellipsis.length;\n\n\tif (lastWord.length < 15) {\n\t\tconst charsToRemove = chars.length - maxLength + ellipsisLength;\n\t\tconst indexBeforeLastWord = lastWordIndex;\n\t\tconst keepLength = indexBeforeLastWord - charsToRemove;\n\n\t\tif (keepLength > 0) {\n\t\t\treturn (\n\t\t\t\tchars.slice(0, keepLength).join('') + ellipsis + chars.slice(indexBeforeLastWord).join('')\n\t\t\t);\n\t\t}\n\t}\n\n\treturn (\n\t\tchars.slice(0, maxLength - 5 - ellipsisLength).join('') + ellipsis + chars.slice(-5).join('')\n\t);\n}\n"],"mappings":";;AAAA,MAAa,YAAY,MAAc,SAAS,OAC/C,KAAK,SAAS,SAAS,KAAK,MAAM,GAAG,OAAO,GAAG,QAAQ;;;;;;;AAQxD,SAAgB,mBAAmB,MAAc,WAA2B;CAC3E,MAAMA,QAAkB,EAAE;CAE1B,MAAM,YAAY,IAAI,KAAK,UAAU,QAAW,EAAE,aAAa,YAAY,CAAC;AAE5E,MAAK,MAAM,EAAE,aAAa,UAAU,QAAQ,KAAK,CAChD,OAAM,KAAK,QAAQ;AAGpB,KAAI,MAAM,UAAU,UACnB,QAAO;CAIR,MAAM,gBADsB,MAAM,eAAe,OAAO,GAAG,MAAM,QAAQ,CAAC,GAC9B;CAC5C,MAAM,WAAW,MAAM,MAAM,cAAc;CAC3C,MAAM,WAAW;CACjB,MAAM,iBAAiB;AAEvB,KAAI,SAAS,SAAS,IAAI;EACzB,MAAM,gBAAgB,MAAM,SAAS,YAAY;EACjD,MAAM,sBAAsB;EAC5B,MAAM,aAAa,sBAAsB;AAEzC,MAAI,aAAa,EAChB,QACC,MAAM,MAAM,GAAG,WAAW,CAAC,KAAK,GAAG,GAAG,WAAW,MAAM,MAAM,oBAAoB,CAAC,KAAK,GAAG;;AAK7F,QACC,MAAM,MAAM,GAAG,YAAY,IAAI,eAAe,CAAC,KAAK,GAAG,GAAG,WAAW,MAAM,MAAM,GAAG,CAAC,KAAK,GAAG"}
1
+ {"version":3,"file":"truncate.cjs","names":["chars: string[]"],"sources":["../src/string/truncate.ts"],"sourcesContent":["export const truncate = (text: string, length = 30): string =>\n\ttext.length > length ? text.slice(0, length) + '...' : text;\n\n/**\n * Replace part of given text with ellipsis following the rules below:\n *\n * - Remove chars just before the last word, as long as the last word is under 15 chars\n * - Otherwise preserve the last 5 chars of the name and remove chars before that\n */\nexport function truncateBeforeLast(\n\ttext: string,\n\tmaxLength: number,\n\tlastCharsLength: number = 5,\n): string {\n\tconst chars: string[] = [];\n\n\tconst segmenter = new Intl.Segmenter(undefined, { granularity: 'grapheme' });\n\n\tfor (const { segment } of segmenter.segment(text)) {\n\t\tchars.push(segment);\n\t}\n\n\tif (chars.length <= maxLength) {\n\t\treturn text;\n\t}\n\n\tconst lastWhitespaceIndex = chars.findLastIndex((ch) => ch.match(/^\\s+$/));\n\tconst lastWordIndex = lastWhitespaceIndex + 1;\n\tconst lastWord = chars.slice(lastWordIndex);\n\tconst ellipsis = '…';\n\tconst ellipsisLength = ellipsis.length;\n\n\tif (lastWord.length < 15) {\n\t\tconst charsToRemove = chars.length - maxLength + ellipsisLength;\n\t\tconst indexBeforeLastWord = lastWordIndex;\n\t\tconst keepLength = indexBeforeLastWord - charsToRemove;\n\n\t\tif (keepLength > 0) {\n\t\t\treturn (\n\t\t\t\tchars.slice(0, keepLength).join('') + ellipsis + chars.slice(indexBeforeLastWord).join('')\n\t\t\t);\n\t\t}\n\t}\n\n\tif (lastCharsLength < 1) {\n\t\treturn chars.slice(0, maxLength).join('') + ellipsis;\n\t}\n\n\treturn (\n\t\tchars.slice(0, maxLength - lastCharsLength - ellipsisLength).join('') +\n\t\tellipsis +\n\t\tchars.slice(-lastCharsLength).join('')\n\t);\n}\n"],"mappings":";;AAAA,MAAa,YAAY,MAAc,SAAS,OAC/C,KAAK,SAAS,SAAS,KAAK,MAAM,GAAG,OAAO,GAAG,QAAQ;;;;;;;AAQxD,SAAgB,mBACf,MACA,WACA,kBAA0B,GACjB;CACT,MAAMA,QAAkB,EAAE;CAE1B,MAAM,YAAY,IAAI,KAAK,UAAU,QAAW,EAAE,aAAa,YAAY,CAAC;AAE5E,MAAK,MAAM,EAAE,aAAa,UAAU,QAAQ,KAAK,CAChD,OAAM,KAAK,QAAQ;AAGpB,KAAI,MAAM,UAAU,UACnB,QAAO;CAIR,MAAM,gBADsB,MAAM,eAAe,OAAO,GAAG,MAAM,QAAQ,CAAC,GAC9B;CAC5C,MAAM,WAAW,MAAM,MAAM,cAAc;CAC3C,MAAM,WAAW;CACjB,MAAM,iBAAiB;AAEvB,KAAI,SAAS,SAAS,IAAI;EACzB,MAAM,gBAAgB,MAAM,SAAS,YAAY;EACjD,MAAM,sBAAsB;EAC5B,MAAM,aAAa,sBAAsB;AAEzC,MAAI,aAAa,EAChB,QACC,MAAM,MAAM,GAAG,WAAW,CAAC,KAAK,GAAG,GAAG,WAAW,MAAM,MAAM,oBAAoB,CAAC,KAAK,GAAG;;AAK7F,KAAI,kBAAkB,EACrB,QAAO,MAAM,MAAM,GAAG,UAAU,CAAC,KAAK,GAAG,GAAG;AAG7C,QACC,MAAM,MAAM,GAAG,YAAY,kBAAkB,eAAe,CAAC,KAAK,GAAG,GACrE,WACA,MAAM,MAAM,CAAC,gBAAgB,CAAC,KAAK,GAAG"}
@@ -1,6 +1,6 @@
1
1
  //#region src/string/truncate.d.ts
2
2
  declare const truncate: (text: string, length?: number) => string;
3
- declare function truncateBeforeLast(text: string, maxLength: number): string;
3
+ declare function truncateBeforeLast(text: string, maxLength: number, lastCharsLength?: number): string;
4
4
  //#endregion
5
5
  export { truncateBeforeLast as n, truncate as t };
6
6
  //# sourceMappingURL=truncate.d.cts.map
@@ -1,6 +1,6 @@
1
1
  //#region src/string/truncate.d.ts
2
2
  declare const truncate: (text: string, length?: number) => string;
3
- declare function truncateBeforeLast(text: string, maxLength: number): string;
3
+ declare function truncateBeforeLast(text: string, maxLength: number, lastCharsLength?: number): string;
4
4
  //#endregion
5
5
  export { truncateBeforeLast as n, truncate as t };
6
6
  //# sourceMappingURL=truncate.d.mts.map
package/dist/truncate.mjs CHANGED
@@ -6,7 +6,7 @@ const truncate = (text, length = 30) => text.length > length ? text.slice(0, len
6
6
  * - Remove chars just before the last word, as long as the last word is under 15 chars
7
7
  * - Otherwise preserve the last 5 chars of the name and remove chars before that
8
8
  */
9
- function truncateBeforeLast(text, maxLength) {
9
+ function truncateBeforeLast(text, maxLength, lastCharsLength = 5) {
10
10
  const chars = [];
11
11
  const segmenter = new Intl.Segmenter(void 0, { granularity: "grapheme" });
12
12
  for (const { segment } of segmenter.segment(text)) chars.push(segment);
@@ -21,7 +21,8 @@ function truncateBeforeLast(text, maxLength) {
21
21
  const keepLength = indexBeforeLastWord - charsToRemove;
22
22
  if (keepLength > 0) return chars.slice(0, keepLength).join("") + ellipsis + chars.slice(indexBeforeLastWord).join("");
23
23
  }
24
- return chars.slice(0, maxLength - 5 - ellipsisLength).join("") + ellipsis + chars.slice(-5).join("");
24
+ if (lastCharsLength < 1) return chars.slice(0, maxLength).join("") + ellipsis;
25
+ return chars.slice(0, maxLength - lastCharsLength - ellipsisLength).join("") + ellipsis + chars.slice(-lastCharsLength).join("");
25
26
  }
26
27
 
27
28
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"truncate.mjs","names":["chars: string[]"],"sources":["../src/string/truncate.ts"],"sourcesContent":["export const truncate = (text: string, length = 30): string =>\n\ttext.length > length ? text.slice(0, length) + '...' : text;\n\n/**\n * Replace part of given text with ellipsis following the rules below:\n *\n * - Remove chars just before the last word, as long as the last word is under 15 chars\n * - Otherwise preserve the last 5 chars of the name and remove chars before that\n */\nexport function truncateBeforeLast(text: string, maxLength: number): string {\n\tconst chars: string[] = [];\n\n\tconst segmenter = new Intl.Segmenter(undefined, { granularity: 'grapheme' });\n\n\tfor (const { segment } of segmenter.segment(text)) {\n\t\tchars.push(segment);\n\t}\n\n\tif (chars.length <= maxLength) {\n\t\treturn text;\n\t}\n\n\tconst lastWhitespaceIndex = chars.findLastIndex((ch) => ch.match(/^\\s+$/));\n\tconst lastWordIndex = lastWhitespaceIndex + 1;\n\tconst lastWord = chars.slice(lastWordIndex);\n\tconst ellipsis = '…';\n\tconst ellipsisLength = ellipsis.length;\n\n\tif (lastWord.length < 15) {\n\t\tconst charsToRemove = chars.length - maxLength + ellipsisLength;\n\t\tconst indexBeforeLastWord = lastWordIndex;\n\t\tconst keepLength = indexBeforeLastWord - charsToRemove;\n\n\t\tif (keepLength > 0) {\n\t\t\treturn (\n\t\t\t\tchars.slice(0, keepLength).join('') + ellipsis + chars.slice(indexBeforeLastWord).join('')\n\t\t\t);\n\t\t}\n\t}\n\n\treturn (\n\t\tchars.slice(0, maxLength - 5 - ellipsisLength).join('') + ellipsis + chars.slice(-5).join('')\n\t);\n}\n"],"mappings":";AAAA,MAAa,YAAY,MAAc,SAAS,OAC/C,KAAK,SAAS,SAAS,KAAK,MAAM,GAAG,OAAO,GAAG,QAAQ;;;;;;;AAQxD,SAAgB,mBAAmB,MAAc,WAA2B;CAC3E,MAAMA,QAAkB,EAAE;CAE1B,MAAM,YAAY,IAAI,KAAK,UAAU,QAAW,EAAE,aAAa,YAAY,CAAC;AAE5E,MAAK,MAAM,EAAE,aAAa,UAAU,QAAQ,KAAK,CAChD,OAAM,KAAK,QAAQ;AAGpB,KAAI,MAAM,UAAU,UACnB,QAAO;CAIR,MAAM,gBADsB,MAAM,eAAe,OAAO,GAAG,MAAM,QAAQ,CAAC,GAC9B;CAC5C,MAAM,WAAW,MAAM,MAAM,cAAc;CAC3C,MAAM,WAAW;CACjB,MAAM,iBAAiB;AAEvB,KAAI,SAAS,SAAS,IAAI;EACzB,MAAM,gBAAgB,MAAM,SAAS,YAAY;EACjD,MAAM,sBAAsB;EAC5B,MAAM,aAAa,sBAAsB;AAEzC,MAAI,aAAa,EAChB,QACC,MAAM,MAAM,GAAG,WAAW,CAAC,KAAK,GAAG,GAAG,WAAW,MAAM,MAAM,oBAAoB,CAAC,KAAK,GAAG;;AAK7F,QACC,MAAM,MAAM,GAAG,YAAY,IAAI,eAAe,CAAC,KAAK,GAAG,GAAG,WAAW,MAAM,MAAM,GAAG,CAAC,KAAK,GAAG"}
1
+ {"version":3,"file":"truncate.mjs","names":["chars: string[]"],"sources":["../src/string/truncate.ts"],"sourcesContent":["export const truncate = (text: string, length = 30): string =>\n\ttext.length > length ? text.slice(0, length) + '...' : text;\n\n/**\n * Replace part of given text with ellipsis following the rules below:\n *\n * - Remove chars just before the last word, as long as the last word is under 15 chars\n * - Otherwise preserve the last 5 chars of the name and remove chars before that\n */\nexport function truncateBeforeLast(\n\ttext: string,\n\tmaxLength: number,\n\tlastCharsLength: number = 5,\n): string {\n\tconst chars: string[] = [];\n\n\tconst segmenter = new Intl.Segmenter(undefined, { granularity: 'grapheme' });\n\n\tfor (const { segment } of segmenter.segment(text)) {\n\t\tchars.push(segment);\n\t}\n\n\tif (chars.length <= maxLength) {\n\t\treturn text;\n\t}\n\n\tconst lastWhitespaceIndex = chars.findLastIndex((ch) => ch.match(/^\\s+$/));\n\tconst lastWordIndex = lastWhitespaceIndex + 1;\n\tconst lastWord = chars.slice(lastWordIndex);\n\tconst ellipsis = '…';\n\tconst ellipsisLength = ellipsis.length;\n\n\tif (lastWord.length < 15) {\n\t\tconst charsToRemove = chars.length - maxLength + ellipsisLength;\n\t\tconst indexBeforeLastWord = lastWordIndex;\n\t\tconst keepLength = indexBeforeLastWord - charsToRemove;\n\n\t\tif (keepLength > 0) {\n\t\t\treturn (\n\t\t\t\tchars.slice(0, keepLength).join('') + ellipsis + chars.slice(indexBeforeLastWord).join('')\n\t\t\t);\n\t\t}\n\t}\n\n\tif (lastCharsLength < 1) {\n\t\treturn chars.slice(0, maxLength).join('') + ellipsis;\n\t}\n\n\treturn (\n\t\tchars.slice(0, maxLength - lastCharsLength - ellipsisLength).join('') +\n\t\tellipsis +\n\t\tchars.slice(-lastCharsLength).join('')\n\t);\n}\n"],"mappings":";AAAA,MAAa,YAAY,MAAc,SAAS,OAC/C,KAAK,SAAS,SAAS,KAAK,MAAM,GAAG,OAAO,GAAG,QAAQ;;;;;;;AAQxD,SAAgB,mBACf,MACA,WACA,kBAA0B,GACjB;CACT,MAAMA,QAAkB,EAAE;CAE1B,MAAM,YAAY,IAAI,KAAK,UAAU,QAAW,EAAE,aAAa,YAAY,CAAC;AAE5E,MAAK,MAAM,EAAE,aAAa,UAAU,QAAQ,KAAK,CAChD,OAAM,KAAK,QAAQ;AAGpB,KAAI,MAAM,UAAU,UACnB,QAAO;CAIR,MAAM,gBADsB,MAAM,eAAe,OAAO,GAAG,MAAM,QAAQ,CAAC,GAC9B;CAC5C,MAAM,WAAW,MAAM,MAAM,cAAc;CAC3C,MAAM,WAAW;CACjB,MAAM,iBAAiB;AAEvB,KAAI,SAAS,SAAS,IAAI;EACzB,MAAM,gBAAgB,MAAM,SAAS,YAAY;EACjD,MAAM,sBAAsB;EAC5B,MAAM,aAAa,sBAAsB;AAEzC,MAAI,aAAa,EAChB,QACC,MAAM,MAAM,GAAG,WAAW,CAAC,KAAK,GAAG,GAAG,WAAW,MAAM,MAAM,oBAAoB,CAAC,KAAK,GAAG;;AAK7F,KAAI,kBAAkB,EACrB,QAAO,MAAM,MAAM,GAAG,UAAU,CAAC,KAAK,GAAG,GAAG;AAG7C,QACC,MAAM,MAAM,GAAG,YAAY,kBAAkB,eAAe,CAAC,KAAK,GAAG,GACrE,WACA,MAAM,MAAM,CAAC,gBAAgB,CAAC,KAAK,GAAG"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@n8n/utils",
3
3
  "type": "module",
4
- "version": "1.19.0",
4
+ "version": "1.21.0",
5
5
  "files": [
6
6
  "dist",
7
7
  "LICENSE.md",
@@ -54,6 +54,7 @@
54
54
  "preview": "vite preview",
55
55
  "typecheck": "tsc --noEmit",
56
56
  "test": "vitest run",
57
+ "test:unit": "vitest run",
57
58
  "test:dev": "vitest --silent=false",
58
59
  "lint": "eslint src --quiet",
59
60
  "lint:fix": "eslint src --fix",