pwd-fs 3.4.0 → 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 (44) 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 -7
  10. package/dist/powered-file-system/copy.js +11 -12
  11. package/dist/powered-file-system/empty-dir.d.ts +4 -4
  12. package/dist/powered-file-system/empty-dir.js +9 -10
  13. package/dist/powered-file-system/mkdir.d.ts +9 -6
  14. package/dist/powered-file-system/mkdir.js +9 -10
  15. package/dist/powered-file-system/read.d.ts +10 -6
  16. package/dist/powered-file-system/read.js +9 -5
  17. package/dist/powered-file-system/readdir.d.ts +11 -6
  18. package/dist/powered-file-system/readdir.js +8 -6
  19. package/dist/powered-file-system/readlink.d.ts +9 -4
  20. package/dist/powered-file-system/readlink.js +8 -6
  21. package/dist/powered-file-system/realpath.d.ts +9 -4
  22. package/dist/powered-file-system/realpath.js +8 -6
  23. package/dist/powered-file-system/remove.d.ts +5 -5
  24. package/dist/powered-file-system/remove.js +11 -20
  25. package/dist/powered-file-system/rename.d.ts +5 -5
  26. package/dist/powered-file-system/rename.js +11 -7
  27. package/dist/powered-file-system/stat.d.ts +4 -4
  28. package/dist/powered-file-system/stat.js +8 -6
  29. package/dist/powered-file-system/symlink.d.ts +4 -4
  30. package/dist/powered-file-system/symlink.js +11 -4
  31. package/dist/powered-file-system/test.d.ts +10 -5
  32. package/dist/powered-file-system/test.js +8 -5
  33. package/dist/powered-file-system/write.d.ts +10 -7
  34. package/dist/powered-file-system/write.js +9 -6
  35. package/dist/powered-file-system.d.ts +103 -56
  36. package/dist/powered-file-system.js +5 -48
  37. package/dist/powered-file-system.test.js +25 -0
  38. package/dist/recurse-io-sync.d.ts +1 -1
  39. package/dist/recurse-io-sync.js +11 -25
  40. package/dist/recurse-io.d.ts +1 -1
  41. package/dist/recurse-io.js +31 -49
  42. package/dist/suite.test.js +1 -1
  43. package/package.json +11 -16
  44. package/readme.md +30 -11
@@ -11,7 +11,7 @@ export declare function chmod(src: string, mode: number, callback: NoParamCallba
11
11
  /**
12
12
  * Applies ownership recursively while preserving current values when uid/gid are omitted.
13
13
  */
14
- 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;
15
15
  /**
16
16
  * Copies a file system node into the target directory, creating directories as needed.
17
17
  */
@@ -11,10 +11,22 @@ exports.emptyDir = emptyDir;
11
11
  exports.mkdir = mkdir;
12
12
  const node_fs_1 = __importDefault(require("node:fs"));
13
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
+ }
14
25
  /**
15
26
  * Applies chmod depth-first so directories are updated after their contents.
16
27
  */
17
28
  function chmod(src, mode, callback) {
29
+ callback = once(callback);
18
30
  let reduce = 0;
19
31
  node_fs_1.default.stat(src, (err, stats) => {
20
32
  if (err)
@@ -49,40 +61,38 @@ function chmod(src, mode, callback) {
49
61
  * Applies ownership recursively while preserving current values when uid/gid are omitted.
50
62
  */
51
63
  function chown(src, uid, gid, callback) {
64
+ callback = once(callback);
52
65
  let reduce = 0;
53
66
  node_fs_1.default.stat(src, (err, stats) => {
54
67
  if (err) {
55
68
  return callback(err);
56
69
  }
57
- if (uid === 0) {
58
- uid = stats.uid;
59
- }
60
- if (gid === 0) {
61
- gid = stats.gid;
62
- }
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;
63
73
  if (stats.isDirectory()) {
64
74
  node_fs_1.default.readdir(src, (err, list) => {
65
75
  if (err) {
66
76
  return callback(err);
67
77
  }
68
78
  if (list.length === 0) {
69
- return node_fs_1.default.chown(src, uid, gid, callback);
79
+ return node_fs_1.default.chown(src, nextUid, nextGid, callback);
70
80
  }
71
81
  reduce = list.length;
72
82
  for (const loc of list) {
73
- chown(node_path_1.default.join(src, loc), uid, gid, (err) => {
83
+ chown(node_path_1.default.join(src, loc), nextUid, nextGid, (err) => {
74
84
  if (err) {
75
85
  return callback(err);
76
86
  }
77
87
  if (--reduce === 0) {
78
- node_fs_1.default.chown(src, uid, gid, callback);
88
+ node_fs_1.default.chown(src, nextUid, nextGid, callback);
79
89
  }
80
90
  });
81
91
  }
82
92
  });
83
93
  }
84
94
  else {
85
- node_fs_1.default.chown(src, uid, gid, callback);
95
+ node_fs_1.default.chown(src, nextUid, nextGid, callback);
86
96
  }
87
97
  });
88
98
  }
@@ -90,6 +100,7 @@ function chown(src, uid, gid, callback) {
90
100
  * Copies a file system node into the target directory, creating directories as needed.
91
101
  */
92
102
  function copy(src, dir, options, callback) {
103
+ callback = once(callback);
93
104
  node_fs_1.default.stat(src, (err, stat) => {
94
105
  if (err) {
95
106
  return callback(err);
@@ -135,6 +146,7 @@ function copy(src, dir, options, callback) {
135
146
  if (!options.overwrite) {
136
147
  return create();
137
148
  }
149
+ // Overwrite is implemented as replace-before-copy to support directory targets.
138
150
  node_fs_1.default.lstat(dest, (err, destStat) => {
139
151
  if (err) {
140
152
  if (err.code === 'ENOENT') {
@@ -162,18 +174,18 @@ function copy(src, dir, options, callback) {
162
174
  else {
163
175
  const mode = 0o666 & ~options.umask;
164
176
  const write = () => {
165
- const readStream = node_fs_1.default.createReadStream(src);
166
- const writeStream = node_fs_1.default.createWriteStream(dest, { mode });
167
- readStream.on('error', callback);
168
- writeStream.on('error', callback);
169
- writeStream.on('close', () => {
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
+ }
170
182
  node_fs_1.default.chmod(dest, mode, callback);
171
183
  });
172
- readStream.pipe(writeStream);
173
184
  };
174
185
  if (!options.overwrite) {
175
186
  return write();
176
187
  }
188
+ // Match directory behavior by replacing the existing target before writing.
177
189
  node_fs_1.default.lstat(dest, (err, destStat) => {
178
190
  if (err) {
179
191
  if (err.code === 'ENOENT') {
@@ -203,43 +215,13 @@ function copy(src, dir, options, callback) {
203
215
  * Removes files, directories, and symlinks without following symbolic links.
204
216
  */
205
217
  function remove(src, callback) {
206
- node_fs_1.default.lstat(src, (err, stat) => {
207
- if (err) {
208
- return callback(err);
209
- }
210
- if (stat.isSymbolicLink()) {
211
- return node_fs_1.default.unlink(src, callback);
212
- }
213
- if (stat.isDirectory()) {
214
- node_fs_1.default.readdir(src, (err, list) => {
215
- if (err) {
216
- return callback(err);
217
- }
218
- if (list.length === 0) {
219
- return node_fs_1.default.rmdir(src, callback);
220
- }
221
- let reduce = list.length;
222
- for (const loc of list) {
223
- remove(node_path_1.default.join(src, loc), (err) => {
224
- if (err) {
225
- return callback(err);
226
- }
227
- if (--reduce === 0) {
228
- node_fs_1.default.rmdir(src, callback);
229
- }
230
- });
231
- }
232
- });
233
- }
234
- else {
235
- node_fs_1.default.unlink(src, callback);
236
- }
237
- });
218
+ node_fs_1.default.rm(src, { recursive: true, force: false }, once(callback));
238
219
  }
239
220
  /**
240
221
  * Removes all entries inside a directory while preserving the directory itself.
241
222
  */
242
223
  function emptyDir(src, callback) {
224
+ callback = once(callback);
243
225
  node_fs_1.default.readdir(src, (err, list) => {
244
226
  if (err) {
245
227
  return callback(err);
@@ -249,7 +231,7 @@ function emptyDir(src, callback) {
249
231
  }
250
232
  let reduce = list.length;
251
233
  for (const loc of list) {
252
- remove(node_path_1.default.join(src, loc), (err) => {
234
+ node_fs_1.default.rm(node_path_1.default.join(src, loc), { recursive: true, force: false }, (err) => {
253
235
  if (err) {
254
236
  return callback(err);
255
237
  }
@@ -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.4.0",
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
  }
package/readme.md CHANGED
@@ -12,11 +12,11 @@ It provides:
12
12
  - matching synchronous variants via `{ sync: true }`
13
13
  - recursive helpers for copy, remove, chmod, chown, and mkdir
14
14
 
15
- All relative paths are resolved against `pfs.pwd`.
15
+ Relative paths are resolved against `pfs.pwd`. Absolute paths are used as-is, so `pfs.pwd` is a convenience base path, not a sandbox.
16
16
 
17
17
  ## Why Use It
18
18
 
19
- Use `pwd-fs` when you want file system operations to be scoped to a specific working directory without manually calling `path.resolve()` before every operation.
19
+ Use `pwd-fs` when you want file system operations to be rooted at a specific working directory without manually calling `path.resolve()` before every operation.
20
20
 
21
21
  It is especially useful for:
22
22
 
@@ -40,6 +40,7 @@ npm install pwd-fs
40
40
  - [API](#api)
41
41
  - [`new PoweredFileSystem(pwd?)`](#new-poweredfilesystempwd)
42
42
  - [`pfs.pwd`](#pfspwd)
43
+ - [`pfs.resolve(src)`](#pfsresolvesrc)
43
44
  - [`pfs.constants`](#pfsconstants)
44
45
  - [`PoweredFileSystem.bitmask(mode)`](#poweredfilesystembitmaskmode)
45
46
  - [`pfs.test(src, options?)`](#pfstestsrc-options)
@@ -77,7 +78,7 @@ await pfs.mkdir('./own/project'); // recursively create the directory
77
78
 
78
79
  ## Common Recipes
79
80
 
80
- ### Work inside a scoped directory
81
+ ### Work inside a project directory
81
82
 
82
83
  ```ts
83
84
  import { PoweredFileSystem } from 'pwd-fs';
@@ -137,7 +138,7 @@ if (await pfs.test('./tmp')) {
137
138
 
138
139
  ## Compatibility
139
140
 
140
- - package `engines`: Node.js `>=13.2.0`
141
+ - package `engines`: Node.js `>=18`
141
142
  - module format: CommonJS package output with TypeScript declarations
142
143
  - platform notes:
143
144
  - `chown()` is effectively a no-op on Windows apart from path validation
@@ -158,7 +159,7 @@ import PoweredFileSystem, { pfs, bitmask } from 'pwd-fs';
158
159
 
159
160
  ### `new PoweredFileSystem(pwd?)`
160
161
 
161
- Creates a new instance rooted at `pwd`.
162
+ Creates a new instance with `pwd` as the base directory for relative paths.
162
163
 
163
164
  - `pwd?: string`
164
165
  - default: `process.cwd()`
@@ -171,7 +172,22 @@ const pfs = new PoweredFileSystem('./workspace');
171
172
 
172
173
  ### `pfs.pwd`
173
174
 
174
- Absolute base directory used to resolve all relative paths.
175
+ Absolute base directory used to resolve relative paths.
176
+
177
+ ### `pfs.resolve(src)`
178
+
179
+ Resolves `src` against `pfs.pwd`.
180
+
181
+ ```ts
182
+ const pfs = new PoweredFileSystem('/workspace/project');
183
+ const file = pfs.resolve('./src/index.ts');
184
+ ```
185
+
186
+ Absolute paths are preserved:
187
+
188
+ ```ts
189
+ pfs.resolve('/tmp/outside.txt'); // '/tmp/outside.txt'
190
+ ```
175
191
 
176
192
  ### `pfs.constants`
177
193
 
@@ -506,7 +522,8 @@ Typical cases:
506
522
  - `emptyDir()` fails when the target is not a directory
507
523
  - `write()` fails when the target path points to a directory
508
524
  - `copy()` fails when the source does not exist
509
- - `copy()` also fails when the destination already contains an entry with the same basename as the source, unless `overwrite: true` is used
525
+ - `copy()` fails when the destination already contains an entry with the same basename as the source, unless `overwrite: true` is used
526
+ - `copy()` fails when a directory is copied into itself
510
527
  - `symlink()` fails when the destination already exists
511
528
  - `mkdir()` accepts an existing directory, but fails when a path segment is a file
512
529
 
@@ -535,7 +552,8 @@ Effective permissions:
535
552
 
536
553
  ## Notes
537
554
 
538
- - Relative paths are always resolved against `pfs.pwd`
555
+ - Relative paths are resolved against `pfs.pwd`
556
+ - Absolute paths are not constrained by `pfs.pwd`
539
557
  - `stat()` returns `lstat()` data
540
558
  - `remove()` does not follow symbolic links
541
559
  - `append()` is kept for backward compatibility and is deprecated
@@ -562,9 +580,10 @@ Prefer native `node:fs` APIs directly when you need:
562
580
  ## Development
563
581
 
564
582
  ```bash
565
- npm install
566
- npm run build
567
- npm test
583
+ yarn install --frozen-lockfile
584
+ yarn lint
585
+ yarn build
586
+ yarn test
568
587
  ```
569
588
 
570
589
  ## License