Skip to content

Commit 9c7fa82

Browse files
antfubotantfu
authored andcommitted
refactor(adapters)!: rebuild createDevServer, viteDevBridge, and @devframes/next on initDevframe
One wiring underneath every serving path: the adapters become thin assemblies over the devframe/initiate instance. - createDevServer = initDevframe + a node listener (listen-first so the shared WS tier reports the real port; DF0052 rejection preserved; registry/openBrowser/onReady unchanged; ws/rpcGroup/connectionMeta surfaced from the instance transport) - viteDevBridge bridge mode = instance.nodeMiddleware on Vite's stack; the WS upgrade now shares Vite's own http server at <base>__ws (zero extra ports; pinned devMiddleware.port keeps the explicit side-car) and MCP moves onto the Vite origin at <base>__mcp - @devframes/next reduces to memoization + defaults sugar (key-memoized instance; MCP same-origin through the catch-all route) - initDevframe grows the host-integration options the adapters need: app, distDir: false, origin getter, getStorageDir, destroyUnmatchedUpgrades, onPeerConnect/onPeerDisconnect passthrough - resolveDevServerPort / resolveMcpConnectionMeta move to adapters/_shared (re-exported from adapters/dev unchanged) BREAKING CHANGE: viteDevBridge and @devframes/next advertise same-origin relative endpoints now — websocket { path: '__ws' } on the host origin (or { port, path: '__ws' } for a pinned side-car) and mcp { path: '__mcp' } — instead of side-car-port absolute paths.
1 parent 6ee8816 commit 9c7fa82

14 files changed

Lines changed: 555 additions & 477 deletions

File tree

packages/devframe/src/adapters/_shared.ts

Lines changed: 76 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
1-
import type { DevframeDefinition, DevframeDeploymentKind } from '../types/devframe'
2-
import { cleanDoubleSlashes, withLeadingSlash, withTrailingSlash } from 'ufo'
1+
import type { ConnectionMeta } from '../types/context'
2+
import type { DevframeDefinition, DevframeDeploymentKind, McpRouteOptions } from '../types/devframe'
3+
import { getPort } from 'get-port-please'
4+
import { cleanDoubleSlashes, withLeadingSlash, withoutLeadingSlash, withTrailingSlash } from 'ufo'
5+
import { DEVFRAME_MCP_ROUTE } from '../constants'
6+
7+
const DEFAULT_PORT = 9999
38

49
/**
510
* Resolve the mount base path for a devframe's SPA. Hosted adapters
@@ -18,3 +23,72 @@ export function resolveBasePath(def: DevframeDefinition, kind: DevframeDeploymen
1823
export function normalizeBasePath(base: string): string {
1924
return cleanDoubleSlashes(withTrailingSlash(withLeadingSlash(base)))
2025
}
26+
27+
export interface ResolveDevServerPortOptions {
28+
/** Bind host (passed to `get-port-please` for in-use detection). */
29+
host?: string
30+
/** Override the preferred port. Default: `def.cli?.port ?? 9999`. */
31+
defaultPort?: number
32+
}
33+
34+
/**
35+
* Resolve the listening port for `createDevServer` (and `createHandler`'s
36+
* side-car tiers), honoring the definition's `cli.port` / `cli.portRange` /
37+
* `cli.random` settings. Exposed separately so authors who run their own
38+
* argv parsing can resolve a port up-front (to print it, log it, etc.)
39+
* before starting the server.
40+
*/
41+
export async function resolveDevServerPort(
42+
def: DevframeDefinition,
43+
options: ResolveDevServerPortOptions = {},
44+
): Promise<number> {
45+
const host = options.host ?? def.cli?.host ?? 'localhost'
46+
const port = options.defaultPort ?? def.cli?.port ?? DEFAULT_PORT
47+
// Only include optional fields when set — `get-port-please` spreads
48+
// user options over its defaults, so `portRange: undefined` would
49+
// wipe out the internal `[]` and crash on iteration.
50+
const portOptions: Parameters<typeof getPort>[0] = { port, host }
51+
if (def.cli?.portRange)
52+
portOptions.portRange = def.cli.portRange
53+
if (def.cli?.random)
54+
portOptions.random = def.cli.random
55+
return getPort(portOptions)
56+
}
57+
58+
/**
59+
* Normalize the `cli.mcp` / `mcp` option (`boolean | McpRouteOptions`) into
60+
* concrete options, or `undefined` when the MCP route is disabled.
61+
*/
62+
export function resolveMcpConfig(mcp: boolean | McpRouteOptions | undefined): McpRouteOptions | undefined {
63+
if (!mcp)
64+
return undefined
65+
return mcp === true ? {} : mcp
66+
}
67+
68+
/**
69+
* Resolve the `mcp` entry a `__connection.json` should advertise for a dev
70+
* server started with the given `mcp` option (falling back to `def.cli?.mcp`,
71+
* exactly like `createDevServer`), or `undefined` when the route is
72+
* disabled.
73+
*
74+
* Hosted bridges that hand-roll their connection meta pass the side-car
75+
* `port`: the advertised path becomes absolute (the side-car mounts at `/`)
76+
* and the client dials `<page-host>:<port><path>`. Without `port` the path
77+
* stays relative, resolved against `__connection.json`'s own location (the
78+
* same-server default).
79+
*
80+
* @experimental
81+
*/
82+
export function resolveMcpConnectionMeta(
83+
def: DevframeDefinition,
84+
mcp: boolean | McpRouteOptions | undefined,
85+
port?: number,
86+
): ConnectionMeta['mcp'] {
87+
const config = resolveMcpConfig(mcp ?? def.cli?.mcp)
88+
if (!config)
89+
return undefined
90+
const route = withoutLeadingSlash(config.path ?? DEVFRAME_MCP_ROUTE)
91+
return port != null
92+
? { path: withLeadingSlash(route), port }
93+
: { path: route }
94+
}

0 commit comments

Comments
 (0)