@simplysm/core-node 13.0.76 → 13.0.77
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.
- package/README.md +353 -44
- package/dist/features/fs-watcher.d.ts.map +1 -1
- package/dist/features/fs-watcher.js +2 -2
- package/dist/features/fs-watcher.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -2
- package/dist/index.js.map +1 -1
- package/dist/utils/fs.d.ts +30 -30
- package/dist/utils/fs.d.ts.map +1 -1
- package/dist/utils/fs.js +101 -99
- package/dist/utils/fs.js.map +1 -1
- package/dist/utils/path.d.ts +19 -19
- package/dist/utils/path.d.ts.map +1 -1
- package/dist/utils/path.js +17 -17
- package/dist/utils/path.js.map +1 -1
- package/dist/worker/create-worker.d.ts +1 -1
- package/dist/worker/create-worker.d.ts.map +1 -1
- package/dist/worker/create-worker.js +8 -8
- package/dist/worker/create-worker.js.map +1 -1
- package/dist/worker/types.d.ts +2 -2
- package/dist/worker/types.d.ts.map +1 -1
- package/dist/worker/worker.js +4 -4
- package/dist/worker/worker.js.map +1 -1
- package/package.json +2 -2
- package/src/features/fs-watcher.ts +2 -2
- package/src/index.ts +2 -2
- package/src/utils/fs.ts +570 -562
- package/src/utils/path.ts +24 -24
- package/src/worker/create-worker.ts +9 -9
- package/src/worker/types.ts +6 -6
- package/src/worker/worker.ts +4 -4
- package/tests/utils/fs.spec.ts +123 -123
- package/tests/utils/path.spec.ts +55 -55
package/src/utils/fs.ts
CHANGED
|
@@ -1,562 +1,570 @@
|
|
|
1
|
-
import path from "path";
|
|
2
|
-
import fs from "fs";
|
|
3
|
-
import os from "os";
|
|
4
|
-
import { glob as globRaw, type GlobOptions, globSync as globRawSync } from "glob";
|
|
5
|
-
import {
|
|
6
|
-
import "@simplysm/core-common";
|
|
7
|
-
|
|
8
|
-
//#region Existence Check
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Checks if a file or directory exists (synchronous).
|
|
12
|
-
* @param targetPath - Path to check
|
|
13
|
-
*/
|
|
14
|
-
export function
|
|
15
|
-
return fs.existsSync(targetPath);
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Checks if a file or directory exists (asynchronous).
|
|
20
|
-
* @param targetPath - Path to check
|
|
21
|
-
*/
|
|
22
|
-
export async function
|
|
23
|
-
try {
|
|
24
|
-
await fs.promises.access(targetPath);
|
|
25
|
-
return true;
|
|
26
|
-
} catch {
|
|
27
|
-
return false;
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
//#endregion
|
|
32
|
-
|
|
33
|
-
//#region Create Directory
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Creates a directory (recursive).
|
|
37
|
-
* @param targetPath - Directory path to create
|
|
38
|
-
*/
|
|
39
|
-
export function
|
|
40
|
-
try {
|
|
41
|
-
fs.mkdirSync(targetPath, { recursive: true });
|
|
42
|
-
} catch (err) {
|
|
43
|
-
throw new SdError(err, targetPath);
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* Creates a directory (recursive, asynchronous).
|
|
49
|
-
* @param targetPath - Directory path to create
|
|
50
|
-
*/
|
|
51
|
-
export async function
|
|
52
|
-
try {
|
|
53
|
-
await fs.promises.mkdir(targetPath, { recursive: true });
|
|
54
|
-
} catch (err) {
|
|
55
|
-
throw new SdError(err, targetPath);
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
//#endregion
|
|
60
|
-
|
|
61
|
-
//#region Delete
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* Deletes a file or directory.
|
|
65
|
-
* @param targetPath - Path to delete
|
|
66
|
-
* @remarks The synchronous version fails immediately without retries. Use
|
|
67
|
-
*/
|
|
68
|
-
export function
|
|
69
|
-
try {
|
|
70
|
-
fs.rmSync(targetPath, { recursive: true, force: true });
|
|
71
|
-
} catch (err) {
|
|
72
|
-
throw new SdError(err, targetPath);
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
/**
|
|
77
|
-
* Deletes a file or directory (asynchronous).
|
|
78
|
-
* @param targetPath - Path to delete
|
|
79
|
-
* @remarks The asynchronous version retries up to 6 times (500ms interval) for transient errors like file locks.
|
|
80
|
-
*/
|
|
81
|
-
export async function
|
|
82
|
-
try {
|
|
83
|
-
await fs.promises.rm(targetPath, {
|
|
84
|
-
recursive: true,
|
|
85
|
-
force: true,
|
|
86
|
-
retryDelay: 500,
|
|
87
|
-
maxRetries: 6,
|
|
88
|
-
});
|
|
89
|
-
} catch (err) {
|
|
90
|
-
throw new SdError(err, targetPath);
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
//#endregion
|
|
95
|
-
|
|
96
|
-
//#region Copy
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
}
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
1
|
+
import path from "path";
|
|
2
|
+
import fs from "fs";
|
|
3
|
+
import os from "os";
|
|
4
|
+
import { glob as globRaw, type GlobOptions, globSync as globRawSync } from "glob";
|
|
5
|
+
import { json, SdError } from "@simplysm/core-common";
|
|
6
|
+
import "@simplysm/core-common";
|
|
7
|
+
|
|
8
|
+
//#region Existence Check
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Checks if a file or directory exists (synchronous).
|
|
12
|
+
* @param targetPath - Path to check
|
|
13
|
+
*/
|
|
14
|
+
export function existsSync(targetPath: string): boolean {
|
|
15
|
+
return fs.existsSync(targetPath);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Checks if a file or directory exists (asynchronous).
|
|
20
|
+
* @param targetPath - Path to check
|
|
21
|
+
*/
|
|
22
|
+
export async function exists(targetPath: string): Promise<boolean> {
|
|
23
|
+
try {
|
|
24
|
+
await fs.promises.access(targetPath);
|
|
25
|
+
return true;
|
|
26
|
+
} catch {
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
//#endregion
|
|
32
|
+
|
|
33
|
+
//#region Create Directory
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Creates a directory (recursive).
|
|
37
|
+
* @param targetPath - Directory path to create
|
|
38
|
+
*/
|
|
39
|
+
export function mkdirSync(targetPath: string): void {
|
|
40
|
+
try {
|
|
41
|
+
fs.mkdirSync(targetPath, { recursive: true });
|
|
42
|
+
} catch (err) {
|
|
43
|
+
throw new SdError(err, targetPath);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Creates a directory (recursive, asynchronous).
|
|
49
|
+
* @param targetPath - Directory path to create
|
|
50
|
+
*/
|
|
51
|
+
export async function mkdir(targetPath: string): Promise<void> {
|
|
52
|
+
try {
|
|
53
|
+
await fs.promises.mkdir(targetPath, { recursive: true });
|
|
54
|
+
} catch (err) {
|
|
55
|
+
throw new SdError(err, targetPath);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
//#endregion
|
|
60
|
+
|
|
61
|
+
//#region Delete
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Deletes a file or directory.
|
|
65
|
+
* @param targetPath - Path to delete
|
|
66
|
+
* @remarks The synchronous version fails immediately without retries. Use rm for cases with potential transient errors like file locks.
|
|
67
|
+
*/
|
|
68
|
+
export function rmSync(targetPath: string): void {
|
|
69
|
+
try {
|
|
70
|
+
fs.rmSync(targetPath, { recursive: true, force: true });
|
|
71
|
+
} catch (err) {
|
|
72
|
+
throw new SdError(err, targetPath);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Deletes a file or directory (asynchronous).
|
|
78
|
+
* @param targetPath - Path to delete
|
|
79
|
+
* @remarks The asynchronous version retries up to 6 times (500ms interval) for transient errors like file locks.
|
|
80
|
+
*/
|
|
81
|
+
export async function rm(targetPath: string): Promise<void> {
|
|
82
|
+
try {
|
|
83
|
+
await fs.promises.rm(targetPath, {
|
|
84
|
+
recursive: true,
|
|
85
|
+
force: true,
|
|
86
|
+
retryDelay: 500,
|
|
87
|
+
maxRetries: 6,
|
|
88
|
+
});
|
|
89
|
+
} catch (err) {
|
|
90
|
+
throw new SdError(err, targetPath);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
//#endregion
|
|
95
|
+
|
|
96
|
+
//#region Copy
|
|
97
|
+
|
|
98
|
+
interface CopyEntry {
|
|
99
|
+
sourcePath: string;
|
|
100
|
+
targetPath: string;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function collectCopyEntries(
|
|
104
|
+
sourcePath: string,
|
|
105
|
+
targetPath: string,
|
|
106
|
+
children: string[],
|
|
107
|
+
filter?: (absolutePath: string) => boolean,
|
|
108
|
+
): CopyEntry[] {
|
|
109
|
+
const entries: CopyEntry[] = [];
|
|
110
|
+
for (const childPath of children) {
|
|
111
|
+
if (filter !== undefined && !filter(childPath)) {
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
const relativeChildPath = path.relative(sourcePath, childPath);
|
|
115
|
+
const childTargetPath = path.resolve(targetPath, relativeChildPath);
|
|
116
|
+
entries.push({ sourcePath: childPath, targetPath: childTargetPath });
|
|
117
|
+
}
|
|
118
|
+
return entries;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Copies a file or directory.
|
|
123
|
+
*
|
|
124
|
+
* If sourcePath does not exist, no action is performed and the function returns.
|
|
125
|
+
*
|
|
126
|
+
* @param sourcePath Path of the source to copy
|
|
127
|
+
* @param targetPath Destination path for the copy
|
|
128
|
+
* @param filter A filter function that determines whether to copy.
|
|
129
|
+
* The **absolute path** of each file/directory is passed.
|
|
130
|
+
* Returns true to copy, false to exclude.
|
|
131
|
+
* **Note**: The top-level sourcePath is not subject to filtering;
|
|
132
|
+
* the filter function is applied recursively to all children (direct and indirect).
|
|
133
|
+
* Returning false for a directory skips that directory and all its contents.
|
|
134
|
+
*/
|
|
135
|
+
export function copySync(
|
|
136
|
+
sourcePath: string,
|
|
137
|
+
targetPath: string,
|
|
138
|
+
filter?: (absolutePath: string) => boolean,
|
|
139
|
+
): void {
|
|
140
|
+
if (!existsSync(sourcePath)) {
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
let stats: fs.Stats;
|
|
145
|
+
try {
|
|
146
|
+
stats = fs.lstatSync(sourcePath);
|
|
147
|
+
} catch (err) {
|
|
148
|
+
throw new SdError(err, sourcePath);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (stats.isDirectory()) {
|
|
152
|
+
mkdirSync(targetPath);
|
|
153
|
+
const children = globSync(path.resolve(sourcePath, "*"), { dot: true });
|
|
154
|
+
for (const entry of collectCopyEntries(sourcePath, targetPath, children, filter)) {
|
|
155
|
+
copySync(entry.sourcePath, entry.targetPath, filter);
|
|
156
|
+
}
|
|
157
|
+
} else {
|
|
158
|
+
mkdirSync(path.dirname(targetPath));
|
|
159
|
+
|
|
160
|
+
try {
|
|
161
|
+
fs.copyFileSync(sourcePath, targetPath);
|
|
162
|
+
} catch (err) {
|
|
163
|
+
throw new SdError(err, targetPath);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Copies a file or directory (asynchronous).
|
|
170
|
+
*
|
|
171
|
+
* If sourcePath does not exist, no action is performed and the function returns.
|
|
172
|
+
*
|
|
173
|
+
* @param sourcePath Path of the source to copy
|
|
174
|
+
* @param targetPath Destination path for the copy
|
|
175
|
+
* @param filter A filter function that determines whether to copy.
|
|
176
|
+
* The **absolute path** of each file/directory is passed.
|
|
177
|
+
* Returns true to copy, false to exclude.
|
|
178
|
+
* **Note**: The top-level sourcePath is not subject to filtering;
|
|
179
|
+
* the filter function is applied recursively to all children (direct and indirect).
|
|
180
|
+
* Returning false for a directory skips that directory and all its contents.
|
|
181
|
+
*/
|
|
182
|
+
export async function copy(
|
|
183
|
+
sourcePath: string,
|
|
184
|
+
targetPath: string,
|
|
185
|
+
filter?: (absolutePath: string) => boolean,
|
|
186
|
+
): Promise<void> {
|
|
187
|
+
if (!(await exists(sourcePath))) {
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
let stats: fs.Stats;
|
|
192
|
+
try {
|
|
193
|
+
stats = await fs.promises.lstat(sourcePath);
|
|
194
|
+
} catch (err) {
|
|
195
|
+
throw new SdError(err, sourcePath);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
if (stats.isDirectory()) {
|
|
199
|
+
await mkdir(targetPath);
|
|
200
|
+
const children = await glob(path.resolve(sourcePath, "*"), { dot: true });
|
|
201
|
+
await collectCopyEntries(sourcePath, targetPath, children, filter)
|
|
202
|
+
.parallelAsync(async (entry) => {
|
|
203
|
+
await copy(entry.sourcePath, entry.targetPath, filter);
|
|
204
|
+
});
|
|
205
|
+
} else {
|
|
206
|
+
await mkdir(path.dirname(targetPath));
|
|
207
|
+
|
|
208
|
+
try {
|
|
209
|
+
await fs.promises.copyFile(sourcePath, targetPath);
|
|
210
|
+
} catch (err) {
|
|
211
|
+
throw new SdError(err, targetPath);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
//#endregion
|
|
217
|
+
|
|
218
|
+
//#region Read File
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Reads a file as a UTF-8 string.
|
|
222
|
+
* @param targetPath - Path of the file to read
|
|
223
|
+
*/
|
|
224
|
+
export function readSync(targetPath: string): string {
|
|
225
|
+
try {
|
|
226
|
+
return fs.readFileSync(targetPath, "utf-8");
|
|
227
|
+
} catch (err) {
|
|
228
|
+
throw new SdError(err, targetPath);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Reads a file as a UTF-8 string (asynchronous).
|
|
234
|
+
* @param targetPath - Path of the file to read
|
|
235
|
+
*/
|
|
236
|
+
export async function read(targetPath: string): Promise<string> {
|
|
237
|
+
try {
|
|
238
|
+
return await fs.promises.readFile(targetPath, "utf-8");
|
|
239
|
+
} catch (err) {
|
|
240
|
+
throw new SdError(err, targetPath);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Reads a file as a Buffer.
|
|
246
|
+
* @param targetPath - Path of the file to read
|
|
247
|
+
*/
|
|
248
|
+
export function readBufferSync(targetPath: string): Buffer {
|
|
249
|
+
try {
|
|
250
|
+
return fs.readFileSync(targetPath);
|
|
251
|
+
} catch (err) {
|
|
252
|
+
throw new SdError(err, targetPath);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Reads a file as a Buffer (asynchronous).
|
|
258
|
+
* @param targetPath - Path of the file to read
|
|
259
|
+
*/
|
|
260
|
+
export async function readBuffer(targetPath: string): Promise<Buffer> {
|
|
261
|
+
try {
|
|
262
|
+
return await fs.promises.readFile(targetPath);
|
|
263
|
+
} catch (err) {
|
|
264
|
+
throw new SdError(err, targetPath);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Reads a JSON file (using JsonConvert).
|
|
270
|
+
* @param targetPath - Path of the JSON file to read
|
|
271
|
+
*/
|
|
272
|
+
export function readJsonSync<TData = unknown>(targetPath: string): TData {
|
|
273
|
+
const contents = readSync(targetPath);
|
|
274
|
+
try {
|
|
275
|
+
return json.parse(contents);
|
|
276
|
+
} catch (err) {
|
|
277
|
+
const preview = contents.length > 500 ? contents.slice(0, 500) + "...(truncated)" : contents;
|
|
278
|
+
throw new SdError(err, targetPath + os.EOL + preview);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Reads a JSON file (using JsonConvert, asynchronous).
|
|
284
|
+
* @param targetPath - Path of the JSON file to read
|
|
285
|
+
*/
|
|
286
|
+
export async function readJson<TData = unknown>(targetPath: string): Promise<TData> {
|
|
287
|
+
const contents = await read(targetPath);
|
|
288
|
+
try {
|
|
289
|
+
return json.parse<TData>(contents);
|
|
290
|
+
} catch (err) {
|
|
291
|
+
const preview = contents.length > 500 ? contents.slice(0, 500) + "...(truncated)" : contents;
|
|
292
|
+
throw new SdError(err, targetPath + os.EOL + preview);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
//#endregion
|
|
297
|
+
|
|
298
|
+
//#region Write File
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Writes data to a file (auto-creates parent directories).
|
|
302
|
+
* @param targetPath - Path of the file to write
|
|
303
|
+
* @param data - Data to write (string or binary)
|
|
304
|
+
*/
|
|
305
|
+
export function writeSync(targetPath: string, data: string | Uint8Array): void {
|
|
306
|
+
mkdirSync(path.dirname(targetPath));
|
|
307
|
+
|
|
308
|
+
try {
|
|
309
|
+
fs.writeFileSync(targetPath, data, { flush: true });
|
|
310
|
+
} catch (err) {
|
|
311
|
+
throw new SdError(err, targetPath);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* Writes data to a file (auto-creates parent directories, asynchronous).
|
|
317
|
+
* @param targetPath - Path of the file to write
|
|
318
|
+
* @param data - Data to write (string or binary)
|
|
319
|
+
*/
|
|
320
|
+
export async function write(targetPath: string, data: string | Uint8Array): Promise<void> {
|
|
321
|
+
await mkdir(path.dirname(targetPath));
|
|
322
|
+
|
|
323
|
+
try {
|
|
324
|
+
await fs.promises.writeFile(targetPath, data, { flush: true });
|
|
325
|
+
} catch (err) {
|
|
326
|
+
throw new SdError(err, targetPath);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* Writes data to a JSON file (using JsonConvert).
|
|
332
|
+
* @param targetPath - Path of the JSON file to write
|
|
333
|
+
* @param data - Data to write
|
|
334
|
+
* @param options - JSON serialization options
|
|
335
|
+
*/
|
|
336
|
+
export function writeJsonSync(
|
|
337
|
+
targetPath: string,
|
|
338
|
+
data: unknown,
|
|
339
|
+
options?: {
|
|
340
|
+
replacer?: (this: unknown, key: string | undefined, value: unknown) => unknown;
|
|
341
|
+
space?: string | number;
|
|
342
|
+
},
|
|
343
|
+
): void {
|
|
344
|
+
const jsonStr = json.stringify(data, options);
|
|
345
|
+
writeSync(targetPath, jsonStr);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* Writes data to a JSON file (using JsonConvert, asynchronous).
|
|
350
|
+
* @param targetPath - Path of the JSON file to write
|
|
351
|
+
* @param data - Data to write
|
|
352
|
+
* @param options - JSON serialization options
|
|
353
|
+
*/
|
|
354
|
+
export async function writeJson(
|
|
355
|
+
targetPath: string,
|
|
356
|
+
data: unknown,
|
|
357
|
+
options?: {
|
|
358
|
+
replacer?: (this: unknown, key: string | undefined, value: unknown) => unknown;
|
|
359
|
+
space?: string | number;
|
|
360
|
+
},
|
|
361
|
+
): Promise<void> {
|
|
362
|
+
const jsonStr = json.stringify(data, options);
|
|
363
|
+
await write(targetPath, jsonStr);
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
//#endregion
|
|
367
|
+
|
|
368
|
+
//#region Read Directory
|
|
369
|
+
|
|
370
|
+
/**
|
|
371
|
+
* Reads the contents of a directory.
|
|
372
|
+
* @param targetPath - Path of the directory to read
|
|
373
|
+
*/
|
|
374
|
+
export function readdirSync(targetPath: string): string[] {
|
|
375
|
+
try {
|
|
376
|
+
return fs.readdirSync(targetPath);
|
|
377
|
+
} catch (err) {
|
|
378
|
+
throw new SdError(err, targetPath);
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
/**
|
|
383
|
+
* Reads the contents of a directory (asynchronous).
|
|
384
|
+
* @param targetPath - Path of the directory to read
|
|
385
|
+
*/
|
|
386
|
+
export async function readdir(targetPath: string): Promise<string[]> {
|
|
387
|
+
try {
|
|
388
|
+
return await fs.promises.readdir(targetPath);
|
|
389
|
+
} catch (err) {
|
|
390
|
+
throw new SdError(err, targetPath);
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
//#endregion
|
|
395
|
+
|
|
396
|
+
//#region File Information
|
|
397
|
+
|
|
398
|
+
/**
|
|
399
|
+
* Gets file/directory information (follows symbolic links).
|
|
400
|
+
* @param targetPath - Path to query information for
|
|
401
|
+
*/
|
|
402
|
+
export function statSync(targetPath: string): fs.Stats {
|
|
403
|
+
try {
|
|
404
|
+
return fs.statSync(targetPath);
|
|
405
|
+
} catch (err) {
|
|
406
|
+
throw new SdError(err, targetPath);
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
/**
|
|
411
|
+
* Gets file/directory information (follows symbolic links, asynchronous).
|
|
412
|
+
* @param targetPath - Path to query information for
|
|
413
|
+
*/
|
|
414
|
+
export async function stat(targetPath: string): Promise<fs.Stats> {
|
|
415
|
+
try {
|
|
416
|
+
return await fs.promises.stat(targetPath);
|
|
417
|
+
} catch (err) {
|
|
418
|
+
throw new SdError(err, targetPath);
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
/**
|
|
423
|
+
* Gets file/directory information (does not follow symbolic links).
|
|
424
|
+
* @param targetPath - Path to query information for
|
|
425
|
+
*/
|
|
426
|
+
export function lstatSync(targetPath: string): fs.Stats {
|
|
427
|
+
try {
|
|
428
|
+
return fs.lstatSync(targetPath);
|
|
429
|
+
} catch (err) {
|
|
430
|
+
throw new SdError(err, targetPath);
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
/**
|
|
435
|
+
* Gets file/directory information (does not follow symbolic links, asynchronous).
|
|
436
|
+
* @param targetPath - Path to query information for
|
|
437
|
+
*/
|
|
438
|
+
export async function lstat(targetPath: string): Promise<fs.Stats> {
|
|
439
|
+
try {
|
|
440
|
+
return await fs.promises.lstat(targetPath);
|
|
441
|
+
} catch (err) {
|
|
442
|
+
throw new SdError(err, targetPath);
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
//#endregion
|
|
447
|
+
|
|
448
|
+
//#region Glob
|
|
449
|
+
|
|
450
|
+
/**
|
|
451
|
+
* Searches for files using a glob pattern.
|
|
452
|
+
* @param pattern - Glob pattern (e.g., "**\/*.ts")
|
|
453
|
+
* @param options - glob options
|
|
454
|
+
* @returns Array of absolute paths for matched files
|
|
455
|
+
*/
|
|
456
|
+
export function globSync(pattern: string, options?: GlobOptions): string[] {
|
|
457
|
+
return globRawSync(pattern.replace(/\\/g, "/"), options ?? {}).map((item) =>
|
|
458
|
+
path.resolve(item.toString()),
|
|
459
|
+
);
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
/**
|
|
463
|
+
* Searches for files using a glob pattern (asynchronous).
|
|
464
|
+
* @param pattern - Glob pattern (e.g., "**\/*.ts")
|
|
465
|
+
* @param options - glob options
|
|
466
|
+
* @returns Array of absolute paths for matched files
|
|
467
|
+
*/
|
|
468
|
+
export async function glob(pattern: string, options?: GlobOptions): Promise<string[]> {
|
|
469
|
+
return (await globRaw(pattern.replace(/\\/g, "/"), options ?? {})).map((item) =>
|
|
470
|
+
path.resolve(item.toString()),
|
|
471
|
+
);
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
//#endregion
|
|
475
|
+
|
|
476
|
+
//#region Utilities
|
|
477
|
+
|
|
478
|
+
/**
|
|
479
|
+
* Recursively searches and deletes empty directories under a specified directory.
|
|
480
|
+
* If all child directories are deleted and a parent becomes empty, it will also be deleted.
|
|
481
|
+
*/
|
|
482
|
+
export async function clearEmptyDirectory(dirPath: string): Promise<void> {
|
|
483
|
+
if (!(await exists(dirPath))) return;
|
|
484
|
+
|
|
485
|
+
const childNames = await readdir(dirPath);
|
|
486
|
+
let hasFiles = false;
|
|
487
|
+
|
|
488
|
+
for (const childName of childNames) {
|
|
489
|
+
const childPath = path.resolve(dirPath, childName);
|
|
490
|
+
if ((await lstat(childPath)).isDirectory()) {
|
|
491
|
+
await clearEmptyDirectory(childPath);
|
|
492
|
+
} else {
|
|
493
|
+
hasFiles = true;
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
// If there are files, cannot delete
|
|
498
|
+
if (hasFiles) return;
|
|
499
|
+
|
|
500
|
+
// Only re-check if there were no files (child directories may have been deleted)
|
|
501
|
+
if ((await readdir(dirPath)).length === 0) {
|
|
502
|
+
await rm(dirPath);
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
/**
|
|
507
|
+
* Searches for files matching a glob pattern by traversing parent directories from a start path towards the root.
|
|
508
|
+
* Collects all file paths matching the childGlob pattern in each directory.
|
|
509
|
+
* @param childGlob - Glob pattern to search for in each directory
|
|
510
|
+
* @param fromPath - Path to start searching from
|
|
511
|
+
* @param rootPath - Path to stop searching at (if not specified, searches to filesystem root).
|
|
512
|
+
* **Note**: fromPath must be a child path of rootPath.
|
|
513
|
+
* Otherwise, searches to the filesystem root.
|
|
514
|
+
*/
|
|
515
|
+
export function findAllParentChildPathsSync(
|
|
516
|
+
childGlob: string,
|
|
517
|
+
fromPath: string,
|
|
518
|
+
rootPath?: string,
|
|
519
|
+
): string[] {
|
|
520
|
+
const resultPaths: string[] = [];
|
|
521
|
+
|
|
522
|
+
let current = fromPath;
|
|
523
|
+
while (current) {
|
|
524
|
+
const potential = path.resolve(current, childGlob);
|
|
525
|
+
const globResults = globSync(potential);
|
|
526
|
+
resultPaths.push(...globResults);
|
|
527
|
+
|
|
528
|
+
if (current === rootPath) break;
|
|
529
|
+
|
|
530
|
+
const next = path.dirname(current);
|
|
531
|
+
if (next === current) break;
|
|
532
|
+
current = next;
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
return resultPaths;
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
/**
|
|
539
|
+
* Searches for files matching a glob pattern by traversing parent directories from a start path towards the root (asynchronous).
|
|
540
|
+
* Collects all file paths matching the childGlob pattern in each directory.
|
|
541
|
+
* @param childGlob - Glob pattern to search for in each directory
|
|
542
|
+
* @param fromPath - Path to start searching from
|
|
543
|
+
* @param rootPath - Path to stop searching at (if not specified, searches to filesystem root).
|
|
544
|
+
* **Note**: fromPath must be a child path of rootPath.
|
|
545
|
+
* Otherwise, searches to the filesystem root.
|
|
546
|
+
*/
|
|
547
|
+
export async function findAllParentChildPaths(
|
|
548
|
+
childGlob: string,
|
|
549
|
+
fromPath: string,
|
|
550
|
+
rootPath?: string,
|
|
551
|
+
): Promise<string[]> {
|
|
552
|
+
const resultPaths: string[] = [];
|
|
553
|
+
|
|
554
|
+
let current = fromPath;
|
|
555
|
+
while (current) {
|
|
556
|
+
const potential = path.resolve(current, childGlob);
|
|
557
|
+
const globResults = await glob(potential);
|
|
558
|
+
resultPaths.push(...globResults);
|
|
559
|
+
|
|
560
|
+
if (current === rootPath) break;
|
|
561
|
+
|
|
562
|
+
const next = path.dirname(current);
|
|
563
|
+
if (next === current) break;
|
|
564
|
+
current = next;
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
return resultPaths;
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
//#endregion
|