@statewalker/webrun-files-node 0.9.0 → 0.10.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.
- package/README.md +19 -3
- package/dist/cjs/index.cjs +113 -26
- package/dist/esm/index.js +113 -26
- package/dist/node-files-api.d.ts +3 -0
- package/dist/node-files-api.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/node-files-api.ts +27 -29
package/README.md
CHANGED
|
@@ -32,7 +32,11 @@ console.log(content); // {"debug": true}
|
|
|
32
32
|
|
|
33
33
|
// List files
|
|
34
34
|
for await (const entry of files.list('/')) {
|
|
35
|
-
|
|
35
|
+
if (entry.kind === 'file') {
|
|
36
|
+
console.log(entry.name, entry.kind, entry.size, entry.lastModified);
|
|
37
|
+
} else {
|
|
38
|
+
console.log(entry.name, entry.kind); // directories carry no size or time
|
|
39
|
+
}
|
|
36
40
|
}
|
|
37
41
|
```
|
|
38
42
|
|
|
@@ -57,6 +61,9 @@ virtual path: "/users/alice.json"
|
|
|
57
61
|
real path: "/var/app/data/users/alice.json"
|
|
58
62
|
```
|
|
59
63
|
|
|
64
|
+
Paths are appended to `rootDir` without confinement: `..` segments are kept, not resolved, so a
|
|
65
|
+
virtual path such as `/../etc/passwd` reaches outside `rootDir`. Do not pass untrusted paths.
|
|
66
|
+
|
|
60
67
|
## API Reference
|
|
61
68
|
|
|
62
69
|
### NodeFilesApi
|
|
@@ -65,7 +72,7 @@ real path: "/var/app/data/users/alice.json"
|
|
|
65
72
|
interface NodeFilesApiOptions {
|
|
66
73
|
/**
|
|
67
74
|
* Root directory for file operations.
|
|
68
|
-
*
|
|
75
|
+
* Virtual paths are appended to this directory (see "Path Mapping" for `..`).
|
|
69
76
|
* Defaults to current working directory if not specified.
|
|
70
77
|
*/
|
|
71
78
|
rootDir?: string;
|
|
@@ -114,9 +121,17 @@ Parent directories are created automatically when writing files:
|
|
|
114
121
|
await writeText(files, '/deep/nested/path/file.txt', 'content');
|
|
115
122
|
```
|
|
116
123
|
|
|
124
|
+
### Ordered Listings
|
|
125
|
+
|
|
126
|
+
`readdir` returns names in no guaranteed order, so `list()` reads each directory, stats its entries
|
|
127
|
+
and yields them in path order (as every `FilesApi` does). A recursive listing reads a directory only
|
|
128
|
+
when the listing reaches it, and with `{ after }` skips any directory whose whole subtree sorts at
|
|
129
|
+
or before the cursor.
|
|
130
|
+
|
|
117
131
|
### Recursive Operations
|
|
118
132
|
|
|
119
|
-
Copy and remove work recursively on directories
|
|
133
|
+
Copy and remove work recursively on directories. `move()` is a single `fs.rename`: when the rename
|
|
134
|
+
fails — for example across devices — it returns `false` rather than falling back to copying.
|
|
120
135
|
|
|
121
136
|
```typescript
|
|
122
137
|
// Copy entire directory tree
|
|
@@ -133,6 +148,7 @@ import { mkdtemp, rm } from 'node:fs/promises';
|
|
|
133
148
|
import { tmpdir } from 'node:os';
|
|
134
149
|
import { join } from 'node:path';
|
|
135
150
|
import { NodeFilesApi } from '@statewalker/webrun-files-node';
|
|
151
|
+
import { writeText } from '@statewalker/webrun-files';
|
|
136
152
|
|
|
137
153
|
// Create a temp directory for testing
|
|
138
154
|
const tempDir = await mkdtemp(join(tmpdir(), 'test-'));
|
package/dist/cjs/index.cjs
CHANGED
|
@@ -25,6 +25,89 @@ let node_fs_promises = require("node:fs/promises");
|
|
|
25
25
|
node_fs_promises = __toESM(node_fs_promises, 1);
|
|
26
26
|
//#region ../webrun-files/dist/esm/index.js
|
|
27
27
|
/**
|
|
28
|
+
* The order every `FilesApi.list()` yields: paths compared by Unicode code
|
|
29
|
+
* point, which is the same as comparing their UTF-8 bytes — the order of
|
|
30
|
+
* SQLite's `BINARY` collation and of S3 key listings.
|
|
31
|
+
*
|
|
32
|
+
* Not JavaScript's `<`, which compares UTF-16 code units and so puts a
|
|
33
|
+
* character above U+FFFF (a surrogate pair) before U+E000–U+FFFF.
|
|
34
|
+
*/
|
|
35
|
+
function comparePaths(a, b) {
|
|
36
|
+
const length = Math.min(a.length, b.length);
|
|
37
|
+
for (let i = 0; i < length; i++) {
|
|
38
|
+
const x = a.charCodeAt(i);
|
|
39
|
+
const y = b.charCodeAt(i);
|
|
40
|
+
if (x === y) continue;
|
|
41
|
+
const xSurrogate = x >= 55296 && x <= 57343;
|
|
42
|
+
if (xSurrogate !== (y >= 55296 && y <= 57343)) return xSurrogate ? 1 : -1;
|
|
43
|
+
return x - y;
|
|
44
|
+
}
|
|
45
|
+
return a.length - b.length;
|
|
46
|
+
}
|
|
47
|
+
/** Whether a listing resuming after `after` can still yield anything under `dir`. */
|
|
48
|
+
function subtreeMayFollow(dir, after) {
|
|
49
|
+
return after === void 0 || comparePaths(after, `${dir === "/" ? "" : dir}0`) < 0;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* List a tree in path order for backends that read one directory at a time.
|
|
53
|
+
* `children(dir)` returns a directory's direct entries in any order.
|
|
54
|
+
*
|
|
55
|
+
* Pending entries sit in a min-heap keyed by path. A directory's children all
|
|
56
|
+
* sort after it, so pushing them when the directory is taken never yields out
|
|
57
|
+
* of order. Children are read only when the listing reaches their directory,
|
|
58
|
+
* and never for a directory whose whole subtree lies at or before `after`.
|
|
59
|
+
*/
|
|
60
|
+
async function* listInPathOrder(dir, children, options = {}) {
|
|
61
|
+
const { recursive = false, after } = options;
|
|
62
|
+
const heap = new PathHeap((entry) => entry.path);
|
|
63
|
+
if (!subtreeMayFollow(dir, after)) return;
|
|
64
|
+
for (const entry of await children(dir)) heap.push(entry);
|
|
65
|
+
for (let entry = heap.pop(); entry; entry = heap.pop()) {
|
|
66
|
+
if (after === void 0 || comparePaths(entry.path, after) > 0) yield entry;
|
|
67
|
+
if (recursive && entry.kind === "directory" && subtreeMayFollow(entry.path, after)) for (const child of await children(entry.path)) heap.push(child);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
/** A binary min-heap ordered by {@link comparePaths}. */
|
|
71
|
+
var PathHeap = class {
|
|
72
|
+
#items = [];
|
|
73
|
+
#key;
|
|
74
|
+
constructor(key) {
|
|
75
|
+
this.#key = key;
|
|
76
|
+
}
|
|
77
|
+
push(item) {
|
|
78
|
+
const items = this.#items;
|
|
79
|
+
items.push(item);
|
|
80
|
+
for (let i = items.length - 1; i > 0;) {
|
|
81
|
+
const parent = i - 1 >> 1;
|
|
82
|
+
if (this.#less(items[parent], items[i])) break;
|
|
83
|
+
[items[parent], items[i]] = [items[i], items[parent]];
|
|
84
|
+
i = parent;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
pop() {
|
|
88
|
+
const items = this.#items;
|
|
89
|
+
const top = items[0];
|
|
90
|
+
const last = items.pop();
|
|
91
|
+
if (items.length > 0 && last !== void 0) {
|
|
92
|
+
items[0] = last;
|
|
93
|
+
for (let i = 0;;) {
|
|
94
|
+
const left = 2 * i + 1;
|
|
95
|
+
const right = left + 1;
|
|
96
|
+
let smallest = i;
|
|
97
|
+
if (left < items.length && this.#less(items[left], items[smallest])) smallest = left;
|
|
98
|
+
if (right < items.length && this.#less(items[right], items[smallest])) smallest = right;
|
|
99
|
+
if (smallest === i) break;
|
|
100
|
+
[items[smallest], items[i]] = [items[i], items[smallest]];
|
|
101
|
+
i = smallest;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return top;
|
|
105
|
+
}
|
|
106
|
+
#less(a, b) {
|
|
107
|
+
return comparePaths(this.#key(a), this.#key(b)) < 0;
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
/**
|
|
28
111
|
* Path manipulation utilities for virtual filesystem paths.
|
|
29
112
|
* All paths use forward slashes and start with "/".
|
|
30
113
|
*/
|
|
@@ -123,36 +206,40 @@ var NodeFilesApi = class {
|
|
|
123
206
|
const realPath = this.resolvePath(path);
|
|
124
207
|
await node_fs_promises.mkdir(realPath, { recursive: true });
|
|
125
208
|
}
|
|
209
|
+
/** `readdir` order is unspecified, so the tree is walked in `comparePaths` order. */
|
|
126
210
|
async *list(path, options) {
|
|
127
|
-
|
|
128
|
-
|
|
211
|
+
yield* listInPathOrder(normalizePath(path), (dir) => this.readChildren(dir), options);
|
|
212
|
+
}
|
|
213
|
+
/** A directory's direct entries, in `readdir` order; none if it cannot be read. */
|
|
214
|
+
async readChildren(dir) {
|
|
215
|
+
let entries;
|
|
129
216
|
try {
|
|
130
|
-
|
|
131
|
-
for (const entry of entries) {
|
|
132
|
-
const entryPath = joinPath(normalizedPath, entry.name);
|
|
133
|
-
const fullPath = this.resolvePath(entryPath);
|
|
134
|
-
let stat;
|
|
135
|
-
try {
|
|
136
|
-
stat = await node_fs_promises.stat(fullPath);
|
|
137
|
-
} catch {
|
|
138
|
-
continue;
|
|
139
|
-
}
|
|
140
|
-
yield entry.isDirectory() ? {
|
|
141
|
-
name: entry.name,
|
|
142
|
-
path: entryPath,
|
|
143
|
-
kind: "directory"
|
|
144
|
-
} : {
|
|
145
|
-
name: entry.name,
|
|
146
|
-
path: entryPath,
|
|
147
|
-
kind: "file",
|
|
148
|
-
size: stat.size,
|
|
149
|
-
lastModified: stat.mtimeMs
|
|
150
|
-
};
|
|
151
|
-
if (options?.recursive && entry.isDirectory()) yield* this.list(entryPath, options);
|
|
152
|
-
}
|
|
217
|
+
entries = await node_fs_promises.readdir(this.resolvePath(dir), { withFileTypes: true });
|
|
153
218
|
} catch {
|
|
154
|
-
return;
|
|
219
|
+
return [];
|
|
220
|
+
}
|
|
221
|
+
const infos = [];
|
|
222
|
+
for (const entry of entries) {
|
|
223
|
+
const entryPath = joinPath(dir, entry.name);
|
|
224
|
+
let stat;
|
|
225
|
+
try {
|
|
226
|
+
stat = await node_fs_promises.stat(this.resolvePath(entryPath));
|
|
227
|
+
} catch {
|
|
228
|
+
continue;
|
|
229
|
+
}
|
|
230
|
+
infos.push(entry.isDirectory() ? {
|
|
231
|
+
name: entry.name,
|
|
232
|
+
path: entryPath,
|
|
233
|
+
kind: "directory"
|
|
234
|
+
} : {
|
|
235
|
+
name: entry.name,
|
|
236
|
+
path: entryPath,
|
|
237
|
+
kind: "file",
|
|
238
|
+
size: stat.size,
|
|
239
|
+
lastModified: stat.mtimeMs
|
|
240
|
+
});
|
|
155
241
|
}
|
|
242
|
+
return infos;
|
|
156
243
|
}
|
|
157
244
|
async stats(path) {
|
|
158
245
|
const realPath = this.resolvePath(path);
|
package/dist/esm/index.js
CHANGED
|
@@ -1,6 +1,89 @@
|
|
|
1
1
|
import * as fs from "node:fs/promises";
|
|
2
2
|
//#region ../webrun-files/dist/esm/index.js
|
|
3
3
|
/**
|
|
4
|
+
* The order every `FilesApi.list()` yields: paths compared by Unicode code
|
|
5
|
+
* point, which is the same as comparing their UTF-8 bytes — the order of
|
|
6
|
+
* SQLite's `BINARY` collation and of S3 key listings.
|
|
7
|
+
*
|
|
8
|
+
* Not JavaScript's `<`, which compares UTF-16 code units and so puts a
|
|
9
|
+
* character above U+FFFF (a surrogate pair) before U+E000–U+FFFF.
|
|
10
|
+
*/
|
|
11
|
+
function comparePaths(a, b) {
|
|
12
|
+
const length = Math.min(a.length, b.length);
|
|
13
|
+
for (let i = 0; i < length; i++) {
|
|
14
|
+
const x = a.charCodeAt(i);
|
|
15
|
+
const y = b.charCodeAt(i);
|
|
16
|
+
if (x === y) continue;
|
|
17
|
+
const xSurrogate = x >= 55296 && x <= 57343;
|
|
18
|
+
if (xSurrogate !== (y >= 55296 && y <= 57343)) return xSurrogate ? 1 : -1;
|
|
19
|
+
return x - y;
|
|
20
|
+
}
|
|
21
|
+
return a.length - b.length;
|
|
22
|
+
}
|
|
23
|
+
/** Whether a listing resuming after `after` can still yield anything under `dir`. */
|
|
24
|
+
function subtreeMayFollow(dir, after) {
|
|
25
|
+
return after === void 0 || comparePaths(after, `${dir === "/" ? "" : dir}0`) < 0;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* List a tree in path order for backends that read one directory at a time.
|
|
29
|
+
* `children(dir)` returns a directory's direct entries in any order.
|
|
30
|
+
*
|
|
31
|
+
* Pending entries sit in a min-heap keyed by path. A directory's children all
|
|
32
|
+
* sort after it, so pushing them when the directory is taken never yields out
|
|
33
|
+
* of order. Children are read only when the listing reaches their directory,
|
|
34
|
+
* and never for a directory whose whole subtree lies at or before `after`.
|
|
35
|
+
*/
|
|
36
|
+
async function* listInPathOrder(dir, children, options = {}) {
|
|
37
|
+
const { recursive = false, after } = options;
|
|
38
|
+
const heap = new PathHeap((entry) => entry.path);
|
|
39
|
+
if (!subtreeMayFollow(dir, after)) return;
|
|
40
|
+
for (const entry of await children(dir)) heap.push(entry);
|
|
41
|
+
for (let entry = heap.pop(); entry; entry = heap.pop()) {
|
|
42
|
+
if (after === void 0 || comparePaths(entry.path, after) > 0) yield entry;
|
|
43
|
+
if (recursive && entry.kind === "directory" && subtreeMayFollow(entry.path, after)) for (const child of await children(entry.path)) heap.push(child);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
/** A binary min-heap ordered by {@link comparePaths}. */
|
|
47
|
+
var PathHeap = class {
|
|
48
|
+
#items = [];
|
|
49
|
+
#key;
|
|
50
|
+
constructor(key) {
|
|
51
|
+
this.#key = key;
|
|
52
|
+
}
|
|
53
|
+
push(item) {
|
|
54
|
+
const items = this.#items;
|
|
55
|
+
items.push(item);
|
|
56
|
+
for (let i = items.length - 1; i > 0;) {
|
|
57
|
+
const parent = i - 1 >> 1;
|
|
58
|
+
if (this.#less(items[parent], items[i])) break;
|
|
59
|
+
[items[parent], items[i]] = [items[i], items[parent]];
|
|
60
|
+
i = parent;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
pop() {
|
|
64
|
+
const items = this.#items;
|
|
65
|
+
const top = items[0];
|
|
66
|
+
const last = items.pop();
|
|
67
|
+
if (items.length > 0 && last !== void 0) {
|
|
68
|
+
items[0] = last;
|
|
69
|
+
for (let i = 0;;) {
|
|
70
|
+
const left = 2 * i + 1;
|
|
71
|
+
const right = left + 1;
|
|
72
|
+
let smallest = i;
|
|
73
|
+
if (left < items.length && this.#less(items[left], items[smallest])) smallest = left;
|
|
74
|
+
if (right < items.length && this.#less(items[right], items[smallest])) smallest = right;
|
|
75
|
+
if (smallest === i) break;
|
|
76
|
+
[items[smallest], items[i]] = [items[i], items[smallest]];
|
|
77
|
+
i = smallest;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return top;
|
|
81
|
+
}
|
|
82
|
+
#less(a, b) {
|
|
83
|
+
return comparePaths(this.#key(a), this.#key(b)) < 0;
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
/**
|
|
4
87
|
* Path manipulation utilities for virtual filesystem paths.
|
|
5
88
|
* All paths use forward slashes and start with "/".
|
|
6
89
|
*/
|
|
@@ -99,36 +182,40 @@ var NodeFilesApi = class {
|
|
|
99
182
|
const realPath = this.resolvePath(path);
|
|
100
183
|
await fs.mkdir(realPath, { recursive: true });
|
|
101
184
|
}
|
|
185
|
+
/** `readdir` order is unspecified, so the tree is walked in `comparePaths` order. */
|
|
102
186
|
async *list(path, options) {
|
|
103
|
-
|
|
104
|
-
|
|
187
|
+
yield* listInPathOrder(normalizePath(path), (dir) => this.readChildren(dir), options);
|
|
188
|
+
}
|
|
189
|
+
/** A directory's direct entries, in `readdir` order; none if it cannot be read. */
|
|
190
|
+
async readChildren(dir) {
|
|
191
|
+
let entries;
|
|
105
192
|
try {
|
|
106
|
-
|
|
107
|
-
for (const entry of entries) {
|
|
108
|
-
const entryPath = joinPath(normalizedPath, entry.name);
|
|
109
|
-
const fullPath = this.resolvePath(entryPath);
|
|
110
|
-
let stat;
|
|
111
|
-
try {
|
|
112
|
-
stat = await fs.stat(fullPath);
|
|
113
|
-
} catch {
|
|
114
|
-
continue;
|
|
115
|
-
}
|
|
116
|
-
yield entry.isDirectory() ? {
|
|
117
|
-
name: entry.name,
|
|
118
|
-
path: entryPath,
|
|
119
|
-
kind: "directory"
|
|
120
|
-
} : {
|
|
121
|
-
name: entry.name,
|
|
122
|
-
path: entryPath,
|
|
123
|
-
kind: "file",
|
|
124
|
-
size: stat.size,
|
|
125
|
-
lastModified: stat.mtimeMs
|
|
126
|
-
};
|
|
127
|
-
if (options?.recursive && entry.isDirectory()) yield* this.list(entryPath, options);
|
|
128
|
-
}
|
|
193
|
+
entries = await fs.readdir(this.resolvePath(dir), { withFileTypes: true });
|
|
129
194
|
} catch {
|
|
130
|
-
return;
|
|
195
|
+
return [];
|
|
196
|
+
}
|
|
197
|
+
const infos = [];
|
|
198
|
+
for (const entry of entries) {
|
|
199
|
+
const entryPath = joinPath(dir, entry.name);
|
|
200
|
+
let stat;
|
|
201
|
+
try {
|
|
202
|
+
stat = await fs.stat(this.resolvePath(entryPath));
|
|
203
|
+
} catch {
|
|
204
|
+
continue;
|
|
205
|
+
}
|
|
206
|
+
infos.push(entry.isDirectory() ? {
|
|
207
|
+
name: entry.name,
|
|
208
|
+
path: entryPath,
|
|
209
|
+
kind: "directory"
|
|
210
|
+
} : {
|
|
211
|
+
name: entry.name,
|
|
212
|
+
path: entryPath,
|
|
213
|
+
kind: "file",
|
|
214
|
+
size: stat.size,
|
|
215
|
+
lastModified: stat.mtimeMs
|
|
216
|
+
});
|
|
131
217
|
}
|
|
218
|
+
return infos;
|
|
132
219
|
}
|
|
133
220
|
async stats(path) {
|
|
134
221
|
const realPath = this.resolvePath(path);
|
package/dist/node-files-api.d.ts
CHANGED
|
@@ -17,7 +17,10 @@ export declare class NodeFilesApi implements FilesApi {
|
|
|
17
17
|
read(path: string, options?: ReadOptions): AsyncIterable<Uint8Array>;
|
|
18
18
|
write(path: string, content: Iterable<Uint8Array> | AsyncIterable<Uint8Array>): Promise<void>;
|
|
19
19
|
mkdir(path: string): Promise<void>;
|
|
20
|
+
/** `readdir` order is unspecified, so the tree is walked in `comparePaths` order. */
|
|
20
21
|
list(path: string, options?: ListOptions): AsyncIterable<FileInfo>;
|
|
22
|
+
/** A directory's direct entries, in `readdir` order; none if it cannot be read. */
|
|
23
|
+
private readChildren;
|
|
21
24
|
stats(path: string): Promise<FileStats | undefined>;
|
|
22
25
|
exists(path: string): Promise<boolean>;
|
|
23
26
|
remove(path: string): Promise<boolean>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"node-files-api.d.ts","sourceRoot":"","sources":["../src/node-files-api.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,QAAQ,EACR,SAAS,EACT,QAAQ,EACR,WAAW,EACX,WAAW,EACZ,MAAM,2BAA2B,CAAC;AAGnC,MAAM,WAAW,mBAAmB;IAClC;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;;GAGG;AACH,qBAAa,YAAa,YAAW,QAAQ;IAC3C,OAAO,CAAC,OAAO,CAAS;IAExB,YAAY,OAAO,CAAC,EAAE,mBAAmB,EAExC;IAED,OAAO,CAAC,WAAW;IAKZ,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,WAAW,GAAG,aAAa,CAAC,UAAU,CAAC,CAyC1E;IAEK,KAAK,CACT,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,QAAQ,CAAC,UAAU,CAAC,GAAG,aAAa,CAAC,UAAU,CAAC,GACxD,OAAO,CAAC,IAAI,CAAC,CAuBf;IAEK,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAGvC;
|
|
1
|
+
{"version":3,"file":"node-files-api.d.ts","sourceRoot":"","sources":["../src/node-files-api.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,QAAQ,EACR,SAAS,EACT,QAAQ,EACR,WAAW,EACX,WAAW,EACZ,MAAM,2BAA2B,CAAC;AAGnC,MAAM,WAAW,mBAAmB;IAClC;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;;GAGG;AACH,qBAAa,YAAa,YAAW,QAAQ;IAC3C,OAAO,CAAC,OAAO,CAAS;IAExB,YAAY,OAAO,CAAC,EAAE,mBAAmB,EAExC;IAED,OAAO,CAAC,WAAW;IAKZ,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,WAAW,GAAG,aAAa,CAAC,UAAU,CAAC,CAyC1E;IAEK,KAAK,CACT,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,QAAQ,CAAC,UAAU,CAAC,GAAG,aAAa,CAAC,UAAU,CAAC,GACxD,OAAO,CAAC,IAAI,CAAC,CAuBf;IAEK,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAGvC;IAED,qFAAqF;IAC9E,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,WAAW,GAAG,aAAa,CAAC,QAAQ,CAAC,CAExE;IAED,mFAAmF;YACrE,YAAY;IAgCpB,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,GAAG,SAAS,CAAC,CAkBxD;IAEK,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAS3C;IAEK,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAc3C;IAEK,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAmB3D;IAEK,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAmB3D;CACF"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@statewalker/webrun-files-node",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "Node.js FilesApi implementation",
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
"src"
|
|
30
30
|
],
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"@statewalker/webrun-files": "0.
|
|
32
|
+
"@statewalker/webrun-files": "0.10.0"
|
|
33
33
|
},
|
|
34
34
|
"devDependencies": {
|
|
35
35
|
"@types/node": "^26.2.0",
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
"rolldown": "^1.2.4",
|
|
38
38
|
"typescript": "^7.0.2",
|
|
39
39
|
"vitest": "^4.1.10",
|
|
40
|
-
"@statewalker/webrun-files-tests": "
|
|
40
|
+
"@statewalker/webrun-files-tests": "3.0.0"
|
|
41
41
|
},
|
|
42
42
|
"sideEffects": false,
|
|
43
43
|
"publishConfig": {
|
package/src/node-files-api.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Stats } from "node:fs";
|
|
1
|
+
import type { Dirent, Stats } from "node:fs";
|
|
2
2
|
import * as fs from "node:fs/promises";
|
|
3
3
|
import type {
|
|
4
4
|
FileInfo,
|
|
@@ -7,7 +7,7 @@ import type {
|
|
|
7
7
|
ListOptions,
|
|
8
8
|
ReadOptions,
|
|
9
9
|
} from "@statewalker/webrun-files";
|
|
10
|
-
import { dirname, joinPath, normalizePath } from "@statewalker/webrun-files";
|
|
10
|
+
import { dirname, joinPath, listInPathOrder, normalizePath } from "@statewalker/webrun-files";
|
|
11
11
|
|
|
12
12
|
export interface NodeFilesApiOptions {
|
|
13
13
|
/**
|
|
@@ -109,25 +109,31 @@ export class NodeFilesApi implements FilesApi {
|
|
|
109
109
|
await fs.mkdir(realPath, { recursive: true });
|
|
110
110
|
}
|
|
111
111
|
|
|
112
|
+
/** `readdir` order is unspecified, so the tree is walked in `comparePaths` order. */
|
|
112
113
|
async *list(path: string, options?: ListOptions): AsyncIterable<FileInfo> {
|
|
113
|
-
|
|
114
|
-
|
|
114
|
+
yield* listInPathOrder(normalizePath(path), (dir) => this.readChildren(dir), options);
|
|
115
|
+
}
|
|
115
116
|
|
|
117
|
+
/** A directory's direct entries, in `readdir` order; none if it cannot be read. */
|
|
118
|
+
private async readChildren(dir: string): Promise<FileInfo[]> {
|
|
119
|
+
let entries: Dirent[];
|
|
116
120
|
try {
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
121
|
+
entries = await fs.readdir(this.resolvePath(dir), { withFileTypes: true });
|
|
122
|
+
} catch {
|
|
123
|
+
// Directory doesn't exist or can't be read
|
|
124
|
+
return [];
|
|
125
|
+
}
|
|
126
|
+
const infos: FileInfo[] = [];
|
|
127
|
+
for (const entry of entries) {
|
|
128
|
+
const entryPath = joinPath(dir, entry.name);
|
|
129
|
+
let stat: Stats;
|
|
130
|
+
try {
|
|
131
|
+
stat = await fs.stat(this.resolvePath(entryPath));
|
|
132
|
+
} catch {
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
infos.push(
|
|
136
|
+
entry.isDirectory()
|
|
131
137
|
? { name: entry.name, path: entryPath, kind: "directory" }
|
|
132
138
|
: {
|
|
133
139
|
name: entry.name,
|
|
@@ -135,18 +141,10 @@ export class NodeFilesApi implements FilesApi {
|
|
|
135
141
|
kind: "file",
|
|
136
142
|
size: stat.size,
|
|
137
143
|
lastModified: stat.mtimeMs,
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
yield info;
|
|
141
|
-
|
|
142
|
-
if (options?.recursive && entry.isDirectory()) {
|
|
143
|
-
yield* this.list(entryPath, options);
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
} catch {
|
|
147
|
-
// Directory doesn't exist or can't be read
|
|
148
|
-
return;
|
|
144
|
+
},
|
|
145
|
+
);
|
|
149
146
|
}
|
|
147
|
+
return infos;
|
|
150
148
|
}
|
|
151
149
|
|
|
152
150
|
async stats(path: string): Promise<FileStats | undefined> {
|