@devframes/hub 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.
- package/dist/assemble-Hj36CCzH.mjs +103 -0
- package/dist/client/index.d.mts +64 -50
- package/dist/client/index.mjs +136 -42
- package/dist/{constants-DJrPlLi0.d.mts → constants-Bz4yclHo.d.mts} +14 -5
- package/dist/{constants-D1zicoJ4.mjs → constants-Da23JxCZ.mjs} +12 -5
- package/dist/constants.d.mts +1 -1
- package/dist/constants.mjs +1 -1
- package/dist/{context-DYUkd3il.mjs → context-BWaW6N53.mjs} +259 -225
- package/dist/{context-BJFGlcf8.d.mts → context-Di-fhMKP.d.mts} +17 -17
- package/dist/{index-OpSX0dK5.d.mts → index-faD2d5iX.d.mts} +2 -2
- package/dist/index.d.mts +3 -3
- package/dist/node/build.d.mts +74 -0
- package/dist/node/build.mjs +167 -0
- package/dist/node/index.d.mts +28 -19
- package/dist/node/index.mjs +3 -3
- package/dist/node/initiate.d.mts +39 -32
- package/dist/node/initiate.mjs +60 -122
- package/dist/{remote-url-Bgc7gtsP.mjs → remote-url-UClKLQ5_.mjs} +34 -39
- package/dist/{settings-DwxCyeI6.d.mts → settings-CWHPzCWS.d.mts} +59 -38
- package/dist/types/index.d.mts +3 -3
- package/package.json +4 -3
- package/dist/{dist-R0T2g9Vw.mjs → dist-BP3CGpgZ.mjs} +1 -1
package/dist/node/initiate.d.mts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { i as InstallDevframeOptions, n as DevframeHubContext, t as CreateHubContextOptions } from "../context-
|
|
2
|
-
import { r as DEVFRAMES_HUB_BASE } from "../constants-
|
|
1
|
+
import { i as InstallDevframeOptions, n as DevframeHubContext, t as CreateHubContextOptions } from "../context-Di-fhMKP.mjs";
|
|
2
|
+
import { r as DEVFRAMES_HUB_BASE } from "../constants-Bz4yclHo.mjs";
|
|
3
3
|
import { DevframeInstanceRecord } from "devframe/internal";
|
|
4
|
-
import { ConnectionMeta, DevframeDefinition, DevframeServiceInput, DevframeSseOptions, DevframeStorageScope, DevframeWsOptions,
|
|
4
|
+
import { ConnectionMeta, DevframeDefinition, DevframeServiceInput, DevframeSseOptions, DevframeStorageScope, DevframeWsOptions, McpSetting } from "devframe/types";
|
|
5
5
|
import { DevframeAuthHandler } from "devframe/node/auth";
|
|
6
6
|
import { WsOriginRegistry } from "devframe/rpc/transports/ws-server";
|
|
7
7
|
import { Buffer } from "node:buffer";
|
|
@@ -20,14 +20,14 @@ type Arrayable<T> = T | readonly T[];
|
|
|
20
20
|
* One dock-type → prebuilt renderer-module registration for
|
|
21
21
|
* {@link InitHubOptions.renderers}. The hub serves the module at
|
|
22
22
|
* `<base>__renderers/<type>.mjs` and publishes it in the renderer manifest
|
|
23
|
-
* (the `devframe:dock-renderers` shared-state slot), so any viewer
|
|
24
|
-
* reference UI, a community viewer, a hand-rolled host page
|
|
23
|
+
* (the `devframe:dock-renderers` shared-state slot), so any viewer (the
|
|
24
|
+
* reference UI, a community viewer, a hand-rolled host page) lazily imports
|
|
25
25
|
* it the first time a dock of that `type` needs rendering.
|
|
26
26
|
*
|
|
27
27
|
* The module must be a **self-contained browser ES module** (its framework
|
|
28
28
|
* and styles bundled in) whose {@link DockRendererRegistration.importName}
|
|
29
29
|
* export is a ready `DockRenderer`. Renderer packages ship a node helper
|
|
30
|
-
* returning this shape
|
|
30
|
+
* returning this shape, e.g. `jsonRenderUiRenderer()` from
|
|
31
31
|
* `@devframes/json-render-ui/hub`.
|
|
32
32
|
*/
|
|
33
33
|
interface DockRendererRegistration {
|
|
@@ -43,7 +43,7 @@ interface DockRendererRegistration {
|
|
|
43
43
|
importName?: string;
|
|
44
44
|
}
|
|
45
45
|
/**
|
|
46
|
-
* The UI slot of a hub instance
|
|
46
|
+
* The UI slot of a hub instance: pure data, zero policy. The hub itself is
|
|
47
47
|
* headless: whoever fills this slot decides what a viewer looks like.
|
|
48
48
|
* `@devframes/hub-ui` ships the reference implementation (`createUi()`);
|
|
49
49
|
* Vite DevTools or any community viewer supplies its own object to the same
|
|
@@ -52,14 +52,14 @@ interface DockRendererRegistration {
|
|
|
52
52
|
interface DevframeHubUi {
|
|
53
53
|
/**
|
|
54
54
|
* A standalone viewer SPA (built with relative asset paths) served at the
|
|
55
|
-
* hub base itself
|
|
55
|
+
* hub base itself; open `<base>` in a tab and the devtools are there.
|
|
56
56
|
*/
|
|
57
57
|
viewer?: {
|
|
58
58
|
/** Directory of the prebuilt viewer SPA. */
|
|
59
59
|
distDir: string;
|
|
60
60
|
};
|
|
61
61
|
/**
|
|
62
|
-
* A prebuilt, self-contained script served at `<base>embedded.js
|
|
62
|
+
* A prebuilt, self-contained script served at `<base>embedded.js`: the
|
|
63
63
|
* floating-devtools bootstrap a host page loads with one
|
|
64
64
|
* `<script type="module" src="<base>embedded.js">` tag. Visibility policy
|
|
65
65
|
* (always-on, keyboard-summoned, …) belongs entirely to this entry.
|
|
@@ -77,8 +77,8 @@ interface DevframeHubUi {
|
|
|
77
77
|
*/
|
|
78
78
|
assets?: Record<string, () => string | Uint8Array>;
|
|
79
79
|
/**
|
|
80
|
-
* A setup hook run once during hub init with the hub context
|
|
81
|
-
* slot
|
|
80
|
+
* A setup hook run once during hub init with the hub context, giving the UI
|
|
81
|
+
* slot a chance to publish its own static, boot-time config through the
|
|
82
82
|
* generic `ctx.staticConfig` (serialized into `ConnectionMeta.configs`).
|
|
83
83
|
* The reference UI's `createUi()` uses it to set
|
|
84
84
|
* `ctx.staticConfig.ui = { branding, … }`, reaching every mounted frame and
|
|
@@ -98,7 +98,7 @@ interface InitHubOptions {
|
|
|
98
98
|
*/
|
|
99
99
|
version?: string;
|
|
100
100
|
/**
|
|
101
|
-
* Mount base the hub answers under
|
|
101
|
+
* Mount base the hub answers under, required so the mount path is
|
|
102
102
|
* explicit at the call site (pass the exported {@link DEVFRAMES_HUB_BASE}
|
|
103
103
|
* for the conventional `/__devframes/`). Every mounted devframe lives at
|
|
104
104
|
* `<base><id>/`, so the host app needs exactly one catch-all route. The
|
|
@@ -118,19 +118,19 @@ interface InitHubOptions {
|
|
|
118
118
|
* Host-level wire services to install, on top of whatever the mounted
|
|
119
119
|
* devframes declare. Constructed (option sets merged) at the pre-setup
|
|
120
120
|
* barrier, so every devframe's `setup` sees them ready. Reach for this to
|
|
121
|
-
* configure a shared service centrally
|
|
121
|
+
* configure a shared service centrally, e.g.
|
|
122
122
|
* `services: [createShikiService({ themes })]`.
|
|
123
123
|
*/
|
|
124
124
|
services?: DevframeServiceInput[];
|
|
125
125
|
/**
|
|
126
126
|
* Extra RPC declarations registered at context creation, alongside the
|
|
127
|
-
* hub built-ins
|
|
127
|
+
* hub built-ins, forwarded to `createHubContext`'s
|
|
128
128
|
* `builtinRpcDeclarations`. Declarative mode only (a pre-built `context`
|
|
129
129
|
* already made this choice).
|
|
130
130
|
*/
|
|
131
131
|
rpcDeclarations?: CreateHubContextOptions['builtinRpcDeclarations'];
|
|
132
132
|
/**
|
|
133
|
-
* Bring your own hub context instead of `devframes
|
|
133
|
+
* Bring your own hub context instead of `devframes`, for hosts that
|
|
134
134
|
* assemble `createHubContext` + `ctx.install` themselves (with their own
|
|
135
135
|
* `DevframeHost` serving the frames). The instance then serves only the
|
|
136
136
|
* hub-level endpoints (`__connection.json`, `__index.json`,
|
|
@@ -139,14 +139,14 @@ interface InitHubOptions {
|
|
|
139
139
|
*/
|
|
140
140
|
context?: DevframeHubContext;
|
|
141
141
|
/**
|
|
142
|
-
* Runs once the context exists and every `devframes` entry is mounted
|
|
142
|
+
* Runs once the context exists and every `devframes` entry is mounted;
|
|
143
143
|
* register docks, commands, terminals, and messages surfaces here.
|
|
144
144
|
*/
|
|
145
145
|
configure?: (ctx: DevframeHubContext) => void | Promise<void>;
|
|
146
|
-
/** See {@link DevframeHubUi}
|
|
146
|
+
/** See {@link DevframeHubUi}; omitted, the hub stays fully headless. */
|
|
147
147
|
ui?: DevframeHubUi;
|
|
148
148
|
/**
|
|
149
|
-
* Prebuilt dock-renderer modules to serve and advertise
|
|
149
|
+
* Prebuilt dock-renderer modules to serve and advertise: the composition
|
|
150
150
|
* seam that hands a renderer package (e.g. `@devframes/json-render-ui`) to
|
|
151
151
|
* a prebuilt viewer. Each {@link DockRendererRegistration} is served at
|
|
152
152
|
* `<base>__renderers/<type>.mjs` and published in the renderer manifest;
|
|
@@ -175,11 +175,11 @@ interface InitHubOptions {
|
|
|
175
175
|
* > `ws.sidecar` (auto-port side-car) > the host driving upgrades itself,
|
|
176
176
|
* while `url` overrides the advertisement (tunnel pattern) and `route`
|
|
177
177
|
* renames the upgrade segment (default `__ws`). Pass `false` to serve no
|
|
178
|
-
* WebSocket at all
|
|
178
|
+
* WebSocket at all, so clients connect over SSE instead (`backend: 'sse'`).
|
|
179
179
|
*/
|
|
180
180
|
ws?: DevframeWsOptions | false;
|
|
181
181
|
/**
|
|
182
|
-
* SSE RPC endpoint control, same contract as `initDevframe
|
|
182
|
+
* SSE RPC endpoint control, same contract as `initDevframe`, enabled by
|
|
183
183
|
* default at `<base>__sse` as the more portable transport alongside the
|
|
184
184
|
* WebSocket. Pass `false` to disable, or a {@link DevframeSseOptions} to
|
|
185
185
|
* rename the route.
|
|
@@ -195,11 +195,18 @@ interface InitHubOptions {
|
|
|
195
195
|
*/
|
|
196
196
|
auth?: boolean | DevframeAuthHandler;
|
|
197
197
|
/**
|
|
198
|
-
* Expose the **aggregate** MCP endpoint at `<base>__mcp
|
|
198
|
+
* Expose the **aggregate** MCP endpoint at `<base>__mcp`: one
|
|
199
199
|
* Streamable-HTTP server over the shared context's whole tool registry
|
|
200
|
-
* (ids are already namespaced per plugin).
|
|
200
|
+
* (ids are already namespaced per plugin). Defaults to `'auto'`: the route
|
|
201
|
+
* mounts once any mounted devframe (or an agent-flagged hub command)
|
|
202
|
+
* exposes an agent surface. `true` mounts it
|
|
203
|
+
* unconditionally with the loopback origin gate (trusting same-machine
|
|
204
|
+
* callers), an object opts into an {@link McpRouteOptions.authorization}
|
|
205
|
+
* identity check, `false` keeps it off. A mounted devframe's own `mcp`
|
|
206
|
+
* setting is ignored: the hub's aggregate route covers them all (`DF8005`
|
|
207
|
+
* warns when one asks for MCP while this is `false`).
|
|
201
208
|
*/
|
|
202
|
-
mcp?:
|
|
209
|
+
mcp?: McpSetting;
|
|
203
210
|
/**
|
|
204
211
|
* Public origin the host app is reachable at, or a getter. Derived lazily
|
|
205
212
|
* from the first request when omitted.
|
|
@@ -217,14 +224,14 @@ interface InitHubOptions {
|
|
|
217
224
|
register?: boolean | Partial<DevframeInstanceRecord>;
|
|
218
225
|
/**
|
|
219
226
|
* Advertise that this host runtime resolves **bare-specifier** client
|
|
220
|
-
* scripts (`ClientScriptEntry.importFrom` naming an npm module)
|
|
227
|
+
* scripts (`ClientScriptEntry.importFrom` naming an npm module): a URL
|
|
221
228
|
* template whose `{specifier}` token is replaced with the specifier,
|
|
222
229
|
* published as `ConnectionMeta.configs.dock.clientModuleResolution` and
|
|
223
230
|
* applied by every client-script loader. Declare it only when the host can
|
|
224
|
-
* actually serve npm modules to the browser
|
|
231
|
+
* actually serve npm modules to the browser; a Vite host passes
|
|
225
232
|
* `'/@id/{specifier}'` to route imports through its own module graph
|
|
226
233
|
* (`@devframes/vite/hub` does). Left undeclared, registering a
|
|
227
|
-
* bare-specifier client script warns `DF8111
|
|
234
|
+
* bare-specifier client script warns `DF8111`; ship a self-contained
|
|
228
235
|
* bundle and pass its URL instead.
|
|
229
236
|
*/
|
|
230
237
|
clientModuleResolution?: string;
|
|
@@ -240,12 +247,12 @@ interface InitHubOptions {
|
|
|
240
247
|
interface HubInstance {
|
|
241
248
|
/**
|
|
242
249
|
* The normalized mount base this hub answers under (leading and trailing
|
|
243
|
-
* slash, e.g. `/__devframes/`). Reference it when wiring the mount
|
|
244
|
-
* guards, middleware path checks
|
|
250
|
+
* slash, e.g. `/__devframes/`). Reference it when wiring the mount (route
|
|
251
|
+
* guards, middleware path checks) instead of repeating the string literal.
|
|
245
252
|
*/
|
|
246
253
|
base: string;
|
|
247
254
|
/**
|
|
248
|
-
* Web-standard request handler for the whole hub
|
|
255
|
+
* Web-standard request handler for the whole hub; mount it on one
|
|
249
256
|
* catch-all route under {@link HubInstance.base}.
|
|
250
257
|
*/
|
|
251
258
|
handler: (request: Request) => Promise<Response>;
|
|
@@ -253,7 +260,7 @@ interface HubInstance {
|
|
|
253
260
|
nodeMiddleware: (req: IncomingMessage, res: ServerResponse, next?: (err?: unknown) => void) => void;
|
|
254
261
|
/**
|
|
255
262
|
* Route a host server's `upgrade` events to the shared RPC socket,
|
|
256
|
-
* returning a detach function
|
|
263
|
+
* returning a detach function, the manual counterpart to the `server`
|
|
257
264
|
* option, for hosts that get their `node:http` server only after the hub
|
|
258
265
|
* exists. Available on the default tier; a configured transport (`server`,
|
|
259
266
|
* `ws.port`, `ws.sidecar`, `ws.url`) already owns the socket and reports
|
|
@@ -276,14 +283,14 @@ interface HubInstance {
|
|
|
276
283
|
close: () => Promise<void>;
|
|
277
284
|
}
|
|
278
285
|
/**
|
|
279
|
-
* Initiate a hub instance
|
|
286
|
+
* Initiate a hub instance: the whole multi-devframe devtools surface
|
|
280
287
|
* behind one framework-agnostic, web-standard handler. Every mounted
|
|
281
288
|
* devframe shares one context (merged RPC registry, shared state, docks /
|
|
282
289
|
* terminals / messages / commands), one WebSocket transport, and one Auth;
|
|
283
290
|
* the instance serves each frame's SPA at `<base><id>/`, the discovery
|
|
284
291
|
* endpoints (`__connection.json`, `__index.json`, `__client-imports.js`),
|
|
285
292
|
* the aggregate MCP route, and whatever the {@link DevframeHubUi} slot
|
|
286
|
-
* provides
|
|
293
|
+
* provides, while the hub itself stays headless.
|
|
287
294
|
*
|
|
288
295
|
* The factory is synchronous and initializes eagerly, and binds no port of
|
|
289
296
|
* its own: the WebSocket follows `server` / `ws` (see
|
package/dist/node/initiate.mjs
CHANGED
|
@@ -1,19 +1,15 @@
|
|
|
1
|
-
import { a as normalizeHubBase, i as DOCK_RENDERERS_STATE_KEY, r as DEVFRAMES_HUB_BASE } from "../constants-
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { joinURL,
|
|
5
|
-
import { DEVFRAME_CONNECTION_META_FILENAME, DEVFRAME_DOCK_IMPORTS_FILENAME, DEVFRAME_MCP_ROUTE
|
|
6
|
-
import { createH3DevframeHost, createInstanceShell, importRuntimeModule, resolveInstanceRegister } from "devframe/internal";
|
|
7
|
-
import { resolve } from "pathe";
|
|
8
|
-
import process from "node:process";
|
|
9
|
-
import { existsSync } from "node:fs";
|
|
1
|
+
import { a as normalizeHubBase, i as DOCK_RENDERERS_STATE_KEY, r as DEVFRAMES_HUB_BASE } from "../constants-Da23JxCZ.mjs";
|
|
2
|
+
import { b as diagnostics, t as createHubContext } from "../context-BWaW6N53.mjs";
|
|
3
|
+
import { i as resolveRendererRegistrations, n as renderClientImportsModule, r as resolveDevframesInput, t as mountDevframes } from "../assemble-Hj36CCzH.mjs";
|
|
4
|
+
import { joinURL, withoutLeadingSlash } from "ufo";
|
|
5
|
+
import { DEVFRAME_CONNECTION_META_FILENAME, DEVFRAME_DOCK_IMPORTS_FILENAME, DEVFRAME_MCP_ROUTE } from "devframe/constants";
|
|
10
6
|
import { readFile } from "node:fs/promises";
|
|
7
|
+
import process from "node:process";
|
|
8
|
+
import { createH3DevframeHost, createInstanceShell, importRuntimeModule, loadAutoMcpAdapter, resolveInstanceRegister, resolveMcpConfig } from "devframe/internal";
|
|
9
|
+
import { resolve } from "pathe";
|
|
11
10
|
import { mountStaticHandler } from "devframe/utils/serve-static";
|
|
12
11
|
import { H3 } from "h3";
|
|
13
12
|
//#region src/node/initiate.ts
|
|
14
|
-
function normalizeDevframeEntry(entry) {
|
|
15
|
-
return "devframe" in entry ? entry : { devframe: entry };
|
|
16
|
-
}
|
|
17
13
|
/** Content-type for a UI asset key, inferred from its file extension. */
|
|
18
14
|
function assetContentType(key) {
|
|
19
15
|
if (key.endsWith(".json")) return "application/json; charset=utf-8";
|
|
@@ -23,81 +19,51 @@ function assetContentType(key) {
|
|
|
23
19
|
if (key.endsWith(".html")) return "text/html; charset=utf-8";
|
|
24
20
|
return "application/octet-stream";
|
|
25
21
|
}
|
|
26
|
-
/** Reserved filenames directly under the hub base — a frame id can't shadow them. */
|
|
27
|
-
const RESERVED_HUB_PATHS = [
|
|
28
|
-
DEVFRAME_CONNECTION_META_FILENAME,
|
|
29
|
-
DEVFRAME_DOCK_IMPORTS_FILENAME,
|
|
30
|
-
DEVFRAME_WS_ROUTE,
|
|
31
|
-
DEVFRAME_MCP_ROUTE,
|
|
32
|
-
"__index.json",
|
|
33
|
-
"__renderers",
|
|
34
|
-
"embedded.js"
|
|
35
|
-
];
|
|
36
22
|
/**
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
* conditionally (`isDev && loadInspect()`) without filtering it first.
|
|
23
|
+
* Resolve the shared hub context: reuse a caller-supplied one, or build an
|
|
24
|
+
* h3-backed context that serves the hub's connection meta under every frame.
|
|
40
25
|
*/
|
|
41
|
-
function
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
26
|
+
async function resolveHubContext(options, app, cwd, api) {
|
|
27
|
+
if (options.context && options.devframes?.length) throw diagnostics.DF8002();
|
|
28
|
+
if (options.context) return options.context;
|
|
29
|
+
const host = {
|
|
30
|
+
...createH3DevframeHost({
|
|
31
|
+
origin: () => api.origin() ?? "http://localhost",
|
|
32
|
+
appName: "devframes",
|
|
33
|
+
workspaceRoot: cwd,
|
|
34
|
+
mount: (mountBase, dir) => {
|
|
35
|
+
mountStaticHandler(app, mountBase, dir);
|
|
36
|
+
}
|
|
37
|
+
}),
|
|
38
|
+
...options.getStorageDir ? { getStorageDir: options.getStorageDir } : {},
|
|
39
|
+
/**
|
|
40
|
+
* Serve the hub's own connection meta under every mounted frame's
|
|
41
|
+
* base, so each SPA discovers the shared RPC endpoint via its
|
|
42
|
+
* relative `./__connection.json` fetch; the meta's WS path is
|
|
43
|
+
* hub-base-absolute, so it resolves to the one shared socket no
|
|
44
|
+
* matter how deep the frame base is.
|
|
45
|
+
*/
|
|
46
|
+
mountConnectionMeta: (frameBase) => {
|
|
47
|
+
app.use(joinURL(frameBase, DEVFRAME_CONNECTION_META_FILENAME), () => api.connectionMeta());
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
return createHubContext({
|
|
51
|
+
cwd,
|
|
52
|
+
workspaceRoot: cwd,
|
|
53
|
+
mode: "dev",
|
|
54
|
+
host,
|
|
55
|
+
...options.rpcDeclarations ? { builtinRpcDeclarations: options.rpcDeclarations } : {}
|
|
67
56
|
});
|
|
68
57
|
}
|
|
69
58
|
/**
|
|
70
|
-
*
|
|
71
|
-
* import thunk per dock that carries a client script (`clientScript` on
|
|
72
|
-
* iframe docks, `action`, `renderer`). External viewers import this module
|
|
73
|
-
* from `<base>__client-imports.js` to load per-dock client code into the
|
|
74
|
-
* host page; `importFrom` values are URL paths the host serves, or bare npm
|
|
75
|
-
* specifiers resolved through the host's `clientModuleResolution` template.
|
|
76
|
-
*/
|
|
77
|
-
function renderClientImportsModule(ctx) {
|
|
78
|
-
const template = ctx.staticConfig.dock?.clientModuleResolution;
|
|
79
|
-
const entries = [];
|
|
80
|
-
for (const [id, view] of ctx.docks.views) {
|
|
81
|
-
const scripts = [];
|
|
82
|
-
const anyView = view;
|
|
83
|
-
if (anyView.clientScript) scripts.push(anyView.clientScript);
|
|
84
|
-
if (anyView.action) scripts.push(anyView.action);
|
|
85
|
-
if (anyView.renderer) scripts.push(anyView.renderer);
|
|
86
|
-
if (scripts.length === 0) continue;
|
|
87
|
-
const thunks = scripts.map((script) => `() => import(${JSON.stringify(resolveClientModuleSpecifier(script.importFrom, { template }))})`);
|
|
88
|
-
entries.push(` ${JSON.stringify(id)}: [${thunks.join(", ")}],`);
|
|
89
|
-
}
|
|
90
|
-
return `// Generated by @devframes/hub — dock client-script import map.\nexport const clientImports = {\n${entries.join("\n")}\n}\nexport default clientImports\n`;
|
|
91
|
-
}
|
|
92
|
-
/**
|
|
93
|
-
* Initiate a hub instance — the whole multi-devframe devtools surface
|
|
59
|
+
* Initiate a hub instance: the whole multi-devframe devtools surface
|
|
94
60
|
* behind one framework-agnostic, web-standard handler. Every mounted
|
|
95
61
|
* devframe shares one context (merged RPC registry, shared state, docks /
|
|
96
62
|
* terminals / messages / commands), one WebSocket transport, and one Auth;
|
|
97
63
|
* the instance serves each frame's SPA at `<base><id>/`, the discovery
|
|
98
64
|
* endpoints (`__connection.json`, `__index.json`, `__client-imports.js`),
|
|
99
65
|
* the aggregate MCP route, and whatever the {@link DevframeHubUi} slot
|
|
100
|
-
* provides
|
|
66
|
+
* provides, while the hub itself stays headless.
|
|
101
67
|
*
|
|
102
68
|
* The factory is synchronous and initializes eagerly, and binds no port of
|
|
103
69
|
* its own: the WebSocket follows `server` / `ws` (see
|
|
@@ -127,9 +93,14 @@ function initHub(options) {
|
|
|
127
93
|
...options.name !== void 0 ? { name: options.name } : {},
|
|
128
94
|
rootDir: cwd
|
|
129
95
|
}),
|
|
96
|
+
/**
|
|
97
|
+
* One meta document is served from the hub base *and* from every frame
|
|
98
|
+
* base, so the advertised WS path has to be base-absolute to resolve to
|
|
99
|
+
* the same socket from any depth.
|
|
100
|
+
*/
|
|
130
101
|
absoluteWsPath: true,
|
|
131
102
|
resolveSidecarPort: async (sidecarHost) => {
|
|
132
|
-
const { getPort } = await import("../dist-
|
|
103
|
+
const { getPort } = await import("../dist-BP3CGpgZ.mjs");
|
|
133
104
|
return getPort({
|
|
134
105
|
port: 9777,
|
|
135
106
|
portRange: [9777, 9877],
|
|
@@ -140,54 +111,14 @@ function initHub(options) {
|
|
|
140
111
|
throw diagnostics.DF8003();
|
|
141
112
|
},
|
|
142
113
|
async init(api) {
|
|
143
|
-
|
|
144
|
-
let ctx;
|
|
145
|
-
if (options.context) ctx = options.context;
|
|
146
|
-
else {
|
|
147
|
-
const host = {
|
|
148
|
-
...createH3DevframeHost({
|
|
149
|
-
origin: () => api.origin() ?? "http://localhost",
|
|
150
|
-
appName: "devframes",
|
|
151
|
-
workspaceRoot: cwd,
|
|
152
|
-
mount: (mountBase, dir) => {
|
|
153
|
-
mountStaticHandler(app, mountBase, dir);
|
|
154
|
-
}
|
|
155
|
-
}),
|
|
156
|
-
...options.getStorageDir ? { getStorageDir: options.getStorageDir } : {},
|
|
157
|
-
mountConnectionMeta: (frameBase) => {
|
|
158
|
-
app.use(joinURL(frameBase, DEVFRAME_CONNECTION_META_FILENAME), () => api.connectionMeta());
|
|
159
|
-
}
|
|
160
|
-
};
|
|
161
|
-
ctx = await createHubContext({
|
|
162
|
-
cwd,
|
|
163
|
-
workspaceRoot: cwd,
|
|
164
|
-
mode: "dev",
|
|
165
|
-
host,
|
|
166
|
-
...options.rpcDeclarations ? { builtinRpcDeclarations: options.rpcDeclarations } : {}
|
|
167
|
-
});
|
|
168
|
-
}
|
|
114
|
+
const ctx = await resolveHubContext(options, app, cwd, api);
|
|
169
115
|
if (options.clientModuleResolution) ctx.staticConfig.dock = {
|
|
170
116
|
...ctx.staticConfig.dock,
|
|
171
117
|
clientModuleResolution: options.clientModuleResolution
|
|
172
118
|
};
|
|
173
119
|
const devframes = await resolveDevframesInput(options.devframes ?? []);
|
|
174
120
|
for (const input of options.services ?? []) ctx.services.install(input);
|
|
175
|
-
const setups =
|
|
176
|
-
for (const { devframe: def, dock } of devframes) {
|
|
177
|
-
if (RESERVED_HUB_PATHS.includes(def.id)) throw diagnostics.DF8000({ id: def.id });
|
|
178
|
-
if (!/^[\w.-]+$/.test(def.id)) throw diagnostics.DF8004({ id: def.id });
|
|
179
|
-
const frameBase = withTrailingSlash(joinURL(base, def.id));
|
|
180
|
-
const run = await prepareDevframe(ctx, def, {
|
|
181
|
-
base: frameBase,
|
|
182
|
-
...dock ? { dock } : {}
|
|
183
|
-
});
|
|
184
|
-
if (run) setups.push(run);
|
|
185
|
-
frames.push({
|
|
186
|
-
id: def.id,
|
|
187
|
-
base: frameBase,
|
|
188
|
-
title: def.name
|
|
189
|
-
});
|
|
190
|
-
}
|
|
121
|
+
const setups = await mountDevframes(ctx, devframes, base, frames, options.mcp !== false);
|
|
191
122
|
await ctx.services.ready();
|
|
192
123
|
for (const run of setups) await run();
|
|
193
124
|
await options.configure?.(ctx);
|
|
@@ -198,14 +129,21 @@ function initHub(options) {
|
|
|
198
129
|
...registration.importName ? { importName: registration.importName } : {}
|
|
199
130
|
};
|
|
200
131
|
(await ctx.rpc.sharedState.get(DOCK_RENDERERS_STATE_KEY, { initialValue: {} })).mutate(() => manifest);
|
|
201
|
-
const
|
|
132
|
+
const mcpSetting = options.mcp ?? "auto";
|
|
133
|
+
let mcpModule;
|
|
134
|
+
let mcpConfig;
|
|
135
|
+
if (mcpSetting === "auto") {
|
|
136
|
+
mcpModule = await loadAutoMcpAdapter(ctx.agent);
|
|
137
|
+
if (mcpModule) mcpConfig = { authorization: false };
|
|
138
|
+
} else mcpConfig = resolveMcpConfig(mcpSetting);
|
|
202
139
|
if (!mcpConfig) return { context: ctx };
|
|
203
140
|
const mcpRoute = withoutLeadingSlash(mcpConfig.path ?? DEVFRAME_MCP_ROUTE);
|
|
204
|
-
|
|
205
|
-
const mounted = mountMcpHttp(app, ctx, joinURL(base, mcpRoute), {
|
|
141
|
+
mcpModule ??= await importRuntimeModule("devframe/adapters/mcp");
|
|
142
|
+
const mounted = mcpModule.mountMcpHttp(app, ctx, joinURL(base, mcpRoute), {
|
|
206
143
|
serverName: options.name ?? "devframes-hub",
|
|
207
144
|
serverVersion: options.version ?? "0.0.0",
|
|
208
145
|
exposeSharedState: true,
|
|
146
|
+
authorization: mcpConfig.authorization,
|
|
209
147
|
allowedOrigins: mcpConfig.allowedOrigins
|
|
210
148
|
});
|
|
211
149
|
return {
|
|
@@ -2,7 +2,7 @@ import { REMOTE_CONNECTION_KEY } from "devframe/constants";
|
|
|
2
2
|
//#region src/client-modules.ts
|
|
3
3
|
const URL_SCHEME_RE = /^[a-z][a-z\d+.-]*:/i;
|
|
4
4
|
/**
|
|
5
|
-
* Whether a client-script `importFrom` is a **bare module specifier
|
|
5
|
+
* Whether a client-script `importFrom` is a **bare module specifier**: an
|
|
6
6
|
* npm-style name (`vite-plugin-vue-tracer/client/vite-devtools`) rather than
|
|
7
7
|
* a URL the browser can resolve natively.
|
|
8
8
|
*/
|
|
@@ -15,10 +15,10 @@ function isBareModuleSpecifier(specifier) {
|
|
|
15
15
|
* specifiers pass through untouched; a **bare** specifier resolves through
|
|
16
16
|
* the explicit `resolveClientModule` callback (a viewer's own policy), then
|
|
17
17
|
* the host-advertised `template` (`ConnectionMeta.configs.dock.clientModuleResolution`,
|
|
18
|
-
* its `{specifier}` token replaced
|
|
18
|
+
* its `{specifier}` token replaced, e.g. Vite's `'/@id/{specifier}'`),
|
|
19
19
|
* resolved against `metaBaseUrl` so it targets the host's origin from any
|
|
20
|
-
* viewer. With neither, it stays unchanged
|
|
21
|
-
* cover it.
|
|
20
|
+
* viewer. With neither, it stays unchanged, since a page import map may
|
|
21
|
+
* still cover it.
|
|
22
22
|
*/
|
|
23
23
|
function resolveClientModuleSpecifier(specifier, options = {}) {
|
|
24
24
|
if (!isBareModuleSpecifier(specifier)) return specifier;
|
|
@@ -40,8 +40,8 @@ function resolveClientModuleSpecifier(specifier, options = {}) {
|
|
|
40
40
|
*/
|
|
41
41
|
function clientScriptFailureHint(importFrom, specifier) {
|
|
42
42
|
if (!isBareModuleSpecifier(importFrom)) return "";
|
|
43
|
-
if (specifier === importFrom) return "
|
|
44
|
-
return
|
|
43
|
+
if (specifier === importFrom) return ". The specifier is a bare npm specifier and this host advertises no client-module resolution (`ConnectionMeta.configs.dock.clientModuleResolution`). Serve the script as a self-contained bundle by URL, pass `resolveClientModule` to this client host, or run under a host that resolves bare specifiers (e.g. Vite: `/@id/{specifier}`).";
|
|
44
|
+
return `. The host resolved the bare specifier "${importFrom}" to this URL but could not serve the module. Check the package is installed and resolvable from the host project's root (and built, if its exports point at build output).`;
|
|
45
45
|
}
|
|
46
46
|
//#endregion
|
|
47
47
|
//#region src/remote-url.ts
|
|
@@ -77,42 +77,37 @@ function buildRemoteConnectionUrl(baseUrl, payload, transport = "fragment") {
|
|
|
77
77
|
const beforeHash = hashIdx === -1 ? baseUrl : baseUrl.slice(0, hashIdx);
|
|
78
78
|
return `${beforeHash}${beforeHash.includes("?") ? "&" : "?"}${param}${hash}`;
|
|
79
79
|
}
|
|
80
|
+
function stripParamList(value) {
|
|
81
|
+
const parts = value.split("&");
|
|
82
|
+
const filtered = parts.filter((part) => part.split("=")[0] !== REMOTE_CONNECTION_KEY);
|
|
83
|
+
return [filtered.join("&"), filtered.length !== parts.length];
|
|
84
|
+
}
|
|
85
|
+
/** Strip the descriptor from a URL fragment, preserving its route/query split. */
|
|
86
|
+
function stripHashParams(hash) {
|
|
87
|
+
const routeQueryIdx = hash.indexOf("?");
|
|
88
|
+
if (routeQueryIdx === -1) return stripParamList(hash);
|
|
89
|
+
const route = hash.slice(0, routeQueryIdx);
|
|
90
|
+
const [query, changed] = stripParamList(hash.slice(routeQueryIdx + 1));
|
|
91
|
+
if (!changed) return [hash, false];
|
|
92
|
+
return [query ? `${route}?${query}` : route, true];
|
|
93
|
+
}
|
|
94
|
+
/** Strip the descriptor from a URL query string (the part before any fragment). */
|
|
95
|
+
function stripQueryParams(beforeHash) {
|
|
96
|
+
const queryIdx = beforeHash.indexOf("?");
|
|
97
|
+
if (queryIdx === -1) return [beforeHash, false];
|
|
98
|
+
const path = beforeHash.slice(0, queryIdx);
|
|
99
|
+
const [query, changed] = stripParamList(beforeHash.slice(queryIdx + 1));
|
|
100
|
+
if (!changed) return [beforeHash, false];
|
|
101
|
+
return [query ? `${path}?${query}` : path, true];
|
|
102
|
+
}
|
|
80
103
|
/** Remove the remote connection descriptor from a URL before displaying or copying it. */
|
|
81
104
|
function stripRemoteConnectionFromUrl(input) {
|
|
82
|
-
function stripParamList(value) {
|
|
83
|
-
const parts = value.split("&");
|
|
84
|
-
const filtered = parts.filter((part) => part.split("=")[0] !== REMOTE_CONNECTION_KEY);
|
|
85
|
-
return [filtered.join("&"), filtered.length !== parts.length];
|
|
86
|
-
}
|
|
87
105
|
const hashIdx = input.indexOf("#");
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
if (routeQueryIdx === -1) {
|
|
94
|
-
const [nextHash, didChange] = stripParamList(hash);
|
|
95
|
-
hash = nextHash;
|
|
96
|
-
changed ||= didChange;
|
|
97
|
-
} else {
|
|
98
|
-
const route = hash.slice(0, routeQueryIdx);
|
|
99
|
-
const [query, didChange] = stripParamList(hash.slice(routeQueryIdx + 1));
|
|
100
|
-
if (didChange) {
|
|
101
|
-
hash = query ? `${route}?${query}` : route;
|
|
102
|
-
changed = true;
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
const queryIdx = beforeHash.indexOf("?");
|
|
107
|
-
if (queryIdx !== -1) {
|
|
108
|
-
const path = beforeHash.slice(0, queryIdx);
|
|
109
|
-
const [query, didChange] = stripParamList(beforeHash.slice(queryIdx + 1));
|
|
110
|
-
if (didChange) {
|
|
111
|
-
beforeHash = query ? `${path}?${query}` : path;
|
|
112
|
-
changed = true;
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
if (!changed) return input;
|
|
106
|
+
const rawHash = hashIdx === -1 ? void 0 : input.slice(hashIdx + 1);
|
|
107
|
+
const rawBeforeHash = hashIdx === -1 ? input : input.slice(0, hashIdx);
|
|
108
|
+
const [hash, hashChanged] = rawHash === void 0 ? [void 0, false] : stripHashParams(rawHash);
|
|
109
|
+
const [beforeHash, queryChanged] = stripQueryParams(rawBeforeHash);
|
|
110
|
+
if (!hashChanged && !queryChanged) return input;
|
|
116
111
|
return hash ? `${beforeHash}#${hash}` : beforeHash;
|
|
117
112
|
}
|
|
118
113
|
//#endregion
|