@dfosco/storyboard 0.5.0-beta.40 → 0.5.0-beta.41
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/dist/runtime/index.js +31 -0
- package/dist/runtime/index.js.map +1 -1
- package/dist/runtime/server/main.js +31 -0
- package/dist/runtime/server/main.js.map +1 -1
- package/dist/storyboard-ui.js +309 -304
- package/dist/storyboard-ui.js.map +1 -1
- package/package.json +1 -1
- package/src/core/ui/InspectorPanel.jsx +3 -0
- package/src/internals/canvas/CanvasPage.jsx +1 -1
- package/src/runtime/proxy/controller.ts +28 -0
- package/src/runtime/server/http.ts +4 -0
package/dist/runtime/index.js
CHANGED
|
@@ -508,6 +508,36 @@ var ProxyController = class {
|
|
|
508
508
|
async readLiveCaddyRoutes() {
|
|
509
509
|
return this.caddy.listRoutes();
|
|
510
510
|
}
|
|
511
|
+
/**
|
|
512
|
+
* Reconcile Caddy with this controller's in-memory desired state.
|
|
513
|
+
*
|
|
514
|
+
* Called once on daemon startup. The runtime is the sole writer to Caddy,
|
|
515
|
+
* so any `@id`-tagged route the controller doesn't know about is a leftover
|
|
516
|
+
* from a previous daemon process. Leaving them in place causes the bug
|
|
517
|
+
* where a recycled port hits a stale host (e.g. `storyboard.localhost` →
|
|
518
|
+
* port 1240, which now belongs to a different repo). We delete them.
|
|
519
|
+
*
|
|
520
|
+
* Does NOT touch routes without `@id` — those may be hand-installed Caddy
|
|
521
|
+
* rules outside our ownership; legacy duplicate cleanup happens per-upsert.
|
|
522
|
+
*/
|
|
523
|
+
async reconcileFromCaddy() {
|
|
524
|
+
return this.withLock(async () => {
|
|
525
|
+
if (!await this.caddy.ping()) return;
|
|
526
|
+
const all = await this.caddy.listRoutes();
|
|
527
|
+
const owned = new Set(this.routes.keys());
|
|
528
|
+
const stale = [];
|
|
529
|
+
for (let i = 0; i < all.length; i++) {
|
|
530
|
+
const id = all[i]["@id"];
|
|
531
|
+
if (id && !owned.has(id)) stale.push(i);
|
|
532
|
+
}
|
|
533
|
+
for (const i of stale.sort((a, b) => b - a)) {
|
|
534
|
+
try {
|
|
535
|
+
await this.caddy.deleteRouteAt(i);
|
|
536
|
+
} catch {
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
});
|
|
540
|
+
}
|
|
511
541
|
};
|
|
512
542
|
|
|
513
543
|
// src/runtime/devserver/port-pool.ts
|
|
@@ -1106,6 +1136,7 @@ routes.set("GET /pool/status", (_req, res) => {
|
|
|
1106
1136
|
function createRuntimeServer(opts = {}) {
|
|
1107
1137
|
if (opts.proxyController) proxyController = opts.proxyController;
|
|
1108
1138
|
if (opts.orchestrator) orchestrator = opts.orchestrator;
|
|
1139
|
+
void proxyController.reconcileFromCaddy().catch(() => void 0);
|
|
1109
1140
|
globalThis.__storyboardOrchestratorShutdown = () => orchestrator.shutdown();
|
|
1110
1141
|
const server = http.createServer(async (req, res) => {
|
|
1111
1142
|
try {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/runtime/schema/identity.ts","../../src/runtime/schema/devserver.ts","../../src/runtime/schema/api.ts","../../src/runtime/client/index.ts","../../src/runtime/server/http.ts","../../src/runtime/proxy/caddy.ts","../../src/runtime/proxy/controller.ts","../../src/runtime/devserver/port-pool.ts","../../src/runtime/server/constants.ts","../../src/runtime/devserver/orchestrator.ts","../../src/runtime/pool/hot-pool.ts","../../src/runtime/server/lock.ts","../../src/runtime/server/main.ts"],"sourcesContent":["import { z } from 'zod'\n\n/**\n * The legacy/default devDomain. Acquire requests using this value are rejected\n * unless `allowDefaultDomain` is set — see DevServerOrchestrator for details.\n */\nexport const DEFAULT_DEV_DOMAIN = 'storyboard'\n\n/**\n * A devDomain identifies a Storyboard repo on this machine.\n *\n * The literal default value `\"storyboard\"` is intentionally *not* allowed by\n * `acquire` (see schema/acquire.ts) — every checkout MUST set its own\n * `devDomain` in `storyboard.config.json`. This is the structural fix for H3\n * in the server-state RCA: two repos can never share a host space.\n *\n * Allowed: lowercase letters, digits, hyphens. Must start with a letter.\n * 1–32 chars. The runtime constructs the public host as `${devDomain}.localhost`.\n */\nexport const DevDomain = z\n .string()\n .min(1)\n .max(32)\n .regex(/^[a-z][a-z0-9-]*$/, 'devDomain must match /^[a-z][a-z0-9-]*$/')\n .brand<'DevDomain'>()\nexport type DevDomain = z.infer<typeof DevDomain>\n\n/**\n * A worktree name. `\"main\"` is reserved for the repo root.\n *\n * Names are URL-safe by construction so we never have to escape them when\n * building branch URLs (`/branch--<name>/...`).\n */\nexport const WorktreeName = z\n .string()\n .min(1)\n .max(64)\n .regex(/^[a-z0-9][a-z0-9._-]*$/i, 'worktree name must be URL-safe')\n .brand<'WorktreeName'>()\nexport type WorktreeName = z.infer<typeof WorktreeName>\n\n/**\n * A TCP port the runtime has leased to a devserver. The runtime is the sole\n * authority for port allocation; clients never pick their own port.\n */\nexport const Port = z.number().int().min(1024).max(65535).brand<'Port'>()\nexport type Port = z.infer<typeof Port>\n\n/**\n * The composite key `(devDomain, worktree)` uniquely identifies a devserver.\n *\n * The runtime guarantees at most one devserver per slot — illegal collisions\n * (e.g. two repos trying to claim `(storyboard, main)`) are rejected with\n * `409 CONFLICT` rather than silently overwriting routes.\n */\nexport const DevServerSlot = z.object({\n devDomain: DevDomain,\n worktree: WorktreeName,\n})\nexport type DevServerSlot = z.infer<typeof DevServerSlot>\n\n/**\n * Convert a slot to its canonical string key, used for map lookups and\n * logging. Format: `${devDomain}::${worktree}`.\n */\nexport function slotKey(slot: DevServerSlot): string {\n return `${slot.devDomain}::${slot.worktree}`\n}\n","import { z } from 'zod'\nimport { DevDomain, DevServerSlot, Port, WorktreeName } from './identity.js'\n\n/**\n * DevServer lifecycle FSM.\n *\n * Transitions are enforced in code; illegal transitions throw. This is the\n * structural fix for the per-repo server's \"best-effort\" state — a devserver\n * that thinks it's `ready` but whose port is dead cannot exist here.\n *\n * ```\n * idle → spawning → ready → draining → stopped\n * │ │ │\n * └───────────┴────────┴──────→ stopped (on crash)\n * ```\n */\nexport const DevServerStatus = z.enum([\n 'idle', // pre-warmed in the hot pool, no project bound yet\n 'spawning', // process started, waiting for `ready in …` from Vite stdout\n 'ready', // bound to a slot, accepting traffic via Caddy\n 'draining', // releasing — finishing in-flight requests before kill\n 'stopped', // process exited, slot freed, port returned to the pool\n])\nexport type DevServerStatus = z.infer<typeof DevServerStatus>\n\n/** Allowed FSM transitions. Centralised so misuse is a one-line review catch. */\nexport const ALLOWED_TRANSITIONS: Record<DevServerStatus, readonly DevServerStatus[]> = {\n idle: ['spawning', 'stopped'],\n spawning: ['ready', 'stopped'],\n ready: ['draining', 'stopped'],\n draining: ['stopped'],\n stopped: [],\n} as const\n\nexport class IllegalTransitionError extends Error {\n constructor(from: DevServerStatus, to: DevServerStatus) {\n super(`Illegal devserver transition: ${from} → ${to}`)\n this.name = 'IllegalTransitionError'\n }\n}\n\nexport function assertTransition(from: DevServerStatus, to: DevServerStatus): void {\n if (!ALLOWED_TRANSITIONS[from].includes(to)) {\n throw new IllegalTransitionError(from, to)\n }\n}\n\n/**\n * A devserver record as exposed by the runtime API.\n *\n * `slot` is `null` for hot-pool members that haven't been acquired yet.\n * Once bound, `slot.devDomain + slot.worktree` is unique across the whole\n * runtime; the orchestrator rejects duplicate binds.\n */\nexport const DevServer = z.object({\n id: z.string().uuid(),\n pid: z.number().int().positive(),\n port: Port,\n status: DevServerStatus,\n slot: DevServerSlot.nullable(),\n /** Absolute path of the worktree directory once bound; null while in the pool. */\n cwd: z.string().nullable(),\n /** ISO timestamp; immutable after spawn. */\n spawnedAt: z.string().datetime(),\n /** ISO timestamp of last status change. */\n updatedAt: z.string().datetime(),\n})\nexport type DevServer = z.infer<typeof DevServer>\n\n/**\n * A short-lived lease handed to a CLI client when it acquires a devserver.\n *\n * Leases are the *only* way a client controls a devserver — the runtime\n * refuses commands without a valid leaseId. This means a stale `sb dev`\n * process can't kill a devserver belonging to a newer session.\n */\nexport const Lease = z.object({\n id: z.string().uuid(),\n devServerId: z.string().uuid(),\n slot: DevServerSlot,\n /** Public proxy URL the client should print to the user. Authoritative. */\n url: z.string().url(),\n /** Renew before this timestamp or the lease expires and the devserver drains. */\n expiresAt: z.string().datetime(),\n})\nexport type Lease = z.infer<typeof Lease>\n\n/**\n * A Caddy proxy route owned by the runtime. The `@id` is always the devDomain;\n * this lets the runtime PATCH a single route in place without touching others.\n *\n * `upstreams` is keyed by plain string (validated elsewhere as WorktreeName)\n * to avoid `Partial<Record<branded, …>>` shenanigans at the value-spread sites.\n */\nexport const ProxyRoute = z.object({\n devDomain: DevDomain,\n host: z.string(),\n /** worktree name → upstream port. `main` is the host's catch-all. */\n upstreams: z.record(z.string(), Port),\n})\nexport type ProxyRoute = z.infer<typeof ProxyRoute>\n","import { z } from 'zod'\nimport { DevServerSlot, Port } from './identity.js'\nimport { DevServer, Lease, ProxyRoute } from './devserver.js'\n\n/**\n * `POST /devserver/acquire` — request a devserver for a `(devDomain, worktree)` slot.\n *\n * If a devserver already exists for the slot, the runtime returns its existing\n * lease (renewed). Otherwise it either rents a hot-pool member or spawns a new\n * Vite process. The slot is locked for the duration of the call.\n */\nexport const AcquireRequest = z.object({\n slot: DevServerSlot,\n /** Absolute path of the worktree directory; the runtime spawns Vite with `cwd: targetCwd`. */\n targetCwd: z.string().min(1),\n /** Lease TTL in seconds. Defaults to 5 min; CLI clients renew on each command. */\n ttlSeconds: z.number().int().min(30).max(60 * 60).default(300),\n /**\n * Escape hatch for the deprecated default devDomain `\"storyboard\"`. CI and\n * one-off scripts may pass true; the CLI never does.\n */\n allowDefaultDomain: z.boolean().default(false),\n})\nexport type AcquireRequest = z.infer<typeof AcquireRequest>\n\nexport const AcquireResponse = z.object({\n lease: Lease,\n devServer: DevServer,\n})\nexport type AcquireResponse = z.infer<typeof AcquireResponse>\n\n/** `POST /devserver/release` — relinquish the lease and trigger draining. */\nexport const ReleaseRequest = z.object({\n leaseId: z.string().uuid(),\n})\nexport type ReleaseRequest = z.infer<typeof ReleaseRequest>\n\n/** `POST /devserver/renew` — extend the lease without changing devserver state. */\nexport const RenewRequest = z.object({\n leaseId: z.string().uuid(),\n ttlSeconds: z.number().int().min(30).max(60 * 60).default(300),\n})\nexport type RenewRequest = z.infer<typeof RenewRequest>\n\n/** `GET /proxy/state` — current routing table the runtime believes Caddy holds. */\nexport const ProxyState = z.object({\n routes: z.array(ProxyRoute),\n caddyReachable: z.boolean(),\n})\nexport type ProxyState = z.infer<typeof ProxyState>\n\n/** `GET /pool/status` — hot-pool inventory. */\nexport const PoolStatus = z.object({\n warm: z.number().int().nonnegative(),\n bound: z.number().int().nonnegative(),\n capacity: z.number().int().nonnegative(),\n})\nexport type PoolStatus = z.infer<typeof PoolStatus>\n\n/** `POST /proxy/upsert` — bind (devDomain, worktree) → port in the proxy. */\nexport const ProxyUpsertRequest = z.object({\n devDomain: z.string(),\n worktree: z.string(),\n port: z.number(),\n})\nexport type ProxyUpsertRequest = z.infer<typeof ProxyUpsertRequest>\n\n/** `POST /proxy/remove` — drop a worktree's route from the proxy. */\nexport const ProxyRemoveRequest = z.object({\n devDomain: z.string(),\n worktree: z.string(),\n})\nexport type ProxyRemoveRequest = z.infer<typeof ProxyRemoveRequest>\n\n/** `GET /health` — daemon liveness probe. */\nexport const Health = z.object({\n ok: z.literal(true),\n version: z.string(),\n uptimeSeconds: z.number().nonnegative(),\n port: Port,\n})\nexport type Health = z.infer<typeof Health>\n\n/** Runtime error envelope. All non-2xx responses share this shape. */\nexport const RuntimeError = z.object({\n error: z.string(),\n code: z.enum([\n 'NOT_IMPLEMENTED',\n 'BAD_REQUEST',\n 'CONFLICT',\n 'NOT_FOUND',\n 'FORBIDDEN_DEFAULT_DOMAIN',\n 'INTERNAL',\n 'CADDY_UNREACHABLE',\n 'PORT_EXHAUSTED',\n 'TIMEOUT',\n ]),\n details: z.unknown().optional(),\n})\nexport type RuntimeError = z.infer<typeof RuntimeError>\n","import { spawn } from 'node:child_process'\nimport { fileURLToPath } from 'node:url'\nimport { dirname, resolve } from 'node:path'\nimport { existsSync, readFileSync } from 'node:fs'\nimport {\n AcquireRequest,\n AcquireResponse,\n Health,\n PoolStatus,\n ProxyRemoveRequest,\n ProxyState,\n ProxyUpsertRequest,\n ReleaseRequest,\n RenewRequest,\n RuntimeError,\n} from '../schema/index.js'\nimport type { z } from 'zod'\n\n/**\n * Typed JS/TS client for the Storyboard Runtime daemon.\n *\n * Consumers should always go through this client rather than hand-rolling\n * `fetch` calls — the client is the only place where the daemon's URL is\n * known, and it's the only place where on-demand daemon spawning happens.\n */\n\nconst RUNTIME_BASE = 'http://127.0.0.1:4321'\n\nexport interface RuntimeClientOptions {\n /** Override base URL (mostly for tests). */\n baseUrl?: string\n /** Auto-start the daemon if it isn't running. Default: true. */\n autoStart?: boolean\n}\n\nexport class RuntimeRequestError extends Error {\n constructor(\n message: string,\n public readonly status: number,\n public readonly code: z.infer<typeof RuntimeError>['code'],\n public readonly details?: unknown,\n ) {\n super(message)\n this.name = 'RuntimeRequestError'\n }\n}\n\nasync function request<S extends z.ZodTypeAny>(\n baseUrl: string,\n method: 'GET' | 'POST',\n path: string,\n body: unknown,\n responseSchema: S | null,\n): Promise<S extends z.ZodTypeAny ? z.output<S> : void> {\n const init: RequestInit = {\n method,\n headers: body !== undefined ? { 'Content-Type': 'application/json' } : undefined,\n body: body !== undefined ? JSON.stringify(body) : undefined,\n }\n let res: Response\n try {\n res = await fetch(`${baseUrl}${path}`, init)\n } catch (err) {\n throw new RuntimeRequestError(\n `Cannot reach Storyboard Runtime at ${baseUrl} — is the daemon running? (${(err as Error).message})`,\n 0,\n 'INTERNAL',\n )\n }\n const text = await res.text()\n let parsed: unknown\n try { parsed = text ? JSON.parse(text) : {} }\n catch { parsed = { error: text, code: 'INTERNAL' } }\n\n if (!res.ok) {\n const err = RuntimeError.safeParse(parsed)\n if (err.success) {\n throw new RuntimeRequestError(err.data.error, res.status, err.data.code, err.data.details)\n }\n throw new RuntimeRequestError(`HTTP ${res.status}`, res.status, 'INTERNAL', parsed)\n }\n if (responseSchema === null) return undefined as never\n return responseSchema.parse(parsed) as never\n}\n\n/**\n * Spawn the daemon as a detached child. Resolves once the health endpoint\n * answers (or rejects after a short timeout).\n */\nasync function spawnDaemon(baseUrl: string): Promise<void> {\n const here = dirname(fileURLToPath(import.meta.url))\n // bin/storyboard-runtime.js lives next to dist/, two levels up from\n // dist/runtime/client/index.js (the published path).\n const binPath = resolve(here, '..', '..', '..', 'bin', 'storyboard-runtime.js')\n const child = spawn(process.execPath, [binPath], {\n detached: true,\n stdio: 'ignore',\n env: process.env,\n })\n child.unref()\n\n // Poll /health until the daemon is up.\n const deadline = Date.now() + 5000\n while (Date.now() < deadline) {\n try {\n const r = await fetch(`${baseUrl}/health`)\n if (r.ok) return\n } catch { /* not up yet */ }\n await new Promise(r => setTimeout(r, 100))\n }\n throw new Error(\n `Storyboard Runtime did not become ready within 5s ` +\n `(tried to spawn ${binPath})`,\n )\n}\n\n/**\n * Read the @dfosco/storyboard package.json version that this client is\n * shipping with. Used to detect mismatches against a long-lived daemon\n * that may have been spawned by a previous install.\n */\nfunction readClientVersion(): string {\n try {\n const here = dirname(fileURLToPath(import.meta.url))\n const candidates = [\n resolve(here, '..', '..', '..', 'package.json'),\n resolve(here, '..', '..', 'package.json'),\n ]\n for (const p of candidates) {\n if (existsSync(p)) {\n const pkg = JSON.parse(readFileSync(p, 'utf8')) as { version?: string }\n if (typeof pkg.version === 'string') return pkg.version\n }\n }\n } catch { /* ignore */ }\n return '0.0.0'\n}\n\nconst CLIENT_VERSION = readClientVersion()\n\n/**\n * Send SIGTERM to the daemon PID and clear its lock/pid files so\n * spawnDaemon() can start a fresh one.\n */\nfunction killExistingDaemon(): void {\n try {\n const pidPath = resolve(process.env.HOME || '', '.storyboard', 'runtime.pid')\n if (!existsSync(pidPath)) return\n const pid = Number(readFileSync(pidPath, 'utf8').trim())\n if (Number.isFinite(pid) && pid > 0) {\n try { process.kill(pid, 'SIGTERM') } catch { /* already dead */ }\n }\n } catch { /* ignore */ }\n}\n\nexport class RuntimeClient {\n readonly baseUrl: string\n readonly autoStart: boolean\n\n constructor(opts: RuntimeClientOptions = {}) {\n this.baseUrl = opts.baseUrl ?? RUNTIME_BASE\n this.autoStart = opts.autoStart !== false\n }\n\n async health(): Promise<Health> {\n try {\n const result = await request(this.baseUrl, 'GET', '/health', undefined, Health)\n // Auto-respawn on version mismatch — a long-lived daemon from a\n // previous install otherwise keeps serving stale code after upgrade.\n // Skip when client reports 0.0.0 (dev/source layout where package\n // version isn't meaningful).\n if (\n this.autoStart &&\n CLIENT_VERSION !== '0.0.0' &&\n result.version !== '0.0.0' &&\n result.version !== CLIENT_VERSION\n ) {\n killExistingDaemon()\n // Give the OS a moment to release port 4321\n await new Promise(r => setTimeout(r, 250))\n await spawnDaemon(this.baseUrl)\n return request(this.baseUrl, 'GET', '/health', undefined, Health)\n }\n return result\n } catch (err) {\n if (this.autoStart && err instanceof RuntimeRequestError && err.status === 0) {\n await spawnDaemon(this.baseUrl)\n return request(this.baseUrl, 'GET', '/health', undefined, Health)\n }\n throw err\n }\n }\n\n async acquire(input: z.input<typeof AcquireRequest>): Promise<AcquireResponse> {\n const body = AcquireRequest.parse(input)\n return request(this.baseUrl, 'POST', '/devserver/acquire', body, AcquireResponse)\n }\n\n async release(input: z.input<typeof ReleaseRequest>): Promise<void> {\n const body = ReleaseRequest.parse(input)\n await request(this.baseUrl, 'POST', '/devserver/release', body, null)\n }\n\n async renew(input: z.input<typeof RenewRequest>): Promise<void> {\n const body = RenewRequest.parse(input)\n await request(this.baseUrl, 'POST', '/devserver/renew', body, null)\n }\n\n async proxyState(): Promise<ProxyState> {\n return request(this.baseUrl, 'GET', '/proxy/state', undefined, ProxyState)\n }\n\n async proxyUpsert(input: z.input<typeof ProxyUpsertRequest>): Promise<ProxyState> {\n const body = ProxyUpsertRequest.parse(input)\n return request(this.baseUrl, 'POST', '/proxy/upsert', body, ProxyState)\n }\n\n async proxyRemove(input: z.input<typeof ProxyRemoveRequest>): Promise<ProxyState> {\n const body = ProxyRemoveRequest.parse(input)\n return request(this.baseUrl, 'POST', '/proxy/remove', body, ProxyState)\n }\n\n async poolStatus(): Promise<PoolStatus> {\n return request(this.baseUrl, 'GET', '/pool/status', undefined, PoolStatus)\n }\n}\n\n/** Default singleton client for casual callers. */\nexport const runtime = new RuntimeClient()\n","import http from 'node:http'\nimport { z } from 'zod'\nimport {\n AcquireRequest,\n AcquireResponse,\n DevDomain,\n Health,\n PoolStatus,\n Port,\n ProxyRemoveRequest,\n ProxyState,\n ProxyUpsertRequest,\n ReleaseRequest,\n RenewRequest,\n RuntimeError,\n WorktreeName,\n} from '../schema/index.js'\nimport { ProxyController } from '../proxy/index.js'\nimport { CaddyUnreachableError } from '../proxy/caddy.js'\nimport {\n DevServerOrchestrator,\n ForbiddenDefaultDomainError,\n LeaseNotFoundError,\n DevServerSpawnError,\n SlotCwdConflictError,\n} from '../devserver/index.js'\nimport { HotPool } from '../pool/index.js'\nimport { PortPool } from '../devserver/port-pool.js'\nimport { RUNTIME_PORT, RUNTIME_HOST, RUNTIME_VERSION } from './constants.js'\n\n/**\n * The runtime's HTTP API.\n *\n * Every request is parsed through a zod schema *before* any handler runs;\n * malformed input never reaches the orchestrator. Every response is also\n * shape-checked in development to prevent accidental contract drift.\n *\n * M1 scaffold: all mutating endpoints return `501 NOT_IMPLEMENTED`. The\n * shapes, status codes, and validation are real — only the orchestrator\n * wiring is deferred to M2/M3.\n */\n\nconst startedAt = Date.now()\n\n/**\n * Process-wide singletons. The runtime is one-per-machine, so module-level\n * controllers are fine — there's never more than one instance per node process.\n * Tests inject their own via createRuntimeServer({ ... }).\n *\n * The default daemon ships with a 1-port hot pool so first-acquire latency\n * is dominated by Vite startup, not by the OS-level free-port probe loop.\n * Tunable via env: STORYBOARD_RUNTIME_WARM_PORTS, STORYBOARD_RUNTIME_POOL_CAP.\n */\nlet proxyController = new ProxyController()\nlet _ports = new PortPool()\nlet _hotPool = new HotPool({\n ports: _ports,\n warmTarget: parsePositiveInt(process.env.STORYBOARD_RUNTIME_WARM_PORTS, 1),\n capacity: parsePositiveInt(process.env.STORYBOARD_RUNTIME_POOL_CAP, 4),\n})\nlet orchestrator: DevServerOrchestrator = new DevServerOrchestrator({\n proxy: proxyController,\n ports: _ports,\n hotPool: _hotPool,\n})\n\nfunction parsePositiveInt(raw: string | undefined, fallback: number): number {\n if (!raw) return fallback\n const n = Number(raw)\n return Number.isFinite(n) && n >= 0 ? Math.floor(n) : fallback\n}\n\nfunction sendJson(\n res: http.ServerResponse,\n status: number,\n body: unknown,\n schema?: z.ZodTypeAny,\n): void {\n if (schema && process.env.NODE_ENV !== 'production') {\n schema.parse(body)\n }\n res.writeHead(status, { 'Content-Type': 'application/json' })\n res.end(JSON.stringify(body))\n}\n\nfunction sendError(\n res: http.ServerResponse,\n status: number,\n code: z.infer<typeof RuntimeError>['code'],\n message: string,\n details?: unknown,\n): void {\n const body = RuntimeError.parse({ error: message, code, details })\n sendJson(res, status, body, RuntimeError)\n}\n\nasync function readJsonBody(req: http.IncomingMessage): Promise<unknown> {\n const chunks: Buffer[] = []\n for await (const chunk of req) chunks.push(chunk as Buffer)\n if (chunks.length === 0) return {}\n try {\n return JSON.parse(Buffer.concat(chunks).toString('utf8'))\n } catch {\n throw new Error('Malformed JSON body')\n }\n}\n\nasync function parseBody<S extends z.ZodTypeAny>(\n req: http.IncomingMessage,\n res: http.ServerResponse,\n schema: S,\n): Promise<z.output<S> | null> {\n let raw: unknown\n try {\n raw = await readJsonBody(req)\n } catch (err) {\n sendError(res, 400, 'BAD_REQUEST', (err as Error).message)\n return null\n }\n const result = schema.safeParse(raw)\n if (!result.success) {\n sendError(res, 400, 'BAD_REQUEST', 'Validation failed', result.error.flatten())\n return null\n }\n return result.data\n}\n\ntype Route = (req: http.IncomingMessage, res: http.ServerResponse) => Promise<void> | void\n\nconst routes = new Map<string, Route>()\n\nroutes.set('GET /health', (_req, res) => {\n const body: Health = Health.parse({\n ok: true,\n version: RUNTIME_VERSION,\n uptimeSeconds: (Date.now() - startedAt) / 1000,\n port: RUNTIME_PORT,\n })\n sendJson(res, 200, body, Health)\n})\n\nroutes.set('POST /devserver/acquire', async (req, res) => {\n const body = await parseBody(req, res, AcquireRequest)\n if (!body) return\n try {\n const result = await orchestrator.acquire(body)\n sendJson(res, 200, result, AcquireResponse)\n } catch (err) {\n if (err instanceof ForbiddenDefaultDomainError) {\n sendError(res, 403, 'FORBIDDEN_DEFAULT_DOMAIN', err.message)\n return\n }\n if (err instanceof SlotCwdConflictError) {\n sendError(res, 409, 'CONFLICT', err.message)\n return\n }\n if (err instanceof DevServerSpawnError) {\n sendError(res, 500, 'INTERNAL', err.message, { stderr: err.stderr })\n return\n }\n if (err instanceof CaddyUnreachableError) {\n sendError(res, 503, 'CADDY_UNREACHABLE', err.message)\n return\n }\n sendError(res, 500, 'INTERNAL', (err as Error).message)\n }\n})\n\nroutes.set('POST /devserver/release', async (req, res) => {\n const body = await parseBody(req, res, ReleaseRequest)\n if (!body) return\n try {\n orchestrator.release(body.leaseId)\n sendJson(res, 200, { ok: true })\n } catch (err) {\n if (err instanceof LeaseNotFoundError) {\n sendError(res, 404, 'NOT_FOUND', err.message)\n return\n }\n sendError(res, 500, 'INTERNAL', (err as Error).message)\n }\n})\n\nroutes.set('POST /devserver/renew', async (req, res) => {\n const body = await parseBody(req, res, RenewRequest)\n if (!body) return\n try {\n const lease = orchestrator.renew(body.leaseId, body.ttlSeconds)\n sendJson(res, 200, lease)\n } catch (err) {\n if (err instanceof LeaseNotFoundError) {\n sendError(res, 404, 'NOT_FOUND', err.message)\n return\n }\n sendError(res, 500, 'INTERNAL', (err as Error).message)\n }\n})\n\nroutes.set('GET /devserver/list', (_req, res) => {\n sendJson(res, 200, { devServers: orchestrator.list() })\n})\n\nroutes.set('GET /proxy/state', async (_req, res) => {\n try {\n const body = await proxyController.stateForApi()\n sendJson(res, 200, body, ProxyState)\n } catch (err) {\n sendError(res, 500, 'INTERNAL', (err as Error).message)\n }\n})\n\nroutes.set('POST /proxy/upsert', async (req, res) => {\n const body = await parseBody(req, res, ProxyUpsertRequest)\n if (!body) return\n // Re-validate with branded types — the API request schema is intentionally\n // loose (string/number) so bad input gets a useful 400 rather than the\n // brand regex's cryptic message.\n const dev = DevDomain.safeParse(body.devDomain)\n const wt = WorktreeName.safeParse(body.worktree)\n const port = Port.safeParse(body.port)\n if (!dev.success || !wt.success || !port.success) {\n sendError(res, 400, 'BAD_REQUEST', 'Validation failed', {\n devDomain: dev.success ? null : dev.error.flatten(),\n worktree: wt.success ? null : wt.error.flatten(),\n port: port.success ? null : port.error.flatten(),\n })\n return\n }\n if (dev.data === ('storyboard' as unknown as typeof dev.data)) {\n sendError(res, 403, 'FORBIDDEN_DEFAULT_DOMAIN',\n 'Refusing to bind a route under the default devDomain \"storyboard\". ' +\n 'Set a unique devDomain in storyboard.config.json.')\n return\n }\n try {\n const route = await proxyController.upsert(dev.data, wt.data, port.data)\n sendJson(res, 200, ProxyState.parse({ routes: [route], caddyReachable: true }), ProxyState)\n } catch (err) {\n if (err instanceof CaddyUnreachableError) {\n sendError(res, 503, 'CADDY_UNREACHABLE', err.message)\n return\n }\n sendError(res, 500, 'INTERNAL', (err as Error).message)\n }\n})\n\nroutes.set('POST /proxy/remove', async (req, res) => {\n const body = await parseBody(req, res, ProxyRemoveRequest)\n if (!body) return\n const dev = DevDomain.safeParse(body.devDomain)\n const wt = WorktreeName.safeParse(body.worktree)\n if (!dev.success || !wt.success) {\n sendError(res, 400, 'BAD_REQUEST', 'Validation failed')\n return\n }\n try {\n await proxyController.removeWorktree(dev.data, wt.data)\n sendJson(res, 200, ProxyState.parse({ routes: [], caddyReachable: true }), ProxyState)\n } catch (err) {\n if (err instanceof CaddyUnreachableError) {\n sendError(res, 503, 'CADDY_UNREACHABLE', err.message)\n return\n }\n sendError(res, 500, 'INTERNAL', (err as Error).message)\n }\n})\n\nroutes.set('GET /pool/status', (_req, res) => {\n // Prefer the orchestrator's view (reflects bound count); fall back to the\n // raw-pool snapshot when no hot pool is configured.\n const live = orchestrator.poolStatus()\n const body = PoolStatus.parse(live ?? { warm: 0, bound: 0, capacity: 0 })\n sendJson(res, 200, body, PoolStatus)\n})\n\nexport function createRuntimeServer(opts: {\n proxyController?: ProxyController\n orchestrator?: DevServerOrchestrator\n} = {}): http.Server {\n if (opts.proxyController) proxyController = opts.proxyController\n if (opts.orchestrator) orchestrator = opts.orchestrator\n // Expose orchestrator shutdown for the daemon's signal handler.\n ;(globalThis as { __storyboardOrchestratorShutdown?: () => void }).__storyboardOrchestratorShutdown = () => orchestrator.shutdown()\n const server = http.createServer(async (req, res) => {\n try {\n const url = new URL(req.url ?? '/', `http://${RUNTIME_HOST}:${RUNTIME_PORT}`)\n const key = `${req.method ?? 'GET'} ${url.pathname}`\n const handler = routes.get(key)\n if (!handler) {\n sendError(res, 404, 'NOT_FOUND', `No route for ${key}`)\n return\n }\n await handler(req, res)\n } catch (err) {\n sendError(res, 500, 'INTERNAL', (err as Error).message)\n }\n })\n return server\n}\n","import { z } from 'zod'\nimport { DevDomain, Port, WorktreeName } from '../schema/index.js'\n\n/**\n * Typed Caddy admin API client. The runtime is the SOLE writer to\n * `http://localhost:2019` — this module is the only place in the codebase\n * that issues PATCH/POST/DELETE against Caddy. All cross-repo route races\n * (the doubled-URL bug class) are eliminated by funneling writes here.\n *\n * Reads are also funneled through this module so the runtime's view of\n * Caddy state matches what's actually live.\n */\n\nexport const CADDY_ADMIN_DEFAULT = 'http://localhost:2019'\n\nexport const CaddyRoute = z\n .object({\n '@id': z.string().optional(),\n match: z.array(z.object({ host: z.array(z.string()).optional() }).passthrough()).optional(),\n handle: z.array(z.unknown()).optional(),\n })\n .passthrough()\nexport type CaddyRoute = z.infer<typeof CaddyRoute>\n\nexport interface CaddyAdminOptions {\n baseUrl?: string\n timeoutMs?: number\n}\n\nexport class CaddyUnreachableError extends Error {\n constructor(baseUrl: string, cause?: unknown) {\n super(`Caddy admin API not reachable at ${baseUrl}`)\n this.name = 'CaddyUnreachableError'\n if (cause) (this as Error & { cause?: unknown }).cause = cause\n }\n}\n\nexport class CaddyAdminClient {\n readonly baseUrl: string\n readonly timeoutMs: number\n\n constructor(opts: CaddyAdminOptions = {}) {\n this.baseUrl = opts.baseUrl ?? CADDY_ADMIN_DEFAULT\n this.timeoutMs = opts.timeoutMs ?? 5000\n }\n\n private async fetch(path: string, init: RequestInit = {}): Promise<Response> {\n const controller = new AbortController()\n const t = setTimeout(() => controller.abort(), this.timeoutMs)\n try {\n // Caddy's admin API rejects requests with 403 when the Origin header\n // doesn't match an allowed origin. Node's undici fetch sends an empty\n // Origin by default, which Caddy refuses. Set it explicitly to the\n // admin URL.\n const headers = new Headers(init.headers)\n if (!headers.has('origin')) headers.set('origin', this.baseUrl)\n return await fetch(`${this.baseUrl}${path}`, { ...init, headers, signal: controller.signal })\n } catch (err) {\n throw new CaddyUnreachableError(this.baseUrl, err)\n } finally {\n clearTimeout(t)\n }\n }\n\n async ping(): Promise<boolean> {\n try {\n const r = await this.fetch('/config/')\n return r.ok\n } catch {\n return false\n }\n }\n\n async listRoutes(): Promise<CaddyRoute[]> {\n const r = await this.fetch('/config/apps/http/servers/srv0/routes')\n if (r.status === 404) return []\n if (!r.ok) throw new CaddyUnreachableError(this.baseUrl)\n const raw = (await r.json()) as unknown\n return z.array(CaddyRoute).parse(raw ?? [])\n }\n\n async patchById(id: string, route: CaddyRoute): Promise<boolean> {\n const r = await this.fetch(`/id/${encodeURIComponent(id)}`, {\n method: 'PATCH',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(route),\n })\n return r.ok\n }\n\n async appendRoute(route: CaddyRoute): Promise<void> {\n const r = await this.fetch('/config/apps/http/servers/srv0/routes', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(route),\n })\n if (!r.ok) throw new CaddyUnreachableError(this.baseUrl)\n }\n\n async deleteRouteAt(idx: number): Promise<void> {\n const r = await this.fetch(`/config/apps/http/servers/srv0/routes/${idx}`, {\n method: 'DELETE',\n })\n if (!r.ok && r.status !== 404) throw new CaddyUnreachableError(this.baseUrl)\n }\n\n async bootstrap(): Promise<void> {\n const config = {\n apps: { http: { servers: { srv0: { listen: [':80'], routes: [] } } } },\n }\n const r = await this.fetch('/load', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(config),\n })\n if (!r.ok) throw new CaddyUnreachableError(this.baseUrl)\n }\n}\n\n/**\n * Build a Caddy route for a single devDomain, with one subroute per worktree.\n * The `@id` is the devDomain itself so writes are PATCHable in place.\n */\nexport function buildRouteFor(\n devDomain: DevDomain,\n upstreams: Record<string, Port>,\n mainPort?: Port,\n): CaddyRoute {\n const host = `${devDomain}.localhost`\n const subroutes: unknown[] = []\n\n for (const [worktree, port] of Object.entries(upstreams)) {\n if (worktree === 'main') continue\n subroutes.push({\n match: [{ path: [`/branch--${worktree}/*`] }],\n handle: [{ handler: 'reverse_proxy', upstreams: [{ dial: `localhost:${port}` }] }],\n })\n }\n\n const fallbackPort = mainPort ?? upstreams.main ?? Object.values(upstreams)[0]\n if (fallbackPort) {\n subroutes.push({\n handle: [{ handler: 'reverse_proxy', upstreams: [{ dial: `localhost:${fallbackPort}` }] }],\n })\n }\n\n return CaddyRoute.parse({\n '@id': devDomain,\n match: [{ host: [host] }],\n handle: [{ handler: 'subroute', routes: subroutes }],\n })\n}\n\nvoid WorktreeName\n","import { DevDomain, Port, ProxyRoute, ProxyState, WorktreeName } from '../schema/index.js'\nimport { buildRouteFor, CaddyAdminClient, CaddyUnreachableError, CaddyRoute } from './caddy.js'\n\n/**\n * ProxyController — the runtime's authoritative model of the Caddy proxy.\n *\n * Two invariants the rest of the system depends on:\n *\n * 1. **Sole writer.** Only this controller calls Caddy admin write methods\n * (PATCH/POST/DELETE). The legacy per-repo `caddy reload --config` path\n * that destructively replaced the entire config is no longer reachable\n * from any code under the runtime.\n *\n * 2. **Serialized writes.** Every mutation is queued through `withLock()`.\n * Two concurrent acquires for different devDomains can no longer race\n * each other into producing a Caddy config where one repo's routes\n * silently shadowed another's.\n *\n * The in-memory `routes` map is the desired state. After every write we\n * reconcile against Caddy's actual state and re-PATCH if drift is detected.\n */\n\nexport interface ProxyControllerOptions {\n caddy?: CaddyAdminClient\n}\n\nexport class ProxyController {\n private readonly caddy: CaddyAdminClient\n private readonly routes = new Map<DevDomain, ProxyRoute>()\n private writeChain: Promise<unknown> = Promise.resolve()\n\n constructor(opts: ProxyControllerOptions = {}) {\n this.caddy = opts.caddy ?? new CaddyAdminClient()\n }\n\n private withLock<T>(fn: () => Promise<T>): Promise<T> {\n const next = this.writeChain.then(fn, fn)\n this.writeChain = next.catch(() => undefined)\n return next\n }\n\n async caddyReachable(): Promise<boolean> {\n return this.caddy.ping()\n }\n\n async stateForApi(): Promise<ProxyState> {\n return ProxyState.parse({\n routes: Array.from(this.routes.values()),\n caddyReachable: await this.caddy.ping(),\n })\n }\n\n async upsert(devDomain: DevDomain, worktree: WorktreeName, port: Port): Promise<ProxyRoute> {\n return this.withLock(async () => {\n const existing = this.routes.get(devDomain)\n const upstreams: Record<string, Port> = { ...(existing?.upstreams ?? {}) }\n upstreams[worktree] = port\n const next = ProxyRoute.parse({\n devDomain,\n host: `${devDomain}.localhost`,\n upstreams,\n })\n this.routes.set(devDomain, next)\n await this.syncOne(next)\n return next\n })\n }\n\n async removeWorktree(devDomain: DevDomain, worktree: WorktreeName): Promise<void> {\n return this.withLock(async () => {\n const existing = this.routes.get(devDomain)\n if (!existing) return\n const upstreams: Record<string, Port> = { ...existing.upstreams }\n delete upstreams[worktree]\n if (Object.keys(upstreams).length === 0) {\n this.routes.delete(devDomain)\n await this.removeFromCaddy(devDomain)\n } else {\n const next = ProxyRoute.parse({\n devDomain,\n host: `${devDomain}.localhost`,\n upstreams,\n })\n this.routes.set(devDomain, next)\n await this.syncOne(next)\n }\n })\n }\n\n private async syncOne(route: ProxyRoute): Promise<void> {\n if (!(await this.caddy.ping())) {\n throw new CaddyUnreachableError(this.caddy.baseUrl)\n }\n const caddyRoute = buildRouteFor(route.devDomain, route.upstreams as Record<string, Port>)\n const patched = await this.caddy.patchById(route.devDomain, caddyRoute)\n if (!patched) {\n await this.caddy.appendRoute(caddyRoute)\n }\n await this.cleanupDuplicates(route.devDomain, `${route.devDomain}.localhost`)\n }\n\n private async removeFromCaddy(devDomain: DevDomain): Promise<void> {\n if (!(await this.caddy.ping())) throw new CaddyUnreachableError(this.caddy.baseUrl)\n const all = await this.caddy.listRoutes()\n const indices: number[] = []\n for (let i = 0; i < all.length; i++) {\n if (all[i]!['@id'] === devDomain) indices.push(i)\n }\n for (const i of indices.sort((a, b) => b - a)) {\n await this.caddy.deleteRouteAt(i)\n }\n }\n\n /**\n * Stale-route cleanup. The legacy `caddy reload` path used to leave\n * non-`@id` routes for the same host that would shadow our admin-API\n * route. We sweep them on every successful upsert.\n */\n private async cleanupDuplicates(keepId: DevDomain, host: string): Promise<void> {\n const all = await this.caddy.listRoutes()\n const stale: number[] = []\n for (let i = 0; i < all.length; i++) {\n const r = all[i]!\n if (r['@id'] === keepId) continue\n if (r['@id']) continue\n const hosts = r.match?.[0]?.host ?? []\n if (hosts.includes(host)) stale.push(i)\n }\n for (const i of stale.sort((a, b) => b - a)) {\n try { await this.caddy.deleteRouteAt(i) } catch { /* best-effort */ }\n }\n }\n\n async readLiveCaddyRoutes(): Promise<CaddyRoute[]> {\n return this.caddy.listRoutes()\n }\n}\n","import { createServer } from 'node:net'\nimport { DEVSERVER_PORT_MAX, DEVSERVER_PORT_MIN } from '../server/constants.js'\nimport { Port } from '../schema/index.js'\n\n/**\n * PortPool — leases TCP ports from the runtime's reserved range.\n *\n * The runtime is the sole authority for port allocation. Vite child processes\n * never pick their own port; they're told which port to bind to. This is the\n * structural fix for hypothesis H4 (independent per-repo port registries with\n * no cross-repo awareness): leases live in this single in-memory pool, and\n * collisions are impossible because the pool refuses to hand out a port\n * that's already leased.\n */\n\nexport class PortExhaustedError extends Error {\n constructor() {\n super(`No free ports in range ${DEVSERVER_PORT_MIN}-${DEVSERVER_PORT_MAX}`)\n this.name = 'PortExhaustedError'\n }\n}\n\nexport class PortPool {\n private readonly leased = new Set<number>()\n\n async acquire(): Promise<Port> {\n for (let p = DEVSERVER_PORT_MIN; p <= DEVSERVER_PORT_MAX; p++) {\n if (this.leased.has(p)) continue\n if (await isPortFree(p)) {\n this.leased.add(p)\n return Port.parse(p)\n }\n }\n throw new PortExhaustedError()\n }\n\n release(port: Port | number): void {\n this.leased.delete(Number(port))\n }\n\n isLeased(port: Port | number): boolean {\n return this.leased.has(Number(port))\n }\n\n size(): number {\n return this.leased.size\n }\n}\n\nfunction isPortFree(port: number): Promise<boolean> {\n return new Promise((resolve) => {\n const probe = createServer()\n probe.once('error', () => resolve(false))\n probe.once('listening', () => {\n probe.close(() => resolve(true))\n })\n probe.listen(port, '127.0.0.1')\n })\n}\n","import { homedir } from 'node:os'\nimport { join, dirname, resolve } from 'node:path'\nimport { existsSync, readFileSync } from 'node:fs'\nimport { fileURLToPath } from 'node:url'\n\n/**\n * Runtime constants — single source of truth for ports, paths, and version.\n *\n * The runtime port (`4321`) is intentionally fixed: a single global daemon\n * needs a well-known address so the CLI client can find it without\n * configuration. Port 4321 is RFC-allowed and avoids collisions with\n * Vite (1234), Caddy admin (2019), and the per-repo legacy server (4100-4199).\n */\nexport const RUNTIME_PORT = 4321 as const\nexport const RUNTIME_HOST = '127.0.0.1' as const\n\n/** Where the daemon writes its pidfile and lockfile. */\nexport const RUNTIME_HOME = join(homedir(), '.storyboard')\nexport const PIDFILE = join(RUNTIME_HOME, 'runtime.pid')\nexport const LOCKFILE = join(RUNTIME_HOME, 'runtime.lock')\nexport const STATEFILE = join(RUNTIME_HOME, 'runtime.state.json')\n\n/**\n * Range of TCP ports the runtime leases to Vite devservers. Picked to sit\n * above Vite's default (1234) and below ephemeral-port territory.\n */\nexport const DEVSERVER_PORT_MIN = 1240 as const\nexport const DEVSERVER_PORT_MAX = 1399 as const\n\n/** Caddy admin API endpoint. The runtime is the SOLE writer to this URL. */\nexport const CADDY_ADMIN = 'http://localhost:2019' as const\n\n// Runtime version is read from the package.json at startup so it tracks\n// the published @dfosco/storyboard version. Falls back to \"0.0.0\" if the\n// file can't be located (e.g. dev script runs from a non-standard layout).\nfunction readPackageVersion(): string {\n try {\n const here = dirname(fileURLToPath(import.meta.url))\n // Bundled to dist/runtime/server/main.js → ../../../package.json\n const candidates = [\n resolve(here, '..', '..', '..', 'package.json'),\n resolve(here, '..', '..', 'package.json'),\n ]\n for (const p of candidates) {\n if (existsSync(p)) {\n const pkg = JSON.parse(readFileSync(p, 'utf8')) as { version?: string }\n if (typeof pkg.version === 'string' && pkg.version) return pkg.version\n }\n }\n } catch { /* fallthrough */ }\n return '0.0.0'\n}\n\nexport const RUNTIME_VERSION = readPackageVersion()\n","import { spawn, type ChildProcess } from 'node:child_process'\nimport { existsSync } from 'node:fs'\nimport { resolve as resolvePath } from 'node:path'\nimport { randomUUID } from 'node:crypto'\nimport {\n AcquireRequest,\n AcquireResponse,\n assertTransition,\n DevServer,\n DevServerSlot,\n DevServerStatus,\n DEFAULT_DEV_DOMAIN,\n Lease,\n Port,\n slotKey,\n} from '../schema/index.js'\nimport { ProxyController } from '../proxy/index.js'\nimport { HotPool } from '../pool/index.js'\nimport { PortPool } from './port-pool.js'\n\n/**\n * DevServerOrchestrator — owns the lifecycle of every Vite dev process.\n *\n * Invariants enforced here:\n *\n * 1. **Slot uniqueness.** At most one DevServer per `(devDomain, worktree)`.\n * Acquire on an existing slot returns the same DevServer with a fresh\n * lease. Per-slot mutex prevents concurrent double-spawn.\n *\n * 2. **Default-domain refusal (H3).** `acquire()` rejects requests whose\n * devDomain is the literal `\"storyboard\"` unless `allowDefaultDomain`\n * is true. The CLI never passes `true`; only CI/scripts do.\n *\n * 3. **FSM.** Every status change goes through `assertTransition()`.\n * Illegal transitions throw — no devserver can be `ready` without\n * having gone through `spawning` first.\n *\n * 4. **Lease enforcement.** Release/renew require a valid lease ID. A stale\n * `sb dev` cannot kill a devserver claimed by a newer session.\n */\n\ninterface DevServerInternal {\n id: string\n child: ChildProcess\n pid: number\n port: Port\n status: DevServerStatus\n slot: DevServerSlot\n cwd: string\n spawnedAt: string\n updatedAt: string\n stderrTail: string[]\n readyPromise: Promise<void>\n}\n\ninterface LeaseInternal {\n id: string\n devServerId: string\n slot: DevServerSlot\n url: string\n expiresAt: number\n}\n\nexport class SlotCwdConflictError extends Error {\n constructor(slot: DevServerSlot, existingCwd: string, requestedCwd: string) {\n super(\n `Slot ${slotKey(slot)} is already bound to ${existingCwd}; refusing to rebind to ${requestedCwd}. ` +\n `Two different repositories cannot share devDomain \"${slot.devDomain}\". ` +\n `Set a unique devDomain in storyboard.config.json for one of them.`,\n )\n this.name = 'SlotCwdConflictError'\n }\n}\n\nexport class ForbiddenDefaultDomainError extends Error {\n constructor() {\n super(\n `Refusing to acquire under default devDomain \"${DEFAULT_DEV_DOMAIN}\". ` +\n `Set a unique devDomain in storyboard.config.json or pass allowDefaultDomain=true.`,\n )\n this.name = 'ForbiddenDefaultDomainError'\n }\n}\n\nexport class LeaseNotFoundError extends Error {\n constructor(id: string) {\n super(`No lease ${id}`)\n this.name = 'LeaseNotFoundError'\n }\n}\n\nexport class DevServerSpawnError extends Error {\n readonly stderr: string\n constructor(slot: DevServerSlot, exitCode: number | null, stderr: string) {\n super(`Vite for ${slotKey(slot)} exited (code ${exitCode ?? 'unknown'}) before becoming ready`)\n this.name = 'DevServerSpawnError'\n this.stderr = stderr\n }\n}\n\nexport interface DevServerOrchestratorOptions {\n proxy: ProxyController\n ports?: PortPool\n /** Optional HotPool wrapping `ports` — when provided, acquire goes\n * through the warm ring first to avoid the OS-level probe loop. */\n hotPool?: HotPool\n /** Override Vite spawn for tests — return a child you've prepared. */\n spawnVite?: (cwd: string, port: Port, basePath: string, devDomain: string) => ChildProcess\n readyTimeoutMs?: number\n}\n\nconst DEFAULT_READY_TIMEOUT_MS = 30_000\nconst STDERR_TAIL_MAX = 50\n\nexport class DevServerOrchestrator {\n private readonly proxy: ProxyController\n private readonly ports: PortPool\n private readonly hotPool: HotPool | null\n private readonly readyTimeoutMs: number\n private readonly spawnViteFn: NonNullable<DevServerOrchestratorOptions['spawnVite']>\n\n private readonly bySlot = new Map<string, DevServerInternal>()\n private readonly byId = new Map<string, DevServerInternal>()\n private readonly leases = new Map<string, LeaseInternal>()\n private readonly slotLocks = new Map<string, Promise<unknown>>()\n\n constructor(opts: DevServerOrchestratorOptions) {\n this.proxy = opts.proxy\n this.ports = opts.ports ?? new PortPool()\n this.hotPool = opts.hotPool ?? null\n this.readyTimeoutMs = opts.readyTimeoutMs ?? DEFAULT_READY_TIMEOUT_MS\n this.spawnViteFn = opts.spawnVite ?? defaultSpawnVite\n // Kick the warm ring as soon as the orchestrator is constructed.\n this.hotPool?.warmInBackground()\n }\n\n async acquire(input: AcquireRequest): Promise<AcquireResponse> {\n if (input.slot.devDomain === DEFAULT_DEV_DOMAIN && !input.allowDefaultDomain) {\n throw new ForbiddenDefaultDomainError()\n }\n if (!existsSync(input.targetCwd)) {\n throw new Error(`targetCwd does not exist: ${input.targetCwd}`)\n }\n\n const key = slotKey(input.slot)\n const prior = this.slotLocks.get(key) ?? Promise.resolve()\n const next = prior.then(() => this.acquireLocked(input))\n this.slotLocks.set(key, next.catch(() => undefined))\n return next\n }\n\n private async acquireLocked(input: AcquireRequest): Promise<AcquireResponse> {\n const key = slotKey(input.slot)\n const existing = this.bySlot.get(key)\n\n if (existing && existing.status === 'ready') {\n // M5 (per-devDomain origin): the slot is one shared origin to the\n // browser. Allowing two different working directories to claim the\n // same slot means cookies/SW/storage written by repo A would leak\n // into repo B. Refuse the rebind with a clear error pointing at the\n // user's storyboard.config.json.\n if (existing.cwd !== input.targetCwd) {\n throw new SlotCwdConflictError(input.slot, existing.cwd, input.targetCwd)\n }\n return this.toResponse(existing, this.mintLease(existing, input.ttlSeconds))\n }\n if (existing && existing.status !== 'stopped') {\n try { await existing.readyPromise } catch { /* fall through */ }\n const refreshed = this.bySlot.get(key)\n if (refreshed?.status === 'ready') {\n if (refreshed.cwd !== input.targetCwd) {\n throw new SlotCwdConflictError(input.slot, refreshed.cwd, input.targetCwd)\n }\n return this.toResponse(refreshed, this.mintLease(refreshed, input.ttlSeconds))\n }\n }\n\n const port = await (this.hotPool ? this.hotPool.acquirePort() : this.ports.acquire())\n const id = randomUUID()\n const basePath = input.slot.worktree === 'main' ? '/' : `/branch--${input.slot.worktree}/`\n const child = this.spawnViteFn(input.targetCwd, port, basePath, input.slot.devDomain)\n\n const stderrTail: string[] = []\n let resolveReady: () => void = () => undefined\n let rejectReady: (err: Error) => void = () => undefined\n const readyPromise = new Promise<void>((res, rej) => { resolveReady = res; rejectReady = rej })\n\n const internal: DevServerInternal = {\n id,\n child,\n pid: child.pid ?? 0,\n port,\n status: 'spawning',\n slot: input.slot,\n cwd: input.targetCwd,\n spawnedAt: new Date().toISOString(),\n updatedAt: new Date().toISOString(),\n stderrTail,\n readyPromise,\n }\n this.bySlot.set(key, internal)\n this.byId.set(id, internal)\n\n child.stdout?.on('data', (buf: Buffer) => {\n const text = buf.toString()\n if (internal.status === 'spawning' && /ready in|ready /.test(text)) {\n this.transition(internal, 'ready')\n this.proxy.upsert(internal.slot.devDomain, internal.slot.worktree, internal.port)\n .then(() => resolveReady())\n .catch((err: Error) => resolveReady()) // proxy failure ≠ vite failure; surface via state instead\n .finally(() => undefined)\n // Ensure we don't leave readyPromise pending on proxy failure.\n // Use void to silence floating-promise lint where applicable.\n void rejectReady\n }\n })\n child.stderr?.on('data', (buf: Buffer) => {\n stderrTail.push(buf.toString())\n if (stderrTail.length > STDERR_TAIL_MAX) stderrTail.shift()\n })\n child.on('exit', (code) => {\n if (internal.status === 'stopped') return // already torn down\n const wasReady = internal.status === 'ready'\n this.transition(internal, 'stopped')\n this.bySlot.delete(key)\n this.byId.delete(id)\n // Return the port via hotPool when configured (decrements bound count\n // AND triggers a refill); otherwise straight to the underlying pool.\n if (this.hotPool) this.hotPool.release(internal.port)\n else this.ports.release(internal.port)\n this.proxy.removeWorktree(internal.slot.devDomain, internal.slot.worktree).catch(() => undefined)\n if (!wasReady) {\n rejectReady(new DevServerSpawnError(internal.slot, code, stderrTail.join('')))\n }\n })\n\n const timeout = new Promise<never>((_res, rej) => {\n setTimeout(() => rej(new Error(`Vite ready timeout after ${this.readyTimeoutMs}ms`)), this.readyTimeoutMs).unref()\n })\n try {\n await Promise.race([readyPromise, timeout])\n } catch (err) {\n try { child.kill('SIGTERM') } catch { /* already dead */ }\n throw err\n }\n\n return this.toResponse(internal, this.mintLease(internal, input.ttlSeconds))\n }\n\n release(leaseId: string): void {\n const lease = this.leases.get(leaseId)\n if (!lease) throw new LeaseNotFoundError(leaseId)\n this.leases.delete(leaseId)\n const ds = this.byId.get(lease.devServerId)\n if (!ds) return\n\n const stillReferenced = Array.from(this.leases.values()).some(l => l.devServerId === ds.id)\n if (stillReferenced) return\n\n if (ds.status === 'ready') {\n this.transition(ds, 'draining')\n // Tear down on next tick — synchronous SIGTERM → exit cycles can race the FSM transition.\n setImmediate(() => {\n try { ds.child.kill('SIGTERM') } catch { /* already dead */ }\n })\n }\n }\n\n renew(leaseId: string, ttlSeconds: number): Lease {\n const lease = this.leases.get(leaseId)\n if (!lease) throw new LeaseNotFoundError(leaseId)\n lease.expiresAt = Date.now() + ttlSeconds * 1000\n return Lease.parse({\n id: lease.id,\n devServerId: lease.devServerId,\n slot: lease.slot,\n url: lease.url,\n expiresAt: new Date(lease.expiresAt).toISOString(),\n })\n }\n\n list(): DevServer[] {\n return Array.from(this.byId.values()).map(toDevServer)\n }\n\n /** Pool-status snapshot for the API endpoint. Null when no hot pool configured. */\n poolStatus(): { warm: number; bound: number; capacity: number } | null {\n return this.hotPool?.status() ?? null\n }\n\n shutdown(): void {\n for (const ds of this.byId.values()) {\n try { ds.child.kill('SIGTERM') } catch { /* already dead */ }\n }\n this.hotPool?.drain()\n }\n\n private mintLease(ds: DevServerInternal, ttlSeconds: number): LeaseInternal {\n const id = randomUUID()\n const url = `http://${ds.slot.devDomain}.localhost${ds.slot.worktree === 'main' ? '/' : `/branch--${ds.slot.worktree}/`}`\n const lease: LeaseInternal = {\n id,\n devServerId: ds.id,\n slot: ds.slot,\n url,\n expiresAt: Date.now() + ttlSeconds * 1000,\n }\n this.leases.set(id, lease)\n return lease\n }\n\n private toResponse(ds: DevServerInternal, lease: LeaseInternal): AcquireResponse {\n return AcquireResponse.parse({\n lease: {\n id: lease.id,\n devServerId: lease.devServerId,\n slot: lease.slot,\n url: lease.url,\n expiresAt: new Date(lease.expiresAt).toISOString(),\n },\n devServer: toDevServer(ds),\n })\n }\n\n private transition(ds: DevServerInternal, to: DevServerStatus): void {\n assertTransition(ds.status, to)\n ds.status = to\n ds.updatedAt = new Date().toISOString()\n }\n}\n\nfunction toDevServer(ds: DevServerInternal): DevServer {\n return DevServer.parse({\n id: ds.id,\n pid: ds.pid,\n port: ds.port,\n status: ds.status,\n slot: ds.slot,\n cwd: ds.cwd,\n spawnedAt: ds.spawnedAt,\n updatedAt: ds.updatedAt,\n })\n}\n\nfunction defaultSpawnVite(cwd: string, port: Port, basePath: string, devDomain: string): ChildProcess {\n const localVite = resolvePath(cwd, 'node_modules', '.bin', 'vite')\n const useLocal = existsSync(localVite)\n // dist/devserver/orchestrator.js → ../vite-plugin/wrapper.js\n const wrapperPath = resolvePath(import.meta.dirname ?? '', '..', 'vite-plugin', 'wrapper.js')\n const args = ['--port', String(port)]\n if (existsSync(wrapperPath)) {\n args.push('--config', wrapperPath)\n }\n const branchMatch = basePath.match(/^\\/branch--([^/]+)\\/$/)\n const branch = branchMatch ? branchMatch[1]! : 'main'\n const env = {\n ...process.env,\n VITE_BASE_PATH: basePath,\n STORYBOARD_RUNTIME_BRANCH: branch,\n STORYBOARD_RUNTIME_DOMAIN: devDomain,\n }\n return useLocal\n ? spawn(localVite, args, { cwd, env, stdio: ['ignore', 'pipe', 'pipe'] })\n : spawn('npx', ['vite', ...args], { cwd, env, stdio: ['ignore', 'pipe', 'pipe'] })\n}\n","import { PortPool } from '../devserver/port-pool.js'\nimport { Port } from '../schema/index.js'\n\n/**\n * HotPool — pre-allocates ports so `acquire()` doesn't pay the\n * OS-level free-port probe latency on every call.\n *\n * **What this is.** A ring of warm `Port` leases, refilled in the background.\n * The first N acquires after process start are O(1) instead of doing the\n * sequential probe-and-bind dance through 1240–1399.\n *\n * **What this is NOT.** A pool of pre-spawned Vite *processes*. Vite cannot\n * re-bind to a different project root without restart, so a \"warm Vite\"\n * would have to be killed and respawned on acquire — net negative. We\n * reserve ports only; process spawn still happens at acquire time.\n *\n * Configurable via the `HotPoolOptions`:\n * warmTarget — how many ports to keep pre-allocated (default 1)\n * capacity — hard cap (default 4) — beyond this we stop warming\n */\n\nexport interface HotPoolOptions {\n ports?: PortPool\n warmTarget?: number\n capacity?: number\n}\n\ninterface PoolEntry {\n port: Port\n warmedAt: number\n}\n\nexport class HotPool {\n private readonly ports: PortPool\n readonly warmTarget: number\n readonly capacity: number\n private readonly warm: PoolEntry[] = []\n private warming = false\n\n /** Counts of devservers currently bound to ports issued by this pool. */\n private boundCount = 0\n\n constructor(opts: HotPoolOptions = {}) {\n this.ports = opts.ports ?? new PortPool()\n this.warmTarget = Math.max(0, opts.warmTarget ?? 1)\n this.capacity = Math.max(this.warmTarget, opts.capacity ?? 4)\n }\n\n /** Kick the background refill loop. Safe to call repeatedly. */\n warmInBackground(): void {\n if (this.warming) return\n this.warming = true\n void this.refill().finally(() => { this.warming = false })\n }\n\n /**\n * Hand out a warm port if available; otherwise allocate one synchronously.\n * Either way, kicks the background refill so the next caller is also fast.\n */\n async acquirePort(): Promise<Port> {\n let port: Port\n const head = this.warm.shift()\n if (head) {\n port = head.port\n } else {\n port = await this.ports.acquire()\n }\n this.boundCount += 1\n this.warmInBackground()\n return port\n }\n\n /** Return a port to the underlying PortPool. Decrements bound count. */\n release(port: Port): void {\n this.ports.release(port)\n if (this.boundCount > 0) this.boundCount -= 1\n }\n\n /** Snapshot for the /pool/status endpoint. */\n status(): { warm: number; bound: number; capacity: number } {\n return { warm: this.warm.length, bound: this.boundCount, capacity: this.capacity }\n }\n\n /** Return all warm ports to the pool (used on shutdown). */\n drain(): void {\n while (this.warm.length > 0) {\n const e = this.warm.shift()\n if (e) this.ports.release(e.port)\n }\n }\n\n private async refill(): Promise<void> {\n while (this.warm.length < this.warmTarget) {\n try {\n const port = await this.ports.acquire()\n this.warm.push({ port, warmedAt: Date.now() })\n } catch {\n // Port pool exhausted — stop warming, leave existing entries.\n return\n }\n }\n }\n}\n","import { existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync, openSync, closeSync } from 'node:fs'\nimport { dirname } from 'node:path'\nimport { LOCKFILE, PIDFILE, RUNTIME_HOME } from './constants.js'\n\n/**\n * Lockfile-based singleton enforcement.\n *\n * On daemon start we attempt an exclusive create of `~/.storyboard/runtime.lock`.\n * Two daemons can never coexist — without this guarantee the runtime's whole\n * raison d'être (single source of truth for proxy + devservers) collapses.\n *\n * If the lockfile exists but its PID is dead, we treat it as stale and reclaim\n * it. This handles crashes / `kill -9` cleanly without operator intervention.\n */\n\nexport class RuntimeAlreadyRunningError extends Error {\n constructor(public readonly pid: number) {\n super(`Storyboard Runtime is already running (pid ${pid})`)\n this.name = 'RuntimeAlreadyRunningError'\n }\n}\n\nfunction ensureRuntimeHome(): void {\n if (!existsSync(RUNTIME_HOME)) mkdirSync(RUNTIME_HOME, { recursive: true })\n}\n\n/** Returns true if the OS reports a process with `pid` is alive. */\nfunction isProcessAlive(pid: number): boolean {\n try {\n process.kill(pid, 0)\n return true\n } catch {\n return false\n }\n}\n\n/**\n * Acquire the runtime lock. Throws RuntimeAlreadyRunningError if another\n * live daemon holds it. On success, writes `process.pid` to the pidfile and\n * returns a release function the caller MUST invoke on shutdown.\n */\nexport function acquireRuntimeLock(): () => void {\n ensureRuntimeHome()\n\n if (existsSync(LOCKFILE)) {\n const raw = readFileSync(LOCKFILE, 'utf8').trim()\n const pid = Number(raw)\n if (Number.isFinite(pid) && pid > 0 && isProcessAlive(pid)) {\n throw new RuntimeAlreadyRunningError(pid)\n }\n // Stale lock — the previous daemon crashed. Reclaim it.\n try { unlinkSync(LOCKFILE) } catch { /* race: another claimant */ }\n }\n\n // O_EXCL — atomic create-or-fail. Wins over racing daemons.\n let fd: number\n try {\n fd = openSync(LOCKFILE, 'wx')\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === 'EEXIST') {\n // Another daemon raced us to the lock.\n const raw = readFileSync(LOCKFILE, 'utf8').trim()\n const pid = Number(raw) || 0\n throw new RuntimeAlreadyRunningError(pid)\n }\n throw err\n }\n writeFileSync(fd, String(process.pid))\n closeSync(fd)\n writeFileSync(PIDFILE, String(process.pid))\n\n let released = false\n return function release(): void {\n if (released) return\n released = true\n try { unlinkSync(LOCKFILE) } catch { /* already gone */ }\n try { unlinkSync(PIDFILE) } catch { /* already gone */ }\n }\n}\n\n/** Returns the live daemon's PID, or null if no daemon is running. */\nexport function readLivePid(): number | null {\n if (!existsSync(PIDFILE)) return null\n try {\n const pid = Number(readFileSync(PIDFILE, 'utf8').trim())\n if (!Number.isFinite(pid) || pid <= 0) return null\n return isProcessAlive(pid) ? pid : null\n } catch {\n return null\n }\n}\n\nvoid dirname // tree-shaking hint: keep node:path import even if unused above\n","import { createRuntimeServer } from './http.js'\nimport { acquireRuntimeLock, RuntimeAlreadyRunningError } from './lock.js'\nimport { RUNTIME_HOST, RUNTIME_PORT } from './constants.js'\n\n/**\n * Daemon entrypoint. Run via `node bin/runtime.js` (typically forked &\n * detached by the CLI on first call).\n *\n * Lifecycle:\n * 1. Acquire singleton lock (`~/.storyboard/runtime.lock`). Refuses to\n * start if another live daemon already holds it.\n * 2. Bind the HTTP server on `127.0.0.1:4321`.\n * 3. On SIGINT/SIGTERM, drain in-flight requests, release the lock, exit 0.\n */\nexport async function startDaemon(): Promise<void> {\n let release: () => void\n try {\n release = acquireRuntimeLock()\n } catch (err) {\n if (err instanceof RuntimeAlreadyRunningError) {\n // eslint-disable-next-line no-console\n console.error(`[storyboard-runtime] already running (pid ${err.pid}). Exiting.`)\n process.exit(0)\n }\n throw err\n }\n\n const server = createRuntimeServer()\n\n await new Promise<void>((resolve, reject) => {\n server.once('error', reject)\n server.listen(RUNTIME_PORT, RUNTIME_HOST, () => resolve())\n })\n\n // eslint-disable-next-line no-console\n console.log(`[storyboard-runtime] listening on http://${RUNTIME_HOST}:${RUNTIME_PORT}`)\n\n let shuttingDown = false\n function shutdown(signal: NodeJS.Signals): void {\n if (shuttingDown) return\n shuttingDown = true\n // eslint-disable-next-line no-console\n console.log(`[storyboard-runtime] received ${signal}, shutting down…`)\n // Kill all spawned Vite children before closing the HTTP server.\n try { (globalThis as { __storyboardOrchestratorShutdown?: () => void }).__storyboardOrchestratorShutdown?.() } catch { /* */ }\n server.close(() => {\n release()\n process.exit(0)\n })\n setTimeout(() => {\n release()\n process.exit(0)\n }, 5000).unref()\n }\n process.on('SIGINT', shutdown)\n process.on('SIGTERM', shutdown)\n}\n"],"mappings":"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","names":["z","z","z","z","z","z","dirname","resolve","existsSync","readFileSync","fileURLToPath","dirname","fileURLToPath","resolve","existsSync","readFileSync","resolve","spawn","existsSync","existsSync","spawn","existsSync","readFileSync","dirname","existsSync","readFileSync","resolve"]}
|
|
1
|
+
{"version":3,"sources":["../../src/runtime/schema/identity.ts","../../src/runtime/schema/devserver.ts","../../src/runtime/schema/api.ts","../../src/runtime/client/index.ts","../../src/runtime/server/http.ts","../../src/runtime/proxy/caddy.ts","../../src/runtime/proxy/controller.ts","../../src/runtime/devserver/port-pool.ts","../../src/runtime/server/constants.ts","../../src/runtime/devserver/orchestrator.ts","../../src/runtime/pool/hot-pool.ts","../../src/runtime/server/lock.ts","../../src/runtime/server/main.ts"],"sourcesContent":["import { z } from 'zod'\n\n/**\n * The legacy/default devDomain. Acquire requests using this value are rejected\n * unless `allowDefaultDomain` is set — see DevServerOrchestrator for details.\n */\nexport const DEFAULT_DEV_DOMAIN = 'storyboard'\n\n/**\n * A devDomain identifies a Storyboard repo on this machine.\n *\n * The literal default value `\"storyboard\"` is intentionally *not* allowed by\n * `acquire` (see schema/acquire.ts) — every checkout MUST set its own\n * `devDomain` in `storyboard.config.json`. This is the structural fix for H3\n * in the server-state RCA: two repos can never share a host space.\n *\n * Allowed: lowercase letters, digits, hyphens. Must start with a letter.\n * 1–32 chars. The runtime constructs the public host as `${devDomain}.localhost`.\n */\nexport const DevDomain = z\n .string()\n .min(1)\n .max(32)\n .regex(/^[a-z][a-z0-9-]*$/, 'devDomain must match /^[a-z][a-z0-9-]*$/')\n .brand<'DevDomain'>()\nexport type DevDomain = z.infer<typeof DevDomain>\n\n/**\n * A worktree name. `\"main\"` is reserved for the repo root.\n *\n * Names are URL-safe by construction so we never have to escape them when\n * building branch URLs (`/branch--<name>/...`).\n */\nexport const WorktreeName = z\n .string()\n .min(1)\n .max(64)\n .regex(/^[a-z0-9][a-z0-9._-]*$/i, 'worktree name must be URL-safe')\n .brand<'WorktreeName'>()\nexport type WorktreeName = z.infer<typeof WorktreeName>\n\n/**\n * A TCP port the runtime has leased to a devserver. The runtime is the sole\n * authority for port allocation; clients never pick their own port.\n */\nexport const Port = z.number().int().min(1024).max(65535).brand<'Port'>()\nexport type Port = z.infer<typeof Port>\n\n/**\n * The composite key `(devDomain, worktree)` uniquely identifies a devserver.\n *\n * The runtime guarantees at most one devserver per slot — illegal collisions\n * (e.g. two repos trying to claim `(storyboard, main)`) are rejected with\n * `409 CONFLICT` rather than silently overwriting routes.\n */\nexport const DevServerSlot = z.object({\n devDomain: DevDomain,\n worktree: WorktreeName,\n})\nexport type DevServerSlot = z.infer<typeof DevServerSlot>\n\n/**\n * Convert a slot to its canonical string key, used for map lookups and\n * logging. Format: `${devDomain}::${worktree}`.\n */\nexport function slotKey(slot: DevServerSlot): string {\n return `${slot.devDomain}::${slot.worktree}`\n}\n","import { z } from 'zod'\nimport { DevDomain, DevServerSlot, Port, WorktreeName } from './identity.js'\n\n/**\n * DevServer lifecycle FSM.\n *\n * Transitions are enforced in code; illegal transitions throw. This is the\n * structural fix for the per-repo server's \"best-effort\" state — a devserver\n * that thinks it's `ready` but whose port is dead cannot exist here.\n *\n * ```\n * idle → spawning → ready → draining → stopped\n * │ │ │\n * └───────────┴────────┴──────→ stopped (on crash)\n * ```\n */\nexport const DevServerStatus = z.enum([\n 'idle', // pre-warmed in the hot pool, no project bound yet\n 'spawning', // process started, waiting for `ready in …` from Vite stdout\n 'ready', // bound to a slot, accepting traffic via Caddy\n 'draining', // releasing — finishing in-flight requests before kill\n 'stopped', // process exited, slot freed, port returned to the pool\n])\nexport type DevServerStatus = z.infer<typeof DevServerStatus>\n\n/** Allowed FSM transitions. Centralised so misuse is a one-line review catch. */\nexport const ALLOWED_TRANSITIONS: Record<DevServerStatus, readonly DevServerStatus[]> = {\n idle: ['spawning', 'stopped'],\n spawning: ['ready', 'stopped'],\n ready: ['draining', 'stopped'],\n draining: ['stopped'],\n stopped: [],\n} as const\n\nexport class IllegalTransitionError extends Error {\n constructor(from: DevServerStatus, to: DevServerStatus) {\n super(`Illegal devserver transition: ${from} → ${to}`)\n this.name = 'IllegalTransitionError'\n }\n}\n\nexport function assertTransition(from: DevServerStatus, to: DevServerStatus): void {\n if (!ALLOWED_TRANSITIONS[from].includes(to)) {\n throw new IllegalTransitionError(from, to)\n }\n}\n\n/**\n * A devserver record as exposed by the runtime API.\n *\n * `slot` is `null` for hot-pool members that haven't been acquired yet.\n * Once bound, `slot.devDomain + slot.worktree` is unique across the whole\n * runtime; the orchestrator rejects duplicate binds.\n */\nexport const DevServer = z.object({\n id: z.string().uuid(),\n pid: z.number().int().positive(),\n port: Port,\n status: DevServerStatus,\n slot: DevServerSlot.nullable(),\n /** Absolute path of the worktree directory once bound; null while in the pool. */\n cwd: z.string().nullable(),\n /** ISO timestamp; immutable after spawn. */\n spawnedAt: z.string().datetime(),\n /** ISO timestamp of last status change. */\n updatedAt: z.string().datetime(),\n})\nexport type DevServer = z.infer<typeof DevServer>\n\n/**\n * A short-lived lease handed to a CLI client when it acquires a devserver.\n *\n * Leases are the *only* way a client controls a devserver — the runtime\n * refuses commands without a valid leaseId. This means a stale `sb dev`\n * process can't kill a devserver belonging to a newer session.\n */\nexport const Lease = z.object({\n id: z.string().uuid(),\n devServerId: z.string().uuid(),\n slot: DevServerSlot,\n /** Public proxy URL the client should print to the user. Authoritative. */\n url: z.string().url(),\n /** Renew before this timestamp or the lease expires and the devserver drains. */\n expiresAt: z.string().datetime(),\n})\nexport type Lease = z.infer<typeof Lease>\n\n/**\n * A Caddy proxy route owned by the runtime. The `@id` is always the devDomain;\n * this lets the runtime PATCH a single route in place without touching others.\n *\n * `upstreams` is keyed by plain string (validated elsewhere as WorktreeName)\n * to avoid `Partial<Record<branded, …>>` shenanigans at the value-spread sites.\n */\nexport const ProxyRoute = z.object({\n devDomain: DevDomain,\n host: z.string(),\n /** worktree name → upstream port. `main` is the host's catch-all. */\n upstreams: z.record(z.string(), Port),\n})\nexport type ProxyRoute = z.infer<typeof ProxyRoute>\n","import { z } from 'zod'\nimport { DevServerSlot, Port } from './identity.js'\nimport { DevServer, Lease, ProxyRoute } from './devserver.js'\n\n/**\n * `POST /devserver/acquire` — request a devserver for a `(devDomain, worktree)` slot.\n *\n * If a devserver already exists for the slot, the runtime returns its existing\n * lease (renewed). Otherwise it either rents a hot-pool member or spawns a new\n * Vite process. The slot is locked for the duration of the call.\n */\nexport const AcquireRequest = z.object({\n slot: DevServerSlot,\n /** Absolute path of the worktree directory; the runtime spawns Vite with `cwd: targetCwd`. */\n targetCwd: z.string().min(1),\n /** Lease TTL in seconds. Defaults to 5 min; CLI clients renew on each command. */\n ttlSeconds: z.number().int().min(30).max(60 * 60).default(300),\n /**\n * Escape hatch for the deprecated default devDomain `\"storyboard\"`. CI and\n * one-off scripts may pass true; the CLI never does.\n */\n allowDefaultDomain: z.boolean().default(false),\n})\nexport type AcquireRequest = z.infer<typeof AcquireRequest>\n\nexport const AcquireResponse = z.object({\n lease: Lease,\n devServer: DevServer,\n})\nexport type AcquireResponse = z.infer<typeof AcquireResponse>\n\n/** `POST /devserver/release` — relinquish the lease and trigger draining. */\nexport const ReleaseRequest = z.object({\n leaseId: z.string().uuid(),\n})\nexport type ReleaseRequest = z.infer<typeof ReleaseRequest>\n\n/** `POST /devserver/renew` — extend the lease without changing devserver state. */\nexport const RenewRequest = z.object({\n leaseId: z.string().uuid(),\n ttlSeconds: z.number().int().min(30).max(60 * 60).default(300),\n})\nexport type RenewRequest = z.infer<typeof RenewRequest>\n\n/** `GET /proxy/state` — current routing table the runtime believes Caddy holds. */\nexport const ProxyState = z.object({\n routes: z.array(ProxyRoute),\n caddyReachable: z.boolean(),\n})\nexport type ProxyState = z.infer<typeof ProxyState>\n\n/** `GET /pool/status` — hot-pool inventory. */\nexport const PoolStatus = z.object({\n warm: z.number().int().nonnegative(),\n bound: z.number().int().nonnegative(),\n capacity: z.number().int().nonnegative(),\n})\nexport type PoolStatus = z.infer<typeof PoolStatus>\n\n/** `POST /proxy/upsert` — bind (devDomain, worktree) → port in the proxy. */\nexport const ProxyUpsertRequest = z.object({\n devDomain: z.string(),\n worktree: z.string(),\n port: z.number(),\n})\nexport type ProxyUpsertRequest = z.infer<typeof ProxyUpsertRequest>\n\n/** `POST /proxy/remove` — drop a worktree's route from the proxy. */\nexport const ProxyRemoveRequest = z.object({\n devDomain: z.string(),\n worktree: z.string(),\n})\nexport type ProxyRemoveRequest = z.infer<typeof ProxyRemoveRequest>\n\n/** `GET /health` — daemon liveness probe. */\nexport const Health = z.object({\n ok: z.literal(true),\n version: z.string(),\n uptimeSeconds: z.number().nonnegative(),\n port: Port,\n})\nexport type Health = z.infer<typeof Health>\n\n/** Runtime error envelope. All non-2xx responses share this shape. */\nexport const RuntimeError = z.object({\n error: z.string(),\n code: z.enum([\n 'NOT_IMPLEMENTED',\n 'BAD_REQUEST',\n 'CONFLICT',\n 'NOT_FOUND',\n 'FORBIDDEN_DEFAULT_DOMAIN',\n 'INTERNAL',\n 'CADDY_UNREACHABLE',\n 'PORT_EXHAUSTED',\n 'TIMEOUT',\n ]),\n details: z.unknown().optional(),\n})\nexport type RuntimeError = z.infer<typeof RuntimeError>\n","import { spawn } from 'node:child_process'\nimport { fileURLToPath } from 'node:url'\nimport { dirname, resolve } from 'node:path'\nimport { existsSync, readFileSync } from 'node:fs'\nimport {\n AcquireRequest,\n AcquireResponse,\n Health,\n PoolStatus,\n ProxyRemoveRequest,\n ProxyState,\n ProxyUpsertRequest,\n ReleaseRequest,\n RenewRequest,\n RuntimeError,\n} from '../schema/index.js'\nimport type { z } from 'zod'\n\n/**\n * Typed JS/TS client for the Storyboard Runtime daemon.\n *\n * Consumers should always go through this client rather than hand-rolling\n * `fetch` calls — the client is the only place where the daemon's URL is\n * known, and it's the only place where on-demand daemon spawning happens.\n */\n\nconst RUNTIME_BASE = 'http://127.0.0.1:4321'\n\nexport interface RuntimeClientOptions {\n /** Override base URL (mostly for tests). */\n baseUrl?: string\n /** Auto-start the daemon if it isn't running. Default: true. */\n autoStart?: boolean\n}\n\nexport class RuntimeRequestError extends Error {\n constructor(\n message: string,\n public readonly status: number,\n public readonly code: z.infer<typeof RuntimeError>['code'],\n public readonly details?: unknown,\n ) {\n super(message)\n this.name = 'RuntimeRequestError'\n }\n}\n\nasync function request<S extends z.ZodTypeAny>(\n baseUrl: string,\n method: 'GET' | 'POST',\n path: string,\n body: unknown,\n responseSchema: S | null,\n): Promise<S extends z.ZodTypeAny ? z.output<S> : void> {\n const init: RequestInit = {\n method,\n headers: body !== undefined ? { 'Content-Type': 'application/json' } : undefined,\n body: body !== undefined ? JSON.stringify(body) : undefined,\n }\n let res: Response\n try {\n res = await fetch(`${baseUrl}${path}`, init)\n } catch (err) {\n throw new RuntimeRequestError(\n `Cannot reach Storyboard Runtime at ${baseUrl} — is the daemon running? (${(err as Error).message})`,\n 0,\n 'INTERNAL',\n )\n }\n const text = await res.text()\n let parsed: unknown\n try { parsed = text ? JSON.parse(text) : {} }\n catch { parsed = { error: text, code: 'INTERNAL' } }\n\n if (!res.ok) {\n const err = RuntimeError.safeParse(parsed)\n if (err.success) {\n throw new RuntimeRequestError(err.data.error, res.status, err.data.code, err.data.details)\n }\n throw new RuntimeRequestError(`HTTP ${res.status}`, res.status, 'INTERNAL', parsed)\n }\n if (responseSchema === null) return undefined as never\n return responseSchema.parse(parsed) as never\n}\n\n/**\n * Spawn the daemon as a detached child. Resolves once the health endpoint\n * answers (or rejects after a short timeout).\n */\nasync function spawnDaemon(baseUrl: string): Promise<void> {\n const here = dirname(fileURLToPath(import.meta.url))\n // bin/storyboard-runtime.js lives next to dist/, two levels up from\n // dist/runtime/client/index.js (the published path).\n const binPath = resolve(here, '..', '..', '..', 'bin', 'storyboard-runtime.js')\n const child = spawn(process.execPath, [binPath], {\n detached: true,\n stdio: 'ignore',\n env: process.env,\n })\n child.unref()\n\n // Poll /health until the daemon is up.\n const deadline = Date.now() + 5000\n while (Date.now() < deadline) {\n try {\n const r = await fetch(`${baseUrl}/health`)\n if (r.ok) return\n } catch { /* not up yet */ }\n await new Promise(r => setTimeout(r, 100))\n }\n throw new Error(\n `Storyboard Runtime did not become ready within 5s ` +\n `(tried to spawn ${binPath})`,\n )\n}\n\n/**\n * Read the @dfosco/storyboard package.json version that this client is\n * shipping with. Used to detect mismatches against a long-lived daemon\n * that may have been spawned by a previous install.\n */\nfunction readClientVersion(): string {\n try {\n const here = dirname(fileURLToPath(import.meta.url))\n const candidates = [\n resolve(here, '..', '..', '..', 'package.json'),\n resolve(here, '..', '..', 'package.json'),\n ]\n for (const p of candidates) {\n if (existsSync(p)) {\n const pkg = JSON.parse(readFileSync(p, 'utf8')) as { version?: string }\n if (typeof pkg.version === 'string') return pkg.version\n }\n }\n } catch { /* ignore */ }\n return '0.0.0'\n}\n\nconst CLIENT_VERSION = readClientVersion()\n\n/**\n * Send SIGTERM to the daemon PID and clear its lock/pid files so\n * spawnDaemon() can start a fresh one.\n */\nfunction killExistingDaemon(): void {\n try {\n const pidPath = resolve(process.env.HOME || '', '.storyboard', 'runtime.pid')\n if (!existsSync(pidPath)) return\n const pid = Number(readFileSync(pidPath, 'utf8').trim())\n if (Number.isFinite(pid) && pid > 0) {\n try { process.kill(pid, 'SIGTERM') } catch { /* already dead */ }\n }\n } catch { /* ignore */ }\n}\n\nexport class RuntimeClient {\n readonly baseUrl: string\n readonly autoStart: boolean\n\n constructor(opts: RuntimeClientOptions = {}) {\n this.baseUrl = opts.baseUrl ?? RUNTIME_BASE\n this.autoStart = opts.autoStart !== false\n }\n\n async health(): Promise<Health> {\n try {\n const result = await request(this.baseUrl, 'GET', '/health', undefined, Health)\n // Auto-respawn on version mismatch — a long-lived daemon from a\n // previous install otherwise keeps serving stale code after upgrade.\n // Skip when client reports 0.0.0 (dev/source layout where package\n // version isn't meaningful).\n if (\n this.autoStart &&\n CLIENT_VERSION !== '0.0.0' &&\n result.version !== '0.0.0' &&\n result.version !== CLIENT_VERSION\n ) {\n killExistingDaemon()\n // Give the OS a moment to release port 4321\n await new Promise(r => setTimeout(r, 250))\n await spawnDaemon(this.baseUrl)\n return request(this.baseUrl, 'GET', '/health', undefined, Health)\n }\n return result\n } catch (err) {\n if (this.autoStart && err instanceof RuntimeRequestError && err.status === 0) {\n await spawnDaemon(this.baseUrl)\n return request(this.baseUrl, 'GET', '/health', undefined, Health)\n }\n throw err\n }\n }\n\n async acquire(input: z.input<typeof AcquireRequest>): Promise<AcquireResponse> {\n const body = AcquireRequest.parse(input)\n return request(this.baseUrl, 'POST', '/devserver/acquire', body, AcquireResponse)\n }\n\n async release(input: z.input<typeof ReleaseRequest>): Promise<void> {\n const body = ReleaseRequest.parse(input)\n await request(this.baseUrl, 'POST', '/devserver/release', body, null)\n }\n\n async renew(input: z.input<typeof RenewRequest>): Promise<void> {\n const body = RenewRequest.parse(input)\n await request(this.baseUrl, 'POST', '/devserver/renew', body, null)\n }\n\n async proxyState(): Promise<ProxyState> {\n return request(this.baseUrl, 'GET', '/proxy/state', undefined, ProxyState)\n }\n\n async proxyUpsert(input: z.input<typeof ProxyUpsertRequest>): Promise<ProxyState> {\n const body = ProxyUpsertRequest.parse(input)\n return request(this.baseUrl, 'POST', '/proxy/upsert', body, ProxyState)\n }\n\n async proxyRemove(input: z.input<typeof ProxyRemoveRequest>): Promise<ProxyState> {\n const body = ProxyRemoveRequest.parse(input)\n return request(this.baseUrl, 'POST', '/proxy/remove', body, ProxyState)\n }\n\n async poolStatus(): Promise<PoolStatus> {\n return request(this.baseUrl, 'GET', '/pool/status', undefined, PoolStatus)\n }\n}\n\n/** Default singleton client for casual callers. */\nexport const runtime = new RuntimeClient()\n","import http from 'node:http'\nimport { z } from 'zod'\nimport {\n AcquireRequest,\n AcquireResponse,\n DevDomain,\n Health,\n PoolStatus,\n Port,\n ProxyRemoveRequest,\n ProxyState,\n ProxyUpsertRequest,\n ReleaseRequest,\n RenewRequest,\n RuntimeError,\n WorktreeName,\n} from '../schema/index.js'\nimport { ProxyController } from '../proxy/index.js'\nimport { CaddyUnreachableError } from '../proxy/caddy.js'\nimport {\n DevServerOrchestrator,\n ForbiddenDefaultDomainError,\n LeaseNotFoundError,\n DevServerSpawnError,\n SlotCwdConflictError,\n} from '../devserver/index.js'\nimport { HotPool } from '../pool/index.js'\nimport { PortPool } from '../devserver/port-pool.js'\nimport { RUNTIME_PORT, RUNTIME_HOST, RUNTIME_VERSION } from './constants.js'\n\n/**\n * The runtime's HTTP API.\n *\n * Every request is parsed through a zod schema *before* any handler runs;\n * malformed input never reaches the orchestrator. Every response is also\n * shape-checked in development to prevent accidental contract drift.\n *\n * M1 scaffold: all mutating endpoints return `501 NOT_IMPLEMENTED`. The\n * shapes, status codes, and validation are real — only the orchestrator\n * wiring is deferred to M2/M3.\n */\n\nconst startedAt = Date.now()\n\n/**\n * Process-wide singletons. The runtime is one-per-machine, so module-level\n * controllers are fine — there's never more than one instance per node process.\n * Tests inject their own via createRuntimeServer({ ... }).\n *\n * The default daemon ships with a 1-port hot pool so first-acquire latency\n * is dominated by Vite startup, not by the OS-level free-port probe loop.\n * Tunable via env: STORYBOARD_RUNTIME_WARM_PORTS, STORYBOARD_RUNTIME_POOL_CAP.\n */\nlet proxyController = new ProxyController()\nlet _ports = new PortPool()\nlet _hotPool = new HotPool({\n ports: _ports,\n warmTarget: parsePositiveInt(process.env.STORYBOARD_RUNTIME_WARM_PORTS, 1),\n capacity: parsePositiveInt(process.env.STORYBOARD_RUNTIME_POOL_CAP, 4),\n})\nlet orchestrator: DevServerOrchestrator = new DevServerOrchestrator({\n proxy: proxyController,\n ports: _ports,\n hotPool: _hotPool,\n})\n\nfunction parsePositiveInt(raw: string | undefined, fallback: number): number {\n if (!raw) return fallback\n const n = Number(raw)\n return Number.isFinite(n) && n >= 0 ? Math.floor(n) : fallback\n}\n\nfunction sendJson(\n res: http.ServerResponse,\n status: number,\n body: unknown,\n schema?: z.ZodTypeAny,\n): void {\n if (schema && process.env.NODE_ENV !== 'production') {\n schema.parse(body)\n }\n res.writeHead(status, { 'Content-Type': 'application/json' })\n res.end(JSON.stringify(body))\n}\n\nfunction sendError(\n res: http.ServerResponse,\n status: number,\n code: z.infer<typeof RuntimeError>['code'],\n message: string,\n details?: unknown,\n): void {\n const body = RuntimeError.parse({ error: message, code, details })\n sendJson(res, status, body, RuntimeError)\n}\n\nasync function readJsonBody(req: http.IncomingMessage): Promise<unknown> {\n const chunks: Buffer[] = []\n for await (const chunk of req) chunks.push(chunk as Buffer)\n if (chunks.length === 0) return {}\n try {\n return JSON.parse(Buffer.concat(chunks).toString('utf8'))\n } catch {\n throw new Error('Malformed JSON body')\n }\n}\n\nasync function parseBody<S extends z.ZodTypeAny>(\n req: http.IncomingMessage,\n res: http.ServerResponse,\n schema: S,\n): Promise<z.output<S> | null> {\n let raw: unknown\n try {\n raw = await readJsonBody(req)\n } catch (err) {\n sendError(res, 400, 'BAD_REQUEST', (err as Error).message)\n return null\n }\n const result = schema.safeParse(raw)\n if (!result.success) {\n sendError(res, 400, 'BAD_REQUEST', 'Validation failed', result.error.flatten())\n return null\n }\n return result.data\n}\n\ntype Route = (req: http.IncomingMessage, res: http.ServerResponse) => Promise<void> | void\n\nconst routes = new Map<string, Route>()\n\nroutes.set('GET /health', (_req, res) => {\n const body: Health = Health.parse({\n ok: true,\n version: RUNTIME_VERSION,\n uptimeSeconds: (Date.now() - startedAt) / 1000,\n port: RUNTIME_PORT,\n })\n sendJson(res, 200, body, Health)\n})\n\nroutes.set('POST /devserver/acquire', async (req, res) => {\n const body = await parseBody(req, res, AcquireRequest)\n if (!body) return\n try {\n const result = await orchestrator.acquire(body)\n sendJson(res, 200, result, AcquireResponse)\n } catch (err) {\n if (err instanceof ForbiddenDefaultDomainError) {\n sendError(res, 403, 'FORBIDDEN_DEFAULT_DOMAIN', err.message)\n return\n }\n if (err instanceof SlotCwdConflictError) {\n sendError(res, 409, 'CONFLICT', err.message)\n return\n }\n if (err instanceof DevServerSpawnError) {\n sendError(res, 500, 'INTERNAL', err.message, { stderr: err.stderr })\n return\n }\n if (err instanceof CaddyUnreachableError) {\n sendError(res, 503, 'CADDY_UNREACHABLE', err.message)\n return\n }\n sendError(res, 500, 'INTERNAL', (err as Error).message)\n }\n})\n\nroutes.set('POST /devserver/release', async (req, res) => {\n const body = await parseBody(req, res, ReleaseRequest)\n if (!body) return\n try {\n orchestrator.release(body.leaseId)\n sendJson(res, 200, { ok: true })\n } catch (err) {\n if (err instanceof LeaseNotFoundError) {\n sendError(res, 404, 'NOT_FOUND', err.message)\n return\n }\n sendError(res, 500, 'INTERNAL', (err as Error).message)\n }\n})\n\nroutes.set('POST /devserver/renew', async (req, res) => {\n const body = await parseBody(req, res, RenewRequest)\n if (!body) return\n try {\n const lease = orchestrator.renew(body.leaseId, body.ttlSeconds)\n sendJson(res, 200, lease)\n } catch (err) {\n if (err instanceof LeaseNotFoundError) {\n sendError(res, 404, 'NOT_FOUND', err.message)\n return\n }\n sendError(res, 500, 'INTERNAL', (err as Error).message)\n }\n})\n\nroutes.set('GET /devserver/list', (_req, res) => {\n sendJson(res, 200, { devServers: orchestrator.list() })\n})\n\nroutes.set('GET /proxy/state', async (_req, res) => {\n try {\n const body = await proxyController.stateForApi()\n sendJson(res, 200, body, ProxyState)\n } catch (err) {\n sendError(res, 500, 'INTERNAL', (err as Error).message)\n }\n})\n\nroutes.set('POST /proxy/upsert', async (req, res) => {\n const body = await parseBody(req, res, ProxyUpsertRequest)\n if (!body) return\n // Re-validate with branded types — the API request schema is intentionally\n // loose (string/number) so bad input gets a useful 400 rather than the\n // brand regex's cryptic message.\n const dev = DevDomain.safeParse(body.devDomain)\n const wt = WorktreeName.safeParse(body.worktree)\n const port = Port.safeParse(body.port)\n if (!dev.success || !wt.success || !port.success) {\n sendError(res, 400, 'BAD_REQUEST', 'Validation failed', {\n devDomain: dev.success ? null : dev.error.flatten(),\n worktree: wt.success ? null : wt.error.flatten(),\n port: port.success ? null : port.error.flatten(),\n })\n return\n }\n if (dev.data === ('storyboard' as unknown as typeof dev.data)) {\n sendError(res, 403, 'FORBIDDEN_DEFAULT_DOMAIN',\n 'Refusing to bind a route under the default devDomain \"storyboard\". ' +\n 'Set a unique devDomain in storyboard.config.json.')\n return\n }\n try {\n const route = await proxyController.upsert(dev.data, wt.data, port.data)\n sendJson(res, 200, ProxyState.parse({ routes: [route], caddyReachable: true }), ProxyState)\n } catch (err) {\n if (err instanceof CaddyUnreachableError) {\n sendError(res, 503, 'CADDY_UNREACHABLE', err.message)\n return\n }\n sendError(res, 500, 'INTERNAL', (err as Error).message)\n }\n})\n\nroutes.set('POST /proxy/remove', async (req, res) => {\n const body = await parseBody(req, res, ProxyRemoveRequest)\n if (!body) return\n const dev = DevDomain.safeParse(body.devDomain)\n const wt = WorktreeName.safeParse(body.worktree)\n if (!dev.success || !wt.success) {\n sendError(res, 400, 'BAD_REQUEST', 'Validation failed')\n return\n }\n try {\n await proxyController.removeWorktree(dev.data, wt.data)\n sendJson(res, 200, ProxyState.parse({ routes: [], caddyReachable: true }), ProxyState)\n } catch (err) {\n if (err instanceof CaddyUnreachableError) {\n sendError(res, 503, 'CADDY_UNREACHABLE', err.message)\n return\n }\n sendError(res, 500, 'INTERNAL', (err as Error).message)\n }\n})\n\nroutes.set('GET /pool/status', (_req, res) => {\n // Prefer the orchestrator's view (reflects bound count); fall back to the\n // raw-pool snapshot when no hot pool is configured.\n const live = orchestrator.poolStatus()\n const body = PoolStatus.parse(live ?? { warm: 0, bound: 0, capacity: 0 })\n sendJson(res, 200, body, PoolStatus)\n})\n\nexport function createRuntimeServer(opts: {\n proxyController?: ProxyController\n orchestrator?: DevServerOrchestrator\n} = {}): http.Server {\n if (opts.proxyController) proxyController = opts.proxyController\n if (opts.orchestrator) orchestrator = opts.orchestrator\n // Reconcile Caddy on startup: delete any @id-tagged routes left over from\n // a previous daemon process. Without this, a recycled port can be hit by\n // a stale host route and serve the wrong repo (see \"Wrong domain 421\").\n void proxyController.reconcileFromCaddy().catch(() => undefined)\n // Expose orchestrator shutdown for the daemon's signal handler.\n ;(globalThis as { __storyboardOrchestratorShutdown?: () => void }).__storyboardOrchestratorShutdown = () => orchestrator.shutdown()\n const server = http.createServer(async (req, res) => {\n try {\n const url = new URL(req.url ?? '/', `http://${RUNTIME_HOST}:${RUNTIME_PORT}`)\n const key = `${req.method ?? 'GET'} ${url.pathname}`\n const handler = routes.get(key)\n if (!handler) {\n sendError(res, 404, 'NOT_FOUND', `No route for ${key}`)\n return\n }\n await handler(req, res)\n } catch (err) {\n sendError(res, 500, 'INTERNAL', (err as Error).message)\n }\n })\n return server\n}\n","import { z } from 'zod'\nimport { DevDomain, Port, WorktreeName } from '../schema/index.js'\n\n/**\n * Typed Caddy admin API client. The runtime is the SOLE writer to\n * `http://localhost:2019` — this module is the only place in the codebase\n * that issues PATCH/POST/DELETE against Caddy. All cross-repo route races\n * (the doubled-URL bug class) are eliminated by funneling writes here.\n *\n * Reads are also funneled through this module so the runtime's view of\n * Caddy state matches what's actually live.\n */\n\nexport const CADDY_ADMIN_DEFAULT = 'http://localhost:2019'\n\nexport const CaddyRoute = z\n .object({\n '@id': z.string().optional(),\n match: z.array(z.object({ host: z.array(z.string()).optional() }).passthrough()).optional(),\n handle: z.array(z.unknown()).optional(),\n })\n .passthrough()\nexport type CaddyRoute = z.infer<typeof CaddyRoute>\n\nexport interface CaddyAdminOptions {\n baseUrl?: string\n timeoutMs?: number\n}\n\nexport class CaddyUnreachableError extends Error {\n constructor(baseUrl: string, cause?: unknown) {\n super(`Caddy admin API not reachable at ${baseUrl}`)\n this.name = 'CaddyUnreachableError'\n if (cause) (this as Error & { cause?: unknown }).cause = cause\n }\n}\n\nexport class CaddyAdminClient {\n readonly baseUrl: string\n readonly timeoutMs: number\n\n constructor(opts: CaddyAdminOptions = {}) {\n this.baseUrl = opts.baseUrl ?? CADDY_ADMIN_DEFAULT\n this.timeoutMs = opts.timeoutMs ?? 5000\n }\n\n private async fetch(path: string, init: RequestInit = {}): Promise<Response> {\n const controller = new AbortController()\n const t = setTimeout(() => controller.abort(), this.timeoutMs)\n try {\n // Caddy's admin API rejects requests with 403 when the Origin header\n // doesn't match an allowed origin. Node's undici fetch sends an empty\n // Origin by default, which Caddy refuses. Set it explicitly to the\n // admin URL.\n const headers = new Headers(init.headers)\n if (!headers.has('origin')) headers.set('origin', this.baseUrl)\n return await fetch(`${this.baseUrl}${path}`, { ...init, headers, signal: controller.signal })\n } catch (err) {\n throw new CaddyUnreachableError(this.baseUrl, err)\n } finally {\n clearTimeout(t)\n }\n }\n\n async ping(): Promise<boolean> {\n try {\n const r = await this.fetch('/config/')\n return r.ok\n } catch {\n return false\n }\n }\n\n async listRoutes(): Promise<CaddyRoute[]> {\n const r = await this.fetch('/config/apps/http/servers/srv0/routes')\n if (r.status === 404) return []\n if (!r.ok) throw new CaddyUnreachableError(this.baseUrl)\n const raw = (await r.json()) as unknown\n return z.array(CaddyRoute).parse(raw ?? [])\n }\n\n async patchById(id: string, route: CaddyRoute): Promise<boolean> {\n const r = await this.fetch(`/id/${encodeURIComponent(id)}`, {\n method: 'PATCH',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(route),\n })\n return r.ok\n }\n\n async appendRoute(route: CaddyRoute): Promise<void> {\n const r = await this.fetch('/config/apps/http/servers/srv0/routes', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(route),\n })\n if (!r.ok) throw new CaddyUnreachableError(this.baseUrl)\n }\n\n async deleteRouteAt(idx: number): Promise<void> {\n const r = await this.fetch(`/config/apps/http/servers/srv0/routes/${idx}`, {\n method: 'DELETE',\n })\n if (!r.ok && r.status !== 404) throw new CaddyUnreachableError(this.baseUrl)\n }\n\n async bootstrap(): Promise<void> {\n const config = {\n apps: { http: { servers: { srv0: { listen: [':80'], routes: [] } } } },\n }\n const r = await this.fetch('/load', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(config),\n })\n if (!r.ok) throw new CaddyUnreachableError(this.baseUrl)\n }\n}\n\n/**\n * Build a Caddy route for a single devDomain, with one subroute per worktree.\n * The `@id` is the devDomain itself so writes are PATCHable in place.\n */\nexport function buildRouteFor(\n devDomain: DevDomain,\n upstreams: Record<string, Port>,\n mainPort?: Port,\n): CaddyRoute {\n const host = `${devDomain}.localhost`\n const subroutes: unknown[] = []\n\n for (const [worktree, port] of Object.entries(upstreams)) {\n if (worktree === 'main') continue\n subroutes.push({\n match: [{ path: [`/branch--${worktree}/*`] }],\n handle: [{ handler: 'reverse_proxy', upstreams: [{ dial: `localhost:${port}` }] }],\n })\n }\n\n const fallbackPort = mainPort ?? upstreams.main ?? Object.values(upstreams)[0]\n if (fallbackPort) {\n subroutes.push({\n handle: [{ handler: 'reverse_proxy', upstreams: [{ dial: `localhost:${fallbackPort}` }] }],\n })\n }\n\n return CaddyRoute.parse({\n '@id': devDomain,\n match: [{ host: [host] }],\n handle: [{ handler: 'subroute', routes: subroutes }],\n })\n}\n\nvoid WorktreeName\n","import { DevDomain, Port, ProxyRoute, ProxyState, WorktreeName } from '../schema/index.js'\nimport { buildRouteFor, CaddyAdminClient, CaddyUnreachableError, CaddyRoute } from './caddy.js'\n\n/**\n * ProxyController — the runtime's authoritative model of the Caddy proxy.\n *\n * Two invariants the rest of the system depends on:\n *\n * 1. **Sole writer.** Only this controller calls Caddy admin write methods\n * (PATCH/POST/DELETE). The legacy per-repo `caddy reload --config` path\n * that destructively replaced the entire config is no longer reachable\n * from any code under the runtime.\n *\n * 2. **Serialized writes.** Every mutation is queued through `withLock()`.\n * Two concurrent acquires for different devDomains can no longer race\n * each other into producing a Caddy config where one repo's routes\n * silently shadowed another's.\n *\n * The in-memory `routes` map is the desired state. After every write we\n * reconcile against Caddy's actual state and re-PATCH if drift is detected.\n */\n\nexport interface ProxyControllerOptions {\n caddy?: CaddyAdminClient\n}\n\nexport class ProxyController {\n private readonly caddy: CaddyAdminClient\n private readonly routes = new Map<DevDomain, ProxyRoute>()\n private writeChain: Promise<unknown> = Promise.resolve()\n\n constructor(opts: ProxyControllerOptions = {}) {\n this.caddy = opts.caddy ?? new CaddyAdminClient()\n }\n\n private withLock<T>(fn: () => Promise<T>): Promise<T> {\n const next = this.writeChain.then(fn, fn)\n this.writeChain = next.catch(() => undefined)\n return next\n }\n\n async caddyReachable(): Promise<boolean> {\n return this.caddy.ping()\n }\n\n async stateForApi(): Promise<ProxyState> {\n return ProxyState.parse({\n routes: Array.from(this.routes.values()),\n caddyReachable: await this.caddy.ping(),\n })\n }\n\n async upsert(devDomain: DevDomain, worktree: WorktreeName, port: Port): Promise<ProxyRoute> {\n return this.withLock(async () => {\n const existing = this.routes.get(devDomain)\n const upstreams: Record<string, Port> = { ...(existing?.upstreams ?? {}) }\n upstreams[worktree] = port\n const next = ProxyRoute.parse({\n devDomain,\n host: `${devDomain}.localhost`,\n upstreams,\n })\n this.routes.set(devDomain, next)\n await this.syncOne(next)\n return next\n })\n }\n\n async removeWorktree(devDomain: DevDomain, worktree: WorktreeName): Promise<void> {\n return this.withLock(async () => {\n const existing = this.routes.get(devDomain)\n if (!existing) return\n const upstreams: Record<string, Port> = { ...existing.upstreams }\n delete upstreams[worktree]\n if (Object.keys(upstreams).length === 0) {\n this.routes.delete(devDomain)\n await this.removeFromCaddy(devDomain)\n } else {\n const next = ProxyRoute.parse({\n devDomain,\n host: `${devDomain}.localhost`,\n upstreams,\n })\n this.routes.set(devDomain, next)\n await this.syncOne(next)\n }\n })\n }\n\n private async syncOne(route: ProxyRoute): Promise<void> {\n if (!(await this.caddy.ping())) {\n throw new CaddyUnreachableError(this.caddy.baseUrl)\n }\n const caddyRoute = buildRouteFor(route.devDomain, route.upstreams as Record<string, Port>)\n const patched = await this.caddy.patchById(route.devDomain, caddyRoute)\n if (!patched) {\n await this.caddy.appendRoute(caddyRoute)\n }\n await this.cleanupDuplicates(route.devDomain, `${route.devDomain}.localhost`)\n }\n\n private async removeFromCaddy(devDomain: DevDomain): Promise<void> {\n if (!(await this.caddy.ping())) throw new CaddyUnreachableError(this.caddy.baseUrl)\n const all = await this.caddy.listRoutes()\n const indices: number[] = []\n for (let i = 0; i < all.length; i++) {\n if (all[i]!['@id'] === devDomain) indices.push(i)\n }\n for (const i of indices.sort((a, b) => b - a)) {\n await this.caddy.deleteRouteAt(i)\n }\n }\n\n /**\n * Stale-route cleanup. The legacy `caddy reload` path used to leave\n * non-`@id` routes for the same host that would shadow our admin-API\n * route. We sweep them on every successful upsert.\n */\n private async cleanupDuplicates(keepId: DevDomain, host: string): Promise<void> {\n const all = await this.caddy.listRoutes()\n const stale: number[] = []\n for (let i = 0; i < all.length; i++) {\n const r = all[i]!\n if (r['@id'] === keepId) continue\n if (r['@id']) continue\n const hosts = r.match?.[0]?.host ?? []\n if (hosts.includes(host)) stale.push(i)\n }\n for (const i of stale.sort((a, b) => b - a)) {\n try { await this.caddy.deleteRouteAt(i) } catch { /* best-effort */ }\n }\n }\n\n async readLiveCaddyRoutes(): Promise<CaddyRoute[]> {\n return this.caddy.listRoutes()\n }\n\n /**\n * Reconcile Caddy with this controller's in-memory desired state.\n *\n * Called once on daemon startup. The runtime is the sole writer to Caddy,\n * so any `@id`-tagged route the controller doesn't know about is a leftover\n * from a previous daemon process. Leaving them in place causes the bug\n * where a recycled port hits a stale host (e.g. `storyboard.localhost` →\n * port 1240, which now belongs to a different repo). We delete them.\n *\n * Does NOT touch routes without `@id` — those may be hand-installed Caddy\n * rules outside our ownership; legacy duplicate cleanup happens per-upsert.\n */\n async reconcileFromCaddy(): Promise<void> {\n return this.withLock(async () => {\n if (!(await this.caddy.ping())) return\n const all = await this.caddy.listRoutes()\n const owned = new Set<string>(this.routes.keys())\n const stale: number[] = []\n for (let i = 0; i < all.length; i++) {\n const id = all[i]!['@id']\n if (id && !owned.has(id)) stale.push(i)\n }\n for (const i of stale.sort((a, b) => b - a)) {\n try { await this.caddy.deleteRouteAt(i) } catch { /* best-effort */ }\n }\n })\n }\n}\n","import { createServer } from 'node:net'\nimport { DEVSERVER_PORT_MAX, DEVSERVER_PORT_MIN } from '../server/constants.js'\nimport { Port } from '../schema/index.js'\n\n/**\n * PortPool — leases TCP ports from the runtime's reserved range.\n *\n * The runtime is the sole authority for port allocation. Vite child processes\n * never pick their own port; they're told which port to bind to. This is the\n * structural fix for hypothesis H4 (independent per-repo port registries with\n * no cross-repo awareness): leases live in this single in-memory pool, and\n * collisions are impossible because the pool refuses to hand out a port\n * that's already leased.\n */\n\nexport class PortExhaustedError extends Error {\n constructor() {\n super(`No free ports in range ${DEVSERVER_PORT_MIN}-${DEVSERVER_PORT_MAX}`)\n this.name = 'PortExhaustedError'\n }\n}\n\nexport class PortPool {\n private readonly leased = new Set<number>()\n\n async acquire(): Promise<Port> {\n for (let p = DEVSERVER_PORT_MIN; p <= DEVSERVER_PORT_MAX; p++) {\n if (this.leased.has(p)) continue\n if (await isPortFree(p)) {\n this.leased.add(p)\n return Port.parse(p)\n }\n }\n throw new PortExhaustedError()\n }\n\n release(port: Port | number): void {\n this.leased.delete(Number(port))\n }\n\n isLeased(port: Port | number): boolean {\n return this.leased.has(Number(port))\n }\n\n size(): number {\n return this.leased.size\n }\n}\n\nfunction isPortFree(port: number): Promise<boolean> {\n return new Promise((resolve) => {\n const probe = createServer()\n probe.once('error', () => resolve(false))\n probe.once('listening', () => {\n probe.close(() => resolve(true))\n })\n probe.listen(port, '127.0.0.1')\n })\n}\n","import { homedir } from 'node:os'\nimport { join, dirname, resolve } from 'node:path'\nimport { existsSync, readFileSync } from 'node:fs'\nimport { fileURLToPath } from 'node:url'\n\n/**\n * Runtime constants — single source of truth for ports, paths, and version.\n *\n * The runtime port (`4321`) is intentionally fixed: a single global daemon\n * needs a well-known address so the CLI client can find it without\n * configuration. Port 4321 is RFC-allowed and avoids collisions with\n * Vite (1234), Caddy admin (2019), and the per-repo legacy server (4100-4199).\n */\nexport const RUNTIME_PORT = 4321 as const\nexport const RUNTIME_HOST = '127.0.0.1' as const\n\n/** Where the daemon writes its pidfile and lockfile. */\nexport const RUNTIME_HOME = join(homedir(), '.storyboard')\nexport const PIDFILE = join(RUNTIME_HOME, 'runtime.pid')\nexport const LOCKFILE = join(RUNTIME_HOME, 'runtime.lock')\nexport const STATEFILE = join(RUNTIME_HOME, 'runtime.state.json')\n\n/**\n * Range of TCP ports the runtime leases to Vite devservers. Picked to sit\n * above Vite's default (1234) and below ephemeral-port territory.\n */\nexport const DEVSERVER_PORT_MIN = 1240 as const\nexport const DEVSERVER_PORT_MAX = 1399 as const\n\n/** Caddy admin API endpoint. The runtime is the SOLE writer to this URL. */\nexport const CADDY_ADMIN = 'http://localhost:2019' as const\n\n// Runtime version is read from the package.json at startup so it tracks\n// the published @dfosco/storyboard version. Falls back to \"0.0.0\" if the\n// file can't be located (e.g. dev script runs from a non-standard layout).\nfunction readPackageVersion(): string {\n try {\n const here = dirname(fileURLToPath(import.meta.url))\n // Bundled to dist/runtime/server/main.js → ../../../package.json\n const candidates = [\n resolve(here, '..', '..', '..', 'package.json'),\n resolve(here, '..', '..', 'package.json'),\n ]\n for (const p of candidates) {\n if (existsSync(p)) {\n const pkg = JSON.parse(readFileSync(p, 'utf8')) as { version?: string }\n if (typeof pkg.version === 'string' && pkg.version) return pkg.version\n }\n }\n } catch { /* fallthrough */ }\n return '0.0.0'\n}\n\nexport const RUNTIME_VERSION = readPackageVersion()\n","import { spawn, type ChildProcess } from 'node:child_process'\nimport { existsSync } from 'node:fs'\nimport { resolve as resolvePath } from 'node:path'\nimport { randomUUID } from 'node:crypto'\nimport {\n AcquireRequest,\n AcquireResponse,\n assertTransition,\n DevServer,\n DevServerSlot,\n DevServerStatus,\n DEFAULT_DEV_DOMAIN,\n Lease,\n Port,\n slotKey,\n} from '../schema/index.js'\nimport { ProxyController } from '../proxy/index.js'\nimport { HotPool } from '../pool/index.js'\nimport { PortPool } from './port-pool.js'\n\n/**\n * DevServerOrchestrator — owns the lifecycle of every Vite dev process.\n *\n * Invariants enforced here:\n *\n * 1. **Slot uniqueness.** At most one DevServer per `(devDomain, worktree)`.\n * Acquire on an existing slot returns the same DevServer with a fresh\n * lease. Per-slot mutex prevents concurrent double-spawn.\n *\n * 2. **Default-domain refusal (H3).** `acquire()` rejects requests whose\n * devDomain is the literal `\"storyboard\"` unless `allowDefaultDomain`\n * is true. The CLI never passes `true`; only CI/scripts do.\n *\n * 3. **FSM.** Every status change goes through `assertTransition()`.\n * Illegal transitions throw — no devserver can be `ready` without\n * having gone through `spawning` first.\n *\n * 4. **Lease enforcement.** Release/renew require a valid lease ID. A stale\n * `sb dev` cannot kill a devserver claimed by a newer session.\n */\n\ninterface DevServerInternal {\n id: string\n child: ChildProcess\n pid: number\n port: Port\n status: DevServerStatus\n slot: DevServerSlot\n cwd: string\n spawnedAt: string\n updatedAt: string\n stderrTail: string[]\n readyPromise: Promise<void>\n}\n\ninterface LeaseInternal {\n id: string\n devServerId: string\n slot: DevServerSlot\n url: string\n expiresAt: number\n}\n\nexport class SlotCwdConflictError extends Error {\n constructor(slot: DevServerSlot, existingCwd: string, requestedCwd: string) {\n super(\n `Slot ${slotKey(slot)} is already bound to ${existingCwd}; refusing to rebind to ${requestedCwd}. ` +\n `Two different repositories cannot share devDomain \"${slot.devDomain}\". ` +\n `Set a unique devDomain in storyboard.config.json for one of them.`,\n )\n this.name = 'SlotCwdConflictError'\n }\n}\n\nexport class ForbiddenDefaultDomainError extends Error {\n constructor() {\n super(\n `Refusing to acquire under default devDomain \"${DEFAULT_DEV_DOMAIN}\". ` +\n `Set a unique devDomain in storyboard.config.json or pass allowDefaultDomain=true.`,\n )\n this.name = 'ForbiddenDefaultDomainError'\n }\n}\n\nexport class LeaseNotFoundError extends Error {\n constructor(id: string) {\n super(`No lease ${id}`)\n this.name = 'LeaseNotFoundError'\n }\n}\n\nexport class DevServerSpawnError extends Error {\n readonly stderr: string\n constructor(slot: DevServerSlot, exitCode: number | null, stderr: string) {\n super(`Vite for ${slotKey(slot)} exited (code ${exitCode ?? 'unknown'}) before becoming ready`)\n this.name = 'DevServerSpawnError'\n this.stderr = stderr\n }\n}\n\nexport interface DevServerOrchestratorOptions {\n proxy: ProxyController\n ports?: PortPool\n /** Optional HotPool wrapping `ports` — when provided, acquire goes\n * through the warm ring first to avoid the OS-level probe loop. */\n hotPool?: HotPool\n /** Override Vite spawn for tests — return a child you've prepared. */\n spawnVite?: (cwd: string, port: Port, basePath: string, devDomain: string) => ChildProcess\n readyTimeoutMs?: number\n}\n\nconst DEFAULT_READY_TIMEOUT_MS = 30_000\nconst STDERR_TAIL_MAX = 50\n\nexport class DevServerOrchestrator {\n private readonly proxy: ProxyController\n private readonly ports: PortPool\n private readonly hotPool: HotPool | null\n private readonly readyTimeoutMs: number\n private readonly spawnViteFn: NonNullable<DevServerOrchestratorOptions['spawnVite']>\n\n private readonly bySlot = new Map<string, DevServerInternal>()\n private readonly byId = new Map<string, DevServerInternal>()\n private readonly leases = new Map<string, LeaseInternal>()\n private readonly slotLocks = new Map<string, Promise<unknown>>()\n\n constructor(opts: DevServerOrchestratorOptions) {\n this.proxy = opts.proxy\n this.ports = opts.ports ?? new PortPool()\n this.hotPool = opts.hotPool ?? null\n this.readyTimeoutMs = opts.readyTimeoutMs ?? DEFAULT_READY_TIMEOUT_MS\n this.spawnViteFn = opts.spawnVite ?? defaultSpawnVite\n // Kick the warm ring as soon as the orchestrator is constructed.\n this.hotPool?.warmInBackground()\n }\n\n async acquire(input: AcquireRequest): Promise<AcquireResponse> {\n if (input.slot.devDomain === DEFAULT_DEV_DOMAIN && !input.allowDefaultDomain) {\n throw new ForbiddenDefaultDomainError()\n }\n if (!existsSync(input.targetCwd)) {\n throw new Error(`targetCwd does not exist: ${input.targetCwd}`)\n }\n\n const key = slotKey(input.slot)\n const prior = this.slotLocks.get(key) ?? Promise.resolve()\n const next = prior.then(() => this.acquireLocked(input))\n this.slotLocks.set(key, next.catch(() => undefined))\n return next\n }\n\n private async acquireLocked(input: AcquireRequest): Promise<AcquireResponse> {\n const key = slotKey(input.slot)\n const existing = this.bySlot.get(key)\n\n if (existing && existing.status === 'ready') {\n // M5 (per-devDomain origin): the slot is one shared origin to the\n // browser. Allowing two different working directories to claim the\n // same slot means cookies/SW/storage written by repo A would leak\n // into repo B. Refuse the rebind with a clear error pointing at the\n // user's storyboard.config.json.\n if (existing.cwd !== input.targetCwd) {\n throw new SlotCwdConflictError(input.slot, existing.cwd, input.targetCwd)\n }\n return this.toResponse(existing, this.mintLease(existing, input.ttlSeconds))\n }\n if (existing && existing.status !== 'stopped') {\n try { await existing.readyPromise } catch { /* fall through */ }\n const refreshed = this.bySlot.get(key)\n if (refreshed?.status === 'ready') {\n if (refreshed.cwd !== input.targetCwd) {\n throw new SlotCwdConflictError(input.slot, refreshed.cwd, input.targetCwd)\n }\n return this.toResponse(refreshed, this.mintLease(refreshed, input.ttlSeconds))\n }\n }\n\n const port = await (this.hotPool ? this.hotPool.acquirePort() : this.ports.acquire())\n const id = randomUUID()\n const basePath = input.slot.worktree === 'main' ? '/' : `/branch--${input.slot.worktree}/`\n const child = this.spawnViteFn(input.targetCwd, port, basePath, input.slot.devDomain)\n\n const stderrTail: string[] = []\n let resolveReady: () => void = () => undefined\n let rejectReady: (err: Error) => void = () => undefined\n const readyPromise = new Promise<void>((res, rej) => { resolveReady = res; rejectReady = rej })\n\n const internal: DevServerInternal = {\n id,\n child,\n pid: child.pid ?? 0,\n port,\n status: 'spawning',\n slot: input.slot,\n cwd: input.targetCwd,\n spawnedAt: new Date().toISOString(),\n updatedAt: new Date().toISOString(),\n stderrTail,\n readyPromise,\n }\n this.bySlot.set(key, internal)\n this.byId.set(id, internal)\n\n child.stdout?.on('data', (buf: Buffer) => {\n const text = buf.toString()\n if (internal.status === 'spawning' && /ready in|ready /.test(text)) {\n this.transition(internal, 'ready')\n this.proxy.upsert(internal.slot.devDomain, internal.slot.worktree, internal.port)\n .then(() => resolveReady())\n .catch((err: Error) => resolveReady()) // proxy failure ≠ vite failure; surface via state instead\n .finally(() => undefined)\n // Ensure we don't leave readyPromise pending on proxy failure.\n // Use void to silence floating-promise lint where applicable.\n void rejectReady\n }\n })\n child.stderr?.on('data', (buf: Buffer) => {\n stderrTail.push(buf.toString())\n if (stderrTail.length > STDERR_TAIL_MAX) stderrTail.shift()\n })\n child.on('exit', (code) => {\n if (internal.status === 'stopped') return // already torn down\n const wasReady = internal.status === 'ready'\n this.transition(internal, 'stopped')\n this.bySlot.delete(key)\n this.byId.delete(id)\n // Return the port via hotPool when configured (decrements bound count\n // AND triggers a refill); otherwise straight to the underlying pool.\n if (this.hotPool) this.hotPool.release(internal.port)\n else this.ports.release(internal.port)\n this.proxy.removeWorktree(internal.slot.devDomain, internal.slot.worktree).catch(() => undefined)\n if (!wasReady) {\n rejectReady(new DevServerSpawnError(internal.slot, code, stderrTail.join('')))\n }\n })\n\n const timeout = new Promise<never>((_res, rej) => {\n setTimeout(() => rej(new Error(`Vite ready timeout after ${this.readyTimeoutMs}ms`)), this.readyTimeoutMs).unref()\n })\n try {\n await Promise.race([readyPromise, timeout])\n } catch (err) {\n try { child.kill('SIGTERM') } catch { /* already dead */ }\n throw err\n }\n\n return this.toResponse(internal, this.mintLease(internal, input.ttlSeconds))\n }\n\n release(leaseId: string): void {\n const lease = this.leases.get(leaseId)\n if (!lease) throw new LeaseNotFoundError(leaseId)\n this.leases.delete(leaseId)\n const ds = this.byId.get(lease.devServerId)\n if (!ds) return\n\n const stillReferenced = Array.from(this.leases.values()).some(l => l.devServerId === ds.id)\n if (stillReferenced) return\n\n if (ds.status === 'ready') {\n this.transition(ds, 'draining')\n // Tear down on next tick — synchronous SIGTERM → exit cycles can race the FSM transition.\n setImmediate(() => {\n try { ds.child.kill('SIGTERM') } catch { /* already dead */ }\n })\n }\n }\n\n renew(leaseId: string, ttlSeconds: number): Lease {\n const lease = this.leases.get(leaseId)\n if (!lease) throw new LeaseNotFoundError(leaseId)\n lease.expiresAt = Date.now() + ttlSeconds * 1000\n return Lease.parse({\n id: lease.id,\n devServerId: lease.devServerId,\n slot: lease.slot,\n url: lease.url,\n expiresAt: new Date(lease.expiresAt).toISOString(),\n })\n }\n\n list(): DevServer[] {\n return Array.from(this.byId.values()).map(toDevServer)\n }\n\n /** Pool-status snapshot for the API endpoint. Null when no hot pool configured. */\n poolStatus(): { warm: number; bound: number; capacity: number } | null {\n return this.hotPool?.status() ?? null\n }\n\n shutdown(): void {\n for (const ds of this.byId.values()) {\n try { ds.child.kill('SIGTERM') } catch { /* already dead */ }\n }\n this.hotPool?.drain()\n }\n\n private mintLease(ds: DevServerInternal, ttlSeconds: number): LeaseInternal {\n const id = randomUUID()\n const url = `http://${ds.slot.devDomain}.localhost${ds.slot.worktree === 'main' ? '/' : `/branch--${ds.slot.worktree}/`}`\n const lease: LeaseInternal = {\n id,\n devServerId: ds.id,\n slot: ds.slot,\n url,\n expiresAt: Date.now() + ttlSeconds * 1000,\n }\n this.leases.set(id, lease)\n return lease\n }\n\n private toResponse(ds: DevServerInternal, lease: LeaseInternal): AcquireResponse {\n return AcquireResponse.parse({\n lease: {\n id: lease.id,\n devServerId: lease.devServerId,\n slot: lease.slot,\n url: lease.url,\n expiresAt: new Date(lease.expiresAt).toISOString(),\n },\n devServer: toDevServer(ds),\n })\n }\n\n private transition(ds: DevServerInternal, to: DevServerStatus): void {\n assertTransition(ds.status, to)\n ds.status = to\n ds.updatedAt = new Date().toISOString()\n }\n}\n\nfunction toDevServer(ds: DevServerInternal): DevServer {\n return DevServer.parse({\n id: ds.id,\n pid: ds.pid,\n port: ds.port,\n status: ds.status,\n slot: ds.slot,\n cwd: ds.cwd,\n spawnedAt: ds.spawnedAt,\n updatedAt: ds.updatedAt,\n })\n}\n\nfunction defaultSpawnVite(cwd: string, port: Port, basePath: string, devDomain: string): ChildProcess {\n const localVite = resolvePath(cwd, 'node_modules', '.bin', 'vite')\n const useLocal = existsSync(localVite)\n // dist/devserver/orchestrator.js → ../vite-plugin/wrapper.js\n const wrapperPath = resolvePath(import.meta.dirname ?? '', '..', 'vite-plugin', 'wrapper.js')\n const args = ['--port', String(port)]\n if (existsSync(wrapperPath)) {\n args.push('--config', wrapperPath)\n }\n const branchMatch = basePath.match(/^\\/branch--([^/]+)\\/$/)\n const branch = branchMatch ? branchMatch[1]! : 'main'\n const env = {\n ...process.env,\n VITE_BASE_PATH: basePath,\n STORYBOARD_RUNTIME_BRANCH: branch,\n STORYBOARD_RUNTIME_DOMAIN: devDomain,\n }\n return useLocal\n ? spawn(localVite, args, { cwd, env, stdio: ['ignore', 'pipe', 'pipe'] })\n : spawn('npx', ['vite', ...args], { cwd, env, stdio: ['ignore', 'pipe', 'pipe'] })\n}\n","import { PortPool } from '../devserver/port-pool.js'\nimport { Port } from '../schema/index.js'\n\n/**\n * HotPool — pre-allocates ports so `acquire()` doesn't pay the\n * OS-level free-port probe latency on every call.\n *\n * **What this is.** A ring of warm `Port` leases, refilled in the background.\n * The first N acquires after process start are O(1) instead of doing the\n * sequential probe-and-bind dance through 1240–1399.\n *\n * **What this is NOT.** A pool of pre-spawned Vite *processes*. Vite cannot\n * re-bind to a different project root without restart, so a \"warm Vite\"\n * would have to be killed and respawned on acquire — net negative. We\n * reserve ports only; process spawn still happens at acquire time.\n *\n * Configurable via the `HotPoolOptions`:\n * warmTarget — how many ports to keep pre-allocated (default 1)\n * capacity — hard cap (default 4) — beyond this we stop warming\n */\n\nexport interface HotPoolOptions {\n ports?: PortPool\n warmTarget?: number\n capacity?: number\n}\n\ninterface PoolEntry {\n port: Port\n warmedAt: number\n}\n\nexport class HotPool {\n private readonly ports: PortPool\n readonly warmTarget: number\n readonly capacity: number\n private readonly warm: PoolEntry[] = []\n private warming = false\n\n /** Counts of devservers currently bound to ports issued by this pool. */\n private boundCount = 0\n\n constructor(opts: HotPoolOptions = {}) {\n this.ports = opts.ports ?? new PortPool()\n this.warmTarget = Math.max(0, opts.warmTarget ?? 1)\n this.capacity = Math.max(this.warmTarget, opts.capacity ?? 4)\n }\n\n /** Kick the background refill loop. Safe to call repeatedly. */\n warmInBackground(): void {\n if (this.warming) return\n this.warming = true\n void this.refill().finally(() => { this.warming = false })\n }\n\n /**\n * Hand out a warm port if available; otherwise allocate one synchronously.\n * Either way, kicks the background refill so the next caller is also fast.\n */\n async acquirePort(): Promise<Port> {\n let port: Port\n const head = this.warm.shift()\n if (head) {\n port = head.port\n } else {\n port = await this.ports.acquire()\n }\n this.boundCount += 1\n this.warmInBackground()\n return port\n }\n\n /** Return a port to the underlying PortPool. Decrements bound count. */\n release(port: Port): void {\n this.ports.release(port)\n if (this.boundCount > 0) this.boundCount -= 1\n }\n\n /** Snapshot for the /pool/status endpoint. */\n status(): { warm: number; bound: number; capacity: number } {\n return { warm: this.warm.length, bound: this.boundCount, capacity: this.capacity }\n }\n\n /** Return all warm ports to the pool (used on shutdown). */\n drain(): void {\n while (this.warm.length > 0) {\n const e = this.warm.shift()\n if (e) this.ports.release(e.port)\n }\n }\n\n private async refill(): Promise<void> {\n while (this.warm.length < this.warmTarget) {\n try {\n const port = await this.ports.acquire()\n this.warm.push({ port, warmedAt: Date.now() })\n } catch {\n // Port pool exhausted — stop warming, leave existing entries.\n return\n }\n }\n }\n}\n","import { existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync, openSync, closeSync } from 'node:fs'\nimport { dirname } from 'node:path'\nimport { LOCKFILE, PIDFILE, RUNTIME_HOME } from './constants.js'\n\n/**\n * Lockfile-based singleton enforcement.\n *\n * On daemon start we attempt an exclusive create of `~/.storyboard/runtime.lock`.\n * Two daemons can never coexist — without this guarantee the runtime's whole\n * raison d'être (single source of truth for proxy + devservers) collapses.\n *\n * If the lockfile exists but its PID is dead, we treat it as stale and reclaim\n * it. This handles crashes / `kill -9` cleanly without operator intervention.\n */\n\nexport class RuntimeAlreadyRunningError extends Error {\n constructor(public readonly pid: number) {\n super(`Storyboard Runtime is already running (pid ${pid})`)\n this.name = 'RuntimeAlreadyRunningError'\n }\n}\n\nfunction ensureRuntimeHome(): void {\n if (!existsSync(RUNTIME_HOME)) mkdirSync(RUNTIME_HOME, { recursive: true })\n}\n\n/** Returns true if the OS reports a process with `pid` is alive. */\nfunction isProcessAlive(pid: number): boolean {\n try {\n process.kill(pid, 0)\n return true\n } catch {\n return false\n }\n}\n\n/**\n * Acquire the runtime lock. Throws RuntimeAlreadyRunningError if another\n * live daemon holds it. On success, writes `process.pid` to the pidfile and\n * returns a release function the caller MUST invoke on shutdown.\n */\nexport function acquireRuntimeLock(): () => void {\n ensureRuntimeHome()\n\n if (existsSync(LOCKFILE)) {\n const raw = readFileSync(LOCKFILE, 'utf8').trim()\n const pid = Number(raw)\n if (Number.isFinite(pid) && pid > 0 && isProcessAlive(pid)) {\n throw new RuntimeAlreadyRunningError(pid)\n }\n // Stale lock — the previous daemon crashed. Reclaim it.\n try { unlinkSync(LOCKFILE) } catch { /* race: another claimant */ }\n }\n\n // O_EXCL — atomic create-or-fail. Wins over racing daemons.\n let fd: number\n try {\n fd = openSync(LOCKFILE, 'wx')\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === 'EEXIST') {\n // Another daemon raced us to the lock.\n const raw = readFileSync(LOCKFILE, 'utf8').trim()\n const pid = Number(raw) || 0\n throw new RuntimeAlreadyRunningError(pid)\n }\n throw err\n }\n writeFileSync(fd, String(process.pid))\n closeSync(fd)\n writeFileSync(PIDFILE, String(process.pid))\n\n let released = false\n return function release(): void {\n if (released) return\n released = true\n try { unlinkSync(LOCKFILE) } catch { /* already gone */ }\n try { unlinkSync(PIDFILE) } catch { /* already gone */ }\n }\n}\n\n/** Returns the live daemon's PID, or null if no daemon is running. */\nexport function readLivePid(): number | null {\n if (!existsSync(PIDFILE)) return null\n try {\n const pid = Number(readFileSync(PIDFILE, 'utf8').trim())\n if (!Number.isFinite(pid) || pid <= 0) return null\n return isProcessAlive(pid) ? pid : null\n } catch {\n return null\n }\n}\n\nvoid dirname // tree-shaking hint: keep node:path import even if unused above\n","import { createRuntimeServer } from './http.js'\nimport { acquireRuntimeLock, RuntimeAlreadyRunningError } from './lock.js'\nimport { RUNTIME_HOST, RUNTIME_PORT } from './constants.js'\n\n/**\n * Daemon entrypoint. Run via `node bin/runtime.js` (typically forked &\n * detached by the CLI on first call).\n *\n * Lifecycle:\n * 1. Acquire singleton lock (`~/.storyboard/runtime.lock`). Refuses to\n * start if another live daemon already holds it.\n * 2. Bind the HTTP server on `127.0.0.1:4321`.\n * 3. On SIGINT/SIGTERM, drain in-flight requests, release the lock, exit 0.\n */\nexport async function startDaemon(): Promise<void> {\n let release: () => void\n try {\n release = acquireRuntimeLock()\n } catch (err) {\n if (err instanceof RuntimeAlreadyRunningError) {\n // eslint-disable-next-line no-console\n console.error(`[storyboard-runtime] already running (pid ${err.pid}). Exiting.`)\n process.exit(0)\n }\n throw err\n }\n\n const server = createRuntimeServer()\n\n await new Promise<void>((resolve, reject) => {\n server.once('error', reject)\n server.listen(RUNTIME_PORT, RUNTIME_HOST, () => resolve())\n })\n\n // eslint-disable-next-line no-console\n console.log(`[storyboard-runtime] listening on http://${RUNTIME_HOST}:${RUNTIME_PORT}`)\n\n let shuttingDown = false\n function shutdown(signal: NodeJS.Signals): void {\n if (shuttingDown) return\n shuttingDown = true\n // eslint-disable-next-line no-console\n console.log(`[storyboard-runtime] received ${signal}, shutting down…`)\n // Kill all spawned Vite children before closing the HTTP server.\n try { (globalThis as { __storyboardOrchestratorShutdown?: () => void }).__storyboardOrchestratorShutdown?.() } catch { /* */ }\n server.close(() => {\n release()\n process.exit(0)\n })\n setTimeout(() => {\n release()\n process.exit(0)\n }, 5000).unref()\n }\n process.on('SIGINT', shutdown)\n process.on('SIGTERM', shutdown)\n}\n"],"mappings":"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","names":["z","z","z","z","z","z","dirname","resolve","existsSync","readFileSync","fileURLToPath","dirname","fileURLToPath","resolve","existsSync","readFileSync","resolve","spawn","existsSync","existsSync","spawn","existsSync","readFileSync","dirname","existsSync","readFileSync","resolve"]}
|
|
@@ -352,6 +352,36 @@ var ProxyController = class {
|
|
|
352
352
|
async readLiveCaddyRoutes() {
|
|
353
353
|
return this.caddy.listRoutes();
|
|
354
354
|
}
|
|
355
|
+
/**
|
|
356
|
+
* Reconcile Caddy with this controller's in-memory desired state.
|
|
357
|
+
*
|
|
358
|
+
* Called once on daemon startup. The runtime is the sole writer to Caddy,
|
|
359
|
+
* so any `@id`-tagged route the controller doesn't know about is a leftover
|
|
360
|
+
* from a previous daemon process. Leaving them in place causes the bug
|
|
361
|
+
* where a recycled port hits a stale host (e.g. `storyboard.localhost` →
|
|
362
|
+
* port 1240, which now belongs to a different repo). We delete them.
|
|
363
|
+
*
|
|
364
|
+
* Does NOT touch routes without `@id` — those may be hand-installed Caddy
|
|
365
|
+
* rules outside our ownership; legacy duplicate cleanup happens per-upsert.
|
|
366
|
+
*/
|
|
367
|
+
async reconcileFromCaddy() {
|
|
368
|
+
return this.withLock(async () => {
|
|
369
|
+
if (!await this.caddy.ping()) return;
|
|
370
|
+
const all = await this.caddy.listRoutes();
|
|
371
|
+
const owned = new Set(this.routes.keys());
|
|
372
|
+
const stale = [];
|
|
373
|
+
for (let i = 0; i < all.length; i++) {
|
|
374
|
+
const id = all[i]["@id"];
|
|
375
|
+
if (id && !owned.has(id)) stale.push(i);
|
|
376
|
+
}
|
|
377
|
+
for (const i of stale.sort((a, b) => b - a)) {
|
|
378
|
+
try {
|
|
379
|
+
await this.caddy.deleteRouteAt(i);
|
|
380
|
+
} catch {
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
});
|
|
384
|
+
}
|
|
355
385
|
};
|
|
356
386
|
|
|
357
387
|
// src/runtime/devserver/port-pool.ts
|
|
@@ -950,6 +980,7 @@ routes.set("GET /pool/status", (_req, res) => {
|
|
|
950
980
|
function createRuntimeServer(opts = {}) {
|
|
951
981
|
if (opts.proxyController) proxyController = opts.proxyController;
|
|
952
982
|
if (opts.orchestrator) orchestrator = opts.orchestrator;
|
|
983
|
+
void proxyController.reconcileFromCaddy().catch(() => void 0);
|
|
953
984
|
globalThis.__storyboardOrchestratorShutdown = () => orchestrator.shutdown();
|
|
954
985
|
const server = http.createServer(async (req, res) => {
|
|
955
986
|
try {
|