{"version":3,"file":"init-source.mjs","names":["tarExtract"],"sources":["../../../src/services/resolution/init-source.ts"],"sourcesContent":["import { createWriteStream, mkdtempSync, readFileSync, rmSync } from \"node:fs\";\nimport { tmpdir } from \"node:os\";\nimport { basename, join, resolve } from \"node:path\";\nimport { pipeline } from \"node:stream/promises\";\nimport { execa } from \"execa\";\nimport { extract as tarExtract } from \"tar\";\nimport { findConfigPath, isAppDescriptorPath, loadAppDescriptorConfig } from \"../../config\";\nimport type { BosConfig } from \"../../types\";\nimport { getExtendsRef, parseBosRef } from \"../../utils/bos-ref\";\nimport { fetchResponse } from \"../../utils/http-client\";\nimport { fetchBosConfigFromFastKv } from \"../registry/fastkv\";\nimport { readWorkspaceCatalog } from \"../workspace/catalog\";\nimport { walkExtendsChain } from \"./session\";\n\nexport { readWorkspaceCatalog };\n\nexport interface SourceResult {\n  sourceDir: string;\n  parentConfig: BosConfig;\n  cleanup: () => Promise<void>;\n  /** The ref the tarball actually resolved from — release provenance (ADR 0034). */\n  resolvedRef?: string;\n  resolvedSha?: string;\n}\n\nexport interface CatalogChainSource {\n  catalog: Record<string, string>;\n  repository?: string;\n  extendsChain: string[];\n}\n\nexport async function resolveCatalogChainSource(opts: {\n  /** Local source root (e.g. a downloaded release tarball) — the walk is\n   * always local now: the platform manifest is the chain's catalog authority\n   * (ADR 0034), so no network-side catalog walk exists. */\n  sourceDir: string;\n  /** The bos:// ref the caller resolved this chain from — recorded as the\n   * chain head for provenance (writeResolvedConfig's extendsChain) instead of\n   * the entry's own file path. */\n  originRef?: string;\n  /** Legacy head-ref spelling: synthesized into originRef when originRef is\n   * absent. */\n  extendsAccount?: string;\n  extendsGateway?: string;\n}): Promise<CatalogChainSource> {\n  const entry =\n    findConfigPath(resolve(opts.sourceDir)) ?? join(resolve(opts.sourceDir), \"bos.config.json\");\n  const catalogs: Record<string, string>[] = [];\n  const cleanups: Array<() => Promise<void>> = [];\n  const resolvedRefs: string[] = [];\n  let repository: string | undefined;\n\n  try {\n    await walkExtendsChain(entry, {\n      env: \"production\",\n      collectCatalogs: true,\n      registerCleanup: (fn) => cleanups.push(fn),\n      visit: async (link) => {\n        resolvedRefs.unshift(\n          link.ref.startsWith(\"bos://\") ? link.ref : join(link.baseDir, basename(link.ref)),\n        );\n        catalogs.push(readWorkspaceCatalog(link.sourceDir ?? link.baseDir));\n        const repositoryValue = (link.config as Record<string, unknown>).repository;\n        if (typeof repositoryValue === \"string\" && repository === undefined) {\n          repository = repositoryValue;\n        }\n      },\n    });\n  } finally {\n    for (const cleanup of [...cleanups].reverse()) {\n      await cleanup();\n    }\n  }\n\n  const originRef =\n    opts.originRef ??\n    (opts.extendsAccount !== undefined && opts.extendsGateway !== undefined\n      ? `bos://${opts.extendsAccount}/${opts.extendsGateway}`\n      : undefined);\n\n  return {\n    catalog: Object.assign({}, ...catalogs),\n    repository,\n    extendsChain: originRef ? [originRef, ...resolvedRefs.slice(1)] : resolvedRefs,\n  };\n}\n\nexport async function resolveSourceDir(opts: {\n  extendsAccount: string;\n  extendsGateway: string;\n  source?: string;\n  /** Release ref pinning the tarball (ADR 0034): the canonical `v<version>`\n   * tag. Absent → fail loud — the scaffold surface never pulls a moving\n   * branch head, only released bytes. */\n  ref?: string;\n}): Promise<SourceResult> {\n  if (opts.source) {\n    const sourceDir = resolve(opts.source);\n    const configPath = findConfigPath(sourceDir);\n    if (!configPath) {\n      throw new Error(\n        `No authored config (bos.app.ts or bos.config.json) found in source directory: ${sourceDir}`,\n      );\n    }\n    const parentConfig = isAppDescriptorPath(configPath)\n      ? ((await loadAppDescriptorConfig(configPath)) as BosConfig)\n      : (JSON.parse(readFileSync(configPath, \"utf-8\")) as BosConfig);\n    return { sourceDir, parentConfig, cleanup: async () => {} };\n  }\n\n  const parentConfig = await fetchParentConfig(opts.extendsAccount, opts.extendsGateway);\n\n  if (opts.ref) {\n    if (!parentConfig.repository) {\n      throw new Error(\n        `Release ${opts.ref} requested but the config at bos://${opts.extendsAccount}/${opts.extendsGateway} names no repository`,\n      );\n    }\n    const {\n      dir: sourceDir,\n      cleanup,\n      resolvedRef,\n      resolvedSha,\n    } = await downloadTarball(parentConfig.repository, opts.ref);\n    return { sourceDir, parentConfig, cleanup, resolvedRef, resolvedSha };\n  }\n\n  const chainResult = await resolveRepositoryViaExtendsChain(\n    opts.extendsAccount,\n    opts.extendsGateway,\n  );\n  if (chainResult?.repository) {\n    const {\n      dir: sourceDir,\n      cleanup,\n      resolvedRef,\n      resolvedSha,\n    } = await downloadTarball(chainResult.repository, opts.ref);\n    return { sourceDir, parentConfig: chainResult.config, cleanup, resolvedRef, resolvedSha };\n  }\n\n  return {\n    sourceDir: \"\",\n    parentConfig,\n    cleanup: async () => {},\n  };\n}\n\nexport async function fetchParentConfig(\n  extendsAccount: string,\n  extendsGateway: string,\n): Promise<BosConfig> {\n  const bosUrl = `bos://${extendsAccount}/${extendsGateway}`;\n  return fetchBosConfigFromFastKv<BosConfig>(bosUrl);\n}\n\nexport async function resolveRepositoryViaExtendsChain(\n  extendsAccount: string,\n  extendsGateway: string,\n  visited = new Set<string>(),\n): Promise<{ repository: string; config: BosConfig } | null> {\n  const key = `bos://${extendsAccount}/${extendsGateway}`;\n  if (visited.has(key)) return null;\n  visited.add(key);\n\n  try {\n    const config = await fetchParentConfig(extendsAccount, extendsGateway);\n    if (config.repository) {\n      return { repository: config.repository, config };\n    }\n\n    const extendsRef = getExtendsRef(config as Record<string, unknown>);\n    if (extendsRef) {\n      const normalized = extendsRef.startsWith(\"bos://\") ? extendsRef : `bos://${extendsRef}`;\n      const parsed = parseBosRef(normalized);\n      if (parsed) {\n        const result = await resolveRepositoryViaExtendsChain(\n          parsed.account,\n          parsed.gateway,\n          visited,\n        );\n        if (result) return result;\n      }\n    }\n\n    return null;\n  } catch {\n    return null;\n  }\n}\n\n/**\n * The release tarball ref (ADR 0034 pt 4): the canonical `v<version>` tag —\n * sync's workflow pins and the release script both target it. One ref, one\n * resolution: a version names exactly one release.\n */\nexport function releaseRefCandidates(frameworkVersion: string): string {\n  return `v${frameworkVersion}`;\n}\n\nexport async function downloadTarball(\n  repoUrl: string,\n  ref?: string | string[],\n): Promise<{\n  dir: string;\n  cleanup: () => Promise<void>;\n  resolvedRef?: string;\n  resolvedSha?: string;\n}> {\n  const parsed = parseGitHubUrl(repoUrl);\n  if (!parsed) {\n    throw new Error(`Cannot parse repository URL: ${repoUrl}`);\n  }\n\n  const { owner, repo } = parsed;\n  let response: Response | null = null;\n  let resolvedRef: string | undefined;\n\n  const candidates = ref ? (Array.isArray(ref) ? [...ref] : [ref]) : [\"main\", \"master\"];\n  for (const candidateRef of candidates) {\n    const candidate = await fetchResponse(\n      `https://api.github.com/repos/${owner}/${repo}/tarball/${encodeURIComponent(candidateRef)}`,\n      {\n        headers: { \"User-Agent\": \"everything-dev\" },\n        redirect: \"follow\",\n        timeout: \"60 seconds\",\n      },\n    );\n    if (candidate.ok) {\n      response = candidate;\n      resolvedRef = candidateRef;\n      break;\n    }\n    if (candidate.status !== 404) {\n      throw new Error(\n        `GitHub tarball download failed: ${candidate.status} ${candidate.statusText}`,\n      );\n    }\n  }\n\n  if (!response) {\n    throw new Error(\n      ref\n        ? `GitHub tarball download failed for ${repoUrl}: ref not found (tried ${candidates.join(\", \")})`\n        : `GitHub tarball download failed for ${repoUrl}: tried main and master`,\n    );\n  }\n\n  if (!response.body) {\n    throw new Error(\"GitHub tarball download returned empty body\");\n  }\n\n  const resolvedSha = response.url.match(/\\/([0-9a-f]{40})(?:\\.tar\\.gz)?\\b/)?.[1];\n\n  const tmpDir = mkTmpDir(\"bos-init-tarball-\");\n  const tarballPath = join(tmpDir, \"source.tar.gz\");\n\n  const fileStream = createWriteStream(tarballPath);\n  const reader = response.body as unknown as NodeJS.ReadableStream;\n  await pipeline(reader, fileStream);\n\n  const extractDir = mkTmpDir(\"bos-init-extract-\");\n  try {\n    await tarExtract({ cwd: extractDir, file: tarballPath, strip: 1 });\n  } catch {\n    await execCommand(\"tar\", [\"-xzf\", tarballPath, \"--strip-components=1\", \"-C\", extractDir]);\n  }\n\n  rmSync(tmpDir, { recursive: true, force: true });\n\n  return {\n    dir: extractDir,\n    resolvedRef,\n    resolvedSha,\n    cleanup: async () => {\n      rmSync(extractDir, { recursive: true, force: true });\n    },\n  };\n}\n\nexport function parseGitHubUrl(url: string): { owner: string; repo: string } | null {\n  const httpsMatch = url.match(/^https?:\\/\\/github\\.com\\/([^/]+)\\/([^/]+?)(?:\\.git)?(?:\\/.*)?$/);\n  if (httpsMatch?.[1] && httpsMatch[2]) {\n    return { owner: httpsMatch[1], repo: httpsMatch[2] };\n  }\n\n  const sshMatch = url.match(/^git@github\\.com:([^/]+)\\/([^/]+?)(?:\\.git)?$/);\n  if (sshMatch?.[1] && sshMatch[2]) {\n    return { owner: sshMatch[1], repo: sshMatch[2] };\n  }\n\n  return null;\n}\n\nfunction mkTmpDir(prefix: string): string {\n  return mkdtempSync(join(tmpdir(), `${prefix}-`));\n}\nasync function execCommand(command: string, args: string[]): Promise<void> {\n  await execa(command, args, { stdio: \"pipe\", timeout: 2 * 60_000 });\n}\n"],"mappings":"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"}