@gjsify/fs 0.0.4 → 0.1.1
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 +31 -2
- package/lib/esm/callback.js +251 -15
- package/lib/esm/dirent.js +47 -6
- package/lib/esm/encoding.js +2 -3
- package/lib/esm/errors.js +13 -0
- package/lib/esm/file-handle.js +108 -66
- package/lib/esm/fs-watcher.js +44 -7
- package/lib/esm/index.js +140 -5
- package/lib/esm/promises.js +290 -69
- package/lib/esm/read-stream.js +82 -57
- package/lib/esm/stats.js +138 -18
- package/lib/esm/sync.js +293 -44
- package/lib/esm/write-stream.js +4 -4
- package/lib/types/callback.d.ts +233 -0
- package/lib/types/dirent.d.ts +77 -0
- package/lib/types/encoding.d.ts +6 -0
- package/lib/types/errors.d.ts +7 -0
- package/lib/types/file-handle.d.ts +367 -0
- package/lib/types/fs-watcher.d.ts +17 -0
- package/lib/types/index.d.ts +149 -0
- package/lib/types/promises.d.ts +158 -0
- package/lib/types/read-stream.d.ts +21 -0
- package/lib/types/stats.d.ts +67 -0
- package/lib/types/sync.d.ts +109 -0
- package/lib/types/types/encoding-option.d.ts +2 -0
- package/lib/types/types/file-read-options.d.ts +15 -0
- package/lib/types/types/file-read-result.d.ts +4 -0
- package/lib/types/types/flag-and-open-mode.d.ts +5 -0
- package/lib/types/types/index.d.ts +6 -0
- package/lib/types/types/open-flags.d.ts +1 -0
- package/lib/types/types/read-options.d.ts +5 -0
- package/lib/types/utils.d.ts +2 -0
- package/lib/types/write-stream.d.ts +45 -0
- package/package.json +22 -35
- package/src/callback.spec.ts +284 -30
- package/src/callback.ts +352 -39
- package/src/dirent.ts +56 -8
- package/src/encoding.ts +7 -2
- package/src/errors.spec.ts +389 -0
- package/src/errors.ts +19 -0
- package/src/extended.spec.ts +706 -0
- package/src/file-handle.spec.ts +104 -23
- package/src/file-handle.ts +147 -79
- package/src/fs-watcher.ts +55 -8
- package/src/index.ts +146 -2
- package/src/new-apis.spec.ts +505 -0
- package/src/promises.spec.ts +651 -11
- package/src/promises.ts +353 -81
- package/src/read-stream.ts +98 -74
- package/src/stat.spec.ts +22 -14
- package/src/stats.ts +176 -75
- package/src/streams.spec.ts +455 -0
- package/src/symlink.spec.ts +176 -26
- package/src/sync.spec.ts +204 -32
- package/src/sync.ts +363 -58
- package/src/test.mts +7 -2
- package/src/types/encoding-option.ts +1 -1
- package/src/types/flag-and-open-mode.ts +1 -1
- package/src/types/read-options.ts +2 -2
- package/src/utils.ts +2 -0
- package/src/write-stream.ts +9 -7
- package/tsconfig.json +23 -10
- package/tsconfig.tsbuildinfo +1 -0
- package/lib/cjs/callback.js +0 -112
- package/lib/cjs/dirent.js +0 -98
- package/lib/cjs/encoding.js +0 -34
- package/lib/cjs/file-handle.js +0 -444
- package/lib/cjs/fs-watcher.js +0 -50
- package/lib/cjs/index.js +0 -95
- package/lib/cjs/promises.js +0 -160
- package/lib/cjs/read-stream.js +0 -78
- package/lib/cjs/stats.js +0 -45
- package/lib/cjs/sync.js +0 -126
- package/lib/cjs/types/encoding-option.js +0 -0
- package/lib/cjs/types/file-read-options.js +0 -0
- package/lib/cjs/types/file-read-result.js +0 -0
- package/lib/cjs/types/flag-and-open-mode.js +0 -0
- package/lib/cjs/types/index.js +0 -6
- package/lib/cjs/types/open-flags.js +0 -0
- package/lib/cjs/types/read-options.js +0 -0
- package/lib/cjs/utils.js +0 -18
- package/lib/cjs/write-stream.js +0 -116
- package/test/watch.js +0 -1
- package/test.gjs.js +0 -35359
- package/test.gjs.js.map +0 -7
- package/test.gjs.mjs +0 -40534
- package/test.gjs.mjs.meta.json +0 -1
- package/test.node.js +0 -1479
- package/test.node.js.map +0 -7
- package/test.node.mjs +0 -710
- package/tsconfig.types.json +0 -8
package/src/sync.ts
CHANGED
|
@@ -1,23 +1,20 @@
|
|
|
1
|
+
// Reference: Node.js lib/fs.js (sync API)
|
|
2
|
+
// Reimplemented for GJS using Gio.File synchronous operations
|
|
3
|
+
|
|
1
4
|
import GLib from '@girs/glib-2.0';
|
|
2
5
|
import Gio from '@girs/gio-2.0';
|
|
3
6
|
import { existsSync } from '@gjsify/utils';
|
|
4
|
-
import { Buffer } from 'buffer';
|
|
5
|
-
import { join } from 'path';
|
|
7
|
+
import { Buffer } from 'node:buffer';
|
|
8
|
+
import { join } from 'node:path';
|
|
6
9
|
|
|
7
10
|
import FSWatcher from './fs-watcher.js';
|
|
8
11
|
import { getEncodingFromOptions, encodeUint8Array, decode } from './encoding.js';
|
|
9
12
|
import { FileHandle } from './file-handle.js';
|
|
10
13
|
import { Dirent } from './dirent.js';
|
|
14
|
+
import { Stats, BigIntStats, STAT_ATTRIBUTES } from './stats.js';
|
|
15
|
+
import { createNodeError, isNotFoundError } from './errors.js';
|
|
11
16
|
import { tempDirPath } from './utils.js';
|
|
12
17
|
|
|
13
|
-
export { realpathSync } from '@gjsify/deno_std/node/_fs/_fs_realpath';
|
|
14
|
-
import { readdirSync } from '@gjsify/deno_std/node/_fs/_fs_readdir';
|
|
15
|
-
export { symlinkSync } from '@gjsify/deno_std/node/_fs/_fs_symlink';
|
|
16
|
-
export { lstatSync } from '@gjsify/deno_std/node/_fs/_fs_lstat';
|
|
17
|
-
export { statSync } from '@gjsify/deno_std/node/_fs/_fs_stat';
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
18
|
import type { OpenFlags, EncodingOption } from './types/index.js';
|
|
22
19
|
import type {
|
|
23
20
|
PathLike,
|
|
@@ -26,21 +23,131 @@ import type {
|
|
|
26
23
|
BufferEncodingOption,
|
|
27
24
|
RmOptions,
|
|
28
25
|
RmDirOptions,
|
|
29
|
-
|
|
26
|
+
StatSyncOptions,
|
|
27
|
+
} from 'node:fs'; // Types from @types/node
|
|
28
|
+
|
|
29
|
+
export { existsSync }
|
|
30
|
+
|
|
31
|
+
// --- stat / lstat ---
|
|
32
|
+
|
|
33
|
+
export function statSync(path: PathLike, options?: StatSyncOptions): Stats | BigIntStats | undefined {
|
|
34
|
+
try {
|
|
35
|
+
const file = Gio.File.new_for_path(path.toString());
|
|
36
|
+
const info = file.query_info(STAT_ATTRIBUTES, Gio.FileQueryInfoFlags.NONE, null);
|
|
37
|
+
return options?.bigint ? new BigIntStats(info, path) : new Stats(info, path);
|
|
38
|
+
} catch (err: unknown) {
|
|
39
|
+
if (options?.throwIfNoEntry === false && isNotFoundError(err)) return undefined;
|
|
40
|
+
throw createNodeError(err, 'stat', path);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function lstatSync(path: PathLike, options?: StatSyncOptions): Stats | BigIntStats | undefined {
|
|
45
|
+
try {
|
|
46
|
+
const file = Gio.File.new_for_path(path.toString());
|
|
47
|
+
const info = file.query_info(STAT_ATTRIBUTES, Gio.FileQueryInfoFlags.NOFOLLOW_SYMLINKS, null);
|
|
48
|
+
return options?.bigint ? new BigIntStats(info, path) : new Stats(info, path);
|
|
49
|
+
} catch (err: unknown) {
|
|
50
|
+
if (options?.throwIfNoEntry === false && isNotFoundError(err)) return undefined;
|
|
51
|
+
throw createNodeError(err, 'lstat', path);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// --- readdir ---
|
|
56
|
+
|
|
57
|
+
export function readdirSync(
|
|
58
|
+
path: PathLike,
|
|
59
|
+
options?: { withFileTypes?: boolean; encoding?: string; recursive?: boolean }
|
|
60
|
+
): string[] | Dirent[] {
|
|
61
|
+
const pathStr = path.toString();
|
|
62
|
+
const file = Gio.File.new_for_path(pathStr);
|
|
63
|
+
const enumerator = file.enumerate_children(
|
|
64
|
+
'standard::name,standard::type',
|
|
65
|
+
Gio.FileQueryInfoFlags.NONE,
|
|
66
|
+
null,
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
const result: (string | Dirent)[] = [];
|
|
70
|
+
let info = enumerator.next_file(null);
|
|
71
|
+
|
|
72
|
+
while (info !== null) {
|
|
73
|
+
const childName = info.get_name();
|
|
74
|
+
const childPath = join(pathStr, childName);
|
|
75
|
+
|
|
76
|
+
if (options?.withFileTypes) {
|
|
77
|
+
result.push(new Dirent(childPath, childName));
|
|
78
|
+
} else {
|
|
79
|
+
result.push(childName);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (options?.recursive && info.get_file_type() === Gio.FileType.DIRECTORY) {
|
|
83
|
+
const subEntries = readdirSync(childPath, options);
|
|
84
|
+
for (const entry of subEntries) {
|
|
85
|
+
if (typeof entry === 'string') {
|
|
86
|
+
result.push(join(childName, entry));
|
|
87
|
+
} else {
|
|
88
|
+
result.push(entry);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
info = enumerator.next_file(null);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return result as string[] | Dirent[];
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// --- realpath ---
|
|
100
|
+
|
|
101
|
+
const MAX_SYMLINK_DEPTH = 40; // matches Linux MAXSYMLINKS
|
|
102
|
+
|
|
103
|
+
export function realpathSync(path: PathLike): string {
|
|
104
|
+
let current = Gio.File.new_for_path(path.toString());
|
|
105
|
+
let depth = 0;
|
|
106
|
+
|
|
107
|
+
while (true) {
|
|
108
|
+
const info = current.query_info(
|
|
109
|
+
'standard::is-symlink,standard::symlink-target',
|
|
110
|
+
Gio.FileQueryInfoFlags.NOFOLLOW_SYMLINKS,
|
|
111
|
+
null,
|
|
112
|
+
);
|
|
30
113
|
|
|
31
|
-
|
|
114
|
+
if (!info.get_is_symlink()) {
|
|
115
|
+
return current.get_path()!;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const target = info.get_symlink_target()!;
|
|
119
|
+
const parent = current.get_parent();
|
|
120
|
+
current = parent ? parent.resolve_relative_path(target) : Gio.File.new_for_path(target);
|
|
121
|
+
|
|
122
|
+
if (++depth > MAX_SYMLINK_DEPTH) {
|
|
123
|
+
throw new Error(`ELOOP: too many levels of symbolic links, realpath '${path}'`);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
(realpathSync as unknown as { native: typeof realpathSync }).native = realpathSync;
|
|
128
|
+
|
|
129
|
+
// --- symlink ---
|
|
130
|
+
|
|
131
|
+
export function symlinkSync(target: PathLike, path: PathLike, _type?: 'file' | 'dir' | 'junction'): void {
|
|
132
|
+
const file = Gio.File.new_for_path(path.toString());
|
|
133
|
+
file.make_symbolic_link(target.toString(), null);
|
|
134
|
+
}
|
|
32
135
|
|
|
33
136
|
export function readFileSync(path: string, options = { encoding: null, flag: 'r' }) {
|
|
34
137
|
const file = Gio.File.new_for_path(path);
|
|
35
138
|
|
|
36
|
-
|
|
139
|
+
try {
|
|
140
|
+
const [ok, data] = file.load_contents(null);
|
|
37
141
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
}
|
|
142
|
+
if (!ok) {
|
|
143
|
+
throw createNodeError(new Error('failed to read file'), 'read', path);
|
|
144
|
+
}
|
|
42
145
|
|
|
43
|
-
|
|
146
|
+
return encodeUint8Array(getEncodingFromOptions(options, "buffer"), data);
|
|
147
|
+
} catch (err: unknown) {
|
|
148
|
+
if ((err as { code?: unknown }).code && typeof (err as { code?: unknown }).code === 'string') throw err; // Already a Node error
|
|
149
|
+
throw createNodeError(err, 'read', path);
|
|
150
|
+
}
|
|
44
151
|
}
|
|
45
152
|
|
|
46
153
|
/**
|
|
@@ -104,18 +211,60 @@ export function mkdirSync(path: PathLike, options?: Mode | MakeDirectoryOptions
|
|
|
104
211
|
throw new TypeError("mode as string is currently not supported!");
|
|
105
212
|
}
|
|
106
213
|
|
|
107
|
-
if (
|
|
108
|
-
|
|
109
|
-
throw new Error(`failed to make ${path} directory`);
|
|
214
|
+
if (recursive) {
|
|
215
|
+
return mkdirSyncRecursive(path, mode as number);
|
|
110
216
|
}
|
|
111
217
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
218
|
+
// Non-recursive: create a single directory
|
|
219
|
+
const file = Gio.File.new_for_path(path);
|
|
220
|
+
try {
|
|
221
|
+
file.make_directory(null);
|
|
222
|
+
} catch (err: unknown) {
|
|
223
|
+
throw createNodeError(err, 'mkdir', path);
|
|
115
224
|
}
|
|
116
225
|
return undefined;
|
|
117
226
|
}
|
|
118
227
|
|
|
228
|
+
/**
|
|
229
|
+
* Recursively creates directories, similar to `mkdir -p`.
|
|
230
|
+
* Returns the first directory path created, or undefined if all directories already existed.
|
|
231
|
+
*/
|
|
232
|
+
function mkdirSyncRecursive(pathStr: string, mode: number): string | undefined {
|
|
233
|
+
const file = Gio.File.new_for_path(pathStr);
|
|
234
|
+
|
|
235
|
+
// Try to create the directory directly
|
|
236
|
+
try {
|
|
237
|
+
file.make_directory(null);
|
|
238
|
+
// This directory was created successfully — it's a candidate for "first created"
|
|
239
|
+
return pathStr;
|
|
240
|
+
} catch (err: unknown) {
|
|
241
|
+
const gErr = err as { code?: number };
|
|
242
|
+
// If it already exists, nothing to create
|
|
243
|
+
if (gErr.code === Gio.IOErrorEnum.EXISTS) {
|
|
244
|
+
return undefined;
|
|
245
|
+
}
|
|
246
|
+
// If parent doesn't exist, create parent first then retry
|
|
247
|
+
if (gErr.code === Gio.IOErrorEnum.NOT_FOUND) {
|
|
248
|
+
const parentPath = join(pathStr, '..');
|
|
249
|
+
const resolvedParent = Gio.File.new_for_path(parentPath).get_path()!;
|
|
250
|
+
if (resolvedParent === pathStr) {
|
|
251
|
+
// Reached root, cannot go further
|
|
252
|
+
throw createNodeError(err, 'mkdir', pathStr);
|
|
253
|
+
}
|
|
254
|
+
const firstCreated = mkdirSyncRecursive(resolvedParent, mode);
|
|
255
|
+
// Now create this directory
|
|
256
|
+
const retryFile = Gio.File.new_for_path(pathStr);
|
|
257
|
+
try {
|
|
258
|
+
retryFile.make_directory(null);
|
|
259
|
+
} catch (retryErr: unknown) {
|
|
260
|
+
throw createNodeError(retryErr, 'mkdir', pathStr);
|
|
261
|
+
}
|
|
262
|
+
return firstCreated ?? pathStr;
|
|
263
|
+
}
|
|
264
|
+
throw createNodeError(err, 'mkdir', pathStr);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
119
268
|
/**
|
|
120
269
|
* Synchronous [`rmdir(2)`](http://man7.org/linux/man-pages/man2/rmdir.2.html). Returns `undefined`.
|
|
121
270
|
*
|
|
@@ -125,41 +274,191 @@ export function mkdirSync(path: PathLike, options?: Mode | MakeDirectoryOptions
|
|
|
125
274
|
* To get a behavior similar to the `rm -rf` Unix command, use {@link rmSync} with options `{ recursive: true, force: true }`.
|
|
126
275
|
* @since v0.1.21
|
|
127
276
|
*/
|
|
128
|
-
export function rmdirSync(path: PathLike,
|
|
277
|
+
export function rmdirSync(path: PathLike, _options?: RmDirOptions): void {
|
|
278
|
+
const file = Gio.File.new_for_path(path.toString());
|
|
279
|
+
try {
|
|
280
|
+
// Check if it's a directory
|
|
281
|
+
const info = file.query_info('standard::type', Gio.FileQueryInfoFlags.NONE, null);
|
|
282
|
+
if (info.get_file_type() !== Gio.FileType.DIRECTORY) {
|
|
283
|
+
const err = Object.assign(new Error(), { code: 4 }); // Gio.IOErrorEnum.NOT_DIRECTORY
|
|
284
|
+
throw createNodeError(err, 'rmdir', path);
|
|
285
|
+
}
|
|
286
|
+
// Check if empty — rmdir only removes empty directories (use rmSync for recursive)
|
|
287
|
+
const enumerator = file.enumerate_children('standard::name', Gio.FileQueryInfoFlags.NONE, null);
|
|
288
|
+
if (enumerator.next_file(null) !== null) {
|
|
289
|
+
const err = Object.assign(new Error(), { code: 5 }); // Gio.IOErrorEnum.NOT_EMPTY
|
|
290
|
+
throw createNodeError(err, 'rmdir', path);
|
|
291
|
+
}
|
|
292
|
+
file.delete(null);
|
|
293
|
+
} catch (err: unknown) {
|
|
294
|
+
if ((err as { code?: unknown }).code && typeof (err as { code?: unknown }).code === 'string') throw err; // Already a Node error
|
|
295
|
+
throw createNodeError(err, 'rmdir', path);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
129
298
|
|
|
130
|
-
|
|
299
|
+
export function unlinkSync(path: PathLike): void {
|
|
300
|
+
const file = Gio.File.new_for_path(path.toString());
|
|
301
|
+
try {
|
|
302
|
+
file.delete(null);
|
|
303
|
+
} catch (err: unknown) {
|
|
304
|
+
throw createNodeError(err, 'unlink', path);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
export function writeFileSync(path: string, data: string | Uint8Array) {
|
|
309
|
+
GLib.file_set_contents(path, data);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// --- rename ---
|
|
313
|
+
|
|
314
|
+
export function renameSync(oldPath: PathLike, newPath: PathLike): void {
|
|
315
|
+
const src = Gio.File.new_for_path(oldPath.toString());
|
|
316
|
+
const dest = Gio.File.new_for_path(newPath.toString());
|
|
317
|
+
try {
|
|
318
|
+
src.move(dest, Gio.FileCopyFlags.OVERWRITE, null, null);
|
|
319
|
+
} catch (err: unknown) {
|
|
320
|
+
throw createNodeError(err, 'rename', oldPath, newPath);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// --- copyFile ---
|
|
325
|
+
|
|
326
|
+
export function copyFileSync(src: PathLike, dest: PathLike, mode?: number): void {
|
|
327
|
+
const srcFile = Gio.File.new_for_path(src.toString());
|
|
328
|
+
const destFile = Gio.File.new_for_path(dest.toString());
|
|
329
|
+
let flags = Gio.FileCopyFlags.NONE;
|
|
330
|
+
// mode 0 = default (overwrite), COPYFILE_EXCL (1) = no overwrite
|
|
331
|
+
if (mode && (mode & 1) === 0) {
|
|
332
|
+
flags = Gio.FileCopyFlags.OVERWRITE;
|
|
333
|
+
} else if (!mode) {
|
|
334
|
+
flags = Gio.FileCopyFlags.OVERWRITE;
|
|
335
|
+
}
|
|
336
|
+
try {
|
|
337
|
+
srcFile.copy(destFile, flags, null, null);
|
|
338
|
+
} catch (err: unknown) {
|
|
339
|
+
throw createNodeError(err, 'copyfile', src, dest);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// --- access ---
|
|
344
|
+
|
|
345
|
+
export function accessSync(path: PathLike, mode?: number): void {
|
|
346
|
+
const file = Gio.File.new_for_path(path.toString());
|
|
347
|
+
try {
|
|
348
|
+
const info = file.query_info('access::*', Gio.FileQueryInfoFlags.NONE, null);
|
|
349
|
+
// mode: F_OK=0, R_OK=4, W_OK=2, X_OK=1
|
|
350
|
+
if (mode !== undefined && mode !== 0) {
|
|
351
|
+
// Gio.IOErrorEnum.PERMISSION_DENIED = 14 → maps to EACCES via createNodeError
|
|
352
|
+
const permErr = { code: 14, message: `permission denied, access '${path}'` };
|
|
353
|
+
if ((mode & 4) && !info.get_attribute_boolean('access::can-read')) {
|
|
354
|
+
throw createNodeError(permErr, 'access', path);
|
|
355
|
+
}
|
|
356
|
+
if ((mode & 2) && !info.get_attribute_boolean('access::can-write')) {
|
|
357
|
+
throw createNodeError(permErr, 'access', path);
|
|
358
|
+
}
|
|
359
|
+
if ((mode & 1) && !info.get_attribute_boolean('access::can-execute')) {
|
|
360
|
+
throw createNodeError(permErr, 'access', path);
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
} catch (err: unknown) {
|
|
364
|
+
if ((err as { code?: unknown }).code && typeof (err as { code?: unknown }).code === 'string') throw err; // Already a Node-style error
|
|
365
|
+
throw createNodeError(err, 'access', path);
|
|
366
|
+
}
|
|
367
|
+
}
|
|
131
368
|
|
|
132
|
-
|
|
369
|
+
// --- appendFile ---
|
|
133
370
|
|
|
134
|
-
|
|
135
|
-
|
|
371
|
+
export function appendFileSync(path: PathLike, data: string | Uint8Array, options?: { encoding?: string; mode?: number; flag?: string } | string): void {
|
|
372
|
+
const file = Gio.File.new_for_path(path.toString());
|
|
373
|
+
let bytes: Uint8Array;
|
|
374
|
+
if (typeof data === 'string') {
|
|
375
|
+
bytes = new TextEncoder().encode(data);
|
|
376
|
+
} else {
|
|
377
|
+
bytes = data;
|
|
136
378
|
}
|
|
137
379
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
rmSync(join(path.toString(), childFile.name));
|
|
380
|
+
try {
|
|
381
|
+
const stream = file.append_to(Gio.FileCreateFlags.NONE, null);
|
|
382
|
+
if (bytes.length > 0) {
|
|
383
|
+
stream.write_bytes(new GLib.Bytes(bytes), null);
|
|
143
384
|
}
|
|
385
|
+
stream.close(null);
|
|
386
|
+
} catch (err: unknown) {
|
|
387
|
+
throw createNodeError(err, 'appendfile', path);
|
|
144
388
|
}
|
|
389
|
+
}
|
|
145
390
|
|
|
146
|
-
|
|
391
|
+
// --- readlink ---
|
|
147
392
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
393
|
+
export function readlinkSync(path: PathLike, options?: { encoding?: string } | string): string | Buffer {
|
|
394
|
+
const file = Gio.File.new_for_path(path.toString());
|
|
395
|
+
try {
|
|
396
|
+
const info = file.query_info('standard::symlink-target', Gio.FileQueryInfoFlags.NOFOLLOW_SYMLINKS, null);
|
|
397
|
+
const target = info.get_symlink_target();
|
|
398
|
+
if (!target) {
|
|
399
|
+
throw Object.assign(new Error(`EINVAL: invalid argument, readlink '${path}'`), { code: 'EINVAL', errno: -22, syscall: 'readlink', path: path.toString() });
|
|
400
|
+
}
|
|
401
|
+
const encoding = typeof options === 'string' ? options : options?.encoding;
|
|
402
|
+
if (encoding === 'buffer') {
|
|
403
|
+
return Buffer.from(target);
|
|
404
|
+
}
|
|
405
|
+
return target;
|
|
406
|
+
} catch (err: unknown) {
|
|
407
|
+
if (typeof (err as { code?: unknown }).code === 'string') throw err;
|
|
408
|
+
throw createNodeError(err, 'readlink', path);
|
|
151
409
|
}
|
|
152
410
|
}
|
|
153
411
|
|
|
154
|
-
|
|
155
|
-
|
|
412
|
+
// --- link ---
|
|
413
|
+
|
|
414
|
+
export function linkSync(existingPath: PathLike, newPath: PathLike): void {
|
|
415
|
+
// Gio doesn't have a direct hard link API, use GLib
|
|
416
|
+
const result = GLib.spawn_command_line_sync(`ln ${existingPath.toString()} ${newPath.toString()}`);
|
|
417
|
+
if (!result[0]) {
|
|
418
|
+
throw Object.assign(new Error(`EPERM: operation not permitted, link '${existingPath}' -> '${newPath}'`), { code: 'EPERM', errno: -1, syscall: 'link', path: existingPath.toString(), dest: newPath.toString() });
|
|
419
|
+
}
|
|
156
420
|
}
|
|
157
421
|
|
|
158
|
-
|
|
159
|
-
|
|
422
|
+
// --- truncate ---
|
|
423
|
+
|
|
424
|
+
export function truncateSync(path: PathLike, len?: number): void {
|
|
425
|
+
const file = Gio.File.new_for_path(path.toString());
|
|
426
|
+
try {
|
|
427
|
+
const stream = file.replace(null, false, Gio.FileCreateFlags.NONE, null);
|
|
428
|
+
if (len && len > 0) {
|
|
429
|
+
// Read existing content, truncate to len
|
|
430
|
+
const [, data] = file.load_contents(null);
|
|
431
|
+
const truncated = data.slice(0, len);
|
|
432
|
+
if (truncated.length > 0) {
|
|
433
|
+
stream.write_bytes(new GLib.Bytes(truncated), null);
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
stream.close(null);
|
|
437
|
+
} catch (err: unknown) {
|
|
438
|
+
throw createNodeError(err, 'truncate', path);
|
|
439
|
+
}
|
|
160
440
|
}
|
|
161
441
|
|
|
162
|
-
|
|
442
|
+
// --- chmodSync ---
|
|
443
|
+
|
|
444
|
+
export function chmodSync(path: PathLike, mode: Mode): void {
|
|
445
|
+
const modeNum = typeof mode === 'string' ? parseInt(mode, 8) : mode;
|
|
446
|
+
const result = GLib.spawn_command_line_sync(`chmod ${modeNum.toString(8)} ${path.toString()}`);
|
|
447
|
+
if (!result[0]) {
|
|
448
|
+
throw Object.assign(new Error(`EPERM: operation not permitted, chmod '${path}'`), { code: 'EPERM', errno: -1, syscall: 'chmod', path: path.toString() });
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
// --- chownSync ---
|
|
453
|
+
|
|
454
|
+
export function chownSync(path: PathLike, uid: number, gid: number): void {
|
|
455
|
+
const result = GLib.spawn_command_line_sync(`chown ${uid}:${gid} ${path.toString()}`);
|
|
456
|
+
if (!result[0]) {
|
|
457
|
+
throw Object.assign(new Error(`EPERM: operation not permitted, chown '${path}'`), { code: 'EPERM', errno: -1, syscall: 'chown', path: path.toString() });
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
export function watch(filename: string, options: { persistent?: boolean; recursive?: boolean; encoding?: string } | undefined, listener: ((eventType: string, filename: string | null) => void) | undefined) {
|
|
163
462
|
return new FSWatcher(filename, options, listener);
|
|
164
463
|
}
|
|
165
464
|
|
|
@@ -208,32 +507,38 @@ export function mkdtempSync(prefix: string, options?: EncodingOption | BufferEnc
|
|
|
208
507
|
* @since v14.14.0
|
|
209
508
|
*/
|
|
210
509
|
export function rmSync(path: PathLike, options?: RmOptions): void {
|
|
211
|
-
const
|
|
510
|
+
const pathStr = path.toString();
|
|
511
|
+
const file = Gio.File.new_for_path(pathStr);
|
|
212
512
|
const recursive = options?.recursive || false;
|
|
213
|
-
|
|
214
|
-
|
|
513
|
+
const force = options?.force || false;
|
|
514
|
+
|
|
515
|
+
let dirent: Dirent;
|
|
516
|
+
try {
|
|
517
|
+
dirent = new Dirent(pathStr);
|
|
518
|
+
} catch (err: unknown) {
|
|
519
|
+
if (force && isNotFoundError(err)) return;
|
|
520
|
+
throw createNodeError(err, 'rm', path);
|
|
521
|
+
}
|
|
215
522
|
|
|
216
523
|
if (dirent.isDirectory()) {
|
|
217
524
|
const childFiles = readdirSync(path, { withFileTypes: true });
|
|
218
525
|
|
|
219
526
|
if (!recursive && childFiles.length) {
|
|
220
|
-
|
|
527
|
+
const err = Object.assign(new Error(), { code: 5 }); // Gio.IOErrorEnum.NOT_EMPTY
|
|
528
|
+
throw createNodeError(err, 'rm', path);
|
|
221
529
|
}
|
|
222
|
-
|
|
530
|
+
|
|
223
531
|
for (const childFile of childFiles) {
|
|
224
|
-
if (childFile
|
|
225
|
-
|
|
226
|
-
} else if (childFile.isFile()) {
|
|
227
|
-
rmSync(join(path.toString(), childFile.name), options);
|
|
532
|
+
if (typeof childFile !== 'string') {
|
|
533
|
+
rmSync(join(pathStr, childFile.name), options);
|
|
228
534
|
}
|
|
229
535
|
}
|
|
230
536
|
}
|
|
231
537
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
throw err;
|
|
538
|
+
try {
|
|
539
|
+
file.delete(null);
|
|
540
|
+
} catch (err: unknown) {
|
|
541
|
+
if (force && isNotFoundError(err)) return;
|
|
542
|
+
throw createNodeError(err, 'rm', path);
|
|
238
543
|
}
|
|
239
544
|
}
|
package/src/test.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
import '@gjsify/node-globals';
|
|
2
2
|
import { run } from '@gjsify/unit';
|
|
3
3
|
|
|
4
4
|
import testSuiteCallback from './callback.spec.js';
|
|
@@ -7,5 +7,10 @@ import testSuitePromise from './promises.spec.js';
|
|
|
7
7
|
import testSuiteSync from './sync.spec.js';
|
|
8
8
|
import testSuiteSymlink from './symlink.spec.js';
|
|
9
9
|
import testSuiteStat from './stat.spec.js';
|
|
10
|
+
import testSuiteNewApis from './new-apis.spec.js';
|
|
11
|
+
import testSuiteExtended from './extended.spec.js';
|
|
12
|
+
|
|
13
|
+
import testSuiteErrors from './errors.spec.js';
|
|
14
|
+
import testSuiteStreams from './streams.spec.js';
|
|
10
15
|
|
|
11
|
-
run({testSuiteCallback, testSuiteFileHandle, testSuitePromise, testSuiteSync, testSuiteSymlink, testSuiteStat});
|
|
16
|
+
run({testSuiteCallback, testSuiteFileHandle, testSuitePromise, testSuiteSync, testSuiteSymlink, testSuiteStat, testSuiteNewApis, testSuiteExtended, testSuiteErrors, testSuiteStreams});
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { Abortable } from 'events';
|
|
2
|
-
import type { ObjectEncodingOptions, OpenMode } from 'fs'; // Types from @types/node
|
|
1
|
+
import type { Abortable } from 'node:events';
|
|
2
|
+
import type { ObjectEncodingOptions, OpenMode } from 'node:fs'; // Types from @types/node
|
|
3
3
|
|
|
4
4
|
export type ReadOptions =
|
|
5
5
|
| (ObjectEncodingOptions & Abortable & {
|
package/src/utils.ts
CHANGED
package/src/write-stream.ts
CHANGED
|
@@ -1,12 +1,16 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
// Adapted from Deno (refs/deno/ext/node/polyfills/internal/fs/streams.ts)
|
|
3
|
+
// Copyright (c) 2018-2026 the Deno authors. MIT license.
|
|
4
|
+
// Modifications: Rewritten to use Gio.File for GJS
|
|
5
|
+
|
|
6
|
+
import { Writable } from "node:stream";
|
|
7
|
+
import { fileURLToPath, URL } from "node:url";
|
|
3
8
|
import { open, write, close } from "./callback.js";
|
|
4
9
|
|
|
5
10
|
import type { OpenFlags } from './types/index.js';
|
|
6
|
-
import type { PathLike, WriteStream as IWriteStream } from 'fs';
|
|
7
|
-
import type { CreateWriteStreamOptions } from 'fs/promises'; // Types from @types/node
|
|
11
|
+
import type { PathLike, WriteStream as IWriteStream } from 'node:fs';
|
|
12
|
+
import type { CreateWriteStreamOptions } from 'node:fs/promises'; // Types from @types/node
|
|
8
13
|
|
|
9
|
-
// From Deno
|
|
10
14
|
const kIsPerformingIO = Symbol("kIsPerformingIO");
|
|
11
15
|
const kIoDone = Symbol("kIoDone");
|
|
12
16
|
|
|
@@ -18,8 +22,6 @@ export function toPathIfFileURL(
|
|
|
18
22
|
}
|
|
19
23
|
return fileURLToPath(fileURLOrPath);
|
|
20
24
|
}
|
|
21
|
-
|
|
22
|
-
// Credits https://github.com/denoland/deno_std/blob/main/node/internal/fs/streams.ts
|
|
23
25
|
export class WriteStream extends Writable implements IWriteStream {
|
|
24
26
|
|
|
25
27
|
/**
|
package/tsconfig.json
CHANGED
|
@@ -1,18 +1,31 @@
|
|
|
1
1
|
{
|
|
2
2
|
"compilerOptions": {
|
|
3
3
|
"module": "ESNext",
|
|
4
|
-
"types": ["node"],
|
|
5
4
|
"target": "ESNext",
|
|
6
|
-
"moduleResolution": "
|
|
5
|
+
"moduleResolution": "bundler",
|
|
6
|
+
"types": [
|
|
7
|
+
"node"
|
|
8
|
+
],
|
|
7
9
|
"experimentalDecorators": true,
|
|
10
|
+
"emitDeclarationOnly": true,
|
|
11
|
+
"declaration": true,
|
|
12
|
+
"allowImportingTsExtensions": true,
|
|
8
13
|
"outDir": "lib",
|
|
9
14
|
"rootDir": "src",
|
|
10
|
-
"
|
|
15
|
+
"declarationDir": "lib/types",
|
|
16
|
+
"composite": true,
|
|
17
|
+
"skipLibCheck": true,
|
|
18
|
+
"allowJs": true,
|
|
19
|
+
"checkJs": false,
|
|
20
|
+
"strict": false
|
|
11
21
|
},
|
|
12
|
-
"
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
"
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
22
|
+
"include": [
|
|
23
|
+
"src/**/*.ts"
|
|
24
|
+
],
|
|
25
|
+
"exclude": [
|
|
26
|
+
"src/test.ts",
|
|
27
|
+
"src/test.mts",
|
|
28
|
+
"src/**/*.spec.ts",
|
|
29
|
+
"src/**/*.spec.mts"
|
|
30
|
+
]
|
|
31
|
+
}
|