Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
93 changes: 41 additions & 52 deletions alias.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,63 +78,52 @@ export const alias = {
'@devframes/json-render-ui/spa': r('json-render-ui/src/spa.ts'),
'@devframes/json-render-ui': r('json-render-ui/src/index.ts'),
'json-render/dashboard': fileURLToPath(new URL('./examples/json-render/src/dashboard.ts', import.meta.url)),
'@devframes/plugin-code-server/client': p('code-server/src/client/index.ts'),
'@devframes/plugin-code-server/node': p('code-server/src/node/index.ts'),
'@devframes/plugin-code-server/constants': p('code-server/src/constants.ts'),
'@devframes/plugin-code-server/types': p('code-server/src/types.ts'),
'@devframes/plugin-code-server/rpc': p('code-server/src/rpc/index.ts'),
'@devframes/plugin-code-server/cli': p('code-server/src/cli.ts'),
'@devframes/plugin-code-server/vite': p('code-server/src/vite.ts'),
'@devframes/plugin-code-server': p('code-server/src/index.ts'),
'@devframes/plugin-terminals/client': p('terminals/src/client/index.ts'),
'@devframes/plugin-terminals/node': p('terminals/src/node/index.ts'),
'@devframes/plugin-terminals/constants': p('terminals/src/constants.ts'),
'@devframes/plugin-terminals/types': p('terminals/src/types.ts'),
'@devframes/plugin-terminals/cli': p('terminals/src/cli.ts'),
'@devframes/plugin-terminals/vite': p('terminals/src/vite.ts'),
'@devframes/plugin-terminals': p('terminals/src/index.ts'),
'@devframes/plugin-git': p('git/src/index.ts'),
'@devframes/plugin-code-server/node': p('code-server/src/node/setup.ts'),
'@devframes/plugin-code-server/constants': p('code-server/src/node/constants.ts'),
'@devframes/plugin-code-server/types': p('code-server/src/node/types.ts'),
'@devframes/plugin-code-server/rpc': p('code-server/src/node/rpc/index.ts'),
'@devframes/plugin-code-server/cli': p('code-server/src/node/cli.ts'),
'@devframes/plugin-code-server/vite': p('code-server/src/node/vite.ts'),
'@devframes/plugin-code-server': p('code-server/src/node/index.ts'),
'@devframes/plugin-terminals/client': p('terminals/app/client/index.ts'),
'@devframes/plugin-terminals/node': p('terminals/src/node/setup.ts'),
'@devframes/plugin-terminals/constants': p('terminals/src/node/constants.ts'),
'@devframes/plugin-terminals/types': p('terminals/src/node/types.ts'),
'@devframes/plugin-terminals/cli': p('terminals/src/node/cli.ts'),
'@devframes/plugin-terminals/vite': p('terminals/src/node/vite.ts'),
'@devframes/plugin-terminals': p('terminals/src/node/index.ts'),
'@devframes/plugin-git': p('git/src/node/index.ts'),
'devframe/recipes/interactive-auth': r('devframe/src/recipes/interactive-auth.ts'),
'devframe/recipes/common-rpc-functions': r('devframe/src/recipes/common-rpc-functions.ts'),
'devframe/client': r('devframe/src/client/index.ts'),
'devframe': r('devframe/src'),
'@devframes/plugin-data-inspector/client': p('data-inspector/src/client/index.ts'),
'@devframes/plugin-data-inspector/node': p('data-inspector/src/node/index.ts'),
'@devframes/plugin-data-inspector/registry': p('data-inspector/src/registry/index.ts'),
'@devframes/plugin-data-inspector/engine': p('data-inspector/src/engine/index.ts'),
'@devframes/plugin-data-inspector/inject': p('data-inspector/src/inject/index.ts'),
'@devframes/plugin-data-inspector/cli': p('data-inspector/src/cli.ts'),
'@devframes/plugin-data-inspector/vite': p('data-inspector/src/vite.ts'),
'@devframes/plugin-data-inspector': p('data-inspector/src/index.ts'),
'@devframes/plugin-inspect/client': p('inspect/src/client/index.ts'),
'@devframes/plugin-inspect/node': p('inspect/src/node/index.ts'),
'@devframes/plugin-inspect/cli': p('inspect/src/cli.ts'),
'@devframes/plugin-inspect/vite': p('inspect/src/vite.ts'),
'@devframes/plugin-inspect': p('inspect/src/index.ts'),
'@devframes/plugin-og/client': p('og/src/client/index.ts'),
'@devframes/plugin-og/node': p('og/src/node/index.ts'),
'@devframes/plugin-og/rpc': p('og/src/rpc/index.ts'),
'@devframes/plugin-og/cli': p('og/src/cli.ts'),
'@devframes/plugin-og/vite': p('og/src/vite.ts'),
'@devframes/plugin-og': p('og/src/index.ts'),
'@devframes/plugin-a11y/client': p('a11y/src/client/index.ts'),
'@devframes/plugin-a11y/node': p('a11y/src/node/index.ts'),
'@devframes/plugin-a11y/cli': p('a11y/src/cli.ts'),
'@devframes/plugin-a11y/vite': p('a11y/src/vite.ts'),
'@devframes/plugin-a11y': p('a11y/src/index.ts'),
'@devframes/plugin-messages/client': p('messages/src/client/index.ts'),
'@devframes/plugin-messages/node': p('messages/src/node/index.ts'),
'@devframes/plugin-messages/constants': p('messages/src/constants.ts'),
'@devframes/plugin-messages/rpc': p('messages/src/rpc/index.ts'),
'@devframes/plugin-messages/cli': p('messages/src/cli.ts'),
'@devframes/plugin-messages/vite': p('messages/src/vite.ts'),
'@devframes/plugin-messages': p('messages/src/index.ts'),
'@devframes/plugin-assets/client': p('assets/src/client/index.ts'),
'@devframes/plugin-assets/node': p('assets/src/node/index.ts'),
'@devframes/plugin-assets/rpc': p('assets/src/rpc/index.ts'),
'@devframes/plugin-assets/cli': p('assets/src/cli.ts'),
'@devframes/plugin-assets/vite': p('assets/src/vite.ts'),
'@devframes/plugin-assets': p('assets/src/index.ts'),
'@devframes/plugin-data-inspector/node': p('data-inspector/src/node/setup.ts'),
'@devframes/plugin-data-inspector/registry': p('data-inspector/src/node/registry/index.ts'),
'@devframes/plugin-data-inspector/engine': p('data-inspector/src/node/engine/index.ts'),
'@devframes/plugin-data-inspector/cli': p('data-inspector/src/node/cli.ts'),
'@devframes/plugin-data-inspector': p('data-inspector/src/node/index.ts'),
'@devframes/plugin-inspect/node': p('inspect/src/node/setup.ts'),
'@devframes/plugin-inspect/cli': p('inspect/src/node/cli.ts'),
'@devframes/plugin-inspect': p('inspect/src/node/index.ts'),
'@devframes/plugin-og/node': p('og/src/node/setup.ts'),
'@devframes/plugin-og/rpc': p('og/src/node/rpc/index.ts'),
'@devframes/plugin-og/cli': p('og/src/node/cli.ts'),
'@devframes/plugin-og': p('og/src/node/index.ts'),
'@devframes/plugin-a11y/client-script': p('a11y/src/client-script/index.ts'),
'@devframes/plugin-a11y/node': p('a11y/src/node/setup.ts'),
'@devframes/plugin-a11y/cli': p('a11y/src/node/cli.ts'),
'@devframes/plugin-a11y': p('a11y/src/node/index.ts'),
'@devframes/plugin-messages/client': p('messages/app/client/index.ts'),
'@devframes/plugin-messages/node': p('messages/src/node/setup.ts'),
'@devframes/plugin-messages/constants': p('messages/src/node/constants.ts'),
'@devframes/plugin-messages/rpc': p('messages/src/node/rpc/index.ts'),
'@devframes/plugin-messages/cli': p('messages/src/node/cli.ts'),
'@devframes/plugin-messages': p('messages/src/node/index.ts'),
'@devframes/plugin-assets/node': p('assets/src/node/setup.ts'),
'@devframes/plugin-assets/rpc': p('assets/src/node/rpc/index.ts'),
'@devframes/plugin-assets/cli': p('assets/src/node/cli.ts'),
'@devframes/plugin-assets': p('assets/src/node/index.ts'),
'@devframes/service-git': s('git/src/index.ts'),
'@devframes/service-open': s('open/src/index.ts'),
'@devframes/service-shiki': s('shiki/src/index.ts'),
Expand Down
47 changes: 24 additions & 23 deletions docs/content/1.guide/1.tutorial-server-data-inspector.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ You'll need [Node 24+](https://nodejs.org/) and a terminal. Every code block is

A devframe is two halves talking over a typed connection: the **node side** exposes functions, and the **browser side** calls them and renders the results. Devframe is everything in between: the wire, the UI hosting, auth, builds, and a CLI.

The two halves live in their own folders: the node side under `src/node/`, the web app under `app/`. A `playgrounds/` folder holds hosts that boot the built tool. We'll fill these in as we go.

## Step 1: Define the tool

Everything starts with `defineDevframe`: your tool's name, plus a `setup` where you register what it can do. Create the project and the definition:
Expand All @@ -23,7 +25,7 @@ npm init -y && npm pkg set type=module
npm install devframe && npm install -D typescript
```

```ts [src/data-inspector.ts]
```ts [src/node/data-inspector.ts]
import { defineDevframe } from 'devframe'

// Some example server-side data, whatever you want to peek at while your
Expand Down Expand Up @@ -95,7 +97,7 @@ npm install react react-dom @devframes/vite
npm install -D vite @vitejs/plugin-react @types/react @types/react-dom
```

```html [client/index.html]
```html [app/index.html]
<!doctype html>
<html>
<head>
Expand All @@ -109,14 +111,14 @@ npm install -D vite @vitejs/plugin-react @types/react @types/react-dom
</html>
```

```tsx [client/main.tsx]
```tsx [app/main.tsx]
import { createRoot } from 'react-dom/client'
import { App } from './App'

createRoot(document.getElementById('app')!).render(<App />)
```

```tsx [client/App.tsx]
```tsx [app/App.tsx]
import type { DevframeRpcClient } from 'devframe/client'
import { connectDevframe } from 'devframe/client'
import { useEffect, useState } from 'react'
Expand Down Expand Up @@ -173,14 +175,13 @@ export function App() {

To try what we've built, let Vite serve the UI and hand RPC traffic to devframe:

```ts [vite.client.config.ts]
```ts [app/vite.config.ts]
import { devframeViteBridge } from '@devframes/vite/single'
import react from '@vitejs/plugin-react'
import { defineConfig } from 'vite'
import dataInspectorFrame from './src/data-inspector.ts'
import dataInspectorFrame from '../src/node/data-inspector.ts'

export default defineConfig({
root: 'client',
base: './', // relative asset URLs, so the built UI works under any mount path
build: { outDir: '../dist/client', emptyOutDir: true },
plugins: [
Expand All @@ -193,27 +194,27 @@ export default defineConfig({
```

```sh
npx vite --config vite.client.config.ts
npx vite --config app/vite.config.ts
```

Open the printed URL. The three keys and their types show up, and typing `config.port` or `users.0.name` and hitting **Query** prints the value. Button → `call` → your `handler` → back to the page: that's the whole devframe working.

> [!WARNING]
> `auth: false` trusts anything that can reach the port. It's off here to keep the tutorial simple; turn it on for anything you publish or expose beyond localhost. See [Security](/guide/security).

From here on we reuse this same `src/data-inspector.ts` and `client/` unchanged; all that changes is where they run.
From here on we reuse this same `src/node/data-inspector.ts` and `app/` unchanged; all that changes is where they run.

## Step 4: Dock it in a hub

A [hub](/guide/hub) puts many devframes behind one interface, each a **dock entry** you switch between, the tool's own UI in an iframe. Since our SPA uses a bare `connectDevframe()`, it already works anywhere; the hub just needs the built UI, so point the definition at it:

```ts [src/data-inspector.ts]
```ts [src/node/data-inspector.ts]
import { fileURLToPath } from 'node:url'
// …
const dataInspectorFrame = defineDevframe({
id: 'data-inspector',
/** … */
clientAssets: fileURLToPath(new URL('../dist/client', import.meta.url)),
clientAssets: fileURLToPath(new URL('../../dist/client', import.meta.url)),
setup(ctx) { /* unchanged */ },
})
```
Expand All @@ -222,14 +223,14 @@ Build the UI and stand up a one-devframe hub:

```sh
npm install @devframes/hub @devframes/hub-ui
npx vite build --config vite.client.config.ts
npx vite build --config app/vite.config.ts
```

```ts [vite.hub.config.ts]
```ts [playgrounds/hub.config.ts]
import { createUi } from '@devframes/hub-ui'
import { viteDevframeHub } from '@devframes/vite/hub'
import { defineConfig } from 'vite'
import dataInspectorFrame from './src/data-inspector.ts'
import dataInspectorFrame from '../src/node/data-inspector.ts'

export default defineConfig({
plugins: [
Expand All @@ -242,7 +243,7 @@ export default defineConfig({
```

```sh
npx vite --config vite.hub.config.ts
npx vite --config playgrounds/hub.config.ts
```

Your inspector now sits in the hub's dock rail as a dock entry. Add more to `devframes: [...]` (your own or the [built-in devframes](/add-ons)) and each gets its own. (The hub prints a code to authorize on first connect.)
Expand All @@ -265,14 +266,14 @@ ctx.rpc.register({

```js [scripts/build.mjs]
import { createBuild } from 'devframe/adapters/build'
import dataInspectorFrame from '../src/data-inspector.ts'
import dataInspectorFrame from '../src/node/data-inspector.ts'

await createBuild(dataInspectorFrame, { outDir: 'dist-static' })
```

```sh
npx vite build # refresh dist/client
node scripts/build.mjs # → dist-static/
npx vite build --config app/vite.config.ts # refresh dist/client
node scripts/build.mjs # → dist-static/
```

Serve `dist-static/` anywhere and the meta list renders from the baked snapshot, no Node in sight. `query` takes an argument, so it still needs the live node side (next), or you can bake specific inputs ([Client Assets](/guide/client-assets)).
Expand All @@ -281,16 +282,16 @@ Serve `dist-static/` anywhere and the meta list renders from the baked snapshot,

The definition never depended on Vite. `createDevServer` runs the tool on its own, serving the UI from `clientAssets` and answering RPC live:

```js [scripts/serve.mjs]
```js [playgrounds/serve.mjs]
import { createDevServer } from 'devframe/adapters/dev'
import dataInspectorFrame from '../src/data-inspector.ts'
import dataInspectorFrame from '../src/node/data-inspector.ts'

await createDevServer(dataInspectorFrame, { openBrowser: true })
```

```sh
npx vite build
node scripts/serve.mjs
npx vite build --config app/vite.config.ts
node playgrounds/serve.mjs
```

Same UI, same live calls, no bundler in the loop: this is what you'd drop into your own Node program.
Expand All @@ -302,7 +303,7 @@ Finally, wrap that dev server in a CLI. `devframe/adapters/cac` turns a devframe
```js [bin.mjs]
#!/usr/bin/env node
import { createCac } from 'devframe/adapters/cac'
import dataInspectorFrame from './src/data-inspector.ts'
import dataInspectorFrame from './src/node/data-inspector.ts'

createCac(dataInspectorFrame).parse()
```
Expand Down
77 changes: 56 additions & 21 deletions knip.jsonc
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
// the default export is the conventional single-devframe import. Service
// packages (`create<X>Service`) follow the same rule.
"ignoreIssues": {
"plugins/*/src/node/index.ts": ["duplicates"],
"plugins/*/src/index.ts": ["duplicates"],
"services/*/src/index.ts": ["duplicates"],
// Deprecated rename aliases (old name re-exported next to its canonical
Expand Down Expand Up @@ -209,10 +210,11 @@
// the monorepo. Repeat the `plugins/*` entry glob (a workspace config
// replaces, not merges, it).
"entry": [
"src/{index,cli,vite,constants,types}.ts",
"src/{client,node,rpc,inject,engine,registry}/index.ts"
"src/node/{index,setup,cli}.ts",
"src/node/rpc/index.ts",
"app/**/*.stories.@(ts|tsx)"
],
"ignoreDependencies": ["@devframes/plugin-inspect--assets"]
"ignoreDependencies": ["@devframes/plugin-inspect--assets", "@devframes/vite"]
},
"plugins/inspect/assets-pkg": {
// Assets-only package: no source, just a prebuilt `dist` produced by
Expand All @@ -225,32 +227,60 @@
// so knip can't see the dev-only workspace link. A workspace config
// replaces (not merges) the `plugins/*` glob's `entry`, so repeat it.
"plugins/og": {
"entry": ["src/{index,cli,vite,constants,types}.ts", "src/{client,node,rpc,inject,engine,registry}/index.ts"],
"ignoreDependencies": ["@devframes/plugin-og--assets"]
"entry": [
"src/node/{index,setup,cli}.ts",
"src/node/rpc/index.ts",
"app/**/*.stories.@(ts|tsx)"
],
"ignoreDependencies": ["@devframes/plugin-og--assets", "@devframes/vite"]
},
"plugins/og/assets-pkg": { "entry": [], "project": [] },
"plugins/data-inspector": {
"entry": ["src/{index,cli,vite,constants,types}.ts", "src/{client,node,rpc,inject,engine,registry}/index.ts"],
"ignoreDependencies": ["@devframes/plugin-data-inspector--assets"]
"entry": [
"src/node/{index,setup,cli}.ts",
"src/node/rpc/index.ts",
"src/node/{engine,registry}/index.ts",
"src/inject/index.ts",
"app/**/*.stories.@(ts|tsx)"
],
"ignoreDependencies": ["@devframes/plugin-data-inspector--assets", "@devframes/vite"]
},
"plugins/data-inspector/assets-pkg": { "entry": [], "project": [] },
"plugins/code-server": {
"entry": ["src/{index,cli,vite,constants,types}.ts", "src/{client,node,rpc,inject,engine,registry}/index.ts"],
"entry": [
"src/node/{index,setup,cli,constants,types,vite}.ts",
"src/node/rpc/index.ts",
"app/**/*.stories.@(ts|tsx)"
],
"ignoreDependencies": ["@devframes/plugin-code-server--assets"]
},
"plugins/code-server/assets-pkg": { "entry": [], "project": [] },
"plugins/assets": {
"entry": ["src/{index,cli,vite,constants,types}.ts", "src/{client,node,rpc,inject,engine,registry}/index.ts"],
"ignoreDependencies": ["@devframes/plugin-assets--assets"]
"entry": [
"src/node/{index,setup,cli,constants,types}.ts",
"src/node/rpc/index.ts",
"app/**/*.stories.@(ts|tsx)"
],
"ignoreDependencies": ["@devframes/plugin-assets--assets", "@devframes/vite"]
},
"plugins/assets/assets-pkg": { "entry": [], "project": [] },
"plugins/messages": {
"entry": ["src/{index,cli,vite,constants,types}.ts", "src/{client,node,rpc,inject,engine,registry}/index.ts"],
"ignoreDependencies": ["@devframes/plugin-messages--assets"]
"entry": [
"src/node/{index,setup,cli,constants}.ts",
"src/node/rpc/index.ts",
"app/client/index.ts",
"app/**/*.stories.@(ts|tsx)"
],
"ignoreDependencies": ["@devframes/plugin-messages--assets", "@devframes/vite"]
},
"plugins/messages/assets-pkg": { "entry": [], "project": [] },
"plugins/terminals": {
"entry": ["src/{index,cli,vite,constants,types}.ts", "src/{client,node,rpc,inject,engine,registry}/index.ts"],
"entry": [
"src/node/{index,setup,cli,constants,types,vite}.ts",
"src/node/rpc/index.ts",
"app/client/index.ts",
"app/**/*.stories.@(ts|tsx)"
],
"ignoreDependencies": ["@devframes/plugin-terminals--assets"]
},
"plugins/terminals/assets-pkg": { "entry": [], "project": [] },
Expand All @@ -269,19 +299,24 @@
// `plugins/*` glob's `entry` above, so repeat that pattern here
// alongside the stories.
"entry": [
"src/{index,cli,vite,constants,types}.ts",
"src/{client,node,rpc,inject,engine,registry}/index.ts",
"src/**/*.stories.tsx"
"src/node/{index,setup,cli}.ts",
"src/node/rpc/index.ts",
"src/client-script/index.ts",
"app/**/*.stories.tsx"
],
"ignoreDependencies": ["@devframes/plugin-a11y--assets"]
"ignoreDependencies": ["@devframes/plugin-a11y--assets", "@devframes/vite"]
},
"plugins/git": {
// The React client build's root sits at `src/client` (`next dev
// src/client`), not the workspace root, so knip's default PostCSS
// config glob (workspace-root `postcss.config.*`) misses it.
// The React client build's root sits at `app/` (`next dev app`), not the
// workspace root, so knip's default PostCSS config glob (workspace-root
// `postcss.config.*`) misses it.
"postcss": {
"config": ["src/client/postcss.config.mjs"]
"config": ["app/postcss.config.mjs"]
},
"entry": [
"src/node/{index,cli}.ts",
"app/**/*.stories.@(ts|tsx)"
],
"ignoreDependencies": ["@devframes/plugin-git--assets"]
}
}
Expand Down
Loading
Loading