everything-dev 2.0.0-rc.6 → 2.0.0-rc.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/build.cjs +1 -1
- package/dist/build.mjs +1 -1
- package/dist/bundle-cache.cjs +1 -1
- package/dist/bundle-cache.cjs.map +1 -1
- package/dist/bundle-cache.mjs +1 -1
- package/dist/bundle-cache.mjs.map +1 -1
- package/dist/cli.cjs +2 -2
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.mjs +1 -1
- package/dist/cli.mjs.map +1 -1
- package/dist/commands/deploy.cjs +111 -1
- package/dist/commands/deploy.cjs.map +1 -1
- package/dist/commands/deploy.mjs +112 -2
- package/dist/commands/deploy.mjs.map +1 -1
- package/dist/commands/smoke.cjs +41 -0
- package/dist/commands/smoke.cjs.map +1 -0
- package/dist/commands/smoke.mjs +41 -0
- package/dist/commands/smoke.mjs.map +1 -0
- package/dist/commands/upgrade.cjs +2 -2
- package/dist/commands/upgrade.cjs.map +1 -1
- package/dist/commands/upgrade.mjs +1 -1
- package/dist/commands/upgrade.mjs.map +1 -1
- package/dist/contract.cjs +27 -1
- package/dist/contract.cjs.map +1 -1
- package/dist/contract.d.cts +65 -1
- package/dist/contract.d.mts +65 -1
- package/dist/contract.meta.cjs +13 -1
- package/dist/contract.meta.cjs.map +1 -1
- package/dist/contract.meta.d.cts +23 -1
- package/dist/contract.meta.d.mts +23 -1
- package/dist/contract.meta.mjs +13 -1
- package/dist/contract.meta.mjs.map +1 -1
- package/dist/contract.mjs +26 -2
- package/dist/contract.mjs.map +1 -1
- package/dist/dev-log-pipeline.cjs +6 -0
- package/dist/dev-log-pipeline.cjs.map +1 -1
- package/dist/dev-log-pipeline.mjs +6 -0
- package/dist/dev-log-pipeline.mjs.map +1 -1
- package/dist/dev-program.cjs +20 -3
- package/dist/dev-program.cjs.map +1 -1
- package/dist/dev-program.mjs +20 -4
- package/dist/dev-program.mjs.map +1 -1
- package/dist/dev-session.cjs +1 -0
- package/dist/dev-session.cjs.map +1 -1
- package/dist/dev-session.mjs +1 -0
- package/dist/dev-session.mjs.map +1 -1
- package/dist/fastkv.cjs +21 -3
- package/dist/fastkv.cjs.map +1 -1
- package/dist/fastkv.d.cts +14 -1
- package/dist/fastkv.d.mts +14 -1
- package/dist/fastkv.mjs +21 -5
- package/dist/fastkv.mjs.map +1 -1
- package/dist/{mf.cjs → federation.cjs} +6 -13
- package/dist/federation.cjs.map +1 -0
- package/dist/{mf.d.cts → federation.d.cts} +2 -2
- package/dist/{mf.d.mts → federation.d.mts} +2 -2
- package/dist/{mf.mjs → federation.mjs} +6 -13
- package/dist/federation.mjs.map +1 -0
- package/dist/http-client.cjs +70 -53
- package/dist/http-client.cjs.map +1 -1
- package/dist/http-client.mjs +70 -54
- package/dist/http-client.mjs.map +1 -1
- package/dist/image-deploy.cjs +9 -0
- package/dist/image-deploy.cjs.map +1 -1
- package/dist/image-deploy.mjs +9 -1
- package/dist/image-deploy.mjs.map +1 -1
- package/dist/index.cjs +4 -0
- package/dist/index.d.cts +3 -3
- package/dist/index.d.mts +3 -3
- package/dist/index.mjs +3 -3
- package/dist/orchestrator.cjs +39 -35
- package/dist/orchestrator.cjs.map +1 -1
- package/dist/orchestrator.mjs +39 -35
- package/dist/orchestrator.mjs.map +1 -1
- package/dist/plugin.cjs +2 -0
- package/dist/plugin.cjs.map +1 -1
- package/dist/plugin.d.cts +24 -0
- package/dist/plugin.d.mts +24 -0
- package/dist/plugin.mjs +2 -0
- package/dist/plugin.mjs.map +1 -1
- package/dist/process-registry.cjs +1 -1
- package/dist/process-registry.mjs +1 -1
- package/dist/publish.cjs +3 -3
- package/dist/publish.cjs.map +1 -1
- package/dist/publish.mjs +4 -4
- package/dist/publish.mjs.map +1 -1
- package/dist/resolver.cjs +293 -0
- package/dist/resolver.cjs.map +1 -0
- package/dist/resolver.d.cts +99 -0
- package/dist/resolver.d.mts +99 -0
- package/dist/resolver.mjs +284 -0
- package/dist/resolver.mjs.map +1 -0
- package/dist/smoke.cjs +262 -0
- package/dist/smoke.cjs.map +1 -0
- package/dist/smoke.mjs +260 -0
- package/dist/smoke.mjs.map +1 -0
- package/dist/version-manifest-resolve.cjs +3 -3
- package/dist/version-manifest-resolve.cjs.map +1 -1
- package/dist/version-manifest-resolve.mjs +1 -1
- package/dist/version-manifest-resolve.mjs.map +1 -1
- package/dist/workspace.cjs +1 -1
- package/dist/workspace.mjs +1 -1
- package/package.json +9 -9
- package/dist/bundle-fs-resolve.cjs +0 -235
- package/dist/bundle-fs-resolve.cjs.map +0 -1
- package/dist/bundle-fs-resolve.d.cts +0 -82
- package/dist/bundle-fs-resolve.d.mts +0 -82
- package/dist/bundle-fs-resolve.mjs +0 -228
- package/dist/bundle-fs-resolve.mjs.map +0 -1
- package/dist/bundle-path.cjs +0 -48
- package/dist/bundle-path.cjs.map +0 -1
- package/dist/bundle-path.mjs +0 -45
- package/dist/bundle-path.mjs.map +0 -1
- package/dist/mf.cjs.map +0 -1
- package/dist/mf.mjs.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"deploy.cjs","names":["Context","BosDepsTag","MISSING_CONFIG_MESSAGE","publishToFastKv","fetchConfigHistory","summarizeSlotPins","fetchBosConfigFromFastKv","verifyRollbackSnapshot","BosConfigSchema","buildRollbackPayload","fetchDeployManifests","process","isValidImageDigest","openResolution","resolveDeployImagePlan","resolveImageRef","hasDocker","existsSync","join","colors","icons","buildAndPushImage","deployImageToRailway"],"sources":["../../src/commands/deploy.ts"],"sourcesContent":["import { existsSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport process from \"node:process\";\nimport { Context } from \"effect\";\nimport { MISSING_CONFIG_MESSAGE } from \"../config\";\nimport {\n type ConfigHistoryEntry,\n fetchBosConfigFromFastKv,\n fetchConfigHistory,\n fetchDeployManifests,\n} from \"../fastkv\";\nimport {\n buildAndPushImage,\n deployImageToRailway,\n hasDocker,\n isValidImageDigest,\n resolveDeployImagePlan,\n resolveImageRef,\n} from \"../image-deploy\";\nimport { publishToFastKv } from \"../publish\";\nimport { openResolution } from \"../resolution/session\";\nimport { buildRollbackPayload, summarizeSlotPins, verifyRollbackSnapshot } from \"../rollback\";\nimport type { BosConfigInput } from \"../types\";\nimport { BosConfigSchema } from \"../types\";\nimport { colors, icons } from \"../utils/theme\";\nimport { type BosBuilder, BosDepsTag } from \"./shared\";\n\nexport function registerDeploy(builder: BosBuilder) {\n return {\n publish: builder.publish.handler(async ({ input, context }) => {\n const deps = Context.get(context[\"effect/context\"], BosDepsTag);\n const session = deps.session;\n if (!session?.config) {\n return {\n status: \"error\" as const,\n registryUrl: \"\",\n error: MISSING_CONFIG_MESSAGE,\n };\n }\n\n const result = await publishToFastKv({\n bosConfig: session.config,\n runtimeConfig: session.runtime,\n configDir: session.root,\n env: input.env,\n build: false,\n dryRun: input.dryRun,\n verbose: input.verbose,\n packages: \"all\",\n network: input.network,\n privateKey: input.privateKey,\n wallet: input.wallet,\n registry: input.registry,\n });\n\n return {\n status: result.status,\n registryUrl: result.registryUrl,\n txHash: result.txHash,\n error: result.error,\n built: result.built,\n skipped: result.skipped,\n deployResults: result.deployResults,\n };\n }),\n\n rollback: builder.rollback.handler(async ({ input, context }) => {\n const deps = Context.get(context[\"effect/context\"], BosDepsTag);\n const session = deps.session;\n if (!session) {\n return {\n status: \"error\" as const,\n registryUrl: \"\",\n error: MISSING_CONFIG_MESSAGE,\n };\n }\n\n const { account, domain } = session.config ?? {};\n if (!account || !domain) {\n return {\n status: \"error\" as const,\n registryUrl: \"\",\n error: \"authored config must define account and domain to roll back\",\n };\n }\n\n let history: ConfigHistoryEntry[];\n try {\n history = await fetchConfigHistory({\n accountId: account,\n gatewayId: domain,\n registry: input.registry,\n limit: input.limit,\n });\n } catch (error) {\n return {\n status: \"error\" as const,\n registryUrl: \"\",\n error: `Failed to fetch publish history: ${error instanceof Error ? error.message : String(error)}`,\n };\n }\n\n if (history.length === 0) {\n return {\n status: \"error\" as const,\n registryUrl: \"\",\n error: `No publish history for ${account}/${domain} — nothing to roll back to`,\n };\n }\n\n const historyEntries = history.map((entry) => ({\n blockHeight: entry.blockHeight,\n blockTimestamp: new Date(Number(entry.blockTimestampNs.slice(0, 13))).toISOString(),\n ...(entry.txHash ? { txHash: entry.txHash } : {}),\n summary: summarizeSlotPins(entry.value as BosConfigInput),\n }));\n\n if (input.listOnly) {\n return {\n status: \"list\" as const,\n registryUrl: \"\",\n history: historyEntries,\n };\n }\n\n const target =\n input.version !== undefined\n ? history.find((entry) => {\n const iso = new Date(Number(entry.blockTimestampNs.slice(0, 13))).toISOString();\n return (\n String(entry.blockHeight) === input.version ||\n iso.startsWith(input.version as string)\n );\n })\n : input.previous\n ? history[1]\n : undefined;\n\n if (!target) {\n return {\n status: \"error\" as const,\n registryUrl: \"\",\n history: historyEntries,\n error: input.version\n ? `No history entry matches \"${input.version}\" — pick a block height from the listing`\n : \"Specify --version <block-height> or --previous (interactive selection happens CLI-side)\",\n };\n }\n\n let liveValue: unknown;\n try {\n liveValue = await fetchBosConfigFromFastKv<unknown>(\n `bos://${account}/${domain}`,\n input.registry,\n );\n } catch {\n liveValue = undefined;\n }\n if (liveValue && JSON.stringify(liveValue) === JSON.stringify(target.value)) {\n return {\n status: \"error\" as const,\n registryUrl: \"\",\n error: \"The selected snapshot is identical to the live config — nothing to do\",\n };\n }\n\n const verification = await verifyRollbackSnapshot(target.value as BosConfigInput);\n const slotChecks = verification.slots.map((check) => ({\n slot: check.slot,\n ok: check.ok,\n ...(check.reason ? { reason: check.reason } : {}),\n }));\n if (!verification.ok && verification.verifiable) {\n return {\n status: \"error\" as const,\n registryUrl: \"\",\n verification: slotChecks,\n error:\n \"Refusing to roll back — a pinned slot's bytes are gone or no longer match their SRI\",\n };\n }\n\n if (!verification.verifiable && !input.force) {\n return {\n status: \"error\" as const,\n registryUrl: \"\",\n error:\n \"Snapshot predates version manifests and cannot be verified — its bytes were overwritten in place. Re-run with --force to publish it anyway.\",\n };\n }\n\n const parsedTarget = BosConfigSchema.parse(target.value);\n const payload = buildRollbackPayload(parsedTarget, new Date().toISOString());\n\n const result = await publishToFastKv({\n bosConfig: payload,\n runtimeConfig: session.runtime,\n configDir: session.root,\n env: input.env,\n build: false,\n dryRun: input.dryRun,\n verbose: input.verbose,\n packages: \"all\",\n network: input.network,\n privateKey: input.privateKey,\n wallet: input.wallet,\n registry: input.registry,\n });\n\n return {\n status: result.status,\n registryUrl: result.registryUrl,\n txHash: result.txHash,\n error: result.error,\n history: historyEntries,\n ...(slotChecks.length > 0 ? { verification: slotChecks } : {}),\n };\n }),\n\n deploy: builder.deploy.handler(async ({ input, context }) => {\n const deps = Context.get(context[\"effect/context\"], BosDepsTag);\n const session = deps.session;\n if (!session?.config) {\n return {\n status: \"error\" as const,\n registryUrl: \"\",\n error: MISSING_CONFIG_MESSAGE,\n };\n }\n\n if (input.statusList) {\n if (!session.config?.account || !session.config.domain) {\n return {\n status: \"error\" as const,\n registryUrl: \"\",\n error: \"authored config must define account and domain to list deploy manifests\",\n };\n }\n try {\n const manifests = await fetchDeployManifests({\n accountId: session.config.account,\n gatewayId: session.config.domain,\n registry: input.registry,\n });\n return {\n status: \"list\" as const,\n registryUrl: \"\",\n history: manifests.map((entry) => ({\n key: entry.key,\n blockHeight: entry.blockHeight,\n blockTimestamp: new Date(Number(entry.blockTimestampNs.slice(0, 13))).toISOString(),\n ...((entry.value as { txHash?: string } | undefined)?.txHash\n ? { txHash: (entry.value as { txHash?: string }).txHash }\n : {}),\n ...((entry.value as { publishedAt?: string } | undefined)?.publishedAt\n ? { publishedAt: (entry.value as { publishedAt?: string }).publishedAt }\n : {}),\n })),\n };\n } catch (error) {\n return {\n status: \"error\" as const,\n registryUrl: \"\",\n error: `Failed to list deploy manifests: ${error instanceof Error ? error.message : String(error)}`,\n };\n }\n }\n\n const imageDigestInput = input.imageDigest ?? process.env.BOS_IMAGE_DIGEST;\n if (imageDigestInput && !isValidImageDigest(imageDigestInput)) {\n return {\n status: \"error\" as const,\n registryUrl: \"\",\n error: `Invalid image digest \"${imageDigestInput}\" — expected sha256:<64 hex characters> (--image-digest / BOS_IMAGE_DIGEST)`,\n };\n }\n\n const result = await publishToFastKv({\n bosConfig: session.config,\n runtimeConfig: session.runtime,\n configDir: session.root,\n env: input.env,\n build: input.build,\n dryRun: input.dryRun,\n verbose: input.verbose,\n packages: input.packages,\n network: input.network,\n privateKey: input.privateKey,\n registry: input.registry,\n fullUpload: input.fullUpload,\n });\n\n if (result.status === \"error\") {\n return {\n status: \"error\" as const,\n registryUrl: result.registryUrl,\n txHash: result.txHash,\n built: result.built,\n skipped: result.skipped,\n error: result.error,\n deployResults: result.deployResults,\n };\n }\n\n if (result.status === \"dry-run\") {\n return {\n status: \"dry-run\" as const,\n registryUrl: result.registryUrl,\n built: result.built,\n skipped: result.skipped,\n };\n }\n\n let nextSession = session;\n if (result.publishConfig) {\n const opened = await openResolution({ cwd: session.root });\n if (opened) {\n nextSession = opened;\n }\n }\n\n const configPublished = {\n registryUrl: result.registryUrl,\n txHash: result.txHash,\n fingerprint: result.fingerprint,\n slotPins: result.slotPins,\n built: result.built,\n skipped: result.skipped,\n deployResults: result.deployResults,\n } as const;\n\n let image: string | undefined;\n let imageDigest: string | undefined;\n let service: string | undefined;\n\n const imagePlan = resolveDeployImagePlan({\n // A prebuilt digest pairs with the pushed ref (BOS_IMAGE from the CI\n // image job, or repository derivation) — ci.image is a build-time\n // choice, not a pin-time one; letting it win here could pin a digest\n // from a different image.\n imageRef: resolveImageRef({\n ciImage: imageDigestInput ? undefined : nextSession.config?.ci?.image,\n repository: nextSession.config?.repository,\n env: process.env,\n }),\n imageDigest: imageDigestInput,\n hasDocker: await hasDocker(),\n hasDockerfile: existsSync(join(session.root, \"Dockerfile\")),\n });\n\n if (imagePlan.kind === \"skip\") {\n console.log();\n console.log(colors.yellow(` Image skipped: ${imagePlan.reason}`));\n } else if (imagePlan.kind === \"prebuilt\") {\n image = imagePlan.image;\n imageDigest = imagePlan.digest;\n console.log();\n console.log(\n colors.green(\n ` ${icons.ok} Using pre-pushed image ${imagePlan.image}@${imagePlan.digest.slice(0, 19)}…`,\n ),\n );\n } else {\n console.log();\n try {\n const imageResult = await buildAndPushImage({\n image: imagePlan.image,\n configDir: session.root,\n verbose: input.verbose,\n });\n image = imageResult.image;\n imageDigest = imageResult.digest;\n console.log(\n colors.green(\n ` ${icons.ok} Image pushed ${imageResult.image}:${imageResult.tag}${imageResult.digest ? ` (${imageResult.digest.slice(0, 19)}…)` : \"\"}`,\n ),\n );\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n const imageError =\n message.includes(\"not found\") || message.includes(\"ENOENT\")\n ? \"Docker not found — install Docker to build the runtime image\"\n : `Image build/push failed: ${message}`;\n console.log(colors.yellow(` ${imageError}`));\n return {\n status: \"published\" as const,\n ...configPublished,\n error: `Config published but ${imageError}`,\n };\n }\n }\n\n if (process.env.RAILWAY_TOKEN) {\n const railwayService = input.service ?? nextSession.config?.ci?.railway?.service;\n if (!railwayService) {\n console.log();\n console.log(\n colors.yellow(\n \" Railway deploy skipped: ci.railway.service is not configured in bos.config.json\",\n ),\n );\n return {\n status: \"published\" as const,\n ...configPublished,\n image,\n error:\n \"Config published but Railway deploy failed: ci.railway.service is not configured in bos.config.json\",\n };\n }\n if (!image || !imageDigest) {\n console.log();\n console.log(\n colors.yellow(\n \" Railway deploy skipped: no runtime image digest — set ci.image (or pass BOS_IMAGE_DIGEST) and make the image available\",\n ),\n );\n return {\n status: \"published\" as const,\n ...configPublished,\n image,\n service: railwayService,\n error:\n \"Config published but Railway deploy requires a runtime image (set ci.image, or pass BOS_IMAGE_DIGEST for a pre-pushed image)\",\n };\n }\n\n service = railwayService;\n try {\n await deployImageToRailway({\n service: railwayService,\n configDir: session.root,\n });\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n const railError =\n message.includes(\"not found\") || message.includes(\"ENOENT\")\n ? \"Railway CLI not found. Install it: npm i -g @railway/cli\"\n : `Railway deploy failed: ${message}`;\n console.log(colors.yellow(` ${railError}`));\n return {\n status: \"published\" as const,\n ...configPublished,\n image,\n service,\n error: `Config published but ${railError}`,\n };\n }\n } else {\n console.log();\n console.log(colors.yellow(\" Railway deploy skipped (RAILWAY_TOKEN not set)\"));\n }\n\n return {\n status: \"deployed\" as const,\n ...configPublished,\n image,\n service,\n };\n }),\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"deploy.cjs","names":["fetchConfigHistory","verifyRollbackSnapshot","buildRollbackPayload","BosConfigSchema","publishToFastKv","Context","BosDepsTag","MISSING_CONFIG_MESSAGE","summarizeSlotPins","fetchBosConfigFromFastKv","fetchDeployManifests","process","isValidImageDigest","openResolution","resolveDeployImagePlan","resolveImageRef","hasDocker","existsSync","join","colors","icons","buildAndPushImage","runSmoke","imageRefWithDigest","deployImageToRailway"],"sources":["../../src/commands/deploy.ts"],"sourcesContent":["import { existsSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport process from \"node:process\";\nimport { Context } from \"effect\";\nimport { MISSING_CONFIG_MESSAGE } from \"../config\";\nimport {\n type ConfigHistoryEntry,\n fetchBosConfigFromFastKv,\n fetchConfigHistory,\n fetchDeployManifests,\n} from \"../fastkv\";\nimport {\n buildAndPushImage,\n deployImageToRailway,\n hasDocker,\n imageRefWithDigest,\n isValidImageDigest,\n resolveDeployImagePlan,\n resolveImageRef,\n} from \"../image-deploy\";\nimport { publishToFastKv } from \"../publish\";\nimport { openResolution } from \"../resolution/session\";\nimport { buildRollbackPayload, summarizeSlotPins, verifyRollbackSnapshot } from \"../rollback\";\nimport { runSmoke } from \"../smoke\";\nimport type { BosConfigInput } from \"../types\";\nimport { BosConfigSchema } from \"../types\";\nimport { colors, icons } from \"../utils/theme\";\nimport { type BosBuilder, BosDepsTag } from \"./shared\";\n\ninterface RollbackToPreviousInput {\n account: string;\n domain: string;\n env: \"production\" | \"staging\";\n runtimeConfig: Parameters<typeof publishToFastKv>[0][\"runtimeConfig\"];\n configDir: string;\n registry?: string;\n network?: \"mainnet\" | \"testnet\";\n privateKey?: string;\n wallet?: boolean;\n verbose?: boolean;\n}\n\n/** Roll the registry config back to the previous snapshot in the FastKV\n * publish history — the post-publish smoke failure protocol (ADR 0021):\n * a red boot gate must never leave a new config live against an old image,\n * so the promotion is halted and the config reverts to the last known-good\n * snapshot within seconds. */\nasync function rollbackToPreviousSnapshot(\n input: RollbackToPreviousInput,\n): Promise<{ ok: boolean; blockHeight?: string; error?: string }> {\n let history: ConfigHistoryEntry[];\n try {\n history = await fetchConfigHistory({\n accountId: input.account,\n gatewayId: input.domain,\n registry: input.registry,\n limit: 20,\n });\n } catch (error) {\n return {\n ok: false,\n error: `fetching publish history failed: ${error instanceof Error ? error.message : String(error)}`,\n };\n }\n\n const previous = history[1];\n if (!previous) {\n return {\n ok: false,\n error: \"no previous snapshot in the publish history — nothing to roll back to\",\n };\n }\n\n const verification = await verifyRollbackSnapshot(previous.value as BosConfigInput);\n if (!verification.ok && verification.verifiable) {\n return {\n ok: false,\n error: \"rollback refused — a pinned slot's bytes are gone or no longer match their SRI\",\n };\n }\n\n const payload = buildRollbackPayload(\n BosConfigSchema.parse(previous.value),\n new Date().toISOString(),\n );\n const result = await publishToFastKv({\n bosConfig: payload,\n runtimeConfig: input.runtimeConfig,\n configDir: input.configDir,\n env: input.env,\n build: false,\n dryRun: false,\n verbose: input.verbose,\n packages: \"all\",\n network: input.network,\n privateKey: input.privateKey,\n wallet: input.wallet,\n registry: input.registry,\n exactPayload: true,\n });\n if (result.status !== \"published\") {\n return {\n ok: false,\n error: `rollback publish failed: ${result.error ?? result.status}`,\n };\n }\n return { ok: true, blockHeight: String(previous.blockHeight) };\n}\n\nexport function registerDeploy(builder: BosBuilder) {\n return {\n publish: builder.publish.handler(async ({ input, context }) => {\n const deps = Context.get(context[\"effect/context\"], BosDepsTag);\n const session = deps.session;\n if (!session?.config) {\n return {\n status: \"error\" as const,\n registryUrl: \"\",\n error: MISSING_CONFIG_MESSAGE,\n };\n }\n\n const result = await publishToFastKv({\n bosConfig: session.config,\n runtimeConfig: session.runtime,\n configDir: session.root,\n env: input.env,\n build: false,\n dryRun: input.dryRun,\n verbose: input.verbose,\n packages: \"all\",\n network: input.network,\n privateKey: input.privateKey,\n wallet: input.wallet,\n registry: input.registry,\n });\n\n return {\n status: result.status,\n registryUrl: result.registryUrl,\n txHash: result.txHash,\n error: result.error,\n built: result.built,\n skipped: result.skipped,\n deployResults: result.deployResults,\n };\n }),\n\n rollback: builder.rollback.handler(async ({ input, context }) => {\n const deps = Context.get(context[\"effect/context\"], BosDepsTag);\n const session = deps.session;\n if (!session) {\n return {\n status: \"error\" as const,\n registryUrl: \"\",\n error: MISSING_CONFIG_MESSAGE,\n };\n }\n\n const { account, domain } = session.config ?? {};\n if (!account || !domain) {\n return {\n status: \"error\" as const,\n registryUrl: \"\",\n error: \"authored config must define account and domain to roll back\",\n };\n }\n\n let history: ConfigHistoryEntry[];\n try {\n history = await fetchConfigHistory({\n accountId: account,\n gatewayId: domain,\n registry: input.registry,\n limit: input.limit,\n });\n } catch (error) {\n return {\n status: \"error\" as const,\n registryUrl: \"\",\n error: `Failed to fetch publish history: ${error instanceof Error ? error.message : String(error)}`,\n };\n }\n\n if (history.length === 0) {\n return {\n status: \"error\" as const,\n registryUrl: \"\",\n error: `No publish history for ${account}/${domain} — nothing to roll back to`,\n };\n }\n\n const historyEntries = history.map((entry) => ({\n blockHeight: entry.blockHeight,\n blockTimestamp: new Date(Number(entry.blockTimestampNs.slice(0, 13))).toISOString(),\n ...(entry.txHash ? { txHash: entry.txHash } : {}),\n summary: summarizeSlotPins(entry.value as BosConfigInput),\n }));\n\n if (input.listOnly) {\n return {\n status: \"list\" as const,\n registryUrl: \"\",\n history: historyEntries,\n };\n }\n\n const target =\n input.version !== undefined\n ? history.find((entry) => {\n const iso = new Date(Number(entry.blockTimestampNs.slice(0, 13))).toISOString();\n return (\n String(entry.blockHeight) === input.version ||\n iso.startsWith(input.version as string)\n );\n })\n : input.previous\n ? history[1]\n : undefined;\n\n if (!target) {\n return {\n status: \"error\" as const,\n registryUrl: \"\",\n history: historyEntries,\n error: input.version\n ? `No history entry matches \"${input.version}\" — pick a block height from the listing`\n : \"Specify --version <block-height> or --previous (interactive selection happens CLI-side)\",\n };\n }\n\n let liveValue: unknown;\n try {\n liveValue = await fetchBosConfigFromFastKv<unknown>(\n `bos://${account}/${domain}`,\n input.registry,\n );\n } catch {\n liveValue = undefined;\n }\n if (liveValue && JSON.stringify(liveValue) === JSON.stringify(target.value)) {\n return {\n status: \"error\" as const,\n registryUrl: \"\",\n error: \"The selected snapshot is identical to the live config — nothing to do\",\n };\n }\n\n const verification = await verifyRollbackSnapshot(target.value as BosConfigInput);\n const slotChecks = verification.slots.map((check) => ({\n slot: check.slot,\n ok: check.ok,\n ...(check.reason ? { reason: check.reason } : {}),\n }));\n if (!verification.ok && verification.verifiable) {\n return {\n status: \"error\" as const,\n registryUrl: \"\",\n verification: slotChecks,\n error:\n \"Refusing to roll back — a pinned slot's bytes are gone or no longer match their SRI\",\n };\n }\n\n if (!verification.verifiable && !input.force) {\n return {\n status: \"error\" as const,\n registryUrl: \"\",\n error:\n \"Snapshot predates version manifests and cannot be verified — its bytes were overwritten in place. Re-run with --force to publish it anyway.\",\n };\n }\n\n const parsedTarget = BosConfigSchema.parse(target.value);\n const payload = buildRollbackPayload(parsedTarget, new Date().toISOString());\n\n const result = await publishToFastKv({\n bosConfig: payload,\n runtimeConfig: session.runtime,\n configDir: session.root,\n env: input.env,\n build: false,\n dryRun: input.dryRun,\n verbose: input.verbose,\n packages: \"all\",\n network: input.network,\n privateKey: input.privateKey,\n wallet: input.wallet,\n registry: input.registry,\n exactPayload: true,\n });\n\n return {\n status: result.status,\n registryUrl: result.registryUrl,\n txHash: result.txHash,\n error: result.error,\n history: historyEntries,\n ...(slotChecks.length > 0 ? { verification: slotChecks } : {}),\n };\n }),\n\n deploy: builder.deploy.handler(async ({ input, context }) => {\n const deps = Context.get(context[\"effect/context\"], BosDepsTag);\n const session = deps.session;\n if (!session?.config) {\n return {\n status: \"error\" as const,\n registryUrl: \"\",\n error: MISSING_CONFIG_MESSAGE,\n };\n }\n\n if (input.statusList) {\n if (!session.config?.account || !session.config.domain) {\n return {\n status: \"error\" as const,\n registryUrl: \"\",\n error: \"authored config must define account and domain to list deploy manifests\",\n };\n }\n try {\n const manifests = await fetchDeployManifests({\n accountId: session.config.account,\n gatewayId: session.config.domain,\n registry: input.registry,\n });\n return {\n status: \"list\" as const,\n registryUrl: \"\",\n history: manifests.map((entry) => ({\n key: entry.key,\n blockHeight: entry.blockHeight,\n blockTimestamp: new Date(Number(entry.blockTimestampNs.slice(0, 13))).toISOString(),\n ...((entry.value as { txHash?: string } | undefined)?.txHash\n ? { txHash: (entry.value as { txHash?: string }).txHash }\n : {}),\n ...((entry.value as { publishedAt?: string } | undefined)?.publishedAt\n ? { publishedAt: (entry.value as { publishedAt?: string }).publishedAt }\n : {}),\n })),\n };\n } catch (error) {\n return {\n status: \"error\" as const,\n registryUrl: \"\",\n error: `Failed to list deploy manifests: ${error instanceof Error ? error.message : String(error)}`,\n };\n }\n }\n\n const imageDigestInput = input.imageDigest ?? process.env.BOS_IMAGE_DIGEST;\n if (imageDigestInput && !isValidImageDigest(imageDigestInput)) {\n return {\n status: \"error\" as const,\n registryUrl: \"\",\n error: `Invalid image digest \"${imageDigestInput}\" — expected sha256:<64 hex characters> (--image-digest / BOS_IMAGE_DIGEST)`,\n };\n }\n\n const result = await publishToFastKv({\n bosConfig: session.config,\n runtimeConfig: session.runtime,\n configDir: session.root,\n env: input.env,\n build: input.build,\n dryRun: input.dryRun,\n verbose: input.verbose,\n packages: input.packages,\n network: input.network,\n privateKey: input.privateKey,\n registry: input.registry,\n fullUpload: input.fullUpload,\n });\n\n if (result.status === \"error\") {\n return {\n status: \"error\" as const,\n registryUrl: result.registryUrl,\n txHash: result.txHash,\n built: result.built,\n skipped: result.skipped,\n error: result.error,\n deployResults: result.deployResults,\n };\n }\n\n if (result.status === \"dry-run\") {\n return {\n status: \"dry-run\" as const,\n registryUrl: result.registryUrl,\n built: result.built,\n skipped: result.skipped,\n };\n }\n\n let nextSession = session;\n if (result.publishConfig) {\n const opened = await openResolution({ cwd: session.root });\n if (opened) {\n nextSession = opened;\n }\n }\n\n const configPublished = {\n registryUrl: result.registryUrl,\n txHash: result.txHash,\n fingerprint: result.fingerprint,\n slotPins: result.slotPins,\n built: result.built,\n skipped: result.skipped,\n deployResults: result.deployResults,\n } as const;\n\n let image: string | undefined;\n let imageDigest: string | undefined;\n let service: string | undefined;\n\n const imagePlan = resolveDeployImagePlan({\n // A prebuilt digest pairs with the pushed ref (BOS_IMAGE from the CI\n // image job, or repository derivation) — ci.image is a build-time\n // choice, not a pin-time one; letting it win here could pin a digest\n // from a different image.\n imageRef: resolveImageRef({\n ciImage: imageDigestInput ? undefined : nextSession.config?.ci?.image,\n repository: nextSession.config?.repository,\n env: process.env,\n }),\n imageDigest: imageDigestInput,\n hasDocker: await hasDocker(),\n hasDockerfile: existsSync(join(session.root, \"Dockerfile\")),\n });\n\n if (imagePlan.kind === \"skip\") {\n console.log();\n console.log(colors.yellow(` Image skipped: ${imagePlan.reason}`));\n } else if (imagePlan.kind === \"prebuilt\") {\n image = imagePlan.image;\n imageDigest = imagePlan.digest;\n console.log();\n console.log(\n colors.green(\n ` ${icons.ok} Using pre-pushed image ${imagePlan.image}@${imagePlan.digest.slice(0, 19)}…`,\n ),\n );\n } else {\n console.log();\n try {\n const imageResult = await buildAndPushImage({\n image: imagePlan.image,\n configDir: session.root,\n verbose: input.verbose,\n });\n image = imageResult.image;\n imageDigest = imageResult.digest;\n console.log(\n colors.green(\n ` ${icons.ok} Image pushed ${imageResult.image}:${imageResult.tag}${imageResult.digest ? ` (${imageResult.digest.slice(0, 19)}…)` : \"\"}`,\n ),\n );\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n const imageError =\n message.includes(\"not found\") || message.includes(\"ENOENT\")\n ? \"Docker not found — install Docker to build the runtime image\"\n : `Image build/push failed: ${message}`;\n console.log(colors.yellow(` ${imageError}`));\n return {\n status: \"published\" as const,\n ...configPublished,\n error: `Config published but ${imageError}`,\n };\n }\n }\n\n // Post-publish boot gate (publish → smoke → Railway, ADR 0021): the\n // just-published config is only promoted to Railway if the runtime\n // image — the one this train pinned — boots against it. A red smoke\n // rolls the config back to the previous snapshot, so the new-config/\n // old-image window never survives the deploy run.\n if (input.smoke) {\n if (!image || !imageDigest) {\n console.log();\n console.log(\n colors.yellow(\n \" Post-publish smoke skipped: no runtime image digest — set ci.image (or pass BOS_IMAGE_DIGEST)\",\n ),\n );\n } else {\n console.log();\n console.log(colors.cyan(\" ▶ Post-publish smoke (boot gate)\"));\n const smokeResult = await runSmoke({\n image: imageRefWithDigest(image, imageDigest),\n account: nextSession.config?.account,\n gateway: nextSession.config?.domain,\n });\n if (!smokeResult.ok) {\n const cause = smokeResult.error ?? \"unknown failure\";\n console.log();\n console.log(colors.error(` ${icons.err} Post-publish smoke failed: ${cause}`));\n if (smokeResult.diagnosis) {\n console.log(colors.yellow(` ${smokeResult.diagnosis}`));\n }\n const account = nextSession.config?.account;\n const domain = nextSession.config?.domain;\n if (!account || !domain) {\n console.log(colors.yellow(\" Rollback skipped: no registry identity in the session\"));\n return {\n status: \"published\" as const,\n ...configPublished,\n image,\n error: `Post-publish smoke failed (${cause}) — config NOT rolled back: no registry identity`,\n };\n }\n console.log(colors.yellow(\" Rolling the published config back…\"));\n const rollback = await rollbackToPreviousSnapshot({\n account,\n domain,\n env: input.env,\n runtimeConfig: nextSession.runtime,\n configDir: nextSession.root,\n registry: input.registry,\n network: input.network,\n privateKey: input.privateKey,\n wallet: input.wallet,\n verbose: input.verbose,\n });\n const rollbackNote = rollback.ok\n ? `config rolled back to block ${rollback.blockHeight}`\n : `config NOT rolled back: ${rollback.error ?? \"unknown\"}`;\n console.log(colors.yellow(` ${rollbackNote}`));\n return {\n status: \"published\" as const,\n ...configPublished,\n image,\n error: `Post-publish smoke failed (${cause}) — ${rollbackNote}`,\n };\n }\n console.log(\n colors.green(` ${icons.ok} Post-publish smoke passed: ${smokeResult.summary ?? \"\"}`),\n );\n }\n }\n\n if (process.env.RAILWAY_TOKEN) {\n const railwayService = input.service ?? nextSession.config?.ci?.railway?.service;\n if (!railwayService) {\n console.log();\n console.log(\n colors.yellow(\n \" Railway deploy skipped: ci.railway.service is not configured in bos.config.json\",\n ),\n );\n return {\n status: \"published\" as const,\n ...configPublished,\n image,\n error:\n \"Config published but Railway deploy failed: ci.railway.service is not configured in bos.config.json\",\n };\n }\n if (!image || !imageDigest) {\n console.log();\n console.log(\n colors.yellow(\n \" Railway deploy skipped: no runtime image digest — set ci.image (or pass BOS_IMAGE_DIGEST) and make the image available\",\n ),\n );\n return {\n status: \"published\" as const,\n ...configPublished,\n image,\n service: railwayService,\n error:\n \"Config published but Railway deploy requires a runtime image (set ci.image, or pass BOS_IMAGE_DIGEST for a pre-pushed image)\",\n };\n }\n\n service = railwayService;\n try {\n await deployImageToRailway({\n service: railwayService,\n configDir: session.root,\n });\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n const railError =\n message.includes(\"not found\") || message.includes(\"ENOENT\")\n ? \"Railway CLI not found. Install it: npm i -g @railway/cli\"\n : `Railway deploy failed: ${message}`;\n console.log(colors.yellow(` ${railError}`));\n return {\n status: \"published\" as const,\n ...configPublished,\n image,\n service,\n error: `Config published but ${railError}`,\n };\n }\n } else {\n console.log();\n console.log(colors.yellow(\" Railway deploy skipped (RAILWAY_TOKEN not set)\"));\n }\n\n return {\n status: \"deployed\" as const,\n ...configPublished,\n image,\n service,\n };\n }),\n };\n}\n"],"mappings":"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"}
|
package/dist/commands/deploy.mjs
CHANGED
|
@@ -2,17 +2,73 @@ import { BosConfigSchema } from "../types.mjs";
|
|
|
2
2
|
import { fetchBosConfigFromFastKv, fetchConfigHistory, fetchDeployManifests } from "../fastkv.mjs";
|
|
3
3
|
import { MISSING_CONFIG_MESSAGE } from "../config.mjs";
|
|
4
4
|
import { colors, icons } from "../utils/theme.mjs";
|
|
5
|
-
import { buildAndPushImage, deployImageToRailway, hasDocker, isValidImageDigest, resolveDeployImagePlan, resolveImageRef } from "../image-deploy.mjs";
|
|
5
|
+
import { buildAndPushImage, deployImageToRailway, hasDocker, imageRefWithDigest, isValidImageDigest, resolveDeployImagePlan, resolveImageRef } from "../image-deploy.mjs";
|
|
6
6
|
import { openResolution } from "../resolution/session.mjs";
|
|
7
7
|
import { BosDepsTag } from "./shared.mjs";
|
|
8
8
|
import { buildRollbackPayload, summarizeSlotPins, verifyRollbackSnapshot } from "../rollback.mjs";
|
|
9
9
|
import { publishToFastKv } from "../publish.mjs";
|
|
10
|
+
import { runSmoke } from "../smoke.mjs";
|
|
10
11
|
import { existsSync } from "node:fs";
|
|
11
12
|
import { join } from "node:path";
|
|
12
13
|
import { Context } from "effect";
|
|
13
14
|
import process from "node:process";
|
|
14
15
|
|
|
15
16
|
//#region src/commands/deploy.ts
|
|
17
|
+
/** Roll the registry config back to the previous snapshot in the FastKV
|
|
18
|
+
* publish history — the post-publish smoke failure protocol (ADR 0021):
|
|
19
|
+
* a red boot gate must never leave a new config live against an old image,
|
|
20
|
+
* so the promotion is halted and the config reverts to the last known-good
|
|
21
|
+
* snapshot within seconds. */
|
|
22
|
+
async function rollbackToPreviousSnapshot(input) {
|
|
23
|
+
let history;
|
|
24
|
+
try {
|
|
25
|
+
history = await fetchConfigHistory({
|
|
26
|
+
accountId: input.account,
|
|
27
|
+
gatewayId: input.domain,
|
|
28
|
+
registry: input.registry,
|
|
29
|
+
limit: 20
|
|
30
|
+
});
|
|
31
|
+
} catch (error) {
|
|
32
|
+
return {
|
|
33
|
+
ok: false,
|
|
34
|
+
error: `fetching publish history failed: ${error instanceof Error ? error.message : String(error)}`
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
const previous = history[1];
|
|
38
|
+
if (!previous) return {
|
|
39
|
+
ok: false,
|
|
40
|
+
error: "no previous snapshot in the publish history — nothing to roll back to"
|
|
41
|
+
};
|
|
42
|
+
const verification = await verifyRollbackSnapshot(previous.value);
|
|
43
|
+
if (!verification.ok && verification.verifiable) return {
|
|
44
|
+
ok: false,
|
|
45
|
+
error: "rollback refused — a pinned slot's bytes are gone or no longer match their SRI"
|
|
46
|
+
};
|
|
47
|
+
const payload = buildRollbackPayload(BosConfigSchema.parse(previous.value), (/* @__PURE__ */ new Date()).toISOString());
|
|
48
|
+
const result = await publishToFastKv({
|
|
49
|
+
bosConfig: payload,
|
|
50
|
+
runtimeConfig: input.runtimeConfig,
|
|
51
|
+
configDir: input.configDir,
|
|
52
|
+
env: input.env,
|
|
53
|
+
build: false,
|
|
54
|
+
dryRun: false,
|
|
55
|
+
verbose: input.verbose,
|
|
56
|
+
packages: "all",
|
|
57
|
+
network: input.network,
|
|
58
|
+
privateKey: input.privateKey,
|
|
59
|
+
wallet: input.wallet,
|
|
60
|
+
registry: input.registry,
|
|
61
|
+
exactPayload: true
|
|
62
|
+
});
|
|
63
|
+
if (result.status !== "published") return {
|
|
64
|
+
ok: false,
|
|
65
|
+
error: `rollback publish failed: ${result.error ?? result.status}`
|
|
66
|
+
};
|
|
67
|
+
return {
|
|
68
|
+
ok: true,
|
|
69
|
+
blockHeight: String(previous.blockHeight)
|
|
70
|
+
};
|
|
71
|
+
}
|
|
16
72
|
function registerDeploy(builder) {
|
|
17
73
|
return {
|
|
18
74
|
publish: builder.publish.handler(async ({ input, context }) => {
|
|
@@ -142,7 +198,8 @@ function registerDeploy(builder) {
|
|
|
142
198
|
network: input.network,
|
|
143
199
|
privateKey: input.privateKey,
|
|
144
200
|
wallet: input.wallet,
|
|
145
|
-
registry: input.registry
|
|
201
|
+
registry: input.registry,
|
|
202
|
+
exactPayload: true
|
|
146
203
|
});
|
|
147
204
|
return {
|
|
148
205
|
status: result.status,
|
|
@@ -282,6 +339,59 @@ function registerDeploy(builder) {
|
|
|
282
339
|
};
|
|
283
340
|
}
|
|
284
341
|
}
|
|
342
|
+
if (input.smoke) {
|
|
343
|
+
if (!image || !imageDigest) {
|
|
344
|
+
console.log();
|
|
345
|
+
console.log(colors.yellow(" Post-publish smoke skipped: no runtime image digest — set ci.image (or pass BOS_IMAGE_DIGEST)"));
|
|
346
|
+
} else {
|
|
347
|
+
console.log();
|
|
348
|
+
console.log(colors.cyan(" ▶ Post-publish smoke (boot gate)"));
|
|
349
|
+
const smokeResult = await runSmoke({
|
|
350
|
+
image: imageRefWithDigest(image, imageDigest),
|
|
351
|
+
account: nextSession.config?.account,
|
|
352
|
+
gateway: nextSession.config?.domain
|
|
353
|
+
});
|
|
354
|
+
if (!smokeResult.ok) {
|
|
355
|
+
const cause = smokeResult.error ?? "unknown failure";
|
|
356
|
+
console.log();
|
|
357
|
+
console.log(colors.error(` ${icons.err} Post-publish smoke failed: ${cause}`));
|
|
358
|
+
if (smokeResult.diagnosis) console.log(colors.yellow(` ${smokeResult.diagnosis}`));
|
|
359
|
+
const account = nextSession.config?.account;
|
|
360
|
+
const domain = nextSession.config?.domain;
|
|
361
|
+
if (!account || !domain) {
|
|
362
|
+
console.log(colors.yellow(" Rollback skipped: no registry identity in the session"));
|
|
363
|
+
return {
|
|
364
|
+
status: "published",
|
|
365
|
+
...configPublished,
|
|
366
|
+
image,
|
|
367
|
+
error: `Post-publish smoke failed (${cause}) — config NOT rolled back: no registry identity`
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
console.log(colors.yellow(" Rolling the published config back…"));
|
|
371
|
+
const rollback = await rollbackToPreviousSnapshot({
|
|
372
|
+
account,
|
|
373
|
+
domain,
|
|
374
|
+
env: input.env,
|
|
375
|
+
runtimeConfig: nextSession.runtime,
|
|
376
|
+
configDir: nextSession.root,
|
|
377
|
+
registry: input.registry,
|
|
378
|
+
network: input.network,
|
|
379
|
+
privateKey: input.privateKey,
|
|
380
|
+
wallet: input.wallet,
|
|
381
|
+
verbose: input.verbose
|
|
382
|
+
});
|
|
383
|
+
const rollbackNote = rollback.ok ? `config rolled back to block ${rollback.blockHeight}` : `config NOT rolled back: ${rollback.error ?? "unknown"}`;
|
|
384
|
+
console.log(colors.yellow(` ${rollbackNote}`));
|
|
385
|
+
return {
|
|
386
|
+
status: "published",
|
|
387
|
+
...configPublished,
|
|
388
|
+
image,
|
|
389
|
+
error: `Post-publish smoke failed (${cause}) — ${rollbackNote}`
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
console.log(colors.green(` ${icons.ok} Post-publish smoke passed: ${smokeResult.summary ?? ""}`));
|
|
393
|
+
}
|
|
394
|
+
}
|
|
285
395
|
if (process.env.RAILWAY_TOKEN) {
|
|
286
396
|
const railwayService = input.service ?? nextSession.config?.ci?.railway?.service;
|
|
287
397
|
if (!railwayService) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"deploy.mjs","names":[],"sources":["../../src/commands/deploy.ts"],"sourcesContent":["import { existsSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport process from \"node:process\";\nimport { Context } from \"effect\";\nimport { MISSING_CONFIG_MESSAGE } from \"../config\";\nimport {\n type ConfigHistoryEntry,\n fetchBosConfigFromFastKv,\n fetchConfigHistory,\n fetchDeployManifests,\n} from \"../fastkv\";\nimport {\n buildAndPushImage,\n deployImageToRailway,\n hasDocker,\n isValidImageDigest,\n resolveDeployImagePlan,\n resolveImageRef,\n} from \"../image-deploy\";\nimport { publishToFastKv } from \"../publish\";\nimport { openResolution } from \"../resolution/session\";\nimport { buildRollbackPayload, summarizeSlotPins, verifyRollbackSnapshot } from \"../rollback\";\nimport type { BosConfigInput } from \"../types\";\nimport { BosConfigSchema } from \"../types\";\nimport { colors, icons } from \"../utils/theme\";\nimport { type BosBuilder, BosDepsTag } from \"./shared\";\n\nexport function registerDeploy(builder: BosBuilder) {\n return {\n publish: builder.publish.handler(async ({ input, context }) => {\n const deps = Context.get(context[\"effect/context\"], BosDepsTag);\n const session = deps.session;\n if (!session?.config) {\n return {\n status: \"error\" as const,\n registryUrl: \"\",\n error: MISSING_CONFIG_MESSAGE,\n };\n }\n\n const result = await publishToFastKv({\n bosConfig: session.config,\n runtimeConfig: session.runtime,\n configDir: session.root,\n env: input.env,\n build: false,\n dryRun: input.dryRun,\n verbose: input.verbose,\n packages: \"all\",\n network: input.network,\n privateKey: input.privateKey,\n wallet: input.wallet,\n registry: input.registry,\n });\n\n return {\n status: result.status,\n registryUrl: result.registryUrl,\n txHash: result.txHash,\n error: result.error,\n built: result.built,\n skipped: result.skipped,\n deployResults: result.deployResults,\n };\n }),\n\n rollback: builder.rollback.handler(async ({ input, context }) => {\n const deps = Context.get(context[\"effect/context\"], BosDepsTag);\n const session = deps.session;\n if (!session) {\n return {\n status: \"error\" as const,\n registryUrl: \"\",\n error: MISSING_CONFIG_MESSAGE,\n };\n }\n\n const { account, domain } = session.config ?? {};\n if (!account || !domain) {\n return {\n status: \"error\" as const,\n registryUrl: \"\",\n error: \"authored config must define account and domain to roll back\",\n };\n }\n\n let history: ConfigHistoryEntry[];\n try {\n history = await fetchConfigHistory({\n accountId: account,\n gatewayId: domain,\n registry: input.registry,\n limit: input.limit,\n });\n } catch (error) {\n return {\n status: \"error\" as const,\n registryUrl: \"\",\n error: `Failed to fetch publish history: ${error instanceof Error ? error.message : String(error)}`,\n };\n }\n\n if (history.length === 0) {\n return {\n status: \"error\" as const,\n registryUrl: \"\",\n error: `No publish history for ${account}/${domain} — nothing to roll back to`,\n };\n }\n\n const historyEntries = history.map((entry) => ({\n blockHeight: entry.blockHeight,\n blockTimestamp: new Date(Number(entry.blockTimestampNs.slice(0, 13))).toISOString(),\n ...(entry.txHash ? { txHash: entry.txHash } : {}),\n summary: summarizeSlotPins(entry.value as BosConfigInput),\n }));\n\n if (input.listOnly) {\n return {\n status: \"list\" as const,\n registryUrl: \"\",\n history: historyEntries,\n };\n }\n\n const target =\n input.version !== undefined\n ? history.find((entry) => {\n const iso = new Date(Number(entry.blockTimestampNs.slice(0, 13))).toISOString();\n return (\n String(entry.blockHeight) === input.version ||\n iso.startsWith(input.version as string)\n );\n })\n : input.previous\n ? history[1]\n : undefined;\n\n if (!target) {\n return {\n status: \"error\" as const,\n registryUrl: \"\",\n history: historyEntries,\n error: input.version\n ? `No history entry matches \"${input.version}\" — pick a block height from the listing`\n : \"Specify --version <block-height> or --previous (interactive selection happens CLI-side)\",\n };\n }\n\n let liveValue: unknown;\n try {\n liveValue = await fetchBosConfigFromFastKv<unknown>(\n `bos://${account}/${domain}`,\n input.registry,\n );\n } catch {\n liveValue = undefined;\n }\n if (liveValue && JSON.stringify(liveValue) === JSON.stringify(target.value)) {\n return {\n status: \"error\" as const,\n registryUrl: \"\",\n error: \"The selected snapshot is identical to the live config — nothing to do\",\n };\n }\n\n const verification = await verifyRollbackSnapshot(target.value as BosConfigInput);\n const slotChecks = verification.slots.map((check) => ({\n slot: check.slot,\n ok: check.ok,\n ...(check.reason ? { reason: check.reason } : {}),\n }));\n if (!verification.ok && verification.verifiable) {\n return {\n status: \"error\" as const,\n registryUrl: \"\",\n verification: slotChecks,\n error:\n \"Refusing to roll back — a pinned slot's bytes are gone or no longer match their SRI\",\n };\n }\n\n if (!verification.verifiable && !input.force) {\n return {\n status: \"error\" as const,\n registryUrl: \"\",\n error:\n \"Snapshot predates version manifests and cannot be verified — its bytes were overwritten in place. Re-run with --force to publish it anyway.\",\n };\n }\n\n const parsedTarget = BosConfigSchema.parse(target.value);\n const payload = buildRollbackPayload(parsedTarget, new Date().toISOString());\n\n const result = await publishToFastKv({\n bosConfig: payload,\n runtimeConfig: session.runtime,\n configDir: session.root,\n env: input.env,\n build: false,\n dryRun: input.dryRun,\n verbose: input.verbose,\n packages: \"all\",\n network: input.network,\n privateKey: input.privateKey,\n wallet: input.wallet,\n registry: input.registry,\n });\n\n return {\n status: result.status,\n registryUrl: result.registryUrl,\n txHash: result.txHash,\n error: result.error,\n history: historyEntries,\n ...(slotChecks.length > 0 ? { verification: slotChecks } : {}),\n };\n }),\n\n deploy: builder.deploy.handler(async ({ input, context }) => {\n const deps = Context.get(context[\"effect/context\"], BosDepsTag);\n const session = deps.session;\n if (!session?.config) {\n return {\n status: \"error\" as const,\n registryUrl: \"\",\n error: MISSING_CONFIG_MESSAGE,\n };\n }\n\n if (input.statusList) {\n if (!session.config?.account || !session.config.domain) {\n return {\n status: \"error\" as const,\n registryUrl: \"\",\n error: \"authored config must define account and domain to list deploy manifests\",\n };\n }\n try {\n const manifests = await fetchDeployManifests({\n accountId: session.config.account,\n gatewayId: session.config.domain,\n registry: input.registry,\n });\n return {\n status: \"list\" as const,\n registryUrl: \"\",\n history: manifests.map((entry) => ({\n key: entry.key,\n blockHeight: entry.blockHeight,\n blockTimestamp: new Date(Number(entry.blockTimestampNs.slice(0, 13))).toISOString(),\n ...((entry.value as { txHash?: string } | undefined)?.txHash\n ? { txHash: (entry.value as { txHash?: string }).txHash }\n : {}),\n ...((entry.value as { publishedAt?: string } | undefined)?.publishedAt\n ? { publishedAt: (entry.value as { publishedAt?: string }).publishedAt }\n : {}),\n })),\n };\n } catch (error) {\n return {\n status: \"error\" as const,\n registryUrl: \"\",\n error: `Failed to list deploy manifests: ${error instanceof Error ? error.message : String(error)}`,\n };\n }\n }\n\n const imageDigestInput = input.imageDigest ?? process.env.BOS_IMAGE_DIGEST;\n if (imageDigestInput && !isValidImageDigest(imageDigestInput)) {\n return {\n status: \"error\" as const,\n registryUrl: \"\",\n error: `Invalid image digest \"${imageDigestInput}\" — expected sha256:<64 hex characters> (--image-digest / BOS_IMAGE_DIGEST)`,\n };\n }\n\n const result = await publishToFastKv({\n bosConfig: session.config,\n runtimeConfig: session.runtime,\n configDir: session.root,\n env: input.env,\n build: input.build,\n dryRun: input.dryRun,\n verbose: input.verbose,\n packages: input.packages,\n network: input.network,\n privateKey: input.privateKey,\n registry: input.registry,\n fullUpload: input.fullUpload,\n });\n\n if (result.status === \"error\") {\n return {\n status: \"error\" as const,\n registryUrl: result.registryUrl,\n txHash: result.txHash,\n built: result.built,\n skipped: result.skipped,\n error: result.error,\n deployResults: result.deployResults,\n };\n }\n\n if (result.status === \"dry-run\") {\n return {\n status: \"dry-run\" as const,\n registryUrl: result.registryUrl,\n built: result.built,\n skipped: result.skipped,\n };\n }\n\n let nextSession = session;\n if (result.publishConfig) {\n const opened = await openResolution({ cwd: session.root });\n if (opened) {\n nextSession = opened;\n }\n }\n\n const configPublished = {\n registryUrl: result.registryUrl,\n txHash: result.txHash,\n fingerprint: result.fingerprint,\n slotPins: result.slotPins,\n built: result.built,\n skipped: result.skipped,\n deployResults: result.deployResults,\n } as const;\n\n let image: string | undefined;\n let imageDigest: string | undefined;\n let service: string | undefined;\n\n const imagePlan = resolveDeployImagePlan({\n // A prebuilt digest pairs with the pushed ref (BOS_IMAGE from the CI\n // image job, or repository derivation) — ci.image is a build-time\n // choice, not a pin-time one; letting it win here could pin a digest\n // from a different image.\n imageRef: resolveImageRef({\n ciImage: imageDigestInput ? undefined : nextSession.config?.ci?.image,\n repository: nextSession.config?.repository,\n env: process.env,\n }),\n imageDigest: imageDigestInput,\n hasDocker: await hasDocker(),\n hasDockerfile: existsSync(join(session.root, \"Dockerfile\")),\n });\n\n if (imagePlan.kind === \"skip\") {\n console.log();\n console.log(colors.yellow(` Image skipped: ${imagePlan.reason}`));\n } else if (imagePlan.kind === \"prebuilt\") {\n image = imagePlan.image;\n imageDigest = imagePlan.digest;\n console.log();\n console.log(\n colors.green(\n ` ${icons.ok} Using pre-pushed image ${imagePlan.image}@${imagePlan.digest.slice(0, 19)}…`,\n ),\n );\n } else {\n console.log();\n try {\n const imageResult = await buildAndPushImage({\n image: imagePlan.image,\n configDir: session.root,\n verbose: input.verbose,\n });\n image = imageResult.image;\n imageDigest = imageResult.digest;\n console.log(\n colors.green(\n ` ${icons.ok} Image pushed ${imageResult.image}:${imageResult.tag}${imageResult.digest ? ` (${imageResult.digest.slice(0, 19)}…)` : \"\"}`,\n ),\n );\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n const imageError =\n message.includes(\"not found\") || message.includes(\"ENOENT\")\n ? \"Docker not found — install Docker to build the runtime image\"\n : `Image build/push failed: ${message}`;\n console.log(colors.yellow(` ${imageError}`));\n return {\n status: \"published\" as const,\n ...configPublished,\n error: `Config published but ${imageError}`,\n };\n }\n }\n\n if (process.env.RAILWAY_TOKEN) {\n const railwayService = input.service ?? nextSession.config?.ci?.railway?.service;\n if (!railwayService) {\n console.log();\n console.log(\n colors.yellow(\n \" Railway deploy skipped: ci.railway.service is not configured in bos.config.json\",\n ),\n );\n return {\n status: \"published\" as const,\n ...configPublished,\n image,\n error:\n \"Config published but Railway deploy failed: ci.railway.service is not configured in bos.config.json\",\n };\n }\n if (!image || !imageDigest) {\n console.log();\n console.log(\n colors.yellow(\n \" Railway deploy skipped: no runtime image digest — set ci.image (or pass BOS_IMAGE_DIGEST) and make the image available\",\n ),\n );\n return {\n status: \"published\" as const,\n ...configPublished,\n image,\n service: railwayService,\n error:\n \"Config published but Railway deploy requires a runtime image (set ci.image, or pass BOS_IMAGE_DIGEST for a pre-pushed image)\",\n };\n }\n\n service = railwayService;\n try {\n await deployImageToRailway({\n service: railwayService,\n configDir: session.root,\n });\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n const railError =\n message.includes(\"not found\") || message.includes(\"ENOENT\")\n ? \"Railway CLI not found. Install it: npm i -g @railway/cli\"\n : `Railway deploy failed: ${message}`;\n console.log(colors.yellow(` ${railError}`));\n return {\n status: \"published\" as const,\n ...configPublished,\n image,\n service,\n error: `Config published but ${railError}`,\n };\n }\n } else {\n console.log();\n console.log(colors.yellow(\" Railway deploy skipped (RAILWAY_TOKEN not set)\"));\n }\n\n return {\n status: \"deployed\" as const,\n ...configPublished,\n image,\n service,\n };\n }),\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"deploy.mjs","names":[],"sources":["../../src/commands/deploy.ts"],"sourcesContent":["import { existsSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport process from \"node:process\";\nimport { Context } from \"effect\";\nimport { MISSING_CONFIG_MESSAGE } from \"../config\";\nimport {\n type ConfigHistoryEntry,\n fetchBosConfigFromFastKv,\n fetchConfigHistory,\n fetchDeployManifests,\n} from \"../fastkv\";\nimport {\n buildAndPushImage,\n deployImageToRailway,\n hasDocker,\n imageRefWithDigest,\n isValidImageDigest,\n resolveDeployImagePlan,\n resolveImageRef,\n} from \"../image-deploy\";\nimport { publishToFastKv } from \"../publish\";\nimport { openResolution } from \"../resolution/session\";\nimport { buildRollbackPayload, summarizeSlotPins, verifyRollbackSnapshot } from \"../rollback\";\nimport { runSmoke } from \"../smoke\";\nimport type { BosConfigInput } from \"../types\";\nimport { BosConfigSchema } from \"../types\";\nimport { colors, icons } from \"../utils/theme\";\nimport { type BosBuilder, BosDepsTag } from \"./shared\";\n\ninterface RollbackToPreviousInput {\n account: string;\n domain: string;\n env: \"production\" | \"staging\";\n runtimeConfig: Parameters<typeof publishToFastKv>[0][\"runtimeConfig\"];\n configDir: string;\n registry?: string;\n network?: \"mainnet\" | \"testnet\";\n privateKey?: string;\n wallet?: boolean;\n verbose?: boolean;\n}\n\n/** Roll the registry config back to the previous snapshot in the FastKV\n * publish history — the post-publish smoke failure protocol (ADR 0021):\n * a red boot gate must never leave a new config live against an old image,\n * so the promotion is halted and the config reverts to the last known-good\n * snapshot within seconds. */\nasync function rollbackToPreviousSnapshot(\n input: RollbackToPreviousInput,\n): Promise<{ ok: boolean; blockHeight?: string; error?: string }> {\n let history: ConfigHistoryEntry[];\n try {\n history = await fetchConfigHistory({\n accountId: input.account,\n gatewayId: input.domain,\n registry: input.registry,\n limit: 20,\n });\n } catch (error) {\n return {\n ok: false,\n error: `fetching publish history failed: ${error instanceof Error ? error.message : String(error)}`,\n };\n }\n\n const previous = history[1];\n if (!previous) {\n return {\n ok: false,\n error: \"no previous snapshot in the publish history — nothing to roll back to\",\n };\n }\n\n const verification = await verifyRollbackSnapshot(previous.value as BosConfigInput);\n if (!verification.ok && verification.verifiable) {\n return {\n ok: false,\n error: \"rollback refused — a pinned slot's bytes are gone or no longer match their SRI\",\n };\n }\n\n const payload = buildRollbackPayload(\n BosConfigSchema.parse(previous.value),\n new Date().toISOString(),\n );\n const result = await publishToFastKv({\n bosConfig: payload,\n runtimeConfig: input.runtimeConfig,\n configDir: input.configDir,\n env: input.env,\n build: false,\n dryRun: false,\n verbose: input.verbose,\n packages: \"all\",\n network: input.network,\n privateKey: input.privateKey,\n wallet: input.wallet,\n registry: input.registry,\n exactPayload: true,\n });\n if (result.status !== \"published\") {\n return {\n ok: false,\n error: `rollback publish failed: ${result.error ?? result.status}`,\n };\n }\n return { ok: true, blockHeight: String(previous.blockHeight) };\n}\n\nexport function registerDeploy(builder: BosBuilder) {\n return {\n publish: builder.publish.handler(async ({ input, context }) => {\n const deps = Context.get(context[\"effect/context\"], BosDepsTag);\n const session = deps.session;\n if (!session?.config) {\n return {\n status: \"error\" as const,\n registryUrl: \"\",\n error: MISSING_CONFIG_MESSAGE,\n };\n }\n\n const result = await publishToFastKv({\n bosConfig: session.config,\n runtimeConfig: session.runtime,\n configDir: session.root,\n env: input.env,\n build: false,\n dryRun: input.dryRun,\n verbose: input.verbose,\n packages: \"all\",\n network: input.network,\n privateKey: input.privateKey,\n wallet: input.wallet,\n registry: input.registry,\n });\n\n return {\n status: result.status,\n registryUrl: result.registryUrl,\n txHash: result.txHash,\n error: result.error,\n built: result.built,\n skipped: result.skipped,\n deployResults: result.deployResults,\n };\n }),\n\n rollback: builder.rollback.handler(async ({ input, context }) => {\n const deps = Context.get(context[\"effect/context\"], BosDepsTag);\n const session = deps.session;\n if (!session) {\n return {\n status: \"error\" as const,\n registryUrl: \"\",\n error: MISSING_CONFIG_MESSAGE,\n };\n }\n\n const { account, domain } = session.config ?? {};\n if (!account || !domain) {\n return {\n status: \"error\" as const,\n registryUrl: \"\",\n error: \"authored config must define account and domain to roll back\",\n };\n }\n\n let history: ConfigHistoryEntry[];\n try {\n history = await fetchConfigHistory({\n accountId: account,\n gatewayId: domain,\n registry: input.registry,\n limit: input.limit,\n });\n } catch (error) {\n return {\n status: \"error\" as const,\n registryUrl: \"\",\n error: `Failed to fetch publish history: ${error instanceof Error ? error.message : String(error)}`,\n };\n }\n\n if (history.length === 0) {\n return {\n status: \"error\" as const,\n registryUrl: \"\",\n error: `No publish history for ${account}/${domain} — nothing to roll back to`,\n };\n }\n\n const historyEntries = history.map((entry) => ({\n blockHeight: entry.blockHeight,\n blockTimestamp: new Date(Number(entry.blockTimestampNs.slice(0, 13))).toISOString(),\n ...(entry.txHash ? { txHash: entry.txHash } : {}),\n summary: summarizeSlotPins(entry.value as BosConfigInput),\n }));\n\n if (input.listOnly) {\n return {\n status: \"list\" as const,\n registryUrl: \"\",\n history: historyEntries,\n };\n }\n\n const target =\n input.version !== undefined\n ? history.find((entry) => {\n const iso = new Date(Number(entry.blockTimestampNs.slice(0, 13))).toISOString();\n return (\n String(entry.blockHeight) === input.version ||\n iso.startsWith(input.version as string)\n );\n })\n : input.previous\n ? history[1]\n : undefined;\n\n if (!target) {\n return {\n status: \"error\" as const,\n registryUrl: \"\",\n history: historyEntries,\n error: input.version\n ? `No history entry matches \"${input.version}\" — pick a block height from the listing`\n : \"Specify --version <block-height> or --previous (interactive selection happens CLI-side)\",\n };\n }\n\n let liveValue: unknown;\n try {\n liveValue = await fetchBosConfigFromFastKv<unknown>(\n `bos://${account}/${domain}`,\n input.registry,\n );\n } catch {\n liveValue = undefined;\n }\n if (liveValue && JSON.stringify(liveValue) === JSON.stringify(target.value)) {\n return {\n status: \"error\" as const,\n registryUrl: \"\",\n error: \"The selected snapshot is identical to the live config — nothing to do\",\n };\n }\n\n const verification = await verifyRollbackSnapshot(target.value as BosConfigInput);\n const slotChecks = verification.slots.map((check) => ({\n slot: check.slot,\n ok: check.ok,\n ...(check.reason ? { reason: check.reason } : {}),\n }));\n if (!verification.ok && verification.verifiable) {\n return {\n status: \"error\" as const,\n registryUrl: \"\",\n verification: slotChecks,\n error:\n \"Refusing to roll back — a pinned slot's bytes are gone or no longer match their SRI\",\n };\n }\n\n if (!verification.verifiable && !input.force) {\n return {\n status: \"error\" as const,\n registryUrl: \"\",\n error:\n \"Snapshot predates version manifests and cannot be verified — its bytes were overwritten in place. Re-run with --force to publish it anyway.\",\n };\n }\n\n const parsedTarget = BosConfigSchema.parse(target.value);\n const payload = buildRollbackPayload(parsedTarget, new Date().toISOString());\n\n const result = await publishToFastKv({\n bosConfig: payload,\n runtimeConfig: session.runtime,\n configDir: session.root,\n env: input.env,\n build: false,\n dryRun: input.dryRun,\n verbose: input.verbose,\n packages: \"all\",\n network: input.network,\n privateKey: input.privateKey,\n wallet: input.wallet,\n registry: input.registry,\n exactPayload: true,\n });\n\n return {\n status: result.status,\n registryUrl: result.registryUrl,\n txHash: result.txHash,\n error: result.error,\n history: historyEntries,\n ...(slotChecks.length > 0 ? { verification: slotChecks } : {}),\n };\n }),\n\n deploy: builder.deploy.handler(async ({ input, context }) => {\n const deps = Context.get(context[\"effect/context\"], BosDepsTag);\n const session = deps.session;\n if (!session?.config) {\n return {\n status: \"error\" as const,\n registryUrl: \"\",\n error: MISSING_CONFIG_MESSAGE,\n };\n }\n\n if (input.statusList) {\n if (!session.config?.account || !session.config.domain) {\n return {\n status: \"error\" as const,\n registryUrl: \"\",\n error: \"authored config must define account and domain to list deploy manifests\",\n };\n }\n try {\n const manifests = await fetchDeployManifests({\n accountId: session.config.account,\n gatewayId: session.config.domain,\n registry: input.registry,\n });\n return {\n status: \"list\" as const,\n registryUrl: \"\",\n history: manifests.map((entry) => ({\n key: entry.key,\n blockHeight: entry.blockHeight,\n blockTimestamp: new Date(Number(entry.blockTimestampNs.slice(0, 13))).toISOString(),\n ...((entry.value as { txHash?: string } | undefined)?.txHash\n ? { txHash: (entry.value as { txHash?: string }).txHash }\n : {}),\n ...((entry.value as { publishedAt?: string } | undefined)?.publishedAt\n ? { publishedAt: (entry.value as { publishedAt?: string }).publishedAt }\n : {}),\n })),\n };\n } catch (error) {\n return {\n status: \"error\" as const,\n registryUrl: \"\",\n error: `Failed to list deploy manifests: ${error instanceof Error ? error.message : String(error)}`,\n };\n }\n }\n\n const imageDigestInput = input.imageDigest ?? process.env.BOS_IMAGE_DIGEST;\n if (imageDigestInput && !isValidImageDigest(imageDigestInput)) {\n return {\n status: \"error\" as const,\n registryUrl: \"\",\n error: `Invalid image digest \"${imageDigestInput}\" — expected sha256:<64 hex characters> (--image-digest / BOS_IMAGE_DIGEST)`,\n };\n }\n\n const result = await publishToFastKv({\n bosConfig: session.config,\n runtimeConfig: session.runtime,\n configDir: session.root,\n env: input.env,\n build: input.build,\n dryRun: input.dryRun,\n verbose: input.verbose,\n packages: input.packages,\n network: input.network,\n privateKey: input.privateKey,\n registry: input.registry,\n fullUpload: input.fullUpload,\n });\n\n if (result.status === \"error\") {\n return {\n status: \"error\" as const,\n registryUrl: result.registryUrl,\n txHash: result.txHash,\n built: result.built,\n skipped: result.skipped,\n error: result.error,\n deployResults: result.deployResults,\n };\n }\n\n if (result.status === \"dry-run\") {\n return {\n status: \"dry-run\" as const,\n registryUrl: result.registryUrl,\n built: result.built,\n skipped: result.skipped,\n };\n }\n\n let nextSession = session;\n if (result.publishConfig) {\n const opened = await openResolution({ cwd: session.root });\n if (opened) {\n nextSession = opened;\n }\n }\n\n const configPublished = {\n registryUrl: result.registryUrl,\n txHash: result.txHash,\n fingerprint: result.fingerprint,\n slotPins: result.slotPins,\n built: result.built,\n skipped: result.skipped,\n deployResults: result.deployResults,\n } as const;\n\n let image: string | undefined;\n let imageDigest: string | undefined;\n let service: string | undefined;\n\n const imagePlan = resolveDeployImagePlan({\n // A prebuilt digest pairs with the pushed ref (BOS_IMAGE from the CI\n // image job, or repository derivation) — ci.image is a build-time\n // choice, not a pin-time one; letting it win here could pin a digest\n // from a different image.\n imageRef: resolveImageRef({\n ciImage: imageDigestInput ? undefined : nextSession.config?.ci?.image,\n repository: nextSession.config?.repository,\n env: process.env,\n }),\n imageDigest: imageDigestInput,\n hasDocker: await hasDocker(),\n hasDockerfile: existsSync(join(session.root, \"Dockerfile\")),\n });\n\n if (imagePlan.kind === \"skip\") {\n console.log();\n console.log(colors.yellow(` Image skipped: ${imagePlan.reason}`));\n } else if (imagePlan.kind === \"prebuilt\") {\n image = imagePlan.image;\n imageDigest = imagePlan.digest;\n console.log();\n console.log(\n colors.green(\n ` ${icons.ok} Using pre-pushed image ${imagePlan.image}@${imagePlan.digest.slice(0, 19)}…`,\n ),\n );\n } else {\n console.log();\n try {\n const imageResult = await buildAndPushImage({\n image: imagePlan.image,\n configDir: session.root,\n verbose: input.verbose,\n });\n image = imageResult.image;\n imageDigest = imageResult.digest;\n console.log(\n colors.green(\n ` ${icons.ok} Image pushed ${imageResult.image}:${imageResult.tag}${imageResult.digest ? ` (${imageResult.digest.slice(0, 19)}…)` : \"\"}`,\n ),\n );\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n const imageError =\n message.includes(\"not found\") || message.includes(\"ENOENT\")\n ? \"Docker not found — install Docker to build the runtime image\"\n : `Image build/push failed: ${message}`;\n console.log(colors.yellow(` ${imageError}`));\n return {\n status: \"published\" as const,\n ...configPublished,\n error: `Config published but ${imageError}`,\n };\n }\n }\n\n // Post-publish boot gate (publish → smoke → Railway, ADR 0021): the\n // just-published config is only promoted to Railway if the runtime\n // image — the one this train pinned — boots against it. A red smoke\n // rolls the config back to the previous snapshot, so the new-config/\n // old-image window never survives the deploy run.\n if (input.smoke) {\n if (!image || !imageDigest) {\n console.log();\n console.log(\n colors.yellow(\n \" Post-publish smoke skipped: no runtime image digest — set ci.image (or pass BOS_IMAGE_DIGEST)\",\n ),\n );\n } else {\n console.log();\n console.log(colors.cyan(\" ▶ Post-publish smoke (boot gate)\"));\n const smokeResult = await runSmoke({\n image: imageRefWithDigest(image, imageDigest),\n account: nextSession.config?.account,\n gateway: nextSession.config?.domain,\n });\n if (!smokeResult.ok) {\n const cause = smokeResult.error ?? \"unknown failure\";\n console.log();\n console.log(colors.error(` ${icons.err} Post-publish smoke failed: ${cause}`));\n if (smokeResult.diagnosis) {\n console.log(colors.yellow(` ${smokeResult.diagnosis}`));\n }\n const account = nextSession.config?.account;\n const domain = nextSession.config?.domain;\n if (!account || !domain) {\n console.log(colors.yellow(\" Rollback skipped: no registry identity in the session\"));\n return {\n status: \"published\" as const,\n ...configPublished,\n image,\n error: `Post-publish smoke failed (${cause}) — config NOT rolled back: no registry identity`,\n };\n }\n console.log(colors.yellow(\" Rolling the published config back…\"));\n const rollback = await rollbackToPreviousSnapshot({\n account,\n domain,\n env: input.env,\n runtimeConfig: nextSession.runtime,\n configDir: nextSession.root,\n registry: input.registry,\n network: input.network,\n privateKey: input.privateKey,\n wallet: input.wallet,\n verbose: input.verbose,\n });\n const rollbackNote = rollback.ok\n ? `config rolled back to block ${rollback.blockHeight}`\n : `config NOT rolled back: ${rollback.error ?? \"unknown\"}`;\n console.log(colors.yellow(` ${rollbackNote}`));\n return {\n status: \"published\" as const,\n ...configPublished,\n image,\n error: `Post-publish smoke failed (${cause}) — ${rollbackNote}`,\n };\n }\n console.log(\n colors.green(` ${icons.ok} Post-publish smoke passed: ${smokeResult.summary ?? \"\"}`),\n );\n }\n }\n\n if (process.env.RAILWAY_TOKEN) {\n const railwayService = input.service ?? nextSession.config?.ci?.railway?.service;\n if (!railwayService) {\n console.log();\n console.log(\n colors.yellow(\n \" Railway deploy skipped: ci.railway.service is not configured in bos.config.json\",\n ),\n );\n return {\n status: \"published\" as const,\n ...configPublished,\n image,\n error:\n \"Config published but Railway deploy failed: ci.railway.service is not configured in bos.config.json\",\n };\n }\n if (!image || !imageDigest) {\n console.log();\n console.log(\n colors.yellow(\n \" Railway deploy skipped: no runtime image digest — set ci.image (or pass BOS_IMAGE_DIGEST) and make the image available\",\n ),\n );\n return {\n status: \"published\" as const,\n ...configPublished,\n image,\n service: railwayService,\n error:\n \"Config published but Railway deploy requires a runtime image (set ci.image, or pass BOS_IMAGE_DIGEST for a pre-pushed image)\",\n };\n }\n\n service = railwayService;\n try {\n await deployImageToRailway({\n service: railwayService,\n configDir: session.root,\n });\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n const railError =\n message.includes(\"not found\") || message.includes(\"ENOENT\")\n ? \"Railway CLI not found. Install it: npm i -g @railway/cli\"\n : `Railway deploy failed: ${message}`;\n console.log(colors.yellow(` ${railError}`));\n return {\n status: \"published\" as const,\n ...configPublished,\n image,\n service,\n error: `Config published but ${railError}`,\n };\n }\n } else {\n console.log();\n console.log(colors.yellow(\" Railway deploy skipped (RAILWAY_TOKEN not set)\"));\n }\n\n return {\n status: \"deployed\" as const,\n ...configPublished,\n image,\n service,\n };\n }),\n };\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
const require_shared = require('./shared.cjs');
|
|
2
|
+
const require_smoke = require('../smoke.cjs');
|
|
3
|
+
let effect = require("effect");
|
|
4
|
+
|
|
5
|
+
//#region src/commands/smoke.ts
|
|
6
|
+
function registerSmoke(builder) {
|
|
7
|
+
return { smoke: builder.smoke.handler(async ({ input, context }) => {
|
|
8
|
+
const deps = effect.Context.get(context["effect/context"], require_shared.BosDepsTag);
|
|
9
|
+
const account = input.account ?? deps.session?.config?.account;
|
|
10
|
+
const gateway = input.gateway ?? deps.session?.config?.domain;
|
|
11
|
+
if (!input.image) return {
|
|
12
|
+
status: "error",
|
|
13
|
+
error: "smoke requires an image ref — pass --image <ref>"
|
|
14
|
+
};
|
|
15
|
+
console.log(`smoke: booting ${input.image} on :${input.port}`);
|
|
16
|
+
const result = await require_smoke.runSmoke({
|
|
17
|
+
image: input.image,
|
|
18
|
+
port: input.port,
|
|
19
|
+
keep: input.keep,
|
|
20
|
+
...account ? { account } : {},
|
|
21
|
+
...gateway ? { gateway } : {},
|
|
22
|
+
...input.apiDatabaseUrl ? { apiDatabaseUrl: input.apiDatabaseUrl } : {},
|
|
23
|
+
...input.authDatabaseUrl ? { authDatabaseUrl: input.authDatabaseUrl } : {}
|
|
24
|
+
});
|
|
25
|
+
if (result.ok) return {
|
|
26
|
+
status: "ok",
|
|
27
|
+
image: result.image,
|
|
28
|
+
summary: result.summary
|
|
29
|
+
};
|
|
30
|
+
return {
|
|
31
|
+
status: "error",
|
|
32
|
+
image: result.image,
|
|
33
|
+
...result.diagnosis ? { diagnosis: result.diagnosis } : {},
|
|
34
|
+
error: result.error
|
|
35
|
+
};
|
|
36
|
+
}) };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
//#endregion
|
|
40
|
+
exports.registerSmoke = registerSmoke;
|
|
41
|
+
//# sourceMappingURL=smoke.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"smoke.cjs","names":["Context","BosDepsTag","runSmoke"],"sources":["../../src/commands/smoke.ts"],"sourcesContent":["import { Context } from \"effect\";\nimport { runSmoke } from \"../smoke\";\nimport { type BosBuilder, BosDepsTag } from \"./shared\";\n\nexport function registerSmoke(builder: BosBuilder) {\n return {\n smoke: builder.smoke.handler(async ({ input, context }) => {\n const deps = Context.get(context[\"effect/context\"], BosDepsTag);\n const account = input.account ?? deps.session?.config?.account;\n const gateway = input.gateway ?? deps.session?.config?.domain;\n\n if (!input.image) {\n return {\n status: \"error\" as const,\n error: \"smoke requires an image ref — pass --image <ref>\",\n };\n }\n\n console.log(`smoke: booting ${input.image} on :${input.port}`);\n const result = await runSmoke({\n image: input.image,\n port: input.port,\n keep: input.keep,\n ...(account ? { account } : {}),\n ...(gateway ? { gateway } : {}),\n ...(input.apiDatabaseUrl ? { apiDatabaseUrl: input.apiDatabaseUrl } : {}),\n ...(input.authDatabaseUrl ? { authDatabaseUrl: input.authDatabaseUrl } : {}),\n });\n\n if (result.ok) {\n return {\n status: \"ok\" as const,\n image: result.image,\n summary: result.summary,\n };\n }\n return {\n status: \"error\" as const,\n image: result.image,\n ...(result.diagnosis ? { diagnosis: result.diagnosis } : {}),\n error: result.error,\n };\n }),\n };\n}\n"],"mappings":";;;;;AAIA,SAAgB,cAAc,SAAqB;CACjD,OAAO,EACL,OAAO,QAAQ,MAAM,QAAQ,OAAO,EAAE,OAAO,cAAc;EACzD,MAAM,OAAOA,eAAQ,IAAI,QAAQ,mBAAmBC,yBAAU;EAC9D,MAAM,UAAU,MAAM,WAAW,KAAK,SAAS,QAAQ;EACvD,MAAM,UAAU,MAAM,WAAW,KAAK,SAAS,QAAQ;EAEvD,IAAI,CAAC,MAAM,OACT,OAAO;GACL,QAAQ;GACR,OAAO;EACT;EAGF,QAAQ,IAAI,kBAAkB,MAAM,MAAM,OAAO,MAAM,MAAM;EAC7D,MAAM,SAAS,MAAMC,uBAAS;GAC5B,OAAO,MAAM;GACb,MAAM,MAAM;GACZ,MAAM,MAAM;GACZ,GAAI,UAAU,EAAE,QAAQ,IAAI,CAAC;GAC7B,GAAI,UAAU,EAAE,QAAQ,IAAI,CAAC;GAC7B,GAAI,MAAM,iBAAiB,EAAE,gBAAgB,MAAM,eAAe,IAAI,CAAC;GACvE,GAAI,MAAM,kBAAkB,EAAE,iBAAiB,MAAM,gBAAgB,IAAI,CAAC;EAC5E,CAAC;EAED,IAAI,OAAO,IACT,OAAO;GACL,QAAQ;GACR,OAAO,OAAO;GACd,SAAS,OAAO;EAClB;EAEF,OAAO;GACL,QAAQ;GACR,OAAO,OAAO;GACd,GAAI,OAAO,YAAY,EAAE,WAAW,OAAO,UAAU,IAAI,CAAC;GAC1D,OAAO,OAAO;EAChB;CACF,CAAC,EACH;AACF"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { BosDepsTag } from "./shared.mjs";
|
|
2
|
+
import { runSmoke } from "../smoke.mjs";
|
|
3
|
+
import { Context } from "effect";
|
|
4
|
+
|
|
5
|
+
//#region src/commands/smoke.ts
|
|
6
|
+
function registerSmoke(builder) {
|
|
7
|
+
return { smoke: builder.smoke.handler(async ({ input, context }) => {
|
|
8
|
+
const deps = Context.get(context["effect/context"], BosDepsTag);
|
|
9
|
+
const account = input.account ?? deps.session?.config?.account;
|
|
10
|
+
const gateway = input.gateway ?? deps.session?.config?.domain;
|
|
11
|
+
if (!input.image) return {
|
|
12
|
+
status: "error",
|
|
13
|
+
error: "smoke requires an image ref — pass --image <ref>"
|
|
14
|
+
};
|
|
15
|
+
console.log(`smoke: booting ${input.image} on :${input.port}`);
|
|
16
|
+
const result = await runSmoke({
|
|
17
|
+
image: input.image,
|
|
18
|
+
port: input.port,
|
|
19
|
+
keep: input.keep,
|
|
20
|
+
...account ? { account } : {},
|
|
21
|
+
...gateway ? { gateway } : {},
|
|
22
|
+
...input.apiDatabaseUrl ? { apiDatabaseUrl: input.apiDatabaseUrl } : {},
|
|
23
|
+
...input.authDatabaseUrl ? { authDatabaseUrl: input.authDatabaseUrl } : {}
|
|
24
|
+
});
|
|
25
|
+
if (result.ok) return {
|
|
26
|
+
status: "ok",
|
|
27
|
+
image: result.image,
|
|
28
|
+
summary: result.summary
|
|
29
|
+
};
|
|
30
|
+
return {
|
|
31
|
+
status: "error",
|
|
32
|
+
image: result.image,
|
|
33
|
+
...result.diagnosis ? { diagnosis: result.diagnosis } : {},
|
|
34
|
+
error: result.error
|
|
35
|
+
};
|
|
36
|
+
}) };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
//#endregion
|
|
40
|
+
export { registerSmoke };
|
|
41
|
+
//# sourceMappingURL=smoke.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"smoke.mjs","names":[],"sources":["../../src/commands/smoke.ts"],"sourcesContent":["import { Context } from \"effect\";\nimport { runSmoke } from \"../smoke\";\nimport { type BosBuilder, BosDepsTag } from \"./shared\";\n\nexport function registerSmoke(builder: BosBuilder) {\n return {\n smoke: builder.smoke.handler(async ({ input, context }) => {\n const deps = Context.get(context[\"effect/context\"], BosDepsTag);\n const account = input.account ?? deps.session?.config?.account;\n const gateway = input.gateway ?? deps.session?.config?.domain;\n\n if (!input.image) {\n return {\n status: \"error\" as const,\n error: \"smoke requires an image ref — pass --image <ref>\",\n };\n }\n\n console.log(`smoke: booting ${input.image} on :${input.port}`);\n const result = await runSmoke({\n image: input.image,\n port: input.port,\n keep: input.keep,\n ...(account ? { account } : {}),\n ...(gateway ? { gateway } : {}),\n ...(input.apiDatabaseUrl ? { apiDatabaseUrl: input.apiDatabaseUrl } : {}),\n ...(input.authDatabaseUrl ? { authDatabaseUrl: input.authDatabaseUrl } : {}),\n });\n\n if (result.ok) {\n return {\n status: \"ok\" as const,\n image: result.image,\n summary: result.summary,\n };\n }\n return {\n status: \"error\" as const,\n image: result.image,\n ...(result.diagnosis ? { diagnosis: result.diagnosis } : {}),\n error: result.error,\n };\n }),\n };\n}\n"],"mappings":";;;;;AAIA,SAAgB,cAAc,SAAqB;CACjD,OAAO,EACL,OAAO,QAAQ,MAAM,QAAQ,OAAO,EAAE,OAAO,cAAc;EACzD,MAAM,OAAO,QAAQ,IAAI,QAAQ,mBAAmB,UAAU;EAC9D,MAAM,UAAU,MAAM,WAAW,KAAK,SAAS,QAAQ;EACvD,MAAM,UAAU,MAAM,WAAW,KAAK,SAAS,QAAQ;EAEvD,IAAI,CAAC,MAAM,OACT,OAAO;GACL,QAAQ;GACR,OAAO;EACT;EAGF,QAAQ,IAAI,kBAAkB,MAAM,MAAM,OAAO,MAAM,MAAM;EAC7D,MAAM,SAAS,MAAM,SAAS;GAC5B,OAAO,MAAM;GACb,MAAM,MAAM;GACZ,MAAM,MAAM;GACZ,GAAI,UAAU,EAAE,QAAQ,IAAI,CAAC;GAC7B,GAAI,UAAU,EAAE,QAAQ,IAAI,CAAC;GAC7B,GAAI,MAAM,iBAAiB,EAAE,gBAAgB,MAAM,eAAe,IAAI,CAAC;GACvE,GAAI,MAAM,kBAAkB,EAAE,iBAAiB,MAAM,gBAAgB,IAAI,CAAC;EAC5E,CAAC;EAED,IAAI,OAAO,IACT,OAAO;GACL,QAAQ;GACR,OAAO,OAAO;GACd,SAAS,OAAO;EAClB;EAEF,OAAO;GACL,QAAQ;GACR,OAAO,OAAO;GACd,GAAI,OAAO,YAAY,EAAE,WAAW,OAAO,UAAU,IAAI,CAAC;GAC1D,OAAO,OAAO;EAChB;CACF,CAAC,EACH;AACF"}
|
|
@@ -2,7 +2,7 @@ const require_runtime = require('../_virtual/_rolldown/runtime.cjs');
|
|
|
2
2
|
const require_config = require('../config.cjs');
|
|
3
3
|
const require_theme = require('../utils/theme.cjs');
|
|
4
4
|
const require_resolution_session = require('../resolution/session.cjs');
|
|
5
|
-
const
|
|
5
|
+
const require_federation = require('../federation.cjs');
|
|
6
6
|
const require_build = require('../build.cjs');
|
|
7
7
|
const require_code_artifacts = require('../code-artifacts.cjs');
|
|
8
8
|
const require_dev_program = require('../dev-program.cjs');
|
|
@@ -331,7 +331,7 @@ function registerUpgrade(builder) {
|
|
|
331
331
|
remotes: []
|
|
332
332
|
};
|
|
333
333
|
}
|
|
334
|
-
const report = await
|
|
334
|
+
const report = await require_federation.checkFederationCompat(authored, { timeoutMs: input.timeoutMs });
|
|
335
335
|
return {
|
|
336
336
|
status: report.ok ? "ok" : "fail",
|
|
337
337
|
hostVersion: report.hostVersion,
|