@ttsc/wasm 0.30.3 → 0.31.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 +3 -3
- package/build/build-wasm.cjs +5 -4
- package/build/go-build-cache.cjs +347 -0
- package/build/pack-prepare.cjs +3 -2
- package/cmd/ttsc-wasm/main.go +22 -10
- package/cmd/ttsc-wasm/main_wasm.go +2 -2
- package/dist/.ttsc-wasm-build.json +138 -58
- package/dist/go.mod +7 -5
- package/dist/ttsc.wasm +2 -2
- package/go.mod +7 -5
- package/go.sum +10 -10
- package/host/api.go +34 -41
- package/host/doc.go +7 -6
- package/host/expose.go +42 -0
- package/host/fountain.go +192 -22
- package/host/host.go +10 -25
- package/host/host_native.go +3 -5
- package/host/invoke.go +133 -0
- package/host/plugin.go +37 -72
- package/host/result.go +108 -0
- package/lib/src/BootTtscWorkerTerminationError.d.ts +20 -0
- package/lib/src/BootTtscWorkerTerminationError.js +20 -0
- package/lib/src/BootTtscWorkerTerminationError.js.map +1 -1
- package/lib/src/MemFSError.d.ts +21 -2
- package/lib/src/MemFSError.js +25 -2
- package/lib/src/MemFSError.js.map +1 -1
- package/lib/src/bootTtsc.d.ts +49 -2
- package/lib/src/bootTtsc.js +89 -23
- package/lib/src/bootTtsc.js.map +1 -1
- package/lib/src/createMemFS.d.ts +45 -0
- package/lib/src/createMemFS.js +269 -66
- package/lib/src/createMemFS.js.map +1 -1
- package/lib/src/parseResult.d.ts +19 -0
- package/lib/src/parseResult.js +19 -0
- package/lib/src/parseResult.js.map +1 -1
- package/lib/src/structures/IBootResult.d.ts +20 -2
- package/lib/src/structures/IBootTtscOptions.d.ts +28 -6
- package/lib/src/structures/IFileStats.d.ts +51 -0
- package/lib/src/structures/IMemFSHost.d.ts +89 -0
- package/lib/src/structures/ITtscApi.d.ts +154 -9
- package/lib/src/structures/ITtscBuildOpts.d.ts +18 -1
- package/lib/src/structures/ITtscCompileResult.d.ts +20 -2
- package/lib/src/structures/ITtscDiagnostic.d.ts +25 -1
- package/lib/src/structures/ITtscFileQuery.d.ts +19 -1
- package/lib/src/structures/ITtscFountainDiagnosticsResult.d.ts +20 -1
- package/lib/src/structures/ITtscNodeAtPositionResult.d.ts +18 -1
- package/lib/src/structures/ITtscNodeInfo.d.ts +23 -2
- package/lib/src/structures/ITtscPluginOpts.d.ts +24 -3
- package/lib/src/structures/ITtscPositionQuery.d.ts +21 -4
- package/lib/src/structures/ITtscReleaseSnapshotResult.d.ts +22 -2
- package/lib/src/structures/ITtscResult.d.ts +31 -5
- package/lib/src/structures/ITtscSnapshotHandle.d.ts +19 -1
- package/lib/src/structures/ITtscSnapshotResult.d.ts +22 -1
- package/lib/src/structures/ITtscSnapshotsResult.d.ts +19 -1
- package/lib/src/structures/ITtscSourceFileTextResult.d.ts +19 -1
- package/lib/src/structures/ITtscSourceFilesResult.d.ts +23 -2
- package/lib/src/structures/ITtscSymbolAtPositionResult.d.ts +18 -1
- package/lib/src/structures/ITtscSymbolDeclaration.d.ts +25 -2
- package/lib/src/structures/ITtscSymbolInfo.d.ts +19 -1
- package/lib/src/structures/ITtscTransformResult.d.ts +21 -3
- package/lib/src/structures/ITtscTypeAtPositionResult.d.ts +18 -1
- package/lib/src/structures/ITtscTypeInfo.d.ts +19 -1
- package/lib/src/structures/ITtscVersion.d.ts +28 -1
- package/lib/src/structures/IWasmExecFS.d.ts +310 -0
- package/package.json +4 -2
- package/shim-vendor/shim/ast/enums_gen.go +4 -2
- package/shim-vendor/shim/ast/go.mod +5 -5
- package/shim-vendor/shim/ast/go.sum +10 -10
- package/shim-vendor/shim/ast/lint.go +1088 -20
- package/shim-vendor/shim/ast/parent.go +38 -22
- package/shim-vendor/shim/ast/safe_text.go +48 -26
- package/shim-vendor/shim/ast/shim.go +1307 -20
- package/shim-vendor/shim/ast/test/next_jsdoc_comment_location_reaches_variable_statement_test.go +51 -0
- package/shim-vendor/shim/ast/test/node_text_joins_multi_hop_qualified_name_test.go +24 -9
- package/shim-vendor/shim/ast/test/node_text_joins_one_hop_qualified_name_test.go +9 -5
- package/shim-vendor/shim/ast/test/node_text_returns_empty_for_nil_test.go +13 -5
- package/shim-vendor/shim/ast/test/node_text_returns_identifier_text_test.go +12 -5
- package/shim-vendor/shim/ast/test/node_text_skips_empty_qualified_left_test.go +35 -5
- package/shim-vendor/shim/astnav/go.mod +5 -5
- package/shim-vendor/shim/astnav/go.sum +12 -10
- package/shim-vendor/shim/astnav/shim.go +9 -0
- package/shim-vendor/shim/bundled/go.mod +7 -1
- package/shim-vendor/shim/bundled/go.sum +12 -0
- package/shim-vendor/shim/checker/enums_gen.go +4 -2
- package/shim-vendor/shim/checker/extra-shim.json +6 -0
- package/shim-vendor/shim/checker/go.mod +5 -5
- package/shim-vendor/shim/checker/go.sum +10 -10
- package/shim-vendor/shim/checker/node_builder_fields_gen.go +19 -0
- package/shim-vendor/shim/checker/shim.go +827 -114
- package/shim-vendor/shim/compiler/emit_sourcemap.go +68 -29
- package/shim-vendor/shim/compiler/emitassemble.go +36 -2
- package/shim-vendor/shim/compiler/emithook.go +13 -3
- package/shim-vendor/shim/compiler/go.mod +5 -5
- package/shim-vendor/shim/compiler/go.sum +10 -10
- package/shim-vendor/shim/compiler/incremental.go +56 -20
- package/shim-vendor/shim/compiler/resolution.go +92 -9
- package/shim-vendor/shim/core/enums_gen.go +2 -2
- package/shim-vendor/shim/core/go.mod +6 -1
- package/shim-vendor/shim/core/go.sum +10 -0
- package/shim-vendor/shim/core/modulekind.go +9 -0
- package/shim-vendor/shim/core/shim.go +157 -6
- package/shim-vendor/shim/diagnosticwriter/go.mod +10 -1
- package/shim-vendor/shim/diagnosticwriter/go.sum +20 -0
- package/shim-vendor/shim/diagnosticwriter/lint.go +188 -10
- package/shim-vendor/shim/diagnosticwriter/shim.go +14 -0
- package/shim-vendor/shim/lsp/go.mod +5 -5
- package/shim-vendor/shim/lsp/go.sum +10 -10
- package/shim-vendor/shim/lsp/shim.go +78 -2
- package/shim-vendor/shim/parser/go.mod +10 -1
- package/shim-vendor/shim/parser/go.sum +22 -0
- package/shim-vendor/shim/printer/emithost.go +46 -1
- package/shim-vendor/shim/printer/go.mod +5 -5
- package/shim-vendor/shim/printer/go.sum +10 -10
- package/shim-vendor/shim/printer/shim.go +114 -7
- package/shim-vendor/shim/printer/sourcemap.go +55 -0
- package/shim-vendor/shim/scanner/go.mod +10 -1
- package/shim-vendor/shim/scanner/go.sum +20 -0
- package/shim-vendor/shim/scanner/regexp.go +19 -4
- package/shim-vendor/shim/scanner/shim.go +3 -0
- package/shim-vendor/shim/stringutil/extra-shim.json +1 -0
- package/shim-vendor/shim/stringutil/go.mod +5 -0
- package/shim-vendor/shim/stringutil/go.sum +2 -0
- package/shim-vendor/shim/stringutil/shim.go +119 -0
- package/shim-vendor/shim/transformers/go.mod +5 -5
- package/shim-vendor/shim/transformers/go.sum +12 -10
- package/shim-vendor/shim/transformers/shim.go +12 -3
- package/shim-vendor/shim/tsoptions/command_line_raw.go +44 -0
- package/shim-vendor/shim/tsoptions/go.mod +10 -1
- package/shim-vendor/shim/tsoptions/go.sum +22 -0
- package/shim-vendor/shim/tsoptions/shim.go +1 -1
- package/shim-vendor/shim/tspath/go.mod +1 -1
- package/shim-vendor/shim/tspath/go.sum +6 -0
- package/shim-vendor/shim/vfs/cachedvfs/go.mod +3 -1
- package/shim-vendor/shim/vfs/cachedvfs/go.sum +12 -0
- package/shim-vendor/shim/vfs/decode.go +26 -0
- package/shim-vendor/shim/vfs/decode_test.go +42 -0
- package/shim-vendor/shim/vfs/go.mod +7 -1
- package/shim-vendor/shim/vfs/go.sum +12 -0
- package/shim-vendor/shim/vfs/osvfs/go.mod +7 -1
- package/shim-vendor/shim/vfs/osvfs/go.sum +12 -0
- package/src/BootTtscWorkerTerminationError.ts +22 -0
- package/src/MemFSError.ts +29 -2
- package/src/bootTtsc.ts +104 -28
- package/src/createMemFS.ts +291 -64
- package/src/parseResult.ts +19 -0
- package/src/structures/IBootResult.ts +21 -2
- package/src/structures/IBootTtscOptions.ts +32 -6
- package/src/structures/IFileStats.ts +65 -0
- package/src/structures/IMemFSHost.ts +97 -0
- package/src/structures/ITtscApi.ts +154 -9
- package/src/structures/ITtscBuildOpts.ts +19 -1
- package/src/structures/ITtscCompileResult.ts +21 -2
- package/src/structures/ITtscDiagnostic.ts +32 -1
- package/src/structures/ITtscFileQuery.ts +19 -1
- package/src/structures/ITtscFountainDiagnosticsResult.ts +20 -1
- package/src/structures/ITtscNodeAtPositionResult.ts +18 -1
- package/src/structures/ITtscNodeInfo.ts +27 -2
- package/src/structures/ITtscPluginOpts.ts +28 -3
- package/src/structures/ITtscPositionQuery.ts +21 -4
- package/src/structures/ITtscReleaseSnapshotResult.ts +22 -2
- package/src/structures/ITtscResult.ts +34 -5
- package/src/structures/ITtscSnapshotHandle.ts +19 -1
- package/src/structures/ITtscSnapshotResult.ts +22 -1
- package/src/structures/ITtscSnapshotsResult.ts +19 -1
- package/src/structures/ITtscSourceFileTextResult.ts +19 -1
- package/src/structures/ITtscSourceFilesResult.ts +23 -2
- package/src/structures/ITtscSymbolAtPositionResult.ts +18 -1
- package/src/structures/ITtscSymbolDeclaration.ts +27 -2
- package/src/structures/ITtscSymbolInfo.ts +23 -1
- package/src/structures/ITtscTransformResult.ts +22 -3
- package/src/structures/ITtscTypeAtPositionResult.ts +18 -1
- package/src/structures/ITtscTypeInfo.ts +20 -1
- package/src/structures/ITtscVersion.ts +33 -1
- package/src/structures/IWasmExecFS.ts +336 -0
package/lib/src/createMemFS.js
CHANGED
|
@@ -61,12 +61,110 @@ function normalize(p) {
|
|
|
61
61
|
* descendants. An operation that cannot satisfy that throws (`writeFile`,
|
|
62
62
|
* `mkdirp`) or reports a POSIX error through its callback, having changed no
|
|
63
63
|
* node, byte, or descriptor.
|
|
64
|
+
*
|
|
65
|
+
* @evidence contracts/common.md#principled-implementation
|
|
66
|
+
* The owned Node-shaped fs bridge uses maps for virtual node/descriptor
|
|
67
|
+
* identity and preserves opened nodes independently from paths. The Go js/wasm
|
|
68
|
+
* callback interface is the authority; unsupported link and metadata mutation
|
|
69
|
+
* capabilities are explicit in IWasmExecFS.
|
|
70
|
+
* @evidence contracts/common.md#clear-and-simple-design
|
|
71
|
+
* One closure owns the tree, indexes and descriptor identities. Shared helpers
|
|
72
|
+
* centralize path validation, insertion/removal and byte mutation; separate
|
|
73
|
+
* stream captures and pipe queues reflect non-seekable lifetimes rather than
|
|
74
|
+
* adding filesystem nodes that would blur their operation contracts.
|
|
75
|
+
* @evidence contracts/common.md#prohibited-implementation-shortcuts
|
|
76
|
+
* Virtual flag/errno values are bridge constants, not native-host guesses.
|
|
77
|
+
* The implementation owns its maps rather than patching foreign filesystem
|
|
78
|
+
* methods. Directory indexes, geometric file growth and linked pipe queues
|
|
79
|
+
* correct repeated global scans, full-buffer copies and queue shifts at their
|
|
80
|
+
* owners, without size exceptions or benchmark-specific paths.
|
|
81
|
+
* @evidence contracts/common.md#meaningful-documentation
|
|
82
|
+
* Native paragraphs explain installation, copying and tree ownership; helper
|
|
83
|
+
* and interface comments state cost and unsupported capabilities. This follows
|
|
84
|
+
* the documentation skill's paragraph, units and rationale guidance.
|
|
85
|
+
* @evidence contracts/performance.md#efficient-algorithms
|
|
86
|
+
* Directory lookup visits D direct entries and sorts them in O(D log D),
|
|
87
|
+
* emptiness uses the indexed child count, and rename visits only S subtree
|
|
88
|
+
* nodes plus F open descriptors. Growing file writes amortize copying through
|
|
89
|
+
* geometric capacity; pipe enqueue/dequeue change linked endpoints in O(1).
|
|
90
|
+
* Path normalization costs O(P) path characters; ancestor validation builds
|
|
91
|
+
* d prefixes with total length K, costing O(K + d) time and temporary space
|
|
92
|
+
* under expected constant-time map access after hashing each prefix. Output fragments
|
|
93
|
+
* append without flattening prior text; reading a dirty B-byte capture costs
|
|
94
|
+
* O(B), while an unchanged capture read reuses its materialized string.
|
|
95
|
+
* @evidence contracts/performance.md#reuse-equivalent-work
|
|
96
|
+
* Child membership is maintained once by node insertion/deletion rather than
|
|
97
|
+
* reconstructed for every listing. Text decoding is cached on node identity
|
|
98
|
+
* and invalidated by every content mutation. Open descriptors share their
|
|
99
|
+
* actual inode; public byte reads still copy to preserve caller isolation.
|
|
100
|
+
* Each output stream keeps its own incremental UTF-8 decoder, so a split
|
|
101
|
+
* character is decoded once and capture reads reuse the materialized string
|
|
102
|
+
* until another fragment arrives.
|
|
103
|
+
* @evidence contracts/performance.md#bound-retention-and-release-resources
|
|
104
|
+
* Each host owns its tree, indexes, captures and descriptors. Unlink/rmdir
|
|
105
|
+
* remove tree entries, close releases descriptors, and consumed pipe entries
|
|
106
|
+
* are detached. File write capacity stays at most twice logical length; truncate
|
|
107
|
+
* and replacement release spare storage. Total input and captured output have
|
|
108
|
+
* no fixed bound, so the caller owns deletion, resetStdio and host lifetime.
|
|
64
109
|
*/
|
|
65
110
|
function createMemFS() {
|
|
66
111
|
const nodes = new Map();
|
|
112
|
+
const children = new Map([["/", new Set()]]);
|
|
67
113
|
nodes.set("/", { kind: "dir", data: new Uint8Array(), mtimeMs: Date.now() });
|
|
68
|
-
|
|
69
|
-
|
|
114
|
+
/** Keep parent membership and directory indexes with the owned node tree. */
|
|
115
|
+
function setNode(p, node) {
|
|
116
|
+
nodes.set(p, node);
|
|
117
|
+
children.get(parentDir(p)).add(p);
|
|
118
|
+
if (node.kind === "dir" && !children.has(p))
|
|
119
|
+
children.set(p, new Set());
|
|
120
|
+
}
|
|
121
|
+
/** Remove a validated node and its index; open descriptors retain the node. */
|
|
122
|
+
function deleteNode(p) {
|
|
123
|
+
nodes.delete(p);
|
|
124
|
+
children.get(parentDir(p)).delete(p);
|
|
125
|
+
children.delete(p);
|
|
126
|
+
}
|
|
127
|
+
/** Decode stream fragments incrementally and materialize capture text on read. */
|
|
128
|
+
function createCapture() {
|
|
129
|
+
let streamDecoder = new TextDecoder();
|
|
130
|
+
let chunks = [];
|
|
131
|
+
let cached = "";
|
|
132
|
+
let dirty = false;
|
|
133
|
+
const append = (text) => {
|
|
134
|
+
if (text.length > 0) {
|
|
135
|
+
chunks.push(text);
|
|
136
|
+
cached = "";
|
|
137
|
+
dirty = true;
|
|
138
|
+
}
|
|
139
|
+
return text;
|
|
140
|
+
};
|
|
141
|
+
return {
|
|
142
|
+
output: {
|
|
143
|
+
get buffer() {
|
|
144
|
+
if (dirty) {
|
|
145
|
+
cached = chunks.join("");
|
|
146
|
+
chunks = cached.length > 0 ? [cached] : [];
|
|
147
|
+
dirty = false;
|
|
148
|
+
}
|
|
149
|
+
return cached;
|
|
150
|
+
},
|
|
151
|
+
set buffer(value) {
|
|
152
|
+
streamDecoder = new TextDecoder();
|
|
153
|
+
chunks = value.length > 0 ? [value] : [];
|
|
154
|
+
cached = value;
|
|
155
|
+
dirty = false;
|
|
156
|
+
},
|
|
157
|
+
},
|
|
158
|
+
write(bytes) {
|
|
159
|
+
return append(streamDecoder.decode(bytes, { stream: true }));
|
|
160
|
+
},
|
|
161
|
+
flush() {
|
|
162
|
+
append(streamDecoder.decode());
|
|
163
|
+
},
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
const stdout = createCapture();
|
|
167
|
+
const stderr = createCapture();
|
|
70
168
|
const fdTable = new Map();
|
|
71
169
|
let nextFd = 100;
|
|
72
170
|
// Reserve 1/2 for stdout/stderr writeSync routing.
|
|
@@ -86,6 +184,23 @@ function createMemFS() {
|
|
|
86
184
|
append: true,
|
|
87
185
|
isStderr: true,
|
|
88
186
|
});
|
|
187
|
+
function enqueue(queue, value) {
|
|
188
|
+
const entry = { value };
|
|
189
|
+
if (queue.tail)
|
|
190
|
+
queue.tail.next = entry;
|
|
191
|
+
else
|
|
192
|
+
queue.head = entry;
|
|
193
|
+
queue.tail = entry;
|
|
194
|
+
}
|
|
195
|
+
function dequeue(queue) {
|
|
196
|
+
const entry = queue.head;
|
|
197
|
+
if (!entry)
|
|
198
|
+
return undefined;
|
|
199
|
+
queue.head = entry.next;
|
|
200
|
+
if (!queue.head)
|
|
201
|
+
queue.tail = undefined;
|
|
202
|
+
return entry.value;
|
|
203
|
+
}
|
|
89
204
|
const pipes = new Map();
|
|
90
205
|
/**
|
|
91
206
|
* Drain buffered pipe data into `buffer[offset..offset+length]`. Returns
|
|
@@ -93,15 +208,15 @@ function createMemFS() {
|
|
|
93
208
|
*/
|
|
94
209
|
function drainPipeInto(state, buffer, offset, length) {
|
|
95
210
|
let written = 0;
|
|
96
|
-
while (state.buffers.
|
|
97
|
-
const chunk = state.buffers
|
|
211
|
+
while (state.buffers.head && written < length) {
|
|
212
|
+
const chunk = state.buffers.head.value;
|
|
98
213
|
const copyLen = Math.min(chunk.byteLength, length - written);
|
|
99
214
|
buffer.set(chunk.subarray(0, copyLen), offset + written);
|
|
100
215
|
written += copyLen;
|
|
101
216
|
if (copyLen >= chunk.byteLength)
|
|
102
|
-
state.buffers
|
|
217
|
+
dequeue(state.buffers);
|
|
103
218
|
else
|
|
104
|
-
state.buffers
|
|
219
|
+
state.buffers.head.value = chunk.subarray(copyLen);
|
|
105
220
|
}
|
|
106
221
|
return written;
|
|
107
222
|
}
|
|
@@ -110,9 +225,9 @@ function createMemFS() {
|
|
|
110
225
|
* signal.
|
|
111
226
|
*/
|
|
112
227
|
function flushPipeReaders(state) {
|
|
113
|
-
while (state.pendingReaders.
|
|
114
|
-
(state.buffers.
|
|
115
|
-
const reader = state.pendingReaders
|
|
228
|
+
while (state.pendingReaders.head &&
|
|
229
|
+
(state.buffers.head || state.writeClosed)) {
|
|
230
|
+
const reader = dequeue(state.pendingReaders);
|
|
116
231
|
const n = drainPipeInto(state, reader.buffer, reader.offset, reader.length);
|
|
117
232
|
// Even at EOF (writeClosed && empty buffers) we satisfy with n=0 which
|
|
118
233
|
// signals EOF to the Go-side caller.
|
|
@@ -153,7 +268,7 @@ function createMemFS() {
|
|
|
153
268
|
/** Materialize the directories `missingDirs` reported. */
|
|
154
269
|
function createDirs(paths) {
|
|
155
270
|
for (const path of paths)
|
|
156
|
-
|
|
271
|
+
setNode(path, {
|
|
157
272
|
kind: "dir",
|
|
158
273
|
data: new Uint8Array(),
|
|
159
274
|
mtimeMs: Date.now(),
|
|
@@ -181,22 +296,68 @@ function createMemFS() {
|
|
|
181
296
|
}
|
|
182
297
|
/** True when `dir` has at least one descendant node in the tree. */
|
|
183
298
|
function hasChildren(dir) {
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
299
|
+
return (children.get(dir)?.size ?? 0) > 0;
|
|
300
|
+
}
|
|
301
|
+
/**
|
|
302
|
+
* Allocate zero-filled storage for a file of `length` bytes.
|
|
303
|
+
*
|
|
304
|
+
* The engine refuses a length it cannot allocate with a `RangeError`, which
|
|
305
|
+
* carries no POSIX code and would escape a callback that promises one. That
|
|
306
|
+
* refusal is the virtual filesystem's maximum file size, so it is reported as
|
|
307
|
+
* `EFBIG`; the caller has not yet changed any node when this throws.
|
|
308
|
+
*/
|
|
309
|
+
function allocateFileData(length, syscall, path) {
|
|
310
|
+
try {
|
|
311
|
+
return new Uint8Array(length);
|
|
312
|
+
}
|
|
313
|
+
catch (error) {
|
|
314
|
+
if (error instanceof RangeError)
|
|
315
|
+
throw new MemFSError_1.MemFSError("EFBIG", syscall, path);
|
|
316
|
+
throw error;
|
|
188
317
|
}
|
|
189
|
-
return false;
|
|
190
318
|
}
|
|
191
319
|
/**
|
|
192
320
|
* Grow or shrink a file's byte buffer to exactly `length`, zero-filling any
|
|
193
321
|
* extension. Callers validate `length >= 0` first.
|
|
194
322
|
*/
|
|
195
|
-
function resizeFileData(data, length) {
|
|
196
|
-
const next =
|
|
323
|
+
function resizeFileData(data, length, syscall, path) {
|
|
324
|
+
const next = allocateFileData(length, syscall, path);
|
|
197
325
|
next.set(data.subarray(0, Math.min(length, data.byteLength)));
|
|
198
326
|
return next;
|
|
199
327
|
}
|
|
328
|
+
/** Grow writes geometrically; the public view still exposes only file bytes. */
|
|
329
|
+
function growFileData(data, length, syscall, path) {
|
|
330
|
+
const capacity = data.buffer.byteLength - data.byteOffset;
|
|
331
|
+
if (length <= capacity) {
|
|
332
|
+
const next = new Uint8Array(data.buffer, data.byteOffset, length);
|
|
333
|
+
next.fill(0, data.byteLength);
|
|
334
|
+
return next;
|
|
335
|
+
}
|
|
336
|
+
// Spare capacity is only an optimization: when the engine refuses the
|
|
337
|
+
// doubled size, the exact length still satisfies the write.
|
|
338
|
+
let next;
|
|
339
|
+
if (data.byteLength * 2 > length) {
|
|
340
|
+
try {
|
|
341
|
+
next = allocateFileData(data.byteLength * 2, syscall, path);
|
|
342
|
+
}
|
|
343
|
+
catch {
|
|
344
|
+
next = allocateFileData(length, syscall, path);
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
else
|
|
348
|
+
next = allocateFileData(length, syscall, path);
|
|
349
|
+
next.set(data);
|
|
350
|
+
return next.subarray(0, length);
|
|
351
|
+
}
|
|
352
|
+
/** Reject invalid byte slices before a write or queued read can mutate state. */
|
|
353
|
+
function validSlice(buffer, offset, length) {
|
|
354
|
+
return (Number.isSafeInteger(offset) &&
|
|
355
|
+
Number.isSafeInteger(length) &&
|
|
356
|
+
offset >= 0 &&
|
|
357
|
+
length >= 0 &&
|
|
358
|
+
offset <= buffer.byteLength &&
|
|
359
|
+
length <= buffer.byteLength - offset);
|
|
360
|
+
}
|
|
200
361
|
/**
|
|
201
362
|
* Write `view` through descriptor `entry` and return the bytes stored.
|
|
202
363
|
*
|
|
@@ -220,14 +381,17 @@ function createMemFS() {
|
|
|
220
381
|
const start = entry.append
|
|
221
382
|
? node.data.byteLength
|
|
222
383
|
: (position ?? entry.position);
|
|
223
|
-
if (!Number.
|
|
384
|
+
if (!Number.isSafeInteger(start) || start < 0)
|
|
224
385
|
throw new MemFSError_1.MemFSError("EINVAL", syscall, entry.path);
|
|
225
386
|
if (view.byteLength === 0)
|
|
226
387
|
return 0;
|
|
227
388
|
const end = start + view.byteLength;
|
|
389
|
+
if (!Number.isSafeInteger(end))
|
|
390
|
+
throw new MemFSError_1.MemFSError("EINVAL", syscall, entry.path);
|
|
228
391
|
if (end > node.data.byteLength)
|
|
229
|
-
node.data =
|
|
392
|
+
node.data = growFileData(node.data, end, syscall, entry.path);
|
|
230
393
|
node.data.set(view, start);
|
|
394
|
+
node.text = undefined;
|
|
231
395
|
node.mtimeMs = Date.now();
|
|
232
396
|
if (position === null || entry.append)
|
|
233
397
|
entry.position = end;
|
|
@@ -242,21 +406,23 @@ function createMemFS() {
|
|
|
242
406
|
function moveSubtree(src, dest) {
|
|
243
407
|
const srcPrefix = src + "/";
|
|
244
408
|
const moves = [];
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
409
|
+
const pending = [src];
|
|
410
|
+
while (pending.length > 0) {
|
|
411
|
+
const key = pending.pop();
|
|
412
|
+
const target = key === src ? dest : dest + key.slice(src.length);
|
|
413
|
+
moves.push([key, target, nodes.get(key)]);
|
|
414
|
+
for (const child of children.get(key) ?? [])
|
|
415
|
+
pending.push(child);
|
|
250
416
|
}
|
|
251
417
|
// Delete the whole source subtree first so a nested overwrite cannot leave
|
|
252
418
|
// a stale descendant behind, then reinsert at the destination prefix. Any
|
|
253
419
|
// pre-existing `dest` node is replaced by the reinsert.
|
|
254
|
-
for (
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
for (const [key, node] of moves)
|
|
259
|
-
|
|
420
|
+
for (let i = moves.length - 1; i >= 0; --i)
|
|
421
|
+
deleteNode(moves[i][0]);
|
|
422
|
+
if (nodes.has(dest))
|
|
423
|
+
deleteNode(dest);
|
|
424
|
+
for (const [, key, node] of moves)
|
|
425
|
+
setNode(key, node);
|
|
260
426
|
for (const entry of fdTable.values()) {
|
|
261
427
|
if (entry.path === src)
|
|
262
428
|
entry.path = dest;
|
|
@@ -281,10 +447,11 @@ function createMemFS() {
|
|
|
281
447
|
// file observe the replacement instead of a detached predecessor.
|
|
282
448
|
if (existing) {
|
|
283
449
|
existing.data = bytes;
|
|
450
|
+
existing.text = undefined;
|
|
284
451
|
existing.mtimeMs = Date.now();
|
|
285
452
|
}
|
|
286
453
|
else
|
|
287
|
-
|
|
454
|
+
setNode(norm, { kind: "file", data: bytes, mtimeMs: Date.now() });
|
|
288
455
|
}
|
|
289
456
|
function readFile(p) {
|
|
290
457
|
const node = nodes.get(normalize(p));
|
|
@@ -293,8 +460,10 @@ function createMemFS() {
|
|
|
293
460
|
return new Uint8Array(node.data);
|
|
294
461
|
}
|
|
295
462
|
function readFileText(p) {
|
|
296
|
-
const
|
|
297
|
-
|
|
463
|
+
const node = nodes.get(normalize(p));
|
|
464
|
+
if (!node || node.kind !== "file")
|
|
465
|
+
return null;
|
|
466
|
+
return (node.text ??= decoder.decode(node.data));
|
|
298
467
|
}
|
|
299
468
|
function exists(p) {
|
|
300
469
|
return nodes.has(normalize(p));
|
|
@@ -329,10 +498,10 @@ function createMemFS() {
|
|
|
329
498
|
};
|
|
330
499
|
}
|
|
331
500
|
/**
|
|
332
|
-
* Return immediate children of directory `p`, sorted
|
|
501
|
+
* Return immediate children of directory `p`, sorted by UTF-16 code unit.
|
|
333
502
|
*
|
|
334
|
-
*
|
|
335
|
-
*
|
|
503
|
+
* The maintained child index limits work to this directory's entries. Sorting
|
|
504
|
+
* provides deterministic output without scanning unrelated project files.
|
|
336
505
|
*/
|
|
337
506
|
function readdirSync(p) {
|
|
338
507
|
const norm = normalize(p);
|
|
@@ -341,16 +510,9 @@ function createMemFS() {
|
|
|
341
510
|
throw new MemFSError_1.MemFSError("ENOENT", "readdir", norm);
|
|
342
511
|
if (node.kind !== "dir")
|
|
343
512
|
throw new MemFSError_1.MemFSError("ENOTDIR", "readdir", norm);
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
if (!key.startsWith(prefix) || key === norm)
|
|
348
|
-
continue;
|
|
349
|
-
const rest = key.slice(prefix.length);
|
|
350
|
-
const cut = rest.indexOf("/");
|
|
351
|
-
direct.add(cut === -1 ? rest : rest.slice(0, cut));
|
|
352
|
-
}
|
|
353
|
-
return [...direct].sort();
|
|
513
|
+
return [...children.get(norm)]
|
|
514
|
+
.map((child) => child.slice(norm === "/" ? 1 : norm.length + 1))
|
|
515
|
+
.sort();
|
|
354
516
|
}
|
|
355
517
|
const fs = {
|
|
356
518
|
constants: {
|
|
@@ -364,12 +526,11 @@ function createMemFS() {
|
|
|
364
526
|
},
|
|
365
527
|
writeSync(fd, buf) {
|
|
366
528
|
if (fd === 1) {
|
|
367
|
-
stdout.
|
|
529
|
+
stdout.write(buf);
|
|
368
530
|
return buf.length;
|
|
369
531
|
}
|
|
370
532
|
if (fd === 2) {
|
|
371
|
-
const text =
|
|
372
|
-
stderr.buffer += text;
|
|
533
|
+
const text = stderr.write(buf);
|
|
373
534
|
// Surface wasm-side stderr to the host console in real-time so plugin
|
|
374
535
|
// debug prints / Go panics aren't trapped inside the MemFS buffer.
|
|
375
536
|
// Stripped at end of message for cleaner display.
|
|
@@ -396,6 +557,8 @@ function createMemFS() {
|
|
|
396
557
|
},
|
|
397
558
|
write(fd, buf, offset, length, position, callback) {
|
|
398
559
|
try {
|
|
560
|
+
if (!validSlice(buf, offset, length))
|
|
561
|
+
throw new MemFSError_1.MemFSError("EINVAL", "write");
|
|
399
562
|
// Pipe write: snapshot the data (caller may reuse `buf`) and queue.
|
|
400
563
|
// Synchronously wake any blocked reader so the cooperative wasm
|
|
401
564
|
// scheduler doesn't deadlock waiting for a future fs roundtrip.
|
|
@@ -405,10 +568,14 @@ function createMemFS() {
|
|
|
405
568
|
callback(new MemFSError_1.MemFSError("EBADF", "write"), 0);
|
|
406
569
|
return;
|
|
407
570
|
}
|
|
571
|
+
if (pipeState.readClosed) {
|
|
572
|
+
callback(new MemFSError_1.MemFSError("EPIPE", "write"), 0);
|
|
573
|
+
return;
|
|
574
|
+
}
|
|
408
575
|
if (length > 0) {
|
|
409
576
|
const copy = new Uint8Array(length);
|
|
410
577
|
copy.set(buf.subarray(offset, offset + length));
|
|
411
|
-
pipeState.buffers
|
|
578
|
+
enqueue(pipeState.buffers, copy);
|
|
412
579
|
flushPipeReaders(pipeState);
|
|
413
580
|
}
|
|
414
581
|
callback(null, length);
|
|
@@ -463,7 +630,7 @@ function createMemFS() {
|
|
|
463
630
|
if (missing.length > 0)
|
|
464
631
|
throw new MemFSError_1.MemFSError("ENOENT", "open", missing[0]);
|
|
465
632
|
node = { kind: "file", data: new Uint8Array(), mtimeMs: Date.now() };
|
|
466
|
-
|
|
633
|
+
setNode(norm, node);
|
|
467
634
|
}
|
|
468
635
|
else {
|
|
469
636
|
if (creating && exclusive)
|
|
@@ -480,6 +647,7 @@ function createMemFS() {
|
|
|
480
647
|
}
|
|
481
648
|
if (truncating) {
|
|
482
649
|
node.data = new Uint8Array();
|
|
650
|
+
node.text = undefined;
|
|
483
651
|
node.mtimeMs = Date.now();
|
|
484
652
|
}
|
|
485
653
|
const fd = nextFd++;
|
|
@@ -500,11 +668,19 @@ function createMemFS() {
|
|
|
500
668
|
close(fd, callback) {
|
|
501
669
|
const pipeState = pipes.get(fd);
|
|
502
670
|
if (pipeState) {
|
|
671
|
+
pipes.delete(fd);
|
|
503
672
|
if (fd === pipeState.writeFd) {
|
|
504
673
|
pipeState.writeClosed = true;
|
|
505
674
|
flushPipeReaders(pipeState);
|
|
506
675
|
}
|
|
507
|
-
|
|
676
|
+
else {
|
|
677
|
+
pipeState.readClosed = true;
|
|
678
|
+
pipeState.buffers = {};
|
|
679
|
+
while (pipeState.pendingReaders.head) {
|
|
680
|
+
const reader = dequeue(pipeState.pendingReaders);
|
|
681
|
+
reader.callback(new MemFSError_1.MemFSError("EBADF", "read"), 0);
|
|
682
|
+
}
|
|
683
|
+
}
|
|
508
684
|
callback(null);
|
|
509
685
|
return;
|
|
510
686
|
}
|
|
@@ -512,11 +688,19 @@ function createMemFS() {
|
|
|
512
688
|
callback(new MemFSError_1.MemFSError("EBADF", "close"));
|
|
513
689
|
return;
|
|
514
690
|
}
|
|
691
|
+
if (fd === 1)
|
|
692
|
+
stdout.flush();
|
|
693
|
+
if (fd === 2)
|
|
694
|
+
stderr.flush();
|
|
515
695
|
if (fd > 2)
|
|
516
696
|
fdTable.delete(fd);
|
|
517
697
|
callback(null);
|
|
518
698
|
},
|
|
519
699
|
read(fd, buffer, offset, length, position, callback) {
|
|
700
|
+
if (!validSlice(buffer, offset, length)) {
|
|
701
|
+
callback(new MemFSError_1.MemFSError("EINVAL", "read"), 0);
|
|
702
|
+
return;
|
|
703
|
+
}
|
|
520
704
|
// Pipe read: drain queued chunks; block (defer callback) on empty.
|
|
521
705
|
// Empty + writeClosed → return 0 (EOF). Position is ignored for pipes.
|
|
522
706
|
const pipeState = pipes.get(fd);
|
|
@@ -525,7 +709,11 @@ function createMemFS() {
|
|
|
525
709
|
callback(new MemFSError_1.MemFSError("EBADF", "read"), 0);
|
|
526
710
|
return;
|
|
527
711
|
}
|
|
528
|
-
if (
|
|
712
|
+
if (length === 0) {
|
|
713
|
+
callback(null, 0);
|
|
714
|
+
return;
|
|
715
|
+
}
|
|
716
|
+
if (pipeState.buffers.head) {
|
|
529
717
|
const n = drainPipeInto(pipeState, buffer, offset, length);
|
|
530
718
|
callback(null, n);
|
|
531
719
|
return;
|
|
@@ -534,7 +722,7 @@ function createMemFS() {
|
|
|
534
722
|
callback(null, 0);
|
|
535
723
|
return;
|
|
536
724
|
}
|
|
537
|
-
pipeState.pendingReaders
|
|
725
|
+
enqueue(pipeState.pendingReaders, { buffer, offset, length, callback });
|
|
538
726
|
return;
|
|
539
727
|
}
|
|
540
728
|
const entry = fdTable.get(fd);
|
|
@@ -554,7 +742,7 @@ function createMemFS() {
|
|
|
554
742
|
return;
|
|
555
743
|
}
|
|
556
744
|
const start = position ?? entry.position;
|
|
557
|
-
if (!Number.
|
|
745
|
+
if (!Number.isSafeInteger(start) || start < 0) {
|
|
558
746
|
callback(new MemFSError_1.MemFSError("EINVAL", "read", entry.path), 0);
|
|
559
747
|
return;
|
|
560
748
|
}
|
|
@@ -586,7 +774,7 @@ function createMemFS() {
|
|
|
586
774
|
throw new MemFSError_1.MemFSError("ENOENT", "mkdir", parentDir(norm));
|
|
587
775
|
if (parent.kind !== "dir")
|
|
588
776
|
throw new MemFSError_1.MemFSError("ENOTDIR", "mkdir", parentDir(norm));
|
|
589
|
-
|
|
777
|
+
setNode(norm, {
|
|
590
778
|
kind: "dir",
|
|
591
779
|
data: new Uint8Array(),
|
|
592
780
|
mtimeMs: Date.now(),
|
|
@@ -652,7 +840,7 @@ function createMemFS() {
|
|
|
652
840
|
callback(new MemFSError_1.MemFSError("EISDIR", "unlink", norm));
|
|
653
841
|
return;
|
|
654
842
|
}
|
|
655
|
-
|
|
843
|
+
deleteNode(norm);
|
|
656
844
|
callback(null);
|
|
657
845
|
},
|
|
658
846
|
rename(from, to, callback) {
|
|
@@ -728,7 +916,7 @@ function createMemFS() {
|
|
|
728
916
|
callback(new MemFSError_1.MemFSError("ENOTEMPTY", "rmdir", norm));
|
|
729
917
|
return;
|
|
730
918
|
}
|
|
731
|
-
|
|
919
|
+
deleteNode(norm);
|
|
732
920
|
callback(null);
|
|
733
921
|
},
|
|
734
922
|
chmod(_p, _mode, callback) {
|
|
@@ -773,7 +961,14 @@ function createMemFS() {
|
|
|
773
961
|
callback(new MemFSError_1.MemFSError("EINVAL", "truncate", norm));
|
|
774
962
|
return;
|
|
775
963
|
}
|
|
776
|
-
|
|
964
|
+
try {
|
|
965
|
+
node.data = resizeFileData(node.data, length, "truncate", norm);
|
|
966
|
+
}
|
|
967
|
+
catch (err) {
|
|
968
|
+
callback(err);
|
|
969
|
+
return;
|
|
970
|
+
}
|
|
971
|
+
node.text = undefined;
|
|
777
972
|
node.mtimeMs = Date.now();
|
|
778
973
|
callback(null);
|
|
779
974
|
},
|
|
@@ -805,7 +1000,14 @@ function createMemFS() {
|
|
|
805
1000
|
callback(new MemFSError_1.MemFSError("EINVAL", "ftruncate", entry.path));
|
|
806
1001
|
return;
|
|
807
1002
|
}
|
|
808
|
-
|
|
1003
|
+
try {
|
|
1004
|
+
node.data = resizeFileData(node.data, length, "ftruncate", entry.path);
|
|
1005
|
+
}
|
|
1006
|
+
catch (err) {
|
|
1007
|
+
callback(err);
|
|
1008
|
+
return;
|
|
1009
|
+
}
|
|
1010
|
+
node.text = undefined;
|
|
809
1011
|
node.mtimeMs = Date.now();
|
|
810
1012
|
callback(null);
|
|
811
1013
|
},
|
|
@@ -813,11 +1015,12 @@ function createMemFS() {
|
|
|
813
1015
|
const readFd = nextFd++;
|
|
814
1016
|
const writeFd = nextFd++;
|
|
815
1017
|
const state = {
|
|
816
|
-
buffers:
|
|
817
|
-
pendingReaders:
|
|
1018
|
+
buffers: {},
|
|
1019
|
+
pendingReaders: {},
|
|
818
1020
|
readFd,
|
|
819
1021
|
writeFd,
|
|
820
1022
|
writeClosed: false,
|
|
1023
|
+
readClosed: false,
|
|
821
1024
|
};
|
|
822
1025
|
pipes.set(readFd, state);
|
|
823
1026
|
pipes.set(writeFd, state);
|
|
@@ -831,11 +1034,11 @@ function createMemFS() {
|
|
|
831
1034
|
readFileText,
|
|
832
1035
|
exists,
|
|
833
1036
|
mkdirp,
|
|
834
|
-
stdout,
|
|
835
|
-
stderr,
|
|
1037
|
+
stdout: stdout.output,
|
|
1038
|
+
stderr: stderr.output,
|
|
836
1039
|
resetStdio() {
|
|
837
|
-
stdout.buffer = "";
|
|
838
|
-
stderr.buffer = "";
|
|
1040
|
+
stdout.output.buffer = "";
|
|
1041
|
+
stderr.output.buffer = "";
|
|
839
1042
|
},
|
|
840
1043
|
};
|
|
841
1044
|
}
|