UNPKG

everything-dev

Version:

A consolidated product package for building Module Federation apps with oRPC APIs.

1 lines • 17.6 kB
{"version":3,"file":"service-descriptor.cjs","names":["Context","Layer","existsSync","path"],"sources":["../src/service-descriptor.ts"],"sourcesContent":["import { existsSync } from \"node:fs\";\nimport path from \"node:path\";\nimport { Context, Layer } from \"effect\";\nimport type { JsonObject, RuntimeConfig, SourceMode } from \"./types\";\n\nexport interface AuthSlotShape {\n source: string;\n localPath?: string;\n url?: string;\n}\n\n/**\n * True when a `plugins.<id>` entry is the runtime-config mirror of the\n * app-slot auth plugin (same backend target) — the `auth` service already\n * spawns that backend, so the mirror contributes only its `ui` surface.\n * The single implementation; consumed by the service descriptors, the DAG,\n * the infra planner, and the dev session.\n */\nexport function isAuthMirrorPluginEntry(\n authEntry: AuthSlotShape | undefined,\n pluginId: string,\n pluginConfig: AuthSlotShape,\n): boolean {\n if (pluginId !== \"auth\" || !authEntry) return false;\n if (pluginConfig === authEntry) return true;\n if (pluginConfig.localPath && pluginConfig.localPath === authEntry.localPath) return true;\n if (\n !pluginConfig.localPath &&\n pluginConfig.source === \"remote\" &&\n pluginConfig.url &&\n pluginConfig.url === authEntry.url\n ) {\n return true;\n }\n return false;\n}\n\nexport interface ServiceDescriptor {\n key: string;\n source: SourceMode;\n url: string;\n remoteUrl?: string;\n entry: string;\n name: string;\n localPath?: string;\n port?: number;\n readinessPath: string;\n defaultPort: number;\n integrity?: string;\n proxy?: string;\n variables?: JsonObject;\n secrets?: string[];\n ssr?: boolean;\n command?: string;\n args?: string[];\n env?: Record<string, string>;\n readyPatterns?: RegExp[];\n errorPatterns?: RegExp[];\n /** folder-form plugin UI port — the UI build is a child of this dev process (BOS_UI_PORT) */\n uiPort?: number;\n}\n\nexport class ServiceDescriptorMap extends Context.Service<\n ServiceDescriptorMap,\n Map<string, ServiceDescriptor>\n>()(\"ServiceDescriptorMap\") {}\n\nexport class DevRuntimeConfig extends Context.Service<DevRuntimeConfig, RuntimeConfig>()(\n \"DevRuntimeConfig\",\n) {}\n\nexport class DevGeneratedEnv extends Context.Service<DevGeneratedEnv, Record<string, string>>()(\n \"DevGeneratedEnv\",\n) {}\n\nexport const DevGeneratedEnvLive = (env: Record<string, string>) =>\n Layer.succeed(DevGeneratedEnv, env);\n\nconst PLUGIN_READY_PATTERNS = [/ready in/i, /compiled.*successfully/i, /listening/i, /started/i];\n\nconst PLUGIN_ERROR_PATTERNS = [\n /\\bERROR in\\b/,\n /failed to compile/i,\n /Module not found/i,\n /Cannot find module/i,\n];\n\nconst SERVICE_CONFIGS = {\n host: {\n command: \"pnpm\",\n args: [\"run\", \"dev\"],\n // The host dev server runs on tsx (TS-capable node), so it can consume\n // framework package sources directly — NODE_OPTIONS makes node's\n // resolver pick the `development` export condition. The env var rides\n // the spawn environment (ADR 0026 §2) and is inherited by the host's\n // tsx children. rsbuild/rspack config loaders must NOT rely on it:\n // their .mjs config evaluation resolves dist (freshness is the\n // prerequisite train's job, ADR 0022).\n env: { NODE_OPTIONS: \"--conditions=development\" },\n readyPatterns: [/Host (dev|production) server running at/i, /Server running at/i],\n errorPatterns: [/\\berror\\b(?!s)/i, /\\bfailed to\\b/i, /\\bbuild failed\\b/i, /exception/i],\n defaultPort: 3000,\n readinessPath: \"/health\",\n },\n auth: {\n command: \"pnpm\",\n args: [\"run\", \"dev\"],\n readyPatterns: PLUGIN_READY_PATTERNS,\n errorPatterns: PLUGIN_ERROR_PATTERNS,\n defaultPort: 3002,\n readinessPath: \"/remoteEntry.js\",\n },\n ui: {\n command: \"pnpm\",\n args: [\"run\", \"dev\"],\n readyPatterns: [/\\bready\\s+built in\\b/i, /\\bLocal:\\b/i, /\\bcompiled\\b.*successfully/i],\n errorPatterns: [/\\berror\\b(?!s)/i, /\\bfailed to\\b/i, /\\bbuild failed\\b/i],\n defaultPort: 3003,\n readinessPath: \"/remoteEntry.js\",\n },\n api: {\n command: \"pnpm\",\n args: [\"run\", \"dev\"],\n readyPatterns: PLUGIN_READY_PATTERNS,\n errorPatterns: PLUGIN_ERROR_PATTERNS,\n defaultPort: 3001,\n readinessPath: \"/remoteEntry.js\",\n },\n} as const satisfies Record<\n string,\n Pick<\n ServiceDescriptor,\n \"command\" | \"args\" | \"env\" | \"readyPatterns\" | \"errorPatterns\" | \"defaultPort\" | \"readinessPath\"\n >\n>;\n\nexport function buildServiceDescriptorMap(\n runtimeConfig: RuntimeConfig,\n options?: { ssr?: boolean; proxy?: boolean },\n): Map<string, ServiceDescriptor> {\n const map = new Map<string, ServiceDescriptor>();\n const ssr = options?.ssr ?? false;\n\n const hostIsRemote = runtimeConfig.host.source === \"remote\";\n const hostProbeUrl = hostIsRemote\n ? (runtimeConfig.host.remoteUrl ?? runtimeConfig.host.url)\n : runtimeConfig.host.url;\n map.set(\"host\", {\n key: \"host\",\n source: runtimeConfig.host.source,\n url: hostProbeUrl,\n remoteUrl: runtimeConfig.host.remoteUrl,\n entry: hostIsRemote\n ? hostProbeUrl\n ? `${hostProbeUrl}/mf-manifest.json`\n : \"/mf-manifest.json\"\n : runtimeConfig.host.entry,\n name: runtimeConfig.host.name,\n localPath: runtimeConfig.host.localPath,\n port: runtimeConfig.host.port,\n integrity: runtimeConfig.host.integrity,\n secrets: runtimeConfig.host.secrets,\n ...SERVICE_CONFIGS.host,\n });\n\n map.set(\"ui\", {\n key: \"ui\",\n source: runtimeConfig.ui.source,\n url: runtimeConfig.ui.url,\n remoteUrl: runtimeConfig.ui.source === \"remote\" ? runtimeConfig.ui.url : undefined,\n entry: runtimeConfig.ui.entry,\n name: runtimeConfig.ui.name,\n localPath: runtimeConfig.ui.localPath,\n port: runtimeConfig.ui.port,\n integrity: runtimeConfig.ui.integrity,\n ssr,\n ...SERVICE_CONFIGS.ui,\n env: { BOS_DEV_SERVER: \"1\" },\n // BOS_NO_WATCH stacks (regression/CI) build once and serve the built dist\n // via rsbuild preview — no watcher, no HMR.\n args: process.env.BOS_NO_WATCH === \"1\" ? [\"run\", \"dev:built\"] : SERVICE_CONFIGS.ui.args,\n });\n\n map.set(\"api\", {\n key: \"api\",\n source: runtimeConfig.api.source,\n url: runtimeConfig.api.url,\n remoteUrl: runtimeConfig.api.source === \"remote\" ? runtimeConfig.api.url : undefined,\n entry: runtimeConfig.api.entry,\n name: runtimeConfig.api.name,\n localPath: runtimeConfig.api.localPath,\n port: runtimeConfig.api.port,\n integrity: runtimeConfig.api.integrity,\n proxy: runtimeConfig.api.proxy,\n variables: runtimeConfig.api.variables,\n secrets: runtimeConfig.api.secrets,\n ...SERVICE_CONFIGS.api,\n });\n\n if (runtimeConfig.auth) {\n map.set(\"auth\", {\n key: \"auth\",\n source: runtimeConfig.auth.source,\n url: runtimeConfig.auth.url,\n remoteUrl: runtimeConfig.auth.source === \"remote\" ? runtimeConfig.auth.url : undefined,\n entry: runtimeConfig.auth.entry,\n name: runtimeConfig.auth.name,\n localPath: runtimeConfig.auth.localPath,\n port: runtimeConfig.auth.port,\n integrity: runtimeConfig.auth.integrity,\n proxy: runtimeConfig.auth.proxy,\n variables: runtimeConfig.auth.variables,\n secrets: runtimeConfig.auth.secrets,\n ...SERVICE_CONFIGS.auth,\n });\n }\n\n if (runtimeConfig.plugins) {\n let pluginBasePort = 3010;\n for (const [pluginId, pluginConfig] of Object.entries(runtimeConfig.plugins)) {\n const isAuthMirror = isAuthMirrorPluginEntry(runtimeConfig.auth, pluginId, pluginConfig);\n const pluginKey = `plugin:${pluginId}`;\n const resolvedPort = pluginConfig.port ?? pluginBasePort;\n pluginBasePort = resolvedPort + 1;\n\n // Folder-form ui source: the ui dir lives inside the plugin workspace\n // (no own package.json) — `every-plugin dev` spawns its UI build as a\n // child (BOS_UI_PORT), so no separate plugin-ui service is spawned.\n const uiLocalPath = pluginConfig.ui?.localPath;\n const isFolderFormUi = Boolean(\n uiLocalPath &&\n pluginConfig.ui?.source === \"local\" &&\n existsSync(path.join(uiLocalPath, \"src\", \"routes\")) &&\n !existsSync(path.join(uiLocalPath, \"package.json\")),\n );\n const folderUiPort = pluginConfig.ui?.port ?? pluginBasePort;\n if (isFolderFormUi) pluginBasePort = folderUiPort + 1;\n\n if (!isAuthMirror) {\n map.set(pluginKey, {\n key: pluginKey,\n source: pluginConfig.source,\n url: pluginConfig.url,\n remoteUrl: pluginConfig.source === \"remote\" ? pluginConfig.url : undefined,\n entry: pluginConfig.entry,\n name: pluginConfig.name,\n localPath: pluginConfig.localPath,\n port: resolvedPort,\n integrity: pluginConfig.integrity,\n proxy: pluginConfig.proxy,\n variables: pluginConfig.variables,\n secrets: pluginConfig.secrets,\n command: \"pnpm\",\n args: [\"run\", \"dev\"],\n env: isFolderFormUi ? { BOS_UI_PORT: String(folderUiPort) } : undefined,\n uiPort: isFolderFormUi ? folderUiPort : undefined,\n readyPatterns: PLUGIN_READY_PATTERNS,\n errorPatterns: PLUGIN_ERROR_PATTERNS,\n defaultPort: resolvedPort,\n readinessPath: \"/remoteEntry.js\",\n });\n } else if (isFolderFormUi) {\n // Folder-form ui for the auth mirror: the `auth` app slot owns the\n // dev process (plugin:auth is not spawned), so BOS_UI_PORT rides on\n // the auth descriptor. folderUiPort is the same port the runtime\n // config wired into the mirror's ui.url (withLocalRuntimeUrl).\n const authDescriptor = map.get(\"auth\");\n if (authDescriptor) {\n map.set(\"auth\", {\n ...authDescriptor,\n env: { BOS_UI_PORT: String(folderUiPort) },\n uiPort: folderUiPort,\n });\n }\n }\n\n if (pluginConfig.ui?.localPath && pluginConfig.ui.source === \"local\" && !isFolderFormUi) {\n const uiKey = `plugin-ui:${pluginId}`;\n const uiPort = pluginConfig.ui.port ?? pluginBasePort;\n pluginBasePort = uiPort + 1;\n\n map.set(uiKey, {\n key: uiKey,\n source: pluginConfig.ui.source,\n url: pluginConfig.ui.url,\n entry: pluginConfig.ui.entry,\n name: pluginConfig.ui.name,\n localPath: pluginConfig.ui.localPath,\n port: uiPort,\n integrity: pluginConfig.ui.integrity,\n command: \"pnpm\",\n args: [\"run\", \"dev\"],\n readyPatterns: PLUGIN_READY_PATTERNS,\n errorPatterns: PLUGIN_ERROR_PATTERNS,\n defaultPort: uiPort,\n readinessPath: \"/remoteEntry.js\",\n });\n }\n }\n }\n\n return map;\n}\n\nexport interface AppOrchestrator {\n packages: string[];\n description: string;\n env: Record<string, string>;\n port?: number;\n interactive?: boolean;\n noLogs?: boolean;\n}\n\nexport const ServiceDescriptorMapLive = (map: Map<string, ServiceDescriptor>) =>\n Layer.succeed(ServiceDescriptorMap, map);\n\nexport const DevRuntimeConfigLive = (config: RuntimeConfig) =>\n Layer.succeed(DevRuntimeConfig, config);\n\nexport function buildServiceDescriptorMapFromPlan(\n plan: {\n runtimeConfig: RuntimeConfig;\n resolvedPorts: { host?: number; api?: number; auth?: number; ui?: number; uiSsr?: number };\n },\n options?: { ssr?: boolean; proxy?: boolean },\n): Map<string, ServiceDescriptor> {\n return buildServiceDescriptorMap(plan.runtimeConfig, options);\n}\n\ninterface ServiceDescriptorInfo {\n key: string;\n source: string;\n proxy?: string;\n}\n\nexport function buildDescription(map: Map<string, ServiceDescriptorInfo>): string {\n const descriptors = [...map.values()].filter((d) => !d.key.startsWith(\"plugin:\"));\n\n const allLocal = descriptors.every((d) => d.source === \"local\");\n const hasProxy = [...map.values()].some((d) => d.proxy && d.source === \"local\");\n if (allLocal && !hasProxy) return \"Full Local Development\";\n\n const parts: string[] = [];\n for (const d of descriptors) {\n if (d.source === \"remote\") {\n const label =\n d.key === \"host\"\n ? \"Remote Host\"\n : d.key === \"ui\"\n ? \"Remote UI\"\n : d.key === \"api\"\n ? hasProxy\n ? undefined\n : \"Remote API\"\n : d.key === \"auth\"\n ? \"Remote Auth\"\n : undefined;\n if (label) parts.push(label);\n }\n }\n if (hasProxy) parts.push(\"Proxy API → Production\");\n return parts.join(\" + \") || \"Remote Mode\";\n}\n"],"mappings":"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"}