@devframes/hub 0.9.7 → 0.9.9
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/client/index.d.mts +72 -51
- package/dist/client/index.mjs +119 -44
- package/dist/{constants-DrF61GFx.mjs → constants-BUd6oMci.mjs} +9 -8
- package/dist/{constants-C5t0DeZ0.d.mts → constants-Coo1p3ja.d.mts} +12 -9
- package/dist/constants.d.mts +1 -1
- package/dist/constants.mjs +1 -1
- package/dist/{context-C3hUUCC1.mjs → context-D4L966cN.mjs} +233 -135
- package/dist/{context-D5JSW_X5.d.mts → context-Di-fhMKP.d.mts} +40 -18
- package/dist/{index-DhxIxS-Q.d.mts → index-faD2d5iX.d.mts} +2 -2
- package/dist/index.d.mts +4 -4
- package/dist/node/index.d.mts +28 -19
- package/dist/node/index.mjs +2 -2
- package/dist/node/initiate.d.mts +35 -30
- package/dist/node/initiate.mjs +80 -53
- package/dist/{remote-url-Bgc7gtsP.mjs → remote-url-UClKLQ5_.mjs} +34 -39
- package/dist/{settings-D7whfcx2.d.mts → settings-CWHPzCWS.d.mts} +65 -39
- package/dist/types/index.d.mts +4 -4
- package/package.json +6 -6
package/dist/node/initiate.mjs
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { a as normalizeHubBase, i as DOCK_RENDERERS_STATE_KEY, r as DEVFRAMES_HUB_BASE } from "../constants-
|
|
2
|
-
import { a as resolveClientModuleSpecifier } from "../remote-url-
|
|
3
|
-
import { m as prepareDevframe, t as createHubContext, y as diagnostics } from "../context-
|
|
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
4
|
import { joinURL, withTrailingSlash, withoutLeadingSlash } from "ufo";
|
|
5
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 } from "devframe/internal";
|
|
6
|
+
import { createH3DevframeHost, createInstanceShell, importRuntimeModule, resolveInstanceRegister, resolveMcpConfig } from "devframe/internal";
|
|
7
7
|
import { resolve } from "pathe";
|
|
8
8
|
import process from "node:process";
|
|
9
9
|
import { existsSync } from "node:fs";
|
|
@@ -23,7 +23,7 @@ function assetContentType(key) {
|
|
|
23
23
|
if (key.endsWith(".html")) return "text/html; charset=utf-8";
|
|
24
24
|
return "application/octet-stream";
|
|
25
25
|
}
|
|
26
|
-
/** Reserved filenames directly under the hub base
|
|
26
|
+
/** Reserved filenames directly under the hub base; a frame id can't shadow them. */
|
|
27
27
|
const RESERVED_HUB_PATHS = [
|
|
28
28
|
DEVFRAME_CONNECTION_META_FILENAME,
|
|
29
29
|
DEVFRAME_DOCK_IMPORTS_FILENAME,
|
|
@@ -35,7 +35,7 @@ const RESERVED_HUB_PATHS = [
|
|
|
35
35
|
];
|
|
36
36
|
/**
|
|
37
37
|
* Flatten the `devframes` input: await every thenable, call every factory,
|
|
38
|
-
* spread the arrays, and drop the empty slots
|
|
38
|
+
* spread the arrays, and drop the empty slots, so a host can build the list
|
|
39
39
|
* conditionally (`isDev && loadInspect()`) without filtering it first.
|
|
40
40
|
*/
|
|
41
41
|
function resolveDevframesInput(input) {
|
|
@@ -67,7 +67,7 @@ function resolveRendererRegistrations(registrations) {
|
|
|
67
67
|
});
|
|
68
68
|
}
|
|
69
69
|
/**
|
|
70
|
-
* Render the dock client-script import map as an ES module
|
|
70
|
+
* Render the dock client-script import map as an ES module: one dynamic
|
|
71
71
|
* import thunk per dock that carries a client script (`clientScript` on
|
|
72
72
|
* iframe docks, `action`, `renderer`). External viewers import this module
|
|
73
73
|
* from `<base>__client-imports.js` to load per-dock client code into the
|
|
@@ -87,17 +87,78 @@ function renderClientImportsModule(ctx) {
|
|
|
87
87
|
const thunks = scripts.map((script) => `() => import(${JSON.stringify(resolveClientModuleSpecifier(script.importFrom, { template }))})`);
|
|
88
88
|
entries.push(` ${JSON.stringify(id)}: [${thunks.join(", ")}],`);
|
|
89
89
|
}
|
|
90
|
-
return `// Generated by @devframes/hub
|
|
90
|
+
return `// Generated by @devframes/hub: dock client-script import map.\nexport const clientImports = {\n${entries.join("\n")}\n}\nexport default clientImports\n`;
|
|
91
91
|
}
|
|
92
92
|
/**
|
|
93
|
-
*
|
|
93
|
+
* Resolve the shared hub context: reuse a caller-supplied one, or build an
|
|
94
|
+
* h3-backed context that serves the hub's connection meta under every frame.
|
|
95
|
+
*/
|
|
96
|
+
async function resolveHubContext(options, app, cwd, api) {
|
|
97
|
+
if (options.context && options.devframes?.length) throw diagnostics.DF8002();
|
|
98
|
+
if (options.context) return options.context;
|
|
99
|
+
const host = {
|
|
100
|
+
...createH3DevframeHost({
|
|
101
|
+
origin: () => api.origin() ?? "http://localhost",
|
|
102
|
+
appName: "devframes",
|
|
103
|
+
workspaceRoot: cwd,
|
|
104
|
+
mount: (mountBase, dir) => {
|
|
105
|
+
mountStaticHandler(app, mountBase, dir);
|
|
106
|
+
}
|
|
107
|
+
}),
|
|
108
|
+
...options.getStorageDir ? { getStorageDir: options.getStorageDir } : {},
|
|
109
|
+
/**
|
|
110
|
+
* Serve the hub's own connection meta under every mounted frame's
|
|
111
|
+
* base, so each SPA discovers the shared RPC endpoint via its
|
|
112
|
+
* relative `./__connection.json` fetch; the meta's WS path is
|
|
113
|
+
* hub-base-absolute, so it resolves to the one shared socket no
|
|
114
|
+
* matter how deep the frame base is.
|
|
115
|
+
*/
|
|
116
|
+
mountConnectionMeta: (frameBase) => {
|
|
117
|
+
app.use(joinURL(frameBase, DEVFRAME_CONNECTION_META_FILENAME), () => api.connectionMeta());
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
return createHubContext({
|
|
121
|
+
cwd,
|
|
122
|
+
workspaceRoot: cwd,
|
|
123
|
+
mode: "dev",
|
|
124
|
+
host,
|
|
125
|
+
...options.rpcDeclarations ? { builtinRpcDeclarations: options.rpcDeclarations } : {}
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
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
|
+
* Initiate a hub instance: the whole multi-devframe devtools surface
|
|
94
155
|
* behind one framework-agnostic, web-standard handler. Every mounted
|
|
95
156
|
* devframe shares one context (merged RPC registry, shared state, docks /
|
|
96
157
|
* terminals / messages / commands), one WebSocket transport, and one Auth;
|
|
97
158
|
* the instance serves each frame's SPA at `<base><id>/`, the discovery
|
|
98
159
|
* endpoints (`__connection.json`, `__index.json`, `__client-imports.js`),
|
|
99
160
|
* the aggregate MCP route, and whatever the {@link DevframeHubUi} slot
|
|
100
|
-
* provides
|
|
161
|
+
* provides, while the hub itself stays headless.
|
|
101
162
|
*
|
|
102
163
|
* The factory is synchronous and initializes eagerly, and binds no port of
|
|
103
164
|
* its own: the WebSocket follows `server` / `ws` (see
|
|
@@ -127,6 +188,11 @@ function initHub(options) {
|
|
|
127
188
|
...options.name !== void 0 ? { name: options.name } : {},
|
|
128
189
|
rootDir: cwd
|
|
129
190
|
}),
|
|
191
|
+
/**
|
|
192
|
+
* One meta document is served from the hub base *and* from every frame
|
|
193
|
+
* base, so the advertised WS path has to be base-absolute to resolve to
|
|
194
|
+
* the same socket from any depth.
|
|
195
|
+
*/
|
|
130
196
|
absoluteWsPath: true,
|
|
131
197
|
resolveSidecarPort: async (sidecarHost) => {
|
|
132
198
|
const { getPort } = await import("../dist-R0T2g9Vw.mjs");
|
|
@@ -140,54 +206,14 @@ function initHub(options) {
|
|
|
140
206
|
throw diagnostics.DF8003();
|
|
141
207
|
},
|
|
142
208
|
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
|
-
}
|
|
209
|
+
const ctx = await resolveHubContext(options, app, cwd, api);
|
|
169
210
|
if (options.clientModuleResolution) ctx.staticConfig.dock = {
|
|
170
211
|
...ctx.staticConfig.dock,
|
|
171
212
|
clientModuleResolution: options.clientModuleResolution
|
|
172
213
|
};
|
|
173
214
|
const devframes = await resolveDevframesInput(options.devframes ?? []);
|
|
174
215
|
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
|
-
}
|
|
216
|
+
const setups = await mountDevframes(ctx, devframes, base, frames, !!options.mcp);
|
|
191
217
|
await ctx.services.ready();
|
|
192
218
|
for (const run of setups) await run();
|
|
193
219
|
await options.configure?.(ctx);
|
|
@@ -198,7 +224,7 @@ function initHub(options) {
|
|
|
198
224
|
...registration.importName ? { importName: registration.importName } : {}
|
|
199
225
|
};
|
|
200
226
|
(await ctx.rpc.sharedState.get(DOCK_RENDERERS_STATE_KEY, { initialValue: {} })).mutate(() => manifest);
|
|
201
|
-
const mcpConfig = options.mcp
|
|
227
|
+
const mcpConfig = resolveMcpConfig(options.mcp);
|
|
202
228
|
if (!mcpConfig) return { context: ctx };
|
|
203
229
|
const mcpRoute = withoutLeadingSlash(mcpConfig.path ?? DEVFRAME_MCP_ROUTE);
|
|
204
230
|
const { mountMcpHttp } = await importRuntimeModule("devframe/adapters/mcp");
|
|
@@ -206,6 +232,7 @@ function initHub(options) {
|
|
|
206
232
|
serverName: options.name ?? "devframes-hub",
|
|
207
233
|
serverVersion: options.version ?? "0.0.0",
|
|
208
234
|
exposeSharedState: true,
|
|
235
|
+
authorization: mcpConfig.authorization,
|
|
209
236
|
allowedOrigins: mcpConfig.allowedOrigins
|
|
210
237
|
});
|
|
211
238
|
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
|
|
@@ -17,7 +17,7 @@ interface DevframeDocksHost {
|
|
|
17
17
|
* carrying `params` for the target dock to interpret (e.g. a terminals
|
|
18
18
|
* session id).
|
|
19
19
|
*
|
|
20
|
-
* Any connected client may drive this via the `hub:docks:activate` RPC
|
|
20
|
+
* Any connected client may drive this via the `hub:docks:activate` RPC, so a
|
|
21
21
|
* mounted devframe running in its own iframe can steer the host shell's dock
|
|
22
22
|
* selection, which is otherwise client-local. The request is delivered live
|
|
23
23
|
* to connected clients (broadcast) and mirrored into the
|
|
@@ -27,9 +27,14 @@ interface DevframeDocksHost {
|
|
|
27
27
|
*/
|
|
28
28
|
activate: (dockId: string, params?: Record<string, unknown>) => void;
|
|
29
29
|
}
|
|
30
|
+
/** Current state of one dock panel in a host page. */
|
|
31
|
+
interface DevframeDockPanelState {
|
|
32
|
+
state: 'open' | 'closed' | 'hidden';
|
|
33
|
+
selectedDockId?: string;
|
|
34
|
+
}
|
|
30
35
|
/**
|
|
31
36
|
* A request to switch the active dock. `params` is an opaque, serializable
|
|
32
|
-
* bag the target dock interprets
|
|
37
|
+
* bag the target dock interprets; the terminals dock reads `params.sessionId`
|
|
33
38
|
* to focus a specific session.
|
|
34
39
|
*/
|
|
35
40
|
interface DevframeDockActivation {
|
|
@@ -37,7 +42,7 @@ interface DevframeDockActivation {
|
|
|
37
42
|
params?: Record<string, unknown>;
|
|
38
43
|
}
|
|
39
44
|
/**
|
|
40
|
-
* Shape of the `devframe:docks:active` shared-state slot
|
|
45
|
+
* Shape of the `devframe:docks:active` shared-state slot: the most recent
|
|
41
46
|
* {@link DevframeDockActivation}, or `null` before any activation. Mirrored
|
|
42
47
|
* so a dock that mounts in response to an activation can still converge on the
|
|
43
48
|
* request instead of missing the live broadcast.
|
|
@@ -45,12 +50,19 @@ interface DevframeDockActivation {
|
|
|
45
50
|
interface DevframeDocksActiveState {
|
|
46
51
|
activation: DevframeDockActivation | null;
|
|
47
52
|
}
|
|
53
|
+
/**
|
|
54
|
+
* Known categories the hub orders by default (see
|
|
55
|
+
* {@link import('../constants').DEFAULT_CATEGORIES_ORDER}). Kits may pass their
|
|
56
|
+
* own category ids; `(string & {})` keeps autocomplete on the known set while
|
|
57
|
+
* allowing arbitrary string values. `~builtin` is reserved for the viewer's
|
|
58
|
+
* own built-in views (see {@link DevframeViewBuiltin}) and always sorts last.
|
|
59
|
+
*/
|
|
48
60
|
type DevframeDockEntryCategory = 'framework' | 'app' | 'ui' | 'data' | 'web' | 'performance' | 'advanced' | 'docs' | 'default' | '~builtin' | (string & {});
|
|
49
61
|
type DevframeDockEntryIcon = string | {
|
|
50
62
|
light: string;
|
|
51
63
|
dark: string;
|
|
52
64
|
};
|
|
53
|
-
/** Color for a dock-bar entry's {@link DevframeDockEntryBase.badge}
|
|
65
|
+
/** Color for a dock-bar entry's {@link DevframeDockEntryBase.badge}, mirroring json-render's `TabDescriptor.badgeVariant`. */
|
|
54
66
|
type DevframeDockBadgeVariant = 'default' | 'info' | 'success' | 'warning' | 'danger';
|
|
55
67
|
interface DevframeDockEntryBase {
|
|
56
68
|
id: string;
|
|
@@ -63,17 +75,17 @@ interface DevframeDockEntryBase {
|
|
|
63
75
|
*/
|
|
64
76
|
defaultOrder?: number;
|
|
65
77
|
/**
|
|
66
|
-
* The category of the entry
|
|
78
|
+
* The category of the entry, a field with a dual role that depends on
|
|
67
79
|
* whether {@link groupId} resolves to a registered {@link DevframeViewGroup}:
|
|
68
80
|
*
|
|
69
|
-
* - **Ungrouped (or orphan) entry
|
|
81
|
+
* - **Ungrouped (or orphan) entry**: `category` is the entry's OUTER bucket
|
|
70
82
|
* on the dock bar, ordered by {@link import('../constants').DEFAULT_CATEGORIES_ORDER}.
|
|
71
|
-
* - **Grouped entry** (a `groupId` that resolves to a registered group)
|
|
83
|
+
* - **Grouped entry** (a `groupId` that resolves to a registered group):
|
|
72
84
|
* the OUTER bucket is instead the group's own `category`, and this field is
|
|
73
85
|
* reinterpreted as the entry's IN-GROUP sub-category, used to sub-divide and
|
|
74
86
|
* sort members inside the group's popover / sub-navigation.
|
|
75
87
|
*
|
|
76
|
-
* Falls back to `'default'` when omitted
|
|
88
|
+
* Falls back to `'default'` when omitted, both as an outer bucket and, for a
|
|
77
89
|
* grouped member, as its in-group sub-bucket.
|
|
78
90
|
*
|
|
79
91
|
* @default 'default'
|
|
@@ -93,13 +105,13 @@ interface DevframeDockEntryBase {
|
|
|
93
105
|
/**
|
|
94
106
|
* Render-only conditional visibility expression, same syntax as {@link when}.
|
|
95
107
|
* When it evaluates to `false`, a viewer omits the entry from the rendered
|
|
96
|
-
* dock bar / list, but the entry stays registered and fully reachable
|
|
108
|
+
* dock bar / list, but the entry stays registered and fully reachable:
|
|
97
109
|
* `docks.activate()`/`switchEntry()` by id, RPC lookups, and anything else
|
|
98
110
|
* that walks the raw entry list (e.g. the {@link DevframeViewIframe.subTabs}
|
|
99
111
|
* frame-nav adapter) keep working exactly as if it were visible.
|
|
100
112
|
*
|
|
101
113
|
* Use this instead of {@link when} when an entry must remain part of the
|
|
102
|
-
* model without a dock-bar button of its own
|
|
114
|
+
* model without a dock-bar button of its own; the canonical case is a
|
|
103
115
|
* shared-frame {@link DevframeViewIframe.subTabs anchor}: set
|
|
104
116
|
* `visibility: 'false'` on the anchor so only its synthesized member tabs
|
|
105
117
|
* render, while the anchor itself keeps driving the postMessage nav loop.
|
|
@@ -126,7 +138,7 @@ interface DevframeDockEntryBase {
|
|
|
126
138
|
* under the matching group's button instead of showing it directly on the
|
|
127
139
|
* dock bar.
|
|
128
140
|
*
|
|
129
|
-
* This is a flat pointer
|
|
141
|
+
* This is a flat pointer: membership, not containment. The entry stays an
|
|
130
142
|
* independently-registered, top-level entry; only its rendering is grouped
|
|
131
143
|
* downstream.
|
|
132
144
|
*
|
|
@@ -143,7 +155,7 @@ interface DevframeDockEntryBase {
|
|
|
143
155
|
}
|
|
144
156
|
interface ClientScriptEntry {
|
|
145
157
|
/**
|
|
146
|
-
* What to import
|
|
158
|
+
* What to import: either a **URL the host serves** (a self-contained ES
|
|
147
159
|
* module, e.g. `/@fs/<abs path>` under Vite or a statically-mounted bundle
|
|
148
160
|
* path), or a **bare npm specifier** (e.g.
|
|
149
161
|
* `'vite-plugin-vue-tracer/client/vite-devtools'`), a host-runtime
|
|
@@ -166,7 +178,7 @@ declare module 'devframe/types' {
|
|
|
166
178
|
dock: {
|
|
167
179
|
/**
|
|
168
180
|
* URL template resolving a **bare-specifier** client script to an
|
|
169
|
-
* importable URL on this host
|
|
181
|
+
* importable URL on this host, with `{specifier}` replaced with the bare
|
|
170
182
|
* specifier (a token-less template is used as a prefix). Declaring it
|
|
171
183
|
* advertises the host runtime can serve npm modules to the browser:
|
|
172
184
|
* Vite hosts declare `'/@id/{specifier}'`, routing the import (and its
|
|
@@ -180,6 +192,15 @@ declare module 'devframe/types' {
|
|
|
180
192
|
interface DevframeViewIframe extends DevframeDockEntryBase {
|
|
181
193
|
type: 'iframe';
|
|
182
194
|
url: string;
|
|
195
|
+
/** Request the address bar for this iframe dock. Pass an object to configure its controls. The user's global always-show setting forces it on for every iframe. */
|
|
196
|
+
addressBar?: boolean | {
|
|
197
|
+
/** Override Back visibility. Cross-origin history access may still be blocked by the browser. */
|
|
198
|
+
back?: boolean;
|
|
199
|
+
/** Override Reload visibility. */
|
|
200
|
+
reload?: boolean;
|
|
201
|
+
/** Override Open externally visibility. */
|
|
202
|
+
openExternal?: boolean;
|
|
203
|
+
};
|
|
183
204
|
/**
|
|
184
205
|
* The id of the iframe, if multiple tabs is assigned with the same id, the iframe will be shared.
|
|
185
206
|
*
|
|
@@ -223,10 +244,10 @@ interface DevframeViewIframe extends DevframeDockEntryBase {
|
|
|
223
244
|
* Enable remote-UI mode: the hub injects a connection descriptor
|
|
224
245
|
* (WS URL + pre-approved auth token) into the iframe URL so a hosted
|
|
225
246
|
* page can connect back via `connectRemoteDevframe()` from
|
|
226
|
-
* `@devframes/hub/client
|
|
247
|
+
* `@devframes/hub/client`, without needing to ship a dist with the
|
|
227
248
|
* plugin.
|
|
228
249
|
*
|
|
229
|
-
* Requires dev mode (no effect in build mode
|
|
250
|
+
* Requires dev mode (no effect in build mode, where no WS server exists).
|
|
230
251
|
* When enabled, the dock is automatically hidden in build mode unless
|
|
231
252
|
* the author provides an explicit `when` clause.
|
|
232
253
|
*/
|
|
@@ -234,16 +255,21 @@ interface DevframeViewIframe extends DevframeDockEntryBase {
|
|
|
234
255
|
}
|
|
235
256
|
/**
|
|
236
257
|
* A structured, soft-navigation target within a shared frame. `path` is opaque
|
|
237
|
-
* to the hub
|
|
258
|
+
* to the hub, which the embedded app maps onto its own router.
|
|
238
259
|
*
|
|
239
260
|
* Kept to `path` + `query` so the shape survives shared-state's `Immutable`
|
|
240
261
|
* projection cleanly (a `DevframeViewIframe` must still narrow back from its
|
|
241
262
|
* immutable form). An `unknown`/recursive history-`state` field breaks that
|
|
242
263
|
* round-trip, so richer per-navigation state is intentionally out of scope for
|
|
243
|
-
* now
|
|
264
|
+
* now; carry it in `query` or the app's own store.
|
|
244
265
|
*/
|
|
245
266
|
interface NavTarget {
|
|
246
267
|
path: string;
|
|
268
|
+
/**
|
|
269
|
+
* `readonly` arrays keep this shape stable under the shared-state `Immutable`
|
|
270
|
+
* projection, so a `Immutable<DevframeViewIframe>` still narrows back to
|
|
271
|
+
* `DevframeViewIframe` (a mutable `string[]` would not).
|
|
272
|
+
*/
|
|
247
273
|
query?: Record<string, string | readonly string[]>;
|
|
248
274
|
}
|
|
249
275
|
/**
|
|
@@ -266,7 +292,7 @@ interface RemoteDockOptions {
|
|
|
266
292
|
* How to pass the connection descriptor to the hosted page.
|
|
267
293
|
*
|
|
268
294
|
* - `'fragment'` (default): appended as a URL fragment.
|
|
269
|
-
* Not sent in HTTP requests or Referer headers
|
|
295
|
+
* Not sent in HTTP requests or Referer headers, so safest for auth tokens.
|
|
270
296
|
* - `'query'`: appended as a URL query parameter. Use when your hosting
|
|
271
297
|
* platform rewrites fragments or your SPA router repurposes the fragment
|
|
272
298
|
* for navigation. The token will appear in server access logs and
|
|
@@ -305,7 +331,7 @@ interface DevframeViewLauncher extends DevframeDockEntryBase {
|
|
|
305
331
|
/**
|
|
306
332
|
* Bound command id: the launch button, command palette entry, and any
|
|
307
333
|
* keybinding all resolve to this one handler. A viewer running out of
|
|
308
|
-
* process dispatches it over the `hub:commands:execute` RPC
|
|
334
|
+
* process dispatches it over the `hub:commands:execute` RPC, the
|
|
309
335
|
* serializable path {@link onLaunch} can't cross, since a function is
|
|
310
336
|
* dropped when the entry is projected into the `devframe:docks` shared
|
|
311
337
|
* state. Register the command (with its handler) via `ctx.commands`.
|
|
@@ -343,7 +369,7 @@ interface DevframeViewCustomRender extends DevframeDockEntryBase {
|
|
|
343
369
|
renderer: ClientScriptEntry;
|
|
344
370
|
}
|
|
345
371
|
/**
|
|
346
|
-
* A view rendered natively by the viewer rather than by a plugin
|
|
372
|
+
* A view rendered natively by the viewer rather than by a plugin: the
|
|
347
373
|
* settings panel, the terminals feed, the messages feed, etc. A high-level
|
|
348
374
|
* integration registers the built-in views it wants; the viewer recognizes the
|
|
349
375
|
* reserved `id` and renders its own UI for it.
|
|
@@ -362,12 +388,12 @@ interface DevframeViewBuiltin extends DevframeDockEntryBase {
|
|
|
362
388
|
*
|
|
363
389
|
* A group carries its own `title`/`icon`/`category`/`defaultOrder`/`when`
|
|
364
390
|
* (inherited from {@link DevframeDockEntryBase}) and has no view payload of its
|
|
365
|
-
* own
|
|
391
|
+
* own; hosts render its members in a popover / sub-navigation. It flows
|
|
366
392
|
* through the same `register`/`update`/`values` machinery as every other entry,
|
|
367
393
|
* keyed by `id`.
|
|
368
394
|
*
|
|
369
395
|
* The group's `category` is the OUTER bucket for the group button itself AND
|
|
370
|
-
* for every one of its members
|
|
396
|
+
* for every one of its members; a member's own `category` no longer decides
|
|
371
397
|
* its outer bucket, but is reinterpreted as an in-group sub-category that
|
|
372
398
|
* sub-divides and sorts members inside this group. A group with no `category`
|
|
373
399
|
* buckets itself and its members under `'default'`.
|
|
@@ -383,7 +409,7 @@ interface DevframeViewGroup extends DevframeDockEntryBase {
|
|
|
383
409
|
*/
|
|
384
410
|
defaultChildId?: string;
|
|
385
411
|
/**
|
|
386
|
-
* Per-group override of the in-group sub-category ordering
|
|
412
|
+
* Per-group override of the in-group sub-category ordering: a map of
|
|
387
413
|
* sub-category id → ordering weight (lower sorts earlier), mirroring the
|
|
388
414
|
* shape of {@link import('../constants').DEFAULT_CATEGORIES_ORDER}.
|
|
389
415
|
*
|
|
@@ -415,7 +441,7 @@ interface DevframeViewGroup extends DevframeDockEntryBase {
|
|
|
415
441
|
/**
|
|
416
442
|
* The **open** registry of dock entry variants, keyed by their `type`
|
|
417
443
|
* discriminator. The hub ships the framework-neutral built-ins; opt-in
|
|
418
|
-
* integrations contribute their own variants through declaration merging
|
|
444
|
+
* integrations contribute their own variants through declaration merging,
|
|
419
445
|
* e.g. `@devframes/json-render/hub` adds a `'json-render'` entry. The hub
|
|
420
446
|
* itself stays agnostic: it hard-codes no integration-specific variant.
|
|
421
447
|
*
|
|
@@ -459,16 +485,16 @@ interface DevframeCommandBase {
|
|
|
459
485
|
description?: string;
|
|
460
486
|
/**
|
|
461
487
|
* Icon for the command. Either an Iconify icon string (e.g. "ph:pencil-duotone")
|
|
462
|
-
* or a theme-specific pair `{ light, dark }
|
|
488
|
+
* or a theme-specific pair `{ light, dark }`, the same shape as dock icons.
|
|
463
489
|
*/
|
|
464
490
|
icon?: DevframeDockEntryIcon;
|
|
465
491
|
category?: string;
|
|
466
492
|
/**
|
|
467
493
|
* Whether to show in command palette. Default: true
|
|
468
494
|
*
|
|
469
|
-
* - `true
|
|
470
|
-
* - `false
|
|
471
|
-
* - `'without-children'
|
|
495
|
+
* - `true`: show the command and flatten its children into search results
|
|
496
|
+
* - `false`: hide the command entirely from the palette
|
|
497
|
+
* - `'without-children'`: show the command but don't flatten children into top-level search (children are still accessible via drill-down)
|
|
472
498
|
*/
|
|
473
499
|
showInPalette?: boolean | 'without-children';
|
|
474
500
|
/**
|
|
@@ -483,13 +509,13 @@ interface DevframeCommandBase {
|
|
|
483
509
|
keybindings?: DevframeCommandKeybinding[];
|
|
484
510
|
}
|
|
485
511
|
/**
|
|
486
|
-
* Opt-in agent exposure for a server command
|
|
512
|
+
* Opt-in agent exposure for a server command, mirroring the `agent` field on
|
|
487
513
|
* `defineRpcFunction`. A command carrying this field (and a `handler`) is
|
|
488
514
|
* projected into `ctx.agent` as a callable tool, reaching MCP clients through
|
|
489
515
|
* the devframe MCP adapter.
|
|
490
516
|
*
|
|
491
517
|
* `when` clauses are evaluated client-side only and are **not** enforced for
|
|
492
|
-
* agent calls
|
|
518
|
+
* agent calls; opt in a `when`-gated command only if running it outside its
|
|
493
519
|
* UI context is safe.
|
|
494
520
|
*/
|
|
495
521
|
interface DevframeCommandAgentOptions {
|
|
@@ -501,7 +527,7 @@ interface DevframeCommandAgentOptions {
|
|
|
501
527
|
/** Display title (falls back to the command's `title`). */
|
|
502
528
|
title?: string;
|
|
503
529
|
/**
|
|
504
|
-
* Safety classification
|
|
530
|
+
* Safety classification that drives MCP hint annotations.
|
|
505
531
|
* @default 'action'
|
|
506
532
|
*/
|
|
507
533
|
safety?: 'read' | 'action' | 'destructive';
|
|
@@ -509,7 +535,7 @@ interface DevframeCommandAgentOptions {
|
|
|
509
535
|
tags?: readonly string[];
|
|
510
536
|
/**
|
|
511
537
|
* Positional [Standard Schema](https://standardschema.dev/) validators for
|
|
512
|
-
* the handler's arguments
|
|
538
|
+
* the handler's arguments, the same shape RPC definitions carry (valibot,
|
|
513
539
|
* zod, arktype, devframe's built-in `s` builder, …). Each is advertised
|
|
514
540
|
* under `arg0` / `arg1` / … on the tool's JSON-Schema input. Omitted: the
|
|
515
541
|
* tool takes no arguments.
|
|
@@ -517,7 +543,7 @@ interface DevframeCommandAgentOptions {
|
|
|
517
543
|
args?: readonly StandardSchemaV1[];
|
|
518
544
|
}
|
|
519
545
|
/**
|
|
520
|
-
* Server command input
|
|
546
|
+
* Server command input: what plugins pass to `ctx.commands.register()`.
|
|
521
547
|
*/
|
|
522
548
|
interface DevframeServerCommandInput extends DevframeCommandBase {
|
|
523
549
|
/**
|
|
@@ -530,20 +556,20 @@ interface DevframeServerCommandInput extends DevframeCommandBase {
|
|
|
530
556
|
*/
|
|
531
557
|
agent?: DevframeCommandAgentOptions;
|
|
532
558
|
/**
|
|
533
|
-
* Static sub-commands
|
|
559
|
+
* Static sub-commands, nested arbitrarily deep.
|
|
534
560
|
* Each child must have a globally unique `id`.
|
|
535
561
|
*/
|
|
536
562
|
children?: DevframeServerCommandInput[];
|
|
537
563
|
}
|
|
538
564
|
/**
|
|
539
|
-
* Serializable server command entry
|
|
565
|
+
* Serializable server command entry, sent over RPC (no handler).
|
|
540
566
|
*/
|
|
541
567
|
interface DevframeServerCommandEntry extends DevframeCommandBase {
|
|
542
568
|
source: 'server';
|
|
543
569
|
children?: DevframeServerCommandEntry[];
|
|
544
570
|
}
|
|
545
571
|
/**
|
|
546
|
-
* Client command
|
|
572
|
+
* Client command, registered in the webcomponent context.
|
|
547
573
|
*/
|
|
548
574
|
interface DevframeClientCommand extends DevframeCommandBase {
|
|
549
575
|
source: 'client';
|
|
@@ -553,7 +579,7 @@ interface DevframeClientCommand extends DevframeCommandBase {
|
|
|
553
579
|
*/
|
|
554
580
|
action?: (...args: any[]) => void | DevframeClientCommand[] | Promise<void | DevframeClientCommand[]>;
|
|
555
581
|
/**
|
|
556
|
-
* Static sub-commands
|
|
582
|
+
* Static sub-commands, nested arbitrarily deep.
|
|
557
583
|
*/
|
|
558
584
|
children?: DevframeClientCommand[];
|
|
559
585
|
}
|
|
@@ -614,7 +640,7 @@ interface DevframeCommandShortcutOverrides {
|
|
|
614
640
|
* ```
|
|
615
641
|
*
|
|
616
642
|
* Augmented fields are optional so the hub's `DEFAULT_STATE_USER_SETTINGS()`
|
|
617
|
-
* (which knows nothing about them) stays assignable
|
|
643
|
+
* (which knows nothing about them) stays assignable, so an absent value reads
|
|
618
644
|
* as the viewer's documented default.
|
|
619
645
|
*/
|
|
620
646
|
interface DevframeDocksUserSettings {
|
|
@@ -630,4 +656,4 @@ interface DevframeDocksUserSettings {
|
|
|
630
656
|
commandShortcuts: DevframeCommandShortcutOverrides;
|
|
631
657
|
}
|
|
632
658
|
//#endregion
|
|
633
|
-
export {
|
|
659
|
+
export { DevframeViewIframe as A, DevframeDockUserEntry as C, DevframeViewBuiltin as D, DevframeViewAction as E, RemoteConnectionInfo as F, RemoteDockOptions as I, DevframeViewLauncherStatus as M, FrameSubTabsConfig as N, DevframeViewCustomRender as O, NavTarget as P, DevframeDockPanelState as S, DevframeDocksHost as T, DevframeDockEntry as _, DevframeCommandEntry as a, DevframeDockEntryIcon as b, DevframeCommandShortcutOverrides as c, DevframeServerCommandEntry as d, DevframeServerCommandInput as f, DevframeDockEntriesGrouped as g, DevframeDockBadgeVariant as h, DevframeCommandBase as i, DevframeViewLauncher as j, DevframeViewGroup as k, DevframeCommandsHost as l, DevframeDockActivation as m, DevframeClientCommand as n, DevframeCommandHandle as o, ClientScriptEntry as p, DevframeCommandAgentOptions as r, DevframeCommandKeybinding as s, DevframeDocksUserSettings as t, DevframeCommandsHostEvents as u, DevframeDockEntryBase as v, DevframeDocksActiveState as w, DevframeDockEntryRegistry as x, DevframeDockEntryCategory as y };
|