UNPKG

@visulima/fs

Version:

Human friendly file system utilities for Node.js

206 lines (200 loc) 6.38 kB
import { createRequire as __cjs_createRequire } from "node:module"; const __cjs_require = __cjs_createRequire(import.meta.url); const __cjs_getProcess = typeof globalThis !== "undefined" && typeof globalThis.process !== "undefined" ? globalThis.process : process; const { platform } = __cjs_getProcess; const FALLBACK_NAME = "unnamed"; const MAX_LENGTH = 128; const REPLACEMENT_CHARACTERS = { '"': "ˮ", // 0x02EE - MODIFIER LETTER DOUBLE APOSTROPHE "*": "⁎", // 0x204E - LOW ASTERISK "/": "⁄", // 0x2044 - FRACTION SLASH ":": "꞉", // 0xA789 - MODIFIER LETTER COLON "<": "‹", // 0x2039 - SINGLE LEFT-POINTING ANGLE QUOTATION MARK ">": "›", // 0x203A - SINGLE RIGHT-POINTING ANGLE QUOTATION MARK "?": "ʔ", // 0x0294 - LATIN LETTER GLOTTAL STOP "\\": "∖", // 0x2216 - SET MINUS "|": "ǀ" // 0x01C0 - LATIN LETTER DENTAL CLICK }; const CONTROL_CHARS_REGEX = /[\u0000-\u001F\u0080-\u009F]/g; const RELATIVE_PATHS_REGEX = /^(?:\.+[/\\]+)+|^\.+$/g; const WINDOWS_SPECIAL_NAMES_REGEX = /^(?:con|prn|aux|nul|com\d|lpt\d)$/i; const WINDOWS_FORBIDDEN_CHARS_REGEX = /[<>:"/\\|?*]/g; const UNIX_FORBIDDEN_CHARS_REGEX = /\//g; const TRIM_CHARS = /* @__PURE__ */ new Set([" ", " ", "."]); const hasExtension = (name, lastDotIndex) => lastDotIndex > 0 && lastDotIndex < name.length - 1; const truncateWithExtension = (name, maxLength) => { const lastDotIndex = name.lastIndexOf("."); if (!hasExtension(name, lastDotIndex)) { return name.slice(0, maxLength); } const baseName = name.slice(0, lastDotIndex); const extension = name.slice(lastDotIndex); const maxBaseLength = maxLength - extension.length; return maxBaseLength > 0 ? `${baseName.slice(0, maxBaseLength)}${extension}` : name.slice(0, maxLength); }; const trimSpacesAndPeriods = (input) => { let start = 0; let end = input.length; while (start < end) { const char = input[start]; if (char === void 0 || !TRIM_CHARS.has(char)) { break; } start += 1; } while (end > start) { const char = input[end - 1]; if (char === void 0 || !TRIM_CHARS.has(char)) { break; } end -= 1; } return input.slice(start, end); }; const cleanWindowsName = (name) => { const lastDotIndex = name.lastIndexOf("."); if (hasExtension(name, lastDotIndex)) { const baseName = name.slice(0, lastDotIndex); const extension = name.slice(lastDotIndex); let cleanedBaseName = baseName; while (cleanedBaseName.length > 0) { const char = cleanedBaseName[cleanedBaseName.length - 1]; if (char === void 0 || !TRIM_CHARS.has(char)) { break; } cleanedBaseName = cleanedBaseName.slice(0, -1); } if (cleanedBaseName.length === 0) { return extension.slice(1); } return `${cleanedBaseName}${extension}`; } let cleaned = name; while (cleaned.length > 0) { const char = cleaned[cleaned.length - 1]; if (char === void 0 || !TRIM_CHARS.has(char)) { break; } cleaned = cleaned.slice(0, -1); } return cleaned; }; const isWindowsReservedName = (name) => { const lastDotIndex = name.lastIndexOf("."); if (hasExtension(name, lastDotIndex)) { return false; } if (WINDOWS_SPECIAL_NAMES_REGEX.test(name)) { return true; } let trimmed = name; while (trimmed.length > 0) { const char = trimmed[trimmed.length - 1]; if (char === void 0 || !TRIM_CHARS.has(char)) { break; } trimmed = trimmed.slice(0, -1); } if (trimmed.length > 0 && WINDOWS_SPECIAL_NAMES_REGEX.test(trimmed)) { return true; } return false; }; const cleanFat32Name = (name) => { const lastDotIndex = name.lastIndexOf("."); if (hasExtension(name, lastDotIndex)) { const baseName = name.slice(0, lastDotIndex); const extension = name.slice(lastDotIndex); const cleanedBaseName = trimSpacesAndPeriods(baseName); if (cleanedBaseName.length === 0) { return void 0; } return `${cleanedBaseName}${extension}`; } const trimmed = name.trim(); if (trimmed.length > 0 && trimmed[0] === "." && trimmed.length <= 5) { const secondDotIndex = trimmed.indexOf(".", 1); if (secondDotIndex === -1) { return void 0; } } const cleaned = trimSpacesAndPeriods(name); if (cleaned.length === 0) { return void 0; } return cleaned; }; const getFileSystemType = (options) => { const filesystem = options?.filesystem ?? "auto"; if (filesystem === "auto") { return platform === "win32" ? "win32" : "unix"; } if (filesystem === "darwin") { return "unix"; } return filesystem; }; const sanitize = (name, options) => { if (typeof name !== "string" || name.length === 0) { return FALLBACK_NAME; } const fileSystemType = getFileSystemType(options); let sanitized = name; sanitized = sanitized.replace(CONTROL_CHARS_REGEX, ""); if (sanitized.length === 0) { return FALLBACK_NAME; } sanitized = sanitized.replace(RELATIVE_PATHS_REGEX, ""); if (sanitized.length === 0) { return FALLBACK_NAME; } if (fileSystemType === "win32" || fileSystemType === "fat32") { const lastDotIndex = sanitized.lastIndexOf("."); if (!hasExtension(sanitized, lastDotIndex) && WINDOWS_SPECIAL_NAMES_REGEX.test(sanitized)) { return FALLBACK_NAME; } } sanitized = fileSystemType === "win32" || fileSystemType === "fat32" ? sanitized.replaceAll(WINDOWS_FORBIDDEN_CHARS_REGEX, (char) => REPLACEMENT_CHARACTERS[char] ?? "") : sanitized.replaceAll(UNIX_FORBIDDEN_CHARS_REGEX, REPLACEMENT_CHARACTERS["/"]); sanitized = sanitized.trim(); if (fileSystemType === "win32" || fileSystemType === "fat32") { if (isWindowsReservedName(sanitized)) { return FALLBACK_NAME; } if (fileSystemType === "win32") { sanitized = cleanWindowsName(sanitized); if (sanitized.length === 0) { return FALLBACK_NAME; } } } if (fileSystemType === "fat32") { const cleaned = cleanFat32Name(sanitized); if (cleaned === void 0) { return FALLBACK_NAME; } sanitized = cleaned; } if (sanitized.length === 0) { return FALLBACK_NAME; } const maxLength = options?.maxLength ?? MAX_LENGTH; if (sanitized.length > maxLength) { sanitized = truncateWithExtension(sanitized, maxLength); if (sanitized.length === 0) { return FALLBACK_NAME; } } return sanitized; }; export { sanitize };