@seanhogg/builderforce-webcontainers-core 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 +1 -1
- package/dist/build.d.ts +3 -3
- package/dist/build.d.ts.map +1 -1
- package/dist/build.js +1 -1
- package/dist/build.js.map +1 -1
- package/dist/check/typecheck.d.ts +2 -2
- package/dist/check/typecheck.d.ts.map +1 -1
- package/dist/check/typecheck.js +1 -1
- package/dist/check/typecheck.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/node/childProcess.js +1 -1
- package/dist/node/childProcess.js.map +1 -1
- package/dist/node/misc.js +1 -1
- package/dist/node/misc.js.map +1 -1
- package/dist/shell/builtins.d.ts.map +1 -1
- package/dist/shell/builtins.js +2 -2
- package/dist/shell/builtins.js.map +1 -1
- package/dist/shell/devHandoff.d.ts +1 -1
- package/dist/shell/exec.js +4 -4
- package/dist/shell/exec.js.map +1 -1
- package/dist/shell/expand.js +1 -1
- package/dist/shell/expand.js.map +1 -1
- package/dist/shell/program.d.ts +1 -1
- package/dist/shell/program.js +3 -3
- package/dist/shell/program.js.map +1 -1
- package/dist/system/commands.d.ts +4 -4
- package/dist/system/commands.d.ts.map +1 -1
- package/dist/system/commands.js +3 -3
- package/dist/system/commands.js.map +1 -1
- package/dist/system/index.d.ts +6 -4
- package/dist/system/index.d.ts.map +1 -1
- package/dist/system/index.js +2 -1
- package/dist/system/index.js.map +1 -1
- package/dist/system/kernel.d.ts +9 -16
- package/dist/system/kernel.d.ts.map +1 -1
- package/dist/system/kernel.js +10 -23
- package/dist/system/kernel.js.map +1 -1
- package/dist/system/process.d.ts +18 -14
- package/dist/system/process.d.ts.map +1 -1
- package/dist/system/process.js +12 -10
- package/dist/system/process.js.map +1 -1
- package/dist/system/programs.js +1 -1
- package/dist/system/programs.js.map +1 -1
- package/dist/system/serverEvents.d.ts +27 -0
- package/dist/system/serverEvents.d.ts.map +1 -0
- package/dist/system/serverEvents.js +33 -0
- package/dist/system/serverEvents.js.map +1 -0
- package/dist/system/types.d.ts +4 -4
- package/dist/system/types.d.ts.map +1 -1
- package/dist/vfs.d.ts +11 -16
- package/dist/vfs.d.ts.map +1 -1
- package/dist/vfs.js +3 -8
- package/dist/vfs.js.map +1 -1
- package/package.json +1 -1
- package/src/build.ts +4 -4
- package/src/check/typecheck.ts +4 -4
- package/src/index.ts +1 -1
- package/src/node/childProcess.ts +1 -1
- package/src/node/misc.ts +1 -1
- package/src/shell/builtins.ts +2 -2
- package/src/shell/devHandoff.ts +1 -1
- package/src/shell/exec.ts +4 -4
- package/src/shell/expand.ts +1 -1
- package/src/shell/program.ts +4 -4
- package/src/system/commands.ts +6 -6
- package/src/system/index.ts +6 -4
- package/src/system/kernel.ts +15 -32
- package/src/system/process.ts +28 -20
- package/src/system/programs.ts +1 -1
- package/src/system/serverEvents.ts +44 -0
- package/src/system/types.ts +4 -4
- package/src/vfs.ts +13 -13
package/dist/vfs.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"vfs.js","sourceRoot":"","sources":["../src/vfs.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"vfs.js","sourceRoot":"","sources":["../src/vfs.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@seanhogg/builderforce-webcontainers-core",
|
|
3
|
-
"version": "2026.10.
|
|
3
|
+
"version": "2026.10.6",
|
|
4
4
|
"description": "BuilderForce WebContainers — core. A runtime-agnostic in-browser dev server: virtual file system, module resolution, import rewriting to a package CDN, CSS/JSON/asset modules and project detection. No COOP/COEP required.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.js",
|
package/src/build.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { VirtualFileSystem, type
|
|
1
|
+
import { VirtualFileSystem, type FileTree } from './vfs.js';
|
|
2
2
|
import type { Bundler, BundleOutput } from './bundler.js';
|
|
3
3
|
import type { PackageCdnFactory } from './packageCdn.js';
|
|
4
4
|
import { esmShCdnFactory } from './packageCdn.js';
|
|
@@ -18,8 +18,8 @@ import { findHtmlEntries, rewriteBuiltHtml } from './buildHtml.js';
|
|
|
18
18
|
* content hash in its name and can be cached forever.
|
|
19
19
|
*/
|
|
20
20
|
export interface BuildOptions {
|
|
21
|
-
/** The project: a file system, or files as `
|
|
22
|
-
files: VirtualFileSystem |
|
|
21
|
+
/** The project: a file system, or files as `writeTree()` takes them. */
|
|
22
|
+
files: VirtualFileSystem | FileTree;
|
|
23
23
|
bundler: Bundler;
|
|
24
24
|
/** Package CDN factory (called with `dev: false`). Default: esm.sh. */
|
|
25
25
|
cdn?: PackageCdnFactory;
|
|
@@ -50,7 +50,7 @@ const encoder = new TextEncoder();
|
|
|
50
50
|
function toFileSystem(files: BuildOptions['files']): VirtualFileSystem {
|
|
51
51
|
if (files instanceof VirtualFileSystem) return files;
|
|
52
52
|
const fs = new VirtualFileSystem();
|
|
53
|
-
fs.
|
|
53
|
+
fs.writeTree(files);
|
|
54
54
|
return fs;
|
|
55
55
|
}
|
|
56
56
|
|
package/src/check/typecheck.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type * as TS from 'typescript';
|
|
2
|
-
import { VirtualFileSystem, type
|
|
2
|
+
import { VirtualFileSystem, type FileTree } from '../vfs.js';
|
|
3
3
|
import { readProjectConfig } from '../projectConfig.js';
|
|
4
4
|
import { isBareSpecifier, resolveAlias, splitPackageSpecifier } from '../resolve.js';
|
|
5
5
|
import { acquireExtendedConfigs, acquireTypes, type FetchLike } from './acquire.js';
|
|
@@ -46,10 +46,10 @@ interface Reach {
|
|
|
46
46
|
libFiles: Set<string>;
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
-
function toFileSystem(files: VirtualFileSystem |
|
|
49
|
+
function toFileSystem(files: VirtualFileSystem | FileTree): VirtualFileSystem {
|
|
50
50
|
if (files instanceof VirtualFileSystem) return files;
|
|
51
51
|
const fs = new VirtualFileSystem();
|
|
52
|
-
fs.
|
|
52
|
+
fs.writeTree(files);
|
|
53
53
|
return fs;
|
|
54
54
|
}
|
|
55
55
|
|
|
@@ -78,7 +78,7 @@ function collectReach(ts: typeof TS, fs: VirtualFileSystem, projects: CheckProje
|
|
|
78
78
|
|
|
79
79
|
export async function typecheckProject(
|
|
80
80
|
ts: typeof TS,
|
|
81
|
-
files: VirtualFileSystem |
|
|
81
|
+
files: VirtualFileSystem | FileTree,
|
|
82
82
|
options: TypecheckOptions,
|
|
83
83
|
): Promise<TypecheckResult> {
|
|
84
84
|
const fs = toFileSystem(files);
|
package/src/index.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { VirtualFileSystem } from './vfs.js';
|
|
2
|
-
export type { FileContent,
|
|
2
|
+
export type { FileContent, FileTree, FlatFiles, FsChange } from './vfs.js';
|
|
3
3
|
export { normalizePath, dirname, basename, join, extname } from './paths.js';
|
|
4
4
|
export { DevServer } from './devServer.js';
|
|
5
5
|
export type { DevServerOptions, ServedFile } from './devServer.js';
|
package/src/node/childProcess.ts
CHANGED
|
@@ -68,7 +68,7 @@ class ChildProcess extends EventEmitterBase {
|
|
|
68
68
|
|
|
69
69
|
const env: Record<string, string> = {};
|
|
70
70
|
for (const [k, v] of Object.entries(options.env ?? deps.env())) if (v !== undefined) env[k] = String(v);
|
|
71
|
-
const [file, argv] = options.shell ? ['
|
|
71
|
+
const [file, argv] = options.shell ? ['bfsh', ['-c', [command, ...args].join(' ')]] : [command, args];
|
|
72
72
|
const release = deps.loop.hold();
|
|
73
73
|
try {
|
|
74
74
|
this.handle = deps.system.spawn(file, argv, { cwd: options.cwd ?? deps.cwd(), env });
|
package/src/node/misc.ts
CHANGED
|
@@ -27,7 +27,7 @@ export function createOsModule(env: () => Record<string, string | undefined>): R
|
|
|
27
27
|
hostname: () => 'localhost',
|
|
28
28
|
homedir: () => env().HOME ?? '/home/user',
|
|
29
29
|
tmpdir: () => env().TMPDIR ?? '/tmp',
|
|
30
|
-
userInfo: () => ({ uid: 1000, gid: 1000, username: env().USER ?? 'user', homedir: env().HOME ?? '/home/user', shell: '/bin/
|
|
30
|
+
userInfo: () => ({ uid: 1000, gid: 1000, username: env().USER ?? 'user', homedir: env().HOME ?? '/home/user', shell: '/bin/bfsh' }),
|
|
31
31
|
cpus: () => cpus,
|
|
32
32
|
availableParallelism: () => cpus.length,
|
|
33
33
|
totalmem: () => 4 * 1024 ** 3,
|
package/src/shell/builtins.ts
CHANGED
|
@@ -31,9 +31,9 @@ export const COREUTILS: Record<string, Command> = {
|
|
|
31
31
|
};
|
|
32
32
|
|
|
33
33
|
/** Programs the runtime provides besides coreutils (reported by `which`/`type`). */
|
|
34
|
-
export const SYSTEM_PROGRAMS = ['node', 'npm', 'npx', '
|
|
34
|
+
export const SYSTEM_PROGRAMS = ['node', 'npm', 'npx', 'bfsh', 'sh', 'bash'];
|
|
35
35
|
|
|
36
|
-
const HELP = `
|
|
36
|
+
const HELP = `bfsh — BuilderForce WebContainers shell
|
|
37
37
|
|
|
38
38
|
Builtins: cd, pwd, export, unset, exit, source, history, type, which, help
|
|
39
39
|
Files: ls [-la], cat, mkdir [-p], rm [-rf], cp [-r], mv, touch
|
package/src/shell/devHandoff.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* dev` / `npm start` usually run) cannot run here as-is — Vite itself needs
|
|
4
4
|
* esbuild's native binary — but the runtime already IS a Vite-style dev server:
|
|
5
5
|
* the in-browser DevServer serving the preview. So these commands announce that
|
|
6
|
-
* preview as their server (
|
|
6
|
+
* preview as their server (a listening announcement) and stay "running" until Ctrl-C,
|
|
7
7
|
* which is exactly what a host waiting on `npm run dev` expects.
|
|
8
8
|
*/
|
|
9
9
|
import type { CommandContext } from './context.js';
|
package/src/shell/exec.ts
CHANGED
|
@@ -73,7 +73,7 @@ export class Shell implements ShellState {
|
|
|
73
73
|
script = parse(source);
|
|
74
74
|
} catch (error) {
|
|
75
75
|
if (!(error instanceof ShellSyntaxError)) throw error;
|
|
76
|
-
io.stderr(`
|
|
76
|
+
io.stderr(`bfsh: ${error.message}\n`);
|
|
77
77
|
this.lastStatus = 2;
|
|
78
78
|
return 2;
|
|
79
79
|
}
|
|
@@ -176,7 +176,7 @@ export class Shell implements ShellState {
|
|
|
176
176
|
if (redirect.op === '<') {
|
|
177
177
|
const text = this.system.fs.readText(path);
|
|
178
178
|
if (text === undefined) {
|
|
179
|
-
io.stderr(`
|
|
179
|
+
io.stderr(`bfsh: ${target}: No such file or directory\n`);
|
|
180
180
|
return 1;
|
|
181
181
|
}
|
|
182
182
|
const queue = new InputQueue();
|
|
@@ -186,7 +186,7 @@ export class Shell implements ShellState {
|
|
|
186
186
|
continue;
|
|
187
187
|
}
|
|
188
188
|
if (this.system.fs.isDirectory(path)) {
|
|
189
|
-
io.stderr(`
|
|
189
|
+
io.stderr(`bfsh: ${target}: Is a directory\n`);
|
|
190
190
|
return 1;
|
|
191
191
|
}
|
|
192
192
|
const append = redirect.op === '>>' || redirect.op === '2>>';
|
|
@@ -240,7 +240,7 @@ export class Shell implements ShellState {
|
|
|
240
240
|
child = this.system.spawn(name, args, { cwd: this.cwd, env, terminal: size });
|
|
241
241
|
} catch (error) {
|
|
242
242
|
if ((error as { code?: string }).code !== 'ENOENT') throw error;
|
|
243
|
-
io.stderr(`
|
|
243
|
+
io.stderr(`bfsh: command not found: ${name}\n`);
|
|
244
244
|
return 127;
|
|
245
245
|
}
|
|
246
246
|
child.onStdout(io.stdout);
|
package/src/shell/expand.ts
CHANGED
|
@@ -29,7 +29,7 @@ async function paramValue(seg: Extract<Segment, { kind: 'var' }>, ctx: ExpandCon
|
|
|
29
29
|
case '@':
|
|
30
30
|
case '*': value = ctx.positional.join(' '); break;
|
|
31
31
|
case '!': value = ''; break;
|
|
32
|
-
case '0': value = '
|
|
32
|
+
case '0': value = 'bfsh'; break;
|
|
33
33
|
default: value = /^\d$/.test(seg.name) ? ctx.positional[Number(seg.name) - 1] : ctx.get(seg.name);
|
|
34
34
|
}
|
|
35
35
|
if (!seg.op) return value ?? '';
|
package/src/shell/program.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The `
|
|
2
|
+
* The `bfsh` program: `bfsh -c "…"`, `bfsh script.sh`, a script on stdin, or —
|
|
3
3
|
* attached to a terminal — the interactive shell. Interactively the shell owns
|
|
4
4
|
* the keyboard (raw mode) while it edits a line, and hands it to the running
|
|
5
5
|
* job otherwise: straight through to a terminal-attached child (which has its
|
|
@@ -22,7 +22,7 @@ function createShell(ctx: ProgramContext, positional: string[] = []): Shell {
|
|
|
22
22
|
return new Shell({
|
|
23
23
|
system: ctx.system,
|
|
24
24
|
cwd: ctx.cwd,
|
|
25
|
-
env: { ...ctx.env, SHELL: '/bin/
|
|
25
|
+
env: { ...ctx.env, SHELL: '/bin/bfsh' },
|
|
26
26
|
positional,
|
|
27
27
|
terminalSize: () => (ctx.io.terminal ? { cols: ctx.io.terminal.cols, rows: ctx.io.terminal.rows } : undefined),
|
|
28
28
|
commands: SHELL_COMMANDS,
|
|
@@ -131,7 +131,7 @@ async function interactive(ctx: ProgramContext): Promise<number> {
|
|
|
131
131
|
try {
|
|
132
132
|
await shell.exec(line, io, current.input, controller.signal);
|
|
133
133
|
} catch (error) {
|
|
134
|
-
ctx.io.stderr(`
|
|
134
|
+
ctx.io.stderr(`bfsh: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
135
135
|
}
|
|
136
136
|
job = undefined;
|
|
137
137
|
if (shell.exitRequested !== undefined) return finish(shell.exitRequested);
|
|
@@ -206,7 +206,7 @@ export const shellProgram: Program = async (ctx) => {
|
|
|
206
206
|
const path = args[0].startsWith('/') ? args[0] : join(ctx.cwd, args[0]);
|
|
207
207
|
const source = ctx.system.fs.readText(path);
|
|
208
208
|
if (source === undefined) {
|
|
209
|
-
ctx.io.stderr(`
|
|
209
|
+
ctx.io.stderr(`bfsh: ${args[0]}: No such file or directory\n`);
|
|
210
210
|
return 127;
|
|
211
211
|
}
|
|
212
212
|
return runOnce(ctx, createShell(ctx, args.slice(1)), source.replace(/^#!.*\n/, ''));
|
package/src/system/commands.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Command lookup, shared by the kernel, the browser host and the shell:
|
|
3
|
-
* registered programs first (node, npm,
|
|
3
|
+
* registered programs first (node, npm, bfsh, coreutils), then a path or a
|
|
4
4
|
* `PATH`/`node_modules/.bin` lookup. A found file runs under `node` (a `.bin`
|
|
5
|
-
* shim runs its target directly) or, with a shell shebang, under `
|
|
5
|
+
* shim runs its target directly) or, with a shell shebang, under `bfsh`.
|
|
6
6
|
*/
|
|
7
7
|
import type { VirtualFileSystem } from '../vfs.js';
|
|
8
8
|
import { dirname, extname, join, normalizePath } from '../paths.js';
|
|
@@ -10,7 +10,7 @@ import { readBinLink } from '../installer/bins.js';
|
|
|
10
10
|
import type { Program } from './types.js';
|
|
11
11
|
|
|
12
12
|
export interface ResolvedCommand {
|
|
13
|
-
/** The registered program that will run (`node`, `
|
|
13
|
+
/** The registered program that will run (`node`, `bfsh`, `ls`…). */
|
|
14
14
|
name: string;
|
|
15
15
|
program: Program;
|
|
16
16
|
args: string[];
|
|
@@ -40,12 +40,12 @@ export function findExecutable(fs: VirtualFileSystem, command: string, cwd: stri
|
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
/** How to run a script file: its interpreter and arguments. */
|
|
43
|
-
export function interpreterFor(fs: VirtualFileSystem, path: string): { name: 'node' | '
|
|
43
|
+
export function interpreterFor(fs: VirtualFileSystem, path: string): { name: 'node' | 'bfsh'; args: string[] } {
|
|
44
44
|
const target = readBinLink(fs, path);
|
|
45
45
|
if (target) return { name: 'node', args: [target] };
|
|
46
46
|
const firstLine = (fs.readText(path) ?? '').split('\n', 1)[0] ?? '';
|
|
47
|
-
if (/^#!.*\b(?:sh|bash|
|
|
48
|
-
if (['.sh', '.bash'].includes(extname(path))) return { name: '
|
|
47
|
+
if (/^#!.*\b(?:sh|bash|bfsh|zsh|dash)\b/.test(firstLine)) return { name: 'bfsh', args: [path] };
|
|
48
|
+
if (['.sh', '.bash'].includes(extname(path))) return { name: 'bfsh', args: [path] };
|
|
49
49
|
return { name: 'node', args: [path] };
|
|
50
50
|
}
|
|
51
51
|
|
package/src/system/index.ts
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
export { Kernel, DEFAULT_ENV } from './kernel.js';
|
|
2
|
-
export type { KernelOptions
|
|
3
|
-
export type { System, Program, ProgramContext, ProgramIO, TerminalPort,
|
|
2
|
+
export type { KernelOptions } from './kernel.js';
|
|
3
|
+
export type { System, Program, ProgramContext, ProgramIO, TerminalPort, ProcessOptions, ChildHandle, HttpRequestData, HttpResponseData, HttpHandler } from './types.js';
|
|
4
4
|
export { PortRegistry, parsePortPath } from './ports.js';
|
|
5
5
|
export type { PortEvent } from './ports.js';
|
|
6
|
-
export { runProgram,
|
|
7
|
-
export type {
|
|
6
|
+
export { runProgram, runArguments, toRuntimeProcess, allocatePid } from './process.js';
|
|
7
|
+
export type { RuntimeProcess, RunProgramOptions } from './process.js';
|
|
8
|
+
export { ServerEvents } from './serverEvents.js';
|
|
9
|
+
export type { ServerAddress, PortChange } from './serverEvents.js';
|
|
8
10
|
export { resolveCommand, findExecutable, binDirectories, interpreterFor, commandNotFound } from './commands.js';
|
|
9
11
|
export type { ResolvedCommand } from './commands.js';
|
|
10
12
|
export { InputQueue, LineDiscipline, toTerminal } from './terminal.js';
|
package/src/system/kernel.ts
CHANGED
|
@@ -7,10 +7,11 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import { VirtualFileSystem } from '../vfs.js';
|
|
9
9
|
import type { PackageCache } from '../installer/registry.js';
|
|
10
|
-
import type { ChildHandle, HttpRequestData, HttpResponseData, Program,
|
|
10
|
+
import type { ChildHandle, HttpRequestData, HttpResponseData, Program, ProcessOptions, System } from './types.js';
|
|
11
11
|
import { PortRegistry } from './ports.js';
|
|
12
|
+
import { ServerEvents } from './serverEvents.js';
|
|
12
13
|
import { commandNotFound, resolveCommand } from './commands.js';
|
|
13
|
-
import { runProgram,
|
|
14
|
+
import { runArguments, runProgram, toRuntimeProcess, type RuntimeProcess } from './process.js';
|
|
14
15
|
|
|
15
16
|
export interface KernelOptions {
|
|
16
17
|
fs?: VirtualFileSystem;
|
|
@@ -26,19 +27,11 @@ export interface KernelOptions {
|
|
|
26
27
|
packageCache?: PackageCache;
|
|
27
28
|
}
|
|
28
29
|
|
|
29
|
-
export interface KernelSpawnOptions extends SpawnOptions {
|
|
30
|
-
/** Kept for `@webcontainer/api` parity; output is always delivered. */
|
|
31
|
-
output?: boolean;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
type ServerReadyListener = (port: number, url: string) => void;
|
|
35
|
-
type PortListener = (port: number, type: 'open' | 'close', url: string) => void;
|
|
36
|
-
|
|
37
30
|
export const DEFAULT_ENV: Record<string, string> = {
|
|
38
31
|
PATH: '/usr/local/bin:/usr/bin:/bin',
|
|
39
32
|
HOME: '/home/user',
|
|
40
33
|
USER: 'user',
|
|
41
|
-
SHELL: '/bin/
|
|
34
|
+
SHELL: '/bin/bfsh',
|
|
42
35
|
TERM: 'xterm-256color',
|
|
43
36
|
COLORTERM: 'truecolor',
|
|
44
37
|
LANG: 'en_US.UTF-8',
|
|
@@ -48,20 +41,16 @@ export const DEFAULT_ENV: Record<string, string> = {
|
|
|
48
41
|
export class Kernel {
|
|
49
42
|
readonly fs: VirtualFileSystem;
|
|
50
43
|
readonly ports = new PortRegistry();
|
|
44
|
+
/** Servers listening, ports opening and closing. */
|
|
45
|
+
readonly servers = new ServerEvents();
|
|
51
46
|
readonly env: Record<string, string>;
|
|
52
|
-
private readonly serverReady = new Set<ServerReadyListener>();
|
|
53
|
-
private readonly portListeners = new Set<PortListener>();
|
|
54
47
|
private readonly system: System;
|
|
55
48
|
|
|
56
49
|
constructor(private readonly options: KernelOptions) {
|
|
57
50
|
this.fs = options.fs ?? new VirtualFileSystem();
|
|
58
51
|
this.env = { ...DEFAULT_ENV, ...options.env };
|
|
59
52
|
const serverUrl = (port: number) => options.serverUrl?.(port) ?? `http://localhost:${port}/`;
|
|
60
|
-
this.ports.watch(({ type, port }) => {
|
|
61
|
-
const url = serverUrl(port);
|
|
62
|
-
for (const l of this.portListeners) l(port, type, url);
|
|
63
|
-
if (type === 'open') for (const l of this.serverReady) l(port, url);
|
|
64
|
-
});
|
|
53
|
+
this.ports.watch(({ type, port }) => this.servers.portChanged({ type, port, url: serverUrl(port) }));
|
|
65
54
|
this.system = {
|
|
66
55
|
fs: this.fs,
|
|
67
56
|
fetch: options.fetch ?? ((input, init) => globalThis.fetch(input, init)),
|
|
@@ -69,21 +58,23 @@ export class Kernel {
|
|
|
69
58
|
listen: (port, handler) => this.ports.listen(port, handler),
|
|
70
59
|
loopback: (port, request) => this.ports.request(port, request),
|
|
71
60
|
serverUrl,
|
|
72
|
-
announceServer: (port, url) => this.
|
|
61
|
+
announceServer: (port, url) => this.servers.announce({ port, url }),
|
|
73
62
|
previewUrl: options.previewUrl,
|
|
74
63
|
registry: options.registry,
|
|
75
64
|
packageCache: options.packageCache,
|
|
76
65
|
};
|
|
77
66
|
}
|
|
78
67
|
|
|
79
|
-
/**
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
68
|
+
/** Start a command — `run('npm', ['install'])`, `run('bfsh', { terminal })` — as a stream-based {@link RuntimeProcess}. */
|
|
69
|
+
run(command: string, args?: string[], options?: ProcessOptions): RuntimeProcess;
|
|
70
|
+
run(command: string, options?: ProcessOptions): RuntimeProcess;
|
|
71
|
+
run(command: string, argsOrOptions?: string[] | ProcessOptions, maybeOptions?: ProcessOptions): RuntimeProcess {
|
|
72
|
+
const [args, options] = runArguments(argsOrOptions, maybeOptions);
|
|
73
|
+
return toRuntimeProcess(this.spawnChild(command, args, options), !!options.terminal);
|
|
83
74
|
}
|
|
84
75
|
|
|
85
76
|
/** A child as a program's parent sees it — what `System.spawn` returns. */
|
|
86
|
-
spawnChild(command: string, args: string[] = [], options:
|
|
77
|
+
spawnChild(command: string, args: string[] = [], options: ProcessOptions = {}): ChildHandle {
|
|
87
78
|
const cwd = options.cwd ?? '/';
|
|
88
79
|
const env = { ...this.env, ...options.env };
|
|
89
80
|
const resolved = resolveCommand(this.fs, this.options.programs, command, args, cwd, env);
|
|
@@ -95,12 +86,4 @@ export class Kernel {
|
|
|
95
86
|
request(port: number, request: Partial<HttpRequestData> & { url: string }): Promise<HttpResponseData | undefined> {
|
|
96
87
|
return this.ports.request(port, { method: 'GET', headers: {}, body: null, ...request });
|
|
97
88
|
}
|
|
98
|
-
|
|
99
|
-
on(event: 'server-ready', listener: ServerReadyListener): () => void;
|
|
100
|
-
on(event: 'port', listener: PortListener): () => void;
|
|
101
|
-
on(event: 'server-ready' | 'port', listener: ServerReadyListener | PortListener): () => void {
|
|
102
|
-
const set = (event === 'server-ready' ? this.serverReady : this.portListeners) as Set<typeof listener>;
|
|
103
|
-
set.add(listener);
|
|
104
|
-
return () => set.delete(listener);
|
|
105
|
-
}
|
|
106
89
|
}
|
package/src/system/process.ts
CHANGED
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
* Running one program in-process: its stdin queue, TTY line discipline,
|
|
3
3
|
* output listeners and kill signal, packaged as a {@link ChildHandle}. The core
|
|
4
4
|
* kernel uses it for every process; the browser worker uses it for the one
|
|
5
|
-
* program it hosts. Also the
|
|
6
|
-
*
|
|
5
|
+
* program it hosts. Also {@link RuntimeProcess}, the stream-based handle the
|
|
6
|
+
* public `run()` returns.
|
|
7
7
|
*/
|
|
8
|
-
import type { ChildHandle, Program, ProgramIO,
|
|
8
|
+
import type { ChildHandle, Program, ProgramIO, ProcessOptions, System } from './types.js';
|
|
9
9
|
import { InputQueue, LineDiscipline, toTerminal } from './terminal.js';
|
|
10
10
|
|
|
11
11
|
let nextPid = 1000;
|
|
@@ -14,7 +14,7 @@ export function allocatePid(): number {
|
|
|
14
14
|
return nextPid++;
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
export interface RunProgramOptions extends
|
|
17
|
+
export interface RunProgramOptions extends ProcessOptions {
|
|
18
18
|
program: Program;
|
|
19
19
|
args: string[];
|
|
20
20
|
system: System;
|
|
@@ -122,18 +122,28 @@ export function runProgram(options: RunProgramOptions): ChildHandle {
|
|
|
122
122
|
};
|
|
123
123
|
}
|
|
124
124
|
|
|
125
|
-
/**
|
|
126
|
-
export interface
|
|
125
|
+
/** A running process as a host page sees it: streams for xterm.js or a log, and its exit code. */
|
|
126
|
+
export interface RuntimeProcess {
|
|
127
|
+
readonly pid: number;
|
|
127
128
|
/** stdout and stderr, interleaved as written (`\r\n` line endings when attached to a terminal). */
|
|
128
|
-
readonly
|
|
129
|
-
|
|
130
|
-
readonly
|
|
129
|
+
readonly combinedOutput: ReadableStream<string>;
|
|
130
|
+
/** Keystrokes or piped input; closing it is end-of-file. */
|
|
131
|
+
readonly stdin: WritableStream<string>;
|
|
132
|
+
/** Resolves with the exit code (128 + signal number when killed). */
|
|
133
|
+
wait(): Promise<number>;
|
|
134
|
+
/** Default `SIGTERM`; `SIGKILL` cannot be caught. */
|
|
131
135
|
kill(signal?: string): void;
|
|
132
|
-
|
|
136
|
+
/** Resize the attached terminal. A no-op for a process started without one. */
|
|
137
|
+
resizeTerminal(cols: number, rows: number): void;
|
|
133
138
|
}
|
|
134
139
|
|
|
135
|
-
|
|
136
|
-
|
|
140
|
+
/** `run(command, args?, options?)` and `run(command, options)` — the same call either way. */
|
|
141
|
+
export function runArguments(args: string[] | ProcessOptions = [], options: ProcessOptions = {}): [string[], ProcessOptions] {
|
|
142
|
+
return Array.isArray(args) ? [args, options] : [[], args];
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export function toRuntimeProcess(child: ChildHandle, terminal: boolean): RuntimeProcess {
|
|
146
|
+
const combinedOutput = new ReadableStream<string>({
|
|
137
147
|
start(controller) {
|
|
138
148
|
const push = (chunk: string) => controller.enqueue(terminal ? toTerminal(chunk) : chunk);
|
|
139
149
|
child.onStdout(push);
|
|
@@ -141,18 +151,16 @@ export function toWebContainerProcess(child: ChildHandle, terminal: boolean): We
|
|
|
141
151
|
void child.exit.then(() => controller.close());
|
|
142
152
|
},
|
|
143
153
|
});
|
|
144
|
-
const
|
|
154
|
+
const stdin = new WritableStream<string>({
|
|
145
155
|
write: (chunk) => child.write(chunk),
|
|
146
156
|
close: () => child.closeStdin(),
|
|
147
157
|
});
|
|
148
158
|
return {
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
159
|
+
pid: child.pid,
|
|
160
|
+
combinedOutput,
|
|
161
|
+
stdin,
|
|
162
|
+
wait: () => child.exit,
|
|
152
163
|
kill: (signal) => child.kill(signal),
|
|
153
|
-
resize(
|
|
154
|
-
const size = typeof dimensions === 'number' ? { cols: dimensions, rows: rows ?? 24 } : dimensions;
|
|
155
|
-
child.resize?.(size.cols, size.rows);
|
|
156
|
-
},
|
|
164
|
+
resizeTerminal: (cols, rows) => child.resize?.(cols, rows),
|
|
157
165
|
};
|
|
158
166
|
}
|
package/src/system/programs.ts
CHANGED
|
@@ -20,7 +20,7 @@ function viaShell(command: string): Program {
|
|
|
20
20
|
export function defaultPrograms(): Record<string, Program> {
|
|
21
21
|
const programs: Record<string, Program> = {
|
|
22
22
|
node: nodeProgram,
|
|
23
|
-
|
|
23
|
+
bfsh: shellProgram,
|
|
24
24
|
sh: shellProgram,
|
|
25
25
|
bash: shellProgram,
|
|
26
26
|
npm: viaShell('npm'),
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Who hears about servers in a runtime: a port opening or closing, and a
|
|
3
|
+
* server becoming reachable (which a dev-server handoff announces without
|
|
4
|
+
* opening a port). One instance per runtime — the core `Kernel` and the
|
|
5
|
+
* browser's process host both own one, so the listener contract is the same.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export interface ServerAddress {
|
|
9
|
+
port: number;
|
|
10
|
+
/** Where a browser reaches it (the preview URL for that port). */
|
|
11
|
+
url: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface PortChange extends ServerAddress {
|
|
15
|
+
type: 'open' | 'close';
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export class ServerEvents {
|
|
19
|
+
private readonly listening = new Set<(server: ServerAddress) => void>();
|
|
20
|
+
private readonly changes = new Set<(change: PortChange) => void>();
|
|
21
|
+
|
|
22
|
+
/** A server started listening (or a dev script handed off to the preview); `url` loads it in an iframe. */
|
|
23
|
+
onListening(listener: (server: ServerAddress) => void): () => void {
|
|
24
|
+
this.listening.add(listener);
|
|
25
|
+
return () => this.listening.delete(listener);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** A virtual server opened or closed its port. */
|
|
29
|
+
onPortChange(listener: (change: PortChange) => void): () => void {
|
|
30
|
+
this.changes.add(listener);
|
|
31
|
+
return () => this.changes.delete(listener);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** Report a port table change; an opened port is also a server listening. */
|
|
35
|
+
portChanged(change: PortChange): void {
|
|
36
|
+
for (const listener of this.changes) listener(change);
|
|
37
|
+
if (change.type === 'open') this.announce({ port: change.port, url: change.url });
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Report a reachable server that did not open a port of its own. */
|
|
41
|
+
announce(server: ServerAddress): void {
|
|
42
|
+
for (const listener of this.listening) listener(server);
|
|
43
|
+
}
|
|
44
|
+
}
|
package/src/system/types.ts
CHANGED
|
@@ -24,14 +24,14 @@ export interface ProgramIO {
|
|
|
24
24
|
terminal?: TerminalPort;
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
export interface
|
|
27
|
+
export interface ProcessOptions {
|
|
28
28
|
cwd?: string;
|
|
29
29
|
env?: Record<string, string>;
|
|
30
30
|
/** Attach to a terminal of this size (`isTTY`, line discipline, `\r\n` output). */
|
|
31
31
|
terminal?: { cols: number; rows: number };
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
/** A child as its parent sees it (separate stdout/stderr, unlike the public
|
|
34
|
+
/** A child as its parent sees it (separate stdout/stderr listeners, unlike the streams of the public `RuntimeProcess`). */
|
|
35
35
|
export interface ChildHandle {
|
|
36
36
|
readonly pid: number;
|
|
37
37
|
onStdout(listener: (chunk: string) => void): () => void;
|
|
@@ -64,14 +64,14 @@ export interface System {
|
|
|
64
64
|
fs: VirtualFileSystem;
|
|
65
65
|
/** Outbound network (registry, `http.request`, `fetch`). */
|
|
66
66
|
fetch(input: string, init?: RequestInit): Promise<Response>;
|
|
67
|
-
spawn(command: string, args: string[], options?:
|
|
67
|
+
spawn(command: string, args: string[], options?: ProcessOptions): ChildHandle;
|
|
68
68
|
/** Register a virtual server; throws `EADDRINUSE`. Port 0 picks a free one. Returns the port and an unlisten. */
|
|
69
69
|
listen(port: number, handler: HttpHandler): { port: number; close(): void };
|
|
70
70
|
/** Send a request to a virtual server in this runtime (`http.get('http://localhost:3000')`); undefined when nothing listens. */
|
|
71
71
|
loopback(port: number, request: HttpRequestData): Promise<HttpResponseData | undefined>;
|
|
72
72
|
/** Where a browser reaches a server on `port` (the preview URL), if known. */
|
|
73
73
|
serverUrl(port: number): string | undefined;
|
|
74
|
-
/** Tell the host a server is
|
|
74
|
+
/** Tell the host a server is reachable without opening a port; used by the dev-server handoff. */
|
|
75
75
|
announceServer(port: number, url: string): void;
|
|
76
76
|
/** The in-browser dev server's preview URL, for `vite`/`react-scripts start` handoff. */
|
|
77
77
|
previewUrl?: string;
|
package/src/vfs.ts
CHANGED
|
@@ -2,14 +2,18 @@ import { normalizePath, join, dirname } from './paths.js';
|
|
|
2
2
|
|
|
3
3
|
export type FileContent = string | Uint8Array;
|
|
4
4
|
|
|
5
|
-
/**
|
|
6
|
-
export interface FileSystemTree {
|
|
7
|
-
[name: string]: { file: { contents: FileContent } } | { directory: FileSystemTree };
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
/** A flat `{ 'src/main.tsx': '...' }` map, the other shape callers commonly hold. */
|
|
5
|
+
/** A flat `{ 'src/main.tsx': '...' }` map: path → contents. */
|
|
11
6
|
export type FlatFiles = Record<string, FileContent>;
|
|
12
7
|
|
|
8
|
+
/**
|
|
9
|
+
* Files to write: a name maps to contents (a file) or to another tree (a
|
|
10
|
+
* directory). Names may hold slashes, so a {@link FlatFiles} map is a tree too,
|
|
11
|
+
* and the two styles mix freely: `{ src: { 'main.tsx': '…' }, 'public/a.svg': '…' }`.
|
|
12
|
+
*/
|
|
13
|
+
export interface FileTree {
|
|
14
|
+
[name: string]: FileContent | FileTree;
|
|
15
|
+
}
|
|
16
|
+
|
|
13
17
|
export interface FsChange {
|
|
14
18
|
/** `mkdir` is an explicitly created (possibly empty) directory. */
|
|
15
19
|
type: 'write' | 'remove' | 'mkdir';
|
|
@@ -129,18 +133,14 @@ export class VirtualFileSystem {
|
|
|
129
133
|
return this.mtimes.get(normalizePath(path));
|
|
130
134
|
}
|
|
131
135
|
|
|
132
|
-
/**
|
|
133
|
-
|
|
134
|
-
* path→contents map — or a mix, since each entry is judged on its own.
|
|
135
|
-
*/
|
|
136
|
-
mount(files: FileSystemTree | FlatFiles, at = '/'): void {
|
|
136
|
+
/** Write many files at once, under `at` (default `/`). A nested object is a directory, created even when empty. */
|
|
137
|
+
writeTree(files: FileTree, at = '/'): void {
|
|
137
138
|
for (const [name, entry] of Object.entries(files)) {
|
|
138
139
|
const path = join(at, name);
|
|
139
140
|
if (typeof entry === 'string' || entry instanceof Uint8Array) this.writeFile(path, entry);
|
|
140
|
-
else if ('file' in entry) this.writeFile(path, entry.file.contents);
|
|
141
141
|
else {
|
|
142
142
|
this.mkdir(path);
|
|
143
|
-
this.
|
|
143
|
+
this.writeTree(entry, path);
|
|
144
144
|
}
|
|
145
145
|
}
|
|
146
146
|
}
|