@devframes/vite 0.9.0-beta.3

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.
package/LICENSE.md ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025-PRESENT Anthony Fu <https://github.com/antfu>
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,135 @@
1
+ import { IncomingMessage, ServerResponse } from "node:http";
2
+ import { DevframeDefinition, McpRouteOptions } from "devframe";
3
+ import { DevframeAuthHandler } from "devframe/node/auth";
4
+ import { Plugin } from "vite";
5
+ //#region src/dev-spa.d.ts
6
+ /**
7
+ * The slice of a Vite dev server these plugins touch — deliberately
8
+ * narrower than Vite's real `ViteDevServer` (which carries the module
9
+ * graph, watcher, transform pipeline, …) so a host can hand a plugin
10
+ * anything shaped like this, and a test double only needs to fake two
11
+ * fields. A real `ViteDevServer` satisfies this structurally, so each
12
+ * plugin's `configureServer(server: ViteDevServer)` hook (typed against
13
+ * the real `Plugin` below) is still fully type-safe.
14
+ */
15
+ interface DevframeViteDevServerLike {
16
+ middlewares: {
17
+ use: ((path: string, handler: (req: IncomingMessage, res: ServerResponse, next?: (err?: unknown) => void) => void) => void) & ((handler: (req: IncomingMessage, res: ServerResponse, next?: (err?: unknown) => void) => void) => void);
18
+ };
19
+ /**
20
+ * Deliberately structural (just the one event these plugins listen for)
21
+ * rather than `NodeHttpServer` — Vite's real `ViteDevServer.httpServer`
22
+ * is `http.Server | Http2SecureServer | null`, and `Http2SecureServer`
23
+ * doesn't satisfy `http.Server`'s full shape.
24
+ */
25
+ httpServer?: {
26
+ once: (event: 'close', listener: () => void) => unknown;
27
+ } | null;
28
+ }
29
+ /** A devframe Vite plugin — a real Vite `Plugin`, scoped to its `serve`-only hooks. */
30
+ type DevframeVitePlugin = Plugin;
31
+ interface DevframeVitePluginOptions {
32
+ /**
33
+ * Mount base. Defaults to `def.basePath ?? '/__<id>/'` for this hosted
34
+ * adapter — the devframe shares the origin with the host Vite app.
35
+ *
36
+ * Relative spellings like `'./'` (common for base-agnostic Nuxt builds)
37
+ * are normalized to absolute paths so they compose with Vite's connect
38
+ * router.
39
+ */
40
+ base?: string;
41
+ }
42
+ /**
43
+ * Statically mount a devframe's built SPA (`def.cli.distDir`) at
44
+ * `options.base` inside an existing Vite dev server. No RPC server is
45
+ * started — reach for {@link devframeViteBridge} when the mounted UI
46
+ * needs a live RPC/WebSocket connection back to the devframe.
47
+ *
48
+ * Use this when the devframe ships its own pre-built UI and the host
49
+ * only needs to serve it alongside its own app (e.g. a devtools dock
50
+ * whose backend runs elsewhere, or a hub that only wants the static
51
+ * assets).
52
+ */
53
+ declare function devframeVitePlugin(d: DevframeDefinition, options?: DevframeVitePluginOptions): DevframeVitePlugin;
54
+ interface DevframeViteBridgeOptions {
55
+ /**
56
+ * Mount base. Defaults to `def.basePath ?? '/__<id>/'` for this hosted
57
+ * adapter — the devframe shares the origin with the host Vite app.
58
+ *
59
+ * Relative spellings like `'./'` (common for base-agnostic Nuxt builds)
60
+ * are normalized to absolute paths so they compose with Vite's connect
61
+ * router.
62
+ */
63
+ base?: string;
64
+ /**
65
+ * Pin a side-car port for the RPC socket instead of sharing Vite's
66
+ * server. Default: share Vite's HTTP server (side-car only when Vite
67
+ * has none).
68
+ */
69
+ port?: number;
70
+ /** Override the side-car bind host. Default: `def.cli?.host ?? 'localhost'`. */
71
+ host?: string;
72
+ /** Flag bag forwarded to `def.setup(ctx, { flags })`. */
73
+ flags?: Record<string, unknown>;
74
+ /**
75
+ * Whether the bridged devframe runs its own auth gate. The RPC endpoint is
76
+ * reachable by anything that can open its socket, so it **gates by
77
+ * default**: when unset, authentication resolves through devframe's
78
+ * interactive OTP gate (unless the definition's `cli.auth` opts out), and
79
+ * the bridge prints its code/link banner to stdout. Pass a
80
+ * {@link DevframeAuthHandler} to install a custom scheme, or `false` to opt
81
+ * out for a single-user localhost host that owns the trust boundary
82
+ * another way.
83
+ *
84
+ * @default gated (devframe's interactive OTP, unless `cli.auth` opts out)
85
+ */
86
+ auth?: boolean | DevframeAuthHandler;
87
+ /**
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
90
+ * bridge's `__connection.json`. Overrides `def.cli?.mcp`, `undefined`
91
+ * falls through to it, `false` disables the route regardless.
92
+ */
93
+ mcp?: boolean | McpRouteOptions;
94
+ }
95
+ /**
96
+ * Bridge a devframe's RPC + WebSocket backend into an existing Vite dev
97
+ * server: the host app owns the SPA (`distDir` is never mounted), and this
98
+ * plugin serves discovery (`<base>__connection.json`), the WebSocket RPC
99
+ * upgrade (`<base>__ws`, shared on Vite's own HTTP server), and the
100
+ * optional MCP route through {@link initDevframe}'s node middleware — so
101
+ * the host-served SPA can discover the endpoint via `connectDevframe`.
102
+ *
103
+ * The bridge **gates by default** (devframe's interactive OTP unless the
104
+ * definition's `cli.auth` opts out), printing its code/link banner to
105
+ * stdout, so a bridged devframe isn't silently reachable by anything that
106
+ * can open its socket. Pass `options.auth: false` to opt out for a
107
+ * single-user localhost host, or a {@link DevframeAuthHandler} for a
108
+ * custom scheme.
109
+ *
110
+ * Use this when integrating with frameworks that own the SPA (Nuxt, Astro,
111
+ * SolidStart, plain Vite apps). Reach for {@link devframeVitePlugin} instead
112
+ * when the devframe just needs to serve its own pre-built UI with no live
113
+ * backend, or `createCac` (`devframe/adapters/cac`) for the all-in-one
114
+ * `dev` / `build` / `mcp` shell.
115
+ */
116
+ declare function devframeViteBridge(d: DevframeDefinition, options?: DevframeViteBridgeOptions): DevframeVitePlugin;
117
+ interface DevframeViteOptions extends DevframeViteBridgeOptions {
118
+ /**
119
+ * Start devframe's RPC/WS bridge ({@link devframeViteBridge}) instead of
120
+ * statically mounting the built SPA ({@link devframeVitePlugin}).
121
+ *
122
+ * @default false (static mount, no server)
123
+ */
124
+ bridge?: boolean;
125
+ }
126
+ /**
127
+ * Convenience wrapper around {@link devframeVitePlugin} /
128
+ * {@link devframeViteBridge}, picked via `options.bridge`. Reach for the
129
+ * two underlying plugins directly when a devframe needs both mounted at
130
+ * once (e.g. a bridge for RPC alongside a static mount serving its own
131
+ * bundled UI).
132
+ */
133
+ declare function devframeVite(d: DevframeDefinition, options?: DevframeViteOptions): DevframeVitePlugin;
134
+ //#endregion
135
+ export { DevframeViteBridgeOptions, DevframeViteDevServerLike, DevframeViteOptions, DevframeVitePlugin, DevframeVitePluginOptions, devframeVite, devframeViteBridge, devframeVitePlugin };
@@ -0,0 +1,111 @@
1
+ import { initDevframe } from "devframe/initiate";
2
+ import { diagnostics, normalizeBasePath, resolveBasePath } from "devframe/internal";
3
+ import { serveStaticNodeMiddleware } from "devframe/utils/serve-static";
4
+ import { resolve } from "pathe";
5
+ //#region src/dev-spa.ts
6
+ /**
7
+ * Statically mount a devframe's built SPA (`def.cli.distDir`) at
8
+ * `options.base` inside an existing Vite dev server. No RPC server is
9
+ * started — reach for {@link devframeViteBridge} when the mounted UI
10
+ * needs a live RPC/WebSocket connection back to the devframe.
11
+ *
12
+ * Use this when the devframe ships its own pre-built UI and the host
13
+ * only needs to serve it alongside its own app (e.g. a devtools dock
14
+ * whose backend runs elsewhere, or a hub that only wants the static
15
+ * assets).
16
+ */
17
+ function devframeVitePlugin(d, options = {}) {
18
+ const base = normalizeMountBase(options.base ?? resolveBasePath(d, "hosted"));
19
+ const distDir = d.cli?.distDir;
20
+ return {
21
+ name: `devframe:${d.id}`,
22
+ apply: "serve",
23
+ configureServer(server) {
24
+ if (!distDir) return;
25
+ server.middlewares.use(base, serveStaticNodeMiddleware(resolve(distDir)));
26
+ }
27
+ };
28
+ }
29
+ /**
30
+ * Bridge a devframe's RPC + WebSocket backend into an existing Vite dev
31
+ * server: the host app owns the SPA (`distDir` is never mounted), and this
32
+ * plugin serves discovery (`<base>__connection.json`), the WebSocket RPC
33
+ * upgrade (`<base>__ws`, shared on Vite's own HTTP server), and the
34
+ * optional MCP route through {@link initDevframe}'s node middleware — so
35
+ * the host-served SPA can discover the endpoint via `connectDevframe`.
36
+ *
37
+ * The bridge **gates by default** (devframe's interactive OTP unless the
38
+ * definition's `cli.auth` opts out), printing its code/link banner to
39
+ * stdout, so a bridged devframe isn't silently reachable by anything that
40
+ * can open its socket. Pass `options.auth: false` to opt out for a
41
+ * single-user localhost host, or a {@link DevframeAuthHandler} for a
42
+ * custom scheme.
43
+ *
44
+ * Use this when integrating with frameworks that own the SPA (Nuxt, Astro,
45
+ * SolidStart, plain Vite apps). Reach for {@link devframeVitePlugin} instead
46
+ * when the devframe just needs to serve its own pre-built UI with no live
47
+ * backend, or `createCac` (`devframe/adapters/cac`) for the all-in-one
48
+ * `dev` / `build` / `mcp` shell.
49
+ */
50
+ function devframeViteBridge(d, options = {}) {
51
+ const base = normalizeMountBase(options.base ?? resolveBasePath(d, "hosted"));
52
+ let instance;
53
+ return {
54
+ name: `devframe:${d.id}`,
55
+ apply: "serve",
56
+ async configureServer(server) {
57
+ await instance?.close().catch(() => {});
58
+ instance = void 0;
59
+ try {
60
+ const created = initDevframe(d, {
61
+ base,
62
+ distDir: false,
63
+ flags: options.flags,
64
+ host: options.host,
65
+ ...options.port != null ? { ws: { port: options.port } } : server.httpServer ? { server: server.httpServer } : { ws: { sidecar: true } },
66
+ auth: options.auth,
67
+ mcp: options.mcp
68
+ });
69
+ server.middlewares.use(created.nodeMiddleware);
70
+ await created.ready;
71
+ instance = created;
72
+ } catch (e) {
73
+ diagnostics.DF0033({
74
+ id: d.id,
75
+ reason: String(e),
76
+ cause: e
77
+ }, { method: "warn" });
78
+ return;
79
+ }
80
+ server.httpServer?.once("close", () => {
81
+ instance?.close().catch(() => {});
82
+ });
83
+ },
84
+ async closeBundle() {
85
+ await instance?.close().catch(() => {});
86
+ instance = void 0;
87
+ }
88
+ };
89
+ }
90
+ /**
91
+ * Convenience wrapper around {@link devframeVitePlugin} /
92
+ * {@link devframeViteBridge}, picked via `options.bridge`. Reach for the
93
+ * two underlying plugins directly when a devframe needs both mounted at
94
+ * once (e.g. a bridge for RPC alongside a static mount serving its own
95
+ * bundled UI).
96
+ */
97
+ function devframeVite(d, options = {}) {
98
+ const { bridge, ...rest } = options;
99
+ return bridge ? devframeViteBridge(d, rest) : devframeVitePlugin(d, { base: rest.base });
100
+ }
101
+ /**
102
+ * Make `base` safe for `server.middlewares.use(path, …)`. Vite's connect
103
+ * router matches by absolute URL prefix, so relative spellings like
104
+ * `'./'` (commonly used for base-agnostic Nuxt builds) collapse to the
105
+ * origin root before the shared leading/trailing-slash normalization.
106
+ */
107
+ function normalizeMountBase(base) {
108
+ return normalizeBasePath(base.replace(/^\.\/?/, "/"));
109
+ }
110
+ //#endregion
111
+ export { devframeVite, devframeViteBridge, devframeVitePlugin };
@@ -0,0 +1,26 @@
1
+ import { DevframeClientHost, DevframeClientHost as DevframeClientHost$1, DevframeClientHostOptions, DevframeClientHostOptions as DevframeClientHostOptions$1 } from "@devframes/hub/client";
2
+ //#region src/hub-client.d.ts
3
+ interface MountDevframeHubClientOptions extends DevframeClientHostOptions$1 {
4
+ /**
5
+ * Hub mount base to connect to. Forwarded as `connect.baseURL` when no
6
+ * `rpc` / `connect.baseURL` is supplied.
7
+ *
8
+ * @default '/__devframes/'
9
+ */
10
+ base?: string;
11
+ }
12
+ /**
13
+ * Boot the devframes-hub **client runtime** in the host page — the browser
14
+ * half of {@link import('./hub').viteDevframeHub}. Connects RPC to the hub
15
+ * (defaulting `base` to `/__devframes/`), assembles the shared
16
+ * `DevframeClientContext`, and imports each dock's client script into the
17
+ * page. A thin default-applying wrapper over `@devframes/hub/client`'s
18
+ * `createDevframeClientHost`.
19
+ *
20
+ * Only needed when you render your own dock UI (or override the hub's `ui`).
21
+ * With the default `@devframes/hub-ui`, its injected `embedded.js` boots the
22
+ * client for you, so the host page needs no client code.
23
+ */
24
+ declare function mountDevframeHubClient(options?: MountDevframeHubClientOptions): Promise<DevframeClientHost$1>;
25
+ //#endregion
26
+ export { type DevframeClientHost, type DevframeClientHostOptions, MountDevframeHubClientOptions, mountDevframeHubClient };
@@ -0,0 +1,28 @@
1
+ import { createDevframeClientHost } from "@devframes/hub/client";
2
+ //#region src/hub-client.ts
3
+ /** Default hub mount base — mirrors `@devframes/hub`'s `DEVFRAMES_HUB_BASE`. */
4
+ const DEVFRAMES_HUB_BASE = "/__devframes/";
5
+ /**
6
+ * Boot the devframes-hub **client runtime** in the host page — the browser
7
+ * half of {@link import('./hub').viteDevframeHub}. Connects RPC to the hub
8
+ * (defaulting `base` to `/__devframes/`), assembles the shared
9
+ * `DevframeClientContext`, and imports each dock's client script into the
10
+ * page. A thin default-applying wrapper over `@devframes/hub/client`'s
11
+ * `createDevframeClientHost`.
12
+ *
13
+ * Only needed when you render your own dock UI (or override the hub's `ui`).
14
+ * With the default `@devframes/hub-ui`, its injected `embedded.js` boots the
15
+ * client for you, so the host page needs no client code.
16
+ */
17
+ function mountDevframeHubClient(options = {}) {
18
+ const { base = DEVFRAMES_HUB_BASE, connect, ...rest } = options;
19
+ return createDevframeClientHost({
20
+ ...rest,
21
+ ...rest.rpc ? {} : { connect: {
22
+ baseURL: base,
23
+ ...connect
24
+ } }
25
+ });
26
+ }
27
+ //#endregion
28
+ export { mountDevframeHubClient };
package/dist/hub.d.mts ADDED
@@ -0,0 +1,100 @@
1
+ import { DevframeHubUi, DockRendererRegistration, InitHubOptions } from "@devframes/hub/initiate";
2
+ import { Plugin } from "vite";
3
+ import { DevframeHubContext } from "@devframes/hub/node";
4
+ import { ClientScriptEntry } from "@devframes/hub/types";
5
+ //#region src/hub.d.ts
6
+ interface ViteDevframeHubOptions {
7
+ /**
8
+ * Mount base the hub answers under — every frame lives at `<base><id>/`.
9
+ * Default: `/__devframes/`.
10
+ */
11
+ base?: string;
12
+ /**
13
+ * Pin a side-car port for the RPC/WS server. By default the WebSocket
14
+ * shares Vite's own HTTP server, upgrading at `<base>__ws`.
15
+ */
16
+ port?: number;
17
+ /** Bind host for a pinned side-car server. Default: `localhost`. */
18
+ host?: string;
19
+ /**
20
+ * Devframes to mount as docks. Wrap an entry in `{ devframe, dock }` to
21
+ * customize its synthesized iframe dock (category, `frameId`, `subTabs`, …).
22
+ */
23
+ devframes?: InitHubOptions['devframes'];
24
+ /**
25
+ * Per-dock client scripts, keyed by devframe id. Attached to the mounted
26
+ * iframe dock so the hub client runtime imports them into the host page
27
+ * (e.g. the a11y inspector's in-page agent).
28
+ */
29
+ clientScripts?: Record<string, ClientScriptEntry>;
30
+ /**
31
+ * Prebuilt dock-renderer modules forwarded to `initHub({ renderers })` —
32
+ * each served at `<base>__renderers/<type>.mjs` and published in the
33
+ * renderer manifest (e.g. `jsonRenderUiRenderer()` from
34
+ * `@devframes/json-render-ui/hub`).
35
+ */
36
+ renderers?: readonly DockRendererRegistration[];
37
+ /**
38
+ * Extra RPC declarations registered at context creation, alongside the hub
39
+ * built-ins.
40
+ */
41
+ rpcDeclarations?: InitHubOptions['rpcDeclarations'];
42
+ /**
43
+ * Runs once the context exists and every `devframes` entry is mounted —
44
+ * register docks, commands, terminals, and messages surfaces here.
45
+ */
46
+ configure?: (ctx: DevframeHubContext) => void | Promise<void>;
47
+ /**
48
+ * The hub's UI slot. Defaults to `@devframes/hub-ui`'s `createUi()` (the
49
+ * reference floating dock + standalone viewer) when omitted — install
50
+ * `@devframes/hub-ui` for that default. Pass your own {@link DevframeHubUi}
51
+ * to swap the viewer, or `false` for a headless hub (serve your own UI
52
+ * against `@devframes/hub/client`).
53
+ */
54
+ ui?: DevframeHubUi | false;
55
+ /**
56
+ * The hub's single auth gate. Gates by default (interactive OTP); `false`
57
+ * opts out for a single-user localhost host.
58
+ */
59
+ auth?: InitHubOptions['auth'];
60
+ /**
61
+ * Expose the aggregate MCP endpoint at `<base>__mcp`.
62
+ */
63
+ mcp?: InitHubOptions['mcp'];
64
+ /** Publish this hub in the global instance registry. Default: off. */
65
+ register?: InitHubOptions['register'];
66
+ /** Public origin the host app is reachable at, or a getter. */
67
+ origin?: InitHubOptions['origin'];
68
+ /** Working directory for the hub context. Default: Vite's project root. */
69
+ cwd?: string;
70
+ /** Override where persisted devframe state lives. */
71
+ getStorageDir?: InitHubOptions['getStorageDir'];
72
+ /** Name for the hub instance (logs, diagnostics, MCP server). */
73
+ name?: string;
74
+ /** Version for the hub instance (logs, diagnostics, MCP server). */
75
+ version?: string;
76
+ /**
77
+ * Silence the notice recommending Vite DevTools. See {@link viteDevframeHub}.
78
+ *
79
+ * @default false
80
+ */
81
+ quiet?: boolean;
82
+ }
83
+ /**
84
+ * Mount a whole **devframes-hub** — many integrations under one namespace,
85
+ * one merged RPC registry, one WebSocket — inside an existing Vite dev
86
+ * server. One `initHub()` call, mounted as connect middleware; the WebSocket
87
+ * shares Vite's own HTTP server (upgrading at `<base>__ws`) unless a `port`
88
+ * pins a side-car. The UI defaults to `@devframes/hub-ui` (its `embedded.js`
89
+ * bootstrap is injected into the host page automatically); pass `ui` to swap
90
+ * it or `ui: false` for a headless hub you drive with `@devframes/hub/client`.
91
+ *
92
+ * This mounts one integration-agnostic hub. Vite DevTools
93
+ * (`@vitejs/devtools-kit`) integrates the same hub protocol natively and is
94
+ * the recommended path for a Vite app, so this plugin emits a one-time notice
95
+ * to that effect (silence it with `{ quiet: true }`). For dev-serving a
96
+ * single devframe's own SPA, reach for `@devframes/vite/dev-spa` instead.
97
+ */
98
+ declare function viteDevframeHub(options?: ViteDevframeHubOptions): Plugin;
99
+ //#endregion
100
+ export { ViteDevframeHubOptions, viteDevframeHub };
package/dist/hub.mjs ADDED
@@ -0,0 +1,126 @@
1
+ import { Server } from "node:http";
2
+ import process from "node:process";
3
+ import { DEVFRAMES_HUB_BASE, initHub } from "@devframes/hub/initiate";
4
+ //#region src/hub.ts
5
+ let recommendedViteDevtools = false;
6
+ function recommendViteDevtools() {
7
+ if (recommendedViteDevtools) return;
8
+ recommendedViteDevtools = true;
9
+ 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.");
10
+ }
11
+ /**
12
+ * Mount a whole **devframes-hub** — many integrations under one namespace,
13
+ * one merged RPC registry, one WebSocket — inside an existing Vite dev
14
+ * server. One `initHub()` call, mounted as connect middleware; the WebSocket
15
+ * shares Vite's own HTTP server (upgrading at `<base>__ws`) unless a `port`
16
+ * pins a side-car. The UI defaults to `@devframes/hub-ui` (its `embedded.js`
17
+ * bootstrap is injected into the host page automatically); pass `ui` to swap
18
+ * it or `ui: false` for a headless hub you drive with `@devframes/hub/client`.
19
+ *
20
+ * This mounts one integration-agnostic hub. Vite DevTools
21
+ * (`@vitejs/devtools-kit`) integrates the same hub protocol natively and is
22
+ * the recommended path for a Vite app, so this plugin emits a one-time notice
23
+ * to that effect (silence it with `{ quiet: true }`). For dev-serving a
24
+ * single devframe's own SPA, reach for `@devframes/vite/dev-spa` instead.
25
+ */
26
+ function viteDevframeHub(options = {}) {
27
+ const base = normalizeBase(options.base ?? DEVFRAMES_HUB_BASE);
28
+ let viteConfig;
29
+ let instance;
30
+ let embeddedSrc;
31
+ const teardown = async () => {
32
+ const previous = instance;
33
+ instance = void 0;
34
+ await previous?.close().catch(() => {});
35
+ };
36
+ return {
37
+ name: "devframes:hub",
38
+ apply: "serve",
39
+ configResolved(config) {
40
+ viteConfig = config;
41
+ },
42
+ async configureServer(server) {
43
+ if (!options.quiet) recommendViteDevtools();
44
+ await teardown();
45
+ const cwd = options.cwd ?? viteConfig?.root ?? process.cwd();
46
+ const ui = options.ui === false ? void 0 : options.ui ?? await loadDefaultUi();
47
+ const devframes = attachClientScripts(options.devframes, options.clientScripts);
48
+ const httpServer = server.httpServer instanceof Server ? server.httpServer : void 0;
49
+ const hub = initHub({
50
+ base,
51
+ cwd,
52
+ origin: options.origin ?? (() => {
53
+ const resolved = server.resolvedUrls?.local?.[0];
54
+ return resolved ? new URL(resolved).origin : "";
55
+ }),
56
+ auth: options.auth,
57
+ server: httpServer,
58
+ ...options.port != null ? { ws: { port: options.port } } : httpServer ? {} : { ws: { sidecar: true } },
59
+ ...options.host != null ? { host: options.host } : {},
60
+ ...ui ? { ui } : {},
61
+ ...options.renderers ? { renderers: options.renderers } : {},
62
+ ...options.rpcDeclarations ? { rpcDeclarations: options.rpcDeclarations } : {},
63
+ ...options.mcp != null ? { mcp: options.mcp } : {},
64
+ ...options.register != null ? { register: options.register } : {},
65
+ ...options.getStorageDir ? { getStorageDir: options.getStorageDir } : {},
66
+ ...options.name != null ? { name: options.name } : {},
67
+ ...options.version != null ? { version: options.version } : {},
68
+ devframes,
69
+ ...options.configure ? { configure: options.configure } : {}
70
+ });
71
+ instance = hub;
72
+ embeddedSrc = ui?.embedded ? `${base}embedded.js` : void 0;
73
+ server.middlewares.use(hub.nodeMiddleware);
74
+ server.httpServer?.once("close", () => {
75
+ if (instance !== hub) return;
76
+ teardown();
77
+ });
78
+ },
79
+ transformIndexHtml() {
80
+ if (!embeddedSrc) return;
81
+ return [{
82
+ tag: "script",
83
+ attrs: {
84
+ type: "module",
85
+ src: embeddedSrc
86
+ },
87
+ injectTo: "body"
88
+ }];
89
+ },
90
+ async closeBundle() {
91
+ await teardown();
92
+ }
93
+ };
94
+ }
95
+ /** Lazy-load `@devframes/hub-ui`'s default UI so it stays an optional dep. */
96
+ async function loadDefaultUi() {
97
+ const { createUi } = await import("@devframes/hub-ui");
98
+ return createUi();
99
+ }
100
+ /** Attach per-id client scripts to their devframe mount entries. */
101
+ function attachClientScripts(devframes, clientScripts) {
102
+ if (!devframes || !clientScripts) return devframes;
103
+ return devframes.map((entry) => {
104
+ if (typeof entry === "function" || entry instanceof Promise) return entry;
105
+ const def = entry && typeof entry === "object" && "devframe" in entry ? entry.devframe : entry;
106
+ const clientScript = def ? clientScripts[def.id] : void 0;
107
+ if (!def || !clientScript) return entry;
108
+ return "devframe" in entry ? {
109
+ ...entry,
110
+ dock: {
111
+ clientScript,
112
+ ...entry.dock
113
+ }
114
+ } : {
115
+ devframe: def,
116
+ dock: { clientScript }
117
+ };
118
+ });
119
+ }
120
+ function normalizeBase(base) {
121
+ let out = base.startsWith("/") ? base : `/${base}`;
122
+ if (!out.endsWith("/")) out = `${out}/`;
123
+ return out;
124
+ }
125
+ //#endregion
126
+ export { viteDevframeHub };
@@ -0,0 +1 @@
1
+ export {}
package/dist/index.mjs ADDED
@@ -0,0 +1,4 @@
1
+ //#region src/index.ts
2
+ throw new Error("[@devframes/vite] has no root export. Import from a scoped subpath instead:\n • \"@devframes/vite/dev-spa\" — dev-serve one devframe's SPA with Vite\n • \"@devframes/vite/hub\" — mount a devframes-hub inside a Vite app\n");
3
+ //#endregion
4
+ export {};
package/package.json ADDED
@@ -0,0 +1,69 @@
1
+ {
2
+ "name": "@devframes/vite",
3
+ "type": "module",
4
+ "version": "0.9.0-beta.3",
5
+ "description": "Vite plugin for mounting a devframe inside any Vite-based host (Astro, SolidStart, Nuxt, plain Vite apps).",
6
+ "author": "Anthony Fu <anthonyfu117@hotmail.com>",
7
+ "license": "MIT",
8
+ "homepage": "https://github.com/devframes/devframe#readme",
9
+ "repository": {
10
+ "directory": "packages/vite",
11
+ "type": "git",
12
+ "url": "git+https://github.com/devframes/devframe.git"
13
+ },
14
+ "bugs": "https://github.com/devframes/devframe/issues",
15
+ "keywords": [
16
+ "devframe",
17
+ "vite",
18
+ "devtools"
19
+ ],
20
+ "sideEffects": false,
21
+ "exports": {
22
+ ".": "./dist/index.mjs",
23
+ "./dev-spa": "./dist/dev-spa.mjs",
24
+ "./hub": "./dist/hub.mjs",
25
+ "./hub/client": "./dist/hub-client.mjs",
26
+ "./package.json": "./package.json"
27
+ },
28
+ "types": "./dist/index.d.mts",
29
+ "files": [
30
+ "dist"
31
+ ],
32
+ "peerDependencies": {
33
+ "vite": "^7.0.0 || ^8.0.0",
34
+ "@devframes/hub": "0.9.0-beta.3",
35
+ "devframe": "0.9.0-beta.3",
36
+ "@devframes/hub-ui": "0.9.0-beta.3"
37
+ },
38
+ "peerDependenciesMeta": {
39
+ "@devframes/hub": {
40
+ "optional": true
41
+ },
42
+ "@devframes/hub-ui": {
43
+ "optional": true
44
+ },
45
+ "vite": {
46
+ "optional": true
47
+ }
48
+ },
49
+ "dependencies": {
50
+ "pathe": "^2.0.3"
51
+ },
52
+ "devDependencies": {
53
+ "@modelcontextprotocol/client": "^2.0.0",
54
+ "@types/node": "^26.2.0",
55
+ "get-port-please": "^3.2.0",
56
+ "tsdown": "^0.22.14",
57
+ "vite": "^8.2.1",
58
+ "vitest": "^4.1.10",
59
+ "devframe": "0.9.0-beta.3",
60
+ "@devframes/hub-ui": "0.9.0-beta.3",
61
+ "@devframes/hub": "0.9.0-beta.3"
62
+ },
63
+ "scripts": {
64
+ "build": "tsdown",
65
+ "watch": "tsdown --watch",
66
+ "typecheck": "tsc --noEmit",
67
+ "test": "vitest run"
68
+ }
69
+ }