@cjser/filenamify 7.0.2-cjser.2 → 7.0.3-cjser.2

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.
@@ -26,7 +26,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
26
26
  ));
27
27
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
28
28
 
29
- // packages/@cjser/filenamify.tmp-26-1781714412105/filenamify.js
29
+ // packages/@cjser/filenamify.tmp-25-1788284040504/filenamify.js
30
30
  var filenamify_exports = {};
31
31
  __export(filenamify_exports, {
32
32
  default: () => filenamify
@@ -36,6 +36,8 @@ var import_filename_reserved_regex = __toESM(require("@cjser/filename-reserved-r
36
36
  var MAX_FILENAME_LENGTH = 100;
37
37
  var reRelativePath = /^\.+(\\|\/)|^\.+$/;
38
38
  var reTrailingDotsAndSpaces = /[. ]+$/;
39
+ var reTrailingSpaces = / +$/;
40
+ var reWindowsReservedName = (0, import_filename_reserved_regex.windowsReservedNameRegex)();
39
41
  var reControlChars = /[\p{Control}\p{Format}\p{Zl}\p{Zp}\uFFF0-\uFFFF]/gu;
40
42
  var reControlCharsTest = /[\p{Control}\p{Format}\p{Zl}\p{Zp}\uFFF0-\uFFFF]/u;
41
43
  var isZeroWidthJoiner = (char) => char === "\u200D";
@@ -59,15 +61,18 @@ function truncateFilename(filename, maxLength) {
59
61
  const extension = filename.slice(extensionIndex);
60
62
  const baseBudget = Math.max(0, maxLength - extension.length);
61
63
  const truncatedBase = truncateByGraphemeBudget(base, baseBudget);
62
- return truncatedBase.replace(/ +$/, "") + extension;
64
+ return truncatedBase.replace(reTrailingSpaces, "") + extension;
63
65
  }
64
66
  function ensureNonWindowsReservedName(filename, replacement) {
65
- const extensionIndex = filename.indexOf(".");
66
- const base = extensionIndex === -1 ? filename : filename.slice(0, extensionIndex);
67
- if (!(0, import_filename_reserved_regex.windowsReservedNameRegex)().test(base)) {
67
+ if (!reWindowsReservedName.test(filename)) {
68
68
  return filename;
69
69
  }
70
- return extensionIndex === -1 ? filename + replacement : base + replacement + filename.slice(extensionIndex);
70
+ const extensionIndex = filename.indexOf(".");
71
+ const base = extensionIndex === -1 ? filename : filename.slice(0, extensionIndex);
72
+ const baseWithoutTrailingSpaces = base.replace(reTrailingSpaces, "");
73
+ const extension = extensionIndex === -1 ? "" : filename.slice(extensionIndex);
74
+ const candidate = (baseWithoutTrailingSpaces + replacement + extension).replace(reTrailingDotsAndSpaces, "");
75
+ return reWindowsReservedName.test(candidate) ? baseWithoutTrailingSpaces + "!" + extension : candidate;
71
76
  }
72
77
  function filenamify(string, options = {}) {
73
78
  if (typeof string !== "string") {
@@ -85,8 +90,8 @@ function filenamify(string, options = {}) {
85
90
  string = string.replaceAll(reRepeatedReservedCharacters, "$1");
86
91
  }
87
92
  string = string.replace(reTrailingDotsAndSpaces, "");
88
- string = string.replace(reRelativePath, replacement);
89
- string = string.replace((0, import_filename_reserved_regex.default)(), replacement);
93
+ string = string.replace(reRelativePath, () => replacement);
94
+ string = string.replace((0, import_filename_reserved_regex.default)(), () => replacement);
90
95
  string = string.replaceAll(reControlChars, (char) => isZeroWidthJoiner(char) ? char : replacement);
91
96
  string = string.replace(reTrailingDotsAndSpaces, "");
92
97
  if (string.length === 0) {
@@ -26,7 +26,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
26
26
  ));
27
27
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
28
28
 
29
- // packages/@cjser/filenamify.tmp-26-1781714412105/index.js
29
+ // packages/@cjser/filenamify.tmp-25-1788284040504/index.js
30
30
  var index_exports = {};
31
31
  __export(index_exports, {
32
32
  default: () => filenamify,
@@ -34,11 +34,13 @@ __export(index_exports, {
34
34
  });
35
35
  module.exports = __toCommonJS(index_exports);
36
36
 
37
- // packages/@cjser/filenamify.tmp-26-1781714412105/filenamify.js
37
+ // packages/@cjser/filenamify.tmp-25-1788284040504/filenamify.js
38
38
  var import_filename_reserved_regex = __toESM(require("@cjser/filename-reserved-regex"), 1);
39
39
  var MAX_FILENAME_LENGTH = 100;
40
40
  var reRelativePath = /^\.+(\\|\/)|^\.+$/;
41
41
  var reTrailingDotsAndSpaces = /[. ]+$/;
42
+ var reTrailingSpaces = / +$/;
43
+ var reWindowsReservedName = (0, import_filename_reserved_regex.windowsReservedNameRegex)();
42
44
  var reControlChars = /[\p{Control}\p{Format}\p{Zl}\p{Zp}\uFFF0-\uFFFF]/gu;
43
45
  var reControlCharsTest = /[\p{Control}\p{Format}\p{Zl}\p{Zp}\uFFF0-\uFFFF]/u;
44
46
  var isZeroWidthJoiner = (char) => char === "\u200D";
@@ -62,15 +64,18 @@ function truncateFilename(filename, maxLength) {
62
64
  const extension = filename.slice(extensionIndex);
63
65
  const baseBudget = Math.max(0, maxLength - extension.length);
64
66
  const truncatedBase = truncateByGraphemeBudget(base, baseBudget);
65
- return truncatedBase.replace(/ +$/, "") + extension;
67
+ return truncatedBase.replace(reTrailingSpaces, "") + extension;
66
68
  }
67
69
  function ensureNonWindowsReservedName(filename, replacement) {
68
- const extensionIndex = filename.indexOf(".");
69
- const base = extensionIndex === -1 ? filename : filename.slice(0, extensionIndex);
70
- if (!(0, import_filename_reserved_regex.windowsReservedNameRegex)().test(base)) {
70
+ if (!reWindowsReservedName.test(filename)) {
71
71
  return filename;
72
72
  }
73
- return extensionIndex === -1 ? filename + replacement : base + replacement + filename.slice(extensionIndex);
73
+ const extensionIndex = filename.indexOf(".");
74
+ const base = extensionIndex === -1 ? filename : filename.slice(0, extensionIndex);
75
+ const baseWithoutTrailingSpaces = base.replace(reTrailingSpaces, "");
76
+ const extension = extensionIndex === -1 ? "" : filename.slice(extensionIndex);
77
+ const candidate = (baseWithoutTrailingSpaces + replacement + extension).replace(reTrailingDotsAndSpaces, "");
78
+ return reWindowsReservedName.test(candidate) ? baseWithoutTrailingSpaces + "!" + extension : candidate;
74
79
  }
75
80
  function filenamify(string, options = {}) {
76
81
  if (typeof string !== "string") {
@@ -88,8 +93,8 @@ function filenamify(string, options = {}) {
88
93
  string = string.replaceAll(reRepeatedReservedCharacters, "$1");
89
94
  }
90
95
  string = string.replace(reTrailingDotsAndSpaces, "");
91
- string = string.replace(reRelativePath, replacement);
92
- string = string.replace((0, import_filename_reserved_regex.default)(), replacement);
96
+ string = string.replace(reRelativePath, () => replacement);
97
+ string = string.replace((0, import_filename_reserved_regex.default)(), () => replacement);
93
98
  string = string.replaceAll(reControlChars, (char) => isZeroWidthJoiner(char) ? char : replacement);
94
99
  string = string.replace(reTrailingDotsAndSpaces, "");
95
100
  if (string.length === 0) {
@@ -121,7 +126,7 @@ function truncateByGraphemeBudget(input, budget) {
121
126
  return output;
122
127
  }
123
128
 
124
- // packages/@cjser/filenamify.tmp-26-1781714412105/filenamify-path.js
129
+ // packages/@cjser/filenamify.tmp-25-1788284040504/filenamify-path.js
125
130
  var import_node_path = __toESM(require("node:path"), 1);
126
131
  function filenamifyPath(filePath, options) {
127
132
  filePath = import_node_path.default.resolve(filePath);
package/filenamify.d.ts CHANGED
@@ -4,6 +4,8 @@ export type Options = {
4
4
 
5
5
  Cannot contain: `<` `>` `:` `"` `/` `\` `|` `?` `*` or control characters.
6
6
 
7
+ If the replacement would leave the result as a Windows reserved name, `!` is used instead.
8
+
7
9
  @default '!'
8
10
  */
9
11
  readonly replacement?: string;
@@ -13,6 +15,8 @@ export type Options = {
13
15
 
14
16
  Only the base of the filename is truncated, preserving the extension. If the extension itself is longer than `maxLength`, you will get a string that is longer than `maxLength`, so you need to check for that if you allow arbitrary extensions.
15
17
 
18
+ The result may also exceed `maxLength` when a suffix is needed to avoid a Windows reserved name.
19
+
16
20
  Truncation is grapheme-aware and will not split Unicode characters (surrogate pairs or extended grapheme clusters). If the remaining budget (after accounting for the extension) is smaller than a whole grapheme, the base filename may be truncated to an empty string to avoid splitting.
17
21
 
18
22
  Systems generally allow up to 255 characters, but we default to 100 for usability reasons.
package/filenamify.js CHANGED
@@ -5,6 +5,8 @@ const MAX_FILENAME_LENGTH = 100;
5
5
 
6
6
  const reRelativePath = /^\.+(\\|\/)|^\.+$/;
7
7
  const reTrailingDotsAndSpaces = /[. ]+$/;
8
+ const reTrailingSpaces = / +$/;
9
+ const reWindowsReservedName = windowsReservedNameRegex();
8
10
 
9
11
  // Remove all problematic characters except zero-width joiner (\u200D) needed for emoji
10
12
  const reControlChars = /[\p{Control}\p{Format}\p{Zl}\p{Zp}\uFFF0-\uFFFF]/gu;
@@ -44,20 +46,23 @@ function truncateFilename(filename, maxLength) {
44
46
  const truncatedBase = truncateByGraphemeBudget(base, baseBudget);
45
47
 
46
48
  // Strip trailing spaces from base (not periods - they're not trailing in final filename)
47
- return truncatedBase.replace(/ +$/, '') + extension;
49
+ return truncatedBase.replace(reTrailingSpaces, '') + extension;
48
50
  }
49
51
 
50
52
  function ensureNonWindowsReservedName(filename, replacement) {
51
- const extensionIndex = filename.indexOf('.');
52
- const base = extensionIndex === -1 ? filename : filename.slice(0, extensionIndex);
53
-
54
- if (!windowsReservedNameRegex().test(base)) {
53
+ if (!reWindowsReservedName.test(filename)) {
55
54
  return filename;
56
55
  }
57
56
 
58
- return extensionIndex === -1
59
- ? filename + replacement
60
- : base + replacement + filename.slice(extensionIndex);
57
+ const extensionIndex = filename.indexOf('.');
58
+ const base = extensionIndex === -1 ? filename : filename.slice(0, extensionIndex);
59
+ const baseWithoutTrailingSpaces = base.replace(reTrailingSpaces, '');
60
+ const extension = extensionIndex === -1 ? '' : filename.slice(extensionIndex);
61
+ const candidate = (baseWithoutTrailingSpaces + replacement + extension).replace(reTrailingDotsAndSpaces, '');
62
+
63
+ return reWindowsReservedName.test(candidate)
64
+ ? baseWithoutTrailingSpaces + '!' + extension
65
+ : candidate;
61
66
  }
62
67
 
63
68
  export default function filenamify(string, options = {}) {
@@ -88,8 +93,8 @@ export default function filenamify(string, options = {}) {
88
93
  // so they get stripped rather than replaced
89
94
  string = string.replace(reTrailingDotsAndSpaces, '');
90
95
 
91
- string = string.replace(reRelativePath, replacement);
92
- string = string.replace(filenameReservedRegex(), replacement);
96
+ string = string.replace(reRelativePath, () => replacement);
97
+ string = string.replace(filenameReservedRegex(), () => replacement);
93
98
  string = string.replaceAll(reControlChars, char => isZeroWidthJoiner(char) ? char : replacement);
94
99
 
95
100
  // Trim trailing spaces and periods again (in case replacement created new ones)
@@ -112,7 +117,7 @@ export default function filenamify(string, options = {}) {
112
117
  string = string.replace(reTrailingDotsAndSpaces, '');
113
118
 
114
119
  // Check for Windows reserved names after truncation and stripping
115
- // Windows compatibility takes precedence over maxLength, so we add suffix even if it exceeds limit
120
+ // Windows compatibility takes precedence over maxLength, so the result may exceed it
116
121
  string = ensureNonWindowsReservedName(string, replacement);
117
122
 
118
123
  return string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cjser/filenamify",
3
- "version": "7.0.2-cjser.2",
3
+ "version": "7.0.3-cjser.2",
4
4
  "description": "Convert a string to a valid safe filename",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -56,7 +56,7 @@
56
56
  "dirname"
57
57
  ],
58
58
  "dependencies": {
59
- "@cjser/filename-reserved-regex": "4.0.0-cjser.2"
59
+ "@cjser/filename-reserved-regex": "4.0.1-cjser.2"
60
60
  },
61
61
  "devDependencies": {
62
62
  "ava": "^6.4.1",
@@ -65,11 +65,11 @@
65
65
  "types": "./index.d.ts",
66
66
  "main": "./dist-cjser/index.cjs",
67
67
  "cjser": {
68
- "sourceVersion": "7.0.2",
68
+ "sourceVersion": "7.0.3",
69
69
  "cjserVersion": 2,
70
70
  "original": {
71
71
  "name": "filenamify",
72
- "version": "7.0.2",
72
+ "version": "7.0.3",
73
73
  "exports": {
74
74
  ".": {
75
75
  "types": "./index.d.ts",
@@ -82,7 +82,7 @@
82
82
  },
83
83
  "repository": "sindresorhus/filenamify",
84
84
  "dependencies": {
85
- "filename-reserved-regex": "^4.0.0"
85
+ "filename-reserved-regex": "^4.0.1"
86
86
  },
87
87
  "files": [
88
88
  "filenamify-path.d.ts",
package/readme.md CHANGED
@@ -54,6 +54,8 @@ String to use as replacement for reserved filename characters.
54
54
 
55
55
  Cannot contain: `<` `>` `:` `"` `/` `\` `|` `?` `*` or control characters.
56
56
 
57
+ If the replacement would leave the result as a Windows reserved name, `!` is used instead.
58
+
57
59
  ##### maxLength
58
60
 
59
61
  Type: `number`\
@@ -63,6 +65,8 @@ Truncate the filename to the given length.
63
65
 
64
66
  Only the base of the filename is truncated, preserving the extension. If the extension itself is longer than `maxLength`, you will get a string that is longer than `maxLength`, so you need to check for that if you allow arbitrary extensions.
65
67
 
68
+ The result may also exceed `maxLength` when a suffix is needed to avoid a Windows reserved name.
69
+
66
70
  Truncation is grapheme-aware and will not split Unicode characters (surrogate pairs or extended grapheme clusters). If the remaining budget (after accounting for the extension) is smaller than a whole grapheme, the base filename may be truncated to an empty string to avoid splitting.
67
71
 
68
72
  Systems generally allow up to 255 characters, but we default to 100 for usability reasons.