@warlock.js/fs 4.1.1

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 (46) hide show
  1. package/README.md +76 -0
  2. package/cjs/index.cjs +424 -0
  3. package/cjs/index.cjs.map +1 -0
  4. package/esm/atomic.d.mts +22 -0
  5. package/esm/atomic.mjs +40 -0
  6. package/esm/atomic.mjs.map +1 -0
  7. package/esm/copy.d.mts +14 -0
  8. package/esm/copy.mjs +29 -0
  9. package/esm/copy.mjs.map +1 -0
  10. package/esm/dirs.d.mts +10 -0
  11. package/esm/dirs.mjs +18 -0
  12. package/esm/dirs.mjs.map +1 -0
  13. package/esm/exists.d.mts +44 -0
  14. package/esm/exists.mjs +86 -0
  15. package/esm/exists.mjs.map +1 -0
  16. package/esm/hash.d.mts +32 -0
  17. package/esm/hash.mjs +52 -0
  18. package/esm/hash.mjs.map +1 -0
  19. package/esm/index.d.mts +12 -0
  20. package/esm/index.mjs +13 -0
  21. package/esm/list.d.mts +20 -0
  22. package/esm/list.mjs +39 -0
  23. package/esm/list.mjs.map +1 -0
  24. package/esm/read.d.mts +16 -0
  25. package/esm/read.mjs +28 -0
  26. package/esm/read.mjs.map +1 -0
  27. package/esm/remove.d.mts +14 -0
  28. package/esm/remove.mjs +40 -0
  29. package/esm/remove.mjs.map +1 -0
  30. package/esm/rename.d.mts +9 -0
  31. package/esm/rename.mjs +17 -0
  32. package/esm/rename.mjs.map +1 -0
  33. package/esm/stats.d.mts +16 -0
  34. package/esm/stats.mjs +28 -0
  35. package/esm/stats.mjs.map +1 -0
  36. package/esm/write.d.mts +14 -0
  37. package/esm/write.mjs +29 -0
  38. package/esm/write.mjs.map +1 -0
  39. package/llms-full.txt +570 -0
  40. package/llms.txt +13 -0
  41. package/package.json +24 -0
  42. package/skills/hash-files/SKILL.md +122 -0
  43. package/skills/manage-directories/SKILL.md +140 -0
  44. package/skills/overview/SKILL.md +77 -0
  45. package/skills/read-and-write-files/SKILL.md +107 -0
  46. package/skills/write-atomically/SKILL.md +98 -0
package/README.md ADDED
@@ -0,0 +1,76 @@
1
+ # @warlock.js/fs
2
+
3
+ A pocket-sized filesystem toolkit for Node — the shape you wish `node:fs/promises` had, without pulling in `fs-extra`, `rimraf`, `mkdirp`, `write-file-atomic`, and `hasha`. Standalone: usable in any Node project, no `@warlock.js/core` required.
4
+
5
+ Every helper picks the right defaults — writes create parent directories for you, deletes swallow `ENOENT`, `atomicWriteAsync` writes-then-renames, and `hashFileAsync` streams so a 1 GB file doesn't blow the heap.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ yarn add @warlock.js/fs
11
+ # or
12
+ npm install @warlock.js/fs
13
+ ```
14
+
15
+ ## One naming convention
16
+
17
+ That's the whole vocabulary:
18
+
19
+ - **`*Async`** returns a `Promise` — use it in server / runtime code.
20
+ - **bare name** is synchronous — use it in CLI tools, codegen, and one-shot scripts.
21
+
22
+ One canonical name per operation; no aliases to remember. If the obvious name isn't there, the operation isn't there.
23
+
24
+ ## 30-second tour
25
+
26
+ ```ts
27
+ import {
28
+ putJsonFileAsync,
29
+ getJsonFileAsync,
30
+ atomicWriteAsync,
31
+ ensureDirectoryAsync,
32
+ listFilesAsync,
33
+ hashFileAsync,
34
+ } from "@warlock.js/fs";
35
+
36
+ // Write JSON (parent dirs auto-created, pretty-printed at 2 spaces)
37
+ await putJsonFileAsync("./build/manifest.json", { version: "1.0.0" });
38
+
39
+ // Read it back, typed
40
+ const manifest = await getJsonFileAsync<{ version: string }>("./build/manifest.json");
41
+
42
+ // Atomic write — concurrent readers never see a half-written file
43
+ await atomicWriteAsync("./build/state.lock", manifest.version);
44
+
45
+ // Directory ops
46
+ await ensureDirectoryAsync("./build/cache");
47
+ const files = await listFilesAsync("./build"); // full paths, not bare names
48
+
49
+ // Content fingerprint (streaming — constant memory)
50
+ const digest = await hashFileAsync("./build/manifest.json");
51
+ ```
52
+
53
+ ## What you get
54
+
55
+ - **Read + write text and JSON** — `getFileAsync`, `getJsonFileAsync`, `putFileAsync`, `putJsonFileAsync`. Writes auto-create parent directories; JSON variants pretty-print at 2-space indent.
56
+ - **Atomic writes** — `atomicWriteAsync` (accepts `string | Buffer`) and `atomicWriteJsonAsync`, for files other processes read concurrently.
57
+ - **Directory management** — `ensureDirectoryAsync`, `list(Async)` / `listFiles(Async)` / `listDirectories(Async)`, `copyFile(Async)` / `copyDirectory(Async)`, `renameFile(Async)`.
58
+ - **Delete** — `unlink(Async)` and `removeDirectory(Async)`, both `ENOENT`-safe (no-op on missing targets).
59
+ - **Content hashing** — `hashFileAsync` (streaming), `hashFileSmallAsync`, `hashString`, `hashBuffer`. SHA-256 default; `sha1` / `md5` / `sha512` supported.
60
+ - **Existence + stats** — `pathExists`, `fileExists`, `directoryExists`, `lastModified`, `stats` (each with an `*Async` twin).
61
+
62
+ ## Full documentation
63
+
64
+ The complete guide — getting started, essentials, task guides, recipes, and API reference — lives at **[warlock.js.org](https://warlock.js.org/v/latest/fs/)**.
65
+
66
+ ## Tests
67
+
68
+ This package uses Vitest:
69
+
70
+ ```bash
71
+ yarn test
72
+ ```
73
+
74
+ ## License
75
+
76
+ MIT
package/cjs/index.cjs ADDED
@@ -0,0 +1,424 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2
+ //#region \0rolldown/runtime.js
3
+ var __create = Object.create;
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __getProtoOf = Object.getPrototypeOf;
8
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __copyProps = (to, from, except, desc) => {
10
+ if (from && typeof from === "object" || typeof from === "function") {
11
+ for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
12
+ key = keys[i];
13
+ if (!__hasOwnProp.call(to, key) && key !== except) {
14
+ __defProp(to, key, {
15
+ get: ((k) => from[k]).bind(null, key),
16
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
17
+ });
18
+ }
19
+ }
20
+ }
21
+ return to;
22
+ };
23
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
24
+ value: mod,
25
+ enumerable: true
26
+ }) : target, mod));
27
+
28
+ //#endregion
29
+ let node_crypto = require("node:crypto");
30
+ let node_fs_promises = require("node:fs/promises");
31
+ let node_path = require("node:path");
32
+ node_path = __toESM(node_path, 1);
33
+ let node_fs = require("node:fs");
34
+
35
+ //#region ../../@warlock.js/fs/src/atomic.ts
36
+ /**
37
+ * Write a file atomically.
38
+ *
39
+ * Writes to a uniquely-named sibling temp file first, then renames it onto
40
+ * the target. Readers either see the old content or the complete new
41
+ * content — never a half-written file. If anything fails mid-write the
42
+ * temp file is cleaned up.
43
+ *
44
+ * Parent directories are created if missing.
45
+ *
46
+ * @example
47
+ * await atomicWriteAsync("manifest.json", JSON.stringify(data));
48
+ */
49
+ async function atomicWriteAsync(filePath, content) {
50
+ const dir = node_path.default.dirname(filePath);
51
+ await (0, node_fs_promises.mkdir)(dir, { recursive: true });
52
+ const tempPath = node_path.default.join(dir, `.${node_path.default.basename(filePath)}.${(0, node_crypto.randomBytes)(6).toString("hex")}.tmp`);
53
+ try {
54
+ await (0, node_fs_promises.writeFile)(tempPath, content);
55
+ await (0, node_fs_promises.rename)(tempPath, filePath);
56
+ } catch (error) {
57
+ await (0, node_fs_promises.unlink)(tempPath).catch(() => void 0);
58
+ throw error;
59
+ }
60
+ }
61
+ /**
62
+ * Atomic write convenience for JSON values (pretty-printed, 2-space indent).
63
+ */
64
+ async function atomicWriteJsonAsync(filePath, value) {
65
+ await atomicWriteAsync(filePath, JSON.stringify(value, null, 2));
66
+ }
67
+
68
+ //#endregion
69
+ //#region ../../@warlock.js/fs/src/copy.ts
70
+ /**
71
+ * Copy a single file. Creates the destination's parent directory if needed.
72
+ */
73
+ async function copyFileAsync(source, destination) {
74
+ await (0, node_fs_promises.mkdir)(node_path.default.dirname(destination), { recursive: true });
75
+ await (0, node_fs_promises.copyFile)(source, destination);
76
+ }
77
+ function copyFile(source, destination) {
78
+ (0, node_fs.mkdirSync)(node_path.default.dirname(destination), { recursive: true });
79
+ (0, node_fs.copyFileSync)(source, destination);
80
+ }
81
+ /**
82
+ * Recursively copy a directory.
83
+ */
84
+ async function copyDirectoryAsync(source, destination) {
85
+ await (0, node_fs_promises.cp)(source, destination, { recursive: true });
86
+ }
87
+ function copyDirectory(source, destination) {
88
+ (0, node_fs.cpSync)(source, destination, { recursive: true });
89
+ }
90
+
91
+ //#endregion
92
+ //#region ../../@warlock.js/fs/src/dirs.ts
93
+ /**
94
+ * Ensure a directory exists (recursively creating parents).
95
+ * Idempotent — no error if the directory already exists.
96
+ */
97
+ async function ensureDirectoryAsync(path) {
98
+ await (0, node_fs_promises.mkdir)(path, { recursive: true });
99
+ }
100
+ function ensureDirectory(path) {
101
+ (0, node_fs.mkdirSync)(path, { recursive: true });
102
+ }
103
+
104
+ //#endregion
105
+ //#region ../../@warlock.js/fs/src/exists.ts
106
+ /**
107
+ * Check whether a path exists, REGARDLESS of type — resolves `true` for both
108
+ * files and directories, `false` only when nothing is there (ENOENT).
109
+ *
110
+ * Pick the right check for the job:
111
+ * - `pathExistsAsync` — anything at this path (file OR directory).
112
+ * - `fileExistsAsync` — exists AND is a regular file.
113
+ * - `directoryExistsAsync` — exists AND is a directory.
114
+ *
115
+ * @example
116
+ * if (await pathExistsAsync("/var/data")) {
117
+ * // something is there — could be a file or a folder
118
+ * }
119
+ */
120
+ async function pathExistsAsync(path) {
121
+ try {
122
+ await (0, node_fs_promises.access)(path);
123
+ return true;
124
+ } catch {
125
+ return false;
126
+ }
127
+ }
128
+ function pathExists(path) {
129
+ try {
130
+ (0, node_fs.accessSync)(path);
131
+ return true;
132
+ } catch {
133
+ return false;
134
+ }
135
+ }
136
+ /**
137
+ * Check whether a path exists AND is a regular file. Resolves `false` for a
138
+ * directory — use `directoryExistsAsync` for folders, or `pathExistsAsync`
139
+ * when the type does not matter.
140
+ *
141
+ * @example
142
+ * if (await fileExistsAsync("/etc/config.json")) {
143
+ * const raw = await readFile("/etc/config.json", "utf8");
144
+ * }
145
+ */
146
+ async function fileExistsAsync(path) {
147
+ try {
148
+ return (await (0, node_fs_promises.stat)(path)).isFile();
149
+ } catch {
150
+ return false;
151
+ }
152
+ }
153
+ function fileExists(path) {
154
+ try {
155
+ return (0, node_fs.statSync)(path).isFile();
156
+ } catch {
157
+ return false;
158
+ }
159
+ }
160
+ /**
161
+ * Check whether a path exists AND is a directory. Resolves `false` for a
162
+ * regular file — use `fileExistsAsync` for files, or `pathExistsAsync` when
163
+ * the type does not matter.
164
+ *
165
+ * @example
166
+ * if (await directoryExistsAsync("/var/uploads")) {
167
+ * const entries = await readdir("/var/uploads");
168
+ * }
169
+ */
170
+ async function directoryExistsAsync(path) {
171
+ try {
172
+ return (await (0, node_fs_promises.stat)(path)).isDirectory();
173
+ } catch {
174
+ return false;
175
+ }
176
+ }
177
+ function directoryExists(path) {
178
+ try {
179
+ return (0, node_fs.statSync)(path).isDirectory();
180
+ } catch {
181
+ return false;
182
+ }
183
+ }
184
+
185
+ //#endregion
186
+ //#region ../../@warlock.js/fs/src/hash.ts
187
+ /**
188
+ * Compute a hex digest of a file's contents. Uses streaming for large
189
+ * files so memory doesn't spike when hashing big assets.
190
+ *
191
+ * @param path - File to hash.
192
+ * @param algorithm - Hash algorithm (default `sha256`).
193
+ *
194
+ * @example
195
+ * const fingerprint = await hashFile("./bundle.js");
196
+ * const quick = await hashFile("./small.txt", "md5");
197
+ */
198
+ function hashFileAsync(path, algorithm = "sha256") {
199
+ return new Promise((resolve, reject) => {
200
+ const hash = (0, node_crypto.createHash)(algorithm);
201
+ const stream = (0, node_fs.createReadStream)(path);
202
+ stream.on("data", (chunk) => hash.update(chunk));
203
+ stream.on("end", () => resolve(hash.digest("hex")));
204
+ stream.on("error", reject);
205
+ });
206
+ }
207
+ function hashFile(path, algorithm = "sha256") {
208
+ return (0, node_crypto.createHash)(algorithm).update((0, node_fs.readFileSync)(path)).digest("hex");
209
+ }
210
+ /**
211
+ * Hash an in-memory string without touching disk. Convenient when the
212
+ * caller already has the content loaded (e.g. file watcher diffing).
213
+ */
214
+ function hashString(content, algorithm = "sha256") {
215
+ return (0, node_crypto.createHash)(algorithm).update(content).digest("hex");
216
+ }
217
+ /**
218
+ * Hash an arbitrary buffer.
219
+ */
220
+ function hashBuffer(content, algorithm = "sha256") {
221
+ return (0, node_crypto.createHash)(algorithm).update(content).digest("hex");
222
+ }
223
+ /**
224
+ * Like `hashFileAsync` but reads the file in one go. Slightly faster for
225
+ * small files; use the streaming variant for anything > ~1 MB.
226
+ */
227
+ async function hashFileSmallAsync(path, algorithm = "sha256") {
228
+ return (0, node_crypto.createHash)(algorithm).update(await (0, node_fs_promises.readFile)(path)).digest("hex");
229
+ }
230
+
231
+ //#endregion
232
+ //#region ../../@warlock.js/fs/src/list.ts
233
+ /**
234
+ * List immediate children of a directory (files + subdirs), returning
235
+ * full paths.
236
+ */
237
+ async function listAsync(directoryPath) {
238
+ return (await (0, node_fs_promises.readdir)(directoryPath)).map((entry) => node_path.default.join(directoryPath, entry));
239
+ }
240
+ function list(directoryPath) {
241
+ return (0, node_fs.readdirSync)(directoryPath).map((entry) => node_path.default.join(directoryPath, entry));
242
+ }
243
+ /**
244
+ * List only files (not subdirectories) directly inside a directory.
245
+ */
246
+ async function listFilesAsync(directoryPath) {
247
+ const entries = await listAsync(directoryPath);
248
+ return (await Promise.all(entries.map(async (entry) => (await (0, node_fs_promises.stat)(entry)).isFile() ? entry : null))).filter((entry) => entry !== null);
249
+ }
250
+ function listFiles(directoryPath) {
251
+ return list(directoryPath).filter((entry) => (0, node_fs.statSync)(entry).isFile());
252
+ }
253
+ /**
254
+ * List only subdirectories directly inside a directory.
255
+ */
256
+ async function listDirectoriesAsync(directoryPath) {
257
+ const entries = await listAsync(directoryPath);
258
+ return (await Promise.all(entries.map(async (entry) => (await (0, node_fs_promises.stat)(entry)).isDirectory() ? entry : null))).filter((entry) => entry !== null);
259
+ }
260
+ function listDirectories(directoryPath) {
261
+ return list(directoryPath).filter((entry) => (0, node_fs.statSync)(entry).isDirectory());
262
+ }
263
+
264
+ //#endregion
265
+ //#region ../../@warlock.js/fs/src/read.ts
266
+ /**
267
+ * Read a file as UTF-8 text.
268
+ */
269
+ async function getFileAsync(path) {
270
+ return (0, node_fs_promises.readFile)(path, "utf-8");
271
+ }
272
+ function getFile(path) {
273
+ return (0, node_fs.readFileSync)(path, "utf-8");
274
+ }
275
+ /**
276
+ * Read a JSON file and parse it.
277
+ *
278
+ * @throws if the file does not exist or contains invalid JSON.
279
+ */
280
+ async function getJsonFileAsync(path) {
281
+ return JSON.parse(await getFileAsync(path));
282
+ }
283
+ function getJsonFile(path) {
284
+ return JSON.parse(getFile(path));
285
+ }
286
+
287
+ //#endregion
288
+ //#region ../../@warlock.js/fs/src/remove.ts
289
+ /**
290
+ * Delete a single file. No error if the file doesn't exist.
291
+ */
292
+ async function unlinkAsync(path) {
293
+ try {
294
+ await (0, node_fs_promises.unlink)(path);
295
+ } catch (error) {
296
+ if (error?.code !== "ENOENT") throw error;
297
+ }
298
+ }
299
+ function unlink(path) {
300
+ try {
301
+ (0, node_fs.unlinkSync)(path);
302
+ } catch (error) {
303
+ if (error?.code !== "ENOENT") throw error;
304
+ }
305
+ }
306
+ /**
307
+ * Recursively delete a directory and its contents. No error if missing.
308
+ */
309
+ async function removeDirectoryAsync(path) {
310
+ await (0, node_fs_promises.rm)(path, {
311
+ recursive: true,
312
+ force: true
313
+ });
314
+ }
315
+ function removeDirectory(path) {
316
+ (0, node_fs.rmSync)(path, {
317
+ recursive: true,
318
+ force: true
319
+ });
320
+ }
321
+
322
+ //#endregion
323
+ //#region ../../@warlock.js/fs/src/rename.ts
324
+ /**
325
+ * Rename / move a file or directory.
326
+ */
327
+ async function renameFileAsync(from, to) {
328
+ await (0, node_fs_promises.rename)(from, to);
329
+ }
330
+ function renameFile(from, to) {
331
+ (0, node_fs.renameSync)(from, to);
332
+ }
333
+
334
+ //#endregion
335
+ //#region ../../@warlock.js/fs/src/stats.ts
336
+ /**
337
+ * Get last-modified time of a path. Returns a Date.
338
+ *
339
+ * @throws if the path does not exist.
340
+ */
341
+ async function lastModifiedAsync(path) {
342
+ return (await (0, node_fs_promises.stat)(path)).mtime;
343
+ }
344
+ function lastModified(path) {
345
+ return (0, node_fs.statSync)(path).mtime;
346
+ }
347
+ /**
348
+ * Return raw fs.Stats for a path.
349
+ */
350
+ async function statsAsync(path) {
351
+ return (0, node_fs_promises.stat)(path);
352
+ }
353
+ function stats(path) {
354
+ return (0, node_fs.statSync)(path);
355
+ }
356
+
357
+ //#endregion
358
+ //#region ../../@warlock.js/fs/src/write.ts
359
+ /**
360
+ * Write a UTF-8 string to disk, creating any missing parent directories.
361
+ */
362
+ async function putFileAsync(filePath, content) {
363
+ await (0, node_fs_promises.mkdir)(node_path.default.dirname(filePath), { recursive: true });
364
+ await (0, node_fs_promises.writeFile)(filePath, content, "utf-8");
365
+ }
366
+ function putFile(filePath, content) {
367
+ (0, node_fs.mkdirSync)(node_path.default.dirname(filePath), { recursive: true });
368
+ (0, node_fs.writeFileSync)(filePath, content, "utf-8");
369
+ }
370
+ /**
371
+ * Write a JSON-serialisable value to disk (pretty-printed, 2-space indent).
372
+ */
373
+ async function putJsonFileAsync(filePath, value) {
374
+ await putFileAsync(filePath, JSON.stringify(value, null, 2));
375
+ }
376
+ function putJsonFile(filePath, value) {
377
+ putFile(filePath, JSON.stringify(value, null, 2));
378
+ }
379
+
380
+ //#endregion
381
+ exports.atomicWriteAsync = atomicWriteAsync;
382
+ exports.atomicWriteJsonAsync = atomicWriteJsonAsync;
383
+ exports.copyDirectory = copyDirectory;
384
+ exports.copyDirectoryAsync = copyDirectoryAsync;
385
+ exports.copyFile = copyFile;
386
+ exports.copyFileAsync = copyFileAsync;
387
+ exports.directoryExists = directoryExists;
388
+ exports.directoryExistsAsync = directoryExistsAsync;
389
+ exports.ensureDirectory = ensureDirectory;
390
+ exports.ensureDirectoryAsync = ensureDirectoryAsync;
391
+ exports.fileExists = fileExists;
392
+ exports.fileExistsAsync = fileExistsAsync;
393
+ exports.getFile = getFile;
394
+ exports.getFileAsync = getFileAsync;
395
+ exports.getJsonFile = getJsonFile;
396
+ exports.getJsonFileAsync = getJsonFileAsync;
397
+ exports.hashBuffer = hashBuffer;
398
+ exports.hashFile = hashFile;
399
+ exports.hashFileAsync = hashFileAsync;
400
+ exports.hashFileSmallAsync = hashFileSmallAsync;
401
+ exports.hashString = hashString;
402
+ exports.lastModified = lastModified;
403
+ exports.lastModifiedAsync = lastModifiedAsync;
404
+ exports.list = list;
405
+ exports.listAsync = listAsync;
406
+ exports.listDirectories = listDirectories;
407
+ exports.listDirectoriesAsync = listDirectoriesAsync;
408
+ exports.listFiles = listFiles;
409
+ exports.listFilesAsync = listFilesAsync;
410
+ exports.pathExists = pathExists;
411
+ exports.pathExistsAsync = pathExistsAsync;
412
+ exports.putFile = putFile;
413
+ exports.putFileAsync = putFileAsync;
414
+ exports.putJsonFile = putJsonFile;
415
+ exports.putJsonFileAsync = putJsonFileAsync;
416
+ exports.removeDirectory = removeDirectory;
417
+ exports.removeDirectoryAsync = removeDirectoryAsync;
418
+ exports.renameFile = renameFile;
419
+ exports.renameFileAsync = renameFileAsync;
420
+ exports.stats = stats;
421
+ exports.statsAsync = statsAsync;
422
+ exports.unlink = unlink;
423
+ exports.unlinkAsync = unlinkAsync;
424
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.cjs","names":["path","path","path","path"],"sources":["../../../../../@warlock.js/fs/src/atomic.ts","../../../../../@warlock.js/fs/src/copy.ts","../../../../../@warlock.js/fs/src/dirs.ts","../../../../../@warlock.js/fs/src/exists.ts","../../../../../@warlock.js/fs/src/hash.ts","../../../../../@warlock.js/fs/src/list.ts","../../../../../@warlock.js/fs/src/read.ts","../../../../../@warlock.js/fs/src/remove.ts","../../../../../@warlock.js/fs/src/rename.ts","../../../../../@warlock.js/fs/src/stats.ts","../../../../../@warlock.js/fs/src/write.ts"],"sourcesContent":["import { randomBytes } from \"node:crypto\";\nimport { mkdir, rename, unlink, writeFile } from \"node:fs/promises\";\nimport path from \"node:path\";\n\n/**\n * Write a file atomically.\n *\n * Writes to a uniquely-named sibling temp file first, then renames it onto\n * the target. Readers either see the old content or the complete new\n * content — never a half-written file. If anything fails mid-write the\n * temp file is cleaned up.\n *\n * Parent directories are created if missing.\n *\n * @example\n * await atomicWriteAsync(\"manifest.json\", JSON.stringify(data));\n */\nexport async function atomicWriteAsync(filePath: string, content: string | Buffer): Promise<void> {\n const dir = path.dirname(filePath);\n await mkdir(dir, { recursive: true });\n\n // Random suffix so concurrent writers don't fight over the same temp file.\n const tempPath = path.join(dir, `.${path.basename(filePath)}.${randomBytes(6).toString(\"hex\")}.tmp`);\n\n try {\n await writeFile(tempPath, content);\n await rename(tempPath, filePath);\n } catch (error) {\n await unlink(tempPath).catch(() => undefined);\n throw error;\n }\n}\n\n/**\n * Atomic write convenience for JSON values (pretty-printed, 2-space indent).\n */\nexport async function atomicWriteJsonAsync(filePath: string, value: unknown): Promise<void> {\n await atomicWriteAsync(filePath, JSON.stringify(value, null, 2));\n}\n","import { cp, copyFile as copyFilePromise, mkdir } from \"node:fs/promises\";\nimport { copyFileSync, cpSync, mkdirSync } from \"node:fs\";\nimport path from \"node:path\";\n\n/**\n * Copy a single file. Creates the destination's parent directory if needed.\n */\nexport async function copyFileAsync(source: string, destination: string): Promise<void> {\n await mkdir(path.dirname(destination), { recursive: true });\n await copyFilePromise(source, destination);\n}\n\nexport function copyFile(source: string, destination: string): void {\n mkdirSync(path.dirname(destination), { recursive: true });\n copyFileSync(source, destination);\n}\n\n/**\n * Recursively copy a directory.\n */\nexport async function copyDirectoryAsync(source: string, destination: string): Promise<void> {\n await cp(source, destination, { recursive: true });\n}\n\nexport function copyDirectory(source: string, destination: string): void {\n cpSync(source, destination, { recursive: true });\n}\n","import { mkdir } from \"node:fs/promises\";\nimport { mkdirSync } from \"node:fs\";\n\n/**\n * Ensure a directory exists (recursively creating parents).\n * Idempotent — no error if the directory already exists.\n */\nexport async function ensureDirectoryAsync(path: string): Promise<void> {\n await mkdir(path, { recursive: true });\n}\n\nexport function ensureDirectory(path: string): void {\n mkdirSync(path, { recursive: true });\n}\n","import { access, stat } from \"node:fs/promises\";\nimport { accessSync, statSync } from \"node:fs\";\n\n/**\n * Check whether a path exists, REGARDLESS of type — resolves `true` for both\n * files and directories, `false` only when nothing is there (ENOENT).\n *\n * Pick the right check for the job:\n * - `pathExistsAsync` — anything at this path (file OR directory).\n * - `fileExistsAsync` — exists AND is a regular file.\n * - `directoryExistsAsync` — exists AND is a directory.\n *\n * @example\n * if (await pathExistsAsync(\"/var/data\")) {\n * // something is there — could be a file or a folder\n * }\n */\nexport async function pathExistsAsync(path: string): Promise<boolean> {\n try {\n await access(path);\n return true;\n } catch {\n return false;\n }\n}\n\nexport function pathExists(path: string): boolean {\n try {\n accessSync(path);\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Check whether a path exists AND is a regular file. Resolves `false` for a\n * directory — use `directoryExistsAsync` for folders, or `pathExistsAsync`\n * when the type does not matter.\n *\n * @example\n * if (await fileExistsAsync(\"/etc/config.json\")) {\n * const raw = await readFile(\"/etc/config.json\", \"utf8\");\n * }\n */\nexport async function fileExistsAsync(path: string): Promise<boolean> {\n try {\n return (await stat(path)).isFile();\n } catch {\n return false;\n }\n}\n\nexport function fileExists(path: string): boolean {\n try {\n return statSync(path).isFile();\n } catch {\n return false;\n }\n}\n\n/**\n * Check whether a path exists AND is a directory. Resolves `false` for a\n * regular file — use `fileExistsAsync` for files, or `pathExistsAsync` when\n * the type does not matter.\n *\n * @example\n * if (await directoryExistsAsync(\"/var/uploads\")) {\n * const entries = await readdir(\"/var/uploads\");\n * }\n */\nexport async function directoryExistsAsync(path: string): Promise<boolean> {\n try {\n return (await stat(path)).isDirectory();\n } catch {\n return false;\n }\n}\n\nexport function directoryExists(path: string): boolean {\n try {\n return statSync(path).isDirectory();\n } catch {\n return false;\n }\n}\n","import { createHash } from \"node:crypto\";\nimport { createReadStream } from \"node:fs\";\nimport { readFile } from \"node:fs/promises\";\nimport { readFileSync } from \"node:fs\";\n\nexport type HashAlgorithm = \"sha256\" | \"sha1\" | \"md5\" | \"sha512\";\n\n/**\n * Compute a hex digest of a file's contents. Uses streaming for large\n * files so memory doesn't spike when hashing big assets.\n *\n * @param path - File to hash.\n * @param algorithm - Hash algorithm (default `sha256`).\n *\n * @example\n * const fingerprint = await hashFile(\"./bundle.js\");\n * const quick = await hashFile(\"./small.txt\", \"md5\");\n */\nexport function hashFileAsync(\n path: string,\n algorithm: HashAlgorithm = \"sha256\",\n): Promise<string> {\n return new Promise((resolve, reject) => {\n const hash = createHash(algorithm);\n const stream = createReadStream(path);\n stream.on(\"data\", chunk => hash.update(chunk));\n stream.on(\"end\", () => resolve(hash.digest(\"hex\")));\n stream.on(\"error\", reject);\n });\n}\n\nexport function hashFile(path: string, algorithm: HashAlgorithm = \"sha256\"): string {\n return createHash(algorithm).update(readFileSync(path)).digest(\"hex\");\n}\n\n/**\n * Hash an in-memory string without touching disk. Convenient when the\n * caller already has the content loaded (e.g. file watcher diffing).\n */\nexport function hashString(content: string, algorithm: HashAlgorithm = \"sha256\"): string {\n return createHash(algorithm).update(content).digest(\"hex\");\n}\n\n/**\n * Hash an arbitrary buffer.\n */\nexport function hashBuffer(\n content: Buffer | Uint8Array,\n algorithm: HashAlgorithm = \"sha256\",\n): string {\n return createHash(algorithm).update(content).digest(\"hex\");\n}\n\n/**\n * Like `hashFileAsync` but reads the file in one go. Slightly faster for\n * small files; use the streaming variant for anything > ~1 MB.\n */\nexport async function hashFileSmallAsync(\n path: string,\n algorithm: HashAlgorithm = \"sha256\",\n): Promise<string> {\n return createHash(algorithm).update(await readFile(path)).digest(\"hex\");\n}\n","import { readdir, stat } from \"node:fs/promises\";\nimport { readdirSync, statSync } from \"node:fs\";\nimport path from \"node:path\";\n\n/**\n * List immediate children of a directory (files + subdirs), returning\n * full paths.\n */\nexport async function listAsync(directoryPath: string): Promise<string[]> {\n const entries = await readdir(directoryPath);\n return entries.map(entry => path.join(directoryPath, entry));\n}\n\nexport function list(directoryPath: string): string[] {\n return readdirSync(directoryPath).map(entry => path.join(directoryPath, entry));\n}\n\n/**\n * List only files (not subdirectories) directly inside a directory.\n */\nexport async function listFilesAsync(directoryPath: string): Promise<string[]> {\n const entries = await listAsync(directoryPath);\n const checks = await Promise.all(\n entries.map(async entry => ((await stat(entry)).isFile() ? entry : null)),\n );\n return checks.filter((entry): entry is string => entry !== null);\n}\n\nexport function listFiles(directoryPath: string): string[] {\n return list(directoryPath).filter(entry => statSync(entry).isFile());\n}\n\n/**\n * List only subdirectories directly inside a directory.\n */\nexport async function listDirectoriesAsync(directoryPath: string): Promise<string[]> {\n const entries = await listAsync(directoryPath);\n const checks = await Promise.all(\n entries.map(async entry => ((await stat(entry)).isDirectory() ? entry : null)),\n );\n return checks.filter((entry): entry is string => entry !== null);\n}\n\nexport function listDirectories(directoryPath: string): string[] {\n return list(directoryPath).filter(entry => statSync(entry).isDirectory());\n}\n","import { readFile } from \"node:fs/promises\";\nimport { readFileSync } from \"node:fs\";\n\n/**\n * Read a file as UTF-8 text.\n */\nexport async function getFileAsync(path: string): Promise<string> {\n return readFile(path, \"utf-8\");\n}\n\nexport function getFile(path: string): string {\n return readFileSync(path, \"utf-8\");\n}\n\n/**\n * Read a JSON file and parse it.\n *\n * @throws if the file does not exist or contains invalid JSON.\n */\nexport async function getJsonFileAsync<T = unknown>(path: string): Promise<T> {\n return JSON.parse(await getFileAsync(path)) as T;\n}\n\nexport function getJsonFile<T = unknown>(path: string): T {\n return JSON.parse(getFile(path)) as T;\n}\n","import { rm, unlink as unlinkPromise } from \"node:fs/promises\";\nimport { rmSync, unlinkSync } from \"node:fs\";\n\n/**\n * Delete a single file. No error if the file doesn't exist.\n */\nexport async function unlinkAsync(path: string): Promise<void> {\n try {\n await unlinkPromise(path);\n } catch (error: any) {\n if (error?.code !== \"ENOENT\") throw error;\n }\n}\n\nexport function unlink(path: string): void {\n try {\n unlinkSync(path);\n } catch (error: any) {\n if (error?.code !== \"ENOENT\") throw error;\n }\n}\n\n/**\n * Recursively delete a directory and its contents. No error if missing.\n */\nexport async function removeDirectoryAsync(path: string): Promise<void> {\n await rm(path, { recursive: true, force: true });\n}\n\nexport function removeDirectory(path: string): void {\n rmSync(path, { recursive: true, force: true });\n}\n","import { rename as renamePromise } from \"node:fs/promises\";\nimport { renameSync } from \"node:fs\";\n\n/**\n * Rename / move a file or directory.\n */\nexport async function renameFileAsync(from: string, to: string): Promise<void> {\n await renamePromise(from, to);\n}\n\nexport function renameFile(from: string, to: string): void {\n renameSync(from, to);\n}\n","import { stat as statPromise } from \"node:fs/promises\";\nimport { statSync } from \"node:fs\";\n\n/**\n * Get last-modified time of a path. Returns a Date.\n *\n * @throws if the path does not exist.\n */\nexport async function lastModifiedAsync(path: string): Promise<Date> {\n return (await statPromise(path)).mtime;\n}\n\nexport function lastModified(path: string): Date {\n return statSync(path).mtime;\n}\n\n/**\n * Return raw fs.Stats for a path.\n */\nexport async function statsAsync(path: string) {\n return statPromise(path);\n}\n\nexport function stats(path: string) {\n return statSync(path);\n}\n","import { mkdir, writeFile } from \"node:fs/promises\";\nimport { mkdirSync, writeFileSync } from \"node:fs\";\nimport path from \"node:path\";\n\n/**\n * Write a UTF-8 string to disk, creating any missing parent directories.\n */\nexport async function putFileAsync(filePath: string, content: string): Promise<void> {\n await mkdir(path.dirname(filePath), { recursive: true });\n await writeFile(filePath, content, \"utf-8\");\n}\n\nexport function putFile(filePath: string, content: string): void {\n mkdirSync(path.dirname(filePath), { recursive: true });\n writeFileSync(filePath, content, \"utf-8\");\n}\n\n/**\n * Write a JSON-serialisable value to disk (pretty-printed, 2-space indent).\n */\nexport async function putJsonFileAsync(filePath: string, value: unknown): Promise<void> {\n await putFileAsync(filePath, JSON.stringify(value, null, 2));\n}\n\nexport function putJsonFile(filePath: string, value: unknown): void {\n putFile(filePath, JSON.stringify(value, null, 2));\n}\n"],"mappings":"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"}
@@ -0,0 +1,22 @@
1
+ //#region ../../@warlock.js/fs/src/atomic.d.ts
2
+ /**
3
+ * Write a file atomically.
4
+ *
5
+ * Writes to a uniquely-named sibling temp file first, then renames it onto
6
+ * the target. Readers either see the old content or the complete new
7
+ * content — never a half-written file. If anything fails mid-write the
8
+ * temp file is cleaned up.
9
+ *
10
+ * Parent directories are created if missing.
11
+ *
12
+ * @example
13
+ * await atomicWriteAsync("manifest.json", JSON.stringify(data));
14
+ */
15
+ declare function atomicWriteAsync(filePath: string, content: string | Buffer): Promise<void>;
16
+ /**
17
+ * Atomic write convenience for JSON values (pretty-printed, 2-space indent).
18
+ */
19
+ declare function atomicWriteJsonAsync(filePath: string, value: unknown): Promise<void>;
20
+ //#endregion
21
+ export { atomicWriteAsync, atomicWriteJsonAsync };
22
+ //# sourceMappingURL=atomic.d.mts.map
package/esm/atomic.mjs ADDED
@@ -0,0 +1,40 @@
1
+ import { randomBytes } from "node:crypto";
2
+ import { mkdir, rename, unlink, writeFile } from "node:fs/promises";
3
+ import path from "node:path";
4
+
5
+ //#region ../../@warlock.js/fs/src/atomic.ts
6
+ /**
7
+ * Write a file atomically.
8
+ *
9
+ * Writes to a uniquely-named sibling temp file first, then renames it onto
10
+ * the target. Readers either see the old content or the complete new
11
+ * content — never a half-written file. If anything fails mid-write the
12
+ * temp file is cleaned up.
13
+ *
14
+ * Parent directories are created if missing.
15
+ *
16
+ * @example
17
+ * await atomicWriteAsync("manifest.json", JSON.stringify(data));
18
+ */
19
+ async function atomicWriteAsync(filePath, content) {
20
+ const dir = path.dirname(filePath);
21
+ await mkdir(dir, { recursive: true });
22
+ const tempPath = path.join(dir, `.${path.basename(filePath)}.${randomBytes(6).toString("hex")}.tmp`);
23
+ try {
24
+ await writeFile(tempPath, content);
25
+ await rename(tempPath, filePath);
26
+ } catch (error) {
27
+ await unlink(tempPath).catch(() => void 0);
28
+ throw error;
29
+ }
30
+ }
31
+ /**
32
+ * Atomic write convenience for JSON values (pretty-printed, 2-space indent).
33
+ */
34
+ async function atomicWriteJsonAsync(filePath, value) {
35
+ await atomicWriteAsync(filePath, JSON.stringify(value, null, 2));
36
+ }
37
+
38
+ //#endregion
39
+ export { atomicWriteAsync, atomicWriteJsonAsync };
40
+ //# sourceMappingURL=atomic.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"atomic.mjs","names":[],"sources":["../../../../../@warlock.js/fs/src/atomic.ts"],"sourcesContent":["import { randomBytes } from \"node:crypto\";\nimport { mkdir, rename, unlink, writeFile } from \"node:fs/promises\";\nimport path from \"node:path\";\n\n/**\n * Write a file atomically.\n *\n * Writes to a uniquely-named sibling temp file first, then renames it onto\n * the target. Readers either see the old content or the complete new\n * content — never a half-written file. If anything fails mid-write the\n * temp file is cleaned up.\n *\n * Parent directories are created if missing.\n *\n * @example\n * await atomicWriteAsync(\"manifest.json\", JSON.stringify(data));\n */\nexport async function atomicWriteAsync(filePath: string, content: string | Buffer): Promise<void> {\n const dir = path.dirname(filePath);\n await mkdir(dir, { recursive: true });\n\n // Random suffix so concurrent writers don't fight over the same temp file.\n const tempPath = path.join(dir, `.${path.basename(filePath)}.${randomBytes(6).toString(\"hex\")}.tmp`);\n\n try {\n await writeFile(tempPath, content);\n await rename(tempPath, filePath);\n } catch (error) {\n await unlink(tempPath).catch(() => undefined);\n throw error;\n }\n}\n\n/**\n * Atomic write convenience for JSON values (pretty-printed, 2-space indent).\n */\nexport async function atomicWriteJsonAsync(filePath: string, value: unknown): Promise<void> {\n await atomicWriteAsync(filePath, JSON.stringify(value, null, 2));\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAiBA,eAAsB,iBAAiB,UAAkB,SAAyC;CAChG,MAAM,MAAM,KAAK,QAAQ,QAAQ;CACjC,MAAM,MAAM,KAAK,EAAE,WAAW,KAAK,CAAC;CAGpC,MAAM,WAAW,KAAK,KAAK,KAAK,IAAI,KAAK,SAAS,QAAQ,EAAE,GAAG,YAAY,CAAC,EAAE,SAAS,KAAK,EAAE,KAAK;CAEnG,IAAI;EACF,MAAM,UAAU,UAAU,OAAO;EACjC,MAAM,OAAO,UAAU,QAAQ;CACjC,SAAS,OAAO;EACd,MAAM,OAAO,QAAQ,EAAE,YAAY,MAAS;EAC5C,MAAM;CACR;AACF;;;;AAKA,eAAsB,qBAAqB,UAAkB,OAA+B;CAC1F,MAAM,iBAAiB,UAAU,KAAK,UAAU,OAAO,MAAM,CAAC,CAAC;AACjE"}
package/esm/copy.d.mts ADDED
@@ -0,0 +1,14 @@
1
+ //#region ../../@warlock.js/fs/src/copy.d.ts
2
+ /**
3
+ * Copy a single file. Creates the destination's parent directory if needed.
4
+ */
5
+ declare function copyFileAsync(source: string, destination: string): Promise<void>;
6
+ declare function copyFile(source: string, destination: string): void;
7
+ /**
8
+ * Recursively copy a directory.
9
+ */
10
+ declare function copyDirectoryAsync(source: string, destination: string): Promise<void>;
11
+ declare function copyDirectory(source: string, destination: string): void;
12
+ //#endregion
13
+ export { copyDirectory, copyDirectoryAsync, copyFile, copyFileAsync };
14
+ //# sourceMappingURL=copy.d.mts.map
package/esm/copy.mjs ADDED
@@ -0,0 +1,29 @@
1
+ import { copyFile, cp, mkdir } from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { copyFileSync, cpSync, mkdirSync } from "node:fs";
4
+
5
+ //#region ../../@warlock.js/fs/src/copy.ts
6
+ /**
7
+ * Copy a single file. Creates the destination's parent directory if needed.
8
+ */
9
+ async function copyFileAsync(source, destination) {
10
+ await mkdir(path.dirname(destination), { recursive: true });
11
+ await copyFile(source, destination);
12
+ }
13
+ function copyFile$1(source, destination) {
14
+ mkdirSync(path.dirname(destination), { recursive: true });
15
+ copyFileSync(source, destination);
16
+ }
17
+ /**
18
+ * Recursively copy a directory.
19
+ */
20
+ async function copyDirectoryAsync(source, destination) {
21
+ await cp(source, destination, { recursive: true });
22
+ }
23
+ function copyDirectory(source, destination) {
24
+ cpSync(source, destination, { recursive: true });
25
+ }
26
+
27
+ //#endregion
28
+ export { copyDirectory, copyDirectoryAsync, copyFile$1 as copyFile, copyFileAsync };
29
+ //# sourceMappingURL=copy.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"copy.mjs","names":["copyFilePromise","copyFile"],"sources":["../../../../../@warlock.js/fs/src/copy.ts"],"sourcesContent":["import { cp, copyFile as copyFilePromise, mkdir } from \"node:fs/promises\";\nimport { copyFileSync, cpSync, mkdirSync } from \"node:fs\";\nimport path from \"node:path\";\n\n/**\n * Copy a single file. Creates the destination's parent directory if needed.\n */\nexport async function copyFileAsync(source: string, destination: string): Promise<void> {\n await mkdir(path.dirname(destination), { recursive: true });\n await copyFilePromise(source, destination);\n}\n\nexport function copyFile(source: string, destination: string): void {\n mkdirSync(path.dirname(destination), { recursive: true });\n copyFileSync(source, destination);\n}\n\n/**\n * Recursively copy a directory.\n */\nexport async function copyDirectoryAsync(source: string, destination: string): Promise<void> {\n await cp(source, destination, { recursive: true });\n}\n\nexport function copyDirectory(source: string, destination: string): void {\n cpSync(source, destination, { recursive: true });\n}\n"],"mappings":";;;;;;;;AAOA,eAAsB,cAAc,QAAgB,aAAoC;CACtF,MAAM,MAAM,KAAK,QAAQ,WAAW,GAAG,EAAE,WAAW,KAAK,CAAC;CAC1D,MAAMA,SAAgB,QAAQ,WAAW;AAC3C;AAEA,SAAgBC,WAAS,QAAgB,aAA2B;CAClE,UAAU,KAAK,QAAQ,WAAW,GAAG,EAAE,WAAW,KAAK,CAAC;CACxD,aAAa,QAAQ,WAAW;AAClC;;;;AAKA,eAAsB,mBAAmB,QAAgB,aAAoC;CAC3F,MAAM,GAAG,QAAQ,aAAa,EAAE,WAAW,KAAK,CAAC;AACnD;AAEA,SAAgB,cAAc,QAAgB,aAA2B;CACvE,OAAO,QAAQ,aAAa,EAAE,WAAW,KAAK,CAAC;AACjD"}
package/esm/dirs.d.mts ADDED
@@ -0,0 +1,10 @@
1
+ //#region ../../@warlock.js/fs/src/dirs.d.ts
2
+ /**
3
+ * Ensure a directory exists (recursively creating parents).
4
+ * Idempotent — no error if the directory already exists.
5
+ */
6
+ declare function ensureDirectoryAsync(path: string): Promise<void>;
7
+ declare function ensureDirectory(path: string): void;
8
+ //#endregion
9
+ export { ensureDirectory, ensureDirectoryAsync };
10
+ //# sourceMappingURL=dirs.d.mts.map