pwd-fs 3.3.5 → 3.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/dist/index.d.ts +1 -1
  2. package/dist/index.js +1 -1
  3. package/dist/powered-file-system/append.d.ts +9 -6
  4. package/dist/powered-file-system/append.js +4 -4
  5. package/dist/powered-file-system/chmod.d.ts +4 -4
  6. package/dist/powered-file-system/chmod.js +9 -10
  7. package/dist/powered-file-system/chown.d.ts +7 -4
  8. package/dist/powered-file-system/chown.js +18 -8
  9. package/dist/powered-file-system/copy.d.ts +10 -5
  10. package/dist/powered-file-system/copy.js +13 -13
  11. package/dist/powered-file-system/copy.test.js +46 -0
  12. package/dist/powered-file-system/empty-dir.d.ts +7 -0
  13. package/dist/powered-file-system/empty-dir.js +27 -0
  14. package/dist/powered-file-system/empty-dir.test.d.ts +1 -0
  15. package/dist/powered-file-system/empty-dir.test.js +61 -0
  16. package/dist/powered-file-system/mkdir.d.ts +9 -6
  17. package/dist/powered-file-system/mkdir.js +9 -10
  18. package/dist/powered-file-system/read.d.ts +10 -6
  19. package/dist/powered-file-system/read.js +9 -5
  20. package/dist/powered-file-system/readdir.d.ts +11 -6
  21. package/dist/powered-file-system/readdir.js +8 -6
  22. package/dist/powered-file-system/readlink.d.ts +13 -0
  23. package/dist/powered-file-system/readlink.js +28 -0
  24. package/dist/powered-file-system/readlink.test.d.ts +1 -0
  25. package/dist/powered-file-system/readlink.test.js +44 -0
  26. package/dist/powered-file-system/realpath.d.ts +13 -0
  27. package/dist/powered-file-system/realpath.js +28 -0
  28. package/dist/powered-file-system/realpath.test.d.ts +1 -0
  29. package/dist/powered-file-system/realpath.test.js +44 -0
  30. package/dist/powered-file-system/remove.d.ts +5 -5
  31. package/dist/powered-file-system/remove.js +11 -20
  32. package/dist/powered-file-system/rename.d.ts +5 -5
  33. package/dist/powered-file-system/rename.js +11 -7
  34. package/dist/powered-file-system/stat.d.ts +4 -4
  35. package/dist/powered-file-system/stat.js +8 -6
  36. package/dist/powered-file-system/symlink.d.ts +4 -4
  37. package/dist/powered-file-system/symlink.js +11 -4
  38. package/dist/powered-file-system/test.d.ts +10 -5
  39. package/dist/powered-file-system/test.js +8 -5
  40. package/dist/powered-file-system/write.d.ts +10 -7
  41. package/dist/powered-file-system/write.js +9 -6
  42. package/dist/powered-file-system.d.ts +117 -47
  43. package/dist/powered-file-system.js +17 -39
  44. package/dist/powered-file-system.test.js +25 -0
  45. package/dist/recurse-io-sync.d.ts +7 -2
  46. package/dist/recurse-io-sync.js +38 -32
  47. package/dist/recurse-io.d.ts +11 -2
  48. package/dist/recurse-io.js +122 -57
  49. package/dist/suite.test.js +1 -1
  50. package/package.json +11 -16
  51. package/readme.md +133 -12
@@ -25,9 +25,12 @@ const append_1 = require("./powered-file-system/append");
25
25
  const chmod_1 = require("./powered-file-system/chmod");
26
26
  const chown_1 = require("./powered-file-system/chown");
27
27
  const copy_1 = require("./powered-file-system/copy");
28
+ const empty_dir_1 = require("./powered-file-system/empty-dir");
28
29
  const mkdir_1 = require("./powered-file-system/mkdir");
29
30
  const read_1 = require("./powered-file-system/read");
31
+ const readlink_1 = require("./powered-file-system/readlink");
30
32
  const readdir_1 = require("./powered-file-system/readdir");
33
+ const realpath_1 = require("./powered-file-system/realpath");
31
34
  const remove_1 = require("./powered-file-system/remove");
32
35
  const rename_1 = require("./powered-file-system/rename");
33
36
  const stat_1 = require("./powered-file-system/stat");
@@ -38,8 +41,7 @@ __exportStar(require("./bitmask"), exports);
38
41
  /**
39
42
  * Path-aware wrapper around Node's file system APIs.
40
43
  *
41
- * All relative paths are resolved against `pwd`, which makes the instance
42
- * suitable for sandboxed or virtual working-directory workflows.
44
+ * Relative paths are resolved against `pwd`; absolute paths are preserved.
43
45
  */
44
46
  class PoweredFileSystem {
45
47
  pwd;
@@ -63,80 +65,56 @@ class PoweredFileSystem {
63
65
  this.pwd = pwd ? node_path_1.default.resolve(pwd) : process.cwd();
64
66
  }
65
67
  /**
66
- * Checks whether the given path is accessible with the requested mode.
68
+ * Resolves relative paths against `pwd` while preserving absolute paths.
67
69
  */
70
+ resolve(src) {
71
+ return node_path_1.default.resolve(this.pwd, src);
72
+ }
68
73
  test(src, options) {
69
74
  return test_1.test.call(this, src, options);
70
75
  }
71
- /**
72
- * Returns `lstat` information for a path.
73
- */
74
76
  stat(src, options) {
75
77
  return stat_1.stat.call(this, src, options);
76
78
  }
77
- /**
78
- * Applies a mode recursively to a file or directory tree.
79
- */
80
79
  chmod(src, mode, options) {
81
80
  return chmod_1.chmod.call(this, src, mode, options);
82
81
  }
83
- /**
84
- * Applies ownership recursively to a file or directory tree.
85
- */
86
82
  chown(src, options) {
87
83
  return chown_1.chown.call(this, src, options);
88
84
  }
89
- /**
90
- * Creates a symbolic link from `dest` to `src`.
91
- */
92
85
  symlink(src, dest, options) {
93
86
  return symlink_1.symlink.call(this, src, dest, options);
94
87
  }
95
- /**
96
- * Copies `src` into the destination directory.
97
- */
98
88
  copy(src, dest, options) {
99
89
  return copy_1.copy.call(this, src, dest, options);
100
90
  }
101
- /**
102
- * Renames or moves a file system node.
103
- */
104
91
  rename(src, dest, options) {
105
92
  return rename_1.rename.call(this, src, dest, options);
106
93
  }
107
- /**
108
- * Removes a file system node recursively.
109
- */
110
94
  remove(src, options) {
111
95
  return remove_1.remove.call(this, src, options);
112
96
  }
113
- /**
114
- * Reads a file relative to the current instance root.
115
- */
97
+ emptyDir(src, options) {
98
+ return empty_dir_1.emptyDir.call(this, src, options);
99
+ }
116
100
  read(src, options) {
117
101
  return read_1.read.call(this, src, options);
118
102
  }
119
- /**
120
- * Writes a file and applies the resulting permissions explicitly.
121
- */
122
103
  write(src, data, options) {
123
104
  return write_1.write.call(this, src, data, options);
124
105
  }
125
- /**
126
- * @deprecated Use `write(..., { flag: 'a' })` instead.
127
- */
128
106
  append(src, data, options) {
129
107
  return append_1.append.call(this, src, data, options);
130
108
  }
131
- /**
132
- * Lists directory entries relative to the current instance root.
133
- */
134
109
  readdir(dir, options) {
135
110
  return readdir_1.readdir.call(this, dir, options);
136
111
  }
137
- /**
138
- * Creates a directory tree relative to the current instance root.
139
- */
112
+ readlink(src, options) {
113
+ return readlink_1.readlink.call(this, src, options);
114
+ }
115
+ realpath(src, options) {
116
+ return realpath_1.realpath.call(this, src, options);
117
+ }
140
118
  mkdir(dir, options) {
141
119
  return mkdir_1.mkdir.call(this, dir, options);
142
120
  }
@@ -4,8 +4,10 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  const node_assert_1 = __importDefault(require("node:assert"));
7
+ const node_path_1 = __importDefault(require("node:path"));
7
8
  const node_test_1 = require("node:test");
8
9
  const index_1 = require("./index");
10
+ const test_utils_1 = require("./test-utils");
9
11
  /**
10
12
  * Verifies constructor path resolution semantics for the main API surface.
11
13
  */
@@ -18,4 +20,27 @@ const index_1 = require("./index");
18
20
  const { pwd } = new index_1.PoweredFileSystem(__dirname);
19
21
  (0, node_assert_1.default)(pwd === __dirname);
20
22
  });
23
+ (0, node_test_1.it)('Positive: Resolve should accept paths inside the working directory', () => {
24
+ const tmpDir = (0, test_utils_1.createTmpDir)();
25
+ try {
26
+ const pfs = new index_1.PoweredFileSystem(tmpDir);
27
+ const resolved = pfs.resolve('./nested/file.txt');
28
+ (0, node_assert_1.default)(resolved === node_path_1.default.join(tmpDir, 'nested', 'file.txt'));
29
+ }
30
+ finally {
31
+ (0, test_utils_1.restore)(tmpDir);
32
+ }
33
+ });
34
+ (0, node_test_1.it)('Positive: Resolve should preserve absolute paths outside the working directory', () => {
35
+ const tmpDir = (0, test_utils_1.createTmpDir)();
36
+ const outsidePath = node_path_1.default.resolve(node_path_1.default.dirname(tmpDir), 'outside.txt');
37
+ try {
38
+ const pfs = new index_1.PoweredFileSystem(tmpDir);
39
+ const resolved = pfs.resolve(outsidePath);
40
+ (0, node_assert_1.default)(resolved === outsidePath);
41
+ }
42
+ finally {
43
+ (0, test_utils_1.restore)(tmpDir);
44
+ }
45
+ });
21
46
  });
@@ -1,3 +1,4 @@
1
+ import type { ICopyOptions } from './recurse-io';
1
2
  /**
2
3
  * Synchronous counterpart of the recursive chmod implementation.
3
4
  */
@@ -5,15 +6,19 @@ export declare function chmodSync(src: string, mode: number): void;
5
6
  /**
6
7
  * Synchronous counterpart of the recursive chown implementation.
7
8
  */
8
- export declare function chownSync(src: string, uid: number, gid: number): void;
9
+ export declare function chownSync(src: string, uid: number | undefined, gid: number | undefined): void;
9
10
  /**
10
11
  * Synchronously copies a file system node into the target directory.
11
12
  */
12
- export declare function copySync(src: string, dir: string, umask: number): void;
13
+ export declare function copySync(src: string, dir: string, options: ICopyOptions): void;
13
14
  /**
14
15
  * Synchronously removes files, directories, and symlinks without following links.
15
16
  */
16
17
  export declare function removeSync(src: string): void;
18
+ /**
19
+ * Synchronously removes all entries inside a directory while preserving it.
20
+ */
21
+ export declare function emptyDirSync(src: string): void;
17
22
  /**
18
23
  * Synchronously creates a directory tree using permissions derived from umask.
19
24
  */
@@ -7,6 +7,7 @@ exports.chmodSync = chmodSync;
7
7
  exports.chownSync = chownSync;
8
8
  exports.copySync = copySync;
9
9
  exports.removeSync = removeSync;
10
+ exports.emptyDirSync = emptyDirSync;
10
11
  exports.mkdirSync = mkdirSync;
11
12
  const node_fs_1 = __importDefault(require("node:fs"));
12
13
  const node_path_1 = __importDefault(require("node:path"));
@@ -28,60 +29,65 @@ function chmodSync(src, mode) {
28
29
  */
29
30
  function chownSync(src, uid, gid) {
30
31
  const stats = node_fs_1.default.statSync(src);
31
- if (uid === 0) {
32
- uid = stats.uid;
33
- }
34
- if (gid === 0) {
35
- gid = stats.gid;
36
- }
32
+ // `0` is a valid uid/gid, so only nullish values mean "preserve current owner".
33
+ const nextUid = uid ?? stats.uid;
34
+ const nextGid = gid ?? stats.gid;
37
35
  if (stats.isDirectory()) {
38
36
  const list = node_fs_1.default.readdirSync(src);
39
37
  for (const loc of list) {
40
- chownSync(node_path_1.default.join(src, loc), uid, gid);
38
+ chownSync(node_path_1.default.join(src, loc), nextUid, nextGid);
41
39
  }
42
40
  }
43
- node_fs_1.default.chownSync(src, uid, gid);
41
+ node_fs_1.default.chownSync(src, nextUid, nextGid);
44
42
  }
45
43
  /**
46
44
  * Synchronously copies a file system node into the target directory.
47
45
  */
48
- function copySync(src, dir, umask) {
46
+ function copySync(src, dir, options) {
49
47
  const stat = node_fs_1.default.statSync(src);
48
+ const loc = node_path_1.default.basename(src);
49
+ const dest = node_path_1.default.join(dir, loc);
50
+ if (dest === src) {
51
+ throw new Error(`Source and destination are identical: ${src}`);
52
+ }
53
+ if (options.filter && options.filter(src, dest) === false) {
54
+ return;
55
+ }
50
56
  if (stat.isDirectory()) {
51
57
  const list = node_fs_1.default.readdirSync(src);
52
- const loc = node_path_1.default.basename(src);
53
- const mode = 0o777 & ~umask;
54
- dir = node_path_1.default.join(dir, loc);
55
- node_fs_1.default.mkdirSync(dir, { mode });
58
+ const mode = 0o777 & ~options.umask;
59
+ // Overwrite is implemented as replace-before-copy to support directory targets.
60
+ if (options.overwrite && node_fs_1.default.existsSync(dest)) {
61
+ removeSync(dest);
62
+ }
63
+ node_fs_1.default.mkdirSync(dest, { mode });
56
64
  for (const loc of list) {
57
- copySync(node_path_1.default.join(src, loc), dir, umask);
65
+ copySync(node_path_1.default.join(src, loc), dest, options);
58
66
  }
59
67
  }
60
68
  else {
61
- const loc = node_path_1.default.basename(src);
62
- const use = node_path_1.default.join(dir, loc);
63
- node_fs_1.default.copyFileSync(src, use);
64
- node_fs_1.default.chmodSync(use, 0o666 & ~umask);
69
+ // Match directory behavior by replacing the existing target before writing.
70
+ if (options.overwrite && node_fs_1.default.existsSync(dest)) {
71
+ removeSync(dest);
72
+ }
73
+ const flags = options.overwrite ? 0 : node_fs_1.default.constants.COPYFILE_EXCL;
74
+ node_fs_1.default.copyFileSync(src, dest, flags);
75
+ node_fs_1.default.chmodSync(dest, 0o666 & ~options.umask);
65
76
  }
66
77
  }
67
78
  /**
68
79
  * Synchronously removes files, directories, and symlinks without following links.
69
80
  */
70
81
  function removeSync(src) {
71
- const stats = node_fs_1.default.lstatSync(src);
72
- if (stats.isSymbolicLink()) {
73
- node_fs_1.default.unlinkSync(src);
74
- return;
75
- }
76
- if (stats.isDirectory()) {
77
- const list = node_fs_1.default.readdirSync(src);
78
- for (const loc of list) {
79
- removeSync(node_path_1.default.join(src, loc));
80
- }
81
- node_fs_1.default.rmdirSync(src);
82
- }
83
- else {
84
- node_fs_1.default.unlinkSync(src);
82
+ node_fs_1.default.rmSync(src, { recursive: true, force: false });
83
+ }
84
+ /**
85
+ * Synchronously removes all entries inside a directory while preserving it.
86
+ */
87
+ function emptyDirSync(src) {
88
+ const list = node_fs_1.default.readdirSync(src);
89
+ for (const loc of list) {
90
+ node_fs_1.default.rmSync(node_path_1.default.join(src, loc), { recursive: true, force: false });
85
91
  }
86
92
  }
87
93
  /**
@@ -1,4 +1,9 @@
1
1
  import { NoParamCallback } from 'node:fs';
2
+ export interface ICopyOptions {
3
+ umask: number;
4
+ overwrite: boolean;
5
+ filter?: (src: string, dest: string) => boolean;
6
+ }
2
7
  /**
3
8
  * Applies chmod depth-first so directories are updated after their contents.
4
9
  */
@@ -6,15 +11,19 @@ export declare function chmod(src: string, mode: number, callback: NoParamCallba
6
11
  /**
7
12
  * Applies ownership recursively while preserving current values when uid/gid are omitted.
8
13
  */
9
- export declare function chown(src: string, uid: number, gid: number, callback: NoParamCallback): void;
14
+ export declare function chown(src: string, uid: number | undefined, gid: number | undefined, callback: NoParamCallback): void;
10
15
  /**
11
16
  * Copies a file system node into the target directory, creating directories as needed.
12
17
  */
13
- export declare function copy(src: string, dir: string, umask: number, callback: NoParamCallback): void;
18
+ export declare function copy(src: string, dir: string, options: ICopyOptions, callback: NoParamCallback): void;
14
19
  /**
15
20
  * Removes files, directories, and symlinks without following symbolic links.
16
21
  */
17
22
  export declare function remove(src: string, callback: NoParamCallback): void;
23
+ /**
24
+ * Removes all entries inside a directory while preserving the directory itself.
25
+ */
26
+ export declare function emptyDir(src: string, callback: NoParamCallback): void;
18
27
  /**
19
28
  * Creates a directory tree with the permissions derived from the provided umask.
20
29
  */
@@ -7,13 +7,26 @@ exports.chmod = chmod;
7
7
  exports.chown = chown;
8
8
  exports.copy = copy;
9
9
  exports.remove = remove;
10
+ exports.emptyDir = emptyDir;
10
11
  exports.mkdir = mkdir;
11
12
  const node_fs_1 = __importDefault(require("node:fs"));
12
13
  const node_path_1 = __importDefault(require("node:path"));
14
+ function once(callback) {
15
+ let called = false;
16
+ return (err) => {
17
+ // Recursive branches can fail concurrently; report only the first terminal result.
18
+ if (called) {
19
+ return;
20
+ }
21
+ called = true;
22
+ callback(err);
23
+ };
24
+ }
13
25
  /**
14
26
  * Applies chmod depth-first so directories are updated after their contents.
15
27
  */
16
28
  function chmod(src, mode, callback) {
29
+ callback = once(callback);
17
30
  let reduce = 0;
18
31
  node_fs_1.default.stat(src, (err, stats) => {
19
32
  if (err)
@@ -48,95 +61,153 @@ function chmod(src, mode, callback) {
48
61
  * Applies ownership recursively while preserving current values when uid/gid are omitted.
49
62
  */
50
63
  function chown(src, uid, gid, callback) {
64
+ callback = once(callback);
51
65
  let reduce = 0;
52
66
  node_fs_1.default.stat(src, (err, stats) => {
53
67
  if (err) {
54
68
  return callback(err);
55
69
  }
56
- if (uid === 0) {
57
- uid = stats.uid;
58
- }
59
- if (gid === 0) {
60
- gid = stats.gid;
61
- }
70
+ // `0` is a valid uid/gid, so only nullish values mean "preserve current owner".
71
+ const nextUid = uid ?? stats.uid;
72
+ const nextGid = gid ?? stats.gid;
62
73
  if (stats.isDirectory()) {
63
74
  node_fs_1.default.readdir(src, (err, list) => {
64
75
  if (err) {
65
76
  return callback(err);
66
77
  }
67
78
  if (list.length === 0) {
68
- return node_fs_1.default.chown(src, uid, gid, callback);
79
+ return node_fs_1.default.chown(src, nextUid, nextGid, callback);
69
80
  }
70
81
  reduce = list.length;
71
82
  for (const loc of list) {
72
- chown(node_path_1.default.join(src, loc), uid, gid, (err) => {
83
+ chown(node_path_1.default.join(src, loc), nextUid, nextGid, (err) => {
73
84
  if (err) {
74
85
  return callback(err);
75
86
  }
76
87
  if (--reduce === 0) {
77
- node_fs_1.default.chown(src, uid, gid, callback);
88
+ node_fs_1.default.chown(src, nextUid, nextGid, callback);
78
89
  }
79
90
  });
80
91
  }
81
92
  });
82
93
  }
83
94
  else {
84
- node_fs_1.default.chown(src, uid, gid, callback);
95
+ node_fs_1.default.chown(src, nextUid, nextGid, callback);
85
96
  }
86
97
  });
87
98
  }
88
99
  /**
89
100
  * Copies a file system node into the target directory, creating directories as needed.
90
101
  */
91
- function copy(src, dir, umask, callback) {
102
+ function copy(src, dir, options, callback) {
103
+ callback = once(callback);
92
104
  node_fs_1.default.stat(src, (err, stat) => {
93
105
  if (err) {
94
106
  return callback(err);
95
107
  }
108
+ const loc = node_path_1.default.basename(src);
109
+ const dest = node_path_1.default.join(dir, loc);
110
+ if (dest === src) {
111
+ return callback(new Error(`Source and destination are identical: ${src}`));
112
+ }
113
+ if (options.filter && options.filter(src, dest) === false) {
114
+ return callback(null);
115
+ }
96
116
  if (stat.isDirectory()) {
97
117
  node_fs_1.default.readdir(src, (err, list) => {
98
118
  if (err) {
99
119
  return callback(err);
100
120
  }
101
- const loc = node_path_1.default.basename(src);
102
- const destDir = node_path_1.default.join(dir, loc);
103
- const mode = 0o777 & ~umask;
104
- node_fs_1.default.mkdir(destDir, { mode }, (err) => {
121
+ const mode = 0o777 & ~options.umask;
122
+ const create = () => {
123
+ node_fs_1.default.mkdir(dest, { mode }, (err) => {
124
+ if (err) {
125
+ if (err.code === 'EEXIST') {
126
+ err = new Error(`Target already exists: ${dest}`);
127
+ }
128
+ return callback(err);
129
+ }
130
+ if (list.length === 0) {
131
+ return callback(null);
132
+ }
133
+ let reduce = list.length;
134
+ for (const item of list) {
135
+ copy(node_path_1.default.join(src, item), dest, options, (err) => {
136
+ if (err) {
137
+ return callback(err);
138
+ }
139
+ if (--reduce === 0) {
140
+ callback(null);
141
+ }
142
+ });
143
+ }
144
+ });
145
+ };
146
+ if (!options.overwrite) {
147
+ return create();
148
+ }
149
+ // Overwrite is implemented as replace-before-copy to support directory targets.
150
+ node_fs_1.default.lstat(dest, (err, destStat) => {
105
151
  if (err) {
106
- if (err.code === 'EEXIST') {
107
- err = new Error(`Target already exists: ${destDir}`);
152
+ if (err.code === 'ENOENT') {
153
+ return create();
108
154
  }
109
155
  return callback(err);
110
156
  }
111
- if (list.length === 0) {
112
- return callback(null);
113
- }
114
- let reduce = list.length;
115
- for (const item of list) {
116
- copy(node_path_1.default.join(src, item), destDir, umask, (err) => {
157
+ if (destStat.isDirectory()) {
158
+ return remove(dest, (err) => {
117
159
  if (err) {
118
160
  return callback(err);
119
161
  }
120
- if (--reduce === 0) {
121
- callback(null);
122
- }
162
+ create();
123
163
  });
124
164
  }
165
+ node_fs_1.default.unlink(dest, (err) => {
166
+ if (err) {
167
+ return callback(err);
168
+ }
169
+ create();
170
+ });
125
171
  });
126
172
  });
127
173
  }
128
174
  else {
129
- const loc = node_path_1.default.basename(src);
130
- const dest = node_path_1.default.join(dir, loc);
131
- const mode = 0o666 & ~umask;
132
- const readStream = node_fs_1.default.createReadStream(src);
133
- const writeStream = node_fs_1.default.createWriteStream(dest, { mode });
134
- readStream.on('error', callback);
135
- writeStream.on('error', callback);
136
- writeStream.on('close', () => {
137
- node_fs_1.default.chmod(dest, mode, callback);
175
+ const mode = 0o666 & ~options.umask;
176
+ const write = () => {
177
+ const flags = options.overwrite ? 0 : node_fs_1.default.constants.COPYFILE_EXCL;
178
+ node_fs_1.default.copyFile(src, dest, flags, (err) => {
179
+ if (err) {
180
+ return callback(err);
181
+ }
182
+ node_fs_1.default.chmod(dest, mode, callback);
183
+ });
184
+ };
185
+ if (!options.overwrite) {
186
+ return write();
187
+ }
188
+ // Match directory behavior by replacing the existing target before writing.
189
+ node_fs_1.default.lstat(dest, (err, destStat) => {
190
+ if (err) {
191
+ if (err.code === 'ENOENT') {
192
+ return write();
193
+ }
194
+ return callback(err);
195
+ }
196
+ if (destStat.isDirectory()) {
197
+ return remove(dest, (err) => {
198
+ if (err) {
199
+ return callback(err);
200
+ }
201
+ write();
202
+ });
203
+ }
204
+ node_fs_1.default.unlink(dest, (err) => {
205
+ if (err) {
206
+ return callback(err);
207
+ }
208
+ write();
209
+ });
138
210
  });
139
- readStream.pipe(writeStream);
140
211
  }
141
212
  });
142
213
  }
@@ -144,37 +215,31 @@ function copy(src, dir, umask, callback) {
144
215
  * Removes files, directories, and symlinks without following symbolic links.
145
216
  */
146
217
  function remove(src, callback) {
147
- node_fs_1.default.lstat(src, (err, stat) => {
218
+ node_fs_1.default.rm(src, { recursive: true, force: false }, once(callback));
219
+ }
220
+ /**
221
+ * Removes all entries inside a directory while preserving the directory itself.
222
+ */
223
+ function emptyDir(src, callback) {
224
+ callback = once(callback);
225
+ node_fs_1.default.readdir(src, (err, list) => {
148
226
  if (err) {
149
227
  return callback(err);
150
228
  }
151
- if (stat.isSymbolicLink()) {
152
- return node_fs_1.default.unlink(src, callback);
229
+ if (list.length === 0) {
230
+ return callback(null);
153
231
  }
154
- if (stat.isDirectory()) {
155
- node_fs_1.default.readdir(src, (err, list) => {
232
+ let reduce = list.length;
233
+ for (const loc of list) {
234
+ node_fs_1.default.rm(node_path_1.default.join(src, loc), { recursive: true, force: false }, (err) => {
156
235
  if (err) {
157
236
  return callback(err);
158
237
  }
159
- if (list.length === 0) {
160
- return node_fs_1.default.rmdir(src, callback);
161
- }
162
- let reduce = list.length;
163
- for (const loc of list) {
164
- remove(node_path_1.default.join(src, loc), (err) => {
165
- if (err) {
166
- return callback(err);
167
- }
168
- if (--reduce === 0) {
169
- node_fs_1.default.rmdir(src, callback);
170
- }
171
- });
238
+ if (--reduce === 0) {
239
+ callback(null);
172
240
  }
173
241
  });
174
242
  }
175
- else {
176
- node_fs_1.default.unlink(src, callback);
177
- }
178
243
  });
179
244
  }
180
245
  /**
@@ -31,7 +31,7 @@ const distDir = node_path_1.default.resolve(__dirname);
31
31
  // The runner skips itself and forwards the rest to Node's native test harness.
32
32
  const testFiles = collectTestFiles(distDir).filter((file) => file !== __filename);
33
33
  if (!testFiles.length) {
34
- console.warn("⚠️ No test files found in dist/");
34
+ console.warn("No test files found in dist/");
35
35
  process.exit(0);
36
36
  }
37
37
  const { status } = (0, node_child_process_1.spawnSync)(process.execPath, ['--test', ...testFiles], {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pwd-fs",
3
- "version": "3.3.5",
3
+ "version": "3.5.0",
4
4
  "description": "Path-aware file system utilities with scoped working directories and recursive operations",
5
5
  "keywords": [
6
6
  "umask",
@@ -31,16 +31,11 @@
31
31
  "url": "https://github.com/woodger/pwd-fs/issues"
32
32
  },
33
33
  "engines": {
34
- "node": ">=13.2.0"
35
- },
36
- "main": "./dist/index.js",
37
- "types": "./dist/index.d.ts",
38
- "exports": {
39
- ".": {
40
- "types": "./dist/index.d.ts",
41
- "default": "./dist/index.js"
42
- }
34
+ "node": ">=18"
43
35
  },
36
+ "packageManager": "yarn@1.22.22",
37
+ "main": "dist/index.js",
38
+ "types": "dist/index.d.ts",
44
39
  "files": [
45
40
  "dist",
46
41
  "readme.md",
@@ -53,12 +48,12 @@
53
48
  },
54
49
  "devDependencies": {
55
50
  "@eslint/js": "^10.0.1",
56
- "@types/chance": "^1.1.7",
57
- "@types/node": "^24.5.1",
51
+ "@types/chance": "^1.1.8",
52
+ "@types/node": "^25.6.2",
58
53
  "chance": "^1.1.13",
59
- "eslint": "^10.1.0",
60
- "globals": "^17.4.0",
61
- "typescript": "^5.9.2",
62
- "typescript-eslint": "^8.57.2"
54
+ "eslint": "^10.3.0",
55
+ "globals": "^17.6.0",
56
+ "typescript": "^6.0.3",
57
+ "typescript-eslint": "^8.59.2"
63
58
  }
64
59
  }