everything-dev
Version:
A consolidated product package for building Module Federation apps with oRPC APIs.
1 lines • 35.7 kB
Source Map (JSON)
{"version":3,"file":"runtime-map.mjs","names":[],"sources":["../../../src/services/codegen/runtime-map.ts"],"sourcesContent":["/**\n * The composition inspector's generator (#591/#594): a *generated,\n * machine- and human-readable* effective route tree for a resolved runtime.\n *\n * Reads every ui source's route manifest (local `src/manifest.gen.json`, or\n * the published `<ui>/manifest.gen.json` for remote sources), applies the\n * same core-wins precedence the host's `constructTree` applies at runtime,\n * and emits:\n *\n * .bos/generated/runtime-map.json machine/human inventory\n * ui/src/lib/runtime-map.gen.ts typed REMOTE_ROUTES union + metadata\n *\n * This is a *static model* of composition: nav labels are runtime-only (they\n * live in route options, not manifests) and are reported as null.\n *\n * Loud degradation: a remote manifest that can't be fetched is reported as\n * UNRESOLVED and — in strict mode (`bos types gen`) — fails the run, so\n * typecheck catches an upstream rename instead of silently shrinking the\n * map. Dev/build/train paths run warn-mode (a flaky remote must not block a\n * boot; the runtime load fails loudly on its own). Set\n * BOS_MAP_ALLOW_UNRESOLVED=1 for offline work (the map is still written,\n * with the unresolved sources).\n */\nimport { existsSync, mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\nimport { Clock, Data, Effect } from \"effect\";\n\nexport class RuntimeMapError extends Data.TaggedError(\"RuntimeMapError\")<{\n readonly message: string;\n readonly cause?: unknown;\n}> {\n override toString(): string {\n return this.message;\n }\n}\n\nconst envBool = (name: string): boolean => process.env[name] === \"1\";\n\nconst isoNow = (ms: number): string => new Date(ms).toISOString();\n\nimport {\n MANIFEST_VERSION,\n type PluginManifest,\n resolveMountSegment,\n} from \"every-plugin/ui/manifest\";\nimport type { RuntimeConfig } from \"../../types\";\n\nconst CORE_UI_KEY = \"ui\";\nconst FETCH_TIMEOUT_MS = Number(process.env.BOS_MAP_FETCH_TIMEOUT_MS ?? 15_000);\n\nexport interface RouteRecord {\n id: string;\n type: string;\n path?: string;\n parentId?: string;\n mount?: string;\n}\n\nexport interface MapSource {\n key: string;\n kind: \"core\" | \"auth-ui\" | \"plugin-ui\";\n source: \"local\" | \"remote\";\n location: string;\n manifestPath?: string;\n manifestUrl?: string;\n pin?: string;\n note?: string;\n manifest: PluginManifest;\n}\n\nexport interface UnresolvedSource {\n key: string;\n kind: string;\n reason: string;\n}\n\nexport interface ComposedRoute {\n id: string;\n owner: string;\n mount: string;\n type: string;\n path: string | null;\n fullPath: string;\n core: boolean;\n nav: null;\n}\n\nexport interface ComposedRuntimeMap {\n mounts: Array<{ id: string; declaredBy: string; recordId: string }>;\n routes: ComposedRoute[];\n shadowed: Array<{ id: string; owner: string; kind: \"mount\" | \"route\"; reason: string }>;\n}\n\nfunction joinPath(a: string, b?: string): string {\n const left = a && a !== \"/\" ? a.replace(/\\/+$/, \"\") : \"\";\n const right = b && b !== \"/\" ? (b ?? \"\").replace(/^\\/+/, \"\") : \"\";\n if (!left) return right ? `/${right}` : \"/\";\n if (!right) return left;\n return `${left}/${right}`;\n}\n\nfunction isHttp(value: string | undefined): value is string {\n return typeof value === \"string\" && /^https?:\\/\\//.test(value);\n}\n\nfunction manifestUrlFor(base: string): string {\n return new URL(\"manifest.gen.json\", base.endsWith(\"/\") ? base : `${base}/`).toString();\n}\n\ninterface SourceDescriptor {\n key: string;\n kind: \"core\" | \"auth-ui\" | \"plugin-ui\";\n source: \"local\" | \"remote\";\n location: string;\n manifestPath?: string;\n manifestUrl?: string;\n pin?: string;\n note?: string;\n}\n\nfunction describeSlot(\n _root: string,\n key: string,\n kind: SourceDescriptor[\"kind\"],\n slot:\n | { url?: string; localPath?: string; source?: string; pin?: { manifest?: string } }\n | undefined,\n unresolved: UnresolvedSource[],\n): SourceDescriptor | null {\n if (!slot) return null;\n const pin = slot.pin?.manifest;\n if (slot.localPath) {\n const manifestPath = join(slot.localPath, \"src/manifest.gen.json\");\n if (existsSync(manifestPath)) {\n return { key, kind, source: \"local\", location: slot.localPath, manifestPath, pin };\n }\n if (isHttp(slot.url)) {\n return {\n key,\n kind,\n source: \"remote\",\n location: slot.url,\n manifestUrl: manifestUrlFor(slot.url),\n pin,\n note: `local path \"${slot.localPath}\" is absent; using the published bundle`,\n };\n }\n unresolved.push({\n key,\n kind,\n reason: `local path \"${slot.localPath}\" is absent and there is no production target`,\n });\n return null;\n }\n if (isHttp(slot.url)) {\n return {\n key,\n kind,\n source: \"remote\",\n location: slot.url,\n manifestUrl: manifestUrlFor(slot.url),\n pin,\n };\n }\n unresolved.push({ key, kind, reason: \"no local workspace or remote target\" });\n return null;\n}\n\nfunction validateManifest(source: SourceDescriptor, manifest: PluginManifest): void {\n if (!manifest || typeof manifest !== \"object\" || !Array.isArray(manifest.routes)) {\n throw new Error(`source \"${source.key}\": manifest is not a route manifest`);\n }\n if (manifest.manifestVersion !== MANIFEST_VERSION) {\n throw new Error(\n `source \"${source.key}\": manifest version ${manifest.manifestVersion} (expected ${MANIFEST_VERSION})`,\n );\n }\n}\n\n/**\n * Static model of every-plugin's `constructTree` precedence: core ui first,\n * then sources by key; core wins route/mount claims, peer collisions throw.\n */\nexport function composeRuntimeMap(\n sources: Array<SourceDescriptor & { manifest: PluginManifest }>,\n): ComposedRuntimeMap {\n const ordered = [\n ...sources.filter((source) => source.key === CORE_UI_KEY),\n ...sources\n .filter((source) => source.key !== CORE_UI_KEY)\n .sort((a, b) => a.key.localeCompare(b.key)),\n ];\n\n const mounts = new Map<string, { key: string; recordId: string }>();\n const mountByLayoutId = new Map<string, string>();\n const shadowed: ComposedRuntimeMap[\"shadowed\"] = [];\n\n for (const source of ordered) {\n for (const record of source.manifest.routes) {\n if (!record.mount) continue;\n const existing = mounts.get(record.mount);\n if (existing) {\n if (existing.key === CORE_UI_KEY && source.key !== CORE_UI_KEY) {\n shadowed.push({\n id: record.id,\n owner: source.key,\n kind: \"mount\",\n reason: `mount \"${record.mount}\" is declared by core ui`,\n });\n mountByLayoutId.set(record.id, record.mount);\n continue;\n }\n throw new Error(\n `mount \"${record.mount}\" declared by multiple sources: \"${existing.key}\" and \"${source.key}\"`,\n );\n }\n mounts.set(record.mount, { key: source.key, recordId: record.id });\n mountByLayoutId.set(record.id, record.mount);\n }\n }\n\n const routes: ComposedRoute[] = [];\n const claims = new Map<string, Map<string, string>>();\n\n for (const source of ordered) {\n const pending = source.manifest.routes.filter((record) => !record.mount);\n const byId = new Map<string, { fullPath: string; mount: string }>();\n const shadowedIds = new Set<string>();\n let progressed = true;\n\n while (pending.length > 0 && progressed) {\n progressed = false;\n for (let index = 0; index < pending.length; index += 1) {\n const record = pending[index]!;\n\n let parent: { fullPath: string; mount: string; key: string };\n if (record.parentId) {\n if (shadowedIds.has(record.parentId)) {\n shadowedIds.add(record.id);\n shadowed.push({\n id: record.id,\n owner: source.key,\n kind: \"route\",\n reason: `parent \"${record.parentId}\" is shadowed`,\n });\n pending.splice(index, 1);\n index -= 1;\n progressed = true;\n continue;\n }\n const local = byId.get(record.parentId);\n if (local) {\n parent = {\n fullPath: local.fullPath,\n mount: local.mount,\n key: `${source.key}:${record.parentId}`,\n };\n } else if (mountByLayoutId.has(record.parentId)) {\n const mount = mountByLayoutId.get(record.parentId)!;\n parent = { fullPath: \"\", mount, key: `mount:${mount}` };\n } else {\n continue;\n }\n } else {\n const firstSegment = record.id.split(\"/\")[0] ?? \"\";\n if (!firstSegment.startsWith(\"_\")) {\n throw new Error(`route \"${record.id}\" in \"${source.key}\" must live under a mount`);\n }\n const mount = resolveMountSegment(firstSegment.slice(1));\n if (!mount) {\n throw new Error(\n `unknown mount \"_${firstSegment.slice(1)}\" (route \"${record.id}\" in \"${source.key}\")`,\n );\n }\n if (!mounts.has(mount)) {\n throw new Error(\n `mount \"${mount}\" is not declared by any source (route \"${record.id}\" in \"${source.key}\")`,\n );\n }\n parent = { fullPath: \"\", mount, key: `mount:${mount}` };\n }\n\n pending.splice(index, 1);\n index -= 1;\n progressed = true;\n\n const isLayoutRoute = record.type === \"layout\" || record.path === undefined;\n if (!isLayoutRoute) {\n const siblings = claims.get(parent.key) ?? new Map<string, string>();\n const claimant = siblings.get(record.path!);\n if (claimant) {\n if (source.key !== CORE_UI_KEY && claimant.startsWith(`${CORE_UI_KEY}:`)) {\n shadowedIds.add(record.id);\n shadowed.push({\n id: record.id,\n owner: source.key,\n kind: \"route\",\n reason: `path \"${record.path}\" is claimed by \"${claimant}\"`,\n });\n continue;\n }\n throw new Error(\n `path collision: \"${source.key}\" route \"${record.id}\" declares \"${record.path}\" already claimed by \"${claimant}\"`,\n );\n }\n siblings.set(record.path!, `${source.key}:${record.id}`);\n claims.set(parent.key, siblings);\n }\n\n const fullPath = joinPath(parent.fullPath, record.path);\n byId.set(record.id, { fullPath, mount: parent.mount });\n routes.push({\n id: record.id,\n owner: source.key,\n mount: parent.mount,\n type: record.type,\n path: record.path ?? null,\n fullPath,\n core: source.key === CORE_UI_KEY,\n nav: null,\n });\n }\n }\n\n if (pending.length > 0) {\n throw new Error(\n `unresolvable parentage in \"${source.key}\": ${pending.map((record) => record.id).join(\", \")}`,\n );\n }\n }\n\n return {\n mounts: [...mounts.entries()].map(([id, value]) => ({\n id,\n declaredBy: value.key,\n recordId: value.recordId,\n })),\n routes,\n shadowed,\n };\n}\n\nasync function fetchRemoteManifest(url: string): Promise<PluginManifest> {\n const response = await fetch(url, { signal: AbortSignal.timeout(FETCH_TIMEOUT_MS) });\n if (!response.ok) throw new Error(`HTTP ${response.status} ${response.statusText}`);\n return (await response.json()) as PluginManifest;\n}\n\nasync function fetchPluginManifestAsset(url: string): Promise<PluginManifestAsset> {\n const response = await fetch(url, { signal: AbortSignal.timeout(FETCH_TIMEOUT_MS) });\n if (!response.ok) throw new Error(`HTTP ${response.status} ${response.statusText}`);\n const asset = (await response.json()) as PluginManifestAsset;\n if (!asset || typeof asset !== \"object\" || asset.kind !== \"every-plugin/manifest\") {\n throw new Error(\"not a plugin manifest\");\n }\n return asset;\n}\n\nfunction pluginManifestUrlFor(base: string): string {\n return new URL(\"plugin.manifest.json\", base.endsWith(\"/\") ? base : `${base}/`).toString();\n}\n\n/** The backend inventory (#594 review): host, api, the auth server, and every\n * plugin backend — the \"who serves this runtime, from where\" analog of the ui\n * sources list. Local backends are their own truth (workspace path); remote\n * backends fetch their published `plugin.manifest.json` for the version +\n * contract hash drift signal. The host is source-level only (it ships no\n * manifest asset — it is not an every-plugin module). */\nfunction describeBackend(\n root: string,\n key: string,\n kind: BackendRecord[\"kind\"],\n slot:\n | {\n name?: string;\n url?: string;\n localPath?: string;\n source?: string;\n remoteUrl?: string;\n remoteEntry?: string;\n pin?: { manifest?: string };\n integrity?: string;\n dependsOn?: string[];\n }\n | undefined,\n): BackendRecord | null {\n if (!slot) return null;\n const name = typeof slot.name === \"string\" ? slot.name : null;\n const pin = slot.pin?.manifest;\n const dependsOn = slot.dependsOn;\n if (slot.localPath) {\n return {\n key,\n kind,\n name,\n source: \"local\",\n location: slot.localPath.startsWith(root)\n ? slot.localPath.slice(root.length + 1)\n : slot.localPath,\n ...(pin ? { pin } : {}),\n ...(slot.integrity ? { integrity: slot.integrity } : {}),\n ...(dependsOn ? { dependsOn } : {}),\n };\n }\n const remote = slot.remoteUrl ?? slot.url;\n if (isHttp(remote)) {\n return {\n key,\n kind,\n name,\n source: \"remote\",\n location: remote,\n // the host ships no plugin manifest asset (it is not an every-plugin\n // module) — probing it 404s on every real host and, in strict mode,\n // fails types gen in every scaffolded child\n ...(kind !== \"host\" ? { pluginManifestUrl: pluginManifestUrlFor(remote) } : {}),\n ...(pin ? { pin } : {}),\n ...(slot.integrity ? { integrity: slot.integrity } : {}),\n ...(dependsOn ? { dependsOn } : {}),\n };\n }\n return null;\n}\n\nexport interface RuntimeMapResult {\n routeCount: number;\n remoteCount: number;\n sourceCount: number;\n backendCount: number;\n unresolved: UnresolvedSource[];\n jsonPath: string;\n tsPath: string | undefined;\n}\n\n/** The published `plugin.manifest.json` (EmitPluginManifest asset) — the\n * backend inventory's remote drift signal: which plugin version, and which\n * contract bytes, is this runtime actually composing? */\nexport interface PluginManifestAsset {\n schemaVersion: number;\n kind: string;\n plugin?: { name?: string; version?: string };\n contract?: { types?: { sha256?: string } };\n}\n\nexport interface BackendRecord {\n key: string;\n kind: \"host\" | \"api\" | \"auth\" | \"plugin\";\n name: string | null;\n source: \"local\" | \"remote\";\n /** Workspace-relative path (local) or URL (remote). */\n location: string;\n pin?: string;\n integrity?: string;\n dependsOn?: string[];\n /** Remote backends only: the composed plugin bundle's version + contract\n * hash, from its published `plugin.manifest.json`. */\n pluginVersion?: string;\n contractSha256?: string;\n /** Where the remote manifest asset was fetched from. */\n pluginManifestUrl?: string;\n note?: string;\n}\n\n/** The TS emission requires a local core ui workspace (`ui/src/lib/`); a\n * remote-only core ui skips it with a note. */\nfunction renderTypeScript(map: GeneratedRuntimeMap): string {\n const remoteRoutes = map.routes\n .filter((route) => !route.core)\n .map((route) => ({\n id: route.id,\n path: route.fullPath,\n mount: route.mount,\n owner: route.owner,\n type: route.type,\n }));\n const json = (value: unknown) => JSON.stringify(value, null, 2);\n\n return `// Generated by bos types gen — do not edit.\n// Regenerate with \\`bos types gen\\` (or \\`pnpm run types:gen\\`). A static model of the\n// composed runtime: core ui wins route/mount claims; plugin routes are listed\n// below. Nav labels are runtime-only (route options, not manifests).\n// See docs/agents/runtime-map.md for the investigation workflows.\n\nexport const RUNTIME_MAP_GENERATED_AT = ${JSON.stringify(map.generatedAt)};\nexport const RUNTIME_MAP_ENV = ${JSON.stringify(map.env)};\nexport const RUNTIME_PLATFORM = ${JSON.stringify(map.platform)};\n\nexport const RUNTIME_SOURCES = ${json(map.sources)} as const;\n\n/** The serving side: host, api, the auth server, and every plugin backend —\n * source-level inventory; remote entries carry the composed plugin version\n * and contract hash. */\nexport const RUNTIME_BACKENDS = ${json(map.backends)} as const;\nexport type RuntimeBackend = (typeof RUNTIME_BACKENDS)[number];\n\n/** Routes owned by a plugin/remote ui source (not the core). */\nexport const REMOTE_ROUTES = ${json(remoteRoutes)} as const;\nexport type RemoteRoute = (typeof REMOTE_ROUTES)[number];\nexport type RemotePath = RemoteRoute[\"path\"];\n\nexport const RUNTIME_MOUNTS = ${json(map.mounts)} as const;\nexport const SHADOWED_ROUTES = ${json(map.shadowed)} as const;\n\nexport const UNRESOLVED_SOURCES = ${json(map.unresolved)} as const;\nexport type UnresolvedSource = (typeof UNRESOLVED_SOURCES)[number];\n`;\n}\n\ninterface GeneratedRuntimeMap {\n generatedAt: string;\n env: string;\n platform: string | null;\n extends: string | null;\n sources: Array<{\n key: string;\n kind: string;\n source: string;\n location: string;\n manifestUrl: string | null;\n pin: string | null;\n note: string | null;\n }>;\n backends: BackendRecord[];\n unresolved: UnresolvedSource[];\n mounts: ComposedRuntimeMap[\"mounts\"];\n routes: ComposedRuntimeMap[\"routes\"];\n shadowed: ComposedRuntimeMap[\"shadowed\"];\n}\n\nexport const generateRuntimeMapEffect = Effect.fn(\"generateRuntimeMap\")(function* (\n configDir: string,\n runtimeConfig: RuntimeConfig,\n opts?: { strict?: boolean; env?: string; platform?: string | null; extendsChain?: string[] },\n): Effect.fn.Return<RuntimeMapResult | null, Error> {\n const allowUnresolved = envBool(\"BOS_MAP_ALLOW_UNRESOLVED\");\n const unresolved: UnresolvedSource[] = [];\n\n const descriptors = [\n describeSlot(configDir, CORE_UI_KEY, \"core\", runtimeConfig.ui, unresolved),\n describeSlot(configDir, \"auth\", \"auth-ui\", runtimeConfig.auth?.ui, unresolved),\n ];\n for (const [key, plugin] of Object.entries(runtimeConfig.plugins ?? {})) {\n // `plugins.auth` is the auth mirror (the auth app slot's backend owns\n // it — planner isAuthMirrorFor): its ui is the same source already\n // described under the \"auth\" key — describing it twice collides with\n // its own claims.\n if (key === \"auth\" && runtimeConfig.auth !== undefined) continue;\n descriptors.push(describeSlot(configDir, key, \"plugin-ui\", plugin.ui, unresolved));\n }\n\n const sources: Array<SourceDescriptor & { manifest: PluginManifest }> = [];\n for (const descriptor of descriptors) {\n if (!descriptor) continue;\n if (descriptor.source === \"local\" && descriptor.manifestPath) {\n const manifest = JSON.parse(readFileSync(descriptor.manifestPath, \"utf8\")) as PluginManifest;\n validateManifest(descriptor, manifest);\n sources.push({ ...descriptor, manifest });\n } else if (descriptor.manifestUrl) {\n // Effect generators cannot await — the fetch failure degrades to an\n // unresolved entry inside the promise.\n yield* Effect.tryPromise({\n try: async () => {\n try {\n const manifest = await fetchRemoteManifest(descriptor.manifestUrl!);\n validateManifest(descriptor, manifest);\n sources.push({ ...descriptor, manifest });\n } catch (error) {\n unresolved.push({\n key: descriptor.key,\n kind: descriptor.kind,\n reason: `fetch ${descriptor.manifestUrl} failed: ${error instanceof Error ? error.message : String(error)}`,\n });\n }\n },\n catch: (cause) => new RuntimeMapError({ message: String(cause) }),\n });\n }\n }\n\n // Backend inventory (#594 review): the auth mirror dedupes against the\n // auth app slot's backend (same source, same key collision rule). Effect\n // generators cannot await — the fetch loop runs inside the promise.\n const backendSlots: Array<BackendRecord | null> = [\n describeBackend(configDir, \"host\", \"host\", runtimeConfig.host),\n describeBackend(configDir, \"api\", \"api\", runtimeConfig.api),\n describeBackend(configDir, \"auth\", \"auth\", runtimeConfig.auth),\n ];\n for (const [key, plugin] of Object.entries(runtimeConfig.plugins ?? {})) {\n if (key === \"auth\" && runtimeConfig.auth !== undefined) continue;\n backendSlots.push(describeBackend(configDir, key, \"plugin\", plugin));\n }\n const backends: BackendRecord[] = yield* Effect.tryPromise({\n try: async () => {\n const collected: BackendRecord[] = [];\n for (const backend of backendSlots) {\n if (!backend) continue;\n if (backend.pluginManifestUrl) {\n try {\n const asset = await fetchPluginManifestAsset(backend.pluginManifestUrl);\n collected.push({\n ...backend,\n pluginVersion: asset.plugin?.version,\n contractSha256: asset.contract?.types?.sha256,\n });\n } catch (error) {\n const reason = `plugin manifest ${backend.pluginManifestUrl} failed: ${error instanceof Error ? error.message : String(error)}`;\n unresolved.push({ key: backend.key, kind: backend.kind, reason });\n collected.push({ ...backend, note: reason });\n }\n } else {\n collected.push(backend);\n }\n }\n return collected;\n },\n catch: (cause) => new RuntimeMapError({ message: String(cause) }),\n });\n\n if (sources.length === 0 && unresolved.length === 0 && backends.length === 0) return null;\n\n if (unresolved.length > 0 && !allowUnresolved) {\n for (const entry of unresolved) {\n yield* Effect.logError(\n `[runtime-map] UNRESOLVED ${entry.key} (${entry.kind}): ${entry.reason}`,\n );\n }\n if (opts?.strict) {\n // the reasons ride in the error — the wire (e.g. the typesGen JSON)\n // only sees this message, never the console.error lines above\n return yield* new RuntimeMapError({\n message: `runtime map is incomplete — ${unresolved.map((entry) => `${entry.key} (${entry.kind}): ${entry.reason}`).join(\"; \")} — fix the source or set BOS_MAP_ALLOW_UNRESOLVED=1 for offline work`,\n });\n }\n }\n\n const composed = composeRuntimeMap(sources);\n const map: GeneratedRuntimeMap = {\n generatedAt: isoNow(yield* Clock.currentTimeMillis),\n env: opts?.env ?? \"unknown\",\n platform: opts?.platform ?? null,\n extends: opts?.extendsChain?.join(\" → \") ?? null,\n sources: sources.map((source) => ({\n key: source.key,\n kind: source.kind,\n source: source.source,\n // workspace-relative for local sources — no absolute paths in the\n // committed/generated surface\n location:\n source.source === \"local\" && source.location.startsWith(configDir)\n ? source.location.slice(configDir.length + 1)\n : source.location,\n manifestUrl: source.manifestUrl ?? null,\n pin: source.pin ?? null,\n note: source.note ?? null,\n })),\n backends,\n unresolved,\n mounts: composed.mounts,\n routes: composed.routes,\n shadowed: composed.shadowed,\n };\n\n const jsonPath = join(configDir, \".bos/generated/runtime-map.json\");\n mkdirSync(dirname(jsonPath), { recursive: true });\n writeFileSync(jsonPath, `${JSON.stringify(map, null, 2)}\\n`);\n\n // The TS emission requires a local core ui workspace; a remote-only core\n // ui skips it with a note.\n let tsPath: string | undefined;\n const uiLocalPath = runtimeConfig.ui?.source === \"local\" ? runtimeConfig.ui.localPath : undefined;\n if (uiLocalPath && existsSync(join(uiLocalPath, \"src\"))) {\n tsPath = join(uiLocalPath, \"src/lib/runtime-map.gen.ts\");\n mkdirSync(dirname(tsPath), { recursive: true });\n writeFileSync(tsPath, renderTypeScript(map));\n }\n\n const remoteCount = map.routes.filter((route) => !route.core).length;\n const remoteBackends = backends.filter((backend) => backend.source === \"remote\").length;\n yield* Effect.log(\n `[runtime-map] ${map.routes.length} routes (${remoteCount} remote) from ${sources.length} sources; ${backends.length} backends (${remoteBackends} remote)`,\n );\n for (const entry of unresolved) {\n yield* Effect.logWarning(`[runtime-map] unresolved ${entry.key}: ${entry.reason}`);\n }\n yield* Effect.log(\n `[runtime-map] wrote ${jsonPath}${tsPath ? ` + ${tsPath}` : \" (no local core ui — ts emission skipped)\"}`,\n );\n\n return {\n routeCount: map.routes.length,\n remoteCount,\n sourceCount: sources.length,\n backendCount: backends.length,\n unresolved,\n jsonPath,\n tsPath,\n };\n});\n"],"mappings":"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"}