feat(hub): static hub builds with buildHub - #339
Conversation
The sync variant's native directory copy fails (EACCES) on shared-mount filesystems (virtiofs, Docker Desktop mounts), breaking the workspace build there.
Bake a whole hub into a static deploy, the multi-devframe counterpart of devframe's createBuild: buildHub() from @devframes/hub/build copies each devframe's SPA and page script, writes a static connection meta at the hub and every frame base, and bakes the shared RPC dump (static/snapshot RPCs plus every shared-state key), so the client runtime and every panel boot from any static file server with no live server. - devframe core: shared writeStaticRpcDump (reused by createBuild), an undefined fallback for un-baked shared-state keys, and per-frame metas can re-point relative resolution at the hub's own meta via a served baseUrl. - static client degradation: createMessagesClient keeps handles local on a static backend, and dock activation (messages panel deep links) rides a same-origin BroadcastChannel instead of the RPC relay. - @devframes/vite/hub: opt-in build option bakes the hub into vite build output and injects embedded.js into the built HTML. - examples: a11y-messages-playground and hub-vite-minimal ship production builds (build + preview), covered by a static-hub e2e suite (a11y scanning the production app over the in-page channel, baked messages feed, cross-dock activation).
◈ PR Lens
Architecture 11 components touched across 5 lanes. Inside the changed components — 2 viewsComponent view — Static Hub Build Pipeline Static build orchestration in @devframes/hub/build, Vite plugin integration, and live-only devframe filtering. Component view — Static Client Runtime & Offline Communications Client runtime behavior under static backend mode, including BroadcastChannel dock switching and in-page channel DOM scanning. Data flow
The other flows — 1 sequence
Drill down
|
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
A devframe whose value is inherently live (terminals over PTY sessions, the code-server process proxy, the assets manager) declares capabilities.build: false; buildHub now honors it the way createBuild does: the devframe is never mounted, so no dock, no SPA copy, no frame in __index.json, and none of its RPCs join the static dump. DF8007 logs each skip so a missing dock stays traceable. The terminals and code-server plugins declare the capability (assets already did).
The skip is the declared behavior, not a condition to report; drop the DF8007 diagnostic and its docs page.
# Conflicts: # docs/content/6.errors/DF8005.md # examples/next-runtime-snapshot/scripts/build-spa.mjs # packages/hub/src/node/initiate.ts # plugins/code-server/src/index.ts # plugins/git/scripts/build-spa.mjs # plugins/terminals/src/index.ts
tsdown hoists the module computing a11yClientScriptBundlePath into a shared chunk at the dist root, where the import.meta.url-relative path escapes the package (plugins/dist/...), so a hub consuming the built package mounted no page script. Resolving the package's own ./client-script export is depth-independent; the relative path stays as the unbuilt-bundle fallback.
What
Production/static build mode for the hub, so a built app served from any static file server ships working devtools.
buildHub()(new@devframes/hub/buildsubpath): the multi-devframe counterpart ofcreateBuild. Copies each devframe's SPA to<outDir>/<id>/and absolute-path page scripts to<id>/__page-script/, copies the UI slot's viewer/embedded.js/renderer modules, writes the discovery documents, abackend: 'static'__connection.jsonat the hub base and every frame base, and bakes the shared RPC dump — including a snapshot of every shared-state key (docks with rewritten page-script URLs, commands, renderer manifest), socreateDevframeClientRuntime()and every panel boot with no live server.writeStaticRpcDumpextracted and shared withcreateBuild; the shared-state dump gains anundefinedfallback (un-baked keys resolve like the live handler instead of failing); a servedConnectionMeta.baseUrlre-points relative resolution, so a frame SPA that fetches its per-frame meta still finds the hub's dump.createMessagesClientkeeps handles local on astaticbackend (the a11y page script reports unchanged), and dock-activation deep links (e.g. the messages panel's "View in a11y inspector") ride a same-originBroadcastChannel— newHUB_EVENTS.broadcastChannel.docksActivate— since no server can relay them. Fixed aDataCloneErrorposting Vue reactive proxies from the messages panel.@devframes/vite/hub: opt-inbuild: truebakes the hub intovite buildoutput at<outDir><base>and injectsembedded.jsinto the built HTML.a11y-messages-playground(hand-rolled host,buildHubdirectly) andhub-vite-minimal(vite kit +@devframes/hub-ui) both gainbuild/preview; the playground bakes a demo message whose activate action exercises the static navigation path.capabilities.build: false(same flagcreateBuildhonors) is silently skipped bybuildHub— no dock, no SPA copy, no frame in__index.json, none of its RPCs in the dump. The terminals and code-server plugins now declare it (assets already did), so a hub mounting every built-in bakes only the tools that mean something statically.a11y-messages-hub-staticPlaywright suite — dock rail from the baked shared state, the a11y page script scanning the production app and highlighting over the in-page channel, the baked messages feed, and cross-dock activation over the BroadcastChannel. Manually verifiedhub-vite-minimal's embedded dock + standalone viewer statically (baked OG snapshot, graceful `not found in dump store` for live-server tools). Unit tests forbuildHub(DF8006guard included); docs for the guide, hub-api reference, events reference, vite kit page, and the new error code.Also: the two Next static-export copy scripts use
fs.promises.cp(the sync native copy EACCESes on shared-mount filesystems).Created with the help of an agent.