@gjsify/fs 0.1.15 → 0.2.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 (67) hide show
  1. package/lib/esm/callback.js +22 -13
  2. package/lib/esm/cp.js +253 -0
  3. package/lib/esm/dir.js +160 -0
  4. package/lib/esm/fd-ops.js +189 -0
  5. package/lib/esm/file-handle.js +263 -84
  6. package/lib/esm/fs-watcher.js +88 -4
  7. package/lib/esm/glob.js +164 -0
  8. package/lib/esm/index.js +128 -2
  9. package/lib/esm/promises.js +90 -27
  10. package/lib/esm/read-stream.js +53 -43
  11. package/lib/esm/stat-watcher.js +121 -0
  12. package/lib/esm/statfs.js +57 -0
  13. package/lib/esm/sync.js +70 -52
  14. package/lib/esm/utils.js +7 -0
  15. package/lib/esm/utimes.js +62 -0
  16. package/lib/esm/write-stream.js +2 -5
  17. package/lib/types/cp.d.ts +18 -0
  18. package/lib/types/cp.spec.d.ts +2 -0
  19. package/lib/types/dir.d.ts +29 -0
  20. package/lib/types/dir.spec.d.ts +2 -0
  21. package/lib/types/fd-ops.d.ts +57 -0
  22. package/lib/types/fd-ops.spec.d.ts +2 -0
  23. package/lib/types/file-handle.d.ts +34 -4
  24. package/lib/types/fs-watcher.d.ts +9 -2
  25. package/lib/types/glob.d.ts +8 -0
  26. package/lib/types/glob.spec.d.ts +2 -0
  27. package/lib/types/index.d.ts +51 -1
  28. package/lib/types/promises.d.ts +31 -4
  29. package/lib/types/read-stream.d.ts +3 -1
  30. package/lib/types/stat-watcher.d.ts +21 -0
  31. package/lib/types/statfs.d.ts +35 -0
  32. package/lib/types/statfs.spec.d.ts +2 -0
  33. package/lib/types/sync.d.ts +4 -7
  34. package/lib/types/utils.d.ts +2 -0
  35. package/lib/types/utimes.d.ts +13 -0
  36. package/lib/types/utimes.spec.d.ts +2 -0
  37. package/lib/types/watch.spec.d.ts +2 -0
  38. package/lib/types/watchfile.spec.d.ts +2 -0
  39. package/lib/types/write-stream.d.ts +1 -2
  40. package/package.json +12 -12
  41. package/src/callback.ts +22 -13
  42. package/src/cp.spec.ts +181 -0
  43. package/src/cp.ts +328 -0
  44. package/src/dir.spec.ts +204 -0
  45. package/src/dir.ts +199 -0
  46. package/src/fd-ops.spec.ts +234 -0
  47. package/src/fd-ops.ts +251 -0
  48. package/src/file-handle.ts +264 -94
  49. package/src/fs-watcher.ts +101 -6
  50. package/src/glob.spec.ts +201 -0
  51. package/src/glob.ts +205 -0
  52. package/src/index.ts +74 -0
  53. package/src/promises.ts +94 -29
  54. package/src/read-stream.ts +49 -43
  55. package/src/stat-watcher.ts +116 -0
  56. package/src/statfs.spec.ts +67 -0
  57. package/src/statfs.ts +92 -0
  58. package/src/streams.spec.ts +58 -0
  59. package/src/sync.ts +75 -57
  60. package/src/test.mts +13 -2
  61. package/src/utils.ts +10 -0
  62. package/src/utimes.spec.ts +113 -0
  63. package/src/utimes.ts +97 -0
  64. package/src/watch.spec.ts +171 -0
  65. package/src/watchfile.spec.ts +185 -0
  66. package/src/write-stream.ts +5 -8
  67. package/tsconfig.tsbuildinfo +1 -1
@@ -2,7 +2,7 @@ import Gio from "@girs/gio-2.0";
2
2
  import GLib from "@girs/glib-2.0";
3
3
  import { Buffer } from "node:buffer";
4
4
  import { Readable } from "node:stream";
5
- import { URL, fileURLToPath } from "node:url";
5
+ import { normalizePath } from "./utils.js";
6
6
  class ReadStream extends Readable {
7
7
  bytesRead = 0;
8
8
  path;
@@ -10,56 +10,62 @@ class ReadStream extends Readable {
10
10
  fd = null;
11
11
  _gioFile;
12
12
  _inputStream = null;
13
+ _cancellable = new Gio.Cancellable();
13
14
  _start;
14
15
  _end;
15
16
  _pos;
16
17
  close(callback) {
18
+ this._cancellable.cancel();
17
19
  if (this._inputStream) {
18
- try {
19
- this._inputStream.close(null);
20
- } catch {
21
- }
20
+ this._inputStream.close_async(GLib.PRIORITY_DEFAULT, null, () => {
21
+ });
22
22
  this._inputStream = null;
23
23
  }
24
24
  this.destroy();
25
25
  if (callback) callback(null);
26
26
  }
27
27
  constructor(path, opts) {
28
- if (path instanceof URL) {
29
- path = fileURLToPath(path);
30
- }
28
+ const pathStr = normalizePath(path);
31
29
  super({
32
30
  highWaterMark: opts?.highWaterMark ?? 64 * 1024,
33
31
  encoding: opts?.encoding,
34
32
  objectMode: false
35
33
  });
36
- this.path = path.toString();
37
- this._gioFile = Gio.File.new_for_path(this.path.toString());
34
+ this.path = pathStr;
35
+ this._gioFile = Gio.File.new_for_path(pathStr);
38
36
  this._start = opts?.start ?? 0;
39
37
  this._end = opts?.end ?? Infinity;
40
38
  this._pos = this._start;
41
- Promise.resolve().then(() => {
42
- if (!this._inputStream && !this.destroyed) {
43
- try {
44
- this._inputStream = this._gioFile.read(null);
45
- this.pending = false;
46
- this.emit("open", 0);
47
- this.emit("ready");
48
- if (this._start > 0 && this._inputStream.can_seek()) {
49
- this._inputStream.seek(this._start, GLib.SeekType.SET, null);
50
- }
51
- } catch (err) {
52
- this.destroy(err);
39
+ }
40
+ // Use _construct() for async file opening so the stream machinery defers
41
+ // _read() until the file is open. This avoids the fragile _pendingReadSize
42
+ // pattern and correctly handles backpressure via the constructed flag.
43
+ _construct(callback) {
44
+ this._gioFile.read_async(GLib.PRIORITY_DEFAULT, this._cancellable, (_source, asyncResult) => {
45
+ if (this.destroyed) {
46
+ callback();
47
+ return;
48
+ }
49
+ try {
50
+ this._inputStream = this._gioFile.read_finish(asyncResult);
51
+ this.pending = false;
52
+ this.emit("open", 0);
53
+ this.emit("ready");
54
+ if (this._start > 0 && this._inputStream.can_seek()) {
55
+ this._inputStream.seek(this._start, GLib.SeekType.SET, null);
56
+ }
57
+ callback();
58
+ } catch (err) {
59
+ if (!this._cancellable.is_cancelled()) {
60
+ callback(err);
53
61
  }
54
62
  }
55
63
  });
56
64
  }
57
65
  _read(size) {
58
- if (!this._inputStream) {
59
- if (this.destroyed) return;
60
- Promise.resolve().then(() => this._read(size));
61
- return;
62
- }
66
+ this._doRead(size);
67
+ }
68
+ _doRead(size) {
63
69
  let toRead = size;
64
70
  if (this._end !== Infinity) {
65
71
  const remaining = this._end - this._pos + 1;
@@ -69,26 +75,30 @@ class ReadStream extends Readable {
69
75
  }
70
76
  toRead = Math.min(size, remaining);
71
77
  }
72
- try {
73
- const gbytes = this._inputStream.read_bytes(toRead, null);
74
- const data = gbytes.get_data();
75
- if (!data || data.length === 0) {
76
- this.push(null);
77
- return;
78
+ const stream = this._inputStream;
79
+ stream.read_bytes_async(toRead, GLib.PRIORITY_DEFAULT, this._cancellable, (_source, asyncResult) => {
80
+ try {
81
+ const gbytes = stream.read_bytes_finish(asyncResult);
82
+ const data = gbytes.get_data();
83
+ if (!data || data.length === 0) {
84
+ this.push(null);
85
+ return;
86
+ }
87
+ this.bytesRead += data.length;
88
+ this._pos += data.length;
89
+ this.push(Buffer.from(data));
90
+ } catch (err) {
91
+ if (!this._cancellable.is_cancelled()) {
92
+ this.destroy(err);
93
+ }
78
94
  }
79
- this.bytesRead += data.length;
80
- this._pos += data.length;
81
- this.push(Buffer.from(data));
82
- } catch (err) {
83
- this.destroy(err);
84
- }
95
+ });
85
96
  }
86
97
  _destroy(error, callback) {
98
+ this._cancellable.cancel();
87
99
  if (this._inputStream) {
88
- try {
89
- this._inputStream.close(null);
90
- } catch {
91
- }
100
+ this._inputStream.close_async(GLib.PRIORITY_DEFAULT, null, () => {
101
+ });
92
102
  this._inputStream = null;
93
103
  }
94
104
  callback(error);
@@ -0,0 +1,121 @@
1
+ import { EventEmitter } from "node:events";
2
+ import { statSync } from "./sync.js";
3
+ function zeroedStat() {
4
+ return {
5
+ dev: 0,
6
+ ino: 0,
7
+ mode: 0,
8
+ nlink: 0,
9
+ uid: 0,
10
+ gid: 0,
11
+ rdev: 0,
12
+ size: 0,
13
+ blksize: 0,
14
+ blocks: 0,
15
+ atimeMs: 0,
16
+ mtimeMs: 0,
17
+ ctimeMs: 0,
18
+ birthtimeMs: 0,
19
+ atime: /* @__PURE__ */ new Date(0),
20
+ mtime: /* @__PURE__ */ new Date(0),
21
+ ctime: /* @__PURE__ */ new Date(0),
22
+ birthtime: /* @__PURE__ */ new Date(0),
23
+ isFile: () => false,
24
+ isDirectory: () => false,
25
+ isBlockDevice: () => false,
26
+ isCharacterDevice: () => false,
27
+ isSymbolicLink: () => false,
28
+ isFIFO: () => false,
29
+ isSocket: () => false
30
+ };
31
+ }
32
+ class StatWatcher extends EventEmitter {
33
+ _path;
34
+ _interval;
35
+ _timerId = null;
36
+ _prev;
37
+ _changeCount = 0;
38
+ constructor(path, interval) {
39
+ super();
40
+ this._path = path;
41
+ this._interval = interval;
42
+ this._prev = zeroedStat();
43
+ }
44
+ start() {
45
+ try {
46
+ this._prev = statSync(this._path);
47
+ } catch {
48
+ }
49
+ this._timerId = setInterval(() => {
50
+ let curr;
51
+ try {
52
+ curr = statSync(this._path);
53
+ } catch {
54
+ curr = zeroedStat();
55
+ }
56
+ const prev = this._prev;
57
+ if (curr.mtimeMs !== prev.mtimeMs || curr.size !== prev.size || curr.ino !== prev.ino) {
58
+ this._prev = curr;
59
+ this.emit("change", curr, prev);
60
+ }
61
+ }, this._interval);
62
+ }
63
+ stop() {
64
+ if (this._timerId !== null) {
65
+ clearInterval(this._timerId);
66
+ this._timerId = null;
67
+ }
68
+ this.emit("stop");
69
+ }
70
+ addChangeListener(listener) {
71
+ this._changeCount++;
72
+ this.on("change", listener);
73
+ }
74
+ removeChangeListener(listener) {
75
+ this._changeCount--;
76
+ this.removeListener("change", listener);
77
+ }
78
+ removeAllChangeListeners() {
79
+ this._changeCount = 0;
80
+ this.removeAllListeners("change");
81
+ }
82
+ get changeListenerCount() {
83
+ return this._changeCount;
84
+ }
85
+ }
86
+ const statWatchers = /* @__PURE__ */ new Map();
87
+ function watchFile(filename, options, listener) {
88
+ if (typeof options === "function") {
89
+ listener = options;
90
+ options = {};
91
+ }
92
+ const interval = options.interval ?? 5007;
93
+ const resolved = filename.toString();
94
+ let watcher = statWatchers.get(resolved);
95
+ if (!watcher) {
96
+ watcher = new StatWatcher(resolved, interval);
97
+ watcher.start();
98
+ statWatchers.set(resolved, watcher);
99
+ }
100
+ if (listener) watcher.addChangeListener(listener);
101
+ return watcher;
102
+ }
103
+ function unwatchFile(filename, listener) {
104
+ const resolved = filename.toString();
105
+ const watcher = statWatchers.get(resolved);
106
+ if (!watcher) return;
107
+ if (listener) {
108
+ watcher.removeChangeListener(listener);
109
+ } else {
110
+ watcher.removeAllChangeListeners();
111
+ }
112
+ if (watcher.changeListenerCount === 0) {
113
+ watcher.stop();
114
+ statWatchers.delete(resolved);
115
+ }
116
+ }
117
+ export {
118
+ StatWatcher,
119
+ unwatchFile,
120
+ watchFile
121
+ };
@@ -0,0 +1,57 @@
1
+ import Gio from "@girs/gio-2.0";
2
+ import GLib from "@girs/glib-2.0";
3
+ import { normalizePath } from "./utils.js";
4
+ const FS_INFO_ATTRS = [
5
+ "filesystem::size",
6
+ "filesystem::free"
7
+ ].join(",");
8
+ const BSIZE = 4096;
9
+ function buildStatFs(info) {
10
+ const totalBytes = Number(info.get_attribute_uint64("filesystem::size") ?? 0);
11
+ const freeBytes = Number(info.get_attribute_uint64("filesystem::free") ?? 0);
12
+ const blocks = Math.floor(totalBytes / BSIZE);
13
+ const bfree = Math.floor(freeBytes / BSIZE);
14
+ return { type: 0, bsize: BSIZE, blocks, bfree, bavail: bfree, files: 0, ffree: 0 };
15
+ }
16
+ function buildBigIntStatFs(info) {
17
+ const totalBytes = BigInt(info.get_attribute_uint64("filesystem::size") ?? 0);
18
+ const freeBytes = BigInt(info.get_attribute_uint64("filesystem::free") ?? 0);
19
+ const bsize = BigInt(BSIZE);
20
+ const blocks = totalBytes / bsize;
21
+ const bfree = freeBytes / bsize;
22
+ return { type: 0n, bsize, blocks, bfree, bavail: bfree, files: 0n, ffree: 0n };
23
+ }
24
+ function statfsSync(path, options) {
25
+ const file = Gio.File.new_for_path(normalizePath(path));
26
+ const info = file.query_filesystem_info(FS_INFO_ATTRS, null);
27
+ return options?.bigint === true ? buildBigIntStatFs(info) : buildStatFs(info);
28
+ }
29
+ function queryFsInfoAsync(path, useBigInt) {
30
+ return new Promise((resolve, reject) => {
31
+ const file = Gio.File.new_for_path(normalizePath(path));
32
+ file.query_filesystem_info_async(FS_INFO_ATTRS, GLib.PRIORITY_DEFAULT, null, (_s, res) => {
33
+ try {
34
+ const info = file.query_filesystem_info_finish(res);
35
+ resolve(useBigInt ? buildBigIntStatFs(info) : buildStatFs(info));
36
+ } catch (err) {
37
+ reject(err);
38
+ }
39
+ });
40
+ });
41
+ }
42
+ function statfs(path, optionsOrCb, callback) {
43
+ if (typeof optionsOrCb === "function") {
44
+ callback = optionsOrCb;
45
+ optionsOrCb = {};
46
+ }
47
+ const useBigInt = optionsOrCb?.bigint === true;
48
+ queryFsInfoAsync(path, useBigInt).then((result) => callback(null, result), (err) => callback(err, null));
49
+ }
50
+ async function statfsAsync(path, options) {
51
+ return queryFsInfoAsync(path, options?.bigint === true);
52
+ }
53
+ export {
54
+ statfs,
55
+ statfsAsync,
56
+ statfsSync
57
+ };
package/lib/esm/sync.js CHANGED
@@ -9,29 +9,31 @@ import { FileHandle } from "./file-handle.js";
9
9
  import { Dirent } from "./dirent.js";
10
10
  import { Stats, BigIntStats, STAT_ATTRIBUTES } from "./stats.js";
11
11
  import { createNodeError, isNotFoundError } from "./errors.js";
12
- import { tempDirPath } from "./utils.js";
12
+ import { tempDirPath, normalizePath } from "./utils.js";
13
13
  function statSync(path, options) {
14
+ const pathStr = normalizePath(path);
14
15
  try {
15
- const file = Gio.File.new_for_path(path.toString());
16
+ const file = Gio.File.new_for_path(pathStr);
16
17
  const info = file.query_info(STAT_ATTRIBUTES, Gio.FileQueryInfoFlags.NONE, null);
17
- return options?.bigint ? new BigIntStats(info, path) : new Stats(info, path);
18
+ return options?.bigint ? new BigIntStats(info, pathStr) : new Stats(info, pathStr);
18
19
  } catch (err) {
19
20
  if (options?.throwIfNoEntry === false && isNotFoundError(err)) return void 0;
20
- throw createNodeError(err, "stat", path);
21
+ throw createNodeError(err, "stat", pathStr);
21
22
  }
22
23
  }
23
24
  function lstatSync(path, options) {
25
+ const pathStr = normalizePath(path);
24
26
  try {
25
- const file = Gio.File.new_for_path(path.toString());
27
+ const file = Gio.File.new_for_path(pathStr);
26
28
  const info = file.query_info(STAT_ATTRIBUTES, Gio.FileQueryInfoFlags.NOFOLLOW_SYMLINKS, null);
27
- return options?.bigint ? new BigIntStats(info, path) : new Stats(info, path);
29
+ return options?.bigint ? new BigIntStats(info, pathStr) : new Stats(info, pathStr);
28
30
  } catch (err) {
29
31
  if (options?.throwIfNoEntry === false && isNotFoundError(err)) return void 0;
30
- throw createNodeError(err, "lstat", path);
32
+ throw createNodeError(err, "lstat", pathStr);
31
33
  }
32
34
  }
33
35
  function readdirSync(path, options) {
34
- const pathStr = path.toString();
36
+ const pathStr = normalizePath(path);
35
37
  const file = Gio.File.new_for_path(pathStr);
36
38
  const enumerator = file.enumerate_children(
37
39
  "standard::name,standard::type",
@@ -64,7 +66,8 @@ function readdirSync(path, options) {
64
66
  }
65
67
  const MAX_SYMLINK_DEPTH = 40;
66
68
  function realpathSync(path) {
67
- let current = Gio.File.new_for_path(path.toString());
69
+ const pathStr = normalizePath(path);
70
+ let current = Gio.File.new_for_path(pathStr);
68
71
  let depth = 0;
69
72
  while (true) {
70
73
  const info = current.query_info(
@@ -79,26 +82,29 @@ function realpathSync(path) {
79
82
  const parent = current.get_parent();
80
83
  current = parent ? parent.resolve_relative_path(target) : Gio.File.new_for_path(target);
81
84
  if (++depth > MAX_SYMLINK_DEPTH) {
82
- throw new Error(`ELOOP: too many levels of symbolic links, realpath '${path}'`);
85
+ throw new Error(`ELOOP: too many levels of symbolic links, realpath '${pathStr}'`);
83
86
  }
84
87
  }
85
88
  }
86
89
  realpathSync.native = realpathSync;
87
90
  function symlinkSync(target, path, _type) {
88
- const file = Gio.File.new_for_path(path.toString());
89
- file.make_symbolic_link(target.toString(), null);
91
+ const pathStr = normalizePath(path);
92
+ const targetStr = normalizePath(target);
93
+ const file = Gio.File.new_for_path(pathStr);
94
+ file.make_symbolic_link(targetStr, null);
90
95
  }
91
96
  function readFileSync(path, options = { encoding: null, flag: "r" }) {
92
- const file = Gio.File.new_for_path(path);
97
+ const pathStr = normalizePath(path);
98
+ const file = Gio.File.new_for_path(pathStr);
93
99
  try {
94
100
  const [ok, data] = file.load_contents(null);
95
101
  if (!ok) {
96
- throw createNodeError(new Error("failed to read file"), "read", path);
102
+ throw createNodeError(new Error("failed to read file"), "read", pathStr);
97
103
  }
98
104
  return encodeUint8Array(getEncodingFromOptions(options, "buffer"), data);
99
105
  } catch (err) {
100
106
  if (err.code && typeof err.code === "string") throw err;
101
- throw createNodeError(err, "read", path);
107
+ throw createNodeError(err, "read", pathStr);
102
108
  }
103
109
  }
104
110
  function mkdirSync(path, options) {
@@ -110,9 +116,7 @@ function mkdirSync(path, options) {
110
116
  } else {
111
117
  mode = options || 511;
112
118
  }
113
- if (typeof path !== "string") {
114
- path = path.toString();
115
- }
119
+ path = normalizePath(path);
116
120
  if (typeof mode === "string") {
117
121
  throw new TypeError("mode as string is currently not supported!");
118
122
  }
@@ -156,47 +160,53 @@ function mkdirSyncRecursive(pathStr, mode) {
156
160
  }
157
161
  }
158
162
  function rmdirSync(path, _options) {
159
- const file = Gio.File.new_for_path(path.toString());
163
+ const pathStr = normalizePath(path);
164
+ const file = Gio.File.new_for_path(pathStr);
160
165
  try {
161
166
  const info = file.query_info("standard::type", Gio.FileQueryInfoFlags.NONE, null);
162
167
  if (info.get_file_type() !== Gio.FileType.DIRECTORY) {
163
168
  const err = Object.assign(new Error(), { code: 4 });
164
- throw createNodeError(err, "rmdir", path);
169
+ throw createNodeError(err, "rmdir", pathStr);
165
170
  }
166
171
  const enumerator = file.enumerate_children("standard::name", Gio.FileQueryInfoFlags.NONE, null);
167
172
  if (enumerator.next_file(null) !== null) {
168
173
  const err = Object.assign(new Error(), { code: 5 });
169
- throw createNodeError(err, "rmdir", path);
174
+ throw createNodeError(err, "rmdir", pathStr);
170
175
  }
171
176
  file.delete(null);
172
177
  } catch (err) {
173
178
  if (err.code && typeof err.code === "string") throw err;
174
- throw createNodeError(err, "rmdir", path);
179
+ throw createNodeError(err, "rmdir", pathStr);
175
180
  }
176
181
  }
177
182
  function unlinkSync(path) {
178
- const file = Gio.File.new_for_path(path.toString());
183
+ const pathStr = normalizePath(path);
184
+ const file = Gio.File.new_for_path(pathStr);
179
185
  try {
180
186
  file.delete(null);
181
187
  } catch (err) {
182
- throw createNodeError(err, "unlink", path);
188
+ throw createNodeError(err, "unlink", pathStr);
183
189
  }
184
190
  }
185
191
  function writeFileSync(path, data) {
186
- GLib.file_set_contents(path, data);
192
+ GLib.file_set_contents(normalizePath(path), data);
187
193
  }
188
194
  function renameSync(oldPath, newPath) {
189
- const src = Gio.File.new_for_path(oldPath.toString());
190
- const dest = Gio.File.new_for_path(newPath.toString());
195
+ const oldStr = normalizePath(oldPath);
196
+ const newStr = normalizePath(newPath);
197
+ const src = Gio.File.new_for_path(oldStr);
198
+ const dest = Gio.File.new_for_path(newStr);
191
199
  try {
192
200
  src.move(dest, Gio.FileCopyFlags.OVERWRITE, null, null);
193
201
  } catch (err) {
194
- throw createNodeError(err, "rename", oldPath, newPath);
202
+ throw createNodeError(err, "rename", oldStr, newStr);
195
203
  }
196
204
  }
197
205
  function copyFileSync(src, dest, mode) {
198
- const srcFile = Gio.File.new_for_path(src.toString());
199
- const destFile = Gio.File.new_for_path(dest.toString());
206
+ const srcStr = normalizePath(src);
207
+ const destStr = normalizePath(dest);
208
+ const srcFile = Gio.File.new_for_path(srcStr);
209
+ const destFile = Gio.File.new_for_path(destStr);
200
210
  let flags = Gio.FileCopyFlags.NONE;
201
211
  if (mode && (mode & 1) === 0) {
202
212
  flags = Gio.FileCopyFlags.OVERWRITE;
@@ -206,32 +216,34 @@ function copyFileSync(src, dest, mode) {
206
216
  try {
207
217
  srcFile.copy(destFile, flags, null, null);
208
218
  } catch (err) {
209
- throw createNodeError(err, "copyfile", src, dest);
219
+ throw createNodeError(err, "copyfile", srcStr, destStr);
210
220
  }
211
221
  }
212
222
  function accessSync(path, mode) {
213
- const file = Gio.File.new_for_path(path.toString());
223
+ const pathStr = normalizePath(path);
224
+ const file = Gio.File.new_for_path(pathStr);
214
225
  try {
215
226
  const info = file.query_info("access::*", Gio.FileQueryInfoFlags.NONE, null);
216
227
  if (mode !== void 0 && mode !== 0) {
217
- const permErr = { code: 14, message: `permission denied, access '${path}'` };
228
+ const permErr = { code: 14, message: `permission denied, access '${pathStr}'` };
218
229
  if (mode & 4 && !info.get_attribute_boolean("access::can-read")) {
219
- throw createNodeError(permErr, "access", path);
230
+ throw createNodeError(permErr, "access", pathStr);
220
231
  }
221
232
  if (mode & 2 && !info.get_attribute_boolean("access::can-write")) {
222
- throw createNodeError(permErr, "access", path);
233
+ throw createNodeError(permErr, "access", pathStr);
223
234
  }
224
235
  if (mode & 1 && !info.get_attribute_boolean("access::can-execute")) {
225
- throw createNodeError(permErr, "access", path);
236
+ throw createNodeError(permErr, "access", pathStr);
226
237
  }
227
238
  }
228
239
  } catch (err) {
229
240
  if (err.code && typeof err.code === "string") throw err;
230
- throw createNodeError(err, "access", path);
241
+ throw createNodeError(err, "access", pathStr);
231
242
  }
232
243
  }
233
244
  function appendFileSync(path, data, options) {
234
- const file = Gio.File.new_for_path(path.toString());
245
+ const pathStr = normalizePath(path);
246
+ const file = Gio.File.new_for_path(pathStr);
235
247
  let bytes;
236
248
  if (typeof data === "string") {
237
249
  bytes = new TextEncoder().encode(data);
@@ -245,16 +257,17 @@ function appendFileSync(path, data, options) {
245
257
  }
246
258
  stream.close(null);
247
259
  } catch (err) {
248
- throw createNodeError(err, "appendfile", path);
260
+ throw createNodeError(err, "appendfile", pathStr);
249
261
  }
250
262
  }
251
263
  function readlinkSync(path, options) {
252
- const file = Gio.File.new_for_path(path.toString());
264
+ const pathStr = normalizePath(path);
265
+ const file = Gio.File.new_for_path(pathStr);
253
266
  try {
254
267
  const info = file.query_info("standard::symlink-target", Gio.FileQueryInfoFlags.NOFOLLOW_SYMLINKS, null);
255
268
  const target = info.get_symlink_target();
256
269
  if (!target) {
257
- throw Object.assign(new Error(`EINVAL: invalid argument, readlink '${path}'`), { code: "EINVAL", errno: -22, syscall: "readlink", path: path.toString() });
270
+ throw Object.assign(new Error(`EINVAL: invalid argument, readlink '${pathStr}'`), { code: "EINVAL", errno: -22, syscall: "readlink", path: pathStr });
258
271
  }
259
272
  const encoding = typeof options === "string" ? options : options?.encoding;
260
273
  if (encoding === "buffer") {
@@ -263,17 +276,20 @@ function readlinkSync(path, options) {
263
276
  return target;
264
277
  } catch (err) {
265
278
  if (typeof err.code === "string") throw err;
266
- throw createNodeError(err, "readlink", path);
279
+ throw createNodeError(err, "readlink", pathStr);
267
280
  }
268
281
  }
269
282
  function linkSync(existingPath, newPath) {
270
- const result = GLib.spawn_command_line_sync(`ln ${existingPath.toString()} ${newPath.toString()}`);
283
+ const existingStr = normalizePath(existingPath);
284
+ const newStr = normalizePath(newPath);
285
+ const result = GLib.spawn_command_line_sync(`ln ${existingStr} ${newStr}`);
271
286
  if (!result[0]) {
272
- throw Object.assign(new Error(`EPERM: operation not permitted, link '${existingPath}' -> '${newPath}'`), { code: "EPERM", errno: -1, syscall: "link", path: existingPath.toString(), dest: newPath.toString() });
287
+ throw Object.assign(new Error(`EPERM: operation not permitted, link '${existingStr}' -> '${newStr}'`), { code: "EPERM", errno: -1, syscall: "link", path: existingStr, dest: newStr });
273
288
  }
274
289
  }
275
290
  function truncateSync(path, len) {
276
- const file = Gio.File.new_for_path(path.toString());
291
+ const pathStr = normalizePath(path);
292
+ const file = Gio.File.new_for_path(pathStr);
277
293
  try {
278
294
  const stream = file.replace(null, false, Gio.FileCreateFlags.NONE, null);
279
295
  if (len && len > 0) {
@@ -285,24 +301,26 @@ function truncateSync(path, len) {
285
301
  }
286
302
  stream.close(null);
287
303
  } catch (err) {
288
- throw createNodeError(err, "truncate", path);
304
+ throw createNodeError(err, "truncate", pathStr);
289
305
  }
290
306
  }
291
307
  function chmodSync(path, mode) {
308
+ const pathStr = normalizePath(path);
292
309
  const modeNum = typeof mode === "string" ? parseInt(mode, 8) : mode;
293
- const result = GLib.spawn_command_line_sync(`chmod ${modeNum.toString(8)} ${path.toString()}`);
310
+ const result = GLib.spawn_command_line_sync(`chmod ${modeNum.toString(8)} ${pathStr}`);
294
311
  if (!result[0]) {
295
- throw Object.assign(new Error(`EPERM: operation not permitted, chmod '${path}'`), { code: "EPERM", errno: -1, syscall: "chmod", path: path.toString() });
312
+ throw Object.assign(new Error(`EPERM: operation not permitted, chmod '${pathStr}'`), { code: "EPERM", errno: -1, syscall: "chmod", path: pathStr });
296
313
  }
297
314
  }
298
315
  function chownSync(path, uid, gid) {
299
- const result = GLib.spawn_command_line_sync(`chown ${uid}:${gid} ${path.toString()}`);
316
+ const pathStr = normalizePath(path);
317
+ const result = GLib.spawn_command_line_sync(`chown ${uid}:${gid} ${pathStr}`);
300
318
  if (!result[0]) {
301
- throw Object.assign(new Error(`EPERM: operation not permitted, chown '${path}'`), { code: "EPERM", errno: -1, syscall: "chown", path: path.toString() });
319
+ throw Object.assign(new Error(`EPERM: operation not permitted, chown '${pathStr}'`), { code: "EPERM", errno: -1, syscall: "chown", path: pathStr });
302
320
  }
303
321
  }
304
322
  function watch(filename, options, listener) {
305
- return new FSWatcher(filename, options, listener);
323
+ return new FSWatcher(normalizePath(filename), options, listener);
306
324
  }
307
325
  function openSync(path, flags, mode) {
308
326
  return new FileHandle({ path, flags, mode });
@@ -317,7 +335,7 @@ function mkdtempSync(prefix, options) {
317
335
  return decode(path, encoding);
318
336
  }
319
337
  function rmSync(path, options) {
320
- const pathStr = path.toString();
338
+ const pathStr = normalizePath(path);
321
339
  const file = Gio.File.new_for_path(pathStr);
322
340
  const recursive = options?.recursive || false;
323
341
  const force = options?.force || false;
package/lib/esm/utils.js CHANGED
@@ -1,4 +1,10 @@
1
1
  import { existsSync } from "./sync.js";
2
+ import { fileURLToPath, URL as NodeURL } from "node:url";
3
+ function normalizePath(path) {
4
+ if (path instanceof URL || path instanceof NodeURL) return fileURLToPath(path);
5
+ if (typeof path === "string") return path;
6
+ return path.toString();
7
+ }
2
8
  const CHARS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
3
9
  function randomName() {
4
10
  return [...Array(6)].map(
@@ -13,6 +19,7 @@ function tempDirPath(prefix) {
13
19
  return path;
14
20
  }
15
21
  export {
22
+ normalizePath,
16
23
  randomName,
17
24
  tempDirPath
18
25
  };