wasm-idle 1.0.12 → 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 +150 -37
- package/dist/playground/applicationAssets.js +2 -0
- 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/commonlisp.d.ts +8 -0
- package/dist/playground/commonlisp.js +33 -0
- package/dist/playground/commonlispAssets.d.ts +30 -0
- package/dist/playground/commonlispAssets.js +184 -0
- 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.d.ts +1 -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/index.d.ts +1 -1
- package/dist/playground/index.js +14 -0
- 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/javaRuntimeStdin.d.ts +9 -0
- package/dist/playground/javaRuntimeStdin.js +145 -0
- package/dist/playground/lean4.d.ts +13 -0
- package/dist/playground/lean4.js +50 -0
- package/dist/playground/lean4Assets.d.ts +25 -0
- package/dist/playground/lean4Assets.js +123 -0
- package/dist/playground/lfortran.js +10 -0
- package/dist/playground/lfortranAssets.d.ts +2 -2
- package/dist/playground/loadingProgress.js +4 -1
- 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 +32 -0
- package/dist/playground/wasmCommonLispVersion.js +33 -0
- 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 +110 -0
- package/dist/playground/wasmLean4Version.js +111 -0
- 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 +92 -39
- package/dist/playground/worker/lua.js +2 -3
- package/dist/playground/worker/php.js +15 -9
- package/dist/playground/worker/python.js +131 -131
- package/dist/playground/worker/pythonDebugPreview.d.ts +2 -0
- package/dist/playground/worker/pythonDebugPreview.js +55 -0
- package/dist/playground/worker/pythonExecution.d.ts +8 -0
- package/dist/playground/worker/pythonExecution.js +183 -0
- package/dist/playground/worker/pythonStdio.d.ts +32 -0
- package/dist/playground/worker/pythonStdio.js +253 -0
- 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 +30 -15
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import { AssetIntegrityError, defineRuntimeRegistryManifest, UnsupportedBrowserFeatureError, preflightRuntimeAssets, ResourceLimitError, RuntimeConfigurationError, verifyRuntimeAssetIntegrity } from '@wasm-idle/core';
|
|
2
|
+
import { WASM_COMMONLISP_PROFILE as profile } from './wasmCommonLispVersion';
|
|
3
|
+
export const COMMONLISP_INITIAL_MEMORY_BYTES = 64 * 1024 * 1024;
|
|
4
|
+
/** ecl.wasm is delivered as the producer's pinned gzip bytes; `.bin` keeps hosts from adding
|
|
5
|
+
* Content-Encoding, so the browser never transparently decodes it before verification. */
|
|
6
|
+
export const COMMONLISP_WASM_STORAGE_PATH = 'ecl.wasm.gz.bin';
|
|
7
|
+
const identityAsset = (name, mediaType) => ({
|
|
8
|
+
key: name,
|
|
9
|
+
path: name,
|
|
10
|
+
compressedSha256: profile.assets[name].sha256,
|
|
11
|
+
uncompressedSha256: profile.assets[name].sha256,
|
|
12
|
+
compressedBytes: profile.assets[name].bytes,
|
|
13
|
+
uncompressedBytes: profile.assets[name].bytes,
|
|
14
|
+
encoding: 'identity',
|
|
15
|
+
mediaType
|
|
16
|
+
});
|
|
17
|
+
const deliveryAssets = [
|
|
18
|
+
identityAsset('producer-receipt.json', 'application/json'),
|
|
19
|
+
identityAsset('ecl.mjs', 'text/javascript'),
|
|
20
|
+
{
|
|
21
|
+
key: 'ecl.wasm',
|
|
22
|
+
path: COMMONLISP_WASM_STORAGE_PATH,
|
|
23
|
+
compressedSha256: profile.assets['ecl.wasm.gz'].sha256,
|
|
24
|
+
uncompressedSha256: profile.runtime['ecl.wasm'].sha256,
|
|
25
|
+
compressedBytes: profile.assets['ecl.wasm.gz'].bytes,
|
|
26
|
+
uncompressedBytes: profile.runtime['ecl.wasm'].bytes,
|
|
27
|
+
encoding: 'gzip',
|
|
28
|
+
mediaType: 'application/wasm'
|
|
29
|
+
}
|
|
30
|
+
];
|
|
31
|
+
export const COMMONLISP_RUNTIME_MANIFEST = defineRuntimeRegistryManifest({
|
|
32
|
+
schemaVersion: 2,
|
|
33
|
+
manifestId: 'wasm-idle/commonlisp',
|
|
34
|
+
revision: profile.assets['producer-receipt.json'].sha256,
|
|
35
|
+
runtimes: [
|
|
36
|
+
{
|
|
37
|
+
runtimeId: 'COMMONLISP',
|
|
38
|
+
identity: {
|
|
39
|
+
languageId: 'COMMONLISP',
|
|
40
|
+
implementationId: 'ecl-emscripten',
|
|
41
|
+
implementationVersion: profile.version,
|
|
42
|
+
profile: {
|
|
43
|
+
profileId: profile.profileId,
|
|
44
|
+
manifestSchemaVersion: 1,
|
|
45
|
+
manifestSha256: profile.assets['producer-receipt.json'].sha256,
|
|
46
|
+
protocolVersion: 1,
|
|
47
|
+
trustProfileId: profile.profileId,
|
|
48
|
+
trustProfileSchemaVersion: 1
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
capabilities: {
|
|
52
|
+
stdin: 'streaming',
|
|
53
|
+
workspace: true,
|
|
54
|
+
abort: true,
|
|
55
|
+
artifacts: false,
|
|
56
|
+
streamingOutput: true
|
|
57
|
+
},
|
|
58
|
+
workerLifetime: { mode: 'per-run' },
|
|
59
|
+
requiredBrowserFeatures: ['wasm', 'wasm-exceptions', 'decompression-stream'],
|
|
60
|
+
assetRoot: '.',
|
|
61
|
+
assets: deliveryAssets,
|
|
62
|
+
contracts: {
|
|
63
|
+
routeId: 'COMMONLISP',
|
|
64
|
+
runtimeAssetKey: 'commonlisp',
|
|
65
|
+
documentationId: 'commonlisp',
|
|
66
|
+
syncTarget: 'wasm-commonlisp',
|
|
67
|
+
browserTestId: 'COMMONLISP'
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
]
|
|
71
|
+
});
|
|
72
|
+
export function resolveCommonLispRuntimeAssetConfig(assets, currentUrl) {
|
|
73
|
+
const root = typeof assets === 'string' ? assets : assets.rootUrl || '';
|
|
74
|
+
const override = typeof assets === 'string' ? undefined : assets.commonlisp?.baseUrl;
|
|
75
|
+
const baseUrl = new URL(override ?? `${root.replace(/\/+$/, '')}/wasm-commonlisp/`, currentUrl);
|
|
76
|
+
if (!['http:', 'https:'].includes(baseUrl.protocol) ||
|
|
77
|
+
baseUrl.username ||
|
|
78
|
+
baseUrl.password ||
|
|
79
|
+
baseUrl.search ||
|
|
80
|
+
baseUrl.hash) {
|
|
81
|
+
throw new RuntimeConfigurationError('Common Lisp requires a credential-free HTTP(S) asset directory.', { runtimeId: 'COMMONLISP' });
|
|
82
|
+
}
|
|
83
|
+
if (!baseUrl.pathname.endsWith('/'))
|
|
84
|
+
baseUrl.pathname += '/';
|
|
85
|
+
const workerUrl = new URL('runner-worker.js', baseUrl);
|
|
86
|
+
workerUrl.searchParams.set('v', profile.workerReceipt.sha256);
|
|
87
|
+
return {
|
|
88
|
+
baseUrl: baseUrl.href,
|
|
89
|
+
workerUrl: workerUrl.href,
|
|
90
|
+
manifestFingerprint: profile.assets['producer-receipt.json'].sha256,
|
|
91
|
+
workerReceipt: profile.workerReceipt
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
export async function preflightCommonLispRuntimeAssets(baseUrl, options) {
|
|
95
|
+
if (options.limits.maxWasmMemoryBytes < COMMONLISP_INITIAL_MEMORY_BYTES) {
|
|
96
|
+
throw new ResourceLimitError('Common Lisp needs at least 64 MiB of Wasm memory.', {
|
|
97
|
+
runtimeId: 'COMMONLISP',
|
|
98
|
+
phase: 'asset',
|
|
99
|
+
resource: 'wasm-memory',
|
|
100
|
+
actual: COMMONLISP_INITIAL_MEMORY_BYTES,
|
|
101
|
+
limit: options.limits.maxWasmMemoryBytes
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
const loaded = new Map();
|
|
105
|
+
const total = deliveryAssets.reduce((sum, asset) => sum + asset.compressedBytes, 0);
|
|
106
|
+
const result = await preflightRuntimeAssets({
|
|
107
|
+
manifest: COMMONLISP_RUNTIME_MANIFEST,
|
|
108
|
+
runtimeId: 'COMMONLISP',
|
|
109
|
+
rootUrl: baseUrl,
|
|
110
|
+
assetUrls: Object.fromEntries(deliveryAssets.map((asset) => [
|
|
111
|
+
asset.key,
|
|
112
|
+
new URL(`${asset.path}?v=${asset.compressedSha256}`, baseUrl)
|
|
113
|
+
])),
|
|
114
|
+
limits: options.limits,
|
|
115
|
+
persistentCache: options.persistentCache,
|
|
116
|
+
signal: options.signal,
|
|
117
|
+
fetch: options.fetch,
|
|
118
|
+
redirect: 'error',
|
|
119
|
+
requireExactResponseUrl: true,
|
|
120
|
+
maxConcurrentDownloads: 2,
|
|
121
|
+
maxTotalDeliveryBytes: total,
|
|
122
|
+
reportProgress(progress) {
|
|
123
|
+
loaded.set(progress.assetKey, progress.loadedBytes);
|
|
124
|
+
options.reportProgress?.(0.04 + (0.13 * [...loaded.values()].reduce((a, b) => a + b, 0)) / total, `Verifying ECL ${progress.assetKey}`);
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
const wasm = await decompressCommonLispWasm(result.assets['ecl.wasm'].bytes, options.signal);
|
|
128
|
+
return Object.freeze({
|
|
129
|
+
fingerprint: profile.assets['producer-receipt.json'].sha256,
|
|
130
|
+
'producer-receipt.json': result.assets['producer-receipt.json'].bytes,
|
|
131
|
+
'ecl.mjs': result.assets['ecl.mjs'].bytes,
|
|
132
|
+
'ecl.wasm': wasm
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
/** Decode the verified gzip delivery into an exactly sized buffer and verify the logical receipt. */
|
|
136
|
+
export async function decompressCommonLispWasm(compressed, signal) {
|
|
137
|
+
const context = { runtimeId: 'COMMONLISP', profileId: profile.profileId };
|
|
138
|
+
if (typeof DecompressionStream !== 'function')
|
|
139
|
+
throw new UnsupportedBrowserFeatureError('DecompressionStream(gzip)', {
|
|
140
|
+
...context,
|
|
141
|
+
phase: 'asset'
|
|
142
|
+
});
|
|
143
|
+
const expected = profile.runtime['ecl.wasm'];
|
|
144
|
+
const body = new Response(Uint8Array.from(compressed)).body;
|
|
145
|
+
const reader = body.pipeThrough(new DecompressionStream('gzip')).getReader();
|
|
146
|
+
const wasm = new Uint8Array(expected.bytes);
|
|
147
|
+
let offset = 0;
|
|
148
|
+
try {
|
|
149
|
+
for (;;) {
|
|
150
|
+
signal?.throwIfAborted();
|
|
151
|
+
const { done, value } = await reader.read();
|
|
152
|
+
if (done)
|
|
153
|
+
break;
|
|
154
|
+
if (offset + value.byteLength > wasm.byteLength)
|
|
155
|
+
throw new AssetIntegrityError('ECL ecl.wasm gzip output exceeds its receipt', context);
|
|
156
|
+
wasm.set(value, offset);
|
|
157
|
+
offset += value.byteLength;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
catch (error) {
|
|
161
|
+
void reader.cancel().catch(() => undefined);
|
|
162
|
+
if (error instanceof AssetIntegrityError || signal?.aborted)
|
|
163
|
+
throw error;
|
|
164
|
+
throw new AssetIntegrityError('ECL ecl.wasm gzip decompression failed', {
|
|
165
|
+
...context,
|
|
166
|
+
cause: error
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
if (offset !== wasm.byteLength)
|
|
170
|
+
throw new AssetIntegrityError('ECL ecl.wasm gzip output is truncated', context);
|
|
171
|
+
await verifyRuntimeAssetIntegrity({
|
|
172
|
+
asset: 'ecl.wasm',
|
|
173
|
+
bytes: wasm,
|
|
174
|
+
expected: {
|
|
175
|
+
...expected,
|
|
176
|
+
uncompressedSha256: expected.sha256,
|
|
177
|
+
uncompressedBytes: expected.bytes
|
|
178
|
+
},
|
|
179
|
+
stage: 'uncompressed',
|
|
180
|
+
mimeType: 'application/wasm',
|
|
181
|
+
...context
|
|
182
|
+
});
|
|
183
|
+
return wasm;
|
|
184
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { BrowserClangArtifact, BrowserClangCompileRequest } from '@wasm-idle/llvm-core/clang';
|
|
2
|
+
import type { CompilerDiagnostic } from '@wasm-idle/llvm-core/clang';
|
|
2
3
|
/** Only immutable compiler output lives here, never a worker, instance, or filesystem. */
|
|
3
4
|
export declare class CompiledArtifactCache {
|
|
4
5
|
private entries;
|
|
@@ -9,7 +10,8 @@ export declare class CompiledArtifactCache {
|
|
|
9
10
|
private maxBytes;
|
|
10
11
|
constructor(maxEntries?: number, maxBytes?: number);
|
|
11
12
|
get(key: string): BrowserClangArtifact | undefined;
|
|
12
|
-
|
|
13
|
+
getDiagnostics(key: string): CompilerDiagnostic[];
|
|
14
|
+
set(key: string, artifact: BrowserClangArtifact, diagnostics?: CompilerDiagnostic[]): void;
|
|
13
15
|
clear(): void;
|
|
14
16
|
stats(): {
|
|
15
17
|
hits: number;
|
|
@@ -1,3 +1,12 @@
|
|
|
1
|
+
function snapshotArtifact(artifact) {
|
|
2
|
+
const { bytes, wasm, ...metadata } = artifact;
|
|
3
|
+
return {
|
|
4
|
+
...structuredClone(metadata),
|
|
5
|
+
// Compiled modules are immutable. Byte views must not retain an oversized backing buffer.
|
|
6
|
+
wasm,
|
|
7
|
+
bytes: ArrayBuffer.isView(bytes) ? Uint8Array.from(bytes) : bytes.slice(0)
|
|
8
|
+
};
|
|
9
|
+
}
|
|
1
10
|
/** Only immutable compiler output lives here, never a worker, instance, or filesystem. */
|
|
2
11
|
export class CompiledArtifactCache {
|
|
3
12
|
entries = new Map();
|
|
@@ -7,6 +16,10 @@ export class CompiledArtifactCache {
|
|
|
7
16
|
maxEntries;
|
|
8
17
|
maxBytes;
|
|
9
18
|
constructor(maxEntries = 8, maxBytes = 32 * 1024 * 1024) {
|
|
19
|
+
if (!Number.isSafeInteger(maxEntries) || maxEntries < 0)
|
|
20
|
+
throw new TypeError('Invalid compiled artifact cache maxEntries');
|
|
21
|
+
if (!Number.isSafeInteger(maxBytes) || maxBytes < 0)
|
|
22
|
+
throw new TypeError('Invalid compiled artifact cache maxBytes');
|
|
10
23
|
this.maxEntries = maxEntries;
|
|
11
24
|
this.maxBytes = maxBytes;
|
|
12
25
|
}
|
|
@@ -19,17 +32,29 @@ export class CompiledArtifactCache {
|
|
|
19
32
|
this.hits++;
|
|
20
33
|
this.entries.delete(key);
|
|
21
34
|
this.entries.set(key, entry);
|
|
22
|
-
return entry.artifact;
|
|
35
|
+
return snapshotArtifact(entry.artifact);
|
|
36
|
+
}
|
|
37
|
+
getDiagnostics(key) {
|
|
38
|
+
return this.entries.get(key)?.diagnostics.map((diagnostic) => ({ ...diagnostic })) ?? [];
|
|
23
39
|
}
|
|
24
|
-
set(key, artifact) {
|
|
25
|
-
|
|
40
|
+
set(key, artifact, diagnostics = []) {
|
|
41
|
+
// A zero budget disables retention without disabling compilation.
|
|
42
|
+
if (this.maxEntries === 0 || this.maxBytes === 0)
|
|
43
|
+
return;
|
|
44
|
+
const size = artifact.bytes.byteLength +
|
|
45
|
+
key.length * 2 +
|
|
46
|
+
(diagnostics.length ? JSON.stringify(diagnostics).length * 2 : 0);
|
|
26
47
|
if (size > this.maxBytes)
|
|
27
48
|
return;
|
|
28
49
|
const old = this.entries.get(key);
|
|
29
50
|
if (old)
|
|
30
51
|
this.bytes -= old.size;
|
|
31
52
|
this.entries.delete(key);
|
|
32
|
-
this.entries.set(key, {
|
|
53
|
+
this.entries.set(key, {
|
|
54
|
+
artifact: snapshotArtifact(artifact),
|
|
55
|
+
diagnostics: diagnostics.map((diagnostic) => ({ ...diagnostic })),
|
|
56
|
+
size
|
|
57
|
+
});
|
|
33
58
|
this.bytes += size;
|
|
34
59
|
while (this.entries.size > this.maxEntries || this.bytes > this.maxBytes) {
|
|
35
60
|
const oldest = this.entries.keys().next().value;
|
|
@@ -52,7 +77,7 @@ export class CompiledArtifactCache {
|
|
|
52
77
|
}
|
|
53
78
|
export function clangCompileKey(runtimeBaseUrl, maxAssetBytes, request, languageSysroots = false) {
|
|
54
79
|
return JSON.stringify([
|
|
55
|
-
'wasm-idle/clang-cache-
|
|
80
|
+
'wasm-idle/clang-cache-v3',
|
|
56
81
|
runtimeBaseUrl,
|
|
57
82
|
maxAssetBytes,
|
|
58
83
|
languageSysroots,
|
|
@@ -62,6 +87,7 @@ export function clangCompileKey(runtimeBaseUrl, maxAssetBytes, request, language
|
|
|
62
87
|
request.compileArgs,
|
|
63
88
|
request.cVersion,
|
|
64
89
|
request.cppVersion,
|
|
90
|
+
request.maxWasmMemoryBytes,
|
|
65
91
|
[...(request.workspaceFiles || [])].sort((a, b) => a.path.localeCompare(b.path))
|
|
66
92
|
]);
|
|
67
93
|
}
|
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { ContainerWorkspaceEntry } from './protocol';
|
|
3
|
+
import {
|
|
4
|
+
buildExplorerModel,
|
|
5
|
+
explorerBreadcrumbs,
|
|
6
|
+
EXPLORER_PAGE_SIZE,
|
|
7
|
+
resolveExplorerDirectory,
|
|
8
|
+
visibleExplorerRows
|
|
9
|
+
} from './explorer';
|
|
10
|
+
|
|
11
|
+
interface Props {
|
|
12
|
+
entries: readonly ContainerWorkspaceEntry[];
|
|
13
|
+
selectedPath?: string;
|
|
14
|
+
initialDirectory?: string;
|
|
15
|
+
busy?: boolean;
|
|
16
|
+
onOpenFile: (entry: ContainerWorkspaceEntry) => void;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
let {
|
|
20
|
+
entries,
|
|
21
|
+
selectedPath,
|
|
22
|
+
initialDirectory = '',
|
|
23
|
+
busy = false,
|
|
24
|
+
onOpenFile
|
|
25
|
+
}: Props = $props();
|
|
26
|
+
let requestedDirectory = $state('');
|
|
27
|
+
let expanded = $state(new Set<string>());
|
|
28
|
+
let offset = $state(0);
|
|
29
|
+
const model = $derived(buildExplorerModel(entries));
|
|
30
|
+
const directory = $derived(resolveExplorerDirectory(model, requestedDirectory));
|
|
31
|
+
const breadcrumbs = $derived(explorerBreadcrumbs(directory));
|
|
32
|
+
const visible = $derived(visibleExplorerRows(model, directory, expanded, offset));
|
|
33
|
+
|
|
34
|
+
$effect(() => {
|
|
35
|
+
requestedDirectory = initialDirectory;
|
|
36
|
+
expanded = new Set();
|
|
37
|
+
offset = 0;
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
function enterDirectory(path: string) {
|
|
41
|
+
requestedDirectory = resolveExplorerDirectory(model, path);
|
|
42
|
+
offset = 0;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function toggleDirectory(path: string) {
|
|
46
|
+
const next = new Set(expanded);
|
|
47
|
+
if (next.has(path)) next.delete(path);
|
|
48
|
+
else next.add(path);
|
|
49
|
+
expanded = next;
|
|
50
|
+
offset = 0;
|
|
51
|
+
}
|
|
52
|
+
</script>
|
|
53
|
+
|
|
54
|
+
<div class="container-explorer">
|
|
55
|
+
<nav aria-label="Workspace folder" class="breadcrumbs">
|
|
56
|
+
<button
|
|
57
|
+
type="button"
|
|
58
|
+
class="parent-button"
|
|
59
|
+
aria-label="Go to parent folder"
|
|
60
|
+
title="Go to parent folder"
|
|
61
|
+
disabled={!directory}
|
|
62
|
+
onclick={() => enterDirectory(model.entries.get(directory)?.parent ?? '')}>↑</button
|
|
63
|
+
>
|
|
64
|
+
<ol>
|
|
65
|
+
{#each breadcrumbs as crumb (crumb.path)}
|
|
66
|
+
<li>
|
|
67
|
+
<button
|
|
68
|
+
type="button"
|
|
69
|
+
title={crumb.path ? `/workspace/${crumb.path}` : '/workspace'}
|
|
70
|
+
aria-current={crumb.path === directory ? 'location' : undefined}
|
|
71
|
+
onclick={() => enterDirectory(crumb.path)}>{crumb.name}</button
|
|
72
|
+
>
|
|
73
|
+
</li>
|
|
74
|
+
{/each}
|
|
75
|
+
</ol>
|
|
76
|
+
</nav>
|
|
77
|
+
<div class="files">
|
|
78
|
+
{#if visible.rows.length === 0}
|
|
79
|
+
<p class="empty">This folder is empty.</p>
|
|
80
|
+
{:else}
|
|
81
|
+
<ul aria-label="Workspace files">
|
|
82
|
+
{#each visible.rows as row (row.entry.path)}
|
|
83
|
+
{@const entry = row.entry}
|
|
84
|
+
<li style:padding-left={`${Math.min(row.depth, 16) * 12}px`}>
|
|
85
|
+
{#if entry.type === 'directory'}
|
|
86
|
+
<button
|
|
87
|
+
type="button"
|
|
88
|
+
class="toggle"
|
|
89
|
+
aria-label={`${row.expanded ? 'Collapse' : 'Expand'} /workspace/${entry.path}`}
|
|
90
|
+
aria-expanded={row.expanded}
|
|
91
|
+
onclick={() => toggleDirectory(entry.path)}
|
|
92
|
+
>{row.expanded ? '▾' : '▸'}</button
|
|
93
|
+
>
|
|
94
|
+
<button
|
|
95
|
+
type="button"
|
|
96
|
+
class="entry directory"
|
|
97
|
+
data-container-directory={entry.path}
|
|
98
|
+
aria-label={`Open folder /workspace/${entry.path}`}
|
|
99
|
+
title={`/workspace/${entry.path}/`}
|
|
100
|
+
onclick={() => enterDirectory(entry.path)}
|
|
101
|
+
>
|
|
102
|
+
<span class="material-symbols-outlined" aria-hidden="true"
|
|
103
|
+
>folder</span
|
|
104
|
+
>
|
|
105
|
+
<span>{entry.name}</span>
|
|
106
|
+
</button>
|
|
107
|
+
{:else if entry.type === 'file'}
|
|
108
|
+
<button
|
|
109
|
+
type="button"
|
|
110
|
+
class="entry file"
|
|
111
|
+
class:active={entry.path === selectedPath}
|
|
112
|
+
data-container-path={entry.path}
|
|
113
|
+
aria-current={entry.path === selectedPath ? 'true' : undefined}
|
|
114
|
+
title={`/workspace/${entry.path} (${entry.size} bytes)`}
|
|
115
|
+
disabled={busy}
|
|
116
|
+
onclick={() => onOpenFile(entry)}
|
|
117
|
+
>
|
|
118
|
+
<span class="material-symbols-outlined" aria-hidden="true"
|
|
119
|
+
>draft</span
|
|
120
|
+
>
|
|
121
|
+
<span>{entry.name}</span>
|
|
122
|
+
</button>
|
|
123
|
+
{:else}
|
|
124
|
+
<div
|
|
125
|
+
class="entry symlink"
|
|
126
|
+
title={`/workspace/${entry.path} (symbolic link; target is not followed)`}
|
|
127
|
+
>
|
|
128
|
+
<span class="material-symbols-outlined" aria-hidden="true"
|
|
129
|
+
>link</span
|
|
130
|
+
>
|
|
131
|
+
<span>{entry.name}</span>
|
|
132
|
+
<small>link</small>
|
|
133
|
+
</div>
|
|
134
|
+
{/if}
|
|
135
|
+
</li>
|
|
136
|
+
{/each}
|
|
137
|
+
</ul>
|
|
138
|
+
{/if}
|
|
139
|
+
</div>
|
|
140
|
+
{#if visible.offset > 0 || visible.remaining > 0}
|
|
141
|
+
<div class="pagination">
|
|
142
|
+
{#if visible.offset > 0}<button
|
|
143
|
+
type="button"
|
|
144
|
+
onclick={() => (offset = visible.offset - EXPLORER_PAGE_SIZE)}
|
|
145
|
+
>Previous files</button
|
|
146
|
+
>{/if}
|
|
147
|
+
<span aria-live="polite"
|
|
148
|
+
>{visible.offset + 1}–{visible.offset + visible.rows.length} of {visible.total}</span
|
|
149
|
+
>
|
|
150
|
+
{#if visible.remaining > 0}<button
|
|
151
|
+
type="button"
|
|
152
|
+
onclick={() => (offset = visible.offset + EXPLORER_PAGE_SIZE)}
|
|
153
|
+
>Show more ({visible.remaining} remaining)</button
|
|
154
|
+
>{/if}
|
|
155
|
+
</div>
|
|
156
|
+
{/if}
|
|
157
|
+
</div>
|
|
158
|
+
|
|
159
|
+
<style>
|
|
160
|
+
.container-explorer {
|
|
161
|
+
display: flex;
|
|
162
|
+
flex-direction: column;
|
|
163
|
+
min-height: 36px;
|
|
164
|
+
overflow: hidden;
|
|
165
|
+
}
|
|
166
|
+
.breadcrumbs {
|
|
167
|
+
display: flex;
|
|
168
|
+
align-items: flex-start;
|
|
169
|
+
gap: 4px;
|
|
170
|
+
padding: 4px 0;
|
|
171
|
+
border-bottom: 1px solid var(--line-soft, #e5e7eb);
|
|
172
|
+
}
|
|
173
|
+
.breadcrumbs ol {
|
|
174
|
+
display: flex;
|
|
175
|
+
flex-wrap: wrap;
|
|
176
|
+
align-items: center;
|
|
177
|
+
gap: 2px;
|
|
178
|
+
min-width: 0;
|
|
179
|
+
margin: 0;
|
|
180
|
+
padding: 0;
|
|
181
|
+
list-style: none;
|
|
182
|
+
}
|
|
183
|
+
.breadcrumbs li {
|
|
184
|
+
display: flex;
|
|
185
|
+
align-items: center;
|
|
186
|
+
min-width: 0;
|
|
187
|
+
}
|
|
188
|
+
.breadcrumbs li + li::before {
|
|
189
|
+
content: '/';
|
|
190
|
+
color: var(--text-4, #888);
|
|
191
|
+
font-size: 10px;
|
|
192
|
+
}
|
|
193
|
+
button {
|
|
194
|
+
border: 0;
|
|
195
|
+
border-radius: 6px;
|
|
196
|
+
background: transparent;
|
|
197
|
+
color: var(--text-2, #535c6d);
|
|
198
|
+
cursor: pointer;
|
|
199
|
+
font: inherit;
|
|
200
|
+
}
|
|
201
|
+
button:disabled {
|
|
202
|
+
cursor: not-allowed;
|
|
203
|
+
opacity: 0.6;
|
|
204
|
+
}
|
|
205
|
+
button:hover:enabled,
|
|
206
|
+
button.active {
|
|
207
|
+
background: var(--primary-soft, #eaf2ff);
|
|
208
|
+
color: var(--primary-strong, #2563eb);
|
|
209
|
+
}
|
|
210
|
+
button:focus-visible {
|
|
211
|
+
outline: 2px solid var(--primary, #3182f6);
|
|
212
|
+
outline-offset: -2px;
|
|
213
|
+
}
|
|
214
|
+
.breadcrumbs button {
|
|
215
|
+
max-width: 200px;
|
|
216
|
+
padding: 4px 5px;
|
|
217
|
+
overflow-wrap: anywhere;
|
|
218
|
+
text-align: left;
|
|
219
|
+
font: 10px var(--mono, monospace);
|
|
220
|
+
}
|
|
221
|
+
.breadcrumbs button[aria-current] {
|
|
222
|
+
color: var(--text, #1f2937);
|
|
223
|
+
}
|
|
224
|
+
.breadcrumbs .parent-button {
|
|
225
|
+
flex: 0 0 26px;
|
|
226
|
+
text-align: center;
|
|
227
|
+
font-size: 15px;
|
|
228
|
+
}
|
|
229
|
+
.files {
|
|
230
|
+
min-height: 36px;
|
|
231
|
+
overflow: auto;
|
|
232
|
+
}
|
|
233
|
+
.files ul {
|
|
234
|
+
margin: 0;
|
|
235
|
+
padding: 4px 0;
|
|
236
|
+
list-style: none;
|
|
237
|
+
}
|
|
238
|
+
.files li {
|
|
239
|
+
display: flex;
|
|
240
|
+
align-items: center;
|
|
241
|
+
box-sizing: border-box;
|
|
242
|
+
min-width: 0;
|
|
243
|
+
}
|
|
244
|
+
.entry {
|
|
245
|
+
display: flex;
|
|
246
|
+
align-items: center;
|
|
247
|
+
flex: 1 1 auto;
|
|
248
|
+
gap: 6px;
|
|
249
|
+
min-width: 0;
|
|
250
|
+
min-height: 30px;
|
|
251
|
+
padding: 5px 8px;
|
|
252
|
+
text-align: left;
|
|
253
|
+
font: 11px var(--mono, monospace);
|
|
254
|
+
color: var(--text-2, #535c6d);
|
|
255
|
+
}
|
|
256
|
+
.entry > span:last-child {
|
|
257
|
+
min-width: 0;
|
|
258
|
+
overflow: hidden;
|
|
259
|
+
text-overflow: ellipsis;
|
|
260
|
+
white-space: nowrap;
|
|
261
|
+
}
|
|
262
|
+
.entry.file,
|
|
263
|
+
.entry.symlink {
|
|
264
|
+
margin-left: 24px;
|
|
265
|
+
}
|
|
266
|
+
.toggle {
|
|
267
|
+
flex: 0 0 24px;
|
|
268
|
+
width: 24px;
|
|
269
|
+
min-height: 30px;
|
|
270
|
+
padding: 0;
|
|
271
|
+
font-size: 13px;
|
|
272
|
+
}
|
|
273
|
+
.material-symbols-outlined {
|
|
274
|
+
flex: 0 0 auto;
|
|
275
|
+
font-size: 16px;
|
|
276
|
+
}
|
|
277
|
+
.symlink {
|
|
278
|
+
color: var(--text-3, #7a8393);
|
|
279
|
+
}
|
|
280
|
+
.symlink span:nth-child(2) {
|
|
281
|
+
overflow: hidden;
|
|
282
|
+
text-overflow: ellipsis;
|
|
283
|
+
white-space: nowrap;
|
|
284
|
+
}
|
|
285
|
+
.symlink small {
|
|
286
|
+
margin-left: auto;
|
|
287
|
+
font-size: 9px;
|
|
288
|
+
}
|
|
289
|
+
.empty {
|
|
290
|
+
margin: 8px;
|
|
291
|
+
color: var(--text-3, #7a8393);
|
|
292
|
+
font-size: 11px;
|
|
293
|
+
}
|
|
294
|
+
.pagination {
|
|
295
|
+
display: flex;
|
|
296
|
+
flex-wrap: wrap;
|
|
297
|
+
align-items: center;
|
|
298
|
+
gap: 5px;
|
|
299
|
+
border-top: 1px solid var(--line-soft, #e5e7eb);
|
|
300
|
+
padding: 5px 0;
|
|
301
|
+
color: var(--text-3, #7a8393);
|
|
302
|
+
font-size: 10px;
|
|
303
|
+
}
|
|
304
|
+
.pagination button {
|
|
305
|
+
padding: 5px;
|
|
306
|
+
}
|
|
307
|
+
</style>
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { SvelteComponentTyped } from "svelte";
|
|
2
|
+
declare const __propDef: {
|
|
3
|
+
props: Record<string, never>;
|
|
4
|
+
events: {
|
|
5
|
+
[evt: string]: CustomEvent<any>;
|
|
6
|
+
};
|
|
7
|
+
slots: {};
|
|
8
|
+
};
|
|
9
|
+
export type ContainerExplorerProps = typeof __propDef.props;
|
|
10
|
+
export type ContainerExplorerEvents = typeof __propDef.events;
|
|
11
|
+
export type ContainerExplorerSlots = typeof __propDef.slots;
|
|
12
|
+
export default class ContainerExplorer extends SvelteComponentTyped<ContainerExplorerProps, ContainerExplorerEvents, ContainerExplorerSlots> {
|
|
13
|
+
}
|
|
14
|
+
export {};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { PlaygroundBinding, SandboxRuntimeAssets } from '../sandbox';
|
|
2
|
+
import type { TerminalShellBinding } from '@wasm-idle/terminal';
|
|
3
|
+
import type { WorkspaceFile as ContainerWorkspaceFile } from '@wasm-idle/core';
|
|
4
|
+
import { type ContainerWorkerClient } from './client';
|
|
5
|
+
import type { ContainerWorkerEvent, ContainerWorkspaceEntry } from './protocol';
|
|
6
|
+
type BindingOptions = {
|
|
7
|
+
applicationRootURL: string;
|
|
8
|
+
workspaceId: string;
|
|
9
|
+
getLanguage?: () => string;
|
|
10
|
+
/** Deterministic transport contract tests only. */
|
|
11
|
+
clientFactory?: () => ContainerWorkerClient;
|
|
12
|
+
};
|
|
13
|
+
export interface ContainerPlaygroundBinding extends PlaygroundBinding {
|
|
14
|
+
readonly shell: TerminalShellBinding;
|
|
15
|
+
inspect(): Promise<unknown>;
|
|
16
|
+
setShellDirectory(language: string): Promise<void>;
|
|
17
|
+
syncWorkspace(language: string, files: readonly ContainerWorkspaceFile[]): Promise<void>;
|
|
18
|
+
readWorkspace(language: string): Promise<ContainerWorkspaceFile[]>;
|
|
19
|
+
listWorkspaceFiles(): Promise<ContainerWorkspaceEntry[]>;
|
|
20
|
+
publishedWorkspace(language: string): ContainerWorkspaceFile[];
|
|
21
|
+
acceptWorkspace(language: string, files: readonly ContainerWorkspaceFile[]): void;
|
|
22
|
+
onWorkspaceChange(listener: (event: ContainerWorkerEvent) => void): () => void;
|
|
23
|
+
}
|
|
24
|
+
/** Existing Terminal/Debug controls use this facade; every execution belongs to the owner Container. */
|
|
25
|
+
export declare function createContainerPlaygroundBinding(runtimeAssets: SandboxRuntimeAssets, options: BindingOptions): ContainerPlaygroundBinding;
|
|
26
|
+
export {};
|