UNPKG

everything-dev

Version:

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

1 lines 15.8 kB
{"version":3,"file":"shared-deps.cjs","names":["isPlainObject","SharedDepMapSchema","rebuildOrderedConfig"],"sources":["../src/shared-deps.ts"],"sourcesContent":["import { createHash } from \"node:crypto\";\nimport { existsSync, mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\nimport { type BosEnv, isPlainObject, type ResolvedConfigMeta, rebuildOrderedConfig } from \"./merge\";\nimport { type SharedDepConfig, SharedDepMapSchema } from \"./types\";\n\ninterface PackageJson {\n workspaces?: {\n packages?: string[];\n catalog?: Record<string, string>;\n };\n}\n\ninterface SharedDepRef {\n source: string;\n config: SharedDepConfig;\n}\n\ntype SharedDepsConfig = Record<string, unknown>;\n\ninterface NormalizedSharedDepConfig {\n version: string;\n requiredVersion: string | false;\n singleton: boolean;\n eager: boolean;\n strictVersion: boolean;\n shareScope: string;\n}\n\nexport interface ResolvedSharedDep {\n name: string;\n version: string;\n requiredVersion: string;\n shareScope: string;\n singleton: boolean;\n eager: boolean;\n strictVersion: boolean;\n sources: string[];\n}\n\nexport interface ResolvedSharedDeps {\n deps: Record<string, ResolvedSharedDep>;\n fingerprintSha256: string;\n}\n\nexport interface SharedDepsSyncResult {\n mode: \"catalog->bos\" | \"bos->catalog\";\n hostMode: \"local\" | \"remote\";\n bosConfigChanged: boolean;\n catalogChanged: boolean;\n generatedChanged: boolean;\n resolved: ResolvedSharedDeps;\n}\n\nfunction sha256(input: string): string {\n return createHash(\"sha256\").update(input).digest(\"hex\");\n}\n\nfunction extractSemverExact(input: unknown): string | null {\n if (typeof input !== \"string\") return null;\n const match = input.match(/\\d+\\.\\d+\\.\\d+(?:-[0-9A-Za-z.-]+)?/);\n return match ? match[0] : null;\n}\n\nfunction caretRange(version: string): string {\n return `^${version}`;\n}\n\nfunction stableDepsObject(\n deps: Record<string, ResolvedSharedDep>,\n): Record<string, ResolvedSharedDep> {\n const keys = Object.keys(deps).sort((a, b) => a.localeCompare(b));\n const out: Record<string, ResolvedSharedDep> = {};\n for (const key of keys) {\n out[key] = deps[key]!;\n }\n return out;\n}\n\nfunction normalizeSharedDepConfig(config: SharedDepConfig): NormalizedSharedDepConfig {\n return {\n version: config.version,\n requiredVersion: config.requiredVersion ?? false,\n singleton: config.singleton ?? false,\n eager: config.eager ?? false,\n strictVersion: config.strictVersion ?? false,\n shareScope: config.shareScope ?? \"default\",\n };\n}\n\nfunction getObject(value: unknown): Record<string, unknown> | undefined {\n return isPlainObject(value) ? value : undefined;\n}\n\nfunction getSharedDepsMap(\n value: unknown,\n source: string,\n): Record<string, SharedDepConfig> | undefined {\n if (value === undefined) return undefined;\n const parsed = SharedDepMapSchema.safeParse(value);\n if (!parsed.success) {\n throw new Error(`Invalid shared dependency map at ${source}`);\n }\n return value as Record<string, SharedDepConfig>;\n}\n\nfunction writeFileIfChanged(filePath: string, nextContent: string): boolean {\n try {\n const current = readFileSync(filePath, \"utf-8\");\n if (current === nextContent) return false;\n } catch {\n // file does not exist yet\n }\n\n writeFileSync(filePath, nextContent);\n return true;\n}\n\nfunction fingerprintResolved(deps: Record<string, ResolvedSharedDep>): string {\n return sha256(JSON.stringify(stableDepsObject(deps)));\n}\n\nfunction isSameSharedDepConfig(a: SharedDepConfig, b: SharedDepConfig): boolean {\n const left = normalizeSharedDepConfig(a);\n const right = normalizeSharedDepConfig(b);\n\n return (\n left.version === right.version &&\n left.requiredVersion === right.requiredVersion &&\n left.singleton === right.singleton &&\n left.eager === right.eager &&\n left.strictVersion === right.strictVersion &&\n left.shareScope === right.shareScope\n );\n}\n\nfunction collectSharedDepRefs(bosConfig: SharedDepsConfig): Record<string, SharedDepRef[]> {\n const refs = new Map<string, SharedDepRef[]>();\n\n const app = getObject(bosConfig.app);\n const appUi = getObject(app?.ui);\n const appApi = getObject(app?.api);\n const appAuth = getObject(app?.auth);\n const plugins = getObject(bosConfig.plugins);\n\n if (appUi && \"shared\" in appUi) {\n throw new Error(\n \"app.ui.shared is no longer supported. Move shared deps to app.api.shared, app.auth.shared, or plugins.*.shared.\",\n );\n }\n\n const append = (source: string, shared: Record<string, SharedDepConfig> | undefined) => {\n if (!shared) return;\n\n for (const [name, config] of Object.entries(shared)) {\n const existing = refs.get(name);\n if (!existing) {\n refs.set(name, [{ source, config }]);\n continue;\n }\n\n if (!isSameSharedDepConfig(existing[0]!.config, config)) {\n const previous = existing.map((ref) => ref.source).join(\", \");\n throw new Error(\n `Conflicting shared dependency \"${name}\" between ${previous} and ${source}`,\n );\n }\n\n existing.push({ source, config });\n }\n };\n\n append(\"app.api\", getSharedDepsMap(appApi?.shared, \"app.api.shared\"));\n append(\"app.auth\", getSharedDepsMap(appAuth?.shared, \"app.auth.shared\"));\n\n for (const [pluginId, plugin] of Object.entries(plugins ?? {})) {\n const pluginRecord = getObject(plugin);\n if (!pluginRecord) continue;\n\n const pluginApp = getObject(pluginRecord.app);\n const pluginUi = getObject(pluginApp?.ui);\n if (pluginUi && \"shared\" in pluginUi) {\n throw new Error(\n `app.ui.shared is no longer supported in plugins.${pluginId}. Move shared deps to app.api.shared, app.auth.shared, or plugins.*.shared.`,\n );\n }\n append(\n `plugins.${pluginId}`,\n getSharedDepsMap(pluginRecord.shared, `plugins.${pluginId}.shared`),\n );\n }\n\n return Object.fromEntries(refs);\n}\n\nexport async function syncResolvedSharedDeps(opts: {\n configDir: string;\n hostMode: \"local\" | \"remote\";\n bosConfig?: SharedDepsConfig;\n env?: BosEnv;\n extendsChain?: string[];\n}): Promise<SharedDepsSyncResult> {\n const bosConfigPath = join(opts.configDir, \"bos.config.json\");\n const resolvedConfigPath = join(opts.configDir, \".bos\", \"bos.resolved-config.json\");\n const packageJsonPath = join(opts.configDir, \"package.json\");\n const generatedPath = join(opts.configDir, \".bos\", \"generated\", \"shared-deps.json\");\n\n const bosConfig: unknown = opts.bosConfig ?? JSON.parse(readFileSync(bosConfigPath, \"utf-8\"));\n if (!isPlainObject(bosConfig)) {\n throw new Error(\"bos.config.json must be an object\");\n }\n\n const pkgJson = existsSync(packageJsonPath)\n ? (JSON.parse(readFileSync(packageJsonPath, \"utf-8\")) as PackageJson)\n : {};\n\n const originalBos = JSON.stringify(bosConfig);\n const originalPkg = JSON.stringify(pkgJson);\n\n const mode = opts.hostMode === \"local\" ? \"catalog->bos\" : \"bos->catalog\";\n const refsByName = collectSharedDepRefs(bosConfig);\n const catalog = pkgJson.workspaces?.catalog ?? {};\n\n const resolvedDeps: Record<string, ResolvedSharedDep> = {};\n\n for (const [name, refs] of Object.entries(refsByName)) {\n const first = refs[0];\n if (!first) continue;\n\n const exactFromConfig =\n extractSemverExact(first.config.version) ?? extractSemverExact(first.config.requiredVersion);\n const exactFromCatalog = extractSemverExact(catalog[name]);\n const version =\n mode === \"catalog->bos\"\n ? (exactFromCatalog ?? exactFromConfig)\n : (exactFromConfig ?? exactFromCatalog);\n\n if (!version) {\n const sources = refs.map((ref) => ref.source).join(\", \");\n throw new Error(\n `Could not resolve exact version for shared dependency \"${name}\" from ${sources}`,\n );\n }\n\n if (mode === \"catalog->bos\" && exactFromCatalog === null && exactFromConfig) {\n catalog[name] = exactFromConfig;\n }\n\n if (mode === \"bos->catalog\" && catalog[name] !== version) {\n catalog[name] = version;\n }\n\n for (const ref of refs) {\n ref.config.version = version;\n ref.config.requiredVersion = caretRange(version);\n ref.config.shareScope = ref.config.shareScope ?? \"default\";\n }\n\n resolvedDeps[name] = {\n name,\n version,\n requiredVersion: caretRange(version),\n shareScope: first.config.shareScope ?? \"default\",\n singleton: first.config.singleton ?? false,\n eager: first.config.eager ?? false,\n strictVersion: first.config.strictVersion ?? false,\n sources: refs.map((ref) => ref.source).sort((a, b) => a.localeCompare(b)),\n };\n }\n\n if (!pkgJson.workspaces) {\n pkgJson.workspaces = { packages: [], catalog: {} };\n }\n pkgJson.workspaces.catalog = catalog;\n\n const nextBos = JSON.stringify(bosConfig);\n const nextPkg = JSON.stringify(pkgJson);\n const bosConfigChanged = nextBos !== originalBos;\n const catalogChanged = nextPkg !== originalPkg;\n\n if (bosConfigChanged) {\n const resolvedDir = dirname(resolvedConfigPath);\n if (!existsSync(resolvedDir)) {\n mkdirSync(resolvedDir, { recursive: true });\n }\n\n const ordered = rebuildOrderedConfig(bosConfig);\n const meta: ResolvedConfigMeta = {\n env: opts.env ?? \"development\",\n resolvedAt: new Date().toISOString(),\n extendsChain: opts.extendsChain ?? [],\n source: \"shared-sync\",\n };\n const resolvedOutput = {\n _resolved: meta,\n ...ordered,\n };\n\n writeFileIfChanged(resolvedConfigPath, `${JSON.stringify(resolvedOutput, null, 2)}\\n`);\n }\n\n if (catalogChanged) {\n writeFileIfChanged(packageJsonPath, `${JSON.stringify(pkgJson, null, 2)}\\n`);\n }\n\n const stableResolvedDeps = stableDepsObject(resolvedDeps);\n const resolved: ResolvedSharedDeps = {\n deps: stableResolvedDeps,\n fingerprintSha256: fingerprintResolved(stableResolvedDeps),\n };\n\n const nextGenerated = {\n schemaVersion: 1,\n kind: \"everything-dev/shared-deps\",\n generatedAt: new Date().toISOString(),\n deps: stableResolvedDeps,\n fingerprintSha256: resolved.fingerprintSha256,\n inputs: {\n mode,\n hostMode: opts.hostMode,\n },\n };\n\n let prevFingerprint: string | null = null;\n try {\n const prev = JSON.parse(readFileSync(generatedPath, \"utf-8\"));\n prevFingerprint = prev?.fingerprintSha256 ?? null;\n } catch {\n // ignore\n }\n\n mkdirSync(dirname(generatedPath), { recursive: true });\n writeFileIfChanged(generatedPath, `${JSON.stringify(nextGenerated, null, 2)}\\n`);\n\n const generatedChanged = prevFingerprint !== nextGenerated.fingerprintSha256;\n\n return {\n mode,\n hostMode: opts.hostMode,\n bosConfigChanged,\n catalogChanged,\n generatedChanged,\n resolved,\n };\n}\n"],"mappings":";;;;;;;;AAsDA,SAAS,OAAO,OAAuB;AACrC,oCAAkB,SAAS,CAAC,OAAO,MAAM,CAAC,OAAO,MAAM;;AAGzD,SAAS,mBAAmB,OAA+B;AACzD,KAAI,OAAO,UAAU,SAAU,QAAO;CACtC,MAAM,QAAQ,MAAM,MAAM,oCAAoC;AAC9D,QAAO,QAAQ,MAAM,KAAK;;AAG5B,SAAS,WAAW,SAAyB;AAC3C,QAAO,IAAI;;AAGb,SAAS,iBACP,MACmC;CACnC,MAAM,OAAO,OAAO,KAAK,KAAK,CAAC,MAAM,GAAG,MAAM,EAAE,cAAc,EAAE,CAAC;CACjE,MAAM,MAAyC,EAAE;AACjD,MAAK,MAAM,OAAO,KAChB,KAAI,OAAO,KAAK;AAElB,QAAO;;AAGT,SAAS,yBAAyB,QAAoD;AACpF,QAAO;EACL,SAAS,OAAO;EAChB,iBAAiB,OAAO,mBAAmB;EAC3C,WAAW,OAAO,aAAa;EAC/B,OAAO,OAAO,SAAS;EACvB,eAAe,OAAO,iBAAiB;EACvC,YAAY,OAAO,cAAc;EAClC;;AAGH,SAAS,UAAU,OAAqD;AACtE,QAAOA,4BAAc,MAAM,GAAG,QAAQ;;AAGxC,SAAS,iBACP,OACA,QAC6C;AAC7C,KAAI,UAAU,OAAW,QAAO;AAEhC,KAAI,CADWC,iCAAmB,UAAU,MACjC,CAAC,QACV,OAAM,IAAI,MAAM,oCAAoC,SAAS;AAE/D,QAAO;;AAGT,SAAS,mBAAmB,UAAkB,aAA8B;AAC1E,KAAI;AAEF,gCAD6B,UAAU,QAC5B,KAAK,YAAa,QAAO;SAC9B;AAIR,4BAAc,UAAU,YAAY;AACpC,QAAO;;AAGT,SAAS,oBAAoB,MAAiD;AAC5E,QAAO,OAAO,KAAK,UAAU,iBAAiB,KAAK,CAAC,CAAC;;AAGvD,SAAS,sBAAsB,GAAoB,GAA6B;CAC9E,MAAM,OAAO,yBAAyB,EAAE;CACxC,MAAM,QAAQ,yBAAyB,EAAE;AAEzC,QACE,KAAK,YAAY,MAAM,WACvB,KAAK,oBAAoB,MAAM,mBAC/B,KAAK,cAAc,MAAM,aACzB,KAAK,UAAU,MAAM,SACrB,KAAK,kBAAkB,MAAM,iBAC7B,KAAK,eAAe,MAAM;;AAI9B,SAAS,qBAAqB,WAA6D;CACzF,MAAM,uBAAO,IAAI,KAA6B;CAE9C,MAAM,MAAM,UAAU,UAAU,IAAI;CACpC,MAAM,QAAQ,UAAU,KAAK,GAAG;CAChC,MAAM,SAAS,UAAU,KAAK,IAAI;CAClC,MAAM,UAAU,UAAU,KAAK,KAAK;CACpC,MAAM,UAAU,UAAU,UAAU,QAAQ;AAE5C,KAAI,SAAS,YAAY,MACvB,OAAM,IAAI,MACR,kHACD;CAGH,MAAM,UAAU,QAAgB,WAAwD;AACtF,MAAI,CAAC,OAAQ;AAEb,OAAK,MAAM,CAAC,MAAM,WAAW,OAAO,QAAQ,OAAO,EAAE;GACnD,MAAM,WAAW,KAAK,IAAI,KAAK;AAC/B,OAAI,CAAC,UAAU;AACb,SAAK,IAAI,MAAM,CAAC;KAAE;KAAQ;KAAQ,CAAC,CAAC;AACpC;;AAGF,OAAI,CAAC,sBAAsB,SAAS,GAAI,QAAQ,OAAO,EAAE;IACvD,MAAM,WAAW,SAAS,KAAK,QAAQ,IAAI,OAAO,CAAC,KAAK,KAAK;AAC7D,UAAM,IAAI,MACR,kCAAkC,KAAK,YAAY,SAAS,OAAO,SACpE;;AAGH,YAAS,KAAK;IAAE;IAAQ;IAAQ,CAAC;;;AAIrC,QAAO,WAAW,iBAAiB,QAAQ,QAAQ,iBAAiB,CAAC;AACrE,QAAO,YAAY,iBAAiB,SAAS,QAAQ,kBAAkB,CAAC;AAExE,MAAK,MAAM,CAAC,UAAU,WAAW,OAAO,QAAQ,WAAW,EAAE,CAAC,EAAE;EAC9D,MAAM,eAAe,UAAU,OAAO;AACtC,MAAI,CAAC,aAAc;EAGnB,MAAM,WAAW,UADC,UAAU,aAAa,IACL,EAAE,GAAG;AACzC,MAAI,YAAY,YAAY,SAC1B,OAAM,IAAI,MACR,mDAAmD,SAAS,6EAC7D;AAEH,SACE,WAAW,YACX,iBAAiB,aAAa,QAAQ,WAAW,SAAS,SAAS,CACpE;;AAGH,QAAO,OAAO,YAAY,KAAK;;AAGjC,eAAsB,uBAAuB,MAMX;CAChC,MAAM,oCAAqB,KAAK,WAAW,kBAAkB;CAC7D,MAAM,yCAA0B,KAAK,WAAW,QAAQ,2BAA2B;CACnF,MAAM,sCAAuB,KAAK,WAAW,eAAe;CAC5D,MAAM,oCAAqB,KAAK,WAAW,QAAQ,aAAa,mBAAmB;CAEnF,MAAM,YAAqB,KAAK,aAAa,KAAK,gCAAmB,eAAe,QAAQ,CAAC;AAC7F,KAAI,CAACD,4BAAc,UAAU,CAC3B,OAAM,IAAI,MAAM,oCAAoC;CAGtD,MAAM,kCAAqB,gBAAgB,GACtC,KAAK,gCAAmB,iBAAiB,QAAQ,CAAC,GACnD,EAAE;CAEN,MAAM,cAAc,KAAK,UAAU,UAAU;CAC7C,MAAM,cAAc,KAAK,UAAU,QAAQ;CAE3C,MAAM,OAAO,KAAK,aAAa,UAAU,iBAAiB;CAC1D,MAAM,aAAa,qBAAqB,UAAU;CAClD,MAAM,UAAU,QAAQ,YAAY,WAAW,EAAE;CAEjD,MAAM,eAAkD,EAAE;AAE1D,MAAK,MAAM,CAAC,MAAM,SAAS,OAAO,QAAQ,WAAW,EAAE;EACrD,MAAM,QAAQ,KAAK;AACnB,MAAI,CAAC,MAAO;EAEZ,MAAM,kBACJ,mBAAmB,MAAM,OAAO,QAAQ,IAAI,mBAAmB,MAAM,OAAO,gBAAgB;EAC9F,MAAM,mBAAmB,mBAAmB,QAAQ,MAAM;EAC1D,MAAM,UACJ,SAAS,iBACJ,oBAAoB,kBACpB,mBAAmB;AAE1B,MAAI,CAAC,SAAS;GACZ,MAAM,UAAU,KAAK,KAAK,QAAQ,IAAI,OAAO,CAAC,KAAK,KAAK;AACxD,SAAM,IAAI,MACR,0DAA0D,KAAK,SAAS,UACzE;;AAGH,MAAI,SAAS,kBAAkB,qBAAqB,QAAQ,gBAC1D,SAAQ,QAAQ;AAGlB,MAAI,SAAS,kBAAkB,QAAQ,UAAU,QAC/C,SAAQ,QAAQ;AAGlB,OAAK,MAAM,OAAO,MAAM;AACtB,OAAI,OAAO,UAAU;AACrB,OAAI,OAAO,kBAAkB,WAAW,QAAQ;AAChD,OAAI,OAAO,aAAa,IAAI,OAAO,cAAc;;AAGnD,eAAa,QAAQ;GACnB;GACA;GACA,iBAAiB,WAAW,QAAQ;GACpC,YAAY,MAAM,OAAO,cAAc;GACvC,WAAW,MAAM,OAAO,aAAa;GACrC,OAAO,MAAM,OAAO,SAAS;GAC7B,eAAe,MAAM,OAAO,iBAAiB;GAC7C,SAAS,KAAK,KAAK,QAAQ,IAAI,OAAO,CAAC,MAAM,GAAG,MAAM,EAAE,cAAc,EAAE,CAAC;GAC1E;;AAGH,KAAI,CAAC,QAAQ,WACX,SAAQ,aAAa;EAAE,UAAU,EAAE;EAAE,SAAS,EAAE;EAAE;AAEpD,SAAQ,WAAW,UAAU;CAE7B,MAAM,UAAU,KAAK,UAAU,UAAU;CACzC,MAAM,UAAU,KAAK,UAAU,QAAQ;CACvC,MAAM,mBAAmB,YAAY;CACrC,MAAM,iBAAiB,YAAY;AAEnC,KAAI,kBAAkB;EACpB,MAAM,qCAAsB,mBAAmB;AAC/C,MAAI,yBAAY,YAAY,CAC1B,wBAAU,aAAa,EAAE,WAAW,MAAM,CAAC;EAG7C,MAAM,UAAUE,mCAAqB,UAAU;EAO/C,MAAM,iBAAiB;GACrB,WAAW;IANX,KAAK,KAAK,OAAO;IACjB,6BAAY,IAAI,MAAM,EAAC,aAAa;IACpC,cAAc,KAAK,gBAAgB,EAAE;IACrC,QAAQ;IAGO;GACf,GAAG;GACJ;AAED,qBAAmB,oBAAoB,GAAG,KAAK,UAAU,gBAAgB,MAAM,EAAE,CAAC,IAAI;;AAGxF,KAAI,eACF,oBAAmB,iBAAiB,GAAG,KAAK,UAAU,SAAS,MAAM,EAAE,CAAC,IAAI;CAG9E,MAAM,qBAAqB,iBAAiB,aAAa;CACzD,MAAM,WAA+B;EACnC,MAAM;EACN,mBAAmB,oBAAoB,mBAAmB;EAC3D;CAED,MAAM,gBAAgB;EACpB,eAAe;EACf,MAAM;EACN,8BAAa,IAAI,MAAM,EAAC,aAAa;EACrC,MAAM;EACN,mBAAmB,SAAS;EAC5B,QAAQ;GACN;GACA,UAAU,KAAK;GAChB;EACF;CAED,IAAI,kBAAiC;AACrC,KAAI;AAEF,oBADa,KAAK,gCAAmB,eAAe,QAAQ,CACtC,EAAE,qBAAqB;SACvC;AAIR,+CAAkB,cAAc,EAAE,EAAE,WAAW,MAAM,CAAC;AACtD,oBAAmB,eAAe,GAAG,KAAK,UAAU,eAAe,MAAM,EAAE,CAAC,IAAI;CAEhF,MAAM,mBAAmB,oBAAoB,cAAc;AAE3D,QAAO;EACL;EACA,UAAU,KAAK;EACf;EACA;EACA;EACA;EACD"}