You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
pnpm test# pnpm build && vitest (api snapshot guards against stale dist)
25
-
pnpm typecheck # tsc --noEmit
25
+
pnpm typecheck #turbo run typecheck (per-package tsc --noEmit)
26
26
pnpm lint --fix # ESLint via @antfu/eslint-config
27
27
pnpm start # tsx src/index.ts
28
28
```
29
29
30
30
The `pnpm test` script intentionally runs `build` first so `tsnapi` snapshots compare against fresh `dist/`. `tsdown-stale-guard` enforces this in `test/api-snapshot.test.ts`.
31
31
32
+
`pnpm typecheck` fans out through Turbo: every workspace package owns a `"typecheck": "tsc --noEmit"` script and its own `tsconfig.json` (extending `tsconfig.base.json` with an explicit `include`). Cross-package imports resolve to source through the `paths` aliases in `tsconfig.base.json`, so no prior build is needed. Any package added under `packages/*` or `plugins/*` is typechecked automatically once it ships that `typecheck` script — add one to every new package so it can't silently skip type errors.
33
+
32
34
## Conventions
33
35
34
36
- RPC functions must use `defineRpcFunction`; always namespace IDs (`my-plugin:fn-name`).
> Dock entry "`{id}`" cannot set groupId to its own id
10
+
11
+
## Cause
12
+
13
+
A dock entry registered with `groupId` pointing at its own `id`. `groupId` is a pointer to a *different* group entry the entry belongs to, so a self-reference would describe an entry that collapses under itself.
14
+
15
+
## Fix
16
+
17
+
- Point `groupId` at the `id` of a `type: 'group'` entry, such as `groupId: 'nuxt'`.
18
+
- Omit `groupId` entirely to keep the entry as a normal top-level dock entry.
19
+
20
+
## Source
21
+
22
+
-[`packages/hub/src/node/host-docks.ts`](https://github.com/devframes/devframe/blob/main/packages/hub/src/node/host-docks.ts) — `DevframeDocksHost.register()` and `update()` throw this when `view.groupId === view.id`.
> Dock group "`{id}`" cannot itself belong to a group (nested groups are unsupported)
10
+
11
+
## Cause
12
+
13
+
A `type: 'group'` entry was registered with `groupId` set. Dock grouping is one level deep: a group collects member entries, but a group cannot itself be a member of another group.
14
+
15
+
## Fix
16
+
17
+
- Remove `groupId` from the group entry so it stays a top-level dock-bar button.
18
+
- Keep members one level under their group; place each member's `groupId` on the leaf entry, not on another group.
19
+
20
+
## Source
21
+
22
+
-[`packages/hub/src/node/host-docks.ts`](https://github.com/devframes/devframe/blob/main/packages/hub/src/node/host-docks.ts) — `DevframeDocksHost.register()` and `update()` throw this when `view.type === 'group'` and `view.groupId` is set.
> Devframe "`{name}`" (id "`{id}`") is already mounted on this hub
10
+
11
+
## Cause
12
+
13
+
`mountDevframe(ctx, def)` was called with a devframe whose `id` already belongs to another devframe mounted on the same hub. Devframes are deduplicated by `id`, and the definition's `duplicationStrategy` is `'warn'` (the default) or `'throw'`.
14
+
15
+
## Fix
16
+
17
+
Set `duplicationStrategy` on the definition to choose how duplicates are handled:
18
+
19
+
-`'warn'` (default) — keep the first registration, drop the later one, and emit this warning.
20
+
-`'silent'` — drop the later one without warning.
21
+
-`'throw'` — surface duplicates as a thrown error.
22
+
-`'duplicate'` — let every instance coexist under a disambiguated dock id (`my-tool`, `my-tool-2`, …).
23
+
24
+
Otherwise, remove the redundant `mountDevframe` call so each devframe is mounted once.
25
+
26
+
## Source
27
+
28
+
-[`packages/hub/src/node/mount-devframe.ts`](https://github.com/devframes/devframe/blob/main/packages/hub/src/node/mount-devframe.ts) — `mountDevframe()` emits this when a devframe sharing an already-mounted `id` is mounted and the strategy is not `'duplicate'`.
Copy file name to clipboardExpand all lines: docs/guide/devframe-definition.md
+29-1Lines changed: 29 additions & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -15,6 +15,10 @@ import * as v from 'valibot'
15
15
exportdefaultdefineDevframe({
16
16
id: 'my-devframe',
17
17
name: 'My Devframe',
18
+
version: '1.0.0',
19
+
packageName: 'my-devframe',
20
+
homepage: 'https://github.com/me/my-devframe',
21
+
description: 'A one-line summary of what the tool does.',
18
22
icon: 'ph:gauge-duotone',
19
23
setup(ctx) {
20
24
// Register your RPC functions, shared state, etc. here.
@@ -36,15 +40,39 @@ Host adapters (such as the [`vite` adapter](/adapters/vite) for Vite DevTools) d
36
40
|-------|------|-------------|
37
41
|`id`|`string`|**Required.** Unique, namespaced identifier (kebab-case). Used as a prefix for RPC names, dock IDs, and MCP tool names. |
38
42
|`name`|`string`|**Required.** Display name shown in the dock and agent manifests. |
43
+
|`version`|`string`|**Required.** Semver of the tool, surfaced in hub UIs and diagnostics. |
44
+
|`packageName`|`string`|**Required.** npm package name the devframe ships in (e.g. `@scope/my-tool`). |
45
+
|`homepage`|`string`|**Required.** Project homepage or documentation URL. |
46
+
|`description`|`string`|**Required.** One-line summary of what the tool does. |
39
47
|`icon`|`string \| { light, dark }`| Optional Iconify name or URL; supports light/dark pairs. |
40
-
|`version`|`string`| Optional version string surfaced to clients. |
41
48
|`basePath`|`string`| Optional mount path override. Defaults depend on the adapter: `/` for standalone (`cli` / `spa` / `build`), `/.<id>/` for hosted (`vite` / `embedded`). |
49
+
|`duplicationStrategy`|`'warn' \| 'silent' \| 'throw' \| 'duplicate'`| How a hub reacts when another devframe sharing this `id` is mounted onto the same hub. Defaults to `'warn'`. See [Hub](./hub). Hub adapters consult it; standalone adapters ignore it. |
42
50
|`capabilities`|`{ dev?, build?, spa? }`| Per-runtime feature flags. A `boolean` applies to the runtime as a whole; an object enables individual features. |
43
51
|`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`. |
44
52
|`setupBrowser`|`(ctx) => void \| Promise<void>`| Browser-only entry used by the SPA adapter. |
45
53
|`cli`|`DevframeCliOptions`| Defaults for the CLI adapter. See [CLI options](#cli-options) below. |
46
54
|`spa`|`DevframeSpaOptions`| Defaults for the SPA adapter (`base`, `loader`). |
47
55
56
+
### Sourcing metadata from `package.json`
57
+
58
+
Keep `version`, `packageName`, `homepage`, and `description` in sync with the package you publish by importing them straight from its `package.json`. Note that the package's `name` field maps to `packageName` — the devframe `name` is a separate display label.
The default import with a `with { type: 'json' }` attribute resolves under both bundlers and Node's native TypeScript execution. Bundlers also support the destructured `import { version } from '../package.json'` form when the devframe is always bundled before it runs.
75
+
48
76
### Runtime flags
49
77
50
78
The `ctx.mode` field is either `'dev'` or `'build'`. Use it to gate work that should only run in one runtime:
|`ctx.docks`|`register / update / values`| Multi-tool dock entries (iframes, launchers, json-render, custom-render) and groups that collapse them under one button. |
19
19
|`ctx.terminals`|`register / startChildProcess`| Aggregate terminal sessions, stream output over a well-known channel. |
Framework kits typically wrap this in a plugin shell. `@vitejs/devtools-kit`'s `createPluginFromDevframe` returns a Vite `Plugin` whose `devtools.setup` calls into `mountDevframe`.
45
45
46
+
### Duplicate devframes
47
+
48
+
When a devframe sharing an already-mounted `id` is mounted onto the same hub, its `duplicationStrategy` decides what happens. By default the first registration wins:
49
+
50
+
| Strategy | Behavior |
51
+
|---|---|
52
+
|`'warn'` (default) | Keep the first registration, drop the later one, and emit `DF8105`. |
53
+
|`'silent'`| Drop the later one without warning. |
54
+
|`'throw'`| Throw `DF8105`. |
55
+
|`'duplicate'`| Let every instance coexist under a disambiguated dock id (`my-tool`, `my-tool-2`, …). |
56
+
57
+
```ts
58
+
defineDevframe({
59
+
id: 'my-tool',
60
+
// …
61
+
duplicationStrategy: 'duplicate',
62
+
})
63
+
```
64
+
65
+
## Grouping dock entries
66
+
67
+
When a hub combines many integrations, related dock entries can collapse under a single dock-bar button. A `type: 'group'` entry is that button; any entry pointing its `groupId` at the group's `id` becomes a member.
68
+
69
+
```ts
70
+
ctx.docks.register({
71
+
type: 'group',
72
+
id: 'nuxt',
73
+
title: 'Nuxt',
74
+
icon: 'logos:nuxt-icon',
75
+
category: 'framework',
76
+
defaultChildId: 'nuxt:overview', // optional; popover-only when omitted
77
+
})
78
+
79
+
ctx.docks.register({
80
+
type: 'iframe',
81
+
id: 'nuxt:overview',
82
+
title: 'Overview',
83
+
icon: 'ph:gauge-duotone',
84
+
url: '/__nuxt-overview/',
85
+
groupId: 'nuxt', // joins the group above
86
+
})
87
+
```
88
+
89
+
`groupId` lives on every entry kind, so iframes, launchers, json-render panels, and custom-render views all join groups the same way. The group and its members stay independent top-level entries in `devframe:docks`; a downstream UI derives the visual collapse by matching each member's `groupId` to the group's `id` and renders members in a popover or sub-navigation. `defaultChildId` names the member opened when the group button is activated.
90
+
91
+
Grouping is one level deep: members join a group, and a group is always a top-level button. A member whose group is never registered renders as a normal top-level entry, so registration order is free.
92
+
46
93
## The protocol — what the UI sees
47
94
48
95
A hub-aware UI doesn't import any hub classes; it reads three shared-state keys and one RPC method:
0 commit comments