@sorrell/utilities 1.2.15 → 1.2.16

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 (60) hide show
  1. package/Distribution/PackageExports.Generated.json +10 -0
  2. package/Distribution/Types/FileSystem/FileSystem.d.cts +2 -0
  3. package/Distribution/Types/FileSystem/FileSystem.d.cts.map +1 -1
  4. package/Distribution/Types/FileSystem/FileSystem.d.mts +2 -0
  5. package/Distribution/Types/FileSystem/FileSystem.d.mts.map +1 -1
  6. package/Distribution/Types/FileSystem/FileSystem.d.ts +2 -0
  7. package/Distribution/Types/FileSystem/FileSystem.d.ts.map +1 -1
  8. package/Distribution/Types/FileSystem/Module/Module.Internal.d.cts +7 -0
  9. package/Distribution/Types/FileSystem/Module/Module.Internal.d.cts.map +10 -0
  10. package/Distribution/Types/FileSystem/Module/Module.Internal.d.mts +7 -0
  11. package/Distribution/Types/FileSystem/Module/Module.Internal.d.mts.map +10 -0
  12. package/Distribution/Types/FileSystem/Module/Module.Internal.d.ts +7 -0
  13. package/Distribution/Types/FileSystem/Module/Module.Internal.d.ts.map +1 -0
  14. package/Distribution/Types/FileSystem/Module/Module.Types.d.cts +14 -0
  15. package/Distribution/Types/FileSystem/Module/Module.Types.d.cts.map +10 -0
  16. package/Distribution/Types/FileSystem/Module/Module.Types.d.mts +14 -0
  17. package/Distribution/Types/FileSystem/Module/Module.Types.d.mts.map +10 -0
  18. package/Distribution/Types/FileSystem/Module/Module.Types.d.ts +14 -0
  19. package/Distribution/Types/FileSystem/Module/Module.Types.d.ts.map +1 -0
  20. package/Distribution/Types/FileSystem/Module/Module.d.cts +15 -0
  21. package/Distribution/Types/FileSystem/Module/Module.d.cts.map +10 -0
  22. package/Distribution/Types/FileSystem/Module/Module.d.mts +15 -0
  23. package/Distribution/Types/FileSystem/Module/Module.d.mts.map +10 -0
  24. package/Distribution/Types/FileSystem/Module/Module.d.ts +15 -0
  25. package/Distribution/Types/FileSystem/Module/Module.d.ts.map +1 -0
  26. package/Distribution/Types/FileSystem/Module/index.d.cts +9 -0
  27. package/Distribution/Types/FileSystem/Module/index.d.cts.map +10 -0
  28. package/Distribution/Types/FileSystem/Module/index.d.mts +9 -0
  29. package/Distribution/Types/FileSystem/Module/index.d.mts.map +10 -0
  30. package/Distribution/Types/FileSystem/Module/index.d.ts +9 -0
  31. package/Distribution/Types/FileSystem/Module/index.d.ts.map +1 -0
  32. package/Distribution/Types/FileSystem/index.d.cts +1 -0
  33. package/Distribution/Types/FileSystem/index.d.cts.map +1 -1
  34. package/Distribution/Types/FileSystem/index.d.mts +1 -0
  35. package/Distribution/Types/FileSystem/index.d.mts.map +1 -1
  36. package/Distribution/Types/FileSystem/index.d.ts +1 -0
  37. package/Distribution/Types/FileSystem/index.d.ts.map +1 -1
  38. package/Distribution/Types/String/String.d.cts +1 -0
  39. package/Distribution/Types/String/String.d.cts.map +1 -1
  40. package/Distribution/Types/String/String.d.mts +1 -0
  41. package/Distribution/Types/String/String.d.mts.map +1 -1
  42. package/Distribution/Types/String/String.d.ts +1 -0
  43. package/Distribution/Types/String/String.d.ts.map +1 -1
  44. package/Distribution/fs-module.cjs +161 -0
  45. package/Distribution/fs-module.cjs.map +7 -0
  46. package/Distribution/fs-module.js +128 -0
  47. package/Distribution/fs-module.js.map +7 -0
  48. package/Distribution/fs.cjs +116 -2
  49. package/Distribution/fs.cjs.map +4 -4
  50. package/Distribution/fs.js +125 -5
  51. package/Distribution/fs.js.map +4 -4
  52. package/Distribution/index.cjs +144 -37
  53. package/Distribution/index.cjs.map +4 -4
  54. package/Distribution/index.js +147 -40
  55. package/Distribution/index.js.map +4 -4
  56. package/Distribution/string.cjs +4 -0
  57. package/Distribution/string.cjs.map +2 -2
  58. package/Distribution/string.js +4 -0
  59. package/Distribution/string.js.map +2 -2
  60. package/package.json +11 -1
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../Source/FileSystem/Module/index.ts", "../Source/FileSystem/FileSystem.ts", "../Source/String/String.ts", "../Source/FileSystem/Module/Module.ts"],
4
+ "sourcesContent": ["/**\n * @file index.ts\n * @author Gage Sorrell <gage@sorrell.sh>\n * @copyright (c) 2026 Gage Sorrell\n * @license MIT\n */\nexport * from \"./Module.ts\";\nexport * from \"./Module.Types.ts\";\n", "/**\n * @file FileSystem.ts\n * @author Gage Sorrell <gage@sorrell.sh>\n * @copyright (c) 2026 Gage Sorrell\n * @license MIT\n */\nimport * as FileSystem from \"node:fs/promises\";\nimport * as Path from \"node:path\";\nimport { basename, dirname, extname, join } from \"path\";\nimport { type FFileExtension } from \"./FileSystem.Types.ts\";\nimport { type FileHandle } from \"fs/promises\";\nimport { promises as Fs } from \"fs\";\nimport { constants as FsConstants } from \"fs\";\nimport os from \"os\";\nasync function PathExists(Path: string): Promise<boolean> {\n try {\n await Fs.access(Path, FsConstants.F_OK);\n return true;\n }\n catch {\n return false;\n }\n}\n/**\n * @param Extension - The file extension that you wish to test.\n * @returns Whether the file extension is valid on the current platform.\n */\nexport function IsSupportedFileExtension(Extension: FFileExtension): boolean {\n let NormalizedExtension: string = Extension.slice(1);\n if (NormalizedExtension.startsWith(\".\")) {\n NormalizedExtension = NormalizedExtension.slice(1);\n }\n if (NormalizedExtension.length === 0) {\n return false;\n }\n if (NormalizedExtension.includes(\"/\")\n || NormalizedExtension.includes(\"\\\\\")\n || NormalizedExtension.includes(\"\\0\")) {\n return false;\n }\n if (os.platform() === \"win32\") {\n /* eslint-disable-next-line no-control-regex */\n const HasIllegalCharacters: boolean = /[<>:\"/\\\\|?*\\x00-\\x1F]/u.test(NormalizedExtension);\n if (HasIllegalCharacters) {\n return false;\n }\n if (/[ .]$/u.test(NormalizedExtension)) {\n return false;\n }\n }\n return true;\n}\n/**\n * Given a path at which we wish to create a new file, check if a file at that\n * path already exists, and if so, then append `(${ number })` before the file\n * extension, consistent with the Windows Explorer handles conflicting file\n * names when pasting files.\n */\nexport async function GetSafeNewPath(InPath: string): Promise<string> {\n const DirectoryPath: string = dirname(InPath);\n const Extension: string = extname(InPath);\n const FileName: string = basename(InPath);\n const BaseFileName: string = Extension === \"\"\n ? FileName\n : basename(InPath, Extension);\n let CandidatePath: string = InPath;\n let Index: number = 1;\n while (await PathExists(CandidatePath)) {\n const CandidateFileName: string = `${BaseFileName} (${Index})${Extension}`;\n CandidatePath = join(DirectoryPath, CandidateFileName);\n Index++;\n }\n return CandidatePath;\n}\n/* eslint-disable-next-line @typescript-eslint/typedef */\nexport const ReservedWindowsFileNames = [\n \"CON\",\n \"PRN\",\n \"AUX\",\n \"NUL\",\n \"COM1\",\n \"COM2\",\n \"COM3\",\n \"COM4\",\n \"COM5\",\n \"COM6\",\n \"COM7\",\n \"COM8\",\n \"COM9\",\n \"LPT1\",\n \"LPT2\",\n \"LPT3\",\n \"LPT4\",\n \"LPT5\",\n \"LPT6\",\n \"LPT7\",\n \"LPT8\",\n \"LPT9\",\n] as const;\nexport const InvalidCharacters: RegExp = /[<>:\"/\\\\|?*\\u0000-\\u001F]/u;\n/**\n * @param DirectoryPath - The path to the directory in which you wish to check.\n * @param FileName - The desired file name.\n * @param PersistNewFile - *(Optional)* Whether to keep the otherwise-temporary\n * file created at the desired path.\n * @param Extension - *(Optional)* If provided, the function will only return `true`\n * if `FileName.endsWith(Extension)` *and* the `Extension` is a valid\n * file extension.\n * @returns Whether a file of the given `FileName` can be created in `DirectoryPath`.\n *\n * @remarks This *does* attempt to create a file at the desired path. The file is\n * temporary iff `!PersistNewFile`, and is never created when this function\n * returns `false`.\n */\nexport async function IsValidFileName(DirectoryPath: string, FileName: string, PersistNewFile: boolean = false, Extension: FFileExtension | undefined = undefined): Promise<boolean> {\n const ExtensionSafe: FFileExtension | null | \"\" = Extension !== undefined\n ? (Extension.startsWith(\".\") && IsSupportedFileExtension(Extension))\n ? Extension\n : null\n : \"\";\n const IsExtensionImproper: boolean = (ExtensionSafe === null ||\n ExtensionSafe === \".\" ||\n !FileName.endsWith(ExtensionSafe));\n if (IsExtensionImproper) {\n return false;\n }\n const FilePath: string = join(DirectoryPath, FileName);\n try {\n const ThisFileHandle: FileHandle = await Fs.open(FilePath, \"wx\");\n await ThisFileHandle.close();\n if (!PersistNewFile) {\n await Fs.unlink(FilePath);\n }\n return true;\n }\n catch {\n return false;\n }\n}\n/**\n * Write a text file to a given {@link Path} having contents {@link Contents}.\n *\n * @param Path - The path of the file that will be written.\n * @param Contents - The text contents of the file to write.\n *\n * @returns {Promise<void>} A {@link Promise} that resolves when the call to {@link Fs.writeFile} resolves.\n *\n * @example\n * ```typescript\n * import { WriteTextFile } from \"@sorrell/utilities/fs\";\n * import { resolve } from \"path\";\n *\n * const MyReadMe: string = \"# ReadMe\\n\\nThis package accomplishes...\\n\";\n * const MyReadMePath: string = resolve(\".\");\n *\n * await WriteTextFile(MyReadMePath, MyReadMe);\n * ```\n */\nexport async function WriteTextFile(Path: string, Contents: string): Promise<void> {\n await Fs.writeFile(Path, Contents, { encoding: \"utf-8\" });\n}\ntype DeletePhase = \"Scanning\" | \"Deleting\" | \"Done\";\ntype DeleteEntryKind = \"File\" | \"Directory\" | \"Other\";\ntype DeleteEntry = {\n EntryPath: string;\n Kind: DeleteEntryKind;\n Size: number;\n};\ntype DeleteProgress = {\n Phase: DeletePhase;\n CurrentPath: string | null;\n DiscoveredEntries: number;\n TotalEntries: number;\n DeletedEntries: number;\n TotalBytes: number;\n DeletedBytes: number;\n};\ntype DeleteWithProgressOptions = {\n OnProgress?: (Progress: DeleteProgress) => void;\n Signal?: AbortSignal;\n};\nfunction IsMissingFileError(ErrorValue: unknown): boolean {\n return (typeof ErrorValue === \"object\" &&\n ErrorValue !== null &&\n \"code\" in ErrorValue &&\n ErrorValue.code === \"ENOENT\");\n}\nfunction CloneProgress(Progress: DeleteProgress): DeleteProgress {\n return { ...Progress };\n}\nasync function BuildDeletionPlan(RootPath: string, Progress: DeleteProgress, Options: DeleteWithProgressOptions): Promise<DeleteEntry[]> {\n const Entries: DeleteEntry[] = [];\n async function Visit(CurrentPath: string): Promise<void> {\n Options.Signal?.throwIfAborted();\n Progress.Phase = \"Scanning\";\n Progress.CurrentPath = CurrentPath;\n Options.OnProgress?.(CloneProgress(Progress));\n let Stats;\n try {\n Stats = await FileSystem.lstat(CurrentPath);\n }\n catch (ErrorValue) {\n if (IsMissingFileError(ErrorValue)) {\n return;\n }\n throw ErrorValue;\n }\n if (Stats.isDirectory()) {\n const Children = await FileSystem.readdir(CurrentPath, {\n withFileTypes: true\n });\n for (const Child of Children) {\n await Visit(Path.join(CurrentPath, Child.name));\n }\n Entries.push({\n EntryPath: CurrentPath,\n Kind: \"Directory\",\n Size: 0\n });\n }\n else {\n const Size = Stats.isFile() ? Stats.size : 0;\n Entries.push({\n EntryPath: CurrentPath,\n Kind: Stats.isFile() ? \"File\" : \"Other\",\n Size\n });\n Progress.TotalBytes += Size;\n }\n Progress.DiscoveredEntries = Entries.length;\n Options.OnProgress?.(CloneProgress(Progress));\n }\n await Visit(RootPath);\n return Entries;\n}\nexport async function DeleteWithProgress(RootPath: string, Options: DeleteWithProgressOptions = {}): Promise<void> {\n const Progress: DeleteProgress = {\n Phase: \"Scanning\",\n CurrentPath: null,\n DiscoveredEntries: 0,\n TotalEntries: 0,\n DeletedEntries: 0,\n TotalBytes: 0,\n DeletedBytes: 0\n };\n const Entries = await BuildDeletionPlan(RootPath, Progress, Options);\n Progress.Phase = \"Deleting\";\n Progress.TotalEntries = Entries.length;\n Progress.CurrentPath = null;\n Options.OnProgress?.(CloneProgress(Progress));\n for (const Entry of Entries) {\n Options.Signal?.throwIfAborted();\n Progress.CurrentPath = Entry.EntryPath;\n Options.OnProgress?.(CloneProgress(Progress));\n try {\n if (Entry.Kind === \"Directory\") {\n await FileSystem.rmdir(Entry.EntryPath);\n }\n else {\n await FileSystem.unlink(Entry.EntryPath);\n }\n }\n catch (ErrorValue) {\n if (!IsMissingFileError(ErrorValue)) {\n throw ErrorValue;\n }\n }\n Progress.DeletedEntries += 1;\n Progress.DeletedBytes += Entry.Size;\n Options.OnProgress?.(CloneProgress(Progress));\n }\n Progress.Phase = \"Done\";\n Progress.CurrentPath = null;\n Options.OnProgress?.(CloneProgress(Progress));\n}\nfunction FormatBytes(Bytes: number): string {\n const Units = [\"B\", \"KB\", \"MB\", \"GB\", \"TB\"];\n let Value = Bytes;\n let UnitIndex = 0;\n while (Value >= 1024 && UnitIndex < Units.length - 1) {\n Value /= 1024;\n UnitIndex += 1;\n }\n return `${Value.toFixed(UnitIndex === 0 ? 0 : 1)} ${Units[UnitIndex]}`;\n}\n", "/**\n * @file String.ts\n * @author Gage Sorrell <gage@sorrell.sh>\n * @copyright (c) 2026 Gage Sorrell\n * @license MIT\n */\n/**\n * @module String\n * Functions for manipulating strings.\n */\n// @TODO TEMPORARY.\n/* eslint-disable jsdoc/require-example */\n/**\n * Does the given {@link TestString} contain only letters?\n * This is equivalent to Python's\n * {@link https://docs.python.org/3/library/stdtypes.html#str.isalpha | isalpha} function.\n *\n * @remarks This is an alias for {@link IsAlpha}.\n *\n * @param TestString - The string that you wish to test.\n *\n * @returns {boolean} Whether the given string contains *only* (Latin alphabet) letters.\n */\nexport function IsLetters(TestString: string): boolean {\n return /^[a-zA-Z]*$/.test(TestString);\n}\n/**\n * Does the given {@link TestString} contain only letters?\n * This is equivalent to Python's\n * {@link https://docs.python.org/3/library/stdtypes.html#str.isalpha | isalpha} function.\n *\n * @param TestString - The string that you wish to test.\n *\n * @returns {boolean} Whether the given string contains *only* (Latin alphabet) letters.\n */\nexport function IsAlpha(TestString: string): boolean {\n return IsLetters(TestString);\n}\n/**\n * Does the given {@link TestString} contain only digits?\n *\n * This is equivalent to Python's\n * {@link https://docs.python.org/3/library/stdtypes.html#str.isnumeric | isnumeric} function.\n *\n * @param TestString - The string that you wish to test.\n *\n * @returns {boolean} Whether the given string contains *only* numeric digits.\n */\nexport function IsNumeric(TestString: string): boolean {\n return /^\\d+$/.test(TestString);\n}\n/**\n * This function provides a convenient way to define long strings across multiple lines.\n * The linebreaks and leading indentation are removed from the final string.\n * Please see the example below for how this is to be done.\n *\n * @param Content - The given string to dedent.\n * @param IndentLength - The number of spaces for which this function will check. This\n * is necessary only if the number of spaces used in the {@link Content} is not a multiple\n * of two.\n *\n * @returns {string} The given {@link Content}, but with the spaces at the beginning\n * of each line (excluding the first line if no spaces exist in the first line) removed.\n *\n * @example\n * ```typescript\n * import { Dedent } from \"@sorrell/utilities/string\";\n *\n * const MyLongString: string = Dedent(`Lorem ipsum dolor sit amet, consectetur adipiscing elit,\n * sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad\n * minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea\n * commodo consequat.`);\n *\n * // `MyLongString` <- `\"Lorem ipsum dolor sit amet, consectetur adipiscing elit, \\\n * sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad \\\n * minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea \\\n * commodo consequat.\"`\n * ```\n */\nexport function Dedent(Content: string, IndentLength?: number): string {\n const WhitespaceSubstring: string = ((): string => {\n if (IndentLength !== undefined) {\n return \"\\n\" + \" \".repeat(IndentLength);\n }\n let WhitespaceSubstring: string = \"\\n\";\n while (true) {\n const TestString: string = WhitespaceSubstring + \" \";\n if (Content.includes(TestString)) {\n WhitespaceSubstring = TestString;\n }\n else {\n break;\n }\n }\n return WhitespaceSubstring;\n })();\n return Content.replaceAll(WhitespaceSubstring, \"\");\n}\nexport function GetUtf8ByteLength(Text: string): number {\n return new TextEncoder().encode(Text).length;\n}\n", "/**\n * @file Module.ts\n * @author Gage Sorrell <gage@sorrell.sh>\n * @copyright (c) 2026 Gage Sorrell\n * @license MIT\n */\nimport { InvalidCharacters, ReservedWindowsFileNames } from \"../FileSystem.ts\";\nimport type { ExtensionPolicy } from \"./Module.Types.ts\";\nimport { GetUtf8ByteLength } from \"../../String/String.ts\";\n/* eslint-disable @typescript-eslint/typedef */\nexport namespace ValidExtensions {\n export /**\n * The valid file extensions for TypeScript *source* modules.\n */ const Source = [\n \".ts\",\n \".tsx\",\n \".mts\",\n \".cts\"\n ] as const;\n export /**\n * The valid file extensions for TypeScript *declaration* modules.\n */ const Declaration = [\n \".d.ts\",\n \".d.mts\",\n \".d.cts\"\n ] as const;\n export /**\n * The valid file extensions for *any* TypeScript module.\n */ const Any = [\n \".ts\",\n \".tsx\",\n \".mts\",\n \".cts\",\n \".d.ts\",\n \".d.mts\",\n \".d.cts\"\n ] as const;\n}\n/* eslint-enable @typescript-eslint/typedef */\nexport function HasTypeScriptExtension(FileName: string): boolean {\n return /\\.(ts|tsx|mts|cts|d\\.ts|d\\.mts|d\\.cts)$/iu.test(FileName);\n}\nexport function IsValidFileName(FileName: string, ExtensionPolicy: ExtensionPolicy = \"Disallow\"): boolean {\n if (FileName.length === 0) {\n return false;\n }\n if (FileName === \".\" || FileName === \"..\") {\n return false;\n }\n const DoesExtensionSatisfyPolicy: boolean = ((ExtensionPolicy === \"Disallow\" && !HasTypeScriptExtension(FileName)) ||\n (ExtensionPolicy === \"Require\" && HasTypeScriptExtension(FileName)) ||\n (Array.isArray(ExtensionPolicy) && ExtensionPolicy.some(FileName.endsWith)));\n if (!DoesExtensionSatisfyPolicy) {\n return false;\n }\n if (FileName.endsWith(\".\") || FileName.endsWith(\" \")) {\n return false;\n }\n if (GetUtf8ByteLength(FileName) > 255) {\n return false;\n }\n if (InvalidCharacters.test(FileName)) {\n return false;\n }\n const FileNameWithoutDots: string | undefined = FileName.split(\".\")[0]?.toUpperCase();\n const FileNameContainsWindowsReserved: boolean = (FileNameWithoutDots !== undefined &&\n (ReservedWindowsFileNames as ReadonlyArray<string>).includes(FileNameWithoutDots));\n if (FileNameContainsWindowsReserved) {\n return false;\n }\n return true;\n}\n"],
5
+ "mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACMA,iBAA4B;AAC5B,WAAsB;AACtB,kBAAiD;AAGjD,gBAA+B;AAC/B,IAAAA,aAAyC;AACzC,gBAAe;AA8DR,IAAM,2BAA2B;AAAA,EACpC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACJ;AACO,IAAM,oBAA4B;;;ACDlC,SAAS,kBAAkB,MAAsB;AACpD,SAAO,IAAI,YAAY,EAAE,OAAO,IAAI,EAAE;AAC1C;;;AC1FO,IAAU;AAAA,CAAV,CAAUC,qBAAV;AAGO,EAAMA,iBAAA,SAAS;AAAA,IACrB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACJ;AAGU,EAAMA,iBAAA,cAAc;AAAA,IAC1B;AAAA,IACA;AAAA,IACA;AAAA,EACJ;AAGU,EAAMA,iBAAA,MAAM;AAAA,IAClB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACJ;AAAA,GA1Ba;AA6BV,SAAS,uBAAuB,UAA2B;AAC9D,SAAO,4CAA4C,KAAK,QAAQ;AACpE;AACO,SAAS,gBAAgB,UAAkB,kBAAmC,YAAqB;AACtG,MAAI,SAAS,WAAW,GAAG;AACvB,WAAO;AAAA,EACX;AACA,MAAI,aAAa,OAAO,aAAa,MAAM;AACvC,WAAO;AAAA,EACX;AACA,QAAM,6BAAwC,oBAAoB,cAAc,CAAC,uBAAuB,QAAQ,KAC3G,oBAAoB,aAAa,uBAAuB,QAAQ,KAChE,MAAM,QAAQ,eAAe,KAAK,gBAAgB,KAAK,SAAS,QAAQ;AAC7E,MAAI,CAAC,4BAA4B;AAC7B,WAAO;AAAA,EACX;AACA,MAAI,SAAS,SAAS,GAAG,KAAK,SAAS,SAAS,GAAG,GAAG;AAClD,WAAO;AAAA,EACX;AACA,MAAI,kBAAkB,QAAQ,IAAI,KAAK;AACnC,WAAO;AAAA,EACX;AACA,MAAI,kBAAkB,KAAK,QAAQ,GAAG;AAClC,WAAO;AAAA,EACX;AACA,QAAM,sBAA0C,SAAS,MAAM,GAAG,EAAE,CAAC,GAAG,YAAY;AACpF,QAAM,kCAA4C,wBAAwB,UACrE,yBAAmD,SAAS,mBAAmB;AACpF,MAAI,iCAAiC;AACjC,WAAO;AAAA,EACX;AACA,SAAO;AACX;",
6
+ "names": ["import_fs", "ValidExtensions"]
7
+ }
@@ -0,0 +1,128 @@
1
+ // Source/FileSystem/FileSystem.ts
2
+ import * as FileSystem from "node:fs/promises";
3
+ import * as Path from "node:path";
4
+ import { basename, dirname, extname, join as join2 } from "path";
5
+ import { promises as Fs } from "fs";
6
+ import { constants as FsConstants } from "fs";
7
+ import os from "os";
8
+ var ReservedWindowsFileNames = [
9
+ "CON",
10
+ "PRN",
11
+ "AUX",
12
+ "NUL",
13
+ "COM1",
14
+ "COM2",
15
+ "COM3",
16
+ "COM4",
17
+ "COM5",
18
+ "COM6",
19
+ "COM7",
20
+ "COM8",
21
+ "COM9",
22
+ "LPT1",
23
+ "LPT2",
24
+ "LPT3",
25
+ "LPT4",
26
+ "LPT5",
27
+ "LPT6",
28
+ "LPT7",
29
+ "LPT8",
30
+ "LPT9"
31
+ ];
32
+ var InvalidCharacters = /[<>:"/\\|?*\u0000-\u001F]/u;
33
+
34
+ // Source/String/String.ts
35
+ function GetUtf8ByteLength(Text) {
36
+ return new TextEncoder().encode(Text).length;
37
+ }
38
+
39
+ // Source/FileSystem/Module/Module.ts
40
+ var ValidExtensions;
41
+ ((ValidExtensions2) => {
42
+ ValidExtensions2.Source = [
43
+ ".ts",
44
+ ".tsx",
45
+ ".mts",
46
+ ".cts"
47
+ ];
48
+ ValidExtensions2.Declaration = [
49
+ ".d.ts",
50
+ ".d.mts",
51
+ ".d.cts"
52
+ ];
53
+ ValidExtensions2.Any = [
54
+ ".ts",
55
+ ".tsx",
56
+ ".mts",
57
+ ".cts",
58
+ ".d.ts",
59
+ ".d.mts",
60
+ ".d.cts"
61
+ ];
62
+ })(ValidExtensions || (ValidExtensions = {}));
63
+ function HasTypeScriptExtension(FileName) {
64
+ return /\.(ts|tsx|mts|cts|d\.ts|d\.mts|d\.cts)$/iu.test(FileName);
65
+ }
66
+ function IsValidFileName(FileName, ExtensionPolicy = "Disallow") {
67
+ if (FileName.length === 0) {
68
+ return false;
69
+ }
70
+ if (FileName === "." || FileName === "..") {
71
+ return false;
72
+ }
73
+ const DoesExtensionSatisfyPolicy = ExtensionPolicy === "Disallow" && !HasTypeScriptExtension(FileName) || ExtensionPolicy === "Require" && HasTypeScriptExtension(FileName) || Array.isArray(ExtensionPolicy) && ExtensionPolicy.some(FileName.endsWith);
74
+ if (!DoesExtensionSatisfyPolicy) {
75
+ return false;
76
+ }
77
+ if (FileName.endsWith(".") || FileName.endsWith(" ")) {
78
+ return false;
79
+ }
80
+ if (GetUtf8ByteLength(FileName) > 255) {
81
+ return false;
82
+ }
83
+ if (InvalidCharacters.test(FileName)) {
84
+ return false;
85
+ }
86
+ const FileNameWithoutDots = FileName.split(".")[0]?.toUpperCase();
87
+ const FileNameContainsWindowsReserved = FileNameWithoutDots !== void 0 && ReservedWindowsFileNames.includes(FileNameWithoutDots);
88
+ if (FileNameContainsWindowsReserved) {
89
+ return false;
90
+ }
91
+ return true;
92
+ }
93
+ export {
94
+ HasTypeScriptExtension,
95
+ IsValidFileName,
96
+ ValidExtensions
97
+ };
98
+ /**
99
+ * @file FileSystem.ts
100
+ * @author Gage Sorrell <gage@sorrell.sh>
101
+ * @copyright (c) 2026 Gage Sorrell
102
+ * @license MIT
103
+ */
104
+ /**
105
+ * @file String.ts
106
+ * @author Gage Sorrell <gage@sorrell.sh>
107
+ * @copyright (c) 2026 Gage Sorrell
108
+ * @license MIT
109
+ */
110
+ /**
111
+ * @file Module.ts
112
+ * @author Gage Sorrell <gage@sorrell.sh>
113
+ * @copyright (c) 2026 Gage Sorrell
114
+ * @license MIT
115
+ */
116
+ /**
117
+ * @file Module.Types.ts
118
+ * @author Gage Sorrell <gage@sorrell.sh>
119
+ * @copyright (c) 2026 Gage Sorrell
120
+ * @license MIT
121
+ */
122
+ /**
123
+ * @file index.ts
124
+ * @author Gage Sorrell <gage@sorrell.sh>
125
+ * @copyright (c) 2026 Gage Sorrell
126
+ * @license MIT
127
+ */
128
+ //# sourceMappingURL=fs-module.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../Source/FileSystem/FileSystem.ts", "../Source/String/String.ts", "../Source/FileSystem/Module/Module.ts"],
4
+ "sourcesContent": ["/**\n * @file FileSystem.ts\n * @author Gage Sorrell <gage@sorrell.sh>\n * @copyright (c) 2026 Gage Sorrell\n * @license MIT\n */\nimport * as FileSystem from \"node:fs/promises\";\nimport * as Path from \"node:path\";\nimport { basename, dirname, extname, join } from \"path\";\nimport { type FFileExtension } from \"./FileSystem.Types.ts\";\nimport { type FileHandle } from \"fs/promises\";\nimport { promises as Fs } from \"fs\";\nimport { constants as FsConstants } from \"fs\";\nimport os from \"os\";\nasync function PathExists(Path: string): Promise<boolean> {\n try {\n await Fs.access(Path, FsConstants.F_OK);\n return true;\n }\n catch {\n return false;\n }\n}\n/**\n * @param Extension - The file extension that you wish to test.\n * @returns Whether the file extension is valid on the current platform.\n */\nexport function IsSupportedFileExtension(Extension: FFileExtension): boolean {\n let NormalizedExtension: string = Extension.slice(1);\n if (NormalizedExtension.startsWith(\".\")) {\n NormalizedExtension = NormalizedExtension.slice(1);\n }\n if (NormalizedExtension.length === 0) {\n return false;\n }\n if (NormalizedExtension.includes(\"/\")\n || NormalizedExtension.includes(\"\\\\\")\n || NormalizedExtension.includes(\"\\0\")) {\n return false;\n }\n if (os.platform() === \"win32\") {\n /* eslint-disable-next-line no-control-regex */\n const HasIllegalCharacters: boolean = /[<>:\"/\\\\|?*\\x00-\\x1F]/u.test(NormalizedExtension);\n if (HasIllegalCharacters) {\n return false;\n }\n if (/[ .]$/u.test(NormalizedExtension)) {\n return false;\n }\n }\n return true;\n}\n/**\n * Given a path at which we wish to create a new file, check if a file at that\n * path already exists, and if so, then append `(${ number })` before the file\n * extension, consistent with the Windows Explorer handles conflicting file\n * names when pasting files.\n */\nexport async function GetSafeNewPath(InPath: string): Promise<string> {\n const DirectoryPath: string = dirname(InPath);\n const Extension: string = extname(InPath);\n const FileName: string = basename(InPath);\n const BaseFileName: string = Extension === \"\"\n ? FileName\n : basename(InPath, Extension);\n let CandidatePath: string = InPath;\n let Index: number = 1;\n while (await PathExists(CandidatePath)) {\n const CandidateFileName: string = `${BaseFileName} (${Index})${Extension}`;\n CandidatePath = join(DirectoryPath, CandidateFileName);\n Index++;\n }\n return CandidatePath;\n}\n/* eslint-disable-next-line @typescript-eslint/typedef */\nexport const ReservedWindowsFileNames = [\n \"CON\",\n \"PRN\",\n \"AUX\",\n \"NUL\",\n \"COM1\",\n \"COM2\",\n \"COM3\",\n \"COM4\",\n \"COM5\",\n \"COM6\",\n \"COM7\",\n \"COM8\",\n \"COM9\",\n \"LPT1\",\n \"LPT2\",\n \"LPT3\",\n \"LPT4\",\n \"LPT5\",\n \"LPT6\",\n \"LPT7\",\n \"LPT8\",\n \"LPT9\",\n] as const;\nexport const InvalidCharacters: RegExp = /[<>:\"/\\\\|?*\\u0000-\\u001F]/u;\n/**\n * @param DirectoryPath - The path to the directory in which you wish to check.\n * @param FileName - The desired file name.\n * @param PersistNewFile - *(Optional)* Whether to keep the otherwise-temporary\n * file created at the desired path.\n * @param Extension - *(Optional)* If provided, the function will only return `true`\n * if `FileName.endsWith(Extension)` *and* the `Extension` is a valid\n * file extension.\n * @returns Whether a file of the given `FileName` can be created in `DirectoryPath`.\n *\n * @remarks This *does* attempt to create a file at the desired path. The file is\n * temporary iff `!PersistNewFile`, and is never created when this function\n * returns `false`.\n */\nexport async function IsValidFileName(DirectoryPath: string, FileName: string, PersistNewFile: boolean = false, Extension: FFileExtension | undefined = undefined): Promise<boolean> {\n const ExtensionSafe: FFileExtension | null | \"\" = Extension !== undefined\n ? (Extension.startsWith(\".\") && IsSupportedFileExtension(Extension))\n ? Extension\n : null\n : \"\";\n const IsExtensionImproper: boolean = (ExtensionSafe === null ||\n ExtensionSafe === \".\" ||\n !FileName.endsWith(ExtensionSafe));\n if (IsExtensionImproper) {\n return false;\n }\n const FilePath: string = join(DirectoryPath, FileName);\n try {\n const ThisFileHandle: FileHandle = await Fs.open(FilePath, \"wx\");\n await ThisFileHandle.close();\n if (!PersistNewFile) {\n await Fs.unlink(FilePath);\n }\n return true;\n }\n catch {\n return false;\n }\n}\n/**\n * Write a text file to a given {@link Path} having contents {@link Contents}.\n *\n * @param Path - The path of the file that will be written.\n * @param Contents - The text contents of the file to write.\n *\n * @returns {Promise<void>} A {@link Promise} that resolves when the call to {@link Fs.writeFile} resolves.\n *\n * @example\n * ```typescript\n * import { WriteTextFile } from \"@sorrell/utilities/fs\";\n * import { resolve } from \"path\";\n *\n * const MyReadMe: string = \"# ReadMe\\n\\nThis package accomplishes...\\n\";\n * const MyReadMePath: string = resolve(\".\");\n *\n * await WriteTextFile(MyReadMePath, MyReadMe);\n * ```\n */\nexport async function WriteTextFile(Path: string, Contents: string): Promise<void> {\n await Fs.writeFile(Path, Contents, { encoding: \"utf-8\" });\n}\ntype DeletePhase = \"Scanning\" | \"Deleting\" | \"Done\";\ntype DeleteEntryKind = \"File\" | \"Directory\" | \"Other\";\ntype DeleteEntry = {\n EntryPath: string;\n Kind: DeleteEntryKind;\n Size: number;\n};\ntype DeleteProgress = {\n Phase: DeletePhase;\n CurrentPath: string | null;\n DiscoveredEntries: number;\n TotalEntries: number;\n DeletedEntries: number;\n TotalBytes: number;\n DeletedBytes: number;\n};\ntype DeleteWithProgressOptions = {\n OnProgress?: (Progress: DeleteProgress) => void;\n Signal?: AbortSignal;\n};\nfunction IsMissingFileError(ErrorValue: unknown): boolean {\n return (typeof ErrorValue === \"object\" &&\n ErrorValue !== null &&\n \"code\" in ErrorValue &&\n ErrorValue.code === \"ENOENT\");\n}\nfunction CloneProgress(Progress: DeleteProgress): DeleteProgress {\n return { ...Progress };\n}\nasync function BuildDeletionPlan(RootPath: string, Progress: DeleteProgress, Options: DeleteWithProgressOptions): Promise<DeleteEntry[]> {\n const Entries: DeleteEntry[] = [];\n async function Visit(CurrentPath: string): Promise<void> {\n Options.Signal?.throwIfAborted();\n Progress.Phase = \"Scanning\";\n Progress.CurrentPath = CurrentPath;\n Options.OnProgress?.(CloneProgress(Progress));\n let Stats;\n try {\n Stats = await FileSystem.lstat(CurrentPath);\n }\n catch (ErrorValue) {\n if (IsMissingFileError(ErrorValue)) {\n return;\n }\n throw ErrorValue;\n }\n if (Stats.isDirectory()) {\n const Children = await FileSystem.readdir(CurrentPath, {\n withFileTypes: true\n });\n for (const Child of Children) {\n await Visit(Path.join(CurrentPath, Child.name));\n }\n Entries.push({\n EntryPath: CurrentPath,\n Kind: \"Directory\",\n Size: 0\n });\n }\n else {\n const Size = Stats.isFile() ? Stats.size : 0;\n Entries.push({\n EntryPath: CurrentPath,\n Kind: Stats.isFile() ? \"File\" : \"Other\",\n Size\n });\n Progress.TotalBytes += Size;\n }\n Progress.DiscoveredEntries = Entries.length;\n Options.OnProgress?.(CloneProgress(Progress));\n }\n await Visit(RootPath);\n return Entries;\n}\nexport async function DeleteWithProgress(RootPath: string, Options: DeleteWithProgressOptions = {}): Promise<void> {\n const Progress: DeleteProgress = {\n Phase: \"Scanning\",\n CurrentPath: null,\n DiscoveredEntries: 0,\n TotalEntries: 0,\n DeletedEntries: 0,\n TotalBytes: 0,\n DeletedBytes: 0\n };\n const Entries = await BuildDeletionPlan(RootPath, Progress, Options);\n Progress.Phase = \"Deleting\";\n Progress.TotalEntries = Entries.length;\n Progress.CurrentPath = null;\n Options.OnProgress?.(CloneProgress(Progress));\n for (const Entry of Entries) {\n Options.Signal?.throwIfAborted();\n Progress.CurrentPath = Entry.EntryPath;\n Options.OnProgress?.(CloneProgress(Progress));\n try {\n if (Entry.Kind === \"Directory\") {\n await FileSystem.rmdir(Entry.EntryPath);\n }\n else {\n await FileSystem.unlink(Entry.EntryPath);\n }\n }\n catch (ErrorValue) {\n if (!IsMissingFileError(ErrorValue)) {\n throw ErrorValue;\n }\n }\n Progress.DeletedEntries += 1;\n Progress.DeletedBytes += Entry.Size;\n Options.OnProgress?.(CloneProgress(Progress));\n }\n Progress.Phase = \"Done\";\n Progress.CurrentPath = null;\n Options.OnProgress?.(CloneProgress(Progress));\n}\nfunction FormatBytes(Bytes: number): string {\n const Units = [\"B\", \"KB\", \"MB\", \"GB\", \"TB\"];\n let Value = Bytes;\n let UnitIndex = 0;\n while (Value >= 1024 && UnitIndex < Units.length - 1) {\n Value /= 1024;\n UnitIndex += 1;\n }\n return `${Value.toFixed(UnitIndex === 0 ? 0 : 1)} ${Units[UnitIndex]}`;\n}\n", "/**\n * @file String.ts\n * @author Gage Sorrell <gage@sorrell.sh>\n * @copyright (c) 2026 Gage Sorrell\n * @license MIT\n */\n/**\n * @module String\n * Functions for manipulating strings.\n */\n// @TODO TEMPORARY.\n/* eslint-disable jsdoc/require-example */\n/**\n * Does the given {@link TestString} contain only letters?\n * This is equivalent to Python's\n * {@link https://docs.python.org/3/library/stdtypes.html#str.isalpha | isalpha} function.\n *\n * @remarks This is an alias for {@link IsAlpha}.\n *\n * @param TestString - The string that you wish to test.\n *\n * @returns {boolean} Whether the given string contains *only* (Latin alphabet) letters.\n */\nexport function IsLetters(TestString: string): boolean {\n return /^[a-zA-Z]*$/.test(TestString);\n}\n/**\n * Does the given {@link TestString} contain only letters?\n * This is equivalent to Python's\n * {@link https://docs.python.org/3/library/stdtypes.html#str.isalpha | isalpha} function.\n *\n * @param TestString - The string that you wish to test.\n *\n * @returns {boolean} Whether the given string contains *only* (Latin alphabet) letters.\n */\nexport function IsAlpha(TestString: string): boolean {\n return IsLetters(TestString);\n}\n/**\n * Does the given {@link TestString} contain only digits?\n *\n * This is equivalent to Python's\n * {@link https://docs.python.org/3/library/stdtypes.html#str.isnumeric | isnumeric} function.\n *\n * @param TestString - The string that you wish to test.\n *\n * @returns {boolean} Whether the given string contains *only* numeric digits.\n */\nexport function IsNumeric(TestString: string): boolean {\n return /^\\d+$/.test(TestString);\n}\n/**\n * This function provides a convenient way to define long strings across multiple lines.\n * The linebreaks and leading indentation are removed from the final string.\n * Please see the example below for how this is to be done.\n *\n * @param Content - The given string to dedent.\n * @param IndentLength - The number of spaces for which this function will check. This\n * is necessary only if the number of spaces used in the {@link Content} is not a multiple\n * of two.\n *\n * @returns {string} The given {@link Content}, but with the spaces at the beginning\n * of each line (excluding the first line if no spaces exist in the first line) removed.\n *\n * @example\n * ```typescript\n * import { Dedent } from \"@sorrell/utilities/string\";\n *\n * const MyLongString: string = Dedent(`Lorem ipsum dolor sit amet, consectetur adipiscing elit,\n * sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad\n * minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea\n * commodo consequat.`);\n *\n * // `MyLongString` <- `\"Lorem ipsum dolor sit amet, consectetur adipiscing elit, \\\n * sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad \\\n * minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea \\\n * commodo consequat.\"`\n * ```\n */\nexport function Dedent(Content: string, IndentLength?: number): string {\n const WhitespaceSubstring: string = ((): string => {\n if (IndentLength !== undefined) {\n return \"\\n\" + \" \".repeat(IndentLength);\n }\n let WhitespaceSubstring: string = \"\\n\";\n while (true) {\n const TestString: string = WhitespaceSubstring + \" \";\n if (Content.includes(TestString)) {\n WhitespaceSubstring = TestString;\n }\n else {\n break;\n }\n }\n return WhitespaceSubstring;\n })();\n return Content.replaceAll(WhitespaceSubstring, \"\");\n}\nexport function GetUtf8ByteLength(Text: string): number {\n return new TextEncoder().encode(Text).length;\n}\n", "/**\n * @file Module.ts\n * @author Gage Sorrell <gage@sorrell.sh>\n * @copyright (c) 2026 Gage Sorrell\n * @license MIT\n */\nimport { InvalidCharacters, ReservedWindowsFileNames } from \"../FileSystem.ts\";\nimport type { ExtensionPolicy } from \"./Module.Types.ts\";\nimport { GetUtf8ByteLength } from \"../../String/String.ts\";\n/* eslint-disable @typescript-eslint/typedef */\nexport namespace ValidExtensions {\n export /**\n * The valid file extensions for TypeScript *source* modules.\n */ const Source = [\n \".ts\",\n \".tsx\",\n \".mts\",\n \".cts\"\n ] as const;\n export /**\n * The valid file extensions for TypeScript *declaration* modules.\n */ const Declaration = [\n \".d.ts\",\n \".d.mts\",\n \".d.cts\"\n ] as const;\n export /**\n * The valid file extensions for *any* TypeScript module.\n */ const Any = [\n \".ts\",\n \".tsx\",\n \".mts\",\n \".cts\",\n \".d.ts\",\n \".d.mts\",\n \".d.cts\"\n ] as const;\n}\n/* eslint-enable @typescript-eslint/typedef */\nexport function HasTypeScriptExtension(FileName: string): boolean {\n return /\\.(ts|tsx|mts|cts|d\\.ts|d\\.mts|d\\.cts)$/iu.test(FileName);\n}\nexport function IsValidFileName(FileName: string, ExtensionPolicy: ExtensionPolicy = \"Disallow\"): boolean {\n if (FileName.length === 0) {\n return false;\n }\n if (FileName === \".\" || FileName === \"..\") {\n return false;\n }\n const DoesExtensionSatisfyPolicy: boolean = ((ExtensionPolicy === \"Disallow\" && !HasTypeScriptExtension(FileName)) ||\n (ExtensionPolicy === \"Require\" && HasTypeScriptExtension(FileName)) ||\n (Array.isArray(ExtensionPolicy) && ExtensionPolicy.some(FileName.endsWith)));\n if (!DoesExtensionSatisfyPolicy) {\n return false;\n }\n if (FileName.endsWith(\".\") || FileName.endsWith(\" \")) {\n return false;\n }\n if (GetUtf8ByteLength(FileName) > 255) {\n return false;\n }\n if (InvalidCharacters.test(FileName)) {\n return false;\n }\n const FileNameWithoutDots: string | undefined = FileName.split(\".\")[0]?.toUpperCase();\n const FileNameContainsWindowsReserved: boolean = (FileNameWithoutDots !== undefined &&\n (ReservedWindowsFileNames as ReadonlyArray<string>).includes(FileNameWithoutDots));\n if (FileNameContainsWindowsReserved) {\n return false;\n }\n return true;\n}\n"],
5
+ "mappings": ";AAMA,YAAY,gBAAgB;AAC5B,YAAY,UAAU;AACtB,SAAS,UAAU,SAAS,SAAS,QAAAA,aAAY;AAGjD,SAAS,YAAY,UAAU;AAC/B,SAAS,aAAa,mBAAmB;AACzC,OAAO,QAAQ;AA8DR,IAAM,2BAA2B;AAAA,EACpC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACJ;AACO,IAAM,oBAA4B;;;ACDlC,SAAS,kBAAkB,MAAsB;AACpD,SAAO,IAAI,YAAY,EAAE,OAAO,IAAI,EAAE;AAC1C;;;AC1FO,IAAU;AAAA,CAAV,CAAUC,qBAAV;AAGO,EAAMA,iBAAA,SAAS;AAAA,IACrB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACJ;AAGU,EAAMA,iBAAA,cAAc;AAAA,IAC1B;AAAA,IACA;AAAA,IACA;AAAA,EACJ;AAGU,EAAMA,iBAAA,MAAM;AAAA,IAClB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACJ;AAAA,GA1Ba;AA6BV,SAAS,uBAAuB,UAA2B;AAC9D,SAAO,4CAA4C,KAAK,QAAQ;AACpE;AACO,SAAS,gBAAgB,UAAkB,kBAAmC,YAAqB;AACtG,MAAI,SAAS,WAAW,GAAG;AACvB,WAAO;AAAA,EACX;AACA,MAAI,aAAa,OAAO,aAAa,MAAM;AACvC,WAAO;AAAA,EACX;AACA,QAAM,6BAAwC,oBAAoB,cAAc,CAAC,uBAAuB,QAAQ,KAC3G,oBAAoB,aAAa,uBAAuB,QAAQ,KAChE,MAAM,QAAQ,eAAe,KAAK,gBAAgB,KAAK,SAAS,QAAQ;AAC7E,MAAI,CAAC,4BAA4B;AAC7B,WAAO;AAAA,EACX;AACA,MAAI,SAAS,SAAS,GAAG,KAAK,SAAS,SAAS,GAAG,GAAG;AAClD,WAAO;AAAA,EACX;AACA,MAAI,kBAAkB,QAAQ,IAAI,KAAK;AACnC,WAAO;AAAA,EACX;AACA,MAAI,kBAAkB,KAAK,QAAQ,GAAG;AAClC,WAAO;AAAA,EACX;AACA,QAAM,sBAA0C,SAAS,MAAM,GAAG,EAAE,CAAC,GAAG,YAAY;AACpF,QAAM,kCAA4C,wBAAwB,UACrE,yBAAmD,SAAS,mBAAmB;AACpF,MAAI,iCAAiC;AACjC,WAAO;AAAA,EACX;AACA,SAAO;AACX;",
6
+ "names": ["join", "ValidExtensions"]
7
+ }
@@ -32,19 +32,22 @@ var FileSystem_exports = {};
32
32
  __export(FileSystem_exports, {
33
33
  DeleteWithProgress: () => DeleteWithProgress,
34
34
  GetSafeNewPath: () => GetSafeNewPath,
35
+ InvalidCharacters: () => InvalidCharacters,
35
36
  IsSupportedFileExtension: () => IsSupportedFileExtension,
36
37
  IsValidFileName: () => IsValidFileName,
38
+ Module: () => Module_exports,
39
+ ReservedWindowsFileNames: () => ReservedWindowsFileNames,
37
40
  WriteTextFile: () => WriteTextFile
38
41
  });
39
42
  module.exports = __toCommonJS(FileSystem_exports);
40
43
 
41
44
  // Source/FileSystem/FileSystem.ts
45
+ var FileSystem = __toESM(require("node:fs/promises"), 1);
46
+ var Path = __toESM(require("node:path"), 1);
42
47
  var import_path = require("path");
43
48
  var import_fs = require("fs");
44
49
  var import_fs2 = require("fs");
45
50
  var import_os = __toESM(require("os"), 1);
46
- var FileSystem = __toESM(require("node:fs/promises"), 1);
47
- var Path = __toESM(require("node:path"), 1);
48
51
  async function PathExists(Path2) {
49
52
  try {
50
53
  await import_fs.promises.access(Path2, import_fs2.constants.F_OK);
@@ -89,6 +92,31 @@ async function GetSafeNewPath(InPath) {
89
92
  }
90
93
  return CandidatePath;
91
94
  }
95
+ var ReservedWindowsFileNames = [
96
+ "CON",
97
+ "PRN",
98
+ "AUX",
99
+ "NUL",
100
+ "COM1",
101
+ "COM2",
102
+ "COM3",
103
+ "COM4",
104
+ "COM5",
105
+ "COM6",
106
+ "COM7",
107
+ "COM8",
108
+ "COM9",
109
+ "LPT1",
110
+ "LPT2",
111
+ "LPT3",
112
+ "LPT4",
113
+ "LPT5",
114
+ "LPT6",
115
+ "LPT7",
116
+ "LPT8",
117
+ "LPT9"
118
+ ];
119
+ var InvalidCharacters = /[<>:"/\\|?*\u0000-\u001F]/u;
92
120
  async function IsValidFileName(DirectoryPath, FileName, PersistNewFile = false, Extension = void 0) {
93
121
  const ExtensionSafe = Extension !== void 0 ? Extension.startsWith(".") && IsSupportedFileExtension(Extension) ? Extension : null : "";
94
122
  const IsExtensionImproper = ExtensionSafe === null || ExtensionSafe === "." || !FileName.endsWith(ExtensionSafe);
@@ -197,6 +225,74 @@ async function DeleteWithProgress(RootPath, Options = {}) {
197
225
  Progress.CurrentPath = null;
198
226
  Options.OnProgress?.(CloneProgress(Progress));
199
227
  }
228
+
229
+ // Source/FileSystem/Module/index.ts
230
+ var Module_exports = {};
231
+ __export(Module_exports, {
232
+ HasTypeScriptExtension: () => HasTypeScriptExtension,
233
+ IsValidFileName: () => IsValidFileName2,
234
+ ValidExtensions: () => ValidExtensions
235
+ });
236
+
237
+ // Source/String/String.ts
238
+ function GetUtf8ByteLength(Text) {
239
+ return new TextEncoder().encode(Text).length;
240
+ }
241
+
242
+ // Source/FileSystem/Module/Module.ts
243
+ var ValidExtensions;
244
+ ((ValidExtensions2) => {
245
+ ValidExtensions2.Source = [
246
+ ".ts",
247
+ ".tsx",
248
+ ".mts",
249
+ ".cts"
250
+ ];
251
+ ValidExtensions2.Declaration = [
252
+ ".d.ts",
253
+ ".d.mts",
254
+ ".d.cts"
255
+ ];
256
+ ValidExtensions2.Any = [
257
+ ".ts",
258
+ ".tsx",
259
+ ".mts",
260
+ ".cts",
261
+ ".d.ts",
262
+ ".d.mts",
263
+ ".d.cts"
264
+ ];
265
+ })(ValidExtensions || (ValidExtensions = {}));
266
+ function HasTypeScriptExtension(FileName) {
267
+ return /\.(ts|tsx|mts|cts|d\.ts|d\.mts|d\.cts)$/iu.test(FileName);
268
+ }
269
+ function IsValidFileName2(FileName, ExtensionPolicy = "Disallow") {
270
+ if (FileName.length === 0) {
271
+ return false;
272
+ }
273
+ if (FileName === "." || FileName === "..") {
274
+ return false;
275
+ }
276
+ const DoesExtensionSatisfyPolicy = ExtensionPolicy === "Disallow" && !HasTypeScriptExtension(FileName) || ExtensionPolicy === "Require" && HasTypeScriptExtension(FileName) || Array.isArray(ExtensionPolicy) && ExtensionPolicy.some(FileName.endsWith);
277
+ if (!DoesExtensionSatisfyPolicy) {
278
+ return false;
279
+ }
280
+ if (FileName.endsWith(".") || FileName.endsWith(" ")) {
281
+ return false;
282
+ }
283
+ if (GetUtf8ByteLength(FileName) > 255) {
284
+ return false;
285
+ }
286
+ if (InvalidCharacters.test(FileName)) {
287
+ return false;
288
+ }
289
+ const FileNameWithoutDots = FileName.split(".")[0]?.toUpperCase();
290
+ const FileNameContainsWindowsReserved = FileNameWithoutDots !== void 0 && ReservedWindowsFileNames.includes(FileNameWithoutDots);
291
+ if (FileNameContainsWindowsReserved) {
292
+ return false;
293
+ }
294
+ return true;
295
+ }
200
296
  /**
201
297
  * @file FileSystem.ts
202
298
  * @author Gage Sorrell <gage@sorrell.sh>
@@ -209,6 +305,24 @@ async function DeleteWithProgress(RootPath, Options = {}) {
209
305
  * @copyright (c) 2026 Gage Sorrell
210
306
  * @license MIT
211
307
  */
308
+ /**
309
+ * @file String.ts
310
+ * @author Gage Sorrell <gage@sorrell.sh>
311
+ * @copyright (c) 2026 Gage Sorrell
312
+ * @license MIT
313
+ */
314
+ /**
315
+ * @file Module.ts
316
+ * @author Gage Sorrell <gage@sorrell.sh>
317
+ * @copyright (c) 2026 Gage Sorrell
318
+ * @license MIT
319
+ */
320
+ /**
321
+ * @file Module.Types.ts
322
+ * @author Gage Sorrell <gage@sorrell.sh>
323
+ * @copyright (c) 2026 Gage Sorrell
324
+ * @license MIT
325
+ */
212
326
  /**
213
327
  * @file index.ts
214
328
  * @author Gage Sorrell <gage@sorrell.sh>
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
- "sources": ["../Source/FileSystem/index.ts", "../Source/FileSystem/FileSystem.ts"],
4
- "sourcesContent": ["/**\n * @file index.ts\n * @author Gage Sorrell <gage@sorrell.sh>\n * @copyright (c) 2026 Gage Sorrell\n * @license MIT\n */\nexport * from \"./FileSystem.ts\";\nexport * from \"./FileSystem.Types.ts\";\n", "/**\n * @file FileSystem.ts\n * @author Gage Sorrell <gage@sorrell.sh>\n * @copyright (c) 2026 Gage Sorrell\n * @license MIT\n */\nimport { basename, dirname, extname, join } from \"path\";\nimport { type FFileExtension } from \"./FileSystem.Types.ts\";\nimport { type FileHandle } from \"fs/promises\";\nimport { promises as Fs } from \"fs\";\nimport { constants as FsConstants } from \"fs\";\nimport os from \"os\";\nasync function PathExists(Path: string): Promise<boolean> {\n try {\n await Fs.access(Path, FsConstants.F_OK);\n return true;\n }\n catch {\n return false;\n }\n}\n/**\n * @param Extension - The file extension that you wish to test.\n * @returns Whether the file extension is valid on the current platform.\n */\nexport function IsSupportedFileExtension(Extension: FFileExtension): boolean {\n let NormalizedExtension: string = Extension.slice(1);\n if (NormalizedExtension.startsWith(\".\")) {\n NormalizedExtension = NormalizedExtension.slice(1);\n }\n if (NormalizedExtension.length === 0) {\n return false;\n }\n if (NormalizedExtension.includes(\"/\")\n || NormalizedExtension.includes(\"\\\\\")\n || NormalizedExtension.includes(\"\\0\")) {\n return false;\n }\n if (os.platform() === \"win32\") {\n /* eslint-disable-next-line no-control-regex */\n const HasIllegalCharacters: boolean = /[<>:\"/\\\\|?*\\x00-\\x1F]/u.test(NormalizedExtension);\n if (HasIllegalCharacters) {\n return false;\n }\n if (/[ .]$/u.test(NormalizedExtension)) {\n return false;\n }\n }\n return true;\n}\n/**\n * Given a path at which we wish to create a new file, check if a file at that\n * path already exists, and if so, then append `(${ number })` before the file\n * extension, consistent with the Windows Explorer handles conflicting file\n * names when pasting files.\n */\nexport async function GetSafeNewPath(InPath: string): Promise<string> {\n const DirectoryPath: string = dirname(InPath);\n const Extension: string = extname(InPath);\n const FileName: string = basename(InPath);\n const BaseFileName: string = Extension === \"\"\n ? FileName\n : basename(InPath, Extension);\n let CandidatePath: string = InPath;\n let Index: number = 1;\n while (await PathExists(CandidatePath)) {\n const CandidateFileName: string = `${BaseFileName} (${Index})${Extension}`;\n CandidatePath = join(DirectoryPath, CandidateFileName);\n Index++;\n }\n return CandidatePath;\n}\n/**\n * @param DirectoryPath - The path to the directory in which you wish to check.\n * @param FileName - The desired file name.\n * @param PersistNewFile - *(Optional)* Whether to keep the otherwise-temporary\n * file created at the desired path.\n * @param Extension - *(Optional)* If provided, the function will only return `true`\n * if `FileName.endsWith(Extension)` *and* the `Extension` is a valid\n * file extension.\n * @returns Whether a file of the given `FileName` can be created in `DirectoryPath`.\n *\n * @remarks This *does* attempt to create a file at the desired path. The file is\n * temporary iff `!PersistNewFile`, and is never created when this function\n * returns `false`.\n */\nexport async function IsValidFileName(DirectoryPath: string, FileName: string, PersistNewFile: boolean = false, Extension: FFileExtension | undefined = undefined): Promise<boolean> {\n const ExtensionSafe: FFileExtension | null | \"\" = Extension !== undefined\n ? (Extension.startsWith(\".\") && IsSupportedFileExtension(Extension))\n ? Extension\n : null\n : \"\";\n const IsExtensionImproper: boolean = (ExtensionSafe === null ||\n ExtensionSafe === \".\" ||\n !FileName.endsWith(ExtensionSafe));\n if (IsExtensionImproper) {\n return false;\n }\n const FilePath: string = join(DirectoryPath, FileName);\n try {\n const ThisFileHandle: FileHandle = await Fs.open(FilePath, \"wx\");\n await ThisFileHandle.close();\n if (!PersistNewFile) {\n await Fs.unlink(FilePath);\n }\n return true;\n }\n catch {\n return false;\n }\n}\n/**\n * Write a text file to a given {@link Path} having contents {@link Contents}.\n *\n * @param Path - The path of the file that will be written.\n * @param Contents - The text contents of the file to write.\n *\n * @returns {Promise<void>} A {@link Promise} that resolves when the call to {@link Fs.writeFile} resolves.\n *\n * @example\n * ```typescript\n * import { WriteTextFile } from \"@sorrell/utilities/fs\";\n * import { resolve } from \"path\";\n *\n * const MyReadMe: string = \"# ReadMe\\n\\nThis package accomplishes...\\n\";\n * const MyReadMePath: string = resolve(\".\");\n *\n * await WriteTextFile(MyReadMePath, MyReadMe);\n * ```\n */\nexport async function WriteTextFile(Path: string, Contents: string): Promise<void> {\n await Fs.writeFile(Path, Contents, { encoding: \"utf-8\" });\n}\nimport * as FileSystem from \"node:fs/promises\";\nimport * as Path from \"node:path\";\nimport * as Readline from \"node:readline\";\ntype DeletePhase = \"Scanning\" | \"Deleting\" | \"Done\";\ntype DeleteEntryKind = \"File\" | \"Directory\" | \"Other\";\ntype DeleteEntry = {\n EntryPath: string;\n Kind: DeleteEntryKind;\n Size: number;\n};\ntype DeleteProgress = {\n Phase: DeletePhase;\n CurrentPath: string | null;\n DiscoveredEntries: number;\n TotalEntries: number;\n DeletedEntries: number;\n TotalBytes: number;\n DeletedBytes: number;\n};\ntype DeleteWithProgressOptions = {\n OnProgress?: (Progress: DeleteProgress) => void;\n Signal?: AbortSignal;\n};\nfunction IsMissingFileError(ErrorValue: unknown): boolean {\n return (typeof ErrorValue === \"object\" &&\n ErrorValue !== null &&\n \"code\" in ErrorValue &&\n ErrorValue.code === \"ENOENT\");\n}\nfunction CloneProgress(Progress: DeleteProgress): DeleteProgress {\n return { ...Progress };\n}\nasync function BuildDeletionPlan(RootPath: string, Progress: DeleteProgress, Options: DeleteWithProgressOptions): Promise<DeleteEntry[]> {\n const Entries: DeleteEntry[] = [];\n async function Visit(CurrentPath: string): Promise<void> {\n Options.Signal?.throwIfAborted();\n Progress.Phase = \"Scanning\";\n Progress.CurrentPath = CurrentPath;\n Options.OnProgress?.(CloneProgress(Progress));\n let Stats;\n try {\n Stats = await FileSystem.lstat(CurrentPath);\n }\n catch (ErrorValue) {\n if (IsMissingFileError(ErrorValue)) {\n return;\n }\n throw ErrorValue;\n }\n if (Stats.isDirectory()) {\n const Children = await FileSystem.readdir(CurrentPath, {\n withFileTypes: true\n });\n for (const Child of Children) {\n await Visit(Path.join(CurrentPath, Child.name));\n }\n Entries.push({\n EntryPath: CurrentPath,\n Kind: \"Directory\",\n Size: 0\n });\n }\n else {\n const Size = Stats.isFile() ? Stats.size : 0;\n Entries.push({\n EntryPath: CurrentPath,\n Kind: Stats.isFile() ? \"File\" : \"Other\",\n Size\n });\n Progress.TotalBytes += Size;\n }\n Progress.DiscoveredEntries = Entries.length;\n Options.OnProgress?.(CloneProgress(Progress));\n }\n await Visit(RootPath);\n return Entries;\n}\nexport async function DeleteWithProgress(RootPath: string, Options: DeleteWithProgressOptions = {}): Promise<void> {\n const Progress: DeleteProgress = {\n Phase: \"Scanning\",\n CurrentPath: null,\n DiscoveredEntries: 0,\n TotalEntries: 0,\n DeletedEntries: 0,\n TotalBytes: 0,\n DeletedBytes: 0\n };\n const Entries = await BuildDeletionPlan(RootPath, Progress, Options);\n Progress.Phase = \"Deleting\";\n Progress.TotalEntries = Entries.length;\n Progress.CurrentPath = null;\n Options.OnProgress?.(CloneProgress(Progress));\n for (const Entry of Entries) {\n Options.Signal?.throwIfAborted();\n Progress.CurrentPath = Entry.EntryPath;\n Options.OnProgress?.(CloneProgress(Progress));\n try {\n if (Entry.Kind === \"Directory\") {\n await FileSystem.rmdir(Entry.EntryPath);\n }\n else {\n await FileSystem.unlink(Entry.EntryPath);\n }\n }\n catch (ErrorValue) {\n if (!IsMissingFileError(ErrorValue)) {\n throw ErrorValue;\n }\n }\n Progress.DeletedEntries += 1;\n Progress.DeletedBytes += Entry.Size;\n Options.OnProgress?.(CloneProgress(Progress));\n }\n Progress.Phase = \"Done\";\n Progress.CurrentPath = null;\n Options.OnProgress?.(CloneProgress(Progress));\n}\nfunction FormatBytes(Bytes: number): string {\n const Units = [\"B\", \"KB\", \"MB\", \"GB\", \"TB\"];\n let Value = Bytes;\n let UnitIndex = 0;\n while (Value >= 1024 && UnitIndex < Units.length - 1) {\n Value /= 1024;\n UnitIndex += 1;\n }\n return `${Value.toFixed(UnitIndex === 0 ? 0 : 1)} ${Units[UnitIndex]}`;\n}\n"],
5
- "mappings": "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",
6
- "names": ["import_fs", "Path", "Fs", "FsConstants", "os"]
3
+ "sources": ["../Source/FileSystem/index.ts", "../Source/FileSystem/FileSystem.ts", "../Source/FileSystem/Module/index.ts", "../Source/String/String.ts", "../Source/FileSystem/Module/Module.ts"],
4
+ "sourcesContent": ["/**\n * @file index.ts\n * @author Gage Sorrell <gage@sorrell.sh>\n * @copyright (c) 2026 Gage Sorrell\n * @license MIT\n */\nexport * from \"./FileSystem.ts\";\nexport * from \"./FileSystem.Types.ts\";\nexport * as Module from \"./Module/index.ts\";\n", "/**\n * @file FileSystem.ts\n * @author Gage Sorrell <gage@sorrell.sh>\n * @copyright (c) 2026 Gage Sorrell\n * @license MIT\n */\nimport * as FileSystem from \"node:fs/promises\";\nimport * as Path from \"node:path\";\nimport { basename, dirname, extname, join } from \"path\";\nimport { type FFileExtension } from \"./FileSystem.Types.ts\";\nimport { type FileHandle } from \"fs/promises\";\nimport { promises as Fs } from \"fs\";\nimport { constants as FsConstants } from \"fs\";\nimport os from \"os\";\nasync function PathExists(Path: string): Promise<boolean> {\n try {\n await Fs.access(Path, FsConstants.F_OK);\n return true;\n }\n catch {\n return false;\n }\n}\n/**\n * @param Extension - The file extension that you wish to test.\n * @returns Whether the file extension is valid on the current platform.\n */\nexport function IsSupportedFileExtension(Extension: FFileExtension): boolean {\n let NormalizedExtension: string = Extension.slice(1);\n if (NormalizedExtension.startsWith(\".\")) {\n NormalizedExtension = NormalizedExtension.slice(1);\n }\n if (NormalizedExtension.length === 0) {\n return false;\n }\n if (NormalizedExtension.includes(\"/\")\n || NormalizedExtension.includes(\"\\\\\")\n || NormalizedExtension.includes(\"\\0\")) {\n return false;\n }\n if (os.platform() === \"win32\") {\n /* eslint-disable-next-line no-control-regex */\n const HasIllegalCharacters: boolean = /[<>:\"/\\\\|?*\\x00-\\x1F]/u.test(NormalizedExtension);\n if (HasIllegalCharacters) {\n return false;\n }\n if (/[ .]$/u.test(NormalizedExtension)) {\n return false;\n }\n }\n return true;\n}\n/**\n * Given a path at which we wish to create a new file, check if a file at that\n * path already exists, and if so, then append `(${ number })` before the file\n * extension, consistent with the Windows Explorer handles conflicting file\n * names when pasting files.\n */\nexport async function GetSafeNewPath(InPath: string): Promise<string> {\n const DirectoryPath: string = dirname(InPath);\n const Extension: string = extname(InPath);\n const FileName: string = basename(InPath);\n const BaseFileName: string = Extension === \"\"\n ? FileName\n : basename(InPath, Extension);\n let CandidatePath: string = InPath;\n let Index: number = 1;\n while (await PathExists(CandidatePath)) {\n const CandidateFileName: string = `${BaseFileName} (${Index})${Extension}`;\n CandidatePath = join(DirectoryPath, CandidateFileName);\n Index++;\n }\n return CandidatePath;\n}\n/* eslint-disable-next-line @typescript-eslint/typedef */\nexport const ReservedWindowsFileNames = [\n \"CON\",\n \"PRN\",\n \"AUX\",\n \"NUL\",\n \"COM1\",\n \"COM2\",\n \"COM3\",\n \"COM4\",\n \"COM5\",\n \"COM6\",\n \"COM7\",\n \"COM8\",\n \"COM9\",\n \"LPT1\",\n \"LPT2\",\n \"LPT3\",\n \"LPT4\",\n \"LPT5\",\n \"LPT6\",\n \"LPT7\",\n \"LPT8\",\n \"LPT9\",\n] as const;\nexport const InvalidCharacters: RegExp = /[<>:\"/\\\\|?*\\u0000-\\u001F]/u;\n/**\n * @param DirectoryPath - The path to the directory in which you wish to check.\n * @param FileName - The desired file name.\n * @param PersistNewFile - *(Optional)* Whether to keep the otherwise-temporary\n * file created at the desired path.\n * @param Extension - *(Optional)* If provided, the function will only return `true`\n * if `FileName.endsWith(Extension)` *and* the `Extension` is a valid\n * file extension.\n * @returns Whether a file of the given `FileName` can be created in `DirectoryPath`.\n *\n * @remarks This *does* attempt to create a file at the desired path. The file is\n * temporary iff `!PersistNewFile`, and is never created when this function\n * returns `false`.\n */\nexport async function IsValidFileName(DirectoryPath: string, FileName: string, PersistNewFile: boolean = false, Extension: FFileExtension | undefined = undefined): Promise<boolean> {\n const ExtensionSafe: FFileExtension | null | \"\" = Extension !== undefined\n ? (Extension.startsWith(\".\") && IsSupportedFileExtension(Extension))\n ? Extension\n : null\n : \"\";\n const IsExtensionImproper: boolean = (ExtensionSafe === null ||\n ExtensionSafe === \".\" ||\n !FileName.endsWith(ExtensionSafe));\n if (IsExtensionImproper) {\n return false;\n }\n const FilePath: string = join(DirectoryPath, FileName);\n try {\n const ThisFileHandle: FileHandle = await Fs.open(FilePath, \"wx\");\n await ThisFileHandle.close();\n if (!PersistNewFile) {\n await Fs.unlink(FilePath);\n }\n return true;\n }\n catch {\n return false;\n }\n}\n/**\n * Write a text file to a given {@link Path} having contents {@link Contents}.\n *\n * @param Path - The path of the file that will be written.\n * @param Contents - The text contents of the file to write.\n *\n * @returns {Promise<void>} A {@link Promise} that resolves when the call to {@link Fs.writeFile} resolves.\n *\n * @example\n * ```typescript\n * import { WriteTextFile } from \"@sorrell/utilities/fs\";\n * import { resolve } from \"path\";\n *\n * const MyReadMe: string = \"# ReadMe\\n\\nThis package accomplishes...\\n\";\n * const MyReadMePath: string = resolve(\".\");\n *\n * await WriteTextFile(MyReadMePath, MyReadMe);\n * ```\n */\nexport async function WriteTextFile(Path: string, Contents: string): Promise<void> {\n await Fs.writeFile(Path, Contents, { encoding: \"utf-8\" });\n}\ntype DeletePhase = \"Scanning\" | \"Deleting\" | \"Done\";\ntype DeleteEntryKind = \"File\" | \"Directory\" | \"Other\";\ntype DeleteEntry = {\n EntryPath: string;\n Kind: DeleteEntryKind;\n Size: number;\n};\ntype DeleteProgress = {\n Phase: DeletePhase;\n CurrentPath: string | null;\n DiscoveredEntries: number;\n TotalEntries: number;\n DeletedEntries: number;\n TotalBytes: number;\n DeletedBytes: number;\n};\ntype DeleteWithProgressOptions = {\n OnProgress?: (Progress: DeleteProgress) => void;\n Signal?: AbortSignal;\n};\nfunction IsMissingFileError(ErrorValue: unknown): boolean {\n return (typeof ErrorValue === \"object\" &&\n ErrorValue !== null &&\n \"code\" in ErrorValue &&\n ErrorValue.code === \"ENOENT\");\n}\nfunction CloneProgress(Progress: DeleteProgress): DeleteProgress {\n return { ...Progress };\n}\nasync function BuildDeletionPlan(RootPath: string, Progress: DeleteProgress, Options: DeleteWithProgressOptions): Promise<DeleteEntry[]> {\n const Entries: DeleteEntry[] = [];\n async function Visit(CurrentPath: string): Promise<void> {\n Options.Signal?.throwIfAborted();\n Progress.Phase = \"Scanning\";\n Progress.CurrentPath = CurrentPath;\n Options.OnProgress?.(CloneProgress(Progress));\n let Stats;\n try {\n Stats = await FileSystem.lstat(CurrentPath);\n }\n catch (ErrorValue) {\n if (IsMissingFileError(ErrorValue)) {\n return;\n }\n throw ErrorValue;\n }\n if (Stats.isDirectory()) {\n const Children = await FileSystem.readdir(CurrentPath, {\n withFileTypes: true\n });\n for (const Child of Children) {\n await Visit(Path.join(CurrentPath, Child.name));\n }\n Entries.push({\n EntryPath: CurrentPath,\n Kind: \"Directory\",\n Size: 0\n });\n }\n else {\n const Size = Stats.isFile() ? Stats.size : 0;\n Entries.push({\n EntryPath: CurrentPath,\n Kind: Stats.isFile() ? \"File\" : \"Other\",\n Size\n });\n Progress.TotalBytes += Size;\n }\n Progress.DiscoveredEntries = Entries.length;\n Options.OnProgress?.(CloneProgress(Progress));\n }\n await Visit(RootPath);\n return Entries;\n}\nexport async function DeleteWithProgress(RootPath: string, Options: DeleteWithProgressOptions = {}): Promise<void> {\n const Progress: DeleteProgress = {\n Phase: \"Scanning\",\n CurrentPath: null,\n DiscoveredEntries: 0,\n TotalEntries: 0,\n DeletedEntries: 0,\n TotalBytes: 0,\n DeletedBytes: 0\n };\n const Entries = await BuildDeletionPlan(RootPath, Progress, Options);\n Progress.Phase = \"Deleting\";\n Progress.TotalEntries = Entries.length;\n Progress.CurrentPath = null;\n Options.OnProgress?.(CloneProgress(Progress));\n for (const Entry of Entries) {\n Options.Signal?.throwIfAborted();\n Progress.CurrentPath = Entry.EntryPath;\n Options.OnProgress?.(CloneProgress(Progress));\n try {\n if (Entry.Kind === \"Directory\") {\n await FileSystem.rmdir(Entry.EntryPath);\n }\n else {\n await FileSystem.unlink(Entry.EntryPath);\n }\n }\n catch (ErrorValue) {\n if (!IsMissingFileError(ErrorValue)) {\n throw ErrorValue;\n }\n }\n Progress.DeletedEntries += 1;\n Progress.DeletedBytes += Entry.Size;\n Options.OnProgress?.(CloneProgress(Progress));\n }\n Progress.Phase = \"Done\";\n Progress.CurrentPath = null;\n Options.OnProgress?.(CloneProgress(Progress));\n}\nfunction FormatBytes(Bytes: number): string {\n const Units = [\"B\", \"KB\", \"MB\", \"GB\", \"TB\"];\n let Value = Bytes;\n let UnitIndex = 0;\n while (Value >= 1024 && UnitIndex < Units.length - 1) {\n Value /= 1024;\n UnitIndex += 1;\n }\n return `${Value.toFixed(UnitIndex === 0 ? 0 : 1)} ${Units[UnitIndex]}`;\n}\n", "/**\n * @file index.ts\n * @author Gage Sorrell <gage@sorrell.sh>\n * @copyright (c) 2026 Gage Sorrell\n * @license MIT\n */\nexport * from \"./Module.ts\";\nexport * from \"./Module.Types.ts\";\n", "/**\n * @file String.ts\n * @author Gage Sorrell <gage@sorrell.sh>\n * @copyright (c) 2026 Gage Sorrell\n * @license MIT\n */\n/**\n * @module String\n * Functions for manipulating strings.\n */\n// @TODO TEMPORARY.\n/* eslint-disable jsdoc/require-example */\n/**\n * Does the given {@link TestString} contain only letters?\n * This is equivalent to Python's\n * {@link https://docs.python.org/3/library/stdtypes.html#str.isalpha | isalpha} function.\n *\n * @remarks This is an alias for {@link IsAlpha}.\n *\n * @param TestString - The string that you wish to test.\n *\n * @returns {boolean} Whether the given string contains *only* (Latin alphabet) letters.\n */\nexport function IsLetters(TestString: string): boolean {\n return /^[a-zA-Z]*$/.test(TestString);\n}\n/**\n * Does the given {@link TestString} contain only letters?\n * This is equivalent to Python's\n * {@link https://docs.python.org/3/library/stdtypes.html#str.isalpha | isalpha} function.\n *\n * @param TestString - The string that you wish to test.\n *\n * @returns {boolean} Whether the given string contains *only* (Latin alphabet) letters.\n */\nexport function IsAlpha(TestString: string): boolean {\n return IsLetters(TestString);\n}\n/**\n * Does the given {@link TestString} contain only digits?\n *\n * This is equivalent to Python's\n * {@link https://docs.python.org/3/library/stdtypes.html#str.isnumeric | isnumeric} function.\n *\n * @param TestString - The string that you wish to test.\n *\n * @returns {boolean} Whether the given string contains *only* numeric digits.\n */\nexport function IsNumeric(TestString: string): boolean {\n return /^\\d+$/.test(TestString);\n}\n/**\n * This function provides a convenient way to define long strings across multiple lines.\n * The linebreaks and leading indentation are removed from the final string.\n * Please see the example below for how this is to be done.\n *\n * @param Content - The given string to dedent.\n * @param IndentLength - The number of spaces for which this function will check. This\n * is necessary only if the number of spaces used in the {@link Content} is not a multiple\n * of two.\n *\n * @returns {string} The given {@link Content}, but with the spaces at the beginning\n * of each line (excluding the first line if no spaces exist in the first line) removed.\n *\n * @example\n * ```typescript\n * import { Dedent } from \"@sorrell/utilities/string\";\n *\n * const MyLongString: string = Dedent(`Lorem ipsum dolor sit amet, consectetur adipiscing elit,\n * sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad\n * minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea\n * commodo consequat.`);\n *\n * // `MyLongString` <- `\"Lorem ipsum dolor sit amet, consectetur adipiscing elit, \\\n * sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad \\\n * minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea \\\n * commodo consequat.\"`\n * ```\n */\nexport function Dedent(Content: string, IndentLength?: number): string {\n const WhitespaceSubstring: string = ((): string => {\n if (IndentLength !== undefined) {\n return \"\\n\" + \" \".repeat(IndentLength);\n }\n let WhitespaceSubstring: string = \"\\n\";\n while (true) {\n const TestString: string = WhitespaceSubstring + \" \";\n if (Content.includes(TestString)) {\n WhitespaceSubstring = TestString;\n }\n else {\n break;\n }\n }\n return WhitespaceSubstring;\n })();\n return Content.replaceAll(WhitespaceSubstring, \"\");\n}\nexport function GetUtf8ByteLength(Text: string): number {\n return new TextEncoder().encode(Text).length;\n}\n", "/**\n * @file Module.ts\n * @author Gage Sorrell <gage@sorrell.sh>\n * @copyright (c) 2026 Gage Sorrell\n * @license MIT\n */\nimport { InvalidCharacters, ReservedWindowsFileNames } from \"../FileSystem.ts\";\nimport type { ExtensionPolicy } from \"./Module.Types.ts\";\nimport { GetUtf8ByteLength } from \"../../String/String.ts\";\n/* eslint-disable @typescript-eslint/typedef */\nexport namespace ValidExtensions {\n export /**\n * The valid file extensions for TypeScript *source* modules.\n */ const Source = [\n \".ts\",\n \".tsx\",\n \".mts\",\n \".cts\"\n ] as const;\n export /**\n * The valid file extensions for TypeScript *declaration* modules.\n */ const Declaration = [\n \".d.ts\",\n \".d.mts\",\n \".d.cts\"\n ] as const;\n export /**\n * The valid file extensions for *any* TypeScript module.\n */ const Any = [\n \".ts\",\n \".tsx\",\n \".mts\",\n \".cts\",\n \".d.ts\",\n \".d.mts\",\n \".d.cts\"\n ] as const;\n}\n/* eslint-enable @typescript-eslint/typedef */\nexport function HasTypeScriptExtension(FileName: string): boolean {\n return /\\.(ts|tsx|mts|cts|d\\.ts|d\\.mts|d\\.cts)$/iu.test(FileName);\n}\nexport function IsValidFileName(FileName: string, ExtensionPolicy: ExtensionPolicy = \"Disallow\"): boolean {\n if (FileName.length === 0) {\n return false;\n }\n if (FileName === \".\" || FileName === \"..\") {\n return false;\n }\n const DoesExtensionSatisfyPolicy: boolean = ((ExtensionPolicy === \"Disallow\" && !HasTypeScriptExtension(FileName)) ||\n (ExtensionPolicy === \"Require\" && HasTypeScriptExtension(FileName)) ||\n (Array.isArray(ExtensionPolicy) && ExtensionPolicy.some(FileName.endsWith)));\n if (!DoesExtensionSatisfyPolicy) {\n return false;\n }\n if (FileName.endsWith(\".\") || FileName.endsWith(\" \")) {\n return false;\n }\n if (GetUtf8ByteLength(FileName) > 255) {\n return false;\n }\n if (InvalidCharacters.test(FileName)) {\n return false;\n }\n const FileNameWithoutDots: string | undefined = FileName.split(\".\")[0]?.toUpperCase();\n const FileNameContainsWindowsReserved: boolean = (FileNameWithoutDots !== undefined &&\n (ReservedWindowsFileNames as ReadonlyArray<string>).includes(FileNameWithoutDots));\n if (FileNameContainsWindowsReserved) {\n return false;\n }\n return true;\n}\n"],
5
+ "mappings": "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",
6
+ "names": ["import_fs", "Path", "Fs", "FsConstants", "os", "IsValidFileName", "ValidExtensions", "IsValidFileName"]
7
7
  }
@@ -1,10 +1,16 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __export = (target, all) => {
3
+ for (var name in all)
4
+ __defProp(target, name, { get: all[name], enumerable: true });
5
+ };
6
+
1
7
  // Source/FileSystem/FileSystem.ts
2
- import { basename, dirname, extname, join } from "path";
8
+ import * as FileSystem from "node:fs/promises";
9
+ import * as Path from "node:path";
10
+ import { basename, dirname, extname, join as join2 } from "path";
3
11
  import { promises as Fs } from "fs";
4
12
  import { constants as FsConstants } from "fs";
5
13
  import os from "os";
6
- import * as FileSystem from "node:fs/promises";
7
- import * as Path from "node:path";
8
14
  async function PathExists(Path2) {
9
15
  try {
10
16
  await Fs.access(Path2, FsConstants.F_OK);
@@ -44,18 +50,43 @@ async function GetSafeNewPath(InPath) {
44
50
  let Index = 1;
45
51
  while (await PathExists(CandidatePath)) {
46
52
  const CandidateFileName = `${BaseFileName} (${Index})${Extension}`;
47
- CandidatePath = join(DirectoryPath, CandidateFileName);
53
+ CandidatePath = join2(DirectoryPath, CandidateFileName);
48
54
  Index++;
49
55
  }
50
56
  return CandidatePath;
51
57
  }
58
+ var ReservedWindowsFileNames = [
59
+ "CON",
60
+ "PRN",
61
+ "AUX",
62
+ "NUL",
63
+ "COM1",
64
+ "COM2",
65
+ "COM3",
66
+ "COM4",
67
+ "COM5",
68
+ "COM6",
69
+ "COM7",
70
+ "COM8",
71
+ "COM9",
72
+ "LPT1",
73
+ "LPT2",
74
+ "LPT3",
75
+ "LPT4",
76
+ "LPT5",
77
+ "LPT6",
78
+ "LPT7",
79
+ "LPT8",
80
+ "LPT9"
81
+ ];
82
+ var InvalidCharacters = /[<>:"/\\|?*\u0000-\u001F]/u;
52
83
  async function IsValidFileName(DirectoryPath, FileName, PersistNewFile = false, Extension = void 0) {
53
84
  const ExtensionSafe = Extension !== void 0 ? Extension.startsWith(".") && IsSupportedFileExtension(Extension) ? Extension : null : "";
54
85
  const IsExtensionImproper = ExtensionSafe === null || ExtensionSafe === "." || !FileName.endsWith(ExtensionSafe);
55
86
  if (IsExtensionImproper) {
56
87
  return false;
57
88
  }
58
- const FilePath = join(DirectoryPath, FileName);
89
+ const FilePath = join2(DirectoryPath, FileName);
59
90
  try {
60
91
  const ThisFileHandle = await Fs.open(FilePath, "wx");
61
92
  await ThisFileHandle.close();
@@ -157,11 +188,82 @@ async function DeleteWithProgress(RootPath, Options = {}) {
157
188
  Progress.CurrentPath = null;
158
189
  Options.OnProgress?.(CloneProgress(Progress));
159
190
  }
191
+
192
+ // Source/FileSystem/Module/index.ts
193
+ var Module_exports = {};
194
+ __export(Module_exports, {
195
+ HasTypeScriptExtension: () => HasTypeScriptExtension,
196
+ IsValidFileName: () => IsValidFileName2,
197
+ ValidExtensions: () => ValidExtensions
198
+ });
199
+
200
+ // Source/String/String.ts
201
+ function GetUtf8ByteLength(Text) {
202
+ return new TextEncoder().encode(Text).length;
203
+ }
204
+
205
+ // Source/FileSystem/Module/Module.ts
206
+ var ValidExtensions;
207
+ ((ValidExtensions2) => {
208
+ ValidExtensions2.Source = [
209
+ ".ts",
210
+ ".tsx",
211
+ ".mts",
212
+ ".cts"
213
+ ];
214
+ ValidExtensions2.Declaration = [
215
+ ".d.ts",
216
+ ".d.mts",
217
+ ".d.cts"
218
+ ];
219
+ ValidExtensions2.Any = [
220
+ ".ts",
221
+ ".tsx",
222
+ ".mts",
223
+ ".cts",
224
+ ".d.ts",
225
+ ".d.mts",
226
+ ".d.cts"
227
+ ];
228
+ })(ValidExtensions || (ValidExtensions = {}));
229
+ function HasTypeScriptExtension(FileName) {
230
+ return /\.(ts|tsx|mts|cts|d\.ts|d\.mts|d\.cts)$/iu.test(FileName);
231
+ }
232
+ function IsValidFileName2(FileName, ExtensionPolicy = "Disallow") {
233
+ if (FileName.length === 0) {
234
+ return false;
235
+ }
236
+ if (FileName === "." || FileName === "..") {
237
+ return false;
238
+ }
239
+ const DoesExtensionSatisfyPolicy = ExtensionPolicy === "Disallow" && !HasTypeScriptExtension(FileName) || ExtensionPolicy === "Require" && HasTypeScriptExtension(FileName) || Array.isArray(ExtensionPolicy) && ExtensionPolicy.some(FileName.endsWith);
240
+ if (!DoesExtensionSatisfyPolicy) {
241
+ return false;
242
+ }
243
+ if (FileName.endsWith(".") || FileName.endsWith(" ")) {
244
+ return false;
245
+ }
246
+ if (GetUtf8ByteLength(FileName) > 255) {
247
+ return false;
248
+ }
249
+ if (InvalidCharacters.test(FileName)) {
250
+ return false;
251
+ }
252
+ const FileNameWithoutDots = FileName.split(".")[0]?.toUpperCase();
253
+ const FileNameContainsWindowsReserved = FileNameWithoutDots !== void 0 && ReservedWindowsFileNames.includes(FileNameWithoutDots);
254
+ if (FileNameContainsWindowsReserved) {
255
+ return false;
256
+ }
257
+ return true;
258
+ }
160
259
  export {
161
260
  DeleteWithProgress,
162
261
  GetSafeNewPath,
262
+ InvalidCharacters,
163
263
  IsSupportedFileExtension,
164
264
  IsValidFileName,
265
+ Module_exports as Module,
266
+ ReservedWindowsFileNames,
165
267
  WriteTextFile
166
268
  };
167
269
  /**
@@ -176,6 +278,24 @@ export {
176
278
  * @copyright (c) 2026 Gage Sorrell
177
279
  * @license MIT
178
280
  */
281
+ /**
282
+ * @file String.ts
283
+ * @author Gage Sorrell <gage@sorrell.sh>
284
+ * @copyright (c) 2026 Gage Sorrell
285
+ * @license MIT
286
+ */
287
+ /**
288
+ * @file Module.ts
289
+ * @author Gage Sorrell <gage@sorrell.sh>
290
+ * @copyright (c) 2026 Gage Sorrell
291
+ * @license MIT
292
+ */
293
+ /**
294
+ * @file Module.Types.ts
295
+ * @author Gage Sorrell <gage@sorrell.sh>
296
+ * @copyright (c) 2026 Gage Sorrell
297
+ * @license MIT
298
+ */
179
299
  /**
180
300
  * @file index.ts
181
301
  * @author Gage Sorrell <gage@sorrell.sh>