Skip to content

Commit dbb1303

Browse files
committed
Merge remote-tracking branch 'upstream/main' into plugin-terminal-2
2 parents 4ac8146 + 885ec30 commit dbb1303

73 files changed

Lines changed: 1796 additions & 292 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

AGENTS.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,15 @@ pnpm install # requires pnpm@11.x
2222
pnpm build # tsdown
2323
pnpm dev # tsdown --watch
2424
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)
2626
pnpm lint --fix # ESLint via @antfu/eslint-config
2727
pnpm start # tsx src/index.ts
2828
```
2929

3030
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`.
3131

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+
3234
## Conventions
3335

3436
- RPC functions must use `defineRpcFunction`; always namespace IDs (`my-plugin:fn-name`).

docs/errors/DF8101.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ outline: deep
66

77
## Message
88

9-
> Cannot change the id of a dock. Use register() to add new docks.
9+
> Cannot change the id of dock "`{id}`" to "`{attempted}`". Dock ids are immutable once registered
1010
1111
## Cause
1212

docs/errors/DF8102.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ outline: deep
66

77
## Message
88

9-
> Dock with id "`{id}`" is not registered. Use register() to add new docks.
9+
> Dock with id "`{id}`" is not registered and cannot be updated
1010
1111
## Cause
1212

docs/errors/DF8103.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
---
2+
outline: deep
3+
---
4+
5+
# DF8103: Dock Entry Cannot Group Itself
6+
7+
## Message
8+
9+
> 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`.

docs/errors/DF8104.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
---
2+
outline: deep
3+
---
4+
5+
# DF8104: Nested Dock Groups Unsupported
6+
7+
## Message
8+
9+
> 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.

docs/errors/DF8105.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
---
2+
outline: deep
3+
---
4+
5+
# DF8105: Devframe Already Mounted
6+
7+
## Message
8+
9+
> 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'`.

docs/guide/devframe-definition.md

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@ import * as v from 'valibot'
1515
export default defineDevframe({
1616
id: 'my-devframe',
1717
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.',
1822
icon: 'ph:gauge-duotone',
1923
setup(ctx) {
2024
// 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
3640
|-------|------|-------------|
3741
| `id` | `string` | **Required.** Unique, namespaced identifier (kebab-case). Used as a prefix for RPC names, dock IDs, and MCP tool names. |
3842
| `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. |
3947
| `icon` | `string \| { light, dark }` | Optional Iconify name or URL; supports light/dark pairs. |
40-
| `version` | `string` | Optional version string surfaced to clients. |
4148
| `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. |
4250
| `capabilities` | `{ dev?, build?, spa? }` | Per-runtime feature flags. A `boolean` applies to the runtime as a whole; an object enables individual features. |
4351
| `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`. |
4452
| `setupBrowser` | `(ctx) => void \| Promise<void>` | Browser-only entry used by the SPA adapter. |
4553
| `cli` | `DevframeCliOptions` | Defaults for the CLI adapter. See [CLI options](#cli-options) below. |
4654
| `spa` | `DevframeSpaOptions` | Defaults for the SPA adapter (`base`, `loader`). |
4755

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.
59+
60+
```ts
61+
import pkg from '../package.json' with { type: 'json' }
62+
63+
export default defineDevframe({
64+
id: 'my-devframe',
65+
name: 'My Devframe', // display label
66+
version: pkg.version,
67+
packageName: pkg.name,
68+
homepage: pkg.homepage,
69+
description: pkg.description,
70+
setup(ctx) { /**/ },
71+
})
72+
```
73+
74+
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+
4876
### Runtime flags
4977

5078
The `ctx.mode` field is either `'dev'` or `'build'`. Use it to gate work that should only run in one runtime:

docs/guide/hub.md

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ A hub-aware node context (`DevframeHubContext`) extends `DevframeNodeContext` wi
1515

1616
| Subsystem | Surface | Purpose |
1717
|---|---|---|
18-
| `ctx.docks` | `register / update / values` | Multi-tool dock entries (iframes, launchers, json-render, custom-render). |
18+
| `ctx.docks` | `register / update / values` | Multi-tool dock entries (iframes, launchers, json-render, custom-render) and groups that collapse them under one button. |
1919
| `ctx.terminals` | `register / startChildProcess` | Aggregate terminal sessions, stream output over a well-known channel. |
2020
| `ctx.messages` | `add / update / remove / clear` | Server-side toast/notification queue (FIFO, capped at 1000). |
2121
| `ctx.commands` | `register / execute / list` | Hierarchical command palette with keybindings and `when` clauses. |
@@ -43,6 +43,53 @@ await mountDevframe(ctx, myDevframe)
4343

4444
Framework kits typically wrap this in a plugin shell. `@vitejs/devtools-kit`'s `createPluginFromDevframe` returns a Vite `Plugin` whose `devtools.setup` calls into `mountDevframe`.
4545

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+
4693
## The protocol — what the UI sees
4794

4895
A hub-aware UI doesn't import any hub classes; it reads three shared-state keys and one RPC method:

docs/guide/index.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,10 @@ import { createCli } from 'devframe/adapters/cli'
5555
const devframe = defineDevframe({
5656
id: 'my-devframe',
5757
name: 'My Devframe',
58+
version: '1.0.0',
59+
packageName: 'my-devframe',
60+
homepage: 'https://github.com/me/my-devframe',
61+
description: 'A one-line summary of what the tool does.',
5862
icon: 'ph:gauge-duotone',
5963
cli: {
6064
distDir: 'client/dist',

examples/files-inspector/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
{
22
"name": "files-inspector-example",
33
"type": "module",
4-
"version": "0.5.2",
4+
"version": "0.5.4",
55
"private": true,
66
"description": "End-to-end devframe demo — lists files in cwd via RPC, exercises CLI dev/build/spa surfaces.",
7+
"homepage": "https://github.com/devframes/devframe/tree/main/examples/files-inspector",
78
"main": "src/devframe.ts",
89
"bin": {
910
"files-inspector": "./bin.mjs"

0 commit comments

Comments
 (0)