{"version":3,"file":"version-manifest-resolve.cjs","names":["bundleUrlToStagedPath","existsSync","resolveContained","path","readdirSync","statSync","WorkspaceVersionManifestSchema","readFileSync","createHash"],"sources":["../src/version-manifest-resolve.ts"],"sourcesContent":["import { createHash } from \"node:crypto\";\nimport { existsSync, readdirSync, readFileSync, statSync } from \"node:fs\";\nimport path from \"node:path\";\nimport {\n  type WorkspaceVersionManifest,\n  WorkspaceVersionManifestSchema,\n} from \"every-plugin/version-manifest\";\nimport { bundleUrlToStagedPath, resolveContained } from \"./bundle-path\";\n\n/**\n * Version-manifest slot resolution (atomic-deploys 04): a config slot's\n * `pin` ({ manifest, integrity }) resolves to the derived entry-level fields\n * every consumer needs. The cache is keyed by the full pin — the pinned\n * bytes are immutable, so a verified entry is cached for the process\n * lifetime and serves as last-known-good if the origin later fails; a fetch\n * or SRI failure with nothing cached propagates (a boot-time resolution\n * failure fails the deploy, which is the platform's rollback).\n */\n\nexport interface ResolvedSlotVersion {\n  /** Absolute URL of the content-hashed container entry. */\n  entryUrl: string;\n  entryIntegrity: string;\n  /** Absolute URL of the hashed browser manifest (mf-manifest), when present. */\n  browserManifestUrl?: string;\n  /** Absolute URL of the content-hashed SSR entry, when present. */\n  ssrEntryUrl?: string;\n  ssrIntegrity?: string;\n  /** Full per-file digest map of the dist (object path → SRI), when the\n   * pinned manifest carries one — the diff base for skip-unchanged uploads. */\n  files?: Record<string, string>;\n}\n\nexport interface SlotPin {\n  /** versioned manifest filename, relative to the slot's production base */\n  manifest: string;\n  /** the manifest document's own SRI */\n  integrity: string;\n}\n\nconst cache = new Map<string, ResolvedSlotVersion>();\n\nexport function clearSlotVersionCache(): void {\n  cache.clear();\n}\n\nfunction fetchText(url: string, fetchImpl: typeof fetch): Promise<string> {\n  return fetchImpl(url).then((response) => {\n    if (!response.ok) throw new Error(`version manifest fetch failed: ${response.status}`);\n    return response.text();\n  });\n}\n\nfunction slotVersionFromManifest(\n  base: string,\n  manifest: WorkspaceVersionManifest,\n): ResolvedSlotVersion {\n  const trimmedBase = base.replace(/\\/$/, \"\");\n  return {\n    entryUrl: `${trimmedBase}/${manifest.entry}`,\n    entryIntegrity: manifest.entryIntegrity,\n    ...(manifest.browserManifest\n      ? { browserManifestUrl: `${trimmedBase}/${manifest.browserManifest.file}` }\n      : {}),\n    ...(manifest.ssr\n      ? {\n          ssrEntryUrl: `${trimmedBase}/${manifest.ssr.entry}`,\n          ssrIntegrity: manifest.ssr.integrity,\n        }\n      : {}),\n    ...(manifest.files ? { files: manifest.files } : {}),\n  };\n}\n\n/**\n * Staged-slot preference (ADR 0020/0021 self-contained tier): when a runtime\n * stages its own namespace under `BOS_BUNDLE_DIR`, a slot resolves from the\n * staged dist's own version manifest instead of the config's pin — pinned\n * manifest bytes that predate the image (deploy lag after a release bump)\n * cannot exist in the staged namespace, so their hashed files would 404 the\n * disk and fall through to the network, where the stale immutable bytes\n * still answer 200 and boot a foreign build. The staged manifest is trusted\n * (staged by the image build itself) and skipped entirely when the slot is\n * absent from the staged namespace or the pinned manifest is present (the\n * normal healthy case, which keeps the pin's SRI check).\n */\nfunction readStagedSlotVersion(base: string, pin: SlotPin): ResolvedSlotVersion | null {\n  const bundleDir = process.env.BOS_BUNDLE_DIR;\n  if (!bundleDir) return null;\n\n  const slotDir = bundleUrlToStagedPath(base, bundleDir);\n  if (!slotDir || !existsSync(slotDir)) return null;\n\n  const pinnedManifestPath = resolveContained(slotDir, pin.manifest);\n  if (!pinnedManifestPath || existsSync(pinnedManifestPath)) return null;\n\n  const versionsDir = path.join(slotDir, \"versions\");\n  if (!existsSync(versionsDir)) return null;\n  const manifests = readdirSync(versionsDir)\n    .filter((name) => name.endsWith(\".json\"))\n    .map((name) => path.join(versionsDir, name))\n    .filter((file) => statSync(file).isFile())\n    .sort((a, b) => statSync(b).mtimeMs - statSync(a).mtimeMs || (a < b ? -1 : 1));\n  if (manifests.length === 0) return null;\n  const manifestPath = manifests[0];\n  if (!manifestPath) return null;\n\n  try {\n    const manifest = WorkspaceVersionManifestSchema.parse(\n      JSON.parse(readFileSync(manifestPath, \"utf8\")),\n    );\n    return slotVersionFromManifest(base, manifest);\n  } catch {\n    return null;\n  }\n}\n\nexport async function resolveSlotVersion(input: {\n  base: string;\n  pin: SlotPin;\n  fetchImpl?: typeof fetch;\n}): Promise<ResolvedSlotVersion> {\n  const key = `${input.base}::${input.pin.manifest}::${input.pin.integrity}`;\n  // content-addressed: a verified pin is cached for the process lifetime and\n  // never refetched — the cache IS the last-known-good\n  const cached = cache.get(key);\n  if (cached) return cached;\n\n  const staged = readStagedSlotVersion(input.base, input.pin);\n  if (staged) {\n    cache.set(key, staged);\n    return staged;\n  }\n\n  const manifestUrl = `${input.base.replace(/\\/$/, \"\")}/${input.pin.manifest.replace(/^\\//, \"\")}`;\n  const body = await fetchText(manifestUrl, input.fetchImpl ?? fetch);\n  const computed = `sha384-${createHash(\"sha384\").update(body).digest(\"base64\")}`;\n  if (computed !== input.pin.integrity) {\n    throw new Error(\n      `[SRI] version manifest integrity mismatch for ${manifestUrl}\\n  Expected: ${input.pin.integrity}\\n  Computed: ${computed}`,\n    );\n  }\n  const manifest: WorkspaceVersionManifest = WorkspaceVersionManifestSchema.parse(JSON.parse(body));\n\n  const resolved = slotVersionFromManifest(input.base, manifest);\n  cache.set(key, resolved);\n  return resolved;\n}\n"],"mappings":"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"}