Skip to content

Commit df3cefd

Browse files
authored
refactor(devframe)!: add cac adapter (createCac) as an optional peer dependency; deprecate the cli alias (#96)
1 parent d984ae6 commit df3cefd

54 files changed

Lines changed: 392 additions & 286 deletions

File tree

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: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ These reinforce devframe's positioning as "the container for one devtool integra
6262
- **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 `nostics` are fine — ad-hoc `console.log`s baked into adapters are not.
6363
- **Mount path depends on adapter context.** Given `id: 'foo'`, the default mount path is `/__foo/` for *hosted* adapters (`vite`, `embedded`) and `/` for *standalone* adapters (`cli`, `spa`, `build`). Authors override via `DevframeDefinition.basePath`. Don't hardcode mount paths in adapter code paths that may run standalone.
6464
- **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 (`connectDevframe`) resolves `.connection.json` relative to the runtime base automatically.
65-
- **CLI flags compose from both sides.** The `cac` instance backing `createCli` is exposed both to the `DevframeDefinition` (`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`.
65+
- **CLI flags compose from both sides.** The `cac` instance backing `createCac` is exposed both to the `DevframeDefinition` (`cli.configure(cli)`) — for capabilities contributed by the tool itself — and to the `createCac` caller — for flags added at the final assembly stage. Parsed flag values are forwarded to `setup(ctx, { flags })`. Never hardcode domain-specific flags into `createCac`.
6666

6767
## Structured Diagnostics (Error Codes)
6868

alias.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ export const alias = {
3232
'devframe/utils/streaming-channel': r('devframe/src/utils/streaming-channel.ts'),
3333
'devframe/utils/structured-clone': r('devframe/src/utils/structured-clone.ts'),
3434
'devframe/utils/when': r('devframe/src/utils/when.ts'),
35+
'devframe/adapters/cac': r('devframe/src/adapters/cac.ts'),
3536
'devframe/adapters/cli': r('devframe/src/adapters/cli.ts'),
3637
'devframe/adapters/dev': r('devframe/src/adapters/dev.ts'),
3738
'devframe/adapters/build': r('devframe/src/adapters/build.ts'),

docs/.vitepress/config.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ function guideItems(prefix: string) {
3838
function adaptersItems(prefix: string) {
3939
return [
4040
{ text: 'Overview', link: `${prefix}/adapters/` },
41-
{ text: 'CLI', link: `${prefix}/adapters/cli` },
41+
{ text: 'CLI (cac)', link: `${prefix}/adapters/cac` },
4242
{ text: 'Dev', link: `${prefix}/adapters/dev` },
4343
{ text: 'Build', link: `${prefix}/adapters/build` },
4444
{ text: 'Vite', link: `${prefix}/adapters/vite` },
Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,21 @@
22
outline: deep
33
---
44

5-
# CLI
5+
# CLI (cac)
66

7-
The CLI adapter wraps a `DevframeDefinition` in a `cac`-powered command-line interface. From one entry it spins up an `h3` dev server with WebSocket RPC, builds static snapshots, builds SPA bundles, or starts an MCP server.
7+
The cac adapter wraps a `DevframeDefinition` in a [`cac`](https://github.com/cacjs/cac)-powered command-line interface. From one entry it spins up an `h3` dev server with WebSocket RPC, builds static snapshots, builds SPA bundles, or starts an MCP server.
8+
9+
`cac` is an optional peer dependency, pulled in only through this adapter — install it alongside `devframe` to opt into `createCac`:
10+
11+
```sh
12+
npm install devframe cac
13+
```
14+
15+
Tools that assemble their own command-line shell from the [lower-level factories](#use-your-own-cli-framework) never import this adapter, so they run without `cac`.
816

917
```ts
1018
import { defineDevframe } from 'devframe'
11-
import { createCli } from 'devframe/adapters/cli'
19+
import { createCac } from 'devframe/adapters/cac'
1220

1321
const devframe = defineDevframe({
1422
id: 'my-devframe',
@@ -17,9 +25,11 @@ const devframe = defineDevframe({
1725
setup(ctx) { /* register docks, RPC, etc. */ },
1826
})
1927

20-
await createCli(devframe).parse()
28+
await createCac(devframe).parse()
2129
```
2230

31+
The `devframe/adapters/cli` entry (`createCli`) remains as a deprecated alias for this module — new code should import `createCac` from `devframe/adapters/cac`.
32+
2333
Running the resulting binary:
2434

2535
```sh
@@ -34,18 +44,18 @@ Standalone CLI serves the SPA at `/` by default. The `/__devframe/` prefix is fo
3444

3545
## Options
3646

37-
`createCli(def, options?)` accepts:
47+
`createCac(def, options?)` accepts:
3848

3949
| Option | Default | Description |
4050
|--------|---------|-------------|
4151
| `defaultPort` | `9999` (or `def.cli?.port`) | Port used by the dev command when `--port` isn't provided. |
4252
| `configureCli` || `(cli: CAC) => void` — final hook to add commands/flags at the assembly stage, after the definition's `cli.configure` runs. |
4353
| `onReady` || `(info: { origin, port, app }) => void \| Promise<void>` — called once the dev server is listening. Use this to print your own startup banner. |
4454

45-
`createCli` returns a `CliHandle`:
55+
`createCac` returns a `CacHandle`:
4656

4757
```ts
48-
interface CliHandle {
58+
interface CacHandle {
4959
cli: CAC // raw cac instance — mutate before calling parse()
5060
parse: (argv?: string[]) => Promise<void>
5161
}
@@ -80,14 +90,14 @@ defineDevframe({
8090
})
8191
```
8292

83-
`distDir` is the only required field; everything else has sensible defaults. The `configure` hook runs *before* the `configureCli` option passed to `createCli`, so the final tool author always has the last word on flags.
93+
`distDir` is the only required field; everything else has sensible defaults. The `configure` hook runs *before* the `configureCli` option passed to `createCac`, so the final tool author always has the last word on flags.
8494

8595
## Headless logging
8696

8797
Devframe leaves startup output to the application. Wire `onReady` to print your own banner:
8898

8999
```ts
90-
await createCli(devframe, {
100+
await createCac(devframe, {
91101
onReady({ origin }) {
92102
console.log(`ESLint Config Inspector ready at ${origin}`)
93103
},
@@ -98,13 +108,13 @@ Structured diagnostics (via `nostics`) continue to surface through their normal
98108

99109
## Use your own CLI framework
100110

101-
To integrate devframe into an existing commander / yargs program — or to expose a different command structure than `createCli`'s `dev` / `build` / `mcp` triplet — drop down to the peer factories. Same `DevframeDefinition`, different shell:
111+
To integrate devframe into an existing commander / yargs program — or to expose a different command structure than `createCac`'s `dev` / `build` / `mcp` triplet — drop down to the peer factories. Same `DevframeDefinition`, different shell:
102112

103113
| Building block | Entry | Purpose |
104114
|----------------|-------|---------|
105115
| [`createDevServer(def, opts?)`](./dev) | `devframe/adapters/dev` | h3 + WebSocket RPC + SPA mount |
106116
| [`createBuild(def, opts?)`](./build) | `devframe/adapters/build` | Static deploy |
107117
| [`createMcpServer(def, opts?)`](./mcp) | `devframe/adapters/mcp` | stdio MCP server |
108-
| `parseCliFlags(schema, raw)` | `devframe/adapters/cli` | Validate a flag bag against a `CliFlagsSchema` |
118+
| `parseCliFlags(schema, raw)` | `devframe/adapters/cac` | Validate a flag bag against a `CliFlagsSchema` |
109119

110120
See the [Standalone CLI guide](/guide/standalone-cli#use-your-own-cli-framework) for a worked commander example.

docs/adapters/dev.md

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

55
# Dev
66

7-
The `dev` adapter is the building block `createCli` uses internally — h3 + WebSocket RPC + the author's SPA mounted at the resolved base path. Reach for it directly to mount the dev server inside an existing CLI program (commander, yargs, hand-rolled CAC) or to attach custom middleware to the underlying h3 app.
7+
The `dev` adapter is the building block `createCac` uses internally — h3 + WebSocket RPC + the author's SPA mounted at the resolved base path. Reach for it directly to mount the dev server inside an existing CLI program (commander, yargs, hand-rolled CAC) or to attach custom middleware to the underlying h3 app.
88

99
```ts
1010
import { createDevServer } from 'devframe/adapters/dev'

docs/adapters/index.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,13 @@ outline: deep
66

77
An adapter takes a `DevframeDefinition` and deploys it into a specific runtime — a standalone CLI, a Vite plugin, a static snapshot, an embedded host, or an MCP server. Each adapter ships at its own entry point (`devframe/adapters/<name>`); the bundler pulls in only the ones you use.
88

9-
Every adapter factory has the shape `createXxx(devframeDef, options?)`.
9+
Every adapter factory has the shape `createXxx(devframeDef, options?)`. Some adapters draw on an optional peer dependency, installed only when you opt into that adapter: `cac` pulls in [`cac`](https://github.com/cacjs/cac), and `mcp` pulls in [`@modelcontextprotocol/sdk`](https://github.com/modelcontextprotocol/typescript-sdk).
1010

1111
## Comparison
1212

1313
| Adapter | Entry | Factory | Best for |
1414
|---------|-------|---------|----------|
15-
| [`cli`](./cli) | `devframe/adapters/cli` | `createCli(def, options?)` | Standalone tools run via `node ./my-tool.js` |
15+
| [`cac`](./cac) | `devframe/adapters/cac` | `createCac(def, options?)` | Standalone tools run via `node ./my-tool.js` |
1616
| [`dev`](./dev) | `devframe/adapters/dev` | `createDevServer(def, options?)` | Run the dev server programmatically — drive it from any CLI framework |
1717
| [`build`](./build) | `devframe/adapters/build` | `createBuild(def, options?)` | Offline reports, CI artifacts, deployable SPA snapshots |
1818
| [`vite`](./vite) | `@vitejs/devtools-kit/node` | `createPluginFromDevframe(def, options?)` | Mount the definition into Vite DevTools (or any compatible host) |

docs/guide/client.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ The client runs in one of two modes depending on the backend advertised in `__de
6060

6161
| Backend | When | Capabilities |
6262
|---------|------|--------------|
63-
| `websocket` | Dev mode (`createCli`, Kit) | Full read/write, broadcasts, shared-state mutation. Requires auth. |
63+
| `websocket` | Dev mode (`createCac`, Kit) | Full read/write, broadcasts, shared-state mutation. Requires auth. |
6464
| `static` | Build / SPA output | Read-only — all calls resolve against the baked RPC dump. |
6565

6666
The client picks a mode automatically from the backend field. Mode-specific code paths like `broadcast` are scoped to `websocket`.

docs/guide/devframe-definition.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ outline: deep
44

55
# Devframe Definition
66

7-
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.
7+
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 `createCac`, `createBuild`, `createMcpServer`, the `vite` adapter's `createPluginFromDevframe`, and so on.
88

99
## Minimal definition
1010

@@ -51,7 +51,7 @@ export default defineDevframe({
5151
| `basePath` | `string` | Optional mount path override. Defaults depend on the adapter: `/` for standalone (`cli` / `spa` / `build`), `/.<id>/` for hosted (`vite` / `embedded`). |
5252
| `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. |
5353
| `capabilities` | `{ dev?, build?, spa? }` | Per-runtime feature flags. A `boolean` applies to the runtime as a whole; an object enables individual features. |
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`. |
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 `createCac`. |
5555
| `setupBrowser` | `(ctx) => void \| Promise<void>` | Browser-only entry used by the SPA adapter. |
5656
| `cli` | `DevframeCliOptions` | Defaults for the CLI adapter. See [CLI options](#cli-options) below. |
5757
| `spa` | `DevframeSpaOptions` | Defaults for the SPA adapter (`base`, `loader`). |
@@ -185,7 +185,7 @@ defineDevframe({
185185
| `host` | `string` | Default bind host. |
186186
| `open` | `boolean \| string` | `true` opens the origin, a string opens a specific path, `false` disables. Matches the `--open` / `--no-open` flags. |
187187
| `auth` | `boolean` | Disable the WS trust flow when the tool is localhost-only and single-user. Default `true`. |
188-
| `configure` | `(cli: CAC) => void` | Contribute capability flags/commands. Runs before `createCli`'s `configureCli` option so the final tool author always has the last word. |
188+
| `configure` | `(cli: CAC) => void` | Contribute capability flags/commands. Runs before `createCac`'s `configureCli` option so the final tool author always has the last word. |
189189

190190
`setup(ctx, info)` receives `info.flags` populated from both devframe's built-in flags and any you declared via `configure` — saves duplicating flag parsing.
191191

@@ -210,12 +210,12 @@ The definition is a plain value, so wire it into multiple adapters from the same
210210
```ts
211211
import { createPluginFromDevframe } from '@vitejs/devtools-kit/node'
212212
import { createBuild } from 'devframe/adapters/build'
213-
import { createCli } from 'devframe/adapters/cli'
213+
import { createCac } from 'devframe/adapters/cac'
214214

215215
const devframe = defineDevframe({ id: 'my-devframe', name: 'My Devframe', setup() {} })
216216

217217
// 1. Standalone CLI:
218-
await createCli(devframe).parse()
218+
await createCac(devframe).parse()
219219

220220
// 2. Offline snapshot:
221221
await createBuild(devframe, { outDir: 'dist-static' })

docs/guide/index.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ Devframe keeps its surface focused on one tool, so the same definition stays por
1717
- **App-owned file watching.** Wire your own watcher (chokidar, fs.watch, …) and signal change via `ctx.rpc.sharedState.set(...)` or event-typed RPCs.
1818
- **Context-aware mount paths.** Standalone adapters (`cli`, `spa`, `build`) serve at `/` by default; hosted adapters (`vite`, `embedded`) serve at `/.<id>/`. Override via `DevframeDefinition.basePath`.
1919
- **SPAs own their base at runtime.** Build with relative asset paths (`vite.base: './'`); `connectDevframe` discovers the effective base from the executing script's location.
20-
- **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.
20+
- **CLI flags compose.** The `cac` instance is exposed to both the devframe (`cli.configure`) and the caller of `createCac`, so capability flags and app flags merge cleanly.
2121

2222
## What Devframe provides
2323

@@ -47,7 +47,7 @@ A minimal devframe with a CLI entry point:
4747

4848
```ts twoslash
4949
import { defineDevframe, defineRpcFunction } from 'devframe'
50-
import { createCli } from 'devframe/adapters/cli'
50+
import { createCac } from 'devframe/adapters/cac'
5151

5252
const devframe = defineDevframe({
5353
id: 'my-devframe',
@@ -70,7 +70,7 @@ const devframe = defineDevframe({
7070
},
7171
})
7272

73-
await createCli(devframe).parse()
73+
await createCac(devframe).parse()
7474
```
7575

7676
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).
@@ -91,7 +91,7 @@ Devframe deploys the same `DevframeDefinition` through one of these adapters:
9191

9292
| Adapter | Entry | Target |
9393
|---------|-------|--------|
94-
| `cli` | `createCli(d).parse()` | Standalone CLI with dev / build / mcp subcommands |
94+
| `cli` | `createCac(d).parse()` | Standalone CLI with dev / build / mcp subcommands |
9595
| `vite` | `createPluginFromDevframe(d, opts?)` *(from `@vitejs/devtools-kit/node`)* | Mount the devframe into Vite DevTools (or another compatible host) |
9696
| `build` | `createBuild(d, opts?)` | Self-contained static deploy with baked RPC dumps |
9797
| `embedded` | `createEmbedded(d, { ctx })` | Runtime registration into an existing host |

0 commit comments

Comments
 (0)