diff --git a/infra/scripts/move-day-deploy.sh b/infra/scripts/move-day-deploy.sh index a281a06a..e1615486 100755 --- a/infra/scripts/move-day-deploy.sh +++ b/infra/scripts/move-day-deploy.sh @@ -23,9 +23,14 @@ X_BEARER_TOKEN=$(op read "op://allthings/allthings X app/Bearer Token") # stores images through the old account's upload Worker. MEDIA_UPLOAD_URL=$(op read "op://allthings/allthings media upload/url") MEDIA_UPLOAD_TOKEN=$(op read "op://allthings/allthings media upload/token") +# The draft preview reads through draft_collab and seals rounds with this key. +NEON_COLLAB_URL=$(op read "op://allthings/allthings draft_collab/credential") +COLLAB_ANSWERS_KEY=$(op read "op://allthings/allthings collab answers key/credential") : "${NEON_READER_URL:?empty}" "${NEON_SYNC_URL:?empty}" "${LUMA_API_KEY:?empty}" "${X_BEARER_TOKEN:?empty}" : "${MEDIA_UPLOAD_URL:?empty}" "${MEDIA_UPLOAD_TOKEN:?empty}" +: "${NEON_COLLAB_URL:?empty}" "${COLLAB_ANSWERS_KEY:?empty}" export NEON_READER_URL NEON_SYNC_URL LUMA_API_KEY X_BEARER_TOKEN MEDIA_UPLOAD_URL MEDIA_UPLOAD_TOKEN +export NEON_COLLAB_URL COLLAB_ANSWERS_KEY # Prove the allthings profile can still deploy before waiting on it. bun run plan --stage prod --profile allthings >/dev/null diff --git a/infra/tests/move-day-deploy.test.ts b/infra/tests/move-day-deploy.test.ts new file mode 100644 index 00000000..faa4d2f2 --- /dev/null +++ b/infra/tests/move-day-deploy.test.ts @@ -0,0 +1,52 @@ +import { describe, expect, test } from "bun:test"; +import { readdir } from "node:fs/promises"; + +const INFRA = new URL("../", import.meta.url); + +/** + * Every setting the stack reads from the environment: each `Config.String`, + * `Config.Redacted` and `requiredSecret` name in alchemy.run.ts and src/. + */ +async function stackSettings(): Promise> { + const files = [ + new URL("alchemy.run.ts", INFRA), + ...(await readdir(new URL("src/", INFRA))) + .filter((name) => name.endsWith(".ts")) + .map((name) => new URL(`src/${name}`, INFRA)), + ]; + const names = new Set(); + for (const file of files) { + const source = await Bun.file(file).text(); + for (const match of source.matchAll( + /(?:Config\.(?:String|Redacted)|requiredSecret)\("([A-Z0-9_]+)"\)/g, + )) { + names.add(match[1]!); + } + } + return names; +} + +/** The names move-day-deploy.sh exports to the deploy. */ +async function exported(): Promise> { + const script = await Bun.file( + new URL("scripts/move-day-deploy.sh", INFRA), + ).text(); + return new Set( + [...script.matchAll(/^export ([A-Z0-9_ ]+)$/gm)].flatMap((match) => + match[1]!.trim().split(/\s+/), + ), + ); +} + +describe("move-day-deploy.sh", () => { + test("finds the stack's settings", async () => { + expect(await stackSettings()).toContain("NEON_READER_URL"); + }); + + test("exports every setting the stack reads, so its plan can't fail on move day", async () => { + const names = await exported(); + expect( + [...(await stackSettings())].filter((name) => !names.has(name)), + ).toEqual([]); + }); +});