devframe 0.9.0-beta.4 → 0.9.0-beta.5

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.
Files changed (65) hide show
  1. package/dist/{_shared-CGPVmXUM.mjs → _shared-CKZPvSrN.mjs} +2 -1
  2. package/dist/{_shared-Bm8p4-fD.d.mts → _shared-QkI6ljJS.d.mts} +2 -2
  3. package/dist/adapters/build.d.mts +10 -12
  4. package/dist/adapters/build.mjs +23 -27
  5. package/dist/adapters/cac.d.mts +1 -1
  6. package/dist/adapters/cac.mjs +3 -4
  7. package/dist/adapters/dev.d.mts +19 -5
  8. package/dist/adapters/dev.mjs +2 -2
  9. package/dist/adapters/embedded.d.mts +1 -1
  10. package/dist/adapters/initiate.d.mts +4 -3
  11. package/dist/adapters/initiate.mjs +1 -1
  12. package/dist/adapters/mcp.d.mts +1 -1
  13. package/dist/adapters/mcp.mjs +1 -1
  14. package/dist/cli/main.mjs +2 -2
  15. package/dist/client/index.d.mts +1 -1
  16. package/dist/client/index.mjs +2 -1175
  17. package/dist/constants.d.mts +1 -2
  18. package/dist/constants.mjs +1 -2
  19. package/dist/{context-DV4uWieM.d.mts → context-CQkRTbAc.d.mts} +1 -1
  20. package/dist/{context-b6iSomtv.mjs → context-D0HD0F7C.mjs} +19 -7
  21. package/dist/{context-CMSryXs-.mjs → context-D0jErmlx.mjs} +1 -1
  22. package/dist/{dev-4xjiynUD.mjs → dev-QEuT8SOF.mjs} +15 -9
  23. package/dist/{devframe-5oblos7I.d.mts → devframe-Cfx6doi4.d.mts} +41 -64
  24. package/dist/{diagnostics-C7IGWppM.mjs → diagnostics-DEuH3P5O.mjs} +32 -0
  25. package/dist/{host-agent-BBkKT27Q.mjs → host-agent-C5HjzfBz.mjs} +41 -3
  26. package/dist/{host-h3-CzCZwtuV.mjs → host-h3-Crkzm42q.mjs} +2 -2
  27. package/dist/{http-Z0w9baEe.mjs → http-CVwj7b4g.mjs} +2 -2
  28. package/dist/{index-BzDl02lt.d.mts → index-MYi1SZVL.d.mts} +2 -2
  29. package/dist/index.d.mts +3 -2
  30. package/dist/{instance-registry-Du_hbx-j.mjs → instance-registry-M2p-z6xE.mjs} +1 -1
  31. package/dist/{instance-shell-B0TwOxCl.mjs → instance-shell-DJ8s-MFw.mjs} +5 -5
  32. package/dist/{instance-shell-B-BSVp37.d.mts → instance-shell-D_UA0Idz.d.mts} +2 -2
  33. package/dist/internal/index.d.mts +72 -10
  34. package/dist/internal/index.mjs +7 -7
  35. package/dist/node/auth.d.mts +2 -2
  36. package/dist/node/auth.mjs +1 -1
  37. package/dist/node/hub-internals.d.mts +2 -2
  38. package/dist/node/hub-internals.mjs +2 -2
  39. package/dist/node/index.d.mts +1 -1
  40. package/dist/node/index.mjs +2 -2
  41. package/dist/recipes/common-rpc-functions.d.mts +1 -1
  42. package/dist/recipes/interactive-auth.d.mts +2 -2
  43. package/dist/recipes/interactive-auth.mjs +2 -2
  44. package/dist/remote-assets-DclsYyoL.d.mts +106 -0
  45. package/dist/rpc/transports/ws-bun.d.mts +1 -1
  46. package/dist/{rpc-core-B4Qm66Qt.d.mts → rpc-core-C8JQ3p7A.d.mts} +2 -2
  47. package/dist/{rpc-core-Cva-0pw0.mjs → rpc-core-IhcWl_xn.mjs} +1 -1
  48. package/dist/state-Cc1IeSmQ.mjs +1291 -0
  49. package/dist/storage-BVuipbGS.mjs +137 -0
  50. package/dist/types/index.d.mts +3 -2
  51. package/dist/utils/events.d.mts +1 -1
  52. package/dist/utils/open.d.mts +2 -10
  53. package/dist/utils/open.mjs +56 -1
  54. package/dist/utils/remote-assets.d.mts +15 -0
  55. package/dist/utils/remote-assets.mjs +309 -0
  56. package/dist/utils/serve-static.d.mts +13 -5
  57. package/dist/utils/serve-static.mjs +103 -9
  58. package/dist/utils/shared-state.d.mts +1 -1
  59. package/dist/utils/streaming-channel.d.mts +1 -1
  60. package/package.json +2 -3
  61. package/skills/devframe/SKILL.md +4 -5
  62. package/dist/open-Dz4V4irZ.mjs +0 -547
  63. package/dist/state-BeUHDDjk.mjs +0 -108
  64. package/dist/storage-CaUVrBrG.mjs +0 -1122
  65. package/skills/devframe/templates/spa-devframe.ts +0 -38
@@ -1,9 +1,9 @@
1
1
  import { createReadStream } from "node:fs";
2
- import { extname, join, normalize, resolve, sep } from "pathe";
3
2
  import { stat } from "node:fs/promises";
4
- import { H3, defineHandler } from "h3";
5
3
  import { Readable } from "node:stream";
6
4
  import { lookup } from "mrmime";
5
+ import { extname, join, normalize, resolve, sep } from "pathe";
6
+ import { H3, defineHandler } from "h3";
7
7
  //#region src/utils/serve-static.ts
8
8
  const HTML_EXTENSIONS = [".html", ".htm"];
9
9
  async function statFile(abs) {
@@ -74,7 +74,43 @@ function normalizeOptions(options) {
74
74
  };
75
75
  }
76
76
  /**
77
- * h3 event handler that serves files from `dir` with SPA fallback.
77
+ * Drive one request through a {@link RemoteAssetsStore}: the store's
78
+ * `Response` on a hit, a 404 on a miss, or a 502 (styled error page for HTML
79
+ * navigations) on provider failure — shared between the h3 and connect flavors.
80
+ */
81
+ async function remoteResponse(store, urlPath, accept) {
82
+ try {
83
+ return await store.serve(urlPath) ?? new Response(null, { status: 404 });
84
+ } catch (error) {
85
+ if (typeof accept === "string" && accept.includes("text/html")) return new Response(remoteErrorPage(store.assets.package, store.assets.version, error instanceof Error ? error.message : String(error)), {
86
+ status: 502,
87
+ headers: { "Content-Type": "text/html; charset=utf-8" }
88
+ });
89
+ return new Response(null, { status: 502 });
90
+ }
91
+ }
92
+ function serveRemoteAssetsHandler(store) {
93
+ return defineHandler(async (event) => {
94
+ const method = event.req.method;
95
+ if (method !== "GET" && method !== "HEAD") {
96
+ event.res.status = 405;
97
+ event.res.headers.set("Allow", "GET, HEAD");
98
+ return "";
99
+ }
100
+ const res = await remoteResponse(store, event.url.pathname, event.req.headers.get("accept"));
101
+ event.res.status = res.status;
102
+ res.headers.forEach((v, k) => event.res.headers.set(k, v));
103
+ if (method === "HEAD") {
104
+ await res.body?.cancel().catch(() => {});
105
+ return "";
106
+ }
107
+ return res.body ?? "";
108
+ });
109
+ }
110
+ /**
111
+ * h3 event handler that serves files from `source` with SPA fallback — a
112
+ * local directory, or a {@link RemoteAssetsStore} whose files stream through
113
+ * the CDN back-proxy into the local cache.
78
114
  *
79
115
  * Drop-in replacement for `fromNodeMiddleware(sirv(dir, { dev: true, single: true }))`
80
116
  * when the surrounding server is an h3 app — no `Cache-Control` beyond
@@ -82,8 +118,9 @@ function normalizeOptions(options) {
82
118
  * file extension falls back to `<dir>/index.html` so client-side routing
83
119
  * works.
84
120
  */
85
- function serveStaticHandler(dir, options) {
86
- const absDir = resolve(dir);
121
+ function serveStaticHandler(source, options) {
122
+ if (typeof source !== "string") return serveRemoteAssetsHandler(source);
123
+ const absDir = resolve(source);
87
124
  const opts = normalizeOptions(options);
88
125
  return defineHandler(async (event) => {
89
126
  const method = event.req.method;
@@ -109,9 +146,9 @@ function serveStaticHandler(dir, options) {
109
146
  * from `event.url.pathname`, so the file resolver sees paths relative to
110
147
  * `dir`.
111
148
  */
112
- function mountStaticHandler(app, base, dir, options) {
149
+ function mountStaticHandler(app, base, source, options) {
113
150
  const staticApp = new H3();
114
- staticApp.use(serveStaticHandler(dir, options));
151
+ staticApp.use(serveStaticHandler(source, options));
115
152
  app.mount(base.replace(/\/$/, ""), staticApp);
116
153
  }
117
154
  /**
@@ -121,8 +158,8 @@ function mountStaticHandler(app, base, dir, options) {
121
158
  * Connect stack — avoids forcing the host package to depend on h3 just to
122
159
  * adapt an event handler back into Node middleware.
123
160
  */
124
- function serveStaticNodeMiddleware(dir, options) {
125
- const absDir = resolve(dir);
161
+ function serveStaticNodeMiddleware(source, options) {
162
+ const absDir = typeof source === "string" ? resolve(source) : void 0;
126
163
  const opts = normalizeOptions(options);
127
164
  return (req, res, next) => {
128
165
  (async () => {
@@ -138,6 +175,22 @@ function serveStaticNodeMiddleware(dir, options) {
138
175
  return;
139
176
  }
140
177
  const url = req.url ?? "/";
178
+ if (absDir === void 0) {
179
+ const response = await remoteResponse(source, url, req.headers.accept);
180
+ if (response.status === 404 && next) {
181
+ next();
182
+ return;
183
+ }
184
+ res.statusCode = response.status;
185
+ response.headers.forEach((v, k) => res.setHeader(k, v));
186
+ if (method === "HEAD" || !response.body) {
187
+ await response.body?.cancel().catch(() => {});
188
+ res.end();
189
+ return;
190
+ }
191
+ Readable.fromWeb(response.body).pipe(res);
192
+ return;
193
+ }
141
194
  const file = await resolveTarget(absDir, url, opts.indexNames, opts.single);
142
195
  if (!file) {
143
196
  if (next) {
@@ -164,5 +217,46 @@ function serveStaticNodeMiddleware(dir, options) {
164
217
  });
165
218
  };
166
219
  }
220
+ /**
221
+ * Minimal, dependency-free HTML shown when a remote-assets request cannot
222
+ * be satisfied (no installed package, no cache, provider unreachable).
223
+ */
224
+ function remoteErrorPage(pkg, version, reason) {
225
+ const esc = (s) => s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
226
+ const name = esc(pkg);
227
+ const ver = esc(version);
228
+ return `<!doctype html>
229
+ <html lang="en">
230
+ <head>
231
+ <meta charset="utf-8">
232
+ <meta name="viewport" content="width=device-width, initial-scale=1">
233
+ <title>Client assets unavailable</title>
234
+ <style>
235
+ :root { color-scheme: light dark; }
236
+ body { margin: 0; min-height: 100vh; display: grid; place-items: center; font: 14px/1.6 ui-sans-serif, system-ui, sans-serif; background: #fafafa; color: #27272a; }
237
+ main { max-width: 32rem; padding: 2rem; }
238
+ h1 { font-size: 1rem; font-weight: 600; margin: 0 0 .5rem; }
239
+ p { margin: .5rem 0; opacity: .8; }
240
+ pre { font: 12px/1.6 ui-monospace, monospace; background: rgb(125 125 125 / .12); border-radius: 6px; padding: .75rem 1rem; overflow-x: auto; }
241
+ button { font: inherit; padding: .35rem 1rem; border-radius: 6px; border: 1px solid rgb(125 125 125 / .3); background: transparent; color: inherit; cursor: pointer; }
242
+ button:hover { background: rgb(125 125 125 / .08); }
243
+ .muted { opacity: .55; font-size: 12px; }
244
+ @media (prefers-color-scheme: dark) { body { background: #111; color: #d4d4d8; } }
245
+ </style>
246
+ </head>
247
+ <body>
248
+ <main>
249
+ <h1>Client assets unavailable</h1>
250
+ <p>The UI for this tool is served from <code>${name}@${ver}</code>, which could not be reached.</p>
251
+ <pre>${esc(reason)}</pre>
252
+ <p>To use it without network access, install the assets package locally:</p>
253
+ <pre>npm install ${name}@${ver}</pre>
254
+ <button onclick="location.reload()">Retry</button>
255
+ <p class="muted">devframe remote assets</p>
256
+ </main>
257
+ </body>
258
+ </html>
259
+ `;
260
+ }
167
261
  //#endregion
168
262
  export { mountStaticHandler, serveStaticHandler, serveStaticNodeMiddleware };
@@ -1,2 +1,2 @@
1
- import { _t as SharedState, bt as SharedStatePatch, ft as Immutable, gt as ImmutableSet, ht as ImmutableObject, mt as ImmutableMap, pt as ImmutableArray, vt as SharedStateEvents, xt as createSharedState, yt as SharedStateOptions } from "../devframe-5oblos7I.mjs";
1
+ import { at as Immutable, ct as ImmutableObject, dt as SharedStateEvents, ft as SharedStateOptions, lt as ImmutableSet, mt as createSharedState, ot as ImmutableArray, pt as SharedStatePatch, st as ImmutableMap, ut as SharedState } from "../devframe-Cfx6doi4.mjs";
2
2
  export { Immutable, ImmutableArray, ImmutableMap, ImmutableObject, ImmutableSet, SharedState, SharedStateEvents, SharedStateOptions, SharedStatePatch, createSharedState };
@@ -1,2 +1,2 @@
1
- import { at as CreateStreamSinkOptions, ct as StreamSink, dt as createStreamSink, it as CreateStreamReaderOptions, lt as StreamSinkEvents, ot as StreamErrorPayload, rt as BufferedChunk, st as StreamReader, ut as createStreamReader } from "../devframe-5oblos7I.mjs";
1
+ import { $ as StreamErrorPayload, Q as CreateStreamSinkOptions, X as BufferedChunk, Z as CreateStreamReaderOptions, et as StreamReader, it as createStreamSink, nt as StreamSinkEvents, rt as createStreamReader, tt as StreamSink } from "../devframe-Cfx6doi4.mjs";
2
2
  export { BufferedChunk, CreateStreamReaderOptions, CreateStreamSinkOptions, StreamErrorPayload, StreamReader, StreamSink, StreamSinkEvents, createStreamReader, createStreamSink };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "devframe",
3
3
  "type": "module",
4
- "version": "0.9.0-beta.4",
4
+ "version": "0.9.0-beta.5",
5
5
  "description": "Framework for building one portable devtool integration that runs in any viewer.",
6
6
  "author": "Anthony Fu <anthonyfu117@hotmail.com>",
7
7
  "license": "MIT",
@@ -52,6 +52,7 @@
52
52
  "./utils/launch-editor": "./dist/utils/launch-editor.mjs",
53
53
  "./utils/nanoid": "./dist/utils/nanoid.mjs",
54
54
  "./utils/open": "./dist/utils/open.mjs",
55
+ "./utils/remote-assets": "./dist/utils/remote-assets.mjs",
55
56
  "./utils/simple-schema": "./dist/utils/simple-schema.mjs",
56
57
  "./utils/serve-static": "./dist/utils/serve-static.mjs",
57
58
  "./utils/shared-state": "./dist/utils/shared-state.mjs",
@@ -89,7 +90,6 @@
89
90
  "@standard-schema/spec": "^1.1.0",
90
91
  "birpc": "^4.1.0",
91
92
  "crossws": "^0.4.10",
92
- "destr": "^2.0.5",
93
93
  "h3": "^2.0.1-rc.26",
94
94
  "mrmime": "^2.0.1",
95
95
  "nostics": "^1.2.0",
@@ -106,7 +106,6 @@
106
106
  "mlly": "^1.8.2",
107
107
  "obug": "^2.1.4",
108
108
  "ohash": "^2.0.11",
109
- "open": "^11.0.0",
110
109
  "p-limit": "^7.3.1",
111
110
  "perfect-debounce": "^2.1.0",
112
111
  "structured-clone-es": "^2.0.1",
@@ -5,7 +5,7 @@ description: >
5
5
  framework- and build-tool-agnostic foundation for defining a
6
6
  devtool once and serving it in many places. Covers
7
7
  DevframeDefinition, picking the right deployment adapter
8
- (cli / build / spa / vite / embedded / mcp), designing RPC
8
+ (cli / build / vite / embedded / mcp), designing RPC
9
9
  contracts, exposing an agent-native surface over MCP, and
10
10
  wiring the author's SPA client. For host-level features (docks,
11
11
  terminals, palette, etc.), the devframe can be mounted into a
@@ -85,7 +85,7 @@ export default defineDevframe({
85
85
 
86
86
  `setup(ctx)` registers RPC functions, shared state, diagnostics, and any other devframe-level wiring. Host adapters can augment `ctx` with extra surfaces - for example, mounting into Vite DevTools via `createPluginFromDevframe(d)` exposes `docks`, `terminals`, `messages`, and `commands` on the augmented context, and the kit auto-derives an iframe dock entry from `id` / `name` / `icon` / `basePath`. For richer host-side behaviour (custom-render, terminals, palette commands) pass `options.setup` to `createPluginFromDevframe`.
87
87
 
88
- See `templates/counter-devframe.ts` for a runnable counter example, `templates/spa-devframe.ts` for an SPA-ready shape, and `templates/vite-client.ts` for the author's client entry.
88
+ See `templates/counter-devframe.ts` for a runnable counter example and `templates/vite-client.ts` for the author's client entry.
89
89
 
90
90
  ## Scoped context (preferred)
91
91
 
@@ -504,7 +504,7 @@ const data = await my.rpc.call('get-stats', { limit: 10 })
504
504
  `connectDevframe` auto-detects the backend via `/.devframe/.connection.json`:
505
505
 
506
506
  - **websocket** (dev mode) - full read/write, requires auth handshake. Listen for token updates on the `devframe-auth` BroadcastChannel.
507
- - **static** (build / spa output) - read-only, resolves calls from the baked RPC dump.
507
+ - **static** (build output) - read-only, resolves calls from the baked RPC dump.
508
508
 
509
509
  Use `my.rpc.sharedState(key)` for observable state, `my.rpc.register(defineRpcFunction(...))` to receive server broadcasts, `my.rpc.callOptional(...)` when a missing handler should resolve to `undefined` instead of throwing, and `my.settings.{project,global}` for persisted per-workspace / per-user settings synced from the server.
510
510
 
@@ -530,13 +530,12 @@ At runtime, static clients look up the argument hash in the dump; misses resolve
530
530
 
531
531
  ## CLI adapter subcommands
532
532
 
533
- `createCac(devframe).parse()` gives the tool four subcommands out of the box:
533
+ `createCac(devframe).parse()` gives the tool three subcommands out of the box:
534
534
 
535
535
  | Subcommand | Action |
536
536
  |------------|--------|
537
537
  | *(default)* | Dev server on port 9999 (or `--port`) - WebSocket RPC, `cli.distDir` served at `/.devframe/` |
538
538
  | `build` | Static snapshot → `./dist-static/` (configurable via `--out-dir`) |
539
- | `spa` | Deployable SPA → `./dist-spa/` |
540
539
  | `mcp` | stdio MCP server |
541
540
 
542
541
  **Bring your own CLI framework?** `createCac` (`devframe/adapters/cac`) is just a cac wrapper around three peer factories - `createDevServer` (`devframe/adapters/dev`), `createBuild` (`devframe/adapters/build`), and `createMcpServer` (`devframe/adapters/mcp`). Use them directly with commander/yargs/oclif when `createCac`'s baked-in command structure doesn't fit. `cac` is an optional peer dependency pulled in only through `devframe/adapters/cac`, so bring-your-own-CLI tools run without installing it. `createDevServer` returns a `StartedServer` handle (`origin`, `port`, `app`, `wss`, `close()`) so you can wire SIGINT / hot-reload teardown into the surrounding program. `parseCliFlags(schema, raw)` and `defineCliFlags(...)` (both from `devframe/adapters/cac`) validate an arbitrary flag bag against a `CliFlagsSchema` - the helpers are framework-agnostic.