@holoscript/std 6.0.3 → 7.0.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 (69) hide show
  1. package/dist/{chunk-7HVUYGPS.js → chunk-DZHAVOCH.js} +12 -5
  2. package/dist/chunk-DZHAVOCH.js.map +1 -0
  3. package/dist/chunk-PR4QN5HX.js +43 -0
  4. package/dist/chunk-PR4QN5HX.js.map +1 -0
  5. package/dist/chunk-XBA4ARST.js +9313 -0
  6. package/dist/chunk-XBA4ARST.js.map +1 -0
  7. package/dist/{chunk-W2Q3LUCM.js → chunk-XJIFG7G3.js} +3 -3
  8. package/dist/chunk-XJIFG7G3.js.map +1 -0
  9. package/dist/collections.js +1 -0
  10. package/dist/fs/__tests__/file-watch-system.test.d.ts +2 -0
  11. package/dist/fs/__tests__/file-watch-system.test.d.ts.map +1 -0
  12. package/dist/fs/__tests__/path-utilities.test.d.ts +2 -0
  13. package/dist/fs/__tests__/path-utilities.test.d.ts.map +1 -0
  14. package/dist/fs/__tests__/path.test.d.ts +2 -0
  15. package/dist/fs/__tests__/path.test.d.ts.map +1 -0
  16. package/dist/fs/fs.d.ts +288 -0
  17. package/dist/fs/fs.d.ts.map +1 -0
  18. package/dist/fs/index.cjs +9338 -0
  19. package/dist/fs/index.cjs.map +1 -0
  20. package/dist/fs/index.d.ts +51 -0
  21. package/dist/fs/index.d.ts.map +1 -0
  22. package/dist/fs/index.js +204 -0
  23. package/dist/fs/index.js.map +1 -0
  24. package/dist/fs/path.d.ts +135 -0
  25. package/dist/fs/path.d.ts.map +1 -0
  26. package/dist/fs/watch.d.ts +133 -0
  27. package/dist/fs/watch.d.ts.map +1 -0
  28. package/dist/index.cjs +12660 -3435
  29. package/dist/index.cjs.map +1 -1
  30. package/dist/index.d.ts +1 -0
  31. package/dist/index.d.ts.map +1 -1
  32. package/dist/index.js +8 -3
  33. package/dist/index.js.map +1 -1
  34. package/dist/math.cjs +6 -2
  35. package/dist/math.cjs.map +1 -1
  36. package/dist/math.js +2 -1
  37. package/dist/string.cjs +2 -2
  38. package/dist/string.cjs.map +1 -1
  39. package/dist/string.js +2 -1
  40. package/dist/time.js +1 -0
  41. package/dist/traits/EconomicPrimitives.js +1 -0
  42. package/dist/traits/EconomicTraits.js +1 -0
  43. package/dist/traits/VRRTraits.d.ts +2 -2
  44. package/dist/types.d.ts.map +1 -1
  45. package/package.json +7 -2
  46. package/src/__tests__/index.test.ts +8 -8
  47. package/src/__tests__/standard-library-fundamentals.test.ts +21 -4
  48. package/src/__tests__/string-decoupled.test.ts +6 -7
  49. package/src/fs/__tests__/file-watch-system.test.ts +675 -0
  50. package/src/fs/__tests__/path-utilities.test.ts +375 -0
  51. package/src/fs/__tests__/path.test.ts +287 -0
  52. package/src/fs/fs.ts +692 -0
  53. package/src/fs/index.ts +194 -0
  54. package/src/fs/path.ts +310 -0
  55. package/src/fs/watch.ts +413 -0
  56. package/src/index.ts +3 -1
  57. package/src/math.ts +1 -1
  58. package/src/string.ts +2 -2
  59. package/src/traits/ARTraits.ts +2 -2
  60. package/src/traits/IoTTraits.ts +2 -2
  61. package/src/traits/VRRTraits.ts +2 -2
  62. package/src/traits/__tests__/ARTraits.test.ts +3 -9
  63. package/src/traits/__tests__/EconomicPrimitives.test.ts +7 -7
  64. package/src/traits/__tests__/EconomicTraits.test.ts +9 -7
  65. package/src/types.ts +10 -3
  66. package/tsup.config.ts +3 -0
  67. package/CHANGELOG.md +0 -7
  68. package/dist/chunk-7HVUYGPS.js.map +0 -1
  69. package/dist/chunk-W2Q3LUCM.js.map +0 -1
package/src/fs/fs.ts ADDED
@@ -0,0 +1,692 @@
1
+ /**
2
+ * @holoscript/fs - File System Module
3
+ *
4
+ * File and directory operations for HoloScript Plus programs.
5
+ */
6
+
7
+ import * as fs from 'fs';
8
+ import * as fsPromises from 'fs/promises';
9
+ import * as nodePath from 'path';
10
+ import { glob as globAsync } from 'glob';
11
+
12
+ export type Encoding = BufferEncoding;
13
+
14
+ /**
15
+ * File stats information
16
+ */
17
+ export interface FileStats {
18
+ size: number;
19
+ isFile: boolean;
20
+ isDirectory: boolean;
21
+ isSymlink: boolean;
22
+ createdAt: Date;
23
+ modifiedAt: Date;
24
+ accessedAt: Date;
25
+ mode: number;
26
+ }
27
+
28
+ /**
29
+ * Directory entry
30
+ */
31
+ export interface DirEntry {
32
+ name: string;
33
+ path: string;
34
+ isFile: boolean;
35
+ isDirectory: boolean;
36
+ isSymlink: boolean;
37
+ }
38
+
39
+ /**
40
+ * Read options
41
+ */
42
+ export interface ReadOptions {
43
+ encoding?: Encoding;
44
+ }
45
+
46
+ /**
47
+ * Write options
48
+ */
49
+ export interface WriteOptions {
50
+ encoding?: Encoding;
51
+ mode?: number;
52
+ flag?: string;
53
+ append?: boolean;
54
+ }
55
+
56
+ /**
57
+ * Copy options
58
+ */
59
+ export interface CopyOptions {
60
+ overwrite?: boolean;
61
+ recursive?: boolean;
62
+ filter?: (src: string) => boolean;
63
+ }
64
+
65
+ // ============================================================================
66
+ // File Reading
67
+ // ============================================================================
68
+
69
+ /**
70
+ * Read a file as text
71
+ */
72
+ export async function readText(path: string, encoding: Encoding = 'utf-8'): Promise<string> {
73
+ return fsPromises.readFile(path, { encoding });
74
+ }
75
+
76
+ /**
77
+ * Read a file as text (sync)
78
+ */
79
+ export function readTextSync(path: string, encoding: Encoding = 'utf-8'): string {
80
+ return fs.readFileSync(path, { encoding });
81
+ }
82
+
83
+ /**
84
+ * Read a file as bytes
85
+ */
86
+ export async function readBytes(path: string): Promise<Buffer> {
87
+ return fsPromises.readFile(path);
88
+ }
89
+
90
+ /**
91
+ * Read a file as bytes (sync)
92
+ */
93
+ export function readBytesSync(path: string): Buffer {
94
+ return fs.readFileSync(path);
95
+ }
96
+
97
+ /**
98
+ * Read a JSON file
99
+ */
100
+ export async function readJson<T = unknown>(path: string): Promise<T> {
101
+ const text = await readText(path);
102
+ return JSON.parse(text);
103
+ }
104
+
105
+ /**
106
+ * Read a JSON file (sync)
107
+ */
108
+ export function readJsonSync<T = unknown>(path: string): T {
109
+ const text = readTextSync(path);
110
+ return JSON.parse(text);
111
+ }
112
+
113
+ /**
114
+ * Read file lines as an array
115
+ */
116
+ export async function readLines(path: string, encoding: Encoding = 'utf-8'): Promise<string[]> {
117
+ const text = await readText(path, encoding);
118
+ return text.split(/\r?\n/);
119
+ }
120
+
121
+ /**
122
+ * Read file lines as an array (sync)
123
+ */
124
+ export function readLinesSync(path: string, encoding: Encoding = 'utf-8'): string[] {
125
+ const text = readTextSync(path, encoding);
126
+ return text.split(/\r?\n/);
127
+ }
128
+
129
+ // ============================================================================
130
+ // File Writing
131
+ // ============================================================================
132
+
133
+ /**
134
+ * Write text to a file
135
+ */
136
+ export async function writeText(
137
+ path: string,
138
+ content: string,
139
+ options: WriteOptions = {}
140
+ ): Promise<void> {
141
+ const { encoding = 'utf-8', mode, flag = options.append ? 'a' : 'w' } = options;
142
+ await ensureDir(nodePath.dirname(path));
143
+ await fsPromises.writeFile(path, content, { encoding, mode, flag });
144
+ }
145
+
146
+ /**
147
+ * Write text to a file (sync)
148
+ */
149
+ export function writeTextSync(path: string, content: string, options: WriteOptions = {}): void {
150
+ const { encoding = 'utf-8', mode, flag = options.append ? 'a' : 'w' } = options;
151
+ ensureDirSync(nodePath.dirname(path));
152
+ fs.writeFileSync(path, content, { encoding, mode, flag });
153
+ }
154
+
155
+ /**
156
+ * Write bytes to a file
157
+ */
158
+ export async function writeBytes(
159
+ path: string,
160
+ content: Buffer,
161
+ options: WriteOptions = {}
162
+ ): Promise<void> {
163
+ const { mode, flag = options.append ? 'a' : 'w' } = options;
164
+ await ensureDir(nodePath.dirname(path));
165
+ await fsPromises.writeFile(path, content, { mode, flag });
166
+ }
167
+
168
+ /**
169
+ * Write bytes to a file (sync)
170
+ */
171
+ export function writeBytesSync(path: string, content: Buffer, options: WriteOptions = {}): void {
172
+ const { mode, flag = options.append ? 'a' : 'w' } = options;
173
+ ensureDirSync(nodePath.dirname(path));
174
+ fs.writeFileSync(path, content, { mode, flag });
175
+ }
176
+
177
+ /**
178
+ * Write JSON to a file
179
+ */
180
+ export async function writeJson(
181
+ path: string,
182
+ data: unknown,
183
+ options: WriteOptions & { pretty?: boolean } = {}
184
+ ): Promise<void> {
185
+ const { pretty = true, ...writeOpts } = options;
186
+ const text = pretty ? JSON.stringify(data, null, 2) : JSON.stringify(data);
187
+ await writeText(path, text, writeOpts);
188
+ }
189
+
190
+ /**
191
+ * Write JSON to a file (sync)
192
+ */
193
+ export function writeJsonSync(
194
+ path: string,
195
+ data: unknown,
196
+ options: WriteOptions & { pretty?: boolean } = {}
197
+ ): void {
198
+ const { pretty = true, ...writeOpts } = options;
199
+ const text = pretty ? JSON.stringify(data, null, 2) : JSON.stringify(data);
200
+ writeTextSync(path, text, writeOpts);
201
+ }
202
+
203
+ /**
204
+ * Append text to a file
205
+ */
206
+ export async function appendText(
207
+ path: string,
208
+ content: string,
209
+ encoding: Encoding = 'utf-8'
210
+ ): Promise<void> {
211
+ await writeText(path, content, { encoding, append: true });
212
+ }
213
+
214
+ /**
215
+ * Append text to a file (sync)
216
+ */
217
+ export function appendTextSync(path: string, content: string, encoding: Encoding = 'utf-8'): void {
218
+ writeTextSync(path, content, { encoding, append: true });
219
+ }
220
+
221
+ /**
222
+ * Append a line to a file
223
+ */
224
+ export async function appendLine(
225
+ path: string,
226
+ line: string,
227
+ encoding: Encoding = 'utf-8'
228
+ ): Promise<void> {
229
+ await appendText(path, line + '\n', encoding);
230
+ }
231
+
232
+ /**
233
+ * Append a line to a file (sync)
234
+ */
235
+ export function appendLineSync(path: string, line: string, encoding: Encoding = 'utf-8'): void {
236
+ appendTextSync(path, line + '\n', encoding);
237
+ }
238
+
239
+ // ============================================================================
240
+ // File/Directory Operations
241
+ // ============================================================================
242
+
243
+ /**
244
+ * Check if a path exists
245
+ */
246
+ export async function exists(path: string): Promise<boolean> {
247
+ try {
248
+ await fsPromises.access(path);
249
+ return true;
250
+ } catch {
251
+ return false;
252
+ }
253
+ }
254
+
255
+ /**
256
+ * Check if a path exists (sync)
257
+ */
258
+ export function existsSync(path: string): boolean {
259
+ return fs.existsSync(path);
260
+ }
261
+
262
+ /**
263
+ * Check if path is a file
264
+ */
265
+ export async function isFile(path: string): Promise<boolean> {
266
+ try {
267
+ const stats = await fsPromises.stat(path);
268
+ return stats.isFile();
269
+ } catch {
270
+ return false;
271
+ }
272
+ }
273
+
274
+ /**
275
+ * Check if path is a file (sync)
276
+ */
277
+ export function isFileSync(path: string): boolean {
278
+ try {
279
+ return fs.statSync(path).isFile();
280
+ } catch {
281
+ return false;
282
+ }
283
+ }
284
+
285
+ /**
286
+ * Check if path is a directory
287
+ */
288
+ export async function isDirectory(path: string): Promise<boolean> {
289
+ try {
290
+ const stats = await fsPromises.stat(path);
291
+ return stats.isDirectory();
292
+ } catch {
293
+ return false;
294
+ }
295
+ }
296
+
297
+ /**
298
+ * Check if path is a directory (sync)
299
+ */
300
+ export function isDirectorySync(path: string): boolean {
301
+ try {
302
+ return fs.statSync(path).isDirectory();
303
+ } catch {
304
+ return false;
305
+ }
306
+ }
307
+
308
+ /**
309
+ * Get file/directory stats
310
+ */
311
+ export async function stat(path: string): Promise<FileStats> {
312
+ const stats = await fsPromises.stat(path);
313
+ return {
314
+ size: stats.size,
315
+ isFile: stats.isFile(),
316
+ isDirectory: stats.isDirectory(),
317
+ isSymlink: stats.isSymbolicLink(),
318
+ createdAt: stats.birthtime,
319
+ modifiedAt: stats.mtime,
320
+ accessedAt: stats.atime,
321
+ mode: stats.mode,
322
+ };
323
+ }
324
+
325
+ /**
326
+ * Get file/directory stats (sync)
327
+ */
328
+ export function statSync(path: string): FileStats {
329
+ const stats = fs.statSync(path);
330
+ return {
331
+ size: stats.size,
332
+ isFile: stats.isFile(),
333
+ isDirectory: stats.isDirectory(),
334
+ isSymlink: stats.isSymbolicLink(),
335
+ createdAt: stats.birthtime,
336
+ modifiedAt: stats.mtime,
337
+ accessedAt: stats.atime,
338
+ mode: stats.mode,
339
+ };
340
+ }
341
+
342
+ /**
343
+ * Create a directory (and parents if needed)
344
+ */
345
+ export async function mkdir(path: string, recursive = true): Promise<void> {
346
+ await fsPromises.mkdir(path, { recursive });
347
+ }
348
+
349
+ /**
350
+ * Create a directory (sync)
351
+ */
352
+ export function mkdirSync(path: string, recursive = true): void {
353
+ fs.mkdirSync(path, { recursive });
354
+ }
355
+
356
+ /**
357
+ * Ensure a directory exists
358
+ */
359
+ export async function ensureDir(path: string): Promise<void> {
360
+ if (!(await exists(path))) {
361
+ await mkdir(path, true);
362
+ }
363
+ }
364
+
365
+ /**
366
+ * Ensure a directory exists (sync)
367
+ */
368
+ export function ensureDirSync(path: string): void {
369
+ if (!existsSync(path)) {
370
+ mkdirSync(path, true);
371
+ }
372
+ }
373
+
374
+ /**
375
+ * Ensure a file exists (create empty if not)
376
+ */
377
+ export async function ensureFile(path: string): Promise<void> {
378
+ if (!(await exists(path))) {
379
+ await ensureDir(nodePath.dirname(path));
380
+ await writeText(path, '');
381
+ }
382
+ }
383
+
384
+ /**
385
+ * Ensure a file exists (sync)
386
+ */
387
+ export function ensureFileSync(path: string): void {
388
+ if (!existsSync(path)) {
389
+ ensureDirSync(nodePath.dirname(path));
390
+ writeTextSync(path, '');
391
+ }
392
+ }
393
+
394
+ /**
395
+ * Remove a file or directory
396
+ */
397
+ export async function remove(path: string, recursive = true): Promise<void> {
398
+ await fsPromises.rm(path, { recursive, force: true });
399
+ }
400
+
401
+ /**
402
+ * Remove a file or directory (sync)
403
+ */
404
+ export function removeSync(path: string, recursive = true): void {
405
+ fs.rmSync(path, { recursive, force: true });
406
+ }
407
+
408
+ /**
409
+ * Copy a file or directory
410
+ */
411
+ export async function copy(src: string, dest: string, options: CopyOptions = {}): Promise<void> {
412
+ const { overwrite = true, recursive = true, filter } = options;
413
+
414
+ if (filter && !filter(src)) return;
415
+
416
+ const srcStat = await stat(src);
417
+
418
+ if (srcStat.isDirectory) {
419
+ await ensureDir(dest);
420
+
421
+ if (recursive) {
422
+ const entries = await readDir(src);
423
+ for (const entry of entries) {
424
+ await copy(entry.path, nodePath.join(dest, entry.name), options);
425
+ }
426
+ }
427
+ } else {
428
+ if (!overwrite && (await exists(dest))) {
429
+ return;
430
+ }
431
+ await ensureDir(nodePath.dirname(dest));
432
+ await fsPromises.copyFile(src, dest);
433
+ }
434
+ }
435
+
436
+ /**
437
+ * Copy a file or directory (sync)
438
+ */
439
+ export function copySync(src: string, dest: string, options: CopyOptions = {}): void {
440
+ const { overwrite = true, recursive = true, filter } = options;
441
+
442
+ if (filter && !filter(src)) return;
443
+
444
+ const srcStat = statSync(src);
445
+
446
+ if (srcStat.isDirectory) {
447
+ ensureDirSync(dest);
448
+
449
+ if (recursive) {
450
+ const entries = readDirSync(src);
451
+ for (const entry of entries) {
452
+ copySync(entry.path, nodePath.join(dest, entry.name), options);
453
+ }
454
+ }
455
+ } else {
456
+ if (!overwrite && existsSync(dest)) {
457
+ return;
458
+ }
459
+ ensureDirSync(nodePath.dirname(dest));
460
+ fs.copyFileSync(src, dest);
461
+ }
462
+ }
463
+
464
+ /**
465
+ * Move/rename a file or directory
466
+ */
467
+ export async function move(src: string, dest: string, overwrite = true): Promise<void> {
468
+ if (!overwrite && (await exists(dest))) {
469
+ throw new Error(`Destination already exists: ${dest}`);
470
+ }
471
+ await ensureDir(nodePath.dirname(dest));
472
+ await fsPromises.rename(src, dest);
473
+ }
474
+
475
+ /**
476
+ * Move/rename a file or directory (sync)
477
+ */
478
+ export function moveSync(src: string, dest: string, overwrite = true): void {
479
+ if (!overwrite && existsSync(dest)) {
480
+ throw new Error(`Destination already exists: ${dest}`);
481
+ }
482
+ ensureDirSync(nodePath.dirname(dest));
483
+ fs.renameSync(src, dest);
484
+ }
485
+
486
+ // ============================================================================
487
+ // Directory Reading
488
+ // ============================================================================
489
+
490
+ /**
491
+ * Read directory entries
492
+ */
493
+ export async function readDir(path: string): Promise<DirEntry[]> {
494
+ const entries = await fsPromises.readdir(path, { withFileTypes: true });
495
+ return entries.map((entry) => ({
496
+ name: entry.name,
497
+ path: nodePath.join(path, entry.name),
498
+ isFile: entry.isFile(),
499
+ isDirectory: entry.isDirectory(),
500
+ isSymlink: entry.isSymbolicLink(),
501
+ }));
502
+ }
503
+
504
+ /**
505
+ * Read directory entries (sync)
506
+ */
507
+ export function readDirSync(path: string): DirEntry[] {
508
+ const entries = fs.readdirSync(path, { withFileTypes: true });
509
+ return entries.map((entry) => ({
510
+ name: entry.name,
511
+ path: nodePath.join(path, entry.name),
512
+ isFile: entry.isFile(),
513
+ isDirectory: entry.isDirectory(),
514
+ isSymlink: entry.isSymbolicLink(),
515
+ }));
516
+ }
517
+
518
+ /**
519
+ * List files in a directory (names only)
520
+ */
521
+ export async function listFiles(path: string): Promise<string[]> {
522
+ const entries = await readDir(path);
523
+ return entries.filter((e) => e.isFile).map((e) => e.name);
524
+ }
525
+
526
+ /**
527
+ * List files in a directory (sync)
528
+ */
529
+ export function listFilesSync(path: string): string[] {
530
+ const entries = readDirSync(path);
531
+ return entries.filter((e) => e.isFile).map((e) => e.name);
532
+ }
533
+
534
+ /**
535
+ * List directories in a directory (names only)
536
+ */
537
+ export async function listDirs(path: string): Promise<string[]> {
538
+ const entries = await readDir(path);
539
+ return entries.filter((e) => e.isDirectory).map((e) => e.name);
540
+ }
541
+
542
+ /**
543
+ * List directories in a directory (sync)
544
+ */
545
+ export function listDirsSync(path: string): string[] {
546
+ const entries = readDirSync(path);
547
+ return entries.filter((e) => e.isDirectory).map((e) => e.name);
548
+ }
549
+
550
+ /**
551
+ * Walk directory recursively
552
+ */
553
+ export async function* walk(
554
+ path: string,
555
+ options: { depth?: number; followSymlinks?: boolean } = {}
556
+ ): AsyncGenerator<DirEntry> {
557
+ const { depth = Infinity, followSymlinks = false } = options;
558
+
559
+ async function* walkDir(dir: string, currentDepth: number): AsyncGenerator<DirEntry> {
560
+ if (currentDepth > depth) return;
561
+
562
+ const entries = await readDir(dir);
563
+ for (const entry of entries) {
564
+ yield entry;
565
+
566
+ if (entry.isDirectory || (followSymlinks && entry.isSymlink)) {
567
+ yield* walkDir(entry.path, currentDepth + 1);
568
+ }
569
+ }
570
+ }
571
+
572
+ yield* walkDir(path, 0);
573
+ }
574
+
575
+ /**
576
+ * Find files matching a glob pattern
577
+ */
578
+ export async function glob(
579
+ pattern: string,
580
+ options: { cwd?: string; ignore?: string[] } = {}
581
+ ): Promise<string[]> {
582
+ const { cwd = process.cwd(), ignore = [] } = options;
583
+ return globAsync(pattern, { cwd, ignore, absolute: true });
584
+ }
585
+
586
+ /**
587
+ * Find files matching a glob pattern (sync)
588
+ */
589
+ export function globSync(
590
+ pattern: string,
591
+ options: { cwd?: string; ignore?: string[] } = {}
592
+ ): string[] {
593
+ const { cwd = process.cwd(), ignore = [] } = options;
594
+ return globAsync.sync(pattern, { cwd, ignore, absolute: true });
595
+ }
596
+
597
+ // ============================================================================
598
+ // Temp Files
599
+ // ============================================================================
600
+
601
+ /**
602
+ * Get the system temp directory
603
+ */
604
+ export function tempDir(): string {
605
+ return require('os').tmpdir();
606
+ }
607
+
608
+ /**
609
+ * Create a temporary file
610
+ */
611
+ export async function createTempFile(
612
+ prefix = 'tmp-',
613
+ suffix = '',
614
+ dir?: string
615
+ ): Promise<{ path: string; cleanup: () => Promise<void> }> {
616
+ const tempDirectory = dir || tempDir();
617
+ const filename = `${prefix}${Date.now()}-${Math.random().toString(36).slice(2)}${suffix}`;
618
+ const filePath = nodePath.join(tempDirectory, filename);
619
+
620
+ await writeText(filePath, '');
621
+
622
+ return {
623
+ path: filePath,
624
+ cleanup: async () => {
625
+ await remove(filePath);
626
+ },
627
+ };
628
+ }
629
+
630
+ /**
631
+ * Create a temporary directory
632
+ */
633
+ export async function createTempDir(
634
+ prefix = 'tmp-'
635
+ ): Promise<{ path: string; cleanup: () => Promise<void> }> {
636
+ const tempDirectory = tempDir();
637
+ const dirname = `${prefix}${Date.now()}-${Math.random().toString(36).slice(2)}`;
638
+ const dirPath = nodePath.join(tempDirectory, dirname);
639
+
640
+ await mkdir(dirPath);
641
+
642
+ return {
643
+ path: dirPath,
644
+ cleanup: async () => {
645
+ await remove(dirPath, true);
646
+ },
647
+ };
648
+ }
649
+
650
+ // ============================================================================
651
+ // File Size Utilities
652
+ // ============================================================================
653
+
654
+ /**
655
+ * Get file size in bytes
656
+ */
657
+ export async function fileSize(path: string): Promise<number> {
658
+ const stats = await stat(path);
659
+ return stats.size;
660
+ }
661
+
662
+ /**
663
+ * Get file size in bytes (sync)
664
+ */
665
+ export function fileSizeSync(path: string): number {
666
+ const stats = statSync(path);
667
+ return stats.size;
668
+ }
669
+
670
+ /**
671
+ * Get directory size (recursive)
672
+ */
673
+ export async function dirSize(path: string): Promise<number> {
674
+ let total = 0;
675
+ for await (const entry of walk(path)) {
676
+ if (entry.isFile) {
677
+ total += await fileSize(entry.path);
678
+ }
679
+ }
680
+ return total;
681
+ }
682
+
683
+ /**
684
+ * Format bytes as human-readable string
685
+ */
686
+ export function formatSize(bytes: number, decimals = 2): string {
687
+ if (bytes === 0) return '0 B';
688
+ const k = 1024;
689
+ const sizes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB'];
690
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
691
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(decimals)) + ' ' + sizes[i];
692
+ }