wasm-idle 1.1.0 → 1.1.2
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 +140 -37
- package/dist/playground/assetBridge.js +106 -31
- package/dist/playground/assets.d.ts +2 -2
- package/dist/playground/c3.js +22 -0
- package/dist/playground/c3Memory.d.ts +19 -0
- package/dist/playground/c3Memory.js +117 -0
- package/dist/playground/cachedClang.js +143 -34
- package/dist/playground/clangAssetIntegrity.js +6 -6
- package/dist/playground/clangWorkerProtocol.d.ts +12 -1
- package/dist/playground/cobol.d.ts +3 -0
- package/dist/playground/cobol.js +10 -1
- package/dist/playground/commonlispAssets.d.ts +2 -2
- package/dist/playground/compiledArtifactCache.d.ts +3 -1
- package/dist/playground/compiledArtifactCache.js +31 -5
- package/dist/playground/container/ContainerExplorer.svelte +307 -0
- package/dist/playground/container/ContainerExplorer.svelte.d.ts +14 -0
- package/dist/playground/container/binding.d.ts +26 -0
- package/dist/playground/container/binding.js +247 -0
- package/dist/playground/container/clang-runtime.d.ts +38 -0
- package/dist/playground/container/clang-runtime.js +343 -0
- package/dist/playground/container/client.d.ts +15 -0
- package/dist/playground/container/client.js +189 -0
- package/dist/playground/container/explorer.d.ts +31 -0
- package/dist/playground/container/explorer.js +112 -0
- package/dist/playground/container/owner.worker.js +390 -0
- package/dist/playground/container/protocol.d.ts +73 -0
- package/dist/playground/container/protocol.js +28 -0
- package/dist/playground/container/release.d.ts +31 -0
- package/dist/playground/container/release.js +612 -0
- package/dist/playground/container/runtime-adapter.d.ts +73 -0
- package/dist/playground/container/runtime-adapter.js +474 -0
- package/dist/playground/container/shell-control.d.ts +49 -0
- package/dist/playground/container/shell-control.js +303 -0
- package/dist/playground/container/workspace.d.ts +8 -0
- package/dist/playground/container/workspace.js +29 -0
- package/dist/playground/d.d.ts +6 -1
- package/dist/playground/d.js +83 -5
- package/dist/playground/dotnet.js +6 -8
- package/dist/playground/fortran.d.ts +3 -0
- package/dist/playground/fortran.js +10 -1
- package/dist/playground/go.js +14 -3
- package/dist/playground/goDebugInstrumentation.js +184 -54
- package/dist/playground/java.js +3 -2
- package/dist/playground/javaRuntimeErrors.d.ts +9 -0
- package/dist/playground/javaRuntimeErrors.js +60 -0
- package/dist/playground/lean4Assets.d.ts +4 -4
- package/dist/playground/lfortran.js +10 -0
- package/dist/playground/lfortranAssets.d.ts +2 -2
- package/dist/playground/nim.js +22 -0
- package/dist/playground/objectivec.d.ts +3 -0
- package/dist/playground/objectivec.js +11 -2
- package/dist/playground/options.d.ts +2 -0
- package/dist/playground/python.js +8 -0
- package/dist/playground/runtimeAssetCache.d.ts +1 -32
- package/dist/playground/runtimeAssetCache.js +1 -193
- package/dist/playground/runtimeSession.d.ts +2 -2
- package/dist/playground/runtimeSession.js +11 -11
- package/dist/playground/rust.d.ts +6 -0
- package/dist/playground/rust.js +103 -6
- package/dist/playground/staticStdinRing.d.ts +2 -1
- package/dist/playground/staticStdinRing.js +21 -10
- package/dist/playground/staticWorkerRuntime.d.ts +15 -1
- package/dist/playground/staticWorkerRuntime.js +119 -17
- package/dist/playground/tinygo.d.ts +3 -0
- package/dist/playground/tinygo.js +81 -6
- package/dist/playground/v.d.ts +3 -0
- package/dist/playground/v.js +10 -1
- package/dist/playground/wasmAwkVersion.d.ts +13 -13
- package/dist/playground/wasmAwkVersion.js +4 -4
- package/dist/playground/wasmBqnVersion.d.ts +2 -2
- package/dist/playground/wasmBqnVersion.js +2 -2
- package/dist/playground/wasmC3Version.d.ts +2 -2
- package/dist/playground/wasmC3Version.js +2 -2
- package/dist/playground/wasmClojureScriptVersion.d.ts +2 -2
- package/dist/playground/wasmClojureScriptVersion.js +2 -2
- package/dist/playground/wasmCommonLispVersion.d.ts +2 -2
- package/dist/playground/wasmCommonLispVersion.js +2 -2
- package/dist/playground/wasmDIntegrity.d.ts +5 -5
- package/dist/playground/wasmDIntegrity.js +5 -5
- package/dist/playground/wasmDVersion.d.ts +16 -1
- package/dist/playground/wasmDVersion.js +16 -1
- package/dist/playground/wasmDebugVersion.d.ts +1 -1
- package/dist/playground/wasmDebugVersion.js +1 -1
- package/dist/playground/wasmForthVersion.d.ts +2 -2
- package/dist/playground/wasmForthVersion.js +2 -2
- package/dist/playground/wasmGleamVersion.d.ts +2 -2
- package/dist/playground/wasmGleamVersion.js +2 -2
- package/dist/playground/wasmGrainVersion.d.ts +2 -2
- package/dist/playground/wasmGrainVersion.js +2 -2
- package/dist/playground/wasmJVersion.d.ts +2 -2
- package/dist/playground/wasmJVersion.js +2 -2
- package/dist/playground/wasmJanetVersion.js +2 -2
- package/dist/playground/wasmJuliaVersion.js +2 -2
- package/dist/playground/wasmLean4Version.d.ts +5 -5
- package/dist/playground/wasmLean4Version.js +5 -5
- package/dist/playground/wasmLfortranVersion.d.ts +2 -2
- package/dist/playground/wasmLfortranVersion.js +2 -2
- package/dist/playground/wasmNimVersion.d.ts +9 -9
- package/dist/playground/wasmNimVersion.js +6 -6
- package/dist/playground/wasmOcamlVersion.d.ts +2 -2
- package/dist/playground/wasmOcamlVersion.js +1 -1
- package/dist/playground/wasmPascalVersion.js +2 -2
- package/dist/playground/wasmPerlVersion.js +2 -2
- package/dist/playground/wasmPrologVersion.d.ts +2 -2
- package/dist/playground/wasmPrologVersion.js +2 -2
- package/dist/playground/wasmReScriptVersion.d.ts +2 -2
- package/dist/playground/wasmReScriptVersion.js +2 -2
- package/dist/playground/wasmRustVersion.d.ts +8 -8
- package/dist/playground/wasmRustVersion.js +7 -7
- package/dist/playground/wasmTclVersion.d.ts +4 -4
- package/dist/playground/wasmTclVersion.js +2 -2
- package/dist/playground/wasmTinyGoVersion.d.ts +14 -14
- package/dist/playground/wasmTinyGoVersion.js +14 -14
- package/dist/playground/wasmTypeScriptVersion.d.ts +5 -5
- package/dist/playground/wasmTypeScriptVersion.js +5 -5
- package/dist/playground/worker/cachedClangCompile.js +52 -4
- package/dist/playground/worker/cachedClangExecute.js +6 -5
- package/dist/playground/worker/clang.js +32 -10
- package/dist/playground/worker/cobol.js +2 -2
- package/dist/playground/worker/d.js +12 -5
- package/dist/playground/worker/fortran.js +2 -2
- package/dist/playground/worker/go.js +16 -5
- package/dist/playground/worker/haskell.js +10 -7
- package/dist/playground/worker/java.js +75 -10
- package/dist/playground/worker/lua.js +2 -3
- package/dist/playground/worker/php.js +15 -9
- package/dist/playground/worker/python.js +46 -9
- package/dist/playground/worker/pythonExecution.js +17 -1
- package/dist/playground/worker/pythonWorkspaceFiles.d.ts +6 -0
- package/dist/playground/worker/pythonWorkspaceFiles.js +26 -0
- package/dist/playground/worker/pythonWorkspaceImports.d.ts +4 -0
- package/dist/playground/worker/pythonWorkspaceImports.js +129 -0
- package/dist/playground/worker/r.js +13 -6
- package/dist/playground/worker/rust.js +34 -2
- package/dist/playground/worker/sqlite.js +4 -3
- package/dist/playground/worker/tinygo.js +11 -3
- package/dist/playground/worker/typescript.js +2 -3
- package/dist/playground/worker/v.js +2 -2
- package/dist/playground/worker/wat.js +2 -3
- package/dist/playground/worker/zig.js +20 -6
- package/dist/playground/workerSession.d.ts +1 -28
- package/dist/playground/workerSession.js +1 -164
- package/package.json +20 -13
- package/dist/playground/worker/python.runtime.cases.js +0 -374
- /package/dist/playground/{worker/python.runtime.cases.d.ts → container/owner.worker.d.ts} +0 -0
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
export const SHELL_CONTROL_COMMAND = 'wasm-idle-shell-control';
|
|
2
|
+
export const SHELL_CONTROL_FUNCTION_ENV = 'BASH_FUNC___wasm_idle_poll%%';
|
|
3
|
+
export const SHELL_CONTROL_ROUTE = `/usr/local/bin/${SHELL_CONTROL_COMMAND}`;
|
|
4
|
+
export const SHELL_CONTROL_MOUNT = '/usr/share/wasm-idle-shell-control';
|
|
5
|
+
export const SHELL_CONTROL_RCFILE = `${SHELL_CONTROL_MOUNT}/rcfile`;
|
|
6
|
+
export const SHELL_CONTROL_REQUEST = '/tmp/.wasm-idle-shell-control-request';
|
|
7
|
+
const SHELL_INPUTRC = '/tmp/.wasm-idle-shell-inputrc';
|
|
8
|
+
const ADAPTER = 'website-shell-control-v1';
|
|
9
|
+
const MAX_GENERATION = 2147483647;
|
|
10
|
+
const encoder = new TextEncoder();
|
|
11
|
+
const abortError = (message) => new DOMException(message, 'AbortError');
|
|
12
|
+
const canonicalDirectory = (path) => path.length <= 4096 &&
|
|
13
|
+
path.startsWith('/') &&
|
|
14
|
+
!path.includes('\0') &&
|
|
15
|
+
(path === '/' ||
|
|
16
|
+
path
|
|
17
|
+
.slice(1)
|
|
18
|
+
.split('/')
|
|
19
|
+
.every((part) => part && part !== '.' && part !== '..'));
|
|
20
|
+
function rcfile(session, languages) {
|
|
21
|
+
return [
|
|
22
|
+
'# Trusted application rcfile. The mailbox is data and is never evaluated.',
|
|
23
|
+
'__wasm_idle_poll() {',
|
|
24
|
+
' local __wic_version __wic_session __wic_generation __wic_language __wic_extra __wic_oldpwd __wic_fd',
|
|
25
|
+
` local __wic_expected_session='${session}'`,
|
|
26
|
+
// WASIX dup2 restoration loses stdin's polling rights. Read through a fresh
|
|
27
|
+
// descriptor instead of temporarily replacing the live terminal's fd 0.
|
|
28
|
+
` [[ -f '${SHELL_CONTROL_REQUEST}' && -r '${SHELL_CONTROL_REQUEST}' ]] || return 0`,
|
|
29
|
+
` if builtin exec {__wic_fd}< '${SHELL_CONTROL_REQUEST}'; then :; else return 0; fi`,
|
|
30
|
+
' if IFS=\' \' builtin read -r -n 160 -u "$__wic_fd" __wic_version __wic_session __wic_generation __wic_language __wic_extra; then',
|
|
31
|
+
' builtin exec {__wic_fd}<&-',
|
|
32
|
+
' else',
|
|
33
|
+
' builtin exec {__wic_fd}<&-',
|
|
34
|
+
' return 0',
|
|
35
|
+
' fi',
|
|
36
|
+
' [[ "$__wic_version" == 1 && "$__wic_session" == "$__wic_expected_session" && -z "$__wic_extra" ]] || return 0',
|
|
37
|
+
' [[ "$__wic_generation" =~ ^[1-9][0-9]{0,9}$ ]] || return 0',
|
|
38
|
+
` (( __wic_generation <= ${MAX_GENERATION} && __wic_generation > \${__wasm_idle_control_generation:-0} )) || return 0`,
|
|
39
|
+
` case "$__wic_language" in ${languages.join('|')}) ;; *) return 0 ;; esac`,
|
|
40
|
+
' __wasm_idle_control_generation=$__wic_generation',
|
|
41
|
+
' __wic_oldpwd=$PWD',
|
|
42
|
+
' if builtin cd -P -- "/workspace/$__wic_language"; then',
|
|
43
|
+
` if '${SHELL_CONTROL_ROUTE}' 1 "$__wic_session" "$__wic_generation" "$__wic_language" ok; then :; else :; fi`,
|
|
44
|
+
' [[ "$__wic_oldpwd" != "$PWD" ]] && return 124',
|
|
45
|
+
' else',
|
|
46
|
+
` if '${SHELL_CONTROL_ROUTE}' 1 "$__wic_session" "$__wic_generation" "$__wic_language" failed; then :; else :; fi`,
|
|
47
|
+
' fi',
|
|
48
|
+
' return 0',
|
|
49
|
+
'}',
|
|
50
|
+
// The native primary Readline hook preserves both $? and PIPESTATUS.
|
|
51
|
+
// A PROMPT_COMMAND wrapper cannot restore PIPESTATUS and must not own polling.
|
|
52
|
+
'readonly -f __wasm_idle_poll',
|
|
53
|
+
''
|
|
54
|
+
].join('\n');
|
|
55
|
+
}
|
|
56
|
+
/** Host directory requests are read only at the producer's real primary-prompt boundary. */
|
|
57
|
+
export function createShellControl(options) {
|
|
58
|
+
const languages = [...new Set(options.languages)].sort();
|
|
59
|
+
if (!languages.length || languages.some((language) => !/^[A-Z][A-Z0-9_]{0,63}$/.test(language)))
|
|
60
|
+
throw new Error('Shell control requires a fixed language allowlist');
|
|
61
|
+
const allowed = new Set(languages);
|
|
62
|
+
const session = options.session ??
|
|
63
|
+
Array.from(crypto.getRandomValues(new Uint8Array(16)), (byte) => byte.toString(16).padStart(2, '0')).join('');
|
|
64
|
+
if (!/^[a-f0-9]{32}$/.test(session))
|
|
65
|
+
throw new Error('Invalid shell control session');
|
|
66
|
+
let cwd = options.initialDirectory ?? '/workspace';
|
|
67
|
+
if (cwd !== '/workspace' && !languages.some((language) => cwd === `/workspace/${language}`))
|
|
68
|
+
throw new Error('Invalid initial shell directory');
|
|
69
|
+
let filesystem, disposed = false, generation = 0, appliedGeneration = 0;
|
|
70
|
+
let pending, error = null;
|
|
71
|
+
let lastAcknowledgement;
|
|
72
|
+
const inspect = () => Object.freeze({
|
|
73
|
+
ready: Boolean(filesystem),
|
|
74
|
+
disposed,
|
|
75
|
+
cwd,
|
|
76
|
+
pendingCwd: pending?.cwd ?? null,
|
|
77
|
+
generation,
|
|
78
|
+
appliedGeneration,
|
|
79
|
+
error
|
|
80
|
+
});
|
|
81
|
+
const changed = () => {
|
|
82
|
+
try {
|
|
83
|
+
options.onChange?.(inspect());
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
/* Observers do not own protocol state. */
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
const currentFilesystem = () => {
|
|
90
|
+
if (disposed)
|
|
91
|
+
throw new Error('Shell directory control has been disposed');
|
|
92
|
+
if (!filesystem)
|
|
93
|
+
throw new Error('Shell directory control has not been set up');
|
|
94
|
+
return filesystem;
|
|
95
|
+
};
|
|
96
|
+
const ensureDirectory = (fs, path) => {
|
|
97
|
+
if (fs.lstat('/workspace').type !== 'directory' ||
|
|
98
|
+
fs.realpath('/workspace') !== '/workspace')
|
|
99
|
+
throw new Error('Workspace root is not a directory');
|
|
100
|
+
try {
|
|
101
|
+
if (fs.lstat(path).type !== 'directory' || fs.realpath(path) !== path)
|
|
102
|
+
throw new Error('Shell directory must be an actual project directory');
|
|
103
|
+
}
|
|
104
|
+
catch (reason) {
|
|
105
|
+
if (reason.code !== 'ENOENT')
|
|
106
|
+
throw reason;
|
|
107
|
+
fs.mkdir(path);
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
const writeRequest = (fs, value) => {
|
|
111
|
+
if (fs.lstat('/tmp').type !== 'directory' || fs.realpath('/tmp') !== '/tmp')
|
|
112
|
+
throw new Error('Shell control needs the private temporary mount');
|
|
113
|
+
try {
|
|
114
|
+
if (fs.lstat(SHELL_CONTROL_REQUEST).type !== 'file')
|
|
115
|
+
throw new Error('Shell control mailbox must be a regular file');
|
|
116
|
+
}
|
|
117
|
+
catch (reason) {
|
|
118
|
+
if (reason.code !== 'ENOENT')
|
|
119
|
+
throw reason;
|
|
120
|
+
}
|
|
121
|
+
fs.writeFile(SHELL_CONTROL_REQUEST, encoder.encode(value), { mode: 0o600 });
|
|
122
|
+
};
|
|
123
|
+
const acknowledge = (args, actualCwd) => {
|
|
124
|
+
if (disposed ||
|
|
125
|
+
!filesystem ||
|
|
126
|
+
args.length !== 5 ||
|
|
127
|
+
args[0] !== '1' ||
|
|
128
|
+
args[1] !== session ||
|
|
129
|
+
!/^[1-9][0-9]{0,9}$/.test(args[2]) ||
|
|
130
|
+
!allowed.has(args[3]) ||
|
|
131
|
+
!['ok', 'failed'].includes(args[4]) ||
|
|
132
|
+
!canonicalDirectory(actualCwd))
|
|
133
|
+
return 126;
|
|
134
|
+
const received = Number(args[2]), language = args[3], verdict = args[4];
|
|
135
|
+
if (verdict === 'ok' && actualCwd !== `/workspace/${language}`)
|
|
136
|
+
return 126;
|
|
137
|
+
if (received > MAX_GENERATION || received > generation)
|
|
138
|
+
return 126;
|
|
139
|
+
if (received < generation)
|
|
140
|
+
return 0; // A superseded completion cannot publish the new request.
|
|
141
|
+
if (!pending)
|
|
142
|
+
return lastAcknowledgement?.generation === received &&
|
|
143
|
+
lastAcknowledgement.language === language &&
|
|
144
|
+
lastAcknowledgement.cwd === actualCwd &&
|
|
145
|
+
lastAcknowledgement.verdict === verdict
|
|
146
|
+
? 0
|
|
147
|
+
: 126;
|
|
148
|
+
if (pending.language !== language || (verdict === 'ok' && actualCwd !== pending.cwd))
|
|
149
|
+
return 126;
|
|
150
|
+
const completed = pending;
|
|
151
|
+
pending = undefined;
|
|
152
|
+
cwd = actualCwd;
|
|
153
|
+
lastAcknowledgement = { generation: received, language, cwd: actualCwd, verdict };
|
|
154
|
+
if (verdict === 'ok') {
|
|
155
|
+
appliedGeneration = received;
|
|
156
|
+
error = null;
|
|
157
|
+
completed.resolve();
|
|
158
|
+
}
|
|
159
|
+
else {
|
|
160
|
+
error = `Bash could not enter ${completed.cwd}`;
|
|
161
|
+
completed.reject(new Error(error));
|
|
162
|
+
}
|
|
163
|
+
changed();
|
|
164
|
+
return 0;
|
|
165
|
+
};
|
|
166
|
+
const adapter = {
|
|
167
|
+
id: ADAPTER,
|
|
168
|
+
filesystem: 'live',
|
|
169
|
+
async start(request) {
|
|
170
|
+
if (request.fs !== currentFilesystem() ||
|
|
171
|
+
request.invocation.command !== SHELL_CONTROL_COMMAND)
|
|
172
|
+
throw new Error('Shell control command belongs to a different container');
|
|
173
|
+
const emptyOutput = () => new ReadableStream({
|
|
174
|
+
start(controller) {
|
|
175
|
+
controller.close();
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
return {
|
|
179
|
+
stdin: new WritableStream({
|
|
180
|
+
write(bytes) {
|
|
181
|
+
if (bytes.length)
|
|
182
|
+
throw new Error('Shell control does not consume program input');
|
|
183
|
+
}
|
|
184
|
+
}),
|
|
185
|
+
stdout: emptyOutput(),
|
|
186
|
+
stderr: emptyOutput(),
|
|
187
|
+
async run() {
|
|
188
|
+
return {
|
|
189
|
+
code: request.signal.aborted
|
|
190
|
+
? 143
|
|
191
|
+
: acknowledge(request.invocation.args, request.invocation.cwd)
|
|
192
|
+
};
|
|
193
|
+
},
|
|
194
|
+
async dispose() { }
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
};
|
|
198
|
+
const commands = Object.freeze({
|
|
199
|
+
[SHELL_CONTROL_COMMAND]: Object.freeze({
|
|
200
|
+
adapter: ADAPTER,
|
|
201
|
+
bytes: new Uint8Array(),
|
|
202
|
+
argv0: SHELL_CONTROL_COMMAND
|
|
203
|
+
})
|
|
204
|
+
});
|
|
205
|
+
const configuration = Object.freeze({
|
|
206
|
+
mountPath: SHELL_CONTROL_MOUNT,
|
|
207
|
+
rcfilePath: SHELL_CONTROL_RCFILE,
|
|
208
|
+
requestPath: SHELL_CONTROL_REQUEST,
|
|
209
|
+
// The pinned WASIX host requests inherited write access even for O_RDONLY.
|
|
210
|
+
// Standard Bash function import bootstraps the trusted body without opening
|
|
211
|
+
// a readonly file; the opt-in producer makes this one imported function readonly.
|
|
212
|
+
args: Object.freeze(['--noprofile', '--norc', '-i']),
|
|
213
|
+
env: Object.freeze({
|
|
214
|
+
WASM_IDLE_SHELL_CONTROL: '1',
|
|
215
|
+
INPUTRC: SHELL_INPUTRC,
|
|
216
|
+
[SHELL_CONTROL_FUNCTION_ENV]: `() ${rcfile(session, languages).match(/__wasm_idle_poll\(\) (\{[\s\S]*?\n\})/)[1]}`
|
|
217
|
+
})
|
|
218
|
+
});
|
|
219
|
+
return Object.freeze({
|
|
220
|
+
adapter,
|
|
221
|
+
commands,
|
|
222
|
+
route: Object.freeze({ path: SHELL_CONTROL_ROUTE, command: SHELL_CONTROL_COMMAND }),
|
|
223
|
+
setup(fs) {
|
|
224
|
+
if (disposed)
|
|
225
|
+
throw new Error('Shell directory control has been disposed');
|
|
226
|
+
if (filesystem) {
|
|
227
|
+
if (filesystem !== fs)
|
|
228
|
+
throw new Error('Shell control cannot share containers');
|
|
229
|
+
return configuration;
|
|
230
|
+
}
|
|
231
|
+
ensureDirectory(fs, cwd);
|
|
232
|
+
writeRequest(fs, `1 ${session} 0 -\n`);
|
|
233
|
+
// The pinned C locale otherwise interprets UTF-8 input as Meta keys.
|
|
234
|
+
// Keep this on the writable mount: WASIX overrequests rights on open.
|
|
235
|
+
fs.writeFile(SHELL_INPUTRC, encoder.encode('set input-meta on\nset convert-meta off\nset output-meta on\n'), { mode: 0o600 });
|
|
236
|
+
fs.mountReadonly(SHELL_CONTROL_MOUNT, [
|
|
237
|
+
{
|
|
238
|
+
path: 'rcfile',
|
|
239
|
+
type: 'file',
|
|
240
|
+
mode: 0o444,
|
|
241
|
+
data: encoder.encode(rcfile(session, languages))
|
|
242
|
+
}
|
|
243
|
+
]);
|
|
244
|
+
filesystem = fs;
|
|
245
|
+
changed();
|
|
246
|
+
return configuration;
|
|
247
|
+
},
|
|
248
|
+
requestDirectory(language) {
|
|
249
|
+
try {
|
|
250
|
+
const fs = currentFilesystem();
|
|
251
|
+
if (!allowed.has(language))
|
|
252
|
+
throw new Error('Unknown shell project directory');
|
|
253
|
+
if (pending?.language === language)
|
|
254
|
+
return pending.promise;
|
|
255
|
+
if (generation === MAX_GENERATION)
|
|
256
|
+
throw new Error('Shell directory generation exhausted');
|
|
257
|
+
const target = `/workspace/${language}`, nextGeneration = generation + 1;
|
|
258
|
+
ensureDirectory(fs, target);
|
|
259
|
+
writeRequest(fs, `1 ${session} ${nextGeneration} ${language}\n`);
|
|
260
|
+
pending?.reject(abortError('Shell directory request was superseded'));
|
|
261
|
+
let resolve, reject;
|
|
262
|
+
const promise = new Promise((yes, no) => {
|
|
263
|
+
resolve = yes;
|
|
264
|
+
reject = no;
|
|
265
|
+
});
|
|
266
|
+
pending = { language, cwd: target, promise, resolve, reject };
|
|
267
|
+
generation = nextGeneration;
|
|
268
|
+
error = null;
|
|
269
|
+
changed();
|
|
270
|
+
return promise;
|
|
271
|
+
}
|
|
272
|
+
catch (reason) {
|
|
273
|
+
return Promise.reject(reason);
|
|
274
|
+
}
|
|
275
|
+
},
|
|
276
|
+
onInvocation(invocation) {
|
|
277
|
+
return invocation.path === SHELL_CONTROL_ROUTE
|
|
278
|
+
? acknowledge(invocation.argv.slice(1), invocation.cwd)
|
|
279
|
+
: undefined;
|
|
280
|
+
},
|
|
281
|
+
inspect,
|
|
282
|
+
dispose() {
|
|
283
|
+
if (disposed)
|
|
284
|
+
return;
|
|
285
|
+
disposed = true;
|
|
286
|
+
pending?.reject(abortError('Shell directory control was disposed'));
|
|
287
|
+
pending = undefined;
|
|
288
|
+
if (filesystem) {
|
|
289
|
+
try {
|
|
290
|
+
if (filesystem.lstat(SHELL_CONTROL_REQUEST).type === 'file' &&
|
|
291
|
+
new TextDecoder()
|
|
292
|
+
.decode(filesystem.readFile(SHELL_CONTROL_REQUEST))
|
|
293
|
+
.startsWith(`1 ${session} `))
|
|
294
|
+
filesystem.unlink(SHELL_CONTROL_REQUEST);
|
|
295
|
+
}
|
|
296
|
+
catch {
|
|
297
|
+
/* The guest may already have removed the temporary mailbox. */
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
changed();
|
|
301
|
+
}
|
|
302
|
+
});
|
|
303
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { WorkspaceFile } from '../../../routes/workspaceCodec';
|
|
2
|
+
/** Reconcile independent terminal edits without discarding an unpublished editor revision. */
|
|
3
|
+
export declare function mergeContainerWorkspace(local: readonly WorkspaceFile[], baseline: readonly WorkspaceFile[], remote: readonly WorkspaceFile[], options?: {
|
|
4
|
+
acceptRemoteConflicts?: boolean;
|
|
5
|
+
}): {
|
|
6
|
+
files: WorkspaceFile[];
|
|
7
|
+
conflicts: string[];
|
|
8
|
+
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
const same = (a, b) => a === b ||
|
|
2
|
+
(!!a && !!b && a.content === b.content && (a.encoding ?? 'utf-8') === (b.encoding ?? 'utf-8'));
|
|
3
|
+
/** Reconcile independent terminal edits without discarding an unpublished editor revision. */
|
|
4
|
+
export function mergeContainerWorkspace(local, baseline, remote, options = {}) {
|
|
5
|
+
const localByPath = new Map(local.map((file) => [file.path, file]));
|
|
6
|
+
const baseByPath = new Map(baseline.map((file) => [file.path, file]));
|
|
7
|
+
const remoteByPath = new Map(remote.map((file) => [file.path, file]));
|
|
8
|
+
const conflicts = [];
|
|
9
|
+
const files = [];
|
|
10
|
+
for (const path of new Set([
|
|
11
|
+
...localByPath.keys(),
|
|
12
|
+
...baseByPath.keys(),
|
|
13
|
+
...remoteByPath.keys()
|
|
14
|
+
])) {
|
|
15
|
+
const here = localByPath.get(path), before = baseByPath.get(path), there = remoteByPath.get(path);
|
|
16
|
+
let selected;
|
|
17
|
+
if (same(here, before))
|
|
18
|
+
selected = there;
|
|
19
|
+
else if (same(there, before) || same(here, there))
|
|
20
|
+
selected = here;
|
|
21
|
+
else {
|
|
22
|
+
conflicts.push(path);
|
|
23
|
+
selected = options.acceptRemoteConflicts ? there : here;
|
|
24
|
+
}
|
|
25
|
+
if (selected)
|
|
26
|
+
files.push({ ...selected });
|
|
27
|
+
}
|
|
28
|
+
return { files, conflicts };
|
|
29
|
+
}
|
package/dist/playground/d.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type PlaygroundRuntimeAssets, type ResolvedDRuntimeAssetConfig } from './assets';
|
|
2
|
+
import { RuntimeAssetCache } from '@wasm-idle/core';
|
|
2
3
|
import { type CompilerDiagnostic, type SandboxExecutionOptions } from './options';
|
|
3
4
|
import type { Sandbox, SandboxProgress } from './sandbox';
|
|
4
5
|
declare class D implements Sandbox {
|
|
@@ -19,6 +20,8 @@ declare class D implements Sandbox {
|
|
|
19
20
|
pendingEof: boolean;
|
|
20
21
|
private activeOperation;
|
|
21
22
|
private disposed;
|
|
23
|
+
private runtimeAssetCache;
|
|
24
|
+
private ownsRuntimeAssetCache;
|
|
22
25
|
private disposePromise;
|
|
23
26
|
private readonly disposeCancellation;
|
|
24
27
|
private readonly workerSession;
|
|
@@ -37,8 +40,10 @@ declare class D implements Sandbox {
|
|
|
37
40
|
write(input: string): void;
|
|
38
41
|
eof(): void;
|
|
39
42
|
private flushPendingInput;
|
|
40
|
-
run(code: string, prepare: boolean,
|
|
43
|
+
run(code: string, prepare: boolean, log?: boolean, progress?: SandboxProgress, args?: string[], options?: SandboxExecutionOptions): Promise<boolean | string>;
|
|
44
|
+
private runLoaded;
|
|
41
45
|
private cancelOperation;
|
|
46
|
+
setRuntimeAssetCache(cache: RuntimeAssetCache): void;
|
|
42
47
|
kill(): void;
|
|
43
48
|
terminate(reason?: unknown): void;
|
|
44
49
|
clear(): Promise<void>;
|
package/dist/playground/d.js
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { resolveDRuntimeAssetConfig } from './assets';
|
|
2
|
-
import { BusyError, CancelledError, createRuntimeAssetsKey, DEFAULT_WORKSPACE_LIMITS, DiagnosticLimitError, OutputLimitError, RuntimeConfigurationError, TimeoutError, resolveExecutionLimits, validateExecutionWorkspace } from '@wasm-idle/core';
|
|
2
|
+
import { BusyError, CancelledError, createRuntimeAssetsKey, createRuntimeWasmModuleService, DEFAULT_WORKSPACE_LIMITS, DiagnosticLimitError, OutputLimitError, RuntimeConfigurationError, RuntimeAssetCache, TimeoutError, resolveExecutionLimits, validateExecutionWorkspace } from '@wasm-idle/core';
|
|
3
3
|
import { resolveSandboxExecutionArgs } from './options';
|
|
4
4
|
import { flushBufferedEof, flushQueuedStdin, resetBufferedStdin } from './stdinBuffer';
|
|
5
5
|
import { createWasmIdleSharedBuffer } from './sharedBuffer';
|
|
6
6
|
import { WorkerSession } from './workerSession';
|
|
7
7
|
import { reportWorkerInputReady, reportWorkerProgress } from './workerProgress';
|
|
8
|
+
import { WASM_D_COMPILER_PROFILE } from './wasmDVersion';
|
|
9
|
+
import { WASM_D_OUTER_ASSET_RECEIPTS } from './wasmDIntegrity';
|
|
8
10
|
const OUTPUT_ENCODER = new TextEncoder();
|
|
9
11
|
const abortReason = (signal, phase) => signal.reason !== undefined
|
|
10
12
|
? signal.reason
|
|
@@ -27,6 +29,8 @@ class D {
|
|
|
27
29
|
pendingEof = false;
|
|
28
30
|
activeOperation = null;
|
|
29
31
|
disposed = false;
|
|
32
|
+
runtimeAssetCache = new RuntimeAssetCache();
|
|
33
|
+
ownsRuntimeAssetCache = true;
|
|
30
34
|
disposePromise = null;
|
|
31
35
|
disposeCancellation = new CancelledError('D sandbox disposed', {
|
|
32
36
|
phase: 'dispose',
|
|
@@ -382,7 +386,19 @@ class D {
|
|
|
382
386
|
this.waitingForInput = false;
|
|
383
387
|
}
|
|
384
388
|
}
|
|
385
|
-
run(code, prepare,
|
|
389
|
+
async run(code, prepare, log = true, progress, args = [], options = {}) {
|
|
390
|
+
if (!this.worker && this.moduleUrl && this.outerIntegrity && !this.disposed) {
|
|
391
|
+
await this.load({
|
|
392
|
+
d: {
|
|
393
|
+
moduleUrl: this.moduleUrl,
|
|
394
|
+
manifestUrl: this.manifestUrl,
|
|
395
|
+
integrity: this.outerIntegrity
|
|
396
|
+
}
|
|
397
|
+
}, '', log, [], options, progress);
|
|
398
|
+
}
|
|
399
|
+
return this.runLoaded(code, prepare, log, progress, args, options);
|
|
400
|
+
}
|
|
401
|
+
runLoaded(code, prepare, _log = true, _prog, args = [], options = {}) {
|
|
386
402
|
let activeOperation;
|
|
387
403
|
try {
|
|
388
404
|
activeOperation = this.beginOperation('execute');
|
|
@@ -467,6 +483,12 @@ class D {
|
|
|
467
483
|
catch {
|
|
468
484
|
// Handler cleanup must not replace the operation result.
|
|
469
485
|
}
|
|
486
|
+
// Only immutable compiler modules survive an execution. Retire the worker, its
|
|
487
|
+
// Emscripten heaps, filesystem and compiled guest before the next run. A
|
|
488
|
+
// prepare-only call has executed no guest; keep its compiled artifact for
|
|
489
|
+
// the following execution instead of compiling the same source twice.
|
|
490
|
+
if (!prepare)
|
|
491
|
+
this.workerSession.reset();
|
|
470
492
|
return true;
|
|
471
493
|
};
|
|
472
494
|
const failRun = (error, disposeWorker = false) => {
|
|
@@ -549,15 +571,58 @@ class D {
|
|
|
549
571
|
return;
|
|
550
572
|
this.begin = Date.now();
|
|
551
573
|
try {
|
|
552
|
-
|
|
574
|
+
const modulePorts = {};
|
|
575
|
+
const profile = WASM_D_COMPILER_PROFILE;
|
|
576
|
+
// Custom runtime bundles keep their independent compiler path. A worker
|
|
577
|
+
// cannot choose the host's receipt, loader profile or authorization key.
|
|
578
|
+
if (typeof MessageChannel === 'function' &&
|
|
579
|
+
this.outerIntegrity?.['runtime/runtime-manifest.v1.json'].sha256 ===
|
|
580
|
+
profile.manifestSha256 &&
|
|
581
|
+
this.outerIntegrity?.['index.js'].sha256 ===
|
|
582
|
+
WASM_D_OUTER_ASSET_RECEIPTS['index.js'].sha256) {
|
|
583
|
+
for (const kind of ['ldc2', 'lld']) {
|
|
584
|
+
const service = createRuntimeWasmModuleService({
|
|
585
|
+
cache: this.runtimeAssetCache,
|
|
586
|
+
receipt: profile[kind],
|
|
587
|
+
identity: JSON.stringify([
|
|
588
|
+
'wasm-d-verified-compiler-v1',
|
|
589
|
+
this.activeDAssetsKey,
|
|
590
|
+
profile.version,
|
|
591
|
+
profile.manifestSha256,
|
|
592
|
+
kind,
|
|
593
|
+
...(kind === 'lld'
|
|
594
|
+
? [
|
|
595
|
+
profile.lldProfile,
|
|
596
|
+
profile.lldLoaderSha256,
|
|
597
|
+
profile.lldDataSha256
|
|
598
|
+
]
|
|
599
|
+
: [])
|
|
600
|
+
]),
|
|
601
|
+
compileKey: kind === 'lld'
|
|
602
|
+
? 'emscripten-lld16-instantiate-v1'
|
|
603
|
+
: 'ldc2-wasi-preview1-v1',
|
|
604
|
+
maxAssetBytes: limits.maxAssetBytes,
|
|
605
|
+
signal
|
|
606
|
+
});
|
|
607
|
+
activeOperation.cleanups.push(() => service.close());
|
|
608
|
+
modulePorts[kind] = service.port;
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
const message = {
|
|
553
612
|
code,
|
|
554
613
|
prepare,
|
|
555
614
|
buffer: this.buffer,
|
|
556
615
|
args: programArgs,
|
|
557
616
|
stdin,
|
|
558
617
|
fileName: workspace.activePath,
|
|
559
|
-
log: _log
|
|
560
|
-
|
|
618
|
+
log: _log,
|
|
619
|
+
...(modulePorts.ldc2 ? { compilerModulePorts: modulePorts } : {})
|
|
620
|
+
};
|
|
621
|
+
const transfers = Object.values(modulePorts);
|
|
622
|
+
if (transfers.length)
|
|
623
|
+
worker.postMessage(message, transfers);
|
|
624
|
+
else
|
|
625
|
+
worker.postMessage(message);
|
|
561
626
|
}
|
|
562
627
|
catch (error) {
|
|
563
628
|
failRun(error, true);
|
|
@@ -585,6 +650,17 @@ class D {
|
|
|
585
650
|
this.workerSession.terminate(reason);
|
|
586
651
|
this.cleanupOperation(operation);
|
|
587
652
|
}
|
|
653
|
+
setRuntimeAssetCache(cache) {
|
|
654
|
+
if (cache === this.runtimeAssetCache)
|
|
655
|
+
return;
|
|
656
|
+
if (this.worker || this.activeOperation || this.disposed) {
|
|
657
|
+
throw new Error('Cannot replace the D runtime asset cache while its worker is active or disposed');
|
|
658
|
+
}
|
|
659
|
+
if (this.ownsRuntimeAssetCache)
|
|
660
|
+
this.runtimeAssetCache.dispose();
|
|
661
|
+
this.runtimeAssetCache = cache;
|
|
662
|
+
this.ownsRuntimeAssetCache = false;
|
|
663
|
+
}
|
|
588
664
|
kill() {
|
|
589
665
|
this.terminate();
|
|
590
666
|
}
|
|
@@ -620,6 +696,8 @@ class D {
|
|
|
620
696
|
if (this.disposePromise)
|
|
621
697
|
return this.disposePromise;
|
|
622
698
|
this.disposed = true;
|
|
699
|
+
if (this.ownsRuntimeAssetCache)
|
|
700
|
+
this.runtimeAssetCache.dispose();
|
|
623
701
|
this.disposePromise = Promise.resolve();
|
|
624
702
|
const activeOperation = this.activeOperation;
|
|
625
703
|
delete this.worker;
|
|
@@ -495,15 +495,13 @@ class Dotnet {
|
|
|
495
495
|
const hasExplicitStdin = !prepare && explicitStdin !== undefined;
|
|
496
496
|
if (runUid !== this.uid)
|
|
497
497
|
return '';
|
|
498
|
-
if (!prepare &&
|
|
499
|
-
!hasExplicitStdin &&
|
|
500
|
-
this.pendingInput.length === 0 &&
|
|
501
|
-
!this.pendingEof &&
|
|
502
|
-
readsConsoleStdin(code)) {
|
|
498
|
+
if (!prepare && !hasExplicitStdin && !this.pendingEof && readsConsoleStdin(code)) {
|
|
503
499
|
reportWorkerInputReady(progress, `${this.languageLabel} runtime ready for input`);
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
500
|
+
// The program receives stdin as one string, so wait for EOF instead of
|
|
501
|
+
// starting with only the input written before the first chunk arrived.
|
|
502
|
+
while (runUid === this.uid && !this.pendingEof) {
|
|
503
|
+
await new Promise((resolve) => this.stdinWaiters.push(resolve));
|
|
504
|
+
}
|
|
507
505
|
}
|
|
508
506
|
if (runUid !== this.uid)
|
|
509
507
|
return '';
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { WorkerAssetBridge } from './assetBridge';
|
|
2
|
+
import type { RuntimeAssetCache } from './runtimeAssetCache';
|
|
2
3
|
import { type PlaygroundRuntimeAssets } from './assets';
|
|
3
4
|
import type { SandboxExecutionOptions } from './options';
|
|
4
5
|
import type { Sandbox, SandboxProgress } from './sandbox';
|
|
@@ -18,6 +19,7 @@ declare class Fortran implements Sandbox {
|
|
|
18
19
|
assetBridge: WorkerAssetBridge | null;
|
|
19
20
|
activeFortranAssetsKey: string;
|
|
20
21
|
private activeOperation;
|
|
22
|
+
private runtimeAssetCache?;
|
|
21
23
|
private preserveOperationOnWorkerDispose;
|
|
22
24
|
private disposed;
|
|
23
25
|
private disposePromise;
|
|
@@ -25,6 +27,7 @@ declare class Fortran implements Sandbox {
|
|
|
25
27
|
private readonly workerSession;
|
|
26
28
|
private disposedConfigurationError;
|
|
27
29
|
private requireOperationIdle;
|
|
30
|
+
setRuntimeAssetCache(cache: RuntimeAssetCache): void;
|
|
28
31
|
private beginOperation;
|
|
29
32
|
private isOperationActive;
|
|
30
33
|
private releaseOperation;
|
|
@@ -37,6 +37,7 @@ class Fortran {
|
|
|
37
37
|
assetBridge = null;
|
|
38
38
|
activeFortranAssetsKey = '';
|
|
39
39
|
activeOperation = null;
|
|
40
|
+
runtimeAssetCache;
|
|
40
41
|
preserveOperationOnWorkerDispose = false;
|
|
41
42
|
disposed = false;
|
|
42
43
|
disposePromise = null;
|
|
@@ -88,6 +89,14 @@ class Fortran {
|
|
|
88
89
|
});
|
|
89
90
|
}
|
|
90
91
|
}
|
|
92
|
+
setRuntimeAssetCache(cache) {
|
|
93
|
+
if (this.runtimeAssetCache === cache)
|
|
94
|
+
return;
|
|
95
|
+
if (this.disposed || this.worker || this.activeOperation) {
|
|
96
|
+
throw new RuntimeConfigurationError('Cannot replace an active Fortran compiler cache');
|
|
97
|
+
}
|
|
98
|
+
this.runtimeAssetCache = cache;
|
|
99
|
+
}
|
|
91
100
|
beginOperation(phase) {
|
|
92
101
|
this.requireOperationIdle();
|
|
93
102
|
const operation = {
|
|
@@ -287,7 +296,7 @@ class Fortran {
|
|
|
287
296
|
}
|
|
288
297
|
let assetBridge;
|
|
289
298
|
try {
|
|
290
|
-
assetBridge = new WorkerAssetBridge(worker, 'clang', clangAssets, progress, limits.maxAssetBytes);
|
|
299
|
+
assetBridge = new WorkerAssetBridge(worker, 'clang', clangAssets, progress, limits.maxAssetBytes, false, this.runtimeAssetCache);
|
|
291
300
|
assetBridge.setExecutionPersistentCache(options.persistentCache);
|
|
292
301
|
}
|
|
293
302
|
catch (error) {
|
package/dist/playground/go.js
CHANGED
|
@@ -328,7 +328,7 @@ class Go {
|
|
|
328
328
|
worker.onmessage = null;
|
|
329
329
|
return;
|
|
330
330
|
}
|
|
331
|
-
const { output, results, error, buffer, diagnostic, progress, debugEvent, phase } = event.data;
|
|
331
|
+
const { output, results, error, fatal, buffer, diagnostic, progress, debugEvent, phase } = event.data;
|
|
332
332
|
if (phase === 'compile' || phase === 'execute') {
|
|
333
333
|
if (phase !== activePhase)
|
|
334
334
|
armTimeout(phase);
|
|
@@ -342,8 +342,12 @@ class Go {
|
|
|
342
342
|
reportWorkerProgress(_prog, progress);
|
|
343
343
|
if (output)
|
|
344
344
|
this.output(output);
|
|
345
|
+
// The worker compiles only the active source, which the compiler names main.go.
|
|
345
346
|
if (diagnostic)
|
|
346
|
-
this.oncompilerdiagnostic?.(
|
|
347
|
+
this.oncompilerdiagnostic?.(options.activePath &&
|
|
348
|
+
/(?:^|[\\/])main\.go$/u.test(diagnostic.fileName ?? '')
|
|
349
|
+
? { ...diagnostic, fileName: options.activePath }
|
|
350
|
+
: diagnostic);
|
|
347
351
|
if (debugEvent)
|
|
348
352
|
this.ondebug?.(debugEvent);
|
|
349
353
|
if (results) {
|
|
@@ -367,7 +371,14 @@ class Go {
|
|
|
367
371
|
this.waitingForInput = false;
|
|
368
372
|
this.pendingEof = false;
|
|
369
373
|
this.workerSession.complete(operation);
|
|
370
|
-
this.
|
|
374
|
+
if (fatal === true && this.worker === worker) {
|
|
375
|
+
// The worker abandoned a program that may still be running; terminating
|
|
376
|
+
// it stops that program, and disposal reports the debug stop.
|
|
377
|
+
this.workerSession.reset();
|
|
378
|
+
}
|
|
379
|
+
else {
|
|
380
|
+
this.ondebug?.({ type: 'stop' });
|
|
381
|
+
}
|
|
371
382
|
reject(error);
|
|
372
383
|
}
|
|
373
384
|
};
|