| import { n as colors } from "./diagnostics-reporter-CsIG85Q5.mjs"; | ||
| import { createBuild } from "./adapters/build.mjs"; | ||
| import { n as resolveDevServerPort, t as createDevServer } from "./dev-DmdXbLyJ.mjs"; | ||
| import process from "node:process"; | ||
| import cac from "cac"; | ||
| import { safeParse } from "valibot"; | ||
| //#region src/adapters/flags.ts | ||
| /** | ||
| * Identity helper that preserves the literal schema-map type — use this | ||
| * so `InferCliFlags<typeof myFlags>` resolves to the right object shape. | ||
| * | ||
| * ```ts | ||
| * const appFlags = defineCliFlags({ | ||
| * depth: v.pipe(v.number(), v.integer()), | ||
| * config: v.optional(v.string()), | ||
| * }) | ||
| * | ||
| * defineDevframe({ | ||
| * cli: { flags: appFlags }, | ||
| * setup(ctx, info) { | ||
| * const flags = info.flags as InferCliFlags<typeof appFlags> | ||
| * flags.depth // number | ||
| * flags.config // string | undefined | ||
| * }, | ||
| * }) | ||
| * ``` | ||
| */ | ||
| function defineCliFlags(flags) { | ||
| return flags; | ||
| } | ||
| /** | ||
| * Best-effort probe of a valibot schema to decide whether the | ||
| * corresponding CAC option takes a value. Unwraps `optional` / `nullable` | ||
| * / `nullish` / `default` / `pipe` wrappers then matches on the inner | ||
| * type's kind. | ||
| */ | ||
| function getSchemaKind(schema) { | ||
| let current = schema; | ||
| while (current) { | ||
| const kind = current.type; | ||
| if (kind === "optional" || kind === "nullable" || kind === "nullish" || kind === "undefined") { | ||
| current = current.wrapped ?? current.inner; | ||
| continue; | ||
| } | ||
| if (kind === "pipe" && Array.isArray(current.pipe) && current.pipe.length > 0) { | ||
| current = current.pipe[0]; | ||
| continue; | ||
| } | ||
| return kind; | ||
| } | ||
| return "unknown"; | ||
| } | ||
| /** Whether the CAC option for this schema should be a boolean flag. */ | ||
| function isBooleanFlag(schema) { | ||
| return getSchemaKind(schema) === "boolean"; | ||
| } | ||
| /** Validate and coerce the raw cac-parsed bag against a {@link CliFlagsSchema}. */ | ||
| function parseCliFlags(schema, raw) { | ||
| const flags = {}; | ||
| const issues = []; | ||
| for (const [key, fieldSchema] of Object.entries(schema)) { | ||
| const result = safeParse(fieldSchema, raw[key]); | ||
| if (result.success) flags[key] = result.output; | ||
| else issues.push(`--${toKebab(key)}: ${result.issues.map((i) => i.message).join(", ")}`); | ||
| } | ||
| for (const [key, value] of Object.entries(raw)) if (!(key in schema) && !(key in flags)) flags[key] = value; | ||
| return issues.length ? { | ||
| flags, | ||
| issues | ||
| } : { flags }; | ||
| } | ||
| function toKebab(camel) { | ||
| return camel.replaceAll(/([a-z])([A-Z])/g, "$1-$2").toLowerCase(); | ||
| } | ||
| /** Kebab-case a schema key for CAC option registration. */ | ||
| function flagKeyToOption(camel) { | ||
| return toKebab(camel); | ||
| } | ||
| //#endregion | ||
| //#region src/adapters/cac.ts | ||
| /** | ||
| * Wrap a {@link DevframeDefinition} in a `cac`-powered command-line | ||
| * interface exposing `dev` / `build` / `mcp` subcommands. | ||
| * | ||
| * Requires the optional `cac` peer dependency. | ||
| */ | ||
| function createCac(d, options = {}) { | ||
| const defaultPort = options.defaultPort ?? d.cli?.port ?? 9999; | ||
| const defaultHost = d.cli?.host ?? "localhost"; | ||
| const cli = cac(d.cli?.command ?? d.id); | ||
| const devCommand = cli.command("[...args]", "Start a local dev server").option("--port <port>", "Port to listen on").option("--host <host>", "Host to bind to", { default: defaultHost }).option("--open", "Open the browser on start").option("--no-open", "Do not open the browser").option("--no-auth", "Disable the interactive authentication gate").option("--mcp", "Expose an MCP server over HTTP at /__mcp (use --no-mcp to disable) [experimental]"); | ||
| if (d.cli?.flags) for (const [key, schema] of Object.entries(d.cli.flags)) { | ||
| const optionName = flagKeyToOption(key); | ||
| const description = schema.description ?? ""; | ||
| if (isBooleanFlag(schema)) devCommand.option(`--${optionName}`, description); | ||
| else devCommand.option(`--${optionName} <value>`, description); | ||
| } | ||
| devCommand.action(async (_args, rawFlags) => { | ||
| const flags = resolveTypedFlags(d, rawFlags); | ||
| const host = flags.host ?? defaultHost; | ||
| const port = flags.port ?? await resolveDevServerPort(d, { | ||
| host, | ||
| defaultPort | ||
| }); | ||
| const mcp = flags.mcp; | ||
| await createDevServer(d, { | ||
| host, | ||
| port, | ||
| flags, | ||
| mcp, | ||
| onReady: options.onReady | ||
| }); | ||
| }); | ||
| cli.command("build", "Build a self-contained static deploy of the devframe").option("--out-dir <outDir>", "Output directory", { default: "dist-static" }).option("--base <base>", "URL base", { default: "/" }).option("--pretty", "Pretty-print dump JSON (larger on disk)").action(async (flags) => { | ||
| await createBuild(d, { | ||
| outDir: flags.outDir, | ||
| base: flags.base, | ||
| pretty: flags.pretty | ||
| }); | ||
| }); | ||
| cli.command("mcp", "Start an MCP server exposing agent-facing tools (stdio) [experimental]").action(async () => { | ||
| const { createMcpServer } = await import("./adapters/mcp.mjs"); | ||
| await createMcpServer(d, { | ||
| transport: "stdio", | ||
| onReady: ({ transport }) => { | ||
| console.error(`[devframe] "${d.id}" MCP server ready (${transport})`); | ||
| } | ||
| }); | ||
| }); | ||
| d.cli?.configure?.(cli); | ||
| options.configureCli?.(cli); | ||
| cli.help(); | ||
| cli.version("0.0.0"); | ||
| return { | ||
| cli, | ||
| async parse(argv = process.argv) { | ||
| cli.parse(argv, { run: false }); | ||
| await cli.runMatchedCommand(); | ||
| } | ||
| }; | ||
| } | ||
| function resolveTypedFlags(d, raw) { | ||
| if (!d.cli?.flags) return raw; | ||
| const { flags, issues } = parseCliFlags(d.cli.flags, raw); | ||
| if (issues?.length) { | ||
| for (const issue of issues) console.error(colors.red`[devframe] invalid flag — ${issue}`); | ||
| process.exit(1); | ||
| } | ||
| return flags; | ||
| } | ||
| //#endregion | ||
| export { defineCliFlags as n, parseCliFlags as r, createCac as t }; |
| import { b as DevframeNodeContext, ht as SharedState } from "./devframe-C18zEiex.mjs"; | ||
| //#region src/node/hub-internals/context.d.ts | ||
| interface InternalAnonymousAuthStorage { | ||
| trusted: Record<string, { | ||
| authToken: string; | ||
| ua: string; | ||
| origin: string; | ||
| timestamp: number; | ||
| } | undefined>; | ||
| } | ||
| interface RemoteTokenRecord { | ||
| dockId: string; | ||
| /** Dock URL origin — matched against WS handshake `Origin` header when `originLock` is on. */ | ||
| origin: string; | ||
| originLock: boolean; | ||
| } | ||
| interface DevframeInternalContext { | ||
| storage: { | ||
| auth: SharedState<InternalAnonymousAuthStorage>; | ||
| }; | ||
| /** | ||
| * Revoke an auth token: remove from storage and notify all connected clients | ||
| * using this token that they are no longer trusted. | ||
| */ | ||
| revokeAuthToken: (token: string) => Promise<void>; | ||
| /** | ||
| * Session-only tokens issued to remote-UI iframe docks. Not persisted — | ||
| * regenerated on every dev-server restart. | ||
| */ | ||
| remoteTokens: Map<string, RemoteTokenRecord>; | ||
| allocateRemoteToken: (dockId: string, origin: string, originLock: boolean) => string; | ||
| revokeRemoteToken: (token: string) => void; | ||
| revokeRemoteTokensForDock: (dockId: string) => void; | ||
| /** | ||
| * Returns true if `token` is a valid remote token and, when `originLock` is | ||
| * on, `requestOrigin` matches the recorded dock origin. | ||
| */ | ||
| isRemoteTokenTrusted: (token: string, requestOrigin?: string) => boolean; | ||
| /** | ||
| * Populated by `createWsServer` once the WS port is bound. Consumed by the | ||
| * docks host when enriching remote iframe URLs with a connection descriptor. | ||
| */ | ||
| wsEndpoint?: { | ||
| /** Full `ws://` or `wss://` URL with host and port. */ | ||
| url: string; | ||
| }; | ||
| } | ||
| declare const internalContextMap: WeakMap<DevframeNodeContext, DevframeInternalContext>; | ||
| declare function getInternalContext(context: DevframeNodeContext): DevframeInternalContext; | ||
| //#endregion | ||
| export { internalContextMap as a, getInternalContext as i, InternalAnonymousAuthStorage as n, RemoteTokenRecord as r, DevframeInternalContext as t }; |
| import { DEVFRAME_CONNECTION_META_FILENAME } from "./constants.mjs"; | ||
| import { a as diagnostics } from "./storage-D_Xy9v1l.mjs"; | ||
| import { n as createHostContext, t as createH3DevframeHost } from "./host-h3-SjwRTwkE.mjs"; | ||
| import { i as normalizeHttpServerUrl, t as startHttpAndWs } from "./server-BFsuZI9e.mjs"; | ||
| import { n as resolveBasePath, t as normalizeBasePath } from "./_shared-bWRzeSa0.mjs"; | ||
| import { t as open } from "./open-Deb5xmIT.mjs"; | ||
| import { mountStaticHandler } from "./utils/serve-static.mjs"; | ||
| import { createInteractiveAuth } from "./recipes/interactive-auth.mjs"; | ||
| import { resolve } from "pathe"; | ||
| import process$1 from "node:process"; | ||
| import { networkInterfaces } from "node:os"; | ||
| import { H3 } from "h3"; | ||
| import { createServer } from "node:net"; | ||
| import { joinURL, withBase, withLeadingSlash, withoutLeadingSlash } from "ufo"; | ||
| //#region ../../node_modules/.pnpm/get-port-please@3.2.0/node_modules/get-port-please/dist/index.mjs | ||
| const unsafePorts = /* @__PURE__ */ new Set([ | ||
| 1, | ||
| 7, | ||
| 9, | ||
| 11, | ||
| 13, | ||
| 15, | ||
| 17, | ||
| 19, | ||
| 20, | ||
| 21, | ||
| 22, | ||
| 23, | ||
| 25, | ||
| 37, | ||
| 42, | ||
| 43, | ||
| 53, | ||
| 69, | ||
| 77, | ||
| 79, | ||
| 87, | ||
| 95, | ||
| 101, | ||
| 102, | ||
| 103, | ||
| 104, | ||
| 109, | ||
| 110, | ||
| 111, | ||
| 113, | ||
| 115, | ||
| 117, | ||
| 119, | ||
| 123, | ||
| 135, | ||
| 137, | ||
| 139, | ||
| 143, | ||
| 161, | ||
| 179, | ||
| 389, | ||
| 427, | ||
| 465, | ||
| 512, | ||
| 513, | ||
| 514, | ||
| 515, | ||
| 526, | ||
| 530, | ||
| 531, | ||
| 532, | ||
| 540, | ||
| 548, | ||
| 554, | ||
| 556, | ||
| 563, | ||
| 587, | ||
| 601, | ||
| 636, | ||
| 989, | ||
| 990, | ||
| 993, | ||
| 995, | ||
| 1719, | ||
| 1720, | ||
| 1723, | ||
| 2049, | ||
| 3659, | ||
| 4045, | ||
| 5060, | ||
| 5061, | ||
| 6e3, | ||
| 6566, | ||
| 6665, | ||
| 6666, | ||
| 6667, | ||
| 6668, | ||
| 6669, | ||
| 6697, | ||
| 10080 | ||
| ]); | ||
| function isUnsafePort(port) { | ||
| return unsafePorts.has(port); | ||
| } | ||
| function isSafePort(port) { | ||
| return !isUnsafePort(port); | ||
| } | ||
| var GetPortError = class extends Error { | ||
| constructor(message, opts) { | ||
| super(message, opts); | ||
| this.message = message; | ||
| } | ||
| name = "GetPortError"; | ||
| }; | ||
| function _log(verbose, message) { | ||
| if (verbose) console.log(`[get-port] ${message}`); | ||
| } | ||
| function _generateRange(from, to) { | ||
| if (to < from) return []; | ||
| const r = []; | ||
| for (let index = from; index <= to; index++) r.push(index); | ||
| return r; | ||
| } | ||
| function _tryPort(port, host) { | ||
| return new Promise((resolve) => { | ||
| const server = createServer(); | ||
| server.unref(); | ||
| server.on("error", () => { | ||
| resolve(false); | ||
| }); | ||
| server.listen({ | ||
| port, | ||
| host | ||
| }, () => { | ||
| const { port: port2 } = server.address(); | ||
| server.close(() => { | ||
| resolve(isSafePort(port2) && port2); | ||
| }); | ||
| }); | ||
| }); | ||
| } | ||
| function _getLocalHosts(additional) { | ||
| const hosts = new Set(additional); | ||
| for (const _interface of Object.values(networkInterfaces())) for (const config of _interface || []) if (config.address && !config.internal && !config.address.startsWith("fe80::") && !config.address.startsWith("169.254")) hosts.add(config.address); | ||
| return [...hosts]; | ||
| } | ||
| async function _findPort(ports, host) { | ||
| for (const port of ports) { | ||
| const r = await _tryPort(port, host); | ||
| if (r) return r; | ||
| } | ||
| } | ||
| function _fmtOnHost(hostname) { | ||
| return hostname ? `on host ${JSON.stringify(hostname)}` : "on any host"; | ||
| } | ||
| const HOSTNAME_RE = /^(?!-)[\d.:A-Za-z-]{1,63}(?<!-)$/; | ||
| function _validateHostname(hostname, _public, verbose) { | ||
| if (hostname && !HOSTNAME_RE.test(hostname)) { | ||
| const fallbackHost = _public ? "0.0.0.0" : "127.0.0.1"; | ||
| _log(verbose, `Invalid hostname: ${JSON.stringify(hostname)}. Using ${JSON.stringify(fallbackHost)} as fallback.`); | ||
| return fallbackHost; | ||
| } | ||
| return hostname; | ||
| } | ||
| async function getPort(_userOptions = {}) { | ||
| if (typeof _userOptions === "number" || typeof _userOptions === "string") _userOptions = { port: Number.parseInt(_userOptions + "") || 0 }; | ||
| const _port = Number(_userOptions.port ?? process.env.PORT); | ||
| const _userSpecifiedAnyPort = Boolean(_userOptions.port || _userOptions.ports?.length || _userOptions.portRange?.length); | ||
| const options = { | ||
| random: _port === 0, | ||
| ports: [], | ||
| portRange: [], | ||
| alternativePortRange: _userSpecifiedAnyPort ? [] : [3e3, 3100], | ||
| verbose: false, | ||
| ..._userOptions, | ||
| port: _port, | ||
| host: _validateHostname(_userOptions.host ?? process.env.HOST, _userOptions.public, _userOptions.verbose) | ||
| }; | ||
| if (options.random && !_userSpecifiedAnyPort) return getRandomPort(options.host); | ||
| const portsToCheck = [ | ||
| options.port, | ||
| ...options.ports, | ||
| ..._generateRange(...options.portRange) | ||
| ].filter((port) => { | ||
| if (!port) return false; | ||
| if (!isSafePort(port)) { | ||
| _log(options.verbose, `Ignoring unsafe port: ${port}`); | ||
| return false; | ||
| } | ||
| return true; | ||
| }); | ||
| if (portsToCheck.length === 0) portsToCheck.push(3e3); | ||
| let availablePort = await _findPort(portsToCheck, options.host); | ||
| if (!availablePort && options.alternativePortRange.length > 0) { | ||
| availablePort = await _findPort(_generateRange(...options.alternativePortRange), options.host); | ||
| if (portsToCheck.length > 0) { | ||
| let message = `Unable to find an available port (tried ${portsToCheck.join("-")} ${_fmtOnHost(options.host)}).`; | ||
| if (availablePort) message += ` Using alternative port ${availablePort}.`; | ||
| _log(options.verbose, message); | ||
| } | ||
| } | ||
| if (!availablePort && _userOptions.random !== false) { | ||
| availablePort = await getRandomPort(options.host); | ||
| if (availablePort) _log(options.verbose, `Using random port ${availablePort}`); | ||
| } | ||
| if (!availablePort) { | ||
| const triedRanges = [ | ||
| options.port, | ||
| options.portRange.join("-"), | ||
| options.alternativePortRange.join("-") | ||
| ].filter(Boolean).join(", "); | ||
| throw new GetPortError(`Unable to find an available port ${_fmtOnHost(options.host)} (tried ${triedRanges})`); | ||
| } | ||
| return availablePort; | ||
| } | ||
| async function getRandomPort(host) { | ||
| const port = await checkPort(0, host); | ||
| if (port === false) throw new GetPortError(`Unable to find a random port ${_fmtOnHost(host)}`); | ||
| return port; | ||
| } | ||
| async function checkPort(port, host = process.env.HOST, verbose) { | ||
| if (!host) host = _getLocalHosts([void 0, "0.0.0.0"]); | ||
| if (!Array.isArray(host)) return _tryPort(port, host); | ||
| for (const _host of host) { | ||
| const _port = await _tryPort(port, _host); | ||
| if (_port === false) { | ||
| if (port < 1024 && verbose) _log(verbose, `Unable to listen to the privileged port ${port} ${_fmtOnHost(_host)}`); | ||
| return false; | ||
| } | ||
| if (port === 0 && _port !== 0) port = _port; | ||
| } | ||
| return port; | ||
| } | ||
| //#endregion | ||
| //#region src/adapters/dev.ts | ||
| const DEFAULT_PORT = 9999; | ||
| /** | ||
| * Resolve the listening port for {@link createDevServer}, honoring the | ||
| * definition's `cli.port` / `cli.portRange` / `cli.random` settings. | ||
| * Exposed separately so authors who run their own argv parsing can | ||
| * resolve a port up-front (to print it, log it, etc.) before starting | ||
| * the server. | ||
| */ | ||
| async function resolveDevServerPort(def, options = {}) { | ||
| const host = options.host ?? def.cli?.host ?? "localhost"; | ||
| const portOptions = { | ||
| port: options.defaultPort ?? def.cli?.port ?? DEFAULT_PORT, | ||
| host | ||
| }; | ||
| if (def.cli?.portRange) portOptions.portRange = def.cli.portRange; | ||
| if (def.cli?.random) portOptions.random = def.cli.random; | ||
| return getPort(portOptions); | ||
| } | ||
| /** | ||
| * Start a devframe dev server for a {@link DevframeDefinition} — | ||
| * h3 + WebSocket RPC + (optionally) the author's SPA mounted at the | ||
| * resolved base path. | ||
| * | ||
| * When `distDir` is omitted (and `def.cli?.distDir` is unset) the | ||
| * server runs in **bridge mode**: only `__connection.json` and the WS | ||
| * endpoint are mounted, with no SPA mount. The SPA is expected to be | ||
| * hosted elsewhere (e.g. by a parent Vite/Nuxt dev server) — see | ||
| * `viteDevBridge({ devMiddleware })`. | ||
| * | ||
| * Returns the underlying {@link StartedServer} handle so callers can | ||
| * close it gracefully (SIGINT, hot-reload, test teardown). | ||
| * | ||
| * Use this directly when integrating devframe into an existing CLI | ||
| * framework (commander, yargs, hand-rolled CAC). For the all-in-one | ||
| * `dev` / `build` / `mcp` shell, reach for {@link createCac} instead. | ||
| */ | ||
| async function createDevServer(def, options = {}) { | ||
| const distDir = options.distDir ?? def.cli?.distDir; | ||
| const host = options.host ?? def.cli?.host ?? "localhost"; | ||
| const port = options.port ?? await resolveDevServerPort(def, { host }); | ||
| const flags = options.flags ?? {}; | ||
| const basePath = options.basePath ? normalizeBasePath(options.basePath) : resolveBasePath(def, "standalone"); | ||
| const app = options.app ?? new H3(); | ||
| const h3Host = createH3DevframeHost({ | ||
| origin: normalizeHttpServerUrl(host, port), | ||
| appName: def.id, | ||
| mount: (base, dir) => { | ||
| mountStaticHandler(app, base, dir); | ||
| } | ||
| }); | ||
| const ctx = await createHostContext({ | ||
| cwd: process$1.cwd(), | ||
| mode: "dev", | ||
| host: h3Host | ||
| }); | ||
| const setupInfo = { flags }; | ||
| await def.setup(ctx, setupInfo); | ||
| const mcpConfig = resolveMcpConfig(options.mcp ?? def.cli?.mcp); | ||
| let mcpDispose; | ||
| let mcpMeta; | ||
| if (mcpConfig) { | ||
| const mcpRoute = withoutLeadingSlash(mcpConfig.path ?? "__mcp"); | ||
| const mcpPath = joinURL(basePath, mcpRoute); | ||
| let mountMcpHttp; | ||
| try { | ||
| ({mountMcpHttp} = await import("./http-BaCZYhIR.mjs")); | ||
| } catch (error) { | ||
| const reason = error instanceof Error ? error.message : String(error); | ||
| throw diagnostics.DF0017({ | ||
| transport: "http", | ||
| reason, | ||
| cause: error | ||
| }); | ||
| } | ||
| mcpDispose = mountMcpHttp(app, ctx, mcpPath, { | ||
| serverName: `${def.id} (devframe)`, | ||
| serverVersion: def.version ?? "0.0.0", | ||
| exposeSharedState: true, | ||
| allowedOrigins: mcpConfig.allowedOrigins | ||
| }).dispose; | ||
| mcpMeta = { path: mcpRoute }; | ||
| } | ||
| const { bindPath, wsPort, meta } = resolveWsConnection(def, options, basePath); | ||
| const connectionMetaPath = joinURL(basePath, DEVFRAME_CONNECTION_META_FILENAME); | ||
| app.use(connectionMetaPath, () => ({ | ||
| backend: "websocket", | ||
| websocket: meta, | ||
| ...mcpMeta ? { mcp: mcpMeta } : {} | ||
| })); | ||
| if (distDir) mountStaticHandler(app, basePath, resolve(distDir)); | ||
| const authOption = flags.auth === false ? false : def.cli?.auth; | ||
| let authHandler; | ||
| let resolvedAuth; | ||
| if (authOption === false) resolvedAuth = false; | ||
| else if (typeof authOption === "object") { | ||
| authHandler = authOption; | ||
| resolvedAuth = authOption; | ||
| } else { | ||
| authHandler = createInteractiveAuth(ctx); | ||
| resolvedAuth = authHandler; | ||
| } | ||
| const started = await startHttpAndWs({ | ||
| context: ctx, | ||
| host, | ||
| port, | ||
| app, | ||
| path: bindPath, | ||
| wsPort, | ||
| auth: resolvedAuth, | ||
| onReady: async (info) => { | ||
| authHandler?.printBanner(); | ||
| await options.onReady?.(info); | ||
| await maybeOpenBrowser(def, flags, `${info.origin}${basePath}`, options.openBrowser, authHandler); | ||
| } | ||
| }); | ||
| if (mcpDispose) { | ||
| const closeServer = started.close; | ||
| started.close = async () => { | ||
| await mcpDispose(); | ||
| await closeServer(); | ||
| }; | ||
| } | ||
| return started; | ||
| } | ||
| /** | ||
| * Normalize the `cli.mcp` / `mcp` option (`boolean | McpRouteOptions`) into | ||
| * concrete options, or `undefined` when the MCP route is disabled. | ||
| */ | ||
| function resolveMcpConfig(mcp) { | ||
| if (!mcp) return void 0; | ||
| return mcp === true ? {} : mcp; | ||
| } | ||
| /** | ||
| * Resolve the three WS connection scenarios from the definition / call-site | ||
| * config into a concrete server bind path, optional dedicated port, and the | ||
| * `__connection.json` descriptor the browser resolves. | ||
| */ | ||
| function resolveWsConnection(def, options, basePath) { | ||
| const ws = options.ws ?? def.cli?.ws ?? {}; | ||
| const route = withoutLeadingSlash(ws.route ?? "__devframe_ws"); | ||
| if (ws.url) return { | ||
| bindPath: joinURL(basePath, route), | ||
| wsPort: void 0, | ||
| meta: ws.url | ||
| }; | ||
| if (ws.port != null) return { | ||
| bindPath: withLeadingSlash(route), | ||
| wsPort: ws.port, | ||
| meta: { | ||
| port: ws.port, | ||
| path: route | ||
| } | ||
| }; | ||
| return { | ||
| bindPath: joinURL(basePath, route), | ||
| wsPort: void 0, | ||
| meta: { path: route } | ||
| }; | ||
| } | ||
| async function maybeOpenBrowser(def, flags, origin, override, authHandler) { | ||
| const flagsOpen = flags.open; | ||
| const cliOpen = def.cli?.open; | ||
| const resolved = override ?? flagsOpen ?? cliOpen; | ||
| if (resolved === void 0 || resolved === false) return; | ||
| const target = typeof resolved === "string" ? withBase(resolved, origin) : origin; | ||
| const authorizedTarget = authHandler?.buildOpenUrl?.(target) ?? target; | ||
| try { | ||
| await open(authorizedTarget); | ||
| } catch {} | ||
| } | ||
| //#endregion | ||
| export { resolveDevServerPort as n, createDevServer as t }; |
Sorry, the diff of this file is too big to display
| import { W as DevframeNodeRpcSession, b as DevframeNodeContext, ht as SharedState } from "./devframe-C18zEiex.mjs"; | ||
| import { n as InternalAnonymousAuthStorage } from "./context-CuEeZkUg.mjs"; | ||
| //#region src/node/auth/revoke.d.ts | ||
| /** | ||
| * Flip `isTrusted` to false on any live WS clients connected with `token` | ||
| * and broadcast the `auth:revoked` event so they can react. | ||
| * | ||
| * Shared between persisted-auth revocation and remote-dock token revocation. | ||
| */ | ||
| declare function revokeActiveConnectionsForToken(context: DevframeNodeContext, token: string): Promise<void>; | ||
| /** | ||
| * Revoke an auth token: remove from storage and notify all connected clients | ||
| * using this token that they are no longer trusted. | ||
| */ | ||
| declare function revokeAuthToken(context: DevframeNodeContext, storage: SharedState<InternalAnonymousAuthStorage>, token: string): Promise<void>; | ||
| //#endregion | ||
| //#region src/node/auth/state.d.ts | ||
| /** | ||
| * The current one-time authentication code. Display this to the user (e.g. in | ||
| * the dev-server terminal) so they can type it into the browser to authenticate. | ||
| */ | ||
| declare function getTempAuthCode(): string; | ||
| /** | ||
| * Rotate the authentication code, resetting its expiry window and failed-attempt | ||
| * counter. Call this when a new authentication flow begins (e.g. when an | ||
| * untrusted client starts authenticating) so the displayed code is freshly | ||
| * valid for its full TTL. | ||
| */ | ||
| declare function refreshTempAuthCode(): string; | ||
| /** | ||
| * Build a "magic link" authentication URL that embeds a one-time code (OTP) as | ||
| * a query parameter. Opening it authenticates the client without typing — print | ||
| * it on startup (devframe stays headless, so the host prints its own banner). | ||
| * Defaults to the current code; the link is subject to the same TTL. | ||
| */ | ||
| declare function buildOtpAuthUrl(baseUrl: string, code?: string): string; | ||
| /** | ||
| * Re-authenticate a connection that presents a previously-issued bearer token. | ||
| * Returns `true` and marks the session trusted when the token is known. | ||
| * | ||
| * Used by the `anonymous:devframe:auth` handler so a client that already | ||
| * authenticated (token persisted in the browser) is trusted on reconnect | ||
| * without entering the code again. | ||
| */ | ||
| declare function verifyAuthToken(token: string, session: DevframeNodeRpcSession, storage: SharedState<InternalAnonymousAuthStorage>): boolean; | ||
| /** | ||
| * Exchange a one-time authentication code for a fresh, node-issued bearer token. | ||
| * | ||
| * On success this mints a high-entropy token, records it in the trusted store, | ||
| * marks the calling session trusted, rotates the code, and returns the token | ||
| * for the client to persist. Returns `null` on any failure. | ||
| * | ||
| * Because the code is short and human-typed, verification is hardened against | ||
| * brute force: it enforces a time-to-live, compares in constant time, and | ||
| * rotates the code after {@link TEMP_AUTH_MAX_ATTEMPTS} failed attempts so an | ||
| * attacker cannot keep guessing against the same code. | ||
| */ | ||
| declare function exchangeTempAuthCode(code: string, session: DevframeNodeRpcSession, info: { | ||
| ua: string; | ||
| origin: string; | ||
| }, storage: SharedState<InternalAnonymousAuthStorage>): string | null; | ||
| //#endregion | ||
| export { verifyAuthToken as a, refreshTempAuthCode as i, exchangeTempAuthCode as n, revokeActiveConnectionsForToken as o, getTempAuthCode as r, revokeAuthToken as s, buildOtpAuthUrl as t }; |
| import { $ as DevframeRpcServerFunctions, Q as DevframeRpcClientFunctions, W as DevframeNodeRpcSession, _ as ConnectionMeta, b as DevframeNodeContext, p as DevframeAuthHandler } from "./devframe-C18zEiex.mjs"; | ||
| import "./index-BSmqhVju.mjs"; | ||
| import { Peer } from "crossws"; | ||
| import { BirpcGroup, EventOptions } from "birpc"; | ||
| import { NodeAdapter } from "crossws/adapters/node"; | ||
| import { Server } from "node:http"; | ||
| import { H3 } from "h3"; | ||
| //#region src/node/server.d.ts | ||
| interface StartHttpAndWsOptions { | ||
| context: DevframeNodeContext; | ||
| host?: string; | ||
| port: number; | ||
| /** | ||
| * Optional h3 app to mount on. When omitted a fresh one is created; | ||
| * when provided, callers can add their own routes (static handlers, | ||
| * auth middleware, etc.) first. | ||
| */ | ||
| app?: H3; | ||
| /** | ||
| * Bind the WS endpoint to a single upgrade route (e.g. `/__devframe_ws`) instead of | ||
| * claiming every upgrade on the port. This lets the socket share a server | ||
| * with other upgrade handlers (Vite HMR, a host framework's own sockets) | ||
| * and is what the SPA's `__connection.json` points at. When omitted, the WS | ||
| * server handles every upgrade on the port (legacy behaviour). | ||
| */ | ||
| path?: string; | ||
| /** | ||
| * Bind the WS endpoint on its own port instead of sharing the HTTP server's. | ||
| * The HTTP/SPA server still listens on `port`; the socket gets a dedicated | ||
| * `ws` server on `wsPort` (same `host`). Use this for the "different port" | ||
| * connection scenario. Ignored when a `server` is supplied. | ||
| */ | ||
| wsPort?: number; | ||
| /** | ||
| * Mount the WS endpoint onto an existing HTTP server, sharing its port, | ||
| * rather than creating and listening on a fresh one. Use this to embed | ||
| * devframe's RPC socket inside a host server (e.g. a Vite dev server) — pair | ||
| * it with `path` so it coexists with the host's routes. The caller owns the | ||
| * server's lifecycle: {@link StartedServer.close} detaches devframe's upgrade | ||
| * listener but leaves the host server running. When set, `host`/`port` are | ||
| * only used to report the resolved origin. | ||
| */ | ||
| server?: Server; | ||
| /** | ||
| * Authentication for the server: | ||
| * | ||
| * - `true` (default) — no gate; every registered method is callable | ||
| * regardless of trust (today's behavior, unchanged). | ||
| * - `false` — the RPC server is started without a trust handshake. | ||
| * Intended for single-user localhost tools where an auth round-trip | ||
| * would only get in the way. A noop `anonymous:devframe:auth` handler | ||
| * is registered so the browser client's unconditional handshake call | ||
| * succeeds and auto-trusts. | ||
| * - A {@link DevframeAuthHandler} (e.g. from | ||
| * `devframe/recipes/interactive-auth`'s `createInteractiveAuth`) — | ||
| * registers its `rpcFunctions`, wires its `authorize` as the resolver | ||
| * gate, and wires its `onConnect` on every new peer. This is the | ||
| * fully-authenticated server: an untrusted caller can only reach | ||
| * `anonymous:`-prefixed methods (see `isAnonymousRpcMethod`). | ||
| */ | ||
| auth?: boolean | DevframeAuthHandler; | ||
| /** | ||
| * Lower-level escape hatch: gate individual RPC calls by method name and | ||
| * session without a full {@link DevframeAuthHandler}. Ignored when `auth` | ||
| * is a handler object (its own `authorize` is used); combine with `auth: | ||
| * true` to layer a custom policy on top of an otherwise ungated server. | ||
| */ | ||
| authorize?: (methodName: string, session: DevframeNodeRpcSession) => boolean; | ||
| /** | ||
| * Called once per new WS connection, right after its session is created | ||
| * (before any RPC call is dispatched). Runs after the auth handler's own | ||
| * `onConnect` (when `auth` is a {@link DevframeAuthHandler}), so it can | ||
| * observe — but not override — the connect-time trust decision. | ||
| */ | ||
| onPeerConnect?: (peer: Peer, session: DevframeNodeRpcSession) => void; | ||
| /** | ||
| * Forwarded verbatim to the internal `createRpcServer`'s birpc | ||
| * `rpcOptions`, alongside the resolver `startHttpAndWs` installs for | ||
| * auth/session wiring. Use this so a host that owns its own structured | ||
| * diagnostics (e.g. a coded error reporter) keeps seeing RPC failures | ||
| * instead of them being silently absorbed by delegating to | ||
| * `startHttpAndWs`. Returning `true` from either callback suppresses | ||
| * birpc's own error response to the caller — see birpc's | ||
| * `EventOptions` for the full contract. | ||
| */ | ||
| rpcOptions?: Pick<EventOptions<DevframeRpcClientFunctions, DevframeRpcServerFunctions, false>, 'onFunctionError' | 'onGeneralError'>; | ||
| /** | ||
| * Extra origins to accept on the WS upgrade beyond the loopback default | ||
| * (`localhost`/`127.0.0.1`/`::1` and any `Origin`-less request from a | ||
| * native client). Add your LAN/tunnel origin here when reaching the tool | ||
| * from another host. Pass `false` to disable origin checking entirely | ||
| * (not recommended). Default: loopback-only. | ||
| */ | ||
| allowedOrigins?: readonly string[] | false; | ||
| /** | ||
| * Called once the WS server is bound so callers can mount static | ||
| * handlers whose origin depends on the resolved port, or print their | ||
| * own startup banner. Devframe does not print one itself. | ||
| */ | ||
| onReady?: (info: { | ||
| origin: string; | ||
| port: number; | ||
| app: H3; | ||
| }) => void | Promise<void>; | ||
| } | ||
| interface StartedServer { | ||
| /** Listening origin, e.g. `http://localhost:9999`. */ | ||
| origin: string; | ||
| port: number; | ||
| app: H3; | ||
| /** The crossws node adapter driving the RPC socket (connected peers, pub/sub). */ | ||
| ws: NodeAdapter; | ||
| rpcGroup: BirpcGroup<DevframeRpcClientFunctions, DevframeRpcServerFunctions, false>; | ||
| /** | ||
| * The {@link ConnectionMeta} descriptor for this server — the same shape | ||
| * a `__connection.json` route should serve so a devframe client's | ||
| * `resolveWsUrl` can dial back in. Reflects the `path` / `wsPort` this | ||
| * server was started with and the `jsonSerializable` methods currently | ||
| * registered on `context.rpc`. | ||
| */ | ||
| connectionMeta: () => ConnectionMeta; | ||
| close: () => Promise<void>; | ||
| } | ||
| /** | ||
| * Compose an h3 + WebSocket server for a devframe context. The RPC | ||
| * group is bound to `context.rpc.functions`; the WS endpoint lives on | ||
| * the same port as the HTTP server. | ||
| */ | ||
| declare function startHttpAndWs(options: StartHttpAndWsOptions): Promise<StartedServer>; | ||
| //#endregion | ||
| export { StartedServer as n, startHttpAndWs as r, StartHttpAndWsOptions as t }; |
@@ -1,2 +0,2 @@ | ||
| import { r as DevframeDefinition } from "../devframe-elfUcUJG.mjs"; | ||
| import { r as DevframeDefinition } from "../devframe-C18zEiex.mjs"; | ||
| //#region src/adapters/build.d.ts | ||
@@ -3,0 +3,0 @@ interface CreateBuildOptions { |
@@ -1,2 +0,2 @@ | ||
| import { Ft as parseCliFlags, Mt as CliFlagsSchema, Nt as InferCliFlags, Pt as defineCliFlags, r as DevframeDefinition } from "../devframe-elfUcUJG.mjs"; | ||
| import { Ft as parseCliFlags, Mt as CliFlagsSchema, Nt as InferCliFlags, Pt as defineCliFlags, r as DevframeDefinition } from "../devframe-C18zEiex.mjs"; | ||
| import { CAC } from "cac"; | ||
@@ -3,0 +3,0 @@ import { H3 } from "h3"; |
@@ -1,2 +0,2 @@ | ||
| import { n as defineCliFlags, r as parseCliFlags, t as createCac } from "../cac-CQB28Ucm.mjs"; | ||
| import { n as defineCliFlags, r as parseCliFlags, t as createCac } from "../cac-BK-MiL3k.mjs"; | ||
| export { createCac, defineCliFlags, parseCliFlags }; |
@@ -1,2 +0,2 @@ | ||
| import { Ft as parseCliFlags, Mt as CliFlagsSchema, Nt as InferCliFlags, Pt as defineCliFlags } from "../devframe-elfUcUJG.mjs"; | ||
| import { Ft as parseCliFlags, Mt as CliFlagsSchema, Nt as InferCliFlags, Pt as defineCliFlags } from "../devframe-C18zEiex.mjs"; | ||
| import { CacHandle, CreateCacOptions, createCac } from "./cac.mjs"; | ||
@@ -3,0 +3,0 @@ //#region src/adapters/cli.d.ts |
@@ -1,2 +0,2 @@ | ||
| import { n as defineCliFlags, r as parseCliFlags, t as createCac } from "../cac-CQB28Ucm.mjs"; | ||
| import { n as defineCliFlags, r as parseCliFlags, t as createCac } from "../cac-BK-MiL3k.mjs"; | ||
| //#region src/adapters/cli.ts | ||
@@ -3,0 +3,0 @@ /** @deprecated Use `createCac` from `devframe/adapters/cac` instead. */ |
@@ -1,3 +0,3 @@ | ||
| import { d as McpRouteOptions, r as DevframeDefinition, u as DevframeWsOptions } from "../devframe-elfUcUJG.mjs"; | ||
| import { n as StartedServer } from "../server-kmetRyhu.mjs"; | ||
| import { d as McpRouteOptions, r as DevframeDefinition, u as DevframeWsOptions } from "../devframe-C18zEiex.mjs"; | ||
| import { n as StartedServer } from "../server-BLaM21gv.mjs"; | ||
| import { H3 } from "h3"; | ||
@@ -4,0 +4,0 @@ //#region src/adapters/dev.d.ts |
@@ -1,2 +0,2 @@ | ||
| import { n as resolveDevServerPort, t as createDevServer } from "../dev-J2PqMPTn.mjs"; | ||
| import { n as resolveDevServerPort, t as createDevServer } from "../dev-DmdXbLyJ.mjs"; | ||
| export { createDevServer, resolveDevServerPort }; |
@@ -1,2 +0,2 @@ | ||
| import { b as DevframeNodeContext, r as DevframeDefinition } from "../devframe-elfUcUJG.mjs"; | ||
| import { b as DevframeNodeContext, r as DevframeDefinition } from "../devframe-C18zEiex.mjs"; | ||
| //#region src/adapters/embedded.d.ts | ||
@@ -3,0 +3,0 @@ interface CreateEmbeddedOptions { |
@@ -1,2 +0,2 @@ | ||
| import { r as DevframeDefinition } from "../devframe-elfUcUJG.mjs"; | ||
| import { r as DevframeDefinition } from "../devframe-C18zEiex.mjs"; | ||
| import "@modelcontextprotocol/sdk/server/index.js"; | ||
@@ -3,0 +3,0 @@ //#region src/adapters/mcp/build-server.d.ts |
@@ -1,2 +0,2 @@ | ||
| import { $ as DevframeRpcServerFunctions, F as ScopedSharedStates, I as SettingsForNamespace, J as RpcSharedStateHost, N as ScopedRpcFn, O as DevframeSettings, P as ScopedServerFunctions, Q as DevframeRpcClientFunctions, _ as ConnectionMeta, at as StreamReader, ht as SharedState, kt as EventEmitter, ot as StreamSink, q as RpcSharedStateGetOptions } from "../devframe-elfUcUJG.mjs"; | ||
| import { $ as DevframeRpcServerFunctions, F as ScopedSharedStates, I as SettingsForNamespace, J as RpcSharedStateHost, N as ScopedRpcFn, O as DevframeSettings, P as ScopedServerFunctions, Q as DevframeRpcClientFunctions, _ as ConnectionMeta, at as StreamReader, ht as SharedState, kt as EventEmitter, ot as StreamSink, q as RpcSharedStateGetOptions } from "../devframe-C18zEiex.mjs"; | ||
| import { _ as RpcFunctionDefinition, w as RpcFunctionsCollector } from "../types-CrzNxXKq.mjs"; | ||
@@ -3,0 +3,0 @@ import { C as RpcCacheManager, w as RpcCacheOptions } from "../index-DgsLFhZg.mjs"; |
@@ -1,2 +0,2 @@ | ||
| import { r as DevframeDefinition } from "../devframe-elfUcUJG.mjs"; | ||
| import { r as DevframeDefinition } from "../devframe-C18zEiex.mjs"; | ||
| //#region src/helpers/vite.d.ts | ||
@@ -3,0 +3,0 @@ interface ViteDevBridgeOptions { |
@@ -5,3 +5,3 @@ import { DEVFRAME_CONNECTION_META_FILENAME, DEVFRAME_WS_ROUTE } from "../constants.mjs"; | ||
| import { serveStaticNodeMiddleware } from "../utils/serve-static.mjs"; | ||
| import { n as resolveDevServerPort, t as createDevServer } from "../dev-J2PqMPTn.mjs"; | ||
| import { n as resolveDevServerPort, t as createDevServer } from "../dev-DmdXbLyJ.mjs"; | ||
| import { resolve } from "pathe"; | ||
@@ -8,0 +8,0 @@ //#region src/helpers/vite.ts |
+1
-1
@@ -1,2 +0,2 @@ | ||
| import { $ as DevframeRpcServerFunctions, A as DevframeSettingsStore, At as EventUnsubscribe, B as DevframeDefineDiagnosticsOptions, C as DevframeServicesHost, Ct as AgentResourceContent, D as DevframeScopedStreamingHost, Dt as DevframeAgentHost, E as DevframeScopedNodeRpc, Et as AgentToolInput, F as ScopedSharedStates, G as RpcBroadcastOptions, H as DevframeDiagnosticsHost, I as SettingsForNamespace, J as RpcSharedStateHost, K as RpcFunctionsHost, L as DevframeViewHost, M as ScopedClientFunctions, N as ScopedRpcFn, O as DevframeSettings, Ot as DevframeAgentHostEvents, P as ScopedServerFunctions, Q as DevframeRpcClientFunctions, R as DevframeHost, S as DevframeServiceOf, St as AgentResource, T as DevframeScopedNodeContext, Tt as AgentTool, U as DevframeDiagnosticsLogger, V as DevframeDiagnosticsDefinition, W as DevframeNodeRpcSession, X as RpcStreamingChannelOptions, Y as RpcStreamingChannel, Z as RpcStreamingHost, _ as ConnectionMeta, a as DevframeDockDefaults, b as DevframeNodeContext, bt as AgentHandle, c as DevframeSetupInfo, d as McpRouteOptions, et as DevframeRpcSharedStates, f as defineDevframe, g as Thenable, h as PartialWithoutId, i as DevframeDeploymentKind, j as ScopedBroadcastOptions, jt as EventsMap, k as DevframeSettingsRegistry, kt as EventEmitter, l as DevframeSpaOptions, m as EntriesToObject, n as DevframeCliOptions, o as DevframeDuplicationStrategy, q as RpcSharedStateGetOptions, r as DevframeDefinition, s as DevframeRuntime, t as DevframeBrowserContext, u as DevframeWsOptions, v as ConnectionMetaWebsocket, w as DevframeServicesRegistry, wt as AgentResourceInput, x as DevframeServiceId, xt as AgentManifest, y as DevframeCapabilities, z as DevframeStorageScope } from "./devframe-elfUcUJG.mjs"; | ||
| import { $ as DevframeRpcServerFunctions, A as DevframeSettingsStore, At as EventUnsubscribe, B as DevframeDefineDiagnosticsOptions, C as DevframeServicesHost, Ct as AgentResourceContent, D as DevframeScopedStreamingHost, Dt as DevframeAgentHost, E as DevframeScopedNodeRpc, Et as AgentToolInput, F as ScopedSharedStates, G as RpcBroadcastOptions, H as DevframeDiagnosticsHost, I as SettingsForNamespace, J as RpcSharedStateHost, K as RpcFunctionsHost, L as DevframeViewHost, M as ScopedClientFunctions, N as ScopedRpcFn, O as DevframeSettings, Ot as DevframeAgentHostEvents, P as ScopedServerFunctions, Q as DevframeRpcClientFunctions, R as DevframeHost, S as DevframeServiceOf, St as AgentResource, T as DevframeScopedNodeContext, Tt as AgentTool, U as DevframeDiagnosticsLogger, V as DevframeDiagnosticsDefinition, W as DevframeNodeRpcSession, X as RpcStreamingChannelOptions, Y as RpcStreamingChannel, Z as RpcStreamingHost, _ as ConnectionMeta, a as DevframeDockDefaults, b as DevframeNodeContext, bt as AgentHandle, c as DevframeSetupInfo, d as McpRouteOptions, et as DevframeRpcSharedStates, f as defineDevframe, g as Thenable, h as PartialWithoutId, i as DevframeDeploymentKind, j as ScopedBroadcastOptions, jt as EventsMap, k as DevframeSettingsRegistry, kt as EventEmitter, l as DevframeSpaOptions, m as EntriesToObject, n as DevframeCliOptions, o as DevframeDuplicationStrategy, q as RpcSharedStateGetOptions, r as DevframeDefinition, s as DevframeRuntime, t as DevframeBrowserContext, u as DevframeWsOptions, v as ConnectionMetaWebsocket, w as DevframeServicesRegistry, wt as AgentResourceInput, x as DevframeServiceId, xt as AgentManifest, y as DevframeCapabilities, z as DevframeStorageScope } from "./devframe-C18zEiex.mjs"; | ||
| import { C as RpcFunctionType, T as RpcReturnSchema, _ as RpcFunctionDefinition, g as RpcFunctionAgentOptions, i as RpcArgsSchema } from "./types-CrzNxXKq.mjs"; | ||
@@ -3,0 +3,0 @@ import "./index-DgsLFhZg.mjs"; |
@@ -1,3 +0,3 @@ | ||
| import { p as DevframeAuthHandler } from "../devframe-elfUcUJG.mjs"; | ||
| import { a as verifyAuthToken, i as refreshTempAuthCode, n as exchangeTempAuthCode, o as revokeActiveConnectionsForToken, r as getTempAuthCode, s as revokeAuthToken, t as buildOtpAuthUrl } from "../index-D8RNkmY_.mjs"; | ||
| import { p as DevframeAuthHandler } from "../devframe-C18zEiex.mjs"; | ||
| import { a as verifyAuthToken, i as refreshTempAuthCode, n as exchangeTempAuthCode, o as revokeActiveConnectionsForToken, r as getTempAuthCode, s as revokeAuthToken, t as buildOtpAuthUrl } from "../index-BSmqhVju.mjs"; | ||
| export { DevframeAuthHandler, buildOtpAuthUrl, exchangeTempAuthCode, getTempAuthCode, refreshTempAuthCode, revokeActiveConnectionsForToken, revokeAuthToken, verifyAuthToken }; |
@@ -1,3 +0,3 @@ | ||
| import { i as DevframeDeploymentKind, r as DevframeDefinition } from "../devframe-elfUcUJG.mjs"; | ||
| import { a as internalContextMap, i as getInternalContext, n as InternalAnonymousAuthStorage, r as RemoteTokenRecord, t as DevframeInternalContext } from "../context-D0md0kxv.mjs"; | ||
| import { i as DevframeDeploymentKind, r as DevframeDefinition } from "../devframe-C18zEiex.mjs"; | ||
| import { a as internalContextMap, i as getInternalContext, n as InternalAnonymousAuthStorage, r as RemoteTokenRecord, t as DevframeInternalContext } from "../context-CuEeZkUg.mjs"; | ||
| //#region src/adapters/_shared.d.ts | ||
@@ -4,0 +4,0 @@ /** |
@@ -1,5 +0,5 @@ | ||
| import { C as DevframeServicesHost, Ct as AgentResourceContent, Dt as DevframeAgentHost$1, Et as AgentToolInput, H as DevframeDiagnosticsHost$1, J as RpcSharedStateHost, K as RpcFunctionsHost, L as DevframeViewHost$1, O as DevframeSettings, Ot as DevframeAgentHostEvents, R as DevframeHost, S as DevframeServiceOf, St as AgentResource, T as DevframeScopedNodeContext, Tt as AgentTool, U as DevframeDiagnosticsLogger, Z as RpcStreamingHost, b as DevframeNodeContext, bt as AgentHandle, ht as SharedState, kt as EventEmitter, wt as AgentResourceInput, x as DevframeServiceId, xt as AgentManifest } from "../devframe-elfUcUJG.mjs"; | ||
| import { C as DevframeServicesHost, Ct as AgentResourceContent, Dt as DevframeAgentHost$1, Et as AgentToolInput, H as DevframeDiagnosticsHost$1, J as RpcSharedStateHost, K as RpcFunctionsHost, L as DevframeViewHost$1, O as DevframeSettings, Ot as DevframeAgentHostEvents, R as DevframeHost, S as DevframeServiceOf, St as AgentResource, T as DevframeScopedNodeContext, Tt as AgentTool, U as DevframeDiagnosticsLogger, Z as RpcStreamingHost, b as DevframeNodeContext, bt as AgentHandle, ht as SharedState, kt as EventEmitter, wt as AgentResourceInput, x as DevframeServiceId, xt as AgentManifest } from "../devframe-C18zEiex.mjs"; | ||
| import { v as RpcFunctionDefinitionAny } from "../types-CrzNxXKq.mjs"; | ||
| import "../index-DgsLFhZg.mjs"; | ||
| import { n as StartedServer, r as startHttpAndWs, t as StartHttpAndWsOptions } from "../server-kmetRyhu.mjs"; | ||
| import { n as StartedServer, r as startHttpAndWs, t as StartHttpAndWsOptions } from "../server-BLaM21gv.mjs"; | ||
| import { BirpcGroup } from "birpc"; | ||
@@ -6,0 +6,0 @@ //#region src/node/context.d.ts |
@@ -1,3 +0,3 @@ | ||
| import { b as DevframeNodeContext, p as DevframeAuthHandler } from "../devframe-elfUcUJG.mjs"; | ||
| import "../index-D8RNkmY_.mjs"; | ||
| import { b as DevframeNodeContext, p as DevframeAuthHandler } from "../devframe-C18zEiex.mjs"; | ||
| import "../index-BSmqhVju.mjs"; | ||
| //#region src/recipes/interactive-auth.d.ts | ||
@@ -4,0 +4,0 @@ interface CreateInteractiveAuthOptions { |
@@ -121,2 +121,5 @@ import { n as colors } from "../diagnostics-reporter-CsIG85Q5.mjs"; | ||
| } | ||
| function buildOpenUrl(url) { | ||
| return buildOtpAuthUrl(url); | ||
| } | ||
| return { | ||
@@ -130,3 +133,4 @@ rpcFunctions: [ | ||
| onConnect, | ||
| printBanner | ||
| printBanner, | ||
| buildOpenUrl | ||
| }; | ||
@@ -133,0 +137,0 @@ } |
@@ -1,2 +0,2 @@ | ||
| import "../devframe-elfUcUJG.mjs"; | ||
| import "../devframe-C18zEiex.mjs"; | ||
| import { E as Thenable, S as RpcFunctionSetupResult, c as RpcDump, g as RpcFunctionAgentOptions } from "../types-CrzNxXKq.mjs"; | ||
@@ -3,0 +3,0 @@ import "../index-DgsLFhZg.mjs"; |
@@ -1,4 +0,4 @@ | ||
| import { $ as DevframeRpcServerFunctions, A as DevframeSettingsStore, At as EventUnsubscribe, B as DevframeDefineDiagnosticsOptions, C as DevframeServicesHost, Ct as AgentResourceContent, D as DevframeScopedStreamingHost, Dt as DevframeAgentHost, E as DevframeScopedNodeRpc, Et as AgentToolInput, F as ScopedSharedStates, G as RpcBroadcastOptions, H as DevframeDiagnosticsHost, I as SettingsForNamespace, J as RpcSharedStateHost, K as RpcFunctionsHost, L as DevframeViewHost, M as ScopedClientFunctions, N as ScopedRpcFn, O as DevframeSettings, Ot as DevframeAgentHostEvents, P as ScopedServerFunctions, Q as DevframeRpcClientFunctions, R as DevframeHost, S as DevframeServiceOf, St as AgentResource, T as DevframeScopedNodeContext, Tt as AgentTool, U as DevframeDiagnosticsLogger, V as DevframeDiagnosticsDefinition, W as DevframeNodeRpcSession, X as RpcStreamingChannelOptions, Y as RpcStreamingChannel, Z as RpcStreamingHost, _ as ConnectionMeta, a as DevframeDockDefaults, b as DevframeNodeContext, bt as AgentHandle, c as DevframeSetupInfo, d as McpRouteOptions, et as DevframeRpcSharedStates, f as defineDevframe, g as Thenable, h as PartialWithoutId, i as DevframeDeploymentKind, j as ScopedBroadcastOptions, jt as EventsMap, k as DevframeSettingsRegistry, kt as EventEmitter, l as DevframeSpaOptions, m as EntriesToObject, n as DevframeCliOptions, o as DevframeDuplicationStrategy, q as RpcSharedStateGetOptions, r as DevframeDefinition, s as DevframeRuntime, t as DevframeBrowserContext, u as DevframeWsOptions, v as ConnectionMetaWebsocket, w as DevframeServicesRegistry, wt as AgentResourceInput, x as DevframeServiceId, xt as AgentManifest, y as DevframeCapabilities, z as DevframeStorageScope } from "../devframe-elfUcUJG.mjs"; | ||
| import { $ as DevframeRpcServerFunctions, A as DevframeSettingsStore, At as EventUnsubscribe, B as DevframeDefineDiagnosticsOptions, C as DevframeServicesHost, Ct as AgentResourceContent, D as DevframeScopedStreamingHost, Dt as DevframeAgentHost, E as DevframeScopedNodeRpc, Et as AgentToolInput, F as ScopedSharedStates, G as RpcBroadcastOptions, H as DevframeDiagnosticsHost, I as SettingsForNamespace, J as RpcSharedStateHost, K as RpcFunctionsHost, L as DevframeViewHost, M as ScopedClientFunctions, N as ScopedRpcFn, O as DevframeSettings, Ot as DevframeAgentHostEvents, P as ScopedServerFunctions, Q as DevframeRpcClientFunctions, R as DevframeHost, S as DevframeServiceOf, St as AgentResource, T as DevframeScopedNodeContext, Tt as AgentTool, U as DevframeDiagnosticsLogger, V as DevframeDiagnosticsDefinition, W as DevframeNodeRpcSession, X as RpcStreamingChannelOptions, Y as RpcStreamingChannel, Z as RpcStreamingHost, _ as ConnectionMeta, a as DevframeDockDefaults, b as DevframeNodeContext, bt as AgentHandle, c as DevframeSetupInfo, d as McpRouteOptions, et as DevframeRpcSharedStates, f as defineDevframe, g as Thenable, h as PartialWithoutId, i as DevframeDeploymentKind, j as ScopedBroadcastOptions, jt as EventsMap, k as DevframeSettingsRegistry, kt as EventEmitter, l as DevframeSpaOptions, m as EntriesToObject, n as DevframeCliOptions, o as DevframeDuplicationStrategy, q as RpcSharedStateGetOptions, r as DevframeDefinition, s as DevframeRuntime, t as DevframeBrowserContext, u as DevframeWsOptions, v as ConnectionMetaWebsocket, w as DevframeServicesRegistry, wt as AgentResourceInput, x as DevframeServiceId, xt as AgentManifest, y as DevframeCapabilities, z as DevframeStorageScope } from "../devframe-C18zEiex.mjs"; | ||
| import { g as RpcFunctionAgentOptions } from "../types-CrzNxXKq.mjs"; | ||
| import { t as DevframeNodeRpcSessionMeta } from "../ws-server-9-wn7MNQ.mjs"; | ||
| export { AgentHandle, AgentManifest, AgentResource, AgentResourceContent, AgentResourceInput, AgentTool, AgentToolInput, ConnectionMeta, ConnectionMetaWebsocket, DevframeAgentHost, DevframeAgentHostEvents, DevframeBrowserContext, DevframeCapabilities, DevframeCliOptions, DevframeDefineDiagnosticsOptions, DevframeDefinition, DevframeDeploymentKind, DevframeDiagnosticsDefinition, DevframeDiagnosticsHost, DevframeDiagnosticsLogger, DevframeDockDefaults, DevframeDuplicationStrategy, DevframeHost, DevframeNodeContext, DevframeNodeRpcSession, type DevframeNodeRpcSessionMeta, DevframeRpcClientFunctions, DevframeRpcServerFunctions, DevframeRpcSharedStates, DevframeRuntime, DevframeScopedNodeContext, DevframeScopedNodeRpc, DevframeScopedStreamingHost, DevframeServiceId, DevframeServiceOf, DevframeServicesHost, DevframeServicesRegistry, DevframeSettings, DevframeSettingsRegistry, DevframeSettingsStore, DevframeSetupInfo, DevframeSpaOptions, DevframeStorageScope, DevframeViewHost, DevframeWsOptions, EntriesToObject, EventEmitter, EventUnsubscribe, EventsMap, McpRouteOptions, PartialWithoutId, RpcBroadcastOptions, type RpcFunctionAgentOptions, RpcFunctionsHost, RpcSharedStateGetOptions, RpcSharedStateHost, RpcStreamingChannel, RpcStreamingChannelOptions, RpcStreamingHost, ScopedBroadcastOptions, ScopedClientFunctions, ScopedRpcFn, ScopedServerFunctions, ScopedSharedStates, SettingsForNamespace, Thenable, defineDevframe }; |
@@ -1,2 +0,2 @@ | ||
| import { jt as EventsMap, kt as EventEmitter } from "../devframe-elfUcUJG.mjs"; | ||
| import { jt as EventsMap, kt as EventEmitter } from "../devframe-C18zEiex.mjs"; | ||
| //#region src/utils/events.d.ts | ||
@@ -3,0 +3,0 @@ /** |
@@ -1,2 +0,2 @@ | ||
| import { _t as SharedStateOptions, dt as ImmutableArray, ft as ImmutableMap, gt as SharedStateEvents, ht as SharedState, mt as ImmutableSet, pt as ImmutableObject, ut as Immutable, vt as SharedStatePatch, yt as createSharedState } from "../devframe-elfUcUJG.mjs"; | ||
| import { _t as SharedStateOptions, dt as ImmutableArray, ft as ImmutableMap, gt as SharedStateEvents, ht as SharedState, mt as ImmutableSet, pt as ImmutableObject, ut as Immutable, vt as SharedStatePatch, yt as createSharedState } from "../devframe-C18zEiex.mjs"; | ||
| export { Immutable, ImmutableArray, ImmutableMap, ImmutableObject, ImmutableSet, SharedState, SharedStateEvents, SharedStateOptions, SharedStatePatch, createSharedState }; |
@@ -1,2 +0,2 @@ | ||
| import { at as StreamReader, ct as createStreamReader, it as StreamErrorPayload, lt as createStreamSink, nt as CreateStreamReaderOptions, ot as StreamSink, rt as CreateStreamSinkOptions, st as StreamSinkEvents, tt as BufferedChunk } from "../devframe-elfUcUJG.mjs"; | ||
| import { at as StreamReader, ct as createStreamReader, it as StreamErrorPayload, lt as createStreamSink, nt as CreateStreamReaderOptions, ot as StreamSink, rt as CreateStreamSinkOptions, st as StreamSinkEvents, tt as BufferedChunk } from "../devframe-C18zEiex.mjs"; | ||
| export { BufferedChunk, CreateStreamReaderOptions, CreateStreamSinkOptions, StreamErrorPayload, StreamReader, StreamSink, StreamSinkEvents, createStreamReader, createStreamSink }; |
+1
-1
| { | ||
| "name": "devframe", | ||
| "type": "module", | ||
| "version": "0.7.7", | ||
| "version": "0.7.8", | ||
| "description": "Framework for building generic devframes", | ||
@@ -6,0 +6,0 @@ "author": "Anthony Fu <anthonyfu117@hotmail.com>", |
@@ -572,3 +572,3 @@ --- | ||
| - **Authentication** exchanges a 6-digit one-time code (shown in the developer's terminal) for a node-issued bearer token via `requestTrustWithCode(code)`. The code is single-use, expires in 5 min, compared in constant time, and rotates after repeated failures — show it only in the terminal, never over the network. | ||
| - **Magic-link (optional):** print `buildOtpAuthUrl(origin)` — `<origin>/?devframe_otp=<code>`. `connectDevframe` reads the code, exchanges it, and strips it from the URL. Integrations can opt out (`otpParam: false`) and drive it via the exposed `authenticateWithUrlOtp(rpc)` / `consumeOtpFromUrl()` client utilities. Only the single-use code rides the URL, never the bearer; treat the printed link like the code itself. | ||
| - **Magic-link (optional):** print `buildOtpAuthUrl(origin)` — `<origin>/?devframe_otp=<code>`. `connectDevframe` reads the code, exchanges it, and strips it from the URL. Integrations can opt out (`otpParam: false`) and drive it via the exposed `authenticateWithUrlOtp(rpc)` / `consumeOtpFromUrl()` client utilities. Only the single-use code rides the URL, never the bearer; treat the printed link like the code itself. The standalone CLI's `--open` does this automatically via `DevframeAuthHandler.buildOpenUrl` — the launched tab already carries the OTP, no prompt needed. | ||
| - **Tokens are secrets.** The bearer token rides the WS URL (`?devframe_auth_token=…`) — serve over `wss://`/`https://` beyond loopback. Never log the token or code, never bake them into build output. Revoke via `revokeAuthToken(...)`; clients drop to untrusted on `devframe:auth:revoked`. | ||
@@ -575,0 +575,0 @@ - **Authorize handlers.** Any trusted client can call any registered function — validate inputs, and mark state-changing functions `type: 'destructive'` so MCP/agent clients prompt first. |
| import { n as colors } from "./diagnostics-reporter-CsIG85Q5.mjs"; | ||
| import { createBuild } from "./adapters/build.mjs"; | ||
| import { n as resolveDevServerPort, t as createDevServer } from "./dev-J2PqMPTn.mjs"; | ||
| import process from "node:process"; | ||
| import cac from "cac"; | ||
| import { safeParse } from "valibot"; | ||
| //#region src/adapters/flags.ts | ||
| /** | ||
| * Identity helper that preserves the literal schema-map type — use this | ||
| * so `InferCliFlags<typeof myFlags>` resolves to the right object shape. | ||
| * | ||
| * ```ts | ||
| * const appFlags = defineCliFlags({ | ||
| * depth: v.pipe(v.number(), v.integer()), | ||
| * config: v.optional(v.string()), | ||
| * }) | ||
| * | ||
| * defineDevframe({ | ||
| * cli: { flags: appFlags }, | ||
| * setup(ctx, info) { | ||
| * const flags = info.flags as InferCliFlags<typeof appFlags> | ||
| * flags.depth // number | ||
| * flags.config // string | undefined | ||
| * }, | ||
| * }) | ||
| * ``` | ||
| */ | ||
| function defineCliFlags(flags) { | ||
| return flags; | ||
| } | ||
| /** | ||
| * Best-effort probe of a valibot schema to decide whether the | ||
| * corresponding CAC option takes a value. Unwraps `optional` / `nullable` | ||
| * / `nullish` / `default` / `pipe` wrappers then matches on the inner | ||
| * type's kind. | ||
| */ | ||
| function getSchemaKind(schema) { | ||
| let current = schema; | ||
| while (current) { | ||
| const kind = current.type; | ||
| if (kind === "optional" || kind === "nullable" || kind === "nullish" || kind === "undefined") { | ||
| current = current.wrapped ?? current.inner; | ||
| continue; | ||
| } | ||
| if (kind === "pipe" && Array.isArray(current.pipe) && current.pipe.length > 0) { | ||
| current = current.pipe[0]; | ||
| continue; | ||
| } | ||
| return kind; | ||
| } | ||
| return "unknown"; | ||
| } | ||
| /** Whether the CAC option for this schema should be a boolean flag. */ | ||
| function isBooleanFlag(schema) { | ||
| return getSchemaKind(schema) === "boolean"; | ||
| } | ||
| /** Validate and coerce the raw cac-parsed bag against a {@link CliFlagsSchema}. */ | ||
| function parseCliFlags(schema, raw) { | ||
| const flags = {}; | ||
| const issues = []; | ||
| for (const [key, fieldSchema] of Object.entries(schema)) { | ||
| const result = safeParse(fieldSchema, raw[key]); | ||
| if (result.success) flags[key] = result.output; | ||
| else issues.push(`--${toKebab(key)}: ${result.issues.map((i) => i.message).join(", ")}`); | ||
| } | ||
| for (const [key, value] of Object.entries(raw)) if (!(key in schema) && !(key in flags)) flags[key] = value; | ||
| return issues.length ? { | ||
| flags, | ||
| issues | ||
| } : { flags }; | ||
| } | ||
| function toKebab(camel) { | ||
| return camel.replaceAll(/([a-z])([A-Z])/g, "$1-$2").toLowerCase(); | ||
| } | ||
| /** Kebab-case a schema key for CAC option registration. */ | ||
| function flagKeyToOption(camel) { | ||
| return toKebab(camel); | ||
| } | ||
| //#endregion | ||
| //#region src/adapters/cac.ts | ||
| /** | ||
| * Wrap a {@link DevframeDefinition} in a `cac`-powered command-line | ||
| * interface exposing `dev` / `build` / `mcp` subcommands. | ||
| * | ||
| * Requires the optional `cac` peer dependency. | ||
| */ | ||
| function createCac(d, options = {}) { | ||
| const defaultPort = options.defaultPort ?? d.cli?.port ?? 9999; | ||
| const defaultHost = d.cli?.host ?? "localhost"; | ||
| const cli = cac(d.cli?.command ?? d.id); | ||
| const devCommand = cli.command("[...args]", "Start a local dev server").option("--port <port>", "Port to listen on").option("--host <host>", "Host to bind to", { default: defaultHost }).option("--open", "Open the browser on start").option("--no-open", "Do not open the browser").option("--no-auth", "Disable the interactive authentication gate").option("--mcp", "Expose an MCP server over HTTP at /__mcp (use --no-mcp to disable) [experimental]"); | ||
| if (d.cli?.flags) for (const [key, schema] of Object.entries(d.cli.flags)) { | ||
| const optionName = flagKeyToOption(key); | ||
| const description = schema.description ?? ""; | ||
| if (isBooleanFlag(schema)) devCommand.option(`--${optionName}`, description); | ||
| else devCommand.option(`--${optionName} <value>`, description); | ||
| } | ||
| devCommand.action(async (_args, rawFlags) => { | ||
| const flags = resolveTypedFlags(d, rawFlags); | ||
| const host = flags.host ?? defaultHost; | ||
| const port = flags.port ?? await resolveDevServerPort(d, { | ||
| host, | ||
| defaultPort | ||
| }); | ||
| const mcp = flags.mcp; | ||
| await createDevServer(d, { | ||
| host, | ||
| port, | ||
| flags, | ||
| mcp, | ||
| onReady: options.onReady | ||
| }); | ||
| }); | ||
| cli.command("build", "Build a self-contained static deploy of the devframe").option("--out-dir <outDir>", "Output directory", { default: "dist-static" }).option("--base <base>", "URL base", { default: "/" }).option("--pretty", "Pretty-print dump JSON (larger on disk)").action(async (flags) => { | ||
| await createBuild(d, { | ||
| outDir: flags.outDir, | ||
| base: flags.base, | ||
| pretty: flags.pretty | ||
| }); | ||
| }); | ||
| cli.command("mcp", "Start an MCP server exposing agent-facing tools (stdio) [experimental]").action(async () => { | ||
| const { createMcpServer } = await import("./adapters/mcp.mjs"); | ||
| await createMcpServer(d, { | ||
| transport: "stdio", | ||
| onReady: ({ transport }) => { | ||
| console.error(`[devframe] "${d.id}" MCP server ready (${transport})`); | ||
| } | ||
| }); | ||
| }); | ||
| d.cli?.configure?.(cli); | ||
| options.configureCli?.(cli); | ||
| cli.help(); | ||
| cli.version("0.0.0"); | ||
| return { | ||
| cli, | ||
| async parse(argv = process.argv) { | ||
| cli.parse(argv, { run: false }); | ||
| await cli.runMatchedCommand(); | ||
| } | ||
| }; | ||
| } | ||
| function resolveTypedFlags(d, raw) { | ||
| if (!d.cli?.flags) return raw; | ||
| const { flags, issues } = parseCliFlags(d.cli.flags, raw); | ||
| if (issues?.length) { | ||
| for (const issue of issues) console.error(colors.red`[devframe] invalid flag — ${issue}`); | ||
| process.exit(1); | ||
| } | ||
| return flags; | ||
| } | ||
| //#endregion | ||
| export { defineCliFlags as n, parseCliFlags as r, createCac as t }; |
| import { b as DevframeNodeContext, ht as SharedState } from "./devframe-elfUcUJG.mjs"; | ||
| //#region src/node/hub-internals/context.d.ts | ||
| interface InternalAnonymousAuthStorage { | ||
| trusted: Record<string, { | ||
| authToken: string; | ||
| ua: string; | ||
| origin: string; | ||
| timestamp: number; | ||
| } | undefined>; | ||
| } | ||
| interface RemoteTokenRecord { | ||
| dockId: string; | ||
| /** Dock URL origin — matched against WS handshake `Origin` header when `originLock` is on. */ | ||
| origin: string; | ||
| originLock: boolean; | ||
| } | ||
| interface DevframeInternalContext { | ||
| storage: { | ||
| auth: SharedState<InternalAnonymousAuthStorage>; | ||
| }; | ||
| /** | ||
| * Revoke an auth token: remove from storage and notify all connected clients | ||
| * using this token that they are no longer trusted. | ||
| */ | ||
| revokeAuthToken: (token: string) => Promise<void>; | ||
| /** | ||
| * Session-only tokens issued to remote-UI iframe docks. Not persisted — | ||
| * regenerated on every dev-server restart. | ||
| */ | ||
| remoteTokens: Map<string, RemoteTokenRecord>; | ||
| allocateRemoteToken: (dockId: string, origin: string, originLock: boolean) => string; | ||
| revokeRemoteToken: (token: string) => void; | ||
| revokeRemoteTokensForDock: (dockId: string) => void; | ||
| /** | ||
| * Returns true if `token` is a valid remote token and, when `originLock` is | ||
| * on, `requestOrigin` matches the recorded dock origin. | ||
| */ | ||
| isRemoteTokenTrusted: (token: string, requestOrigin?: string) => boolean; | ||
| /** | ||
| * Populated by `createWsServer` once the WS port is bound. Consumed by the | ||
| * docks host when enriching remote iframe URLs with a connection descriptor. | ||
| */ | ||
| wsEndpoint?: { | ||
| /** Full `ws://` or `wss://` URL with host and port. */ | ||
| url: string; | ||
| }; | ||
| } | ||
| declare const internalContextMap: WeakMap<DevframeNodeContext, DevframeInternalContext>; | ||
| declare function getInternalContext(context: DevframeNodeContext): DevframeInternalContext; | ||
| //#endregion | ||
| export { internalContextMap as a, getInternalContext as i, InternalAnonymousAuthStorage as n, RemoteTokenRecord as r, DevframeInternalContext as t }; |
| import { DEVFRAME_CONNECTION_META_FILENAME } from "./constants.mjs"; | ||
| import { a as diagnostics } from "./storage-D_Xy9v1l.mjs"; | ||
| import { n as createHostContext, t as createH3DevframeHost } from "./host-h3-SjwRTwkE.mjs"; | ||
| import { i as normalizeHttpServerUrl, t as startHttpAndWs } from "./server-BFsuZI9e.mjs"; | ||
| import { n as resolveBasePath, t as normalizeBasePath } from "./_shared-bWRzeSa0.mjs"; | ||
| import { t as open } from "./open-Deb5xmIT.mjs"; | ||
| import { mountStaticHandler } from "./utils/serve-static.mjs"; | ||
| import { createInteractiveAuth } from "./recipes/interactive-auth.mjs"; | ||
| import { resolve } from "pathe"; | ||
| import process$1 from "node:process"; | ||
| import { networkInterfaces } from "node:os"; | ||
| import { H3 } from "h3"; | ||
| import { createServer } from "node:net"; | ||
| import { joinURL, withBase, withLeadingSlash, withoutLeadingSlash } from "ufo"; | ||
| //#region ../../node_modules/.pnpm/get-port-please@3.2.0/node_modules/get-port-please/dist/index.mjs | ||
| const unsafePorts = /* @__PURE__ */ new Set([ | ||
| 1, | ||
| 7, | ||
| 9, | ||
| 11, | ||
| 13, | ||
| 15, | ||
| 17, | ||
| 19, | ||
| 20, | ||
| 21, | ||
| 22, | ||
| 23, | ||
| 25, | ||
| 37, | ||
| 42, | ||
| 43, | ||
| 53, | ||
| 69, | ||
| 77, | ||
| 79, | ||
| 87, | ||
| 95, | ||
| 101, | ||
| 102, | ||
| 103, | ||
| 104, | ||
| 109, | ||
| 110, | ||
| 111, | ||
| 113, | ||
| 115, | ||
| 117, | ||
| 119, | ||
| 123, | ||
| 135, | ||
| 137, | ||
| 139, | ||
| 143, | ||
| 161, | ||
| 179, | ||
| 389, | ||
| 427, | ||
| 465, | ||
| 512, | ||
| 513, | ||
| 514, | ||
| 515, | ||
| 526, | ||
| 530, | ||
| 531, | ||
| 532, | ||
| 540, | ||
| 548, | ||
| 554, | ||
| 556, | ||
| 563, | ||
| 587, | ||
| 601, | ||
| 636, | ||
| 989, | ||
| 990, | ||
| 993, | ||
| 995, | ||
| 1719, | ||
| 1720, | ||
| 1723, | ||
| 2049, | ||
| 3659, | ||
| 4045, | ||
| 5060, | ||
| 5061, | ||
| 6e3, | ||
| 6566, | ||
| 6665, | ||
| 6666, | ||
| 6667, | ||
| 6668, | ||
| 6669, | ||
| 6697, | ||
| 10080 | ||
| ]); | ||
| function isUnsafePort(port) { | ||
| return unsafePorts.has(port); | ||
| } | ||
| function isSafePort(port) { | ||
| return !isUnsafePort(port); | ||
| } | ||
| var GetPortError = class extends Error { | ||
| constructor(message, opts) { | ||
| super(message, opts); | ||
| this.message = message; | ||
| } | ||
| name = "GetPortError"; | ||
| }; | ||
| function _log(verbose, message) { | ||
| if (verbose) console.log(`[get-port] ${message}`); | ||
| } | ||
| function _generateRange(from, to) { | ||
| if (to < from) return []; | ||
| const r = []; | ||
| for (let index = from; index <= to; index++) r.push(index); | ||
| return r; | ||
| } | ||
| function _tryPort(port, host) { | ||
| return new Promise((resolve) => { | ||
| const server = createServer(); | ||
| server.unref(); | ||
| server.on("error", () => { | ||
| resolve(false); | ||
| }); | ||
| server.listen({ | ||
| port, | ||
| host | ||
| }, () => { | ||
| const { port: port2 } = server.address(); | ||
| server.close(() => { | ||
| resolve(isSafePort(port2) && port2); | ||
| }); | ||
| }); | ||
| }); | ||
| } | ||
| function _getLocalHosts(additional) { | ||
| const hosts = new Set(additional); | ||
| for (const _interface of Object.values(networkInterfaces())) for (const config of _interface || []) if (config.address && !config.internal && !config.address.startsWith("fe80::") && !config.address.startsWith("169.254")) hosts.add(config.address); | ||
| return [...hosts]; | ||
| } | ||
| async function _findPort(ports, host) { | ||
| for (const port of ports) { | ||
| const r = await _tryPort(port, host); | ||
| if (r) return r; | ||
| } | ||
| } | ||
| function _fmtOnHost(hostname) { | ||
| return hostname ? `on host ${JSON.stringify(hostname)}` : "on any host"; | ||
| } | ||
| const HOSTNAME_RE = /^(?!-)[\d.:A-Za-z-]{1,63}(?<!-)$/; | ||
| function _validateHostname(hostname, _public, verbose) { | ||
| if (hostname && !HOSTNAME_RE.test(hostname)) { | ||
| const fallbackHost = _public ? "0.0.0.0" : "127.0.0.1"; | ||
| _log(verbose, `Invalid hostname: ${JSON.stringify(hostname)}. Using ${JSON.stringify(fallbackHost)} as fallback.`); | ||
| return fallbackHost; | ||
| } | ||
| return hostname; | ||
| } | ||
| async function getPort(_userOptions = {}) { | ||
| if (typeof _userOptions === "number" || typeof _userOptions === "string") _userOptions = { port: Number.parseInt(_userOptions + "") || 0 }; | ||
| const _port = Number(_userOptions.port ?? process.env.PORT); | ||
| const _userSpecifiedAnyPort = Boolean(_userOptions.port || _userOptions.ports?.length || _userOptions.portRange?.length); | ||
| const options = { | ||
| random: _port === 0, | ||
| ports: [], | ||
| portRange: [], | ||
| alternativePortRange: _userSpecifiedAnyPort ? [] : [3e3, 3100], | ||
| verbose: false, | ||
| ..._userOptions, | ||
| port: _port, | ||
| host: _validateHostname(_userOptions.host ?? process.env.HOST, _userOptions.public, _userOptions.verbose) | ||
| }; | ||
| if (options.random && !_userSpecifiedAnyPort) return getRandomPort(options.host); | ||
| const portsToCheck = [ | ||
| options.port, | ||
| ...options.ports, | ||
| ..._generateRange(...options.portRange) | ||
| ].filter((port) => { | ||
| if (!port) return false; | ||
| if (!isSafePort(port)) { | ||
| _log(options.verbose, `Ignoring unsafe port: ${port}`); | ||
| return false; | ||
| } | ||
| return true; | ||
| }); | ||
| if (portsToCheck.length === 0) portsToCheck.push(3e3); | ||
| let availablePort = await _findPort(portsToCheck, options.host); | ||
| if (!availablePort && options.alternativePortRange.length > 0) { | ||
| availablePort = await _findPort(_generateRange(...options.alternativePortRange), options.host); | ||
| if (portsToCheck.length > 0) { | ||
| let message = `Unable to find an available port (tried ${portsToCheck.join("-")} ${_fmtOnHost(options.host)}).`; | ||
| if (availablePort) message += ` Using alternative port ${availablePort}.`; | ||
| _log(options.verbose, message); | ||
| } | ||
| } | ||
| if (!availablePort && _userOptions.random !== false) { | ||
| availablePort = await getRandomPort(options.host); | ||
| if (availablePort) _log(options.verbose, `Using random port ${availablePort}`); | ||
| } | ||
| if (!availablePort) { | ||
| const triedRanges = [ | ||
| options.port, | ||
| options.portRange.join("-"), | ||
| options.alternativePortRange.join("-") | ||
| ].filter(Boolean).join(", "); | ||
| throw new GetPortError(`Unable to find an available port ${_fmtOnHost(options.host)} (tried ${triedRanges})`); | ||
| } | ||
| return availablePort; | ||
| } | ||
| async function getRandomPort(host) { | ||
| const port = await checkPort(0, host); | ||
| if (port === false) throw new GetPortError(`Unable to find a random port ${_fmtOnHost(host)}`); | ||
| return port; | ||
| } | ||
| async function checkPort(port, host = process.env.HOST, verbose) { | ||
| if (!host) host = _getLocalHosts([void 0, "0.0.0.0"]); | ||
| if (!Array.isArray(host)) return _tryPort(port, host); | ||
| for (const _host of host) { | ||
| const _port = await _tryPort(port, _host); | ||
| if (_port === false) { | ||
| if (port < 1024 && verbose) _log(verbose, `Unable to listen to the privileged port ${port} ${_fmtOnHost(_host)}`); | ||
| return false; | ||
| } | ||
| if (port === 0 && _port !== 0) port = _port; | ||
| } | ||
| return port; | ||
| } | ||
| //#endregion | ||
| //#region src/adapters/dev.ts | ||
| const DEFAULT_PORT = 9999; | ||
| /** | ||
| * Resolve the listening port for {@link createDevServer}, honoring the | ||
| * definition's `cli.port` / `cli.portRange` / `cli.random` settings. | ||
| * Exposed separately so authors who run their own argv parsing can | ||
| * resolve a port up-front (to print it, log it, etc.) before starting | ||
| * the server. | ||
| */ | ||
| async function resolveDevServerPort(def, options = {}) { | ||
| const host = options.host ?? def.cli?.host ?? "localhost"; | ||
| const portOptions = { | ||
| port: options.defaultPort ?? def.cli?.port ?? DEFAULT_PORT, | ||
| host | ||
| }; | ||
| if (def.cli?.portRange) portOptions.portRange = def.cli.portRange; | ||
| if (def.cli?.random) portOptions.random = def.cli.random; | ||
| return getPort(portOptions); | ||
| } | ||
| /** | ||
| * Start a devframe dev server for a {@link DevframeDefinition} — | ||
| * h3 + WebSocket RPC + (optionally) the author's SPA mounted at the | ||
| * resolved base path. | ||
| * | ||
| * When `distDir` is omitted (and `def.cli?.distDir` is unset) the | ||
| * server runs in **bridge mode**: only `__connection.json` and the WS | ||
| * endpoint are mounted, with no SPA mount. The SPA is expected to be | ||
| * hosted elsewhere (e.g. by a parent Vite/Nuxt dev server) — see | ||
| * `viteDevBridge({ devMiddleware })`. | ||
| * | ||
| * Returns the underlying {@link StartedServer} handle so callers can | ||
| * close it gracefully (SIGINT, hot-reload, test teardown). | ||
| * | ||
| * Use this directly when integrating devframe into an existing CLI | ||
| * framework (commander, yargs, hand-rolled CAC). For the all-in-one | ||
| * `dev` / `build` / `mcp` shell, reach for {@link createCac} instead. | ||
| */ | ||
| async function createDevServer(def, options = {}) { | ||
| const distDir = options.distDir ?? def.cli?.distDir; | ||
| const host = options.host ?? def.cli?.host ?? "localhost"; | ||
| const port = options.port ?? await resolveDevServerPort(def, { host }); | ||
| const flags = options.flags ?? {}; | ||
| const basePath = options.basePath ? normalizeBasePath(options.basePath) : resolveBasePath(def, "standalone"); | ||
| const app = options.app ?? new H3(); | ||
| const h3Host = createH3DevframeHost({ | ||
| origin: normalizeHttpServerUrl(host, port), | ||
| appName: def.id, | ||
| mount: (base, dir) => { | ||
| mountStaticHandler(app, base, dir); | ||
| } | ||
| }); | ||
| const ctx = await createHostContext({ | ||
| cwd: process$1.cwd(), | ||
| mode: "dev", | ||
| host: h3Host | ||
| }); | ||
| const setupInfo = { flags }; | ||
| await def.setup(ctx, setupInfo); | ||
| const mcpConfig = resolveMcpConfig(options.mcp ?? def.cli?.mcp); | ||
| let mcpDispose; | ||
| let mcpMeta; | ||
| if (mcpConfig) { | ||
| const mcpRoute = withoutLeadingSlash(mcpConfig.path ?? "__mcp"); | ||
| const mcpPath = joinURL(basePath, mcpRoute); | ||
| let mountMcpHttp; | ||
| try { | ||
| ({mountMcpHttp} = await import("./http-BaCZYhIR.mjs")); | ||
| } catch (error) { | ||
| const reason = error instanceof Error ? error.message : String(error); | ||
| throw diagnostics.DF0017({ | ||
| transport: "http", | ||
| reason, | ||
| cause: error | ||
| }); | ||
| } | ||
| mcpDispose = mountMcpHttp(app, ctx, mcpPath, { | ||
| serverName: `${def.id} (devframe)`, | ||
| serverVersion: def.version ?? "0.0.0", | ||
| exposeSharedState: true, | ||
| allowedOrigins: mcpConfig.allowedOrigins | ||
| }).dispose; | ||
| mcpMeta = { path: mcpRoute }; | ||
| } | ||
| const { bindPath, wsPort, meta } = resolveWsConnection(def, options, basePath); | ||
| const connectionMetaPath = joinURL(basePath, DEVFRAME_CONNECTION_META_FILENAME); | ||
| app.use(connectionMetaPath, () => ({ | ||
| backend: "websocket", | ||
| websocket: meta, | ||
| ...mcpMeta ? { mcp: mcpMeta } : {} | ||
| })); | ||
| if (distDir) mountStaticHandler(app, basePath, resolve(distDir)); | ||
| const authOption = flags.auth === false ? false : def.cli?.auth; | ||
| let authHandler; | ||
| let resolvedAuth; | ||
| if (authOption === false) resolvedAuth = false; | ||
| else if (typeof authOption === "object") { | ||
| authHandler = authOption; | ||
| resolvedAuth = authOption; | ||
| } else { | ||
| authHandler = createInteractiveAuth(ctx); | ||
| resolvedAuth = authHandler; | ||
| } | ||
| const started = await startHttpAndWs({ | ||
| context: ctx, | ||
| host, | ||
| port, | ||
| app, | ||
| path: bindPath, | ||
| wsPort, | ||
| auth: resolvedAuth, | ||
| onReady: async (info) => { | ||
| authHandler?.printBanner(); | ||
| await options.onReady?.(info); | ||
| await maybeOpenBrowser(def, flags, `${info.origin}${basePath}`, options.openBrowser); | ||
| } | ||
| }); | ||
| if (mcpDispose) { | ||
| const closeServer = started.close; | ||
| started.close = async () => { | ||
| await mcpDispose(); | ||
| await closeServer(); | ||
| }; | ||
| } | ||
| return started; | ||
| } | ||
| /** | ||
| * Normalize the `cli.mcp` / `mcp` option (`boolean | McpRouteOptions`) into | ||
| * concrete options, or `undefined` when the MCP route is disabled. | ||
| */ | ||
| function resolveMcpConfig(mcp) { | ||
| if (!mcp) return void 0; | ||
| return mcp === true ? {} : mcp; | ||
| } | ||
| /** | ||
| * Resolve the three WS connection scenarios from the definition / call-site | ||
| * config into a concrete server bind path, optional dedicated port, and the | ||
| * `__connection.json` descriptor the browser resolves. | ||
| */ | ||
| function resolveWsConnection(def, options, basePath) { | ||
| const ws = options.ws ?? def.cli?.ws ?? {}; | ||
| const route = withoutLeadingSlash(ws.route ?? "__devframe_ws"); | ||
| if (ws.url) return { | ||
| bindPath: joinURL(basePath, route), | ||
| wsPort: void 0, | ||
| meta: ws.url | ||
| }; | ||
| if (ws.port != null) return { | ||
| bindPath: withLeadingSlash(route), | ||
| wsPort: ws.port, | ||
| meta: { | ||
| port: ws.port, | ||
| path: route | ||
| } | ||
| }; | ||
| return { | ||
| bindPath: joinURL(basePath, route), | ||
| wsPort: void 0, | ||
| meta: { path: route } | ||
| }; | ||
| } | ||
| async function maybeOpenBrowser(def, flags, origin, override) { | ||
| const flagsOpen = flags.open; | ||
| const cliOpen = def.cli?.open; | ||
| const resolved = override ?? flagsOpen ?? cliOpen; | ||
| if (resolved === void 0 || resolved === false) return; | ||
| const target = typeof resolved === "string" ? withBase(resolved, origin) : origin; | ||
| try { | ||
| await open(target); | ||
| } catch {} | ||
| } | ||
| //#endregion | ||
| export { resolveDevServerPort as n, createDevServer as t }; |
Sorry, the diff of this file is too big to display
| import { W as DevframeNodeRpcSession, b as DevframeNodeContext, ht as SharedState } from "./devframe-elfUcUJG.mjs"; | ||
| import { n as InternalAnonymousAuthStorage } from "./context-D0md0kxv.mjs"; | ||
| //#region src/node/auth/revoke.d.ts | ||
| /** | ||
| * Flip `isTrusted` to false on any live WS clients connected with `token` | ||
| * and broadcast the `auth:revoked` event so they can react. | ||
| * | ||
| * Shared between persisted-auth revocation and remote-dock token revocation. | ||
| */ | ||
| declare function revokeActiveConnectionsForToken(context: DevframeNodeContext, token: string): Promise<void>; | ||
| /** | ||
| * Revoke an auth token: remove from storage and notify all connected clients | ||
| * using this token that they are no longer trusted. | ||
| */ | ||
| declare function revokeAuthToken(context: DevframeNodeContext, storage: SharedState<InternalAnonymousAuthStorage>, token: string): Promise<void>; | ||
| //#endregion | ||
| //#region src/node/auth/state.d.ts | ||
| /** | ||
| * The current one-time authentication code. Display this to the user (e.g. in | ||
| * the dev-server terminal) so they can type it into the browser to authenticate. | ||
| */ | ||
| declare function getTempAuthCode(): string; | ||
| /** | ||
| * Rotate the authentication code, resetting its expiry window and failed-attempt | ||
| * counter. Call this when a new authentication flow begins (e.g. when an | ||
| * untrusted client starts authenticating) so the displayed code is freshly | ||
| * valid for its full TTL. | ||
| */ | ||
| declare function refreshTempAuthCode(): string; | ||
| /** | ||
| * Build a "magic link" authentication URL that embeds a one-time code (OTP) as | ||
| * a query parameter. Opening it authenticates the client without typing — print | ||
| * it on startup (devframe stays headless, so the host prints its own banner). | ||
| * Defaults to the current code; the link is subject to the same TTL. | ||
| */ | ||
| declare function buildOtpAuthUrl(baseUrl: string, code?: string): string; | ||
| /** | ||
| * Re-authenticate a connection that presents a previously-issued bearer token. | ||
| * Returns `true` and marks the session trusted when the token is known. | ||
| * | ||
| * Used by the `anonymous:devframe:auth` handler so a client that already | ||
| * authenticated (token persisted in the browser) is trusted on reconnect | ||
| * without entering the code again. | ||
| */ | ||
| declare function verifyAuthToken(token: string, session: DevframeNodeRpcSession, storage: SharedState<InternalAnonymousAuthStorage>): boolean; | ||
| /** | ||
| * Exchange a one-time authentication code for a fresh, node-issued bearer token. | ||
| * | ||
| * On success this mints a high-entropy token, records it in the trusted store, | ||
| * marks the calling session trusted, rotates the code, and returns the token | ||
| * for the client to persist. Returns `null` on any failure. | ||
| * | ||
| * Because the code is short and human-typed, verification is hardened against | ||
| * brute force: it enforces a time-to-live, compares in constant time, and | ||
| * rotates the code after {@link TEMP_AUTH_MAX_ATTEMPTS} failed attempts so an | ||
| * attacker cannot keep guessing against the same code. | ||
| */ | ||
| declare function exchangeTempAuthCode(code: string, session: DevframeNodeRpcSession, info: { | ||
| ua: string; | ||
| origin: string; | ||
| }, storage: SharedState<InternalAnonymousAuthStorage>): string | null; | ||
| //#endregion | ||
| export { verifyAuthToken as a, refreshTempAuthCode as i, exchangeTempAuthCode as n, revokeActiveConnectionsForToken as o, getTempAuthCode as r, revokeAuthToken as s, buildOtpAuthUrl as t }; |
| import { $ as DevframeRpcServerFunctions, Q as DevframeRpcClientFunctions, W as DevframeNodeRpcSession, _ as ConnectionMeta, b as DevframeNodeContext, p as DevframeAuthHandler } from "./devframe-elfUcUJG.mjs"; | ||
| import "./index-D8RNkmY_.mjs"; | ||
| import { Peer } from "crossws"; | ||
| import { BirpcGroup, EventOptions } from "birpc"; | ||
| import { NodeAdapter } from "crossws/adapters/node"; | ||
| import { Server } from "node:http"; | ||
| import { H3 } from "h3"; | ||
| //#region src/node/server.d.ts | ||
| interface StartHttpAndWsOptions { | ||
| context: DevframeNodeContext; | ||
| host?: string; | ||
| port: number; | ||
| /** | ||
| * Optional h3 app to mount on. When omitted a fresh one is created; | ||
| * when provided, callers can add their own routes (static handlers, | ||
| * auth middleware, etc.) first. | ||
| */ | ||
| app?: H3; | ||
| /** | ||
| * Bind the WS endpoint to a single upgrade route (e.g. `/__devframe_ws`) instead of | ||
| * claiming every upgrade on the port. This lets the socket share a server | ||
| * with other upgrade handlers (Vite HMR, a host framework's own sockets) | ||
| * and is what the SPA's `__connection.json` points at. When omitted, the WS | ||
| * server handles every upgrade on the port (legacy behaviour). | ||
| */ | ||
| path?: string; | ||
| /** | ||
| * Bind the WS endpoint on its own port instead of sharing the HTTP server's. | ||
| * The HTTP/SPA server still listens on `port`; the socket gets a dedicated | ||
| * `ws` server on `wsPort` (same `host`). Use this for the "different port" | ||
| * connection scenario. Ignored when a `server` is supplied. | ||
| */ | ||
| wsPort?: number; | ||
| /** | ||
| * Mount the WS endpoint onto an existing HTTP server, sharing its port, | ||
| * rather than creating and listening on a fresh one. Use this to embed | ||
| * devframe's RPC socket inside a host server (e.g. a Vite dev server) — pair | ||
| * it with `path` so it coexists with the host's routes. The caller owns the | ||
| * server's lifecycle: {@link StartedServer.close} detaches devframe's upgrade | ||
| * listener but leaves the host server running. When set, `host`/`port` are | ||
| * only used to report the resolved origin. | ||
| */ | ||
| server?: Server; | ||
| /** | ||
| * Authentication for the server: | ||
| * | ||
| * - `true` (default) — no gate; every registered method is callable | ||
| * regardless of trust (today's behavior, unchanged). | ||
| * - `false` — the RPC server is started without a trust handshake. | ||
| * Intended for single-user localhost tools where an auth round-trip | ||
| * would only get in the way. A noop `anonymous:devframe:auth` handler | ||
| * is registered so the browser client's unconditional handshake call | ||
| * succeeds and auto-trusts. | ||
| * - A {@link DevframeAuthHandler} (e.g. from | ||
| * `devframe/recipes/interactive-auth`'s `createInteractiveAuth`) — | ||
| * registers its `rpcFunctions`, wires its `authorize` as the resolver | ||
| * gate, and wires its `onConnect` on every new peer. This is the | ||
| * fully-authenticated server: an untrusted caller can only reach | ||
| * `anonymous:`-prefixed methods (see `isAnonymousRpcMethod`). | ||
| */ | ||
| auth?: boolean | DevframeAuthHandler; | ||
| /** | ||
| * Lower-level escape hatch: gate individual RPC calls by method name and | ||
| * session without a full {@link DevframeAuthHandler}. Ignored when `auth` | ||
| * is a handler object (its own `authorize` is used); combine with `auth: | ||
| * true` to layer a custom policy on top of an otherwise ungated server. | ||
| */ | ||
| authorize?: (methodName: string, session: DevframeNodeRpcSession) => boolean; | ||
| /** | ||
| * Called once per new WS connection, right after its session is created | ||
| * (before any RPC call is dispatched). Runs after the auth handler's own | ||
| * `onConnect` (when `auth` is a {@link DevframeAuthHandler}), so it can | ||
| * observe — but not override — the connect-time trust decision. | ||
| */ | ||
| onPeerConnect?: (peer: Peer, session: DevframeNodeRpcSession) => void; | ||
| /** | ||
| * Forwarded verbatim to the internal `createRpcServer`'s birpc | ||
| * `rpcOptions`, alongside the resolver `startHttpAndWs` installs for | ||
| * auth/session wiring. Use this so a host that owns its own structured | ||
| * diagnostics (e.g. a coded error reporter) keeps seeing RPC failures | ||
| * instead of them being silently absorbed by delegating to | ||
| * `startHttpAndWs`. Returning `true` from either callback suppresses | ||
| * birpc's own error response to the caller — see birpc's | ||
| * `EventOptions` for the full contract. | ||
| */ | ||
| rpcOptions?: Pick<EventOptions<DevframeRpcClientFunctions, DevframeRpcServerFunctions, false>, 'onFunctionError' | 'onGeneralError'>; | ||
| /** | ||
| * Extra origins to accept on the WS upgrade beyond the loopback default | ||
| * (`localhost`/`127.0.0.1`/`::1` and any `Origin`-less request from a | ||
| * native client). Add your LAN/tunnel origin here when reaching the tool | ||
| * from another host. Pass `false` to disable origin checking entirely | ||
| * (not recommended). Default: loopback-only. | ||
| */ | ||
| allowedOrigins?: readonly string[] | false; | ||
| /** | ||
| * Called once the WS server is bound so callers can mount static | ||
| * handlers whose origin depends on the resolved port, or print their | ||
| * own startup banner. Devframe does not print one itself. | ||
| */ | ||
| onReady?: (info: { | ||
| origin: string; | ||
| port: number; | ||
| app: H3; | ||
| }) => void | Promise<void>; | ||
| } | ||
| interface StartedServer { | ||
| /** Listening origin, e.g. `http://localhost:9999`. */ | ||
| origin: string; | ||
| port: number; | ||
| app: H3; | ||
| /** The crossws node adapter driving the RPC socket (connected peers, pub/sub). */ | ||
| ws: NodeAdapter; | ||
| rpcGroup: BirpcGroup<DevframeRpcClientFunctions, DevframeRpcServerFunctions, false>; | ||
| /** | ||
| * The {@link ConnectionMeta} descriptor for this server — the same shape | ||
| * a `__connection.json` route should serve so a devframe client's | ||
| * `resolveWsUrl` can dial back in. Reflects the `path` / `wsPort` this | ||
| * server was started with and the `jsonSerializable` methods currently | ||
| * registered on `context.rpc`. | ||
| */ | ||
| connectionMeta: () => ConnectionMeta; | ||
| close: () => Promise<void>; | ||
| } | ||
| /** | ||
| * Compose an h3 + WebSocket server for a devframe context. The RPC | ||
| * group is bound to `context.rpc.functions`; the WS endpoint lives on | ||
| * the same port as the HTTP server. | ||
| */ | ||
| declare function startHttpAndWs(options: StartHttpAndWsOptions): Promise<StartedServer>; | ||
| //#endregion | ||
| export { StartedServer as n, startHttpAndWs as r, StartHttpAndWsOptions as t }; |
Sorry, the diff of this file is too big to display
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
615938
0.2%12252
0.14%