@devframes/vite 0.9.0-beta.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.
- package/LICENSE.md +21 -0
- package/dist/hub-client.d.mts +26 -0
- package/dist/hub-client.mjs +27 -0
- package/dist/hub.d.mts +100 -0
- package/dist/hub.mjs +122 -0
- package/dist/index.d.mts +1 -0
- package/dist/index.mjs +4 -0
- package/dist/single.d.mts +135 -0
- package/dist/single.mjs +114 -0
- package/package.json +69 -0
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,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,27 @@
|
|
|
1
|
+
import { createDevframeClientHost } from "@devframes/hub/client";
|
|
2
|
+
import { DEVFRAMES_HUB_BASE } from "@devframes/hub/constants";
|
|
3
|
+
//#region src/hub-client.ts
|
|
4
|
+
/**
|
|
5
|
+
* Boot the devframes-hub **client runtime** in the host page — the browser
|
|
6
|
+
* half of {@link import('./hub').viteDevframeHub}. Connects RPC to the hub
|
|
7
|
+
* (defaulting `base` to `/__devframes/`), assembles the shared
|
|
8
|
+
* `DevframeClientContext`, and imports each dock's client script into the
|
|
9
|
+
* page. A thin default-applying wrapper over `@devframes/hub/client`'s
|
|
10
|
+
* `createDevframeClientHost`.
|
|
11
|
+
*
|
|
12
|
+
* Only needed when you render your own dock UI (or override the hub's `ui`).
|
|
13
|
+
* With the default `@devframes/hub-ui`, its injected `embedded.js` boots the
|
|
14
|
+
* client for you, so the host page needs no client code.
|
|
15
|
+
*/
|
|
16
|
+
function mountDevframeHubClient(options = {}) {
|
|
17
|
+
const { base = DEVFRAMES_HUB_BASE, connect, ...rest } = options;
|
|
18
|
+
return createDevframeClientHost({
|
|
19
|
+
...rest,
|
|
20
|
+
...rest.rpc ? {} : { connect: {
|
|
21
|
+
baseURL: base,
|
|
22
|
+
...connect
|
|
23
|
+
} }
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
//#endregion
|
|
27
|
+
export { mountDevframeHubClient };
|
package/dist/hub.d.mts
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { DevframeHubUi, DockRendererRegistration, InitHubOptions } from "@devframes/hub/initiate";
|
|
2
|
+
import { DevframeHubContext } from "@devframes/hub/node";
|
|
3
|
+
import { ClientScriptEntry } from "@devframes/hub/types";
|
|
4
|
+
import { Plugin } from "vite";
|
|
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/single` instead.
|
|
97
|
+
*/
|
|
98
|
+
declare function viteDevframeHub(options?: ViteDevframeHubOptions): Plugin;
|
|
99
|
+
//#endregion
|
|
100
|
+
export { ViteDevframeHubOptions, viteDevframeHub };
|
package/dist/hub.mjs
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import process from "node:process";
|
|
2
|
+
import { Server } from "node:http";
|
|
3
|
+
import { DEVFRAMES_HUB_BASE, normalizeHubBase } from "@devframes/hub/constants";
|
|
4
|
+
import { initHub } from "@devframes/hub/initiate";
|
|
5
|
+
//#region src/hub.ts
|
|
6
|
+
let recommendedViteDevtools = false;
|
|
7
|
+
function recommendViteDevtools() {
|
|
8
|
+
if (recommendedViteDevtools) return;
|
|
9
|
+
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
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Mount a whole **devframes-hub** — many integrations under one namespace,
|
|
14
|
+
* one merged RPC registry, one WebSocket — inside an existing Vite dev
|
|
15
|
+
* server. One `initHub()` call, mounted as connect middleware; the WebSocket
|
|
16
|
+
* shares Vite's own HTTP server (upgrading at `<base>__ws`) unless a `port`
|
|
17
|
+
* pins a side-car. The UI defaults to `@devframes/hub-ui` (its `embedded.js`
|
|
18
|
+
* bootstrap is injected into the host page automatically); pass `ui` to swap
|
|
19
|
+
* it or `ui: false` for a headless hub you drive with `@devframes/hub/client`.
|
|
20
|
+
*
|
|
21
|
+
* This mounts one integration-agnostic hub. Vite DevTools
|
|
22
|
+
* (`@vitejs/devtools-kit`) integrates the same hub protocol natively and is
|
|
23
|
+
* the recommended path for a Vite app, so this plugin emits a one-time notice
|
|
24
|
+
* to that effect (silence it with `{ quiet: true }`). For dev-serving a
|
|
25
|
+
* single devframe's own SPA, reach for `@devframes/vite/single` instead.
|
|
26
|
+
*/
|
|
27
|
+
function viteDevframeHub(options = {}) {
|
|
28
|
+
const base = normalizeHubBase(options.base ?? DEVFRAMES_HUB_BASE);
|
|
29
|
+
let viteConfig;
|
|
30
|
+
let instance;
|
|
31
|
+
let embeddedSrc;
|
|
32
|
+
const teardown = async () => {
|
|
33
|
+
const previous = instance;
|
|
34
|
+
instance = void 0;
|
|
35
|
+
await previous?.close().catch(() => {});
|
|
36
|
+
};
|
|
37
|
+
return {
|
|
38
|
+
name: "devframes:hub",
|
|
39
|
+
apply: "serve",
|
|
40
|
+
configResolved(config) {
|
|
41
|
+
viteConfig = config;
|
|
42
|
+
},
|
|
43
|
+
async configureServer(server) {
|
|
44
|
+
if (!options.quiet) recommendViteDevtools();
|
|
45
|
+
await teardown();
|
|
46
|
+
const cwd = options.cwd ?? viteConfig?.root ?? process.cwd();
|
|
47
|
+
const ui = options.ui === false ? void 0 : options.ui ?? await loadDefaultUi();
|
|
48
|
+
const devframes = attachClientScripts(options.devframes, options.clientScripts);
|
|
49
|
+
const httpServer = server.httpServer instanceof Server ? server.httpServer : void 0;
|
|
50
|
+
const hub = initHub({
|
|
51
|
+
base,
|
|
52
|
+
cwd,
|
|
53
|
+
origin: options.origin ?? (() => {
|
|
54
|
+
const resolved = server.resolvedUrls?.local?.[0];
|
|
55
|
+
return resolved ? new URL(resolved).origin : "";
|
|
56
|
+
}),
|
|
57
|
+
auth: options.auth,
|
|
58
|
+
server: httpServer,
|
|
59
|
+
...options.port != null ? { ws: { port: options.port } } : httpServer ? {} : { ws: { sidecar: true } },
|
|
60
|
+
...options.host != null ? { host: options.host } : {},
|
|
61
|
+
...ui ? { ui } : {},
|
|
62
|
+
...options.renderers ? { renderers: options.renderers } : {},
|
|
63
|
+
...options.rpcDeclarations ? { rpcDeclarations: options.rpcDeclarations } : {},
|
|
64
|
+
...options.mcp != null ? { mcp: options.mcp } : {},
|
|
65
|
+
...options.register != null ? { register: options.register } : {},
|
|
66
|
+
...options.getStorageDir ? { getStorageDir: options.getStorageDir } : {},
|
|
67
|
+
...options.name != null ? { name: options.name } : {},
|
|
68
|
+
...options.version != null ? { version: options.version } : {},
|
|
69
|
+
devframes,
|
|
70
|
+
...options.configure ? { configure: options.configure } : {}
|
|
71
|
+
});
|
|
72
|
+
instance = hub;
|
|
73
|
+
embeddedSrc = ui?.embedded ? `${base}embedded.js` : void 0;
|
|
74
|
+
server.middlewares.use(hub.nodeMiddleware);
|
|
75
|
+
server.httpServer?.once("close", () => {
|
|
76
|
+
if (instance !== hub) return;
|
|
77
|
+
teardown();
|
|
78
|
+
});
|
|
79
|
+
},
|
|
80
|
+
transformIndexHtml() {
|
|
81
|
+
if (!embeddedSrc) return;
|
|
82
|
+
return [{
|
|
83
|
+
tag: "script",
|
|
84
|
+
attrs: {
|
|
85
|
+
type: "module",
|
|
86
|
+
src: embeddedSrc
|
|
87
|
+
},
|
|
88
|
+
injectTo: "body"
|
|
89
|
+
}];
|
|
90
|
+
},
|
|
91
|
+
async closeBundle() {
|
|
92
|
+
await teardown();
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
/** Lazy-load `@devframes/hub-ui`'s default UI so it stays an optional dep. */
|
|
97
|
+
async function loadDefaultUi() {
|
|
98
|
+
const { createUi } = await import("@devframes/hub-ui");
|
|
99
|
+
return createUi();
|
|
100
|
+
}
|
|
101
|
+
/** Attach per-id client scripts to their devframe mount entries. */
|
|
102
|
+
function attachClientScripts(devframes, clientScripts) {
|
|
103
|
+
if (!devframes || !clientScripts) return devframes;
|
|
104
|
+
return devframes.map((entry) => {
|
|
105
|
+
if (typeof entry === "function" || entry instanceof Promise) return entry;
|
|
106
|
+
const def = entry && typeof entry === "object" && "devframe" in entry ? entry.devframe : entry;
|
|
107
|
+
const clientScript = def ? clientScripts[def.id] : void 0;
|
|
108
|
+
if (!def || !clientScript) return entry;
|
|
109
|
+
return "devframe" in entry ? {
|
|
110
|
+
...entry,
|
|
111
|
+
dock: {
|
|
112
|
+
clientScript,
|
|
113
|
+
...entry.dock
|
|
114
|
+
}
|
|
115
|
+
} : {
|
|
116
|
+
devframe: def,
|
|
117
|
+
dock: { clientScript }
|
|
118
|
+
};
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
//#endregion
|
|
122
|
+
export { viteDevframeHub };
|
package/dist/index.d.mts
ADDED
|
@@ -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/single\" — 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 {};
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { IncomingMessage, ServerResponse } from "node:http";
|
|
2
|
+
import { Plugin } from "vite";
|
|
3
|
+
import { DevframeDefinition, McpRouteOptions } from "devframe";
|
|
4
|
+
import { DevframeAuthHandler } from "devframe/node/auth";
|
|
5
|
+
//#region src/single.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 };
|
package/dist/single.mjs
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import process from "node:process";
|
|
2
|
+
import { initDevframe } from "devframe/initiate";
|
|
3
|
+
import { diagnostics, normalizeBasePath, resolveBasePath } from "devframe/internal";
|
|
4
|
+
import { resolveStaticAssetsSource } from "devframe/utils/remote-assets";
|
|
5
|
+
import { serveStaticNodeMiddleware } from "devframe/utils/serve-static";
|
|
6
|
+
import { join, resolve } from "pathe";
|
|
7
|
+
//#region src/single.ts
|
|
8
|
+
/**
|
|
9
|
+
* Statically mount a devframe's built SPA (`def.cli.distDir`) at
|
|
10
|
+
* `options.base` inside an existing Vite dev server. No RPC server is
|
|
11
|
+
* started — reach for {@link devframeViteBridge} when the mounted UI
|
|
12
|
+
* needs a live RPC/WebSocket connection back to the devframe.
|
|
13
|
+
*
|
|
14
|
+
* Use this when the devframe ships its own pre-built UI and the host
|
|
15
|
+
* only needs to serve it alongside its own app (e.g. a devtools dock
|
|
16
|
+
* whose backend runs elsewhere, or a hub that only wants the static
|
|
17
|
+
* assets).
|
|
18
|
+
*/
|
|
19
|
+
function devframeVitePlugin(d, options = {}) {
|
|
20
|
+
const base = normalizeMountBase(options.base ?? resolveBasePath(d, "hosted"));
|
|
21
|
+
const distDir = d.cli?.distDir;
|
|
22
|
+
return {
|
|
23
|
+
name: `devframe:${d.id}`,
|
|
24
|
+
apply: "serve",
|
|
25
|
+
configureServer(server) {
|
|
26
|
+
if (!distDir) return;
|
|
27
|
+
const source = resolveStaticAssetsSource(distDir, join(process.cwd(), "node_modules", `.${d.id}`, "devframe"));
|
|
28
|
+
server.middlewares.use(base, serveStaticNodeMiddleware(typeof source === "string" ? resolve(source) : source));
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Bridge a devframe's RPC + WebSocket backend into an existing Vite dev
|
|
34
|
+
* server: the host app owns the SPA (`distDir` is never mounted), and this
|
|
35
|
+
* plugin serves discovery (`<base>__connection.json`), the WebSocket RPC
|
|
36
|
+
* upgrade (`<base>__ws`, shared on Vite's own HTTP server), and the
|
|
37
|
+
* optional MCP route through {@link initDevframe}'s node middleware — so
|
|
38
|
+
* the host-served SPA can discover the endpoint via `connectDevframe`.
|
|
39
|
+
*
|
|
40
|
+
* The bridge **gates by default** (devframe's interactive OTP unless the
|
|
41
|
+
* definition's `cli.auth` opts out), printing its code/link banner to
|
|
42
|
+
* stdout, so a bridged devframe isn't silently reachable by anything that
|
|
43
|
+
* can open its socket. Pass `options.auth: false` to opt out for a
|
|
44
|
+
* single-user localhost host, or a {@link DevframeAuthHandler} for a
|
|
45
|
+
* custom scheme.
|
|
46
|
+
*
|
|
47
|
+
* Use this when integrating with frameworks that own the SPA (Nuxt, Astro,
|
|
48
|
+
* SolidStart, plain Vite apps). Reach for {@link devframeVitePlugin} instead
|
|
49
|
+
* when the devframe just needs to serve its own pre-built UI with no live
|
|
50
|
+
* backend, or `createCac` (`devframe/adapters/cac`) for the all-in-one
|
|
51
|
+
* `dev` / `build` / `mcp` shell.
|
|
52
|
+
*/
|
|
53
|
+
function devframeViteBridge(d, options = {}) {
|
|
54
|
+
const base = normalizeMountBase(options.base ?? resolveBasePath(d, "hosted"));
|
|
55
|
+
let instance;
|
|
56
|
+
return {
|
|
57
|
+
name: `devframe:${d.id}`,
|
|
58
|
+
apply: "serve",
|
|
59
|
+
async configureServer(server) {
|
|
60
|
+
await instance?.close().catch(() => {});
|
|
61
|
+
instance = void 0;
|
|
62
|
+
try {
|
|
63
|
+
const created = initDevframe(d, {
|
|
64
|
+
base,
|
|
65
|
+
distDir: false,
|
|
66
|
+
flags: options.flags,
|
|
67
|
+
host: options.host,
|
|
68
|
+
...options.port != null ? { ws: { port: options.port } } : server.httpServer ? { server: server.httpServer } : { ws: { sidecar: true } },
|
|
69
|
+
auth: options.auth,
|
|
70
|
+
mcp: options.mcp
|
|
71
|
+
});
|
|
72
|
+
server.middlewares.use(created.nodeMiddleware);
|
|
73
|
+
await created.ready;
|
|
74
|
+
instance = created;
|
|
75
|
+
} catch (e) {
|
|
76
|
+
diagnostics.DF0033({
|
|
77
|
+
id: d.id,
|
|
78
|
+
reason: String(e),
|
|
79
|
+
cause: e
|
|
80
|
+
}, { method: "warn" });
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
server.httpServer?.once("close", () => {
|
|
84
|
+
instance?.close().catch(() => {});
|
|
85
|
+
});
|
|
86
|
+
},
|
|
87
|
+
async closeBundle() {
|
|
88
|
+
await instance?.close().catch(() => {});
|
|
89
|
+
instance = void 0;
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Convenience wrapper around {@link devframeVitePlugin} /
|
|
95
|
+
* {@link devframeViteBridge}, picked via `options.bridge`. Reach for the
|
|
96
|
+
* two underlying plugins directly when a devframe needs both mounted at
|
|
97
|
+
* once (e.g. a bridge for RPC alongside a static mount serving its own
|
|
98
|
+
* bundled UI).
|
|
99
|
+
*/
|
|
100
|
+
function devframeVite(d, options = {}) {
|
|
101
|
+
const { bridge, ...rest } = options;
|
|
102
|
+
return bridge ? devframeViteBridge(d, rest) : devframeVitePlugin(d, { base: rest.base });
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Make `base` safe for `server.middlewares.use(path, …)`. Vite's connect
|
|
106
|
+
* router matches by absolute URL prefix, so relative spellings like
|
|
107
|
+
* `'./'` (commonly used for base-agnostic Nuxt builds) collapse to the
|
|
108
|
+
* origin root before the shared leading/trailing-slash normalization.
|
|
109
|
+
*/
|
|
110
|
+
function normalizeMountBase(base) {
|
|
111
|
+
return normalizeBasePath(base.replace(/^\.\/?/, "/"));
|
|
112
|
+
}
|
|
113
|
+
//#endregion
|
|
114
|
+
export { devframeVite, devframeViteBridge, devframeVitePlugin };
|
package/package.json
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@devframes/vite",
|
|
3
|
+
"type": "module",
|
|
4
|
+
"version": "0.9.0-beta.10",
|
|
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
|
+
"./single": "./dist/single.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-ui": "0.9.0-beta.10",
|
|
35
|
+
"@devframes/hub": "0.9.0-beta.10",
|
|
36
|
+
"devframe": "0.9.0-beta.10"
|
|
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
|
+
"@devframes/hub": "0.9.0-beta.10",
|
|
60
|
+
"@devframes/hub-ui": "0.9.0-beta.10",
|
|
61
|
+
"devframe": "0.9.0-beta.10"
|
|
62
|
+
},
|
|
63
|
+
"scripts": {
|
|
64
|
+
"build": "tsdown",
|
|
65
|
+
"watch": "tsdown --watch",
|
|
66
|
+
"typecheck": "tsc --noEmit",
|
|
67
|
+
"test": "vitest run"
|
|
68
|
+
}
|
|
69
|
+
}
|