@devframes/vite 0.9.8 → 0.9.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -10,7 +10,7 @@ interface MountDevframeHubClientOptions extends DevframeClientRuntimeOptions$1 {
10
10
  base?: string;
11
11
  }
12
12
  /**
13
- * Boot the devframes-hub **client runtime** in the host page — the browser
13
+ * Boot the devframes-hub **client runtime** in the host page: the browser
14
14
  * half of {@link import('./hub').viteDevframeHub}. Connects RPC to the hub
15
15
  * (defaulting `base` to `/__devframes/`), assembles the shared
16
16
  * `DevframeClientContext`, and imports each dock's client script into the
@@ -2,7 +2,7 @@ import { createDevframeClientRuntime } from "@devframes/hub/client";
2
2
  import { DEVFRAMES_HUB_BASE } from "@devframes/hub/constants";
3
3
  //#region src/hub-client.ts
4
4
  /**
5
- * Boot the devframes-hub **client runtime** in the host page — the browser
5
+ * Boot the devframes-hub **client runtime** in the host page: the browser
6
6
  * half of {@link import('./hub').viteDevframeHub}. Connects RPC to the hub
7
7
  * (defaulting `base` to `/__devframes/`), assembles the shared
8
8
  * `DevframeClientContext`, and imports each dock's client script into the
package/dist/hub.d.mts CHANGED
@@ -5,7 +5,7 @@ import { Plugin } from "vite";
5
5
  //#region src/hub.d.ts
6
6
  interface ViteDevframeHubOptions {
7
7
  /**
8
- * Mount base the hub answers under — every frame lives at `<base><id>/`.
8
+ * Mount base the hub answers under; every frame lives at `<base><id>/`.
9
9
  * Default: `/__devframes/`.
10
10
  */
11
11
  base?: string;
@@ -28,7 +28,7 @@ interface ViteDevframeHubOptions {
28
28
  */
29
29
  clientScripts?: Record<string, ClientScriptEntry>;
30
30
  /**
31
- * Prebuilt dock-renderer modules forwarded to `initHub({ renderers })` —
31
+ * Prebuilt dock-renderer modules forwarded to `initHub({ renderers })`,
32
32
  * each served at `<base>__renderers/<type>.mjs` and published in the
33
33
  * renderer manifest (e.g. `jsonRenderUiRenderer()` from
34
34
  * `@devframes/json-render-ui/hub`).
@@ -40,13 +40,13 @@ interface ViteDevframeHubOptions {
40
40
  */
41
41
  rpcDeclarations?: InitHubOptions['rpcDeclarations'];
42
42
  /**
43
- * Runs once the context exists and every `devframes` entry is mounted —
43
+ * Runs once the context exists and every `devframes` entry is mounted;
44
44
  * register docks, commands, terminals, and messages surfaces here.
45
45
  */
46
46
  configure?: (ctx: DevframeHubContext) => void | Promise<void>;
47
47
  /**
48
48
  * The hub's UI slot. Defaults to `@devframes/hub-ui`'s `createUi()` (the
49
- * reference floating dock + standalone viewer) when omitted — install
49
+ * reference floating dock + standalone viewer) when omitted, so install
50
50
  * `@devframes/hub-ui` for that default. Pass your own {@link DevframeHubUi}
51
51
  * to swap the hub UI provider, or `false` for a headless hub (serve your own UI
52
52
  * against `@devframes/hub/client`).
@@ -58,7 +58,9 @@ interface ViteDevframeHubOptions {
58
58
  */
59
59
  auth?: InitHubOptions['auth'];
60
60
  /**
61
- * Expose the aggregate MCP endpoint at `<base>__mcp`.
61
+ * Expose the aggregate MCP endpoint at `<base>__mcp`. Defaults to `'auto'`
62
+ * (mount once any mounted devframe exposes an agent surface); `true`
63
+ * forces it on, `false` off.
62
64
  */
63
65
  mcp?: InitHubOptions['mcp'];
64
66
  /** Publish this hub in the global instance registry. Default: off. */
@@ -79,10 +81,21 @@ interface ViteDevframeHubOptions {
79
81
  * @default false
80
82
  */
81
83
  quiet?: boolean;
84
+ /**
85
+ * Also bake the hub into `vite build` output: `buildHub` writes the whole
86
+ * static hub subtree (each devframe's SPA, the RPC dump, `embedded.js`, …)
87
+ * into `<outDir><base>`, and the UI's `embedded.js` script tag is injected
88
+ * into the built HTML, so the deployed app ships working devtools against
89
+ * a `static` backend (baked reads, no live server). Off by default: a
90
+ * production bundle only carries devtools when the host opts in.
91
+ *
92
+ * @default false
93
+ */
94
+ build?: boolean;
82
95
  }
83
96
  /**
84
- * Mount a whole **devframes-hub** — many integrations under one namespace,
85
- * one merged RPC registry, one WebSocket — inside an existing Vite dev
97
+ * Mount a whole **devframes-hub** (many integrations under one namespace,
98
+ * one merged RPC registry, one WebSocket) inside an existing Vite dev
86
99
  * server. One `initHub()` call, mounted as connect middleware; the WebSocket
87
100
  * shares Vite's own HTTP server (upgrading at `<base>__ws`) unless a `port`
88
101
  * pins a side-car. The UI defaults to `@devframes/hub-ui` (its `embedded.js`
package/dist/hub.mjs CHANGED
@@ -1,4 +1,5 @@
1
1
  import process from "node:process";
2
+ import { resolve } from "pathe";
2
3
  import { Server } from "node:http";
3
4
  import { DEVFRAMES_HUB_BASE, normalizeHubBase } from "@devframes/hub/constants";
4
5
  import { initHub } from "@devframes/hub/initiate";
@@ -7,11 +8,11 @@ let recommendedViteDevtools = false;
7
8
  function recommendViteDevtools() {
8
9
  if (recommendedViteDevtools) return;
9
10
  recommendedViteDevtools = true;
10
- console.warn("[@devframes/vite/hub] Serving a devframes-hub directly inside Vite works, but Vite DevTools (`@vitejs/devtools-kit`) integrates the hub protocol natively — prefer it for a first-class, multi-integration experience. Pass `{ quiet: true }` to silence this notice.");
11
+ console.warn("[@devframes/vite/hub] Serving a devframes-hub directly inside Vite works, but Vite DevTools (`@vitejs/devtools-kit`) integrates the hub protocol natively, so prefer it for a first-class, multi-integration experience. Pass `{ quiet: true }` to silence this notice.");
11
12
  }
12
13
  /**
13
- * Mount a whole **devframes-hub** — many integrations under one namespace,
14
- * one merged RPC registry, one WebSocket — inside an existing Vite dev
14
+ * Mount a whole **devframes-hub** (many integrations under one namespace,
15
+ * one merged RPC registry, one WebSocket) inside an existing Vite dev
15
16
  * server. One `initHub()` call, mounted as connect middleware; the WebSocket
16
17
  * shares Vite's own HTTP server (upgrading at `<base>__ws`) unless a `port`
17
18
  * pins a side-car. The UI defaults to `@devframes/hub-ui` (its `embedded.js`
@@ -29,14 +30,20 @@ function viteDevframeHub(options = {}) {
29
30
  let viteConfig;
30
31
  let instance;
31
32
  let embeddedSrc;
33
+ let buildUi;
32
34
  const teardown = async () => {
33
35
  const previous = instance;
34
36
  instance = void 0;
35
37
  await previous?.close().catch(() => {});
36
38
  };
39
+ const resolveUi = async () => options.ui === false ? void 0 : options.ui ?? await loadDefaultUi();
37
40
  return {
38
41
  name: "devframes:hub",
39
- apply: "serve",
42
+ /**
43
+ * Dev middleware by default; a production bundle only carries the hub
44
+ * when the host opts into the static build.
45
+ */
46
+ apply: (_, env) => env.command === "serve" || options.build === true,
40
47
  configResolved(config) {
41
48
  viteConfig = config;
42
49
  },
@@ -44,31 +51,44 @@ function viteDevframeHub(options = {}) {
44
51
  if (!options.quiet) recommendViteDevtools();
45
52
  await teardown();
46
53
  const cwd = options.cwd ?? viteConfig?.root ?? process.cwd();
47
- const ui = options.ui === false ? void 0 : options.ui ?? await loadDefaultUi();
54
+ const ui = await resolveUi();
48
55
  const devframes = attachClientScripts(options.devframes, options.clientScripts);
49
56
  const httpServer = server.httpServer instanceof Server ? server.httpServer : void 0;
50
57
  const hub = initHub({
51
58
  base,
52
59
  cwd,
60
+ /**
61
+ * Bare-specifier client scripts resolve through Vite's own module
62
+ * graph: `/@id/<specifier>` routes the import (and its transitive
63
+ * bare imports) through Vite's resolution and import-analysis.
64
+ */
53
65
  clientModuleResolution: "/@id/{specifier}",
54
66
  origin: options.origin ?? (() => {
55
67
  const resolved = server.resolvedUrls?.local?.[0];
56
68
  return resolved ? new URL(resolved).origin : "";
57
69
  }),
58
70
  auth: options.auth,
71
+ /**
72
+ * Share Vite's own HTTP server for the WS upgrade at `<base>__ws`, with no
73
+ * side-car port to discover. A pinned `port` uses a side-car instead;
74
+ * an https/http2 dev server (non-`node:http`) asks for an auto-port
75
+ * side-car. Clients discover either via `__connection.json`.
76
+ */
59
77
  server: httpServer,
60
- ...options.port != null ? { ws: { port: options.port } } : httpServer ? {} : { ws: { sidecar: true } },
61
- ...options.host != null ? { host: options.host } : {},
78
+ ...resolveWsBinding(options.port, httpServer),
62
79
  ...ui ? { ui } : {},
63
- ...options.renderers ? { renderers: options.renderers } : {},
64
- ...options.rpcDeclarations ? { rpcDeclarations: options.rpcDeclarations } : {},
65
- ...options.mcp != null ? { mcp: options.mcp } : {},
66
- ...options.register != null ? { register: options.register } : {},
67
- ...options.getStorageDir ? { getStorageDir: options.getStorageDir } : {},
68
- ...options.name != null ? { name: options.name } : {},
69
- ...options.version != null ? { version: options.version } : {},
70
80
  devframes,
71
- ...options.configure ? { configure: options.configure } : {}
81
+ ...pickDefined(options, [
82
+ "host",
83
+ "renderers",
84
+ "rpcDeclarations",
85
+ "mcp",
86
+ "register",
87
+ "getStorageDir",
88
+ "name",
89
+ "version",
90
+ "configure"
91
+ ])
72
92
  });
73
93
  instance = hub;
74
94
  embeddedSrc = ui?.embedded ? `${base}embedded.js` : void 0;
@@ -78,6 +98,11 @@ function viteDevframeHub(options = {}) {
78
98
  teardown();
79
99
  });
80
100
  },
101
+ async buildStart() {
102
+ if (viteConfig?.command !== "build" || !options.build) return;
103
+ buildUi = await resolveUi();
104
+ embeddedSrc = buildUi?.embedded ? `${base}embedded.js` : void 0;
105
+ },
81
106
  transformIndexHtml() {
82
107
  if (!embeddedSrc) return;
83
108
  return [{
@@ -91,9 +116,45 @@ function viteDevframeHub(options = {}) {
91
116
  },
92
117
  async closeBundle() {
93
118
  await teardown();
119
+ if (viteConfig?.command !== "build" || !options.build) return;
120
+ const cwd = options.cwd ?? viteConfig.root;
121
+ const { buildHub } = await import("@devframes/hub/build");
122
+ await buildHub({
123
+ base,
124
+ cwd,
125
+ outDir: resolve(viteConfig.root, viteConfig.build.outDir, base.slice(1)),
126
+ devframes: attachClientScripts(options.devframes, options.clientScripts) ?? [],
127
+ ...buildUi ? { ui: buildUi } : {},
128
+ ...pickDefined(options, [
129
+ "renderers",
130
+ "rpcDeclarations",
131
+ "getStorageDir",
132
+ "name",
133
+ "version",
134
+ "configure"
135
+ ])
136
+ });
94
137
  }
95
138
  };
96
139
  }
140
+ /**
141
+ * Share Vite's own HTTP server for the WS upgrade unless a `port` pins a
142
+ * side-car, or the dev server isn't a plain `node:http` server (https/http2),
143
+ * which needs an auto-port side-car.
144
+ */
145
+ function resolveWsBinding(port, httpServer) {
146
+ if (port != null) return { ws: { port } };
147
+ return httpServer ? {} : { ws: { sidecar: true } };
148
+ }
149
+ /** Forward only the options a caller actually set. */
150
+ function pickDefined(source, keys) {
151
+ const out = {};
152
+ for (const key of keys) {
153
+ const value = source[key];
154
+ if (value != null) out[key] = value;
155
+ }
156
+ return out;
157
+ }
97
158
  /** Lazy-load `@devframes/hub-ui`'s default UI so it stays an optional dep. */
98
159
  async function loadDefaultUi() {
99
160
  const { createUi } = await import("@devframes/hub-ui");
package/dist/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
1
  //#region src/index.ts
2
- throw new Error("[@devframes/vite] has no root export. Import from a scoped subpath instead:\n • \"@devframes/vite/single\" — dev-serve one devframe's SPA with Vite\n • \"@devframes/vite/hub\" — mount a devframes-hub inside a Vite app\n");
2
+ throw new Error("[@devframes/vite] has no root export. Import from a scoped subpath instead:\n • \"@devframes/vite/single\": dev-serve one devframe's SPA with Vite\n • \"@devframes/vite/hub\": mount a devframes-hub inside a Vite app\n");
3
3
  //#endregion
4
4
  export {};
package/dist/single.d.mts CHANGED
@@ -1,10 +1,10 @@
1
1
  import { IncomingMessage, ServerResponse } from "node:http";
2
2
  import { Plugin } from "vite";
3
- import { DevframeDefinition, McpRouteOptions } from "devframe";
3
+ import { DevframeDefinition, McpSetting } from "devframe";
4
4
  import { DevframeAuthHandler } from "devframe/node/auth";
5
5
  //#region src/single.d.ts
6
6
  /**
7
- * The slice of a Vite dev server these plugins touch — deliberately
7
+ * The slice of a Vite dev server these plugins touch, deliberately
8
8
  * narrower than Vite's real `ViteDevServer` (which carries the module
9
9
  * graph, watcher, transform pipeline, …) so a host can hand a plugin
10
10
  * anything shaped like this, and a test double only needs to fake two
@@ -18,7 +18,7 @@ interface DevframeViteDevServerLike {
18
18
  };
19
19
  /**
20
20
  * Deliberately structural (just the one event these plugins listen for)
21
- * rather than `NodeHttpServer` — Vite's real `ViteDevServer.httpServer`
21
+ * rather than `NodeHttpServer`: Vite's real `ViteDevServer.httpServer`
22
22
  * is `http.Server | Http2SecureServer | null`, and `Http2SecureServer`
23
23
  * doesn't satisfy `http.Server`'s full shape.
24
24
  */
@@ -26,12 +26,12 @@ interface DevframeViteDevServerLike {
26
26
  once: (event: 'close', listener: () => void) => unknown;
27
27
  } | null;
28
28
  }
29
- /** A devframe Vite plugin — a real Vite `Plugin`, scoped to its `serve`-only hooks. */
29
+ /** A devframe Vite plugin: a real Vite `Plugin`, scoped to its `serve`-only hooks. */
30
30
  type DevframeVitePlugin = Plugin;
31
31
  interface DevframeVitePluginOptions {
32
32
  /**
33
33
  * Mount base. Defaults to `def.basePath ?? '/__<id>/'` for this hosted
34
- * adapter — the devframe shares the origin with the host Vite app.
34
+ * adapter, so the devframe shares the origin with the host Vite app.
35
35
  *
36
36
  * Relative spellings like `'./'` (common for base-agnostic Nuxt builds)
37
37
  * are normalized to absolute paths so they compose with Vite's connect
@@ -42,7 +42,7 @@ interface DevframeVitePluginOptions {
42
42
  /**
43
43
  * Statically mount a devframe's built SPA (`def.clientAssets`) at
44
44
  * `options.base` inside an existing Vite dev server. No RPC server is
45
- * started — reach for {@link devframeViteBridge} when the mounted UI
45
+ * started; reach for {@link devframeViteBridge} when the mounted UI
46
46
  * needs a live RPC/WebSocket connection back to the devframe.
47
47
  *
48
48
  * Use this when the devframe ships its own pre-built UI and the host
@@ -54,7 +54,7 @@ declare function devframeVitePlugin(d: DevframeDefinition, options?: DevframeVit
54
54
  interface DevframeViteBridgeOptions {
55
55
  /**
56
56
  * Mount base. Defaults to `def.basePath ?? '/__<id>/'` for this hosted
57
- * adapter — the devframe shares the origin with the host Vite app.
57
+ * adapter, so the devframe shares the origin with the host Vite app.
58
58
  *
59
59
  * Relative spellings like `'./'` (common for base-agnostic Nuxt builds)
60
60
  * are normalized to absolute paths so they compose with Vite's connect
@@ -86,18 +86,19 @@ interface DevframeViteBridgeOptions {
86
86
  auth?: boolean | DevframeAuthHandler;
87
87
  /**
88
88
  * Expose the bridge's route-based MCP server (Streamable-HTTP) at
89
- * `<base>__mcp` — on the Vite app's own origin — and advertise it in the
89
+ * `<base>__mcp` (on the Vite app's own origin) and advertise it in the
90
90
  * bridge's `__connection.json`. Overrides `def.cli?.mcp`, `undefined`
91
- * falls through to it, `false` disables the route regardless.
91
+ * falls through to it, then to the `'auto'` default (mount once the agent
92
+ * surface is non-empty); `false` disables the route regardless.
92
93
  */
93
- mcp?: boolean | McpRouteOptions;
94
+ mcp?: McpSetting;
94
95
  }
95
96
  /**
96
97
  * Bridge a devframe's RPC + WebSocket backend into an existing Vite dev
97
98
  * server: the host app owns the SPA (`clientAssets` is never mounted), and this
98
99
  * plugin serves discovery (`<base>__connection.json`), the WebSocket RPC
99
100
  * upgrade (`<base>__ws`, shared on Vite's own HTTP server), and the
100
- * optional MCP route through {@link initDevframe}'s node middleware — so
101
+ * optional MCP route through {@link initDevframe}'s node middleware, so
101
102
  * the host-served SPA can discover the endpoint via `connectDevframe`.
102
103
  *
103
104
  * The bridge **gates by default** (devframe's interactive OTP unless the
package/dist/single.mjs CHANGED
@@ -8,7 +8,7 @@ import { join, resolve } from "pathe";
8
8
  /**
9
9
  * Statically mount a devframe's built SPA (`def.clientAssets`) at
10
10
  * `options.base` inside an existing Vite dev server. No RPC server is
11
- * started — reach for {@link devframeViteBridge} when the mounted UI
11
+ * started; reach for {@link devframeViteBridge} when the mounted UI
12
12
  * needs a live RPC/WebSocket connection back to the devframe.
13
13
  *
14
14
  * Use this when the devframe ships its own pre-built UI and the host
@@ -34,7 +34,7 @@ function devframeVitePlugin(d, options = {}) {
34
34
  * server: the host app owns the SPA (`clientAssets` is never mounted), and this
35
35
  * plugin serves discovery (`<base>__connection.json`), the WebSocket RPC
36
36
  * upgrade (`<base>__ws`, shared on Vite's own HTTP server), and the
37
- * optional MCP route through {@link initDevframe}'s node middleware — so
37
+ * optional MCP route through {@link initDevframe}'s node middleware, so
38
38
  * the host-served SPA can discover the endpoint via `connectDevframe`.
39
39
  *
40
40
  * The bridge **gates by default** (devframe's interactive OTP unless the
@@ -62,10 +62,19 @@ function devframeViteBridge(d, options = {}) {
62
62
  try {
63
63
  const created = initDevframe(d, {
64
64
  base,
65
+ /**
66
+ * The host app owns the SPA in bridge mode, so never mount the
67
+ * definition's own client assets here.
68
+ */
65
69
  distDir: false,
66
70
  flags: options.flags,
67
71
  host: options.host,
68
72
  ...options.port != null ? { ws: { port: options.port } } : server.httpServer ? { server: server.httpServer } : { ws: { sidecar: true } },
73
+ /**
74
+ * Gate by default: an unset `auth` defers to the handler
75
+ * (devframe's interactive OTP unless `cli.auth` opts out) rather
76
+ * than leaving the socket ungated. `false` opts out explicitly.
77
+ */
69
78
  auth: options.auth,
70
79
  mcp: options.mcp
71
80
  });
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@devframes/vite",
3
3
  "type": "module",
4
- "version": "0.9.8",
4
+ "version": "0.9.10",
5
5
  "description": "Vite plugin for mounting a devframe inside any Vite-based host (Astro, SolidStart, Nuxt, plain Vite apps).",
6
6
  "author": "Anthony Fu <anthonyfu117@hotmail.com>",
7
7
  "license": "MIT",
@@ -30,9 +30,9 @@
30
30
  "dist"
31
31
  ],
32
32
  "peerDependencies": {
33
- "@devframes/hub": "0.9.8",
34
- "@devframes/hub-ui": "0.9.8",
35
- "devframe": "0.9.8",
33
+ "@devframes/hub": "0.9.10",
34
+ "@devframes/hub-ui": "0.9.10",
35
+ "devframe": "0.9.10",
36
36
  "vite": "^7.0.0 || ^8.0.0"
37
37
  },
38
38
  "peerDependenciesMeta": {
@@ -50,11 +50,11 @@
50
50
  "pathe": "^2.0.3"
51
51
  },
52
52
  "devDependencies": {
53
- "@devframes/hub": "0.9.8",
54
- "@devframes/hub-ui": "0.9.8",
53
+ "@devframes/hub": "0.9.10",
54
+ "@devframes/hub-ui": "0.9.10",
55
55
  "@modelcontextprotocol/client": "^2.0.0",
56
56
  "@types/node": "^26.4.0",
57
- "devframe": "0.9.8",
57
+ "devframe": "0.9.10",
58
58
  "get-port-please": "^3.2.0",
59
59
  "tsdown": "^0.22.14",
60
60
  "vite": "^8.2.2",