@seanhogg/builderforce-webcontainers 2026.10.5 → 2026.10.6
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 +5 -5
- package/dist/assets.js +1 -1
- package/dist/check/index.js +1 -1
- package/dist/check/index.js.map +1 -1
- package/dist/check/worker.js +6 -6
- package/dist/host.d.ts +16 -16
- package/dist/host.d.ts.map +1 -1
- package/dist/host.js +5 -4
- package/dist/host.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/node/processHost.d.ts +7 -8
- package/dist/node/processHost.d.ts.map +1 -1
- package/dist/node/processHost.js +9 -21
- package/dist/node/processHost.js.map +1 -1
- package/dist/node/protocol.d.ts +1 -1
- package/dist/node/protocol.d.ts.map +1 -1
- package/dist/node/worker.js +13 -13
- package/dist/node/workerProcess.js +2 -2
- package/dist/node/workerProcess.js.map +1 -1
- package/dist/process-worker.js +13 -13
- package/package.json +2 -2
- package/src/check/index.ts +1 -1
- package/src/host.ts +24 -22
- package/src/index.ts +3 -3
- package/src/node/processHost.ts +17 -22
- package/src/node/protocol.ts +1 -1
- package/src/node/workerProcess.ts +2 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@seanhogg/builderforce-webcontainers",
|
|
3
|
-
"version": "2026.10.
|
|
3
|
+
"version": "2026.10.6",
|
|
4
4
|
"description": "BuilderForce WebContainers — browser runtime. Boots an in-browser dev server for a project held in memory and serves it to an iframe through a service worker. Open source, no COOP/COEP, no session caps.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.js",
|
|
@@ -65,7 +65,7 @@
|
|
|
65
65
|
},
|
|
66
66
|
"dependencies": {
|
|
67
67
|
"esbuild-wasm": "^0.24.0",
|
|
68
|
-
"@seanhogg/builderforce-webcontainers-core": "^2026.10.
|
|
68
|
+
"@seanhogg/builderforce-webcontainers-core": "^2026.10.6"
|
|
69
69
|
},
|
|
70
70
|
"devDependencies": {
|
|
71
71
|
"@vue/compiler-sfc": "^3.5.0",
|
package/src/check/index.ts
CHANGED
|
@@ -48,7 +48,7 @@ export function checkInputs(files: VirtualFileSystem | FlatFiles): Record<string
|
|
|
48
48
|
if (files instanceof VirtualFileSystem) fs = files;
|
|
49
49
|
else {
|
|
50
50
|
fs = new VirtualFileSystem();
|
|
51
|
-
fs.
|
|
51
|
+
fs.writeTree(files);
|
|
52
52
|
}
|
|
53
53
|
const out: Record<string, string> = {};
|
|
54
54
|
for (const path of fs.list()) if (isCheckInput(path)) out[path.slice(1)] = fs.readText(path) ?? '';
|
package/src/host.ts
CHANGED
|
@@ -6,13 +6,14 @@ import {
|
|
|
6
6
|
type BuildResult,
|
|
7
7
|
type Bundler,
|
|
8
8
|
type ComponentCompilers,
|
|
9
|
-
type
|
|
10
|
-
type FlatFiles,
|
|
9
|
+
type FileTree,
|
|
11
10
|
type PackageCdnFactory,
|
|
11
|
+
type PortChange,
|
|
12
|
+
type ProcessOptions,
|
|
12
13
|
type ProjectProfile,
|
|
14
|
+
type RuntimeProcess,
|
|
15
|
+
type ServerAddress,
|
|
13
16
|
type Transformer,
|
|
14
|
-
type SpawnOptions,
|
|
15
|
-
type WebContainerProcess,
|
|
16
17
|
} from '@seanhogg/builderforce-webcontainers-core';
|
|
17
18
|
import { PREVIEW_ID, isPortPath, isWireRequest, previewBase, type WireRequest, type WireResponse } from './protocol.js';
|
|
18
19
|
import { createProcessHost, servePortRequest, type NodeRuntimeOptions } from './node/index.js';
|
|
@@ -41,9 +42,9 @@ export interface RelayOptions {
|
|
|
41
42
|
scope?: never;
|
|
42
43
|
}
|
|
43
44
|
|
|
44
|
-
export type
|
|
45
|
+
export type PreviewRuntimeOptions = (SameOriginOptions | RelayOptions) & CommonRuntimeOptions;
|
|
45
46
|
|
|
46
|
-
export interface
|
|
47
|
+
export interface CommonRuntimeOptions {
|
|
47
48
|
/** Compiler. Defaults to esbuild-wasm. */
|
|
48
49
|
transformer?: Transformer;
|
|
49
50
|
/** Package CDN factory, for previews (`dev: true`) and builds (`dev: false`). Defaults to esm.sh. */
|
|
@@ -58,7 +59,7 @@ export interface CommonBootOptions {
|
|
|
58
59
|
attribution?: boolean;
|
|
59
60
|
/** Reload open previews when files change. Default true. */
|
|
60
61
|
liveReload?: boolean;
|
|
61
|
-
/** The Node runtime behind `
|
|
62
|
+
/** The Node runtime behind `run` (process workers, npm registry). */
|
|
62
63
|
node?: NodeRuntimeOptions;
|
|
63
64
|
}
|
|
64
65
|
|
|
@@ -84,7 +85,8 @@ export interface PreviewRuntime {
|
|
|
84
85
|
readonly url: string;
|
|
85
86
|
readonly fs: VirtualFileSystem;
|
|
86
87
|
readonly server: DevServer;
|
|
87
|
-
|
|
88
|
+
/** Write files into the runtime (nested or flat; see `FileTree`). Open previews reload. */
|
|
89
|
+
writeTree(files: FileTree): void;
|
|
88
90
|
/** Whether this runtime can serve the mounted project, and why not. */
|
|
89
91
|
profile(): ProjectProfile;
|
|
90
92
|
/**
|
|
@@ -96,17 +98,16 @@ export interface PreviewRuntime {
|
|
|
96
98
|
/** Uncaught errors and rejections raised inside the preview. */
|
|
97
99
|
onError(listener: (error: PreviewError) => void): () => void;
|
|
98
100
|
/**
|
|
99
|
-
* Run a command — `node server.js`, `npm install`, `npm run dev`, `
|
|
100
|
-
* terminal — in its own Web Worker.
|
|
101
|
-
*
|
|
102
|
-
* `{ terminal: { cols, rows } }` when wiring it to xterm.js.
|
|
101
|
+
* Run a command — `node server.js`, `npm install`, `npm run dev`, `bfsh` for a
|
|
102
|
+
* terminal — in its own Web Worker. Pass `{ terminal: { cols, rows } }` when
|
|
103
|
+
* wiring it to xterm.js.
|
|
103
104
|
*/
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
/**
|
|
107
|
-
|
|
108
|
-
/**
|
|
109
|
-
|
|
105
|
+
run(command: string, args?: string[], options?: ProcessOptions): RuntimeProcess;
|
|
106
|
+
run(command: string, options?: ProcessOptions): RuntimeProcess;
|
|
107
|
+
/** A process started listening (or a dev script handed off to the preview); `url` loads it in an iframe. */
|
|
108
|
+
onServerListening(listener: (server: ServerAddress) => void): () => void;
|
|
109
|
+
/** A virtual server opened or closed its port. */
|
|
110
|
+
onPortChange(listener: (change: PortChange) => void): () => void;
|
|
110
111
|
dispose(): void;
|
|
111
112
|
}
|
|
112
113
|
|
|
@@ -130,7 +131,7 @@ export function toWire(reqId: number, served: Awaited<ReturnType<DevServer['hand
|
|
|
130
131
|
* nothing else: no cross-origin isolation headers, no SharedArrayBuffer. Pass
|
|
131
132
|
* `relayUrl` to serve the preview from its own origin (see `RelayOptions`).
|
|
132
133
|
*/
|
|
133
|
-
export async function
|
|
134
|
+
export async function startPreviewRuntime(options: PreviewRuntimeOptions): Promise<PreviewRuntime> {
|
|
134
135
|
const id = options.id ?? randomId();
|
|
135
136
|
if (!PREVIEW_ID.test(id)) throw new Error(`Preview id "${id}" may only contain letters, digits, "_" and "-".`);
|
|
136
137
|
|
|
@@ -196,7 +197,7 @@ export async function bootPreviewRuntime(options: BootOptions): Promise<PreviewR
|
|
|
196
197
|
url: previewUrl,
|
|
197
198
|
fs,
|
|
198
199
|
server,
|
|
199
|
-
|
|
200
|
+
writeTree: (files) => fs.writeTree(files),
|
|
200
201
|
profile: () => server.profile(),
|
|
201
202
|
async build(buildOptions = {}) {
|
|
202
203
|
const bundler = options.bundler ?? (await createEsbuildWasmBundler());
|
|
@@ -206,8 +207,9 @@ export async function bootPreviewRuntime(options: BootOptions): Promise<PreviewR
|
|
|
206
207
|
errorListeners.add(listener);
|
|
207
208
|
return () => errorListeners.delete(listener);
|
|
208
209
|
},
|
|
209
|
-
|
|
210
|
-
|
|
210
|
+
run: (command: string, args?: string[] | ProcessOptions, runOptions?: ProcessOptions) => processes.run(command, args as string[], runOptions),
|
|
211
|
+
onServerListening: (listener) => processes.servers.onListening(listener),
|
|
212
|
+
onPortChange: (listener) => processes.servers.onPortChange(listener),
|
|
211
213
|
dispose() {
|
|
212
214
|
processes.dispose();
|
|
213
215
|
window.removeEventListener('message', onWindowMessage);
|
package/src/index.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export { startPreviewRuntime } from './host.js';
|
|
2
2
|
export type {
|
|
3
|
-
|
|
3
|
+
PreviewRuntimeOptions,
|
|
4
4
|
SameOriginOptions,
|
|
5
5
|
RelayOptions,
|
|
6
|
-
|
|
6
|
+
CommonRuntimeOptions,
|
|
7
7
|
PreviewRuntime,
|
|
8
8
|
PreviewError,
|
|
9
9
|
RuntimeBuildOptions,
|
package/src/node/processHost.ts
CHANGED
|
@@ -9,15 +9,17 @@
|
|
|
9
9
|
import {
|
|
10
10
|
DEFAULT_ENV,
|
|
11
11
|
PortRegistry,
|
|
12
|
+
ServerEvents,
|
|
12
13
|
allocatePid,
|
|
13
|
-
|
|
14
|
+
runArguments,
|
|
15
|
+
toRuntimeProcess,
|
|
14
16
|
type ChildHandle,
|
|
15
17
|
type FsChange,
|
|
16
18
|
type HttpRequestData,
|
|
17
19
|
type HttpResponseData,
|
|
18
|
-
type
|
|
20
|
+
type ProcessOptions,
|
|
21
|
+
type RuntimeProcess,
|
|
19
22
|
type VirtualFileSystem,
|
|
20
|
-
type WebContainerProcess,
|
|
21
23
|
} from '@seanhogg/builderforce-webcontainers-core';
|
|
22
24
|
import type { FsOp, HostToWorker, WorkerToHost } from './protocol.js';
|
|
23
25
|
import { portPath } from './protocol.js';
|
|
@@ -63,9 +65,9 @@ interface Proc {
|
|
|
63
65
|
|
|
64
66
|
export class ProcessHost {
|
|
65
67
|
readonly ports = new PortRegistry();
|
|
68
|
+
/** Servers listening, ports opening and closing. */
|
|
69
|
+
readonly servers = new ServerEvents();
|
|
66
70
|
private readonly procs = new Set<Proc>();
|
|
67
|
-
private readonly serverReady = new Set<(port: number, url: string) => void>();
|
|
68
|
-
private readonly portListeners = new Set<(port: number, type: 'open' | 'close', url: string) => void>();
|
|
69
71
|
private applyingFrom?: Proc;
|
|
70
72
|
private flushScheduled = false;
|
|
71
73
|
private nextReq = 1;
|
|
@@ -77,8 +79,7 @@ export class ProcessHost {
|
|
|
77
79
|
this.stopPorts = this.ports.watch(({ type, port }) => {
|
|
78
80
|
const url = this.serverUrl(port);
|
|
79
81
|
for (const proc of this.procs) this.post(proc, { type: 'ports', open: type === 'open' ? [port] : [], closed: type === 'close' ? [port] : [] });
|
|
80
|
-
|
|
81
|
-
if (type === 'open') for (const l of this.serverReady) l(port, url);
|
|
82
|
+
this.servers.portChanged({ type, port, url });
|
|
82
83
|
});
|
|
83
84
|
}
|
|
84
85
|
|
|
@@ -86,13 +87,15 @@ export class ProcessHost {
|
|
|
86
87
|
return new URL(portPath(this.options.previewBase, port), this.options.previewOrigin).href;
|
|
87
88
|
}
|
|
88
89
|
|
|
89
|
-
/**
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
90
|
+
/** Start a command in its own Web Worker, as a stream-based {@link RuntimeProcess}. */
|
|
91
|
+
run(command: string, args?: string[], options?: ProcessOptions): RuntimeProcess;
|
|
92
|
+
run(command: string, options?: ProcessOptions): RuntimeProcess;
|
|
93
|
+
run(command: string, argsOrOptions?: string[] | ProcessOptions, maybeOptions?: ProcessOptions): RuntimeProcess {
|
|
94
|
+
const [args, options] = runArguments(argsOrOptions, maybeOptions);
|
|
95
|
+
return toRuntimeProcess(this.spawnChild(command, args, options), !!options.terminal);
|
|
93
96
|
}
|
|
94
97
|
|
|
95
|
-
spawnChild(command: string, args: string[] = [], options:
|
|
98
|
+
spawnChild(command: string, args: string[] = [], options: ProcessOptions = {}): ChildHandle {
|
|
96
99
|
const pid = allocatePid();
|
|
97
100
|
const worker = this.options.createWorker();
|
|
98
101
|
let resolveExit!: (code: number) => void;
|
|
@@ -156,14 +159,6 @@ export class ProcessHost {
|
|
|
156
159
|
return this.ports.request(port, request);
|
|
157
160
|
}
|
|
158
161
|
|
|
159
|
-
on(event: 'server-ready', listener: (port: number, url: string) => void): () => void;
|
|
160
|
-
on(event: 'port', listener: (port: number, type: 'open' | 'close', url: string) => void): () => void;
|
|
161
|
-
on(event: 'server-ready' | 'port', listener: (...args: any[]) => void): () => void {
|
|
162
|
-
const set = (event === 'server-ready' ? this.serverReady : this.portListeners) as Set<(...args: any[]) => void>;
|
|
163
|
-
set.add(listener);
|
|
164
|
-
return () => set.delete(listener);
|
|
165
|
-
}
|
|
166
|
-
|
|
167
162
|
dispose(): void {
|
|
168
163
|
for (const proc of [...this.procs]) {
|
|
169
164
|
proc.worker.terminate();
|
|
@@ -291,8 +286,8 @@ export class ProcessHost {
|
|
|
291
286
|
case 'loopback':
|
|
292
287
|
void this.ports.request(message.port, message.request).then((response) => this.post(proc, { type: 'loopback-response', reqId: message.reqId, response: response ?? null }));
|
|
293
288
|
return;
|
|
294
|
-
case '
|
|
295
|
-
|
|
289
|
+
case 'listening':
|
|
290
|
+
this.servers.announce({ port: message.port, url: message.url });
|
|
296
291
|
return;
|
|
297
292
|
}
|
|
298
293
|
}
|
package/src/node/protocol.ts
CHANGED
|
@@ -55,7 +55,7 @@ export type WorkerToHost =
|
|
|
55
55
|
| { type: 'unlisten'; port: number }
|
|
56
56
|
| { type: 'http-response'; reqId: number; response: HttpResponseData }
|
|
57
57
|
| { type: 'loopback'; reqId: number; port: number; request: HttpRequestData }
|
|
58
|
-
| { type: '
|
|
58
|
+
| { type: 'listening'; port: number; url: string };
|
|
59
59
|
|
|
60
60
|
/** The preview-relative URL a virtual server on `port` is reachable at. */
|
|
61
61
|
export function portPath(previewBase: string, port: number): string {
|
|
@@ -144,7 +144,7 @@ export function createWorkerProcess(deps: WorkerProcessDeps): { receive(message:
|
|
|
144
144
|
});
|
|
145
145
|
},
|
|
146
146
|
serverUrl: (port) => new URL(portPath(message.previewBase, port), message.previewOrigin).href,
|
|
147
|
-
announceServer: (port, url) => deps.post({ type: '
|
|
147
|
+
announceServer: (port, url) => deps.post({ type: 'listening', port, url }),
|
|
148
148
|
previewUrl: message.previewUrl,
|
|
149
149
|
registry: message.registry,
|
|
150
150
|
packageCache: message.packageCache ? deps.packageCache?.() : undefined,
|
|
@@ -154,7 +154,7 @@ export function createWorkerProcess(deps: WorkerProcessDeps): { receive(message:
|
|
|
154
154
|
|
|
155
155
|
const resolved = resolveCommand(fs, programs, message.command, message.args, message.cwd, message.env);
|
|
156
156
|
if (!resolved) {
|
|
157
|
-
deps.post({ type: 'stderr', data: `
|
|
157
|
+
deps.post({ type: 'stderr', data: `bfsh: command not found: ${message.command}\n` });
|
|
158
158
|
deps.post({ type: 'exit', code: 127 });
|
|
159
159
|
return;
|
|
160
160
|
}
|