Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ Monorepo (`pnpm` workspaces + `turbo`). ESM TypeScript; bundled with `tsdown`. P
| `packages/core` | `@vitejs/devtools` | Vite plugin, CLI, standalone/webcomponents client. Wraps devframe's createHostContext with the Vite plugin scan |
| `packages/kit` | `@vitejs/devtools-kit` | Vite-specific superset of devframe — adds PluginWithDevTools, ViteDevToolsNodeContext, and re-exports devframe's public types |
| `packages/ui` | `@vitejs/devtools-ui` | Shared UI components, composables, and UnoCSS preset (`presetDevToolsUI`). Private, not published |
| `packages/rolldown` | `@vitejs/devtools-rolldown` | Nuxt UI for Rolldown build data. Serves at `/.devtools-rolldown/` |
| `packages/vite` | `@vitejs/devtools-vite` | Nuxt UI for Vite DevTools (WIP). Serves at `/.devtools-vite/` |
| `packages/self-inspect` | `@vitejs/devtools-self-inspect` | Meta-introspection — DevTools for the DevTools. Serves at `/.devtools-self-inspect/` |
| `packages/rolldown` | `@vitejs/devtools-rolldown` | Nuxt UI for Rolldown build data. Serves at `/__devtools-rolldown/` |
| `packages/vite` | `@vitejs/devtools-vite` | Nuxt UI for Vite DevTools (WIP). Serves at `/__devtools-vite/` |
| `packages/self-inspect` | `@vitejs/devtools-self-inspect` | Meta-introspection — DevTools for the DevTools. Serves at `/__devtools-self-inspect/` |
| `packages/webext` | — | Browser extension scaffolding (ancillary) |

Other top-level directories:
Expand Down Expand Up @@ -63,7 +63,7 @@ pnpm -C docs run docs # docs dev server
- Use workspace aliases from `alias.ts`.
- RPC functions must use `defineRpcFunction` from kit; always namespace IDs (`my-plugin:fn-name`).
- Shared state via `devframe/utils/shared-state`; keep values serializable.
- Nuxt UI base paths: `/.devtools-rolldown/`, `/.devtools-vite/`, `/.devtools-self-inspect/`.
- Nuxt UI base paths: `/__devtools-rolldown/`, `/__devtools-vite/`, `/__devtools-self-inspect/`.
- Shared UI components/preset in `packages/ui`; use `presetDevToolsUI` from `@vitejs/devtools-ui/unocss`.
- Currently focused on Rolldown build-mode analysis; dev-mode support is deferred.

Expand All @@ -73,8 +73,8 @@ These apply to everything inside `packages/devframe` and to how host packages (`

- **Headless by default.** No default startup banners, no opinionated logging to stdout, no default styling. Provide hooks (`onReady`, `cli.configure`, etc.); let the application print its own branding. Structured diagnostics via `logs-sdk` are fine — ad-hoc `console.log`s baked into adapters are not.
- **File watching is the app's job, not devframe's.** Don't add a generic watcher primitive. Authors wire chokidar / fs.watch / watchman themselves and signal change via `ctx.rpc.sharedState.set(...)` or event-type RPCs. devframe stays out of the filesystem-observation business.
- **Mount path depends on adapter context.** Given `id: 'foo'`, the default mount path is `/.foo/` for *hosted* adapters (`vite`, `kit`, `embedded`) and `/` for *standalone* adapters (`cli`, `spa`, `build`). Authors override via `DevtoolDefinition.basePath`. Don't hardcode `DEVTOOLS_MOUNT_PATH` in adapter code paths that may run standalone.
- **SPAs own their basePath at runtime.** Build SPAs with relative asset paths (`vite.base: './'`); discover the effective base in the browser from the executing script's location / `document.baseURI`. `createBuild` / `createSpa` copy SPA output verbatim — no HTML rewriting, no build-time `--base` injection. The client (`connectDevtool`) resolves `.connection.json` relative to the runtime base automatically.
- **Mount path depends on adapter context.** Given `id: 'foo'`, the default mount path is `/__foo/` for *hosted* adapters (`vite`, `kit`, `embedded`) and `/` for *standalone* adapters (`cli`, `spa`, `build`). Authors override via `DevtoolDefinition.basePath`. Don't hardcode `DEVTOOLS_MOUNT_PATH` in adapter code paths that may run standalone.
- **SPAs own their basePath at runtime.** Build SPAs with relative asset paths (`vite.base: './'`); discover the effective base in the browser from the executing script's location / `document.baseURI`. `createBuild` / `createSpa` copy SPA output verbatim — no HTML rewriting, no build-time `--base` injection. The client (`connectDevtool`) resolves `__connection.json` relative to the runtime base automatically.
- **CLI flags compose from both sides.** The `cac` instance backing `createCli` is exposed both to the `DevtoolDefinition` (`cli.configure(cli)`) — for capabilities contributed by the tool itself — and to the `createCli` caller — for flags added at the final assembly stage. Parsed flag values are forwarded to `setup(ctx, { flags })`. Never hardcode domain-specific flags into `createCli`.

## Structured Diagnostics (Error Codes)
Expand Down
14 changes: 7 additions & 7 deletions devframe/docs/guide/adapters.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ my-devtool build --out-dir dist-static --base /devtools/
my-devtool mcp # stdio MCP server (experimental)
```

> Standalone CLI serves the SPA at `/` by default — no `/.devtools/` prefix. That prefix is reserved for *hosted* adapters where devframe mounts alongside an existing app. See [Mount paths](#mount-paths) below.
> Standalone CLI serves the SPA at `/` by default — no `/__devtools/` prefix. That prefix is reserved for *hosted* adapters where devframe mounts alongside an existing app. See [Mount paths](#mount-paths) below.

### Options

Expand Down Expand Up @@ -180,7 +180,7 @@ The basePath where a devtool's SPA is mounted depends on the adapter it's runnin
| Adapter kind | Default basePath | Reason |
|--------------|------------------|--------|
| `cli`, `spa`, `build` (standalone) | `/` | The devtool is the only thing on the origin. |
| `vite`, `kit`, `embedded` (hosted) | `/.<id>/` | The devtool shares the origin with a host app and must namespace itself. |
| `vite`, `kit`, `embedded` (hosted) | `/__<id>/` | The devtool shares the origin with a host app and must namespace itself. |

Override either side explicitly with `DevtoolDefinition.basePath`:

Expand All @@ -196,7 +196,7 @@ SPA authors should **build with relative asset paths** (`vite.base: './'`) rathe

## Vite

A thin Vite plugin that mounts a devtool's SPA into an existing Vite dev server as a *hosted* adapter — the mount path defaults to `/.<id>/` to avoid colliding with the app. It **does not** start an RPC WebSocket server — use `kit` or `cli` when you need RPC.
A thin Vite plugin that mounts a devtool's SPA into an existing Vite dev server as a *hosted* adapter — the mount path defaults to `/__<id>/` to avoid colliding with the app. It **does not** start an RPC WebSocket server — use `kit` or `cli` when you need RPC.

```ts
import { createVitePlugin } from 'devframe/adapters/vite'
Expand All @@ -210,7 +210,7 @@ export default defineConfig({

| Option | Default | Description |
|--------|---------|-------------|
| `base` | `def.basePath ?? '/.<id>/'` | Mount path inside the Vite dev server. |
| `base` | `def.basePath ?? '/__<id>/'` | Mount path inside the Vite dev server. |

Use this adapter when a devtool's UI is purely static (no server calls) and you want to surface it during Vite `serve` without shipping a separate dev server. Set `DevtoolDefinition.basePath` on the definition if you want a custom path that stays consistent across adapters.

Expand All @@ -221,7 +221,7 @@ Produces a self-contained static deploy of a devtool:
1. Copies the author's SPA dist (`cli.distDir` or `options.distDir`) into `<outDir>`.
2. Runs `setup(ctx)` with `mode: 'build'`.
3. Collects RPC dumps for every `'static'` function and any `'query'` function with `dump.inputs` / `snapshot: true`.
4. Writes `<outDir>/.connection.json` (`{ backend: 'static' }`) and sharded dump files under `<outDir>/.rpc-dump/` — both at the SPA root so the deployed client discovers them via relative paths from `document.baseURI`.
4. Writes `<outDir>/__connection.json` (`{ backend: 'static' }`) and sharded dump files under `<outDir>/__rpc-dump/` — both at the SPA root so the deployed client discovers them via relative paths from `document.baseURI`.
5. When `def.spa` is set, also writes `<outDir>/spa-loader.json` describing how the SPA hydrates its data.

```ts
Expand All @@ -240,7 +240,7 @@ await createBuild(devtool, {
| `base` | `/` | Absolute URL base the output is served from. |
| `distDir` | `def.cli?.distDir` | Override the SPA dist directory. |

The resulting directory can be hosted by any static web server (`serve`, nginx, GitHub Pages, …). The client auto-detects `static` mode via `./.connection.json` resolved against `document.baseURI` and runs in read-only form.
The resulting directory can be hosted by any static web server (`serve`, nginx, GitHub Pages, …). The client auto-detects `static` mode via `./__connection.json` resolved against `document.baseURI` and runs in read-only form.

> [!TIP]
> `createBuild` copies the SPA verbatim. To deploy under a custom URL base, build your SPA with relative asset paths (`vite.base: './'`) — the client discovers the effective base at runtime. No HTML rewriting is performed at build time.
Expand Down Expand Up @@ -276,7 +276,7 @@ The returned object has the shape `{ name, devtools: { setup, capabilities } }`.

## Embedded

Register a devtool into an already-running context at runtime. Mirrors the internal plugin-scan that Kit runs at startup, but exposes it for callers that need dynamic, post-startup registration. The host decides the mount path; `embedded` is treated as a hosted adapter and inherits the `/.<id>/` default when one is needed.
Register a devtool into an already-running context at runtime. Mirrors the internal plugin-scan that Kit runs at startup, but exposes it for callers that need dynamic, post-startup registration. The host decides the mount path; `embedded` is treated as a hosted adapter and inherits the `/__<id>/` default when one is needed.

```ts
import { createEmbedded } from 'devframe/adapters/embedded'
Expand Down
14 changes: 7 additions & 7 deletions devframe/docs/guide/client.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,11 @@ const rpc = await connectDevtool()
const modules = await rpc.call('my-devtool:get-modules', { limit: 10 })
```

`connectDevtool` auto-detects the backend via `.devtools/.connection.json` and falls back through a sequence of base URLs. No arguments are needed when the client is hosted from the default mount path.
`connectDevtool` auto-detects the backend via `__devtools/__connection.json` and falls back through a sequence of base URLs. No arguments are needed when the client is hosted from the default mount path.

### Runtime basePath discovery

SPAs built for devframe are designed to be **base-agnostic**: the same artifact can be served at `/`, at `/.<id>/`, or at any custom subpath, without rebuilding. `connectDevtool` resolves `.connection.json` relative to the page at runtime by reading `document.baseURI` and the executing script's URL.
SPAs built for devframe are designed to be **base-agnostic**: the same artifact can be served at `/`, at `/__<id>/`, or at any custom subpath, without rebuilding. `connectDevtool` resolves `__connection.json` relative to the page at runtime by reading `document.baseURI` and the executing script's URL.

The practical consequence for SPA authors:

Expand All @@ -46,16 +46,16 @@ await connectDevtool({

| Option | Description |
|--------|-------------|
| `baseURL` | Mount path to probe for `.connection.json`. Accepts an array for fallback. Default: `'./'` — resolved relative to `document.baseURI` so the SPA finds its meta wherever it was deployed. Pass an explicit absolute path (e.g. `'/.devtools/'`) when calling from outside the SPA — for instance, an embedded webcomponent injected into a host app. |
| `baseURL` | Mount path to probe for `__connection.json`. Accepts an array for fallback. Default: `'./'` — resolved relative to `document.baseURI` so the SPA finds its meta wherever it was deployed. Pass an explicit absolute path (e.g. `'/__devtools/'`) when calling from outside the SPA — for instance, an embedded webcomponent injected into a host app. |
| `authToken` | Override the auth token. Defaults to a locally-persisted human-readable id. |
| `cacheOptions` | `true` to enable caching with defaults, or an options object. |
| `wsOptions` | Forwarded to the WebSocket transport (reconnect, heartbeat, etc.). |
| `rpcOptions` | Forwarded to `birpc`. |
| `connectionMeta` | Skip the `.connection.json` fetch with a pre-known descriptor. |
| `connectionMeta` | Skip the `__connection.json` fetch with a pre-known descriptor. |

## Modes

The client runs in one of two modes depending on what the server advertises in `.devtools/.connection.json`:
The client runs in one of two modes depending on what the server advertises in `__devtools/__connection.json`:

| Backend | When | Capabilities |
|---------|------|--------------|
Expand Down Expand Up @@ -156,9 +156,9 @@ const rpc = await connectDevtool({ cacheOptions: true })

With caching on, `query` / `static` function responses are memoized per argument hash. Server-side broadcasts like `rpc:cache:invalidate` clear entries automatically — plugins that mutate state should broadcast that message after the change.

## Discovery (`.connection.json`)
## Discovery (`__connection.json`)

DevFrame writes a small JSON descriptor at `<base>/.connection.json` so the client knows where to connect:
DevFrame writes a small JSON descriptor at `<base>/__connection.json` so the client knows where to connect:

```json
{
Expand Down
4 changes: 2 additions & 2 deletions devframe/docs/guide/devtool-definition.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export default defineDevtool({
title: 'My Devtool',
icon: 'ph:gauge-duotone',
type: 'iframe',
url: '/.devtools/',
url: '/__devtools/',
})
},
})
Expand All @@ -35,7 +35,7 @@ export default defineDevtool({
| `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`), `/.<id>/` for hosted (`vite` / `kit` / `embedded`). |
| `basePath` | `string` | Optional mount path override. Defaults depend on the adapter: `/` for standalone (`cli` / `spa` / `build`), `/__<id>/` for hosted (`vite` / `kit` / `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<void>` | **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<void>` | Browser-only entry used by the SPA adapter. |
Expand Down
6 changes: 3 additions & 3 deletions devframe/docs/guide/dock-system.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,14 @@ export default defineDevtool({
id: 'my-devtool',
name: 'My Devtool',
setup(ctx) {
ctx.views.hostStatic('/.my-devtool/', clientDist)
ctx.views.hostStatic('/__my-devtool/', clientDist)

ctx.docks.register({
id: 'my-devtool:main',
title: 'My Devtool',
icon: 'ph:gauge-duotone',
type: 'iframe',
url: '/.my-devtool/',
url: '/__my-devtool/',
})
},
})
Expand Down Expand Up @@ -225,7 +225,7 @@ The handle only supports `update`. Docks are not individually unregisterable tod
`ctx.views` is a thin helper used for hosting static assets. You'll typically only call `hostStatic`:

```ts
ctx.views.hostStatic('/.my-devtool/', clientDist)
ctx.views.hostStatic('/__my-devtool/', clientDist)
```

Internal state (`buildStaticDirs`) is used by the build adapter — treat it as an implementation detail.
6 changes: 3 additions & 3 deletions devframe/docs/guide/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ DevFrame keeps its surface small and pushes UX decisions to the application cons

- **Headless.** No default startup banners, logging, or styling. Hook into `onReady`, `cli.configure`, and friends to print your own output.
- **App-owned file watching.** Wire your own watcher (chokidar, fs.watch, …) and signal change via `ctx.rpc.sharedState.set(...)` or event-type RPCs. DevFrame does not ship a watcher primitive.
- **Context-aware mount paths.** Standalone adapters (`cli`, `spa`, `build`) serve at `/` by default; hosted adapters (`vite`, `kit`, `embedded`) serve at `/.<id>/`. Override via `DevtoolDefinition.basePath`.
- **Context-aware mount paths.** Standalone adapters (`cli`, `spa`, `build`) serve at `/` by default; hosted adapters (`vite`, `kit`, `embedded`) serve at `/__<id>/`. Override via `DevtoolDefinition.basePath`.
- **SPAs own their base at runtime.** Build with relative asset paths (`vite.base: './'`); `connectDevtool` discovers the effective base from the executing script's location. No HTML rewrites at build time.
- **CLI flags compose.** The `cac` instance is exposed to both the devtool (`cli.configure`) and the caller of `createCli`, so capability flags and app flags merge cleanly.

Expand Down Expand Up @@ -97,7 +97,7 @@ const devtool = defineDevtool({
title: 'My Devtool',
icon: 'ph:gauge-duotone',
type: 'iframe',
url: '/.devtools/',
url: '/__devtools/',
})
},
})
Expand All @@ -113,7 +113,7 @@ node ./my-devtool.js build # self-contained static deploy in dist-static/
node ./my-devtool.js mcp # stdio MCP server (experimental)
```

The CLI adapter serves the SPA at `/` by default. When the same devtool is embedded inside a host (`vite`, `kit`, `embedded`), the default becomes `/.my-devtool/`. Override either side via `defineDevtool({ basePath })`.
The CLI adapter serves the SPA at `/` by default. When the same devtool is embedded inside a host (`vite`, `kit`, `embedded`), the default becomes `/__my-devtool/`. Override either side via `defineDevtool({ basePath })`.

## Adapters at a Glance

Expand Down
2 changes: 1 addition & 1 deletion devframe/docs/guide/nuxt.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ At build time the module:
return { provide: { rpc } }
```

At runtime the built SPA fetches `./.connection.json` (resolved against `document.baseURI`) and branches on the `backend` field — `websocket` in dev, `static` from a `createBuild` snapshot.
At runtime the built SPA fetches `./__connection.json` (resolved against `document.baseURI`) and branches on the `backend` field — `websocket` in dev, `static` from a `createBuild` snapshot.

## Relationship to `createCli`

Expand Down
4 changes: 2 additions & 2 deletions devframe/examples/devframe-files-inspector/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,15 @@ A simplified [node-modules-inspector](https://github.com/antfu/node-modules-insp

The Preact client showcases two patterns relevant to devframe authors:

1. **Runtime base discovery.** The client is built with `vite.base: './'` and reads `document.baseURI` at runtime to resolve its mount path. The same `dist/client` works under any base path (`/.devframe-files-inspector/`, `/`, `/custom/`, …) without rebuilding.
1. **Runtime base discovery.** The client is built with `vite.base: './'` and reads `document.baseURI` at runtime to resolve its mount path. The same `dist/client` works under any base path (`/__devframe-files-inspector/`, `/`, `/custom/`, …) without rebuilding.
2. **Two RPC types.** `:list-files` is a `query` with `dump.inputs: [[]]` (live in dev, baked in static). `:get-cwd` is a `static` RPC.

## Run

```sh
pnpm install
pnpm -C examples/devframe-files-inspector run build # build the Preact client
pnpm -C examples/devframe-files-inspector run dev # http://127.0.0.1:9876/.devframe-files-inspector/
pnpm -C examples/devframe-files-inspector run dev # http://127.0.0.1:9876/__devframe-files-inspector/
pnpm -C examples/devframe-files-inspector run cli:build # static deploy in ./dist/static
serve examples/devframe-files-inspector/dist/static # any static host works (relative paths)
pnpm -C examples/devframe-files-inspector run test # E2E tests
Expand Down
2 changes: 1 addition & 1 deletion devframe/examples/devframe-files-inspector/src/devtool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { defineRpcFunction } from 'devframe'
import { defineDevtool } from 'devframe/types'
import { glob } from 'tinyglobby'

const BASE_PATH = '/.devframe-files-inspector/'
const BASE_PATH = '/__devframe-files-inspector/'
const distDir = fileURLToPath(new URL('../dist/client', import.meta.url))

export default defineDevtool({
Expand Down
Loading
Loading