{"version":3,"file":"shared-deps.mjs","names":[],"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":"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"}