{"version":3,"file":"cdn-deploy.cjs","names":[],"sources":["../src/cdn-deploy.ts"],"sourcesContent":["interface SessionCredentialLike {\n  apiKey?: string;\n  siteUrl?: string;\n  accountId?: string | null;\n}\n\nexport interface CdnDeployInputs {\n  cdnOrigin: string | undefined;\n  storageOrigin: string;\n  apiKey: string | undefined;\n  warning?: string;\n  error?: string;\n}\n\nconst LOCAL_HOSTNAMES = new Set([\"localhost\", \"127.0.0.1\", \"::1\", \"[::1]\", \"0.0.0.0\", \"local\"]);\n\n/** Local/private destinations can never serve production bundles. */\nexport function isLocalOrigin(url: string): boolean {\n  try {\n    const { hostname, protocol } = new URL(url);\n    if (protocol !== \"http:\" && protocol !== \"https:\") return true;\n    const host = hostname.toLowerCase().replace(/^\\[|\\]$/g, \"\");\n    return (\n      LOCAL_HOSTNAMES.has(host) ||\n      host.endsWith(\".localhost\") ||\n      host.endsWith(\".local\") ||\n      /^127\\.\\d+\\.\\d+\\.\\d+$/.test(host) ||\n      /^10\\.\\d+\\.\\d+\\.\\d+$/.test(host) ||\n      /^192\\.168\\.\\d+\\.\\d+$/.test(host) ||\n      /^172\\.(1[6-9]|2\\d|3[01])\\.\\d+\\.\\d+$/.test(host) ||\n      host.startsWith(\"169.254.\") ||\n      (host.includes(\":\") &&\n        (host.startsWith(\"fe80:\") || host.startsWith(\"fc\") || host.startsWith(\"fd\")))\n    );\n  } catch {\n    return true;\n  }\n}\n\n/**\n * Deploy origin resolution (ADR 0020, as amended): the CDN origin comes from\n * the env or the authored `cdn.origin` (inherited via extends) — a hard\n * requirement when uploads are planned, because the host never serves\n * `/bundles` itself. The upload origin is the env, the `bos login` session's\n * siteUrl, or the runtime's own gateway. A session pinned to a local site is\n * a hard error (the origin can only ever be a squatter's port); an env-pinned\n * local origin is deliberate local testing and only warns. Config-only\n * publishes (`uploadsPlanned: false`) ship the committed config verbatim —\n * no origins are resolved and nothing is required.\n */\nexport function resolveCdnDeployInputs(input: {\n  env: Record<string, string | undefined>;\n  bosConfig: { cdn?: { origin?: string } } | null;\n  session: SessionCredentialLike | null;\n  account: string;\n  gateway: string;\n  uploadsPlanned: boolean;\n}): CdnDeployInputs {\n  const envCdnOrigin = input.env.BOS_BUNDLE_CDN_ORIGIN?.replace(/\\/$/, \"\");\n  const configCdnOrigin = input.bosConfig?.cdn?.origin?.replace(/\\/$/, \"\");\n  const storageEnvOrigin = input.env.BOS_STORAGE_ORIGIN?.replace(/\\/$/, \"\");\n  const sessionSite = input.session?.siteUrl?.replace(/\\/$/, \"\");\n  const apiKey = input.env.BOS_STORAGE_API_KEY ?? input.session?.apiKey;\n  const uploadsPlanned = input.uploadsPlanned;\n\n  const storageOriginDefault = storageEnvOrigin ?? sessionSite ?? `https://${input.gateway}`;\n\n  if (!uploadsPlanned) {\n    return { cdnOrigin: undefined, storageOrigin: storageOriginDefault, apiKey };\n  }\n\n  let cdnOrigin: string | undefined;\n  let warning: string | undefined;\n  if (envCdnOrigin) {\n    cdnOrigin = envCdnOrigin;\n    if (isLocalOrigin(envCdnOrigin)) {\n      warning = `BOS_BUNDLE_CDN_ORIGIN is a local URL (${envCdnOrigin}) — deploying to a local stack, published bundle URLs will not resolve publicly.`;\n    }\n  } else if (configCdnOrigin) {\n    if (isLocalOrigin(configCdnOrigin)) {\n      return {\n        cdnOrigin,\n        storageOrigin: storageOriginDefault,\n        apiKey,\n        error:\n          `the authored config sets cdn.origin to the local URL ${configCdnOrigin} — deploy bundle ` +\n          \"origins must be publicly reachable. Set BOS_BUNDLE_CDN_ORIGIN for a local deploy, \" +\n          \"or fix cdn.origin (inherited via extends).\",\n      };\n    }\n    cdnOrigin = configCdnOrigin;\n  } else {\n    return {\n      cdnOrigin,\n      storageOrigin: storageOriginDefault,\n      apiKey,\n      error:\n        \"CDN deploy requires a bundle origin — set `cdn.origin` in bos.config.json \" +\n        \"(children inherit it from the base runtime via extends) or BOS_BUNDLE_CDN_ORIGIN \" +\n        \"in the environment.\",\n    };\n  }\n\n  let storageOrigin: string;\n  if (storageEnvOrigin) {\n    storageOrigin = storageEnvOrigin;\n    if (isLocalOrigin(storageEnvOrigin) && !warning) {\n      warning = `BOS_STORAGE_ORIGIN is a local URL (${storageEnvOrigin}) — uploading to a local stack.`;\n    }\n  } else if (sessionSite) {\n    if (isLocalOrigin(sessionSite)) {\n      return {\n        cdnOrigin,\n        storageOrigin: sessionSite,\n        apiKey,\n        error:\n          `The CLI session pins uploads to ${sessionSite} — a local site can never receive ` +\n          \"production bundles. Re-run `bos login` against the production site, or set \" +\n          \"BOS_STORAGE_ORIGIN explicitly.\",\n      };\n    }\n    storageOrigin = sessionSite;\n  } else {\n    storageOrigin = `https://${input.gateway}`;\n  }\n\n  if (!apiKey) {\n    return {\n      cdnOrigin,\n      storageOrigin,\n      apiKey,\n      warning,\n      error:\n        \"CDN deploy requires bundle-upload credentials — run `bos login --key` and add the printed key \" +\n        \"to GitHub secrets as BOS_STORAGE_API_KEY (or run `bos login` for interactive deploys)\",\n    };\n  }\n  if (input.session?.accountId && input.session.accountId !== input.account) {\n    return {\n      cdnOrigin,\n      storageOrigin,\n      apiKey,\n      warning,\n      error: `The CLI session was created for ${input.session.accountId}, but the configured account is ${input.account}. Re-run bos login under the matching account.`,\n    };\n  }\n  return { cdnOrigin, storageOrigin, apiKey, warning };\n}\n\n/**\n * Pre-upload probe: the storage origin must actually be the platform API.\n * `/.well-known/mcp.json` is the cheapest unauthenticated platform\n * fingerprint — a random service squatting the port (or a stale session\n * pointing at the wrong server) fails here before megabytes move.\n */\nexport async function probeStorageOrigin(\n  storageOrigin: string,\n  fetchImpl: typeof fetch = fetch,\n): Promise<string | undefined> {\n  const probeUrl = `${storageOrigin.replace(/\\/$/, \"\")}/.well-known/mcp.json`;\n  let response: Response;\n  try {\n    response = await fetchImpl(probeUrl, { headers: { accept: \"application/json\" } });\n  } catch (error) {\n    return `unreachable (${String(error instanceof Error ? error.message : error)})`;\n  }\n  if (!response.ok) {\n    return `GET /.well-known/mcp.json responded ${response.status}`;\n  }\n  let body: unknown;\n  try {\n    body = await response.json();\n  } catch {\n    return \"did not return the platform MCP descriptor (non-JSON response)\";\n  }\n  if (!body || typeof body !== \"object\") {\n    return \"did not return the platform MCP descriptor\";\n  }\n  return undefined;\n}\n"],"mappings":"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"}