From b1f022875713fb8a840c02e4d030fffde63cc43a Mon Sep 17 00:00:00 2001 From: Erik Thorelli Date: Sun, 11 Oct 2026 09:08:14 -0700 Subject: [PATCH] Read every secret prod needs in the move-day deploy, held by a test - infra/scripts/move-day-deploy.sh read six of the eight settings a prod deploy needs since the draft preview landed: without NEON_COLLAB_URL and COLLAB_ANSWERS_KEY its opening plan fails ("Expected string at COLLAB_ANSWERS_KEY"), checked against prod on 2026-10-11. It now reads, checks and exports both, from the same vault items as prod-deploy. - infra/tests/move-day-deploy.test.ts: every Config.String, Config.Redacted and requiredSecret name in alchemy.run.ts and src/ must be exported by the script, so a new setting can't break move day again. It fails on main with exactly the two names above. --- infra/scripts/move-day-deploy.sh | 5 +++ infra/tests/move-day-deploy.test.ts | 52 +++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+) create mode 100644 infra/tests/move-day-deploy.test.ts 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([]); + }); +});