import { listTransactions } from "@olenbetong/appframe-updater"; import chalk from "chalk"; import inquirer from "inquirer"; import { renderTransactionsEditor } from "./editor/TransactionsEditor.js"; import { Command } from "./lib/Command.js"; import { drainStdin } from "./lib/drainStdin.js"; import { importJson } from "./lib/importJson.js"; import { Server } from "./lib/Server.js"; const isInteractive = process.stdout.isTTY; type StageStep = "download" | "generate" | "verify-apply" | "apply" | "verify-deploy" | "deploy" | "apply-prompt"; async function runStageOperations(namespace: string, hostname: string, operations: StageStep[] = ["download"]) { let lastSuccessfulStep = "none"; try { let server = new Server(hostname); if (operations.includes("download")) { await server.download(namespace); lastSuccessfulStep = "download"; } if (operations.includes("generate")) { await server.generate(namespace); lastSuccessfulStep = "generate"; } if (operations.includes("verify-apply") && isInteractive) { for ( let transactions = await server.getTransactions("apply", namespace); transactions.length > 0; transactions = await server.getTransactions("apply", namespace) ) { listTransactions(transactions); await drainStdin(); let { action } = await inquirer.prompt({ type: "select", name: "action", message: "Are you sure you want to apply these transactions? (y)", choices: [ { name: "Yes", value: "yes" }, { name: "No", value: "no" }, { name: "Edit transactions", value: "edit" }, ], default: "yes", }); if (action === "yes") { break; } if (action === "no") { process.exit(0); } let instance = await renderTransactionsEditor({ server, namespace }); await instance.waitUntilExit(); } } if (operations.includes("apply")) { await server.apply(namespace); lastSuccessfulStep = "apply"; } if (operations.includes("verify-deploy") && isInteractive) { for ( let transactions = await server.getTransactions("deploy", namespace); transactions.length > 0; transactions = await server.getTransactions("deploy", namespace) ) { listTransactions(transactions); await drainStdin(); let { action } = await inquirer.prompt({ type: "select", name: "action", message: "Are you sure you want to deploy these transactions? (y)", choices: [ { name: "Yes", value: "yes" }, { name: "No", value: "no" }, { name: "Edit transactions", value: "edit" }, ], default: "yes", }); if (action === "yes") { break; } if (action === "no") { process.exit(0); } let instance = await renderTransactionsEditor({ server, namespace }); await instance.waitUntilExit(); } } if (operations.includes("deploy")) { await server.deploy(namespace); lastSuccessfulStep = "deploy"; } } catch (error: any) { error.lastSuccessfulStep = lastSuccessfulStep; throw error; } } async function getNamespace(namespaceArg: string) { let server = new Server("dev.obet.no"); return await server.getNamespaceArgument(namespaceArg); } async function publishFromDev( namespaceArg: string, options: { server: string; skipGenerate?: boolean; apply?: boolean; hold?: boolean }, ) { let namespace = await getNamespace(namespaceArg); let devSteps: StageStep[] = options.skipGenerate ? ["verify-deploy", "deploy"] : ["generate", "verify-deploy", "deploy"]; await runStageOperations(namespace, "dev.obet.no", devSteps); await runStageOperations(namespace, "stage.obet.no", ["download", "apply", "deploy"]); await runStageOperations( namespace, "test.obet.no", options.apply ? ["download", "verify-apply", "apply"] : ["download"], ); if (options.hold) { let server = new Server("test.obet.no"); let transactions = await server.getTransactions("apply", namespace); if (transactions.length > 0) { console.log(""); console.log(chalk.yellow("The following transactions will be put on hold:")); for (; transactions.length > 0; transactions = await server.getTransactions("apply", namespace)) { listTransactions(transactions); if (!isInteractive) { break; } await drainStdin(); let { action } = await inquirer.prompt({ type: "select", name: "action", message: "Are you sure you want to put these transactions on hold? (y)", choices: [ { name: "Yes", value: "yes" }, { name: "No", value: "no" }, { name: "Edit transactions", value: "edit" }, ], default: "yes", }); if (action === "yes") { break; } if (action === "no") { process.exit(0); } let instance = await renderTransactionsEditor({ server, namespace }); await instance.waitUntilExit(); } await Promise.all( transactions.map((t) => server.dsTransactions.update(t.PrimKey ? { PrimKey: t.PrimKey, Status: 4 } : { ID: t.ID, Status: 4 }), ), ); console.log(""); console.log(`Transactions put on hold on ${chalk.green("test.obet.no")}`); } } console.log(""); let transactionStatus = options.apply ? "applied" : "downloaded"; console.log(`Transactions ${transactionStatus} on ${chalk.green("test.obet.no")}`); if (!options.apply) { console.log(`Run ${chalk.blue(`af apply ${namespace}`)} to apply updates.`); } } async function run( namespace: string, options: { server: string; skipGenerate?: boolean; apply?: boolean; hold?: boolean }, ) { try { await publishFromDev(namespace, options); } catch (error: any) { console.error(error.message, error.lastSuccessfulStep && `(Last step completed: ${error.lastSuccessfulStep})`); process.exit(1); } } const appPkg = await importJson("../package.json"); const program = new Command(); program .version(appPkg.version) .addNamespaceArgument(true) .option("--skip-generate", "Skip the generate step") .option("--apply", "Apply the transactions after downloading them") .option("--hold", "Put transactions on hold after downloading them") .action(run); await program.parseAsync(process.argv);