@standardagents/code-web 0.14.3-canary.6cc75f → 0.14.4-branch.9e48e914.ee06ef
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 +9 -1
- package/dist/build.json +1 -1
- package/dist/licenses/jco-transpile.txt +220 -0
- package/dist/manifest.json +1 -1
- package/dist/manifest.sig +1 -1
- package/dist/pkg/standard_web.d.ts +50 -12
- package/dist/pkg/standard_web.js +292 -20
- package/dist/pkg/standard_web_bg.wasm +0 -0
- package/dist/pkg/standard_web_bg.wasm.d.ts +17 -12
- package/dist/plugins/clock-cells/clock_cells.core.wasm +0 -0
- package/dist/plugins/clock-cells/clock_cells.wasm +0 -0
- package/dist/plugins/clock-cells/standard-plugin.json +11 -0
- package/dist/plugins/host/transpile.js +51 -0
- package/dist/plugins/host/ui-jspi.js +14851 -0
- package/dist/plugins/host/ui.core2.wasm +0 -0
- package/dist/plugins/host/ui.core3.wasm +0 -0
- package/dist/plugins/host/ui.js +14675 -0
- package/dist/plugins/host/world.js +172 -0
- package/dist/plugins/jco/cli.js +141 -0
- package/dist/plugins/jco/common.js +16 -0
- package/dist/plugins/jco/config.js +7 -0
- package/dist/plugins/jco/environment.js +24 -0
- package/dist/plugins/jco/filesystem.js +339 -0
- package/dist/plugins/jco/in-memory-filesystem.js +620 -0
- package/dist/plugins/jco/io.js +366 -0
- package/dist/plugins/jco/js-component-bindgen-component.core.wasm +0 -0
- package/dist/plugins/jco/js-component-bindgen-component.core2.wasm +0 -0
- package/dist/plugins/jco/js-component-bindgen-component.js +14272 -0
- package/dist/plugins/jco/opfs-filesystem.js +245 -0
- package/dist/plugins/jco/random.js +51 -0
- package/dist/plugins/plasma-pixels/plasma_pixels.core.wasm +0 -0
- package/dist/plugins/plasma-pixels/plasma_pixels.wasm +0 -0
- package/dist/plugins/plasma-pixels/standard-plugin.json +11 -0
- package/dist/plugins/runtime.js +1044 -0
- package/dist/plugins/samples.json +81 -0
- package/dist/plugins/worker.js +411 -0
- package/package.json +8 -2
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
// Instantiating a plugin's core module against the `ui-plugin` world with
|
|
2
|
+
// glue our own build produced (docs/plugin-runtime.md, "The browser
|
|
3
|
+
// runtime").
|
|
4
|
+
//
|
|
5
|
+
// jco's glue for a component is JavaScript generated from that component:
|
|
6
|
+
// its trampolines depend on which imports the component uses and how its
|
|
7
|
+
// core modules are wired. Running a plugin author's glue, or glue generated
|
|
8
|
+
// in the page from the author's component, would run JavaScript the viewer's
|
|
9
|
+
// build did not produce, which the Worker policy (`script-src 'self'`)
|
|
10
|
+
// forbids. So the build transpiles a *reference* component instead: a dummy
|
|
11
|
+
// core module wit-component makes from `wit/standard-plugin` alone that
|
|
12
|
+
// imports every function of the world and exports every export. Its glue
|
|
13
|
+
// (`ui.js`, and `ui-jspi.js` for JSPI) and its two small wit-component
|
|
14
|
+
// shim modules (`ui.core2.wasm`, `ui.core3.wasm`: the indirect-call table
|
|
15
|
+
// and its fix-up) are fixed files of the viewer's bundle.
|
|
16
|
+
//
|
|
17
|
+
// A plugin contributes exactly one core module: the one of its component
|
|
18
|
+
// that exports the world (`selectMain`). `instantiatePlugin` hands the glue
|
|
19
|
+
// that module in place of the reference one, translating import and export
|
|
20
|
+
// names between the legacy wit-component names our SDK emits
|
|
21
|
+
// (`standard:plugin/surface@2.0.0` / `attach`, `activate`, `cabi_realloc`,
|
|
22
|
+
// `cabi_post_frame`) and the standard names the reference module uses
|
|
23
|
+
// (`cm32p2|standard:plugin/surface@2` / `attach`, `cm32p2||activate`,
|
|
24
|
+
// `cm32p2_realloc`, `cm32p2||frame_post`). The canonical ABI makes both
|
|
25
|
+
// sides' core signatures a function of the WIT alone, and the wasm engine
|
|
26
|
+
// type-checks every import that links to a shim export, so a module that
|
|
27
|
+
// imports anything outside the world, or a world function at another type,
|
|
28
|
+
// fails at instantiation and runs nothing.
|
|
29
|
+
|
|
30
|
+
/** The world's exports, in the plugin's (legacy) spelling. */
|
|
31
|
+
export const WORLD_EXPORTS = ['activate', 'frame', 'event', 'deactivate']
|
|
32
|
+
|
|
33
|
+
/** Imports that wait for the account: async under JSPI, answered from the
|
|
34
|
+
* Worker's mirror (or refused) without it. jco's `asyncImports` names. */
|
|
35
|
+
export const ACCOUNT_IMPORTS = [
|
|
36
|
+
'standard:plugin/values@2.0.0#get',
|
|
37
|
+
'standard:plugin/values@2.0.0#set',
|
|
38
|
+
'standard:plugin/values@2.0.0#delete',
|
|
39
|
+
'standard:plugin/values@2.0.0#keys',
|
|
40
|
+
'standard:plugin/live@2.0.0#publish',
|
|
41
|
+
'standard:plugin/live@2.0.0#delete',
|
|
42
|
+
'standard:plugin/live@2.0.0#subscribe',
|
|
43
|
+
'standard:plugin/live@2.0.0#unsubscribe',
|
|
44
|
+
'standard:plugin/events@2.0.0#emit',
|
|
45
|
+
'standard:plugin/events@2.0.0#on',
|
|
46
|
+
'standard:plugin/events@2.0.0#off',
|
|
47
|
+
'standard:plugin/calls@2.0.0#call',
|
|
48
|
+
'standard:plugin/claims@2.0.0#claim',
|
|
49
|
+
'standard:plugin/secrets@2.0.0#get',
|
|
50
|
+
]
|
|
51
|
+
|
|
52
|
+
/** The reference component's file names (`name: 'ui'` at build time). */
|
|
53
|
+
export const REFERENCE = { main: 'ui.core.wasm', shims: ['ui.core2.wasm', 'ui.core3.wasm'] }
|
|
54
|
+
|
|
55
|
+
const STANDARD = 'cm32p2|'
|
|
56
|
+
|
|
57
|
+
/** A semver's canonical form in standard names: `2.0.0` -> `2`,
|
|
58
|
+
* `0.3.1` -> `0.3`, `0.0.4` -> `0.0.4`. */
|
|
59
|
+
function canonicalVersion(version) {
|
|
60
|
+
const [major, minor, patch] = version.split('.').map(part => Number.parseInt(part, 10))
|
|
61
|
+
if (major) return `${major}`
|
|
62
|
+
if (minor) return `0.${minor}`
|
|
63
|
+
return `0.0.${patch}`
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** An import module name in the standard spelling. */
|
|
67
|
+
export function standardModule(name) {
|
|
68
|
+
if (name.startsWith(STANDARD)) return name
|
|
69
|
+
const at = name.lastIndexOf('@')
|
|
70
|
+
if (at < 0) return `${STANDARD}${name}`
|
|
71
|
+
return `${STANDARD}${name.slice(0, at)}@${canonicalVersion(name.slice(at + 1))}`
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function exportNames(module) {
|
|
75
|
+
return new Set(WebAssembly.Module.exports(module).map(entry => entry.name))
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** Whether a core module exports the whole world, in either spelling. */
|
|
79
|
+
function exportsWorld(names) {
|
|
80
|
+
const legacy = names.has('memory') && names.has('cabi_realloc') && WORLD_EXPORTS.every(name => names.has(name))
|
|
81
|
+
const standard = names.has(`${STANDARD.slice(0, -1)}_memory`) && names.has(`${STANDARD.slice(0, -1)}_realloc`)
|
|
82
|
+
&& WORLD_EXPORTS.every(name => names.has(`${STANDARD}|${name}`))
|
|
83
|
+
return legacy || standard
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** The one core module of a component that exports the world. */
|
|
87
|
+
export function selectMain(modules) {
|
|
88
|
+
const found = modules.filter(module => exportsWorld(exportNames(module)))
|
|
89
|
+
if (found.length !== 1) {
|
|
90
|
+
throw new Error(`the component has ${found.length} core modules that export the ui-plugin world; exactly one must`)
|
|
91
|
+
}
|
|
92
|
+
return found[0]
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Refuses a main module that imports anything the world does not provide.
|
|
97
|
+
* `provided` is the standard-named import object the reference glue builds
|
|
98
|
+
* (or any object with the same keys).
|
|
99
|
+
*/
|
|
100
|
+
export function checkImports(main, provided) {
|
|
101
|
+
for (const entry of WebAssembly.Module.imports(main)) {
|
|
102
|
+
const module = standardModule(entry.module)
|
|
103
|
+
if (entry.kind !== 'function' || typeof provided[module]?.[entry.name] !== 'function') {
|
|
104
|
+
throw new Error(`the plugin imports ${entry.module}#${entry.name}, which is outside the ui-plugin world`)
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** The world's import names, as the reference glue provides them. */
|
|
110
|
+
export function worldImportNames(referenceMain) {
|
|
111
|
+
const provided = {}
|
|
112
|
+
for (const entry of WebAssembly.Module.imports(referenceMain)) {
|
|
113
|
+
;(provided[entry.module] ??= {})[entry.name] = () => {}
|
|
114
|
+
}
|
|
115
|
+
return provided
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/** The main module's exports as the reference glue reads them. */
|
|
119
|
+
function standardExports(exports) {
|
|
120
|
+
if (exports[`${STANDARD.slice(0, -1)}_memory`]) {
|
|
121
|
+
const out = { ...exports }
|
|
122
|
+
out.cm32p2_initialize ??= () => {}
|
|
123
|
+
for (const name of WORLD_EXPORTS) out[`${STANDARD}|${name}_post`] ??= () => {}
|
|
124
|
+
return out
|
|
125
|
+
}
|
|
126
|
+
const out = {
|
|
127
|
+
cm32p2_memory: exports.memory,
|
|
128
|
+
cm32p2_realloc: exports.cabi_realloc,
|
|
129
|
+
// wit-component calls `_initialize` when a module has one.
|
|
130
|
+
cm32p2_initialize: exports._initialize ?? (() => {}),
|
|
131
|
+
}
|
|
132
|
+
for (const name of WORLD_EXPORTS) {
|
|
133
|
+
out[`${STANDARD}|${name}`] = exports[name]
|
|
134
|
+
// A module without a post-return function needs none: nothing the
|
|
135
|
+
// export returns was allocated for the caller.
|
|
136
|
+
out[`${STANDARD}|${name}_post`] = exports[`cabi_post_${name}`] ?? (() => {})
|
|
137
|
+
}
|
|
138
|
+
return out
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Instantiates `main` (the plugin's core module) through the reference
|
|
143
|
+
* glue. `glue` is `ui.js` or `ui-jspi.js`; `shims` maps the reference shim
|
|
144
|
+
* file names to their compiled modules; `imports` is the host import object
|
|
145
|
+
* (runtime.js's `PluginHost.imports()`). `wrap(module, name, fn)` may wrap
|
|
146
|
+
* one core import (the Worker records `attach`'s region address). Returns
|
|
147
|
+
* the component's exports and the plugin's linear memory.
|
|
148
|
+
*/
|
|
149
|
+
export function instantiatePlugin({ glue, shims, main, imports, wrap = (_module, _name, fn) => fn }) {
|
|
150
|
+
const reference = {}
|
|
151
|
+
let memory = null
|
|
152
|
+
const instance = glue.instantiate((name) => {
|
|
153
|
+
if (name === REFERENCE.main) return reference
|
|
154
|
+
const module = shims[name]
|
|
155
|
+
if (!module) throw new Error(`the world glue asked for an unknown core module ${name}`)
|
|
156
|
+
return module
|
|
157
|
+
}, imports, (module, importObject) => {
|
|
158
|
+
if (module !== reference) return new WebAssembly.Instance(module, importObject)
|
|
159
|
+
checkImports(main, importObject)
|
|
160
|
+
const linked = {}
|
|
161
|
+
for (const entry of WebAssembly.Module.imports(main)) {
|
|
162
|
+
const standard = standardModule(entry.module)
|
|
163
|
+
const fn = importObject[standard][entry.name]
|
|
164
|
+
;(linked[entry.module] ??= {})[entry.name] = wrap(standard, entry.name, fn)
|
|
165
|
+
}
|
|
166
|
+
const exports = new WebAssembly.Instance(main, linked).exports
|
|
167
|
+
memory = exports.memory ?? exports.cm32p2_memory
|
|
168
|
+
return { exports: standardExports(exports) }
|
|
169
|
+
})
|
|
170
|
+
if (instance instanceof Promise) throw new Error('the world glue instantiated asynchronously')
|
|
171
|
+
return { instance, memory: () => memory }
|
|
172
|
+
}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { inputStreamCreate, outputStreamCreate, pollableCreate, } from "./io.js";
|
|
2
|
+
export { _setEnv, _setArgs, environment } from "./environment.js";
|
|
3
|
+
export { _setCwd } from "./config.js";
|
|
4
|
+
const symbolDispose = Symbol.dispose ?? Symbol.for("dispose");
|
|
5
|
+
class ComponentExit extends Error {
|
|
6
|
+
exitError = true;
|
|
7
|
+
code;
|
|
8
|
+
constructor(code) {
|
|
9
|
+
super(`Component exited ${code === 0 ? "successfully" : "with error"}`);
|
|
10
|
+
this.code = code;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
export const exit = {
|
|
14
|
+
exit(status) {
|
|
15
|
+
throw new ComponentExit(status.tag === "err" ? 1 : 0);
|
|
16
|
+
},
|
|
17
|
+
// @ts-expect-error - Available only wasi-cli v0.2.12
|
|
18
|
+
exitWithCode(code) {
|
|
19
|
+
throw new ComponentExit(code);
|
|
20
|
+
},
|
|
21
|
+
};
|
|
22
|
+
export function _setStdin(handler) {
|
|
23
|
+
stdinStream.handler = handler;
|
|
24
|
+
}
|
|
25
|
+
export function _setStderr(handler) {
|
|
26
|
+
stderrStream.handler = handler;
|
|
27
|
+
}
|
|
28
|
+
export function _setStdout(handler) {
|
|
29
|
+
stdoutStream.handler = handler;
|
|
30
|
+
}
|
|
31
|
+
const stdinStream = inputStreamCreate({
|
|
32
|
+
blockingRead() {
|
|
33
|
+
throw { tag: "closed" };
|
|
34
|
+
},
|
|
35
|
+
subscribe() {
|
|
36
|
+
return pollableCreate();
|
|
37
|
+
},
|
|
38
|
+
[symbolDispose]() { },
|
|
39
|
+
});
|
|
40
|
+
function consoleStream(writeLine) {
|
|
41
|
+
const decoder = new TextDecoder();
|
|
42
|
+
let pending = "";
|
|
43
|
+
const emitCompleteLines = () => {
|
|
44
|
+
const lines = pending.split("\n");
|
|
45
|
+
pending = lines.pop();
|
|
46
|
+
for (const line of lines) {
|
|
47
|
+
writeLine(line.endsWith("\r") ? line.slice(0, -1) : line);
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
return {
|
|
51
|
+
write(contents) {
|
|
52
|
+
pending += decoder.decode(contents, { stream: true });
|
|
53
|
+
emitCompleteLines();
|
|
54
|
+
},
|
|
55
|
+
flush() {
|
|
56
|
+
pending += decoder.decode();
|
|
57
|
+
if (pending) {
|
|
58
|
+
writeLine(pending);
|
|
59
|
+
}
|
|
60
|
+
pending = "";
|
|
61
|
+
},
|
|
62
|
+
blockingFlush() {
|
|
63
|
+
this.flush?.();
|
|
64
|
+
},
|
|
65
|
+
drop() {
|
|
66
|
+
this.flush?.();
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
const stdoutStream = outputStreamCreate(consoleStream((line) => console.log(line)));
|
|
71
|
+
const stderrStream = outputStreamCreate(consoleStream((line) => console.error(line)));
|
|
72
|
+
export const stdin = {
|
|
73
|
+
getStdin() {
|
|
74
|
+
return stdinStream;
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
export const stdout = {
|
|
78
|
+
getStdout() {
|
|
79
|
+
return stdoutStream;
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
export const stderr = {
|
|
83
|
+
getStderr() {
|
|
84
|
+
return stderrStream;
|
|
85
|
+
},
|
|
86
|
+
};
|
|
87
|
+
class TerminalInput {
|
|
88
|
+
}
|
|
89
|
+
class TerminalOutput {
|
|
90
|
+
}
|
|
91
|
+
export const terminalInput = {
|
|
92
|
+
TerminalInput,
|
|
93
|
+
};
|
|
94
|
+
export const terminalOutput = {
|
|
95
|
+
TerminalOutput,
|
|
96
|
+
};
|
|
97
|
+
export const terminalStderr = {
|
|
98
|
+
getTerminalStderr() {
|
|
99
|
+
return undefined;
|
|
100
|
+
},
|
|
101
|
+
};
|
|
102
|
+
export const terminalStdin = {
|
|
103
|
+
getTerminalStdin() {
|
|
104
|
+
return undefined;
|
|
105
|
+
},
|
|
106
|
+
};
|
|
107
|
+
export const terminalStdout = {
|
|
108
|
+
getTerminalStdout() {
|
|
109
|
+
return undefined;
|
|
110
|
+
},
|
|
111
|
+
};
|
|
112
|
+
/** Create isolated browser CLI interfaces without changing compatibility globals. */
|
|
113
|
+
export function createCli(config = {}) {
|
|
114
|
+
const stdinInstance = inputStreamCreate(config.stdin ?? {
|
|
115
|
+
blockingRead() {
|
|
116
|
+
throw { tag: "closed" };
|
|
117
|
+
},
|
|
118
|
+
subscribe: () => pollableCreate(),
|
|
119
|
+
});
|
|
120
|
+
const stdoutInstance = outputStreamCreate(config.stdout ?? consoleStream((line) => console.log(line)));
|
|
121
|
+
const stderrInstance = outputStreamCreate(config.stderr ?? consoleStream((line) => console.error(line)));
|
|
122
|
+
const env = Object.entries(config.environment ?? {});
|
|
123
|
+
const args = [...(config.arguments ?? [])];
|
|
124
|
+
const cwd = config.initialCwd ?? "/";
|
|
125
|
+
return {
|
|
126
|
+
environment: {
|
|
127
|
+
getEnvironment: () => env.map(([key, value]) => [key, value]),
|
|
128
|
+
getArguments: () => [...args],
|
|
129
|
+
initialCwd: () => cwd,
|
|
130
|
+
},
|
|
131
|
+
exit,
|
|
132
|
+
stdin: { getStdin: () => stdinInstance },
|
|
133
|
+
stdout: { getStdout: () => stdoutInstance },
|
|
134
|
+
stderr: { getStderr: () => stderrInstance },
|
|
135
|
+
terminalInput,
|
|
136
|
+
terminalOutput,
|
|
137
|
+
terminalStdin,
|
|
138
|
+
terminalStdout,
|
|
139
|
+
terminalStderr,
|
|
140
|
+
};
|
|
141
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export const MAX_U64 = (1n << 64n) - 1n;
|
|
2
|
+
/** Validate and return a WebAssembly u64 value. */
|
|
3
|
+
export function checkedU64(value, name) {
|
|
4
|
+
if (typeof value !== "bigint" || value < 0n || value > MAX_U64) {
|
|
5
|
+
throw new TypeError(`${name} must be a valid u64`);
|
|
6
|
+
}
|
|
7
|
+
return value;
|
|
8
|
+
}
|
|
9
|
+
/** Convert a WebAssembly u64 value to a lossless JavaScript number. */
|
|
10
|
+
export function checkedU64AsNumber(value, name) {
|
|
11
|
+
checkedU64(value, name);
|
|
12
|
+
if (value > BigInt(Number.MAX_SAFE_INTEGER)) {
|
|
13
|
+
throw new RangeError(`${name} exceeds JavaScript's safe integer range`);
|
|
14
|
+
}
|
|
15
|
+
return Number(value);
|
|
16
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { _setCwd as fsSetCwd } from "./config.js";
|
|
2
|
+
let _env = [];
|
|
3
|
+
let _args = [];
|
|
4
|
+
let _cwd = "/";
|
|
5
|
+
export function _setEnv(envObj) {
|
|
6
|
+
_env = Object.entries(envObj);
|
|
7
|
+
}
|
|
8
|
+
export function _setArgs(args) {
|
|
9
|
+
_args = args;
|
|
10
|
+
}
|
|
11
|
+
export function _setCwd(cwd) {
|
|
12
|
+
fsSetCwd((_cwd = cwd));
|
|
13
|
+
}
|
|
14
|
+
export const environment = {
|
|
15
|
+
getEnvironment() {
|
|
16
|
+
return _env;
|
|
17
|
+
},
|
|
18
|
+
getArguments() {
|
|
19
|
+
return _args;
|
|
20
|
+
},
|
|
21
|
+
initialCwd() {
|
|
22
|
+
return _cwd;
|
|
23
|
+
},
|
|
24
|
+
};
|
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
import { environment } from "./environment.js";
|
|
2
|
+
import { InMemoryFilesystemAdapter } from "./in-memory-filesystem.js";
|
|
3
|
+
import { _setCwd } from "./config.js";
|
|
4
|
+
export { _setCwd } from "./config.js";
|
|
5
|
+
export { InMemoryFilesystemAdapter } from "./in-memory-filesystem.js";
|
|
6
|
+
export { OpfsFilesystemAdapter, loadOpfsCapability } from "./opfs-filesystem.js";
|
|
7
|
+
class DirectoryEntryStream {
|
|
8
|
+
#implementation;
|
|
9
|
+
static _create(implementation) {
|
|
10
|
+
const stream = new DirectoryEntryStream();
|
|
11
|
+
stream.#implementation = implementation;
|
|
12
|
+
return stream;
|
|
13
|
+
}
|
|
14
|
+
readDirectoryEntry() {
|
|
15
|
+
return this.#implementation.readDirectoryEntry();
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
const directoryEntryStreamCreate = DirectoryEntryStream._create;
|
|
19
|
+
// @ts-expect-error - Deleting static method
|
|
20
|
+
delete DirectoryEntryStream._create;
|
|
21
|
+
class Descriptor {
|
|
22
|
+
#implementation;
|
|
23
|
+
_getImplementation(descriptor) {
|
|
24
|
+
return descriptor.#implementation;
|
|
25
|
+
}
|
|
26
|
+
static _create(implementation) {
|
|
27
|
+
const descriptor = new Descriptor();
|
|
28
|
+
descriptor.#implementation = implementation;
|
|
29
|
+
return descriptor;
|
|
30
|
+
}
|
|
31
|
+
readViaStream(offset) {
|
|
32
|
+
return this.#implementation.readViaStream(offset);
|
|
33
|
+
}
|
|
34
|
+
writeViaStream(offset) {
|
|
35
|
+
return this.#implementation.writeViaStream(offset);
|
|
36
|
+
}
|
|
37
|
+
appendViaStream() {
|
|
38
|
+
return this.#implementation.appendViaStream();
|
|
39
|
+
}
|
|
40
|
+
advise(offset, length, advice) {
|
|
41
|
+
return this.#implementation.advise(offset, length, advice);
|
|
42
|
+
}
|
|
43
|
+
syncData() {
|
|
44
|
+
return this.#implementation.syncData();
|
|
45
|
+
}
|
|
46
|
+
getFlags() {
|
|
47
|
+
return this.#implementation.getFlags();
|
|
48
|
+
}
|
|
49
|
+
getType() {
|
|
50
|
+
return this.#implementation.getType();
|
|
51
|
+
}
|
|
52
|
+
setSize(size) {
|
|
53
|
+
return this.#implementation.setSize(size);
|
|
54
|
+
}
|
|
55
|
+
setTimes(dataAccessTimestamp, dataModificationTimestamp) {
|
|
56
|
+
return this.#implementation.setTimes(dataAccessTimestamp, dataModificationTimestamp);
|
|
57
|
+
}
|
|
58
|
+
read(length, offset) {
|
|
59
|
+
return this.#implementation.read(length, offset);
|
|
60
|
+
}
|
|
61
|
+
write(buffer, offset) {
|
|
62
|
+
return this.#implementation.write(buffer, offset);
|
|
63
|
+
}
|
|
64
|
+
readDirectory() {
|
|
65
|
+
return directoryEntryStreamCreate(this.#implementation.readDirectory());
|
|
66
|
+
}
|
|
67
|
+
sync() {
|
|
68
|
+
return this.#implementation.sync();
|
|
69
|
+
}
|
|
70
|
+
createDirectoryAt(path) {
|
|
71
|
+
return this.#implementation.createDirectoryAt(path);
|
|
72
|
+
}
|
|
73
|
+
stat() {
|
|
74
|
+
return this.#implementation.stat();
|
|
75
|
+
}
|
|
76
|
+
statAt(pathFlags, path) {
|
|
77
|
+
return this.#implementation.statAt(pathFlags, path);
|
|
78
|
+
}
|
|
79
|
+
setTimesAt(pathFlags, path, dataAccessTimestamp, dataModificationTimestamp) {
|
|
80
|
+
return this.#implementation.setTimesAt(pathFlags, path, dataAccessTimestamp, dataModificationTimestamp);
|
|
81
|
+
}
|
|
82
|
+
linkAt(oldPathFlags, oldPath, newDescriptor, newPath) {
|
|
83
|
+
return this.#implementation.linkAt(oldPathFlags, oldPath, descriptorGetImplementation(newDescriptor), newPath);
|
|
84
|
+
}
|
|
85
|
+
openAt(pathFlags, path, openFlags, flags) {
|
|
86
|
+
return descriptorCreate(this.#implementation.openAt(pathFlags, path, openFlags, flags));
|
|
87
|
+
}
|
|
88
|
+
readlinkAt(path) {
|
|
89
|
+
return this.#implementation.readlinkAt(path);
|
|
90
|
+
}
|
|
91
|
+
removeDirectoryAt(path) {
|
|
92
|
+
return this.#implementation.removeDirectoryAt(path);
|
|
93
|
+
}
|
|
94
|
+
renameAt(oldPath, newDescriptor, newPath) {
|
|
95
|
+
return this.#implementation.renameAt(oldPath, descriptorGetImplementation(newDescriptor), newPath);
|
|
96
|
+
}
|
|
97
|
+
symlinkAt(oldPath, newPath) {
|
|
98
|
+
return this.#implementation.symlinkAt(oldPath, newPath);
|
|
99
|
+
}
|
|
100
|
+
unlinkFileAt(path) {
|
|
101
|
+
return this.#implementation.unlinkFileAt(path);
|
|
102
|
+
}
|
|
103
|
+
isSameObject(other) {
|
|
104
|
+
return this.#implementation.isSameObject(descriptorGetImplementation(other));
|
|
105
|
+
}
|
|
106
|
+
metadataHash() {
|
|
107
|
+
return this.#implementation.metadataHash();
|
|
108
|
+
}
|
|
109
|
+
metadataHashAt(pathFlags, path) {
|
|
110
|
+
return this.#implementation.metadataHashAt(pathFlags, path);
|
|
111
|
+
}
|
|
112
|
+
lockShared() {
|
|
113
|
+
return this.#implementation.lockShared?.();
|
|
114
|
+
}
|
|
115
|
+
lockExclusive() {
|
|
116
|
+
return this.#implementation.lockExclusive?.();
|
|
117
|
+
}
|
|
118
|
+
tryLockShared() {
|
|
119
|
+
return this.#implementation.tryLockShared?.() ?? false;
|
|
120
|
+
}
|
|
121
|
+
tryLockExclusive() {
|
|
122
|
+
return this.#implementation.tryLockExclusive?.() ?? false;
|
|
123
|
+
}
|
|
124
|
+
unlock() {
|
|
125
|
+
return this.#implementation.unlock?.();
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
const descriptorGetImplementation = Descriptor.prototype._getImplementation;
|
|
129
|
+
// @ts-expect-error - Deleting prototype method
|
|
130
|
+
delete Descriptor.prototype._getImplementation;
|
|
131
|
+
const descriptorCreate = Descriptor._create;
|
|
132
|
+
// @ts-expect-error - Deleting static method
|
|
133
|
+
delete Descriptor._create;
|
|
134
|
+
const defaultAdapter = new InMemoryFilesystemAdapter();
|
|
135
|
+
let _fileData = { dir: {} };
|
|
136
|
+
let _preopens = [];
|
|
137
|
+
let _rootPreopen = null;
|
|
138
|
+
export const preopens = {
|
|
139
|
+
getDirectories() {
|
|
140
|
+
return _preopens;
|
|
141
|
+
},
|
|
142
|
+
};
|
|
143
|
+
/** Create isolated filesystem namespaces backed by an application-selected adapter. */
|
|
144
|
+
export function createFilesystem({ adapter, preopens: configuredPreopens, }) {
|
|
145
|
+
const entries = Object.entries(configuredPreopens).map(([guestPath, capability]) => [descriptorCreate(adapter.getRoot(capability)), guestPath]);
|
|
146
|
+
let disposed = false;
|
|
147
|
+
return {
|
|
148
|
+
types,
|
|
149
|
+
preopens: {
|
|
150
|
+
getDirectories() {
|
|
151
|
+
if (disposed) {
|
|
152
|
+
throw new Error("filesystem adapter has been disposed");
|
|
153
|
+
}
|
|
154
|
+
return [...entries];
|
|
155
|
+
},
|
|
156
|
+
},
|
|
157
|
+
dispose() {
|
|
158
|
+
if (disposed) {
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
disposed = true;
|
|
162
|
+
adapter.dispose?.();
|
|
163
|
+
},
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
export function _setFileData(fileData) {
|
|
167
|
+
_fileData = fileData;
|
|
168
|
+
if (_rootPreopen) {
|
|
169
|
+
_rootPreopen[0] = descriptorCreate(defaultAdapter.getRoot(fileData));
|
|
170
|
+
}
|
|
171
|
+
else {
|
|
172
|
+
_setPreopens({ "/": fileData });
|
|
173
|
+
}
|
|
174
|
+
const cwd = environment.initialCwd();
|
|
175
|
+
_setCwd(cwd || "/");
|
|
176
|
+
}
|
|
177
|
+
export function _getFileData() {
|
|
178
|
+
return JSON.stringify(_fileData);
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Replace all preopens with the given set.
|
|
182
|
+
* @param preopensConfig - Map of virtual paths to file data entries
|
|
183
|
+
*/
|
|
184
|
+
export function _setPreopens(preopensConfig) {
|
|
185
|
+
_preopens = [];
|
|
186
|
+
_rootPreopen = null;
|
|
187
|
+
for (const [virtualPath, fileData] of Object.entries(preopensConfig)) {
|
|
188
|
+
_addPreopen(virtualPath, fileData);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Add a single preopen mapping.
|
|
193
|
+
* @param virtualPath - The virtual path visible to the guest
|
|
194
|
+
* @param fileData - The file data object representing the directory
|
|
195
|
+
*/
|
|
196
|
+
export function _addPreopen(virtualPath, fileData) {
|
|
197
|
+
const descriptor = descriptorCreate(defaultAdapter.getRoot(fileData));
|
|
198
|
+
const entry = [descriptor, virtualPath];
|
|
199
|
+
_preopens.push(entry);
|
|
200
|
+
if (virtualPath === "/") {
|
|
201
|
+
_rootPreopen = entry;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Add a single preopen backed by a custom adapter (e.g. `OpfsFilesystemAdapter`) instead of the
|
|
206
|
+
* default in-memory one. Lets a host wire an alternative `BrowserFilesystemAdapter` into the
|
|
207
|
+
* top-level `wasi:filesystem/preopens` singleton that transpiled components import statically.
|
|
208
|
+
* @param virtualPath - The virtual path visible to the guest
|
|
209
|
+
* @param adapter - The adapter that will back this preopen
|
|
210
|
+
* @param capability - The adapter-specific capability to load as the preopen's root
|
|
211
|
+
*/
|
|
212
|
+
export function _addPreopenWithAdapter(virtualPath, adapter, capability) {
|
|
213
|
+
const descriptor = descriptorCreate(adapter.getRoot(capability));
|
|
214
|
+
const entry = [descriptor, virtualPath];
|
|
215
|
+
_preopens.push(entry);
|
|
216
|
+
if (virtualPath === "/") {
|
|
217
|
+
_rootPreopen = entry;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
/** Clear all preopens, giving the guest no filesystem access. */
|
|
221
|
+
export function _clearPreopens() {
|
|
222
|
+
_preopens = [];
|
|
223
|
+
_rootPreopen = null;
|
|
224
|
+
}
|
|
225
|
+
/** Get current preopens configuration. */
|
|
226
|
+
export function _getPreopens() {
|
|
227
|
+
return [..._preopens];
|
|
228
|
+
}
|
|
229
|
+
/** Reject host paths because browser filesystems require explicit capabilities. */
|
|
230
|
+
export function _createPreopenDescriptor(hostPreopen) {
|
|
231
|
+
throw new TypeError(`browser preopen ${JSON.stringify(hostPreopen)} is a host path; configure browser file data or an adapter instead`);
|
|
232
|
+
}
|
|
233
|
+
export const types = {
|
|
234
|
+
Descriptor,
|
|
235
|
+
DirectoryEntryStream,
|
|
236
|
+
filesystemErrorCode: (err) => {
|
|
237
|
+
let message;
|
|
238
|
+
if ("payload" in err) {
|
|
239
|
+
message = err.payload;
|
|
240
|
+
}
|
|
241
|
+
else if ("message" in err) {
|
|
242
|
+
message = err.message;
|
|
243
|
+
}
|
|
244
|
+
return convertFsError(message);
|
|
245
|
+
},
|
|
246
|
+
};
|
|
247
|
+
function convertFsError(e) {
|
|
248
|
+
switch (e.code) {
|
|
249
|
+
case "EACCES":
|
|
250
|
+
return "access";
|
|
251
|
+
case "EAGAIN":
|
|
252
|
+
case "EWOULDBLOCK":
|
|
253
|
+
return "would-block";
|
|
254
|
+
case "EALREADY":
|
|
255
|
+
return "already";
|
|
256
|
+
case "EBADF":
|
|
257
|
+
return "bad-descriptor";
|
|
258
|
+
case "EBUSY":
|
|
259
|
+
return "busy";
|
|
260
|
+
case "EDEADLK":
|
|
261
|
+
return "deadlock";
|
|
262
|
+
case "EDQUOT":
|
|
263
|
+
return "quota";
|
|
264
|
+
case "EEXIST":
|
|
265
|
+
return "exist";
|
|
266
|
+
case "EFBIG":
|
|
267
|
+
return "file-too-large";
|
|
268
|
+
case "EILSEQ":
|
|
269
|
+
return "illegal-byte-sequence";
|
|
270
|
+
case "EINPROGRESS":
|
|
271
|
+
return "in-progress";
|
|
272
|
+
case "EINTR":
|
|
273
|
+
return "interrupted";
|
|
274
|
+
case "EINVAL":
|
|
275
|
+
return "invalid";
|
|
276
|
+
case "EIO":
|
|
277
|
+
return "io";
|
|
278
|
+
case "EISDIR":
|
|
279
|
+
return "is-directory";
|
|
280
|
+
case "ELOOP":
|
|
281
|
+
return "loop";
|
|
282
|
+
case "EMLINK":
|
|
283
|
+
return "too-many-links";
|
|
284
|
+
case "EMSGSIZE":
|
|
285
|
+
return "message-size";
|
|
286
|
+
case "ENAMETOOLONG":
|
|
287
|
+
return "name-too-long";
|
|
288
|
+
case "ENODEV":
|
|
289
|
+
return "no-device";
|
|
290
|
+
case "ENOENT":
|
|
291
|
+
return "no-entry";
|
|
292
|
+
case "ENOLCK":
|
|
293
|
+
return "no-lock";
|
|
294
|
+
case "ENOMEM":
|
|
295
|
+
return "insufficient-memory";
|
|
296
|
+
case "ENOSPC":
|
|
297
|
+
return "insufficient-space";
|
|
298
|
+
case "ENOTDIR":
|
|
299
|
+
case "ERR_FS_EISDIR":
|
|
300
|
+
return "not-directory";
|
|
301
|
+
case "ENOTEMPTY":
|
|
302
|
+
return "not-empty";
|
|
303
|
+
case "ENOTRECOVERABLE":
|
|
304
|
+
return "not-recoverable";
|
|
305
|
+
case "ENOTSUP":
|
|
306
|
+
return "unsupported";
|
|
307
|
+
case "ENOTTY":
|
|
308
|
+
return "no-tty";
|
|
309
|
+
// windows gives this error for badly structured `//` reads
|
|
310
|
+
// this seems like a slightly better error than unknown given
|
|
311
|
+
// that it's a common footgun
|
|
312
|
+
case -4094:
|
|
313
|
+
case "ENXIO":
|
|
314
|
+
return "no-such-device";
|
|
315
|
+
case "EOVERFLOW":
|
|
316
|
+
return "overflow";
|
|
317
|
+
case "EPERM":
|
|
318
|
+
return "not-permitted";
|
|
319
|
+
case "EPIPE":
|
|
320
|
+
return "pipe";
|
|
321
|
+
case "EROFS":
|
|
322
|
+
return "read-only";
|
|
323
|
+
case "ESPIPE":
|
|
324
|
+
return "invalid-seek";
|
|
325
|
+
case "ETXTBSY":
|
|
326
|
+
return "text-file-busy";
|
|
327
|
+
case "EXDEV":
|
|
328
|
+
return "cross-device";
|
|
329
|
+
case "UNKNOWN":
|
|
330
|
+
switch (e.errno) {
|
|
331
|
+
case -4094:
|
|
332
|
+
return "no-such-device";
|
|
333
|
+
default:
|
|
334
|
+
throw e;
|
|
335
|
+
}
|
|
336
|
+
default:
|
|
337
|
+
throw e;
|
|
338
|
+
}
|
|
339
|
+
}
|