@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.
- package/dist/{chunk-7HVUYGPS.js → chunk-DZHAVOCH.js} +12 -5
- package/dist/chunk-DZHAVOCH.js.map +1 -0
- package/dist/chunk-PR4QN5HX.js +43 -0
- package/dist/chunk-PR4QN5HX.js.map +1 -0
- package/dist/chunk-XBA4ARST.js +9313 -0
- package/dist/chunk-XBA4ARST.js.map +1 -0
- package/dist/{chunk-W2Q3LUCM.js → chunk-XJIFG7G3.js} +3 -3
- package/dist/chunk-XJIFG7G3.js.map +1 -0
- package/dist/collections.js +1 -0
- package/dist/fs/__tests__/file-watch-system.test.d.ts +2 -0
- package/dist/fs/__tests__/file-watch-system.test.d.ts.map +1 -0
- package/dist/fs/__tests__/path-utilities.test.d.ts +2 -0
- package/dist/fs/__tests__/path-utilities.test.d.ts.map +1 -0
- package/dist/fs/__tests__/path.test.d.ts +2 -0
- package/dist/fs/__tests__/path.test.d.ts.map +1 -0
- package/dist/fs/fs.d.ts +288 -0
- package/dist/fs/fs.d.ts.map +1 -0
- package/dist/fs/index.cjs +9338 -0
- package/dist/fs/index.cjs.map +1 -0
- package/dist/fs/index.d.ts +51 -0
- package/dist/fs/index.d.ts.map +1 -0
- package/dist/fs/index.js +204 -0
- package/dist/fs/index.js.map +1 -0
- package/dist/fs/path.d.ts +135 -0
- package/dist/fs/path.d.ts.map +1 -0
- package/dist/fs/watch.d.ts +133 -0
- package/dist/fs/watch.d.ts.map +1 -0
- package/dist/index.cjs +12660 -3435
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -3
- package/dist/index.js.map +1 -1
- package/dist/math.cjs +6 -2
- package/dist/math.cjs.map +1 -1
- package/dist/math.js +2 -1
- package/dist/string.cjs +2 -2
- package/dist/string.cjs.map +1 -1
- package/dist/string.js +2 -1
- package/dist/time.js +1 -0
- package/dist/traits/EconomicPrimitives.js +1 -0
- package/dist/traits/EconomicTraits.js +1 -0
- package/dist/traits/VRRTraits.d.ts +2 -2
- package/dist/types.d.ts.map +1 -1
- package/package.json +7 -2
- package/src/__tests__/index.test.ts +8 -8
- package/src/__tests__/standard-library-fundamentals.test.ts +21 -4
- package/src/__tests__/string-decoupled.test.ts +6 -7
- package/src/fs/__tests__/file-watch-system.test.ts +675 -0
- package/src/fs/__tests__/path-utilities.test.ts +375 -0
- package/src/fs/__tests__/path.test.ts +287 -0
- package/src/fs/fs.ts +692 -0
- package/src/fs/index.ts +194 -0
- package/src/fs/path.ts +310 -0
- package/src/fs/watch.ts +413 -0
- package/src/index.ts +3 -1
- package/src/math.ts +1 -1
- package/src/string.ts +2 -2
- package/src/traits/ARTraits.ts +2 -2
- package/src/traits/IoTTraits.ts +2 -2
- package/src/traits/VRRTraits.ts +2 -2
- package/src/traits/__tests__/ARTraits.test.ts +3 -9
- package/src/traits/__tests__/EconomicPrimitives.test.ts +7 -7
- package/src/traits/__tests__/EconomicTraits.test.ts +9 -7
- package/src/types.ts +10 -3
- package/tsup.config.ts +3 -0
- package/CHANGELOG.md +0 -7
- package/dist/chunk-7HVUYGPS.js.map +0 -1
- 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
|
+
}
|