UNPKG

everything-dev

Version:

A consolidated product package for building Module Federation apps with oRPC APIs.

346 lines (344 loc) • 12.7 kB
const require_runtime = require('../_virtual/_rolldown/runtime.cjs'); const require_types = require('../types.cjs'); const require_fastkv = require('../fastkv.cjs'); const require_config = require('../config.cjs'); const require_theme = require('../utils/theme.cjs'); const require_image_deploy = require('../image-deploy.cjs'); const require_resolution_session = require('../resolution/session.cjs'); const require_shared = require('./shared.cjs'); const require_rollback = require('../rollback.cjs'); const require_publish = require('../publish.cjs'); let node_fs = require("node:fs"); let node_path = require("node:path"); let effect = require("effect"); let node_process = require("node:process"); node_process = require_runtime.__toESM(node_process, 1); //#region src/commands/deploy.ts function registerDeploy(builder) { return { publish: builder.publish.handler(async ({ input, context }) => { const session = effect.Context.get(context["effect/context"], require_shared.BosDepsTag).session; if (!session?.config) return { status: "error", registryUrl: "", error: require_config.MISSING_CONFIG_MESSAGE }; const result = await require_publish.publishToFastKv({ bosConfig: session.config, runtimeConfig: session.runtime, configDir: session.root, env: input.env, build: false, dryRun: input.dryRun, verbose: input.verbose, packages: "all", network: input.network, privateKey: input.privateKey, wallet: input.wallet, registry: input.registry }); return { status: result.status, registryUrl: result.registryUrl, txHash: result.txHash, error: result.error, built: result.built, skipped: result.skipped, deployResults: result.deployResults }; }), rollback: builder.rollback.handler(async ({ input, context }) => { const session = effect.Context.get(context["effect/context"], require_shared.BosDepsTag).session; if (!session) return { status: "error", registryUrl: "", error: require_config.MISSING_CONFIG_MESSAGE }; const { account, domain } = session.config ?? {}; if (!account || !domain) return { status: "error", registryUrl: "", error: "authored config must define account and domain to roll back" }; let history; try { history = await require_fastkv.fetchConfigHistory({ accountId: account, gatewayId: domain, registry: input.registry, limit: input.limit }); } catch (error) { return { status: "error", registryUrl: "", error: `Failed to fetch publish history: ${error instanceof Error ? error.message : String(error)}` }; } if (history.length === 0) return { status: "error", registryUrl: "", error: `No publish history for ${account}/${domain} — nothing to roll back to` }; const historyEntries = history.map((entry) => ({ blockHeight: entry.blockHeight, blockTimestamp: new Date(Number(entry.blockTimestampNs.slice(0, 13))).toISOString(), ...entry.txHash ? { txHash: entry.txHash } : {}, summary: require_rollback.summarizeSlotPins(entry.value) })); if (input.listOnly) return { status: "list", registryUrl: "", history: historyEntries }; const target = input.version !== void 0 ? history.find((entry) => { const iso = new Date(Number(entry.blockTimestampNs.slice(0, 13))).toISOString(); return String(entry.blockHeight) === input.version || iso.startsWith(input.version); }) : input.previous ? history[1] : void 0; if (!target) return { status: "error", registryUrl: "", history: historyEntries, error: input.version ? `No history entry matches "${input.version}" — pick a block height from the listing` : "Specify --version <block-height> or --previous (interactive selection happens CLI-side)" }; let liveValue; try { liveValue = await require_fastkv.fetchBosConfigFromFastKv(`bos://${account}/${domain}`, input.registry); } catch { liveValue = void 0; } if (liveValue && JSON.stringify(liveValue) === JSON.stringify(target.value)) return { status: "error", registryUrl: "", error: "The selected snapshot is identical to the live config — nothing to do" }; const verification = await require_rollback.verifyRollbackSnapshot(target.value); const slotChecks = verification.slots.map((check) => ({ slot: check.slot, ok: check.ok, ...check.reason ? { reason: check.reason } : {} })); if (!verification.ok && verification.verifiable) return { status: "error", registryUrl: "", verification: slotChecks, error: "Refusing to roll back — a pinned slot's bytes are gone or no longer match their SRI" }; if (!verification.verifiable && !input.force) return { status: "error", registryUrl: "", error: "Snapshot predates version manifests and cannot be verified — its bytes were overwritten in place. Re-run with --force to publish it anyway." }; const parsedTarget = require_types.BosConfigSchema.parse(target.value); const payload = require_rollback.buildRollbackPayload(parsedTarget, (/* @__PURE__ */ new Date()).toISOString()); const result = await require_publish.publishToFastKv({ bosConfig: payload, runtimeConfig: session.runtime, configDir: session.root, env: input.env, build: false, dryRun: input.dryRun, verbose: input.verbose, packages: "all", network: input.network, privateKey: input.privateKey, wallet: input.wallet, registry: input.registry }); return { status: result.status, registryUrl: result.registryUrl, txHash: result.txHash, error: result.error, history: historyEntries, ...slotChecks.length > 0 ? { verification: slotChecks } : {} }; }), deploy: builder.deploy.handler(async ({ input, context }) => { const session = effect.Context.get(context["effect/context"], require_shared.BosDepsTag).session; if (!session?.config) return { status: "error", registryUrl: "", error: require_config.MISSING_CONFIG_MESSAGE }; if (input.statusList) { if (!session.config?.account || !session.config.domain) return { status: "error", registryUrl: "", error: "authored config must define account and domain to list deploy manifests" }; try { return { status: "list", registryUrl: "", history: (await require_fastkv.fetchDeployManifests({ accountId: session.config.account, gatewayId: session.config.domain, registry: input.registry })).map((entry) => ({ key: entry.key, blockHeight: entry.blockHeight, blockTimestamp: new Date(Number(entry.blockTimestampNs.slice(0, 13))).toISOString(), ...entry.value?.txHash ? { txHash: entry.value.txHash } : {}, ...entry.value?.publishedAt ? { publishedAt: entry.value.publishedAt } : {} })) }; } catch (error) { return { status: "error", registryUrl: "", error: `Failed to list deploy manifests: ${error instanceof Error ? error.message : String(error)}` }; } } const imageDigestInput = input.imageDigest ?? node_process.default.env.BOS_IMAGE_DIGEST; if (imageDigestInput && !require_image_deploy.isValidImageDigest(imageDigestInput)) return { status: "error", registryUrl: "", error: `Invalid image digest "${imageDigestInput}" — expected sha256:<64 hex characters> (--image-digest / BOS_IMAGE_DIGEST)` }; const result = await require_publish.publishToFastKv({ bosConfig: session.config, runtimeConfig: session.runtime, configDir: session.root, env: input.env, build: input.build, dryRun: input.dryRun, verbose: input.verbose, packages: input.packages, network: input.network, privateKey: input.privateKey, registry: input.registry, fullUpload: input.fullUpload }); if (result.status === "error") return { status: "error", registryUrl: result.registryUrl, txHash: result.txHash, built: result.built, skipped: result.skipped, error: result.error, deployResults: result.deployResults }; if (result.status === "dry-run") return { status: "dry-run", registryUrl: result.registryUrl, built: result.built, skipped: result.skipped }; let nextSession = session; if (result.publishConfig) { const opened = await require_resolution_session.openResolution({ cwd: session.root }); if (opened) nextSession = opened; } const configPublished = { registryUrl: result.registryUrl, txHash: result.txHash, fingerprint: result.fingerprint, slotPins: result.slotPins, built: result.built, skipped: result.skipped, deployResults: result.deployResults }; let image; let imageDigest; let service; const imagePlan = require_image_deploy.resolveDeployImagePlan({ imageRef: require_image_deploy.resolveImageRef({ ciImage: imageDigestInput ? void 0 : nextSession.config?.ci?.image, repository: nextSession.config?.repository, env: node_process.default.env }), imageDigest: imageDigestInput, hasDocker: await require_image_deploy.hasDocker(), hasDockerfile: (0, node_fs.existsSync)((0, node_path.join)(session.root, "Dockerfile")) }); if (imagePlan.kind === "skip") { console.log(); console.log(require_theme.colors.yellow(` Image skipped: ${imagePlan.reason}`)); } else if (imagePlan.kind === "prebuilt") { image = imagePlan.image; imageDigest = imagePlan.digest; console.log(); console.log(require_theme.colors.green(` ${require_theme.icons.ok} Using pre-pushed image ${imagePlan.image}@${imagePlan.digest.slice(0, 19)}…`)); } else { console.log(); try { const imageResult = await require_image_deploy.buildAndPushImage({ image: imagePlan.image, configDir: session.root, verbose: input.verbose }); image = imageResult.image; imageDigest = imageResult.digest; console.log(require_theme.colors.green(` ${require_theme.icons.ok} Image pushed ${imageResult.image}:${imageResult.tag}${imageResult.digest ? ` (${imageResult.digest.slice(0, 19)}…)` : ""}`)); } catch (error) { const message = error instanceof Error ? error.message : String(error); const imageError = message.includes("not found") || message.includes("ENOENT") ? "Docker not found — install Docker to build the runtime image" : `Image build/push failed: ${message}`; console.log(require_theme.colors.yellow(` ${imageError}`)); return { status: "published", ...configPublished, error: `Config published but ${imageError}` }; } } if (node_process.default.env.RAILWAY_TOKEN) { const railwayService = input.service ?? nextSession.config?.ci?.railway?.service; if (!railwayService) { console.log(); console.log(require_theme.colors.yellow(" Railway deploy skipped: ci.railway.service is not configured in bos.config.json")); return { status: "published", ...configPublished, image, error: "Config published but Railway deploy failed: ci.railway.service is not configured in bos.config.json" }; } if (!image || !imageDigest) { console.log(); console.log(require_theme.colors.yellow(" Railway deploy skipped: no runtime image digest — set ci.image (or pass BOS_IMAGE_DIGEST) and make the image available")); return { status: "published", ...configPublished, image, service: railwayService, error: "Config published but Railway deploy requires a runtime image (set ci.image, or pass BOS_IMAGE_DIGEST for a pre-pushed image)" }; } service = railwayService; try { await require_image_deploy.deployImageToRailway({ image, digest: imageDigest, service: railwayService, configDir: session.root }); } catch (error) { const message = error instanceof Error ? error.message : String(error); const railError = message.includes("not found") || message.includes("ENOENT") ? "Railway CLI not found. Install it: npm i -g @railway/cli" : `Railway deploy failed: ${message}`; console.log(require_theme.colors.yellow(` ${railError}`)); return { status: "published", ...configPublished, image, service, error: `Config published but ${railError}` }; } } else { console.log(); console.log(require_theme.colors.yellow(" Railway deploy skipped (RAILWAY_TOKEN not set)")); } return { status: "deployed", ...configPublished, image, service }; }) }; } //#endregion exports.registerDeploy = registerDeploy; //# sourceMappingURL=deploy.cjs.map