UNPKG

everything-dev

Version:

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

252 lines (250 loc) • 8.77 kB
import { mergeBosConfigWithExtends } from "../merge.mjs"; import { AppDescriptorSchema } from "./schema.mjs"; //#region src/descriptor/resolve.ts function isPlainObject(value) { return Boolean(value) && typeof value === "object" && !Array.isArray(value); } const devRef = (ref) => ref.path ? `local:${ref.path}` : ref.development; function pick(source, keys) { const out = {}; for (const key of keys) if (source[key] !== void 0) out[key] = source[key]; return out; } const ATTACHMENT_FIELDS = [ "name", "proxy", "variables", "secrets", "routes", "shared", "connectSrc", "dependsOn", "version" ]; /** Fields the attachment slots carry beyond the shared attachment set. */ const SLOT_INPUT_FIELDS = [ "development", "extends", "integrity" ]; /** Fields a config-entry may carry that map onto an attachment's slot-free set. */ const ENTRY_ONLY_FIELDS = ["name", "extends"]; const LOCAL_PREFIX = "local:"; function stripLocal(development) { return development?.startsWith(LOCAL_PREFIX) ? development.slice(6) : void 0; } function attachmentToEntry(attachment) { const development = devRef(attachment); return { ...attachment.extends ? { extends: attachment.extends } : {}, ...development ? { development } : {}, ...pick(attachment, [...ATTACHMENT_FIELDS]), ...attachment.ui ? { ui: { ...attachment.ui.name ? { name: attachment.ui.name } : {}, ...attachment.ui.path ? { development: `local:${attachment.ui.path}` } : attachment.ui.development ? { development: attachment.ui.development } : {}, ...attachment.ui.integrity ? { integrity: attachment.ui.integrity } : {}, ...attachment.ui.pin ? { pin: attachment.ui.pin } : {} } } : {} }; } /** Descriptor → authoring-shape `BosConfigInput` (local refs only, no pipeline state). */ function toConfigInput(descriptor) { const input = { ...pick(descriptor, [ "account", "domain", "title", "description", "repository", "testnet", "staging", "ci", "cdn", "publish" ]) }; const app = {}; if (descriptor.host) { const development = devRef(descriptor.host); if (development) app.host = { development, ...pick(descriptor.host, ["secrets"]) }; } if (descriptor.api) { const development = devRef(descriptor.api); if (development) app.api = { development, ...pick(descriptor.api, [ "proxy", "variables", "secrets", "routes", "shared", "dependsOn" ]) }; } if (descriptor.ui) { const development = devRef(descriptor.ui); if (development) app.ui = { development }; } if (descriptor.auth) app.auth = attachmentToEntry(descriptor.auth); if (Object.keys(app).length > 0) input.app = app; if (descriptor.plugins) { const plugins = {}; for (const [key, attachment] of Object.entries(descriptor.plugins)) { const entry = attachmentToEntry(attachment); if (entry.name === key) delete entry.name; plugins[key] = entry; } input.plugins = plugins; } return input; } function applyPipeline(target, fields) { for (const [key, value] of Object.entries(fields)) if (value !== void 0) target[key] = value; } function applyDeployMap(config, entry) { if (!entry) return; if (entry.app && isPlainObject(config.app)) for (const [slot, fields] of Object.entries(entry.app)) { const target = config.app[slot]; if (fields && isPlainObject(target)) applyPipeline(target, fields); } if (entry.plugins && isPlainObject(config.plugins)) for (const [key, fields] of Object.entries(entry.plugins)) { const target = config.plugins[key]; if (fields && isPlainObject(target)) applyPipeline(target, fields); } } /** * Flatten one app against the registry: `extends` inherits the base App's * composition, the child's entries win (depth-1 — chains are deferred, * wayfinder ticket 08). The consumed `extends` key is dropped from the * output; the deploy map injects pipeline state. * * `extends` accepts a registry name or an imported App descriptor value — * an imported parent is an inlined parent: same materialization, no fetch. */ function resolveApp(name, registry, deployMap = {}, seen = /* @__PURE__ */ new Set()) { if (seen.has(name)) throw new Error(`Circular extends detected: ${name}`); const raw = registry[name]; if (!raw) throw new Error(`App "${name}" not found in registry`); const descriptor = AppDescriptorSchema.parse(raw); let parentInput; if (descriptor.extends) { if (typeof descriptor.extends === "string") { const parentRaw = registry[descriptor.extends]; if (!parentRaw) throw new Error(`extends target "${descriptor.extends}" not found in registry`); parentInput = flattenParent(parentRaw); } else parentInput = flattenParent(descriptor.extends); } const childInput = toConfigInput(descriptor); const resolved = { ...parentInput ? mergeBosConfigWithExtends(parentInput, childInput) : childInput }; delete resolved.extends; applyDeployMap(resolved, deployMap[name]); return resolved; } function flattenParent(parentRaw) { const parentDescriptor = AppDescriptorSchema.parse(parentRaw); if (parentDescriptor.extends) { const parentName = typeof parentDescriptor.extends === "string" ? parentDescriptor.extends : parentDescriptor.extends.name; throw new Error(`extends chains deeper than one level are not supported yet ("${parentDescriptor.name}" extends "${parentName ?? "?"}") — wayfinder ticket 08`); } return toConfigInput(parentDescriptor); } /** * Dev-time overlay — merged child-wins over a resolved config by the dev * runner (ports, local-vs-remote sources, seeding). Overlays are never * published: they exist only in `bos.dev.ts` and apply when the environment * is development. */ function applyDevOverlay(resolved, overlay) { return mergeBosConfigWithExtends(resolved, toConfigInput(overlay)); } /** Resolve every app in the registry. */ function resolveApps(registry, deployMap = {}) { return Object.fromEntries(Object.keys(registry).map((name) => [name, resolveApp(name, registry, deployMap)])); } /** * The inverse of `toConfigInput` for the scaffold surface: an authoring-shape * config (as personalizeConfig computes it) back into the authored * descriptor form a child's `bos.app.ts` embeds. Driven by the same field * maps as `toConfigInput` so the two directions cannot drift; pipeline state * (production URLs, integrity) is dropped — it is deploy-map territory, * never authored. */ function configInputToDescriptor(input) { const source = input; const descriptor = { name: input.domain ?? input.account ?? "child.app" }; for (const field of [ "extends", "account", "domain", "title", "description", "repository", "testnet", "staging", "starter", "ci", "cdn", "publish" ]) if (source[field] !== void 0) descriptor[field] = source[field]; const app = input.app ?? {}; const slot = (key) => { const entry = app[key]; if (!entry?.development) return void 0; const out = { path: stripLocal(entry.development) }; for (const field of ATTACHMENT_FIELDS) { if (field === "name") continue; if (entry[field] !== void 0) out[field] = entry[field]; } return out; }; for (const key of [ "host", "ui", "api" ]) { const value = slot(key); if (value) descriptor[key] = value; } const auth = app.auth; if (auth?.development) descriptor.auth = entryToAttachment("auth", auth); const plugins = {}; for (const [key, raw] of Object.entries(input.plugins ?? {})) if (typeof raw === "string") plugins[key] = { name: key, extends: raw }; else if (raw && typeof raw === "object") plugins[key] = entryToAttachment(key, raw); if (Object.keys(plugins).length > 0) descriptor.plugins = plugins; return AppDescriptorSchema.parse(descriptor); } /** Config-entry → authored attachment: name/extends + the shared field map. */ function entryToAttachment(key, entry) { const out = {}; for (const field of ENTRY_ONLY_FIELDS) { const value = entry[field]; if (value !== void 0) out[field] = field === "name" ? value : value; else if (field === "name") out.name = key; } const development = entry.development; const path = stripLocal(development); if (path !== void 0) out.path = path; for (const field of ATTACHMENT_FIELDS) { if (field === "name") continue; if (entry[field] !== void 0) out[field] = entry[field]; } const ui = entry.ui; if (ui && typeof ui === "object") { const uiPath = stripLocal(ui.development); out.ui = { ...typeof ui.name === "string" ? { name: ui.name } : {}, ...uiPath !== void 0 ? { path: uiPath } : {}, ...ui.integrity !== void 0 ? { integrity: ui.integrity } : {}, ...ui.pin !== void 0 ? { pin: ui.pin } : {} }; } return out; } //#endregion export { ATTACHMENT_FIELDS, SLOT_INPUT_FIELDS, applyDevOverlay, configInputToDescriptor, resolveApp, resolveApps, toConfigInput }; //# sourceMappingURL=resolve.mjs.map