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`).
> Scoped RPC registration for namespace "`{namespace}`" received an already-namespaced function name "`{name}`".
10
+
11
+
## Cause
12
+
13
+
A [scoped context](../guide/scoped-context) auto-namespaces the ids you pass it. `ctx.scope('my-plugin').rpc.register(...)` therefore expects a **bare** function name and stores it as `my-plugin:<name>`. Passing a name that already contains a `:` separator would produce a double-prefixed id, so registration throws instead.
- Pass a bare name (no `:` separator) to the scoped `register`.
30
+
- To register a fully-qualified name on purpose, use the unscoped host: `ctx.base.rpc.register(...)` (server) or `client.scope(...).base.client.register(...)` (browser).
31
+
32
+
## Source
33
+
34
+
-[`packages/devframe/src/node/scope.ts`](https://github.com/devframes/devframe/blob/main/packages/devframe/src/node/scope.ts) — `register()` / `update()` throw this when the supplied definition name is already namespaced.
> 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'`.
TypeScript types flow through from the server's `defineRpcFunction` definitions, so argument and return shapes are known at the call site.
123
+
The unscoped `rpc.call('my-devframe:get-modules', ...)` works too. Either way, TypeScript types flow through from the server's `defineRpcFunction` definitions, so argument and return shapes are known at the call site.
122
124
123
125
## Registering client functions
124
126
125
-
The client can register functions that the server calls via `ctx.rpc.broadcast`:
127
+
The client can register functions that the server calls via `rpc.broadcast`:
Client-side mutations round-trip through the server before reappearing locally. See [Shared State](./shared-state) for the full API.
160
162
163
+
## Settings
164
+
165
+
A scoped client also exposes a top-level persisted `settings` store, synced from the server. Read and write per-user (`global`) or per-workspace (`project`) values:
Host adapters (such as the [`vite` adapter](/adapters/vite) for Vite DevTools) derive their mount entry from `id`, `name`, `icon`, and `basePath` automatically.
38
+
`ctx.scope(id)` is the preferred way to consume the context — see [Scoped Context](./scoped-context). Host adapters (such as the [`vite` adapter](/adapters/vite) for Vite DevTools) derive their mount entry from `id`, `name`, `icon`, and `basePath` automatically.
32
39
33
40
## Definition fields
34
41
35
42
| Field | Type | Description |
36
43
|-------|------|-------------|
37
44
|`id`|`string`|**Required.** Unique, namespaced identifier (kebab-case). Used as a prefix for RPC names, dock IDs, and MCP tool names. |
38
45
|`name`|`string`|**Required.** Display name shown in the dock and agent manifests. |
46
+
|`version`|`string`|**Required.** Semver of the tool, surfaced in hub UIs and diagnostics. |
47
+
|`packageName`|`string`|**Required.** npm package name the devframe ships in (e.g. `@scope/my-tool`). |
48
+
|`homepage`|`string`|**Required.** Project homepage or documentation URL. |
49
+
|`description`|`string`|**Required.** One-line summary of what the tool does. |
39
50
|`icon`|`string \| { light, dark }`| Optional Iconify name or URL; supports light/dark pairs. |
40
-
|`version`|`string`| Optional version string surfaced to clients. |
41
51
|`basePath`|`string`| Optional mount path override. Defaults depend on the adapter: `/` for standalone (`cli` / `spa` / `build`), `/.<id>/` for hosted (`vite` / `embedded`). |
52
+
|`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
53
|`capabilities`|`{ dev?, build?, spa? }`| Per-runtime feature flags. A `boolean` applies to the runtime as a whole; an object enables individual features. |
43
54
|`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
55
|`setupBrowser`|`(ctx) => void \| Promise<void>`| Browser-only entry used by the SPA adapter. |
45
56
|`cli`|`DevframeCliOptions`| Defaults for the CLI adapter. See [CLI options](#cli-options) below. |
46
57
|`spa`|`DevframeSpaOptions`| Defaults for the SPA adapter (`base`, `loader`). |
47
58
59
+
### Sourcing metadata from `package.json`
60
+
61
+
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.
78
+
48
79
### Runtime flags
49
80
50
81
The `ctx.mode` field is either `'dev'` or `'build'`. Use it to gate work that should only run in one runtime:
`ctx.scope(id)` returns a namespace-scoped view that auto-prefixes every RPC id, shared-state key, and streaming channel and adds a persisted top-level `settings` store. It's the recommended entry point from a single tool's setup code — see [Scoped Context](./scoped-context).
121
+
87
122
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.
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
+
46
65
## Grouping dock entries
47
66
48
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.
0 commit comments