@cjser/filenamify 7.0.1-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.
@@ -0,0 +1,113 @@
1
+ var __create = Object.create;
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __getProtoOf = Object.getPrototypeOf;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
11
+ var __copyProps = (to, from, except, desc) => {
12
+ if (from && typeof from === "object" || typeof from === "function") {
13
+ for (let key of __getOwnPropNames(from))
14
+ if (!__hasOwnProp.call(to, key) && key !== except)
15
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
16
+ }
17
+ return to;
18
+ };
19
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
20
+ // If the importer is in node compatibility mode or this is not an ESM
21
+ // file that has been converted to a CommonJS file using a Babel-
22
+ // compatible transform (i.e. "__esModule" has not been set), then set
23
+ // "default" to the CommonJS "module.exports" for node compatibility.
24
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
25
+ mod
26
+ ));
27
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
28
+
29
+ // packages/@cjser/filenamify.tmp-26-1778150437771/filenamify.js
30
+ var filenamify_exports = {};
31
+ __export(filenamify_exports, {
32
+ default: () => filenamify
33
+ });
34
+ module.exports = __toCommonJS(filenamify_exports);
35
+ var import_filename_reserved_regex = __toESM(require("@cjser/filename-reserved-regex"), 1);
36
+ var MAX_FILENAME_LENGTH = 100;
37
+ var reRelativePath = /^\.+(\\|\/)|^\.+$/;
38
+ var reTrailingDotsAndSpaces = /[. ]+$/;
39
+ var reControlChars = /[\p{Control}\p{Format}\p{Zl}\p{Zp}\uFFF0-\uFFFF]/gu;
40
+ var reControlCharsTest = /[\p{Control}\p{Format}\p{Zl}\p{Zp}\uFFF0-\uFFFF]/u;
41
+ var isZeroWidthJoiner = (char) => char === "\u200D";
42
+ var reRepeatedReservedCharacters = /([<>:"/\\|?*\u0000-\u001F]){2,}/g;
43
+ var reReplacementReservedCharacters = /[<>:"/\\|?*\u0000-\u001F]/;
44
+ var reUnicodeWhitespace = /[\t\n\r\u00A0\u1680\u2000-\u200A\u202F\u205F\u3000]+/g;
45
+ var segmenter;
46
+ function getSegmenter() {
47
+ segmenter ??= new Intl.Segmenter(void 0, { granularity: "grapheme" });
48
+ return segmenter;
49
+ }
50
+ function truncateFilename(filename, maxLength) {
51
+ if (filename.length <= maxLength) {
52
+ return filename;
53
+ }
54
+ const extensionIndex = filename.lastIndexOf(".");
55
+ if (extensionIndex === -1) {
56
+ return truncateByGraphemeBudget(filename, maxLength);
57
+ }
58
+ const base = filename.slice(0, extensionIndex);
59
+ const extension = filename.slice(extensionIndex);
60
+ const baseBudget = Math.max(0, maxLength - extension.length);
61
+ const truncatedBase = truncateByGraphemeBudget(base, baseBudget);
62
+ return truncatedBase.replace(/ +$/, "") + extension;
63
+ }
64
+ function filenamify(string, options = {}) {
65
+ if (typeof string !== "string") {
66
+ throw new TypeError("Expected a string");
67
+ }
68
+ const replacement = options.replacement ?? "!";
69
+ const hasReservedChars = reReplacementReservedCharacters.test(replacement);
70
+ const hasControlChars = [...replacement].some((char) => reControlCharsTest.test(char) && !isZeroWidthJoiner(char));
71
+ if (hasReservedChars || hasControlChars) {
72
+ throw new Error("Replacement string cannot contain reserved filename characters");
73
+ }
74
+ string = string.normalize("NFC");
75
+ string = string.replaceAll(reUnicodeWhitespace, " ");
76
+ if (replacement.length > 0) {
77
+ string = string.replaceAll(reRepeatedReservedCharacters, "$1");
78
+ }
79
+ string = string.replace(reTrailingDotsAndSpaces, "");
80
+ string = string.replace(reRelativePath, replacement);
81
+ string = string.replace((0, import_filename_reserved_regex.default)(), replacement);
82
+ string = string.replaceAll(reControlChars, (char) => isZeroWidthJoiner(char) ? char : replacement);
83
+ string = string.replace(reTrailingDotsAndSpaces, "");
84
+ if (string.length === 0) {
85
+ string = replacement.replace(reTrailingDotsAndSpaces, "");
86
+ if (string.length === 0 && replacement.length > 0) {
87
+ string = "!";
88
+ }
89
+ }
90
+ const allowedLength = typeof options.maxLength === "number" ? options.maxLength : MAX_FILENAME_LENGTH;
91
+ string = truncateFilename(string, allowedLength);
92
+ string = string.replace(reTrailingDotsAndSpaces, "");
93
+ if ((0, import_filename_reserved_regex.windowsReservedNameRegex)().test(string)) {
94
+ string += replacement;
95
+ }
96
+ return string;
97
+ }
98
+ function truncateByGraphemeBudget(input, budget) {
99
+ if (input.length <= budget) {
100
+ return input;
101
+ }
102
+ let count = 0;
103
+ let output = "";
104
+ for (const { segment } of getSegmenter().segment(input)) {
105
+ const next = count + segment.length;
106
+ if (next > budget) {
107
+ break;
108
+ }
109
+ output += segment;
110
+ count = next;
111
+ }
112
+ return output;
113
+ }
@@ -0,0 +1,127 @@
1
+ var __create = Object.create;
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __getProtoOf = Object.getPrototypeOf;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
11
+ var __copyProps = (to, from, except, desc) => {
12
+ if (from && typeof from === "object" || typeof from === "function") {
13
+ for (let key of __getOwnPropNames(from))
14
+ if (!__hasOwnProp.call(to, key) && key !== except)
15
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
16
+ }
17
+ return to;
18
+ };
19
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
20
+ // If the importer is in node compatibility mode or this is not an ESM
21
+ // file that has been converted to a CommonJS file using a Babel-
22
+ // compatible transform (i.e. "__esModule" has not been set), then set
23
+ // "default" to the CommonJS "module.exports" for node compatibility.
24
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
25
+ mod
26
+ ));
27
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
28
+
29
+ // packages/@cjser/filenamify.tmp-26-1778150437771/index.js
30
+ var index_exports = {};
31
+ __export(index_exports, {
32
+ default: () => filenamify,
33
+ filenamifyPath: () => filenamifyPath
34
+ });
35
+ module.exports = __toCommonJS(index_exports);
36
+
37
+ // packages/@cjser/filenamify.tmp-26-1778150437771/filenamify.js
38
+ var import_filename_reserved_regex = __toESM(require("@cjser/filename-reserved-regex"), 1);
39
+ var MAX_FILENAME_LENGTH = 100;
40
+ var reRelativePath = /^\.+(\\|\/)|^\.+$/;
41
+ var reTrailingDotsAndSpaces = /[. ]+$/;
42
+ var reControlChars = /[\p{Control}\p{Format}\p{Zl}\p{Zp}\uFFF0-\uFFFF]/gu;
43
+ var reControlCharsTest = /[\p{Control}\p{Format}\p{Zl}\p{Zp}\uFFF0-\uFFFF]/u;
44
+ var isZeroWidthJoiner = (char) => char === "\u200D";
45
+ var reRepeatedReservedCharacters = /([<>:"/\\|?*\u0000-\u001F]){2,}/g;
46
+ var reReplacementReservedCharacters = /[<>:"/\\|?*\u0000-\u001F]/;
47
+ var reUnicodeWhitespace = /[\t\n\r\u00A0\u1680\u2000-\u200A\u202F\u205F\u3000]+/g;
48
+ var segmenter;
49
+ function getSegmenter() {
50
+ segmenter ??= new Intl.Segmenter(void 0, { granularity: "grapheme" });
51
+ return segmenter;
52
+ }
53
+ function truncateFilename(filename, maxLength) {
54
+ if (filename.length <= maxLength) {
55
+ return filename;
56
+ }
57
+ const extensionIndex = filename.lastIndexOf(".");
58
+ if (extensionIndex === -1) {
59
+ return truncateByGraphemeBudget(filename, maxLength);
60
+ }
61
+ const base = filename.slice(0, extensionIndex);
62
+ const extension = filename.slice(extensionIndex);
63
+ const baseBudget = Math.max(0, maxLength - extension.length);
64
+ const truncatedBase = truncateByGraphemeBudget(base, baseBudget);
65
+ return truncatedBase.replace(/ +$/, "") + extension;
66
+ }
67
+ function filenamify(string, options = {}) {
68
+ if (typeof string !== "string") {
69
+ throw new TypeError("Expected a string");
70
+ }
71
+ const replacement = options.replacement ?? "!";
72
+ const hasReservedChars = reReplacementReservedCharacters.test(replacement);
73
+ const hasControlChars = [...replacement].some((char) => reControlCharsTest.test(char) && !isZeroWidthJoiner(char));
74
+ if (hasReservedChars || hasControlChars) {
75
+ throw new Error("Replacement string cannot contain reserved filename characters");
76
+ }
77
+ string = string.normalize("NFC");
78
+ string = string.replaceAll(reUnicodeWhitespace, " ");
79
+ if (replacement.length > 0) {
80
+ string = string.replaceAll(reRepeatedReservedCharacters, "$1");
81
+ }
82
+ string = string.replace(reTrailingDotsAndSpaces, "");
83
+ string = string.replace(reRelativePath, replacement);
84
+ string = string.replace((0, import_filename_reserved_regex.default)(), replacement);
85
+ string = string.replaceAll(reControlChars, (char) => isZeroWidthJoiner(char) ? char : replacement);
86
+ string = string.replace(reTrailingDotsAndSpaces, "");
87
+ if (string.length === 0) {
88
+ string = replacement.replace(reTrailingDotsAndSpaces, "");
89
+ if (string.length === 0 && replacement.length > 0) {
90
+ string = "!";
91
+ }
92
+ }
93
+ const allowedLength = typeof options.maxLength === "number" ? options.maxLength : MAX_FILENAME_LENGTH;
94
+ string = truncateFilename(string, allowedLength);
95
+ string = string.replace(reTrailingDotsAndSpaces, "");
96
+ if ((0, import_filename_reserved_regex.windowsReservedNameRegex)().test(string)) {
97
+ string += replacement;
98
+ }
99
+ return string;
100
+ }
101
+ function truncateByGraphemeBudget(input, budget) {
102
+ if (input.length <= budget) {
103
+ return input;
104
+ }
105
+ let count = 0;
106
+ let output = "";
107
+ for (const { segment } of getSegmenter().segment(input)) {
108
+ const next = count + segment.length;
109
+ if (next > budget) {
110
+ break;
111
+ }
112
+ output += segment;
113
+ count = next;
114
+ }
115
+ return output;
116
+ }
117
+
118
+ // packages/@cjser/filenamify.tmp-26-1778150437771/filenamify-path.js
119
+ var import_node_path = __toESM(require("node:path"), 1);
120
+ function filenamifyPath(filePath, options) {
121
+ filePath = import_node_path.default.resolve(filePath);
122
+ return import_node_path.default.join(import_node_path.default.dirname(filePath), filenamify(import_node_path.default.basename(filePath), options));
123
+ }
124
+ // Annotate the CommonJS export names for ESM import in node:
125
+ 0 && (module.exports = {
126
+ filenamifyPath
127
+ });
@@ -0,0 +1,16 @@
1
+ import {type Options} from './filenamify.js';
2
+
3
+ /**
4
+ Convert the filename in a path to a valid filename and return the augmented path.
5
+
6
+ @example
7
+ ```
8
+ import {filenamifyPath} from '@cjser/filenamify';
9
+
10
+ filenamifyPath('foo:bar');
11
+ //=> 'foo!bar'
12
+ ```
13
+ */
14
+ export default function filenamifyPath(path: string, options?: Options): string;
15
+
16
+ export type {Options} from './filenamify.js';
@@ -0,0 +1,7 @@
1
+ import path from 'node:path';
2
+ import filenamify from './filenamify.js';
3
+
4
+ export default function filenamifyPath(filePath, options) {
5
+ filePath = path.resolve(filePath);
6
+ return path.join(path.dirname(filePath), filenamify(path.basename(filePath), options));
7
+ }
@@ -0,0 +1,39 @@
1
+ export type Options = {
2
+ /**
3
+ String to use as replacement for reserved filename characters.
4
+
5
+ Cannot contain: `<` `>` `:` `"` `/` `\` `|` `?` `*` or control characters.
6
+
7
+ @default '!'
8
+ */
9
+ readonly replacement?: string;
10
+
11
+ /**
12
+ Truncate the filename to the given length.
13
+
14
+ 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
+
16
+ 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
+
18
+ Systems generally allow up to 255 characters, but we default to 100 for usability reasons.
19
+
20
+ @default 100
21
+ */
22
+ readonly maxLength?: number;
23
+ };
24
+
25
+ /**
26
+ Convert a string to a valid filename.
27
+
28
+ @example
29
+ ```
30
+ import filenamify from '@cjser/filenamify';
31
+
32
+ filenamify('<foo/bar>');
33
+ //=> '!foo!bar!'
34
+
35
+ filenamify('foo:"bar"', {replacement: '🐴'});
36
+ //=> 'foo🐴bar🐴'
37
+ ```
38
+ */
39
+ export default function filenamify(string: string, options?: Options): string;
package/filenamify.js ADDED
@@ -0,0 +1,128 @@
1
+ import filenameReservedRegex, {windowsReservedNameRegex} from '@cjser/filename-reserved-regex';
2
+
3
+ // Doesn't make sense to have longer filenames
4
+ const MAX_FILENAME_LENGTH = 100;
5
+
6
+ const reRelativePath = /^\.+(\\|\/)|^\.+$/;
7
+ const reTrailingDotsAndSpaces = /[. ]+$/;
8
+
9
+ // Remove all problematic characters except zero-width joiner (\u200D) needed for emoji
10
+ const reControlChars = /[\p{Control}\p{Format}\p{Zl}\p{Zp}\uFFF0-\uFFFF]/gu;
11
+ const reControlCharsTest = /[\p{Control}\p{Format}\p{Zl}\p{Zp}\uFFF0-\uFFFF]/u;
12
+ const isZeroWidthJoiner = char => char === '\u200D';
13
+ const reRepeatedReservedCharacters = /([<>:"/\\|?*\u0000-\u001F]){2,}/g; // eslint-disable-line no-control-regex
14
+
15
+ // For validating replacement string - only truly reserved characters, not trailing spaces/periods
16
+ const reReplacementReservedCharacters = /[<>:"/\\|?*\u0000-\u001F]/; // eslint-disable-line no-control-regex
17
+
18
+ // Normalize various Unicode whitespace characters to regular space
19
+ // Using specific characters instead of \s to avoid matching regular spaces
20
+ const reUnicodeWhitespace = /[\t\n\r\u00A0\u1680\u2000-\u200A\u202F\u205F\u3000]+/g;
21
+
22
+ let segmenter;
23
+ function getSegmenter() {
24
+ segmenter ??= new Intl.Segmenter(undefined, {granularity: 'grapheme'});
25
+ return segmenter;
26
+ }
27
+
28
+ function truncateFilename(filename, maxLength) {
29
+ if (filename.length <= maxLength) {
30
+ return filename;
31
+ }
32
+
33
+ const extensionIndex = filename.lastIndexOf('.');
34
+
35
+ // No extension - simple truncation
36
+ if (extensionIndex === -1) {
37
+ return truncateByGraphemeBudget(filename, maxLength);
38
+ }
39
+
40
+ // Has extension - preserve it and truncate base
41
+ const base = filename.slice(0, extensionIndex);
42
+ const extension = filename.slice(extensionIndex);
43
+ const baseBudget = Math.max(0, maxLength - extension.length);
44
+ const truncatedBase = truncateByGraphemeBudget(base, baseBudget);
45
+
46
+ // Strip trailing spaces from base (not periods - they're not trailing in final filename)
47
+ return truncatedBase.replace(/ +$/, '') + extension;
48
+ }
49
+
50
+ export default function filenamify(string, options = {}) {
51
+ if (typeof string !== 'string') {
52
+ throw new TypeError('Expected a string');
53
+ }
54
+
55
+ const replacement = options.replacement ?? '!';
56
+
57
+ const hasReservedChars = reReplacementReservedCharacters.test(replacement);
58
+ const hasControlChars = [...replacement].some(char => reControlCharsTest.test(char) && !isZeroWidthJoiner(char));
59
+
60
+ if (hasReservedChars || hasControlChars) {
61
+ throw new Error('Replacement string cannot contain reserved filename characters');
62
+ }
63
+
64
+ // Normalize to NFC first to stabilize byte representation and length calculations across platforms.
65
+ string = string.normalize('NFC');
66
+
67
+ // Normalize Unicode whitespace to single spaces
68
+ string = string.replaceAll(reUnicodeWhitespace, ' ');
69
+
70
+ if (replacement.length > 0) {
71
+ string = string.replaceAll(reRepeatedReservedCharacters, '$1');
72
+ }
73
+
74
+ // Trim trailing spaces and periods (Windows rule) - do this BEFORE replacements
75
+ // so they get stripped rather than replaced
76
+ string = string.replace(reTrailingDotsAndSpaces, '');
77
+
78
+ string = string.replace(reRelativePath, replacement);
79
+ string = string.replace(filenameReservedRegex(), replacement);
80
+ string = string.replaceAll(reControlChars, char => isZeroWidthJoiner(char) ? char : replacement);
81
+
82
+ // Trim trailing spaces and periods again (in case replacement created new ones)
83
+ string = string.replace(reTrailingDotsAndSpaces, '');
84
+
85
+ // If the string is now empty, use replacement with trailing spaces/periods stripped
86
+ if (string.length === 0) {
87
+ string = replacement.replace(reTrailingDotsAndSpaces, '');
88
+ // If still empty and replacement wasn't explicitly empty, use '!' as fallback
89
+ if (string.length === 0 && replacement.length > 0) {
90
+ string = '!';
91
+ }
92
+ }
93
+
94
+ // Truncate before Windows reserved name check (truncation can create reserved names)
95
+ const allowedLength = typeof options.maxLength === 'number' ? options.maxLength : MAX_FILENAME_LENGTH;
96
+ string = truncateFilename(string, allowedLength);
97
+
98
+ // Strip trailing spaces/periods after truncation (truncation can create them)
99
+ string = string.replace(reTrailingDotsAndSpaces, '');
100
+
101
+ // Check for Windows reserved names after truncation and stripping
102
+ // Windows compatibility takes precedence over maxLength, so we add suffix even if it exceeds limit
103
+ if (windowsReservedNameRegex().test(string)) {
104
+ string += replacement;
105
+ }
106
+
107
+ return string;
108
+ }
109
+
110
+ function truncateByGraphemeBudget(input, budget) {
111
+ if (input.length <= budget) {
112
+ return input;
113
+ }
114
+
115
+ let count = 0;
116
+ let output = '';
117
+ for (const {segment} of getSegmenter().segment(input)) {
118
+ const next = count + segment.length;
119
+ if (next > budget) {
120
+ break;
121
+ }
122
+
123
+ output += segment;
124
+ count = next;
125
+ }
126
+
127
+ return output;
128
+ }
package/index.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ export {default} from './filenamify.js';
2
+ export * from './filenamify.js';
3
+ export {default as filenamifyPath} from './filenamify-path.js';
package/index.js ADDED
@@ -0,0 +1,2 @@
1
+ export {default} from './filenamify.js';
2
+ export {default as filenamifyPath} from './filenamify-path.js';
package/license ADDED
@@ -0,0 +1,9 @@
1
+ MIT License
2
+
3
+ Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (https://sindresorhus.com)
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6
+
7
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8
+
9
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package/package.json ADDED
@@ -0,0 +1,100 @@
1
+ {
2
+ "name": "@cjser/filenamify",
3
+ "version": "7.0.1-cjser.2",
4
+ "description": "Convert a string to a valid safe filename",
5
+ "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "https://code.moenext.com/3rdeye/cjser.git"
9
+ },
10
+ "funding": "https://github.com/sponsors/sindresorhus",
11
+ "author": {
12
+ "name": "Sindre Sorhus",
13
+ "email": "sindresorhus@gmail.com",
14
+ "url": "https://sindresorhus.com"
15
+ },
16
+ "type": "module",
17
+ "exports": {
18
+ ".": {
19
+ "types": "./index.d.ts",
20
+ "require": "./dist-cjser/index.cjs",
21
+ "default": "./index.js"
22
+ },
23
+ "./browser": {
24
+ "types": "./filenamify.d.ts",
25
+ "require": "./dist-cjser/browser.cjs",
26
+ "default": "./filenamify.js"
27
+ }
28
+ },
29
+ "sideEffects": false,
30
+ "engines": {
31
+ "node": ">=20"
32
+ },
33
+ "scripts": {
34
+ "test": "xo && ava"
35
+ },
36
+ "files": [
37
+ "filenamify-path.d.ts",
38
+ "filenamify-path.js",
39
+ "filenamify.d.ts",
40
+ "filenamify.js",
41
+ "index.d.ts",
42
+ "index.js",
43
+ "dist-cjser"
44
+ ],
45
+ "keywords": [
46
+ "filename",
47
+ "safe",
48
+ "sanitize",
49
+ "file",
50
+ "name",
51
+ "string",
52
+ "path",
53
+ "filepath",
54
+ "convert",
55
+ "valid",
56
+ "dirname"
57
+ ],
58
+ "dependencies": {
59
+ "@cjser/filename-reserved-regex": "4.0.0-cjser.2"
60
+ },
61
+ "devDependencies": {
62
+ "ava": "^6.4.1",
63
+ "xo": "^1.2.2"
64
+ },
65
+ "types": "./index.d.ts",
66
+ "main": "./dist-cjser/index.cjs",
67
+ "cjser": {
68
+ "sourceVersion": "7.0.1",
69
+ "cjserVersion": 2,
70
+ "original": {
71
+ "name": "filenamify",
72
+ "version": "7.0.1",
73
+ "exports": {
74
+ ".": {
75
+ "types": "./index.d.ts",
76
+ "default": "./index.js"
77
+ },
78
+ "./browser": {
79
+ "types": "./filenamify.d.ts",
80
+ "default": "./filenamify.js"
81
+ }
82
+ },
83
+ "repository": "sindresorhus/filenamify",
84
+ "dependencies": {
85
+ "filename-reserved-regex": "^4.0.0"
86
+ },
87
+ "files": [
88
+ "filenamify-path.d.ts",
89
+ "filenamify-path.js",
90
+ "filenamify.d.ts",
91
+ "filenamify.js",
92
+ "index.d.ts",
93
+ "index.js"
94
+ ],
95
+ "scripts": {
96
+ "test": "xo && ava"
97
+ }
98
+ }
99
+ }
100
+ }
package/readme.md ADDED
@@ -0,0 +1,92 @@
1
+ # filenamify
2
+
3
+ > Convert a string to a valid safe filename
4
+
5
+ On Unix-like systems, `/` is reserved. On Windows, [`<>:"/\|?*`](http://msdn.microsoft.com/en-us/library/aa365247%28VS.85%29#naming_conventions) along with trailing periods and spaces are reserved.
6
+
7
+ This module also removes non-printable control characters (including Unicode bidirectional marks) and normalizes Unicode whitespace.
8
+
9
+ ## Install
10
+
11
+ ```sh
12
+ npm install filenamify
13
+ ```
14
+
15
+ ## Usage
16
+
17
+ ```js
18
+ import filenamify from 'filenamify';
19
+
20
+ filenamify('<foo/bar>');
21
+ //=> '!foo!bar!'
22
+
23
+ filenamify('foo:"bar"', {replacement: '🐴'});
24
+ //=> 'foo🐴bar🐴'
25
+ ```
26
+
27
+ ## API
28
+
29
+ ### filenamify(string, options?)
30
+
31
+ Convert a string to a valid filename.
32
+
33
+ ### filenamifyPath(path, options?)
34
+
35
+ Convert the filename in a path to a valid filename and return the augmented path.
36
+
37
+ ```js
38
+ import {filenamifyPath} from 'filenamify';
39
+
40
+ filenamifyPath('foo:bar');
41
+ //=> 'foo!bar'
42
+ ```
43
+
44
+ #### options
45
+
46
+ Type: `object`
47
+
48
+ ##### replacement
49
+
50
+ Type: `string`\
51
+ Default: `'!'`
52
+
53
+ String to use as replacement for reserved filename characters.
54
+
55
+ Cannot contain: `<` `>` `:` `"` `/` `\` `|` `?` `*` or control characters.
56
+
57
+ ##### maxLength
58
+
59
+ Type: `number`\
60
+ Default: `100`
61
+
62
+ Truncate the filename to the given length.
63
+
64
+ 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
+
66
+ 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
+
68
+ Systems generally allow up to 255 characters, but we default to 100 for usability reasons.
69
+
70
+ ## Browser-only import
71
+
72
+ You can also import `filenamify/browser`, which only imports `filenamify` and not `filenamifyPath`, which relies on `path` being available or polyfilled. Importing `filenamify` this way is therefore useful when it is shipped using `webpack` or similar tools, and if `filenamifyPath` is not needed.
73
+
74
+ ```js
75
+ import filenamify from 'filenamify/browser';
76
+
77
+ filenamify('<foo/bar>');
78
+ //=> '!foo!bar!'
79
+ ```
80
+
81
+ ## Related
82
+
83
+ - [filenamify-cli](https://github.com/sindresorhus/filenamify-cli) - CLI for this module
84
+ - [filenamify-url](https://github.com/sindresorhus/filenamify-url) - Convert a URL to a valid filename
85
+ - [valid-filename](https://github.com/sindresorhus/valid-filename) - Check if a string is a valid filename
86
+ - [unused-filename](https://github.com/sindresorhus/unused-filename) - Get a unused filename by appending a number if it exists
87
+ - [slugify](https://github.com/sindresorhus/slugify) - Slugify a string
88
+
89
+ ## cjser
90
+
91
+ This package is a CommonJS-compatible build generated by cjser for projects that still need `require()` support. The source version matches the original npm package version, with a cjser prerelease suffix for this generated build.
92
+ Original repository: https://github.com/sindresorhus/filenamify