From c270f6bc972f546d9bd85af3edf40af9279684f6 Mon Sep 17 00:00:00 2001 From: Marc-Antoine Parent Date: Sun, 12 Apr 2026 12:41:29 -0400 Subject: [PATCH 1/6] ENG-681 first trial --- .github/workflows/test-database.yaml | 40 ++++++++++++++++ packages/database/package.json | 1 + packages/database/scripts/createEnv.mts | 41 +++++++++------- packages/database/scripts/migrate.ts | 5 +- packages/database/scripts/serve_and_test.ts | 52 +++++++++++++++++++++ packages/database/src/dbDotEnv.mjs | 11 +++-- turbo.json | 1 + 7 files changed, 130 insertions(+), 21 deletions(-) create mode 100644 .github/workflows/test-database.yaml create mode 100644 packages/database/scripts/serve_and_test.ts diff --git a/.github/workflows/test-database.yaml b/.github/workflows/test-database.yaml new file mode 100644 index 000000000..21d8bf630 --- /dev/null +++ b/.github/workflows/test-database.yaml @@ -0,0 +1,40 @@ +name: Database tests +on: + pull_request: + paths: + - "packages/database/**" +env: + SUPABASE_USE_DB: local + SUPABASE_PROJECT_ID: test + GITHUB_TEST: test + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + - name: Install pnpm + uses: pnpm/action-setup@v4 + with: + version: 10.15.1 + run_install: false + - name: Setup node + uses: actions/setup-node@v4 + with: + node-version: "20" + cache: "pnpm" + - name: Install Dependencies + run: pnpm install --frozen-lockfile + - name: Get supabase version + working-directory: ./packages/database + run: echo "SUPABASE_VERSION=$(./node_modules/.bin/supabase --version)" >> $GITHUB_ENV + - name: Cache Docker images. + uses: ScribeMD/docker-cache@0.5.0 + with: + key: docker-${{ runner.os }}-${{ env.SUPABASE_VERSION }} + - name: Setup database + working-directory: ./packages/database + run: pnpm run setup + - name: Serve and run tests + working-directory: ./packages/database + run: pnpm run test:withserve diff --git a/packages/database/package.json b/packages/database/package.json index b0a6c8792..a1f6594f2 100644 --- a/packages/database/package.json +++ b/packages/database/package.json @@ -33,6 +33,7 @@ "lint:fix": "eslint --fix . && tsx scripts/lintSchemas.ts -f && tsx scripts/lintFunctions.ts", "migrate": "tsx scripts/migrate.ts", "test": "pnpm run build && cucumber-js", + "test:withserve": "pnpm run build && tsx scripts/serve_and_test.ts", "genenv": "tsx scripts/createEnv.mts", "gentypes": "tsx scripts/genTypes.ts", "dbdiff": "supabase stop && supabase db diff --use-pg-schema", diff --git a/packages/database/scripts/createEnv.mts b/packages/database/scripts/createEnv.mts index 257de9b20..3227fd271 100644 --- a/packages/database/scripts/createEnv.mts +++ b/packages/database/scripts/createEnv.mts @@ -29,12 +29,17 @@ const getVercelToken = () => { }; const makeFnEnv = (envTxt: string): string => { - return envTxt.split('\n').filter(l=>l.match(/^SUPABASE_\w+_KEY/)).map((l)=> l.replace('SUPABASE_', 'SB_')).join('\n'); -} + return envTxt + .split("\n") + .filter((l) => l.match(/^SUPABASE_\w+_KEY/)) + .map((l) => l.replace("SUPABASE_", "SB_")) + .join("\n"); +}; const makeLocalEnv = () => { execSync("supabase start", { - cwd: projectRoot, stdio: "inherit" + cwd: projectRoot, + stdio: "inherit", }); const stdout = execSync("supabase status -o env", { encoding: "utf8", @@ -54,8 +59,8 @@ const makeLocalEnv = () => { ); writeFileSync( join(projectRoot, "supabase/functions/.env"), - makeFnEnv(prefixed) - ) + makeFnEnv(prefixed), + ); }; const makeBranchEnv = async (vercel: Vercel, vercelToken: string) => { @@ -94,11 +99,11 @@ const makeBranchEnv = async (vercel: Vercel, vercelToken: string) => { throw err; } appendFileSync(".env.branch", `NEXT_API_ROOT="https://${url}/api"\n`); - const fromVercel = readFileSync('.env.branch').toString(); + const fromVercel = readFileSync(".env.branch").toString(); writeFileSync( join(projectRoot, "supabase/functions/.env"), - makeFnEnv(fromVercel) - ) + makeFnEnv(fromVercel), + ); }; const makeProductionEnv = async (vercel: Vercel, vercelToken: string) => { @@ -117,22 +122,26 @@ const makeProductionEnv = async (vercel: Vercel, vercelToken: string) => { `vercel -t ${vercelToken} env pull --environment production .env.production`, ); appendFileSync(".env.production", `NEXT_API_ROOT="https://${url}/api"\n`); - const fromVercel = readFileSync('.env.production').toString(); + const fromVercel = readFileSync(".env.production").toString(); writeFileSync( join(projectRoot, "supabase/functions/.env"), - makeFnEnv(fromVercel) - ) + makeFnEnv(fromVercel), + ); }; const main = async (variant: Variant) => { if (process.env.ROAM_BUILD_SCRIPT) { // special case: production build try { - const response = execSync('curl https://discoursegraphs.com/api/supabase/env'); + const response = execSync( + "curl https://discoursegraphs.com/api/supabase/env", + ); const asJson = JSON.parse(response.toString()) as Record; writeFileSync( join(projectRoot, ".env"), - Object.entries(asJson).map(([k,v])=>`${k}=${v}`).join('\n') + Object.entries(asJson) + .map(([k, v]) => `${k}=${v}`) + .join("\n"), ); return; } catch (e) { @@ -140,10 +149,10 @@ const main = async (variant: Variant) => { return; throw new Error("Could not get environment from site"); } - } - else if ( + } else if ( process.env.HOME === "/vercel" || - process.env.GITHUB_ACTIONS !== undefined + (process.env.GITHUB_ACTIONS !== undefined && + process.env.GITHUB_TEST !== "test") ) // Do not execute in deployment or github action. return; diff --git a/packages/database/scripts/migrate.ts b/packages/database/scripts/migrate.ts index 42e5807ba..3b3549640 100644 --- a/packages/database/scripts/migrate.ts +++ b/packages/database/scripts/migrate.ts @@ -6,7 +6,10 @@ import { getVariant } from "@repo/database/dbDotEnv"; const __dirname = dirname(__filename); const projectRoot = join(__dirname, ".."); -if (process.env.HOME === "/vercel" || process.env.GITHUB_ACTIONS === "true") { +if ( + process.env.HOME === "/vercel" || + (process.env.GITHUB_ACTIONS === "true" && process.env.GITHUB_TEST !== "test") +) { console.log("Skipping in production environment"); process.exit(0); } diff --git a/packages/database/scripts/serve_and_test.ts b/packages/database/scripts/serve_and_test.ts new file mode 100644 index 000000000..de59306af --- /dev/null +++ b/packages/database/scripts/serve_and_test.ts @@ -0,0 +1,52 @@ +import { spawn, execSync } from "node:child_process"; +import { join, dirname } from "path"; + +const __dirname = dirname(__filename); +const projectRoot = join(__dirname, ".."); + +if (process.env.GITHUB_TEST !== "test") { +} + +const serve = spawn("supabase", ["functions", "serve"], { + cwd: projectRoot, + detached: true, +}); + +let resolveCallback: ((v: unknown) => void) | undefined = undefined; +let rejectCallback: ((v: unknown) => void) | undefined = undefined; + +const servingReady = new Promise((rsc, rjc) => { + resolveCallback = rsc; + rejectCallback = rjc; +}); + +serve.stdout.on("data", (data: string) => { + console.log(`stdout: ${data}`); + if (data.includes("Serving functions ")) { + console.log("Found serving functions"); + if (resolveCallback === undefined) throw new Error("did not get callback"); + resolveCallback(null); + } +}); + +const doTest = async () => { + await servingReady; + try { + execSync("cucumber-js", { + cwd: projectRoot, + stdio: "inherit", + }); + // will throw on failure + } finally { + if (serve.pid) process.kill(-serve.pid); + } +}; + +doTest() + .then(() => { + console.log("success"); + }) + .catch((err) => { + console.error(err); + process.exit(-1); + }); diff --git a/packages/database/src/dbDotEnv.mjs b/packages/database/src/dbDotEnv.mjs index 505be334c..56392b5e2 100644 --- a/packages/database/src/dbDotEnv.mjs +++ b/packages/database/src/dbDotEnv.mjs @@ -23,9 +23,8 @@ export const getVariant = () => { : process.env["SUPABASE_USE_DB"]; if (variant === undefined) { dotenv.config(); - const dbGlobalEnv = join(findRoot(),'.env'); - if (existsSync(dbGlobalEnv)) - dotenv.config({path: dbGlobalEnv}); + const dbGlobalEnv = join(findRoot(), ".env"); + if (existsSync(dbGlobalEnv)) dotenv.config({ path: dbGlobalEnv }); variant = process.env["SUPABASE_USE_DB"]; } const processHasVars = @@ -39,7 +38,11 @@ export const getVariant = () => { throw new Error("Invalid variant: " + variant); } - if (process.env.HOME === "/vercel" || process.env.GITHUB_ACTIONS === "true") { + if ( + process.env.HOME === "/vercel" || + (process.env.GITHUB_ACTIONS === "true" && + process.env.GITHUB_TEST !== "test") + ) { // deployment should have variables if (!processHasVars) { console.error("Missing SUPABASE variables in deployment"); diff --git a/turbo.json b/turbo.json index a26a287ff..b57ff73d4 100644 --- a/turbo.json +++ b/turbo.json @@ -29,6 +29,7 @@ "GEMINI_API_KEY", "GH_CLIENT_SECRET_PROD", "GITHUB_ACTIONS", + "GITHUB_TEST", "HOME", "OPENAI_API_KEY", "POSTGRES_PASSWORD", From 5b2a9118a77da6ae17929f0412592b2be6087f8a Mon Sep 17 00:00:00 2001 From: Marc-Antoine Parent Date: Mon, 13 Apr 2026 10:15:08 -0400 Subject: [PATCH 2/6] replace outdated cache library --- .github/workflows/test-database.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test-database.yaml b/.github/workflows/test-database.yaml index 21d8bf630..4bf5f8cd2 100644 --- a/.github/workflows/test-database.yaml +++ b/.github/workflows/test-database.yaml @@ -29,7 +29,7 @@ jobs: working-directory: ./packages/database run: echo "SUPABASE_VERSION=$(./node_modules/.bin/supabase --version)" >> $GITHUB_ENV - name: Cache Docker images. - uses: ScribeMD/docker-cache@0.5.0 + uses: AndreKurait/docker-cache@0.6.0 with: key: docker-${{ runner.os }}-${{ env.SUPABASE_VERSION }} - name: Setup database From 2ae0701fcbf294d28be3cb4873ca9169614e4a6e Mon Sep 17 00:00:00 2001 From: Marc-Antoine Parent Date: Mon, 13 Apr 2026 10:22:51 -0400 Subject: [PATCH 3/6] require appropriate vars --- packages/database/scripts/serve_and_test.ts | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/packages/database/scripts/serve_and_test.ts b/packages/database/scripts/serve_and_test.ts index de59306af..9af4f4192 100644 --- a/packages/database/scripts/serve_and_test.ts +++ b/packages/database/scripts/serve_and_test.ts @@ -1,10 +1,14 @@ import { spawn, execSync } from "node:child_process"; import { join, dirname } from "path"; -const __dirname = dirname(__filename); -const projectRoot = join(__dirname, ".."); +const scriptDir = dirname(__filename); +const projectRoot = join(scriptDir, ".."); if (process.env.GITHUB_TEST !== "test") { + console.error("Please set the GITHUB_TEST variable to 'test'"); +} +if (process.env.SUPABASE_PROJECT_ID !== "test") { + console.error("Please set the SUPABASE_PROJECT_ID variable to 'test'"); } const serve = spawn("supabase", ["functions", "serve"], { @@ -13,11 +17,9 @@ const serve = spawn("supabase", ["functions", "serve"], { }); let resolveCallback: ((v: unknown) => void) | undefined = undefined; -let rejectCallback: ((v: unknown) => void) | undefined = undefined; -const servingReady = new Promise((rsc, rjc) => { +const servingReady = new Promise((rsc) => { resolveCallback = rsc; - rejectCallback = rjc; }); serve.stdout.on("data", (data: string) => { From 031640a9352b1efde836c96fb2dc8ffb1e1c2d71 Mon Sep 17 00:00:00 2001 From: Marc-Antoine Parent Date: Mon, 13 Apr 2026 10:49:07 -0400 Subject: [PATCH 4/6] lint corrections --- packages/database/src/dbDotEnv.mjs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/database/src/dbDotEnv.mjs b/packages/database/src/dbDotEnv.mjs index 56392b5e2..b9025bdd7 100644 --- a/packages/database/src/dbDotEnv.mjs +++ b/packages/database/src/dbDotEnv.mjs @@ -1,5 +1,7 @@ import { readFileSync, existsSync } from "node:fs"; import { join, dirname, basename } from "node:path"; +import process from "node:process"; +import console from "node:console"; import { fileURLToPath } from "node:url"; import dotenv from "dotenv"; @@ -14,7 +16,6 @@ const findRoot = () => { } return dir; }; - export const getVariant = () => { const useDbArgPos = (process.argv || []).indexOf("--use-db"); let variant = @@ -79,9 +80,11 @@ export const envContents = () => { if (!path) { // Fallback to process.env when running in production environments const raw = { + /* eslint-disable @typescript-eslint/naming-convention */ SUPABASE_URL: process.env.SUPABASE_URL, SUPABASE_PUBLISHABLE_KEY: process.env.SUPABASE_PUBLISHABLE_KEY, NEXT_API_ROOT: process.env.NEXT_API_ROOT, + /* eslint-enable @typescript-eslint/naming-convention */ }; return Object.fromEntries(Object.entries(raw).filter(([, v]) => !!v)); } From 222f86b330009ea8f277b8a23d72fd184b57fb4e Mon Sep 17 00:00:00 2001 From: Marc-Antoine Parent Date: Mon, 13 Apr 2026 13:25:41 -0400 Subject: [PATCH 5/6] AI corrections --- packages/database/scripts/serve_and_test.ts | 40 +++++++++++++++++---- 1 file changed, 33 insertions(+), 7 deletions(-) diff --git a/packages/database/scripts/serve_and_test.ts b/packages/database/scripts/serve_and_test.ts index 9af4f4192..954260ede 100644 --- a/packages/database/scripts/serve_and_test.ts +++ b/packages/database/scripts/serve_and_test.ts @@ -4,11 +4,16 @@ import { join, dirname } from "path"; const scriptDir = dirname(__filename); const projectRoot = join(scriptDir, ".."); -if (process.env.GITHUB_TEST !== "test") { +if ( + process.env.GITHUB_ACTIONS === "true" && + process.env.GITHUB_TEST !== "test" +) { console.error("Please set the GITHUB_TEST variable to 'test'"); + process.exit(2); } if (process.env.SUPABASE_PROJECT_ID !== "test") { console.error("Please set the SUPABASE_PROJECT_ID variable to 'test'"); + process.exit(2); } const serve = spawn("supabase", ["functions", "serve"], { @@ -16,20 +21,39 @@ const serve = spawn("supabase", ["functions", "serve"], { detached: true, }); -let resolveCallback: ((v: unknown) => void) | undefined = undefined; +let resolveCallback: ((value: unknown) => void) | undefined = undefined; +let rejectCallback: ((reason: unknown) => void) | undefined = undefined; +let serveSuccess = false; +let timeoutClear: NodeJS.Timeout | undefined = undefined; -const servingReady = new Promise((rsc) => { +const servingReady = new Promise((rsc, rjc) => { resolveCallback = rsc; + rejectCallback = rjc; + + // Add timeout + timeoutClear = setTimeout(() => { + rjc(new Error("Timeout waiting for functions to serve")); + }, 30000); // 30 second timeout }); -serve.stdout.on("data", (data: string) => { - console.log(`stdout: ${data}`); - if (data.includes("Serving functions ")) { +serve.stdout.on("data", (data: Buffer) => { + const output = data.toString(); + console.log(`stdout: ${output}`); + if (output.includes("Serving functions ")) { console.log("Found serving functions"); + serveSuccess = true; + clearTimeout(timeoutClear); if (resolveCallback === undefined) throw new Error("did not get callback"); resolveCallback(null); } }); +serve.on("close", () => { + if (!serveSuccess && rejectCallback) + rejectCallback(new Error("serve closed without being ready")); +}); +serve.on("error", (err) => { + if (rejectCallback) rejectCallback(err); +}); const doTest = async () => { await servingReady; @@ -47,8 +71,10 @@ const doTest = async () => { doTest() .then(() => { console.log("success"); + clearTimeout(timeoutClear); }) .catch((err) => { console.error(err); - process.exit(-1); + clearTimeout(timeoutClear); + process.exit(1); }); From a28e59da15162ef1a95d121b9832bd38f77c4c09 Mon Sep 17 00:00:00 2001 From: Marc-Antoine Parent Date: Mon, 13 Apr 2026 21:46:59 -0400 Subject: [PATCH 6/6] disable docker cache --- .github/workflows/test-database.yaml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/test-database.yaml b/.github/workflows/test-database.yaml index 4bf5f8cd2..c110b1ca0 100644 --- a/.github/workflows/test-database.yaml +++ b/.github/workflows/test-database.yaml @@ -25,13 +25,13 @@ jobs: cache: "pnpm" - name: Install Dependencies run: pnpm install --frozen-lockfile - - name: Get supabase version - working-directory: ./packages/database - run: echo "SUPABASE_VERSION=$(./node_modules/.bin/supabase --version)" >> $GITHUB_ENV - - name: Cache Docker images. - uses: AndreKurait/docker-cache@0.6.0 - with: - key: docker-${{ runner.os }}-${{ env.SUPABASE_VERSION }} + # - name: Get supabase version + # working-directory: ./packages/database + # run: echo "SUPABASE_VERSION=$(./node_modules/.bin/supabase --version)" >> $GITHUB_ENV + # - name: Cache Docker images. + # uses: AndreKurait/docker-cache@0.6.0 + # with: + # key: docker-${{ runner.os }}-${{ env.SUPABASE_VERSION }} - name: Setup database working-directory: ./packages/database run: pnpm run setup