From 4bf19185e17031416899e7d7426e663607003ff7 Mon Sep 17 00:00:00 2001 From: Anthony Fu Date: Mon, 11 May 2026 19:40:00 +0900 Subject: [PATCH 1/2] refactor: reframe devframe positioning + rename Vite integration MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reframe the docs/skill to position devframe as a portable, framework- and build-tool-agnostic asset rather than a single-tool surface that "lacks" hub features. Vite DevTools is now described as one host built on devframe, reached via the renamed `vite` adapter (was the `kit` adapter); the docs no longer enumerate things devframe doesn't provide and instead point at Vite DevTools or custom adapters as the escape valve. The previous `vite` SPA-mounting plugin is no longer an adapter — moved to `devframe/helpers/vite` and the factory renamed `createVitePlugin` → `viteDevBridge`. Updated the `@devframes/nuxt` consumer, internal JSDoc, error doc DF0033, tsnapi snapshots, `DevframeRuntime` type literal, and rebranded the client-side `[Vite DevTools]` timeout error to `[devframe]`. The `vite-devtools-auth` BroadcastChannel literal is kept for cross-tab interop with the existing Vite DevTools auth page. --- README.md | 3 +- alias.ts | 2 +- docs/errors/DF0033.md | 6 +- docs/guide/adapters.md | 61 ++++++++++--------- docs/guide/client.md | 4 +- docs/guide/devframe-definition.md | 21 +++---- docs/guide/index.md | 47 +++++--------- docs/guide/streaming.md | 2 +- packages/devframe/README.md | 7 +-- packages/devframe/package.json | 2 +- packages/devframe/src/adapters/_shared.ts | 6 +- packages/devframe/src/adapters/dev.ts | 4 +- packages/devframe/src/client/rpc-ws.ts | 2 +- packages/devframe/src/client/rpc.ts | 7 ++- .../src/{adapters => helpers}/vite.ts | 13 ++-- packages/devframe/src/node/context.ts | 13 ++-- packages/devframe/src/node/diagnostics.ts | 2 +- packages/devframe/src/types/context.ts | 10 +-- packages/devframe/src/types/devframe.ts | 8 +-- packages/devframe/tsdown.config.ts | 2 +- packages/nuxt/src/index.ts | 10 +-- skills/devframe/SKILL.md | 43 +++++++------ skills/devframe/templates/spa-devframe.ts | 4 +- .../tsnapi/devframe/adapters/vite.snapshot.js | 6 -- .../{adapters => helpers}/vite.snapshot.d.ts | 6 +- .../tsnapi/devframe/helpers/vite.snapshot.js | 6 ++ tsconfig.base.json | 4 +- 27 files changed, 143 insertions(+), 158 deletions(-) rename packages/devframe/src/{adapters => helpers}/vite.ts (92%) delete mode 100644 tests/__snapshots__/tsnapi/devframe/adapters/vite.snapshot.js rename tests/__snapshots__/tsnapi/devframe/{adapters => helpers}/vite.snapshot.d.ts (69%) create mode 100644 tests/__snapshots__/tsnapi/devframe/helpers/vite.snapshot.js diff --git a/README.md b/README.md index 498a46e..36fbc5e 100644 --- a/README.md +++ b/README.md @@ -44,8 +44,7 @@ await createCli(devframe).parse() |---------|----------| | `cli` | Standalone CLI tool with `dev` / `build` / `mcp` subcommands. | | `build` | Generates a static, self-contained SPA snapshot. | -| `vite` | Runs as a Vite plugin alongside the host app's dev server. | -| `kit` | Mounts into a DevTools Kit aggregator (e.g. `@vitejs/devtools-kit`). | +| `vite` | Mounts the devframe into Vite DevTools (or any compatible host) via `@vitejs/devtools-kit`. | | `embedded` | Overlays inside another devframe's UI. | | `mcp` | Surfaces the devframe's RPC to coding agents over MCP. | diff --git a/alias.ts b/alias.ts index 37ff564..2fa1f9f 100644 --- a/alias.ts +++ b/alias.ts @@ -32,7 +32,7 @@ export const alias = { 'devframe/adapters/cli': r('devframe/src/adapters/cli.ts'), 'devframe/adapters/dev': r('devframe/src/adapters/dev.ts'), 'devframe/adapters/build': r('devframe/src/adapters/build.ts'), - 'devframe/adapters/vite': r('devframe/src/adapters/vite.ts'), + 'devframe/helpers/vite': r('devframe/src/helpers/vite.ts'), 'devframe/adapters/embedded': r('devframe/src/adapters/embedded.ts'), 'devframe/adapters/mcp': r('devframe/src/adapters/mcp.ts'), '@devframes/nuxt/runtime/plugin.client': r('nuxt/src/runtime/plugin.client.ts'), diff --git a/docs/errors/DF0033.md b/docs/errors/DF0033.md index 6c67a04..8bee827 100644 --- a/docs/errors/DF0033.md +++ b/docs/errors/DF0033.md @@ -10,7 +10,7 @@ outline: deep ## Cause -`createVitePlugin({ devMiddleware })` could not bring up the bridge dev server that pairs a host-served SPA (Vite, Nuxt, Astro, etc.) with devframe's RPC backend. Common reasons: +`viteDevBridge({ devMiddleware })` could not bring up the bridge dev server that pairs a host-served SPA (Vite, Nuxt, Astro, etc.) with devframe's RPC backend. Common reasons: - The preferred port is in use and no fallback range was configured. - Calling `def.setup(ctx)` threw — the devframe's own setup logic surfaced an error. @@ -20,10 +20,10 @@ This is a soft warning — the surrounding Vite dev server keeps running, but th ## Fix -- Pin a port via `cli.port` / `cli.portRange` on the devframe definition, or via `devMiddleware.port` on `createVitePlugin`. +- Pin a port via `cli.port` / `cli.portRange` on the devframe definition, or via `devMiddleware.port` on `viteDevBridge`. - Inspect the `reason` (or the attached `cause`) for the underlying error — fix the setup function or free the port. - For Nuxt: pass `devMiddleware: { port: }` to the `@devframes/nuxt` module. ## Source -- [`packages/devframe/src/adapters/vite.ts`](https://github.com/vitejs/devtools/blob/main/devframe/packages/devframe/src/adapters/vite.ts) — `createVitePlugin({ devMiddleware })` logs `DF0033` when port resolution or `createDevServer` throws during `configureServer`. +- [`packages/devframe/src/helpers/vite.ts`](https://github.com/vitejs/devtools/blob/main/devframe/packages/devframe/src/helpers/vite.ts) — `viteDevBridge({ devMiddleware })` logs `DF0033` when port resolution or `createDevServer` throws during `configureServer`. diff --git a/docs/guide/adapters.md b/docs/guide/adapters.md index 513420d..6d637a5 100644 --- a/docs/guide/adapters.md +++ b/docs/guide/adapters.md @@ -14,9 +14,8 @@ Every adapter factory has the shape `createXxx(devframeDef, options?)`. |---------|-------|---------|----------| | [`cli`](#cli) | `devframe/adapters/cli` | `createCli(def, options?)` | Standalone tools run via `node ./my-tool.js` | | [`dev`](#dev) | `devframe/adapters/dev` | `createDevServer(def, options?)` | Run the dev server programmatically — drive it from any CLI framework | -| [`vite`](#vite) | `devframe/adapters/vite` | `createVitePlugin(def, options?)` | Mount a tool's UI inside an existing Vite dev server | | [`build`](#build) | `devframe/adapters/build` | `createBuild(def, options?)` | Offline reports, CI artifacts, deployable SPA snapshots | -| [`kit`](#kit) | `@vitejs/devtools-kit/node` | `createPluginFromDevframe(def, options?)` | Integrating into Vite DevTools Kit | +| [`vite`](#vite) | `@vitejs/devtools-kit/node` | `createPluginFromDevframe(def, options?)` | Mount the definition into Vite DevTools (or any compatible host) | | [`embedded`](#embedded) | `devframe/adapters/embedded` | `createEmbedded(def, { ctx })` | Runtime registration into an already-running host | | [`mcp`](#mcp) | `devframe/adapters/mcp` | `createMcpServer(def, options?)` | Exposing a devframe to coding agents | @@ -180,7 +179,7 @@ A devframe's SPA basePath depends on which adapter is running it: | Adapter kind | Default basePath | Reason | |--------------|------------------|--------| | `cli`, `spa`, `build` (standalone) | `/` | The devframe owns the origin. | -| `vite`, `kit`, `embedded` (hosted) | `/__/` | The devframe shares the origin with a host app and namespaces itself. | +| `vite`, `embedded` (hosted) | `/__/` | The devframe shares the origin with a host app and namespaces itself. | Override either side explicitly with `DevframeDefinition.basePath`: @@ -194,26 +193,6 @@ defineDevframe({ SPA authors should build with relative asset paths (`vite.base: './'`); the client resolves its connection descriptor relative to the page at runtime. See [Client](./client#runtime-basepath-discovery) for the discovery rules. -## Vite - -A thin Vite plugin that mounts a devframe's SPA into an existing Vite dev server as a *hosted* adapter — the mount path defaults to `/__/` to namespace away from the app. The plugin mounts the SPA only; for RPC, use `kit` or `cli`. - -```ts -import { createVitePlugin } from 'devframe/adapters/vite' -import { defineConfig } from 'vite' -import devframe from './devframe' - -export default defineConfig({ - plugins: [createVitePlugin(devframe)], -}) -``` - -| Option | Default | Description | -|--------|---------|-------------| -| `base` | `def.basePath ?? '/__/'` | Mount path inside the Vite dev server. | - -Use this adapter when a devframe's UI is purely static and you want to surface it during Vite `serve` without shipping a separate dev server. Set `DevframeDefinition.basePath` on the definition for a custom path that stays consistent across adapters. - ## Build Produces a self-contained static deploy of a devframe: @@ -252,9 +231,9 @@ When `def.spa` is set on the definition, `createBuild` also writes `spa-loader.j Deployed SPAs that use `setupBrowser` ship their own client entry that registers the handlers. -## Kit +## Vite -Wraps a `DevframeDefinition` so Vite DevTools Kit's plugin-scan picks it up. The factory lives in `@vitejs/devtools-kit/node` — kit owns docking and process management while devframe stays portable. +The Vite-DevTools adapter — wraps a `DevframeDefinition` so Vite DevTools' kit plugin-scan picks it up. The factory lives in `@vitejs/devtools-kit/node` so devframe itself stays free of any Vite or `@vitejs/*` dependency. The pattern (`definition → host plugin → mount`) is general; other hosts can implement equivalent bridges. ```ts import { createPluginFromDevframe } from '@vitejs/devtools-kit/node' @@ -265,18 +244,18 @@ export default function myVitePlugin() { } ``` -The returned object has the shape `{ name, devtools: { setup, capabilities } }`. Use this adapter when your devframe should live inside the Vite DevTools dock alongside other integrations. Kit synthesises an iframe dock entry from the definition's `id` / `name` / `icon` / `basePath`; for richer kit-specific behaviour (extra terminals, commands, dock overrides) pass `options.setup`. See the [DevTools Kit → DevTools Plugin](https://devtools.vite.dev/kit/devtools-plugin) page for the Vite-specific guide. +The returned object has the shape `{ name, devtools: { setup, capabilities } }`. Use this adapter when your devframe should live inside the Vite DevTools dock alongside other integrations. The kit synthesises an iframe dock entry from the definition's `id` / `name` / `icon` / `basePath`; for richer host-side behaviour (extra terminals, commands, dock overrides) pass `options.setup`. See the [DevTools Kit → DevTools Plugin](https://devtools.vite.dev/kit/devtools-plugin) page for the Vite-specific guide. | Option | Default | Description | |--------|---------|-------------| | `name` | `devframe:` | Override the Vite plugin name. | | `base` | `def.basePath ?? /.${id}/` | Mount path override. | | `dock` | `{}` | Overrides for the synthesized iframe dock entry (category, icon, when). | -| `setup` | — | Additional kit-only setup hook; receives the kit-augmented context. | +| `setup` | — | Additional host-only setup hook; receives the kit-augmented context (Vite DevTools' `docks`, `terminals`, `messages`, `commands`). | ## Embedded -Register a devframe into an already-running context at runtime. Mirrors Kit's internal plugin-scan, but for callers that need dynamic, post-startup registration. The host decides the mount path; `embedded` is a hosted adapter and inherits the `/__/` default when one is needed. +Register a devframe into an already-running context at runtime. Mirrors the `vite` adapter's plugin-scan, but for callers that need dynamic, post-startup registration. The host decides the mount path; `embedded` is a hosted adapter and inherits the `/__/` default when one is needed. ```ts import { createEmbedded } from 'devframe/adapters/embedded' @@ -308,3 +287,29 @@ await createMcpServer(devframe, { transport: 'stdio' }) `@modelcontextprotocol/sdk` is a peer dependency — install it when shipping MCP support. The current transport is `stdio`. See the [Agent-Native](./agent-native) page for the full API, safety model, and Claude Desktop integration example. + +## Helpers + +These are not adapters — they're small utilities for integrating devframe into specific runtimes outside the adapter list. + +### `devframe/helpers/vite` + +A thin Vite plugin for mounting a devframe inside an existing Vite dev server, used by `@devframes/nuxt` and available for any Vite-based host (Astro, SolidStart, plain Vite apps). Two modes: + +- **Static mount** (default) — mounts `def.cli.distDir` at `options.base` (`/__/` by default). No RPC server. +- **Bridge mode** (`devMiddleware: true | {…}`) — skips the static mount; the host app owns the SPA. Devframe spawns a separate RPC + WS server and registers Vite middleware at `__connection.json` so the host-served SPA can discover the WS endpoint. + +```ts +import { viteDevBridge } from 'devframe/helpers/vite' +import { defineConfig } from 'vite' +import devframe from './devframe' + +export default defineConfig({ + plugins: [viteDevBridge(devframe)], +}) +``` + +| Option | Default | Description | +|--------|---------|-------------| +| `base` | `def.basePath ?? '/__/'` | Mount path inside the Vite dev server. | +| `devMiddleware` | `false` | `true` or `{ port?, host?, flags? }` to enable bridge mode. | diff --git a/docs/guide/client.md b/docs/guide/client.md index 0af4168..215db22 100644 --- a/docs/guide/client.md +++ b/docs/guide/client.md @@ -89,7 +89,7 @@ const ok = await rpc.requestTrustWithToken('another-token') ### Broadcast-channel sync -`connectDevframe` listens on `BroadcastChannel('vite-devtools-auth')` for `auth-update` messages. When an auth page in another tab announces a new token, every open client requests trust with it automatically — no reload required. +`connectDevframe` listens on a shared `BroadcastChannel` (named `vite-devtools-auth` for cross-tab handshake interop with Vite DevTools' auth page) for `auth-update` messages. When an auth page in another tab announces a new token, every open client requests trust with it automatically — no reload required. ## Calling functions @@ -183,7 +183,7 @@ await connectDevframe({ ## Remote docks -Remote docks are a kit-side feature (see [Vite DevTools Kit → Remote Client](https://devtools.vite.dev/kit/remote-client)). The kit injects a connection descriptor into the iframe URL; on the hosted page, `connectDevframe` auto-detects the descriptor from the URL fragment / query string — call it as usual: +Remote docks are a host-side feature — hosts that support them (Vite DevTools is one; see [its remote-client docs](https://devtools.vite.dev/kit/remote-client) for that implementation) inject a connection descriptor into the iframe URL. On the hosted page, `connectDevframe` auto-detects the descriptor from the URL fragment / query string — call it as usual: ```ts import { connectDevframe } from 'devframe/client' diff --git a/docs/guide/devframe-definition.md b/docs/guide/devframe-definition.md index 3e3c17e..5459504 100644 --- a/docs/guide/devframe-definition.md +++ b/docs/guide/devframe-definition.md @@ -4,7 +4,7 @@ outline: deep # Devframe Definition -Every Devframe tool starts with a single `defineDevframe` call. The returned `DevframeDefinition` is a portable value that any of the [adapters](./adapters) can consume — the same definition runs under `createCli`, `createBuild`, `createMcpServer`, kit's `createPluginFromDevframe`, and so on. +Every Devframe tool starts with a single `defineDevframe` call. The returned `DevframeDefinition` is a portable value that any of the [adapters](./adapters) can consume — the same definition runs under `createCli`, `createBuild`, `createMcpServer`, the `vite` adapter's `createPluginFromDevframe`, and so on. ## Minimal definition @@ -28,7 +28,7 @@ export default defineDevframe({ }) ``` -When mounted into Vite DevTools via [`createPluginFromDevframe`](./adapters#kit), the dock entry and iframe mount are derived from `id`, `name`, `icon`, and `basePath` automatically. Hub-level features (`docks`, `terminals`, `messages`, `commands`) live on the kit-augmented context. +Host adapters (such as the [`vite` adapter](./adapters#vite) for Vite DevTools) derive their mount entry from `id`, `name`, `icon`, and `basePath` automatically. ## Definition fields @@ -38,7 +38,7 @@ When mounted into Vite DevTools via [`createPluginFromDevframe`](./adapters#kit) | `name` | `string` | **Required.** Display name shown in the dock and agent manifests. | | `icon` | `string \| { light, dark }` | Optional Iconify name or URL; supports light/dark pairs. | | `version` | `string` | Optional version string surfaced to clients. | -| `basePath` | `string` | Optional mount path override. Defaults depend on the adapter: `/` for standalone (`cli` / `spa` / `build`), `/./` for hosted (`vite` / `kit` / `embedded`). | +| `basePath` | `string` | Optional mount path override. Defaults depend on the adapter: `/` for standalone (`cli` / `spa` / `build`), `/./` for hosted (`vite` / `embedded`). | | `capabilities` | `{ dev?, build?, spa? }` | Per-runtime feature flags. A `boolean` applies to the runtime as a whole; an object enables individual features. | | `setup` | `(ctx, info?) => void \| Promise` | **Required.** Server-side entry point. Runs in every runtime. The optional second argument carries runtime metadata — most notably the parsed CLI `flags` when running under `createCli`. | | `setupBrowser` | `(ctx) => void \| Promise` | Browser-only entry used by the SPA adapter. | @@ -84,14 +84,13 @@ interface DevToolsNodeContext { } ``` -Hub-level subsystems — `docks`, `terminals`, `messages`, `commands`, `createJsonRenderer` — live on the kit-augmented context owned by `@vitejs/devtools-kit`. A devframe app that wants to register kit-only behavior does so through the optional `setup` hook on `createPluginFromDevframe`. +Host adapters can augment `ctx` with additional surfaces. For example, the [`vite` adapter](./adapters#vite) exposes Vite DevTools' dock, command, message, and terminal hosts via an optional `setup` hook on `createPluginFromDevframe` — consult the host's docs for those extras. -Each host has a dedicated page: +Each devframe-level host has a dedicated page: - [RPC](./rpc) — `ctx.rpc` - [Shared State](./shared-state) — `ctx.rpc.sharedState` - [Diagnostics](./diagnostics) — `ctx.diagnostics` - [Agent-Native](./agent-native) — `ctx.agent` -- Hub-side surfaces — [Dock System](https://devtools.vite.dev/kit/dock-system), [Commands](https://devtools.vite.dev/kit/commands), [Messages](https://devtools.vite.dev/kit/messages), [Terminals](https://devtools.vite.dev/kit/terminals) — live in the [Vite DevTools Kit](https://devtools.vite.dev/kit/) docs. ## Browser setup @@ -182,15 +181,15 @@ const devframe = defineDevframe({ id: 'my-devframe', name: 'My Devframe', setup( // 1. Standalone CLI: await createCli(devframe).parse() -// 2. Embedded in a Vite project (from `vite.config.ts`): -export const myPlugin = () => createPluginFromDevframe(devframe) - -// 3. Offline snapshot: +// 2. Offline snapshot: await createBuild(devframe, { outDir: 'dist-static' }) + +// 3. Mount into a host (Vite DevTools shown — other hosts can implement equivalents): +export const myPlugin = () => createPluginFromDevframe(devframe) ``` ## What's next - [Adapters](./adapters) — pick a deployment target - [RPC](./rpc) — register server functions -- [Vite DevTools Kit](https://devtools.vite.dev/kit/) — mount your devframe into the multi-integration hub +- [`vite` adapter](./adapters#vite) — mount your devframe into Vite DevTools or another compatible host diff --git a/docs/guide/index.md b/docs/guide/index.md index 39ec39b..d7464d9 100644 --- a/docs/guide/index.md +++ b/docs/guide/index.md @@ -4,21 +4,21 @@ outline: deep # Devframe -**Devframe** is the container for one devtool integration, portable across viewers. You describe a single tool — its RPC surface, its data model, its SPA, its CLI shape — and Devframe deploys the same definition through any number of runtime adapters: a standalone CLI, a self-contained static report, an embedded SPA, an MCP server, or mounted inside a multi-integration hub. +**Devframe is an asset: define your devtool once, serve it anywhere.** You describe a single tool — its RPC surface, its data model, its SPA, its CLI shape — and the same definition deploys through any of the runtime adapters: a standalone CLI, a self-contained static report, an embedded SPA, an MCP server, and more. Devframe is framework- and build-tool-agnostic — it has no Vite dependency and no opinion on what UI framework your SPA uses. -Devframe's surface is one tool. Hub-level features — docking, the command palette, terminal aggregation, cross-tool toasts — live in [`@vitejs/devtools-kit`](https://devtools.vite.dev/kit/). To drop a Devframe app into Vite DevTools, wrap it with `createPluginFromDevframe` from `@vitejs/devtools-kit/node`; the kit synthesises the dock entry from your definition's `id` / `name` / `icon` / `basePath` and routes the hub-level ctx fields (`docks`, `terminals`, …) accordingly. +[Vite DevTools](https://devtools.vite.dev/) is built on top of devframe. If you need an integrated multi-tool host (docks, command palette, terminals, cross-tool toasts), mount your devframe into Vite DevTools via the [`vite` adapter](./adapters#vite) — or build your own host adapter targeting any environment you like. > [!WARNING] Experimental > The Devframe API is still in development and may change between versions. The agent-native surface (`agent` on `defineRpcFunction`, `ctx.agent`, and the MCP adapter) is additionally flagged as experimental. ## Design principles -Devframe keeps its surface small and pushes hub-level UX to the kit consuming it: +Devframe keeps its surface focused on one tool, so the same definition stays portable across runtimes: -- **Single-integration scope.** Devframe describes one tool. Anything that only matters across tools — docks, palette, cross-tool toasts, unified terminals — belongs in the [DevTools Kit](https://devtools.vite.dev/kit/). +- **One tool per definition.** A devframe describes a single integration. Deploy it through any adapter; host-level features that only matter when several tools share a UI (palettes, cross-tool toasts, unified terminals) come from whichever host you mount into — Vite DevTools is one example. - **Headless.** Hook into `onReady`, `cli.configure`, and friends to print your own startup banners and styling — Devframe stays out of the way. - **App-owned file watching.** Wire your own watcher (chokidar, fs.watch, …) and signal change via `ctx.rpc.sharedState.set(...)` or event-typed RPCs. -- **Context-aware mount paths.** Standalone adapters (`cli`, `spa`, `build`) serve at `/` by default; hosted adapters (`vite`, `embedded`, kit's `createPluginFromDevframe`) serve at `/./`. Override via `DevframeDefinition.basePath`. +- **Context-aware mount paths.** Standalone adapters (`cli`, `spa`, `build`) serve at `/` by default; hosted adapters (`vite`, `embedded`) serve at `/./`. Override via `DevframeDefinition.basePath`. - **SPAs own their base at runtime.** Build with relative asset paths (`vite.base: './'`); `connectDevframe` discovers the effective base from the executing script's location. - **CLI flags compose.** The `cac` instance is exposed to both the devframe (`cli.configure`) and the caller of `createCli`, so capability flags and app flags merge cleanly. @@ -36,8 +36,6 @@ Devframe keeps its surface small and pushes hub-level UX to the kit consuming it | **[Client](./client)** | Browser-side RPC client (`connectDevframe`) with auto-auth and WebSocket / static modes. | | **[Agent-Native](./agent-native)** | Opt-in exposure of your tool's surface to coding agents over MCP. | -Hub-only subsystems — [Dock System](https://devtools.vite.dev/kit/dock-system), [Commands](https://devtools.vite.dev/kit/commands), [Messages](https://devtools.vite.dev/kit/messages), [Terminals](https://devtools.vite.dev/kit/terminals) — live in the [Vite DevTools Kit](https://devtools.vite.dev/kit/) docs. - ## Architecture ```mermaid @@ -49,15 +47,13 @@ flowchart TB CLI["cli"] Vite["vite"] Build["build"] - SPA["spa"] - Kit["kit"] Embedded["embedded"] MCP["mcp"] end Adapters --> Ctx["DevToolsNodeContext"] - subgraph Ctx["DevToolsNodeContext (devframe / single-integration)"] + subgraph Ctx["DevToolsNodeContext"] direction TB RPC["rpc"] Views["views (hostStatic)"] @@ -65,10 +61,11 @@ flowchart TB Agent["agent"] end - Ctx -.->|kit augments
via createKitContext| Hub["KitNodeContext
+ docks · terminals · messages · commands"] Ctx <-->|WebSocket or static| Client["DevToolsRpcClient
(browser)"] ``` +Hosts (Vite DevTools is one) can wrap the same definition with their own adapter to augment `ctx` with extras like docks, terminals, and a command palette. + ## Install ```sh @@ -105,17 +102,7 @@ const devframe = defineDevframe({ await createCli(devframe).parse() ``` -Drop the same definition into Vite DevTools — the kit auto-derives the iframe dock entry from `id` / `name` / `icon` / `basePath`: - -```ts -// vite.config.ts -import { createPluginFromDevframe } from '@vitejs/devtools-kit/node' -import devframe from './my-devframe' - -export default { - plugins: [createPluginFromDevframe(devframe)], -} -``` +The same definition can also be deployed through any of the other adapters — for example, mounted into Vite DevTools via the [`vite` adapter](./adapters#vite). Run it: @@ -125,7 +112,7 @@ node ./my-devframe.js build # self-contained static deploy in dist-static/ node ./my-devframe.js mcp # stdio MCP server (experimental) ``` -The CLI adapter serves the SPA at `/` by default. When the same devframe is embedded inside a host (`vite`, `kit`, `embedded`), the default becomes `/.my-devframe/`. Override either side via `defineDevframe({ basePath })`. +The CLI adapter serves the SPA at `/` by default. When the same devframe is embedded inside a host (`vite`, `embedded`), the default becomes `/.my-devframe/`. Override either side via `defineDevframe({ basePath })`. ## Adapters at a glance @@ -134,22 +121,16 @@ Devframe deploys the same `DevframeDefinition` through one of these adapters: | Adapter | Entry | Target | |---------|-------|--------| | `cli` | `createCli(d).parse()` | Standalone CLI with dev / build / mcp subcommands | -| `vite` | `createVitePlugin(d, opts?)` | Plain Vite plugin that mounts the SPA | +| `vite` | `createPluginFromDevframe(d, opts?)` *(from `@vitejs/devtools-kit/node`)* | Mount the devframe into Vite DevTools (or another compatible host) | | `build` | `createBuild(d, opts?)` | Self-contained static deploy with baked RPC dumps | -| **kit (bridge)** | `createPluginFromDevframe(d, opts?)` *(from `@vitejs/devtools-kit/node`)* | Mount the devframe into Vite DevTools' hub UI | | `embedded` | `createEmbedded(d, { ctx })` | Runtime registration into an existing host | | `mcp` | `createMcpServer(d, opts)` | Model Context Protocol server | -`createPluginFromDevframe` lives in the kit because mounting into a multi-integration hub is a kit responsibility. See [Adapters](./adapters) for the full reference. - -## Dependency boundary - -Devframe is the lowest-level package in the Vite DevTools monorepo and is positioned to be extracted into its own repo. Imports from Vite or any `@vitejs/*` package are out of scope, in source and at the dependency-graph level. Hub-only concepts (docks, terminals, messages, commands) belong in the layers above: +See [Adapters](./adapters) for the full reference. -- `@vitejs/devtools-kit` — the hub. Owns docking, terminals, messages, and the command palette; provides `createPluginFromDevframe` to bridge a Devframe app into Vite DevTools. -- `@vitejs/devtools` — the integration. Vite plugin that wraps the kit and exposes Vite DevTools' own UI. +## Framework- and build-tool-agnostic -For porting an existing inspector, use the [`cli`](./adapters#cli) adapter standalone and `createPluginFromDevframe` (from `@vitejs/devtools-kit/node`) to surface it inside Vite DevTools. +Devframe has zero dependencies on Vite or any `@vitejs/*` package — the same definition runs in any Node environment, with any UI framework, against any build tool. Vite DevTools is one host built on top of devframe; mount your definition there with the [`vite` adapter](./adapters#vite), or write adapters for any other host. ## What's next diff --git a/docs/guide/streaming.md b/docs/guide/streaming.md index 9836011..9ad9da7 100644 --- a/docs/guide/streaming.md +++ b/docs/guide/streaming.md @@ -25,7 +25,7 @@ A **channel** owns a wire namespace. Each call to `channel.start()` produces an ## Defining a channel -Create the channel once in `setup`. Channels are framework-neutral, so the same code works under every adapter (`cli`, `vite`, `kit`, `embedded`): +Create the channel once in `setup`. Channels are framework-neutral, so the same code works under every adapter (`cli`, `vite`, `embedded`): ```ts import { defineDevframe, defineRpcFunction } from 'devframe' diff --git a/packages/devframe/README.md b/packages/devframe/README.md index cd0dc7a..067293b 100644 --- a/packages/devframe/README.md +++ b/packages/devframe/README.md @@ -1,8 +1,8 @@ # devframe -Framework-neutral foundation for building generic DevTools. Describe one devframe — its RPC, its data, its SPA, its CLI shape — and deploy the same definition through any of seven adapters. +Framework- and build-tool-agnostic foundation for building generic DevTools. Define your devtool once — its RPC, its data, its SPA, its CLI shape — and deploy the same definition anywhere through a set of pluggable adapters. -Part of the [Vite DevTools](https://devtools.vite.dev) monorepo. Full documentation: [https://devfra.me/](https://devfra.me/). +Full documentation: [https://devfra.me/](https://devfra.me/). ## Install @@ -38,8 +38,7 @@ await createCli(devframe).parse() |---------|----------| | `cli` | Standalone CLI tool with `dev` / `build` / `mcp` subcommands. | | `build` | Generates a static, self-contained SPA snapshot. | -| `vite` | Runs as a Vite plugin alongside the host app's dev server. | -| `kit` | Mounts into the DevTools Kit aggregator. | +| `vite` | Mounts the devframe into Vite DevTools (or any compatible host) via `@vitejs/devtools-kit`. | | `embedded` | Overlays inside another devtool's UI. | | `mcp` | Surfaces the devframe's RPC to coding agents over MCP. | diff --git a/packages/devframe/package.json b/packages/devframe/package.json index 9c65c38..3fbbe2d 100644 --- a/packages/devframe/package.json +++ b/packages/devframe/package.json @@ -25,7 +25,7 @@ "./adapters/dev": "./dist/adapters/dev.mjs", "./adapters/embedded": "./dist/adapters/embedded.mjs", "./adapters/mcp": "./dist/adapters/mcp.mjs", - "./adapters/vite": "./dist/adapters/vite.mjs", + "./helpers/vite": "./dist/helpers/vite.mjs", "./client": "./dist/client/index.mjs", "./constants": "./dist/constants.mjs", "./node": "./dist/node/index.mjs", diff --git a/packages/devframe/src/adapters/_shared.ts b/packages/devframe/src/adapters/_shared.ts index 4db9b6f..6f46014 100644 --- a/packages/devframe/src/adapters/_shared.ts +++ b/packages/devframe/src/adapters/_shared.ts @@ -2,9 +2,9 @@ import type { DevframeDefinition, DevframeDeploymentKind } from '../types/devfra /** * Resolve the mount base path for a devframe's SPA. Hosted adapters - * (`vite`, `kit`, `embedded`) default to `/__/` so they don't - * collide with the host app; standalone adapters (`cli`, `spa`, - * `build`) default to `/` because they own the origin. + * (`vite`, `embedded`) default to `/__/` so they don't collide + * with the host app; standalone adapters (`cli`, `spa`, `build`) + * default to `/` because they own the origin. * * The devframe author can override with `basePath` on the definition. */ diff --git a/packages/devframe/src/adapters/dev.ts b/packages/devframe/src/adapters/dev.ts index a72c6a0..63c2702 100644 --- a/packages/devframe/src/adapters/dev.ts +++ b/packages/devframe/src/adapters/dev.ts @@ -35,7 +35,7 @@ export interface CreateDevServerOptions { * `def.cli?.distDir` is set, the dev server runs in **bridge mode** — * only `__connection.json` and the WS endpoint are mounted; the SPA * is expected to be hosted elsewhere (e.g. by a parent Vite/Nuxt - * dev server via `createVitePlugin({ devMiddleware })`). + * dev server via `viteDevBridge({ devMiddleware })`). */ distDir?: string /** @@ -104,7 +104,7 @@ export async function resolveDevServerPort( * server runs in **bridge mode**: only `__connection.json` and the WS * endpoint are mounted, with no SPA mount. The SPA is expected to be * hosted elsewhere (e.g. by a parent Vite/Nuxt dev server) — see - * `createVitePlugin({ devMiddleware })`. + * `viteDevBridge({ devMiddleware })`. * * Returns the underlying {@link StartedServer} handle so callers can * close it gracefully (SIGINT, hot-reload, test teardown). diff --git a/packages/devframe/src/client/rpc-ws.ts b/packages/devframe/src/client/rpc-ws.ts index 4fab985..b0b99d2 100644 --- a/packages/devframe/src/client/rpc-ws.ts +++ b/packages/devframe/src/client/rpc-ws.ts @@ -114,7 +114,7 @@ export function createWsRpcClientMode( trustedPromise.promise.then(clear), new Promise((resolve, reject) => { const id = setTimeout(() => { - reject(new Error('[Vite DevTools] Timeout waiting for rpc to be trusted')) + reject(new Error('[devframe] Timeout waiting for rpc to be trusted')) }, timeout) clear = () => clearTimeout(id) }), diff --git a/packages/devframe/src/client/rpc.ts b/packages/devframe/src/client/rpc.ts index 6eade6e..d090986 100644 --- a/packages/devframe/src/client/rpc.ts +++ b/packages/devframe/src/client/rpc.ts @@ -173,8 +173,8 @@ export async function getDevToolsRpcClient( ): Promise { // Default to a relative base — the SPA owns its mount path at runtime, // so the connection meta and dump shards live alongside `index.html`. - // Embedded surfaces that run inside a host page (e.g. the Vite DevTools - // webcomponent inject) must pass an explicit `baseURL` because their + // Embedded surfaces that run inside a host page (e.g. a webcomponent + // injected by a host) must pass an explicit `baseURL` because their // `document.baseURI` points at the host app, not the devtool's mount. const { baseURL = './', @@ -313,7 +313,8 @@ export async function getDevToolsRpcClient( context.rpc = rpc void mode.requestTrust() - // Listen for auth updates from other tabs (e.g., auth URL page) + // Listen for auth updates from other tabs (e.g., auth URL page). + // Channel name kept for cross-tab interop with the Vite DevTools auth page. try { const bc = new BroadcastChannel('vite-devtools-auth') bc.onmessage = (event) => { diff --git a/packages/devframe/src/adapters/vite.ts b/packages/devframe/src/helpers/vite.ts similarity index 92% rename from packages/devframe/src/adapters/vite.ts rename to packages/devframe/src/helpers/vite.ts index 74ba88e..4f3fb22 100644 --- a/packages/devframe/src/adapters/vite.ts +++ b/packages/devframe/src/helpers/vite.ts @@ -1,12 +1,12 @@ import type { DevframeDefinition } from '../types/devframe' import { resolve } from 'pathe' +import { resolveBasePath } from '../adapters/_shared' +import { createDevServer, resolveDevServerPort } from '../adapters/dev' import { DEVTOOLS_CONNECTION_META_FILENAME } from '../constants' import { logger } from '../node/diagnostics' import { serveStaticNodeMiddleware } from '../utils/serve-static' -import { resolveBasePath } from './_shared' -import { createDevServer, resolveDevServerPort } from './dev' -export interface CreateVitePluginOptions { +export interface ViteDevBridgeOptions { /** * Mount base. Defaults to `def.basePath ?? '/__/'` for this hosted * adapter — the devframe shares the origin with the host Vite app. @@ -49,9 +49,8 @@ export interface DevframeVitePlugin { } /** - * Vite plugin for hosting a devframe inside a Vite dev server. - * - * Two modes, picked via `options.devMiddleware`: + * Bridge a devframe into an existing Vite dev server. Returns a Vite + * plugin with two modes, picked via `options.devMiddleware`: * * - **static-mount mode** (default) — mounts `def.cli.distDir` at * `options.base` with SPA fallback enabled. No RPC server is started. @@ -67,7 +66,7 @@ export interface DevframeVitePlugin { * (Nuxt, Astro, SolidStart, plain Vite apps). For the all-in-one * `dev` / `build` / `mcp` shell, reach for {@link createCli} instead. */ -export function createVitePlugin(d: DevframeDefinition, options: CreateVitePluginOptions = {}): DevframeVitePlugin { +export function viteDevBridge(d: DevframeDefinition, options: ViteDevBridgeOptions = {}): DevframeVitePlugin { const base = normalizeMountBase(options.base ?? resolveBasePath(d, 'hosted')) if (!options.devMiddleware) { diff --git a/packages/devframe/src/node/context.ts b/packages/devframe/src/node/context.ts index d699f4d..e780f92 100644 --- a/packages/devframe/src/node/context.ts +++ b/packages/devframe/src/node/context.ts @@ -22,13 +22,12 @@ export interface CreateHostContextOptions { } /** - * Framework-neutral core of the DevTools node context. Wires the RPC - * host, view (HTTP file-serving) host, diagnostics, and agent - * subsystems. Hub-level subsystems (`docks`, `terminals`, `messages`, - * `commands`, `createJsonRenderer`) are owned by - * `@vitejs/devtools-kit` — its `createKitContext` wraps this and - * attaches them when the devframe is mounted into a multi-integration - * hub. + * Framework- and build-tool-agnostic core of the DevTools node context. + * Wires the RPC host, view (HTTP file-serving) host, diagnostics, and + * agent subsystems. Host adapters can wrap this to augment `ctx` with + * extra surfaces — for example, `@vitejs/devtools-kit`'s + * `createKitContext` attaches `docks`, `terminals`, `messages`, + * `commands`, and `createJsonRenderer` when mounted into Vite DevTools. */ export async function createHostContext(options: CreateHostContextOptions): Promise { const { cwd, workspaceRoot = cwd, mode, host, builtinRpcDeclarations = [] } = options diff --git a/packages/devframe/src/node/diagnostics.ts b/packages/devframe/src/node/diagnostics.ts index f7e31bc..23a28d9 100644 --- a/packages/devframe/src/node/diagnostics.ts +++ b/packages/devframe/src/node/diagnostics.ts @@ -59,7 +59,7 @@ export const diagnostics = defineDiagnostics({ DF0033: { message: (p: { id: string, reason: string }) => `Failed to start dev RPC bridge for "${p.id}": ${p.reason}`, - hint: 'Verify the bridge port is free and the devframe setup function does not throw. Pin a port via `cli.port` / `cli.portRange` on the definition, or via `devMiddleware.port` on `createVitePlugin`.', + hint: 'Verify the bridge port is free and the devframe setup function does not throw. Pin a port via `cli.port` / `cli.portRange` on the definition, or via `devMiddleware.port` on `viteDevBridge`.', level: 'warn', }, }, diff --git a/packages/devframe/src/types/context.ts b/packages/devframe/src/types/context.ts index fbe8465..78f6d70 100644 --- a/packages/devframe/src/types/context.ts +++ b/packages/devframe/src/types/context.ts @@ -9,11 +9,11 @@ export interface DevToolsCapabilities { } /** - * Framework-neutral node context — RPC + diagnostics + agent + the - * view-host (HTTP file-serving). Hub-level subsystems (docks, - * terminals, messages, commands) are not part of this surface; they are - * added by `@vitejs/devtools-kit`'s `createKitContext` when the devtool - * is mounted into a multi-integration hub. + * Framework- and build-tool-agnostic node context — RPC + diagnostics + + * agent + the view-host (HTTP file-serving). Host adapters can wrap this + * to add their own surfaces; for example, `@vitejs/devtools-kit`'s + * `createKitContext` adds `docks`, `terminals`, `messages`, and + * `commands` when mounted into Vite DevTools. */ export interface DevToolsNodeContext { readonly workspaceRoot: string diff --git a/packages/devframe/src/types/devframe.ts b/packages/devframe/src/types/devframe.ts index b893a34..75588a1 100644 --- a/packages/devframe/src/types/devframe.ts +++ b/packages/devframe/src/types/devframe.ts @@ -2,12 +2,12 @@ import type { CAC } from 'cac' import type { CliFlagsSchema } from '../adapters/flags' import type { DevToolsNodeContext } from './context' -export type DevframeRuntime = 'cli' | 'build' | 'spa' | 'vite' | 'kit' | 'embedded' +export type DevframeRuntime = 'cli' | 'build' | 'spa' | 'vite' | 'embedded' /** * Classification of how a devframe is being deployed. Hosted adapters - * (`vite`, `kit`, `embedded`) share their origin with a host app and - * must namespace their mount path under `/__/`. Standalone adapters + * (`vite`, `embedded`) share their origin with a host app and must + * namespace their mount path under `/__/`. Standalone adapters * (`cli`, `spa`, `build`) own the origin and default to `/`. */ export type DevframeDeploymentKind = 'standalone' | 'hosted' @@ -111,7 +111,7 @@ export interface DevframeDefinition { /** * Mount path override. Defaults depend on the adapter: * `/` for standalone (`cli` / `spa` / `build`), `/__/` for hosted - * (`vite` / `kit` / `embedded`). + * (`vite` / `embedded`). */ basePath?: string capabilities?: { diff --git a/packages/devframe/tsdown.config.ts b/packages/devframe/tsdown.config.ts index b45aa86..d52020b 100644 --- a/packages/devframe/tsdown.config.ts +++ b/packages/devframe/tsdown.config.ts @@ -29,7 +29,7 @@ export default defineConfig({ 'adapters/cli': 'src/adapters/cli.ts', 'adapters/dev': 'src/adapters/dev.ts', 'adapters/build': 'src/adapters/build.ts', - 'adapters/vite': 'src/adapters/vite.ts', + 'helpers/vite': 'src/helpers/vite.ts', 'adapters/embedded': 'src/adapters/embedded.ts', 'adapters/mcp': 'src/adapters/mcp.ts', 'client/index': 'src/client/index.ts', diff --git a/packages/nuxt/src/index.ts b/packages/nuxt/src/index.ts index 725de94..5d2054c 100644 --- a/packages/nuxt/src/index.ts +++ b/packages/nuxt/src/index.ts @@ -1,6 +1,6 @@ import type { DevframeDefinition } from 'devframe/types' import { addPlugin, addVitePlugin, createResolver, defineNuxtModule } from '@nuxt/kit' -import { createVitePlugin } from 'devframe/adapters/vite' +import { viteDevBridge } from 'devframe/helpers/vite' export interface DevframeNuxtModuleOptions { /** @@ -26,8 +26,8 @@ export interface DevframeNuxtModuleOptions { */ devframe?: DevframeDefinition /** - * Dev-time middleware mode. Mirrors `createVitePlugin`'s option of the - * same name. + * Dev-time middleware mode. Mirrors `viteDevBridge`'s option of + * the same name. * * - `true` (default) — when `devframe` is set and Nuxt is in dev * mode, start the RPC bridge with all defaults. @@ -59,7 +59,7 @@ export interface DevframeNuxtModuleOptions { * - Injects a client plugin that calls {@link connectDevframe} once on * page load and exposes the RPC client via `useNuxtApp().$rpc`. * - When `devframe` is provided and Nuxt is in dev mode, registers a - * Vite plugin (via `addVitePlugin(createVitePlugin(devframe, { + * Vite plugin (via `addVitePlugin(viteDevBridge(devframe, { * devMiddleware: ... }))`) that starts the RPC + WS bridge and * serves `${baseURL}__connection.json`. * @@ -129,7 +129,7 @@ export default defineNuxtModule({ ?? (nuxt.options.devServer as any)?.host ?? options.devframe.cli?.host - addVitePlugin(createVitePlugin(options.devframe, { + addVitePlugin(viteDevBridge(options.devframe, { base: options.baseURL ?? './', devMiddleware: { port: mw.port, diff --git a/skills/devframe/SKILL.md b/skills/devframe/SKILL.md index b7e5d8f..55afe95 100644 --- a/skills/devframe/SKILL.md +++ b/skills/devframe/SKILL.md @@ -1,25 +1,27 @@ --- name: devframe description: > - Use when building one devtool integration with devframe — the - portable, framework-neutral container for a single tool. Covers + Use when building a devtool with devframe — the + framework- and build-tool-agnostic foundation for defining a + devtool once and serving it in many places. Covers DevframeDefinition, picking the right deployment adapter (cli / build / spa / vite / embedded / mcp), designing RPC - contracts, exposing an agent-native surface over MCP, and wiring - the author's SPA client. Hub-only concerns (docks, terminals, - commands, the unified messages dock) belong to - `@vitejs/devtools-kit` — see the `vite-devtools-kit` skill for - those. Triggers on `devframe` imports, `defineDevframe`, - `createCli`, `createMcpServer`, `connectDevframe`, and on - migrations of existing inspectors (eslint-config-inspector, - unocss-inspector, node-modules-inspector-style tools) to devframe. + contracts, exposing an agent-native surface over MCP, and + wiring the author's SPA client. For host-level features (docks, + terminals, palette, etc.), the devframe can be mounted into a + host that provides them — Vite DevTools is one supported target, + reached via the `vite` adapter. Triggers on `devframe` imports, + `defineDevframe`, `createCli`, `createMcpServer`, + `connectDevframe`, and on migrations of existing inspectors + (eslint-config-inspector, unocss-inspector, + node-modules-inspector-style tools) to devframe. --- # devframe skill -**Devframe is the container for one devtool integration, portable across viewers.** A devtool built on devframe is a single `DevframeDefinition` plus an author-provided SPA — the same definition deploys as a standalone CLI, a static report, an embedded SPA, an MCP server, or as a dock entry inside the Vite DevTools hub via `createPluginFromDevframe`. +**Devframe is an asset: define your devtool once, serve it anywhere.** A devtool built on devframe is a single `DevframeDefinition` plus an author-provided SPA — the same definition deploys through a set of pluggable adapters (standalone CLI, static report, embedded SPA, MCP server, mounted into a host, etc.). Devframe is framework- and build-tool-agnostic; it has no Vite dependency and makes no UI-framework assumption. -Devframe deliberately stops at the boundary of one tool. Anything that only matters across multiple integrations — docks, terminals, command palette, cross-tool toasts — lives in `@vitejs/devtools-kit`, the hub layer. `devframe` must not depend on Vite, any `@vitejs/*` package, or hub-only concepts; it's the lowest-level layer in the monorepo. +Devframe describes one tool. If you need host-level features (cross-tool palette, integrated terminals, dock aggregation), mount the devframe into a host that provides them — [Vite DevTools](https://devtools.vite.dev/) is the canonical example, reached via the `vite` adapter — or build your own host adapter. `devframe` itself must not depend on Vite or any `@vitejs/*` package. Full reference: [devfra.me/](https://devfra.me/). @@ -31,14 +33,15 @@ All adapter factories share the shape `createXxx(devframeDef, options?)`. |-------------|---------|-------| | Standalone CLI for local use | `createCli(def, options?)` | `devframe/adapters/cli` | | Run the dev server programmatically (any CLI framework) | `createDevServer(def, options?)` | `devframe/adapters/dev` | -| Mount a SPA in an existing Vite dev server | `createVitePlugin(def, options?)` | `devframe/adapters/vite` | | Self-contained static deploy with baked data | `createBuild(def, options?)` | `devframe/adapters/build` | -| Integrate into Vite DevTools | `createPluginFromDevframe(def, options?)` | `@vitejs/devtools-kit/node` | +| Mount into a host (Vite DevTools or any compatible host) | `createPluginFromDevframe(def, options?)` | `@vitejs/devtools-kit/node` | | Register dynamically at runtime | `createEmbedded(def, { ctx })` | `devframe/adapters/embedded` | | Expose to coding agents (MCP) | `createMcpServer(def, options?)` | `devframe/adapters/mcp` *(experimental)* | The same `DevframeDefinition` runs under every adapter — pick based on deployment, not on what the tool does. +For Vite-based hosts that don't use the kit (Nuxt, Astro, SolidStart, plain Vite apps), `devframe/helpers/vite` exports `viteDevBridge(def, options?)` — a Vite plugin that mounts the SPA (static mode) or starts the RPC + WS bridge alongside the host's dev server (`devMiddleware: true`). Not an adapter; just a Vite integration helper. + ## Minimum viable devframe ```ts @@ -59,7 +62,7 @@ export default defineDevframe({ }) ``` -`setup(ctx)` registers RPC functions, shared state, diagnostics, and any other devframe-level wiring. It does **not** receive `docks` / `terminals` / `messages` / `commands` — those are hub features. When mounted into Vite DevTools via `createPluginFromDevframe(d)`, the kit auto-derives an iframe dock entry from `id` / `name` / `icon` / `basePath`; for richer hub-side behaviour (custom-render, terminals, palette commands) pass `options.setup` to `createPluginFromDevframe`. +`setup(ctx)` registers RPC functions, shared state, diagnostics, and any other devframe-level wiring. Host adapters can augment `ctx` with extra surfaces — for example, mounting into Vite DevTools via `createPluginFromDevframe(d)` exposes `docks`, `terminals`, `messages`, and `commands` on the augmented context, and the kit auto-derives an iframe dock entry from `id` / `name` / `icon` / `basePath`. For richer host-side behaviour (custom-render, terminals, palette commands) pass `options.setup` to `createPluginFromDevframe`. See `templates/counter-devframe.ts` for a runnable counter example, `templates/spa-devframe.ts` for an SPA-ready shape, and `templates/vite-client.ts` for the author's client entry. @@ -86,7 +89,7 @@ See `templates/counter-devframe.ts` for a runnable counter example, `templates/s | `ctx.host` | Runtime abstraction — `mountStatic`, `resolveOrigin`, `getStorageDir` | | `ctx.mode` | `'dev'` or `'build'` — gate setup work per runtime | -> Hub-only hosts (`ctx.docks`, `ctx.terminals`, `ctx.messages`, `ctx.commands`, `ctx.createJsonRenderer`) only exist when the devframe is mounted into Vite DevTools via `createPluginFromDevframe`. See the [`vite-devtools-kit` skill](../../skills/vite-devtools-kit) for those. +> Hosts can augment `ctx` with additional surfaces (e.g. Vite DevTools' `docks`, `terminals`, `messages`, `commands`, `createJsonRenderer`). Consult the host's docs — for Vite DevTools, see the [`vite-devtools-kit` skill](../../skills/vite-devtools-kit). ## RPC contracts @@ -238,9 +241,9 @@ Readable.fromWeb(reader.readable).pipe(targetNodeWritable) For chat-style UIs that combine both: keep the **conversation log** in shared state (survives reconnects), and use a streaming channel for **active responses**. The action that starts a response appends a placeholder to shared state; on producer close, commit the joined content back to shared state. Working example: [`devframe/examples/devframe-streaming-chat`](https://github.com/vitejs/devtools/tree/main/devframe/examples/devframe-streaming-chat). -## Mounting into Vite DevTools +## Mounting into a host (Vite DevTools example) -A portable devframe definition is dropped into the Vite DevTools hub via `createPluginFromDevframe`: +A portable devframe can be mounted into any host that ships an adapter for it. Vite DevTools is one supported target — `createPluginFromDevframe` is the Vite-DevTools-specific bridge; other hosts can implement equivalent factories. Example: ```ts // vite.config.ts @@ -263,7 +266,7 @@ export default { } ``` -The kit auto-derives an iframe dock entry from `id` / `name` / `icon` / `basePath`. For dock variations (custom-render, launcher, action, json-render), terminals, palette commands, and toasts, use the `options.setup` hook — those APIs live on the kit-augmented context, not on the devframe-level `setup`. See the [`vite-devtools-kit` skill](../../skills/vite-devtools-kit) for the hub-side reference. +The kit auto-derives an iframe dock entry from `id` / `name` / `icon` / `basePath`. For dock variations (custom-render, launcher, action, json-render), terminals, palette commands, and toasts, use the `options.setup` hook — those APIs live on the host-augmented context, not on the devframe-level `setup`. See the [`vite-devtools-kit` skill](../../skills/vite-devtools-kit) for the Vite-DevTools-specific reference. ## When clauses @@ -419,7 +422,7 @@ Devframe-level pages (one-tool, portable surface): - [Client](https://devfra.me/client) — auth handshake, modes, discovery - [Agent-Native](https://devfra.me/agent-native) — agent field, tools/resources, MCP + Claude Desktop -Hub-only surfaces (Vite DevTools Kit — only available when mounted into the hub): +Host-specific extras (when mounting into Vite DevTools — other hosts have their own equivalents): - [Vite DevTools Kit overview](https://devtools.vite.dev/kit/) - [Dock System](https://devtools.vite.dev/kit/dock-system) — every entry type + remote docks diff --git a/skills/devframe/templates/spa-devframe.ts b/skills/devframe/templates/spa-devframe.ts index a5a7a85..2b40067 100644 --- a/skills/devframe/templates/spa-devframe.ts +++ b/skills/devframe/templates/spa-devframe.ts @@ -1,6 +1,6 @@ // Devframe with setupBrowser + SPA query-loader — deployable as a static site. -// When mounted into Vite DevTools via `createPluginFromDevframe`, the kit -// auto-derives an iframe dock from `id` / `name` / `icon`. +// Host adapters (e.g. the `vite` adapter for Vite DevTools) auto-derive their +// mount entry from `id` / `name` / `icon`. import { defineDevframe, defineRpcFunction } from 'devframe' import * as v from 'valibot' diff --git a/tests/__snapshots__/tsnapi/devframe/adapters/vite.snapshot.js b/tests/__snapshots__/tsnapi/devframe/adapters/vite.snapshot.js deleted file mode 100644 index 75bc009..0000000 --- a/tests/__snapshots__/tsnapi/devframe/adapters/vite.snapshot.js +++ /dev/null @@ -1,6 +0,0 @@ -/** - * Generated by tsnapi — public API snapshot of `devframe/adapters/vite` - */ -// #region Functions -export function createVitePlugin(_, _) {} -// #endregion \ No newline at end of file diff --git a/tests/__snapshots__/tsnapi/devframe/adapters/vite.snapshot.d.ts b/tests/__snapshots__/tsnapi/devframe/helpers/vite.snapshot.d.ts similarity index 69% rename from tests/__snapshots__/tsnapi/devframe/adapters/vite.snapshot.d.ts rename to tests/__snapshots__/tsnapi/devframe/helpers/vite.snapshot.d.ts index 95cd043..e65b035 100644 --- a/tests/__snapshots__/tsnapi/devframe/adapters/vite.snapshot.d.ts +++ b/tests/__snapshots__/tsnapi/devframe/helpers/vite.snapshot.d.ts @@ -1,8 +1,8 @@ /** - * Generated by tsnapi — public API snapshot of `devframe/adapters/vite` + * Generated by tsnapi — public API snapshot of `devframe/helpers/vite` */ // #region Interfaces -export interface CreateVitePluginOptions { +export interface ViteDevBridgeOptions { base?: string; devMiddleware?: boolean | { port?: number; @@ -26,5 +26,5 @@ export interface DevframeVitePlugin { // #endregion // #region Functions -export declare function createVitePlugin(_: DevframeDefinition, _?: CreateVitePluginOptions): DevframeVitePlugin; +export declare function viteDevBridge(_: DevframeDefinition, _?: ViteDevBridgeOptions): DevframeVitePlugin; // #endregion \ No newline at end of file diff --git a/tests/__snapshots__/tsnapi/devframe/helpers/vite.snapshot.js b/tests/__snapshots__/tsnapi/devframe/helpers/vite.snapshot.js new file mode 100644 index 0000000..e4c0424 --- /dev/null +++ b/tests/__snapshots__/tsnapi/devframe/helpers/vite.snapshot.js @@ -0,0 +1,6 @@ +/** + * Generated by tsnapi — public API snapshot of `devframe/helpers/vite` + */ +// #region Functions +export function viteDevBridge(_, _) {} +// #endregion \ No newline at end of file diff --git a/tsconfig.base.json b/tsconfig.base.json index 4e4222c..11ca15d 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -87,8 +87,8 @@ "devframe/adapters/build": [ "./packages/devframe/src/adapters/build.ts" ], - "devframe/adapters/vite": [ - "./packages/devframe/src/adapters/vite.ts" + "devframe/helpers/vite": [ + "./packages/devframe/src/helpers/vite.ts" ], "devframe/adapters/embedded": [ "./packages/devframe/src/adapters/embedded.ts" From 4b466f5b2d7f0546537bc36682e66a375a7e4793 Mon Sep 17 00:00:00 2001 From: Anthony Fu Date: Mon, 11 May 2026 19:47:43 +0900 Subject: [PATCH 2/2] fix: reorder tsnapi snapshot to match alphabetical interface order tsnapi emits interfaces alphabetically; the hand-edited snapshot had ViteDevBridgeOptions before DevframeVitePlugin, causing a snapshot mismatch on all CI matrix jobs. Reordered to DevframeVitePlugin first. Also picks up tsdown's idempotent re-sort of package.json exports (./helpers/vite slotted between ./constants and ./node). --- packages/devframe/package.json | 2 +- .../tsnapi/devframe/helpers/vite.snapshot.d.ts | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/packages/devframe/package.json b/packages/devframe/package.json index 3fbbe2d..40e1210 100644 --- a/packages/devframe/package.json +++ b/packages/devframe/package.json @@ -25,9 +25,9 @@ "./adapters/dev": "./dist/adapters/dev.mjs", "./adapters/embedded": "./dist/adapters/embedded.mjs", "./adapters/mcp": "./dist/adapters/mcp.mjs", - "./helpers/vite": "./dist/helpers/vite.mjs", "./client": "./dist/client/index.mjs", "./constants": "./dist/constants.mjs", + "./helpers/vite": "./dist/helpers/vite.mjs", "./node": "./dist/node/index.mjs", "./node/auth": "./dist/node/auth.mjs", "./node/internal": "./dist/node/internal.mjs", diff --git a/tests/__snapshots__/tsnapi/devframe/helpers/vite.snapshot.d.ts b/tests/__snapshots__/tsnapi/devframe/helpers/vite.snapshot.d.ts index e65b035..a4efd70 100644 --- a/tests/__snapshots__/tsnapi/devframe/helpers/vite.snapshot.d.ts +++ b/tests/__snapshots__/tsnapi/devframe/helpers/vite.snapshot.d.ts @@ -2,14 +2,6 @@ * Generated by tsnapi — public API snapshot of `devframe/helpers/vite` */ // #region Interfaces -export interface ViteDevBridgeOptions { - base?: string; - devMiddleware?: boolean | { - port?: number; - host?: string; - flags?: Record; - }; -} export interface DevframeVitePlugin { name: string; apply: 'serve'; @@ -23,6 +15,14 @@ export interface DevframeVitePlugin { }) => void | Promise; closeBundle?: () => void | Promise; } +export interface ViteDevBridgeOptions { + base?: string; + devMiddleware?: boolean | { + port?: number; + host?: string; + flags?: Record; + }; +} // #endregion // #region Functions