@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 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 file not found — relifso does it under the hood
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 ([3.9 kB](https://bundlephobia.com/package/@reliverse/relifso@latest)), zero deps, modern code, no monkey-patching
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
- - ✌️ **Soon**: Bun v1.2+ ready — ships with Bun-aware enhancements out of the box
19
- - 🔥 **Soon**: Bun-specific features are exposed via `fs.*` when running on Bun
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 **arent implemented yet** — theyre part of the vision for ~`v1.3.0`.
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
- - ✨ Everythings bundled — modern, async, and type-safe.
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 file not found.”
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
- ## Whats Inside?
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 wouldnt be so cool without these gems:
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;
@@ -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
+ }
@@ -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
- /** @deprecated Not used. */
7
- errorOnExist?: boolean;
8
- /** @deprecated Not used. */
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 { copyFileSync, statSync, constants as fsConstants, readdirSync, rmSync } from "node:fs";
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, basename as basenamePath } from "node:path";
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 { overwrite = options.clobber || false, preserveTimestamps = false } = options;
14
- const srcStat = statSync(src, { throwIfNoEntry: true });
15
- if (!srcStat) {
16
- }
17
- let destFinal = dest;
18
- const destStat = statSync(dest, { throwIfNoEntry: false });
19
- if (!srcStat.isDirectory() && destStat?.isDirectory()) {
20
- destFinal = joinPath(dest, basenamePath(src));
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
- const destExists = statSync(destFinal, { throwIfNoEntry: false });
23
- if (destExists && !overwrite) {
24
- throw new Error(`Destination ${destFinal} already exists and overwrite is false.`);
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 destDir = dirname(destFinal);
27
- mkdirsSync(destDir);
76
+ const srcStat = dereference ? statSync(src, { throwIfNoEntry: true }) : lstatSync(src, { throwIfNoEntry: true });
28
77
  if (srcStat.isDirectory()) {
29
- if (overwrite && destExists) {
30
- rmSync(destFinal, { recursive: true, force: true });
78
+ if (!recursive) {
79
+ throw new Error(`Cannot copy directory ${src} without recursive flag`);
80
+ }
81
+ if (ensureDest) {
82
+ mkdirsSync(dest);
31
83
  }
32
- mkdirsSync(destFinal);
33
- const entries = readdirSync(src);
84
+ const entries = readdirSync(src, { withFileTypes: true });
34
85
  for (const entry of entries) {
35
- const srcEntry = joinPath(src, entry);
36
- const destEntry = joinPath(destFinal, entry);
37
- copySync(srcEntry, destEntry, options);
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 (overwrite && destExists) {
41
- rmSync(destFinal, { force: true });
101
+ if (ensureDest) {
102
+ mkdirsSync(dirname(dest));
42
103
  }
43
- copyFileSync(src, destFinal, preserveTimestamps ? fsConstants.COPYFILE_FICLONE : 0);
44
- if (preserveTimestamps) {
45
- console.warn("preserveTimestamps: utimesSync is not implemented for the moment.");
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 { overwrite = options.clobber || false, preserveTimestamps = false } = options;
51
- const srcStat = await statAsync(src).catch((e) => {
52
- if (e.code === "ENOENT") return null;
53
- throw e;
54
- });
55
- if (!srcStat) {
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
- let destFinal = dest;
58
- const destStat = await statAsync(dest).catch((e) => {
59
- if (e.code === "ENOENT") return null;
60
- throw e;
61
- });
62
- if (!srcStat?.isDirectory() && destStat?.isDirectory()) {
63
- destFinal = joinPath(dest, basenamePath(src));
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 destExists = await statAsync(destFinal).catch((e) => {
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 (destExists && !overwrite) {
70
- throw new Error(`Destination ${destFinal} already exists and overwrite is false.`);
178
+ if (!srcStat) {
179
+ throw new Error(`Source ${src} does not exist`);
71
180
  }
72
- const destDir = dirname(destFinal);
73
- await mkdirs(destDir);
74
- if (srcStat?.isDirectory()) {
75
- if (overwrite && destExists) {
76
- await rm(destFinal, { recursive: true, force: true });
77
- }
78
- await mkdirs(destFinal);
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 srcEntry = joinPath(src, entry);
82
- const destEntry = joinPath(destFinal, entry);
83
- await copy(srcEntry, destEntry, options);
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 (overwrite && destExists) {
87
- await rm(destFinal, { force: true });
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, destFinal, preserveTimestamps ? fsConstantsAsync.COPYFILE_FICLONE : 0);
90
- if (preserveTimestamps) {
91
- console.warn("preserveTimestamps: utimes is not implemented for the moment.");
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[]>;