{"version":3,"file":"wait-for-health-BGwKIEZA.cjs","names":["encodeOrgArtifacts","LOCAL_PLATFORM_ORIGIN"],"sources":["../src/constants.ts","../src/worker-runtime-config.ts","../src/org-worker-token.ts","../src/runtime-env.ts","../src/wait-for-health.ts"],"sourcesContent":["export const PROJECT_SERVER_IMAGE = \"keystroke/project-server:local\";\n\nexport const PROJECT_SERVER_PORT = 3000;\n\nexport const PROJECT_SERVER_ROOT = \"/app\";\n\nexport const PROJECT_SERVER_FRAMEWORK_ROOT = \"/opt/keystroke\";\n\nexport const PROJECT_SERVER_FRAMEWORK_NODE_MODULES = `${PROJECT_SERVER_FRAMEWORK_ROOT}/node_modules`;\n\nexport const PROJECT_SERVER_HEALTH = {\n  path: \"/health\",\n  port: PROJECT_SERVER_PORT,\n} as const;\n\nexport const PROJECT_SERVER_DEPLOY_STATUS = {\n  path: \"/deploy-status\",\n  port: PROJECT_SERVER_PORT,\n} as const;\n","import { resolvePlatformWorkerToken, resolveWorkerPlatformUrl } from \"./runtime-env\";\n\n/** Resolved platform ↔ project-server worker auth contract. */\nexport type WorkerRuntimeConfig = {\n  /** Platform API base URL reachable from the project-server runtime. */\n  platformUrl: string;\n  /** Bearer token for platform `/internal` routes. */\n  workerToken: string;\n};\n\nexport class WorkerRuntimeConfigError extends Error {\n  readonly missing: readonly string[];\n\n  constructor(missing: string[]) {\n    super(\n      `Worker runtime config incomplete (missing: ${missing.join(\", \")}). ` +\n        \"In production set PLATFORM_WORKER_TOKEN and PUBLIC_PLATFORM_URL (or PLATFORM_URL).\",\n    );\n    this.name = \"WorkerRuntimeConfigError\";\n    this.missing = missing;\n  }\n}\n\n/** Fail fast when the platform cannot start worker-mode project servers. */\nexport function resolveWorkerRuntimeConfig(\n  source: NodeJS.ProcessEnv = process.env,\n): WorkerRuntimeConfig {\n  const platformUrl = resolveWorkerPlatformUrl(source);\n  const workerToken = resolvePlatformWorkerToken(source);\n  const missing: string[] = [];\n\n  if (!platformUrl) {\n    missing.push(\"PUBLIC_PLATFORM_URL or PLATFORM_URL\");\n  }\n\n  if (!workerToken) {\n    missing.push(\"PLATFORM_WORKER_TOKEN\");\n  }\n\n  if (missing.length > 0) {\n    throw new WorkerRuntimeConfigError(missing);\n  }\n\n  return { platformUrl: platformUrl!, workerToken: workerToken! };\n}\n","import { createHmac, timingSafeEqual } from \"node:crypto\";\n\nfunction sign(secret: string, organizationId: string): string {\n  return createHmac(\"sha256\", secret).update(`org-worker:${organizationId}`).digest(\"base64url\");\n}\n\n/**\n * Per-org worker token: `{organizationId}.{hmac}`. Self-describing so the\n * platform can tell which org a machine is calling as, and stateless — verified\n * by re-signing with the shared `PLATFORM_WORKER_TOKEN` secret.\n */\nexport function mintOrgWorkerToken(secret: string, organizationId: string): string {\n  return `${organizationId}.${sign(secret, organizationId)}`;\n}\n\nexport type VerifiedOrgWorkerToken = {\n  organizationId: string;\n};\n\nexport function verifyOrgWorkerToken(\n  secret: string | undefined,\n  token: string | undefined,\n): VerifiedOrgWorkerToken | undefined {\n  if (!secret || !token) {\n    return undefined;\n  }\n\n  // The signature is base64url (no dots), so the last dot ends the org id.\n  const separator = token.lastIndexOf(\".\");\n  if (separator <= 0) {\n    return undefined;\n  }\n\n  const organizationId = token.slice(0, separator);\n  const signature = Buffer.from(token.slice(separator + 1));\n  const expected = Buffer.from(sign(secret, organizationId));\n  if (signature.length !== expected.length || !timingSafeEqual(signature, expected)) {\n    return undefined;\n  }\n\n  return { organizationId };\n}\n","import {\n  LOCAL_PLATFORM_ORIGIN,\n  resolveExplicitPublicPlatformOrigin,\n} from \"@keystrokehq/shared/platform-origin\";\n\nimport type { ProjectRuntimeDatabase } from \"./runtime\";\nimport { encodeOrgArtifacts, type OrgArtifactSpec } from \"./runtime\";\nimport {\n  PROJECT_SERVER_FRAMEWORK_NODE_MODULES,\n  PROJECT_SERVER_HEALTH,\n  PROJECT_SERVER_IMAGE,\n  PROJECT_SERVER_PORT,\n  PROJECT_SERVER_ROOT,\n} from \"./constants\";\nimport type { HostingOptions } from \"./plugin\";\nimport { resolveWorkerRuntimeConfig } from \"./worker-runtime-config\";\nimport { mintOrgWorkerToken } from \"./org-worker-token\";\n\n/** Non-secret runtime config forwarded into org workers. Never include platform secrets. */\nconst RUNTIME_ENV_KEYS = [\n  \"PUBLIC_PLATFORM_URL\",\n  \"PUBLIC_PLATFORM_PROXY_URL\",\n  \"PUBLIC_WEB_URL\",\n  \"REDIS_URL\",\n  \"KEYSTROKE_QUEUE_PREFIX\",\n] as const;\n\n/**\n * Observability ingest config for org workers (PostHog error tracking + crash reports).\n * Ingest tokens only — never forward `POSTHOG_API_KEY` (management) into workers.\n */\nconst OBSERVABILITY_ENV_KEYS = [\n  \"POSTHOG_PROJECT_TOKEN\",\n  \"POSTHOG_HOST\",\n  \"POSTHOG_FLUSH_MODE\",\n  \"KEYSTROKE_ENVIRONMENT\",\n  \"KEYSTROKE_RELEASE\",\n] as const;\n\n/** Env keys that must never be forwarded into untrusted org workers. */\nexport const FORBIDDEN_WORKER_ENV_KEYS = [\n  \"BETTER_AUTH_SECRET\",\n  \"BETTER_AUTH_API_KEY\",\n  \"DATABASE_URL\",\n  \"POSTHOG_API_KEY\",\n  \"POSTGRES_PASSWORD\",\n  \"PLATFORM_WORKER_TOKEN\",\n  \"STRIPE_SECRET_KEY\",\n  \"CREDENTIAL_ENCRYPTION_KEY\",\n  \"MODAL_TOKEN_ID\",\n  \"MODAL_TOKEN_SECRET\",\n  \"MODEL_TOKEN_SECRET\",\n  \"TURNSTILE_SECRET\",\n  \"UPSTASH_REDIS_URL\",\n] as const;\n\n/** Provider and gateway env forwarded from the platform into project servers. */\nconst PROVIDER_ENV_KEYS = [\n  \"ANTHROPIC_API_KEY\",\n  \"OPENAI_API_KEY\",\n  \"AI_GATEWAY_API_KEY\",\n  \"AI_GATEWAY_BASE_URL\",\n] as const;\n\n/** Shared dev token when `PLATFORM_WORKER_TOKEN` is unset (non-production only). */\nexport const DEV_PLATFORM_WORKER_TOKEN = \"dev-platform-worker-token\";\n\nexport type RuntimeLaunchSpec = {\n  image: string;\n  env: Record<string, string>;\n  port: number;\n  health: typeof PROJECT_SERVER_HEALTH;\n};\n\n/**\n * Builds the launch spec for an org worker. Each deploy artifact is encoded in\n * `KEYSTROKE_ORG_ARTIFACTS` — there is no base image, so at least one artifact is required.\n */\nexport function resolveRuntimeLaunch(\n  options: HostingOptions,\n  artifacts: OrgArtifactSpec[],\n  database: ProjectRuntimeDatabase,\n  extraEnv?: Record<string, string>,\n): RuntimeLaunchSpec {\n  if (artifacts.length === 0) {\n    throw new Error(\"Cannot start an org worker without at least one artifact\");\n  }\n\n  const env = options.env ?? process.env;\n\n  return {\n    image: resolveProjectServerImage(env, options.image),\n    env: buildRuntimeEnv(env, artifacts, database, extraEnv),\n    port: PROJECT_SERVER_PORT,\n    health: PROJECT_SERVER_HEALTH,\n  };\n}\n\n/** @deprecated Single-project launch — prefer org {@link resolveRuntimeLaunch}. */\nexport function resolveSingleProjectRuntimeLaunch(\n  options: HostingOptions,\n  artifact: { storageKey: string; artifactId: string; artifactVersion: number },\n  database: ProjectRuntimeDatabase & { projectId: string },\n  extraEnv?: Record<string, string>,\n): RuntimeLaunchSpec {\n  return resolveRuntimeLaunch(\n    options,\n    [\n      {\n        projectId: database.projectId,\n        artifactStorageKey: artifact.storageKey,\n        artifactId: artifact.artifactId,\n        artifactVersion: artifact.artifactVersion,\n      },\n    ],\n    database,\n    extraEnv,\n  );\n}\n\n/** Env vars passed into the org worker container/machine. */\nexport function buildRuntimeEnv(\n  source: NodeJS.ProcessEnv,\n  artifacts: OrgArtifactSpec[],\n  database: ProjectRuntimeDatabase,\n  extraEnv?: Record<string, string>,\n): Record<string, string> {\n  const entries = new Map<string, string>([\n    [\"PORT\", String(PROJECT_SERVER_PORT)],\n    [\"KEYSTROKE_ROOT\", PROJECT_SERVER_ROOT],\n    [\"KEYSTROKE_RUNTIME_NODE_MODULES\", PROJECT_SERVER_FRAMEWORK_NODE_MODULES],\n    [\"KEYSTROKE_ORG_ARTIFACTS\", encodeOrgArtifacts(artifacts)],\n    [\"ORGANIZATION_ID\", database.organizationId],\n    [\"TENANT_ID\", database.organizationId],\n  ]);\n\n  for (const key of RUNTIME_ENV_KEYS) {\n    const value = source[key]?.trim();\n    if (!value) {\n      continue;\n    }\n    entries.set(key, value);\n  }\n\n  for (const key of OBSERVABILITY_ENV_KEYS) {\n    const value = source[key]?.trim();\n    if (!value) {\n      continue;\n    }\n    entries.set(key, value);\n  }\n\n  // OSS self-host: forward BYO provider/gateway env. Cloud llmProxy supplies extraEnv instead.\n  if (!extraEnv) {\n    for (const key of PROVIDER_ENV_KEYS) {\n      const value = source[key]?.trim();\n      if (value) {\n        entries.set(key, value);\n      }\n    }\n  }\n\n  // Project containers always run as workers: they resolve credentials/secrets through\n  // the platform internal API and are deliberately never given the decryption key.\n  // The token is minted per org so the platform knows which org a machine calls as.\n  const worker = resolveWorkerRuntimeConfig(source);\n  entries.set(\"PLATFORM_URL\", worker.platformUrl);\n  entries.set(\n    \"WORKER_INTERNAL_TOKEN\",\n    mintOrgWorkerToken(worker.workerToken, database.organizationId),\n  );\n\n  if (extraEnv) {\n    for (const [key, value] of Object.entries(extraEnv)) {\n      entries.set(key, value);\n    }\n  }\n\n  for (const key of FORBIDDEN_WORKER_ENV_KEYS) {\n    entries.delete(key);\n  }\n\n  return Object.fromEntries(entries);\n}\n\nexport function resolvePlatformWorkerToken(\n  source: NodeJS.ProcessEnv = process.env,\n): string | undefined {\n  const explicit = source.PLATFORM_WORKER_TOKEN?.trim() ?? source.WORKER_INTERNAL_TOKEN?.trim();\n  if (explicit) {\n    return explicit;\n  }\n\n  if (source.NODE_ENV === \"production\") {\n    return undefined;\n  }\n\n  return DEV_PLATFORM_WORKER_TOKEN;\n}\n\n/** Platform API URL reachable from a project-server container. */\nexport function resolveWorkerPlatformUrl(\n  source: NodeJS.ProcessEnv = process.env,\n): string | undefined {\n  const raw =\n    source.PLATFORM_URL?.trim() ??\n    resolveExplicitPublicPlatformOrigin(source) ??\n    (source.NODE_ENV === \"production\" ? undefined : LOCAL_PLATFORM_ORIGIN);\n  if (!raw) {\n    return undefined;\n  }\n\n  return rewriteLoopbackUrl(raw);\n}\n\n/** Rewrite loopback hosts so containers can reach Postgres and other host services. */\nexport function rewriteLoopbackHost(host: string): string {\n  if (host === \"localhost\" || host === \"127.0.0.1\") {\n    return \"host.docker.internal\";\n  }\n\n  return host;\n}\n\nexport function rewriteLoopbackUrl(url: string): string {\n  try {\n    const parsed = new URL(url);\n    parsed.hostname = rewriteLoopbackHost(parsed.hostname);\n    return parsed.toString().replace(/\\/$/, \"\");\n  } catch {\n    return url;\n  }\n}\n\n/** dockerode expects `KEY=value` strings. */\nexport function formatDockerEnv(env: Record<string, string>): string[] {\n  return Object.entries(env).map(([key, value]) => `${key}=${value}`);\n}\n\nexport function resolveProjectServerImage(\n  env: NodeJS.ProcessEnv = process.env,\n  override?: string,\n): string {\n  return override ?? env.PROJECT_DOCKER_IMAGE ?? env.TENANT_DOCKER_IMAGE ?? PROJECT_SERVER_IMAGE;\n}\n","import { PROJECT_SERVER_HEALTH } from \"./constants\";\n\nconst DEFAULT_TIMEOUT_MS = 120_000;\nconst DEFAULT_INTERVAL_MS = 500;\n\nexport type WaitForHealthOptions = {\n  timeoutMs?: number;\n  intervalMs?: number;\n  fetchImpl?: typeof fetch;\n};\n\nexport async function waitForHealth(\n  baseUrl: string,\n  options: WaitForHealthOptions = {},\n): Promise<void> {\n  const fetchImpl = options.fetchImpl ?? fetch;\n  const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n  const intervalMs = options.intervalMs ?? DEFAULT_INTERVAL_MS;\n  const deadline = Date.now() + timeoutMs;\n  const url = new URL(PROJECT_SERVER_HEALTH.path, baseUrl);\n\n  while (Date.now() < deadline) {\n    try {\n      const response = await fetchImpl(url, { signal: AbortSignal.timeout(2_000) });\n      if (response.ok) {\n        return;\n      }\n    } catch {\n      // retry until timeout\n    }\n\n    await sleep(intervalMs);\n  }\n\n  throw new Error(`Project server health check timed out after ${timeoutMs}ms (${url})`);\n}\n\nfunction sleep(ms: number): Promise<void> {\n  return new Promise((resolve) => {\n    setTimeout(resolve, ms);\n  });\n}\n"],"mappings":"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"}