@swifttui/web 0.0.13 → 0.0.15
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 +24 -10
- package/dist/index.d.ts +9 -0
- package/dist/index.js +9 -0
- package/dist/manifest.d.ts +2 -0
- package/dist/manifest.js +2 -0
- package/dist/src/AccessibilityTree.js +156 -0
- package/dist/src/AccessibilityTree.js.map +1 -0
- package/dist/src/BoxDrawingRenderer.js +1106 -0
- package/dist/src/BoxDrawingRenderer.js.map +1 -0
- package/dist/src/WebHostApp.d.ts +41 -0
- package/dist/src/WebHostApp.js +135 -0
- package/dist/src/WebHostApp.js.map +1 -0
- package/dist/src/WebHostSceneManifest.d.ts +18 -0
- package/dist/src/WebHostSceneManifest.js +70 -0
- package/dist/src/WebHostSceneManifest.js.map +1 -0
- package/dist/src/WebHostSceneRuntime.d.ts +112 -0
- package/dist/src/WebHostSceneRuntime.js +651 -0
- package/dist/src/WebHostSceneRuntime.js.map +1 -0
- package/dist/src/WebHostSurfaceTransport.d.ts +166 -0
- package/dist/src/WebHostSurfaceTransport.js +252 -0
- package/dist/src/WebHostSurfaceTransport.js.map +1 -0
- package/dist/src/WebHostTerminalStyle.d.ts +92 -0
- package/dist/src/WebHostTerminalStyle.js +277 -0
- package/dist/src/WebHostTerminalStyle.js.map +1 -0
- package/dist/src/WebHostTestFixtures.d.ts +5 -0
- package/dist/src/WebHostTestFixtures.js +9 -0
- package/dist/src/WebHostTestFixtures.js.map +1 -0
- package/dist/src/WebSocketSceneBridge.d.ts +53 -0
- package/dist/src/WebSocketSceneBridge.js +124 -0
- package/dist/src/WebSocketSceneBridge.js.map +1 -0
- package/dist/src/wasi/BrowserWASIBridge.d.ts +33 -0
- package/dist/src/wasi/BrowserWASIBridge.js +97 -0
- package/dist/src/wasi/BrowserWASIBridge.js.map +1 -0
- package/dist/src/wasi/SharedInputQueue.d.ts +31 -0
- package/dist/src/wasi/SharedInputQueue.js +102 -0
- package/dist/src/wasi/SharedInputQueue.js.map +1 -0
- package/dist/src/wasi/StdIOPipe.d.ts +15 -0
- package/dist/src/wasi/StdIOPipe.js +56 -0
- package/dist/src/wasi/StdIOPipe.js.map +1 -0
- package/dist/src/wasi/WasiPollScheduler.js +114 -0
- package/dist/src/wasi/WasiPollScheduler.js.map +1 -0
- package/dist/src/wasi/WasmSceneRuntime.d.ts +23 -0
- package/dist/src/wasi/WasmSceneRuntime.js +119 -0
- package/dist/src/wasi/WasmSceneRuntime.js.map +1 -0
- package/dist/src/wasi/WasmSceneWorker.d.ts +27 -0
- package/dist/src/wasi/WasmSceneWorker.js +109 -0
- package/dist/src/wasi/WasmSceneWorker.js.map +1 -0
- package/dist/testing.d.ts +2 -0
- package/dist/testing.js +2 -0
- package/dist/wasi-worker.d.ts +2 -0
- package/dist/wasi-worker.js +2 -0
- package/dist/wasi.d.ts +6 -0
- package/dist/wasi.js +6 -0
- package/dist/websocket.d.ts +2 -0
- package/dist/websocket.js +2 -0
- package/package.json +49 -18
- package/AGENTS.md +0 -52
- package/cli.ts +0 -168
- package/index.html +0 -50
- package/index.ts +0 -8
- package/manifest.ts +0 -1
- package/src/AccessibilityTree.ts +0 -262
- package/src/BoxDrawingRenderer.ts +0 -585
- package/src/PublicEntrypointBoundary.test.ts +0 -20
- package/src/WebHostApp.test.ts +0 -222
- package/src/WebHostApp.ts +0 -269
- package/src/WebHostSceneManifest.test.ts +0 -38
- package/src/WebHostSceneManifest.ts +0 -156
- package/src/WebHostSceneRuntime.test.ts +0 -1982
- package/src/WebHostSceneRuntime.ts +0 -1142
- package/src/WebHostSurfaceTransport.test.ts +0 -362
- package/src/WebHostSurfaceTransport.ts +0 -691
- package/src/WebHostTerminalStyle.test.ts +0 -123
- package/src/WebHostTerminalStyle.ts +0 -471
- package/src/WebHostTestFixtures.ts +0 -10
- package/src/WebSocketSceneBridge.test.ts +0 -198
- package/src/WebSocketSceneBridge.ts +0 -233
- package/src/browser.ts +0 -59
- package/src/wasi/BrowserWASIBridge.test.ts +0 -168
- package/src/wasi/BrowserWASIBridge.ts +0 -167
- package/src/wasi/SharedInputQueue.test.ts +0 -146
- package/src/wasi/SharedInputQueue.ts +0 -199
- package/src/wasi/StdIOPipe.ts +0 -72
- package/src/wasi/WasiPollScheduler.test.ts +0 -176
- package/src/wasi/WasiPollScheduler.ts +0 -305
- package/src/wasi/WasmSceneRuntime.ts +0 -205
- package/src/wasi/WasmSceneWorker.ts +0 -182
- package/testing.ts +0 -1
- package/tsconfig.json +0 -29
- package/wasi-worker.ts +0 -1
- package/wasi.ts +0 -4
- package/websocket.ts +0 -1
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { WebHostSceneRuntime } from "../WebHostSceneRuntime.js";
|
|
2
|
+
import { SharedInputQueueWriter, createSharedInputQueue } from "./SharedInputQueue.js";
|
|
3
|
+
//#region src/wasi/WasmSceneRuntime.ts
|
|
4
|
+
const workerModuleURL = new URL("./wasm-scene-worker.js", import.meta.url);
|
|
5
|
+
function createWasmSceneRuntimeFactory(wasmURL, factoryOptions = {}) {
|
|
6
|
+
return (options) => {
|
|
7
|
+
const runtime = new WasmSceneRuntime(options, wasmURL, factoryOptions);
|
|
8
|
+
factoryOptions.onRuntimeCreated?.(runtime);
|
|
9
|
+
return runtime;
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
var WasmSceneRuntime = class extends WebHostSceneRuntime {
|
|
13
|
+
bridge;
|
|
14
|
+
wasmURL;
|
|
15
|
+
onSceneResize;
|
|
16
|
+
workerModuleURL;
|
|
17
|
+
inputQueue;
|
|
18
|
+
inputWriter;
|
|
19
|
+
detachBridgeInputListener;
|
|
20
|
+
detachResizeListener;
|
|
21
|
+
worker;
|
|
22
|
+
didMount = false;
|
|
23
|
+
constructor(options, wasmURL, factoryOptions) {
|
|
24
|
+
let inputQueue;
|
|
25
|
+
let inputWriter;
|
|
26
|
+
try {
|
|
27
|
+
inputQueue = createSharedInputQueue();
|
|
28
|
+
inputWriter = new SharedInputQueueWriter(inputQueue);
|
|
29
|
+
} catch (error) {
|
|
30
|
+
console.error("[SwiftTUIWeb] failed to create shared stdin queue", error);
|
|
31
|
+
}
|
|
32
|
+
super({
|
|
33
|
+
...options,
|
|
34
|
+
onInput: (chunk) => {
|
|
35
|
+
try {
|
|
36
|
+
inputWriter?.write(chunk);
|
|
37
|
+
} catch (error) {
|
|
38
|
+
console.error("[SwiftTUIWeb] failed to enqueue terminal input", error);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
this.bridge = options.bridge;
|
|
43
|
+
this.wasmURL = wasmURL;
|
|
44
|
+
this.onSceneResize = factoryOptions.onSceneResize;
|
|
45
|
+
this.workerModuleURL = factoryOptions.workerModuleURL ?? workerModuleURL;
|
|
46
|
+
this.inputQueue = inputQueue;
|
|
47
|
+
this.inputWriter = inputWriter;
|
|
48
|
+
}
|
|
49
|
+
async mount() {
|
|
50
|
+
await super.mount();
|
|
51
|
+
if (this.didMount) return;
|
|
52
|
+
this.didMount = true;
|
|
53
|
+
this.detachBridgeInputListener = this.bridge?.stdin.subscribe((chunk) => {
|
|
54
|
+
this.inputWriter?.write(chunk);
|
|
55
|
+
});
|
|
56
|
+
this.detachResizeListener = this.bridge?.subscribeResize((columns, rows, cellWidth, cellHeight) => {
|
|
57
|
+
this.onSceneResize?.({
|
|
58
|
+
sceneId: this.descriptor.id,
|
|
59
|
+
columns,
|
|
60
|
+
rows,
|
|
61
|
+
cellWidth,
|
|
62
|
+
cellHeight
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
const initialColumns = Number(this.bridge?.environment.TUIGUI_COLUMNS ?? "0") || 0;
|
|
66
|
+
const initialRows = Number(this.bridge?.environment.TUIGUI_ROWS ?? "0") || 0;
|
|
67
|
+
if (!this.bridge && initialColumns > 0 && initialRows > 0) this.onSceneResize?.({
|
|
68
|
+
sceneId: this.descriptor.id,
|
|
69
|
+
columns: initialColumns,
|
|
70
|
+
rows: initialRows
|
|
71
|
+
});
|
|
72
|
+
if (!this.inputQueue || !this.inputWriter || !this.bridge) {
|
|
73
|
+
this.writeOutput("\r\nSwiftTUI WASI browser runtime requires SharedArrayBuffer-backed stdin. Serve the app with COOP/COEP headers.\r\n");
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
this.worker = new Worker(this.workerModuleURL, { type: "module" });
|
|
77
|
+
this.worker.addEventListener("message", (event) => {
|
|
78
|
+
this.handleWorkerMessage(event.data);
|
|
79
|
+
});
|
|
80
|
+
this.worker.addEventListener("error", (event) => {
|
|
81
|
+
this.bridge?.stderr.write(`\nSwiftTUI WASI worker failed: ${event.message || "unknown worker error"}\n`);
|
|
82
|
+
});
|
|
83
|
+
const environment = { ...this.bridge.environment };
|
|
84
|
+
const message = {
|
|
85
|
+
type: "start",
|
|
86
|
+
wasmURL: this.wasmURL.href,
|
|
87
|
+
environment,
|
|
88
|
+
inputQueue: this.inputQueue
|
|
89
|
+
};
|
|
90
|
+
this.worker.postMessage(message);
|
|
91
|
+
}
|
|
92
|
+
dispose() {
|
|
93
|
+
this.detachBridgeInputListener?.();
|
|
94
|
+
this.detachResizeListener?.();
|
|
95
|
+
this.inputWriter?.close();
|
|
96
|
+
this.worker?.terminate();
|
|
97
|
+
super.dispose();
|
|
98
|
+
}
|
|
99
|
+
handleWorkerMessage(message) {
|
|
100
|
+
switch (message.type) {
|
|
101
|
+
case "stdout":
|
|
102
|
+
this.bridge?.stdout.write(message.chunk);
|
|
103
|
+
break;
|
|
104
|
+
case "stderr":
|
|
105
|
+
this.bridge?.stderr.write(message.chunk);
|
|
106
|
+
break;
|
|
107
|
+
case "exit":
|
|
108
|
+
if (message.code !== 0) this.bridge?.stderr.write(`\nSwiftTUI WASI app exited with code ${message.code}.\n`);
|
|
109
|
+
break;
|
|
110
|
+
case "error":
|
|
111
|
+
this.bridge?.stderr.write(`\nFailed to start SwiftTUI WASI app: ${message.message}\n`);
|
|
112
|
+
break;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
//#endregion
|
|
117
|
+
export { createWasmSceneRuntimeFactory };
|
|
118
|
+
|
|
119
|
+
//# sourceMappingURL=WasmSceneRuntime.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"WasmSceneRuntime.js","names":[],"sources":["../../../src/wasi/WasmSceneRuntime.ts"],"sourcesContent":["import {\n WebHostSceneRuntime,\n type WebHostSceneRuntimeOptions,\n} from \"../WebHostSceneRuntime.ts\";\nimport {\n encodeResizeControlMessage,\n type BrowserWASIBridge,\n} from \"./BrowserWASIBridge.ts\";\n\nimport {\n SharedInputQueueWriter,\n createSharedInputQueue,\n type SharedInputQueueBuffers,\n} from \"./SharedInputQueue.ts\";\n\nconst workerModuleURL = new URL(\"./wasm-scene-worker.js\", import.meta.url);\n\ninterface WorkerStartMessage {\n type: \"start\";\n wasmURL: string;\n environment: Record<string, string>;\n inputQueue: SharedInputQueueBuffers;\n}\n\ninterface WorkerOutputMessage {\n type: \"stdout\" | \"stderr\";\n chunk: Uint8Array;\n}\n\ninterface WorkerExitMessage {\n type: \"exit\";\n code: number;\n}\n\ninterface WorkerErrorMessage {\n type: \"error\";\n message: string;\n}\n\ntype WorkerMessage = WorkerOutputMessage | WorkerExitMessage | WorkerErrorMessage;\n\nexport interface WasmSceneResizeEvent {\n sceneId: string;\n columns: number;\n rows: number;\n cellWidth?: number;\n cellHeight?: number;\n}\n\nexport interface WasmSceneRuntimeHandle {\n readonly descriptor: WebHostSceneRuntime[\"descriptor\"];\n sendInput(chunk: Uint8Array): void;\n}\n\nexport interface WasmSceneRuntimeFactoryOptions {\n onSceneResize?(event: WasmSceneResizeEvent): void;\n onRuntimeCreated?(runtime: WasmSceneRuntimeHandle): void;\n workerModuleURL?: string | URL;\n}\n\nexport function createWasmSceneRuntimeFactory(\n wasmURL: URL,\n factoryOptions: WasmSceneRuntimeFactoryOptions = {}\n): (options: WebHostSceneRuntimeOptions) => WebHostSceneRuntime {\n return (options) => {\n const runtime = new WasmSceneRuntime(options, wasmURL, factoryOptions);\n factoryOptions.onRuntimeCreated?.(runtime);\n return runtime;\n };\n}\n\nclass WasmSceneRuntime extends WebHostSceneRuntime {\n private readonly bridge?: BrowserWASIBridge;\n private readonly wasmURL: URL;\n private readonly onSceneResize?: (event: WasmSceneResizeEvent) => void;\n private readonly workerModuleURL: string | URL;\n private readonly inputQueue?: SharedInputQueueBuffers;\n private readonly inputWriter?: SharedInputQueueWriter;\n\n private detachBridgeInputListener?: () => void;\n private detachResizeListener?: () => void;\n private worker?: Worker;\n private didMount = false;\n\n constructor(\n options: WebHostSceneRuntimeOptions,\n wasmURL: URL,\n factoryOptions: WasmSceneRuntimeFactoryOptions\n ) {\n let inputQueue: SharedInputQueueBuffers | undefined;\n let inputWriter: SharedInputQueueWriter | undefined;\n\n try {\n inputQueue = createSharedInputQueue();\n inputWriter = new SharedInputQueueWriter(inputQueue);\n } catch (error) {\n console.error(\"[SwiftTUIWeb] failed to create shared stdin queue\", error);\n }\n\n super({\n ...options,\n onInput: (chunk) => {\n try {\n inputWriter?.write(chunk);\n } catch (error) {\n console.error(\"[SwiftTUIWeb] failed to enqueue terminal input\", error);\n }\n },\n });\n\n this.bridge = options.bridge;\n this.wasmURL = wasmURL;\n this.onSceneResize = factoryOptions.onSceneResize;\n this.workerModuleURL = factoryOptions.workerModuleURL ?? workerModuleURL;\n this.inputQueue = inputQueue;\n this.inputWriter = inputWriter;\n }\n\n override async mount(): Promise<void> {\n await super.mount();\n if (this.didMount) {\n return;\n }\n\n this.didMount = true;\n this.detachBridgeInputListener = this.bridge?.stdin.subscribe((chunk) => {\n this.inputWriter?.write(chunk);\n });\n this.detachResizeListener = this.bridge?.subscribeResize((columns, rows, cellWidth, cellHeight) => {\n this.onSceneResize?.({\n sceneId: this.descriptor.id,\n columns,\n rows,\n cellWidth,\n cellHeight,\n });\n });\n\n const initialColumns = Number(this.bridge?.environment.TUIGUI_COLUMNS ?? \"0\") || 0;\n const initialRows = Number(this.bridge?.environment.TUIGUI_ROWS ?? \"0\") || 0;\n if (!this.bridge && initialColumns > 0 && initialRows > 0) {\n this.onSceneResize?.({\n sceneId: this.descriptor.id,\n columns: initialColumns,\n rows: initialRows,\n });\n }\n\n if (!this.inputQueue || !this.inputWriter || !this.bridge) {\n this.writeOutput(\n \"\\r\\nSwiftTUI WASI browser runtime requires SharedArrayBuffer-backed stdin. Serve the app with COOP/COEP headers.\\r\\n\"\n );\n return;\n }\n\n this.worker = new Worker(this.workerModuleURL, { type: \"module\" });\n this.worker.addEventListener(\"message\", (event: MessageEvent<WorkerMessage>) => {\n this.handleWorkerMessage(event.data);\n });\n this.worker.addEventListener(\"error\", (event) => {\n this.bridge?.stderr.write(\n `\\nSwiftTUI WASI worker failed: ${event.message || \"unknown worker error\"}\\n`\n );\n });\n\n const environment = { ...this.bridge.environment };\n\n const message: WorkerStartMessage = {\n type: \"start\",\n wasmURL: this.wasmURL.href,\n environment,\n inputQueue: this.inputQueue,\n };\n this.worker.postMessage(message);\n }\n\n override dispose(): void {\n this.detachBridgeInputListener?.();\n this.detachResizeListener?.();\n this.inputWriter?.close();\n this.worker?.terminate();\n super.dispose();\n }\n\n private handleWorkerMessage(\n message: WorkerMessage\n ): void {\n switch (message.type) {\n case \"stdout\":\n this.bridge?.stdout.write(message.chunk);\n break;\n case \"stderr\":\n this.bridge?.stderr.write(message.chunk);\n break;\n case \"exit\":\n if (message.code !== 0) {\n this.bridge?.stderr.write(`\\nSwiftTUI WASI app exited with code ${message.code}.\\n`);\n }\n break;\n case \"error\":\n this.bridge?.stderr.write(`\\nFailed to start SwiftTUI WASI app: ${message.message}\\n`);\n break;\n }\n }\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { SharedInputQueueBuffers } from "./SharedInputQueue.js";
|
|
2
|
+
|
|
3
|
+
//#region src/wasi/WasmSceneWorker.d.ts
|
|
4
|
+
interface StartWasmSceneWorkerMessage {
|
|
5
|
+
type: "start";
|
|
6
|
+
wasmURL: string;
|
|
7
|
+
environment: Record<string, string>;
|
|
8
|
+
inputQueue: SharedInputQueueBuffers;
|
|
9
|
+
}
|
|
10
|
+
interface OutputWasmSceneWorkerMessage {
|
|
11
|
+
type: "stdout" | "stderr";
|
|
12
|
+
chunk: Uint8Array;
|
|
13
|
+
}
|
|
14
|
+
interface ExitWasmSceneWorkerMessage {
|
|
15
|
+
type: "exit";
|
|
16
|
+
code: number;
|
|
17
|
+
}
|
|
18
|
+
interface ErrorWasmSceneWorkerMessage {
|
|
19
|
+
type: "error";
|
|
20
|
+
message: string;
|
|
21
|
+
}
|
|
22
|
+
type WasmSceneWorkerMessage = StartWasmSceneWorkerMessage;
|
|
23
|
+
type WasmSceneWorkerResponse = OutputWasmSceneWorkerMessage | ExitWasmSceneWorkerMessage | ErrorWasmSceneWorkerMessage;
|
|
24
|
+
declare function startWasmSceneWorker(): void;
|
|
25
|
+
//#endregion
|
|
26
|
+
export { ErrorWasmSceneWorkerMessage, ExitWasmSceneWorkerMessage, OutputWasmSceneWorkerMessage, StartWasmSceneWorkerMessage, WasmSceneWorkerMessage, WasmSceneWorkerResponse, startWasmSceneWorker };
|
|
27
|
+
//# sourceMappingURL=WasmSceneWorker.d.ts.map
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { SharedInputQueueReader } from "./SharedInputQueue.js";
|
|
2
|
+
import { WasiPollScheduler } from "./WasiPollScheduler.js";
|
|
3
|
+
import { ConsoleStdout, Fd, WASI, wasi } from "@bjorn3/browser_wasi_shim";
|
|
4
|
+
//#region src/wasi/WasmSceneWorker.ts
|
|
5
|
+
function startWasmSceneWorker() {
|
|
6
|
+
globalThis.addEventListener("message", (event) => {
|
|
7
|
+
if (event.data.type !== "start") return;
|
|
8
|
+
startWasmScene(event.data);
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
var BlockingInputFileDescriptor = class extends Fd {
|
|
12
|
+
reader;
|
|
13
|
+
fdstat = (() => {
|
|
14
|
+
const fdstat = new wasi.Fdstat(wasi.FILETYPE_CHARACTER_DEVICE, 0);
|
|
15
|
+
fdstat.fs_rights_base = BigInt(wasi.RIGHTS_FD_READ);
|
|
16
|
+
return fdstat;
|
|
17
|
+
})();
|
|
18
|
+
constructor(inputQueue) {
|
|
19
|
+
super();
|
|
20
|
+
this.reader = new SharedInputQueueReader(inputQueue);
|
|
21
|
+
}
|
|
22
|
+
fd_fdstat_get() {
|
|
23
|
+
return {
|
|
24
|
+
ret: wasi.ERRNO_SUCCESS,
|
|
25
|
+
fdstat: this.fdstat
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
fd_filestat_get() {
|
|
29
|
+
return {
|
|
30
|
+
ret: wasi.ERRNO_SUCCESS,
|
|
31
|
+
filestat: new wasi.Filestat(0n, wasi.FILETYPE_CHARACTER_DEVICE, 0n)
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
fd_read(size) {
|
|
35
|
+
const chunk = this.reader.readAvailable(size);
|
|
36
|
+
if (chunk) return {
|
|
37
|
+
ret: wasi.ERRNO_SUCCESS,
|
|
38
|
+
data: chunk
|
|
39
|
+
};
|
|
40
|
+
if (this.reader.isClosed()) return {
|
|
41
|
+
ret: wasi.ERRNO_SUCCESS,
|
|
42
|
+
data: new Uint8Array()
|
|
43
|
+
};
|
|
44
|
+
return {
|
|
45
|
+
ret: wasi.ERRNO_AGAIN,
|
|
46
|
+
data: new Uint8Array()
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
availableBytes() {
|
|
50
|
+
return this.reader.availableBytes();
|
|
51
|
+
}
|
|
52
|
+
waitForReadable(timeoutMilliseconds) {
|
|
53
|
+
return this.reader.waitForReadable(timeoutMilliseconds);
|
|
54
|
+
}
|
|
55
|
+
isClosed() {
|
|
56
|
+
return this.reader.isClosed();
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
function installWasiPollScheduler(wasiBridge, stdin) {
|
|
60
|
+
const originalPoll = wasiBridge.wasiImport.poll_oneoff;
|
|
61
|
+
if (typeof originalPoll !== "function") return;
|
|
62
|
+
const scheduler = new WasiPollScheduler({
|
|
63
|
+
memory: () => wasiBridge.inst?.exports.memory,
|
|
64
|
+
stdin,
|
|
65
|
+
fallbackPoll: (inPtr, outPtr, nsubscriptions, neventsPtr) => originalPoll(inPtr, outPtr, nsubscriptions, neventsPtr)
|
|
66
|
+
});
|
|
67
|
+
wasiBridge.wasiImport.poll_oneoff = (inPtr, outPtr, nsubscriptions, neventsPtr) => scheduler.pollOneOff(inPtr, outPtr, nsubscriptions, neventsPtr);
|
|
68
|
+
}
|
|
69
|
+
async function startWasmScene(message) {
|
|
70
|
+
try {
|
|
71
|
+
const stdin = new BlockingInputFileDescriptor(message.inputQueue);
|
|
72
|
+
const wasiBridge = new WASI(["app.wasm"], Object.entries(message.environment).map(([key, value]) => `${key}=${value}`), [
|
|
73
|
+
stdin,
|
|
74
|
+
new ConsoleStdout((chunk) => {
|
|
75
|
+
postWorkerMessage({
|
|
76
|
+
type: "stdout",
|
|
77
|
+
chunk
|
|
78
|
+
});
|
|
79
|
+
}),
|
|
80
|
+
new ConsoleStdout((chunk) => {
|
|
81
|
+
postWorkerMessage({
|
|
82
|
+
type: "stderr",
|
|
83
|
+
chunk
|
|
84
|
+
});
|
|
85
|
+
})
|
|
86
|
+
]);
|
|
87
|
+
installWasiPollScheduler(wasiBridge, stdin);
|
|
88
|
+
const response = await fetch(message.wasmURL);
|
|
89
|
+
if (!response.ok) throw new Error(`failed to load ${message.wasmURL}: ${response.status} ${response.statusText}`);
|
|
90
|
+
const module = await WebAssembly.compile(await response.arrayBuffer());
|
|
91
|
+
const instance = await WebAssembly.instantiate(module, { wasi_snapshot_preview1: wasiBridge.wasiImport });
|
|
92
|
+
postWorkerMessage({
|
|
93
|
+
type: "exit",
|
|
94
|
+
code: wasiBridge.start(instance)
|
|
95
|
+
});
|
|
96
|
+
} catch (error) {
|
|
97
|
+
postWorkerMessage({
|
|
98
|
+
type: "error",
|
|
99
|
+
message: error instanceof Error ? error.message : String(error)
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
function postWorkerMessage(message) {
|
|
104
|
+
globalThis.postMessage(message);
|
|
105
|
+
}
|
|
106
|
+
//#endregion
|
|
107
|
+
export { startWasmSceneWorker };
|
|
108
|
+
|
|
109
|
+
//# sourceMappingURL=WasmSceneWorker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"WasmSceneWorker.js","names":[],"sources":["../../../src/wasi/WasmSceneWorker.ts"],"sourcesContent":["import { ConsoleStdout, Fd, WASI, wasi } from \"@bjorn3/browser_wasi_shim\";\nimport {\n SharedInputQueueReader,\n type SharedInputQueueBuffers,\n} from \"./SharedInputQueue.ts\";\nimport { WasiPollScheduler } from \"./WasiPollScheduler.ts\";\n\nexport interface StartWasmSceneWorkerMessage {\n type: \"start\";\n wasmURL: string;\n environment: Record<string, string>;\n inputQueue: SharedInputQueueBuffers;\n}\n\nexport interface OutputWasmSceneWorkerMessage {\n type: \"stdout\" | \"stderr\";\n chunk: Uint8Array;\n}\n\nexport interface ExitWasmSceneWorkerMessage {\n type: \"exit\";\n code: number;\n}\n\nexport interface ErrorWasmSceneWorkerMessage {\n type: \"error\";\n message: string;\n}\n\nexport type WasmSceneWorkerMessage = StartWasmSceneWorkerMessage;\nexport type WasmSceneWorkerResponse =\n | OutputWasmSceneWorkerMessage\n | ExitWasmSceneWorkerMessage\n | ErrorWasmSceneWorkerMessage;\n\nexport function startWasmSceneWorker(): void {\n globalThis.addEventListener(\"message\", (event: MessageEvent<WasmSceneWorkerMessage>) => {\n if (event.data.type !== \"start\") {\n return;\n }\n\n void startWasmScene(event.data);\n });\n}\n\nclass BlockingInputFileDescriptor extends Fd {\n private readonly reader: SharedInputQueueReader;\n private readonly fdstat = (() => {\n const fdstat = new wasi.Fdstat(wasi.FILETYPE_CHARACTER_DEVICE, 0);\n fdstat.fs_rights_base = BigInt(wasi.RIGHTS_FD_READ);\n return fdstat;\n })();\n\n constructor(inputQueue: SharedInputQueueBuffers) {\n super();\n this.reader = new SharedInputQueueReader(inputQueue);\n }\n\n override fd_fdstat_get(): { ret: number; fdstat: wasi.Fdstat } {\n return {\n ret: wasi.ERRNO_SUCCESS,\n fdstat: this.fdstat,\n };\n }\n\n override fd_filestat_get(): { ret: number; filestat: wasi.Filestat } {\n return {\n ret: wasi.ERRNO_SUCCESS,\n filestat: new wasi.Filestat(0n, wasi.FILETYPE_CHARACTER_DEVICE, 0n),\n };\n }\n\n override fd_read(size: number): { ret: number; data: Uint8Array } {\n const chunk = this.reader.readAvailable(size);\n if (chunk) {\n return {\n ret: wasi.ERRNO_SUCCESS,\n data: chunk,\n };\n }\n\n if (this.reader.isClosed()) {\n return {\n ret: wasi.ERRNO_SUCCESS,\n data: new Uint8Array(),\n };\n }\n\n return {\n ret: wasi.ERRNO_AGAIN,\n data: new Uint8Array(),\n };\n }\n\n availableBytes(): number {\n return this.reader.availableBytes();\n }\n\n waitForReadable(\n timeoutMilliseconds?: number\n ) {\n return this.reader.waitForReadable(timeoutMilliseconds);\n }\n\n isClosed(): boolean {\n return this.reader.isClosed();\n }\n}\n\nfunction installWasiPollScheduler(\n wasiBridge: WASI,\n stdin: BlockingInputFileDescriptor\n): void {\n const originalPoll = wasiBridge.wasiImport.poll_oneoff;\n if (typeof originalPoll !== \"function\") {\n return;\n }\n\n const scheduler = new WasiPollScheduler({\n memory: () => wasiBridge.inst?.exports.memory as WebAssembly.Memory | undefined,\n stdin,\n fallbackPoll: (inPtr, outPtr, nsubscriptions, neventsPtr) =>\n originalPoll(inPtr, outPtr, nsubscriptions, neventsPtr),\n });\n wasiBridge.wasiImport.poll_oneoff = (inPtr, outPtr, nsubscriptions, neventsPtr) =>\n scheduler.pollOneOff(inPtr, outPtr, nsubscriptions, neventsPtr);\n}\n\nasync function startWasmScene(\n message: StartWasmSceneWorkerMessage\n): Promise<void> {\n try {\n const stdin = new BlockingInputFileDescriptor(message.inputQueue);\n const wasiBridge = new WASI(\n [\"app.wasm\"],\n Object.entries(message.environment).map(([key, value]) => `${key}=${value}`),\n [\n stdin,\n new ConsoleStdout((chunk) => {\n postWorkerMessage({\n type: \"stdout\",\n chunk,\n });\n }),\n new ConsoleStdout((chunk) => {\n postWorkerMessage({\n type: \"stderr\",\n chunk,\n });\n }),\n ]\n );\n installWasiPollScheduler(wasiBridge, stdin);\n\n const response = await fetch(message.wasmURL);\n if (!response.ok) {\n throw new Error(`failed to load ${message.wasmURL}: ${response.status} ${response.statusText}`);\n }\n\n const module = await WebAssembly.compile(await response.arrayBuffer());\n const instance = await WebAssembly.instantiate(module, {\n wasi_snapshot_preview1: wasiBridge.wasiImport,\n });\n\n const exitCode = wasiBridge.start(instance as WebAssembly.Instance);\n postWorkerMessage({\n type: \"exit\",\n code: exitCode,\n });\n } catch (error) {\n postWorkerMessage({\n type: \"error\",\n message: error instanceof Error ? error.message : String(error),\n });\n }\n}\n\nfunction postWorkerMessage(\n message: WasmSceneWorkerResponse\n): void {\n globalThis.postMessage(message);\n}\n"],"mappings":";;;;AAmCA,SAAgB,uBAA6B;CAC3C,WAAW,iBAAiB,YAAY,UAAgD;EACtF,IAAI,MAAM,KAAK,SAAS,SACtB;EAGF,eAAoB,MAAM,IAAI;CAChC,CAAC;AACH;AAEA,IAAM,8BAAN,cAA0C,GAAG;CAC3C;CACA,gBAAiC;EAC/B,MAAM,SAAS,IAAI,KAAK,OAAO,KAAK,2BAA2B,CAAC;EAChE,OAAO,iBAAiB,OAAO,KAAK,cAAc;EAClD,OAAO;CACT,EAAA,CAAG;CAEH,YAAY,YAAqC;EAC/C,MAAM;EACN,KAAK,SAAS,IAAI,uBAAuB,UAAU;CACrD;CAEA,gBAA+D;EAC7D,OAAO;GACL,KAAK,KAAK;GACV,QAAQ,KAAK;EACf;CACF;CAEA,kBAAqE;EACnE,OAAO;GACL,KAAK,KAAK;GACV,UAAU,IAAI,KAAK,SAAS,IAAI,KAAK,2BAA2B,EAAE;EACpE;CACF;CAEA,QAAiB,MAAiD;EAChE,MAAM,QAAQ,KAAK,OAAO,cAAc,IAAI;EAC5C,IAAI,OACF,OAAO;GACL,KAAK,KAAK;GACV,MAAM;EACR;EAGF,IAAI,KAAK,OAAO,SAAS,GACvB,OAAO;GACL,KAAK,KAAK;GACV,MAAM,IAAI,WAAW;EACvB;EAGF,OAAO;GACL,KAAK,KAAK;GACV,MAAM,IAAI,WAAW;EACvB;CACF;CAEA,iBAAyB;EACvB,OAAO,KAAK,OAAO,eAAe;CACpC;CAEA,gBACE,qBACA;EACA,OAAO,KAAK,OAAO,gBAAgB,mBAAmB;CACxD;CAEA,WAAoB;EAClB,OAAO,KAAK,OAAO,SAAS;CAC9B;AACF;AAEA,SAAS,yBACP,YACA,OACM;CACN,MAAM,eAAe,WAAW,WAAW;CAC3C,IAAI,OAAO,iBAAiB,YAC1B;CAGF,MAAM,YAAY,IAAI,kBAAkB;EACtC,cAAc,WAAW,MAAM,QAAQ;EACvC;EACA,eAAe,OAAO,QAAQ,gBAAgB,eAC5C,aAAa,OAAO,QAAQ,gBAAgB,UAAU;CAC1D,CAAC;CACD,WAAW,WAAW,eAAe,OAAO,QAAQ,gBAAgB,eAClE,UAAU,WAAW,OAAO,QAAQ,gBAAgB,UAAU;AAClE;AAEA,eAAe,eACb,SACe;CACf,IAAI;EACF,MAAM,QAAQ,IAAI,4BAA4B,QAAQ,UAAU;EAChE,MAAM,aAAa,IAAI,KACrB,CAAC,UAAU,GACX,OAAO,QAAQ,QAAQ,WAAW,CAAC,CAAC,KAAK,CAAC,KAAK,WAAW,GAAG,IAAI,GAAG,OAAO,GAC3E;GACE;GACA,IAAI,eAAe,UAAU;IAC3B,kBAAkB;KAChB,MAAM;KACN;IACF,CAAC;GACH,CAAC;GACD,IAAI,eAAe,UAAU;IAC3B,kBAAkB;KAChB,MAAM;KACN;IACF,CAAC;GACH,CAAC;EACH,CACF;EACA,yBAAyB,YAAY,KAAK;EAE1C,MAAM,WAAW,MAAM,MAAM,QAAQ,OAAO;EAC5C,IAAI,CAAC,SAAS,IACZ,MAAM,IAAI,MAAM,kBAAkB,QAAQ,QAAQ,IAAI,SAAS,OAAO,GAAG,SAAS,YAAY;EAGhG,MAAM,SAAS,MAAM,YAAY,QAAQ,MAAM,SAAS,YAAY,CAAC;EACrE,MAAM,WAAW,MAAM,YAAY,YAAY,QAAQ,EACrD,wBAAwB,WAAW,WACrC,CAAC;EAGD,kBAAkB;GAChB,MAAM;GACN,MAHe,WAAW,MAAM,QAGnB;EACf,CAAC;CACH,SAAS,OAAO;EACd,kBAAkB;GAChB,MAAM;GACN,SAAS,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;EAChE,CAAC;CACH;AACF;AAEA,SAAS,kBACP,SACM;CACN,WAAW,YAAY,OAAO;AAChC"}
|
package/dist/testing.js
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { ErrorWasmSceneWorkerMessage, ExitWasmSceneWorkerMessage, OutputWasmSceneWorkerMessage, StartWasmSceneWorkerMessage, WasmSceneWorkerMessage, WasmSceneWorkerResponse, startWasmSceneWorker } from "./src/wasi/WasmSceneWorker.js";
|
|
2
|
+
export { ErrorWasmSceneWorkerMessage, ExitWasmSceneWorkerMessage, OutputWasmSceneWorkerMessage, StartWasmSceneWorkerMessage, WasmSceneWorkerMessage, WasmSceneWorkerResponse, startWasmSceneWorker };
|
package/dist/wasi.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { encodeRenderStyleControlMessage, encodeResizeControlMessage } from "./src/WebHostSurfaceTransport.js";
|
|
2
|
+
import { StdIOPipe } from "./src/wasi/StdIOPipe.js";
|
|
3
|
+
import { BrowserWASIBridge, BrowserWASIBridgeOptions, BrowserWASIOutputSink } from "./src/wasi/BrowserWASIBridge.js";
|
|
4
|
+
import { SharedInputQueueBuffers, SharedInputQueueReader, SharedInputQueueWriter, SharedInputReadiness, createSharedInputQueue, hydrateSharedInputQueue, sharedInputQueueDefaultCapacity } from "./src/wasi/SharedInputQueue.js";
|
|
5
|
+
import { WasmSceneResizeEvent, WasmSceneRuntimeFactoryOptions, WasmSceneRuntimeHandle, createWasmSceneRuntimeFactory } from "./src/wasi/WasmSceneRuntime.js";
|
|
6
|
+
export { BrowserWASIBridge, BrowserWASIBridgeOptions, BrowserWASIOutputSink, SharedInputQueueBuffers, SharedInputQueueReader, SharedInputQueueWriter, SharedInputReadiness, StdIOPipe, WasmSceneResizeEvent, WasmSceneRuntimeFactoryOptions, WasmSceneRuntimeHandle, createSharedInputQueue, createWasmSceneRuntimeFactory, encodeRenderStyleControlMessage, encodeResizeControlMessage, hydrateSharedInputQueue, sharedInputQueueDefaultCapacity };
|
package/dist/wasi.js
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { StdIOPipe } from "./src/wasi/StdIOPipe.js";
|
|
2
|
+
import { encodeRenderStyleControlMessage, encodeResizeControlMessage } from "./src/WebHostSurfaceTransport.js";
|
|
3
|
+
import { BrowserWASIBridge } from "./src/wasi/BrowserWASIBridge.js";
|
|
4
|
+
import { SharedInputQueueReader, SharedInputQueueWriter, createSharedInputQueue, hydrateSharedInputQueue, sharedInputQueueDefaultCapacity } from "./src/wasi/SharedInputQueue.js";
|
|
5
|
+
import { createWasmSceneRuntimeFactory } from "./src/wasi/WasmSceneRuntime.js";
|
|
6
|
+
export { BrowserWASIBridge, SharedInputQueueReader, SharedInputQueueWriter, StdIOPipe, createSharedInputQueue, createWasmSceneRuntimeFactory, encodeRenderStyleControlMessage, encodeResizeControlMessage, hydrateSharedInputQueue, sharedInputQueueDefaultCapacity };
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { WebSocketSceneBridge, WebSocketSceneBridgeFactory, WebSocketSceneBridgeOptions, WebSocketSceneSocket, webSocketSceneURL } from "./src/WebSocketSceneBridge.js";
|
|
2
|
+
export { WebSocketSceneBridge, WebSocketSceneBridgeFactory, WebSocketSceneBridgeOptions, WebSocketSceneSocket, webSocketSceneURL };
|
package/package.json
CHANGED
|
@@ -1,33 +1,64 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@swifttui/web",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.15",
|
|
4
4
|
"license": "MIT",
|
|
5
|
-
"
|
|
5
|
+
"type": "module",
|
|
6
|
+
"sideEffects": false,
|
|
7
|
+
"engines": {
|
|
8
|
+
"node": ">=18"
|
|
9
|
+
},
|
|
10
|
+
"main": "./dist/index.js",
|
|
11
|
+
"module": "./dist/index.js",
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"files": [
|
|
14
|
+
"dist",
|
|
15
|
+
"style.css"
|
|
16
|
+
],
|
|
6
17
|
"exports": {
|
|
7
|
-
".":
|
|
8
|
-
|
|
18
|
+
".": {
|
|
19
|
+
"types": "./dist/index.d.ts",
|
|
20
|
+
"import": "./dist/index.js"
|
|
21
|
+
},
|
|
22
|
+
"./manifest": {
|
|
23
|
+
"types": "./dist/manifest.d.ts",
|
|
24
|
+
"import": "./dist/manifest.js"
|
|
25
|
+
},
|
|
26
|
+
"./testing": {
|
|
27
|
+
"types": "./dist/testing.d.ts",
|
|
28
|
+
"import": "./dist/testing.js"
|
|
29
|
+
},
|
|
30
|
+
"./wasi": {
|
|
31
|
+
"types": "./dist/wasi.d.ts",
|
|
32
|
+
"import": "./dist/wasi.js"
|
|
33
|
+
},
|
|
34
|
+
"./wasi-worker": {
|
|
35
|
+
"types": "./dist/wasi-worker.d.ts",
|
|
36
|
+
"import": "./dist/wasi-worker.js"
|
|
37
|
+
},
|
|
38
|
+
"./websocket": {
|
|
39
|
+
"types": "./dist/websocket.d.ts",
|
|
40
|
+
"import": "./dist/websocket.js"
|
|
41
|
+
},
|
|
9
42
|
"./style.css": "./style.css",
|
|
10
|
-
"./
|
|
11
|
-
"./wasi": "./wasi.ts",
|
|
12
|
-
"./wasi-worker": "./wasi-worker.ts",
|
|
13
|
-
"./websocket": "./websocket.ts"
|
|
43
|
+
"./package.json": "./package.json"
|
|
14
44
|
},
|
|
15
|
-
"type": "module",
|
|
16
45
|
"scripts": {
|
|
17
|
-
"build
|
|
18
|
-
"build:
|
|
19
|
-
"build:
|
|
20
|
-
"build": "bun run cli.ts build",
|
|
21
|
-
"
|
|
22
|
-
"
|
|
46
|
+
"build": "tsdown",
|
|
47
|
+
"build:manifest": "bun run --cwd ../build build:manifest -- --dist ../web/dist-demo",
|
|
48
|
+
"build:wasm": "bun run --cwd ../build build:wasm -- --dist ../web/dist-demo",
|
|
49
|
+
"build:web": "bun run cli.ts build:web --dist dist-demo",
|
|
50
|
+
"build:app": "bun run cli.ts build --dist dist-demo",
|
|
51
|
+
"dev": "bun run cli.ts dev --dist dist-demo",
|
|
52
|
+
"test": "bun test",
|
|
53
|
+
"prepublishOnly": "tsdown"
|
|
54
|
+
},
|
|
55
|
+
"publishConfig": {
|
|
56
|
+
"access": "public"
|
|
23
57
|
},
|
|
24
58
|
"dependencies": {
|
|
25
59
|
"@bjorn3/browser_wasi_shim": "^0.4.2"
|
|
26
60
|
},
|
|
27
61
|
"devDependencies": {
|
|
28
62
|
"@types/bun": "1.3.13"
|
|
29
|
-
},
|
|
30
|
-
"peerDependencies": {
|
|
31
|
-
"typescript": "^5"
|
|
32
63
|
}
|
|
33
64
|
}
|
package/AGENTS.md
DELETED
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
# AGENTS.md
|
|
2
|
-
|
|
3
|
-
Guidance for agentic assistants working in **`@swifttui/web`**. Keep this
|
|
4
|
-
concise; [`README.md`](README.md) is the full reference.
|
|
5
|
-
|
|
6
|
-
## What this package is
|
|
7
|
-
|
|
8
|
-
The **browser runtime** for SwiftTUI apps. It owns the browser-safe runtime
|
|
9
|
-
APIs: scene-manifest loading, canvas rendering, ARIA mounting, WebSocket scene
|
|
10
|
-
bridges, and WASI scene bridges. Build/packaging tooling lives in the sibling
|
|
11
|
-
[`@swifttui/build`](../build) workspace package — keep that split.
|
|
12
|
-
|
|
13
|
-
It lives in the repo's Bun workspace (`packages/web` in `swift-tui-web`). Run
|
|
14
|
-
`bun install` from the repo root or any package dir; one root `bun.lock` is
|
|
15
|
-
maintained.
|
|
16
|
-
|
|
17
|
-
## Toolchains
|
|
18
|
-
|
|
19
|
-
- **Bun** for dev, bundling, and the test runner.
|
|
20
|
-
- **`swiftly`** Swift 6.3.1 for any Swift the build path triggers
|
|
21
|
-
(`swiftly run swift --version`). Do not use bare `swift`/`xcrun swift`.
|
|
22
|
-
|
|
23
|
-
## Commands
|
|
24
|
-
|
|
25
|
-
```bash
|
|
26
|
-
bun test # this package's tests (or `bun run test`)
|
|
27
|
-
bun run build:web # bundle index.html + browser entrypoint
|
|
28
|
-
bun run build -- --app <Exe> # full build (manifest + wasm + web)
|
|
29
|
-
bun run dev # watch/dev
|
|
30
|
-
```
|
|
31
|
-
|
|
32
|
-
`build:manifest` and `build:wasm` delegate to `@swifttui/build` and default to
|
|
33
|
-
`--configuration release`. The org-level gate for this repo is `bun run ci`
|
|
34
|
-
(install --frozen-lockfile + test + build:web), run from the `swift-tui-web` root.
|
|
35
|
-
|
|
36
|
-
## Architecture notes
|
|
37
|
-
|
|
38
|
-
- Transport is SwiftTUI's **`web-surface` WASI transport**: the Swift runner
|
|
39
|
-
emits raster-surface records on stdout and the host draws rects/text to a
|
|
40
|
-
canvas. **No terminal emulator** — does not use `ghostty-web`/`ghostty-vt.wasm`.
|
|
41
|
-
`BrowserWASIBridge` sets `TUIGUI_TRANSPORT=surface`.
|
|
42
|
-
- Entry points: `createWebHostApp` (`.`), `createWasmSceneRuntimeFactory`
|
|
43
|
-
(`./wasi`), `startWasmSceneWorker` (`./wasi-worker`). Subpath exports are
|
|
44
|
-
declared in `package.json` — keep `exports` in sync when adding modules.
|
|
45
|
-
- Scene switching is controller-managed and retains existing scene runtimes.
|
|
46
|
-
- Terminal styling is host-owned via `WebHostTerminalStyle` (one active
|
|
47
|
-
palette/theme pair); the library ships no built-in mode switcher.
|
|
48
|
-
|
|
49
|
-
## Conventions
|
|
50
|
-
|
|
51
|
-
`AGENTS.md` is the real file; `CLAUDE.md` is a symlink to it. Edit `AGENTS.md`.
|
|
52
|
-
Tests are colocated as `*.test.ts` (browser-only specs use `*.browser.ts`).
|
package/cli.ts
DELETED
|
@@ -1,168 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bun
|
|
2
|
-
|
|
3
|
-
import { mkdir } from "node:fs/promises";
|
|
4
|
-
import { join, resolve } from "node:path";
|
|
5
|
-
import {
|
|
6
|
-
buildAppWasm,
|
|
7
|
-
buildSwiftTUIWebApp,
|
|
8
|
-
generateSceneManifest,
|
|
9
|
-
type WasmBuildConfiguration,
|
|
10
|
-
} from "../build/index.ts";
|
|
11
|
-
|
|
12
|
-
void runCli(process.argv.slice(2));
|
|
13
|
-
|
|
14
|
-
async function runCli(argv: string[]): Promise<void> {
|
|
15
|
-
const command = argv[0] ?? "build";
|
|
16
|
-
const flags = parseFlags(argv.slice(1));
|
|
17
|
-
const packagePath = resolve(flags["package-path"] ?? "../../");
|
|
18
|
-
const distPath = resolve(flags["dist"] ?? "./dist");
|
|
19
|
-
const appExecutable = flags.app ?? flags.product ?? flags["app-product"] ?? "";
|
|
20
|
-
const wasmConfiguration = parseWasmBuildConfiguration(flags.configuration ?? "release");
|
|
21
|
-
|
|
22
|
-
switch (command) {
|
|
23
|
-
case "build:manifest":
|
|
24
|
-
assertAppExecutable(appExecutable);
|
|
25
|
-
await generateSceneManifest({
|
|
26
|
-
packagePath,
|
|
27
|
-
outputPath: join(distPath, "scene-manifest.json"),
|
|
28
|
-
appExecutable,
|
|
29
|
-
});
|
|
30
|
-
return;
|
|
31
|
-
case "build:wasm":
|
|
32
|
-
assertAppExecutable(appExecutable);
|
|
33
|
-
await buildAppWasm({
|
|
34
|
-
configuration: wasmConfiguration,
|
|
35
|
-
packagePath,
|
|
36
|
-
outputDirectory: distPath,
|
|
37
|
-
product: appExecutable,
|
|
38
|
-
});
|
|
39
|
-
return;
|
|
40
|
-
case "build:web":
|
|
41
|
-
await bunBuildWeb({
|
|
42
|
-
outputDirectory: distPath,
|
|
43
|
-
});
|
|
44
|
-
return;
|
|
45
|
-
case "build":
|
|
46
|
-
assertAppExecutable(appExecutable);
|
|
47
|
-
await buildSwiftTUIWebApp({
|
|
48
|
-
configuration: wasmConfiguration,
|
|
49
|
-
packagePath,
|
|
50
|
-
outputDirectory: distPath,
|
|
51
|
-
product: appExecutable,
|
|
52
|
-
});
|
|
53
|
-
await bunBuildWeb({
|
|
54
|
-
outputDirectory: distPath,
|
|
55
|
-
});
|
|
56
|
-
return;
|
|
57
|
-
case "dev":
|
|
58
|
-
await bunServe(packagePath, distPath);
|
|
59
|
-
return;
|
|
60
|
-
default:
|
|
61
|
-
throw new Error(`unknown command: ${command}`);
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
async function bunBuildWeb(options: {
|
|
66
|
-
outputDirectory: string;
|
|
67
|
-
}): Promise<void> {
|
|
68
|
-
await mkdir(options.outputDirectory, { recursive: true });
|
|
69
|
-
const proc = Bun.spawn({
|
|
70
|
-
cmd: [
|
|
71
|
-
"bun",
|
|
72
|
-
"build",
|
|
73
|
-
"./index.html",
|
|
74
|
-
"--outdir",
|
|
75
|
-
options.outputDirectory,
|
|
76
|
-
],
|
|
77
|
-
stdout: "pipe",
|
|
78
|
-
stderr: "pipe",
|
|
79
|
-
});
|
|
80
|
-
const [stdout, stderr, exitCode] = await Promise.all([
|
|
81
|
-
new Response(proc.stdout).text(),
|
|
82
|
-
new Response(proc.stderr).text(),
|
|
83
|
-
proc.exited,
|
|
84
|
-
]);
|
|
85
|
-
|
|
86
|
-
if (exitCode !== 0) {
|
|
87
|
-
throw new Error([stdout, stderr].filter(Boolean).join("\n").trim() || "web build failed");
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
async function bunServe(
|
|
92
|
-
packagePath: string,
|
|
93
|
-
distPath: string
|
|
94
|
-
): Promise<void> {
|
|
95
|
-
const server = Bun.serve({
|
|
96
|
-
port: Number(process.env.PORT ?? "3000"),
|
|
97
|
-
development: {
|
|
98
|
-
hmr: true,
|
|
99
|
-
console: true,
|
|
100
|
-
},
|
|
101
|
-
fetch(request) {
|
|
102
|
-
const url = new URL(request.url);
|
|
103
|
-
if (url.pathname === "/") {
|
|
104
|
-
return new Response(Bun.file(join(distPath, "index.html")));
|
|
105
|
-
}
|
|
106
|
-
if (url.pathname === "/scene-manifest.json") {
|
|
107
|
-
return new Response(Bun.file(join(distPath, "scene-manifest.json")));
|
|
108
|
-
}
|
|
109
|
-
if (url.pathname.startsWith("/assets/")) {
|
|
110
|
-
return new Response(Bun.file(join(distPath, url.pathname.slice(1))));
|
|
111
|
-
}
|
|
112
|
-
if (url.pathname.startsWith("/src/")) {
|
|
113
|
-
return new Response(Bun.file(join(packagePath, url.pathname.slice(1))));
|
|
114
|
-
}
|
|
115
|
-
return new Response("not found", { status: 404 });
|
|
116
|
-
},
|
|
117
|
-
});
|
|
118
|
-
|
|
119
|
-
console.log(`WebHost dev server running at ${server.url.href}`);
|
|
120
|
-
await new Promise<void>(() => {});
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
function parseFlags(
|
|
124
|
-
argv: string[]
|
|
125
|
-
): Record<string, string> {
|
|
126
|
-
const flags: Record<string, string> = {};
|
|
127
|
-
for (let index = 0; index < argv.length; index += 1) {
|
|
128
|
-
const value = argv[index];
|
|
129
|
-
if (!value.startsWith("--")) {
|
|
130
|
-
continue;
|
|
131
|
-
}
|
|
132
|
-
const equalsIndex = value.indexOf("=");
|
|
133
|
-
if (equalsIndex !== -1) {
|
|
134
|
-
flags[value.slice(2, equalsIndex)] = value.slice(equalsIndex + 1);
|
|
135
|
-
continue;
|
|
136
|
-
}
|
|
137
|
-
const name = value.slice(2);
|
|
138
|
-
const next = argv[index + 1];
|
|
139
|
-
if (next && !next.startsWith("--")) {
|
|
140
|
-
flags[name] = next;
|
|
141
|
-
index += 1;
|
|
142
|
-
} else {
|
|
143
|
-
flags[name] = "true";
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
return flags;
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
function parseWasmBuildConfiguration(
|
|
150
|
-
value: string
|
|
151
|
-
): WasmBuildConfiguration {
|
|
152
|
-
switch (value) {
|
|
153
|
-
case "debug":
|
|
154
|
-
return "debug";
|
|
155
|
-
case "release":
|
|
156
|
-
return "release";
|
|
157
|
-
default:
|
|
158
|
-
throw new Error(`unsupported wasm build configuration: ${value}`);
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
function assertAppExecutable(
|
|
163
|
-
value: string
|
|
164
|
-
): asserts value is string {
|
|
165
|
-
if (!value) {
|
|
166
|
-
throw new Error("missing --app or --product flag");
|
|
167
|
-
}
|
|
168
|
-
}
|