@devframes/next 0.8.2 → 0.9.0-beta.2
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.d.mts +1 -1
- package/dist/index.d.mts +30 -27
- package/dist/index.mjs +76 -82
- package/package.json +5 -5
package/dist/client.d.mts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { DevframeConnectionStatus, DevframeRpcClient } from "devframe/client";
|
|
2
2
|
import { ReactNode } from "react";
|
|
3
|
-
import { ConnectionMeta } from "devframe
|
|
3
|
+
import { ConnectionMeta } from "devframe";
|
|
4
4
|
//#region src/client.d.ts
|
|
5
5
|
interface DevframeRpcState {
|
|
6
6
|
/** The connected client, or `null` while the initial connect is in flight. */
|
package/dist/index.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { ConnectionMeta, DevframeDefinition, DevframeHost, DevframeNodeContext, DevframeStorageScope } from "devframe
|
|
1
|
+
import { InitDevframeOptions } from "devframe/initiate";
|
|
2
|
+
import { ConnectionMeta, DevframeDefinition, DevframeHost, DevframeNodeContext, DevframeStorageScope } from "devframe";
|
|
3
3
|
//#region src/config.d.ts
|
|
4
4
|
/**
|
|
5
5
|
* Minimal shape of the bits of `next.config` this helper reads/writes, so the
|
|
@@ -45,34 +45,40 @@ interface CreateDevframeNextHandlerOptions {
|
|
|
45
45
|
/** Flag bag forwarded to `def.setup(ctx, { flags })`. */
|
|
46
46
|
flags?: Record<string, unknown>;
|
|
47
47
|
/**
|
|
48
|
-
* Whether the side-car runs its own auth gate. **Gates by default**
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
48
|
+
* Whether the side-car runs its own auth gate. **Gates by default**
|
|
49
|
+
* (devframe's interactive OTP unless the definition's `cli.auth` opts
|
|
50
|
+
* out), so the side-car socket isn't silently reachable by anything that
|
|
51
|
+
* can open it. Pass `false` to opt out for a single-user localhost host,
|
|
52
|
+
* or a handler for a custom scheme.
|
|
53
53
|
*/
|
|
54
|
-
auth?:
|
|
54
|
+
auth?: InitDevframeOptions['auth'];
|
|
55
55
|
/** Origin the Next app is reachable at, for docks needing an absolute URL. */
|
|
56
56
|
resolveOrigin?: () => string;
|
|
57
57
|
/** Override where persisted devframe state lives (defaults under the cwd / home). */
|
|
58
58
|
getStorageDir?: (scope: DevframeStorageScope) => string;
|
|
59
59
|
/**
|
|
60
|
-
* Expose the
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
60
|
+
* Expose the route-based MCP server (Streamable-HTTP) at `<base>__mcp` —
|
|
61
|
+
* on the Next app's own origin, through the same catch-all route as the
|
|
62
|
+
* SPA — and advertise it in the handler's `__connection.json`. Overrides
|
|
63
|
+
* `def.cli?.mcp`, `undefined` falls through to it, `false` disables the
|
|
64
|
+
* route regardless.
|
|
65
65
|
*
|
|
66
66
|
* @experimental
|
|
67
67
|
*/
|
|
68
|
-
mcp?:
|
|
68
|
+
mcp?: InitDevframeOptions['mcp'];
|
|
69
|
+
/**
|
|
70
|
+
* Memoization key for the handler. Next re-runs route modules across
|
|
71
|
+
* dev-time reloads; the key makes a re-run return the live handler instead
|
|
72
|
+
* of leaking side-car servers. Default: `@devframes/next:<def.id>:<base>`.
|
|
73
|
+
*/
|
|
74
|
+
key?: string;
|
|
69
75
|
}
|
|
70
76
|
interface DevframeNextHandler {
|
|
71
77
|
/**
|
|
72
78
|
* WHATWG-`fetch` handler for the catch-all App Router route. Serves the
|
|
73
|
-
* plugin's built SPA at `base` and answers `<base
|
|
74
|
-
* the
|
|
75
|
-
*
|
|
79
|
+
* plugin's built SPA at `base` and answers `<base>__connection.json` with
|
|
80
|
+
* the RPC endpoint. Awaits {@link DevframeNextHandler.ready} so the first
|
|
81
|
+
* request doesn't race the server boot.
|
|
76
82
|
*/
|
|
77
83
|
fetch: (request: Request) => Promise<Response>;
|
|
78
84
|
/** Resolves once the side-car RPC/WS server is listening. */
|
|
@@ -81,16 +87,13 @@ interface DevframeNextHandler {
|
|
|
81
87
|
close: () => Promise<void>;
|
|
82
88
|
}
|
|
83
89
|
/**
|
|
84
|
-
* Host a **single** devframe from a Next.js App Router app — the
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
-
*
|
|
88
|
-
*
|
|
89
|
-
* starts a side-car RPC/WS dev server (via `createDevServer` in bridge mode) on
|
|
90
|
-
* its own port, advertising that endpoint at `<base>/__connection.json` so the
|
|
91
|
-
* SPA's `connectDevframe()` can dial back in.
|
|
90
|
+
* Host a **single** devframe from a Next.js App Router app — the Next
|
|
91
|
+
* counterpart to `viteDevBridge`, reduced to memoization + defaults over
|
|
92
|
+
* `initDevframe` (Next's route handlers can't accept WS upgrades, so the
|
|
93
|
+
* RPC socket lives on the instance's side-car port, advertised at
|
|
94
|
+
* `<base>__connection.json`).
|
|
92
95
|
*
|
|
93
|
-
* ```ts [app
|
|
96
|
+
* ```ts [app/%5F_my-tool/[[...path]]/route.ts]
|
|
94
97
|
* import myDevframe from '@/devframe'
|
|
95
98
|
* import { createDevframeNextHandler } from '@devframes/next'
|
|
96
99
|
*
|
|
@@ -101,7 +104,7 @@ interface DevframeNextHandler {
|
|
|
101
104
|
* export const GET = handler.fetch
|
|
102
105
|
* ```
|
|
103
106
|
*
|
|
104
|
-
* For a hub hosting many devframes at once, use
|
|
107
|
+
* For a hub hosting many devframes at once, use `createDevframeNextHost`
|
|
105
108
|
* directly with `@devframes/hub`.
|
|
106
109
|
*/
|
|
107
110
|
declare function createDevframeNextHandler(def: DevframeDefinition, options?: CreateDevframeNextHandlerOptions): DevframeNextHandler;
|
package/dist/index.mjs
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { homedir } from "node:os";
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import process from "node:process";
|
|
4
|
-
import {
|
|
5
|
-
import { DEVFRAME_CONNECTION_META_FILENAME
|
|
4
|
+
import { initDevframe } from "devframe/initiate";
|
|
5
|
+
import { DEVFRAME_CONNECTION_META_FILENAME } from "devframe/constants";
|
|
6
6
|
import { serveStaticHandler } from "devframe/utils/serve-static";
|
|
7
7
|
import { H3 } from "h3";
|
|
8
8
|
//#region src/config.ts
|
|
@@ -31,6 +31,80 @@ function withDevframe(nextConfig = {}) {
|
|
|
31
31
|
};
|
|
32
32
|
}
|
|
33
33
|
//#endregion
|
|
34
|
+
//#region src/handler.ts
|
|
35
|
+
/** Ensure a mount base has a single leading and trailing slash. */
|
|
36
|
+
function normalizeBase(base) {
|
|
37
|
+
return `/${base}/`.replace(/\/{2,}/g, "/");
|
|
38
|
+
}
|
|
39
|
+
const REGISTRY_KEY = Symbol.for("@devframes/next:handler-registry");
|
|
40
|
+
/**
|
|
41
|
+
* Handlers memoized per key on `globalThis`. Next re-evaluates route modules
|
|
42
|
+
* on every dev-time reload, so without this each reload would start a fresh
|
|
43
|
+
* side-car WebSocket server and leak the previous one.
|
|
44
|
+
*/
|
|
45
|
+
function handlerRegistry() {
|
|
46
|
+
const holder = globalThis;
|
|
47
|
+
holder[REGISTRY_KEY] ??= /* @__PURE__ */ new Map();
|
|
48
|
+
return holder[REGISTRY_KEY];
|
|
49
|
+
}
|
|
50
|
+
function defaultGetStorageDir(scope) {
|
|
51
|
+
const cwd = process.cwd();
|
|
52
|
+
if (scope === "workspace") return join(cwd, ".devframe");
|
|
53
|
+
if (scope === "project") return join(cwd, "node_modules/.devframe");
|
|
54
|
+
return join(homedir(), ".devframe");
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Host a **single** devframe from a Next.js App Router app — the Next
|
|
58
|
+
* counterpart to `viteDevBridge`, reduced to memoization + defaults over
|
|
59
|
+
* `initDevframe` (Next's route handlers can't accept WS upgrades, so the
|
|
60
|
+
* RPC socket lives on the instance's side-car port, advertised at
|
|
61
|
+
* `<base>__connection.json`).
|
|
62
|
+
*
|
|
63
|
+
* ```ts [app/%5F_my-tool/[[...path]]/route.ts]
|
|
64
|
+
* import myDevframe from '@/devframe'
|
|
65
|
+
* import { createDevframeNextHandler } from '@devframes/next'
|
|
66
|
+
*
|
|
67
|
+
* export const runtime = 'nodejs'
|
|
68
|
+
* export const dynamic = 'force-dynamic'
|
|
69
|
+
*
|
|
70
|
+
* const handler = createDevframeNextHandler(myDevframe)
|
|
71
|
+
* export const GET = handler.fetch
|
|
72
|
+
* ```
|
|
73
|
+
*
|
|
74
|
+
* For a hub hosting many devframes at once, use `createDevframeNextHost`
|
|
75
|
+
* directly with `@devframes/hub`.
|
|
76
|
+
*/
|
|
77
|
+
function createDevframeNextHandler(def, options = {}) {
|
|
78
|
+
const distDir = def.cli?.distDir;
|
|
79
|
+
if (!distDir) throw new Error(`[@devframes/next] createDevframeNextHandler("${def.id}") needs a built SPA to serve, but "cli.distDir" is not set on the devframe definition.`);
|
|
80
|
+
const base = normalizeBase(options.base ?? def.basePath ?? `/__${def.id}/`);
|
|
81
|
+
const key = options.key ?? `@devframes/next:${def.id}:${base}`;
|
|
82
|
+
const registry = handlerRegistry();
|
|
83
|
+
const memoized = registry.get(key);
|
|
84
|
+
if (memoized) return memoized;
|
|
85
|
+
const instance = initDevframe(def, {
|
|
86
|
+
base,
|
|
87
|
+
distDir,
|
|
88
|
+
host: options.host,
|
|
89
|
+
flags: options.flags,
|
|
90
|
+
auth: options.auth,
|
|
91
|
+
mcp: options.mcp,
|
|
92
|
+
ws: options.port != null ? { port: options.port } : { sidecar: true },
|
|
93
|
+
...options.resolveOrigin ? { origin: options.resolveOrigin } : {},
|
|
94
|
+
getStorageDir: options.getStorageDir ?? defaultGetStorageDir
|
|
95
|
+
});
|
|
96
|
+
const handler = {
|
|
97
|
+
fetch: (request) => instance.handler(request),
|
|
98
|
+
ready: instance.ready,
|
|
99
|
+
close: async () => {
|
|
100
|
+
if (registry.get(key) === handler) registry.delete(key);
|
|
101
|
+
await instance.close();
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
registry.set(key, handler);
|
|
105
|
+
return handler;
|
|
106
|
+
}
|
|
107
|
+
//#endregion
|
|
34
108
|
//#region src/host.ts
|
|
35
109
|
const META_SUFFIX = `/${DEVFRAME_CONNECTION_META_FILENAME}`;
|
|
36
110
|
/** Drop trailing slashes from a mount base (`/__git/` → `/__git`). */
|
|
@@ -105,84 +179,4 @@ function createDevframeNextHost(options) {
|
|
|
105
179
|
};
|
|
106
180
|
}
|
|
107
181
|
//#endregion
|
|
108
|
-
//#region src/handler.ts
|
|
109
|
-
/** Ensure a mount base has a single leading and trailing slash. */
|
|
110
|
-
function normalizeBase(base) {
|
|
111
|
-
return `/${base}/`.replace(/\/{2,}/g, "/");
|
|
112
|
-
}
|
|
113
|
-
function defaultGetStorageDir(scope) {
|
|
114
|
-
const cwd = process.cwd();
|
|
115
|
-
if (scope === "workspace") return join(cwd, ".devframe");
|
|
116
|
-
if (scope === "project") return join(cwd, "node_modules/.devframe");
|
|
117
|
-
return join(homedir(), ".devframe");
|
|
118
|
-
}
|
|
119
|
-
/**
|
|
120
|
-
* Host a **single** devframe from a Next.js App Router app — the convenience
|
|
121
|
-
* wrapper over {@link createDevframeNextHost} for the common case of mounting
|
|
122
|
-
* one plugin (the Next counterpart to `viteDevBridge`'s bridge mode).
|
|
123
|
-
*
|
|
124
|
-
* It statically serves `def.cli.distDir` at `base` through the Next route and
|
|
125
|
-
* starts a side-car RPC/WS dev server (via `createDevServer` in bridge mode) on
|
|
126
|
-
* its own port, advertising that endpoint at `<base>/__connection.json` so the
|
|
127
|
-
* SPA's `connectDevframe()` can dial back in.
|
|
128
|
-
*
|
|
129
|
-
* ```ts [app/__my-tool/[[...path]]/route.ts]
|
|
130
|
-
* import myDevframe from '@/devframe'
|
|
131
|
-
* import { createDevframeNextHandler } from '@devframes/next'
|
|
132
|
-
*
|
|
133
|
-
* export const runtime = 'nodejs'
|
|
134
|
-
* export const dynamic = 'force-dynamic'
|
|
135
|
-
*
|
|
136
|
-
* const handler = createDevframeNextHandler(myDevframe)
|
|
137
|
-
* export const GET = handler.fetch
|
|
138
|
-
* ```
|
|
139
|
-
*
|
|
140
|
-
* For a hub hosting many devframes at once, use {@link createDevframeNextHost}
|
|
141
|
-
* directly with `@devframes/hub`.
|
|
142
|
-
*/
|
|
143
|
-
function createDevframeNextHandler(def, options = {}) {
|
|
144
|
-
const distDir = def.cli?.distDir;
|
|
145
|
-
if (!distDir) throw new Error(`[@devframes/next] createDevframeNextHandler("${def.id}") needs a built SPA to serve, but "cli.distDir" is not set on the devframe definition.`);
|
|
146
|
-
const base = normalizeBase(options.base ?? def.basePath ?? `/__${def.id}/`);
|
|
147
|
-
const hostName = options.host ?? def.cli?.host;
|
|
148
|
-
const nextHost = createDevframeNextHost({
|
|
149
|
-
resolveOrigin: options.resolveOrigin ?? (() => ""),
|
|
150
|
-
getStorageDir: options.getStorageDir ?? defaultGetStorageDir
|
|
151
|
-
});
|
|
152
|
-
nextHost.host.mountStatic(base, distDir);
|
|
153
|
-
nextHost.host.mountConnectionMeta?.(base);
|
|
154
|
-
let started;
|
|
155
|
-
const ready = (async () => {
|
|
156
|
-
const port = options.port ?? await resolveDevServerPort(def, { host: hostName });
|
|
157
|
-
started = await createDevServer(def, {
|
|
158
|
-
host: hostName,
|
|
159
|
-
port,
|
|
160
|
-
flags: options.flags,
|
|
161
|
-
openBrowser: false,
|
|
162
|
-
auth: options.auth,
|
|
163
|
-
mcp: options.mcp
|
|
164
|
-
});
|
|
165
|
-
const mcpMeta = resolveMcpConnectionMeta(def, options.mcp, port);
|
|
166
|
-
nextHost.setConnectionMeta({
|
|
167
|
-
backend: "websocket",
|
|
168
|
-
websocket: {
|
|
169
|
-
port,
|
|
170
|
-
path: `/${DEVFRAME_WS_ROUTE}`
|
|
171
|
-
},
|
|
172
|
-
...mcpMeta ? { mcp: mcpMeta } : {}
|
|
173
|
-
});
|
|
174
|
-
})();
|
|
175
|
-
return {
|
|
176
|
-
async fetch(request) {
|
|
177
|
-
await ready;
|
|
178
|
-
return nextHost.fetch(request);
|
|
179
|
-
},
|
|
180
|
-
ready,
|
|
181
|
-
async close() {
|
|
182
|
-
await ready.catch(() => {});
|
|
183
|
-
await started?.close();
|
|
184
|
-
}
|
|
185
|
-
};
|
|
186
|
-
}
|
|
187
|
-
//#endregion
|
|
188
182
|
export { createDevframeNextHandler, createDevframeNextHost, withDevframe };
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@devframes/next",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.
|
|
5
|
-
"description": "Next.js host integration
|
|
4
|
+
"version": "0.9.0-beta.2",
|
|
5
|
+
"description": "Next.js host integration that serves mounted devframes from an App Router route.",
|
|
6
6
|
"author": "Anthony Fu <anthonyfu117@hotmail.com>",
|
|
7
7
|
"license": "MIT",
|
|
8
8
|
"homepage": "https://github.com/devframes/devframe#readme",
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
"peerDependencies": {
|
|
38
38
|
"next": "^14.0.0 || ^15.0.0 || ^16.0.0",
|
|
39
39
|
"react": "^18.0.0 || ^19.0.0",
|
|
40
|
-
"devframe": "0.
|
|
40
|
+
"devframe": "0.9.0-beta.2"
|
|
41
41
|
},
|
|
42
42
|
"peerDependenciesMeta": {
|
|
43
43
|
"next": {
|
|
@@ -51,11 +51,11 @@
|
|
|
51
51
|
"h3": "^2.0.1-rc.26"
|
|
52
52
|
},
|
|
53
53
|
"devDependencies": {
|
|
54
|
-
"@types/node": "^26.
|
|
54
|
+
"@types/node": "^26.2.0",
|
|
55
55
|
"@types/react": "^19.2.18",
|
|
56
56
|
"react": "^19.2.8",
|
|
57
57
|
"tsdown": "^0.22.14",
|
|
58
|
-
"devframe": "0.
|
|
58
|
+
"devframe": "0.9.0-beta.2"
|
|
59
59
|
},
|
|
60
60
|
"scripts": {
|
|
61
61
|
"build": "tsdown",
|