Skip to content

Commit 64236ac

Browse files
antfubotantfu
andauthored
refactor(recipes): rename openHelpers to commonRpcFunctions and harden openInEditor (#141)
Co-authored-by: Anthony Fu <github@antfu.me>
1 parent 9b55cb8 commit 64236ac

21 files changed

Lines changed: 362 additions & 189 deletions

alias.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ export const alias = {
7373
'@devframes/plugin-terminals': p('terminals/src/index.ts'),
7474
'@devframes/plugin-git': p('git/src/index.ts'),
7575
'devframe/recipes/interactive-auth': r('devframe/src/recipes/interactive-auth.ts'),
76+
'devframe/recipes/common-rpc-functions': r('devframe/src/recipes/common-rpc-functions.ts'),
7677
'devframe/recipes/open-helpers': r('devframe/src/recipes/open-helpers.ts'),
7778
'devframe/client': r('devframe/src/client/index.ts'),
7879
'devframe': r('devframe/src'),

docs/.vitepress/config.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ function helpersItems(prefix: string) {
5656
{ text: 'Vite Bridge', link: `${prefix}/helpers/vite-bridge` },
5757
{ text: 'Nuxt Module', link: `${prefix}/helpers/nuxt` },
5858
{ text: 'Next Helper', link: `${prefix}/helpers/next` },
59-
{ text: 'Open Helpers', link: `${prefix}/helpers/open-helpers` },
59+
{ text: 'Common RPC Functions', link: `${prefix}/helpers/common-rpc-functions` },
6060
{ text: 'Interactive Auth', link: `${prefix}/helpers/interactive-auth` },
6161
] satisfies DefaultTheme.NavItemWithLink[]
6262
}

docs/guide/standalone-cli.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -199,9 +199,9 @@ defineDevframe({
199199

200200
The adapter derives each flag's CAC option from its schema — booleans become `--verbose` / `--no-verbose`; everything else becomes `--depth <value>`. Keys are camelCase in TypeScript, kebab-case on the command line (`configFile``--config-file`). Flags that aren't in your schema (`--host`, `--port`, or anything added via `cli.configure`) still pass through untouched.
201201

202-
## Open helpers
202+
## Common RPC functions
203203

204-
For the two actions every CLI devtool needs — open a file in the editor, reveal a path in the OS file explorer — use the prebuilt recipes from `devframe/recipes/open-helpers` instead of re-implementing them. See [Helpers → Open Helpers](/helpers/open-helpers) for the full reference.
204+
For the two actions every CLI devtool needs — open a file in the editor, reveal a path in the OS file explorer — use the prebuilt recipes from `devframe/recipes/common-rpc-functions` instead of re-implementing them. See [Helpers → Common RPC Functions](/helpers/common-rpc-functions) for the full reference.
205205

206206
## Snapshot queries for static builds
207207

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
---
2+
outline: deep
3+
---
4+
5+
# Common RPC Functions
6+
7+
Prebuilt RPC actions for the two file-system actions every CLI devtool needs — opening a file in the editor, revealing a path in the OS file explorer. Use the recipe instead of re-implementing them so every devframe converges on the same registered names and payload shape.
8+
9+
```ts
10+
import { commonRpcFunctions } from 'devframe/recipes/common-rpc-functions'
11+
12+
defineDevframe({
13+
id: 'my-tool',
14+
name: 'My Tool',
15+
setup(ctx) {
16+
commonRpcFunctions.forEach(fn => ctx.rpc.register(fn))
17+
},
18+
})
19+
```
20+
21+
## Exports
22+
23+
| Export | Registered name | Type | Args | Purpose |
24+
|--------|------------------|------|------|---------|
25+
| `openInEditor` | `devframe:open-in-editor` | `action` | `[filename: string, editor?: KnownEditor]` | Open the file in the user's editor via [`launchEditor`](./utilities#devframe-utils-launch-editor). `filename` accepts `file`, `file:line`, or `file:line:column`. The optional `editor` picks the editor command explicitly instead of relying on auto-detection. |
26+
| `openInFinder` | `devframe:open-in-finder` | `action` | `[path: string]` | Reveal the path in the OS file explorer via [`open`](./utilities#devframe-utils-open). |
27+
| `commonRpcFunctions` || `readonly [openInEditor, openInFinder]` || Convenience array for batch registration. |
28+
| `KNOWN_EDITORS` || `readonly string[]` || The editor commands `openInEditor`'s `editor` argument accepts (`code`, `vim`, `subl`, `idea`, …). |
29+
| `KnownEditor` || type || Union of `KNOWN_EDITORS`. |
30+
31+
Both functions are `action`-type RPCs returning `void` and use `valibot` schemas for their arguments — `openInEditor`'s `editor` argument is `v.optional(v.picklist(KNOWN_EDITORS))`, so a value outside `KNOWN_EDITORS` fails validation rather than reaching the underlying `launch-editor` process spawn. Both handlers dynamically `import()` their underlying `devframe/utils/*` implementation, so the `launch-editor` and `open` dependencies only load when the recipe actually runs.
32+
33+
The `devframe/recipes/open-helpers` entry (`openHelpers`) remains as a deprecated alias for this module — new code should import `commonRpcFunctions` from `devframe/recipes/common-rpc-functions`.
34+
35+
## Pick and choose
36+
37+
Register only the helper you need rather than the whole array:
38+
39+
```ts
40+
import { openInEditor } from 'devframe/recipes/common-rpc-functions'
41+
42+
defineDevframe({
43+
id: 'my-tool',
44+
setup(ctx) {
45+
ctx.rpc.register(openInEditor)
46+
},
47+
})
48+
```
49+
50+
## On the client
51+
52+
The SPA calls these like any other RPC:
53+
54+
```ts
55+
const rpc = await connectDevframe()
56+
await rpc.call('devframe:open-in-editor', 'src/main.ts:42:7')
57+
await rpc.call('devframe:open-in-editor', 'src/main.ts:42:7', 'code')
58+
await rpc.call('devframe:open-in-finder', '/abs/path/to/dir')
59+
```
60+
61+
`launchEditor`'s editor auto-detection reads the `LAUNCH_EDITOR` environment variable on the server side when no `editor` argument is passed — there is no client-side configuration.

docs/helpers/index.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ Helpers are the optional, opt-in surface around the core `defineDevframe` API: s
1212
| [Vite Bridge](./vite-bridge) | `devframe/helpers/vite` | Vite plugin for mounting a devframe inside any Vite-based host (Astro, SolidStart, plain Vite). |
1313
| [Nuxt Module](./nuxt) | `@devframes/nuxt` | Nuxt module that wires a Nuxt SPA as a devframe client and serves the dev-time RPC bridge. |
1414
| [Next Helper](./next) | `@devframes/next` | Route-handler host + React client for mounting devframes inside a Next.js App Router app (experimental). |
15-
| [Open Helpers](./open-helpers) | `devframe/recipes/open-helpers` | Prebuilt RPC actions for "open in editor" and "reveal in Finder". |
15+
| [Common RPC Functions](./common-rpc-functions) | `devframe/recipes/common-rpc-functions` | Prebuilt RPC actions for "open in editor" and "reveal in Finder". |
1616
| [Interactive Auth](./interactive-auth) | `devframe/recipes/interactive-auth` | Ready-made OTP auth layer — handshake, resolver gate, connect-time trust, and the code/link banner. |
1717

1818
Helpers vs. [adapters](/adapters/): an adapter takes a `DevframeDefinition` and deploys it as a runnable surface (CLI, dev server, static build, MCP server). A helper is a smaller piece — a Vite plugin, a Nuxt module, a recipe, a utility function — that you compose alongside an adapter.

docs/helpers/open-helpers.md

Lines changed: 0 additions & 56 deletions
This file was deleted.

docs/helpers/utilities.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ launchEditor('src/main.ts:42:7')
4646
launchEditor('src/main.ts:42:7', 'code')
4747
```
4848

49-
The auto-detection reads the `LAUNCH_EDITOR` environment variable and falls back to common defaults. Most devframes consume this through the prebuilt `openInEditor` recipe — see [Open helpers](./open-helpers).
49+
The auto-detection reads the `LAUNCH_EDITOR` environment variable and falls back to common defaults. Most devframes consume this through the prebuilt `openInEditor` recipe — see [Common RPC Functions](./common-rpc-functions).
5050

5151
### `devframe/utils/hash`
5252

packages/devframe/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
"./node": "./dist/node/index.mjs",
3333
"./node/auth": "./dist/node/auth.mjs",
3434
"./node/hub-internals": "./dist/node/hub-internals.mjs",
35+
"./recipes/common-rpc-functions": "./dist/recipes/common-rpc-functions.mjs",
3536
"./recipes/interactive-auth": "./dist/recipes/interactive-auth.mjs",
3637
"./recipes/open-helpers": "./dist/recipes/open-helpers.mjs",
3738
"./rpc": "./dist/rpc/index.mjs",
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import * as v from 'valibot'
2+
import { describe, expect, it } from 'vitest'
3+
import { commonRpcFunctions, KNOWN_EDITORS, openInEditor, openInFinder } from '../common-rpc-functions'
4+
import { openHelpers } from '../open-helpers'
5+
6+
describe('recipes/common-rpc-functions', () => {
7+
it('exposes `openInEditor` as a devframe-namespaced action', () => {
8+
expect(openInEditor.name).toBe('devframe:open-in-editor')
9+
expect(openInEditor.type).toBe('action')
10+
expect(openInEditor.args).toHaveLength(2)
11+
expect(typeof openInEditor.handler).toBe('function')
12+
})
13+
14+
it('restricts `openInEditor`\'s optional second argument to `KNOWN_EDITORS`', () => {
15+
expect(KNOWN_EDITORS).toContain('code')
16+
expect(KNOWN_EDITORS).toContain('vim')
17+
18+
const editorSchema = openInEditor.args[1]
19+
expect(v.safeParse(editorSchema, undefined).success).toBe(true)
20+
for (const editor of KNOWN_EDITORS)
21+
expect(v.safeParse(editorSchema, editor).success).toBe(true)
22+
expect(v.safeParse(editorSchema, 'not-a-real-editor').success).toBe(false)
23+
})
24+
25+
it('exposes `openInFinder` as a devframe-namespaced action', () => {
26+
expect(openInFinder.name).toBe('devframe:open-in-finder')
27+
expect(openInFinder.type).toBe('action')
28+
expect(openInFinder.args).toHaveLength(1)
29+
expect(typeof openInFinder.handler).toBe('function')
30+
})
31+
32+
it('bundles both helpers in `commonRpcFunctions`', () => {
33+
expect(commonRpcFunctions).toHaveLength(2)
34+
expect(commonRpcFunctions).toContain(openInEditor)
35+
expect(commonRpcFunctions).toContain(openInFinder)
36+
})
37+
38+
it('keeps the deprecated `devframe/recipes/open-helpers` entry working as an alias', () => {
39+
expect(openHelpers).toBe(commonRpcFunctions)
40+
})
41+
})

packages/devframe/src/recipes/__tests__/open-helpers.test.ts

Lines changed: 0 additions & 24 deletions
This file was deleted.

0 commit comments

Comments
 (0)