everything-dev 2.0.0-rc.6 → 2.0.0-rc.8

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.
Files changed (148) hide show
  1. package/README.md +2 -2
  2. package/dist/api/auth-middleware.cjs +14 -1
  3. package/dist/api/auth-middleware.cjs.map +1 -1
  4. package/dist/api/auth-middleware.d.cts +9 -0
  5. package/dist/api/auth-middleware.d.mts +9 -0
  6. package/dist/api/auth-middleware.mjs +14 -1
  7. package/dist/api/auth-middleware.mjs.map +1 -1
  8. package/dist/build.cjs +1 -1
  9. package/dist/build.mjs +1 -1
  10. package/dist/bundle-cache.cjs +1 -1
  11. package/dist/bundle-cache.cjs.map +1 -1
  12. package/dist/bundle-cache.mjs +1 -1
  13. package/dist/bundle-cache.mjs.map +1 -1
  14. package/dist/cli/init.cjs +22 -10
  15. package/dist/cli/init.cjs.map +1 -1
  16. package/dist/cli/init.mjs +22 -10
  17. package/dist/cli/init.mjs.map +1 -1
  18. package/dist/cli.cjs +6 -5
  19. package/dist/cli.cjs.map +1 -1
  20. package/dist/cli.mjs +5 -4
  21. package/dist/cli.mjs.map +1 -1
  22. package/dist/commands/deploy.cjs +111 -1
  23. package/dist/commands/deploy.cjs.map +1 -1
  24. package/dist/commands/deploy.mjs +112 -2
  25. package/dist/commands/deploy.mjs.map +1 -1
  26. package/dist/commands/keys.cjs +1 -1
  27. package/dist/commands/keys.cjs.map +1 -1
  28. package/dist/commands/keys.mjs +1 -1
  29. package/dist/commands/keys.mjs.map +1 -1
  30. package/dist/commands/smoke.cjs +41 -0
  31. package/dist/commands/smoke.cjs.map +1 -0
  32. package/dist/commands/smoke.mjs +41 -0
  33. package/dist/commands/smoke.mjs.map +1 -0
  34. package/dist/commands/upgrade.cjs +2 -2
  35. package/dist/commands/upgrade.cjs.map +1 -1
  36. package/dist/commands/upgrade.mjs +1 -1
  37. package/dist/commands/upgrade.mjs.map +1 -1
  38. package/dist/contract.cjs +27 -1
  39. package/dist/contract.cjs.map +1 -1
  40. package/dist/contract.d.cts +65 -1
  41. package/dist/contract.d.mts +65 -1
  42. package/dist/contract.meta.cjs +14 -2
  43. package/dist/contract.meta.cjs.map +1 -1
  44. package/dist/contract.meta.d.cts +24 -2
  45. package/dist/contract.meta.d.mts +24 -2
  46. package/dist/contract.meta.mjs +14 -2
  47. package/dist/contract.meta.mjs.map +1 -1
  48. package/dist/contract.mjs +26 -2
  49. package/dist/contract.mjs.map +1 -1
  50. package/dist/db/core.cjs +22 -4
  51. package/dist/db/core.cjs.map +1 -1
  52. package/dist/db/core.mjs +22 -4
  53. package/dist/db/core.mjs.map +1 -1
  54. package/dist/dev-log-pipeline.cjs +6 -0
  55. package/dist/dev-log-pipeline.cjs.map +1 -1
  56. package/dist/dev-log-pipeline.mjs +6 -0
  57. package/dist/dev-log-pipeline.mjs.map +1 -1
  58. package/dist/dev-program.cjs +20 -3
  59. package/dist/dev-program.cjs.map +1 -1
  60. package/dist/dev-program.mjs +20 -4
  61. package/dist/dev-program.mjs.map +1 -1
  62. package/dist/dev-session.cjs +1 -0
  63. package/dist/dev-session.cjs.map +1 -1
  64. package/dist/dev-session.mjs +1 -0
  65. package/dist/dev-session.mjs.map +1 -1
  66. package/dist/fastkv.cjs +21 -3
  67. package/dist/fastkv.cjs.map +1 -1
  68. package/dist/fastkv.d.cts +14 -1
  69. package/dist/fastkv.d.mts +14 -1
  70. package/dist/fastkv.mjs +21 -5
  71. package/dist/fastkv.mjs.map +1 -1
  72. package/dist/{mf.cjs → federation.cjs} +6 -13
  73. package/dist/federation.cjs.map +1 -0
  74. package/dist/{mf.d.cts → federation.d.cts} +2 -2
  75. package/dist/{mf.d.mts → federation.d.mts} +2 -2
  76. package/dist/{mf.mjs → federation.mjs} +6 -13
  77. package/dist/federation.mjs.map +1 -0
  78. package/dist/http-client.cjs +84 -57
  79. package/dist/http-client.cjs.map +1 -1
  80. package/dist/http-client.mjs +84 -58
  81. package/dist/http-client.mjs.map +1 -1
  82. package/dist/image-deploy.cjs +9 -0
  83. package/dist/image-deploy.cjs.map +1 -1
  84. package/dist/image-deploy.mjs +9 -1
  85. package/dist/image-deploy.mjs.map +1 -1
  86. package/dist/index.cjs +4 -0
  87. package/dist/index.d.cts +3 -3
  88. package/dist/index.d.mts +3 -3
  89. package/dist/index.mjs +3 -3
  90. package/dist/infra/planner.cjs +6 -2
  91. package/dist/infra/planner.cjs.map +1 -1
  92. package/dist/infra/planner.mjs +6 -2
  93. package/dist/infra/planner.mjs.map +1 -1
  94. package/dist/orchestrator.cjs +39 -35
  95. package/dist/orchestrator.cjs.map +1 -1
  96. package/dist/orchestrator.mjs +39 -35
  97. package/dist/orchestrator.mjs.map +1 -1
  98. package/dist/plugin.cjs +2 -0
  99. package/dist/plugin.cjs.map +1 -1
  100. package/dist/plugin.d.cts +24 -0
  101. package/dist/plugin.d.mts +24 -0
  102. package/dist/plugin.mjs +2 -0
  103. package/dist/plugin.mjs.map +1 -1
  104. package/dist/process-registry.cjs +1 -1
  105. package/dist/process-registry.mjs +1 -1
  106. package/dist/publish.cjs +3 -3
  107. package/dist/publish.cjs.map +1 -1
  108. package/dist/publish.mjs +4 -4
  109. package/dist/publish.mjs.map +1 -1
  110. package/dist/resolver.cjs +293 -0
  111. package/dist/resolver.cjs.map +1 -0
  112. package/dist/resolver.d.cts +99 -0
  113. package/dist/resolver.d.mts +99 -0
  114. package/dist/resolver.mjs +284 -0
  115. package/dist/resolver.mjs.map +1 -0
  116. package/dist/smoke.cjs +262 -0
  117. package/dist/smoke.cjs.map +1 -0
  118. package/dist/smoke.mjs +260 -0
  119. package/dist/smoke.mjs.map +1 -0
  120. package/dist/ui/auth.d.cts +217 -123
  121. package/dist/ui/auth.d.mts +217 -123
  122. package/dist/ui/router-client.cjs +2 -1
  123. package/dist/ui/router-client.cjs.map +1 -1
  124. package/dist/ui/router-client.mjs +2 -1
  125. package/dist/ui/router-client.mjs.map +1 -1
  126. package/dist/version-manifest-resolve.cjs +3 -3
  127. package/dist/version-manifest-resolve.cjs.map +1 -1
  128. package/dist/version-manifest-resolve.mjs +1 -1
  129. package/dist/version-manifest-resolve.mjs.map +1 -1
  130. package/dist/workspace.cjs +1 -1
  131. package/dist/workspace.mjs +1 -1
  132. package/package.json +18 -18
  133. package/skills/api-and-auth/SKILL.md +12 -9
  134. package/skills/api-and-auth/references/middleware.md +38 -0
  135. package/skills/cli-reference/SKILL.md +22 -6
  136. package/skills/talk-to-the-app/SKILL.md +32 -5
  137. package/dist/bundle-fs-resolve.cjs +0 -235
  138. package/dist/bundle-fs-resolve.cjs.map +0 -1
  139. package/dist/bundle-fs-resolve.d.cts +0 -82
  140. package/dist/bundle-fs-resolve.d.mts +0 -82
  141. package/dist/bundle-fs-resolve.mjs +0 -228
  142. package/dist/bundle-fs-resolve.mjs.map +0 -1
  143. package/dist/bundle-path.cjs +0 -48
  144. package/dist/bundle-path.cjs.map +0 -1
  145. package/dist/bundle-path.mjs +0 -45
  146. package/dist/bundle-path.mjs.map +0 -1
  147. package/dist/mf.cjs.map +0 -1
  148. 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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"}
@@ -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":";;;;;;;;;;;;;;;AA2BA,SAAgB,eAAe,SAAqB;CAClD,OAAO;EACL,SAAS,QAAQ,QAAQ,QAAQ,OAAO,EAAE,OAAO,cAAc;GAE7D,MAAM,UADO,QAAQ,IAAI,QAAQ,mBAAmB,UACjC,CAAC,CAAC;GACrB,IAAI,CAAC,SAAS,QACZ,OAAO;IACL,QAAQ;IACR,aAAa;IACb,OAAO;GACT;GAGF,MAAM,SAAS,MAAM,gBAAgB;IACnC,WAAW,QAAQ;IACnB,eAAe,QAAQ;IACvB,WAAW,QAAQ;IACnB,KAAK,MAAM;IACX,OAAO;IACP,QAAQ,MAAM;IACd,SAAS,MAAM;IACf,UAAU;IACV,SAAS,MAAM;IACf,YAAY,MAAM;IAClB,QAAQ,MAAM;IACd,UAAU,MAAM;GAClB,CAAC;GAED,OAAO;IACL,QAAQ,OAAO;IACf,aAAa,OAAO;IACpB,QAAQ,OAAO;IACf,OAAO,OAAO;IACd,OAAO,OAAO;IACd,SAAS,OAAO;IAChB,eAAe,OAAO;GACxB;EACF,CAAC;EAED,UAAU,QAAQ,SAAS,QAAQ,OAAO,EAAE,OAAO,cAAc;GAE/D,MAAM,UADO,QAAQ,IAAI,QAAQ,mBAAmB,UACjC,CAAC,CAAC;GACrB,IAAI,CAAC,SACH,OAAO;IACL,QAAQ;IACR,aAAa;IACb,OAAO;GACT;GAGF,MAAM,EAAE,SAAS,WAAW,QAAQ,UAAU,CAAC;GAC/C,IAAI,CAAC,WAAW,CAAC,QACf,OAAO;IACL,QAAQ;IACR,aAAa;IACb,OAAO;GACT;GAGF,IAAI;GACJ,IAAI;IACF,UAAU,MAAM,mBAAmB;KACjC,WAAW;KACX,WAAW;KACX,UAAU,MAAM;KAChB,OAAO,MAAM;IACf,CAAC;GACH,SAAS,OAAO;IACd,OAAO;KACL,QAAQ;KACR,aAAa;KACb,OAAO,oCAAoC,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;IAClG;GACF;GAEA,IAAI,QAAQ,WAAW,GACrB,OAAO;IACL,QAAQ;IACR,aAAa;IACb,OAAO,0BAA0B,QAAQ,GAAG,OAAO;GACrD;GAGF,MAAM,iBAAiB,QAAQ,KAAK,WAAW;IAC7C,aAAa,MAAM;IACnB,gBAAgB,IAAI,KAAK,OAAO,MAAM,iBAAiB,MAAM,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC,YAAY;IAClF,GAAI,MAAM,SAAS,EAAE,QAAQ,MAAM,OAAO,IAAI,CAAC;IAC/C,SAAS,kBAAkB,MAAM,KAAuB;GAC1D,EAAE;GAEF,IAAI,MAAM,UACR,OAAO;IACL,QAAQ;IACR,aAAa;IACb,SAAS;GACX;GAGF,MAAM,SACJ,MAAM,YAAY,SACd,QAAQ,MAAM,UAAU;IACtB,MAAM,MAAM,IAAI,KAAK,OAAO,MAAM,iBAAiB,MAAM,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC,YAAY;IAC9E,OACE,OAAO,MAAM,WAAW,MAAM,MAAM,WACpC,IAAI,WAAW,MAAM,OAAiB;GAE1C,CAAC,IACD,MAAM,WACJ,QAAQ,KACR;GAER,IAAI,CAAC,QACH,OAAO;IACL,QAAQ;IACR,aAAa;IACb,SAAS;IACT,OAAO,MAAM,UACT,6BAA6B,MAAM,QAAQ,4CAC3C;GACN;GAGF,IAAI;GACJ,IAAI;IACF,YAAY,MAAM,yBAChB,SAAS,QAAQ,GAAG,UACpB,MAAM,QACR;GACF,QAAQ;IACN,YAAY;GACd;GACA,IAAI,aAAa,KAAK,UAAU,SAAS,MAAM,KAAK,UAAU,OAAO,KAAK,GACxE,OAAO;IACL,QAAQ;IACR,aAAa;IACb,OAAO;GACT;GAGF,MAAM,eAAe,MAAM,uBAAuB,OAAO,KAAuB;GAChF,MAAM,aAAa,aAAa,MAAM,KAAK,WAAW;IACpD,MAAM,MAAM;IACZ,IAAI,MAAM;IACV,GAAI,MAAM,SAAS,EAAE,QAAQ,MAAM,OAAO,IAAI,CAAC;GACjD,EAAE;GACF,IAAI,CAAC,aAAa,MAAM,aAAa,YACnC,OAAO;IACL,QAAQ;IACR,aAAa;IACb,cAAc;IACd,OACE;GACJ;GAGF,IAAI,CAAC,aAAa,cAAc,CAAC,MAAM,OACrC,OAAO;IACL,QAAQ;IACR,aAAa;IACb,OACE;GACJ;GAGF,MAAM,eAAe,gBAAgB,MAAM,OAAO,KAAK;GACvD,MAAM,UAAU,qBAAqB,+BAAc,IAAI,KAAK,EAAC,CAAC,YAAY,CAAC;GAE3E,MAAM,SAAS,MAAM,gBAAgB;IACnC,WAAW;IACX,eAAe,QAAQ;IACvB,WAAW,QAAQ;IACnB,KAAK,MAAM;IACX,OAAO;IACP,QAAQ,MAAM;IACd,SAAS,MAAM;IACf,UAAU;IACV,SAAS,MAAM;IACf,YAAY,MAAM;IAClB,QAAQ,MAAM;IACd,UAAU,MAAM;GAClB,CAAC;GAED,OAAO;IACL,QAAQ,OAAO;IACf,aAAa,OAAO;IACpB,QAAQ,OAAO;IACf,OAAO,OAAO;IACd,SAAS;IACT,GAAI,WAAW,SAAS,IAAI,EAAE,cAAc,WAAW,IAAI,CAAC;GAC9D;EACF,CAAC;EAED,QAAQ,QAAQ,OAAO,QAAQ,OAAO,EAAE,OAAO,cAAc;GAE3D,MAAM,UADO,QAAQ,IAAI,QAAQ,mBAAmB,UACjC,CAAC,CAAC;GACrB,IAAI,CAAC,SAAS,QACZ,OAAO;IACL,QAAQ;IACR,aAAa;IACb,OAAO;GACT;GAGF,IAAI,MAAM,YAAY;IACpB,IAAI,CAAC,QAAQ,QAAQ,WAAW,CAAC,QAAQ,OAAO,QAC9C,OAAO;KACL,QAAQ;KACR,aAAa;KACb,OAAO;IACT;IAEF,IAAI;KAMF,OAAO;MACL,QAAQ;MACR,aAAa;MACb,UAAS,MARa,qBAAqB;OAC3C,WAAW,QAAQ,OAAO;OAC1B,WAAW,QAAQ,OAAO;OAC1B,UAAU,MAAM;MAClB,CAAC,EAImB,CAAC,KAAK,WAAW;OACjC,KAAK,MAAM;OACX,aAAa,MAAM;OACnB,gBAAgB,IAAI,KAAK,OAAO,MAAM,iBAAiB,MAAM,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC,YAAY;OAClF,GAAK,MAAM,OAA2C,SAClD,EAAE,QAAS,MAAM,MAA8B,OAAO,IACtD,CAAC;OACL,GAAK,MAAM,OAAgD,cACvD,EAAE,aAAc,MAAM,MAAmC,YAAY,IACrE,CAAC;MACP,EAAE;KACJ;IACF,SAAS,OAAO;KACd,OAAO;MACL,QAAQ;MACR,aAAa;MACb,OAAO,oCAAoC,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;KAClG;IACF;GACF;GAEA,MAAM,mBAAmB,MAAM,eAAe,QAAQ,IAAI;GAC1D,IAAI,oBAAoB,CAAC,mBAAmB,gBAAgB,GAC1D,OAAO;IACL,QAAQ;IACR,aAAa;IACb,OAAO,yBAAyB,iBAAiB;GACnD;GAGF,MAAM,SAAS,MAAM,gBAAgB;IACnC,WAAW,QAAQ;IACnB,eAAe,QAAQ;IACvB,WAAW,QAAQ;IACnB,KAAK,MAAM;IACX,OAAO,MAAM;IACb,QAAQ,MAAM;IACd,SAAS,MAAM;IACf,UAAU,MAAM;IAChB,SAAS,MAAM;IACf,YAAY,MAAM;IAClB,UAAU,MAAM;IAChB,YAAY,MAAM;GACpB,CAAC;GAED,IAAI,OAAO,WAAW,SACpB,OAAO;IACL,QAAQ;IACR,aAAa,OAAO;IACpB,QAAQ,OAAO;IACf,OAAO,OAAO;IACd,SAAS,OAAO;IAChB,OAAO,OAAO;IACd,eAAe,OAAO;GACxB;GAGF,IAAI,OAAO,WAAW,WACpB,OAAO;IACL,QAAQ;IACR,aAAa,OAAO;IACpB,OAAO,OAAO;IACd,SAAS,OAAO;GAClB;GAGF,IAAI,cAAc;GAClB,IAAI,OAAO,eAAe;IACxB,MAAM,SAAS,MAAM,eAAe,EAAE,KAAK,QAAQ,KAAK,CAAC;IACzD,IAAI,QACF,cAAc;GAElB;GAEA,MAAM,kBAAkB;IACtB,aAAa,OAAO;IACpB,QAAQ,OAAO;IACf,aAAa,OAAO;IACpB,UAAU,OAAO;IACjB,OAAO,OAAO;IACd,SAAS,OAAO;IAChB,eAAe,OAAO;GACxB;GAEA,IAAI;GACJ,IAAI;GACJ,IAAI;GAEJ,MAAM,YAAY,uBAAuB;IAKvC,UAAU,gBAAgB;KACxB,SAAS,mBAAmB,SAAY,YAAY,QAAQ,IAAI;KAChE,YAAY,YAAY,QAAQ;KAChC,KAAK,QAAQ;IACf,CAAC;IACD,aAAa;IACb,WAAW,MAAM,UAAU;IAC3B,eAAe,WAAW,KAAK,QAAQ,MAAM,YAAY,CAAC;GAC5D,CAAC;GAED,IAAI,UAAU,SAAS,QAAQ;IAC7B,QAAQ,IAAI;IACZ,QAAQ,IAAI,OAAO,OAAO,oBAAoB,UAAU,QAAQ,CAAC;GACnE,OAAO,IAAI,UAAU,SAAS,YAAY;IACxC,QAAQ,UAAU;IAClB,cAAc,UAAU;IACxB,QAAQ,IAAI;IACZ,QAAQ,IACN,OAAO,MACL,KAAK,MAAM,GAAG,0BAA0B,UAAU,MAAM,GAAG,UAAU,OAAO,MAAM,GAAG,EAAE,EAAE,EAC3F,CACF;GACF,OAAO;IACL,QAAQ,IAAI;IACZ,IAAI;KACF,MAAM,cAAc,MAAM,kBAAkB;MAC1C,OAAO,UAAU;MACjB,WAAW,QAAQ;MACnB,SAAS,MAAM;KACjB,CAAC;KACD,QAAQ,YAAY;KACpB,cAAc,YAAY;KAC1B,QAAQ,IACN,OAAO,MACL,KAAK,MAAM,GAAG,gBAAgB,YAAY,MAAM,GAAG,YAAY,MAAM,YAAY,SAAS,KAAK,YAAY,OAAO,MAAM,GAAG,EAAE,EAAE,MAAM,IACvI,CACF;IACF,SAAS,OAAO;KACd,MAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;KACrE,MAAM,aACJ,QAAQ,SAAS,WAAW,KAAK,QAAQ,SAAS,QAAQ,IACtD,iEACA,4BAA4B;KAClC,QAAQ,IAAI,OAAO,OAAO,KAAK,YAAY,CAAC;KAC5C,OAAO;MACL,QAAQ;MACR,GAAG;MACH,OAAO,wBAAwB;KACjC;IACF;GACF;GAEA,IAAI,QAAQ,IAAI,eAAe;IAC7B,MAAM,iBAAiB,MAAM,WAAW,YAAY,QAAQ,IAAI,SAAS;IACzE,IAAI,CAAC,gBAAgB;KACnB,QAAQ,IAAI;KACZ,QAAQ,IACN,OAAO,OACL,mFACF,CACF;KACA,OAAO;MACL,QAAQ;MACR,GAAG;MACH;MACA,OACE;KACJ;IACF;IACA,IAAI,CAAC,SAAS,CAAC,aAAa;KAC1B,QAAQ,IAAI;KACZ,QAAQ,IACN,OAAO,OACL,0HACF,CACF;KACA,OAAO;MACL,QAAQ;MACR,GAAG;MACH;MACA,SAAS;MACT,OACE;KACJ;IACF;IAEA,UAAU;IACV,IAAI;KACF,MAAM,qBAAqB;MACzB,SAAS;MACT,WAAW,QAAQ;KACrB,CAAC;IACH,SAAS,OAAO;KACd,MAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;KACrE,MAAM,YACJ,QAAQ,SAAS,WAAW,KAAK,QAAQ,SAAS,QAAQ,IACtD,6DACA,0BAA0B;KAChC,QAAQ,IAAI,OAAO,OAAO,KAAK,WAAW,CAAC;KAC3C,OAAO;MACL,QAAQ;MACR,GAAG;MACH;MACA;MACA,OAAO,wBAAwB;KACjC;IACF;GACF,OAAO;IACL,QAAQ,IAAI;IACZ,QAAQ,IAAI,OAAO,OAAO,kDAAkD,CAAC;GAC/E;GAEA,OAAO;IACL,QAAQ;IACR,GAAG;IACH;IACA;GACF;EACF,CAAC;CACH;AACF"}
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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"}
@@ -50,7 +50,7 @@ function registerKeys(builder) {
50
50
  console.log();
51
51
  console.log(` Found ${oldKeys.length} existing publish key${oldKeys.length > 1 ? "s" : ""}:`);
52
52
  for (const k of oldKeys) console.log(` ${require_theme.colors.dim(k)}`);
53
- if (input.removeOldKeys !== false) try {
53
+ if (input.removeOldKeys === true) try {
54
54
  await require_near_cli.deleteAccessKeys(account, oldKeys, network);
55
55
  console.log(` ${require_theme.colors.green("✓")} Removed ${oldKeys.length} old key${oldKeys.length > 1 ? "s" : ""}`);
56
56
  } catch {
@@ -1 +1 @@
1
- {"version":3,"file":"keys.cjs","names":["Context","BosDepsTag","MISSING_CONFIG_MESSAGE","getNetworkIdForAccount","getRegistryNamespaceForAccount","Effect","ensureNearCli","listPublishKeys","addFunctionCallAccessKey","colors","deleteAccessKeys","startDeviceLogin","openInBrowser","process","readSessionHandle","generateNearKeyPair","join","nearCredentialsPath","parseKey"],"sources":["../../src/commands/keys.ts"],"sourcesContent":["import { join } from \"node:path\";\nimport process from \"node:process\";\nimport { Context, Effect } from \"effect\";\nimport { type KeyPair, parseKey } from \"near-kit\";\nimport { openInBrowser, startDeviceLogin } from \"../auth-login\";\nimport {\n deleteSessionHandle,\n nearCredentialsPath,\n readSessionHandle,\n removePublishedKeyFile,\n type SessionCredential,\n writeSessionHandle,\n} from \"../auth-session\";\nimport { MISSING_CONFIG_MESSAGE } from \"../config\";\nimport type { LoginResult } from \"../contract\";\nimport { getRegistryNamespaceForAccount } from \"../fastkv\";\nimport {\n addFunctionCallAccessKey,\n deleteAccessKeys,\n ensureNearCli,\n generateNearKeyPair,\n listPublishKeys,\n} from \"../near-cli\";\nimport { getNetworkIdForAccount } from \"../network\";\nimport type { ResolutionSession } from \"../resolution/session\";\nimport { colors } from \"../utils/theme\";\nimport { type BosBuilder, BosDepsTag } from \"./shared\";\n\nconst PUBLISH_FUNCTION_NAMES = [\"__fastdata_kv\"];\n\nexport function registerKeys(builder: BosBuilder) {\n return {\n keyPublish: builder.keyPublish.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 account: \"\",\n network: \"mainnet\" as const,\n env: input.env,\n contract: \"\",\n allowance: input.allowance,\n functionNames: PUBLISH_FUNCTION_NAMES,\n error: MISSING_CONFIG_MESSAGE,\n };\n }\n\n const account =\n input.env === \"staging\"\n ? (session.config.staging?.account ?? session.config.account)\n : session.config.account;\n const network = getNetworkIdForAccount(account);\n const contract = getRegistryNamespaceForAccount(account, input.registry);\n try {\n await Effect.runPromise(ensureNearCli);\n\n const oldKeys = await listPublishKeys({ account, contract, network });\n\n const keyPair = await addFunctionCallAccessKey({\n account,\n contract,\n allowance: input.allowance,\n functionNames: PUBLISH_FUNCTION_NAMES,\n network,\n });\n\n if (oldKeys.length > 0) {\n console.log();\n console.log(\n ` Found ${oldKeys.length} existing publish key${oldKeys.length > 1 ? \"s\" : \"\"}:`,\n );\n for (const k of oldKeys) {\n console.log(` ${colors.dim(k)}`);\n }\n\n if (input.removeOldKeys !== false) {\n try {\n await deleteAccessKeys(account, oldKeys, network);\n console.log(\n ` ${colors.green(\"✓\")} Removed ${oldKeys.length} old key${\n oldKeys.length > 1 ? \"s\" : \"\"\n }`,\n );\n } catch {\n console.log(\n ` ${colors.yellow(\"⚠\")} Failed to remove old key${\n oldKeys.length > 1 ? \"s\" : \"\"\n } (new key still active)`,\n );\n }\n } else {\n console.log(` ${colors.dim(\"Old key(s) retained.\")}`);\n }\n }\n\n return {\n status: \"published\" as const,\n account,\n network,\n env: input.env,\n contract,\n allowance: input.allowance,\n functionNames: PUBLISH_FUNCTION_NAMES,\n publicKey: keyPair.publicKey,\n privateKey: keyPair.privateKey,\n };\n } catch (error) {\n return {\n status: \"error\" as const,\n account,\n network,\n env: input.env,\n contract,\n allowance: input.allowance,\n functionNames: PUBLISH_FUNCTION_NAMES,\n error: error instanceof Error ? error.message : \"Unknown error\",\n };\n }\n }),\n\n login: builder.login.handler(async ({ input, context }) => {\n const deps = Context.get(context[\"effect/context\"], BosDepsTag);\n const staging = input.env === \"staging\";\n const account = staging\n ? (deps.session?.config?.staging?.account ?? deps.session?.config?.account ?? \"\")\n : (deps.session?.config?.account ?? \"\");\n\n try {\n const siteUrl = resolveLoginSiteUrl(input.site, deps.session, staging);\n const login = await startDeviceLogin({\n siteUrl,\n device: input.device,\n account: account || undefined,\n expiresIn: input.expiresIn,\n });\n\n console.log();\n console.log(` One-time code: ${colors.cyan(login.userCode)}`);\n await openInBrowser(login.verificationUrl).catch((error: unknown) => {\n console.log(colors.yellow(` ⚠ ${(error as Error).message}`));\n });\n console.log();\n console.log(` Waiting for approval at ${colors.dim(login.verificationUrl)}…`);\n\n const approval = await login.waitForApproval();\n\n if (!approval.apiKey) {\n return {\n status: \"error\" as const,\n siteUrl,\n loginUrl: login.verificationUrl,\n error: \"Login approved but no CLI credential was created\",\n };\n }\n\n const expiresAt = new Date(Date.now() + input.expiresIn * 1000).toISOString();\n writeSessionHandle(deps.session?.root ?? process.cwd(), {\n version: 1,\n credential: {\n kind: \"session\",\n apiKey: approval.apiKey.key,\n apiKeyId: approval.apiKey.id,\n accountId: approval.accountId,\n label: input.device ?? siteUrl,\n siteUrl,\n createdAt: new Date().toISOString(),\n expiresAt,\n },\n publishKey: null,\n delegateKey: null,\n });\n\n let warning: string | null = null;\n if (account && approval.accountId && approval.accountId !== account) {\n warning = `Logged in as ${approval.accountId}, but the authored config account is ${account}. Publishes will use the configured account.`;\n }\n\n let publishKey: LoginResult[\"publishKey\"] = null;\n if (input.key) {\n try {\n publishKey = await exportPublishKey(account, input.registry);\n } catch (error) {\n warning = `Session stored, but publish-key export failed: ${\n error instanceof Error ? error.message : \"unknown error\"\n }`;\n }\n }\n\n return {\n status: \"logged-in\" as const,\n siteUrl,\n accountId: approval.accountId,\n expiresAt,\n loginUrl: login.verificationUrl,\n publishKey,\n warning,\n };\n } catch (error) {\n return {\n status: \"error\" as const,\n siteUrl: input.site ?? \"\",\n error: error instanceof Error ? error.message : \"Unknown error\",\n };\n }\n }),\n\n logout: builder.logout.handler(async ({ input, context }) => {\n const deps = Context.get(context[\"effect/context\"], BosDepsTag);\n const configDir = input.configDir ?? deps.session?.root ?? process.cwd();\n\n try {\n const session = readSessionHandle(configDir);\n let revokedApiKey = false;\n let removedPublishKey = false;\n let warning: string | null = null;\n\n if (session?.credential) {\n revokedApiKey = await revokeApiKey(session.credential).catch(() => false);\n if (!revokedApiKey) {\n warning =\n \"Could not revoke the API key remotely — revoke it manually under Settings → API keys.\";\n }\n }\n\n if (session?.publishKey) {\n const account = session.credential?.accountId ?? deps.session?.config?.account ?? \"\";\n removePublishedKeyFile(session.publishKey.network, account, session.publishKey.publicKey);\n removedPublishKey = true;\n warning = warning\n ? `${warning} Re-run ${colors.cyan(\"bos login --key\")} to re-export a publish key.`\n : \"Removed the exported publish key. Re-run bos login --key to re-export one.\";\n }\n\n deleteSessionHandle(configDir);\n return {\n status: \"logged-out\" as const,\n revokedApiKey,\n removedPublishKey,\n warning,\n };\n } catch (error) {\n return {\n status: \"error\" as const,\n revokedApiKey: false,\n removedPublishKey: false,\n error: error instanceof Error ? error.message : \"Unknown error\",\n };\n }\n }),\n };\n}\n\nfunction resolveLoginSiteUrl(\n siteInput: string | undefined,\n session: ResolutionSession | null,\n staging: boolean,\n): string {\n if (siteInput) {\n return siteInput.replace(/\\/+$/, \"\");\n }\n if (staging) {\n const stagingDomain = session?.config?.staging?.domain ?? session?.config?.domain;\n return stagingDomain ? `https://${stagingDomain}` : (session?.runtime?.ui?.url ?? \"\");\n }\n const devUiUrl = session?.runtime?.ui?.url;\n if (devUiUrl && /^https?:\\/\\/(localhost|127\\.0\\.0\\.1)/.test(devUiUrl)) {\n return devUiUrl.replace(/\\/+$/, \"\");\n }\n const domain = session?.config?.domain;\n return domain ? `https://${domain}` : \"\";\n}\n\nasync function exportPublishKey(\n account: string,\n registry: string | undefined,\n): Promise<NonNullable<LoginResult[\"publishKey\"]>> {\n if (!account) {\n throw new Error(\"the authored config has no account to export a publish key for\");\n }\n\n await Effect.runPromise(ensureNearCli);\n\n const network = getNetworkIdForAccount(account);\n const contract = getRegistryNamespaceForAccount(account, registry);\n await listPublishKeys({ account, contract, network });\n\n const keyPair = generateNearKeyPair();\n await addFunctionCallAccessKey({\n account,\n contract,\n allowance: \"1NEAR\",\n functionNames: PUBLISH_FUNCTION_NAMES,\n network,\n keyPair,\n });\n\n const { FileKeyStore } = await import(\"near-kit/keys/file\");\n const keyStore = new FileKeyStore(\"~/.near-credentials\", network);\n await keyStore.add(account, parseNearPrivateKey(keyPair.privateKey));\n try {\n const { statSync, chmodSync } = await import(\"node:fs\");\n const { homedir } = await import(\"node:os\");\n const credentialPath = join(\n homedir(),\n \".near-credentials\",\n network === \"mainnet\" ? \"mainnet\" : network,\n `${account}.json`,\n );\n if (statSync(credentialPath).mode & 0o077) {\n chmodSync(credentialPath, 0o600);\n }\n } catch {}\n\n return {\n publicKey: keyPair.publicKey,\n network,\n contract,\n exportedTo: nearCredentialsPath(network, account),\n };\n}\nfunction parseNearPrivateKey(privateKey: string): KeyPair {\n return parseKey(privateKey) as KeyPair;\n}\n\nasync function revokeApiKey(credential: SessionCredential): Promise<boolean> {\n if (!credential.apiKey || !credential.siteUrl) return false;\n try {\n const response = await fetch(`${credential.siteUrl}/api/auth/api-key/delete`, {\n method: \"POST\",\n headers: {\n \"x-api-key\": credential.apiKey,\n \"content-type\": \"application/json\",\n },\n body: JSON.stringify({ keyId: credential.apiKeyId }),\n signal: AbortSignal.timeout(10_000),\n });\n return response.ok;\n } catch {\n return false;\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;AA4BA,MAAM,yBAAyB,CAAC,eAAe;AAE/C,SAAgB,aAAa,SAAqB;CAChD,OAAO;EACL,YAAY,QAAQ,WAAW,QAAQ,OAAO,EAAE,OAAO,cAAc;GAEnE,MAAM,UADOA,eAAQ,IAAI,QAAQ,mBAAmBC,yBACjC,CAAC,CAAC;GACrB,IAAI,CAAC,SAAS,QACZ,OAAO;IACL,QAAQ;IACR,SAAS;IACT,SAAS;IACT,KAAK,MAAM;IACX,UAAU;IACV,WAAW,MAAM;IACjB,eAAe;IACf,OAAOC;GACT;GAGF,MAAM,UACJ,MAAM,QAAQ,YACT,QAAQ,OAAO,SAAS,WAAW,QAAQ,OAAO,UACnD,QAAQ,OAAO;GACrB,MAAM,UAAUC,uCAAuB,OAAO;GAC9C,MAAM,WAAWC,8CAA+B,SAAS,MAAM,QAAQ;GACvE,IAAI;IACF,MAAMC,cAAO,WAAWC,8BAAa;IAErC,MAAM,UAAU,MAAMC,iCAAgB;KAAE;KAAS;KAAU;IAAQ,CAAC;IAEpE,MAAM,UAAU,MAAMC,0CAAyB;KAC7C;KACA;KACA,WAAW,MAAM;KACjB,eAAe;KACf;IACF,CAAC;IAED,IAAI,QAAQ,SAAS,GAAG;KACtB,QAAQ,IAAI;KACZ,QAAQ,IACN,WAAW,QAAQ,OAAO,uBAAuB,QAAQ,SAAS,IAAI,MAAM,GAAG,EACjF;KACA,KAAK,MAAM,KAAK,SACd,QAAQ,IAAI,OAAOC,qBAAO,IAAI,CAAC,GAAG;KAGpC,IAAI,MAAM,kBAAkB,OAC1B,IAAI;MACF,MAAMC,kCAAiB,SAAS,SAAS,OAAO;MAChD,QAAQ,IACN,KAAKD,qBAAO,MAAM,GAAG,EAAE,WAAW,QAAQ,OAAO,UAC/C,QAAQ,SAAS,IAAI,MAAM,IAE/B;KACF,QAAQ;MACN,QAAQ,IACN,KAAKA,qBAAO,OAAO,GAAG,EAAE,2BACtB,QAAQ,SAAS,IAAI,MAAM,GAC5B,wBACH;KACF;UAEA,QAAQ,IAAI,KAAKA,qBAAO,IAAI,sBAAsB,GAAG;IAEzD;IAEA,OAAO;KACL,QAAQ;KACR;KACA;KACA,KAAK,MAAM;KACX;KACA,WAAW,MAAM;KACjB,eAAe;KACf,WAAW,QAAQ;KACnB,YAAY,QAAQ;IACtB;GACF,SAAS,OAAO;IACd,OAAO;KACL,QAAQ;KACR;KACA;KACA,KAAK,MAAM;KACX;KACA,WAAW,MAAM;KACjB,eAAe;KACf,OAAO,iBAAiB,QAAQ,MAAM,UAAU;IAClD;GACF;EACF,CAAC;EAED,OAAO,QAAQ,MAAM,QAAQ,OAAO,EAAE,OAAO,cAAc;GACzD,MAAM,OAAOT,eAAQ,IAAI,QAAQ,mBAAmBC,yBAAU;GAC9D,MAAM,UAAU,MAAM,QAAQ;GAC9B,MAAM,UAAU,UACX,KAAK,SAAS,QAAQ,SAAS,WAAW,KAAK,SAAS,QAAQ,WAAW,KAC3E,KAAK,SAAS,QAAQ,WAAW;GAEtC,IAAI;IACF,MAAM,UAAU,oBAAoB,MAAM,MAAM,KAAK,SAAS,OAAO;IACrE,MAAM,QAAQ,MAAMU,oCAAiB;KACnC;KACA,QAAQ,MAAM;KACd,SAAS,WAAW;KACpB,WAAW,MAAM;IACnB,CAAC;IAED,QAAQ,IAAI;IACZ,QAAQ,IAAI,oBAAoBF,qBAAO,KAAK,MAAM,QAAQ,GAAG;IAC7D,MAAMG,iCAAc,MAAM,eAAe,CAAC,CAAC,OAAO,UAAmB;KACnE,QAAQ,IAAIH,qBAAO,OAAO,OAAQ,MAAgB,SAAS,CAAC;IAC9D,CAAC;IACD,QAAQ,IAAI;IACZ,QAAQ,IAAI,6BAA6BA,qBAAO,IAAI,MAAM,eAAe,EAAE,EAAE;IAE7E,MAAM,WAAW,MAAM,MAAM,gBAAgB;IAE7C,IAAI,CAAC,SAAS,QACZ,OAAO;KACL,QAAQ;KACR;KACA,UAAU,MAAM;KAChB,OAAO;IACT;IAGF,MAAM,YAAY,IAAI,KAAK,KAAK,IAAI,IAAI,MAAM,YAAY,GAAI,CAAC,CAAC,YAAY;IAC5E,wCAAmB,KAAK,SAAS,QAAQI,qBAAQ,IAAI,GAAG;KACtD,SAAS;KACT,YAAY;MACV,MAAM;MACN,QAAQ,SAAS,OAAO;MACxB,UAAU,SAAS,OAAO;MAC1B,WAAW,SAAS;MACpB,OAAO,MAAM,UAAU;MACvB;MACA,4BAAW,IAAI,KAAK,EAAC,CAAC,YAAY;MAClC;KACF;KACA,YAAY;KACZ,aAAa;IACf,CAAC;IAED,IAAI,UAAyB;IAC7B,IAAI,WAAW,SAAS,aAAa,SAAS,cAAc,SAC1D,UAAU,gBAAgB,SAAS,UAAU,uCAAuC,QAAQ;IAG9F,IAAI,aAAwC;IAC5C,IAAI,MAAM,KACR,IAAI;KACF,aAAa,MAAM,iBAAiB,SAAS,MAAM,QAAQ;IAC7D,SAAS,OAAO;KACd,UAAU,kDACR,iBAAiB,QAAQ,MAAM,UAAU;IAE7C;IAGF,OAAO;KACL,QAAQ;KACR;KACA,WAAW,SAAS;KACpB;KACA,UAAU,MAAM;KAChB;KACA;IACF;GACF,SAAS,OAAO;IACd,OAAO;KACL,QAAQ;KACR,SAAS,MAAM,QAAQ;KACvB,OAAO,iBAAiB,QAAQ,MAAM,UAAU;IAClD;GACF;EACF,CAAC;EAED,QAAQ,QAAQ,OAAO,QAAQ,OAAO,EAAE,OAAO,cAAc;GAC3D,MAAM,OAAOb,eAAQ,IAAI,QAAQ,mBAAmBC,yBAAU;GAC9D,MAAM,YAAY,MAAM,aAAa,KAAK,SAAS,QAAQY,qBAAQ,IAAI;GAEvE,IAAI;IACF,MAAM,UAAUC,uCAAkB,SAAS;IAC3C,IAAI,gBAAgB;IACpB,IAAI,oBAAoB;IACxB,IAAI,UAAyB;IAE7B,IAAI,SAAS,YAAY;KACvB,gBAAgB,MAAM,aAAa,QAAQ,UAAU,CAAC,CAAC,YAAY,KAAK;KACxE,IAAI,CAAC,eACH,UACE;IAEN;IAEA,IAAI,SAAS,YAAY;KACvB,MAAM,UAAU,QAAQ,YAAY,aAAa,KAAK,SAAS,QAAQ,WAAW;KAClF,4CAAuB,QAAQ,WAAW,SAAS,SAAS,QAAQ,WAAW,SAAS;KACxF,oBAAoB;KACpB,UAAU,UACN,GAAG,QAAQ,UAAUL,qBAAO,KAAK,iBAAiB,EAAE,gCACpD;IACN;IAEA,yCAAoB,SAAS;IAC7B,OAAO;KACL,QAAQ;KACR;KACA;KACA;IACF;GACF,SAAS,OAAO;IACd,OAAO;KACL,QAAQ;KACR,eAAe;KACf,mBAAmB;KACnB,OAAO,iBAAiB,QAAQ,MAAM,UAAU;IAClD;GACF;EACF,CAAC;CACH;AACF;AAEA,SAAS,oBACP,WACA,SACA,SACQ;CACR,IAAI,WACF,OAAO,UAAU,QAAQ,QAAQ,EAAE;CAErC,IAAI,SAAS;EACX,MAAM,gBAAgB,SAAS,QAAQ,SAAS,UAAU,SAAS,QAAQ;EAC3E,OAAO,gBAAgB,WAAW,kBAAmB,SAAS,SAAS,IAAI,OAAO;CACpF;CACA,MAAM,WAAW,SAAS,SAAS,IAAI;CACvC,IAAI,YAAY,uCAAuC,KAAK,QAAQ,GAClE,OAAO,SAAS,QAAQ,QAAQ,EAAE;CAEpC,MAAM,SAAS,SAAS,QAAQ;CAChC,OAAO,SAAS,WAAW,WAAW;AACxC;AAEA,eAAe,iBACb,SACA,UACiD;CACjD,IAAI,CAAC,SACH,MAAM,IAAI,MAAM,gEAAgE;CAGlF,MAAMJ,cAAO,WAAWC,8BAAa;CAErC,MAAM,UAAUH,uCAAuB,OAAO;CAC9C,MAAM,WAAWC,8CAA+B,SAAS,QAAQ;CACjE,MAAMG,iCAAgB;EAAE;EAAS;EAAU;CAAQ,CAAC;CAEpD,MAAM,UAAUQ,qCAAoB;CACpC,MAAMP,0CAAyB;EAC7B;EACA;EACA,WAAW;EACX,eAAe;EACf;EACA;CACF,CAAC;CAED,MAAM,EAAE,iBAAiB,MAAM,OAAO;CAEtC,MAAM,IADe,aAAa,uBAAuB,OAC5C,CAAC,CAAC,IAAI,SAAS,oBAAoB,QAAQ,UAAU,CAAC;CACnE,IAAI;EACF,MAAM,EAAE,UAAU,cAAc,MAAM,OAAO;EAC7C,MAAM,EAAE,YAAY,MAAM,OAAO;EACjC,MAAM,qBAAiBQ,gBACrB,QAAQ,GACR,qBACA,YAAY,YAAY,YAAY,SACpC,GAAG,QAAQ,MACb;EACA,IAAI,SAAS,cAAc,CAAC,CAAC,OAAO,IAClC,UAAU,gBAAgB,GAAK;CAEnC,QAAQ,CAAC;CAET,OAAO;EACL,WAAW,QAAQ;EACnB;EACA;EACA,YAAYC,yCAAoB,SAAS,OAAO;CAClD;AACF;AACA,SAAS,oBAAoB,YAA6B;CACxD,WAAOC,mBAAS,UAAU;AAC5B;AAEA,eAAe,aAAa,YAAiD;CAC3E,IAAI,CAAC,WAAW,UAAU,CAAC,WAAW,SAAS,OAAO;CACtD,IAAI;EAUF,QAAO,MATgB,MAAM,GAAG,WAAW,QAAQ,2BAA2B;GAC5E,QAAQ;GACR,SAAS;IACP,aAAa,WAAW;IACxB,gBAAgB;GAClB;GACA,MAAM,KAAK,UAAU,EAAE,OAAO,WAAW,SAAS,CAAC;GACnD,QAAQ,YAAY,QAAQ,GAAM;EACpC,CAAC,EACc,CAAC;CAClB,QAAQ;EACN,OAAO;CACT;AACF"}
1
+ {"version":3,"file":"keys.cjs","names":["Context","BosDepsTag","MISSING_CONFIG_MESSAGE","getNetworkIdForAccount","getRegistryNamespaceForAccount","Effect","ensureNearCli","listPublishKeys","addFunctionCallAccessKey","colors","deleteAccessKeys","startDeviceLogin","openInBrowser","process","readSessionHandle","generateNearKeyPair","join","nearCredentialsPath","parseKey"],"sources":["../../src/commands/keys.ts"],"sourcesContent":["import { join } from \"node:path\";\nimport process from \"node:process\";\nimport { Context, Effect } from \"effect\";\nimport { type KeyPair, parseKey } from \"near-kit\";\nimport { openInBrowser, startDeviceLogin } from \"../auth-login\";\nimport {\n deleteSessionHandle,\n nearCredentialsPath,\n readSessionHandle,\n removePublishedKeyFile,\n type SessionCredential,\n writeSessionHandle,\n} from \"../auth-session\";\nimport { MISSING_CONFIG_MESSAGE } from \"../config\";\nimport type { LoginResult } from \"../contract\";\nimport { getRegistryNamespaceForAccount } from \"../fastkv\";\nimport {\n addFunctionCallAccessKey,\n deleteAccessKeys,\n ensureNearCli,\n generateNearKeyPair,\n listPublishKeys,\n} from \"../near-cli\";\nimport { getNetworkIdForAccount } from \"../network\";\nimport type { ResolutionSession } from \"../resolution/session\";\nimport { colors } from \"../utils/theme\";\nimport { type BosBuilder, BosDepsTag } from \"./shared\";\n\nconst PUBLISH_FUNCTION_NAMES = [\"__fastdata_kv\"];\n\nexport function registerKeys(builder: BosBuilder) {\n return {\n keyPublish: builder.keyPublish.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 account: \"\",\n network: \"mainnet\" as const,\n env: input.env,\n contract: \"\",\n allowance: input.allowance,\n functionNames: PUBLISH_FUNCTION_NAMES,\n error: MISSING_CONFIG_MESSAGE,\n };\n }\n\n const account =\n input.env === \"staging\"\n ? (session.config.staging?.account ?? session.config.account)\n : session.config.account;\n const network = getNetworkIdForAccount(account);\n const contract = getRegistryNamespaceForAccount(account, input.registry);\n try {\n await Effect.runPromise(ensureNearCli);\n\n const oldKeys = await listPublishKeys({ account, contract, network });\n\n const keyPair = await addFunctionCallAccessKey({\n account,\n contract,\n allowance: input.allowance,\n functionNames: PUBLISH_FUNCTION_NAMES,\n network,\n });\n\n if (oldKeys.length > 0) {\n console.log();\n console.log(\n ` Found ${oldKeys.length} existing publish key${oldKeys.length > 1 ? \"s\" : \"\"}:`,\n );\n for (const k of oldKeys) {\n console.log(` ${colors.dim(k)}`);\n }\n\n if (input.removeOldKeys === true) {\n try {\n await deleteAccessKeys(account, oldKeys, network);\n console.log(\n ` ${colors.green(\"✓\")} Removed ${oldKeys.length} old key${\n oldKeys.length > 1 ? \"s\" : \"\"\n }`,\n );\n } catch {\n console.log(\n ` ${colors.yellow(\"⚠\")} Failed to remove old key${\n oldKeys.length > 1 ? \"s\" : \"\"\n } (new key still active)`,\n );\n }\n } else {\n console.log(` ${colors.dim(\"Old key(s) retained.\")}`);\n }\n }\n\n return {\n status: \"published\" as const,\n account,\n network,\n env: input.env,\n contract,\n allowance: input.allowance,\n functionNames: PUBLISH_FUNCTION_NAMES,\n publicKey: keyPair.publicKey,\n privateKey: keyPair.privateKey,\n };\n } catch (error) {\n return {\n status: \"error\" as const,\n account,\n network,\n env: input.env,\n contract,\n allowance: input.allowance,\n functionNames: PUBLISH_FUNCTION_NAMES,\n error: error instanceof Error ? error.message : \"Unknown error\",\n };\n }\n }),\n\n login: builder.login.handler(async ({ input, context }) => {\n const deps = Context.get(context[\"effect/context\"], BosDepsTag);\n const staging = input.env === \"staging\";\n const account = staging\n ? (deps.session?.config?.staging?.account ?? deps.session?.config?.account ?? \"\")\n : (deps.session?.config?.account ?? \"\");\n\n try {\n const siteUrl = resolveLoginSiteUrl(input.site, deps.session, staging);\n const login = await startDeviceLogin({\n siteUrl,\n device: input.device,\n account: account || undefined,\n expiresIn: input.expiresIn,\n });\n\n console.log();\n console.log(` One-time code: ${colors.cyan(login.userCode)}`);\n await openInBrowser(login.verificationUrl).catch((error: unknown) => {\n console.log(colors.yellow(` ⚠ ${(error as Error).message}`));\n });\n console.log();\n console.log(` Waiting for approval at ${colors.dim(login.verificationUrl)}…`);\n\n const approval = await login.waitForApproval();\n\n if (!approval.apiKey) {\n return {\n status: \"error\" as const,\n siteUrl,\n loginUrl: login.verificationUrl,\n error: \"Login approved but no CLI credential was created\",\n };\n }\n\n const expiresAt = new Date(Date.now() + input.expiresIn * 1000).toISOString();\n writeSessionHandle(deps.session?.root ?? process.cwd(), {\n version: 1,\n credential: {\n kind: \"session\",\n apiKey: approval.apiKey.key,\n apiKeyId: approval.apiKey.id,\n accountId: approval.accountId,\n label: input.device ?? siteUrl,\n siteUrl,\n createdAt: new Date().toISOString(),\n expiresAt,\n },\n publishKey: null,\n delegateKey: null,\n });\n\n let warning: string | null = null;\n if (account && approval.accountId && approval.accountId !== account) {\n warning = `Logged in as ${approval.accountId}, but the authored config account is ${account}. Publishes will use the configured account.`;\n }\n\n let publishKey: LoginResult[\"publishKey\"] = null;\n if (input.key) {\n try {\n publishKey = await exportPublishKey(account, input.registry);\n } catch (error) {\n warning = `Session stored, but publish-key export failed: ${\n error instanceof Error ? error.message : \"unknown error\"\n }`;\n }\n }\n\n return {\n status: \"logged-in\" as const,\n siteUrl,\n accountId: approval.accountId,\n expiresAt,\n loginUrl: login.verificationUrl,\n publishKey,\n warning,\n };\n } catch (error) {\n return {\n status: \"error\" as const,\n siteUrl: input.site ?? \"\",\n error: error instanceof Error ? error.message : \"Unknown error\",\n };\n }\n }),\n\n logout: builder.logout.handler(async ({ input, context }) => {\n const deps = Context.get(context[\"effect/context\"], BosDepsTag);\n const configDir = input.configDir ?? deps.session?.root ?? process.cwd();\n\n try {\n const session = readSessionHandle(configDir);\n let revokedApiKey = false;\n let removedPublishKey = false;\n let warning: string | null = null;\n\n if (session?.credential) {\n revokedApiKey = await revokeApiKey(session.credential).catch(() => false);\n if (!revokedApiKey) {\n warning =\n \"Could not revoke the API key remotely — revoke it manually under Settings → API keys.\";\n }\n }\n\n if (session?.publishKey) {\n const account = session.credential?.accountId ?? deps.session?.config?.account ?? \"\";\n removePublishedKeyFile(session.publishKey.network, account, session.publishKey.publicKey);\n removedPublishKey = true;\n warning = warning\n ? `${warning} Re-run ${colors.cyan(\"bos login --key\")} to re-export a publish key.`\n : \"Removed the exported publish key. Re-run bos login --key to re-export one.\";\n }\n\n deleteSessionHandle(configDir);\n return {\n status: \"logged-out\" as const,\n revokedApiKey,\n removedPublishKey,\n warning,\n };\n } catch (error) {\n return {\n status: \"error\" as const,\n revokedApiKey: false,\n removedPublishKey: false,\n error: error instanceof Error ? error.message : \"Unknown error\",\n };\n }\n }),\n };\n}\n\nfunction resolveLoginSiteUrl(\n siteInput: string | undefined,\n session: ResolutionSession | null,\n staging: boolean,\n): string {\n if (siteInput) {\n return siteInput.replace(/\\/+$/, \"\");\n }\n if (staging) {\n const stagingDomain = session?.config?.staging?.domain ?? session?.config?.domain;\n return stagingDomain ? `https://${stagingDomain}` : (session?.runtime?.ui?.url ?? \"\");\n }\n const devUiUrl = session?.runtime?.ui?.url;\n if (devUiUrl && /^https?:\\/\\/(localhost|127\\.0\\.0\\.1)/.test(devUiUrl)) {\n return devUiUrl.replace(/\\/+$/, \"\");\n }\n const domain = session?.config?.domain;\n return domain ? `https://${domain}` : \"\";\n}\n\nasync function exportPublishKey(\n account: string,\n registry: string | undefined,\n): Promise<NonNullable<LoginResult[\"publishKey\"]>> {\n if (!account) {\n throw new Error(\"the authored config has no account to export a publish key for\");\n }\n\n await Effect.runPromise(ensureNearCli);\n\n const network = getNetworkIdForAccount(account);\n const contract = getRegistryNamespaceForAccount(account, registry);\n await listPublishKeys({ account, contract, network });\n\n const keyPair = generateNearKeyPair();\n await addFunctionCallAccessKey({\n account,\n contract,\n allowance: \"1NEAR\",\n functionNames: PUBLISH_FUNCTION_NAMES,\n network,\n keyPair,\n });\n\n const { FileKeyStore } = await import(\"near-kit/keys/file\");\n const keyStore = new FileKeyStore(\"~/.near-credentials\", network);\n await keyStore.add(account, parseNearPrivateKey(keyPair.privateKey));\n try {\n const { statSync, chmodSync } = await import(\"node:fs\");\n const { homedir } = await import(\"node:os\");\n const credentialPath = join(\n homedir(),\n \".near-credentials\",\n network === \"mainnet\" ? \"mainnet\" : network,\n `${account}.json`,\n );\n if (statSync(credentialPath).mode & 0o077) {\n chmodSync(credentialPath, 0o600);\n }\n } catch {}\n\n return {\n publicKey: keyPair.publicKey,\n network,\n contract,\n exportedTo: nearCredentialsPath(network, account),\n };\n}\nfunction parseNearPrivateKey(privateKey: string): KeyPair {\n return parseKey(privateKey) as KeyPair;\n}\n\nasync function revokeApiKey(credential: SessionCredential): Promise<boolean> {\n if (!credential.apiKey || !credential.siteUrl) return false;\n try {\n const response = await fetch(`${credential.siteUrl}/api/auth/api-key/delete`, {\n method: \"POST\",\n headers: {\n \"x-api-key\": credential.apiKey,\n \"content-type\": \"application/json\",\n },\n body: JSON.stringify({ keyId: credential.apiKeyId }),\n signal: AbortSignal.timeout(10_000),\n });\n return response.ok;\n } catch {\n return false;\n }\n}\n"],"mappings":"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"}
@@ -48,7 +48,7 @@ function registerKeys(builder) {
48
48
  console.log();
49
49
  console.log(` Found ${oldKeys.length} existing publish key${oldKeys.length > 1 ? "s" : ""}:`);
50
50
  for (const k of oldKeys) console.log(` ${colors.dim(k)}`);
51
- if (input.removeOldKeys !== false) try {
51
+ if (input.removeOldKeys === true) try {
52
52
  await deleteAccessKeys(account, oldKeys, network);
53
53
  console.log(` ${colors.green("✓")} Removed ${oldKeys.length} old key${oldKeys.length > 1 ? "s" : ""}`);
54
54
  } catch {
@@ -1 +1 @@
1
- {"version":3,"file":"keys.mjs","names":[],"sources":["../../src/commands/keys.ts"],"sourcesContent":["import { join } from \"node:path\";\nimport process from \"node:process\";\nimport { Context, Effect } from \"effect\";\nimport { type KeyPair, parseKey } from \"near-kit\";\nimport { openInBrowser, startDeviceLogin } from \"../auth-login\";\nimport {\n deleteSessionHandle,\n nearCredentialsPath,\n readSessionHandle,\n removePublishedKeyFile,\n type SessionCredential,\n writeSessionHandle,\n} from \"../auth-session\";\nimport { MISSING_CONFIG_MESSAGE } from \"../config\";\nimport type { LoginResult } from \"../contract\";\nimport { getRegistryNamespaceForAccount } from \"../fastkv\";\nimport {\n addFunctionCallAccessKey,\n deleteAccessKeys,\n ensureNearCli,\n generateNearKeyPair,\n listPublishKeys,\n} from \"../near-cli\";\nimport { getNetworkIdForAccount } from \"../network\";\nimport type { ResolutionSession } from \"../resolution/session\";\nimport { colors } from \"../utils/theme\";\nimport { type BosBuilder, BosDepsTag } from \"./shared\";\n\nconst PUBLISH_FUNCTION_NAMES = [\"__fastdata_kv\"];\n\nexport function registerKeys(builder: BosBuilder) {\n return {\n keyPublish: builder.keyPublish.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 account: \"\",\n network: \"mainnet\" as const,\n env: input.env,\n contract: \"\",\n allowance: input.allowance,\n functionNames: PUBLISH_FUNCTION_NAMES,\n error: MISSING_CONFIG_MESSAGE,\n };\n }\n\n const account =\n input.env === \"staging\"\n ? (session.config.staging?.account ?? session.config.account)\n : session.config.account;\n const network = getNetworkIdForAccount(account);\n const contract = getRegistryNamespaceForAccount(account, input.registry);\n try {\n await Effect.runPromise(ensureNearCli);\n\n const oldKeys = await listPublishKeys({ account, contract, network });\n\n const keyPair = await addFunctionCallAccessKey({\n account,\n contract,\n allowance: input.allowance,\n functionNames: PUBLISH_FUNCTION_NAMES,\n network,\n });\n\n if (oldKeys.length > 0) {\n console.log();\n console.log(\n ` Found ${oldKeys.length} existing publish key${oldKeys.length > 1 ? \"s\" : \"\"}:`,\n );\n for (const k of oldKeys) {\n console.log(` ${colors.dim(k)}`);\n }\n\n if (input.removeOldKeys !== false) {\n try {\n await deleteAccessKeys(account, oldKeys, network);\n console.log(\n ` ${colors.green(\"✓\")} Removed ${oldKeys.length} old key${\n oldKeys.length > 1 ? \"s\" : \"\"\n }`,\n );\n } catch {\n console.log(\n ` ${colors.yellow(\"⚠\")} Failed to remove old key${\n oldKeys.length > 1 ? \"s\" : \"\"\n } (new key still active)`,\n );\n }\n } else {\n console.log(` ${colors.dim(\"Old key(s) retained.\")}`);\n }\n }\n\n return {\n status: \"published\" as const,\n account,\n network,\n env: input.env,\n contract,\n allowance: input.allowance,\n functionNames: PUBLISH_FUNCTION_NAMES,\n publicKey: keyPair.publicKey,\n privateKey: keyPair.privateKey,\n };\n } catch (error) {\n return {\n status: \"error\" as const,\n account,\n network,\n env: input.env,\n contract,\n allowance: input.allowance,\n functionNames: PUBLISH_FUNCTION_NAMES,\n error: error instanceof Error ? error.message : \"Unknown error\",\n };\n }\n }),\n\n login: builder.login.handler(async ({ input, context }) => {\n const deps = Context.get(context[\"effect/context\"], BosDepsTag);\n const staging = input.env === \"staging\";\n const account = staging\n ? (deps.session?.config?.staging?.account ?? deps.session?.config?.account ?? \"\")\n : (deps.session?.config?.account ?? \"\");\n\n try {\n const siteUrl = resolveLoginSiteUrl(input.site, deps.session, staging);\n const login = await startDeviceLogin({\n siteUrl,\n device: input.device,\n account: account || undefined,\n expiresIn: input.expiresIn,\n });\n\n console.log();\n console.log(` One-time code: ${colors.cyan(login.userCode)}`);\n await openInBrowser(login.verificationUrl).catch((error: unknown) => {\n console.log(colors.yellow(` ⚠ ${(error as Error).message}`));\n });\n console.log();\n console.log(` Waiting for approval at ${colors.dim(login.verificationUrl)}…`);\n\n const approval = await login.waitForApproval();\n\n if (!approval.apiKey) {\n return {\n status: \"error\" as const,\n siteUrl,\n loginUrl: login.verificationUrl,\n error: \"Login approved but no CLI credential was created\",\n };\n }\n\n const expiresAt = new Date(Date.now() + input.expiresIn * 1000).toISOString();\n writeSessionHandle(deps.session?.root ?? process.cwd(), {\n version: 1,\n credential: {\n kind: \"session\",\n apiKey: approval.apiKey.key,\n apiKeyId: approval.apiKey.id,\n accountId: approval.accountId,\n label: input.device ?? siteUrl,\n siteUrl,\n createdAt: new Date().toISOString(),\n expiresAt,\n },\n publishKey: null,\n delegateKey: null,\n });\n\n let warning: string | null = null;\n if (account && approval.accountId && approval.accountId !== account) {\n warning = `Logged in as ${approval.accountId}, but the authored config account is ${account}. Publishes will use the configured account.`;\n }\n\n let publishKey: LoginResult[\"publishKey\"] = null;\n if (input.key) {\n try {\n publishKey = await exportPublishKey(account, input.registry);\n } catch (error) {\n warning = `Session stored, but publish-key export failed: ${\n error instanceof Error ? error.message : \"unknown error\"\n }`;\n }\n }\n\n return {\n status: \"logged-in\" as const,\n siteUrl,\n accountId: approval.accountId,\n expiresAt,\n loginUrl: login.verificationUrl,\n publishKey,\n warning,\n };\n } catch (error) {\n return {\n status: \"error\" as const,\n siteUrl: input.site ?? \"\",\n error: error instanceof Error ? error.message : \"Unknown error\",\n };\n }\n }),\n\n logout: builder.logout.handler(async ({ input, context }) => {\n const deps = Context.get(context[\"effect/context\"], BosDepsTag);\n const configDir = input.configDir ?? deps.session?.root ?? process.cwd();\n\n try {\n const session = readSessionHandle(configDir);\n let revokedApiKey = false;\n let removedPublishKey = false;\n let warning: string | null = null;\n\n if (session?.credential) {\n revokedApiKey = await revokeApiKey(session.credential).catch(() => false);\n if (!revokedApiKey) {\n warning =\n \"Could not revoke the API key remotely — revoke it manually under Settings → API keys.\";\n }\n }\n\n if (session?.publishKey) {\n const account = session.credential?.accountId ?? deps.session?.config?.account ?? \"\";\n removePublishedKeyFile(session.publishKey.network, account, session.publishKey.publicKey);\n removedPublishKey = true;\n warning = warning\n ? `${warning} Re-run ${colors.cyan(\"bos login --key\")} to re-export a publish key.`\n : \"Removed the exported publish key. Re-run bos login --key to re-export one.\";\n }\n\n deleteSessionHandle(configDir);\n return {\n status: \"logged-out\" as const,\n revokedApiKey,\n removedPublishKey,\n warning,\n };\n } catch (error) {\n return {\n status: \"error\" as const,\n revokedApiKey: false,\n removedPublishKey: false,\n error: error instanceof Error ? error.message : \"Unknown error\",\n };\n }\n }),\n };\n}\n\nfunction resolveLoginSiteUrl(\n siteInput: string | undefined,\n session: ResolutionSession | null,\n staging: boolean,\n): string {\n if (siteInput) {\n return siteInput.replace(/\\/+$/, \"\");\n }\n if (staging) {\n const stagingDomain = session?.config?.staging?.domain ?? session?.config?.domain;\n return stagingDomain ? `https://${stagingDomain}` : (session?.runtime?.ui?.url ?? \"\");\n }\n const devUiUrl = session?.runtime?.ui?.url;\n if (devUiUrl && /^https?:\\/\\/(localhost|127\\.0\\.0\\.1)/.test(devUiUrl)) {\n return devUiUrl.replace(/\\/+$/, \"\");\n }\n const domain = session?.config?.domain;\n return domain ? `https://${domain}` : \"\";\n}\n\nasync function exportPublishKey(\n account: string,\n registry: string | undefined,\n): Promise<NonNullable<LoginResult[\"publishKey\"]>> {\n if (!account) {\n throw new Error(\"the authored config has no account to export a publish key for\");\n }\n\n await Effect.runPromise(ensureNearCli);\n\n const network = getNetworkIdForAccount(account);\n const contract = getRegistryNamespaceForAccount(account, registry);\n await listPublishKeys({ account, contract, network });\n\n const keyPair = generateNearKeyPair();\n await addFunctionCallAccessKey({\n account,\n contract,\n allowance: \"1NEAR\",\n functionNames: PUBLISH_FUNCTION_NAMES,\n network,\n keyPair,\n });\n\n const { FileKeyStore } = await import(\"near-kit/keys/file\");\n const keyStore = new FileKeyStore(\"~/.near-credentials\", network);\n await keyStore.add(account, parseNearPrivateKey(keyPair.privateKey));\n try {\n const { statSync, chmodSync } = await import(\"node:fs\");\n const { homedir } = await import(\"node:os\");\n const credentialPath = join(\n homedir(),\n \".near-credentials\",\n network === \"mainnet\" ? \"mainnet\" : network,\n `${account}.json`,\n );\n if (statSync(credentialPath).mode & 0o077) {\n chmodSync(credentialPath, 0o600);\n }\n } catch {}\n\n return {\n publicKey: keyPair.publicKey,\n network,\n contract,\n exportedTo: nearCredentialsPath(network, account),\n };\n}\nfunction parseNearPrivateKey(privateKey: string): KeyPair {\n return parseKey(privateKey) as KeyPair;\n}\n\nasync function revokeApiKey(credential: SessionCredential): Promise<boolean> {\n if (!credential.apiKey || !credential.siteUrl) return false;\n try {\n const response = await fetch(`${credential.siteUrl}/api/auth/api-key/delete`, {\n method: \"POST\",\n headers: {\n \"x-api-key\": credential.apiKey,\n \"content-type\": \"application/json\",\n },\n body: JSON.stringify({ keyId: credential.apiKeyId }),\n signal: AbortSignal.timeout(10_000),\n });\n return response.ok;\n } catch {\n return false;\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"keys.mjs","names":[],"sources":["../../src/commands/keys.ts"],"sourcesContent":["import { join } from \"node:path\";\nimport process from \"node:process\";\nimport { Context, Effect } from \"effect\";\nimport { type KeyPair, parseKey } from \"near-kit\";\nimport { openInBrowser, startDeviceLogin } from \"../auth-login\";\nimport {\n deleteSessionHandle,\n nearCredentialsPath,\n readSessionHandle,\n removePublishedKeyFile,\n type SessionCredential,\n writeSessionHandle,\n} from \"../auth-session\";\nimport { MISSING_CONFIG_MESSAGE } from \"../config\";\nimport type { LoginResult } from \"../contract\";\nimport { getRegistryNamespaceForAccount } from \"../fastkv\";\nimport {\n addFunctionCallAccessKey,\n deleteAccessKeys,\n ensureNearCli,\n generateNearKeyPair,\n listPublishKeys,\n} from \"../near-cli\";\nimport { getNetworkIdForAccount } from \"../network\";\nimport type { ResolutionSession } from \"../resolution/session\";\nimport { colors } from \"../utils/theme\";\nimport { type BosBuilder, BosDepsTag } from \"./shared\";\n\nconst PUBLISH_FUNCTION_NAMES = [\"__fastdata_kv\"];\n\nexport function registerKeys(builder: BosBuilder) {\n return {\n keyPublish: builder.keyPublish.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 account: \"\",\n network: \"mainnet\" as const,\n env: input.env,\n contract: \"\",\n allowance: input.allowance,\n functionNames: PUBLISH_FUNCTION_NAMES,\n error: MISSING_CONFIG_MESSAGE,\n };\n }\n\n const account =\n input.env === \"staging\"\n ? (session.config.staging?.account ?? session.config.account)\n : session.config.account;\n const network = getNetworkIdForAccount(account);\n const contract = getRegistryNamespaceForAccount(account, input.registry);\n try {\n await Effect.runPromise(ensureNearCli);\n\n const oldKeys = await listPublishKeys({ account, contract, network });\n\n const keyPair = await addFunctionCallAccessKey({\n account,\n contract,\n allowance: input.allowance,\n functionNames: PUBLISH_FUNCTION_NAMES,\n network,\n });\n\n if (oldKeys.length > 0) {\n console.log();\n console.log(\n ` Found ${oldKeys.length} existing publish key${oldKeys.length > 1 ? \"s\" : \"\"}:`,\n );\n for (const k of oldKeys) {\n console.log(` ${colors.dim(k)}`);\n }\n\n if (input.removeOldKeys === true) {\n try {\n await deleteAccessKeys(account, oldKeys, network);\n console.log(\n ` ${colors.green(\"✓\")} Removed ${oldKeys.length} old key${\n oldKeys.length > 1 ? \"s\" : \"\"\n }`,\n );\n } catch {\n console.log(\n ` ${colors.yellow(\"⚠\")} Failed to remove old key${\n oldKeys.length > 1 ? \"s\" : \"\"\n } (new key still active)`,\n );\n }\n } else {\n console.log(` ${colors.dim(\"Old key(s) retained.\")}`);\n }\n }\n\n return {\n status: \"published\" as const,\n account,\n network,\n env: input.env,\n contract,\n allowance: input.allowance,\n functionNames: PUBLISH_FUNCTION_NAMES,\n publicKey: keyPair.publicKey,\n privateKey: keyPair.privateKey,\n };\n } catch (error) {\n return {\n status: \"error\" as const,\n account,\n network,\n env: input.env,\n contract,\n allowance: input.allowance,\n functionNames: PUBLISH_FUNCTION_NAMES,\n error: error instanceof Error ? error.message : \"Unknown error\",\n };\n }\n }),\n\n login: builder.login.handler(async ({ input, context }) => {\n const deps = Context.get(context[\"effect/context\"], BosDepsTag);\n const staging = input.env === \"staging\";\n const account = staging\n ? (deps.session?.config?.staging?.account ?? deps.session?.config?.account ?? \"\")\n : (deps.session?.config?.account ?? \"\");\n\n try {\n const siteUrl = resolveLoginSiteUrl(input.site, deps.session, staging);\n const login = await startDeviceLogin({\n siteUrl,\n device: input.device,\n account: account || undefined,\n expiresIn: input.expiresIn,\n });\n\n console.log();\n console.log(` One-time code: ${colors.cyan(login.userCode)}`);\n await openInBrowser(login.verificationUrl).catch((error: unknown) => {\n console.log(colors.yellow(` ⚠ ${(error as Error).message}`));\n });\n console.log();\n console.log(` Waiting for approval at ${colors.dim(login.verificationUrl)}…`);\n\n const approval = await login.waitForApproval();\n\n if (!approval.apiKey) {\n return {\n status: \"error\" as const,\n siteUrl,\n loginUrl: login.verificationUrl,\n error: \"Login approved but no CLI credential was created\",\n };\n }\n\n const expiresAt = new Date(Date.now() + input.expiresIn * 1000).toISOString();\n writeSessionHandle(deps.session?.root ?? process.cwd(), {\n version: 1,\n credential: {\n kind: \"session\",\n apiKey: approval.apiKey.key,\n apiKeyId: approval.apiKey.id,\n accountId: approval.accountId,\n label: input.device ?? siteUrl,\n siteUrl,\n createdAt: new Date().toISOString(),\n expiresAt,\n },\n publishKey: null,\n delegateKey: null,\n });\n\n let warning: string | null = null;\n if (account && approval.accountId && approval.accountId !== account) {\n warning = `Logged in as ${approval.accountId}, but the authored config account is ${account}. Publishes will use the configured account.`;\n }\n\n let publishKey: LoginResult[\"publishKey\"] = null;\n if (input.key) {\n try {\n publishKey = await exportPublishKey(account, input.registry);\n } catch (error) {\n warning = `Session stored, but publish-key export failed: ${\n error instanceof Error ? error.message : \"unknown error\"\n }`;\n }\n }\n\n return {\n status: \"logged-in\" as const,\n siteUrl,\n accountId: approval.accountId,\n expiresAt,\n loginUrl: login.verificationUrl,\n publishKey,\n warning,\n };\n } catch (error) {\n return {\n status: \"error\" as const,\n siteUrl: input.site ?? \"\",\n error: error instanceof Error ? error.message : \"Unknown error\",\n };\n }\n }),\n\n logout: builder.logout.handler(async ({ input, context }) => {\n const deps = Context.get(context[\"effect/context\"], BosDepsTag);\n const configDir = input.configDir ?? deps.session?.root ?? process.cwd();\n\n try {\n const session = readSessionHandle(configDir);\n let revokedApiKey = false;\n let removedPublishKey = false;\n let warning: string | null = null;\n\n if (session?.credential) {\n revokedApiKey = await revokeApiKey(session.credential).catch(() => false);\n if (!revokedApiKey) {\n warning =\n \"Could not revoke the API key remotely — revoke it manually under Settings → API keys.\";\n }\n }\n\n if (session?.publishKey) {\n const account = session.credential?.accountId ?? deps.session?.config?.account ?? \"\";\n removePublishedKeyFile(session.publishKey.network, account, session.publishKey.publicKey);\n removedPublishKey = true;\n warning = warning\n ? `${warning} Re-run ${colors.cyan(\"bos login --key\")} to re-export a publish key.`\n : \"Removed the exported publish key. Re-run bos login --key to re-export one.\";\n }\n\n deleteSessionHandle(configDir);\n return {\n status: \"logged-out\" as const,\n revokedApiKey,\n removedPublishKey,\n warning,\n };\n } catch (error) {\n return {\n status: \"error\" as const,\n revokedApiKey: false,\n removedPublishKey: false,\n error: error instanceof Error ? error.message : \"Unknown error\",\n };\n }\n }),\n };\n}\n\nfunction resolveLoginSiteUrl(\n siteInput: string | undefined,\n session: ResolutionSession | null,\n staging: boolean,\n): string {\n if (siteInput) {\n return siteInput.replace(/\\/+$/, \"\");\n }\n if (staging) {\n const stagingDomain = session?.config?.staging?.domain ?? session?.config?.domain;\n return stagingDomain ? `https://${stagingDomain}` : (session?.runtime?.ui?.url ?? \"\");\n }\n const devUiUrl = session?.runtime?.ui?.url;\n if (devUiUrl && /^https?:\\/\\/(localhost|127\\.0\\.0\\.1)/.test(devUiUrl)) {\n return devUiUrl.replace(/\\/+$/, \"\");\n }\n const domain = session?.config?.domain;\n return domain ? `https://${domain}` : \"\";\n}\n\nasync function exportPublishKey(\n account: string,\n registry: string | undefined,\n): Promise<NonNullable<LoginResult[\"publishKey\"]>> {\n if (!account) {\n throw new Error(\"the authored config has no account to export a publish key for\");\n }\n\n await Effect.runPromise(ensureNearCli);\n\n const network = getNetworkIdForAccount(account);\n const contract = getRegistryNamespaceForAccount(account, registry);\n await listPublishKeys({ account, contract, network });\n\n const keyPair = generateNearKeyPair();\n await addFunctionCallAccessKey({\n account,\n contract,\n allowance: \"1NEAR\",\n functionNames: PUBLISH_FUNCTION_NAMES,\n network,\n keyPair,\n });\n\n const { FileKeyStore } = await import(\"near-kit/keys/file\");\n const keyStore = new FileKeyStore(\"~/.near-credentials\", network);\n await keyStore.add(account, parseNearPrivateKey(keyPair.privateKey));\n try {\n const { statSync, chmodSync } = await import(\"node:fs\");\n const { homedir } = await import(\"node:os\");\n const credentialPath = join(\n homedir(),\n \".near-credentials\",\n network === \"mainnet\" ? \"mainnet\" : network,\n `${account}.json`,\n );\n if (statSync(credentialPath).mode & 0o077) {\n chmodSync(credentialPath, 0o600);\n }\n } catch {}\n\n return {\n publicKey: keyPair.publicKey,\n network,\n contract,\n exportedTo: nearCredentialsPath(network, account),\n };\n}\nfunction parseNearPrivateKey(privateKey: string): KeyPair {\n return parseKey(privateKey) as KeyPair;\n}\n\nasync function revokeApiKey(credential: SessionCredential): Promise<boolean> {\n if (!credential.apiKey || !credential.siteUrl) return false;\n try {\n const response = await fetch(`${credential.siteUrl}/api/auth/api-key/delete`, {\n method: \"POST\",\n headers: {\n \"x-api-key\": credential.apiKey,\n \"content-type\": \"application/json\",\n },\n body: JSON.stringify({ keyId: credential.apiKeyId }),\n signal: AbortSignal.timeout(10_000),\n });\n return response.ok;\n } catch {\n return false;\n }\n}\n"],"mappings":"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"}