@reliverse/relifso 1.3.0 → 1.4.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/README.md +67 -12
- package/bin/impl/bun.d.ts +34 -0
- package/bin/impl/bun.js +147 -0
- package/bin/impl/copy.d.ts +15 -3
- package/bin/impl/copy.js +194 -60
- package/bin/impl/dive-async.d.ts +11 -0
- package/bin/impl/dive-async.js +88 -0
- package/bin/impl/extras.d.ts +15 -0
- package/bin/impl/extras.js +47 -0
- package/bin/impl/file-utils.d.ts +20 -0
- package/bin/impl/file-utils.js +63 -0
- package/bin/impl/logger.d.ts +1 -0
- package/bin/impl/logger.js +7 -0
- package/bin/impl/mkdirs.js +10 -3
- package/bin/impl/move.d.ts +10 -0
- package/bin/impl/move.js +92 -45
- package/bin/impl/read-file.d.ts +5 -15
- package/bin/impl/read-file.js +57 -0
- package/bin/impl/read-json.d.ts +3 -0
- package/bin/impl/read-json.js +83 -4
- package/bin/impl/write-file.js +31 -6
- package/bin/impl/write-json.d.ts +12 -10
- package/bin/impl/write-json.js +92 -18
- package/bin/mod.d.ts +25 -35
- package/bin/mod.js +59 -143
- package/package.json +3 -62
package/README.md
CHANGED
|
@@ -1,26 +1,26 @@
|
|
|
1
1
|
# Relifso • Node.js Filesystem Toolkit Library
|
|
2
2
|
|
|
3
|
-
> @reliverse/relifso is a modern filesystem toolkit for builders. drop-in replacement for `node:fs` and `fs-extra` — powered by native promises, built with es modules, and packed with dx-focused utilities.
|
|
4
|
-
|
|
5
3
|
[sponsor](https://github.com/sponsors/blefnk) — [discord](https://discord.gg/Pb8uKbwpsJ) — [npm](https://npmjs.com/package/@reliverse/relifso) — [github](https://github.com/reliverse/relifso)
|
|
6
4
|
|
|
5
|
+
> @reliverse/relifso is a modern node and bun filesystem toolkit. drop-in replacement for `node:fs` and `fs-extra` — powered by native promises, built with es modules, and packed with dx-focused and bun-aware utilities.
|
|
6
|
+
|
|
7
7
|
## Features
|
|
8
8
|
|
|
9
9
|
- 🪄 Everything you love from `fs-extra` — now simpler, cleaner, and more beginner-friendly
|
|
10
10
|
- ⚙️ Drop-in replacement for `node:fs` — with native `Promise`, `async/await`, and sync variants
|
|
11
|
-
- 🤝 Forget about `try-catch` for common errors like
|
|
11
|
+
- 🤝 Forget about `try-catch` for common errors like "file not found" — relifso does it under the hood
|
|
12
12
|
- 🧯 Gracefully handles errors like `EMFILE` (reading or writing a lot of files at once) and other edge cases
|
|
13
13
|
- 📚 Consistent error-first behavior — even for legacy APIs like `fs.exists()`
|
|
14
14
|
- 📦 First-class ESM and full TypeScript support — no config hacks required
|
|
15
|
-
- 🧼 Zero bloat — small size ([
|
|
15
|
+
- 🧼 Zero bloat — small size ([4 kB](https://bundlephobia.com/package/@reliverse/relifso@latest)), zero deps, modern code, no monkey-patching
|
|
16
16
|
- 🎯 Supports all Node.js v16+ features — optimized for Node.js v22+
|
|
17
17
|
- 🧪 **Soon**: Ready for upcoming Node.js v22+ experimental features
|
|
18
|
-
- ✌️
|
|
19
|
-
- 🔥
|
|
18
|
+
- ✌️ Bun v1.2+ ready — ships with Bun-aware enhancements out of the box
|
|
19
|
+
- 🔥 Bun-specific features are exposed via `fs.*` when running on Bun
|
|
20
20
|
|
|
21
21
|
## Heads Up
|
|
22
22
|
|
|
23
|
-
- **Most of the things** mentioned in this doc **aren
|
|
23
|
+
- **Most of the things** mentioned in this doc **aren't implemented yet** — they're part of the vision for ~`v1.5.0`.
|
|
24
24
|
- Got thoughts? Ideas? Send your feedback in [Discord](https://discord.gg/Pb8uKbwpsJ) or use [GitHub Issues](https://github.com/reliverse/relifso/issues).
|
|
25
25
|
- Your feedback means the world and helps shape where this project goes next. Thank you!
|
|
26
26
|
|
|
@@ -65,9 +65,9 @@ if (await pathExists("dist/index.ts")) {
|
|
|
65
65
|
}
|
|
66
66
|
```
|
|
67
67
|
|
|
68
|
-
- ✨ Everything
|
|
68
|
+
- ✨ Everything's bundled — modern, async, and type-safe.
|
|
69
69
|
- 🧼 No more boilerplate like `promisify(fs.removeSync)` or using `mkdirp`, `ncp`, or `rimraf`.
|
|
70
|
-
- 🌱 No more weird `try/catch` for common errors like
|
|
70
|
+
- 🌱 No more weird `try/catch` for common errors like "file not found."
|
|
71
71
|
- ✌️ Just clean, predictable APIs built for 2025 and beyond.
|
|
72
72
|
|
|
73
73
|
## Example
|
|
@@ -118,7 +118,7 @@ All async methods return a `Promise` if no callback is passed.
|
|
|
118
118
|
- Compatible with Node.js 16+, best with 22+
|
|
119
119
|
- Async methods are built from the sync versions — no wrappers, no bloat
|
|
120
120
|
|
|
121
|
-
## What
|
|
121
|
+
## What's Inside?
|
|
122
122
|
|
|
123
123
|
- All async methods follow the `Promise` pattern by default.
|
|
124
124
|
- All sync methods are safe and throw errors when needed.
|
|
@@ -181,7 +181,7 @@ All async methods return a `Promise` if no callback is passed.
|
|
|
181
181
|
- [isSymlink](https://uwx-node-modules.github.io/fsxt/functions/isSymlink.html)
|
|
182
182
|
- [~~lchmod~~](https://uwx-node-modules.github.io/fsxt/functions/lchmod.html)
|
|
183
183
|
- [lchown](https://uwx-node-modules.github.io/fsxt/functions/lchown.html)
|
|
184
|
-
- [link](https://uwx-node-modules.github.io/fsxt/functions/link.html)
|
|
184
|
+
- [`link`](https://uwx-node-modules.github.io/fsxt/functions/link.html)
|
|
185
185
|
- [lstat](https://uwx-node-modules.github.io/fsxt/functions/lstat.html)
|
|
186
186
|
- [lutimes](https://uwx-node-modules.github.io/fsxt/functions/lutimes.html)
|
|
187
187
|
- [mapChildren](https://uwx-node-modules.github.io/fsxt/functions/mapChildren.html)
|
|
@@ -270,6 +270,61 @@ All async methods return a `Promise` if no callback is passed.
|
|
|
270
270
|
- [utimesSync](https://uwx-node-modules.github.io/fsxt/functions/utimesSync.html)
|
|
271
271
|
- [writevSync](https://uwx-node-modules.github.io/fsxt/functions/writevSync.html)
|
|
272
272
|
|
|
273
|
+
## Bun Integration
|
|
274
|
+
|
|
275
|
+
Relifso provides first-class support for Bun with automatic fallbacks to Node.js APIs. Here's how it works:
|
|
276
|
+
|
|
277
|
+
### Automatic Runtime Detection
|
|
278
|
+
|
|
279
|
+
```ts
|
|
280
|
+
import { isBun } from "@reliverse/relifso";
|
|
281
|
+
|
|
282
|
+
if (isBun) {
|
|
283
|
+
console.log("Running in Bun!");
|
|
284
|
+
} else {
|
|
285
|
+
console.log("Running in Node.js");
|
|
286
|
+
}
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
### Optimized File Operations
|
|
290
|
+
|
|
291
|
+
When running in Bun, relifso automatically uses Bun's optimized file system APIs:
|
|
292
|
+
|
|
293
|
+
- `Bun.file()` for file operations
|
|
294
|
+
- Native file existence checks
|
|
295
|
+
- Optimized file size and type detection
|
|
296
|
+
- Fast last modified time access
|
|
297
|
+
|
|
298
|
+
### Graceful Fallbacks
|
|
299
|
+
|
|
300
|
+
All Bun-specific operations include automatic fallbacks to Node.js APIs:
|
|
301
|
+
|
|
302
|
+
```ts
|
|
303
|
+
import { getStats } from "@reliverse/relifso";
|
|
304
|
+
|
|
305
|
+
// In Bun: Uses Bun.file() for faster stats
|
|
306
|
+
// In Node.js: Falls back to fs.stat()
|
|
307
|
+
const stats = await getStats("file.txt");
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
### Available Bun-Specific Utilities
|
|
311
|
+
|
|
312
|
+
- `getFile(path)` - Get a Bun file reference
|
|
313
|
+
- `exists(path)` - Check file existence using Bun's API
|
|
314
|
+
- `size(path)` - Get file size using Bun's API
|
|
315
|
+
- `type(path)` - Get file MIME type using Bun's API
|
|
316
|
+
- `lastModified(path)` - Get file last modified time
|
|
317
|
+
- `getStats(path)` - Get file stats with Bun optimization
|
|
318
|
+
- `getStatsSync(path)` - Synchronous version of getStats
|
|
319
|
+
|
|
320
|
+
### Error Handling
|
|
321
|
+
|
|
322
|
+
All Bun-specific operations include proper error handling:
|
|
323
|
+
|
|
324
|
+
- Runtime detection errors
|
|
325
|
+
- File operation failures
|
|
326
|
+
- Automatic fallback to Node.js APIs when needed
|
|
327
|
+
|
|
273
328
|
## Contributing
|
|
274
329
|
|
|
275
330
|
...
|
|
@@ -285,7 +340,7 @@ All async methods return a `Promise` if no callback is passed.
|
|
|
285
340
|
|
|
286
341
|
## Shoutouts
|
|
287
342
|
|
|
288
|
-
Relifso wouldn
|
|
343
|
+
Relifso wouldn't be so cool without these gems:
|
|
289
344
|
|
|
290
345
|
- [`node:fs`](https://nodejs.org/api/fs.html)+[`node:path`](https://nodejs.org/api/path.html) — origins
|
|
291
346
|
- [`fs-extra`](https://github.com/jprichardson/node-fs-extra) — classic, reliable
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { Stats } from "node:fs";
|
|
2
|
+
export declare const isBun: string | false;
|
|
3
|
+
/**
|
|
4
|
+
* Get a Bun file reference with validation and error handling
|
|
5
|
+
*/
|
|
6
|
+
export declare function getFileBun(path: string): Bun.BunFile;
|
|
7
|
+
/**
|
|
8
|
+
* Check if a file exists using Bun's optimized API
|
|
9
|
+
*/
|
|
10
|
+
export declare function existsBun(path: string): Promise<boolean>;
|
|
11
|
+
/**
|
|
12
|
+
* Get file size using Bun's optimized API
|
|
13
|
+
*/
|
|
14
|
+
export declare function sizeBun(path: string): Promise<number>;
|
|
15
|
+
/**
|
|
16
|
+
* Get file type using Bun's optimized API
|
|
17
|
+
*/
|
|
18
|
+
export declare function typeBun(path: string): Promise<string>;
|
|
19
|
+
/**
|
|
20
|
+
* Get file last modified time using Bun's optimized API
|
|
21
|
+
*/
|
|
22
|
+
export declare function lastModifiedBun(path: string): Promise<Date>;
|
|
23
|
+
/**
|
|
24
|
+
* Convert Bun file stats to Node.js Stats object
|
|
25
|
+
*/
|
|
26
|
+
export declare function toNodeStatsBun(path: string): Promise<Stats>;
|
|
27
|
+
/**
|
|
28
|
+
* Get file stats using Bun's optimized API with fallback to Node.js
|
|
29
|
+
*/
|
|
30
|
+
export declare function getStatsBun(path: string): Promise<Stats>;
|
|
31
|
+
/**
|
|
32
|
+
* Get file stats synchronously using Bun's optimized API with fallback to Node.js
|
|
33
|
+
*/
|
|
34
|
+
export declare function getStatsSyncBun(path: string): Stats;
|
package/bin/impl/bun.js
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { statSync } from "node:fs";
|
|
2
|
+
import { stat } from "node:fs/promises";
|
|
3
|
+
import { logInternal } from "./logger.js";
|
|
4
|
+
export const isBun = typeof process !== "undefined" && process.versions.bun;
|
|
5
|
+
export function getFileBun(path) {
|
|
6
|
+
if (!isBun) {
|
|
7
|
+
throw new Error("Bun runtime not detected");
|
|
8
|
+
}
|
|
9
|
+
try {
|
|
10
|
+
return Bun.file(path);
|
|
11
|
+
} catch (error) {
|
|
12
|
+
throw error;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
export async function existsBun(path) {
|
|
16
|
+
if (!isBun) {
|
|
17
|
+
throw new Error("Bun runtime not detected");
|
|
18
|
+
}
|
|
19
|
+
try {
|
|
20
|
+
const file = Bun.file(path);
|
|
21
|
+
return await file.exists();
|
|
22
|
+
} catch (error) {
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
export async function sizeBun(path) {
|
|
27
|
+
if (!isBun) {
|
|
28
|
+
throw new Error("Bun runtime not detected");
|
|
29
|
+
}
|
|
30
|
+
try {
|
|
31
|
+
const file = Bun.file(path);
|
|
32
|
+
return file.size;
|
|
33
|
+
} catch (error) {
|
|
34
|
+
throw error;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
export async function typeBun(path) {
|
|
38
|
+
if (!isBun) {
|
|
39
|
+
throw new Error("Bun runtime not detected");
|
|
40
|
+
}
|
|
41
|
+
try {
|
|
42
|
+
const file = Bun.file(path);
|
|
43
|
+
return file.type;
|
|
44
|
+
} catch (error) {
|
|
45
|
+
throw error;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
export async function lastModifiedBun(path) {
|
|
49
|
+
if (!isBun) {
|
|
50
|
+
throw new Error("Bun runtime not detected");
|
|
51
|
+
}
|
|
52
|
+
try {
|
|
53
|
+
const file = Bun.file(path);
|
|
54
|
+
return new Date(file.lastModified);
|
|
55
|
+
} catch (error) {
|
|
56
|
+
throw error;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
export async function toNodeStatsBun(path) {
|
|
60
|
+
if (!isBun) {
|
|
61
|
+
throw new Error("Bun runtime not detected");
|
|
62
|
+
}
|
|
63
|
+
try {
|
|
64
|
+
const file = Bun.file(path);
|
|
65
|
+
const size = file.size;
|
|
66
|
+
const lastModified = new Date(file.lastModified);
|
|
67
|
+
return {
|
|
68
|
+
dev: 0,
|
|
69
|
+
ino: 0,
|
|
70
|
+
mode: 0,
|
|
71
|
+
nlink: 0,
|
|
72
|
+
uid: 0,
|
|
73
|
+
gid: 0,
|
|
74
|
+
rdev: 0,
|
|
75
|
+
size,
|
|
76
|
+
blksize: 0,
|
|
77
|
+
blocks: 0,
|
|
78
|
+
atimeMs: lastModified.getTime(),
|
|
79
|
+
mtimeMs: lastModified.getTime(),
|
|
80
|
+
ctimeMs: lastModified.getTime(),
|
|
81
|
+
birthtimeMs: lastModified.getTime(),
|
|
82
|
+
atime: lastModified,
|
|
83
|
+
mtime: lastModified,
|
|
84
|
+
ctime: lastModified,
|
|
85
|
+
birthtime: lastModified,
|
|
86
|
+
isDirectory: () => false,
|
|
87
|
+
// Bun doesn't provide this info directly
|
|
88
|
+
isFile: () => true,
|
|
89
|
+
isBlockDevice: () => false,
|
|
90
|
+
isCharacterDevice: () => false,
|
|
91
|
+
isSymbolicLink: () => false,
|
|
92
|
+
isFIFO: () => false,
|
|
93
|
+
isSocket: () => false
|
|
94
|
+
};
|
|
95
|
+
} catch (error) {
|
|
96
|
+
throw error;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
export async function getStatsBun(path) {
|
|
100
|
+
if (!isBun) {
|
|
101
|
+
return stat(path);
|
|
102
|
+
}
|
|
103
|
+
try {
|
|
104
|
+
return await toNodeStatsBun(path);
|
|
105
|
+
} catch (error) {
|
|
106
|
+
return stat(path);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
export function getStatsSyncBun(path) {
|
|
110
|
+
if (!isBun) {
|
|
111
|
+
return statSync(path);
|
|
112
|
+
}
|
|
113
|
+
try {
|
|
114
|
+
const file = Bun.file(path);
|
|
115
|
+
const size = file.size;
|
|
116
|
+
const lastModified = new Date(file.lastModified);
|
|
117
|
+
return {
|
|
118
|
+
dev: 0,
|
|
119
|
+
ino: 0,
|
|
120
|
+
mode: 0,
|
|
121
|
+
nlink: 0,
|
|
122
|
+
uid: 0,
|
|
123
|
+
gid: 0,
|
|
124
|
+
rdev: 0,
|
|
125
|
+
size,
|
|
126
|
+
blksize: 0,
|
|
127
|
+
blocks: 0,
|
|
128
|
+
atimeMs: lastModified.getTime(),
|
|
129
|
+
mtimeMs: lastModified.getTime(),
|
|
130
|
+
ctimeMs: lastModified.getTime(),
|
|
131
|
+
birthtimeMs: lastModified.getTime(),
|
|
132
|
+
atime: lastModified,
|
|
133
|
+
mtime: lastModified,
|
|
134
|
+
ctime: lastModified,
|
|
135
|
+
birthtime: lastModified,
|
|
136
|
+
isDirectory: () => false,
|
|
137
|
+
isFile: () => true,
|
|
138
|
+
isBlockDevice: () => false,
|
|
139
|
+
isCharacterDevice: () => false,
|
|
140
|
+
isSymbolicLink: () => false,
|
|
141
|
+
isFIFO: () => false,
|
|
142
|
+
isSocket: () => false
|
|
143
|
+
};
|
|
144
|
+
} catch (error) {
|
|
145
|
+
return statSync(path);
|
|
146
|
+
}
|
|
147
|
+
}
|
package/bin/impl/copy.d.ts
CHANGED
|
@@ -1,12 +1,24 @@
|
|
|
1
1
|
export interface CopyOptions {
|
|
2
|
+
/** Whether to overwrite existing files. Default: true */
|
|
3
|
+
force?: boolean;
|
|
4
|
+
/** Whether to overwrite existing files. Alias for force. Default: true */
|
|
2
5
|
overwrite?: boolean;
|
|
3
6
|
/** @deprecated Use `overwrite`. */
|
|
4
7
|
clobber?: boolean;
|
|
8
|
+
/** Whether to preserve timestamps. Default: false */
|
|
5
9
|
preserveTimestamps?: boolean;
|
|
6
|
-
/**
|
|
7
|
-
|
|
8
|
-
/**
|
|
10
|
+
/** Whether to recursively copy directories. Default: false */
|
|
11
|
+
recursive?: boolean;
|
|
12
|
+
/** Whether to dereference symlinks. Default: false */
|
|
9
13
|
dereference?: boolean;
|
|
14
|
+
/** Whether to throw an error if the destination exists. Default: false */
|
|
15
|
+
errorOnExist?: boolean;
|
|
16
|
+
/** Whether to ensure source exists before copying (default: true) */
|
|
17
|
+
ensureSource?: boolean;
|
|
18
|
+
/** Whether to ensure destination directory exists (default: true) */
|
|
19
|
+
ensureDest?: boolean;
|
|
20
|
+
/** Whether to verify operation success (default: true) */
|
|
21
|
+
verify?: boolean;
|
|
10
22
|
/** @deprecated Not used. */
|
|
11
23
|
filter?: (src: string, dest: string) => boolean;
|
|
12
24
|
}
|
package/bin/impl/copy.js
CHANGED
|
@@ -1,94 +1,228 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
copyFileSync,
|
|
3
|
+
statSync,
|
|
4
|
+
constants as fsConstants,
|
|
5
|
+
readdirSync,
|
|
6
|
+
rmSync,
|
|
7
|
+
lstatSync,
|
|
8
|
+
readlinkSync,
|
|
9
|
+
symlinkSync
|
|
10
|
+
} from "node:fs";
|
|
2
11
|
import {
|
|
3
12
|
stat as statAsync,
|
|
4
13
|
copyFile as copyFileAsync,
|
|
5
14
|
constants as fsConstantsAsync,
|
|
6
15
|
readdir,
|
|
7
|
-
rm
|
|
16
|
+
rm,
|
|
17
|
+
lstat,
|
|
18
|
+
readlink,
|
|
19
|
+
symlink
|
|
8
20
|
} from "node:fs/promises";
|
|
9
|
-
import { dirname, join as joinPath
|
|
21
|
+
import { dirname, join as joinPath } from "node:path";
|
|
10
22
|
import { mkdirsSync } from "./mkdirs.js";
|
|
11
23
|
import { mkdirs } from "./mkdirs.js";
|
|
24
|
+
import { isBun, getFileBun, getStatsBun, getStatsSyncBun } from "./bun.js";
|
|
25
|
+
import { logInternal } from "./logger.js";
|
|
26
|
+
import { pathExists, pathExistsSync } from "./path-exists.js";
|
|
12
27
|
export function copySync(src, dest, options = {}) {
|
|
13
|
-
const {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
28
|
+
const {
|
|
29
|
+
overwrite = options.clobber ?? true,
|
|
30
|
+
force = true,
|
|
31
|
+
recursive = false,
|
|
32
|
+
dereference = false,
|
|
33
|
+
errorOnExist = false,
|
|
34
|
+
ensureSource = true,
|
|
35
|
+
ensureDest = true,
|
|
36
|
+
verify = true
|
|
37
|
+
} = options;
|
|
38
|
+
if (ensureSource && !pathExistsSync(src)) {
|
|
39
|
+
throw new Error(`Source ${src} does not exist.`);
|
|
21
40
|
}
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
41
|
+
if (isBun && !dereference) {
|
|
42
|
+
try {
|
|
43
|
+
const srcStat2 = getStatsSyncBun(src);
|
|
44
|
+
if (!srcStat2.isDirectory()) {
|
|
45
|
+
try {
|
|
46
|
+
const destStat = getStatsSyncBun(dest);
|
|
47
|
+
if (destStat) {
|
|
48
|
+
if (errorOnExist) {
|
|
49
|
+
throw new Error(`Destination ${dest} already exists.`);
|
|
50
|
+
}
|
|
51
|
+
if (!force && !overwrite) {
|
|
52
|
+
throw new Error(`Destination ${dest} already exists and overwrite is false.`);
|
|
53
|
+
}
|
|
54
|
+
if (force || overwrite) {
|
|
55
|
+
rmSync(dest, { force: true });
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
} catch (_error) {
|
|
59
|
+
}
|
|
60
|
+
if (ensureDest) {
|
|
61
|
+
const destDir = dirname(dest);
|
|
62
|
+
mkdirsSync(destDir);
|
|
63
|
+
}
|
|
64
|
+
try {
|
|
65
|
+
copyFileSync(src, dest, fsConstants.COPYFILE_FICLONE);
|
|
66
|
+
if (verify && !pathExistsSync(dest)) {
|
|
67
|
+
throw new Error(`Copy operation failed: destination ${dest} does not exist after copy`);
|
|
68
|
+
}
|
|
69
|
+
return;
|
|
70
|
+
} catch (error) {
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
} catch (error) {
|
|
74
|
+
}
|
|
25
75
|
}
|
|
26
|
-
const
|
|
27
|
-
mkdirsSync(destDir);
|
|
76
|
+
const srcStat = dereference ? statSync(src, { throwIfNoEntry: true }) : lstatSync(src, { throwIfNoEntry: true });
|
|
28
77
|
if (srcStat.isDirectory()) {
|
|
29
|
-
if (
|
|
30
|
-
|
|
78
|
+
if (!recursive) {
|
|
79
|
+
throw new Error(`Cannot copy directory ${src} without recursive flag`);
|
|
80
|
+
}
|
|
81
|
+
if (ensureDest) {
|
|
82
|
+
mkdirsSync(dest);
|
|
31
83
|
}
|
|
32
|
-
|
|
33
|
-
const entries = readdirSync(src);
|
|
84
|
+
const entries = readdirSync(src, { withFileTypes: true });
|
|
34
85
|
for (const entry of entries) {
|
|
35
|
-
const
|
|
36
|
-
const
|
|
37
|
-
|
|
86
|
+
const srcPath = joinPath(src, entry.name);
|
|
87
|
+
const destPath = joinPath(dest, entry.name);
|
|
88
|
+
if (entry.isDirectory()) {
|
|
89
|
+
copySync(srcPath, destPath, options);
|
|
90
|
+
} else if (entry.isFile()) {
|
|
91
|
+
copyFileSync(srcPath, destPath, fsConstants.COPYFILE_FICLONE);
|
|
92
|
+
} else if (entry.isSymbolicLink()) {
|
|
93
|
+
const target = readlinkSync(srcPath);
|
|
94
|
+
symlinkSync(target, destPath);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
if (verify && !pathExistsSync(dest)) {
|
|
98
|
+
throw new Error(`Copy operation failed: destination directory ${dest} does not exist after copy`);
|
|
38
99
|
}
|
|
39
100
|
} else {
|
|
40
|
-
if (
|
|
41
|
-
|
|
101
|
+
if (ensureDest) {
|
|
102
|
+
mkdirsSync(dirname(dest));
|
|
42
103
|
}
|
|
43
|
-
|
|
44
|
-
if (
|
|
45
|
-
|
|
104
|
+
const destExists = statSync(dest, { throwIfNoEntry: false });
|
|
105
|
+
if (destExists) {
|
|
106
|
+
if (errorOnExist) {
|
|
107
|
+
throw new Error(`Destination ${dest} already exists.`);
|
|
108
|
+
}
|
|
109
|
+
if (!force && !overwrite) {
|
|
110
|
+
throw new Error(`Destination ${dest} already exists and overwrite is false.`);
|
|
111
|
+
}
|
|
112
|
+
if (force || overwrite) {
|
|
113
|
+
rmSync(dest, { force: true });
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
copyFileSync(src, dest, fsConstants.COPYFILE_FICLONE);
|
|
117
|
+
if (verify && !pathExistsSync(dest)) {
|
|
118
|
+
throw new Error(`Copy operation failed: destination file ${dest} does not exist after copy`);
|
|
46
119
|
}
|
|
47
120
|
}
|
|
48
121
|
}
|
|
49
122
|
export async function copy(src, dest, options = {}) {
|
|
50
|
-
const {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
123
|
+
const {
|
|
124
|
+
overwrite = options.clobber ?? true,
|
|
125
|
+
force = true,
|
|
126
|
+
recursive = false,
|
|
127
|
+
dereference = false,
|
|
128
|
+
errorOnExist = false,
|
|
129
|
+
ensureSource = true,
|
|
130
|
+
ensureDest = true,
|
|
131
|
+
verify = true
|
|
132
|
+
} = options;
|
|
133
|
+
if (ensureSource && !await pathExists(src)) {
|
|
134
|
+
throw new Error(`Source ${src} does not exist.`);
|
|
56
135
|
}
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
136
|
+
if (isBun && !dereference) {
|
|
137
|
+
try {
|
|
138
|
+
const srcStat2 = await getStatsBun(src);
|
|
139
|
+
if (!srcStat2.isDirectory()) {
|
|
140
|
+
const srcFile = getFileBun(src);
|
|
141
|
+
const destFile = getFileBun(dest);
|
|
142
|
+
try {
|
|
143
|
+
const destStat = await getStatsBun(dest);
|
|
144
|
+
if (destStat) {
|
|
145
|
+
if (errorOnExist) {
|
|
146
|
+
throw new Error(`Destination ${dest} already exists.`);
|
|
147
|
+
}
|
|
148
|
+
if (!force && !overwrite) {
|
|
149
|
+
throw new Error(`Destination ${dest} already exists and overwrite is false.`);
|
|
150
|
+
}
|
|
151
|
+
if (force || overwrite) {
|
|
152
|
+
await rm(dest, { force: true });
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
} catch (_error) {
|
|
156
|
+
}
|
|
157
|
+
if (ensureDest) {
|
|
158
|
+
const destDir = dirname(dest);
|
|
159
|
+
await mkdirs(destDir);
|
|
160
|
+
}
|
|
161
|
+
try {
|
|
162
|
+
const content = await srcFile.text();
|
|
163
|
+
await Bun.write(destFile, content);
|
|
164
|
+
if (verify && !await pathExists(dest)) {
|
|
165
|
+
throw new Error(`Bun write failed: destination ${dest} does not exist after write`);
|
|
166
|
+
}
|
|
167
|
+
return;
|
|
168
|
+
} catch (error) {
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
} catch (error) {
|
|
172
|
+
}
|
|
64
173
|
}
|
|
65
|
-
const
|
|
174
|
+
const srcStat = await (dereference ? statAsync(src) : lstat(src)).catch((e) => {
|
|
66
175
|
if (e.code === "ENOENT") return null;
|
|
67
176
|
throw e;
|
|
68
177
|
});
|
|
69
|
-
if (
|
|
70
|
-
throw new Error(`
|
|
178
|
+
if (!srcStat) {
|
|
179
|
+
throw new Error(`Source ${src} does not exist`);
|
|
71
180
|
}
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
const entries = await readdir(src);
|
|
181
|
+
if (srcStat.isDirectory()) {
|
|
182
|
+
if (!recursive) {
|
|
183
|
+
throw new Error(`Cannot copy directory ${src} without recursive flag`);
|
|
184
|
+
}
|
|
185
|
+
if (ensureDest) {
|
|
186
|
+
await mkdirs(dest);
|
|
187
|
+
}
|
|
188
|
+
const entries = await readdir(src, { withFileTypes: true });
|
|
80
189
|
for (const entry of entries) {
|
|
81
|
-
const
|
|
82
|
-
const
|
|
83
|
-
|
|
190
|
+
const srcPath = joinPath(src, entry.name);
|
|
191
|
+
const destPath = joinPath(dest, entry.name);
|
|
192
|
+
if (entry.isDirectory()) {
|
|
193
|
+
await copy(srcPath, destPath, options);
|
|
194
|
+
} else if (entry.isFile()) {
|
|
195
|
+
await copyFileAsync(srcPath, destPath, fsConstantsAsync.COPYFILE_FICLONE);
|
|
196
|
+
} else if (entry.isSymbolicLink()) {
|
|
197
|
+
const target = await readlink(srcPath);
|
|
198
|
+
await symlink(target, destPath);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
if (verify && !await pathExists(dest)) {
|
|
202
|
+
throw new Error(`Copy operation failed: destination directory ${dest} does not exist after copy`);
|
|
84
203
|
}
|
|
85
204
|
} else {
|
|
86
|
-
if (
|
|
87
|
-
await
|
|
205
|
+
if (ensureDest) {
|
|
206
|
+
await mkdirs(dirname(dest));
|
|
207
|
+
}
|
|
208
|
+
const destExists = await statAsync(dest).catch((e) => {
|
|
209
|
+
if (e.code === "ENOENT") return null;
|
|
210
|
+
throw e;
|
|
211
|
+
});
|
|
212
|
+
if (destExists) {
|
|
213
|
+
if (errorOnExist) {
|
|
214
|
+
throw new Error(`Destination ${dest} already exists.`);
|
|
215
|
+
}
|
|
216
|
+
if (!force && !overwrite) {
|
|
217
|
+
throw new Error(`Destination ${dest} already exists and overwrite is false.`);
|
|
218
|
+
}
|
|
219
|
+
if (force || overwrite) {
|
|
220
|
+
await rm(dest, { force: true });
|
|
221
|
+
}
|
|
88
222
|
}
|
|
89
|
-
await copyFileAsync(src,
|
|
90
|
-
if (
|
|
91
|
-
|
|
223
|
+
await copyFileAsync(src, dest, fsConstantsAsync.COPYFILE_FICLONE);
|
|
224
|
+
if (verify && !await pathExists(dest)) {
|
|
225
|
+
throw new Error(`Copy operation failed: destination file ${dest} does not exist after copy`);
|
|
92
226
|
}
|
|
93
227
|
}
|
|
94
228
|
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Stats } from "node:fs";
|
|
2
|
+
import type { DiveOptions } from "./dive.js";
|
|
3
|
+
/**
|
|
4
|
+
* Recursively dives into a directory and yields files and directories.
|
|
5
|
+
* @param directory - The directory to dive into.
|
|
6
|
+
* @param action - An optional callback function to execute for each file or directory.
|
|
7
|
+
* @param options - An optional object containing options for the dive.
|
|
8
|
+
* @returns A Promise that resolves to an array of file paths if no action is provided, or void if an action is provided.
|
|
9
|
+
*/
|
|
10
|
+
export declare function dive(directory: string, action: (file: string, stat: Stats) => void | Promise<void>, options?: DiveOptions): Promise<void>;
|
|
11
|
+
export declare function dive(directory: string, options?: DiveOptions): Promise<string[]>;
|