@devframes/hub 0.9.9 → 0.9.11

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.
@@ -0,0 +1,99 @@
1
+ import { a as resolveClientModuleSpecifier } from "./remote-url-UClKLQ5_.mjs";
2
+ import { v as prepareDevframe, y as diagnostics } from "./context-Cdrtg-Kk.mjs";
3
+ import { joinURL, withTrailingSlash } from "ufo";
4
+ import { DEVFRAME_CONNECTION_META_FILENAME, DEVFRAME_DOCK_IMPORTS_FILENAME, DEVFRAME_MCP_ROUTE, DEVFRAME_WS_ROUTE } from "devframe/constants";
5
+ import { existsSync } from "node:fs";
6
+ import { resolve } from "pathe";
7
+ //#region src/node/assemble.ts
8
+ /** Reserved filenames directly under the hub base; a frame id can't shadow them. */
9
+ const RESERVED_HUB_PATHS = [
10
+ DEVFRAME_CONNECTION_META_FILENAME,
11
+ DEVFRAME_DOCK_IMPORTS_FILENAME,
12
+ DEVFRAME_WS_ROUTE,
13
+ DEVFRAME_MCP_ROUTE,
14
+ "__index.json",
15
+ "__renderers",
16
+ "embedded.js"
17
+ ];
18
+ function normalizeDevframeEntry(entry) {
19
+ return "devframe" in entry ? entry : { devframe: entry };
20
+ }
21
+ /**
22
+ * Flatten the `devframes` input: await every thenable, call every factory,
23
+ * spread the arrays, and drop the empty slots, so a host can build the list
24
+ * conditionally (`isDev && loadInspect()`) without filtering it first.
25
+ */
26
+ function resolveDevframesInput(input) {
27
+ return Promise.all(input.map(async (entry) => {
28
+ const resolved = await (typeof entry === "function" ? entry() : entry);
29
+ return (Array.isArray(resolved) ? resolved : [resolved]).filter((slot) => slot != null).map(normalizeDevframeEntry);
30
+ })).then((arrays) => arrays.flat());
31
+ }
32
+ /**
33
+ * Validate the renderer-module registrations fail-fast: route-safe types
34
+ * (each becomes the `<base>__renderers/<type>.mjs` URL segment), one module
35
+ * per type, and an existing bundle file (renderer packages are prebuilt).
36
+ */
37
+ function resolveRendererRegistrations(registrations) {
38
+ const seen = /* @__PURE__ */ new Set();
39
+ return registrations.map((registration) => {
40
+ if (!/^[\w.-]+$/.test(registration.type)) throw diagnostics.DF8110({ type: registration.type });
41
+ if (seen.has(registration.type)) throw diagnostics.DF8108({ type: registration.type });
42
+ seen.add(registration.type);
43
+ const file = resolve(registration.file);
44
+ if (!existsSync(file)) throw diagnostics.DF8109({
45
+ type: registration.type,
46
+ file
47
+ });
48
+ return {
49
+ ...registration,
50
+ file
51
+ };
52
+ });
53
+ }
54
+ /**
55
+ * Render the dock client-script import map as an ES module: one dynamic
56
+ * import thunk per dock that carries a client script (`clientScript` on
57
+ * iframe docks, `action`, `renderer`). External viewers import this module
58
+ * from `<base>__client-imports.js` to load per-dock client code into the
59
+ * host page; `importFrom` values are URL paths the host serves, or bare npm
60
+ * specifiers resolved through the host's `clientModuleResolution` template.
61
+ */
62
+ function renderClientImportsModule(ctx) {
63
+ const template = ctx.staticConfig.dock?.clientModuleResolution;
64
+ const entries = [];
65
+ for (const [id, view] of ctx.docks.views) {
66
+ const scripts = [];
67
+ const anyView = view;
68
+ if (anyView.clientScript) scripts.push(anyView.clientScript);
69
+ if (anyView.action) scripts.push(anyView.action);
70
+ if (anyView.renderer) scripts.push(anyView.renderer);
71
+ if (scripts.length === 0) continue;
72
+ const thunks = scripts.map((script) => `() => import(${JSON.stringify(resolveClientModuleSpecifier(script.importFrom, { template }))})`);
73
+ entries.push(` ${JSON.stringify(id)}: [${thunks.join(", ")}],`);
74
+ }
75
+ return `// Generated by @devframes/hub: dock client-script import map.\nexport const clientImports = {\n${entries.join("\n")}\n}\nexport default clientImports\n`;
76
+ }
77
+ /**
78
+ * Pass 1: mount each devframe under `<base><id>/` (SPA, meta, iframe dock)
79
+ * and queue its declared services, guarding the id against reserved hub
80
+ * filenames and route-pattern characters. Returns the deferred setup thunks;
81
+ * each mounted frame is recorded on `ctx.frames`.
82
+ */
83
+ async function mountDevframes(ctx, devframes, base, hubMcpEnabled) {
84
+ const setups = [];
85
+ for (const { devframe: def, dock } of devframes) {
86
+ if (RESERVED_HUB_PATHS.includes(def.id)) throw diagnostics.DF8000({ id: def.id });
87
+ if (!/^[\w.-]+$/.test(def.id)) throw diagnostics.DF8004({ id: def.id });
88
+ if (!hubMcpEnabled && def.cli?.mcp) diagnostics.DF8005({ id: def.id });
89
+ const frameBase = withTrailingSlash(joinURL(base, def.id));
90
+ const run = await prepareDevframe(ctx, def, {
91
+ base: frameBase,
92
+ ...dock ? { dock } : {}
93
+ });
94
+ if (run) setups.push(run);
95
+ }
96
+ return setups;
97
+ }
98
+ //#endregion
99
+ export { resolveRendererRegistrations as i, renderClientImportsModule as n, resolveDevframesInput as r, mountDevframes as t };
@@ -1,6 +1,6 @@
1
- import { C as DevframeMessageEntryInput, O as DevframeMessagesClient } from "../context-Di-fhMKP.mjs";
1
+ import { k as DevframeMessagesClient, w as DevframeMessageEntryInput } from "../context-BmHQp89a.mjs";
2
2
  import { A as DevframeViewIframe, C as DevframeDockUserEntry, F as RemoteConnectionInfo, P as NavTarget, S as DevframeDockPanelState, _ as DevframeDockEntry, a as DevframeCommandEntry, b as DevframeDockEntryIcon, g as DevframeDockEntriesGrouped, n as DevframeClientCommand, p as ClientScriptEntry, s as DevframeCommandKeybinding, t as DevframeDocksUserSettings } from "../settings-CWHPzCWS.mjs";
3
- import "../index-faD2d5iX.mjs";
3
+ import "../index-Dv8iu9St.mjs";
4
4
  import { DevframeClientRpcHost, DevframeConnection, DevframeConnectionStatus, DevframeRpcClient, DevframeRpcClientOptions, DevframeRpcContext, RpcClientEvents, RpcClientEvents as RpcClientEvents$1 } from "devframe/client";
5
5
  import { EventEmitter } from "devframe/types";
6
6
  import { SharedState } from "devframe/utils/shared-state";
@@ -1,4 +1,4 @@
1
- import { i as DOCK_RENDERERS_STATE_KEY, n as DEFAULT_STATE_USER_SETTINGS, o as HUB_EVENTS, t as DEFAULT_CATEGORIES_ORDER } from "../constants-BUd6oMci.mjs";
1
+ import { i as DOCK_RENDERERS_STATE_KEY, n as DEFAULT_STATE_USER_SETTINGS, o as HUB_EVENTS, t as DEFAULT_CATEGORIES_ORDER } from "../constants-Da23JxCZ.mjs";
2
2
  import { a as resolveClientModuleSpecifier, n as stripRemoteConnectionFromUrl, r as clientScriptFailureHint, t as buildRemoteConnectionUrl } from "../remote-url-UClKLQ5_.mjs";
3
3
  import { REMOTE_CONNECTION_KEY } from "devframe/constants";
4
4
  import { connectDevframe, getDevframeRpcClient, resolveWsUrl } from "devframe/client";
@@ -362,6 +362,7 @@ function serializeNavQuery(query) {
362
362
  * dock client script reports into the very feed the server writes to.
363
363
  */
364
364
  function createMessagesClient(rpc, options = {}) {
365
+ if (rpc.connectionMeta?.backend === "static") return createLocalMessagesClient(options);
365
366
  const { call } = rpc;
366
367
  function makeHandle(entry) {
367
368
  let current = entry;
@@ -404,6 +405,58 @@ function createMessagesClient(rpc, options = {}) {
404
405
  debug: levelShortcut("debug")
405
406
  };
406
407
  }
408
+ /** In-page-only writes for a static backend: same surface, no wire. */
409
+ function createLocalMessagesClient(options) {
410
+ let nextId = 0;
411
+ function makeEntry(input) {
412
+ return {
413
+ timestamp: Date.now(),
414
+ ...options.defaults,
415
+ ...input,
416
+ id: input.id ?? `local-${++nextId}`,
417
+ from: "browser"
418
+ };
419
+ }
420
+ function makeHandle(entry) {
421
+ let current = entry;
422
+ return {
423
+ get entry() {
424
+ return current;
425
+ },
426
+ get id() {
427
+ return current.id;
428
+ },
429
+ async update(patch) {
430
+ current = {
431
+ ...current,
432
+ ...patch,
433
+ id: current.id,
434
+ from: current.from
435
+ };
436
+ return current;
437
+ },
438
+ dismiss: async () => {}
439
+ };
440
+ }
441
+ const add = async (input) => makeHandle(makeEntry(input));
442
+ function levelShortcut(level) {
443
+ return (message, extra) => add({
444
+ ...extra,
445
+ message,
446
+ level
447
+ });
448
+ }
449
+ return {
450
+ add,
451
+ remove: async () => {},
452
+ clear: async () => {},
453
+ info: levelShortcut("info"),
454
+ warn: levelShortcut("warn"),
455
+ error: levelShortcut("error"),
456
+ success: levelShortcut("success"),
457
+ debug: levelShortcut("debug")
458
+ };
459
+ }
407
460
  //#endregion
408
461
  //#region src/client/renderers.ts
409
462
  /**
@@ -576,6 +629,11 @@ async function createDevframeClientRuntime(options = {}) {
576
629
  type: "action",
577
630
  handler: (activation) => activateHandler(activation)
578
631
  });
632
+ if (rpc.connectionMeta?.backend === "static" && typeof BroadcastChannel !== "undefined") {
633
+ const activateChannel = new BroadcastChannel(HUB_EVENTS.broadcastChannel.docksActivate);
634
+ activateChannel.onmessage = (event) => activateHandler(event.data);
635
+ disposers.push(() => activateChannel.close());
636
+ }
579
637
  if (getDevframeClientContext()) console.warn("[@devframes/hub] A client context is already published on this page; replacing it. Boot createDevframeClientRuntime() once per page (e.g. HTML injection combined with a manual import boots it twice).");
580
638
  setDevframeClientContext(context);
581
639
  const loadedScripts = /* @__PURE__ */ new Set();
@@ -72,6 +72,15 @@ declare const HUB_EVENTS: {
72
72
  readonly postMessage: {
73
73
  readonly frameNav: "devframe:frame-nav";
74
74
  };
75
+ /**
76
+ * Same-origin `BroadcastChannel` names, `devframe:` prefix. Used on a
77
+ * `static` backend, where no live server can relay a client's request to
78
+ * its sibling browsing contexts (e.g. a panel iframe steering the host
79
+ * page's dock selection).
80
+ */
81
+ readonly broadcastChannel: {
82
+ readonly docksActivate: "devframe:docks:activate";
83
+ };
75
84
  };
76
85
  //#endregion
77
86
  //#region src/constants.d.ts
@@ -65,7 +65,14 @@ const HUB_EVENTS = {
65
65
  /** Streaming channel ids (server → client), `devframe:` prefix. */
66
66
  stream: { terminals: "devframe:terminals" },
67
67
  /** `postMessage` channels for host ↔ iframe protocols, `devframe:` prefix. */
68
- postMessage: { frameNav: "devframe:frame-nav" }
68
+ postMessage: { frameNav: "devframe:frame-nav" },
69
+ /**
70
+ * Same-origin `BroadcastChannel` names, `devframe:` prefix. Used on a
71
+ * `static` backend, where no live server can relay a client's request to
72
+ * its sibling browsing contexts (e.g. a panel iframe steering the host
73
+ * page's dock selection).
74
+ */
75
+ broadcastChannel: { docksActivate: "devframe:docks:activate" }
69
76
  };
70
77
  //#endregion
71
78
  //#region src/constants.ts
@@ -1,3 +1,3 @@
1
- import { a as normalizeHubBase, i as DOCK_RENDERERS_STATE_KEY, n as DEFAULT_STATE_USER_SETTINGS, o as HUB_EVENTS, r as DEVFRAMES_HUB_BASE, t as DEFAULT_CATEGORIES_ORDER } from "./constants-Coo1p3ja.mjs";
1
+ import { a as normalizeHubBase, i as DOCK_RENDERERS_STATE_KEY, n as DEFAULT_STATE_USER_SETTINGS, o as HUB_EVENTS, r as DEVFRAMES_HUB_BASE, t as DEFAULT_CATEGORIES_ORDER } from "./constants-Bz4yclHo.mjs";
2
2
  export * from "devframe/constants";
3
3
  export { DEFAULT_CATEGORIES_ORDER, DEFAULT_STATE_USER_SETTINGS, DEVFRAMES_HUB_BASE, DOCK_RENDERERS_STATE_KEY, HUB_EVENTS, normalizeHubBase };
@@ -1,3 +1,3 @@
1
- import { a as normalizeHubBase, i as DOCK_RENDERERS_STATE_KEY, n as DEFAULT_STATE_USER_SETTINGS, o as HUB_EVENTS, r as DEVFRAMES_HUB_BASE, t as DEFAULT_CATEGORIES_ORDER } from "./constants-BUd6oMci.mjs";
1
+ import { a as normalizeHubBase, i as DOCK_RENDERERS_STATE_KEY, n as DEFAULT_STATE_USER_SETTINGS, o as HUB_EVENTS, r as DEVFRAMES_HUB_BASE, t as DEFAULT_CATEGORIES_ORDER } from "./constants-Da23JxCZ.mjs";
2
2
  export * from "devframe/constants";
3
3
  export { DEFAULT_CATEGORIES_ORDER, DEFAULT_STATE_USER_SETTINGS, DEVFRAMES_HUB_BASE, DOCK_RENDERERS_STATE_KEY, HUB_EVENTS, normalizeHubBase };
@@ -476,6 +476,20 @@ declare module 'devframe/types' {
476
476
  'hub:terminals:resize': (id: string, cols: number, rows: number) => Promise<void>;
477
477
  }
478
478
  }
479
+ /**
480
+ * A devframe mounted into a hub context, recorded as it is installed
481
+ * (whether through `initHub({ devframes })`, `buildHub`, or `ctx.install`).
482
+ * Enumerable via {@link DevframeHubContext.frames} so a host that mounted the
483
+ * context itself can still discover what to advertise in `__index.json`.
484
+ */
485
+ interface HubMountedFrame {
486
+ /** Dock id the devframe mounted under (disambiguated for duplicates). */
487
+ id: string;
488
+ /** Hub-base-relative mount base of the frame's SPA (trailing slash). */
489
+ base: string;
490
+ /** Human title (the definition's `name`). */
491
+ title: string;
492
+ }
479
493
  /**
480
494
  * Hub-augmented node context that extends devframe's framework-neutral
481
495
  * `DevframeNodeContext` with the hub-level subsystems (`docks`,
@@ -495,6 +509,13 @@ interface DevframeHubContext extends DevframeNodeContext {
495
509
  terminals: DevframeTerminalsHost;
496
510
  messages: DevframeMessagesHost;
497
511
  commands: DevframeCommandsHost;
512
+ /**
513
+ * Every devframe mounted into this context, in mount order. Populated by
514
+ * `ctx.install` (and the batch mount `initHub`/`buildHub` run through it),
515
+ * so a host that assembled and mounted the context itself can hand it to
516
+ * `buildHub({ context })` to emit the discovery documents.
517
+ */
518
+ readonly frames: readonly HubMountedFrame[];
498
519
  /**
499
520
  * Install a {@link DevframeDefinition} into this hub: serve its SPA at the
500
521
  * resolved base, synthesize an iframe dock from its metadata, and run its
@@ -518,4 +539,4 @@ interface CreateHubContextOptions extends CreateHostContextOptions {}
518
539
  */
519
540
  declare function createHubContext(options: CreateHubContextOptions): Promise<DevframeHubContext>;
520
541
  //#endregion
521
- export { DevframeMessagesLevelShortcuts as A, DevframeMessageEntryInput as C, DevframeMessageShortcutInput as D, DevframeMessageLevel as E, DevframeMessagesClient as O, DevframeMessageEntryFrom as S, DevframeMessageHandle as T, DevframeMessageAction as _, DevframeChildProcessExecuteOptions as a, DevframeMessageElementPosition as b, DevframeChildProcessTerminalSession as c, DevframePtyResult as d, DevframePtyTerminalSession as f, DevframeTerminalsHost as g, DevframeTerminalStatus as h, InstallDevframeOptions as i, DevframeMessagesListDelta as j, DevframeMessagesHost as k, DevframePtyExecuteOptions as l, DevframeTerminalSessionBase as m, DevframeHubContext as n, DevframeChildProcessOutput as o, DevframeTerminalSession as p, createHubContext as r, DevframeChildProcessResult as s, CreateHubContextOptions as t, DevframePtyOutput as u, DevframeMessageActivateAction as v, DevframeMessageFilePosition as w, DevframeMessageEntry as x, DevframeMessageCommandAction as y };
542
+ export { DevframeMessagesHost as A, DevframeMessageEntryFrom as C, DevframeMessageLevel as D, DevframeMessageHandle as E, DevframeMessagesListDelta as M, DevframeMessageShortcutInput as O, DevframeMessageEntry as S, DevframeMessageFilePosition as T, DevframeTerminalsHost as _, InstallDevframeOptions as a, DevframeMessageCommandAction as b, DevframeChildProcessResult as c, DevframePtyOutput as d, DevframePtyResult as f, DevframeTerminalStatus as g, DevframeTerminalSessionBase as h, createHubContext as i, DevframeMessagesLevelShortcuts as j, DevframeMessagesClient as k, DevframeChildProcessTerminalSession as l, DevframeTerminalSession as m, DevframeHubContext as n, DevframeChildProcessExecuteOptions as o, DevframePtyTerminalSession as p, HubMountedFrame as r, DevframeChildProcessOutput as s, CreateHubContextOptions as t, DevframePtyExecuteOptions as u, DevframeMessageAction as v, DevframeMessageEntryInput as w, DevframeMessageElementPosition as x, DevframeMessageActivateAction as y };
@@ -1,17 +1,17 @@
1
1
  import { r as defineHubRpcFunction } from "./define-Ceekw2EO.mjs";
2
- import { n as DEFAULT_STATE_USER_SETTINGS, o as HUB_EVENTS } from "./constants-BUd6oMci.mjs";
2
+ import { n as DEFAULT_STATE_USER_SETTINGS, o as HUB_EVENTS } from "./constants-Da23JxCZ.mjs";
3
3
  import { i as isBareModuleSpecifier, t as buildRemoteConnectionUrl } from "./remote-url-UClKLQ5_.mjs";
4
4
  import { joinURL, withTrailingSlash } from "ufo";
5
5
  import { createEventEmitter } from "devframe/utils/events";
6
- import { createHostContext, createStorage } from "devframe/node";
7
- import { getInternalContext, resolveBasePath } from "devframe/node/hub-internals";
8
- import { debounce } from "perfect-debounce";
6
+ import { existsSync } from "node:fs";
7
+ import process from "node:process";
9
8
  import { coerceAgentPositionalArgs, resolveClientAssets } from "devframe/internal";
10
- import { defineDiagnostics } from "devframe/utils/nostics";
11
9
  import { basename, dirname, isAbsolute, join, resolve } from "pathe";
10
+ import { defineDiagnostics } from "devframe/utils/nostics";
11
+ import { getInternalContext, resolveBasePath } from "devframe/node/hub-internals";
12
+ import { createHostContext, createStorage } from "devframe/node";
13
+ import { debounce } from "perfect-debounce";
12
14
  import { nanoid } from "devframe/utils/nanoid";
13
- import process from "node:process";
14
- import { existsSync } from "node:fs";
15
15
  //#region src/node/diagnostics.ts
16
16
  /**
17
17
  * Hub-side diagnostics for docks, terminals, messages, and commands.
@@ -32,8 +32,8 @@ const diagnostics = defineDiagnostics({
32
32
  fix: "The filenames directly under the hub base (`__connection.json`, `__ws`, `__index.json`, `__client-imports.js`, `__mcp`, `embedded.js`) are reserved for the hub protocol. Rename the devframe id, or override its mount with a non-colliding `basePath`."
33
33
  },
34
34
  DF8002: {
35
- why: "initHub received both `devframes` and `context`; the two assembly modes are mutually exclusive.",
36
- fix: "Pass `devframes` to let the instance create the hub context and mount each frame itself, or pass a pre-built `context` (your host already mounted the frames), but never both."
35
+ why: "`initHub`/`buildHub` received both `devframes` and `context`; the two assembly modes are mutually exclusive.",
36
+ fix: "Pass `devframes` to let it create the hub context and mount each frame itself, or pass a pre-built `context` (your host already mounted the frames), but never both."
37
37
  },
38
38
  DF8003: {
39
39
  why: "connectionMeta() was called before initHub finished initializing.",
@@ -44,8 +44,12 @@ const diagnostics = defineDiagnostics({
44
44
  fix: "Ids become route segments, so they may only contain letters, digits, `_`, `-`, and `.`; `:` and `*` are route-pattern markers to the underlying router, and `/` would escape the segment. Set a route-safe `id` on the definition (e.g. `my_plugin` instead of `my:plugin`)."
45
45
  },
46
46
  DF8005: {
47
- why: (p) => `Devframe "${p.id}" requests an MCP route, but the hub's aggregate MCP is off, so its tools are not exposed over MCP.`,
48
- fix: "A hub exposes one aggregate MCP endpoint over every mounted devframe, so per-devframe `mcp` settings are ignored. Enable the hub's own MCP (pass `mcp` to `initHub`) to surface this devframe's tools, or drop `mcp` from the devframe to silence this warning."
47
+ why: (p) => `Devframe "${p.id}" requests an MCP route, but the hub's aggregate MCP is off (\`mcp: false\`), so its tools are not exposed over MCP.`,
48
+ fix: "A hub exposes one aggregate MCP endpoint over every mounted devframe, so per-devframe `mcp` settings are ignored. Drop `mcp: false` from `initHub` (the `'auto'` default mounts the aggregate route once agent tools exist) to surface this devframe's tools, or drop `mcp` from the devframe to silence this warning."
49
+ },
50
+ DF8006: {
51
+ why: (p) => `A static hub build can only write mounts under its own base: "${p.urlBase}" escapes "${p.base}".`,
52
+ fix: "buildHub maps each mount base to a directory under its `outDir`, so every mount must live under the hub base. Drop the `basePath` override (or the `ctx.install` base) that points outside it, or move the hub `base` up so it contains the mount."
49
53
  },
50
54
  DF8100: {
51
55
  why: (p) => `Dock with id "${p.id}" is already registered`,
@@ -128,6 +132,139 @@ const diagnostics = defineDiagnostics({
128
132
  }
129
133
  });
130
134
  //#endregion
135
+ //#region src/node/install-devframe.ts
136
+ /**
137
+ * Find the next free dock id derived from `baseId`. Returns `baseId`
138
+ * when it is unused, otherwise appends `-2`, `-3`, … until a free slot
139
+ * is found. Used by the `'duplicate'` strategy so co-existing instances
140
+ * never collide in the dock registry.
141
+ */
142
+ function nextAvailableDockId(views, baseId) {
143
+ if (!views.has(baseId)) return baseId;
144
+ let n = 2;
145
+ while (views.has(`${baseId}-${n}`)) n++;
146
+ return `${baseId}-${n}`;
147
+ }
148
+ /**
149
+ * When a dock's `clientScript.importFrom` is an absolute filesystem path, serve
150
+ * its directory under `<base>__page-script/` and rewrite `importFrom` to that
151
+ * URL. A URL or bare specifier (not existing on disk) passes through untouched.
152
+ */
153
+ async function resolvePageScriptClientScript(ctx, clientScript, base) {
154
+ if (!clientScript?.importFrom) return clientScript;
155
+ const { importFrom } = clientScript;
156
+ if (!isAbsolute(importFrom) || !existsSync(importFrom)) return clientScript;
157
+ const scriptBase = withTrailingSlash(joinURL(base, "__page-script"));
158
+ ctx.views.hostStatic(scriptBase, dirname(importFrom));
159
+ return {
160
+ ...clientScript,
161
+ importFrom: joinURL(scriptBase, basename(importFrom))
162
+ };
163
+ }
164
+ /**
165
+ * Framework-neutral primitive backing {@link DevframeHubContext.install} -
166
+ * installs a {@link DevframeDefinition} as a dock inside a hub-aware context:
167
+ * serves the devframe's SPA at the resolved base path, synthesizes an iframe
168
+ * dock entry from the definition's metadata, and runs the definition's
169
+ * `setup(ctx)`. Reach for it through `ctx.install(devframe)` rather than
170
+ * calling it directly.
171
+ *
172
+ * Framework kits wrap `ctx.install` with their own plugin/middleware
173
+ * machinery, e.g. `@vitejs/devtools-kit`'s `createPluginFromDevframe`
174
+ * returns a Vite `Plugin` whose `devtools.setup` ultimately delegates here.
175
+ */
176
+ /**
177
+ * Phase one of an install: run the duplication guard, serve the SPA + meta,
178
+ * register the iframe dock, and queue the definition's declarative wire
179
+ * services, everything up to (but not including) `setup(ctx)`. Returns a
180
+ * deferred setup thunk, or `null` when the devframe was deduplicated.
181
+ *
182
+ * The hub's initial batch uses this to collect every devframe's services
183
+ * across the whole hub, `ready()` them once, and only then run the setups,
184
+ * so services are ready before any setup, and a plugin can consume a service
185
+ * another plugin declared regardless of mount order.
186
+ */
187
+ /**
188
+ * Serve a devframe's SPA (and the hub's connection meta) under `base`, if the
189
+ * definition ships client assets.
190
+ */
191
+ async function serveDevframeAssets(ctx, d, id, base) {
192
+ const clientAssets = resolveClientAssets(d);
193
+ if (!clientAssets) return;
194
+ if (ctx.host.mountConnectionMeta) await ctx.host.mountConnectionMeta(base);
195
+ else diagnostics.DF8106({
196
+ id,
197
+ name: d.name,
198
+ base
199
+ });
200
+ ctx.views.hostStatic(base, typeof clientAssets === "string" ? resolve(clientAssets) : clientAssets, d.importMetaUrl);
201
+ }
202
+ /**
203
+ * Whether a devframe stays out of a static hub build: `capabilities.build:
204
+ * false` declares its value inherently live (a terminal, a process proxy),
205
+ * so `buildHub` never mounts it, registers its dock, or bakes its RPCs.
206
+ */
207
+ function skippedInStaticBuild(ctx, d) {
208
+ return ctx.mode === "build" && d.capabilities?.build === false;
209
+ }
210
+ async function prepareDevframe(ctx, d, options = {}) {
211
+ if (skippedInStaticBuild(ctx, d)) return null;
212
+ const strategy = d.duplicationStrategy ?? "warn";
213
+ const isDuplicate = ctx.docks.views.has(d.id);
214
+ if (isDuplicate && strategy !== "duplicate") {
215
+ if (strategy === "throw") throw diagnostics.DF8105({
216
+ id: d.id,
217
+ name: d.name
218
+ });
219
+ if (strategy === "warn") diagnostics.DF8105({
220
+ id: d.id,
221
+ name: d.name
222
+ });
223
+ return null;
224
+ }
225
+ const id = isDuplicate ? nextAvailableDockId(ctx.docks.views, d.id) : d.id;
226
+ const base = options.base ?? (id === d.id ? resolveBasePath(d, "hosted") : resolveBasePath({
227
+ ...d,
228
+ id,
229
+ basePath: void 0
230
+ }, "hosted"));
231
+ const dockDefaults = {
232
+ ...d.dock,
233
+ ...options.dock
234
+ };
235
+ const clientScript = await resolvePageScriptClientScript(ctx, dockDefaults.clientScript, base);
236
+ if (clientScript) dockDefaults.clientScript = clientScript;
237
+ await serveDevframeAssets(ctx, d, id, base);
238
+ ctx.frames.push({
239
+ id,
240
+ base,
241
+ title: d.name
242
+ });
243
+ ctx.docks.register({
244
+ id,
245
+ title: d.name,
246
+ icon: d.icon,
247
+ ...dockDefaults,
248
+ type: "iframe",
249
+ url: base
250
+ });
251
+ for (const input of d.services ?? []) ctx.services.install(input, { resolveFrom: d.importMetaUrl });
252
+ return () => Promise.resolve(d.setup(ctx));
253
+ }
254
+ /**
255
+ * Install a {@link DevframeDefinition} into a hub in one call: serve its SPA,
256
+ * register its dock, ready its services, and run `setup(ctx)`. The imperative
257
+ * counterpart to the hub's declarative `devframes` list (which batches the
258
+ * phases via {@link prepareDevframe}); use it from `configure(ctx)` or
259
+ * wherever you hold the context to plug in an extra devframe after startup.
260
+ */
261
+ async function installDevframe(ctx, d, options = {}) {
262
+ const run = await prepareDevframe(ctx, d, options);
263
+ if (!run) return;
264
+ await ctx.services.ready();
265
+ await run();
266
+ }
267
+ //#endregion
131
268
  //#region src/node/host-commands.ts
132
269
  function findChildCommand(command, id) {
133
270
  for (const child of command.children ?? []) {
@@ -952,125 +1089,6 @@ var DevframeTerminalsHost = class {
952
1089
  }
953
1090
  };
954
1091
  //#endregion
955
- //#region src/node/install-devframe.ts
956
- /**
957
- * Find the next free dock id derived from `baseId`. Returns `baseId`
958
- * when it is unused, otherwise appends `-2`, `-3`, … until a free slot
959
- * is found. Used by the `'duplicate'` strategy so co-existing instances
960
- * never collide in the dock registry.
961
- */
962
- function nextAvailableDockId(views, baseId) {
963
- if (!views.has(baseId)) return baseId;
964
- let n = 2;
965
- while (views.has(`${baseId}-${n}`)) n++;
966
- return `${baseId}-${n}`;
967
- }
968
- /**
969
- * When a dock's `clientScript.importFrom` is an absolute filesystem path, serve
970
- * its directory under `<base>__page-script/` and rewrite `importFrom` to that
971
- * URL. A URL or bare specifier (not existing on disk) passes through untouched.
972
- */
973
- async function resolvePageScriptClientScript(ctx, clientScript, base) {
974
- if (!clientScript?.importFrom) return clientScript;
975
- const { importFrom } = clientScript;
976
- if (!isAbsolute(importFrom) || !existsSync(importFrom)) return clientScript;
977
- const scriptBase = withTrailingSlash(joinURL(base, "__page-script"));
978
- await ctx.host.mountStatic(scriptBase, dirname(importFrom));
979
- return {
980
- ...clientScript,
981
- importFrom: joinURL(scriptBase, basename(importFrom))
982
- };
983
- }
984
- /**
985
- * Framework-neutral primitive backing {@link DevframeHubContext.install} -
986
- * installs a {@link DevframeDefinition} as a dock inside a hub-aware context:
987
- * serves the devframe's SPA at the resolved base path, synthesizes an iframe
988
- * dock entry from the definition's metadata, and runs the definition's
989
- * `setup(ctx)`. Reach for it through `ctx.install(devframe)` rather than
990
- * calling it directly.
991
- *
992
- * Framework kits wrap `ctx.install` with their own plugin/middleware
993
- * machinery, e.g. `@vitejs/devtools-kit`'s `createPluginFromDevframe`
994
- * returns a Vite `Plugin` whose `devtools.setup` ultimately delegates here.
995
- */
996
- /**
997
- * Phase one of an install: run the duplication guard, serve the SPA + meta,
998
- * register the iframe dock, and queue the definition's declarative wire
999
- * services, everything up to (but not including) `setup(ctx)`. Returns a
1000
- * deferred setup thunk, or `null` when the devframe was deduplicated.
1001
- *
1002
- * The hub's initial batch uses this to collect every devframe's services
1003
- * across the whole hub, `ready()` them once, and only then run the setups,
1004
- * so services are ready before any setup, and a plugin can consume a service
1005
- * another plugin declared regardless of mount order.
1006
- */
1007
- /**
1008
- * Serve a devframe's SPA (and the hub's connection meta) under `base`, if the
1009
- * definition ships client assets.
1010
- */
1011
- async function serveDevframeAssets(ctx, d, id, base) {
1012
- const clientAssets = resolveClientAssets(d);
1013
- if (!clientAssets) return;
1014
- if (ctx.host.mountConnectionMeta) await ctx.host.mountConnectionMeta(base);
1015
- else diagnostics.DF8106({
1016
- id,
1017
- name: d.name,
1018
- base
1019
- });
1020
- ctx.views.hostStatic(base, typeof clientAssets === "string" ? resolve(clientAssets) : clientAssets, d.importMetaUrl);
1021
- }
1022
- async function prepareDevframe(ctx, d, options = {}) {
1023
- const strategy = d.duplicationStrategy ?? "warn";
1024
- const isDuplicate = ctx.docks.views.has(d.id);
1025
- if (isDuplicate && strategy !== "duplicate") {
1026
- if (strategy === "throw") throw diagnostics.DF8105({
1027
- id: d.id,
1028
- name: d.name
1029
- });
1030
- if (strategy === "warn") diagnostics.DF8105({
1031
- id: d.id,
1032
- name: d.name
1033
- });
1034
- return null;
1035
- }
1036
- const id = isDuplicate ? nextAvailableDockId(ctx.docks.views, d.id) : d.id;
1037
- const base = options.base ?? (id === d.id ? resolveBasePath(d, "hosted") : resolveBasePath({
1038
- ...d,
1039
- id,
1040
- basePath: void 0
1041
- }, "hosted"));
1042
- const dockDefaults = {
1043
- ...d.dock,
1044
- ...options.dock
1045
- };
1046
- const clientScript = await resolvePageScriptClientScript(ctx, dockDefaults.clientScript, base);
1047
- if (clientScript) dockDefaults.clientScript = clientScript;
1048
- await serveDevframeAssets(ctx, d, id, base);
1049
- ctx.docks.register({
1050
- id,
1051
- title: d.name,
1052
- icon: d.icon,
1053
- ...dockDefaults,
1054
- type: "iframe",
1055
- url: base
1056
- });
1057
- for (const input of d.services ?? []) ctx.services.install(input, { resolveFrom: d.importMetaUrl });
1058
- return () => Promise.resolve(d.setup(ctx));
1059
- }
1060
- /**
1061
- * Install a {@link DevframeDefinition} into a hub in one call: serve its SPA,
1062
- * register its dock, ready its services, and run `setup(ctx)`. The imperative
1063
- * counterpart to the hub's declarative `devframes` list (which batches the
1064
- * phases via {@link prepareDevframe}); use it from `configure(ctx)` or
1065
- * wherever you hold the context to plug in an extra devframe after startup.
1066
- */
1067
- async function installDevframe(ctx, d, options = {}) {
1068
- const run = await prepareDevframe(ctx, d, options);
1069
- if (!run) return;
1070
- await ctx.services.ready();
1071
- await run();
1072
- }
1073
- //#endregion
1074
1092
  //#region src/node/rpc-builtins.ts
1075
1093
  /**
1076
1094
  * Resolve an interactive (PTY) terminal session by id, or throw. Sessions
@@ -1275,6 +1293,7 @@ async function createHubContext(options) {
1275
1293
  context.terminals = terminals;
1276
1294
  context.messages = messages;
1277
1295
  context.commands = commands;
1296
+ context.frames = [];
1278
1297
  context.install = (devframe, options) => installDevframe(context, devframe, options);
1279
1298
  await docks.init();
1280
1299
  const debounceMs = options.mode === "build" ? 0 : 10;
@@ -1324,4 +1343,4 @@ async function createHubContext(options) {
1324
1343
  return context;
1325
1344
  }
1326
1345
  //#endregion
1327
- export { DevframeDocksHost as _, hubMessagesAdd as a, hubMessagesUpdate as c, hubTerminalsRestart as d, hubTerminalsTerminate as f, DevframeMessagesHost as g, DevframeTerminalsHost as h, hubDocksActivate as i, hubTerminalsRemove as l, prepareDevframe as m, builtinHubRpcDeclarations as n, hubMessagesClear as o, hubTerminalsWrite as p, hubCommandsExecute as r, hubMessagesRemove as s, createHubContext as t, hubTerminalsResize as u, DevframeCommandsHost as v, diagnostics as y };
1346
+ export { DevframeCommandsHost as _, hubMessagesAdd as a, hubMessagesUpdate as c, hubTerminalsRestart as d, hubTerminalsTerminate as f, DevframeDocksHost as g, DevframeMessagesHost as h, hubDocksActivate as i, hubTerminalsRemove as l, DevframeTerminalsHost as m, builtinHubRpcDeclarations as n, hubMessagesClear as o, hubTerminalsWrite as p, hubCommandsExecute as r, hubMessagesRemove as s, createHubContext as t, hubTerminalsResize as u, prepareDevframe as v, diagnostics as y };
@@ -1,4 +1,4 @@
1
- import "./context-Di-fhMKP.mjs";
1
+ import "./context-BmHQp89a.mjs";
2
2
  import "./settings-CWHPzCWS.mjs";
3
3
  import { RpcDefinitionsFilter, RpcDefinitionsToFunctions } from "devframe/rpc";
4
4
  import { ConnectionMeta as ConnectionMeta$1, DevframeCapabilities, DevframeDiagnosticsHost, DevframeDiagnosticsLogger, DevframeHost as DevframeHost$1, DevframeNodeRpcSession, DevframeRpcClientFunctions, DevframeRpcServerFunctions, DevframeRpcSharedStates, DevframeViewHost, EventEmitter as EventEmitter$1, EventUnsubscribe, EventsMap, RpcBroadcastOptions, RpcFunctionsHost, RpcSharedStateGetOptions, RpcSharedStateHost, RpcStreamingChannel, RpcStreamingChannelOptions, RpcStreamingHost } from "devframe/types";
package/dist/index.d.mts CHANGED
@@ -1,6 +1,6 @@
1
- import { A as DevframeMessagesLevelShortcuts, C as DevframeMessageEntryInput, D as DevframeMessageShortcutInput, E as DevframeMessageLevel, O as DevframeMessagesClient, S as DevframeMessageEntryFrom, T as DevframeMessageHandle, _ as DevframeMessageAction, a as DevframeChildProcessExecuteOptions, b as DevframeMessageElementPosition, c as DevframeChildProcessTerminalSession, d as DevframePtyResult, f as DevframePtyTerminalSession, g as DevframeTerminalsHost, h as DevframeTerminalStatus, j as DevframeMessagesListDelta, k as DevframeMessagesHost, l as DevframePtyExecuteOptions, m as DevframeTerminalSessionBase, n as DevframeHubContext, o as DevframeChildProcessOutput, p as DevframeTerminalSession, s as DevframeChildProcessResult, t as CreateHubContextOptions, u as DevframePtyOutput, v as DevframeMessageActivateAction, w as DevframeMessageFilePosition, x as DevframeMessageEntry, y as DevframeMessageCommandAction } from "./context-Di-fhMKP.mjs";
1
+ import { A as DevframeMessagesHost, C as DevframeMessageEntryFrom, D as DevframeMessageLevel, E as DevframeMessageHandle, M as DevframeMessagesListDelta, O as DevframeMessageShortcutInput, S as DevframeMessageEntry, T as DevframeMessageFilePosition, _ as DevframeTerminalsHost, b as DevframeMessageCommandAction, c as DevframeChildProcessResult, d as DevframePtyOutput, f as DevframePtyResult, g as DevframeTerminalStatus, h as DevframeTerminalSessionBase, j as DevframeMessagesLevelShortcuts, k as DevframeMessagesClient, l as DevframeChildProcessTerminalSession, m as DevframeTerminalSession, n as DevframeHubContext, o as DevframeChildProcessExecuteOptions, p as DevframePtyTerminalSession, s as DevframeChildProcessOutput, t as CreateHubContextOptions, u as DevframePtyExecuteOptions, v as DevframeMessageAction, w as DevframeMessageEntryInput, x as DevframeMessageElementPosition, y as DevframeMessageActivateAction } from "./context-BmHQp89a.mjs";
2
2
  import { A as DevframeViewIframe, C as DevframeDockUserEntry, D as DevframeViewBuiltin, E as DevframeViewAction, F as RemoteConnectionInfo, I as RemoteDockOptions, M as DevframeViewLauncherStatus, N as FrameSubTabsConfig, O as DevframeViewCustomRender, P as NavTarget, S as DevframeDockPanelState, T as DevframeDocksHost, _ as DevframeDockEntry, a as DevframeCommandEntry, b as DevframeDockEntryIcon, c as DevframeCommandShortcutOverrides, d as DevframeServerCommandEntry, f as DevframeServerCommandInput, g as DevframeDockEntriesGrouped, h as DevframeDockBadgeVariant, i as DevframeCommandBase, j as DevframeViewLauncher, k as DevframeViewGroup, l as DevframeCommandsHost, m as DevframeDockActivation, n as DevframeClientCommand, o as DevframeCommandHandle, p as ClientScriptEntry, r as DevframeCommandAgentOptions, s as DevframeCommandKeybinding, t as DevframeDocksUserSettings, u as DevframeCommandsHostEvents, v as DevframeDockEntryBase, w as DevframeDocksActiveState, x as DevframeDockEntryRegistry, y as DevframeDockEntryCategory } from "./settings-CWHPzCWS.mjs";
3
- import { S as RpcStreamingHost, _ as RpcFunctionsHost, a as DevframeHost, b as RpcStreamingChannel, c as DevframeRpcServerFunctions, d as EventEmitter, f as EventUnsubscribe, g as RpcDefinitionsToFunctions, h as RpcDefinitionsFilter, i as DevframeDiagnosticsLogger, l as DevframeRpcSharedStates, m as RpcBroadcastOptions, n as DevframeCapabilities, o as DevframeNodeRpcSession, p as EventsMap, r as DevframeDiagnosticsHost, s as DevframeRpcClientFunctions, t as ConnectionMeta, u as DevframeViewHost, v as RpcSharedStateGetOptions, x as RpcStreamingChannelOptions, y as RpcSharedStateHost } from "./index-faD2d5iX.mjs";
3
+ import { S as RpcStreamingHost, _ as RpcFunctionsHost, a as DevframeHost, b as RpcStreamingChannel, c as DevframeRpcServerFunctions, d as EventEmitter, f as EventUnsubscribe, g as RpcDefinitionsToFunctions, h as RpcDefinitionsFilter, i as DevframeDiagnosticsLogger, l as DevframeRpcSharedStates, m as RpcBroadcastOptions, n as DevframeCapabilities, o as DevframeNodeRpcSession, p as EventsMap, r as DevframeDiagnosticsHost, s as DevframeRpcClientFunctions, t as ConnectionMeta, u as DevframeViewHost, v as RpcSharedStateGetOptions, x as RpcStreamingChannelOptions, y as RpcSharedStateHost } from "./index-Dv8iu9St.mjs";
4
4
  import { WhenContext, WhenExpression } from "devframe/utils/when";
5
5
  //#region src/define.d.ts
6
6
  declare const defineHubRpcFunction: <NAME extends string, TYPE extends import("devframe/rpc").RpcFunctionType, ARGS extends any[], RETURN = void, const AS extends import("devframe/rpc").RpcArgsSchema | undefined = undefined, const RS extends import("devframe/rpc").RpcReturnSchema | undefined = undefined>(definition: import("devframe/rpc").RpcFunctionDefinition<NAME, TYPE, ARGS, RETURN, AS, RS, DevframeHubContext>) => import("devframe/rpc").RpcFunctionDefinition<NAME, TYPE, ARGS, RETURN, AS, RS, DevframeHubContext>;
@@ -0,0 +1,93 @@
1
+ import { n as DevframeHubContext, t as CreateHubContextOptions } from "../context-BmHQp89a.mjs";
2
+ import { DevframeHubUi, DevframesInput, DockRendererRegistration } from "./initiate.mjs";
3
+ import { DevframeServiceInput, DevframeStorageScope } from "devframe/types";
4
+ //#region src/node/build.d.ts
5
+ interface BuildHubOptions {
6
+ /**
7
+ * Output directory the hub subtree is written into. It corresponds to the
8
+ * hub {@link BuildHubOptions.base} at serve time: building with
9
+ * `base: '/__devframes/'` into `dist/__devframes` makes the deployed app's
10
+ * `dist/` servable as-is by any static file server.
11
+ */
12
+ outDir: string;
13
+ /**
14
+ * Mount base the deployed hub answers under (baked into every absolute URL
15
+ * the build emits: page-script rewrites, renderer modules, per-frame meta
16
+ * pointers). Default: `/__devframes/`.
17
+ */
18
+ base?: string;
19
+ /** Devframes to bake as docks, same input as `initHub({ devframes })`. */
20
+ devframes?: DevframesInput;
21
+ /**
22
+ * Bring your own already-mounted hub context instead of `devframes`, the
23
+ * build counterpart of `initHub({ context })`: a host that assembled
24
+ * `createHubContext` + `ctx.install` itself (Vite DevTools' kit-augmented
25
+ * context, devframes mounted from Vite plugins) hands the mounted context
26
+ * here and `buildHub` bakes it, reading `ctx.frames` and
27
+ * `ctx.views.buildStaticDirs` for what to emit. `configure` and the UI
28
+ * slot's `setup` still run against it, so pass them here rather than running
29
+ * them yourself. Mutually exclusive with `devframes`.
30
+ */
31
+ context?: DevframeHubContext;
32
+ /** Host-level wire services, same contract as `initHub({ services })`. */
33
+ services?: DevframeServiceInput[];
34
+ /** Extra RPC declarations registered at context creation. */
35
+ rpcDeclarations?: CreateHubContextOptions['builtinRpcDeclarations'];
36
+ /**
37
+ * Runs once the context exists and every `devframes` entry is mounted;
38
+ * register docks, commands, and messages surfaces here, exactly like
39
+ * `initHub({ configure })`. Everything it registers is baked into the
40
+ * static shared-state snapshot.
41
+ */
42
+ configure?: (ctx: DevframeHubContext) => void | Promise<void>;
43
+ /**
44
+ * The hub's UI slot: the viewer SPA is copied to the hub base, the
45
+ * `embedded.js` bootstrap next to it, and `setup(ctx)` runs so its static
46
+ * config (branding, dock preferences) is baked into the connection meta.
47
+ */
48
+ ui?: DevframeHubUi;
49
+ /** Prebuilt dock-renderer modules, copied to `<base>__renderers/<type>.mjs`. */
50
+ renderers?: readonly DockRendererRegistration[];
51
+ /** Name written into `<base>__index.json`. */
52
+ name?: string;
53
+ /** Version written into `<base>__index.json`. */
54
+ version?: string;
55
+ /** Working directory for the hub context. Default: `process.cwd()`. */
56
+ cwd?: string;
57
+ /** Override where persisted devframe state lives during the build. */
58
+ getStorageDir?: (scope: DevframeStorageScope) => string;
59
+ /** Pretty-print RPC dump JSON files. Default: `false` (minified shards). */
60
+ pretty?: boolean;
61
+ /**
62
+ * Remove {@link BuildHubOptions.outDir} before writing. Default `true`,
63
+ * matching a from-scratch build. Pass `false` to bake into a directory that
64
+ * already holds sibling output (an app's own `dist/` the hub subtree lives
65
+ * beside).
66
+ */
67
+ clean?: boolean;
68
+ }
69
+ /**
70
+ * Produce a self-contained static deploy of a whole hub, the multi-devframe
71
+ * counterpart of devframe's `createBuild`:
72
+ *
73
+ * - Build a `mode: 'build'` hub context and mount every devframe (or reuse a
74
+ * `context` a host mounted itself): each SPA is copied to `<outDir>/<id>/`,
75
+ * an absolute-path page script to `<outDir>/<id>/__page-script/`, and its
76
+ * `setup(ctx)` runs.
77
+ * - Copy the UI slot's viewer SPA, `embedded.js`, and the renderer
78
+ * modules, and write the discovery documents (`__index.json`,
79
+ * `__client-imports.js`).
80
+ * - Write `__connection.json` (`{ backend: 'static' }`, carrying
81
+ * `ctx.staticConfig` as `configs`) at the hub base and at every frame
82
+ * base (each pointing back at the hub's own meta via `baseUrl`).
83
+ * - Bake the shared RPC dump under `<outDir>/__rpc-dump/`: every
84
+ * `static`/`snapshot` RPC plus a snapshot of each shared-state key
85
+ * (docks, commands, renderer manifest), so `createDevframeClientRuntime`
86
+ * and every frame SPA boot from the dump with no live server.
87
+ *
88
+ * Reads work from the baked dump; live writes (messages, terminals, commands
89
+ * execution) have no server and degrade to no-ops in the clients.
90
+ */
91
+ declare function buildHub(options: BuildHubOptions): Promise<void>;
92
+ //#endregion
93
+ export { BuildHubOptions, buildHub };
@@ -0,0 +1,198 @@
1
+ import { a as normalizeHubBase, i as DOCK_RENDERERS_STATE_KEY } from "../constants-Da23JxCZ.mjs";
2
+ import { t as createHubContext, y as diagnostics } from "../context-Cdrtg-Kk.mjs";
3
+ import { i as resolveRendererRegistrations, n as renderClientImportsModule, r as resolveDevframesInput, t as mountDevframes } from "../assemble-BvY5jfgi.mjs";
4
+ import { joinURL } from "ufo";
5
+ import { DEVFRAME_CONNECTION_META_FILENAME, DEVFRAME_DOCK_IMPORTS_FILENAME } from "devframe/constants";
6
+ import { existsSync } from "node:fs";
7
+ import fs from "node:fs/promises";
8
+ import process from "node:process";
9
+ import { createH3DevframeHost } from "devframe/internal";
10
+ import { collectStaticRpcDump, writeStaticRpcDump } from "devframe/rpc/dump";
11
+ import { colors } from "devframe/utils/colors";
12
+ import { resolveStaticAssetsSource } from "devframe/utils/remote-assets";
13
+ import { dirname, resolve } from "pathe";
14
+ //#region src/node/build.ts
15
+ /**
16
+ * Produce a self-contained static deploy of a whole hub, the multi-devframe
17
+ * counterpart of devframe's `createBuild`:
18
+ *
19
+ * - Build a `mode: 'build'` hub context and mount every devframe (or reuse a
20
+ * `context` a host mounted itself): each SPA is copied to `<outDir>/<id>/`,
21
+ * an absolute-path page script to `<outDir>/<id>/__page-script/`, and its
22
+ * `setup(ctx)` runs.
23
+ * - Copy the UI slot's viewer SPA, `embedded.js`, and the renderer
24
+ * modules, and write the discovery documents (`__index.json`,
25
+ * `__client-imports.js`).
26
+ * - Write `__connection.json` (`{ backend: 'static' }`, carrying
27
+ * `ctx.staticConfig` as `configs`) at the hub base and at every frame
28
+ * base (each pointing back at the hub's own meta via `baseUrl`).
29
+ * - Bake the shared RPC dump under `<outDir>/__rpc-dump/`: every
30
+ * `static`/`snapshot` RPC plus a snapshot of each shared-state key
31
+ * (docks, commands, renderer manifest), so `createDevframeClientRuntime`
32
+ * and every frame SPA boot from the dump with no live server.
33
+ *
34
+ * Reads work from the baked dump; live writes (messages, terminals, commands
35
+ * execution) have no server and degrade to no-ops in the clients.
36
+ */
37
+ async function buildHub(options) {
38
+ if (options.context && options.devframes?.length) throw diagnostics.DF8002();
39
+ const base = normalizeHubBase(options.base ?? "/__devframes/");
40
+ const cwd = options.cwd ?? process.cwd();
41
+ const outDir = resolve(cwd, options.outDir);
42
+ const rendererRegistrations = resolveRendererRegistrations(options.renderers ?? []);
43
+ const ctx = options.context ?? await createAndMountContext(options, base, cwd);
44
+ await options.configure?.(ctx);
45
+ await options.ui?.setup?.(ctx);
46
+ if (options.clean !== false && existsSync(outDir)) await fs.rm(outDir, { recursive: true });
47
+ await fs.mkdir(outDir, { recursive: true });
48
+ /** Map a hub-base-relative URL base to its on-disk location under `outDir`. */
49
+ const resolveOutPath = (urlBase) => {
50
+ if (!urlBase.startsWith(base)) throw diagnostics.DF8006({
51
+ urlBase,
52
+ base
53
+ });
54
+ return resolve(outDir, urlBase.slice(base.length));
55
+ };
56
+ await copyBuildStatics(ctx, resolveOutPath);
57
+ await publishRendererManifest(ctx, rendererRegistrations, base, outDir);
58
+ await writeUiArtifacts(options.ui, outDir);
59
+ await fs.writeFile(resolve(outDir, DEVFRAME_DOCK_IMPORTS_FILENAME), renderClientImportsModule(ctx), "utf-8");
60
+ await writeHubIndex(ctx, base, outDir, options);
61
+ await writeConnectionMetas(ctx, base, outDir, resolveOutPath);
62
+ console.log(colors.cyan`[devframes-hub] writing RPC dump to ${resolve(outDir, "__rpc-dump")}`);
63
+ const dump = await collectStaticRpcDump(ctx.rpc.definitions.values(), ctx);
64
+ await writeStaticRpcDump(dump, outDir, { pretty: options.pretty });
65
+ const count = ctx.frames.length;
66
+ console.log(colors.green`[devframes-hub] built ${count} devframe${count === 1 ? "" : "s"} -> ${outDir}`);
67
+ }
68
+ /**
69
+ * Build a `mode: 'build'` hub context and mount the `devframes` input. The host
70
+ * copies nothing live (`mountStatic`/`mountConnectionMeta` are no-ops): every
71
+ * static is copied afterwards from `ctx.views.buildStaticDirs`, and each frame's
72
+ * meta is written from `ctx.frames`.
73
+ */
74
+ async function createAndMountContext(options, base, cwd) {
75
+ const host = {
76
+ ...createH3DevframeHost({
77
+ origin: "http://localhost",
78
+ appName: "devframes",
79
+ workspaceRoot: cwd,
80
+ mount: () => {}
81
+ }),
82
+ ...options.getStorageDir ? { getStorageDir: options.getStorageDir } : {},
83
+ mountConnectionMeta: () => {}
84
+ };
85
+ const ctx = await createHubContext({
86
+ cwd,
87
+ workspaceRoot: cwd,
88
+ mode: "build",
89
+ host,
90
+ ...options.rpcDeclarations ? { builtinRpcDeclarations: options.rpcDeclarations } : {}
91
+ });
92
+ const devframes = await resolveDevframesInput(options.devframes ?? []);
93
+ for (const input of options.services ?? []) ctx.services.install(input);
94
+ const setups = await mountDevframes(ctx, devframes, base, false);
95
+ await ctx.services.ready();
96
+ for (const run of setups) await run();
97
+ return ctx;
98
+ }
99
+ /**
100
+ * Copy every static the context registered through `ctx.views.hostStatic`
101
+ * (recorded in `ctx.views.buildStaticDirs`): a local dir verbatim, a remote
102
+ * source by materializing every listed file. Reads the list rather than
103
+ * relying on a live host `mountStatic`, so a context whose host copied no
104
+ * statics at mount time (the build host, or a kit's) still gets its assets in.
105
+ * Each source re-resolves with the `resolveFrom` it was mounted with, so a
106
+ * remote source (e.g. a plugin's `--assets` package) resolves to the same
107
+ * locally-installed copy it would serve live.
108
+ */
109
+ async function copyBuildStatics(ctx, resolveOutPath) {
110
+ const storageDir = ctx.host.getStorageDir("project");
111
+ for (const { baseUrl, source, resolveFrom } of ctx.views.buildStaticDirs) {
112
+ const target = resolveOutPath(baseUrl);
113
+ const resolved = resolveStaticAssetsSource(source, storageDir, resolveFrom);
114
+ await fs.mkdir(dirname(target), { recursive: true });
115
+ if (typeof resolved === "string") await fs.cp(resolved, target, { recursive: true });
116
+ else await resolved.materialize(target);
117
+ }
118
+ }
119
+ /**
120
+ * Publish the renderer manifest exactly like `initHub` does, so it is baked
121
+ * into the shared-state snapshot, and copy each prebuilt module to the URL
122
+ * the manifest advertises.
123
+ */
124
+ async function publishRendererManifest(ctx, registrations, base, outDir) {
125
+ const manifest = {};
126
+ for (const registration of registrations) {
127
+ manifest[registration.type] = {
128
+ importFrom: joinURL(base, "__renderers", `${registration.type}.mjs`),
129
+ ...registration.importName ? { importName: registration.importName } : {}
130
+ };
131
+ await fs.mkdir(resolve(outDir, "__renderers"), { recursive: true });
132
+ await fs.copyFile(registration.file, resolve(outDir, "__renderers", `${registration.type}.mjs`));
133
+ }
134
+ (await ctx.rpc.sharedState.get(DOCK_RENDERERS_STATE_KEY, { initialValue: {} })).mutate(() => manifest);
135
+ }
136
+ /**
137
+ * Copy the UI slot's artifacts: the viewer SPA owns the hub base (copied
138
+ * before the discovery documents, so those win over same-named files it
139
+ * ships), `embedded.js` next to it, plus any produced assets.
140
+ */
141
+ async function writeUiArtifacts(ui, outDir) {
142
+ if (ui?.viewer) await fs.cp(resolve(ui.viewer.distDir), outDir, { recursive: true });
143
+ if (ui?.embedded) await fs.copyFile(resolve(ui.embedded.entry), resolve(outDir, "embedded.js"));
144
+ for (const [key, produce] of Object.entries(ui?.assets ?? {})) {
145
+ const target = resolve(outDir, key);
146
+ await fs.mkdir(dirname(target), { recursive: true });
147
+ await fs.writeFile(target, produce());
148
+ }
149
+ }
150
+ /** Write `<base>__index.json`: the discovery document listing every frame. */
151
+ async function writeHubIndex(ctx, base, outDir, options) {
152
+ await fs.writeFile(resolve(outDir, "__index.json"), `${JSON.stringify({
153
+ name: options.name,
154
+ version: options.version,
155
+ base,
156
+ frames: ctx.frames.map(({ id, base, title }) => ({
157
+ id,
158
+ base,
159
+ title
160
+ })),
161
+ endpoints: {
162
+ connection: DEVFRAME_CONNECTION_META_FILENAME,
163
+ clientImports: DEVFRAME_DOCK_IMPORTS_FILENAME,
164
+ index: "__index.json",
165
+ ...options.ui?.embedded ? { embedded: "embedded.js" } : {}
166
+ }
167
+ }, null, 2)}\n`, "utf-8");
168
+ }
169
+ /**
170
+ * Write the `backend: 'static'` connection meta at the hub base, and a copy at
171
+ * every frame base that served its own SPA (i.e. registered a static mount at
172
+ * that base) whose `baseUrl` points relative resolution (the RPC dump) back at
173
+ * the hub's own meta, so a frame SPA that fetched its per-frame copy (instead
174
+ * of inheriting the host page's connection) still finds the shared dump.
175
+ */
176
+ async function writeConnectionMetas(ctx, base, outDir, resolveOutPath) {
177
+ const jsonSerializableMethods = [];
178
+ for (const def of ctx.rpc.definitions.values()) if (def.jsonSerializable === true) jsonSerializableMethods.push(def.name);
179
+ const meta = {
180
+ backend: "static",
181
+ jsonSerializableMethods,
182
+ ...Object.keys(ctx.staticConfig).length > 0 ? { configs: ctx.staticConfig } : {}
183
+ };
184
+ await fs.writeFile(resolve(outDir, DEVFRAME_CONNECTION_META_FILENAME), JSON.stringify(meta, null, 2), "utf-8");
185
+ const frameMeta = {
186
+ ...meta,
187
+ baseUrl: joinURL(base, DEVFRAME_CONNECTION_META_FILENAME)
188
+ };
189
+ const servedBases = new Set(ctx.views.buildStaticDirs.map((dir) => dir.baseUrl));
190
+ for (const frame of ctx.frames) {
191
+ if (!servedBases.has(frame.base)) continue;
192
+ const target = resolve(resolveOutPath(frame.base), DEVFRAME_CONNECTION_META_FILENAME);
193
+ await fs.mkdir(dirname(target), { recursive: true });
194
+ await fs.writeFile(target, JSON.stringify(frameMeta, null, 2), "utf-8");
195
+ }
196
+ }
197
+ //#endregion
198
+ export { buildHub };
@@ -1,4 +1,4 @@
1
- import { C as DevframeMessageEntryInput, D as DevframeMessageShortcutInput, T as DevframeMessageHandle, a as DevframeChildProcessExecuteOptions, c as DevframeChildProcessTerminalSession, f as DevframePtyTerminalSession, g as DevframeTerminalsHost$1, i as InstallDevframeOptions, j as DevframeMessagesListDelta, k as DevframeMessagesHost$1, l as DevframePtyExecuteOptions, m as DevframeTerminalSessionBase, n as DevframeHubContext, p as DevframeTerminalSession, r as createHubContext, t as CreateHubContextOptions, x as DevframeMessageEntry } from "../context-Di-fhMKP.mjs";
1
+ import { A as DevframeMessagesHost$1, E as DevframeMessageHandle, M as DevframeMessagesListDelta, O as DevframeMessageShortcutInput, S as DevframeMessageEntry, _ as DevframeTerminalsHost$1, a as InstallDevframeOptions, h as DevframeTerminalSessionBase, i as createHubContext, l as DevframeChildProcessTerminalSession, m as DevframeTerminalSession, n as DevframeHubContext, o as DevframeChildProcessExecuteOptions, p as DevframePtyTerminalSession, r as HubMountedFrame, t as CreateHubContextOptions, u as DevframePtyExecuteOptions, w as DevframeMessageEntryInput } from "../context-BmHQp89a.mjs";
2
2
  import { C as DevframeDockUserEntry, T as DevframeDocksHost$1, _ as DevframeDockEntry, d as DevframeServerCommandEntry, f as DevframeServerCommandInput, l as DevframeCommandsHost$1, o as DevframeCommandHandle, p as ClientScriptEntry, t as DevframeDocksUserSettings } from "../settings-CWHPzCWS.mjs";
3
3
  import { RpcFunctionDefinitionAny } from "devframe/rpc";
4
4
  import { SharedState } from "devframe/utils/shared-state";
@@ -388,4 +388,4 @@ declare const builtinHubRpcDeclarations: readonly RpcFunctionDefinitionAny[];
388
388
  */
389
389
  declare function createSimpleClientScript(fn: string | ((ctx: any) => void)): ClientScriptEntry;
390
390
  //#endregion
391
- export { CreateHubContextOptions, DevframeCommandsHost, DevframeDocksHost, DevframeHubContext, DevframeMessagesHost, DevframeTerminalsHost, type InstallDevframeOptions, builtinHubRpcDeclarations, createHubContext, createSimpleClientScript, hubCommandsExecute, hubDocksActivate, hubMessagesAdd, hubMessagesClear, hubMessagesRemove, hubMessagesUpdate, hubTerminalsRemove, hubTerminalsResize, hubTerminalsRestart, hubTerminalsTerminate, hubTerminalsWrite };
391
+ export { CreateHubContextOptions, DevframeCommandsHost, DevframeDocksHost, DevframeHubContext, DevframeMessagesHost, DevframeTerminalsHost, HubMountedFrame, type InstallDevframeOptions, builtinHubRpcDeclarations, createHubContext, createSimpleClientScript, hubCommandsExecute, hubDocksActivate, hubMessagesAdd, hubMessagesClear, hubMessagesRemove, hubMessagesUpdate, hubTerminalsRemove, hubTerminalsResize, hubTerminalsRestart, hubTerminalsTerminate, hubTerminalsWrite };
@@ -1,8 +1,8 @@
1
- import { _ as DevframeDocksHost, a as hubMessagesAdd, c as hubMessagesUpdate, d as hubTerminalsRestart, f as hubTerminalsTerminate, g as DevframeMessagesHost, h as DevframeTerminalsHost, i as hubDocksActivate, l as hubTerminalsRemove, n as builtinHubRpcDeclarations, o as hubMessagesClear, p as hubTerminalsWrite, r as hubCommandsExecute, s as hubMessagesRemove, t as createHubContext, u as hubTerminalsResize, v as DevframeCommandsHost } from "../context-D4L966cN.mjs";
1
+ import { _ as DevframeCommandsHost, a as hubMessagesAdd, c as hubMessagesUpdate, d as hubTerminalsRestart, f as hubTerminalsTerminate, g as DevframeDocksHost, h as DevframeMessagesHost, i as hubDocksActivate, l as hubTerminalsRemove, m as DevframeTerminalsHost, n as builtinHubRpcDeclarations, o as hubMessagesClear, p as hubTerminalsWrite, r as hubCommandsExecute, s as hubMessagesRemove, t as createHubContext, u as hubTerminalsResize } from "../context-Cdrtg-Kk.mjs";
2
2
  import "ufo";
3
- import "pathe";
4
- import "node:process";
5
3
  import "node:fs";
4
+ import "node:process";
5
+ import "pathe";
6
6
  import { builtinModules } from "node:module";
7
7
  import "node:url";
8
8
  import assert from "node:assert";
@@ -1,7 +1,7 @@
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-Coo1p3ja.mjs";
1
+ import { a as InstallDevframeOptions, n as DevframeHubContext, t as CreateHubContextOptions } from "../context-BmHQp89a.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, McpRouteOptions } from "devframe/types";
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";
@@ -197,14 +197,16 @@ interface InitHubOptions {
197
197
  /**
198
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). Disabled by default; `true`
201
- * mounts it with the loopback origin gate (trusting same-machine callers),
202
- * an object opts into an {@link McpRouteOptions.authorization} identity
203
- * check. A mounted devframe's own `mcp` setting is ignored: the hub's
204
- * aggregate route covers them all (`DF8005` warns when one asks for MCP
205
- * while this is off).
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`).
206
208
  */
207
- mcp?: boolean | McpRouteOptions;
209
+ mcp?: McpSetting;
208
210
  /**
209
211
  * Public origin the host app is reachable at, or a getter. Derived lazily
210
212
  * from the first request when omitted.
@@ -1,19 +1,15 @@
1
- import { a as normalizeHubBase, i as DOCK_RENDERERS_STATE_KEY, r as DEVFRAMES_HUB_BASE } from "../constants-BUd6oMci.mjs";
2
- import { a as resolveClientModuleSpecifier } from "../remote-url-UClKLQ5_.mjs";
3
- import { m as prepareDevframe, t as createHubContext, y as diagnostics } from "../context-D4L966cN.mjs";
4
- import { joinURL, withTrailingSlash, withoutLeadingSlash } from "ufo";
5
- import { DEVFRAME_CONNECTION_META_FILENAME, DEVFRAME_DOCK_IMPORTS_FILENAME, DEVFRAME_MCP_ROUTE, DEVFRAME_WS_ROUTE } from "devframe/constants";
6
- import { createH3DevframeHost, createInstanceShell, importRuntimeModule, resolveInstanceRegister, resolveMcpConfig } 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 { t as createHubContext, y as diagnostics } from "../context-Cdrtg-Kk.mjs";
3
+ import { i as resolveRendererRegistrations, n as renderClientImportsModule, r as resolveDevframesInput, t as mountDevframes } from "../assemble-BvY5jfgi.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,72 +19,6 @@ 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
- /**
37
- * Flatten the `devframes` input: await every thenable, call every factory,
38
- * spread the arrays, and drop the empty slots, so a host can build the list
39
- * conditionally (`isDev && loadInspect()`) without filtering it first.
40
- */
41
- function resolveDevframesInput(input) {
42
- return Promise.all(input.map(async (entry) => {
43
- const resolved = await (typeof entry === "function" ? entry() : entry);
44
- return (Array.isArray(resolved) ? resolved : [resolved]).filter((slot) => slot != null).map(normalizeDevframeEntry);
45
- })).then((arrays) => arrays.flat());
46
- }
47
- /**
48
- * Validate the renderer-module registrations fail-fast: route-safe types
49
- * (each becomes the `<base>__renderers/<type>.mjs` URL segment), one module
50
- * per type, and an existing bundle file (renderer packages are prebuilt).
51
- */
52
- function resolveRendererRegistrations(registrations) {
53
- const seen = /* @__PURE__ */ new Set();
54
- return registrations.map((registration) => {
55
- if (!/^[\w.-]+$/.test(registration.type)) throw diagnostics.DF8110({ type: registration.type });
56
- if (seen.has(registration.type)) throw diagnostics.DF8108({ type: registration.type });
57
- seen.add(registration.type);
58
- const file = resolve(registration.file);
59
- if (!existsSync(file)) throw diagnostics.DF8109({
60
- type: registration.type,
61
- file
62
- });
63
- return {
64
- ...registration,
65
- file
66
- };
67
- });
68
- }
69
- /**
70
- * Render the dock client-script import map as an ES module: one dynamic
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
22
  /**
93
23
  * Resolve the shared hub context: reuse a caller-supplied one, or build an
94
24
  * h3-backed context that serves the hub's connection meta under every frame.
@@ -126,31 +56,6 @@ async function resolveHubContext(options, app, cwd, api) {
126
56
  });
127
57
  }
128
58
  /**
129
- * Pass 1: mount each devframe under `<base><id>/` (SPA, meta, iframe dock)
130
- * and queue its declared services, guarding the id against reserved hub
131
- * filenames and route-pattern characters. Returns the deferred setup thunks.
132
- */
133
- async function mountDevframes(ctx, devframes, base, frames, hubMcpEnabled) {
134
- const setups = [];
135
- for (const { devframe: def, dock } of devframes) {
136
- if (RESERVED_HUB_PATHS.includes(def.id)) throw diagnostics.DF8000({ id: def.id });
137
- if (!/^[\w.-]+$/.test(def.id)) throw diagnostics.DF8004({ id: def.id });
138
- if (!hubMcpEnabled && def.cli?.mcp) diagnostics.DF8005({ id: def.id });
139
- const frameBase = withTrailingSlash(joinURL(base, def.id));
140
- const run = await prepareDevframe(ctx, def, {
141
- base: frameBase,
142
- ...dock ? { dock } : {}
143
- });
144
- if (run) setups.push(run);
145
- frames.push({
146
- id: def.id,
147
- base: frameBase,
148
- title: def.name
149
- });
150
- }
151
- return setups;
152
- }
153
- /**
154
59
  * Initiate a hub instance: the whole multi-devframe devtools surface
155
60
  * behind one framework-agnostic, web-standard handler. Every mounted
156
61
  * devframe shares one context (merged RPC registry, shared state, docks /
@@ -170,7 +75,6 @@ function initHub(options) {
170
75
  const baseNoSlash = base.slice(0, -1);
171
76
  const app = new H3();
172
77
  const cwd = options.cwd ?? process.cwd();
173
- const frames = [];
174
78
  const rendererRegistrations = resolveRendererRegistrations(options.renderers ?? []);
175
79
  const shell = createInstanceShell({
176
80
  base,
@@ -195,7 +99,7 @@ function initHub(options) {
195
99
  */
196
100
  absoluteWsPath: true,
197
101
  resolveSidecarPort: async (sidecarHost) => {
198
- const { getPort } = await import("../dist-R0T2g9Vw.mjs");
102
+ const { getPort } = await import("../dist-BP3CGpgZ.mjs");
199
103
  return getPort({
200
104
  port: 9777,
201
105
  portRange: [9777, 9877],
@@ -213,7 +117,7 @@ function initHub(options) {
213
117
  };
214
118
  const devframes = await resolveDevframesInput(options.devframes ?? []);
215
119
  for (const input of options.services ?? []) ctx.services.install(input);
216
- const setups = await mountDevframes(ctx, devframes, base, frames, !!options.mcp);
120
+ const setups = await mountDevframes(ctx, devframes, base, options.mcp !== false);
217
121
  await ctx.services.ready();
218
122
  for (const run of setups) await run();
219
123
  await options.configure?.(ctx);
@@ -224,11 +128,17 @@ function initHub(options) {
224
128
  ...registration.importName ? { importName: registration.importName } : {}
225
129
  };
226
130
  (await ctx.rpc.sharedState.get(DOCK_RENDERERS_STATE_KEY, { initialValue: {} })).mutate(() => manifest);
227
- const mcpConfig = resolveMcpConfig(options.mcp);
131
+ const mcpSetting = options.mcp ?? "auto";
132
+ let mcpModule;
133
+ let mcpConfig;
134
+ if (mcpSetting === "auto") {
135
+ mcpModule = await loadAutoMcpAdapter(ctx.agent);
136
+ if (mcpModule) mcpConfig = { authorization: false };
137
+ } else mcpConfig = resolveMcpConfig(mcpSetting);
228
138
  if (!mcpConfig) return { context: ctx };
229
139
  const mcpRoute = withoutLeadingSlash(mcpConfig.path ?? DEVFRAME_MCP_ROUTE);
230
- const { mountMcpHttp } = await importRuntimeModule("devframe/adapters/mcp");
231
- const mounted = mountMcpHttp(app, ctx, joinURL(base, mcpRoute), {
140
+ mcpModule ??= await importRuntimeModule("devframe/adapters/mcp");
141
+ const mounted = mcpModule.mountMcpHttp(app, ctx, joinURL(base, mcpRoute), {
232
142
  serverName: options.name ?? "devframes-hub",
233
143
  serverVersion: options.version ?? "0.0.0",
234
144
  exposeSharedState: true,
@@ -247,7 +157,11 @@ function initHub(options) {
247
157
  name: options.name,
248
158
  version: options.version,
249
159
  base,
250
- frames,
160
+ frames: ctx.frames.map(({ id, base, title }) => ({
161
+ id,
162
+ base,
163
+ title
164
+ })),
251
165
  endpoints: {
252
166
  connection: DEVFRAME_CONNECTION_META_FILENAME,
253
167
  clientImports: DEVFRAME_DOCK_IMPORTS_FILENAME,
@@ -1,4 +1,4 @@
1
- import { A as DevframeMessagesLevelShortcuts, C as DevframeMessageEntryInput, D as DevframeMessageShortcutInput, E as DevframeMessageLevel, O as DevframeMessagesClient, S as DevframeMessageEntryFrom, T as DevframeMessageHandle, _ as DevframeMessageAction, a as DevframeChildProcessExecuteOptions, b as DevframeMessageElementPosition, c as DevframeChildProcessTerminalSession, d as DevframePtyResult, f as DevframePtyTerminalSession, g as DevframeTerminalsHost, h as DevframeTerminalStatus, j as DevframeMessagesListDelta, k as DevframeMessagesHost, l as DevframePtyExecuteOptions, m as DevframeTerminalSessionBase, n as DevframeHubContext, o as DevframeChildProcessOutput, p as DevframeTerminalSession, s as DevframeChildProcessResult, t as CreateHubContextOptions, u as DevframePtyOutput, v as DevframeMessageActivateAction, w as DevframeMessageFilePosition, x as DevframeMessageEntry, y as DevframeMessageCommandAction } from "../context-Di-fhMKP.mjs";
1
+ import { A as DevframeMessagesHost, C as DevframeMessageEntryFrom, D as DevframeMessageLevel, E as DevframeMessageHandle, M as DevframeMessagesListDelta, O as DevframeMessageShortcutInput, S as DevframeMessageEntry, T as DevframeMessageFilePosition, _ as DevframeTerminalsHost, b as DevframeMessageCommandAction, c as DevframeChildProcessResult, d as DevframePtyOutput, f as DevframePtyResult, g as DevframeTerminalStatus, h as DevframeTerminalSessionBase, j as DevframeMessagesLevelShortcuts, k as DevframeMessagesClient, l as DevframeChildProcessTerminalSession, m as DevframeTerminalSession, n as DevframeHubContext, o as DevframeChildProcessExecuteOptions, p as DevframePtyTerminalSession, s as DevframeChildProcessOutput, t as CreateHubContextOptions, u as DevframePtyExecuteOptions, v as DevframeMessageAction, w as DevframeMessageEntryInput, x as DevframeMessageElementPosition, y as DevframeMessageActivateAction } from "../context-BmHQp89a.mjs";
2
2
  import { A as DevframeViewIframe, C as DevframeDockUserEntry, D as DevframeViewBuiltin, E as DevframeViewAction, F as RemoteConnectionInfo, I as RemoteDockOptions, M as DevframeViewLauncherStatus, N as FrameSubTabsConfig, O as DevframeViewCustomRender, P as NavTarget, S as DevframeDockPanelState, T as DevframeDocksHost, _ as DevframeDockEntry, a as DevframeCommandEntry, b as DevframeDockEntryIcon, c as DevframeCommandShortcutOverrides, d as DevframeServerCommandEntry, f as DevframeServerCommandInput, g as DevframeDockEntriesGrouped, h as DevframeDockBadgeVariant, i as DevframeCommandBase, j as DevframeViewLauncher, k as DevframeViewGroup, l as DevframeCommandsHost, m as DevframeDockActivation, n as DevframeClientCommand, o as DevframeCommandHandle, p as ClientScriptEntry, r as DevframeCommandAgentOptions, s as DevframeCommandKeybinding, t as DevframeDocksUserSettings, u as DevframeCommandsHostEvents, v as DevframeDockEntryBase, w as DevframeDocksActiveState, x as DevframeDockEntryRegistry, y as DevframeDockEntryCategory } from "../settings-CWHPzCWS.mjs";
3
- import { S as RpcStreamingHost, _ as RpcFunctionsHost, a as DevframeHost, b as RpcStreamingChannel, c as DevframeRpcServerFunctions, d as EventEmitter, f as EventUnsubscribe, g as RpcDefinitionsToFunctions, h as RpcDefinitionsFilter, i as DevframeDiagnosticsLogger, l as DevframeRpcSharedStates, m as RpcBroadcastOptions, n as DevframeCapabilities, o as DevframeNodeRpcSession, p as EventsMap, r as DevframeDiagnosticsHost, s as DevframeRpcClientFunctions, t as ConnectionMeta, u as DevframeViewHost, v as RpcSharedStateGetOptions, x as RpcStreamingChannelOptions, y as RpcSharedStateHost } from "../index-faD2d5iX.mjs";
3
+ import { S as RpcStreamingHost, _ as RpcFunctionsHost, a as DevframeHost, b as RpcStreamingChannel, c as DevframeRpcServerFunctions, d as EventEmitter, f as EventUnsubscribe, g as RpcDefinitionsToFunctions, h as RpcDefinitionsFilter, i as DevframeDiagnosticsLogger, l as DevframeRpcSharedStates, m as RpcBroadcastOptions, n as DevframeCapabilities, o as DevframeNodeRpcSession, p as EventsMap, r as DevframeDiagnosticsHost, s as DevframeRpcClientFunctions, t as ConnectionMeta, u as DevframeViewHost, v as RpcSharedStateGetOptions, x as RpcStreamingChannelOptions, y as RpcSharedStateHost } from "../index-Dv8iu9St.mjs";
4
4
  export { ClientScriptEntry, type ConnectionMeta, type CreateHubContextOptions, type DevframeCapabilities, DevframeChildProcessExecuteOptions, DevframeChildProcessOutput, DevframeChildProcessResult, DevframeChildProcessTerminalSession, DevframeClientCommand, DevframeCommandAgentOptions, DevframeCommandBase, DevframeCommandEntry, DevframeCommandHandle, DevframeCommandKeybinding, DevframeCommandShortcutOverrides, DevframeCommandsHost, DevframeCommandsHostEvents, type DevframeDiagnosticsHost, type DevframeDiagnosticsLogger, DevframeDockActivation, DevframeDockBadgeVariant, DevframeDockEntriesGrouped, DevframeDockEntry, DevframeDockEntryBase, DevframeDockEntryCategory, DevframeDockEntryIcon, DevframeDockEntryRegistry, DevframeDockPanelState, DevframeDockUserEntry, DevframeDocksActiveState, DevframeDocksHost, DevframeDocksUserSettings, type DevframeHost, type DevframeHubContext, DevframeMessageAction, DevframeMessageActivateAction, DevframeMessageCommandAction, DevframeMessageElementPosition, DevframeMessageEntry, DevframeMessageEntryFrom, DevframeMessageEntryInput, DevframeMessageFilePosition, DevframeMessageHandle, DevframeMessageLevel, DevframeMessageShortcutInput, DevframeMessagesClient, DevframeMessagesHost, DevframeMessagesLevelShortcuts, DevframeMessagesListDelta, type DevframeNodeRpcSession, DevframePtyExecuteOptions, DevframePtyOutput, DevframePtyResult, DevframePtyTerminalSession, type DevframeRpcClientFunctions, type DevframeRpcServerFunctions, type DevframeRpcSharedStates, DevframeServerCommandEntry, DevframeServerCommandInput, DevframeTerminalSession, DevframeTerminalSessionBase, DevframeTerminalStatus, DevframeTerminalsHost, DevframeViewAction, DevframeViewBuiltin, DevframeViewCustomRender, DevframeViewGroup, type DevframeViewHost, DevframeViewIframe, DevframeViewLauncher, DevframeViewLauncherStatus, type EventEmitter, type EventUnsubscribe, type EventsMap, FrameSubTabsConfig, NavTarget, RemoteConnectionInfo, RemoteDockOptions, type RpcBroadcastOptions, type RpcDefinitionsFilter, type RpcDefinitionsToFunctions, type RpcFunctionsHost, type RpcSharedStateGetOptions, type RpcSharedStateHost, type RpcStreamingChannel, type RpcStreamingChannelOptions, type RpcStreamingHost };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@devframes/hub",
3
3
  "type": "module",
4
- "version": "0.9.9",
4
+ "version": "0.9.11",
5
5
  "description": "Hub layer that orchestrates devframe docks, terminals, messages, and commands on any host.",
6
6
  "author": "Anthony Fu <anthonyfu117@hotmail.com>",
7
7
  "license": "MIT",
@@ -22,6 +22,7 @@
22
22
  "sideEffects": false,
23
23
  "exports": {
24
24
  ".": "./dist/index.mjs",
25
+ "./build": "./dist/node/build.mjs",
25
26
  "./client": "./dist/client/index.mjs",
26
27
  "./constants": "./dist/constants.mjs",
27
28
  "./initiate": "./dist/node/initiate.mjs",
@@ -34,7 +35,7 @@
34
35
  "dist"
35
36
  ],
36
37
  "peerDependencies": {
37
- "devframe": "0.9.9"
38
+ "devframe": "0.9.11"
38
39
  },
39
40
  "dependencies": {
40
41
  "@standard-schema/spec": "^1.1.0",
@@ -48,7 +49,7 @@
48
49
  },
49
50
  "devDependencies": {
50
51
  "@types/node": "^26.4.0",
51
- "devframe": "0.9.9",
52
+ "devframe": "0.9.11",
52
53
  "get-port-please": "^3.2.0",
53
54
  "mlly": "^1.8.2",
54
55
  "tsdown": "^0.22.14",
@@ -1,5 +1,5 @@
1
- import "node:path";
2
1
  import "node:fs/promises";
2
+ import "node:path";
3
3
  import { createServer } from "node:net";
4
4
  import { networkInterfaces } from "node:os";
5
5
  //#region ../../node_modules/.pnpm/get-port-please@3.2.0/node_modules/get-port-please/dist/index.mjs