tiny-essentials 1.11.0 → 1.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/README.md +12 -3
  2. package/dist/node_modules/firebase-functions/lib/common/trace.cjs +82 -0
  3. package/dist/node_modules/firebase-functions/lib/logger/common.cjs +57 -0
  4. package/dist/node_modules/firebase-functions/lib/logger/index.cjs +158 -0
  5. package/dist/v1/TinyBasicsEs.js +1653 -1
  6. package/dist/v1/TinyBasicsEs.min.js +1 -1
  7. package/dist/v1/TinyDragDropDetector.js +307 -0
  8. package/dist/v1/TinyDragDropDetector.min.js +1 -0
  9. package/dist/v1/TinyDragger.js +3563 -0
  10. package/dist/v1/TinyDragger.min.js +2 -0
  11. package/dist/v1/TinyDragger.min.js.LICENSE.txt +8 -0
  12. package/dist/v1/TinyEssentials.js +2783 -2
  13. package/dist/v1/TinyEssentials.min.js +1 -1
  14. package/dist/v1/TinyNotifyCenter.js +32 -0
  15. package/dist/v1/TinyNotifyCenter.min.js +1 -1
  16. package/dist/v1/TinyToastNotify.js +24 -0
  17. package/dist/v1/TinyToastNotify.min.js +1 -1
  18. package/dist/v1/TinyUploadClicker.js +5235 -0
  19. package/dist/v1/TinyUploadClicker.min.js +2 -0
  20. package/dist/v1/TinyUploadClicker.min.js.LICENSE.txt +8 -0
  21. package/dist/v1/basics/fileManager.cjs +524 -0
  22. package/dist/v1/basics/fileManager.d.mts +209 -0
  23. package/dist/v1/basics/fileManager.mjs +443 -0
  24. package/dist/v1/basics/fullScreen.cjs +134 -0
  25. package/dist/v1/basics/fullScreen.d.mts +8 -0
  26. package/dist/v1/basics/fullScreen.mjs +121 -0
  27. package/dist/v1/basics/html.cjs +262 -0
  28. package/dist/v1/basics/html.d.mts +63 -0
  29. package/dist/v1/basics/html.mjs +208 -0
  30. package/dist/v1/basics/index.cjs +40 -0
  31. package/dist/v1/basics/index.d.mts +38 -1
  32. package/dist/v1/basics/index.mjs +4 -1
  33. package/dist/v1/build/TinyDragDropDetector.cjs +7 -0
  34. package/dist/v1/build/TinyDragDropDetector.d.mts +3 -0
  35. package/dist/v1/build/TinyDragDropDetector.mjs +2 -0
  36. package/dist/v1/build/TinyDragger.cjs +7 -0
  37. package/dist/v1/build/TinyDragger.d.mts +3 -0
  38. package/dist/v1/build/TinyDragger.mjs +2 -0
  39. package/dist/v1/build/TinyUploadClicker.cjs +7 -0
  40. package/dist/v1/build/TinyUploadClicker.d.mts +3 -0
  41. package/dist/v1/build/TinyUploadClicker.mjs +2 -0
  42. package/dist/v1/css/TinyDraggerExample.css +21 -0
  43. package/dist/v1/css/TinyDraggerExample.min.css +1 -0
  44. package/dist/v1/index.cjs +44 -0
  45. package/dist/v1/index.d.mts +40 -1
  46. package/dist/v1/index.mjs +6 -1
  47. package/dist/v1/libs/TinyDragDropDetector.cjs +268 -0
  48. package/dist/v1/libs/TinyDragDropDetector.d.mts +123 -0
  49. package/dist/v1/libs/TinyDragDropDetector.mjs +228 -0
  50. package/dist/v1/libs/TinyDragger.cjs +801 -0
  51. package/dist/v1/libs/TinyDragger.d.mts +300 -0
  52. package/dist/v1/libs/TinyDragger.mjs +689 -0
  53. package/dist/v1/libs/TinyNotifyCenter.cjs +32 -0
  54. package/dist/v1/libs/TinyNotifyCenter.d.mts +9 -0
  55. package/dist/v1/libs/TinyNotifyCenter.mjs +28 -0
  56. package/dist/v1/libs/TinyToastNotify.cjs +24 -0
  57. package/dist/v1/libs/TinyToastNotify.d.mts +8 -0
  58. package/dist/v1/libs/TinyToastNotify.mjs +20 -0
  59. package/dist/v1/libs/TinyUploadClicker.cjs +223 -0
  60. package/dist/v1/libs/TinyUploadClicker.d.mts +80 -0
  61. package/dist/v1/libs/TinyUploadClicker.mjs +182 -0
  62. package/docs/v1/README.md +8 -2
  63. package/docs/v1/basics/fileManager.md +222 -0
  64. package/docs/v1/basics/fullScreen.md +183 -0
  65. package/docs/v1/basics/html.md +243 -0
  66. package/docs/v1/libs/TinyDragDropDetector.md +143 -0
  67. package/docs/v1/libs/TinyDragger.md +415 -0
  68. package/docs/v1/libs/TinyLevelUp.md +1 -1
  69. package/docs/v1/libs/TinyNotifyCenter.md +20 -0
  70. package/docs/v1/libs/TinyToastNotify.md +19 -0
  71. package/docs/v1/libs/TinyUploadClicker.md +108 -0
  72. package/package.json +12 -2
@@ -0,0 +1,209 @@
1
+ /**
2
+ * Reads and parses a JSON file.
3
+ * Throws an error if the file content is not valid JSON.
4
+ * @param {string} filePath
5
+ * @returns {any}
6
+ */
7
+ export function readJsonFile(filePath: string): any;
8
+ /**
9
+ * Saves an object as JSON to a file.
10
+ * Automatically creates the directory if it does not exist.
11
+ * @param {string} filePath
12
+ * @param {any} data
13
+ * @param {number} [spaces=2]
14
+ */
15
+ export function writeJsonFile(filePath: string, data: any, spaces?: number): void;
16
+ /**
17
+ * Reads and parses a JSON file.
18
+ * Throws an error if the file content is not valid JSON.
19
+ * @param {string} filePath
20
+ * @returns {Promise<any>}
21
+ */
22
+ export function readJsonFileAsync(filePath: string): Promise<any>;
23
+ /**
24
+ * Saves an object as JSON to a file.
25
+ * Automatically creates the directory if it does not exist.
26
+ * @param {string} filePath
27
+ * @param {any} data
28
+ * @param {number} [spaces=2]
29
+ * @returns {Promise<void>}
30
+ */
31
+ export function writeJsonFileAsync(filePath: string, data: any, spaces?: number): Promise<void>;
32
+ /**
33
+ * Ensures that the directory exists, creating it recursively if needed.
34
+ * @param {string} dirPath
35
+ */
36
+ export function ensureDirectory(dirPath: string): void;
37
+ /**
38
+ * Clears all contents inside a directory but keeps the directory.
39
+ * @param {string} dirPath
40
+ */
41
+ export function clearDirectory(dirPath: string): void;
42
+ /**
43
+ * Checks whether a file exists.
44
+ * @param {string} filePath
45
+ * @returns {boolean}
46
+ */
47
+ export function fileExists(filePath: string): boolean;
48
+ /**
49
+ * Checks whether a directory exists.
50
+ * @param {string} dirPath
51
+ * @returns {boolean}
52
+ */
53
+ export function dirExists(dirPath: string): boolean;
54
+ /**
55
+ * Checks whether a directory is empty.
56
+ * @param {string} dirPath
57
+ * @returns {boolean}
58
+ */
59
+ export function isDirEmpty(dirPath: string): boolean;
60
+ /**
61
+ * Copies a file to a destination.
62
+ * @param {string} src
63
+ * @param {string} dest
64
+ * @param {number} [mode]
65
+ */
66
+ export function ensureCopyFile(src: string, dest: string, mode?: number): void;
67
+ /**
68
+ * Deletes a file (If the file exists).
69
+ * @param {string} filePath
70
+ * @returns {boolean}
71
+ */
72
+ export function tryDeleteFile(filePath: string): boolean;
73
+ /**
74
+ * Copies a file to a destination.
75
+ * @param {string} src
76
+ * @param {string} dest
77
+ * @param {number} [mode]
78
+ * @returns {Promise<void>}
79
+ */
80
+ export function ensureCopyFileAsync(src: string, dest: string, mode?: number): Promise<void>;
81
+ /**
82
+ * Deletes a file (If the file exists).
83
+ * @param {string} filePath
84
+ * @returns {Promise<boolean>}
85
+ */
86
+ export function tryDeleteFileAsync(filePath: string): Promise<boolean>;
87
+ /**
88
+ * Writes text to a file (Ensures that the directory exists, creating it recursively if needed).
89
+ * @param {string} filePath
90
+ * @param {string} content
91
+ * @param {fs.WriteFileOptions} [ops='utf-8']
92
+ */
93
+ export function writeTextFile(filePath: string, content: string, ops?: fs.WriteFileOptions): void;
94
+ /**
95
+ * Writes text to a file (Ensures that the directory exists, creating it recursively if needed).
96
+ * @param {string} filePath
97
+ * @param {string} content
98
+ * @param {fs.WriteFileOptions} [ops='utf-8']
99
+ * @returns {Promise<void>}
100
+ */
101
+ export function writeTextFileAsync(filePath: string, content: string, ops?: fs.WriteFileOptions): Promise<void>;
102
+ /**
103
+ * Lists all files and dirs in a directory (optionally recursive).
104
+ * @param {string} dirPath
105
+ * @param {boolean} [recursive=false]
106
+ * @returns {{ files: string[]; dirs: string[] }}
107
+ */
108
+ export function listFiles(dirPath: string, recursive?: boolean): {
109
+ files: string[];
110
+ dirs: string[];
111
+ };
112
+ /**
113
+ * Lists all directories in a directory (optionally recursive).
114
+ * @param {string} dirPath
115
+ * @param {boolean} [recursive=false]
116
+ * @returns {string[]}
117
+ */
118
+ export function listDirs(dirPath: string, recursive?: boolean): string[];
119
+ /**
120
+ * Returns the size of a file in bytes.
121
+ * @param {string} filePath
122
+ * @returns {number}
123
+ */
124
+ export function fileSize(filePath: string): number;
125
+ /**
126
+ * Returns the total size of a directory in bytes.
127
+ * @param {string} dirPath
128
+ * @returns {number}
129
+ */
130
+ export function dirSize(dirPath: string): number;
131
+ /**
132
+ * Creates a backup copy of a file with .bak timestamp suffix.
133
+ * @param {string} filePath
134
+ * @param {string} [ext='bak']
135
+ */
136
+ export function backupFile(filePath: string, ext?: string): void;
137
+ /**
138
+ * Returns the most recent backup file path for a given file.
139
+ * @param {string} filePath
140
+ * @param {string} [ext='bak']
141
+ * @returns {string} Full path to the most recent backup
142
+ */
143
+ export function getLatestBackupPath(filePath: string, ext?: string): string;
144
+ /**
145
+ * Restores the most recent backup of a file.
146
+ * @param {string} filePath
147
+ * @param {string} [ext='bak']
148
+ */
149
+ export function restoreLatestBackup(filePath: string, ext?: string): void;
150
+ /**
151
+ * Restores the most recent backup of a file.
152
+ * @param {string} filePath
153
+ * @param {string} [ext='bak']
154
+ * @returns {Promise<void>}
155
+ */
156
+ export function restoreLatestBackupAsync(filePath: string, ext?: string): Promise<void>;
157
+ /**
158
+ * Creates a backup copy of a file with .bak timestamp suffix.
159
+ * @param {string} filePath
160
+ * @param {string} [ext='bak']
161
+ * @returns {Promise<void>}
162
+ */
163
+ export function backupFileAsync(filePath: string, ext?: string): Promise<void>;
164
+ /**
165
+ * Renames multiple files in a directory using a rename function.
166
+ * @param {string} dirPath - The target directory.
167
+ * @param {(original: string, index: number) => string} renameFn - Function that returns new filename.
168
+ * @param {string[]} [extensions] - Optional: Only rename files with these extensions.
169
+ *
170
+ * @throws {TypeError} If any argument has an invalid type.
171
+ * @throws {Error} If the directory does not exist or contains invalid files.
172
+ */
173
+ export function renameFileBatch(dirPath: string, renameFn: (original: string, index: number) => string, extensions?: string[]): void;
174
+ /**
175
+ * Renames files using regex replacement.
176
+ * @param {string} dirPath
177
+ * @param {RegExp} pattern - Regex to match in the filename.
178
+ * @param {string} replacement - Replacement string.
179
+ * @param {string[]} [extensions]
180
+ */
181
+ export function renameFileRegex(dirPath: string, pattern: RegExp, replacement: string, extensions?: string[]): void;
182
+ /**
183
+ * Adds a prefix or suffix to filenames.
184
+ * @param {string} dirPath
185
+ * @param {{ prefix?: string, suffix?: string }} options
186
+ * @param {string[]} [extensions]
187
+ */
188
+ export function renameFileAddPrefixSuffix(dirPath: string, { prefix, suffix }: {
189
+ prefix?: string;
190
+ suffix?: string;
191
+ }, extensions?: string[]): void;
192
+ /**
193
+ * Normalizes all filenames to lowercase (or uppercase).
194
+ * @param {string} dirPath
195
+ * @param {'lower' | 'upper' | 'title'} mode
196
+ * @param {string[]} [extensions]
197
+ * @param {boolean} [normalizeExt=false] - Whether to apply case change to file extensions too.
198
+ * @throws {Error} If mode is invalid.
199
+ */
200
+ export function renameFileNormalizeCase(dirPath: string, mode?: "lower" | "upper" | "title", extensions?: string[], normalizeExt?: boolean): void;
201
+ /**
202
+ * Pads numbers in filenames (e.g., "img1.jpg" -> "img001.jpg").
203
+ * @param {string} dirPath
204
+ * @param {number} totalDigits
205
+ * @param {string[]} [extensions]
206
+ */
207
+ export function renameFilePadNumbers(dirPath: string, totalDigits?: number, extensions?: string[]): void;
208
+ import fs from 'fs';
209
+ //# sourceMappingURL=fileManager.d.mts.map
@@ -0,0 +1,443 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import { toTitleCase } from './text.mjs';
4
+ /*========================*
5
+ * JSON Operations
6
+ *========================*/
7
+ /**
8
+ * Reads and parses a JSON file.
9
+ * Throws an error if the file content is not valid JSON.
10
+ * @param {string} filePath
11
+ * @returns {any}
12
+ */
13
+ export function readJsonFile(filePath) {
14
+ if (!fs.existsSync(filePath))
15
+ throw new Error(`File not found: ${filePath}`);
16
+ const content = fs.readFileSync(filePath, 'utf-8');
17
+ return JSON.parse(content);
18
+ }
19
+ /**
20
+ * Saves an object as JSON to a file.
21
+ * Automatically creates the directory if it does not exist.
22
+ * @param {string} filePath
23
+ * @param {any} data
24
+ * @param {number} [spaces=2]
25
+ */
26
+ export function writeJsonFile(filePath, data, spaces = 2) {
27
+ const json = JSON.stringify(data, null, spaces);
28
+ fs.writeFileSync(filePath, json, 'utf-8');
29
+ }
30
+ /**
31
+ * Reads and parses a JSON file.
32
+ * Throws an error if the file content is not valid JSON.
33
+ * @param {string} filePath
34
+ * @returns {Promise<any>}
35
+ */
36
+ export async function readJsonFileAsync(filePath) {
37
+ if (!fs.existsSync(filePath))
38
+ throw new Error(`File not found: ${filePath}`);
39
+ const content = await fs.promises.readFile(filePath, 'utf-8');
40
+ return JSON.parse(content);
41
+ }
42
+ /**
43
+ * Saves an object as JSON to a file.
44
+ * Automatically creates the directory if it does not exist.
45
+ * @param {string} filePath
46
+ * @param {any} data
47
+ * @param {number} [spaces=2]
48
+ * @returns {Promise<void>}
49
+ */
50
+ export function writeJsonFileAsync(filePath, data, spaces = 2) {
51
+ const json = JSON.stringify(data, null, spaces);
52
+ return fs.promises.writeFile(filePath, json, 'utf-8');
53
+ }
54
+ /*========================*
55
+ * Directory Management
56
+ *========================*/
57
+ /**
58
+ * Ensures that the directory exists, creating it recursively if needed.
59
+ * @param {string} dirPath
60
+ */
61
+ export function ensureDirectory(dirPath) {
62
+ if (!fs.existsSync(dirPath)) {
63
+ fs.mkdirSync(dirPath, { recursive: true });
64
+ }
65
+ }
66
+ /**
67
+ * Clears all contents inside a directory but keeps the directory.
68
+ * @param {string} dirPath
69
+ */
70
+ export function clearDirectory(dirPath) {
71
+ if (!fs.existsSync(dirPath))
72
+ return;
73
+ const files = fs.readdirSync(dirPath);
74
+ for (const file of files) {
75
+ const fullPath = path.join(dirPath, file);
76
+ const stat = fs.lstatSync(fullPath);
77
+ if (stat.isDirectory()) {
78
+ fs.rmSync(fullPath, { recursive: true, force: true });
79
+ }
80
+ else {
81
+ fs.unlinkSync(fullPath);
82
+ }
83
+ }
84
+ }
85
+ /*========================*
86
+ * File Checks
87
+ *========================*/
88
+ /**
89
+ * Checks whether a file exists.
90
+ * @param {string} filePath
91
+ * @returns {boolean}
92
+ */
93
+ export function fileExists(filePath) {
94
+ return fs.existsSync(filePath) && fs.lstatSync(filePath).isFile();
95
+ }
96
+ /**
97
+ * Checks whether a directory exists.
98
+ * @param {string} dirPath
99
+ * @returns {boolean}
100
+ */
101
+ export function dirExists(dirPath) {
102
+ return fs.existsSync(dirPath) && fs.lstatSync(dirPath).isDirectory();
103
+ }
104
+ /**
105
+ * Checks whether a directory is empty.
106
+ * @param {string} dirPath
107
+ * @returns {boolean}
108
+ */
109
+ export function isDirEmpty(dirPath) {
110
+ return fs.existsSync(dirPath) && fs.readdirSync(dirPath).length === 0;
111
+ }
112
+ /*========================*
113
+ * File Operations
114
+ *========================*/
115
+ /**
116
+ * Copies a file to a destination.
117
+ * @param {string} src
118
+ * @param {string} dest
119
+ * @param {number} [mode]
120
+ */
121
+ export function ensureCopyFile(src, dest, mode) {
122
+ ensureDirectory(path.dirname(dest));
123
+ fs.copyFileSync(src, dest, mode);
124
+ }
125
+ /**
126
+ * Deletes a file (If the file exists).
127
+ * @param {string} filePath
128
+ * @returns {boolean}
129
+ */
130
+ export function tryDeleteFile(filePath) {
131
+ if (fileExists(filePath)) {
132
+ fs.unlinkSync(filePath);
133
+ return true;
134
+ }
135
+ return false;
136
+ }
137
+ /**
138
+ * Copies a file to a destination.
139
+ * @param {string} src
140
+ * @param {string} dest
141
+ * @param {number} [mode]
142
+ * @returns {Promise<void>}
143
+ */
144
+ export function ensureCopyFileAsync(src, dest, mode) {
145
+ ensureDirectory(path.dirname(dest));
146
+ return fs.promises.copyFile(src, dest, mode);
147
+ }
148
+ /**
149
+ * Deletes a file (If the file exists).
150
+ * @param {string} filePath
151
+ * @returns {Promise<boolean>}
152
+ */
153
+ export async function tryDeleteFileAsync(filePath) {
154
+ if (fileExists(filePath)) {
155
+ await fs.promises.unlink(filePath);
156
+ return true;
157
+ }
158
+ return false;
159
+ }
160
+ /*========================*
161
+ * Text Operations
162
+ *========================*/
163
+ /**
164
+ * Writes text to a file (Ensures that the directory exists, creating it recursively if needed).
165
+ * @param {string} filePath
166
+ * @param {string} content
167
+ * @param {fs.WriteFileOptions} [ops='utf-8']
168
+ */
169
+ export function writeTextFile(filePath, content, ops = 'utf-8') {
170
+ const dir = path.dirname(filePath);
171
+ ensureDirectory(dir);
172
+ fs.writeFileSync(filePath, content, ops);
173
+ }
174
+ /**
175
+ * Writes text to a file (Ensures that the directory exists, creating it recursively if needed).
176
+ * @param {string} filePath
177
+ * @param {string} content
178
+ * @param {fs.WriteFileOptions} [ops='utf-8']
179
+ * @returns {Promise<void>}
180
+ */
181
+ export function writeTextFileAsync(filePath, content, ops = 'utf-8') {
182
+ const dir = path.dirname(filePath);
183
+ ensureDirectory(dir);
184
+ return fs.promises.writeFile(filePath, content, ops);
185
+ }
186
+ /*========================*
187
+ * Directory Listings
188
+ *========================*/
189
+ /**
190
+ * Lists all files and dirs in a directory (optionally recursive).
191
+ * @param {string} dirPath
192
+ * @param {boolean} [recursive=false]
193
+ * @returns {{ files: string[]; dirs: string[] }}
194
+ */
195
+ export function listFiles(dirPath, recursive = false) {
196
+ /** @type {{ files: string[]; dirs: string[] }} */
197
+ const results = { files: [], dirs: [] };
198
+ if (!dirExists(dirPath))
199
+ return results;
200
+ const entries = fs.readdirSync(dirPath);
201
+ for (const entry of entries) {
202
+ const fullPath = path.join(dirPath, entry);
203
+ const stat = fs.lstatSync(fullPath);
204
+ if (stat.isDirectory()) {
205
+ results.dirs.push(fullPath);
206
+ if (recursive) {
207
+ const results2 = listFiles(fullPath, true);
208
+ results.files.push(...results2.files);
209
+ results.dirs.push(...results2.dirs);
210
+ }
211
+ }
212
+ else {
213
+ results.files.push(fullPath);
214
+ }
215
+ }
216
+ return results;
217
+ }
218
+ /**
219
+ * Lists all directories in a directory (optionally recursive).
220
+ * @param {string} dirPath
221
+ * @param {boolean} [recursive=false]
222
+ * @returns {string[]}
223
+ */
224
+ export function listDirs(dirPath, recursive = false) {
225
+ /** @type {string[]} */
226
+ const results = [];
227
+ if (!dirExists(dirPath))
228
+ return results;
229
+ const entries = fs.readdirSync(dirPath);
230
+ for (const entry of entries) {
231
+ const fullPath = path.join(dirPath, entry);
232
+ const stat = fs.lstatSync(fullPath);
233
+ if (stat.isDirectory()) {
234
+ results.push(fullPath);
235
+ if (recursive) {
236
+ results.push(...listDirs(fullPath, true));
237
+ }
238
+ }
239
+ }
240
+ return results;
241
+ }
242
+ /*========================*
243
+ * File Info
244
+ *========================*/
245
+ /**
246
+ * Returns the size of a file in bytes.
247
+ * @param {string} filePath
248
+ * @returns {number}
249
+ */
250
+ export function fileSize(filePath) {
251
+ if (!fileExists(filePath))
252
+ return 0;
253
+ const stats = fs.statSync(filePath);
254
+ return stats.size;
255
+ }
256
+ /**
257
+ * Returns the total size of a directory in bytes.
258
+ * @param {string} dirPath
259
+ * @returns {number}
260
+ */
261
+ export function dirSize(dirPath) {
262
+ let total = 0;
263
+ const files = listFiles(dirPath, true).files;
264
+ for (const file of files) {
265
+ total += fileSize(file);
266
+ }
267
+ return total;
268
+ }
269
+ /*========================*
270
+ * Backup Utilities
271
+ *========================*/
272
+ /**
273
+ * Creates a backup copy of a file with .bak timestamp suffix.
274
+ * @param {string} filePath
275
+ * @param {string} [ext='bak']
276
+ */
277
+ export function backupFile(filePath, ext = 'bak') {
278
+ if (!fileExists(filePath))
279
+ return;
280
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
281
+ const backupPath = `${filePath}.${ext}.${timestamp}`;
282
+ ensureCopyFile(filePath, backupPath);
283
+ }
284
+ /**
285
+ * Returns the most recent backup file path for a given file.
286
+ * @param {string} filePath
287
+ * @param {string} [ext='bak']
288
+ * @returns {string} Full path to the most recent backup
289
+ */
290
+ export function getLatestBackupPath(filePath, ext = 'bak') {
291
+ const dir = path.dirname(filePath);
292
+ const baseName = path.basename(filePath);
293
+ const backups = fs
294
+ .readdirSync(dir)
295
+ .filter((name) => name.startsWith(`${baseName}.${ext}.`))
296
+ .sort()
297
+ .reverse();
298
+ if (backups.length === 0)
299
+ throw new Error(`No backups found for ${filePath}`);
300
+ return path.join(dir, backups[0]);
301
+ }
302
+ /**
303
+ * Restores the most recent backup of a file.
304
+ * @param {string} filePath
305
+ * @param {string} [ext='bak']
306
+ */
307
+ export function restoreLatestBackup(filePath, ext = 'bak') {
308
+ const latestBackup = getLatestBackupPath(filePath, ext);
309
+ ensureCopyFile(latestBackup, filePath);
310
+ }
311
+ /**
312
+ * Restores the most recent backup of a file.
313
+ * @param {string} filePath
314
+ * @param {string} [ext='bak']
315
+ * @returns {Promise<void>}
316
+ */
317
+ export function restoreLatestBackupAsync(filePath, ext = 'bak') {
318
+ const latestBackup = getLatestBackupPath(filePath, ext);
319
+ return ensureCopyFileAsync(latestBackup, filePath);
320
+ }
321
+ /**
322
+ * Creates a backup copy of a file with .bak timestamp suffix.
323
+ * @param {string} filePath
324
+ * @param {string} [ext='bak']
325
+ * @returns {Promise<void>}
326
+ */
327
+ export async function backupFileAsync(filePath, ext = 'bak') {
328
+ if (!fileExists(filePath))
329
+ return;
330
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
331
+ const backupPath = `${filePath}.${ext}.${timestamp}`;
332
+ return ensureCopyFileAsync(filePath, backupPath);
333
+ }
334
+ /*========================*
335
+ * Rename Utilities
336
+ *========================*/
337
+ /**
338
+ * Renames multiple files in a directory using a rename function.
339
+ * @param {string} dirPath - The target directory.
340
+ * @param {(original: string, index: number) => string} renameFn - Function that returns new filename.
341
+ * @param {string[]} [extensions] - Optional: Only rename files with these extensions.
342
+ *
343
+ * @throws {TypeError} If any argument has an invalid type.
344
+ * @throws {Error} If the directory does not exist or contains invalid files.
345
+ */
346
+ export function renameFileBatch(dirPath, renameFn, extensions = []) {
347
+ // Validate types
348
+ if (typeof dirPath !== 'string')
349
+ throw new TypeError('dirPath must be a string');
350
+ if (typeof renameFn !== 'function')
351
+ throw new TypeError('renameFn must be a function');
352
+ if (!Array.isArray(extensions))
353
+ throw new TypeError('extensions must be an array of strings');
354
+ if (!fs.existsSync(dirPath) || !fs.statSync(dirPath).isDirectory())
355
+ throw new Error(`Directory not found or invalid: ${dirPath}`);
356
+ for (const ext of extensions) {
357
+ if (typeof ext !== 'string' || !ext.startsWith('.'))
358
+ throw new TypeError(`Invalid extension: ${ext}`);
359
+ }
360
+ const files = listFiles(dirPath).files;
361
+ let index = 0;
362
+ for (const file of files) {
363
+ const ext = path.extname(file);
364
+ if (extensions.length && !extensions.includes(ext))
365
+ continue;
366
+ const originalName = path.basename(file);
367
+ const newName = renameFn(originalName, index++);
368
+ const newPath = path.join(dirPath, newName);
369
+ if (originalName === newName)
370
+ continue;
371
+ fs.renameSync(file, newPath);
372
+ }
373
+ }
374
+ /**
375
+ * Renames files using regex replacement.
376
+ * @param {string} dirPath
377
+ * @param {RegExp} pattern - Regex to match in the filename.
378
+ * @param {string} replacement - Replacement string.
379
+ * @param {string[]} [extensions]
380
+ */
381
+ export function renameFileRegex(dirPath, pattern, replacement, extensions = []) {
382
+ renameFileBatch(dirPath, (filename) => {
383
+ const ext = path.extname(filename);
384
+ const name = path.basename(filename, ext).replace(pattern, replacement);
385
+ return `${name}${ext}`;
386
+ }, extensions);
387
+ }
388
+ /**
389
+ * Adds a prefix or suffix to filenames.
390
+ * @param {string} dirPath
391
+ * @param {{ prefix?: string, suffix?: string }} options
392
+ * @param {string[]} [extensions]
393
+ */
394
+ export function renameFileAddPrefixSuffix(dirPath, { prefix = '', suffix = '' }, extensions = []) {
395
+ renameFileBatch(dirPath, (filename) => {
396
+ const ext = path.extname(filename);
397
+ const name = path.basename(filename, ext);
398
+ return `${prefix}${name}${suffix}${ext}`;
399
+ }, extensions);
400
+ }
401
+ /**
402
+ * Normalizes all filenames to lowercase (or uppercase).
403
+ * @param {string} dirPath
404
+ * @param {'lower' | 'upper' | 'title'} mode
405
+ * @param {string[]} [extensions]
406
+ * @param {boolean} [normalizeExt=false] - Whether to apply case change to file extensions too.
407
+ * @throws {Error} If mode is invalid.
408
+ */
409
+ export function renameFileNormalizeCase(dirPath, mode = 'lower', extensions = [], normalizeExt = false) {
410
+ if (typeof mode !== 'string' || !['lower', 'upper', 'title'].includes(mode))
411
+ throw new Error(`Invalid mode "${mode}". Must be 'lower', 'upper' or 'title'.`);
412
+ renameFileBatch(dirPath, (filename) => {
413
+ /**
414
+ * @param {string} text
415
+ * @returns {string}
416
+ */
417
+ const changeToMode = (text) => {
418
+ if (mode === 'lower')
419
+ return text.toLowerCase();
420
+ else if (mode === 'upper')
421
+ return text.toUpperCase();
422
+ else if (mode === 'title')
423
+ return toTitleCase(text);
424
+ else
425
+ return text;
426
+ };
427
+ const rawExt = path.extname(filename);
428
+ const ext = normalizeExt ? changeToMode(rawExt) : rawExt;
429
+ const name = changeToMode(path.basename(filename, rawExt));
430
+ return `${name}${ext}`;
431
+ }, extensions);
432
+ }
433
+ /**
434
+ * Pads numbers in filenames (e.g., "img1.jpg" -> "img001.jpg").
435
+ * @param {string} dirPath
436
+ * @param {number} totalDigits
437
+ * @param {string[]} [extensions]
438
+ */
439
+ export function renameFilePadNumbers(dirPath, totalDigits = 3, extensions = []) {
440
+ renameFileBatch(dirPath, (filename) => {
441
+ return filename.replace(/\d+/, (match) => match.padStart(totalDigits, '0'));
442
+ }, extensions);
443
+ }