@gjsify/fs 0.3.21 → 0.4.3
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/lib/esm/_virtual/_rolldown/runtime.js +1 -1
- package/lib/esm/callback.js +1 -1
- package/lib/esm/cp.js +1 -1
- package/lib/esm/dir.js +1 -1
- package/lib/esm/dirent.js +1 -1
- package/lib/esm/encoding.js +1 -1
- package/lib/esm/errors.js +1 -1
- package/lib/esm/fd-ops.js +1 -1
- package/lib/esm/file-handle.js +1 -1
- package/lib/esm/fs-watcher.js +1 -1
- package/lib/esm/glob.js +1 -1
- package/lib/esm/read-stream.js +1 -1
- package/lib/esm/stat-watcher.js +1 -1
- package/lib/esm/statfs.js +1 -1
- package/lib/esm/stats.js +1 -1
- package/lib/esm/sync.js +1 -1
- package/lib/esm/utils.js +1 -1
- package/lib/esm/utimes.js +1 -1
- package/lib/esm/write-stream.js +1 -1
- package/package.json +51 -48
- package/src/callback.spec.ts +0 -296
- package/src/callback.ts +0 -684
- package/src/cp.spec.ts +0 -181
- package/src/cp.ts +0 -328
- package/src/dir.spec.ts +0 -204
- package/src/dir.ts +0 -199
- package/src/dirent.ts +0 -165
- package/src/encoding.ts +0 -45
- package/src/errors.spec.ts +0 -389
- package/src/errors.ts +0 -19
- package/src/extended.spec.ts +0 -706
- package/src/fd-ops.spec.ts +0 -234
- package/src/fd-ops.ts +0 -251
- package/src/file-handle.spec.ts +0 -115
- package/src/file-handle.ts +0 -856
- package/src/fs-watcher.ts +0 -198
- package/src/glob.spec.ts +0 -201
- package/src/glob.ts +0 -205
- package/src/index.ts +0 -313
- package/src/new-apis.spec.ts +0 -505
- package/src/promises.spec.ts +0 -812
- package/src/promises.ts +0 -686
- package/src/read-stream.ts +0 -128
- package/src/stat-watcher.ts +0 -116
- package/src/stat.spec.ts +0 -87
- package/src/statfs.spec.ts +0 -67
- package/src/statfs.ts +0 -92
- package/src/stats.ts +0 -207
- package/src/streams.spec.ts +0 -513
- package/src/symlink.spec.ts +0 -188
- package/src/sync.spec.ts +0 -377
- package/src/sync.ts +0 -562
- package/src/test.mts +0 -27
- package/src/types/encoding-option.ts +0 -3
- package/src/types/file-read-options.ts +0 -15
- package/src/types/file-read-result.ts +0 -4
- package/src/types/flag-and-open-mode.ts +0 -6
- package/src/types/index.ts +0 -6
- package/src/types/open-flags.ts +0 -14
- package/src/types/read-options.ts +0 -9
- package/src/utils.ts +0 -31
- package/src/utimes.spec.ts +0 -113
- package/src/utimes.ts +0 -97
- package/src/watch.spec.ts +0 -171
- package/src/watchfile.spec.ts +0 -185
- package/src/write-stream.ts +0 -142
- package/test/file.txt +0 -1
- package/tsconfig.json +0 -29
- package/tsconfig.tsbuildinfo +0 -1
package/src/fs-watcher.ts
DELETED
|
@@ -1,198 +0,0 @@
|
|
|
1
|
-
// Reference: Node.js lib/internal/fs/watchers.js
|
|
2
|
-
// Reimplemented for GJS using Gio.FileMonitor
|
|
3
|
-
|
|
4
|
-
import GLib from '@girs/glib-2.0';
|
|
5
|
-
import Gio from '@girs/gio-2.0';
|
|
6
|
-
import { EventEmitter } from 'node:events';
|
|
7
|
-
import { normalizePath } from './utils.js';
|
|
8
|
-
const privates = new WeakMap;
|
|
9
|
-
|
|
10
|
-
import type { FSWatcher as IFSWatcher, PathLike, WatchOptions } from 'node:fs';
|
|
11
|
-
|
|
12
|
-
export class FSWatcher extends EventEmitter implements IFSWatcher {
|
|
13
|
-
|
|
14
|
-
constructor(filename: PathLike, options, listener) {
|
|
15
|
-
super();
|
|
16
|
-
if (!options || typeof options !== 'object')
|
|
17
|
-
options = {persistent: true};
|
|
18
|
-
|
|
19
|
-
const persistent = options.persistent !== false;
|
|
20
|
-
const cancellable = Gio.Cancellable.new();
|
|
21
|
-
const pathStr = normalizePath(filename);
|
|
22
|
-
const file = Gio.File.new_for_path(pathStr);
|
|
23
|
-
const watcher = file.monitor(Gio.FileMonitorFlags.NONE, cancellable);
|
|
24
|
-
watcher.connect('changed', changed.bind(this));
|
|
25
|
-
|
|
26
|
-
// When persistent is true, acquire a reference on the default GLib main context
|
|
27
|
-
// so the main loop stays alive while this watcher is active.
|
|
28
|
-
// This mirrors Node.js behavior where persistent watchers keep the event loop alive.
|
|
29
|
-
let sourceId: number | null = null;
|
|
30
|
-
if (persistent) {
|
|
31
|
-
// Add a never-firing timeout source to keep the mainloop alive.
|
|
32
|
-
// This is a lightweight way to hold a ref on the main context.
|
|
33
|
-
sourceId = (GLib.timeout_add as unknown as (priority: number, interval: number, fn: () => boolean) => number)(
|
|
34
|
-
GLib.PRIORITY_LOW,
|
|
35
|
-
2147483647,
|
|
36
|
-
() => GLib.SOURCE_CONTINUE,
|
|
37
|
-
);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
privates.set(this, {
|
|
41
|
-
persistent,
|
|
42
|
-
cancellable,
|
|
43
|
-
sourceId,
|
|
44
|
-
// even if never used later on, the monitor needs to be
|
|
45
|
-
// attached to this instance or GJS reference counter
|
|
46
|
-
// will ignore it and no watch will ever happen
|
|
47
|
-
watcher
|
|
48
|
-
});
|
|
49
|
-
if (listener) this.on('change', listener);
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
close() {
|
|
53
|
-
const priv = privates.get(this);
|
|
54
|
-
if (!priv.cancellable.is_cancelled()) {
|
|
55
|
-
priv.cancellable.cancel();
|
|
56
|
-
if (priv.sourceId !== null) {
|
|
57
|
-
GLib.source_remove(priv.sourceId);
|
|
58
|
-
priv.sourceId = null;
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* When called, requests that the Node.js event loop not exit so long as the
|
|
65
|
-
* FSWatcher is active. Calling ref() multiple times has no effect.
|
|
66
|
-
*/
|
|
67
|
-
ref(): this {
|
|
68
|
-
const priv = privates.get(this);
|
|
69
|
-
if (!priv.persistent && !priv.cancellable.is_cancelled()) {
|
|
70
|
-
priv.persistent = true;
|
|
71
|
-
priv.sourceId = (GLib.timeout_add as unknown as (priority: number, interval: number, fn: () => boolean) => number)(
|
|
72
|
-
GLib.PRIORITY_LOW,
|
|
73
|
-
2147483647,
|
|
74
|
-
() => GLib.SOURCE_CONTINUE,
|
|
75
|
-
);
|
|
76
|
-
}
|
|
77
|
-
return this;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
* When called, the active FSWatcher will not require the Node.js event loop
|
|
82
|
-
* to remain active. Calling unref() multiple times has no effect.
|
|
83
|
-
*/
|
|
84
|
-
unref(): this {
|
|
85
|
-
const priv = privates.get(this);
|
|
86
|
-
if (priv.persistent) {
|
|
87
|
-
priv.persistent = false;
|
|
88
|
-
if (priv.sourceId !== null) {
|
|
89
|
-
GLib.source_remove(priv.sourceId);
|
|
90
|
-
priv.sourceId = null;
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
return this;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
};
|
|
97
|
-
|
|
98
|
-
function changed(watcher, file, otherFile, eventType) {
|
|
99
|
-
switch (eventType) {
|
|
100
|
-
case Gio.FileMonitorEvent.CHANGES_DONE_HINT:
|
|
101
|
-
this.emit('change', 'change', file.get_basename());
|
|
102
|
-
break;
|
|
103
|
-
case Gio.FileMonitorEvent.DELETED:
|
|
104
|
-
case Gio.FileMonitorEvent.CREATED:
|
|
105
|
-
case Gio.FileMonitorEvent.RENAMED:
|
|
106
|
-
case Gio.FileMonitorEvent.MOVED_IN:
|
|
107
|
-
case Gio.FileMonitorEvent.MOVED_OUT:
|
|
108
|
-
this.emit('rename', 'rename', file.get_basename());
|
|
109
|
-
break;
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
export default FSWatcher;
|
|
114
|
-
|
|
115
|
-
// ─── fs.promises.watch ────────────────────────────────────────────────────────
|
|
116
|
-
|
|
117
|
-
type WatchEvent = { eventType: string; filename: string | null };
|
|
118
|
-
|
|
119
|
-
function gioEventToNodeType(eventType: Gio.FileMonitorEvent): string | null {
|
|
120
|
-
switch (eventType) {
|
|
121
|
-
case Gio.FileMonitorEvent.CHANGES_DONE_HINT: return 'change';
|
|
122
|
-
case Gio.FileMonitorEvent.DELETED:
|
|
123
|
-
case Gio.FileMonitorEvent.CREATED:
|
|
124
|
-
case Gio.FileMonitorEvent.RENAMED:
|
|
125
|
-
case Gio.FileMonitorEvent.MOVED_IN:
|
|
126
|
-
case Gio.FileMonitorEvent.MOVED_OUT: return 'rename';
|
|
127
|
-
default: return null;
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
export async function* watchAsync(
|
|
132
|
-
filename: PathLike,
|
|
133
|
-
options?: WatchOptions & { signal?: AbortSignal },
|
|
134
|
-
): AsyncIterableIterator<WatchEvent> {
|
|
135
|
-
const signal = (options as any)?.signal as AbortSignal | undefined;
|
|
136
|
-
|
|
137
|
-
if (signal?.aborted) return;
|
|
138
|
-
|
|
139
|
-
const pathStr = normalizePath(filename);
|
|
140
|
-
const file = Gio.File.new_for_path(pathStr);
|
|
141
|
-
const cancellable = Gio.Cancellable.new();
|
|
142
|
-
|
|
143
|
-
let watcher: Gio.FileMonitor;
|
|
144
|
-
try {
|
|
145
|
-
watcher = file.monitor(Gio.FileMonitorFlags.NONE, cancellable);
|
|
146
|
-
} catch {
|
|
147
|
-
return;
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
const eventQueue: WatchEvent[] = [];
|
|
151
|
-
const waiterQueue: Array<{ resolve: (r: IteratorResult<WatchEvent>) => void }> = [];
|
|
152
|
-
let finished = false;
|
|
153
|
-
|
|
154
|
-
function enqueue(event: WatchEvent): void {
|
|
155
|
-
if (finished) return;
|
|
156
|
-
if (waiterQueue.length > 0) {
|
|
157
|
-
waiterQueue.shift()!.resolve({ value: event, done: false });
|
|
158
|
-
} else {
|
|
159
|
-
eventQueue.push(event);
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
function terminate(): void {
|
|
164
|
-
if (finished) return;
|
|
165
|
-
finished = true;
|
|
166
|
-
if (!cancellable.is_cancelled()) cancellable.cancel();
|
|
167
|
-
while (waiterQueue.length > 0) {
|
|
168
|
-
waiterQueue.shift()!.resolve({ value: undefined as any, done: true });
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
const signalId = watcher.connect('changed', (_mon: Gio.FileMonitor, changedFile: Gio.File, _otherFile: Gio.File | null, eventType: Gio.FileMonitorEvent) => {
|
|
173
|
-
const type = gioEventToNodeType(eventType);
|
|
174
|
-
if (type === null) return;
|
|
175
|
-
enqueue({ eventType: type, filename: changedFile?.get_basename() ?? null });
|
|
176
|
-
});
|
|
177
|
-
|
|
178
|
-
const abortHandler = () => terminate();
|
|
179
|
-
signal?.addEventListener('abort', abortHandler);
|
|
180
|
-
|
|
181
|
-
try {
|
|
182
|
-
while (!finished) {
|
|
183
|
-
if (eventQueue.length > 0) {
|
|
184
|
-
yield eventQueue.shift()!;
|
|
185
|
-
continue;
|
|
186
|
-
}
|
|
187
|
-
const result = await new Promise<IteratorResult<WatchEvent>>(resolve => {
|
|
188
|
-
waiterQueue.push({ resolve });
|
|
189
|
-
});
|
|
190
|
-
if (result.done) break;
|
|
191
|
-
yield result.value;
|
|
192
|
-
}
|
|
193
|
-
} finally {
|
|
194
|
-
signal?.removeEventListener('abort', abortHandler);
|
|
195
|
-
try { watcher.disconnect(signalId); } catch {}
|
|
196
|
-
if (!cancellable.is_cancelled()) cancellable.cancel();
|
|
197
|
-
}
|
|
198
|
-
}
|
package/src/glob.spec.ts
DELETED
|
@@ -1,201 +0,0 @@
|
|
|
1
|
-
// Ported from refs/bun/test/js/node/fs/glob.test.ts
|
|
2
|
-
// Original: MIT, Oven & contributors.
|
|
3
|
-
// Rewritten for @gjsify/unit — behavior preserved, assertion dialect adapted.
|
|
4
|
-
|
|
5
|
-
import { describe, it, expect } from '@gjsify/unit';
|
|
6
|
-
import {
|
|
7
|
-
globSync,
|
|
8
|
-
glob,
|
|
9
|
-
promises,
|
|
10
|
-
mkdirSync,
|
|
11
|
-
writeFileSync,
|
|
12
|
-
mkdtempSync,
|
|
13
|
-
rmSync,
|
|
14
|
-
} from 'node:fs';
|
|
15
|
-
import { join } from 'node:path';
|
|
16
|
-
import { tmpdir } from 'node:os';
|
|
17
|
-
|
|
18
|
-
function makeTmp(): string {
|
|
19
|
-
return mkdtempSync(join(tmpdir(), 'gjsify-glob-'));
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export default async () => {
|
|
23
|
-
await describe('fs.globSync', async () => {
|
|
24
|
-
await it('matches *.ts files in flat directory', async () => {
|
|
25
|
-
const tmp = makeTmp();
|
|
26
|
-
writeFileSync(join(tmp, 'a.ts'), '');
|
|
27
|
-
writeFileSync(join(tmp, 'b.ts'), '');
|
|
28
|
-
writeFileSync(join(tmp, 'c.txt'), '');
|
|
29
|
-
|
|
30
|
-
const results = globSync('*.ts', { cwd: tmp });
|
|
31
|
-
expect(results.sort()).toStrictEqual(['a.ts', 'b.ts']);
|
|
32
|
-
rmSync(tmp, { recursive: true, force: true });
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
await it('matches **/*.ts recursively', async () => {
|
|
36
|
-
const tmp = makeTmp();
|
|
37
|
-
mkdirSync(join(tmp, 'sub'));
|
|
38
|
-
writeFileSync(join(tmp, 'root.ts'), '');
|
|
39
|
-
writeFileSync(join(tmp, 'sub', 'nested.ts'), '');
|
|
40
|
-
writeFileSync(join(tmp, 'sub', 'other.txt'), '');
|
|
41
|
-
|
|
42
|
-
const results = globSync('**/*.ts', { cwd: tmp });
|
|
43
|
-
expect(results.sort()).toStrictEqual(['root.ts', 'sub/nested.ts']);
|
|
44
|
-
rmSync(tmp, { recursive: true, force: true });
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
await it('matches files in a subdirectory pattern', async () => {
|
|
48
|
-
const tmp = makeTmp();
|
|
49
|
-
mkdirSync(join(tmp, 'src'));
|
|
50
|
-
writeFileSync(join(tmp, 'src', 'index.ts'), '');
|
|
51
|
-
writeFileSync(join(tmp, 'src', 'util.ts'), '');
|
|
52
|
-
writeFileSync(join(tmp, 'index.ts'), '');
|
|
53
|
-
|
|
54
|
-
const results = globSync('src/*.ts', { cwd: tmp });
|
|
55
|
-
expect(results.sort()).toStrictEqual(['src/index.ts', 'src/util.ts']);
|
|
56
|
-
rmSync(tmp, { recursive: true, force: true });
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
await it('supports {a,b} alternation', async () => {
|
|
60
|
-
const tmp = makeTmp();
|
|
61
|
-
writeFileSync(join(tmp, 'a.ts'), '');
|
|
62
|
-
writeFileSync(join(tmp, 'b.ts'), '');
|
|
63
|
-
writeFileSync(join(tmp, 'c.ts'), '');
|
|
64
|
-
|
|
65
|
-
const results = globSync('*.{ts,js}', { cwd: tmp });
|
|
66
|
-
expect(results.sort()).toStrictEqual(['a.ts', 'b.ts', 'c.ts']);
|
|
67
|
-
rmSync(tmp, { recursive: true, force: true });
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
await it('** matches all files and directories', async () => {
|
|
71
|
-
const tmp = makeTmp();
|
|
72
|
-
mkdirSync(join(tmp, 'sub'));
|
|
73
|
-
writeFileSync(join(tmp, 'a.ts'), '');
|
|
74
|
-
writeFileSync(join(tmp, 'sub', 'b.ts'), '');
|
|
75
|
-
|
|
76
|
-
const results = globSync('**', { cwd: tmp });
|
|
77
|
-
// Should include '.', 'a.ts', 'sub', 'sub/b.ts'
|
|
78
|
-
expect(results.includes('a.ts')).toBe(true);
|
|
79
|
-
expect(results.includes('sub/b.ts')).toBe(true);
|
|
80
|
-
rmSync(tmp, { recursive: true, force: true });
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
await it('a/** matches the directory itself and its contents', async () => {
|
|
84
|
-
const tmp = makeTmp();
|
|
85
|
-
mkdirSync(join(tmp, 'sub'));
|
|
86
|
-
writeFileSync(join(tmp, 'sub', 'x.ts'), '');
|
|
87
|
-
writeFileSync(join(tmp, 'root.ts'), '');
|
|
88
|
-
|
|
89
|
-
const results = globSync('sub/**', { cwd: tmp });
|
|
90
|
-
expect(results.includes('sub')).toBe(true);
|
|
91
|
-
expect(results.includes('sub/x.ts')).toBe(true);
|
|
92
|
-
expect(results.includes('root.ts')).toBe(false);
|
|
93
|
-
rmSync(tmp, { recursive: true, force: true });
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
await it('accepts array of patterns', async () => {
|
|
97
|
-
const tmp = makeTmp();
|
|
98
|
-
writeFileSync(join(tmp, 'a.ts'), '');
|
|
99
|
-
writeFileSync(join(tmp, 'b.js'), '');
|
|
100
|
-
writeFileSync(join(tmp, 'c.txt'), '');
|
|
101
|
-
|
|
102
|
-
const results = globSync(['*.ts', '*.js'], { cwd: tmp });
|
|
103
|
-
expect(results.sort()).toStrictEqual(['a.ts', 'b.js']);
|
|
104
|
-
rmSync(tmp, { recursive: true, force: true });
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
await it('exclude function filters out matching paths', async () => {
|
|
108
|
-
const tmp = makeTmp();
|
|
109
|
-
writeFileSync(join(tmp, 'a.ts'), '');
|
|
110
|
-
writeFileSync(join(tmp, 'b.ts'), '');
|
|
111
|
-
|
|
112
|
-
// Use **/*.ts so Node.js native also invokes the exclude function
|
|
113
|
-
// (flat patterns like *.ts skip directory traversal and exclude is not called)
|
|
114
|
-
const results = globSync('**/*.ts', {
|
|
115
|
-
cwd: tmp,
|
|
116
|
-
exclude: (p) => p === 'a.ts',
|
|
117
|
-
});
|
|
118
|
-
expect(results).toStrictEqual(['b.ts']);
|
|
119
|
-
rmSync(tmp, { recursive: true, force: true });
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
await it('returns empty array when no files match', async () => {
|
|
123
|
-
const tmp = makeTmp();
|
|
124
|
-
writeFileSync(join(tmp, 'a.txt'), '');
|
|
125
|
-
|
|
126
|
-
const results = globSync('*.ts', { cwd: tmp });
|
|
127
|
-
expect(results).toStrictEqual([]);
|
|
128
|
-
rmSync(tmp, { recursive: true, force: true });
|
|
129
|
-
});
|
|
130
|
-
|
|
131
|
-
await it('returns empty array for non-existent cwd', async () => {
|
|
132
|
-
const results = globSync('*.ts', { cwd: '/nonexistent-dir-gjsify-test' });
|
|
133
|
-
expect(results).toStrictEqual([]);
|
|
134
|
-
});
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
await describe('fs.glob (callback)', async () => {
|
|
138
|
-
await it('calls back with matched files', async () => {
|
|
139
|
-
const tmp = makeTmp();
|
|
140
|
-
writeFileSync(join(tmp, 'hello.ts'), '');
|
|
141
|
-
writeFileSync(join(tmp, 'world.ts'), '');
|
|
142
|
-
|
|
143
|
-
const results = await new Promise<string[]>((resolve, reject) => {
|
|
144
|
-
glob('*.ts', { cwd: tmp }, (err, matches) => {
|
|
145
|
-
if (err) return reject(err);
|
|
146
|
-
resolve(matches);
|
|
147
|
-
});
|
|
148
|
-
});
|
|
149
|
-
|
|
150
|
-
expect(results.sort()).toStrictEqual(['hello.ts', 'world.ts']);
|
|
151
|
-
rmSync(tmp, { recursive: true, force: true });
|
|
152
|
-
});
|
|
153
|
-
|
|
154
|
-
await it('accepts callback as second argument (no options)', async () => {
|
|
155
|
-
const tmp = makeTmp();
|
|
156
|
-
writeFileSync(join(tmp, 'file.ts'), '');
|
|
157
|
-
|
|
158
|
-
const results = await new Promise<string[]>((resolve, reject) => {
|
|
159
|
-
(glob as any)('*.ts', (err: any, matches: string[]) => {
|
|
160
|
-
if (err) return reject(err);
|
|
161
|
-
resolve(matches);
|
|
162
|
-
});
|
|
163
|
-
});
|
|
164
|
-
|
|
165
|
-
// No cwd given — just verify it completes without error
|
|
166
|
-
expect(Array.isArray(results)).toBe(true);
|
|
167
|
-
rmSync(tmp, { recursive: true, force: true });
|
|
168
|
-
});
|
|
169
|
-
});
|
|
170
|
-
|
|
171
|
-
await describe('fs.promises.glob', async () => {
|
|
172
|
-
await it('async iterates matched files', async () => {
|
|
173
|
-
const tmp = makeTmp();
|
|
174
|
-
writeFileSync(join(tmp, 'alpha.ts'), '');
|
|
175
|
-
writeFileSync(join(tmp, 'beta.ts'), '');
|
|
176
|
-
|
|
177
|
-
const results: string[] = [];
|
|
178
|
-
for await (const match of promises.glob('*.ts', { cwd: tmp })) {
|
|
179
|
-
results.push(match);
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
expect(results.sort()).toStrictEqual(['alpha.ts', 'beta.ts']);
|
|
183
|
-
rmSync(tmp, { recursive: true, force: true });
|
|
184
|
-
});
|
|
185
|
-
|
|
186
|
-
await it('async iterates recursively with **/*.ts', async () => {
|
|
187
|
-
const tmp = makeTmp();
|
|
188
|
-
mkdirSync(join(tmp, 'lib'));
|
|
189
|
-
writeFileSync(join(tmp, 'index.ts'), '');
|
|
190
|
-
writeFileSync(join(tmp, 'lib', 'helper.ts'), '');
|
|
191
|
-
|
|
192
|
-
const results: string[] = [];
|
|
193
|
-
for await (const match of promises.glob('**/*.ts', { cwd: tmp })) {
|
|
194
|
-
results.push(match);
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
expect(results.sort()).toStrictEqual(['index.ts', 'lib/helper.ts']);
|
|
198
|
-
rmSync(tmp, { recursive: true, force: true });
|
|
199
|
-
});
|
|
200
|
-
});
|
|
201
|
-
};
|
package/src/glob.ts
DELETED
|
@@ -1,205 +0,0 @@
|
|
|
1
|
-
// Reference: Node.js lib/internal/fs/glob.js
|
|
2
|
-
// Reimplemented for GJS using readdirSync (recursive) + pattern matching
|
|
3
|
-
|
|
4
|
-
import { readdirSync } from './sync.js';
|
|
5
|
-
import { normalizePath } from './utils.js';
|
|
6
|
-
import type { PathLike } from 'node:fs';
|
|
7
|
-
|
|
8
|
-
export interface GlobOptions {
|
|
9
|
-
cwd?: string | URL;
|
|
10
|
-
exclude?: string | string[] | ((path: string) => boolean);
|
|
11
|
-
withFileTypes?: boolean;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
// ─── Pattern → RegExp conversion ─────────────────────────────────────────────
|
|
15
|
-
|
|
16
|
-
/** Convert a single glob segment (no `/`) to a regex source string */
|
|
17
|
-
function segmentToRegexSrc(seg: string): string {
|
|
18
|
-
// Handle extglob: !(pattern), *(pattern), +(pattern), ?(pattern), @(pattern)
|
|
19
|
-
const extglob = /^([!*+?@])\((.+)\)$/.exec(seg);
|
|
20
|
-
if (extglob) {
|
|
21
|
-
const [, type, inner] = extglob;
|
|
22
|
-
const parts = inner.split('|').map(p => segmentToRegexSrc(p));
|
|
23
|
-
const group = '(?:' + parts.join('|') + ')';
|
|
24
|
-
switch (type) {
|
|
25
|
-
case '!': return '(?!(?:' + parts.join('|') + '))[^/]*';
|
|
26
|
-
case '*': return group + '*';
|
|
27
|
-
case '+': return group + '+';
|
|
28
|
-
case '?': return group + '?';
|
|
29
|
-
case '@': return group;
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
let result = '';
|
|
34
|
-
let i = 0;
|
|
35
|
-
while (i < seg.length) {
|
|
36
|
-
const c = seg[i];
|
|
37
|
-
if (c === '*') { result += '[^/]*'; i++; continue; }
|
|
38
|
-
if (c === '?') { result += '[^/]'; i++; continue; }
|
|
39
|
-
if (c === '[') {
|
|
40
|
-
// Character class — pass through to regex
|
|
41
|
-
const end = seg.indexOf(']', i + 1);
|
|
42
|
-
if (end === -1) { result += '\\['; i++; continue; }
|
|
43
|
-
result += seg.slice(i, end + 1);
|
|
44
|
-
i = end + 1;
|
|
45
|
-
continue;
|
|
46
|
-
}
|
|
47
|
-
if (c === '{') {
|
|
48
|
-
// Alternation
|
|
49
|
-
const end = seg.indexOf('}', i + 1);
|
|
50
|
-
if (end === -1) { result += '\\{'; i++; continue; }
|
|
51
|
-
const alts = seg.slice(i + 1, end).split(',').map(a => segmentToRegexSrc(a.trim()));
|
|
52
|
-
result += '(?:' + alts.join('|') + ')';
|
|
53
|
-
i = end + 1;
|
|
54
|
-
continue;
|
|
55
|
-
}
|
|
56
|
-
// Escape regex special chars
|
|
57
|
-
if ('.+^$|()[]{}'.includes(c)) {
|
|
58
|
-
result += '\\' + c;
|
|
59
|
-
} else {
|
|
60
|
-
result += c;
|
|
61
|
-
}
|
|
62
|
-
i++;
|
|
63
|
-
}
|
|
64
|
-
return result;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
/**
|
|
68
|
-
* Convert a glob pattern to a RegExp that matches relative POSIX paths.
|
|
69
|
-
*/
|
|
70
|
-
function globToRegex(pattern: string): RegExp {
|
|
71
|
-
// Normalize: collapse multiple slashes, remove leading './'
|
|
72
|
-
pattern = pattern.replace(/\/+/g, '/').replace(/^\.\//, '');
|
|
73
|
-
|
|
74
|
-
const segments = pattern.split('/');
|
|
75
|
-
const parts: string[] = [];
|
|
76
|
-
|
|
77
|
-
for (let si = 0; si < segments.length; si++) {
|
|
78
|
-
const seg = segments[si];
|
|
79
|
-
const isLast = si === segments.length - 1;
|
|
80
|
-
|
|
81
|
-
if (seg === '**') {
|
|
82
|
-
if (isLast) {
|
|
83
|
-
if (parts.length > 0 && parts[parts.length - 1] === '/') {
|
|
84
|
-
// Remove the trailing '/' so "a/" becomes optional: "a(?:/.*)?
|
|
85
|
-
parts.pop();
|
|
86
|
-
parts.push('(?:/.+)?');
|
|
87
|
-
} else {
|
|
88
|
-
// '**' at root level: match '.' or anything
|
|
89
|
-
parts.push('(?:.+)?');
|
|
90
|
-
}
|
|
91
|
-
} else {
|
|
92
|
-
// Not last: match zero or more path segments with trailing slash
|
|
93
|
-
parts.push('(?:[^/]+/)*');
|
|
94
|
-
}
|
|
95
|
-
} else {
|
|
96
|
-
parts.push(segmentToRegexSrc(seg));
|
|
97
|
-
if (!isLast) parts.push('/');
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
return new RegExp('^(?:' + parts.join('') + ')$');
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
// ─── Exclude logic ────────────────────────────────────────────────────────────
|
|
105
|
-
|
|
106
|
-
function buildExcludePredicate(exclude: GlobOptions['exclude']): ((path: string) => boolean) | null {
|
|
107
|
-
if (!exclude) return null;
|
|
108
|
-
if (typeof exclude === 'function') return exclude;
|
|
109
|
-
const patterns = Array.isArray(exclude) ? exclude : [exclude];
|
|
110
|
-
const regexes = patterns.map(p => globToRegex(p));
|
|
111
|
-
return (path: string) => regexes.some(rx => rx.test(path));
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
// ─── Walk + match ─────────────────────────────────────────────────────────────
|
|
115
|
-
|
|
116
|
-
function matchAll(pattern: string, cwd: string, exclude: GlobOptions['exclude']): string[] {
|
|
117
|
-
const regex = globToRegex(pattern);
|
|
118
|
-
const isExcluded = buildExcludePredicate(exclude);
|
|
119
|
-
|
|
120
|
-
// Get all entries recursively (files + directories)
|
|
121
|
-
let allEntries: string[];
|
|
122
|
-
try {
|
|
123
|
-
allEntries = readdirSync(cwd, { recursive: true }) as string[];
|
|
124
|
-
} catch {
|
|
125
|
-
return [];
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
// Include '.' itself for patterns like '**' that match the root
|
|
129
|
-
const candidates = ['.', ...allEntries];
|
|
130
|
-
|
|
131
|
-
const results: string[] = [];
|
|
132
|
-
for (const entry of candidates) {
|
|
133
|
-
// Normalize separators for matching
|
|
134
|
-
const normalized = entry.replace(/\\/g, '/');
|
|
135
|
-
if (!regex.test(normalized)) continue;
|
|
136
|
-
if (isExcluded && isExcluded(normalized)) continue;
|
|
137
|
-
results.push(entry);
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
return results;
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
// ─── Public API ───────────────────────────────────────────────────────────────
|
|
144
|
-
|
|
145
|
-
export function globSync(
|
|
146
|
-
pattern: string | string[],
|
|
147
|
-
options?: GlobOptions,
|
|
148
|
-
): string[] {
|
|
149
|
-
const patterns = Array.isArray(pattern) ? pattern : [pattern];
|
|
150
|
-
const cwd = options?.cwd
|
|
151
|
-
? normalizePath(options.cwd as PathLike)
|
|
152
|
-
: (globalThis as any).process?.cwd?.() ?? '/';
|
|
153
|
-
const exclude = options?.exclude;
|
|
154
|
-
|
|
155
|
-
const seen = new Set<string>();
|
|
156
|
-
const results: string[] = [];
|
|
157
|
-
|
|
158
|
-
for (const p of patterns) {
|
|
159
|
-
for (const match of matchAll(p, cwd, exclude)) {
|
|
160
|
-
if (!seen.has(match)) {
|
|
161
|
-
seen.add(match);
|
|
162
|
-
results.push(match);
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
return results;
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
export function glob(
|
|
171
|
-
pattern: string | string[],
|
|
172
|
-
options: GlobOptions | ((err: NodeJS.ErrnoException | null, matches: string[]) => void),
|
|
173
|
-
callback?: (err: NodeJS.ErrnoException | null, matches: string[]) => void,
|
|
174
|
-
): void {
|
|
175
|
-
let opts: GlobOptions;
|
|
176
|
-
let cb: (err: NodeJS.ErrnoException | null, matches: string[]) => void;
|
|
177
|
-
|
|
178
|
-
if (typeof options === 'function') {
|
|
179
|
-
cb = options;
|
|
180
|
-
opts = {};
|
|
181
|
-
} else {
|
|
182
|
-
cb = callback!;
|
|
183
|
-
opts = options || {};
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
Promise.resolve().then(() => {
|
|
187
|
-
try {
|
|
188
|
-
const matches = globSync(pattern, opts);
|
|
189
|
-
cb(null, matches);
|
|
190
|
-
} catch (err: unknown) {
|
|
191
|
-
cb(err as NodeJS.ErrnoException, []);
|
|
192
|
-
}
|
|
193
|
-
});
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
// promises.glob returns an async iterator
|
|
197
|
-
export async function* globAsync(
|
|
198
|
-
pattern: string | string[],
|
|
199
|
-
options?: GlobOptions,
|
|
200
|
-
): AsyncIterableIterator<string> {
|
|
201
|
-
const matches = globSync(pattern, options);
|
|
202
|
-
for (const m of matches) {
|
|
203
|
-
yield m;
|
|
204
|
-
}
|
|
205
|
-
}
|