From 0717b5dd082d2edf527df20b7f65fc92c011d966 Mon Sep 17 00:00:00 2001 From: Tahmid Ahmed Date: Thu, 15 Jan 2026 20:07:35 -0500 Subject: [PATCH 001/101] 641: wip --- .../test/backend-pre-test/action.yml | 20 ++- .github/scripts/package.json | 16 ++ .github/scripts/pnpm-lock.yaml | 155 ++++++++++++++++++ .github/scripts/run-backend-compile-tests.sh | 13 -- .github/scripts/run-backend-compile-tests.ts | 21 +++ .github/scripts/tsconfig.json | 23 +++ 6 files changed, 234 insertions(+), 14 deletions(-) create mode 100644 .github/scripts/package.json create mode 100644 .github/scripts/pnpm-lock.yaml delete mode 100644 .github/scripts/run-backend-compile-tests.sh create mode 100644 .github/scripts/run-backend-compile-tests.ts create mode 100644 .github/scripts/tsconfig.json diff --git a/.github/composite/test/backend-pre-test/action.yml b/.github/composite/test/backend-pre-test/action.yml index e58004745..899af3cbb 100644 --- a/.github/composite/test/backend-pre-test/action.yml +++ b/.github/composite/test/backend-pre-test/action.yml @@ -20,6 +20,24 @@ runs: javac -version echo "JAVA_HOME=$JAVA_HOME" + - uses: pnpm/action-setup@v3 + with: + version: 10 + + - uses: actions/setup-node@v4 + with: + node-version: 20 + cache: "pnpm" + cache-dependency-path: ".github/scripts/pnpm-lock.yaml" + + - uses: oven-sh/setup-bun@v2 + with: + bun-version: latest + + - name: Install deps + shell: bash + run: pnpm --dir .github/scripts install --frozen-lockfile + - name: Run script shell: bash - run: bash .github/scripts/run-backend-compile-tests.sh + run: bun .github/scripts/run-backend-compile-tests.ts diff --git a/.github/scripts/package.json b/.github/scripts/package.json new file mode 100644 index 000000000..ac73670cd --- /dev/null +++ b/.github/scripts/package.json @@ -0,0 +1,16 @@ +{ + "name": "scripts", + "version": "1.0.0", + "description": "CodeBloom CI Scripts", + "scripts": {}, + "keywords": [], + "author": "Tahmid Ahmed", + "license": "MIT", + "packageManager": "pnpm@10.24.0", + "dependencies": { + "bun": "^1.3.6" + }, + "devDependencies": { + "@types/bun": "^1.3.6" + } +} diff --git a/.github/scripts/pnpm-lock.yaml b/.github/scripts/pnpm-lock.yaml new file mode 100644 index 000000000..046bcf34a --- /dev/null +++ b/.github/scripts/pnpm-lock.yaml @@ -0,0 +1,155 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + bun: + specifier: ^1.3.6 + version: 1.3.6 + devDependencies: + '@types/bun': + specifier: ^1.3.6 + version: 1.3.6 + +packages: + + '@oven/bun-darwin-aarch64@1.3.6': + resolution: {integrity: sha512-27rypIapNkYboOSylkf1tD9UW9Ado2I+P1NBL46Qz29KmOjTL6WuJ7mHDC5O66CYxlOkF5r93NPDAC3lFHYBXw==} + cpu: [arm64] + os: [darwin] + + '@oven/bun-darwin-x64-baseline@1.3.6': + resolution: {integrity: sha512-nqtr+pTsHqusYpG2OZc6s+AmpWDB/FmBvstrK0y5zkti4OqnCuu7Ev2xNjS7uyb47NrAFF40pWqkpaio5XEd7w==} + cpu: [x64] + os: [darwin] + + '@oven/bun-darwin-x64@1.3.6': + resolution: {integrity: sha512-I82xGzPkBxzBKgbl8DsA0RfMQCWTWjNmLjIEkW1ECiv3qK02kHGQ5FGUr/29L/SuvnGsULW4tBTRNZiMzL37nA==} + cpu: [x64] + os: [darwin] + + '@oven/bun-linux-aarch64-musl@1.3.6': + resolution: {integrity: sha512-FR+iJt17rfFgYgpxL3M67AUwujOgjw52ZJzB9vElI5jQXNjTyOKf8eH4meSk4vjlYF3h/AjKYd6pmN0OIUlVKQ==} + cpu: [arm64] + os: [linux] + + '@oven/bun-linux-aarch64@1.3.6': + resolution: {integrity: sha512-YaQEAYjBanoOOtpqk/c5GGcfZIyxIIkQ2m1TbHjedRmJNwxzWBhGinSARFkrRIc3F8pRIGAopXKvJ/2rjN1LzQ==} + cpu: [arm64] + os: [linux] + + '@oven/bun-linux-x64-baseline@1.3.6': + resolution: {integrity: sha512-jRmnX18ak8WzqLrex3siw0PoVKyIeI5AiCv4wJLgSs7VKfOqrPycfHIWfIX2jdn7ngqbHFPzI09VBKANZ4Pckg==} + cpu: [x64] + os: [linux] + + '@oven/bun-linux-x64-musl-baseline@1.3.6': + resolution: {integrity: sha512-7FjVnxnRTp/AgWqSQRT/Vt9TYmvnZ+4M+d9QOKh/Lf++wIFXFGSeAgD6bV1X/yr2UPVmZDk+xdhr2XkU7l2v3w==} + cpu: [x64] + os: [linux] + + '@oven/bun-linux-x64-musl@1.3.6': + resolution: {integrity: sha512-YeXcJ9K6vJAt1zSkeA21J6pTe7PgDMLTHKGI3nQBiMYnYf7Ob3K+b/ChSCznrJG7No5PCPiQPg4zTgA+BOTmSA==} + cpu: [x64] + os: [linux] + + '@oven/bun-linux-x64@1.3.6': + resolution: {integrity: sha512-egfngj0dfJ868cf30E7B+ye9KUWSebYxOG4l9YP5eWeMXCtenpenx0zdKtAn9qxJgEJym5AN6trtlk+J6x8Lig==} + cpu: [x64] + os: [linux] + + '@oven/bun-windows-x64-baseline@1.3.6': + resolution: {integrity: sha512-PFUa7JL4lGoyyppeS4zqfuoXXih+gSE0XxhDMrCPVEUev0yhGNd/tbWBvcdpYnUth80owENoGjc8s5Knopv9wA==} + cpu: [x64] + os: [win32] + + '@oven/bun-windows-x64@1.3.6': + resolution: {integrity: sha512-Sr1KwUcbB0SEpnSPO22tNJppku2khjFluEst+mTGhxHzAGQTQncNeJxDnt3F15n+p9Q+mlcorxehd68n1siikQ==} + cpu: [x64] + os: [win32] + + '@types/bun@1.3.6': + resolution: {integrity: sha512-uWCv6FO/8LcpREhenN1d1b6fcspAB+cefwD7uti8C8VffIv0Um08TKMn98FynpTiU38+y2dUO55T11NgDt8VAA==} + + '@types/node@25.0.9': + resolution: {integrity: sha512-/rpCXHlCWeqClNBwUhDcusJxXYDjZTyE8v5oTO7WbL8eij2nKhUeU89/6xgjU7N4/Vh3He0BtyhJdQbDyhiXAw==} + + bun-types@1.3.6: + resolution: {integrity: sha512-OlFwHcnNV99r//9v5IIOgQ9Uk37gZqrNMCcqEaExdkVq3Avwqok1bJFmvGMCkCE0FqzdY8VMOZpfpR3lwI+CsQ==} + + bun@1.3.6: + resolution: {integrity: sha512-Tn98GlZVN2WM7+lg/uGn5DzUao37Yc0PUz7yzYHdeF5hd+SmHQGbCUIKE4Sspdgtxn49LunK3mDNBC2Qn6GJjw==} + cpu: [arm64, x64] + os: [darwin, linux, win32] + hasBin: true + + undici-types@7.16.0: + resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==} + +snapshots: + + '@oven/bun-darwin-aarch64@1.3.6': + optional: true + + '@oven/bun-darwin-x64-baseline@1.3.6': + optional: true + + '@oven/bun-darwin-x64@1.3.6': + optional: true + + '@oven/bun-linux-aarch64-musl@1.3.6': + optional: true + + '@oven/bun-linux-aarch64@1.3.6': + optional: true + + '@oven/bun-linux-x64-baseline@1.3.6': + optional: true + + '@oven/bun-linux-x64-musl-baseline@1.3.6': + optional: true + + '@oven/bun-linux-x64-musl@1.3.6': + optional: true + + '@oven/bun-linux-x64@1.3.6': + optional: true + + '@oven/bun-windows-x64-baseline@1.3.6': + optional: true + + '@oven/bun-windows-x64@1.3.6': + optional: true + + '@types/bun@1.3.6': + dependencies: + bun-types: 1.3.6 + + '@types/node@25.0.9': + dependencies: + undici-types: 7.16.0 + + bun-types@1.3.6: + dependencies: + '@types/node': 25.0.9 + + bun@1.3.6: + optionalDependencies: + '@oven/bun-darwin-aarch64': 1.3.6 + '@oven/bun-darwin-x64': 1.3.6 + '@oven/bun-darwin-x64-baseline': 1.3.6 + '@oven/bun-linux-aarch64': 1.3.6 + '@oven/bun-linux-aarch64-musl': 1.3.6 + '@oven/bun-linux-x64': 1.3.6 + '@oven/bun-linux-x64-baseline': 1.3.6 + '@oven/bun-linux-x64-musl': 1.3.6 + '@oven/bun-linux-x64-musl-baseline': 1.3.6 + '@oven/bun-windows-x64': 1.3.6 + '@oven/bun-windows-x64-baseline': 1.3.6 + + undici-types@7.16.0: {} diff --git a/.github/scripts/run-backend-compile-tests.sh b/.github/scripts/run-backend-compile-tests.sh deleted file mode 100644 index 1f9e29aa7..000000000 --- a/.github/scripts/run-backend-compile-tests.sh +++ /dev/null @@ -1,13 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -RED='\033[0;31m' - -# fmt -./mvnw spotless:check - -# lint -./mvnw checkstyle:check - -# compile -./mvnw -B verify -Dmaven.test.skip=true --no-transfer-progress diff --git a/.github/scripts/run-backend-compile-tests.ts b/.github/scripts/run-backend-compile-tests.ts new file mode 100644 index 000000000..1c4ca57c6 --- /dev/null +++ b/.github/scripts/run-backend-compile-tests.ts @@ -0,0 +1,21 @@ +import { $ } from "bun"; + +async function main() { + // fmt + await $`./mvnw spotless:check`; + + // lint + await $`./mvnw checkstyle:check`; + + // compile + await $`./mvnw -B verify -Dmaven.test.skip=true --no-transfer-progress`; +} + +main() + .then(() => { + process.exit(0); + }) + .catch((e) => { + console.error(e); + process.exit(1); + }); diff --git a/.github/scripts/tsconfig.json b/.github/scripts/tsconfig.json new file mode 100644 index 000000000..de49a4cea --- /dev/null +++ b/.github/scripts/tsconfig.json @@ -0,0 +1,23 @@ +{ + "compilerOptions": { + "baseUrl": "./", + "allowJs": true, + "checkJs": true, + "skipLibCheck": true, + "strict": true, + "noEmit": true, + "esModuleInterop": true, + "resolveJsonModule": true, + "experimentalDecorators": true, + "paths": { + "@/*": ["src/*"] + } + }, + "include": [ + "**/*.ts", + "**/*.tsx", + "src/lib/types/*.d.ts", + "vitest.config.mts" + ], + "exclude": ["node_modules"] +} From 9f30e9920513b21216106c87b92d035df4e1b3ed Mon Sep 17 00:00:00 2001 From: Tahmid Ahmed Date: Thu, 15 Jan 2026 21:11:05 -0500 Subject: [PATCH 002/101] 641: wip pt2 --- .../test/backend-pre-test/action.yml | 21 ++- .github/scripts/bun.lock | 48 ++++++ .github/scripts/fn/colors.ts | 11 ++ .github/scripts/fn/run-backend-instance.ts | 70 ++++++++ .github/scripts/fn/run-local-db.ts | 75 +++++++++ .github/scripts/local-db.sh | 52 ------ .github/scripts/package.json | 1 - .github/scripts/pnpm-lock.yaml | 155 ------------------ .github/scripts/run-backend-compile-tests.ts | 2 +- .github/scripts/run-backend-instance.sh | 38 ----- .github/scripts/run-backend-tests.ts | 29 ++++ .github/scripts/tsconfig.json | 42 +++-- .github/workflows/ci-cd.yml | 25 ++- 13 files changed, 287 insertions(+), 282 deletions(-) create mode 100644 .github/scripts/bun.lock create mode 100644 .github/scripts/fn/colors.ts create mode 100644 .github/scripts/fn/run-backend-instance.ts create mode 100644 .github/scripts/fn/run-local-db.ts delete mode 100644 .github/scripts/local-db.sh delete mode 100644 .github/scripts/pnpm-lock.yaml delete mode 100644 .github/scripts/run-backend-instance.sh create mode 100644 .github/scripts/run-backend-tests.ts diff --git a/.github/composite/test/backend-pre-test/action.yml b/.github/composite/test/backend-pre-test/action.yml index 899af3cbb..61d2494bf 100644 --- a/.github/composite/test/backend-pre-test/action.yml +++ b/.github/composite/test/backend-pre-test/action.yml @@ -20,23 +20,22 @@ runs: javac -version echo "JAVA_HOME=$JAVA_HOME" - - uses: pnpm/action-setup@v3 - with: - version: 10 - - - uses: actions/setup-node@v4 - with: - node-version: 20 - cache: "pnpm" - cache-dependency-path: ".github/scripts/pnpm-lock.yaml" - - uses: oven-sh/setup-bun@v2 with: bun-version: latest + - name: Cache Bun dependencies + uses: actions/cache@v4 + with: + path: ~/.bun/install/cache + # Hash your lockfile to invalidate the cache when dependencies change + key: ${{ runner.os }}-bun-${{ hashFiles('**/pnpm-lock.yaml') }} + restore-keys: | + ${{ runner.os }}-bun- + - name: Install deps shell: bash - run: pnpm --dir .github/scripts install --frozen-lockfile + run: bun install --cwd .github/scripts --frozen-lockfile - name: Run script shell: bash diff --git a/.github/scripts/bun.lock b/.github/scripts/bun.lock new file mode 100644 index 000000000..6b3eea5d0 --- /dev/null +++ b/.github/scripts/bun.lock @@ -0,0 +1,48 @@ +{ + "lockfileVersion": 1, + "configVersion": 1, + "workspaces": { + "": { + "name": "scripts", + "dependencies": { + "bun": "^1.3.6", + }, + "devDependencies": { + "@types/bun": "^1.3.6", + }, + }, + }, + "packages": { + "@oven/bun-darwin-aarch64": ["@oven/bun-darwin-aarch64@1.3.6", "", { "os": "darwin", "cpu": "arm64" }, "sha512-27rypIapNkYboOSylkf1tD9UW9Ado2I+P1NBL46Qz29KmOjTL6WuJ7mHDC5O66CYxlOkF5r93NPDAC3lFHYBXw=="], + + "@oven/bun-darwin-x64": ["@oven/bun-darwin-x64@1.3.6", "", { "os": "darwin", "cpu": "x64" }, "sha512-I82xGzPkBxzBKgbl8DsA0RfMQCWTWjNmLjIEkW1ECiv3qK02kHGQ5FGUr/29L/SuvnGsULW4tBTRNZiMzL37nA=="], + + "@oven/bun-darwin-x64-baseline": ["@oven/bun-darwin-x64-baseline@1.3.6", "", { "os": "darwin", "cpu": "x64" }, "sha512-nqtr+pTsHqusYpG2OZc6s+AmpWDB/FmBvstrK0y5zkti4OqnCuu7Ev2xNjS7uyb47NrAFF40pWqkpaio5XEd7w=="], + + "@oven/bun-linux-aarch64": ["@oven/bun-linux-aarch64@1.3.6", "", { "os": "linux", "cpu": "arm64" }, "sha512-YaQEAYjBanoOOtpqk/c5GGcfZIyxIIkQ2m1TbHjedRmJNwxzWBhGinSARFkrRIc3F8pRIGAopXKvJ/2rjN1LzQ=="], + + "@oven/bun-linux-aarch64-musl": ["@oven/bun-linux-aarch64-musl@1.3.6", "", { "os": "linux", "cpu": "arm64" }, "sha512-FR+iJt17rfFgYgpxL3M67AUwujOgjw52ZJzB9vElI5jQXNjTyOKf8eH4meSk4vjlYF3h/AjKYd6pmN0OIUlVKQ=="], + + "@oven/bun-linux-x64": ["@oven/bun-linux-x64@1.3.6", "", { "os": "linux", "cpu": "x64" }, "sha512-egfngj0dfJ868cf30E7B+ye9KUWSebYxOG4l9YP5eWeMXCtenpenx0zdKtAn9qxJgEJym5AN6trtlk+J6x8Lig=="], + + "@oven/bun-linux-x64-baseline": ["@oven/bun-linux-x64-baseline@1.3.6", "", { "os": "linux", "cpu": "x64" }, "sha512-jRmnX18ak8WzqLrex3siw0PoVKyIeI5AiCv4wJLgSs7VKfOqrPycfHIWfIX2jdn7ngqbHFPzI09VBKANZ4Pckg=="], + + "@oven/bun-linux-x64-musl": ["@oven/bun-linux-x64-musl@1.3.6", "", { "os": "linux", "cpu": "x64" }, "sha512-YeXcJ9K6vJAt1zSkeA21J6pTe7PgDMLTHKGI3nQBiMYnYf7Ob3K+b/ChSCznrJG7No5PCPiQPg4zTgA+BOTmSA=="], + + "@oven/bun-linux-x64-musl-baseline": ["@oven/bun-linux-x64-musl-baseline@1.3.6", "", { "os": "linux", "cpu": "x64" }, "sha512-7FjVnxnRTp/AgWqSQRT/Vt9TYmvnZ+4M+d9QOKh/Lf++wIFXFGSeAgD6bV1X/yr2UPVmZDk+xdhr2XkU7l2v3w=="], + + "@oven/bun-windows-x64": ["@oven/bun-windows-x64@1.3.6", "", { "os": "win32", "cpu": "x64" }, "sha512-Sr1KwUcbB0SEpnSPO22tNJppku2khjFluEst+mTGhxHzAGQTQncNeJxDnt3F15n+p9Q+mlcorxehd68n1siikQ=="], + + "@oven/bun-windows-x64-baseline": ["@oven/bun-windows-x64-baseline@1.3.6", "", { "os": "win32", "cpu": "x64" }, "sha512-PFUa7JL4lGoyyppeS4zqfuoXXih+gSE0XxhDMrCPVEUev0yhGNd/tbWBvcdpYnUth80owENoGjc8s5Knopv9wA=="], + + "@types/bun": ["@types/bun@1.3.6", "", { "dependencies": { "bun-types": "1.3.6" } }, "sha512-uWCv6FO/8LcpREhenN1d1b6fcspAB+cefwD7uti8C8VffIv0Um08TKMn98FynpTiU38+y2dUO55T11NgDt8VAA=="], + + "@types/node": ["@types/node@25.0.9", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-/rpCXHlCWeqClNBwUhDcusJxXYDjZTyE8v5oTO7WbL8eij2nKhUeU89/6xgjU7N4/Vh3He0BtyhJdQbDyhiXAw=="], + + "bun": ["bun@1.3.6", "", { "optionalDependencies": { "@oven/bun-darwin-aarch64": "1.3.6", "@oven/bun-darwin-x64": "1.3.6", "@oven/bun-darwin-x64-baseline": "1.3.6", "@oven/bun-linux-aarch64": "1.3.6", "@oven/bun-linux-aarch64-musl": "1.3.6", "@oven/bun-linux-x64": "1.3.6", "@oven/bun-linux-x64-baseline": "1.3.6", "@oven/bun-linux-x64-musl": "1.3.6", "@oven/bun-linux-x64-musl-baseline": "1.3.6", "@oven/bun-windows-x64": "1.3.6", "@oven/bun-windows-x64-baseline": "1.3.6" }, "os": [ "linux", "win32", "darwin", ], "cpu": [ "x64", "arm64", ], "bin": { "bun": "bin/bun.exe", "bunx": "bin/bunx.exe" } }, "sha512-Tn98GlZVN2WM7+lg/uGn5DzUao37Yc0PUz7yzYHdeF5hd+SmHQGbCUIKE4Sspdgtxn49LunK3mDNBC2Qn6GJjw=="], + + "bun-types": ["bun-types@1.3.6", "", { "dependencies": { "@types/node": "*" } }, "sha512-OlFwHcnNV99r//9v5IIOgQ9Uk37gZqrNMCcqEaExdkVq3Avwqok1bJFmvGMCkCE0FqzdY8VMOZpfpR3lwI+CsQ=="], + + "undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="], + } +} diff --git a/.github/scripts/fn/colors.ts b/.github/scripts/fn/colors.ts new file mode 100644 index 000000000..c9aaa68d4 --- /dev/null +++ b/.github/scripts/fn/colors.ts @@ -0,0 +1,11 @@ +const CYAN = "\x1b[36m"; +const RESET = "\x1b[0m"; +const DIM = "\x1b[2m"; + +export function cyan(s: string) { + return `${CYAN}${s}${RESET}`; +} + +export function dim(s: string) { + return `${DIM}${s}${RESET}`; +} diff --git a/.github/scripts/fn/run-backend-instance.ts b/.github/scripts/fn/run-backend-instance.ts new file mode 100644 index 000000000..782a9623a --- /dev/null +++ b/.github/scripts/fn/run-backend-instance.ts @@ -0,0 +1,70 @@ +import { $ } from "bun"; +import { cyan } from "./colors"; + +let be: Bun.Subprocess<"ignore", Bun.BunFile, "inherit"> | undefined; + +async function start() { + try { + console.log("Starting backend instance..."); + + await $`java -version`; + await $`javac -version`; + console.log(`JAVA_HOME=${process.env.JAVA_HOME}`); + + const logFile = Bun.file("backend.log"); + be = Bun.spawn( + ["./mvnw", "-Dspring-boot.run.profiles=ci", "spring-boot:run"], + { + stdout: logFile, + }, + ); + + console.log("Waiting for backend to become ready."); + + let ready = false; + const attempts = 30; + + for (let i = 1; i <= attempts; i++) { + try { + const response = await fetch("http://localhost:8080/api"); + const data = (await response.json()) as { success: boolean }; + + if (data.success === true) { + console.log("Backend is up!"); + ready = true; + break; + } + } catch (_) {} + + console.log(`Waiting for backend... (${i}/${attempts})`); + await Bun.sleep(2000); + } + + if (!ready) { + console.error("Backend failed to start in time."); + end(); + } + + console.log("backend ready"); + } catch (e) { + console.error(e); + end(); + } +} + +async function end() { + if (be) { + if (!be.killed) { + be.kill(); + } + console.log(cyan("=== BACKEND LOGS ===")); + console.log(cyan(await Bun.file("backend.log").text())); + console.log(cyan("=== BACKEND LOGS ===")); + } + process.exit(1); +} + +export const backend = { + start, + end, +}; diff --git a/.github/scripts/fn/run-local-db.ts b/.github/scripts/fn/run-local-db.ts new file mode 100644 index 000000000..43b315d93 --- /dev/null +++ b/.github/scripts/fn/run-local-db.ts @@ -0,0 +1,75 @@ +import { $ } from "bun"; + +async function start() { + try { + console.log("Starting postgres container..."); + + await $`docker rm -f codebloom-db`; + + await $`docker run -d \ + --name codebloom-db \ + -e POSTGRES_USER=postgres \ + -e POSTGRES_PASSWORD=postgres \ + -e POSTGRES_DB=codebloom \ + -p 5440:5432 \ + postgres:16`; + + console.log("Waiting for postgres to become ready."); + + let ready = false; + const attempts = 30; + + for (let i = 1; i <= attempts; i++) { + const check = await $`docker exec codebloom-db pg_isready -U postgres` + .quiet() + .nothrow(); + + if (check.exitCode === 0) { + console.log("postgres is ready!"); + ready = true; + break; + } + + console.log(`Waiting for backend... (${i}/${attempts})`); + await Bun.sleep(2000); + } + + if (!ready) { + console.error("postgres failed to start in time."); + process.exit(1); + } + + process.env.DATABASE_HOST = "localhost"; + process.env.DATABASE_PORT = "5440"; + process.env.DATABASE_NAME = "codebloom"; + process.env.DATABASE_USER = "postgres"; + process.env.DATABASE_PASSWORD = "postgres"; + + console.log("postres started, running migrations..."); + + await $`./mvnw flyway:migrate -Dflyway.locations=filesystem:./db`; + + console.log("postgres ready"); + } catch (e) { + console.error(e); + end(); + } +} + +async function end() { + console.log("Stopping and removing postgres container..."); + await $`docker logs codebloom-db`.nothrow(); + await $`docker stop codebloom-db`.quiet().nothrow(); + await $`docker rm codebloom-db`.quiet().nothrow(); + + delete process.env.DATABASE_HOST; + delete process.env.DATABASE_PORT; + delete process.env.DATABASE_NAME; + delete process.env.DATABASE_USER; + delete process.env.DATABASE_PASSWORD; +} + +export const db = { + start, + end, +}; diff --git a/.github/scripts/local-db.sh b/.github/scripts/local-db.sh deleted file mode 100644 index 2decab01d..000000000 --- a/.github/scripts/local-db.sh +++ /dev/null @@ -1,52 +0,0 @@ -#!/usr/bin/env bash - -db_cleanup() { - echo "Stopping and removing postgres container..." - docker stop codebloom-db >/dev/null 2>&1 || true - docker rm codebloom-db >/dev/null 2>&1 || true - - unset DATABASE_HOST - unset DATABASE_PORT - unset DATABASE_NAME - unset DATABASE_USER - unset DATABASE_PASSWORD -} - -db_startup() { - echo "Starting postgres container..." - docker rm -f codebloom-db >/dev/null 2>&1 || true - docker run -d \ - --name codebloom-db \ - -e POSTGRES_USER=postgres \ - -e POSTGRES_PASSWORD=postgres \ - -e POSTGRES_DB=codebloom \ - -p 5440:5432 \ - postgres:16 - - echo "Waiting for postgres to become ready." - for i in {1..30}; do - if docker exec codebloom-db pg_isready -U postgres >/dev/null 2>&1; then - echo "postgres is ready!" - break - fi - echo "Waiting for postgres, sleep 2... ($i/30)" - sleep 2 - done - - if ! docker exec codebloom-db pg_isready -U postgres >/dev/null 2>&1; then - echo "postgres failed to start in time." - docker logs codebloom-db || true - exit 1 - fi - - export DATABASE_HOST=localhost - export DATABASE_PORT=5440 - export DATABASE_NAME=codebloom - export DATABASE_USER=postgres - export DATABASE_PASSWORD=postgres - - echo "postgres ready. migrating now..." - ./mvnw flyway:migrate -Dflyway.locations=filesystem:./db - echo "postgres migration complete" - -} diff --git a/.github/scripts/package.json b/.github/scripts/package.json index ac73670cd..c5c7a8a5a 100644 --- a/.github/scripts/package.json +++ b/.github/scripts/package.json @@ -6,7 +6,6 @@ "keywords": [], "author": "Tahmid Ahmed", "license": "MIT", - "packageManager": "pnpm@10.24.0", "dependencies": { "bun": "^1.3.6" }, diff --git a/.github/scripts/pnpm-lock.yaml b/.github/scripts/pnpm-lock.yaml deleted file mode 100644 index 046bcf34a..000000000 --- a/.github/scripts/pnpm-lock.yaml +++ /dev/null @@ -1,155 +0,0 @@ -lockfileVersion: '9.0' - -settings: - autoInstallPeers: true - excludeLinksFromLockfile: false - -importers: - - .: - dependencies: - bun: - specifier: ^1.3.6 - version: 1.3.6 - devDependencies: - '@types/bun': - specifier: ^1.3.6 - version: 1.3.6 - -packages: - - '@oven/bun-darwin-aarch64@1.3.6': - resolution: {integrity: sha512-27rypIapNkYboOSylkf1tD9UW9Ado2I+P1NBL46Qz29KmOjTL6WuJ7mHDC5O66CYxlOkF5r93NPDAC3lFHYBXw==} - cpu: [arm64] - os: [darwin] - - '@oven/bun-darwin-x64-baseline@1.3.6': - resolution: {integrity: sha512-nqtr+pTsHqusYpG2OZc6s+AmpWDB/FmBvstrK0y5zkti4OqnCuu7Ev2xNjS7uyb47NrAFF40pWqkpaio5XEd7w==} - cpu: [x64] - os: [darwin] - - '@oven/bun-darwin-x64@1.3.6': - resolution: {integrity: sha512-I82xGzPkBxzBKgbl8DsA0RfMQCWTWjNmLjIEkW1ECiv3qK02kHGQ5FGUr/29L/SuvnGsULW4tBTRNZiMzL37nA==} - cpu: [x64] - os: [darwin] - - '@oven/bun-linux-aarch64-musl@1.3.6': - resolution: {integrity: sha512-FR+iJt17rfFgYgpxL3M67AUwujOgjw52ZJzB9vElI5jQXNjTyOKf8eH4meSk4vjlYF3h/AjKYd6pmN0OIUlVKQ==} - cpu: [arm64] - os: [linux] - - '@oven/bun-linux-aarch64@1.3.6': - resolution: {integrity: sha512-YaQEAYjBanoOOtpqk/c5GGcfZIyxIIkQ2m1TbHjedRmJNwxzWBhGinSARFkrRIc3F8pRIGAopXKvJ/2rjN1LzQ==} - cpu: [arm64] - os: [linux] - - '@oven/bun-linux-x64-baseline@1.3.6': - resolution: {integrity: sha512-jRmnX18ak8WzqLrex3siw0PoVKyIeI5AiCv4wJLgSs7VKfOqrPycfHIWfIX2jdn7ngqbHFPzI09VBKANZ4Pckg==} - cpu: [x64] - os: [linux] - - '@oven/bun-linux-x64-musl-baseline@1.3.6': - resolution: {integrity: sha512-7FjVnxnRTp/AgWqSQRT/Vt9TYmvnZ+4M+d9QOKh/Lf++wIFXFGSeAgD6bV1X/yr2UPVmZDk+xdhr2XkU7l2v3w==} - cpu: [x64] - os: [linux] - - '@oven/bun-linux-x64-musl@1.3.6': - resolution: {integrity: sha512-YeXcJ9K6vJAt1zSkeA21J6pTe7PgDMLTHKGI3nQBiMYnYf7Ob3K+b/ChSCznrJG7No5PCPiQPg4zTgA+BOTmSA==} - cpu: [x64] - os: [linux] - - '@oven/bun-linux-x64@1.3.6': - resolution: {integrity: sha512-egfngj0dfJ868cf30E7B+ye9KUWSebYxOG4l9YP5eWeMXCtenpenx0zdKtAn9qxJgEJym5AN6trtlk+J6x8Lig==} - cpu: [x64] - os: [linux] - - '@oven/bun-windows-x64-baseline@1.3.6': - resolution: {integrity: sha512-PFUa7JL4lGoyyppeS4zqfuoXXih+gSE0XxhDMrCPVEUev0yhGNd/tbWBvcdpYnUth80owENoGjc8s5Knopv9wA==} - cpu: [x64] - os: [win32] - - '@oven/bun-windows-x64@1.3.6': - resolution: {integrity: sha512-Sr1KwUcbB0SEpnSPO22tNJppku2khjFluEst+mTGhxHzAGQTQncNeJxDnt3F15n+p9Q+mlcorxehd68n1siikQ==} - cpu: [x64] - os: [win32] - - '@types/bun@1.3.6': - resolution: {integrity: sha512-uWCv6FO/8LcpREhenN1d1b6fcspAB+cefwD7uti8C8VffIv0Um08TKMn98FynpTiU38+y2dUO55T11NgDt8VAA==} - - '@types/node@25.0.9': - resolution: {integrity: sha512-/rpCXHlCWeqClNBwUhDcusJxXYDjZTyE8v5oTO7WbL8eij2nKhUeU89/6xgjU7N4/Vh3He0BtyhJdQbDyhiXAw==} - - bun-types@1.3.6: - resolution: {integrity: sha512-OlFwHcnNV99r//9v5IIOgQ9Uk37gZqrNMCcqEaExdkVq3Avwqok1bJFmvGMCkCE0FqzdY8VMOZpfpR3lwI+CsQ==} - - bun@1.3.6: - resolution: {integrity: sha512-Tn98GlZVN2WM7+lg/uGn5DzUao37Yc0PUz7yzYHdeF5hd+SmHQGbCUIKE4Sspdgtxn49LunK3mDNBC2Qn6GJjw==} - cpu: [arm64, x64] - os: [darwin, linux, win32] - hasBin: true - - undici-types@7.16.0: - resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==} - -snapshots: - - '@oven/bun-darwin-aarch64@1.3.6': - optional: true - - '@oven/bun-darwin-x64-baseline@1.3.6': - optional: true - - '@oven/bun-darwin-x64@1.3.6': - optional: true - - '@oven/bun-linux-aarch64-musl@1.3.6': - optional: true - - '@oven/bun-linux-aarch64@1.3.6': - optional: true - - '@oven/bun-linux-x64-baseline@1.3.6': - optional: true - - '@oven/bun-linux-x64-musl-baseline@1.3.6': - optional: true - - '@oven/bun-linux-x64-musl@1.3.6': - optional: true - - '@oven/bun-linux-x64@1.3.6': - optional: true - - '@oven/bun-windows-x64-baseline@1.3.6': - optional: true - - '@oven/bun-windows-x64@1.3.6': - optional: true - - '@types/bun@1.3.6': - dependencies: - bun-types: 1.3.6 - - '@types/node@25.0.9': - dependencies: - undici-types: 7.16.0 - - bun-types@1.3.6: - dependencies: - '@types/node': 25.0.9 - - bun@1.3.6: - optionalDependencies: - '@oven/bun-darwin-aarch64': 1.3.6 - '@oven/bun-darwin-x64': 1.3.6 - '@oven/bun-darwin-x64-baseline': 1.3.6 - '@oven/bun-linux-aarch64': 1.3.6 - '@oven/bun-linux-aarch64-musl': 1.3.6 - '@oven/bun-linux-x64': 1.3.6 - '@oven/bun-linux-x64-baseline': 1.3.6 - '@oven/bun-linux-x64-musl': 1.3.6 - '@oven/bun-linux-x64-musl-baseline': 1.3.6 - '@oven/bun-windows-x64': 1.3.6 - '@oven/bun-windows-x64-baseline': 1.3.6 - - undici-types@7.16.0: {} diff --git a/.github/scripts/run-backend-compile-tests.ts b/.github/scripts/run-backend-compile-tests.ts index 1c4ca57c6..a2aad8a60 100644 --- a/.github/scripts/run-backend-compile-tests.ts +++ b/.github/scripts/run-backend-compile-tests.ts @@ -13,7 +13,7 @@ async function main() { main() .then(() => { - process.exit(0); + process.exit(); }) .catch((e) => { console.error(e); diff --git a/.github/scripts/run-backend-instance.sh b/.github/scripts/run-backend-instance.sh deleted file mode 100644 index 5c9a5549a..000000000 --- a/.github/scripts/run-backend-instance.sh +++ /dev/null @@ -1,38 +0,0 @@ -#!/usr/bin/env bash - -backend_cleanup() { - echo "[INFO] ===== BACKEND LOGS START =====" - cat backend.log || echo "[WARN] backend.log not found" - echo "[INFO] ===== BACKEND LOGS END =====" - if kill $(cat spring_pid.txt) >/dev/null 2>&1; then - echo "Backend process killed successfully." - else - echo "Backend was not running or already stopped." - fi -} - -backend_startup() { - java -version - javac -version - echo "JAVA_HOME=$JAVA_HOME" - - ./mvnw -Dspring-boot.run.profiles=ci spring-boot:run >backend.log 2>&1 & - echo $! >spring_pid.txt - - backend_started=false - for i in {1..30}; do - if curl -s http://localhost:8080/api | grep -q '"success":true'; then - echo "Backend is up!" - backend_started=true - break - fi - echo "Waiting for backend... ($i/30)" - sleep 5 - done - - if [ "$backend_started" = false ]; then - echo "Backend failed to start in time." - exit 1 - fi - -} diff --git a/.github/scripts/run-backend-tests.ts b/.github/scripts/run-backend-tests.ts new file mode 100644 index 000000000..0c2389d98 --- /dev/null +++ b/.github/scripts/run-backend-tests.ts @@ -0,0 +1,29 @@ +import { $ } from "bun"; +import { db } from "./fn/run-local-db"; + +async function main() { + try { + await db.start(); + + await $`./mvnw -B install -D skipTests --no-transfer-progress`; + + await $`./mvnw -B exec:java -e -D exec.mainClass=com.microsoft.playwright.CLI -D exec.args="install-deps"`; + await $`./mvnw -B exec:java -e -D exec.mainClass=com.microsoft.playwright.CLI -D exec.args="install firefox"`; + + await $`corepack enable pnpm`; + await $`cd email && pnpm i --frozen-lockfile && ./email.sh && cd ..`; + + await $`./mvnw clean verify -Dspring.profiles.active=ci`; + } finally { + await db.end(); + } +} + +main() + .then(() => { + process.exit(0); + }) + .catch((e) => { + console.error(e); + process.exit(1); + }); diff --git a/.github/scripts/tsconfig.json b/.github/scripts/tsconfig.json index de49a4cea..c3c57e20d 100644 --- a/.github/scripts/tsconfig.json +++ b/.github/scripts/tsconfig.json @@ -1,23 +1,29 @@ { "compilerOptions": { - "baseUrl": "./", + // Environment setup & latest features + "lib": ["ESNext"], + "target": "ESNext", + "module": "Preserve", + "moduleDetection": "force", + "jsx": "react-jsx", "allowJs": true, - "checkJs": true, - "skipLibCheck": true, - "strict": true, + + // Bundler mode + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, "noEmit": true, - "esModuleInterop": true, - "resolveJsonModule": true, - "experimentalDecorators": true, - "paths": { - "@/*": ["src/*"] - } - }, - "include": [ - "**/*.ts", - "**/*.tsx", - "src/lib/types/*.d.ts", - "vitest.config.mts" - ], - "exclude": ["node_modules"] + + // Best practices + "strict": true, + "skipLibCheck": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedIndexedAccess": true, + "noImplicitOverride": true, + + // Some stricter flags (disabled by default) + "noUnusedLocals": false, + "noUnusedParameters": false, + "noPropertyAccessFromIndexSignature": false + } } diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml index 20c2280f8..2f86294b3 100644 --- a/.github/workflows/ci-cd.yml +++ b/.github/workflows/ci-cd.yml @@ -40,11 +40,6 @@ jobs: - name: Disable man-db uses: ./.github/composite/disable-mandb - - name: Set up Node.js - uses: actions/setup-node@v4 - with: - node-version: 20 - - name: Set up OpenJDK 25 uses: actions/setup-java@v4 with: @@ -65,8 +60,26 @@ jobs: GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} UNLOAD_ENVIRONMENTS: ci,ci-app + - uses: oven-sh/setup-bun@v2 + with: + bun-version: latest + + - name: Cache Bun dependencies + uses: actions/cache@v4 + with: + path: ~/.bun/install/cache + # Hash your lockfile to invalidate the cache when dependencies change + key: ${{ runner.os }}-bun-${{ hashFiles('**/pnpm-lock.yaml') }} + restore-keys: | + ${{ runner.os }}-bun- + + - name: Install deps + shell: bash + run: bun install --cwd .github/scripts --frozen-lockfile + - name: Run script - run: bash .github/scripts/run-backend-tests.sh + shell: bash + run: bun .github/scripts/run-backend-tests.ts - name: Upload JaCoCo HTML report uses: actions/upload-artifact@v4 From 3337b0353b7e88c3d7220655c2f196caa9d01b4c Mon Sep 17 00:00:00 2001 From: Tahmid Ahmed Date: Thu, 15 Jan 2026 21:55:21 -0500 Subject: [PATCH 003/101] 641: wip pt3 641: wip fix coloring 641: wip fix coloring pt2 641: update ai review 641: add load-secrets 641: add load-secrets pt2 641: add load-secrets pt3 641: add load-secrets pt4 641: add load-secrets pt5 fix writing logic 641: add load-secrets pt5 fix writing logic wdekfjnewkgfn --- .github/composite/load-secrets/action.yml | 20 ++++- .github/scripts/fn/colors.ts | 98 +++++++++++++++++++++- .github/scripts/fn/run-backend-instance.ts | 12 ++- .github/scripts/fn/run-local-db.ts | 12 ++- .github/scripts/load-secrets.sh | 67 --------------- .github/scripts/load-secrets.ts | 96 +++++++++++++++++++++ .github/scripts/run-frontend-tests.sh | 15 ---- .github/scripts/run-frontend-tests.ts | 27 ++++++ .github/workflows/ai-review.yml | 20 ++++- .github/workflows/ci-cd.yml | 20 ++++- 10 files changed, 292 insertions(+), 95 deletions(-) delete mode 100644 .github/scripts/load-secrets.sh create mode 100644 .github/scripts/load-secrets.ts delete mode 100644 .github/scripts/run-frontend-tests.sh create mode 100644 .github/scripts/run-frontend-tests.ts diff --git a/.github/composite/load-secrets/action.yml b/.github/composite/load-secrets/action.yml index 64392f6da..25df02b5b 100644 --- a/.github/composite/load-secrets/action.yml +++ b/.github/composite/load-secrets/action.yml @@ -37,8 +37,24 @@ runs: shell: bash run: git-crypt --version - - name: Run load secrets script + - uses: oven-sh/setup-bun@v2 + with: + bun-version: latest + + - name: Cache Bun dependencies + uses: actions/cache@v4 + with: + path: ~/.bun/install/cache + key: ${{ runner.os }}-bun-${{ hashFiles('**/pnpm-lock.yaml') }} + restore-keys: | + ${{ runner.os }}-bun- + + - name: Install deps + shell: bash + run: bun install --cwd .github/scripts --frozen-lockfile + + - name: Run script shell: bash - run: bash .github/scripts/load-secrets.sh + run: bun .github/scripts/load-secrets.ts env: UNLOAD_ENVIRONMENTS: ${{ inputs.UNLOAD_ENVIRONMENTS }} diff --git a/.github/scripts/fn/colors.ts b/.github/scripts/fn/colors.ts index c9aaa68d4..aebe42cbd 100644 --- a/.github/scripts/fn/colors.ts +++ b/.github/scripts/fn/colors.ts @@ -1,11 +1,103 @@ -const CYAN = "\x1b[36m"; -const RESET = "\x1b[0m"; -const DIM = "\x1b[2m"; +export function black(s: string) { + return `${BLACK}${s}${RESET}`; +} + +export function red(s: string) { + return `${RED}${s}${RESET}`; +} + +export function green(s: string) { + return `${GREEN}${s}${RESET}`; +} + +export function yellow(s: string) { + return `${YELLOW}${s}${RESET}`; +} + +export function blue(s: string) { + return `${BLUE}${s}${RESET}`; +} + +export function magenta(s: string) { + return `${MAGENTA}${s}${RESET}`; +} export function cyan(s: string) { return `${CYAN}${s}${RESET}`; } +export function white(s: string) { + return `${WHITE}${s}${RESET}`; +} + +export function gray(s: string) { + return `${GRAY}${s}${RESET}`; +} + +export function brightRed(s: string) { + return `${BRIGHT_RED}${s}${RESET}`; +} + +export function brightGreen(s: string) { + return `${BRIGHT_GREEN}${s}${RESET}`; +} + +export function brightYellow(s: string) { + return `${BRIGHT_YELLOW}${s}${RESET}`; +} + +export function brightBlue(s: string) { + return `${BRIGHT_BLUE}${s}${RESET}`; +} + +export function brightMagenta(s: string) { + return `${BRIGHT_MAGENTA}${s}${RESET}`; +} + +export function brightCyan(s: string) { + return `${BRIGHT_CYAN}${s}${RESET}`; +} + +export function brightWhite(s: string) { + return `${BRIGHT_WHITE}${s}${RESET}`; +} + +export function bold(s: string) { + return `${BOLD}${s}${RESET}`; +} + export function dim(s: string) { return `${DIM}${s}${RESET}`; } + +export function italic(s: string) { + return `${ITALIC}${s}${RESET}`; +} + +export function underline(s: string) { + return `${UNDERLINE}${s}${RESET}`; +} + +const BLACK = "\x1b[30m"; +const RED = "\x1b[31m"; +const GREEN = "\x1b[32m"; +const YELLOW = "\x1b[33m"; +const BLUE = "\x1b[34m"; +const MAGENTA = "\x1b[35m"; +const CYAN = "\x1b[36m"; +const WHITE = "\x1b[37m"; +const GRAY = "\x1b[90m"; + +const BRIGHT_RED = "\x1b[91m"; +const BRIGHT_GREEN = "\x1b[92m"; +const BRIGHT_YELLOW = "\x1b[93m"; +const BRIGHT_BLUE = "\x1b[94m"; +const BRIGHT_MAGENTA = "\x1b[95m"; +const BRIGHT_CYAN = "\x1b[96m"; +const BRIGHT_WHITE = "\x1b[97m"; + +const RESET = "\x1b[0m"; +const BOLD = "\x1b[1m"; +const DIM = "\x1b[2m"; +const ITALIC = "\x1b[3m"; +const UNDERLINE = "\x1b[4m"; diff --git a/.github/scripts/fn/run-backend-instance.ts b/.github/scripts/fn/run-backend-instance.ts index 782a9623a..c35ae98b4 100644 --- a/.github/scripts/fn/run-backend-instance.ts +++ b/.github/scripts/fn/run-backend-instance.ts @@ -42,7 +42,8 @@ async function start() { if (!ready) { console.error("Backend failed to start in time."); - end(); + await end(); + process.exit(1); } console.log("backend ready"); @@ -58,10 +59,13 @@ async function end() { be.kill(); } console.log(cyan("=== BACKEND LOGS ===")); - console.log(cyan(await Bun.file("backend.log").text())); - console.log(cyan("=== BACKEND LOGS ===")); + const logs = await Bun.file("backend.log").text(); + logs + .split("\n") + .filter((s) => s.length > 0) + .forEach((line) => console.log(cyan(line))); + console.log(cyan("=== BACKEND LOGS END ===")); } - process.exit(1); } export const backend = { diff --git a/.github/scripts/fn/run-local-db.ts b/.github/scripts/fn/run-local-db.ts index 43b315d93..97298a6ba 100644 --- a/.github/scripts/fn/run-local-db.ts +++ b/.github/scripts/fn/run-local-db.ts @@ -1,4 +1,5 @@ import { $ } from "bun"; +import { brightGreen, brightMagenta } from "./colors"; async function start() { try { @@ -36,6 +37,7 @@ async function start() { if (!ready) { console.error("postgres failed to start in time."); + await end(); process.exit(1); } @@ -58,7 +60,15 @@ async function start() { async function end() { console.log("Stopping and removing postgres container..."); - await $`docker logs codebloom-db`.nothrow(); + + console.log(brightMagenta("=== DB LOGS ===")); + const logs = await $`docker logs codebloom-db`.text(); + logs + .split("\n") + .filter((s) => s.length > 0) + .forEach((line) => console.log(brightMagenta(line))); + console.log(brightMagenta("=== DB LOGS END ===")); + await $`docker stop codebloom-db`.quiet().nothrow(); await $`docker rm codebloom-db`.quiet().nothrow(); diff --git a/.github/scripts/load-secrets.sh b/.github/scripts/load-secrets.sh deleted file mode 100644 index 860495abf..000000000 --- a/.github/scripts/load-secrets.sh +++ /dev/null @@ -1,67 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -git-crypt unlock - -# UNLOAD_ENVIRONMENTS="prod,staging,dev" -IFS=',' read -ra ENVS <<<"${UNLOAD_ENVIRONMENTS:-}" - -declare -A LOADED - -for v in "${ENVS[@]}"; do - ENV_FILE=".env.${v}" - if [[ -f "$ENV_FILE" ]]; then - echo "Loading $ENV_FILE" - declare -A BEFORE - for VAR in $(compgen -v); do - BEFORE["$VAR"]=1 - done - - source "$ENV_FILE" - - for VAR in $(compgen -v); do - if [[ -z "${BEFORE["$VAR"]:-}" ]]; then - LOADED["$VAR"]=1 - fi - done - else - echo "Warning: $ENV_FILE not found" - fi -done - -EXCLUDED_VARS=( - "PATH" - "HOME" - "PWD" - "SHELL" - "USER" - "DEBUG" - "LOG_LEVEL" - "CI" - "JAVA_HOME" -) - -for VAR in "${!LOADED[@]}"; do - VALUE="${!VAR-}" - - if [[ "$VAR" == "VAR" ]]; then # weird bug - continue - fi - - echo "$VAR=$VALUE" >>"$GITHUB_ENV" - - for EX in "${EXCLUDED_VARS[@]}"; do - if [[ "$VAR" == "$EX" ]]; then - echo "Not masking $VAR: Excluded" - continue 2 - fi - done - - if [[ "$VALUE" == "true" || "$VALUE" == "false" || -z "$VALUE" ]]; then - echo "Not masking $VAR: true/false/empty value" - continue - fi - - echo "Masking $VAR" - echo "::add-mask::$VALUE" -done diff --git a/.github/scripts/load-secrets.ts b/.github/scripts/load-secrets.ts new file mode 100644 index 000000000..3da1aba85 --- /dev/null +++ b/.github/scripts/load-secrets.ts @@ -0,0 +1,96 @@ +import { $ } from "bun"; + +// UNLOAD_ENVIRONMENTS="prod,staging,dev" +const unloadEnvironments = process.env.UNLOAD_ENVIRONMENTS || ""; + +const excludedVars = [ + "PATH", + "HOME", + "PWD", + "SHELL", + "USER", + "DEBUG", + "LOG_LEVEL", + "CI", + "JAVA_HOME", +]; + +async function main() { + await $`git-crypt unlock`; + + const envs = unloadEnvironments + .split(",") + .map((e) => e.trim()) + .filter(Boolean); + + const loaded = new Map(); + + for (const env of envs) { + const envFile = Bun.file(`.env.${env}`); + if (await envFile.exists()) { + console.log(`Loading ${envFile.name}`); + + const content = await envFile.text(); + const lines = content.split("\n").filter((s) => s.length > 0); + + for (const line of lines) { + const trimmed = line.trim(); + if (!trimmed || trimmed.startsWith("#")) continue; + + const match = trimmed.split("=").filter((s) => s.length > 0); + if (match.length === 2) { + const [key, value] = match; + const cleanKey = key.trim(); + let cleanValue = value.trim(); + if ( + (cleanValue.startsWith('"') && cleanValue.endsWith('"')) || + (cleanValue.startsWith("'") && cleanValue.endsWith("'")) + ) { + cleanValue = cleanValue.slice(1, -1); + } + + if (!loaded.has(cleanKey)) { + loaded.set(cleanKey, cleanValue); + } + } + } + } else { + console.log(`Warning: ${envFile} not found`); + } + } + + const githubEnv = process.env.GITHUB_ENV; + if (!githubEnv) { + console.log("Warning: GITHUB_ENV not set, skipping variable export"); + return; + } + + const githubEnvFileWriter = Bun.file(githubEnv).writer(); + + for (const [varName, value] of loaded.entries()) { + githubEnvFileWriter.write(`${varName}=${value}\n`); + if (excludedVars.includes(varName)) { + console.log(`Not masking ${varName}: Excluded`); + continue; + } + + if (value === "true" || value === "false" || value === "") { + console.log(`Not masking ${varName}: true/false/empty value`); + continue; + } + + console.log(`Masking ${varName}`); + console.log(`::add-mask::${value}`); + } + + githubEnvFileWriter.flush(); +} + +main() + .then(() => { + process.exit(0); + }) + .catch((e) => { + console.error(e); + process.exit(1); + }); diff --git a/.github/scripts/run-frontend-tests.sh b/.github/scripts/run-frontend-tests.sh deleted file mode 100644 index 84723f160..000000000 --- a/.github/scripts/run-frontend-tests.sh +++ /dev/null @@ -1,15 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -source "$DIR/local-db.sh" -source "$DIR/run-backend-instance.sh" -trap 'backend_cleanup; db_cleanup' EXIT - -db_startup -backend_startup - -corepack enable pnpm -pnpm --dir js i --frozen-lockfile -pnpm --dir js run generate -pnpm --dir js run test diff --git a/.github/scripts/run-frontend-tests.ts b/.github/scripts/run-frontend-tests.ts new file mode 100644 index 000000000..fee5175f4 --- /dev/null +++ b/.github/scripts/run-frontend-tests.ts @@ -0,0 +1,27 @@ +import { $ } from "bun"; +import { db } from "./fn/run-local-db"; +import { backend } from "./fn/run-backend-instance"; + +async function main() { + try { + await db.start(); + await backend.start(); + + await $`corepack enable pnpm`; + await $`pnpm --dir js i --frozen-lockfile`; + await $`pnpm --dir js run generate`; + await $`pnpm --dir js run test`; + } finally { + await backend.end(); + await db.end(); + } +} + +main() + .then(() => { + process.exit(0); + }) + .catch((e) => { + console.error(e); + process.exit(1); + }); diff --git a/.github/workflows/ai-review.yml b/.github/workflows/ai-review.yml index 800956041..06bf9e4b3 100644 --- a/.github/workflows/ai-review.yml +++ b/.github/workflows/ai-review.yml @@ -103,6 +103,9 @@ jobs: pr_reviewer.enable_review_labels_effort: "false" pr_reviewer.final_update_message: "false" pr_description.publish_description_as_comment: "true" + pr_description.extra_instructions: | + Below is some additional context for the PR from the connected Notion task: + ${{ steps.notion_check.outputs.context }} pr_description.publish_description_as_comment_persistent: "false" pr_description.publish_labels: "false" pr_description.add_original_user_description: "false" @@ -112,8 +115,23 @@ jobs: pr_code_suggestions.enable_help_text: "false" pr_code_suggestions.enable_chat_text: "false" pr_code_suggestions.persistent_comment: "false" - pr_code_suggestions.max_history_len: "4" + pr_code_suggestions.max_history_len: "10" pr_code_suggestions.publish_output_no_suggestions: "true" + pr_code_suggestions.extra_instructions: | + Scrutinize this PR in the context of the following Notion task: + ${{ steps.notion_check.outputs.context }} + + Additional review guidelines: + - Verify that the implementation matches the acceptance criteria from the Notion task + - Check for proper error handling and edge cases + - Ensure code follows existing patterns and architecture in the codebase + - Look for potential security vulnerabilities or data validation issues + - Verify that database migrations (if any) are reversible and safe + - Check for proper logging and monitoring considerations + - Ensure API endpoints follow RESTful conventions and existing API patterns + - Verify that environment variables and secrets are handled securely + - Check for potential performance bottlenecks or N+1 query issues + - Ensure proper test coverage for critical paths pr_code_suggestions.apply_suggestions_checkbox: "true" pr_code_suggestions.suggestions_score_threshold: "0" pr_code_suggestions.new_score_mechanism: "true" diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml index 2f86294b3..1b54eca91 100644 --- a/.github/workflows/ci-cd.yml +++ b/.github/workflows/ci-cd.yml @@ -68,7 +68,6 @@ jobs: uses: actions/cache@v4 with: path: ~/.bun/install/cache - # Hash your lockfile to invalidate the cache when dependencies change key: ${{ runner.os }}-bun-${{ hashFiles('**/pnpm-lock.yaml') }} restore-keys: | ${{ runner.os }}-bun- @@ -133,8 +132,25 @@ jobs: GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} UNLOAD_ENVIRONMENTS: ci-app + - uses: oven-sh/setup-bun@v2 + with: + bun-version: latest + + - name: Cache Bun dependencies + uses: actions/cache@v4 + with: + path: ~/.bun/install/cache + key: ${{ runner.os }}-bun-${{ hashFiles('**/pnpm-lock.yaml') }} + restore-keys: | + ${{ runner.os }}-bun- + + - name: Install deps + shell: bash + run: bun install --cwd .github/scripts --frozen-lockfile + - name: Run script - run: bash .github/scripts/run-frontend-tests.sh + shell: bash + run: bun .github/scripts/run-frontend-tests.ts testBuildImage: name: Build Test Docker Image From a6819a7d93ce1cad7be018c03fc908db9e9cd8d5 Mon Sep 17 00:00:00 2001 From: Tahmid Ahmed Date: Fri, 16 Jan 2026 01:16:38 -0500 Subject: [PATCH 004/101] 641: blee bloo bleeeeeeee --- .../test/backend-pre-test/action.yml | 3 +- .../composite/test/backend-test/action.yml | 53 ++++++++++ .../composite/test/frontend-test/action.yml | 58 +++++++++++ .github/workflows/ci-cd.yml | 98 +------------------ 4 files changed, 114 insertions(+), 98 deletions(-) create mode 100644 .github/composite/test/backend-test/action.yml create mode 100644 .github/composite/test/frontend-test/action.yml diff --git a/.github/composite/test/backend-pre-test/action.yml b/.github/composite/test/backend-pre-test/action.yml index 61d2494bf..2cfa4c0b6 100644 --- a/.github/composite/test/backend-pre-test/action.yml +++ b/.github/composite/test/backend-pre-test/action.yml @@ -28,8 +28,7 @@ runs: uses: actions/cache@v4 with: path: ~/.bun/install/cache - # Hash your lockfile to invalidate the cache when dependencies change - key: ${{ runner.os }}-bun-${{ hashFiles('**/pnpm-lock.yaml') }} + key: ${{ runner.os }}-bun-${{ hashFiles('.github/scripts/bun.lock') }} restore-keys: | ${{ runner.os }}-bun- diff --git a/.github/composite/test/backend-test/action.yml b/.github/composite/test/backend-test/action.yml new file mode 100644 index 000000000..0e0a3bfc9 --- /dev/null +++ b/.github/composite/test/backend-test/action.yml @@ -0,0 +1,53 @@ +name: "Backend test" +description: "Run backend tests" +runs: + using: "composite" + steps: + - name: Disable man-db + uses: ./.github/composite/disable-mandb + + - name: Set up OpenJDK 25 + uses: actions/setup-java@v4 + with: + distribution: "temurin" + java-version: "25" + cache: "maven" + + - name: Set up bun + uses: oven-sh/setup-bun@v2 + with: + bun-version: latest + + - name: Cache Bun dependencies + uses: actions/cache@v4 + with: + path: ~/.bun/install/cache + key: ${{ runner.os }}-bun-${{ hashFiles('.github/scripts/bun.lock') }} + restore-keys: | + ${{ runner.os }}-bun- + + - name: Install deps + shell: bash + run: bun install --cwd .github/scripts --frozen-lockfile + + - name: Load secrets + uses: ./.github/composite/load-secrets + with: + GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }} + GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} + UNLOAD_ENVIRONMENTS: ci-app + + - name: Run script + shell: bash + run: bun .github/scripts/run-backend-tests.ts + + - name: Upload JaCoCo HTML report + uses: actions/upload-artifact@v4 + with: + name: jacoco-report + path: target/site/jacoco/ + + - name: Upload coverage reports to Codecov + uses: codecov/codecov-action@v5 + with: + token: ${{ secrets.CODECOV_TOKEN }} diff --git a/.github/composite/test/frontend-test/action.yml b/.github/composite/test/frontend-test/action.yml new file mode 100644 index 000000000..18367b177 --- /dev/null +++ b/.github/composite/test/frontend-test/action.yml @@ -0,0 +1,58 @@ +name: "Frontend Test" +description: "Run frontend tests" + +inputs: + GPG_PRIVATE_KEY: + description: "GPG Private Key" + required: true + GPG_PASSPHRASE: + description: "GPG Passphrase" + required: true + +runs: + using: "composite" + steps: + - name: Disable man-db + uses: ./.github/composite/disable-mandb + + - name: Set up pnpm + uses: pnpm/action-setup@v4 + with: + version: 10 + cache: true + cache_dependency_path: js/pnpm-lock.yaml + + - name: Set up OpenJDK 25 + uses: actions/setup-java@v4 + with: + distribution: "temurin" + java-version: "25" + cache: "maven" + + - name: Set up bun + uses: oven-sh/setup-bun@v2 + with: + bun-version: latest + + - name: Cache Bun dependencies + uses: actions/cache@v4 + with: + path: ~/.bun/install/cache + key: ${{ runner.os }}-bun-${{ hashFiles('.github/scripts/bun.lock') }} + restore-keys: | + ${{ runner.os }}-bun- + + - name: Install deps + shell: bash + run: bun install --cwd .github/scripts --frozen-lockfile + + - name: Load secrets + uses: ./.github/composite/load-secrets + with: + GPG_PRIVATE_KEY: ${{ inputs.GPG_PRIVATE_KEY }} + GPG_PASSPHRASE: ${{ inputs.GPG_PASSPHRASE }} + UNLOAD_ENVIRONMENTS: ci-app + + - name: Run script + shell: bash + run: bun .github/scripts/run-frontend-tests.ts diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml index 1b54eca91..13e897cc6 100644 --- a/.github/workflows/ci-cd.yml +++ b/.github/workflows/ci-cd.yml @@ -37,59 +37,11 @@ jobs: - name: Checkout Repository uses: actions/checkout@v4 - - name: Disable man-db - uses: ./.github/composite/disable-mandb - - - name: Set up OpenJDK 25 - uses: actions/setup-java@v4 - with: - distribution: "temurin" - java-version: "25" - cache: "maven" - - - name: Verify Java version - run: | - java -version - javac -version - echo "JAVA_HOME=$JAVA_HOME" - - name: Load secrets - uses: ./.github/composite/load-secrets + uses: ./.github/composite/test/backend-test with: GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }} GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} - UNLOAD_ENVIRONMENTS: ci,ci-app - - - uses: oven-sh/setup-bun@v2 - with: - bun-version: latest - - - name: Cache Bun dependencies - uses: actions/cache@v4 - with: - path: ~/.bun/install/cache - key: ${{ runner.os }}-bun-${{ hashFiles('**/pnpm-lock.yaml') }} - restore-keys: | - ${{ runner.os }}-bun- - - - name: Install deps - shell: bash - run: bun install --cwd .github/scripts --frozen-lockfile - - - name: Run script - shell: bash - run: bun .github/scripts/run-backend-tests.ts - - - name: Upload JaCoCo HTML report - uses: actions/upload-artifact@v4 - with: - name: jacoco-report - path: target/site/jacoco/ - - - name: Upload coverage reports to Codecov - uses: codecov/codecov-action@v5 - with: - token: ${{ secrets.CODECOV_TOKEN }} frontendTests: name: Frontend Tests @@ -100,57 +52,11 @@ jobs: - name: Checkout Repository uses: actions/checkout@v4 - - name: Disable man-db - uses: ./.github/composite/disable-mandb - - - name: Set up Node.js - uses: actions/setup-node@v4 - with: - node-version: 20 - - - name: Set up OpenJDK 25 - uses: actions/setup-java@v4 - with: - distribution: "temurin" - java-version: "25" - cache: "maven" - - - name: Verify Java version - run: | - java -version - javac -version - echo "JAVA_HOME=$JAVA_HOME" - - - name: Fix a bug with corepack by installing corepack globally - run: npm i -g corepack@latest - working-directory: js - - name: Load secrets - uses: ./.github/composite/load-secrets + uses: ./.github/composite/test/frontend-test with: GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }} GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} - UNLOAD_ENVIRONMENTS: ci-app - - - uses: oven-sh/setup-bun@v2 - with: - bun-version: latest - - - name: Cache Bun dependencies - uses: actions/cache@v4 - with: - path: ~/.bun/install/cache - key: ${{ runner.os }}-bun-${{ hashFiles('**/pnpm-lock.yaml') }} - restore-keys: | - ${{ runner.os }}-bun- - - - name: Install deps - shell: bash - run: bun install --cwd .github/scripts --frozen-lockfile - - - name: Run script - shell: bash - run: bun .github/scripts/run-frontend-tests.ts testBuildImage: name: Build Test Docker Image From 07f39cfe677139a3844acb73de8e38aa917df39e Mon Sep 17 00:00:00 2001 From: Tahmid Ahmed Date: Fri, 16 Jan 2026 01:21:08 -0500 Subject: [PATCH 005/101] 641: blee bloo bleeeeeeee wefklfkenfwekjnfg --- .github/composite/test/frontend-test/action.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/composite/test/frontend-test/action.yml b/.github/composite/test/frontend-test/action.yml index 18367b177..5ba7a2574 100644 --- a/.github/composite/test/frontend-test/action.yml +++ b/.github/composite/test/frontend-test/action.yml @@ -16,11 +16,12 @@ runs: uses: ./.github/composite/disable-mandb - name: Set up pnpm - uses: pnpm/action-setup@v4 + uses: pnpm/action-setup@master with: version: 10 cache: true cache_dependency_path: js/pnpm-lock.yaml + package_json_file: js/package.json - name: Set up OpenJDK 25 uses: actions/setup-java@v4 From 9e2b54a1ae433bad1a0e05c32431653be4089ea5 Mon Sep 17 00:00:00 2001 From: Tahmid Ahmed Date: Fri, 16 Jan 2026 01:23:29 -0500 Subject: [PATCH 006/101] 641: blee --- .github/composite/test/frontend-test/action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/composite/test/frontend-test/action.yml b/.github/composite/test/frontend-test/action.yml index 5ba7a2574..b2a1bb37d 100644 --- a/.github/composite/test/frontend-test/action.yml +++ b/.github/composite/test/frontend-test/action.yml @@ -18,7 +18,7 @@ runs: - name: Set up pnpm uses: pnpm/action-setup@master with: - version: 10 + version: 10.24.0 cache: true cache_dependency_path: js/pnpm-lock.yaml package_json_file: js/package.json From 621a17710b21e58a9c216248126bffd0c3c13cbd Mon Sep 17 00:00:00 2001 From: Tahmid Ahmed Date: Fri, 16 Jan 2026 01:35:10 -0500 Subject: [PATCH 007/101] 641: lsdfnsdgvlndefblnerb --- .github/composite/test/backend-test/action.yml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.github/composite/test/backend-test/action.yml b/.github/composite/test/backend-test/action.yml index 0e0a3bfc9..a507b5c25 100644 --- a/.github/composite/test/backend-test/action.yml +++ b/.github/composite/test/backend-test/action.yml @@ -1,5 +1,14 @@ name: "Backend test" description: "Run backend tests" + +inputs: + GPG_PRIVATE_KEY: + description: "GPG Private Key" + required: true + GPG_PASSPHRASE: + description: "GPG Passphrase" + required: true + runs: using: "composite" steps: From ca9932c6cb57d32d14181c86ad41af2219d7908b Mon Sep 17 00:00:00 2001 From: Tahmid Ahmed Date: Fri, 16 Jan 2026 02:19:58 -0500 Subject: [PATCH 008/101] 641: neenurrr --- .github/composite/build-image/action.yml | 77 +++++++++++++++ .../composite/test/backend-test/action.yml | 6 ++ .github/scripts/build-image.ts | 95 +++++++++++++++++++ .github/workflows/ci-cd.yml | 61 ++---------- 4 files changed, 186 insertions(+), 53 deletions(-) create mode 100644 .github/composite/build-image/action.yml create mode 100644 .github/scripts/build-image.ts diff --git a/.github/composite/build-image/action.yml b/.github/composite/build-image/action.yml new file mode 100644 index 000000000..cf82223c3 --- /dev/null +++ b/.github/composite/build-image/action.yml @@ -0,0 +1,77 @@ +name: "Build & Upload Docker Image" +description: "Build & (optionally) upload Docker Image to Docker Registry" + +inputs: + GPG_PRIVATE_KEY: + description: "GPG Private Key" + required: true + GPG_PASSPHRASE: + description: "GPG Passphrase" + required: true + DOCKER_UPLOAD: + description: "Boolean indicating whether the image should be uploaded to Docker registry or not." + required: false + default: true + TAG_PREFIX: + description: "Docker tags prefix" + required: false + SERVER_PROFILES: + description: "Profile(s) to apply to Codebloom instance." + required: false + default: prod + +runs: + using: "composite" + steps: + - name: Disable man-db + uses: ./.github/composite/disable-mandb + + - name: Set up pnpm + uses: pnpm/action-setup@master + with: + version: 10.24.0 + cache: true + cache_dependency_path: js/pnpm-lock.yaml + package_json_file: js/package.json + + - name: Set up OpenJDK 25 + uses: actions/setup-java@v4 + with: + distribution: "temurin" + java-version: "25" + cache: "maven" + + - name: Set up bun + uses: oven-sh/setup-bun@v2 + with: + bun-version: latest + + - name: Cache Bun dependencies + uses: actions/cache@v4 + with: + path: ~/.bun/install/cache + key: ${{ runner.os }}-bun-${{ hashFiles('.github/scripts/bun.lock') }} + restore-keys: | + ${{ runner.os }}-bun- + + - name: Install deps + shell: bash + run: bun install --cwd .github/scripts --frozen-lockfile + + - name: Load secrets + uses: ./.github/composite/load-secrets + with: + GPG_PRIVATE_KEY: ${{ inputs.GPG_PRIVATE_KEY }} + GPG_PASSPHRASE: ${{ inputs.GPG_PASSPHRASE }} + UNLOAD_ENVIRONMENTS: ci,ci-app + + - name: Expose GitHub Runtime + uses: crazy-max/ghaction-github-runtime@v3 + + - name: Run script + shell: bash + run: bun .github/scripts/build-image.ts + env: + DOCKER_UPLOAD: ${{ inputs.DOCKER_UPLOAD }} + TAG_PREFIX: ${{ inputs.TAG_PREFIX }} + SERVER_PROFILES: ${{ inputs.SERVER_PROFILES }} diff --git a/.github/composite/test/backend-test/action.yml b/.github/composite/test/backend-test/action.yml index a507b5c25..a240ac154 100644 --- a/.github/composite/test/backend-test/action.yml +++ b/.github/composite/test/backend-test/action.yml @@ -8,6 +8,10 @@ inputs: GPG_PASSPHRASE: description: "GPG Passphrase" required: true + UPLOAD_TEST_COV: + description: "Boolean indicating whether tests should be uploaded to Codecov or not." + required: false + default: true runs: using: "composite" @@ -52,11 +56,13 @@ runs: - name: Upload JaCoCo HTML report uses: actions/upload-artifact@v4 + if: ${{ inputs.UPLOAD_TEST_COV == true }} with: name: jacoco-report path: target/site/jacoco/ - name: Upload coverage reports to Codecov uses: codecov/codecov-action@v5 + if: ${{ inputs.UPLOAD_TEST_COV == true }} with: token: ${{ secrets.CODECOV_TOKEN }} diff --git a/.github/scripts/build-image.ts b/.github/scripts/build-image.ts new file mode 100644 index 000000000..742f093bd --- /dev/null +++ b/.github/scripts/build-image.ts @@ -0,0 +1,95 @@ +import { $ } from "bun"; +import { db } from "./fn/run-local-db"; +import { backend } from "./fn/run-backend-instance"; + +process.env.TZ = "America/New_York"; + +const tagPrefix = process.env.TAG_PREFIX || ""; +const shouldDockerUpload = Boolean(process.env.DOCKER_UPLOAD) || false; +const serverProfiles = process.env.SERVER_PROFILES || "prod"; + +const dockerHubPat = process.env.DOCKER_HUB_PAT; +if (!dockerHubPat) { + throw new Error("DOCKER_HUB_PAT is required."); +} + +async function main() { + try { + await db.start(); + await backend.start(); + + await $`corepack enable pnpm`; + await $`pnpm --dir js i -D --frozen-lockfile`; + await $`pnpm --dir js run generate`; + + // copy old tz format from build-image.sh + const timestamp = new Date() + .toLocaleString("en-US", { + timeZone: process.env.TZ, + year: "numeric", + month: "2-digit", + day: "2-digit", + hour: "2-digit", + minute: "2-digit", + second: "2-digit", + hour12: false, + }) + .replace(/(\d+)\/(\d+)\/(\d+),\s(\d+):(\d+):(\d+)/, "$3.$1.$2-$4.$5.$6"); + + const gitSha = (await $`git rev-parse --short HEAD`.text()).trim(); + + const tags = [ + `tahminator/codebloom:${tagPrefix}latest`, + `tahminator/codebloom:${tagPrefix}${timestamp}`, + `tahminator/codebloom:${tagPrefix}${gitSha}`, + ]; + + console.log("Building image with following tags:"); + tags.forEach((tag) => console.log(tag)); + + if (dockerHubPat) { + console.log("DOCKER_HUB_PAT found"); + } else { + console.log("DOCKER_HUB_PAT missing or empty"); + } + + await $`echo ${dockerHubPat} | docker login -u tahminator --password-stdin`; + + try { + await $`docker buildx create --use --name codebloom-builder`; + } catch { + await $`docker buildx use codebloom-builder`; + } + + const buildMode = shouldDockerUpload ? "--push" : "--load"; + + const viteStagingArg = + serverProfiles === "stg" ? "--build-arg VITE_STAGING=true" : ""; + + const tagArgs = tags.map((tag) => `--tag ${tag}`).join(" "); + + await $`docker buildx build ${buildMode} \ + --file infra/Dockerfile \ + --build-arg SERVER_PROFILES=${serverProfiles} \ + --build-arg COMMIT_SHA=${gitSha} \ + --cache-from=type=gha \ + --cache-to=type=gha,mode=max \ + ${viteStagingArg} \ + ${tagArgs} \ + .`.quiet(); + + console.log("Image pushed successfully."); + } finally { + await backend.end(); + await db.end(); + } +} + +main() + .then(() => { + process.exit(0); + }) + .catch((e) => { + console.error(e); + process.exit(1); + }); diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml index 13e897cc6..44e73da23 100644 --- a/.github/workflows/ci-cd.yml +++ b/.github/workflows/ci-cd.yml @@ -25,7 +25,7 @@ jobs: - name: Checkout Repository uses: actions/checkout@v4 - - name: Run backend pre test + - name: Run workflow uses: ./.github/composite/test/backend-pre-test backendTests: @@ -37,7 +37,7 @@ jobs: - name: Checkout Repository uses: actions/checkout@v4 - - name: Load secrets + - name: Run workflow uses: ./.github/composite/test/backend-test with: GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }} @@ -52,7 +52,7 @@ jobs: - name: Checkout Repository uses: actions/checkout@v4 - - name: Load secrets + - name: Run workflow uses: ./.github/composite/test/frontend-test with: GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }} @@ -69,35 +69,12 @@ jobs: - name: Checkout repository uses: actions/checkout@v4 - - name: Disable man-db - uses: ./.github/composite/disable-mandb - - - name: Set up OpenJDK 25 - uses: actions/setup-java@v4 - with: - distribution: "temurin" - java-version: "25" - cache: "maven" - - - name: Verify Java version - run: | - java -version - javac -version - echo "JAVA_HOME=$JAVA_HOME" - - - name: Expose GitHub Runtime - uses: crazy-max/ghaction-github-runtime@v3 - - - name: Load secrets - uses: ./.github/composite/load-secrets + - name: Run workflow + uses: ./.github/composite/build-image with: GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }} GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} - UNLOAD_ENVIRONMENTS: ci,ci-app - - - name: Run script - run: bash .github/scripts/build-image.sh - env: + TAG_PREFIX: test- DOCKER_UPLOAD: false validateDBSchema: @@ -146,34 +123,12 @@ jobs: - name: Checkout repository uses: actions/checkout@v4 - - name: Disable man-db - uses: ./.github/composite/disable-mandb - - - name: Set up OpenJDK 25 - uses: actions/setup-java@v4 - with: - distribution: "temurin" - java-version: "25" - cache: "maven" - - - name: Verify Java version - run: | - java -version - javac -version - echo "JAVA_HOME=$JAVA_HOME" - - - name: Load secrets - uses: ./.github/composite/load-secrets + - name: Run workflow + uses: ./.github/composite/build-image with: GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }} GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} - UNLOAD_ENVIRONMENTS: ci,ci-app - - - name: Expose GitHub Runtime - uses: crazy-max/ghaction-github-runtime@v3 - - name: Run script - run: bash .github/scripts/build-image.sh redeploy: name: Redeploy on DigitalOcean From d9dd8f04dafc382aec9e74405fe7ef97172e0d95 Mon Sep 17 00:00:00 2001 From: Tahmid Ahmed Date: Fri, 16 Jan 2026 02:26:53 -0500 Subject: [PATCH 009/101] 641: attempt --- .github/scripts/build-image.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/scripts/build-image.ts b/.github/scripts/build-image.ts index 742f093bd..7918c768a 100644 --- a/.github/scripts/build-image.ts +++ b/.github/scripts/build-image.ts @@ -64,9 +64,9 @@ async function main() { const buildMode = shouldDockerUpload ? "--push" : "--load"; const viteStagingArg = - serverProfiles === "stg" ? "--build-arg VITE_STAGING=true" : ""; + serverProfiles === "stg" ? ["--build-arg", "VITE_STAGING=true"] : []; - const tagArgs = tags.map((tag) => `--tag ${tag}`).join(" "); + const tagArgs = tags.flatMap((tag) => ["--tag", tag]); await $`docker buildx build ${buildMode} \ --file infra/Dockerfile \ From cfa852ae56a76920d4712b4c682a468d5839a2c2 Mon Sep 17 00:00:00 2001 From: Tahmid Ahmed Date: Fri, 16 Jan 2026 02:38:11 -0500 Subject: [PATCH 010/101] 641: try to fix messed up leetcodeclienttest --- .../codebloom/scheduled/auth/LeetcodeAuthStealer.java | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/main/java/org/patinanetwork/codebloom/scheduled/auth/LeetcodeAuthStealer.java b/src/main/java/org/patinanetwork/codebloom/scheduled/auth/LeetcodeAuthStealer.java index 0a0178936..6ecb3a20b 100644 --- a/src/main/java/org/patinanetwork/codebloom/scheduled/auth/LeetcodeAuthStealer.java +++ b/src/main/java/org/patinanetwork/codebloom/scheduled/auth/LeetcodeAuthStealer.java @@ -163,7 +163,6 @@ public String getCsrf() { } String stealCookieImpl() { - LOCK.writeLock().lock(); try (Playwright playwright = Playwright.create(); Browser browser = playwright .firefox() @@ -273,10 +272,7 @@ String stealCookieImpl() { } else { log.info("Should be authenticated but not authenticated."); } - } finally { - LOCK.writeLock().unlock(); } - return null; } } From f21616f5bfbda7f411aabb66f3cd8038cd44f108 Mon Sep 17 00:00:00 2001 From: Tahmid Ahmed Date: Sun, 18 Jan 2026 12:40:30 -0500 Subject: [PATCH 011/101] 641: wip --- .do/specs.ts | 87 ++++++++++++ .github/composite/redeploy/action.yml | 86 ++++++++++++ .github/scripts/bun.lock | 123 +++++++++++++++++ .github/scripts/load-secrets.ts | 64 +++++---- .github/scripts/package.json | 1 + .github/scripts/redeploy.ts | 93 +++++++++++++ .github/workflows/deploy-stg.yml | 190 ++------------------------ 7 files changed, 442 insertions(+), 202 deletions(-) create mode 100644 .do/specs.ts create mode 100644 .github/composite/redeploy/action.yml create mode 100644 .github/scripts/redeploy.ts diff --git a/.do/specs.ts b/.do/specs.ts new file mode 100644 index 000000000..42e3ff92d --- /dev/null +++ b/.do/specs.ts @@ -0,0 +1,87 @@ +import type { + App_service_spec, + App_spec, + Apps_image_source_spec, + App_variable_definition, +} from "../.github/scripts/node_modules/@digitalocean/dots"; + +const DIGITALOCEAN_BASE_IMAGE: Apps_image_source_spec = { + registry: "tahminator", + registryType: "DOCKER_HUB", + repository: "codebloom", + // override tag + tag: "latest", +}; + +const DIGITALOCEAN_BASE_SERVICE: App_service_spec = { + name: "codebloom", + healthCheck: { + failureThreshold: 9, + httpPath: "/api", + periodSeconds: 10, + successThreshold: 1, + timeoutSeconds: 1, + }, + livenessHealthCheck: { + failureThreshold: 9, + httpPath: "/api", + periodSeconds: 10, + successThreshold: 1, + timeoutSeconds: 1, + }, + httpPort: 8080, + instanceCount: 1, + instanceSizeSlug: "apps-s-1vcpu-1gb-fixed", +}; + +const DIGITALOCEAN_BASE_SPEC: App_spec = { + region: "nyc", + ingress: { + rules: [ + { + component: { + name: "codebloom", + }, + match: { + path: { + prefix: "/", + }, + }, + }, + ], + }, +}; + +export function prodSpec(envs: App_variable_definition[]): App_spec { + return { + ...DIGITALOCEAN_BASE_SPEC, + name: "codebloom-prod", + services: [ + { + ...DIGITALOCEAN_BASE_SERVICE, + image: { + ...DIGITALOCEAN_BASE_IMAGE, + tag: "latest", + }, + envs, + }, + ], + }; +} + +export function stgSpec(envs: App_variable_definition[]): App_spec { + return { + ...DIGITALOCEAN_BASE_SPEC, + name: "codebloom-stg", + services: [ + { + ...DIGITALOCEAN_BASE_SERVICE, + image: { + ...DIGITALOCEAN_BASE_IMAGE, + tag: "staging-latest", + }, + envs, + }, + ], + }; +} diff --git a/.github/composite/redeploy/action.yml b/.github/composite/redeploy/action.yml new file mode 100644 index 000000000..2ddf855f9 --- /dev/null +++ b/.github/composite/redeploy/action.yml @@ -0,0 +1,86 @@ +name: "Re-Deploy to DigitalOcean" +description: "Trigger a deployment to DigitalOcean and migrate the associated database." + +inputs: + GPG_PRIVATE_KEY: + description: "GPG Private Key" + required: true + GPG_PASSPHRASE: + description: "GPG Passphrase" + required: true + ENVIRONMENT: + description: '"staging" or "production"' + required: false + default: production + +runs: + using: "composite" + steps: + - name: Disable man-db + uses: ./.github/composite/disable-mandb + + - name: Set up pnpm + uses: pnpm/action-setup@master + with: + version: 10.24.0 + cache: true + cache_dependency_path: js/pnpm-lock.yaml + package_json_file: js/package.json + + - name: Set up OpenJDK 25 + uses: actions/setup-java@v4 + with: + distribution: "temurin" + java-version: "25" + cache: "maven" + + - name: Set up bun + uses: oven-sh/setup-bun@v2 + with: + bun-version: latest + + - name: Cache Bun dependencies + uses: actions/cache@v4 + with: + path: ~/.bun/install/cache + key: ${{ runner.os }}-bun-${{ hashFiles('.github/scripts/bun.lock') }} + restore-keys: | + ${{ runner.os }}-bun- + + - name: Install deps + shell: bash + run: bun install --cwd .github/scripts --frozen-lockfile + + - name: Load secrets + uses: ./.github/composite/load-secrets + with: + GPG_PRIVATE_KEY: ${{ inputs.GPG_PRIVATE_KEY }} + GPG_PASSPHRASE: ${{ inputs.GPG_PASSPHRASE }} + UNLOAD_ENVIRONMENTS: ci,staging + + - name: Expose GitHub Runtime + uses: crazy-max/ghaction-github-runtime@v3 + + - name: Check for DB changes + id: db-changes + run: | + git fetch origin main:main + if git diff --name-only main...${{ needs.getPRHead.outputs.sha }} | grep -q '^db/'; then + echo "changed=true" >> $GITHUB_OUTPUT + echo "DB changes detected" + else + echo "changed=false" >> $GITHUB_OUTPUT + echo "No DB changes detected" + fi + + - name: Migrate Staging DB + if: steps.db-changes.outputs.changed == 'true' + run: ./mvnw flyway:migrate -Dflyway.locations=filesystem:./db/migration + + - name: Run script + shell: bash + run: bun .github/scripts/redeploy.ts + env: + DOCKER_UPLOAD: ${{ inputs.DOCKER_UPLOAD }} + TAG_PREFIX: ${{ inputs.TAG_PREFIX }} + SERVER_PROFILES: ${{ inputs.SERVER_PROFILES }} diff --git a/.github/scripts/bun.lock b/.github/scripts/bun.lock index 6b3eea5d0..f140bbe38 100644 --- a/.github/scripts/bun.lock +++ b/.github/scripts/bun.lock @@ -5,6 +5,7 @@ "": { "name": "scripts", "dependencies": { + "@digitalocean/dots": "^1.7.0", "bun": "^1.3.6", }, "devDependencies": { @@ -13,6 +14,38 @@ }, }, "packages": { + "@azure/abort-controller": ["@azure/abort-controller@1.1.0", "", { "dependencies": { "tslib": "^2.2.0" } }, "sha512-TrRLIoSQVzfAJX9H1JeFjzAoDGcoK1IYX1UImfceTZpsyYfWr09Ss1aHW1y5TrrR3iq6RZLBwJ3E24uwPhwahw=="], + + "@azure/core-auth": ["@azure/core-auth@1.10.1", "", { "dependencies": { "@azure/abort-controller": "^2.1.2", "@azure/core-util": "^1.13.0", "tslib": "^2.6.2" } }, "sha512-ykRMW8PjVAn+RS6ww5cmK9U2CyH9p4Q88YJwvUslfuMmN98w/2rdGRLPqJYObapBCdzBVeDgYWdJnFPFb7qzpg=="], + + "@azure/core-http": ["@azure/core-http@3.0.5", "", { "dependencies": { "@azure/abort-controller": "^1.0.0", "@azure/core-auth": "^1.3.0", "@azure/core-tracing": "1.0.0-preview.13", "@azure/core-util": "^1.1.1", "@azure/logger": "^1.0.0", "@types/node-fetch": "^2.5.0", "@types/tunnel": "^0.0.3", "form-data": "^4.0.0", "node-fetch": "^2.6.7", "process": "^0.11.10", "tslib": "^2.2.0", "tunnel": "^0.0.6", "uuid": "^8.3.0", "xml2js": "^0.5.0" } }, "sha512-T8r2q/c3DxNu6mEJfPuJtptUVqwchxzjj32gKcnMi06rdiVONS9rar7kT9T2Am+XvER7uOzpsP79WsqNbdgdWg=="], + + "@azure/core-tracing": ["@azure/core-tracing@1.0.0-preview.13", "", { "dependencies": { "@opentelemetry/api": "^1.0.1", "tslib": "^2.2.0" } }, "sha512-KxDlhXyMlh2Jhj2ykX6vNEU0Vou4nHr025KoSEiz7cS3BNiHNaZcdECk/DmLkEB0as5T7b/TpRcehJ5yV6NeXQ=="], + + "@azure/core-util": ["@azure/core-util@1.13.1", "", { "dependencies": { "@azure/abort-controller": "^2.1.2", "@typespec/ts-http-runtime": "^0.3.0", "tslib": "^2.6.2" } }, "sha512-XPArKLzsvl0Hf0CaGyKHUyVgF7oDnhKoP85Xv6M4StF/1AhfORhZudHtOyf2s+FcbuQ9dPRAjB8J2KvRRMUK2A=="], + + "@azure/logger": ["@azure/logger@1.3.0", "", { "dependencies": { "@typespec/ts-http-runtime": "^0.3.0", "tslib": "^2.6.2" } }, "sha512-fCqPIfOcLE+CGqGPd66c8bZpwAji98tZ4JI9i/mlTNTlsIWslCfpg48s/ypyLxZTump5sypjrKn2/kY7q8oAbA=="], + + "@digitalocean/dots": ["@digitalocean/dots@1.7.0", "", { "dependencies": { "@azure/core-http": "^3.0.5", "@microsoft/kiota-authentication-azure": "^1.0.0-preview.61", "@microsoft/kiota-bundle": "^1.0.0-preview.9", "@microsoft/kiota-http-fetchlibrary": "^1.0.0-preview.97", "dotenv": "^17.2.0", "uuid": "^11.1.0" } }, "sha512-4BD52gSlG7xdxMV7sWXTKi9zpl7dmyK+47WNVkLCTw0p5ktlX7VYhV8lDAuCq10fGF4w7LhCh/vOeuB13+x7aQ=="], + + "@microsoft/kiota-abstractions": ["@microsoft/kiota-abstractions@1.0.0-preview.99", "", { "dependencies": { "@opentelemetry/api": "^1.7.0", "@std-uritemplate/std-uritemplate": "^2.0.0", "tinyduration": "^3.3.0", "tslib": "^2.6.2" } }, "sha512-6qrlwGCO3DbvpA7tszqk7JNQPFPDg8gv5NGMTziwdtHqaQrUALKusm3vdJGPVGrbNiZL6/lBaY5NSUvLtlhRCg=="], + + "@microsoft/kiota-authentication-azure": ["@microsoft/kiota-authentication-azure@1.0.0-preview.99", "", { "dependencies": { "@azure/core-auth": "^1.5.0", "@microsoft/kiota-abstractions": "^1.0.0-preview.99", "@opentelemetry/api": "^1.7.0", "tslib": "^2.6.2" } }, "sha512-gPmk/vx15sBMfjfLPgqPanmyVR/rA5HGd3318VtPS28mXxH07Zn30R6PVqSemhIwSxfiGnPiq0SlTj28BQhTKQ=="], + + "@microsoft/kiota-bundle": ["@microsoft/kiota-bundle@1.0.0-preview.99", "", { "dependencies": { "@microsoft/kiota-abstractions": "^1.0.0-preview.99", "@microsoft/kiota-http-fetchlibrary": "^1.0.0-preview.99", "@microsoft/kiota-serialization-form": "^1.0.0-preview.99", "@microsoft/kiota-serialization-json": "^1.0.0-preview.99", "@microsoft/kiota-serialization-multipart": "^1.0.0-preview.99", "@microsoft/kiota-serialization-text": "^1.0.0-preview.99" } }, "sha512-AxvO+z6UgWMAT2NfXN36CMhAUm/39tUQt8o32axeEJDS/EvZINDAPstbQV+zK7bJRC8kCqUHhCE/fuHX2dXA+g=="], + + "@microsoft/kiota-http-fetchlibrary": ["@microsoft/kiota-http-fetchlibrary@1.0.0-preview.99", "", { "dependencies": { "@microsoft/kiota-abstractions": "^1.0.0-preview.99", "@opentelemetry/api": "^1.7.0", "tslib": "^2.6.2" } }, "sha512-lIruiYf8L7DPG0Fm92QN5YK4zx4sh76EtTONUAqBCxt5AtEJ7KQceBajaXQsjfcndUAp9LmDVdqR44o8sc9/mA=="], + + "@microsoft/kiota-serialization-form": ["@microsoft/kiota-serialization-form@1.0.0-preview.99", "", { "dependencies": { "@microsoft/kiota-abstractions": "^1.0.0-preview.99", "tslib": "^2.6.2" } }, "sha512-BdXxqNfy+5yWTyxpBguqJYy6E9RRotrDClRcUO89okFcR5N6s6xenjsvGw++q9KWNi7qcHrqaG8xlRz1TLk81Q=="], + + "@microsoft/kiota-serialization-json": ["@microsoft/kiota-serialization-json@1.0.0-preview.99", "", { "dependencies": { "@microsoft/kiota-abstractions": "^1.0.0-preview.99", "tslib": "^2.6.2" } }, "sha512-kDmMYmB7XkRprlLviEynRPDkE45JDxqiuUopfBRMvNK4qhzFiJTXGLihZ2FG6fdVu9LbV3/W0QxbeGP35kDK6A=="], + + "@microsoft/kiota-serialization-multipart": ["@microsoft/kiota-serialization-multipart@1.0.0-preview.99", "", { "dependencies": { "@microsoft/kiota-abstractions": "^1.0.0-preview.99", "tslib": "^2.6.2" } }, "sha512-cfviCAVTlZPD+MUgdTIgsX/IfHND+gKQhem3vJeo7l5Qqz2rvvVFXOHwECI19umO9Un1QFKe1V5wg+M+aj90+g=="], + + "@microsoft/kiota-serialization-text": ["@microsoft/kiota-serialization-text@1.0.0-preview.99", "", { "dependencies": { "@microsoft/kiota-abstractions": "^1.0.0-preview.99", "tslib": "^2.6.2" } }, "sha512-gcBffQRI1soHVAiS4YjfMr3SQ9fC8xVUGMfarTTszU4PjpxyFOknaWoFdt/0rvMeavI9jOtbyvDKAf77cZyYIQ=="], + + "@opentelemetry/api": ["@opentelemetry/api@1.9.0", "", {}, "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg=="], + "@oven/bun-darwin-aarch64": ["@oven/bun-darwin-aarch64@1.3.6", "", { "os": "darwin", "cpu": "arm64" }, "sha512-27rypIapNkYboOSylkf1tD9UW9Ado2I+P1NBL46Qz29KmOjTL6WuJ7mHDC5O66CYxlOkF5r93NPDAC3lFHYBXw=="], "@oven/bun-darwin-x64": ["@oven/bun-darwin-x64@1.3.6", "", { "os": "darwin", "cpu": "x64" }, "sha512-I82xGzPkBxzBKgbl8DsA0RfMQCWTWjNmLjIEkW1ECiv3qK02kHGQ5FGUr/29L/SuvnGsULW4tBTRNZiMzL37nA=="], @@ -35,14 +68,104 @@ "@oven/bun-windows-x64-baseline": ["@oven/bun-windows-x64-baseline@1.3.6", "", { "os": "win32", "cpu": "x64" }, "sha512-PFUa7JL4lGoyyppeS4zqfuoXXih+gSE0XxhDMrCPVEUev0yhGNd/tbWBvcdpYnUth80owENoGjc8s5Knopv9wA=="], + "@std-uritemplate/std-uritemplate": ["@std-uritemplate/std-uritemplate@2.0.8", "", {}, "sha512-8oj7jKksoTRxjdPkWKX9FyOKDS8ORBm+ZfTSHm0f3eYha8cI0O1d57gyduOIAX7Y2uUukNDNlxlvGb+FFkE7yQ=="], + "@types/bun": ["@types/bun@1.3.6", "", { "dependencies": { "bun-types": "1.3.6" } }, "sha512-uWCv6FO/8LcpREhenN1d1b6fcspAB+cefwD7uti8C8VffIv0Um08TKMn98FynpTiU38+y2dUO55T11NgDt8VAA=="], "@types/node": ["@types/node@25.0.9", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-/rpCXHlCWeqClNBwUhDcusJxXYDjZTyE8v5oTO7WbL8eij2nKhUeU89/6xgjU7N4/Vh3He0BtyhJdQbDyhiXAw=="], + "@types/node-fetch": ["@types/node-fetch@2.6.13", "", { "dependencies": { "@types/node": "*", "form-data": "^4.0.4" } }, "sha512-QGpRVpzSaUs30JBSGPjOg4Uveu384erbHBoT1zeONvyCfwQxIkUshLAOqN/k9EjGviPRmWTTe6aH2qySWKTVSw=="], + + "@types/tunnel": ["@types/tunnel@0.0.3", "", { "dependencies": { "@types/node": "*" } }, "sha512-sOUTGn6h1SfQ+gbgqC364jLFBw2lnFqkgF3q0WovEHRLMrVD1sd5aufqi/aJObLekJO+Aq5z646U4Oxy6shXMA=="], + + "@typespec/ts-http-runtime": ["@typespec/ts-http-runtime@0.3.2", "", { "dependencies": { "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.0", "tslib": "^2.6.2" } }, "sha512-IlqQ/Gv22xUC1r/WQm4StLkYQmaaTsXAhUVsNE0+xiyf0yRFiH5++q78U3bw6bLKDCTmh0uqKB9eG9+Bt75Dkg=="], + + "agent-base": ["agent-base@7.1.4", "", {}, "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ=="], + + "asynckit": ["asynckit@0.4.0", "", {}, "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="], + "bun": ["bun@1.3.6", "", { "optionalDependencies": { "@oven/bun-darwin-aarch64": "1.3.6", "@oven/bun-darwin-x64": "1.3.6", "@oven/bun-darwin-x64-baseline": "1.3.6", "@oven/bun-linux-aarch64": "1.3.6", "@oven/bun-linux-aarch64-musl": "1.3.6", "@oven/bun-linux-x64": "1.3.6", "@oven/bun-linux-x64-baseline": "1.3.6", "@oven/bun-linux-x64-musl": "1.3.6", "@oven/bun-linux-x64-musl-baseline": "1.3.6", "@oven/bun-windows-x64": "1.3.6", "@oven/bun-windows-x64-baseline": "1.3.6" }, "os": [ "linux", "win32", "darwin", ], "cpu": [ "x64", "arm64", ], "bin": { "bun": "bin/bun.exe", "bunx": "bin/bunx.exe" } }, "sha512-Tn98GlZVN2WM7+lg/uGn5DzUao37Yc0PUz7yzYHdeF5hd+SmHQGbCUIKE4Sspdgtxn49LunK3mDNBC2Qn6GJjw=="], "bun-types": ["bun-types@1.3.6", "", { "dependencies": { "@types/node": "*" } }, "sha512-OlFwHcnNV99r//9v5IIOgQ9Uk37gZqrNMCcqEaExdkVq3Avwqok1bJFmvGMCkCE0FqzdY8VMOZpfpR3lwI+CsQ=="], + "call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="], + + "combined-stream": ["combined-stream@1.0.8", "", { "dependencies": { "delayed-stream": "~1.0.0" } }, "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg=="], + + "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], + + "delayed-stream": ["delayed-stream@1.0.0", "", {}, "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="], + + "dotenv": ["dotenv@17.2.3", "", {}, "sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w=="], + + "dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="], + + "es-define-property": ["es-define-property@1.0.1", "", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="], + + "es-errors": ["es-errors@1.3.0", "", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="], + + "es-object-atoms": ["es-object-atoms@1.1.1", "", { "dependencies": { "es-errors": "^1.3.0" } }, "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA=="], + + "es-set-tostringtag": ["es-set-tostringtag@2.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA=="], + + "form-data": ["form-data@4.0.5", "", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", "hasown": "^2.0.2", "mime-types": "^2.1.12" } }, "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w=="], + + "function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="], + + "get-intrinsic": ["get-intrinsic@1.3.0", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="], + + "get-proto": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="], + + "gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="], + + "has-symbols": ["has-symbols@1.1.0", "", {}, "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="], + + "has-tostringtag": ["has-tostringtag@1.0.2", "", { "dependencies": { "has-symbols": "^1.0.3" } }, "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw=="], + + "hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="], + + "http-proxy-agent": ["http-proxy-agent@7.0.2", "", { "dependencies": { "agent-base": "^7.1.0", "debug": "^4.3.4" } }, "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig=="], + + "https-proxy-agent": ["https-proxy-agent@7.0.6", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "4" } }, "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw=="], + + "math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="], + + "mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="], + + "mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="], + + "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + + "node-fetch": ["node-fetch@2.7.0", "", { "dependencies": { "whatwg-url": "^5.0.0" }, "peerDependencies": { "encoding": "^0.1.0" }, "optionalPeers": ["encoding"] }, "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A=="], + + "process": ["process@0.11.10", "", {}, "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A=="], + + "sax": ["sax@1.4.4", "", {}, "sha512-1n3r/tGXO6b6VXMdFT54SHzT9ytu9yr7TaELowdYpMqY/Ao7EnlQGmAQ1+RatX7Tkkdm6hONI2owqNx2aZj5Sw=="], + + "tinyduration": ["tinyduration@3.4.1", "", {}, "sha512-NemFoamVYn7TmtwZKZ3OiliM9fZkr6EWiTM+wKknco6POSy2gS689xx/pXip0JYp40HXpUw6k65CUYHWYUXdaA=="], + + "tr46": ["tr46@0.0.3", "", {}, "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="], + + "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + + "tunnel": ["tunnel@0.0.6", "", {}, "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg=="], + "undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="], + + "uuid": ["uuid@11.1.0", "", { "bin": { "uuid": "dist/esm/bin/uuid" } }, "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A=="], + + "webidl-conversions": ["webidl-conversions@3.0.1", "", {}, "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="], + + "whatwg-url": ["whatwg-url@5.0.0", "", { "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" } }, "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw=="], + + "xml2js": ["xml2js@0.5.0", "", { "dependencies": { "sax": ">=0.6.0", "xmlbuilder": "~11.0.0" } }, "sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA=="], + + "xmlbuilder": ["xmlbuilder@11.0.1", "", {}, "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA=="], + + "@azure/core-auth/@azure/abort-controller": ["@azure/abort-controller@2.1.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-nBrLsEWm4J2u5LpAPjxADTlq3trDgVZZXHNKabeXZtpq3d3AbN/KGO82R87rdDz5/lYB024rtEf10/q0urNgsA=="], + + "@azure/core-http/uuid": ["uuid@8.3.2", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="], + + "@azure/core-util/@azure/abort-controller": ["@azure/abort-controller@2.1.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-nBrLsEWm4J2u5LpAPjxADTlq3trDgVZZXHNKabeXZtpq3d3AbN/KGO82R87rdDz5/lYB024rtEf10/q0urNgsA=="], } } diff --git a/.github/scripts/load-secrets.ts b/.github/scripts/load-secrets.ts index 3da1aba85..91db2481c 100644 --- a/.github/scripts/load-secrets.ts +++ b/.github/scripts/load-secrets.ts @@ -23,9 +23,42 @@ async function main() { .map((e) => e.trim()) .filter(Boolean); + const loaded = await getEnvVariables(envs, false); + + const githubEnv = process.env.GITHUB_ENV; + if (!githubEnv) { + console.log("Warning: GITHUB_ENV not set, skipping variable export"); + return; + } + + const githubEnvFileWriter = Bun.file(githubEnv).writer(); + + for (const [varName, value] of loaded.entries()) { + githubEnvFileWriter.write(`${varName}=${value}\n`); + if (excludedVars.includes(varName)) { + console.log(`Not masking ${varName}: Excluded`); + continue; + } + + if (value === "true" || value === "false" || value === "") { + console.log(`Not masking ${varName}: true/false/empty value`); + continue; + } + + console.log(`Masking ${varName}`); + console.log(`::add-mask::${value}`); + } + + githubEnvFileWriter.flush(); +} + +export async function getEnvVariables( + environments: string[], + mask = true, +): Promise> { const loaded = new Map(); - for (const env of envs) { + for (const env of environments) { const envFile = Bun.file(`.env.${env}`); if (await envFile.exists()) { console.log(`Loading ${envFile.name}`); @@ -39,7 +72,7 @@ async function main() { const match = trimmed.split("=").filter((s) => s.length > 0); if (match.length === 2) { - const [key, value] = match; + const [key, value] = match as [string, string]; const cleanKey = key.trim(); let cleanValue = value.trim(); if ( @@ -59,31 +92,14 @@ async function main() { } } - const githubEnv = process.env.GITHUB_ENV; - if (!githubEnv) { - console.log("Warning: GITHUB_ENV not set, skipping variable export"); - return; - } - - const githubEnvFileWriter = Bun.file(githubEnv).writer(); - - for (const [varName, value] of loaded.entries()) { - githubEnvFileWriter.write(`${varName}=${value}\n`); - if (excludedVars.includes(varName)) { - console.log(`Not masking ${varName}: Excluded`); - continue; - } - - if (value === "true" || value === "false" || value === "") { - console.log(`Not masking ${varName}: true/false/empty value`); - continue; + if (mask) { + for (const [varName, value] of loaded.entries()) { + console.log(`Masking ${varName}`); + console.log(`::add-mask::${value}`); } - - console.log(`Masking ${varName}`); - console.log(`::add-mask::${value}`); } - githubEnvFileWriter.flush(); + return loaded; } main() diff --git a/.github/scripts/package.json b/.github/scripts/package.json index c5c7a8a5a..73b5407fc 100644 --- a/.github/scripts/package.json +++ b/.github/scripts/package.json @@ -7,6 +7,7 @@ "author": "Tahmid Ahmed", "license": "MIT", "dependencies": { + "@digitalocean/dots": "^1.7.0", "bun": "^1.3.6" }, "devDependencies": { diff --git a/.github/scripts/redeploy.ts b/.github/scripts/redeploy.ts new file mode 100644 index 000000000..8bf756c47 --- /dev/null +++ b/.github/scripts/redeploy.ts @@ -0,0 +1,93 @@ +import { $ } from "bun"; +import { + createDigitalOceanClient, + DigitalOceanApiKeyAuthenticationProvider, + FetchRequestAdapter, + type App_response, + type App_variable_definition, +} from "@digitalocean/dots"; +import { prodSpec, stgSpec } from "../../.do/specs"; +import { getEnvVariables } from "./load-secrets"; + +const projectId = (() => { + const v = process.env.DIGITALOCEAN_PROJECT_ID; + if (!v) { + throw new Error("Project ID is required"); + } + return v; +})(); + +const token = (() => { + const v = process.env.DIGITALOCEAN_PAT; + if (!v) { + throw new Error("DigitalOcean PAT is required"); + } + return v; +})(); + +const environment: Environment = (() => { + const v = process.env.ENVIRONMENT; + if (!v) { + throw new Error("Environment is required"); + } + + if (v !== "staging" && v !== "production") { + throw new Error('Environment must be "staging" or "production"'); + } + + return v; +})(); + +type Environment = "staging" | "production"; + +async function main() { + const authProvider = new DigitalOceanApiKeyAuthenticationProvider(token); + const adapter = new FetchRequestAdapter(authProvider); + const client = createDigitalOceanClient(adapter); + + await $`git-crypt unlock`; + const loaded = await getEnvVariables([environment]); + + const envs: App_variable_definition[] = loaded + .entries() + .map(([key, value]) => { + const env: App_variable_definition = { + key, + value, + scope: "RUN_TIME", + type: "SECRET", + }; + return env; + }) + .toArray(); + + const spec = environment === "staging" ? stgSpec(envs) : prodSpec(envs); + + let res: App_response | undefined; + try { + res = await client.v2.apps.post({ + spec, + projectId, + }); + } catch (e) { + console.error(e); + return process.exit(1); + } + + console.log(res?.app?.projectId); + + await migrateDb(environment); +} + +async function migrateDb(environment: Environment) { + await $`git fetch origin main:main`; +} + +main() + .then(() => { + process.exit(0); + }) + .catch((e) => { + console.error(e); + process.exit(1); + }); diff --git a/.github/workflows/deploy-stg.yml b/.github/workflows/deploy-stg.yml index ebfd27479..5e3627d21 100644 --- a/.github/workflows/deploy-stg.yml +++ b/.github/workflows/deploy-stg.yml @@ -139,7 +139,7 @@ jobs: with: ref: ${{ needs.getPRHead.outputs.sha }} - - name: Run backend pre test + - name: Run workflow uses: ./.github/composite/test/backend-pre-test backendTests: @@ -154,36 +154,11 @@ jobs: with: ref: ${{ needs.getPRHead.outputs.sha }} - - name: Disable man-db - uses: ./.github/composite/disable-mandb - - - name: Set up Node.js - uses: actions/setup-node@v4 - with: - node-version: 20 - - - name: Set up OpenJDK 25 - uses: actions/setup-java@v4 - with: - distribution: "temurin" - java-version: "25" - cache: "maven" - - - name: Verify Java version - run: | - java -version - javac -version - echo "JAVA_HOME=$JAVA_HOME" - - - name: Load secrets - uses: ./.github/composite/load-secrets + - name: Run workflow + uses: ./.github/composite/test/backend-test with: GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }} GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} - UNLOAD_ENVIRONMENTS: ci-app - - - name: Run script - run: bash .github/scripts/run-backend-tests.sh frontendTests: name: Frontend Tests @@ -197,40 +172,11 @@ jobs: with: ref: ${{ needs.getPRHead.outputs.sha }} - - name: Disable man-db - uses: ./.github/composite/disable-mandb - - - name: Set up Node.js - uses: actions/setup-node@v4 - with: - node-version: 20 - - - name: Set up OpenJDK 25 - uses: actions/setup-java@v4 - with: - distribution: "temurin" - java-version: "25" - cache: "maven" - - - name: Verify Java version - run: | - java -version - javac -version - echo "JAVA_HOME=$JAVA_HOME" - - - name: Fix a bug with corepack by installing corepack globally - run: npm i -g corepack@latest - working-directory: js - - - name: Load secrets - uses: ./.github/composite/load-secrets + - name: Run workflow + uses: ./.github/composite/test/frontend-test with: GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }} GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} - UNLOAD_ENVIRONMENTS: ci-app - - - name: Run script - run: bash .github/scripts/run-frontend-tests.sh validateDBSchema: name: Validate DB Schema on Staging DB @@ -290,37 +236,13 @@ jobs: with: ref: ${{ needs.getPRHead.outputs.sha }} - - name: Disable man-db - uses: ./.github/composite/disable-mandb - - - name: Set up OpenJDK 25 - uses: actions/setup-java@v4 - with: - distribution: "temurin" - java-version: "25" - cache: "maven" - - - name: Verify Java version - run: | - java -version - javac -version - echo "JAVA_HOME=$JAVA_HOME" - - - name: Load secrets - uses: ./.github/composite/load-secrets + - name: Run workflow + uses: ./.github/composite/build-image with: GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }} GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} - UNLOAD_ENVIRONMENTS: ci,ci-app - - - name: Expose GitHub Runtime - uses: crazy-max/ghaction-github-runtime@v3 - - - name: Run script - run: bash .github/scripts/build-image.sh - env: - TAG_PREFIX: staging- - SERVER_PROFILES: stg + TAG_PREFIX: staging- + SERVER_PROFILES: stg redeploy: name: Redeploy on DigitalOcean @@ -334,100 +256,12 @@ jobs: with: ref: ${{ needs.getPRHead.outputs.sha }} - - name: Disable man-db - uses: ./.github/composite/disable-mandb - - - name: Set up OpenJDK 25 - uses: actions/setup-java@v4 - with: - distribution: "temurin" - java-version: "25" - cache: "maven" - - - name: Verify Java version - run: | - java -version - javac -version - echo "JAVA_HOME=$JAVA_HOME" - - - name: Load secrets - uses: ./.github/composite/load-secrets + - name: Run workflow + uses: ./.github/composite/build-image with: GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }} GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} - UNLOAD_ENVIRONMENTS: ci,staging - - - name: Install doctl - uses: digitalocean/action-doctl@v2 - with: - token: ${{ env.DIGITAL_OCEAN_PAT }} - - - name: Check for DB changes - id: db-changes - run: | - git fetch origin main:main - if git diff --name-only main...${{ needs.getPRHead.outputs.sha }} | grep -q '^db/'; then - echo "changed=true" >> $GITHUB_OUTPUT - echo "DB changes detected" - else - echo "changed=false" >> $GITHUB_OUTPUT - echo "No DB changes detected" - fi - - - name: Migrate Staging DB - if: steps.db-changes.outputs.changed == 'true' - run: ./mvnw flyway:migrate -Dflyway.locations=filesystem:./db/migration - - - name: Transform App Spec with secrets - run: bash .github/scripts/transform-yaml-with-env.sh .do/stg/app.yml .env.staging - - - name: Update App Spec on DigitalOcean - run: | - OUTPUT=$(doctl apps update ${{ env.DIGITAL_OCEAN_APP_ID }} --spec .do/stg/app.yml --update-sources --output json) - - DEPLOYMENT_ID=$(echo "$OUTPUT" | jq -r '.[0].pending_deployment.id // .[0].in_progress_deployment.id // .[0].active_deployment.id // empty' | head -n 1) - - if [ -z "$DEPLOYMENT_ID" ] || [ "$DEPLOYMENT_ID" == "null" ]; then - echo "Failed to extract deployment ID from app update response" - exit 1 - fi - - echo "DEPLOYMENT_ID=$DEPLOYMENT_ID" >> $GITHUB_ENV - - - name: Poll Deployment Status - run: | - - if [ -z "$DEPLOYMENT_ID" ]; then - echo "Deployment ID is empty." - exit 1 - fi - - echo "Waiting for deployment to be promoted." - - for i in {1..60}; do - RESPONSE=$(curl -s -X GET \ - -H "Content-Type: application/json" \ - -H "Authorization: Bearer ${{ env.DIGITAL_OCEAN_PAT }}" \ - "https://api.digitalocean.com/v2/apps/${{ env.DIGITAL_OCEAN_APP_ID }}/deployments/$DEPLOYMENT_ID") - - PHASE=$(echo "$RESPONSE" | jq -r '.deployment.phase') - - echo "Deployment phase: $PHASE" - - if [ "$PHASE" == "ACTIVE" ]; then - echo "Deployment is active." - exit 0 - elif [ "$PHASE" == "SUPERSEDED" ] || [ "$PHASE" == "ERROR" ] || [ "$PHASE" == "CANCELED" ]; then - echo "Deployment failed with phase: $PHASE" - exit 1 - fi - - echo "Waiting for deployment to complete, sleep 10... ($i/60)" - sleep 10 - done - - echo "Deployment did not reach a valid state within 10 minutes." - exit 1 + ENVIRONMENT: staging notifyStatus: name: Notify on deployment status From 88464c9d4198bbec68300f0c4cc1c40152120e5f Mon Sep 17 00:00:00 2001 From: Tahmid Ahmed Date: Sun, 18 Jan 2026 12:51:34 -0500 Subject: [PATCH 012/101] 641: remove testing from stg for now --- .github/workflows/deploy-stg.yml | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/workflows/deploy-stg.yml b/.github/workflows/deploy-stg.yml index 5e3627d21..fd6ddfc1a 100644 --- a/.github/workflows/deploy-stg.yml +++ b/.github/workflows/deploy-stg.yml @@ -154,11 +154,11 @@ jobs: with: ref: ${{ needs.getPRHead.outputs.sha }} - - name: Run workflow - uses: ./.github/composite/test/backend-test - with: - GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }} - GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} + # - name: Run workflow + # uses: ./.github/composite/test/backend-test + # with: + # GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }} + # GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} frontendTests: name: Frontend Tests @@ -172,11 +172,11 @@ jobs: with: ref: ${{ needs.getPRHead.outputs.sha }} - - name: Run workflow - uses: ./.github/composite/test/frontend-test - with: - GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }} - GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} + # - name: Run workflow + # uses: ./.github/composite/test/frontend-test + # with: + # GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }} + # GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} validateDBSchema: name: Validate DB Schema on Staging DB From 9519240876866e01e4a8738e33555d8af0562d9b Mon Sep 17 00:00:00 2001 From: Tahmid Ahmed Date: Sun, 18 Jan 2026 12:58:17 -0500 Subject: [PATCH 013/101] 641: fix --- .github/workflows/deploy-stg.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy-stg.yml b/.github/workflows/deploy-stg.yml index fd6ddfc1a..69a24499d 100644 --- a/.github/workflows/deploy-stg.yml +++ b/.github/workflows/deploy-stg.yml @@ -257,7 +257,7 @@ jobs: ref: ${{ needs.getPRHead.outputs.sha }} - name: Run workflow - uses: ./.github/composite/build-image + uses: ./.github/composite/redeploy with: GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }} GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} From 31884c42f77bdb04d51c18ee4fa52ea480f22491 Mon Sep 17 00:00:00 2001 From: Tahmid Ahmed Date: Sun, 18 Jan 2026 13:02:46 -0500 Subject: [PATCH 014/101] 641: DszTgNsN9sR5qtiD1kE2O8txI67JPdx/HBuu7V9/0trND/kMLqDe/L4TEShd+uX1 --- .github/scripts/build-image.sh | 60 ---------------------------------- .github/scripts/build-image.ts | 2 +- 2 files changed, 1 insertion(+), 61 deletions(-) delete mode 100644 .github/scripts/build-image.sh diff --git a/.github/scripts/build-image.sh b/.github/scripts/build-image.sh deleted file mode 100644 index cb99d7715..000000000 --- a/.github/scripts/build-image.sh +++ /dev/null @@ -1,60 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -source "$DIR/local-db.sh" -source "$DIR/run-backend-instance.sh" -trap 'backend_cleanup; db_cleanup' EXIT - -export TZ="America/New_York" - -db_startup -backend_startup - -corepack enable pnpm -pnpm --dir js i -D --frozen-lockfile -pnpm --dir js run generate - -TIMESTAMP="$(date +%Y.%m.%d-%H.%M.%S)" -GIT_SHA="$(git rev-parse --short HEAD)" -TAG_PREFIX="${TAG_PREFIX:-}" -TAGS=( - "tahminator/codebloom:${TAG_PREFIX}latest" - "tahminator/codebloom:${TAG_PREFIX}${TIMESTAMP}" - "tahminator/codebloom:${TAG_PREFIX}${GIT_SHA}" -) - -echo "Building image with tags:" -printf ' - %s\n' "${TAGS[@]}" - -[ -n "${DOCKER_HUB_PAT:-}" ] && echo "DOCKER_HUB_PAT found" || echo "DOCKER_HUB_PAT missing or empty" - -echo "${DOCKER_HUB_PAT}" | docker login -u "tahminator" --password-stdin - -docker buildx create --use --name codebloom-builder || docker buildx use codebloom-builder - -if [[ "${DOCKER_UPLOAD:-true}" == "true" ]]; then - BUILD_MODE="--push" -else - BUILD_MODE="--load" -fi -SERVER_PROFILES="${SERVER_PROFILES:-prod}" - -if [[ "$SERVER_PROFILES" == "stg" ]]; then - VITE_STAGING_ARG="--build-arg VITE_STAGING=true" -else - VITE_STAGING_ARG="" -fi - -docker buildx build \ - $BUILD_MODE \ - --file infra/Dockerfile \ - --build-arg SERVER_PROFILES="$SERVER_PROFILES" \ - --build-arg COMMIT_SHA="$GIT_SHA" \ - --cache-from=type=gha \ - --cache-to=type=gha,mode=max \ - $VITE_STAGING_ARG \ - $(printf -- '--tag %s ' "${TAGS[@]}") \ - . - -echo "Image pushed successfully." diff --git a/.github/scripts/build-image.ts b/.github/scripts/build-image.ts index 7918c768a..252ee6971 100644 --- a/.github/scripts/build-image.ts +++ b/.github/scripts/build-image.ts @@ -76,7 +76,7 @@ async function main() { --cache-to=type=gha,mode=max \ ${viteStagingArg} \ ${tagArgs} \ - .`.quiet(); + .`; console.log("Image pushed successfully."); } finally { From 0b57e58375dfddff057b0150cf5329b36c8f54b0 Mon Sep 17 00:00:00 2001 From: Tahmid Ahmed Date: Sun, 18 Jan 2026 13:10:19 -0500 Subject: [PATCH 015/101] 641: dbED/o8z4bDHFV8WlfXEEa2K7IFohTBOkPs3QP1dg9vtYHcZDNRiLuzDZ1vhznvf --- .github/composite/redeploy/action.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/composite/redeploy/action.yml b/.github/composite/redeploy/action.yml index 2ddf855f9..015d198c4 100644 --- a/.github/composite/redeploy/action.yml +++ b/.github/composite/redeploy/action.yml @@ -63,6 +63,7 @@ runs: - name: Check for DB changes id: db-changes + shell: bash run: | git fetch origin main:main if git diff --name-only main...${{ needs.getPRHead.outputs.sha }} | grep -q '^db/'; then @@ -75,6 +76,7 @@ runs: - name: Migrate Staging DB if: steps.db-changes.outputs.changed == 'true' + shell: bash run: ./mvnw flyway:migrate -Dflyway.locations=filesystem:./db/migration - name: Run script From cc7cee1854e2440acec0ee911fdb72c0d65d49ff Mon Sep 17 00:00:00 2001 From: Tahmid Ahmed Date: Sun, 18 Jan 2026 13:25:17 -0500 Subject: [PATCH 016/101] 641: 8bTjM6aBNpPwyo9PgBAOeZPUK2KuuI7IhUvZGwscjLjvL/XyhVyiwL9IJvfphKCR --- .env.ci | Bin 646 -> 879 bytes .github/composite/redeploy/action.yml | 4 +++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.env.ci b/.env.ci index c7ef32c4e767abe229cd2826e257395f51cabd65..4db3841ae0102c795d33a66bca4129db2c855091 100644 GIT binary patch literal 879 zcmV-#1CaaxM@dveQdv+`0NoX=nI#D!h=9BHrsyKCJYliMf4!&7E5)P1L#`H^?ey-B zrdKIq(HlFrTcc}5GH{Y?Acdn7l$2PdjqfTZ5wT0a%|M^p!|jQnq3m7gWr73DN?sek zkcXuXOGS)JERbwa+R@sTWG)Jz!RsX3N$dKQgmw~F5CMrx^+IRe#{SYDH+GdR>3&wK zZFykNVn(kaqFAd|jAQ=9#iVQO<+Oo?+JcE(>sQth!PA)7EWzOtmNvIZ*q3f+=^;z4 ztU-Pi3%K4jz5T{zGjxaLs2slSK$pCpzHqFEU+pUK3NH}KazSm|MR;a?Lc?>^Kc+E{ ze_}&Zk8g*2KrC)%cH}Ri%R3)2<|hiV$ssd!?7g!83);A&(TLUiNgyLT1SC^9Kr=UN zNAY{!Iih|y=!60~nAdPBZ>;>&(3UKMdXWh{${Y>~4Ac@(yuzy9Me%}NH5?`)(#{wT z@77~GZ4WJR(?~$Qx$C4P>L{Z0MmHKK19CheEVtEWo>2rOOSFDw^vBEGQl-N(_?xH4 zE6ayx%B^QY&xpVw5FLD?W7J1|>pq_Za5yBrV|1Ag)4uRHJQ)B13?gF*J_{o2F;bTC zYhIgm+Ep#bFT{ql8poU*Bo>E@2%~bh7%r3bG|(%29XX-L$FQ_N6RFX0OHNCJ`9f(G zQp9`jlQ8Fykt?hxX;VX0$;^?CUEckOlP3Qfw#NeJ-k^}7h4*9+dxKz{R`&XK@s0|Z z$5!4DTl<9^K$Ja0Uj6i-=r(E{xn!1Ul1(4Xv-=E1!;w{<$r4#7-fRkkM?`VbJD#A&WsTTb`8>uz z&o%MPcf#YKZQHmJ{8bhAyc;j#;tA@#>G!@LkLUN%p8?kN%Nl57_hr>&W73SS+XLD> z4QO(^ktYfkl;@Wq)}qYRZbjqhU1;ou7yE5qTsK4@zu`LwT8Oaoreqyq(J}MJFBIA! zTr*EM0g3MS0j(B*=H+I7u9q#o7vy%Z`xvt;LZ9zlK5o zp`f`7(q5b2ql{A{H4;G4fqO;zrC?%Gq$ACaN7MTfC)Ev=H%~j74Sex&*#}zZPgft= zoP1Clj7wfT4l63+``NiV_dLX6BW??1bD!_4&)khDn15-F-u-pM6DCTLRoB6;PO$|!nyOF;@tYLh^HN`AhI~N! z6jJuA)f!&GB)m&Kj;{JoZMuZ*$X-77X8q8JYHO}&{h!`tkN~4U=z?L-n@+uWW2awN z_3T#K-@dF6S?_Vz`1)$P+~l)kRd|R&jEq%C!pF^TX_r(recu*>?~b(g?B74a;i&RT3G<*`p`m!(xxpLx@x40fo71 gjm;g{ADmHy08oFvZ<;`_In~m=dx^%y=nUXJMdG7P;s5{u diff --git a/.github/composite/redeploy/action.yml b/.github/composite/redeploy/action.yml index 015d198c4..7dda93921 100644 --- a/.github/composite/redeploy/action.yml +++ b/.github/composite/redeploy/action.yml @@ -56,7 +56,7 @@ runs: with: GPG_PRIVATE_KEY: ${{ inputs.GPG_PRIVATE_KEY }} GPG_PASSPHRASE: ${{ inputs.GPG_PASSPHRASE }} - UNLOAD_ENVIRONMENTS: ci,staging + UNLOAD_ENVIRONMENTS: ci - name: Expose GitHub Runtime uses: crazy-max/ghaction-github-runtime@v3 @@ -86,3 +86,5 @@ runs: DOCKER_UPLOAD: ${{ inputs.DOCKER_UPLOAD }} TAG_PREFIX: ${{ inputs.TAG_PREFIX }} SERVER_PROFILES: ${{ inputs.SERVER_PROFILES }} + DIGITALOCEAN_PROJECT_ID: ${{ if(inputs.ENVIRONMENT == 'staging', env.STG_DIGITALOCEAN_PROJECT_ID, env.PROD_DIGITALOCEAN_PROJECT_ID) }} + DIGITALOCEAN_PAT: ${{ if(inputs.ENVIRONMENT == 'staging', env.STG_DIGITALOCEAN_PAT, env.PROD_DIGITALOCEAN_PAT) }} From d5726f809d4e9ddf4eedd22248d6fe1d79d15439 Mon Sep 17 00:00:00 2001 From: Tahmid Ahmed Date: Sun, 18 Jan 2026 13:36:24 -0500 Subject: [PATCH 017/101] 641: 8bTjM6aBNpPwyo9PgBAOeZPUK2KuuI7IhUvZGwscjLjvL/XyhVyiwL9IJvfphKCRefwonwef --- .github/composite/redeploy/action.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/composite/redeploy/action.yml b/.github/composite/redeploy/action.yml index 7dda93921..924a6a8b7 100644 --- a/.github/composite/redeploy/action.yml +++ b/.github/composite/redeploy/action.yml @@ -64,6 +64,7 @@ runs: - name: Check for DB changes id: db-changes shell: bash + if: ${{ inputs.ENVIRONMENT == 'staging' }} run: | git fetch origin main:main if git diff --name-only main...${{ needs.getPRHead.outputs.sha }} | grep -q '^db/'; then @@ -75,7 +76,7 @@ runs: fi - name: Migrate Staging DB - if: steps.db-changes.outputs.changed == 'true' + if: ${{ inputs.ENVIRONMENT == 'production' || (inputs.ENVIRONMENT == 'staging' && steps.db-changes.outputs.changed == 'true') }} shell: bash run: ./mvnw flyway:migrate -Dflyway.locations=filesystem:./db/migration From da7ce993d6edddba29203e80bab0814ac4228c19 Mon Sep 17 00:00:00 2001 From: Tahmid Ahmed Date: Sun, 18 Jan 2026 13:37:49 -0500 Subject: [PATCH 018/101] 641: --- .github/composite/redeploy/action.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/composite/redeploy/action.yml b/.github/composite/redeploy/action.yml index 924a6a8b7..c763c9f80 100644 --- a/.github/composite/redeploy/action.yml +++ b/.github/composite/redeploy/action.yml @@ -87,5 +87,5 @@ runs: DOCKER_UPLOAD: ${{ inputs.DOCKER_UPLOAD }} TAG_PREFIX: ${{ inputs.TAG_PREFIX }} SERVER_PROFILES: ${{ inputs.SERVER_PROFILES }} - DIGITALOCEAN_PROJECT_ID: ${{ if(inputs.ENVIRONMENT == 'staging', env.STG_DIGITALOCEAN_PROJECT_ID, env.PROD_DIGITALOCEAN_PROJECT_ID) }} - DIGITALOCEAN_PAT: ${{ if(inputs.ENVIRONMENT == 'staging', env.STG_DIGITALOCEAN_PAT, env.PROD_DIGITALOCEAN_PAT) }} + DIGITALOCEAN_PROJECT_ID: ${{ if(inputs.ENVIRONMENT == 'staging', env.STG_DIGITAL_OCEAN_PROJECT_ID, env.PROD_DIGITAL_OCEAN_PROJECT_ID) }} + DIGITALOCEAN_PAT: ${{ if(inputs.ENVIRONMENT == 'staging', env.STG_DIGITAL_OCEAN_PAT, env.PROD_DIGITAL_OCEAN_PAT) }} From f1bcd6055e897ef818dbbf0590fd53fe9261cb3a Mon Sep 17 00:00:00 2001 From: Tahmid Ahmed Date: Sun, 18 Jan 2026 13:58:00 -0500 Subject: [PATCH 019/101] 641: fkwnfwkf --- .github/composite/redeploy/action.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/composite/redeploy/action.yml b/.github/composite/redeploy/action.yml index c763c9f80..21d051c14 100644 --- a/.github/composite/redeploy/action.yml +++ b/.github/composite/redeploy/action.yml @@ -87,5 +87,5 @@ runs: DOCKER_UPLOAD: ${{ inputs.DOCKER_UPLOAD }} TAG_PREFIX: ${{ inputs.TAG_PREFIX }} SERVER_PROFILES: ${{ inputs.SERVER_PROFILES }} - DIGITALOCEAN_PROJECT_ID: ${{ if(inputs.ENVIRONMENT == 'staging', env.STG_DIGITAL_OCEAN_PROJECT_ID, env.PROD_DIGITAL_OCEAN_PROJECT_ID) }} - DIGITALOCEAN_PAT: ${{ if(inputs.ENVIRONMENT == 'staging', env.STG_DIGITAL_OCEAN_PAT, env.PROD_DIGITAL_OCEAN_PAT) }} + DIGITALOCEAN_PROJECT_ID: ${{ inputs.ENVIRONMENT == 'staging' && env.STG_DIGITAL_OCEAN_PROJECT_ID || env.PROD_DIGITAL_OCEAN_PROJECT_ID }} + DIGITALOCEAN_PAT: ${{ inputs.ENVIRONMENT == 'staging' && env.STG_DIGITAL_OCEAN_PAT || env.PROD_DIGITAL_OCEAN_PAT From 8e8945c8d085aa5e364f1e51aa8ebdd0ceee70e1 Mon Sep 17 00:00:00 2001 From: Tahmid Ahmed Date: Sun, 18 Jan 2026 14:04:58 -0500 Subject: [PATCH 020/101] 641: wfwfnewkgfnekrg --- .github/composite/redeploy/action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/composite/redeploy/action.yml b/.github/composite/redeploy/action.yml index 21d051c14..c6080fdd6 100644 --- a/.github/composite/redeploy/action.yml +++ b/.github/composite/redeploy/action.yml @@ -88,4 +88,4 @@ runs: TAG_PREFIX: ${{ inputs.TAG_PREFIX }} SERVER_PROFILES: ${{ inputs.SERVER_PROFILES }} DIGITALOCEAN_PROJECT_ID: ${{ inputs.ENVIRONMENT == 'staging' && env.STG_DIGITAL_OCEAN_PROJECT_ID || env.PROD_DIGITAL_OCEAN_PROJECT_ID }} - DIGITALOCEAN_PAT: ${{ inputs.ENVIRONMENT == 'staging' && env.STG_DIGITAL_OCEAN_PAT || env.PROD_DIGITAL_OCEAN_PAT + DIGITALOCEAN_PAT: ${{ inputs.ENVIRONMENT == 'staging' && env.STG_DIGITAL_OCEAN_PAT || env.PROD_DIGITAL_OCEAN_PAT }} From b9b14a4210397fc015ea55b2ee5b5b6f09126386 Mon Sep 17 00:00:00 2001 From: Tahmid Ahmed Date: Sun, 18 Jan 2026 14:05:41 -0500 Subject: [PATCH 021/101] 641: remove rebuilding from stg for now --- .github/workflows/deploy-stg.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/deploy-stg.yml b/.github/workflows/deploy-stg.yml index 69a24499d..9d63d46c2 100644 --- a/.github/workflows/deploy-stg.yml +++ b/.github/workflows/deploy-stg.yml @@ -236,13 +236,13 @@ jobs: with: ref: ${{ needs.getPRHead.outputs.sha }} - - name: Run workflow - uses: ./.github/composite/build-image - with: - GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }} - GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} - TAG_PREFIX: staging- - SERVER_PROFILES: stg + # - name: Run workflow + # uses: ./.github/composite/build-image + # with: + # GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }} + # GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} + # TAG_PREFIX: staging- + # SERVER_PROFILES: stg redeploy: name: Redeploy on DigitalOcean From 12dd68401986b74c7da09b2d6591b5392f4aabb7 Mon Sep 17 00:00:00 2001 From: Tahmid Ahmed Date: Sun, 18 Jan 2026 14:35:20 -0500 Subject: [PATCH 022/101] 641: wip --- .github/composite/redeploy/action.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/composite/redeploy/action.yml b/.github/composite/redeploy/action.yml index c6080fdd6..96b674ef7 100644 --- a/.github/composite/redeploy/action.yml +++ b/.github/composite/redeploy/action.yml @@ -87,5 +87,6 @@ runs: DOCKER_UPLOAD: ${{ inputs.DOCKER_UPLOAD }} TAG_PREFIX: ${{ inputs.TAG_PREFIX }} SERVER_PROFILES: ${{ inputs.SERVER_PROFILES }} + ENVIRONMENT: ${{ inputs.ENVIRONMENT }} DIGITALOCEAN_PROJECT_ID: ${{ inputs.ENVIRONMENT == 'staging' && env.STG_DIGITAL_OCEAN_PROJECT_ID || env.PROD_DIGITAL_OCEAN_PROJECT_ID }} DIGITALOCEAN_PAT: ${{ inputs.ENVIRONMENT == 'staging' && env.STG_DIGITAL_OCEAN_PAT || env.PROD_DIGITAL_OCEAN_PAT }} From f01fed7b72bfdfba49144ba8f41b251ac6a9b10e Mon Sep 17 00:00:00 2001 From: Tahmid Ahmed Date: Sun, 18 Jan 2026 14:48:04 -0500 Subject: [PATCH 023/101] 641: wip --- .github/scripts/redeploy.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/scripts/redeploy.ts b/.github/scripts/redeploy.ts index 8bf756c47..2cd4b2358 100644 --- a/.github/scripts/redeploy.ts +++ b/.github/scripts/redeploy.ts @@ -74,7 +74,7 @@ async function main() { return process.exit(1); } - console.log(res?.app?.projectId); + console.log(res?.app ?? "undefined"); await migrateDb(environment); } From ca6656ef1fc9d39cb1e442c4a2ef52a3f02a3a6a Mon Sep 17 00:00:00 2001 From: Tahmid Ahmed Date: Sun, 18 Jan 2026 14:52:37 -0500 Subject: [PATCH 024/101] 641: wip2 --- .github/composite/load-secrets/action.yml | 16 ---------------- .github/scripts/redeploy.ts | 4 ++++ 2 files changed, 4 insertions(+), 16 deletions(-) diff --git a/.github/composite/load-secrets/action.yml b/.github/composite/load-secrets/action.yml index 25df02b5b..c9197f484 100644 --- a/.github/composite/load-secrets/action.yml +++ b/.github/composite/load-secrets/action.yml @@ -37,22 +37,6 @@ runs: shell: bash run: git-crypt --version - - uses: oven-sh/setup-bun@v2 - with: - bun-version: latest - - - name: Cache Bun dependencies - uses: actions/cache@v4 - with: - path: ~/.bun/install/cache - key: ${{ runner.os }}-bun-${{ hashFiles('**/pnpm-lock.yaml') }} - restore-keys: | - ${{ runner.os }}-bun- - - - name: Install deps - shell: bash - run: bun install --cwd .github/scripts --frozen-lockfile - - name: Run script shell: bash run: bun .github/scripts/load-secrets.ts diff --git a/.github/scripts/redeploy.ts b/.github/scripts/redeploy.ts index 2cd4b2358..09dec8386 100644 --- a/.github/scripts/redeploy.ts +++ b/.github/scripts/redeploy.ts @@ -41,12 +41,16 @@ const environment: Environment = (() => { type Environment = "staging" | "production"; async function main() { + console.log("start setup of do"); const authProvider = new DigitalOceanApiKeyAuthenticationProvider(token); const adapter = new FetchRequestAdapter(authProvider); const client = createDigitalOceanClient(adapter); + console.log("step of do created"); + console.log("env load pls"); await $`git-crypt unlock`; const loaded = await getEnvVariables([environment]); + console.log("env load done"); const envs: App_variable_definition[] = loaded .entries() From 738aa5ac230b0bb395ea1b1699e24fba17516197 Mon Sep 17 00:00:00 2001 From: Tahmid Ahmed Date: Sun, 18 Jan 2026 14:55:53 -0500 Subject: [PATCH 025/101] 641: wip3 --- .github/composite/redeploy/action.yml | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/.github/composite/redeploy/action.yml b/.github/composite/redeploy/action.yml index 96b674ef7..1d6c99d29 100644 --- a/.github/composite/redeploy/action.yml +++ b/.github/composite/redeploy/action.yml @@ -61,25 +61,6 @@ runs: - name: Expose GitHub Runtime uses: crazy-max/ghaction-github-runtime@v3 - - name: Check for DB changes - id: db-changes - shell: bash - if: ${{ inputs.ENVIRONMENT == 'staging' }} - run: | - git fetch origin main:main - if git diff --name-only main...${{ needs.getPRHead.outputs.sha }} | grep -q '^db/'; then - echo "changed=true" >> $GITHUB_OUTPUT - echo "DB changes detected" - else - echo "changed=false" >> $GITHUB_OUTPUT - echo "No DB changes detected" - fi - - - name: Migrate Staging DB - if: ${{ inputs.ENVIRONMENT == 'production' || (inputs.ENVIRONMENT == 'staging' && steps.db-changes.outputs.changed == 'true') }} - shell: bash - run: ./mvnw flyway:migrate -Dflyway.locations=filesystem:./db/migration - - name: Run script shell: bash run: bun .github/scripts/redeploy.ts From 499ff03ef8859fa349cf355710485e6d95355e6d Mon Sep 17 00:00:00 2001 From: Tahmid Ahmed Date: Sun, 18 Jan 2026 14:59:31 -0500 Subject: [PATCH 026/101] 641: --- .github/workflows/deploy-stg.yml | 33 +++++++++++++++++++++----------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/.github/workflows/deploy-stg.yml b/.github/workflows/deploy-stg.yml index 9d63d46c2..1ee605408 100644 --- a/.github/workflows/deploy-stg.yml +++ b/.github/workflows/deploy-stg.yml @@ -196,22 +196,33 @@ jobs: - name: Set up OpenJDK 25 uses: actions/setup-java@v4 with: - distribution: "temurin" - java-version: "25" - cache: "maven" + distribution: "temurin" + java-version: "25" + cache: "maven" - - name: Verify Java version - run: | - java -version - javac -version - echo "JAVA_HOME=$JAVA_HOME" + - name: Set up bun + uses: oven-sh/setup-bun@v2 + with: + bun-version: latest + + - name: Cache Bun dependencies + uses: actions/cache@v4 + with: + path: ~/.bun/install/cache + key: ${{ runner.os }}-bun-${{ hashFiles('.github/scripts/bun.lock') }} + restore-keys: | + ${{ runner.os }}-bun- + + - name: Install deps + shell: bash + run: bun install --cwd .github/scripts --frozen-lockfile - name: Load secrets uses: ./.github/composite/load-secrets with: - GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }} - GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} - UNLOAD_ENVIRONMENTS: staging + GPG_PRIVATE_KEY: ${{ inputs.GPG_PRIVATE_KEY }} + GPG_PASSPHRASE: ${{ inputs.GPG_PASSPHRASE }} + UNLOAD_ENVIRONMENTS: ci - name: Validate DB Schema run: ./mvnw flyway:validate -Dflyway.locations=filesystem:./db/migration -Dflyway.ignoreMigrationPatterns="*:pending" From 1b8a1f9c1297106bf0f5a499d74d4ee433dba076 Mon Sep 17 00:00:00 2001 From: Tahmid Ahmed Date: Sun, 18 Jan 2026 15:01:41 -0500 Subject: [PATCH 027/101] 641: --- .github/scripts/run-backend-tests.sh | 34 --------------- .github/scripts/run-frontend-instance.sh | 48 ---------------------- .github/scripts/transform-yaml-with-env.sh | 26 ------------ .github/workflows/deploy-stg.yml | 4 +- 4 files changed, 2 insertions(+), 110 deletions(-) delete mode 100644 .github/scripts/run-backend-tests.sh delete mode 100644 .github/scripts/run-frontend-instance.sh delete mode 100644 .github/scripts/transform-yaml-with-env.sh diff --git a/.github/scripts/run-backend-tests.sh b/.github/scripts/run-backend-tests.sh deleted file mode 100644 index 9c9575178..000000000 --- a/.github/scripts/run-backend-tests.sh +++ /dev/null @@ -1,34 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -source "$DIR/local-db.sh" -source "$DIR/run-backend-instance.sh" -source "$DIR/run-frontend-instance.sh" - -trap 'db_cleanup; backend_cleanup; frontend_cleanup' EXIT - -java -version -javac -version -echo "JAVA_HOME=$JAVA_HOME" - -./mvnw -B install -D skipTests --no-transfer-progress - -./mvnw -B exec:java -e -D exec.mainClass=com.microsoft.playwright.CLI -D exec.args="install-deps" -./mvnw -B exec:java -e -D exec.mainClass=com.microsoft.playwright.CLI -D exec.args="install firefox" - -corepack enable pnpm -cd email -pnpm i --frozen-lockfile -./email.sh -cd .. - -db_startup - -backend_startup - -frontend_startup - -backend_cleanup - -./mvnw clean verify -Dspring.profiles.active=ci \ No newline at end of file diff --git a/.github/scripts/run-frontend-instance.sh b/.github/scripts/run-frontend-instance.sh deleted file mode 100644 index b681c5d6d..000000000 --- a/.github/scripts/run-frontend-instance.sh +++ /dev/null @@ -1,48 +0,0 @@ -#!/usr/bin/env bash - -frontend_cleanup() { - echo "[INFO] ===== FRONTEND LOGS START =====" - cat frontend.log || echo "[WARN] frontend.log not found" - echo "[INFO] ===== FRONTEND LOGS END =====" - if kill $(cat frontend_pid.txt) >/dev/null 2>&1; then - echo "Frontend process killed successfully." - else - echo "Frontend was not running or already stopped." - fi -} - -frontend_startup() { - corepack enable pnpm - cd js - pnpm i --frozen-lockfile - - echo "Generating API schema..." - pnpm run generate - - if [ ! -f "src/lib/api/types/autogen/schema.ts" ]; then - echo "Schema generation failed!" - exit 1 - fi - echo "Schema file generated successfully." - - pnpm run dev >../frontend.log 2>&1 & - echo $! >../frontend_pid.txt - cd .. - - frontend_started=false - for i in {1..30}; do - if curl -s http://localhost:5173/ | grep -q ''; then - echo "Frontend is up!" - frontend_started=true - break - fi - echo "Waiting for frontend... ($i/30)" - sleep 2 - done - - if [ "$frontend_started" = false ]; then - echo "Frontend failed to start in time." - cat frontend.log || true - exit 1 - fi -} \ No newline at end of file diff --git a/.github/scripts/transform-yaml-with-env.sh b/.github/scripts/transform-yaml-with-env.sh deleted file mode 100644 index 34e8343f3..000000000 --- a/.github/scripts/transform-yaml-with-env.sh +++ /dev/null @@ -1,26 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -APP_SPEC_PATH="${1:-.do/prod/app.yml}" -ENV_PATH="${2:-.env}" -TMP_ENVS_YAML="$(mktemp 2>/dev/null || echo "/tmp/tmp_envs_$$.yml")" -if [[ -z "$TMP_ENVS_YAML" ]]; then - echo "Failed to create temp file" >&2 - exit 1 -fi -echo "Parsing environment variables..." -: >"$TMP_ENVS_YAML" - -while IFS='=' read -r key val; do - # Skip empty lines or comments - [[ -z "$key" || "$key" =~ ^# ]] && continue - key=$(echo "$key" | xargs) - val=$(echo "$val" | xargs) - printf " - key: %s\n value: \"%s\"\n scope: RUN_AND_BUILD_TIME\n type: SECRET\n" "$key" "$val" >>"$TMP_ENVS_YAML" -done <"$ENV_PATH" - -echo "Replacing root .envs in $APP_SPEC_PATH ..." -yq -i '.envs = load("'"$TMP_ENVS_YAML"'")' "$APP_SPEC_PATH" - -echo "Updated $APP_SPEC_PATH with $(grep -c 'key:' "$TMP_ENVS_YAML") env vars." -rm -f "$TMP_ENVS_YAML" diff --git a/.github/workflows/deploy-stg.yml b/.github/workflows/deploy-stg.yml index 1ee605408..4e76b3443 100644 --- a/.github/workflows/deploy-stg.yml +++ b/.github/workflows/deploy-stg.yml @@ -220,8 +220,8 @@ jobs: - name: Load secrets uses: ./.github/composite/load-secrets with: - GPG_PRIVATE_KEY: ${{ inputs.GPG_PRIVATE_KEY }} - GPG_PASSPHRASE: ${{ inputs.GPG_PASSPHRASE }} + GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }} + GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} UNLOAD_ENVIRONMENTS: ci - name: Validate DB Schema From 744415d58e206c02eccc0cde4cf6721e7506ae06 Mon Sep 17 00:00:00 2001 From: Tahmid Ahmed <tahmid@tahmid.io> Date: Sun, 18 Jan 2026 15:05:57 -0500 Subject: [PATCH 028/101] 641: fwl --- .github/workflows/deploy-stg.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy-stg.yml b/.github/workflows/deploy-stg.yml index 4e76b3443..e6f5754ae 100644 --- a/.github/workflows/deploy-stg.yml +++ b/.github/workflows/deploy-stg.yml @@ -222,7 +222,7 @@ jobs: with: GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }} GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} - UNLOAD_ENVIRONMENTS: ci + UNLOAD_ENVIRONMENTS: staging - name: Validate DB Schema run: ./mvnw flyway:validate -Dflyway.locations=filesystem:./db/migration -Dflyway.ignoreMigrationPatterns="*:pending" From 4e2cba716effed3153147144daa38d872bf48641 Mon Sep 17 00:00:00 2001 From: Tahmid Ahmed <tahmid@tahmid.io> Date: Sun, 18 Jan 2026 15:12:13 -0500 Subject: [PATCH 029/101] 641: fwlfewf --- .github/scripts/redeploy.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/scripts/redeploy.ts b/.github/scripts/redeploy.ts index 09dec8386..dc09a4174 100644 --- a/.github/scripts/redeploy.ts +++ b/.github/scripts/redeploy.ts @@ -48,7 +48,9 @@ async function main() { console.log("step of do created"); console.log("env load pls"); - await $`git-crypt unlock`; + + // should already be called + // await $`git-crypt unlock`; const loaded = await getEnvVariables([environment]); console.log("env load done"); From a76ef884952766d2303ebb2ec0570b0acc9b082a Mon Sep 17 00:00:00 2001 From: Tahmid Ahmed <tahmid@tahmid.io> Date: Sun, 18 Jan 2026 15:19:40 -0500 Subject: [PATCH 030/101] 641: ewkfnwkfb --- .do/specs.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/.do/specs.ts b/.do/specs.ts index 42e3ff92d..8f88b2a0b 100644 --- a/.do/specs.ts +++ b/.do/specs.ts @@ -72,7 +72,7 @@ export function prodSpec(envs: App_variable_definition[]): App_spec { export function stgSpec(envs: App_variable_definition[]): App_spec { return { ...DIGITALOCEAN_BASE_SPEC, - name: "codebloom-stg", + name: "codebloom-staging", services: [ { ...DIGITALOCEAN_BASE_SERVICE, @@ -83,5 +83,11 @@ export function stgSpec(envs: App_variable_definition[]): App_spec { envs, }, ], + domains: [ + { + domain: "stg.codebloom.patinanetwork.org", + type: "DEFAULT", + }, + ], }; } From 1fa26c94beab4f16d924795c9bc9dd058cdfac58 Mon Sep 17 00:00:00 2001 From: Tahmid Ahmed <tahmid@tahmid.io> Date: Sun, 18 Jan 2026 15:22:59 -0500 Subject: [PATCH 031/101] 641: ewkfnwkfbwefowlg --- .do/specs.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.do/specs.ts b/.do/specs.ts index 8f88b2a0b..4c984d7e6 100644 --- a/.do/specs.ts +++ b/.do/specs.ts @@ -86,7 +86,7 @@ export function stgSpec(envs: App_variable_definition[]): App_spec { domains: [ { domain: "stg.codebloom.patinanetwork.org", - type: "DEFAULT", + type: "PRIMARY", }, ], }; From 14857605183624d1aee4e8e7557866d038d51e7f Mon Sep 17 00:00:00 2001 From: Tahmid Ahmed <tahmid@tahmid.io> Date: Sun, 18 Jan 2026 15:27:40 -0500 Subject: [PATCH 032/101] 641: --- .github/scripts/redeploy.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/scripts/redeploy.ts b/.github/scripts/redeploy.ts index dc09a4174..675644d3c 100644 --- a/.github/scripts/redeploy.ts +++ b/.github/scripts/redeploy.ts @@ -69,6 +69,7 @@ async function main() { const spec = environment === "staging" ? stgSpec(envs) : prodSpec(envs); + console.log("making do call..."); let res: App_response | undefined; try { res = await client.v2.apps.post({ @@ -79,8 +80,9 @@ async function main() { console.error(e); return process.exit(1); } + console.log("do call done"); - console.log(res?.app ?? "undefined"); + console.log(res?.app ?? "failed"); await migrateDb(environment); } From 6ba4364d4aab84492e95bc361203805558a74bee Mon Sep 17 00:00:00 2001 From: Tahmid Ahmed <tahmid@tahmid.io> Date: Sun, 18 Jan 2026 18:15:41 -0500 Subject: [PATCH 033/101] 641: here goes nothing --- .../env/load.ts} | 74 +++---------------- .github/scripts/load-secrets/index.ts | 63 ++++++++++++++++ .github/scripts/redeploy/apps/create.ts | 26 +++++++ .github/scripts/redeploy/apps/get.ts | 46 ++++++++++++ .../{redeploy.ts => redeploy/index.ts} | 33 ++++++--- .github/scripts/redeploy/types.ts | 1 + .github/scripts/tsconfig.json | 3 +- .github/scripts/{fn => utils}/colors.ts | 0 .../{fn => utils}/run-backend-instance.ts | 0 .github/scripts/{fn => utils}/run-local-db.ts | 0 10 files changed, 173 insertions(+), 73 deletions(-) rename .github/scripts/{load-secrets.ts => load-secrets/env/load.ts} (52%) create mode 100644 .github/scripts/load-secrets/index.ts create mode 100644 .github/scripts/redeploy/apps/create.ts create mode 100644 .github/scripts/redeploy/apps/get.ts rename .github/scripts/{redeploy.ts => redeploy/index.ts} (73%) create mode 100644 .github/scripts/redeploy/types.ts rename .github/scripts/{fn => utils}/colors.ts (100%) rename .github/scripts/{fn => utils}/run-backend-instance.ts (100%) rename .github/scripts/{fn => utils}/run-local-db.ts (100%) diff --git a/.github/scripts/load-secrets.ts b/.github/scripts/load-secrets/env/load.ts similarity index 52% rename from .github/scripts/load-secrets.ts rename to .github/scripts/load-secrets/env/load.ts index 91db2481c..7688eba86 100644 --- a/.github/scripts/load-secrets.ts +++ b/.github/scripts/load-secrets/env/load.ts @@ -1,57 +1,14 @@ -import { $ } from "bun"; - -// UNLOAD_ENVIRONMENTS="prod,staging,dev" -const unloadEnvironments = process.env.UNLOAD_ENVIRONMENTS || ""; - -const excludedVars = [ - "PATH", - "HOME", - "PWD", - "SHELL", - "USER", - "DEBUG", - "LOG_LEVEL", - "CI", - "JAVA_HOME", -]; - -async function main() { - await $`git-crypt unlock`; - - const envs = unloadEnvironments - .split(",") - .map((e) => e.trim()) - .filter(Boolean); - - const loaded = await getEnvVariables(envs, false); - - const githubEnv = process.env.GITHUB_ENV; - if (!githubEnv) { - console.log("Warning: GITHUB_ENV not set, skipping variable export"); - return; - } - - const githubEnvFileWriter = Bun.file(githubEnv).writer(); - - for (const [varName, value] of loaded.entries()) { - githubEnvFileWriter.write(`${varName}=${value}\n`); - if (excludedVars.includes(varName)) { - console.log(`Not masking ${varName}: Excluded`); - continue; - } - - if (value === "true" || value === "false" || value === "") { - console.log(`Not masking ${varName}: true/false/empty value`); - continue; - } - - console.log(`Masking ${varName}`); - console.log(`::add-mask::${value}`); - } - - githubEnvFileWriter.flush(); -} - +/** + * @param environments - List of environment files to load. + * @param mask - Should variables be masked. Defaults to `true`. __NOTE: This will only work in a GitHub Action runner.__ + * + * __NOTE: Be very careful of setting `mask` to `false`.__ + * + * @returns a map of the loaded environments as a key and value inside of a map. + * + * _Please note that duplicate environment variables will be overwritten, so + * the order in which you define `environments` does matter._ + */ export async function getEnvVariables( environments: string[], mask = true, @@ -101,12 +58,3 @@ export async function getEnvVariables( return loaded; } - -main() - .then(() => { - process.exit(0); - }) - .catch((e) => { - console.error(e); - process.exit(1); - }); diff --git a/.github/scripts/load-secrets/index.ts b/.github/scripts/load-secrets/index.ts new file mode 100644 index 000000000..1c30e9c2d --- /dev/null +++ b/.github/scripts/load-secrets/index.ts @@ -0,0 +1,63 @@ +import { $ } from "bun"; +import { getEnvVariables } from "load-secrets/env/load"; + +// UNLOAD_ENVIRONMENTS="prod,staging,dev" +const unloadEnvironments = process.env.UNLOAD_ENVIRONMENTS || ""; + +const excludedVars = [ + "PATH", + "HOME", + "PWD", + "SHELL", + "USER", + "DEBUG", + "LOG_LEVEL", + "CI", + "JAVA_HOME", +]; + +async function main() { + await $`git-crypt unlock`; + + const envs = unloadEnvironments + .split(",") + .map((e) => e.trim()) + .filter(Boolean); + + const loaded = await getEnvVariables(envs, false); + + const githubEnv = process.env.GITHUB_ENV; + if (!githubEnv) { + console.log("Warning: GITHUB_ENV not set, skipping variable export"); + return; + } + + const githubEnvFileWriter = Bun.file(githubEnv).writer(); + + for (const [varName, value] of loaded.entries()) { + githubEnvFileWriter.write(`${varName}=${value}\n`); + if (excludedVars.includes(varName)) { + console.log(`Not masking ${varName}: Excluded`); + continue; + } + + if (value === "true" || value === "false" || value === "") { + console.log(`Not masking ${varName}: true/false/empty value`); + continue; + } + + console.log(`Masking ${varName}`); + console.log(`::add-mask::${value}`); + } + + githubEnvFileWriter.flush(); +} + +main() + .then(() => { + process.exit(0); + }) + .catch((e) => { + console.error(e); + process.exit(1); + }); diff --git a/.github/scripts/redeploy/apps/create.ts b/.github/scripts/redeploy/apps/create.ts new file mode 100644 index 000000000..b25f6a984 --- /dev/null +++ b/.github/scripts/redeploy/apps/create.ts @@ -0,0 +1,26 @@ +import type { App_spec } from "@digitalocean/dots"; +import type { DigitalOceanClient } from "@digitalocean/dots/src/dots/digitalOceanClient"; +import type { Environment } from "redeploy/types"; + +export async function _createAppAndgetAppId( + client: DigitalOceanClient, + projectId: string, + spec: App_spec, +): Promise<string | null> { + const res = await client.v2.apps.post({ + spec, + projectId, + }); + + if (!res) { + return null; + } + + const { app } = res; + + if (!app) { + return null; + } + + return app.id ?? null; +} diff --git a/.github/scripts/redeploy/apps/get.ts b/.github/scripts/redeploy/apps/get.ts new file mode 100644 index 000000000..95cb799c5 --- /dev/null +++ b/.github/scripts/redeploy/apps/get.ts @@ -0,0 +1,46 @@ +import type { DigitalOceanClient } from "@digitalocean/dots/src/dots/digitalOceanClient"; +import type { Environment } from "redeploy/types"; + +export async function _getAppId( + client: DigitalOceanClient, + environment: Environment, + projectId: string, +): Promise<string | null> { + const appName = (() => { + if (environment === "staging") { + return "codebloom-staging"; + } + + if (environment === "production") { + return "codebloom-prod"; + } + + throw new Error("This environment is not currently supported"); + })(); + + const res = await client.v2.apps.get({ + queryParameters: { + withProjects: true, + }, + }); + + if (!res) { + return null; + } + + const { apps } = res; + + if (!apps) { + return null; + } + + const foundApp = apps.filter( + (app) => app.projectId == projectId && app.spec?.name == appName, + )[0]; + + if (!foundApp) { + return null; + } + + return foundApp.id ?? null; +} diff --git a/.github/scripts/redeploy.ts b/.github/scripts/redeploy/index.ts similarity index 73% rename from .github/scripts/redeploy.ts rename to .github/scripts/redeploy/index.ts index 675644d3c..1e81f61a8 100644 --- a/.github/scripts/redeploy.ts +++ b/.github/scripts/redeploy/index.ts @@ -6,8 +6,11 @@ import { type App_response, type App_variable_definition, } from "@digitalocean/dots"; -import { prodSpec, stgSpec } from "../../.do/specs"; -import { getEnvVariables } from "./load-secrets"; +import { prodSpec, stgSpec } from "../../../.do/specs"; +import { getEnvVariables } from "load-secrets/env/load"; +import type { Environment } from "redeploy/types"; +import { _createAppAndgetAppId } from "redeploy/apps/create"; +import { _getAppId } from "redeploy/apps/get"; const projectId = (() => { const v = process.env.DIGITALOCEAN_PROJECT_ID; @@ -38,8 +41,6 @@ const environment: Environment = (() => { return v; })(); -type Environment = "staging" | "production"; - async function main() { console.log("start setup of do"); const authProvider = new DigitalOceanApiKeyAuthenticationProvider(token); @@ -69,19 +70,33 @@ async function main() { const spec = environment === "staging" ? stgSpec(envs) : prodSpec(envs); - console.log("making do call..."); + const appId = await (async () => { + const v = await _getAppId(client, environment, projectId); + + if (v) { + return v; + } + + const v2 = await _createAppAndgetAppId(client, projectId, spec); + if (!v2) { + throw new Error( + "App not found and failed to create as well. Please alert Codebloom team.", + ); + } + + return v2; + })(); + let res: App_response | undefined; try { - res = await client.v2.apps.post({ + res = await client.v2.apps.byApp_Id(appId).put({ spec, - projectId, + updateAllSourceVersions: true, }); } catch (e) { console.error(e); return process.exit(1); } - console.log("do call done"); - console.log(res?.app ?? "failed"); await migrateDb(environment); diff --git a/.github/scripts/redeploy/types.ts b/.github/scripts/redeploy/types.ts new file mode 100644 index 000000000..32a48ba5b --- /dev/null +++ b/.github/scripts/redeploy/types.ts @@ -0,0 +1 @@ +export type Environment = "staging" | "production"; diff --git a/.github/scripts/tsconfig.json b/.github/scripts/tsconfig.json index c3c57e20d..82f5612a8 100644 --- a/.github/scripts/tsconfig.json +++ b/.github/scripts/tsconfig.json @@ -24,6 +24,7 @@ // Some stricter flags (disabled by default) "noUnusedLocals": false, "noUnusedParameters": false, - "noPropertyAccessFromIndexSignature": false + "noPropertyAccessFromIndexSignature": false, + "baseUrl": "./" } } diff --git a/.github/scripts/fn/colors.ts b/.github/scripts/utils/colors.ts similarity index 100% rename from .github/scripts/fn/colors.ts rename to .github/scripts/utils/colors.ts diff --git a/.github/scripts/fn/run-backend-instance.ts b/.github/scripts/utils/run-backend-instance.ts similarity index 100% rename from .github/scripts/fn/run-backend-instance.ts rename to .github/scripts/utils/run-backend-instance.ts diff --git a/.github/scripts/fn/run-local-db.ts b/.github/scripts/utils/run-local-db.ts similarity index 100% rename from .github/scripts/fn/run-local-db.ts rename to .github/scripts/utils/run-local-db.ts From 67e955f6a9470322dd4a87fc1cb4c91e4183387d Mon Sep 17 00:00:00 2001 From: Tahmid Ahmed <tahmid@tahmid.io> Date: Sun, 18 Jan 2026 18:20:27 -0500 Subject: [PATCH 034/101] 641: here goes nothing pt2 --- .github/composite/load-secrets/action.yml | 2 +- .github/composite/redeploy/action.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/composite/load-secrets/action.yml b/.github/composite/load-secrets/action.yml index c9197f484..347c05178 100644 --- a/.github/composite/load-secrets/action.yml +++ b/.github/composite/load-secrets/action.yml @@ -39,6 +39,6 @@ runs: - name: Run script shell: bash - run: bun .github/scripts/load-secrets.ts + run: bun .github/scripts/load-secrets env: UNLOAD_ENVIRONMENTS: ${{ inputs.UNLOAD_ENVIRONMENTS }} diff --git a/.github/composite/redeploy/action.yml b/.github/composite/redeploy/action.yml index 1d6c99d29..d0036d47e 100644 --- a/.github/composite/redeploy/action.yml +++ b/.github/composite/redeploy/action.yml @@ -63,7 +63,7 @@ runs: - name: Run script shell: bash - run: bun .github/scripts/redeploy.ts + run: bun .github/scripts/redeploy env: DOCKER_UPLOAD: ${{ inputs.DOCKER_UPLOAD }} TAG_PREFIX: ${{ inputs.TAG_PREFIX }} From 32d53de71e99b2533062033b940eb9ff8b4d1512 Mon Sep 17 00:00:00 2001 From: Tahmid Ahmed <tahmid@tahmid.io> Date: Sun, 18 Jan 2026 19:07:31 -0500 Subject: [PATCH 035/101] 641: attempt to add deployment check logic --- .github/composite/setup-ci/action.yml | 2 ++ .github/scripts/redeploy/db/index.ts | 6 ++++ .github/scripts/redeploy/index.ts | 44 +++++++++++++++++++++------ 3 files changed, 42 insertions(+), 10 deletions(-) create mode 100644 .github/composite/setup-ci/action.yml create mode 100644 .github/scripts/redeploy/db/index.ts diff --git a/.github/composite/setup-ci/action.yml b/.github/composite/setup-ci/action.yml new file mode 100644 index 000000000..dce434153 --- /dev/null +++ b/.github/composite/setup-ci/action.yml @@ -0,0 +1,2 @@ +name: "Setup CI" +description: "Setup bun & " diff --git a/.github/scripts/redeploy/db/index.ts b/.github/scripts/redeploy/db/index.ts new file mode 100644 index 000000000..e839bd09f --- /dev/null +++ b/.github/scripts/redeploy/db/index.ts @@ -0,0 +1,6 @@ +import { $ } from "bun"; +import type { Environment } from "redeploy/types"; + +export async function _migrateDb(environment: Environment) { + await $`git fetch origin main:main`; +} diff --git a/.github/scripts/redeploy/index.ts b/.github/scripts/redeploy/index.ts index 1e81f61a8..9a3c66e87 100644 --- a/.github/scripts/redeploy/index.ts +++ b/.github/scripts/redeploy/index.ts @@ -11,6 +11,7 @@ import { getEnvVariables } from "load-secrets/env/load"; import type { Environment } from "redeploy/types"; import { _createAppAndgetAppId } from "redeploy/apps/create"; import { _getAppId } from "redeploy/apps/get"; +import { _migrateDb } from "redeploy/db"; const projectId = (() => { const v = process.env.DIGITALOCEAN_PROJECT_ID; @@ -42,18 +43,13 @@ const environment: Environment = (() => { })(); async function main() { - console.log("start setup of do"); const authProvider = new DigitalOceanApiKeyAuthenticationProvider(token); const adapter = new FetchRequestAdapter(authProvider); const client = createDigitalOceanClient(adapter); - console.log("step of do created"); - - console.log("env load pls"); // should already be called // await $`git-crypt unlock`; const loaded = await getEnvVariables([environment]); - console.log("env load done"); const envs: App_variable_definition[] = loaded .entries() @@ -97,13 +93,41 @@ async function main() { console.error(e); return process.exit(1); } - console.log(res?.app ?? "failed"); - await migrateDb(environment); -} + const pendingDeploymentId = res?.app?.pendingDeployment?.id; + + if (!pendingDeploymentId) { + console.error("Failed to find pending deployment."); + process.exit(1); + } + + let ready = false; + const attempts = 60; + + for (let i = 1; i <= attempts; i++) { + const res = await client.v2.apps + .byApp_Id(appId) + .deployments.byDeployment_id(pendingDeploymentId) + .get(); + + const phase = res?.deployment?.phase ?? "UNKNOWN"; -async function migrateDb(environment: Environment) { - await $`git fetch origin main:main`; + console.log("Deployment phase:", phase); + + if (phase === "ACTIVE") { + console.log("Deployment has completed!"); + ready = true; + break; + } + + console.log(`Waiting for deployment to complete... (${i}/${attempts})`); + await Bun.sleep(10000); + } + + if (!ready) { + console.error("Deployment did not reach a valid state within 10 minutes."); + process.exit(1); + } } main() From d19408932b7b84be7effd3e4d3d8e92fe5716095 Mon Sep 17 00:00:00 2001 From: Tahmid Ahmed <tahmid@tahmid.io> Date: Sun, 18 Jan 2026 19:13:43 -0500 Subject: [PATCH 036/101] 641: test to make sure it deploys --- .github/scripts/.gitignore | 24 +++ .github/scripts/.prettierignore | 1 + .github/scripts/.prettierrc | 4 + .github/scripts/bun.lock | 223 ++++++++++++++++++++++++ .github/scripts/eslint.config.js | 52 ++++++ .github/scripts/package.json | 7 +- .github/scripts/redeploy/apps/create.ts | 1 - .github/scripts/redeploy/db/index.ts | 4 +- .github/scripts/redeploy/index.ts | 7 +- .github/scripts/run-backend-tests.ts | 2 +- .github/scripts/run-frontend-tests.ts | 4 +- js/src/app/_component/AboutUs.tsx | 2 +- 12 files changed, 321 insertions(+), 10 deletions(-) create mode 100644 .github/scripts/.gitignore create mode 100644 .github/scripts/.prettierignore create mode 100644 .github/scripts/.prettierrc create mode 100644 .github/scripts/eslint.config.js diff --git a/.github/scripts/.gitignore b/.github/scripts/.gitignore new file mode 100644 index 000000000..a547bf36d --- /dev/null +++ b/.github/scripts/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/.github/scripts/.prettierignore b/.github/scripts/.prettierignore new file mode 100644 index 000000000..ea7f33b0d --- /dev/null +++ b/.github/scripts/.prettierignore @@ -0,0 +1 @@ +bun.lock diff --git a/.github/scripts/.prettierrc b/.github/scripts/.prettierrc new file mode 100644 index 000000000..c70c4d8a1 --- /dev/null +++ b/.github/scripts/.prettierrc @@ -0,0 +1,4 @@ +{ + "experimentalTernaries": true, + "semi": true +} diff --git a/.github/scripts/bun.lock b/.github/scripts/bun.lock index f140bbe38..ee868ec47 100644 --- a/.github/scripts/bun.lock +++ b/.github/scripts/bun.lock @@ -9,7 +9,12 @@ "bun": "^1.3.6", }, "devDependencies": { + "@eslint/js": "^9.39.2", "@types/bun": "^1.3.6", + "eslint-plugin-no-relative-import-paths": "^1.6.1", + "eslint-plugin-perfectionist": "^5.3.1", + "global": "^4.4.0", + "typescript-eslint": "^8.53.0", }, }, }, @@ -28,6 +33,32 @@ "@digitalocean/dots": ["@digitalocean/dots@1.7.0", "", { "dependencies": { "@azure/core-http": "^3.0.5", "@microsoft/kiota-authentication-azure": "^1.0.0-preview.61", "@microsoft/kiota-bundle": "^1.0.0-preview.9", "@microsoft/kiota-http-fetchlibrary": "^1.0.0-preview.97", "dotenv": "^17.2.0", "uuid": "^11.1.0" } }, "sha512-4BD52gSlG7xdxMV7sWXTKi9zpl7dmyK+47WNVkLCTw0p5ktlX7VYhV8lDAuCq10fGF4w7LhCh/vOeuB13+x7aQ=="], + "@eslint-community/eslint-utils": ["@eslint-community/eslint-utils@4.9.1", "", { "dependencies": { "eslint-visitor-keys": "^3.4.3" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ=="], + + "@eslint-community/regexpp": ["@eslint-community/regexpp@4.12.2", "", {}, "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew=="], + + "@eslint/config-array": ["@eslint/config-array@0.21.1", "", { "dependencies": { "@eslint/object-schema": "^2.1.7", "debug": "^4.3.1", "minimatch": "^3.1.2" } }, "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA=="], + + "@eslint/config-helpers": ["@eslint/config-helpers@0.4.2", "", { "dependencies": { "@eslint/core": "^0.17.0" } }, "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw=="], + + "@eslint/core": ["@eslint/core@0.17.0", "", { "dependencies": { "@types/json-schema": "^7.0.15" } }, "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ=="], + + "@eslint/eslintrc": ["@eslint/eslintrc@3.3.3", "", { "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", "espree": "^10.0.1", "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.1", "minimatch": "^3.1.2", "strip-json-comments": "^3.1.1" } }, "sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ=="], + + "@eslint/js": ["@eslint/js@9.39.2", "", {}, "sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA=="], + + "@eslint/object-schema": ["@eslint/object-schema@2.1.7", "", {}, "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA=="], + + "@eslint/plugin-kit": ["@eslint/plugin-kit@0.4.1", "", { "dependencies": { "@eslint/core": "^0.17.0", "levn": "^0.4.1" } }, "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA=="], + + "@humanfs/core": ["@humanfs/core@0.19.1", "", {}, "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA=="], + + "@humanfs/node": ["@humanfs/node@0.16.7", "", { "dependencies": { "@humanfs/core": "^0.19.1", "@humanwhocodes/retry": "^0.4.0" } }, "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ=="], + + "@humanwhocodes/module-importer": ["@humanwhocodes/module-importer@1.0.1", "", {}, "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA=="], + + "@humanwhocodes/retry": ["@humanwhocodes/retry@0.4.3", "", {}, "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ=="], + "@microsoft/kiota-abstractions": ["@microsoft/kiota-abstractions@1.0.0-preview.99", "", { "dependencies": { "@opentelemetry/api": "^1.7.0", "@std-uritemplate/std-uritemplate": "^2.0.0", "tinyduration": "^3.3.0", "tslib": "^2.6.2" } }, "sha512-6qrlwGCO3DbvpA7tszqk7JNQPFPDg8gv5NGMTziwdtHqaQrUALKusm3vdJGPVGrbNiZL6/lBaY5NSUvLtlhRCg=="], "@microsoft/kiota-authentication-azure": ["@microsoft/kiota-authentication-azure@1.0.0-preview.99", "", { "dependencies": { "@azure/core-auth": "^1.5.0", "@microsoft/kiota-abstractions": "^1.0.0-preview.99", "@opentelemetry/api": "^1.7.0", "tslib": "^2.6.2" } }, "sha512-gPmk/vx15sBMfjfLPgqPanmyVR/rA5HGd3318VtPS28mXxH07Zn30R6PVqSemhIwSxfiGnPiq0SlTj28BQhTKQ=="], @@ -72,30 +103,84 @@ "@types/bun": ["@types/bun@1.3.6", "", { "dependencies": { "bun-types": "1.3.6" } }, "sha512-uWCv6FO/8LcpREhenN1d1b6fcspAB+cefwD7uti8C8VffIv0Um08TKMn98FynpTiU38+y2dUO55T11NgDt8VAA=="], + "@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="], + + "@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="], + "@types/node": ["@types/node@25.0.9", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-/rpCXHlCWeqClNBwUhDcusJxXYDjZTyE8v5oTO7WbL8eij2nKhUeU89/6xgjU7N4/Vh3He0BtyhJdQbDyhiXAw=="], "@types/node-fetch": ["@types/node-fetch@2.6.13", "", { "dependencies": { "@types/node": "*", "form-data": "^4.0.4" } }, "sha512-QGpRVpzSaUs30JBSGPjOg4Uveu384erbHBoT1zeONvyCfwQxIkUshLAOqN/k9EjGviPRmWTTe6aH2qySWKTVSw=="], "@types/tunnel": ["@types/tunnel@0.0.3", "", { "dependencies": { "@types/node": "*" } }, "sha512-sOUTGn6h1SfQ+gbgqC364jLFBw2lnFqkgF3q0WovEHRLMrVD1sd5aufqi/aJObLekJO+Aq5z646U4Oxy6shXMA=="], + "@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.53.0", "", { "dependencies": { "@eslint-community/regexpp": "^4.12.2", "@typescript-eslint/scope-manager": "8.53.0", "@typescript-eslint/type-utils": "8.53.0", "@typescript-eslint/utils": "8.53.0", "@typescript-eslint/visitor-keys": "8.53.0", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.4.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.53.0", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-eEXsVvLPu8Z4PkFibtuFJLJOTAV/nPdgtSjkGoPpddpFk3/ym2oy97jynY6ic2m6+nc5M8SE1e9v/mHKsulcJg=="], + + "@typescript-eslint/parser": ["@typescript-eslint/parser@8.53.0", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.53.0", "@typescript-eslint/types": "8.53.0", "@typescript-eslint/typescript-estree": "8.53.0", "@typescript-eslint/visitor-keys": "8.53.0", "debug": "^4.4.3" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-npiaib8XzbjtzS2N4HlqPvlpxpmZ14FjSJrteZpPxGUaYPlvhzlzUZ4mZyABo0EFrOWnvyd0Xxroq//hKhtAWg=="], + + "@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.53.0", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.53.0", "@typescript-eslint/types": "^8.53.0", "debug": "^4.4.3" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-Bl6Gdr7NqkqIP5yP9z1JU///Nmes4Eose6L1HwpuVHwScgDPPuEWbUVhvlZmb8hy0vX9syLk5EGNL700WcBlbg=="], + + "@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.53.0", "", { "dependencies": { "@typescript-eslint/types": "8.53.0", "@typescript-eslint/visitor-keys": "8.53.0" } }, "sha512-kWNj3l01eOGSdVBnfAF2K1BTh06WS0Yet6JUgb9Cmkqaz3Jlu0fdVUjj9UI8gPidBWSMqDIglmEXifSgDT/D0g=="], + + "@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.53.0", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-K6Sc0R5GIG6dNoPdOooQ+KtvT5KCKAvTcY8h2rIuul19vxH5OTQk7ArKkd4yTzkw66WnNY0kPPzzcmWA+XRmiA=="], + + "@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.53.0", "", { "dependencies": { "@typescript-eslint/types": "8.53.0", "@typescript-eslint/typescript-estree": "8.53.0", "@typescript-eslint/utils": "8.53.0", "debug": "^4.4.3", "ts-api-utils": "^2.4.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-BBAUhlx7g4SmcLhn8cnbxoxtmS7hcq39xKCgiutL3oNx1TaIp+cny51s8ewnKMpVUKQUGb41RAUWZ9kxYdovuw=="], + + "@typescript-eslint/types": ["@typescript-eslint/types@8.53.0", "", {}, "sha512-Bmh9KX31Vlxa13+PqPvt4RzKRN1XORYSLlAE+sO1i28NkisGbTtSLFVB3l7PWdHtR3E0mVMuC7JilWJ99m2HxQ=="], + + "@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.53.0", "", { "dependencies": { "@typescript-eslint/project-service": "8.53.0", "@typescript-eslint/tsconfig-utils": "8.53.0", "@typescript-eslint/types": "8.53.0", "@typescript-eslint/visitor-keys": "8.53.0", "debug": "^4.4.3", "minimatch": "^9.0.5", "semver": "^7.7.3", "tinyglobby": "^0.2.15", "ts-api-utils": "^2.4.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-pw0c0Gdo7Z4xOG987u3nJ8akL9093yEEKv8QTJ+Bhkghj1xyj8cgPaavlr9rq8h7+s6plUJ4QJYw2gCZodqmGw=="], + + "@typescript-eslint/utils": ["@typescript-eslint/utils@8.53.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", "@typescript-eslint/scope-manager": "8.53.0", "@typescript-eslint/types": "8.53.0", "@typescript-eslint/typescript-estree": "8.53.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-XDY4mXTez3Z1iRDI5mbRhH4DFSt46oaIFsLg+Zn97+sYrXACziXSQcSelMybnVZ5pa1P6xYkPr5cMJyunM1ZDA=="], + + "@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.53.0", "", { "dependencies": { "@typescript-eslint/types": "8.53.0", "eslint-visitor-keys": "^4.2.1" } }, "sha512-LZ2NqIHFhvFwxG0qZeLL9DvdNAHPGCY5dIRwBhyYeU+LfLhcStE1ImjsuTG/WaVh3XysGaeLW8Rqq7cGkPCFvw=="], + "@typespec/ts-http-runtime": ["@typespec/ts-http-runtime@0.3.2", "", { "dependencies": { "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.0", "tslib": "^2.6.2" } }, "sha512-IlqQ/Gv22xUC1r/WQm4StLkYQmaaTsXAhUVsNE0+xiyf0yRFiH5++q78U3bw6bLKDCTmh0uqKB9eG9+Bt75Dkg=="], + "acorn": ["acorn@8.15.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg=="], + + "acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="], + "agent-base": ["agent-base@7.1.4", "", {}, "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ=="], + "ajv": ["ajv@6.12.6", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g=="], + + "ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], + + "argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="], + "asynckit": ["asynckit@0.4.0", "", {}, "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="], + "balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], + + "brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="], + "bun": ["bun@1.3.6", "", { "optionalDependencies": { "@oven/bun-darwin-aarch64": "1.3.6", "@oven/bun-darwin-x64": "1.3.6", "@oven/bun-darwin-x64-baseline": "1.3.6", "@oven/bun-linux-aarch64": "1.3.6", "@oven/bun-linux-aarch64-musl": "1.3.6", "@oven/bun-linux-x64": "1.3.6", "@oven/bun-linux-x64-baseline": "1.3.6", "@oven/bun-linux-x64-musl": "1.3.6", "@oven/bun-linux-x64-musl-baseline": "1.3.6", "@oven/bun-windows-x64": "1.3.6", "@oven/bun-windows-x64-baseline": "1.3.6" }, "os": [ "linux", "win32", "darwin", ], "cpu": [ "x64", "arm64", ], "bin": { "bun": "bin/bun.exe", "bunx": "bin/bunx.exe" } }, "sha512-Tn98GlZVN2WM7+lg/uGn5DzUao37Yc0PUz7yzYHdeF5hd+SmHQGbCUIKE4Sspdgtxn49LunK3mDNBC2Qn6GJjw=="], "bun-types": ["bun-types@1.3.6", "", { "dependencies": { "@types/node": "*" } }, "sha512-OlFwHcnNV99r//9v5IIOgQ9Uk37gZqrNMCcqEaExdkVq3Avwqok1bJFmvGMCkCE0FqzdY8VMOZpfpR3lwI+CsQ=="], "call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="], + "callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="], + + "chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], + + "color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="], + + "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], + "combined-stream": ["combined-stream@1.0.8", "", { "dependencies": { "delayed-stream": "~1.0.0" } }, "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg=="], + "concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="], + + "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="], + "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], + "deep-is": ["deep-is@0.1.4", "", {}, "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="], + "delayed-stream": ["delayed-stream@1.0.0", "", {}, "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="], + "dom-walk": ["dom-walk@0.1.2", "", {}, "sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w=="], + "dotenv": ["dotenv@17.2.3", "", {}, "sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w=="], "dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="], @@ -108,6 +193,44 @@ "es-set-tostringtag": ["es-set-tostringtag@2.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA=="], + "escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="], + + "eslint": ["eslint@9.39.2", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.21.1", "@eslint/config-helpers": "^0.4.2", "@eslint/core": "^0.17.0", "@eslint/eslintrc": "^3.3.1", "@eslint/js": "9.39.2", "@eslint/plugin-kit": "^0.4.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^8.4.0", "eslint-visitor-keys": "^4.2.1", "espree": "^10.4.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw=="], + + "eslint-plugin-no-relative-import-paths": ["eslint-plugin-no-relative-import-paths@1.6.1", "", {}, "sha512-YZNeOnsOrJcwhFw0X29MXjIzu2P/f5X2BZDPWw1R3VUYBRFxNIh77lyoL/XrMU9ewZNQPcEvAgL/cBOT1P330A=="], + + "eslint-plugin-perfectionist": ["eslint-plugin-perfectionist@5.3.1", "", { "dependencies": { "@typescript-eslint/utils": "^8.52.0", "natural-orderby": "^5.0.0" }, "peerDependencies": { "eslint": ">=8.45.0" } }, "sha512-v8kAP8TarQYqDC4kxr343ZNi++/oOlBnmWovsUZpbJ7A/pq1VHGlgsf/fDh4CdEvEstzkrc8NLvoVKtfpsC4oA=="], + + "eslint-scope": ["eslint-scope@8.4.0", "", { "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg=="], + + "eslint-visitor-keys": ["eslint-visitor-keys@4.2.1", "", {}, "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ=="], + + "espree": ["espree@10.4.0", "", { "dependencies": { "acorn": "^8.15.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^4.2.1" } }, "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ=="], + + "esquery": ["esquery@1.7.0", "", { "dependencies": { "estraverse": "^5.1.0" } }, "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g=="], + + "esrecurse": ["esrecurse@4.3.0", "", { "dependencies": { "estraverse": "^5.2.0" } }, "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag=="], + + "estraverse": ["estraverse@5.3.0", "", {}, "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA=="], + + "esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="], + + "fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="], + + "fast-json-stable-stringify": ["fast-json-stable-stringify@2.1.0", "", {}, "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="], + + "fast-levenshtein": ["fast-levenshtein@2.0.6", "", {}, "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw=="], + + "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="], + + "file-entry-cache": ["file-entry-cache@8.0.0", "", { "dependencies": { "flat-cache": "^4.0.0" } }, "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ=="], + + "find-up": ["find-up@5.0.0", "", { "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" } }, "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng=="], + + "flat-cache": ["flat-cache@4.0.1", "", { "dependencies": { "flatted": "^3.2.9", "keyv": "^4.5.4" } }, "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw=="], + + "flatted": ["flatted@3.3.3", "", {}, "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg=="], + "form-data": ["form-data@4.0.5", "", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", "hasown": "^2.0.2", "mime-types": "^2.1.12" } }, "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w=="], "function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="], @@ -116,8 +239,16 @@ "get-proto": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="], + "glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="], + + "global": ["global@4.4.0", "", { "dependencies": { "min-document": "^2.19.0", "process": "^0.11.10" } }, "sha512-wv/LAoHdRE3BeTGz53FAamhGlPLhlssK45usmGFThIi4XqnBmjKQ16u+RNbP7WvigRZDxUsM0J3gcQ5yicaL0w=="], + + "globals": ["globals@14.0.0", "", {}, "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ=="], + "gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="], + "has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="], + "has-symbols": ["has-symbols@1.1.0", "", {}, "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="], "has-tostringtag": ["has-tostringtag@1.0.2", "", { "dependencies": { "has-symbols": "^1.0.3" } }, "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw=="], @@ -128,44 +259,136 @@ "https-proxy-agent": ["https-proxy-agent@7.0.6", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "4" } }, "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw=="], + "ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="], + + "import-fresh": ["import-fresh@3.3.1", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="], + + "imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="], + + "is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="], + + "is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="], + + "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], + + "js-yaml": ["js-yaml@4.1.1", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA=="], + + "json-buffer": ["json-buffer@3.0.1", "", {}, "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ=="], + + "json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="], + + "json-stable-stringify-without-jsonify": ["json-stable-stringify-without-jsonify@1.0.1", "", {}, "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw=="], + + "keyv": ["keyv@4.5.4", "", { "dependencies": { "json-buffer": "3.0.1" } }, "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw=="], + + "levn": ["levn@0.4.1", "", { "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" } }, "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ=="], + + "locate-path": ["locate-path@6.0.0", "", { "dependencies": { "p-locate": "^5.0.0" } }, "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="], + + "lodash.merge": ["lodash.merge@4.6.2", "", {}, "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="], + "math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="], "mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="], "mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="], + "min-document": ["min-document@2.19.2", "", { "dependencies": { "dom-walk": "^0.1.0" } }, "sha512-8S5I8db/uZN8r9HSLFVWPdJCvYOejMcEC82VIzNUc6Zkklf/d1gg2psfE79/vyhWOj4+J8MtwmoOz3TmvaGu5A=="], + + "minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="], + "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + "natural-compare": ["natural-compare@1.4.0", "", {}, "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="], + + "natural-orderby": ["natural-orderby@5.0.0", "", {}, "sha512-kKHJhxwpR/Okycz4HhQKKlhWe4ASEfPgkSWNmKFHd7+ezuQlxkA5cM3+XkBPvm1gmHen3w53qsYAv+8GwRrBlg=="], + "node-fetch": ["node-fetch@2.7.0", "", { "dependencies": { "whatwg-url": "^5.0.0" }, "peerDependencies": { "encoding": "^0.1.0" }, "optionalPeers": ["encoding"] }, "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A=="], + "optionator": ["optionator@0.9.4", "", { "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", "word-wrap": "^1.2.5" } }, "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g=="], + + "p-limit": ["p-limit@3.1.0", "", { "dependencies": { "yocto-queue": "^0.1.0" } }, "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="], + + "p-locate": ["p-locate@5.0.0", "", { "dependencies": { "p-limit": "^3.0.2" } }, "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw=="], + + "parent-module": ["parent-module@1.0.1", "", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="], + + "path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="], + + "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], + + "picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="], + + "prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="], + "process": ["process@0.11.10", "", {}, "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A=="], + "punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="], + + "resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="], + "sax": ["sax@1.4.4", "", {}, "sha512-1n3r/tGXO6b6VXMdFT54SHzT9ytu9yr7TaELowdYpMqY/Ao7EnlQGmAQ1+RatX7Tkkdm6hONI2owqNx2aZj5Sw=="], + "semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="], + + "shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="], + + "shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="], + + "strip-json-comments": ["strip-json-comments@3.1.1", "", {}, "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="], + + "supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], + "tinyduration": ["tinyduration@3.4.1", "", {}, "sha512-NemFoamVYn7TmtwZKZ3OiliM9fZkr6EWiTM+wKknco6POSy2gS689xx/pXip0JYp40HXpUw6k65CUYHWYUXdaA=="], + "tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="], + "tr46": ["tr46@0.0.3", "", {}, "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="], + "ts-api-utils": ["ts-api-utils@2.4.0", "", { "peerDependencies": { "typescript": ">=4.8.4" } }, "sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA=="], + "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], "tunnel": ["tunnel@0.0.6", "", {}, "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg=="], + "type-check": ["type-check@0.4.0", "", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="], + + "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], + + "typescript-eslint": ["typescript-eslint@8.53.0", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.53.0", "@typescript-eslint/parser": "8.53.0", "@typescript-eslint/typescript-estree": "8.53.0", "@typescript-eslint/utils": "8.53.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-xHURCQNxZ1dsWn0sdOaOfCSQG0HKeqSj9OexIxrz6ypU6wHYOdX2I3D2b8s8wFSsSOYJb+6q283cLiLlkEsBYw=="], + "undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="], + "uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="], + "uuid": ["uuid@11.1.0", "", { "bin": { "uuid": "dist/esm/bin/uuid" } }, "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A=="], "webidl-conversions": ["webidl-conversions@3.0.1", "", {}, "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="], "whatwg-url": ["whatwg-url@5.0.0", "", { "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" } }, "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw=="], + "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], + + "word-wrap": ["word-wrap@1.2.5", "", {}, "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="], + "xml2js": ["xml2js@0.5.0", "", { "dependencies": { "sax": ">=0.6.0", "xmlbuilder": "~11.0.0" } }, "sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA=="], "xmlbuilder": ["xmlbuilder@11.0.1", "", {}, "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA=="], + "yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="], + "@azure/core-auth/@azure/abort-controller": ["@azure/abort-controller@2.1.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-nBrLsEWm4J2u5LpAPjxADTlq3trDgVZZXHNKabeXZtpq3d3AbN/KGO82R87rdDz5/lYB024rtEf10/q0urNgsA=="], "@azure/core-http/uuid": ["uuid@8.3.2", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="], "@azure/core-util/@azure/abort-controller": ["@azure/abort-controller@2.1.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-nBrLsEWm4J2u5LpAPjxADTlq3trDgVZZXHNKabeXZtpq3d3AbN/KGO82R87rdDz5/lYB024rtEf10/q0urNgsA=="], + + "@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="], + + "@typescript-eslint/eslint-plugin/ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="], + + "@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="], + + "@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="], } } diff --git a/.github/scripts/eslint.config.js b/.github/scripts/eslint.config.js new file mode 100644 index 000000000..05a3bde39 --- /dev/null +++ b/.github/scripts/eslint.config.js @@ -0,0 +1,52 @@ +import js from "@eslint/js"; +import noRelativeImportPaths from "eslint-plugin-no-relative-import-paths"; +import perfectionist from "eslint-plugin-perfectionist"; +import globals from "globals"; +import tseslint from "typescript-eslint"; + +export default tseslint.config( + { + plugins: { + perfectionist, + }, + rules: { + "perfectionist/sort-imports": "error", + }, + }, + { ignores: ["dist"] }, + { + extends: [js.configs.recommended, ...tseslint.configs.recommended], + files: ["**/*.{ts,tsx}"], + languageOptions: { + ecmaVersion: 2020, + globals: globals.browser, + }, + plugins: { + "no-relative-import-paths": noRelativeImportPaths, + }, + settings: { + "import/resolver": { + alias: { + map: [["@", "./src"]], + extensions: [".ts", ".tsx", ".js", ".jsx"], + }, + }, + }, + rules: { + "no-relative-import-paths/no-relative-import-paths": [ + "error", + { allowSameFolder: false, rootDir: "src", prefix: "@" }, + ], + "@typescript-eslint/no-namespace": ["off"], + "@typescript-eslint/no-non-null-assertion": ["error"], + "@typescript-eslint/no-unused-vars": [ + "error", + { + argsIgnorePattern: "^_", + varsIgnorePattern: "^_", + caughtErrorsIgnorePattern: "^_", + }, + ], + }, + }, +); diff --git a/.github/scripts/package.json b/.github/scripts/package.json index 73b5407fc..d7bdec17a 100644 --- a/.github/scripts/package.json +++ b/.github/scripts/package.json @@ -11,6 +11,11 @@ "bun": "^1.3.6" }, "devDependencies": { - "@types/bun": "^1.3.6" + "@eslint/js": "^9.39.2", + "@types/bun": "^1.3.6", + "eslint-plugin-no-relative-import-paths": "^1.6.1", + "eslint-plugin-perfectionist": "^5.3.1", + "global": "^4.4.0", + "typescript-eslint": "^8.53.0" } } diff --git a/.github/scripts/redeploy/apps/create.ts b/.github/scripts/redeploy/apps/create.ts index b25f6a984..fc9d65b28 100644 --- a/.github/scripts/redeploy/apps/create.ts +++ b/.github/scripts/redeploy/apps/create.ts @@ -1,6 +1,5 @@ import type { App_spec } from "@digitalocean/dots"; import type { DigitalOceanClient } from "@digitalocean/dots/src/dots/digitalOceanClient"; -import type { Environment } from "redeploy/types"; export async function _createAppAndgetAppId( client: DigitalOceanClient, diff --git a/.github/scripts/redeploy/db/index.ts b/.github/scripts/redeploy/db/index.ts index e839bd09f..fd05f4629 100644 --- a/.github/scripts/redeploy/db/index.ts +++ b/.github/scripts/redeploy/db/index.ts @@ -1,6 +1,8 @@ -import { $ } from "bun"; import type { Environment } from "redeploy/types"; +import { $ } from "bun"; + export async function _migrateDb(environment: Environment) { + environment.toUpperCase(); await $`git fetch origin main:main`; } diff --git a/.github/scripts/redeploy/index.ts b/.github/scripts/redeploy/index.ts index 9a3c66e87..bfc03fa53 100644 --- a/.github/scripts/redeploy/index.ts +++ b/.github/scripts/redeploy/index.ts @@ -1,4 +1,5 @@ -import { $ } from "bun"; +import type { Environment } from "redeploy/types"; + import { createDigitalOceanClient, DigitalOceanApiKeyAuthenticationProvider, @@ -6,13 +7,13 @@ import { type App_response, type App_variable_definition, } from "@digitalocean/dots"; -import { prodSpec, stgSpec } from "../../../.do/specs"; import { getEnvVariables } from "load-secrets/env/load"; -import type { Environment } from "redeploy/types"; import { _createAppAndgetAppId } from "redeploy/apps/create"; import { _getAppId } from "redeploy/apps/get"; import { _migrateDb } from "redeploy/db"; +import { prodSpec, stgSpec } from "../../../.do/specs"; + const projectId = (() => { const v = process.env.DIGITALOCEAN_PROJECT_ID; if (!v) { diff --git a/.github/scripts/run-backend-tests.ts b/.github/scripts/run-backend-tests.ts index 0c2389d98..3f6d253ea 100644 --- a/.github/scripts/run-backend-tests.ts +++ b/.github/scripts/run-backend-tests.ts @@ -1,5 +1,5 @@ import { $ } from "bun"; -import { db } from "./fn/run-local-db"; +import { db } from "utils/run-local-db"; async function main() { try { diff --git a/.github/scripts/run-frontend-tests.ts b/.github/scripts/run-frontend-tests.ts index fee5175f4..53d0c4a80 100644 --- a/.github/scripts/run-frontend-tests.ts +++ b/.github/scripts/run-frontend-tests.ts @@ -1,6 +1,6 @@ import { $ } from "bun"; -import { db } from "./fn/run-local-db"; -import { backend } from "./fn/run-backend-instance"; +import { backend } from "utils/run-backend-instance"; +import { db } from "utils/run-local-db"; async function main() { try { diff --git a/js/src/app/_component/AboutUs.tsx b/js/src/app/_component/AboutUs.tsx index a8c7af7ac..6c9fb3583 100644 --- a/js/src/app/_component/AboutUs.tsx +++ b/js/src/app/_component/AboutUs.tsx @@ -45,7 +45,7 @@ export default function AboutUs() { size={"xl"} m={"md"} > - Celebrating CodeBloom's 1 Year Anniversary! 🎉 + Greetings from Bun Shell! </Badge> <Title order={3} style={{ color: "#4cffb0", alignItems: "center" }}> Level Up Your Coding with From 26fb04497101025a21cea19a82d45793d5c3c914 Mon Sep 17 00:00:00 2001 From: Tahmid Ahmed <tahmid@tahmid.io> Date: Sun, 18 Jan 2026 19:22:45 -0500 Subject: [PATCH 037/101] 641: --- .github/scripts/build-image.ts | 4 ++-- .github/scripts/redeploy/index.ts | 4 ++++ .github/workflows/deploy-stg.yml | 14 +++++++------- 3 files changed, 13 insertions(+), 9 deletions(-) diff --git a/.github/scripts/build-image.ts b/.github/scripts/build-image.ts index 252ee6971..9cac4e110 100644 --- a/.github/scripts/build-image.ts +++ b/.github/scripts/build-image.ts @@ -1,6 +1,6 @@ import { $ } from "bun"; -import { db } from "./fn/run-local-db"; -import { backend } from "./fn/run-backend-instance"; +import { backend } from "utils/run-backend-instance"; +import { db } from "utils/run-local-db"; process.env.TZ = "America/New_York"; diff --git a/.github/scripts/redeploy/index.ts b/.github/scripts/redeploy/index.ts index bfc03fa53..ef5c89deb 100644 --- a/.github/scripts/redeploy/index.ts +++ b/.github/scripts/redeploy/index.ts @@ -102,6 +102,8 @@ async function main() { process.exit(1); } + console.log("Deployment ID:", pendingDeploymentId); + let ready = false; const attempts = 60; @@ -111,6 +113,8 @@ async function main() { .deployments.byDeployment_id(pendingDeploymentId) .get(); + console.log("debug, dont keep in", res?.deployment); + const phase = res?.deployment?.phase ?? "UNKNOWN"; console.log("Deployment phase:", phase); diff --git a/.github/workflows/deploy-stg.yml b/.github/workflows/deploy-stg.yml index e6f5754ae..7850f1236 100644 --- a/.github/workflows/deploy-stg.yml +++ b/.github/workflows/deploy-stg.yml @@ -247,13 +247,13 @@ jobs: with: ref: ${{ needs.getPRHead.outputs.sha }} - # - name: Run workflow - # uses: ./.github/composite/build-image - # with: - # GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }} - # GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} - # TAG_PREFIX: staging- - # SERVER_PROFILES: stg + - name: Run workflow + uses: ./.github/composite/build-image + with: + GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }} + GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} + TAG_PREFIX: staging- + SERVER_PROFILES: stg redeploy: name: Redeploy on DigitalOcean From efcd4c12ef412e600d1861fdecc83e0ff1631f0e Mon Sep 17 00:00:00 2001 From: Tahmid Ahmed <tahmid@tahmid.io> Date: Sun, 18 Jan 2026 19:32:24 -0500 Subject: [PATCH 038/101] 641: --- .github/scripts/redeploy/index.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/scripts/redeploy/index.ts b/.github/scripts/redeploy/index.ts index ef5c89deb..190d2dadb 100644 --- a/.github/scripts/redeploy/index.ts +++ b/.github/scripts/redeploy/index.ts @@ -113,8 +113,6 @@ async function main() { .deployments.byDeployment_id(pendingDeploymentId) .get(); - console.log("debug, dont keep in", res?.deployment); - const phase = res?.deployment?.phase ?? "UNKNOWN"; console.log("Deployment phase:", phase); From ea01e493f99e56caca711ea1866ec023367431ef Mon Sep 17 00:00:00 2001 From: Tahmid Ahmed <tahmid@tahmid.io> Date: Sun, 18 Jan 2026 19:45:11 -0500 Subject: [PATCH 039/101] 641: --- .env.ci | Bin 879 -> 649 bytes .github/composite/redeploy/action.yml | 2 -- .github/scripts/redeploy/apps/get.ts | 18 ++++++------------ .github/scripts/redeploy/index.ts | 2 +- 4 files changed, 7 insertions(+), 15 deletions(-) diff --git a/.env.ci b/.env.ci index 4db3841ae0102c795d33a66bca4129db2c855091..15a6003e2e74ce075e6b9153e11e3c455e55bbe2 100644 GIT binary patch literal 649 zcmV;40(SiXM@dveQdv+`01FMU9uv0kr#cM_yZu;QrV~Pfn<T-TX(sKYv-@V<MS0G} zT9we*T%tXB$mg344$yGjiwSOwfh&=7m8rM67y+I>Z+_}*jG(@**%%NVNY2Y164tbn zxXM28{WuY61}3MN`?9rVXwhbnw6>Yu<50ByeO;!R5hL1q23O)}@dH=9optg?z=n53 zaMa68sIFC(ID;PM8h6|g0VktKL=<sj0SHhcT>{Qo!iTK)Cwiwf?PLnJtq4NboFVP} zEtxgDR#`PakN=zgconOThcdh;3vOKKnsCGV!8N8+mAdkQD!)sHAA{`3*w%uvU=;>G z;K;!i?d=Te8IAv=C=pL_Su<;4l)953zMBmhpN94Fz6!C%g3?sq-LWE|xlHxyc4y9M z-Pf2tk%5Ouxj#29n7LKxb$s}6ku6w_W_LqpEU*Da_PSo{-&ifBPU_JzIoxO4!(Yr) zWFU4R(QkFA{Lu;Pqu@LR6ES?^6LVc$JWetoB$5c3dgU&Guu=+Th)2dVUX#P@182<Z zrHMCSV`|j!5DPuy$Zvp}(a!DFh{V|4M}A;kCKYZN%mK+2v#`p-;@n#DfZMdQB$E6V z2IN<}B!rrNQ<D=PKnLXvSqK1fB79%0v>G(jnhLN^7qy*06%RAQ%6wf6gcj?ym8m4N zP5OM9Z23SR3fuo!{^_UyIE^+BbIl{;5jkM$(Ugb>8{pfmIX-d)mDvX``@lJ?hg;2% z)eOSuO(OZ_{3$@<X5zjveieXel|h65hrd*3s;mgQ8T%Tpg2LoPUeLnZ5=>y<r~+Ns jLfOqIyQEdK(!TAeQ~ruB&*jTAOyxS!TEhC#*qpOg_C`b^ literal 879 zcmV-#1CaaxM@dveQdv+`0NoX=nI#D!h=9BHrsyKCJYliMf4!&7E5)P1L#`H^?ey-B zrdKIq(HlFrTcc}5GH{Y?Acdn7l$2PdjqfTZ5wT0a%|M^p!|jQnq3m7gWr73DN?sek zkcXuXOGS)JERbwa+R@sTWG)Jz!RsX3N$dKQgmw~F5CMrx^+IRe#{SYDH+GdR>3&wK zZFykNVn(kaqFAd|jAQ=9#iVQO<+Oo?+JcE(>sQth!PA)7EWzOtmNvIZ*q3f+=^;z4 ztU-Pi3%K4jz5T{zGjxaLs2slSK$pCpzHqFEU+pUK3NH}KazSm|MR;a?Lc?>^Kc+E{ ze_}&Zk8g*2KrC)%cH}Ri%R3)2<|hiV$ssd!?7g!83);A&(TLUiNgyLT1SC^9Kr=UN zNAY{!Iih|y=!60~nAdPBZ>;>&(3UKMdXWh{${Y>~4Ac@(yuzy9Me%}NH5?`)(#{wT z@77~GZ4WJR(?~$Qx$C4P>L{Z0MmHKK19CheEVtEWo>2rOOSFDw^vBEGQl-N(_?xH4 zE6ayx%B^QY&xpVw5FLD?W7J1|>pq_Za5yBrV|1Ag)4uRHJQ)B13?gF*J_{o2F;bTC zYhIgm+Ep#bFT{ql8poU*Bo>E@2%~bh7%r3bG|(%29XX-L$FQ_N6RFX0OHNCJ`9f(G zQp9`jlQ8Fykt?hxX;VX0$;^?CUEckOlP3Qfw#NeJ-k^}7h4*9+dxKz{R`&XK@s0|Z z$5!4DTl<9^K$Ja0Uj6i-=r(E{xn!1Ul1(<p4_p|POq^M^TRKn;8%;qPn2OsNj2$0i z?GjCT-($HqNcnh@w}4E1Jx>4Xv-=E1!;w{<$r4#7-fRkkM?`VbJD#A&WsTTb`8>uz z&o%MPcf#YKZQHmJ{8bhAyc;j#;tA@#>G!@LkLUN%p8?kN%Nl57_hr>&W73SS+XLD> z4QO(^ktYfkl;@Wq)}qYRZbjqhU1;ou7yE5qTsK4@zu`LwT8Oaoreqyq(J}MJFBIA! zTr<w=m|FzK_WFtbktG0)!bIs<e)TyH-c8%}K~tF0+`0#WyIyyWm56sU-oKPh{Aaxv zTY#jS_ao5S65F*aWWM(Sqk%N@$UW(ONLQ6I`W@L;-gc=Mh~F|tz@Onyk!UM&(Nm^_ FI70{Uv@rkx diff --git a/.github/composite/redeploy/action.yml b/.github/composite/redeploy/action.yml index d0036d47e..aee88b144 100644 --- a/.github/composite/redeploy/action.yml +++ b/.github/composite/redeploy/action.yml @@ -69,5 +69,3 @@ runs: TAG_PREFIX: ${{ inputs.TAG_PREFIX }} SERVER_PROFILES: ${{ inputs.SERVER_PROFILES }} ENVIRONMENT: ${{ inputs.ENVIRONMENT }} - DIGITALOCEAN_PROJECT_ID: ${{ inputs.ENVIRONMENT == 'staging' && env.STG_DIGITAL_OCEAN_PROJECT_ID || env.PROD_DIGITAL_OCEAN_PROJECT_ID }} - DIGITALOCEAN_PAT: ${{ inputs.ENVIRONMENT == 'staging' && env.STG_DIGITAL_OCEAN_PAT || env.PROD_DIGITAL_OCEAN_PAT }} diff --git a/.github/scripts/redeploy/apps/get.ts b/.github/scripts/redeploy/apps/get.ts index 95cb799c5..be43a1c05 100644 --- a/.github/scripts/redeploy/apps/get.ts +++ b/.github/scripts/redeploy/apps/get.ts @@ -1,22 +1,16 @@ +import type { App_spec } from "@digitalocean/dots"; import type { DigitalOceanClient } from "@digitalocean/dots/src/dots/digitalOceanClient"; -import type { Environment } from "redeploy/types"; export async function _getAppId( client: DigitalOceanClient, - environment: Environment, projectId: string, + spec: App_spec, ): Promise<string | null> { - const appName = (() => { - if (environment === "staging") { - return "codebloom-staging"; - } + const appName = spec.name; - if (environment === "production") { - return "codebloom-prod"; - } - - throw new Error("This environment is not currently supported"); - })(); + if (!appName) { + throw new Error("App spec name missing, can't find app"); + } const res = await client.v2.apps.get({ queryParameters: { diff --git a/.github/scripts/redeploy/index.ts b/.github/scripts/redeploy/index.ts index 190d2dadb..6a2b51e29 100644 --- a/.github/scripts/redeploy/index.ts +++ b/.github/scripts/redeploy/index.ts @@ -68,7 +68,7 @@ async function main() { const spec = environment === "staging" ? stgSpec(envs) : prodSpec(envs); const appId = await (async () => { - const v = await _getAppId(client, environment, projectId); + const v = await _getAppId(client, projectId, spec); if (v) { return v; From 77f49b7cff94442c45748d9c9a5b68c6711e1112 Mon Sep 17 00:00:00 2001 From: Tahmid Ahmed <tahmid@tahmid.io> Date: Sun, 18 Jan 2026 19:46:27 -0500 Subject: [PATCH 040/101] 641: --- .env.ci | Bin 649 -> 649 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/.env.ci b/.env.ci index 15a6003e2e74ce075e6b9153e11e3c455e55bbe2..87d77dfc1560045c0925e3ff220a27d92ebb102d 100644 GIT binary patch literal 649 zcmV;40(SiXM@dveQdv+`05@^-V|&M;r{-cxnUX6%k+_7vnm>98G*t}iCnG=Y41!&T zS3{lMiC^mQmNsl*B>_`k_3!G=DVM+bhD{Q~P`MbxKT88m|NiXLN!Wggs=A(iv=Y!t zza7O)b-P}#^>?qvLJ`*qz2m8cmASU)UT3~jFhxvk_>&(zco4GA-Z~R$GL<7-@;K-A zuiM_b)dW+^;YdnsU%;3t#6LO7eECJXL!H!Sn>~?@;0e?nX|>ThWAuuhGEnvs+GHpc zH9T^7v9~e-Fm<tSr)b)%v8{vtX2o3!ZFgL?tF0OegrpzYWnhq4Vtg|QjN+L$ytG`W zuyfoSBf)tNN(y7j1yKi?#}HmEh0@q~*i!sQ3)TcshCj4_jh(pjCC}LFcZvG2``#N@ zSFpI!^CR-)U(AjbxL@1Q4T>gLp_BjoS|WxFBeAGYea8A&WTLJj<swSU{9511D}3Pi zE296;b9;9Q&;vfLwNiYqRq3i?*MlTp{dDS_{n=x|cT^-7s7ir~(|pYpD%_|Pb%3&K zlo_~pPJWoC-DcC|3fptDsPh?tXsaHuS`Mo$*W!QG-n)v$T%Wbzk;!zJ;%}c`9Xa1# zrgnsGmdbn{bs%45$THp-R%L;uZIzeoxI2B2r8lYVO6OrO>oFzfwe0-Jy3>@}j}3xf zvuVucPV7)Ys=@u=UPQgt@Tco=5|mz;eeuhtATnCSE_sUbGvZMC^RFV2n<p2rdpS)x z5eXZ;K=I7_#i_0Bw{L5Kz+(wV=-N0yI7?+d6_W|GnmbxP;T+chj;4X8_tu(Wm;P4~ jKT$_+$1JccY(9?wcMn&U&h48K(-u+yop%_HncrzUx{*u0 literal 649 zcmV;40(SiXM@dveQdv+`01FMU9uv0kr#cM_yZu;QrV~Pfn<T-TX(sKYv-@V<MS0G} zT9we*T%tXB$mg344$yGjiwSOwfh&=7m8rM67y+I>Z+_}*jG(@**%%NVNY2Y164tbn zxXM28{WuY61}3MN`?9rVXwhbnw6>Yu<50ByeO;!R5hL1q23O)}@dH=9optg?z=n53 zaMa68sIFC(ID;PM8h6|g0VktKL=<sj0SHhcT>{Qo!iTK)Cwiwf?PLnJtq4NboFVP} zEtxgDR#`PakN=zgconOThcdh;3vOKKnsCGV!8N8+mAdkQD!)sHAA{`3*w%uvU=;>G z;K;!i?d=Te8IAv=C=pL_Su<;4l)953zMBmhpN94Fz6!C%g3?sq-LWE|xlHxyc4y9M z-Pf2tk%5Ouxj#29n7LKxb$s}6ku6w_W_LqpEU*Da_PSo{-&ifBPU_JzIoxO4!(Yr) zWFU4R(QkFA{Lu;Pqu@LR6ES?^6LVc$JWetoB$5c3dgU&Guu=+Th)2dVUX#P@182<Z zrHMCSV`|j!5DPuy$Zvp}(a!DFh{V|4M}A;kCKYZN%mK+2v#`p-;@n#DfZMdQB$E6V z2IN<}B!rrNQ<D=PKnLXvSqK1fB79%0v>G(jnhLN^7qy*06%RAQ%6wf6gcj?ym8m4N zP5OM9Z23SR3fuo!{^_UyIE^+BbIl{;5jkM$(Ugb>8{pfmIX-d)mDvX``@lJ?hg;2% z)eOSuO(OZ_{3$@<X5zjveieXel|h65hrd*3s;mgQ8T%Tpg2LoPUeLnZ5=>y<r~+Ns jLfOqIyQEdK(!TAeQ~ruB&*jTAOyxS!TEhC#*qpOg_C`b^ From 0da982a8bbb9ea22aae3644e04900756d19afc43 Mon Sep 17 00:00:00 2001 From: Tahmid Ahmed <tahmid@tahmid.io> Date: Sun, 18 Jan 2026 20:00:04 -0500 Subject: [PATCH 041/101] 641: --- .github/composite/redeploy/action.yml | 4 + .github/scripts/redeploy/db/index.ts | 31 ++++- .../scripts/redeploy/{ => do}/apps/create.ts | 0 .github/scripts/redeploy/{ => do}/apps/get.ts | 0 .github/scripts/redeploy/do/index.ts | 109 ++++++++++++++++++ .github/scripts/redeploy/index.ts | 107 +++-------------- 6 files changed, 156 insertions(+), 95 deletions(-) rename .github/scripts/redeploy/{ => do}/apps/create.ts (100%) rename .github/scripts/redeploy/{ => do}/apps/get.ts (100%) create mode 100644 .github/scripts/redeploy/do/index.ts diff --git a/.github/composite/redeploy/action.yml b/.github/composite/redeploy/action.yml index aee88b144..609f133b0 100644 --- a/.github/composite/redeploy/action.yml +++ b/.github/composite/redeploy/action.yml @@ -12,6 +12,9 @@ inputs: description: '"staging" or "production"' required: false default: production + SHA: + description: "(Staging only). Current commit SHA" + required: false runs: using: "composite" @@ -69,3 +72,4 @@ runs: TAG_PREFIX: ${{ inputs.TAG_PREFIX }} SERVER_PROFILES: ${{ inputs.SERVER_PROFILES }} ENVIRONMENT: ${{ inputs.ENVIRONMENT }} + SHA: ${{ inputs.SHA }} diff --git a/.github/scripts/redeploy/db/index.ts b/.github/scripts/redeploy/db/index.ts index fd05f4629..8f0db3217 100644 --- a/.github/scripts/redeploy/db/index.ts +++ b/.github/scripts/redeploy/db/index.ts @@ -2,7 +2,34 @@ import type { Environment } from "redeploy/types"; import { $ } from "bun"; -export async function _migrateDb(environment: Environment) { - environment.toUpperCase(); +export async function _migrateDb({ + environment, + envVariables, + sha, +}: { + environment: Environment; + envVariables: Map<string, string>; + sha?: string; +}): Promise<void> { await $`git fetch origin main:main`; + + if (environment === "staging") { + if (!sha) { + throw new Error( + "SHA must be available in ENV if script is being run in staging environment.", + ); + } + const diffOutput = await $`git diff --name-only main...${sha}`.text(); + const files = diffOutput.split("\n"); + + const hasDbChanges = files.some((file) => file.startsWith("db/")); + if (!hasDbChanges) { + console.log("in staging, skipping db migration."); + return; + } + } + + await $.env( + Object.fromEntries(envVariables), + )`./mvnw flyway:migrate -Dflyway.locations=filesystem:./db/migration`; } diff --git a/.github/scripts/redeploy/apps/create.ts b/.github/scripts/redeploy/do/apps/create.ts similarity index 100% rename from .github/scripts/redeploy/apps/create.ts rename to .github/scripts/redeploy/do/apps/create.ts diff --git a/.github/scripts/redeploy/apps/get.ts b/.github/scripts/redeploy/do/apps/get.ts similarity index 100% rename from .github/scripts/redeploy/apps/get.ts rename to .github/scripts/redeploy/do/apps/get.ts diff --git a/.github/scripts/redeploy/do/index.ts b/.github/scripts/redeploy/do/index.ts new file mode 100644 index 000000000..0129e1cf1 --- /dev/null +++ b/.github/scripts/redeploy/do/index.ts @@ -0,0 +1,109 @@ +import type { Environment } from "redeploy/types"; + +import { + DigitalOceanApiKeyAuthenticationProvider, + FetchRequestAdapter, + createDigitalOceanClient, + type App_variable_definition, + type App_response, +} from "@digitalocean/dots"; +import { _createAppAndgetAppId } from "redeploy/do/apps/create"; +import { _getAppId } from "redeploy/do/apps/get"; + +import { prodSpec, stgSpec } from "../../../../.do/specs"; + +export async function _migrateDo({ + token, + environment, + projectId, + envVariables, +}: { + token: string; + environment: Environment; + projectId: string; + envVariables: Map<string, string>; +}): Promise<void> { + const authProvider = new DigitalOceanApiKeyAuthenticationProvider(token); + const adapter = new FetchRequestAdapter(authProvider); + const client = createDigitalOceanClient(adapter); + + const envs: App_variable_definition[] = envVariables + .entries() + .map(([key, value]) => { + const env: App_variable_definition = { + key, + value, + scope: "RUN_TIME", + type: "SECRET", + }; + return env; + }) + .toArray(); + + const spec = environment === "staging" ? stgSpec(envs) : prodSpec(envs); + + const appId = await (async () => { + const v = await _getAppId(client, projectId, spec); + + if (v) { + return v; + } + + const v2 = await _createAppAndgetAppId(client, projectId, spec); + if (!v2) { + throw new Error( + "App not found and failed to create as well. Please alert Codebloom team.", + ); + } + + return v2; + })(); + + let res: App_response | undefined; + try { + res = await client.v2.apps.byApp_Id(appId).put({ + spec, + updateAllSourceVersions: true, + }); + } catch (e) { + console.error(e); + return process.exit(1); + } + + const pendingDeploymentId = res?.app?.pendingDeployment?.id; + + if (!pendingDeploymentId) { + console.error("Failed to find pending deployment."); + process.exit(1); + } + + console.log("Deployment ID:", pendingDeploymentId); + + let ready = false; + const attempts = 60; + + for (let i = 1; i <= attempts; i++) { + const res = await client.v2.apps + .byApp_Id(appId) + .deployments.byDeployment_id(pendingDeploymentId) + .get(); + + const phase = res?.deployment?.phase ?? "UNKNOWN"; + + console.log("Deployment phase:", phase); + + if (phase === "ACTIVE") { + console.log("Deployment has completed!"); + ready = true; + break; + } + + console.log(`Waiting for deployment to complete... (${i}/${attempts})`); + await Bun.sleep(10000); + } + + if (!ready) { + console.error("Deployment did not reach a valid state within 10 minutes."); + process.exit(1); + } +} diff --git a/.github/scripts/redeploy/index.ts b/.github/scripts/redeploy/index.ts index 6a2b51e29..ba5842c0a 100644 --- a/.github/scripts/redeploy/index.ts +++ b/.github/scripts/redeploy/index.ts @@ -1,18 +1,10 @@ import type { Environment } from "redeploy/types"; -import { - createDigitalOceanClient, - DigitalOceanApiKeyAuthenticationProvider, - FetchRequestAdapter, - type App_response, - type App_variable_definition, -} from "@digitalocean/dots"; import { getEnvVariables } from "load-secrets/env/load"; -import { _createAppAndgetAppId } from "redeploy/apps/create"; -import { _getAppId } from "redeploy/apps/get"; import { _migrateDb } from "redeploy/db"; +import { _migrateDo } from "redeploy/do"; -import { prodSpec, stgSpec } from "../../../.do/specs"; +const sha = process.env.SHA; const projectId = (() => { const v = process.env.DIGITALOCEAN_PROJECT_ID; @@ -44,93 +36,22 @@ const environment: Environment = (() => { })(); async function main() { - const authProvider = new DigitalOceanApiKeyAuthenticationProvider(token); - const adapter = new FetchRequestAdapter(authProvider); - const client = createDigitalOceanClient(adapter); - // should already be called // await $`git-crypt unlock`; - const loaded = await getEnvVariables([environment]); - - const envs: App_variable_definition[] = loaded - .entries() - .map(([key, value]) => { - const env: App_variable_definition = { - key, - value, - scope: "RUN_TIME", - type: "SECRET", - }; - return env; - }) - .toArray(); - - const spec = environment === "staging" ? stgSpec(envs) : prodSpec(envs); - - const appId = await (async () => { - const v = await _getAppId(client, projectId, spec); - - if (v) { - return v; - } - - const v2 = await _createAppAndgetAppId(client, projectId, spec); - if (!v2) { - throw new Error( - "App not found and failed to create as well. Please alert Codebloom team.", - ); - } - - return v2; - })(); + const envVariables = await getEnvVariables([environment]); - let res: App_response | undefined; - try { - res = await client.v2.apps.byApp_Id(appId).put({ - spec, - updateAllSourceVersions: true, - }); - } catch (e) { - console.error(e); - return process.exit(1); - } - - const pendingDeploymentId = res?.app?.pendingDeployment?.id; - - if (!pendingDeploymentId) { - console.error("Failed to find pending deployment."); - process.exit(1); - } - - console.log("Deployment ID:", pendingDeploymentId); - - let ready = false; - const attempts = 60; - - for (let i = 1; i <= attempts; i++) { - const res = await client.v2.apps - .byApp_Id(appId) - .deployments.byDeployment_id(pendingDeploymentId) - .get(); - - const phase = res?.deployment?.phase ?? "UNKNOWN"; - - console.log("Deployment phase:", phase); - - if (phase === "ACTIVE") { - console.log("Deployment has completed!"); - ready = true; - break; - } - - console.log(`Waiting for deployment to complete... (${i}/${attempts})`); - await Bun.sleep(10000); - } + await _migrateDb({ + envVariables, + environment, + sha, + }); - if (!ready) { - console.error("Deployment did not reach a valid state within 10 minutes."); - process.exit(1); - } + await _migrateDo({ + projectId, + token, + environment, + envVariables, + }); } main() From 4123ba63289ce2e060ab481edea7b0240e33823a Mon Sep 17 00:00:00 2001 From: Tahmid Ahmed <tahmid@tahmid.io> Date: Sun, 18 Jan 2026 20:16:50 -0500 Subject: [PATCH 042/101] 641: --- .github/scripts/load-secrets/env/load.ts | 2 +- .github/scripts/redeploy/db/index.ts | 5 ----- .github/scripts/redeploy/index.ts | 17 ++++++++++++++--- .github/workflows/deploy-stg.yml | 1 + 4 files changed, 16 insertions(+), 9 deletions(-) diff --git a/.github/scripts/load-secrets/env/load.ts b/.github/scripts/load-secrets/env/load.ts index 7688eba86..081d3a7ee 100644 --- a/.github/scripts/load-secrets/env/load.ts +++ b/.github/scripts/load-secrets/env/load.ts @@ -45,7 +45,7 @@ export async function getEnvVariables( } } } else { - console.log(`Warning: ${envFile} not found`); + console.warn(`Warning: ${envFile} not found`); } } diff --git a/.github/scripts/redeploy/db/index.ts b/.github/scripts/redeploy/db/index.ts index 8f0db3217..a8c5b78a6 100644 --- a/.github/scripts/redeploy/db/index.ts +++ b/.github/scripts/redeploy/db/index.ts @@ -14,11 +14,6 @@ export async function _migrateDb({ await $`git fetch origin main:main`; if (environment === "staging") { - if (!sha) { - throw new Error( - "SHA must be available in ENV if script is being run in staging environment.", - ); - } const diffOutput = await $`git diff --name-only main...${sha}`.text(); const files = diffOutput.split("\n"); diff --git a/.github/scripts/redeploy/index.ts b/.github/scripts/redeploy/index.ts index ba5842c0a..26e21ffc6 100644 --- a/.github/scripts/redeploy/index.ts +++ b/.github/scripts/redeploy/index.ts @@ -4,8 +4,6 @@ import { getEnvVariables } from "load-secrets/env/load"; import { _migrateDb } from "redeploy/db"; import { _migrateDo } from "redeploy/do"; -const sha = process.env.SHA; - const projectId = (() => { const v = process.env.DIGITALOCEAN_PROJECT_ID; if (!v) { @@ -29,7 +27,20 @@ const environment: Environment = (() => { } if (v !== "staging" && v !== "production") { - throw new Error('Environment must be "staging" or "production"'); + throw new Error( + 'Environment must be the string literal "staging" or "production"', + ); + } + + return v; +})(); + +const sha = (() => { + const v = process.env.SHA; + if (environment === "staging" && !v) { + throw new Error( + "SHA must be available in ENV if script is being run in staging environment.", + ); } return v; diff --git a/.github/workflows/deploy-stg.yml b/.github/workflows/deploy-stg.yml index 7850f1236..0d00fb736 100644 --- a/.github/workflows/deploy-stg.yml +++ b/.github/workflows/deploy-stg.yml @@ -273,6 +273,7 @@ jobs: GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }} GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} ENVIRONMENT: staging + SHA: ${{ needs.getPRHead.outputs.sha }} notifyStatus: name: Notify on deployment status From 26e18d6099f5446a891e92aa8553dac3fe6874b3 Mon Sep 17 00:00:00 2001 From: Tahmid Ahmed <tahmid@tahmid.io> Date: Sun, 18 Jan 2026 20:40:55 -0500 Subject: [PATCH 043/101] 641: fn --- .github/scripts/redeploy/db/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/scripts/redeploy/db/index.ts b/.github/scripts/redeploy/db/index.ts index a8c5b78a6..4802970cb 100644 --- a/.github/scripts/redeploy/db/index.ts +++ b/.github/scripts/redeploy/db/index.ts @@ -11,7 +11,7 @@ export async function _migrateDb({ envVariables: Map<string, string>; sha?: string; }): Promise<void> { - await $`git fetch origin main:main`; + await $`git fetch --depth=100 origin main:main`; if (environment === "staging") { const diffOutput = await $`git diff --name-only main...${sha}`.text(); From 77566ef6dc96a992cc452b2c7d3e81e62e4f3d14 Mon Sep 17 00:00:00 2001 From: Tahmid Ahmed <tahmid@tahmid.io> Date: Sun, 18 Jan 2026 20:41:56 -0500 Subject: [PATCH 044/101] 641: test db change --- db/migration/V0069_Create_fake_table.SQL | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 db/migration/V0069_Create_fake_table.SQL diff --git a/db/migration/V0069_Create_fake_table.SQL b/db/migration/V0069_Create_fake_table.SQL new file mode 100644 index 000000000..efe2b30f3 --- /dev/null +++ b/db/migration/V0069_Create_fake_table.SQL @@ -0,0 +1,3 @@ +CREATE TABLE IF NOT EXISTS "FakeTable" ( + id UUID PRIMARY KEY +) From 4b981d63298d4466c3465846da9b4e1c52b842b4 Mon Sep 17 00:00:00 2001 From: Tahmid Ahmed <tahmid@tahmid.io> Date: Sun, 18 Jan 2026 20:55:50 -0500 Subject: [PATCH 045/101] 641: t --- .github/scripts/redeploy/db/index.ts | 2 -- .github/workflows/deploy-stg.yml | 1 + 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/scripts/redeploy/db/index.ts b/.github/scripts/redeploy/db/index.ts index 4802970cb..b3864f3c7 100644 --- a/.github/scripts/redeploy/db/index.ts +++ b/.github/scripts/redeploy/db/index.ts @@ -11,8 +11,6 @@ export async function _migrateDb({ envVariables: Map<string, string>; sha?: string; }): Promise<void> { - await $`git fetch --depth=100 origin main:main`; - if (environment === "staging") { const diffOutput = await $`git diff --name-only main...${sha}`.text(); const files = diffOutput.split("\n"); diff --git a/.github/workflows/deploy-stg.yml b/.github/workflows/deploy-stg.yml index 0d00fb736..8cb91e3a6 100644 --- a/.github/workflows/deploy-stg.yml +++ b/.github/workflows/deploy-stg.yml @@ -266,6 +266,7 @@ jobs: uses: actions/checkout@v4 with: ref: ${{ needs.getPRHead.outputs.sha }} + fetch-depth: 0 - name: Run workflow uses: ./.github/composite/redeploy From 67e2391fa7a3e7c0599e67dc1696a492160bce2c Mon Sep 17 00:00:00 2001 From: Tahmid Ahmed <tahmid@tahmid.io> Date: Sun, 18 Jan 2026 20:58:46 -0500 Subject: [PATCH 046/101] 641: tf --- .github/workflows/deploy-stg.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/deploy-stg.yml b/.github/workflows/deploy-stg.yml index 8cb91e3a6..fb3be2d58 100644 --- a/.github/workflows/deploy-stg.yml +++ b/.github/workflows/deploy-stg.yml @@ -189,6 +189,7 @@ jobs: uses: actions/checkout@v4 with: ref: ${{ needs.getPRHead.outputs.sha }} + fetch-depth: 0 - name: Disable man-db uses: ./.github/composite/disable-mandb From 04841ba68c7df3e5faad6e190aea2b366f3e8a00 Mon Sep 17 00:00:00 2001 From: Tahmid Ahmed <tahmid@tahmid.io> Date: Sun, 18 Jan 2026 21:01:00 -0500 Subject: [PATCH 047/101] 641: tff --- .../{V0069_Create_fake_table.SQL => V0069__Create_fake_table.SQL} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename db/migration/{V0069_Create_fake_table.SQL => V0069__Create_fake_table.SQL} (100%) diff --git a/db/migration/V0069_Create_fake_table.SQL b/db/migration/V0069__Create_fake_table.SQL similarity index 100% rename from db/migration/V0069_Create_fake_table.SQL rename to db/migration/V0069__Create_fake_table.SQL From 8c111ed40b130fe5d67d37ca968ca9ab0de54625 Mon Sep 17 00:00:00 2001 From: Tahmid Ahmed <tahmid@tahmid.io> Date: Sun, 18 Jan 2026 21:09:17 -0500 Subject: [PATCH 048/101] 641: tffvfrgswfgnkerg --- .github/scripts/redeploy/db/index.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/scripts/redeploy/db/index.ts b/.github/scripts/redeploy/db/index.ts index b3864f3c7..a8c5b78a6 100644 --- a/.github/scripts/redeploy/db/index.ts +++ b/.github/scripts/redeploy/db/index.ts @@ -11,6 +11,8 @@ export async function _migrateDb({ envVariables: Map<string, string>; sha?: string; }): Promise<void> { + await $`git fetch origin main:main`; + if (environment === "staging") { const diffOutput = await $`git diff --name-only main...${sha}`.text(); const files = diffOutput.split("\n"); From a5a78e918d24c06ad2add0ff32fe06b2649d5d75 Mon Sep 17 00:00:00 2001 From: Tahmid Ahmed <tahmid@tahmid.io> Date: Sun, 18 Jan 2026 21:18:35 -0500 Subject: [PATCH 049/101] 641: remove test db file --- db/migration/V0069__Create_fake_table.SQL | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 db/migration/V0069__Create_fake_table.SQL diff --git a/db/migration/V0069__Create_fake_table.SQL b/db/migration/V0069__Create_fake_table.SQL deleted file mode 100644 index efe2b30f3..000000000 --- a/db/migration/V0069__Create_fake_table.SQL +++ /dev/null @@ -1,3 +0,0 @@ -CREATE TABLE IF NOT EXISTS "FakeTable" ( - id UUID PRIMARY KEY -) From f094b458df1531727442edfc020a36910bfdc84e Mon Sep 17 00:00:00 2001 From: Tahmid Ahmed <tahmid@tahmid.io> Date: Mon, 19 Jan 2026 00:20:25 -0500 Subject: [PATCH 050/101] 641: ble --- .github/composite/notion-checks/action.yml | 46 ++++++++++ .github/composite/setup-ci/action.yml | 2 - .github/scripts/bun.lock | 75 +++++++++++++++ .github/scripts/notion/commits/index.ts | 1 + .github/scripts/notion/index.ts | 44 +++++++++ .github/scripts/notion/pr/index.ts | 51 ++++++++++ .github/scripts/notion/pr/types.ts | 6 ++ .github/scripts/notion/pr/utils.ts | 92 +++++++++++++++++++ .github/scripts/notion/task/index.ts | 30 ++++++ .github/scripts/package.json | 5 +- .github/scripts/utils/run-backend-instance.ts | 7 +- .github/scripts/utils/run-local-db.ts | 3 +- .github/scripts/utils/send-message.ts | 51 ++++++++++ .github/workflows/pr-verifications.yml | 10 +- 14 files changed, 409 insertions(+), 14 deletions(-) create mode 100644 .github/composite/notion-checks/action.yml delete mode 100644 .github/composite/setup-ci/action.yml create mode 100644 .github/scripts/notion/commits/index.ts create mode 100644 .github/scripts/notion/index.ts create mode 100644 .github/scripts/notion/pr/index.ts create mode 100644 .github/scripts/notion/pr/types.ts create mode 100644 .github/scripts/notion/pr/utils.ts create mode 100644 .github/scripts/notion/task/index.ts create mode 100644 .github/scripts/utils/send-message.ts diff --git a/.github/composite/notion-checks/action.yml b/.github/composite/notion-checks/action.yml new file mode 100644 index 000000000..74429b0d9 --- /dev/null +++ b/.github/composite/notion-checks/action.yml @@ -0,0 +1,46 @@ +name: "Run Notion Checks" +description: "Run Notion validation(s) on the given PR ID" + +inputs: + PR_ID: + description: "PR ID" + required: true + GPG_PRIVATE_KEY: + description: "GPG Private Key" + required: true + GPG_PASSPHRASE: + description: "GPG Passphrase" + required: true + +runs: + using: "composite" + steps: + - name: Disable man-db + uses: ./.github/composite/disable-mandb + + - uses: oven-sh/setup-bun@v2 + with: + bun-version: latest + + - name: Cache Bun dependencies + uses: actions/cache@v4 + with: + path: ~/.bun/install/cache + key: ${{ runner.os }}-bun-${{ hashFiles('.github/scripts/bun.lock') }} + restore-keys: | + ${{ runner.os }}-bun- + + - name: Install deps + shell: bash + run: bun install --cwd .github/scripts --frozen-lockfile + + - name: Load secrets + uses: ./.github/composite/load-secrets + with: + GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }} + GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} + UNLOAD_ENVIRONMENTS: ci + + - name: Run script + shell: bash + run: bun .github/scripts/notion diff --git a/.github/composite/setup-ci/action.yml b/.github/composite/setup-ci/action.yml deleted file mode 100644 index dce434153..000000000 --- a/.github/composite/setup-ci/action.yml +++ /dev/null @@ -1,2 +0,0 @@ -name: "Setup CI" -description: "Setup bun & " diff --git a/.github/scripts/bun.lock b/.github/scripts/bun.lock index ee868ec47..710b29be1 100644 --- a/.github/scripts/bun.lock +++ b/.github/scripts/bun.lock @@ -6,7 +6,10 @@ "name": "scripts", "dependencies": { "@digitalocean/dots": "^1.7.0", + "@notionhq/client": "^5.7.0", + "@octokit/rest": "^22.0.1", "bun": "^1.3.6", + "octokit": "^5.0.5", }, "devDependencies": { "@eslint/js": "^9.39.2", @@ -75,6 +78,62 @@ "@microsoft/kiota-serialization-text": ["@microsoft/kiota-serialization-text@1.0.0-preview.99", "", { "dependencies": { "@microsoft/kiota-abstractions": "^1.0.0-preview.99", "tslib": "^2.6.2" } }, "sha512-gcBffQRI1soHVAiS4YjfMr3SQ9fC8xVUGMfarTTszU4PjpxyFOknaWoFdt/0rvMeavI9jOtbyvDKAf77cZyYIQ=="], + "@notionhq/client": ["@notionhq/client@5.7.0", "", {}, "sha512-On/GI10lJRLSrzU3h/cDl1Oe15qywYrEvyV3KlE4UgtzqMjlPzXG31B7zHjGc/tTgoY8eD3JS4CWlcD64NBmaQ=="], + + "@octokit/app": ["@octokit/app@16.1.2", "", { "dependencies": { "@octokit/auth-app": "^8.1.2", "@octokit/auth-unauthenticated": "^7.0.3", "@octokit/core": "^7.0.6", "@octokit/oauth-app": "^8.0.3", "@octokit/plugin-paginate-rest": "^14.0.0", "@octokit/types": "^16.0.0", "@octokit/webhooks": "^14.0.0" } }, "sha512-8j7sEpUYVj18dxvh0KWj6W/l6uAiVRBl1JBDVRqH1VHKAO/G5eRVl4yEoYACjakWers1DjUkcCHyJNQK47JqyQ=="], + + "@octokit/auth-app": ["@octokit/auth-app@8.1.2", "", { "dependencies": { "@octokit/auth-oauth-app": "^9.0.3", "@octokit/auth-oauth-user": "^6.0.2", "@octokit/request": "^10.0.6", "@octokit/request-error": "^7.0.2", "@octokit/types": "^16.0.0", "toad-cache": "^3.7.0", "universal-github-app-jwt": "^2.2.0", "universal-user-agent": "^7.0.0" } }, "sha512-db8VO0PqXxfzI6GdjtgEFHY9tzqUql5xMFXYA12juq8TeTgPAuiiP3zid4h50lwlIP457p5+56PnJOgd2GGBuw=="], + + "@octokit/auth-oauth-app": ["@octokit/auth-oauth-app@9.0.3", "", { "dependencies": { "@octokit/auth-oauth-device": "^8.0.3", "@octokit/auth-oauth-user": "^6.0.2", "@octokit/request": "^10.0.6", "@octokit/types": "^16.0.0", "universal-user-agent": "^7.0.0" } }, "sha512-+yoFQquaF8OxJSxTb7rnytBIC2ZLbLqA/yb71I4ZXT9+Slw4TziV9j/kyGhUFRRTF2+7WlnIWsePZCWHs+OGjg=="], + + "@octokit/auth-oauth-device": ["@octokit/auth-oauth-device@8.0.3", "", { "dependencies": { "@octokit/oauth-methods": "^6.0.2", "@octokit/request": "^10.0.6", "@octokit/types": "^16.0.0", "universal-user-agent": "^7.0.0" } }, "sha512-zh2W0mKKMh/VWZhSqlaCzY7qFyrgd9oTWmTmHaXnHNeQRCZr/CXy2jCgHo4e4dJVTiuxP5dLa0YM5p5QVhJHbw=="], + + "@octokit/auth-oauth-user": ["@octokit/auth-oauth-user@6.0.2", "", { "dependencies": { "@octokit/auth-oauth-device": "^8.0.3", "@octokit/oauth-methods": "^6.0.2", "@octokit/request": "^10.0.6", "@octokit/types": "^16.0.0", "universal-user-agent": "^7.0.0" } }, "sha512-qLoPPc6E6GJoz3XeDG/pnDhJpTkODTGG4kY0/Py154i/I003O9NazkrwJwRuzgCalhzyIeWQ+6MDvkUmKXjg/A=="], + + "@octokit/auth-token": ["@octokit/auth-token@6.0.0", "", {}, "sha512-P4YJBPdPSpWTQ1NU4XYdvHvXJJDxM6YwpS0FZHRgP7YFkdVxsWcpWGy/NVqlAA7PcPCnMacXlRm1y2PFZRWL/w=="], + + "@octokit/auth-unauthenticated": ["@octokit/auth-unauthenticated@7.0.3", "", { "dependencies": { "@octokit/request-error": "^7.0.2", "@octokit/types": "^16.0.0" } }, "sha512-8Jb1mtUdmBHL7lGmop9mU9ArMRUTRhg8vp0T1VtZ4yd9vEm3zcLwmjQkhNEduKawOOORie61xhtYIhTDN+ZQ3g=="], + + "@octokit/core": ["@octokit/core@7.0.6", "", { "dependencies": { "@octokit/auth-token": "^6.0.0", "@octokit/graphql": "^9.0.3", "@octokit/request": "^10.0.6", "@octokit/request-error": "^7.0.2", "@octokit/types": "^16.0.0", "before-after-hook": "^4.0.0", "universal-user-agent": "^7.0.0" } }, "sha512-DhGl4xMVFGVIyMwswXeyzdL4uXD5OGILGX5N8Y+f6W7LhC1Ze2poSNrkF/fedpVDHEEZ+PHFW0vL14I+mm8K3Q=="], + + "@octokit/endpoint": ["@octokit/endpoint@11.0.2", "", { "dependencies": { "@octokit/types": "^16.0.0", "universal-user-agent": "^7.0.2" } }, "sha512-4zCpzP1fWc7QlqunZ5bSEjxc6yLAlRTnDwKtgXfcI/FxxGoqedDG8V2+xJ60bV2kODqcGB+nATdtap/XYq2NZQ=="], + + "@octokit/graphql": ["@octokit/graphql@9.0.3", "", { "dependencies": { "@octokit/request": "^10.0.6", "@octokit/types": "^16.0.0", "universal-user-agent": "^7.0.0" } }, "sha512-grAEuupr/C1rALFnXTv6ZQhFuL1D8G5y8CN04RgrO4FIPMrtm+mcZzFG7dcBm+nq+1ppNixu+Jd78aeJOYxlGA=="], + + "@octokit/oauth-app": ["@octokit/oauth-app@8.0.3", "", { "dependencies": { "@octokit/auth-oauth-app": "^9.0.2", "@octokit/auth-oauth-user": "^6.0.1", "@octokit/auth-unauthenticated": "^7.0.2", "@octokit/core": "^7.0.5", "@octokit/oauth-authorization-url": "^8.0.0", "@octokit/oauth-methods": "^6.0.1", "@types/aws-lambda": "^8.10.83", "universal-user-agent": "^7.0.0" } }, "sha512-jnAjvTsPepyUaMu9e69hYBuozEPgYqP4Z3UnpmvoIzHDpf8EXDGvTY1l1jK0RsZ194oRd+k6Hm13oRU8EoDFwg=="], + + "@octokit/oauth-authorization-url": ["@octokit/oauth-authorization-url@8.0.0", "", {}, "sha512-7QoLPRh/ssEA/HuHBHdVdSgF8xNLz/Bc5m9fZkArJE5bb6NmVkDm3anKxXPmN1zh6b5WKZPRr3697xKT/yM3qQ=="], + + "@octokit/oauth-methods": ["@octokit/oauth-methods@6.0.2", "", { "dependencies": { "@octokit/oauth-authorization-url": "^8.0.0", "@octokit/request": "^10.0.6", "@octokit/request-error": "^7.0.2", "@octokit/types": "^16.0.0" } }, "sha512-HiNOO3MqLxlt5Da5bZbLV8Zarnphi4y9XehrbaFMkcoJ+FL7sMxH/UlUsCVxpddVu4qvNDrBdaTVE2o4ITK8ng=="], + + "@octokit/openapi-types": ["@octokit/openapi-types@27.0.0", "", {}, "sha512-whrdktVs1h6gtR+09+QsNk2+FO+49j6ga1c55YZudfEG+oKJVvJLQi3zkOm5JjiUXAagWK2tI2kTGKJ2Ys7MGA=="], + + "@octokit/openapi-webhooks-types": ["@octokit/openapi-webhooks-types@12.1.0", "", {}, "sha512-WiuzhOsiOvb7W3Pvmhf8d2C6qaLHXrWiLBP4nJ/4kydu+wpagV5Fkz9RfQwV2afYzv3PB+3xYgp4mAdNGjDprA=="], + + "@octokit/plugin-paginate-graphql": ["@octokit/plugin-paginate-graphql@6.0.0", "", { "peerDependencies": { "@octokit/core": ">=6" } }, "sha512-crfpnIoFiBtRkvPqOyLOsw12XsveYuY2ieP6uYDosoUegBJpSVxGwut9sxUgFFcll3VTOTqpUf8yGd8x1OmAkQ=="], + + "@octokit/plugin-paginate-rest": ["@octokit/plugin-paginate-rest@14.0.0", "", { "dependencies": { "@octokit/types": "^16.0.0" }, "peerDependencies": { "@octokit/core": ">=6" } }, "sha512-fNVRE7ufJiAA3XUrha2omTA39M6IXIc6GIZLvlbsm8QOQCYvpq/LkMNGyFlB1d8hTDzsAXa3OKtybdMAYsV/fw=="], + + "@octokit/plugin-request-log": ["@octokit/plugin-request-log@6.0.0", "", { "peerDependencies": { "@octokit/core": ">=6" } }, "sha512-UkOzeEN3W91/eBq9sPZNQ7sUBvYCqYbrrD8gTbBuGtHEuycE4/awMXcYvx6sVYo7LypPhmQwwpUe4Yyu4QZN5Q=="], + + "@octokit/plugin-rest-endpoint-methods": ["@octokit/plugin-rest-endpoint-methods@17.0.0", "", { "dependencies": { "@octokit/types": "^16.0.0" }, "peerDependencies": { "@octokit/core": ">=6" } }, "sha512-B5yCyIlOJFPqUUeiD0cnBJwWJO8lkJs5d8+ze9QDP6SvfiXSz1BF+91+0MeI1d2yxgOhU/O+CvtiZ9jSkHhFAw=="], + + "@octokit/plugin-retry": ["@octokit/plugin-retry@8.0.3", "", { "dependencies": { "@octokit/request-error": "^7.0.2", "@octokit/types": "^16.0.0", "bottleneck": "^2.15.3" }, "peerDependencies": { "@octokit/core": ">=7" } }, "sha512-vKGx1i3MC0za53IzYBSBXcrhmd+daQDzuZfYDd52X5S0M2otf3kVZTVP8bLA3EkU0lTvd1WEC2OlNNa4G+dohA=="], + + "@octokit/plugin-throttling": ["@octokit/plugin-throttling@11.0.3", "", { "dependencies": { "@octokit/types": "^16.0.0", "bottleneck": "^2.15.3" }, "peerDependencies": { "@octokit/core": "^7.0.0" } }, "sha512-34eE0RkFCKycLl2D2kq7W+LovheM/ex3AwZCYN8udpi6bxsyjZidb2McXs69hZhLmJlDqTSP8cH+jSRpiaijBg=="], + + "@octokit/request": ["@octokit/request@10.0.7", "", { "dependencies": { "@octokit/endpoint": "^11.0.2", "@octokit/request-error": "^7.0.2", "@octokit/types": "^16.0.0", "fast-content-type-parse": "^3.0.0", "universal-user-agent": "^7.0.2" } }, "sha512-v93h0i1yu4idj8qFPZwjehoJx4j3Ntn+JhXsdJrG9pYaX6j/XRz2RmasMUHtNgQD39nrv/VwTWSqK0RNXR8upA=="], + + "@octokit/request-error": ["@octokit/request-error@7.1.0", "", { "dependencies": { "@octokit/types": "^16.0.0" } }, "sha512-KMQIfq5sOPpkQYajXHwnhjCC0slzCNScLHs9JafXc4RAJI+9f+jNDlBNaIMTvazOPLgb4BnlhGJOTbnN0wIjPw=="], + + "@octokit/rest": ["@octokit/rest@22.0.1", "", { "dependencies": { "@octokit/core": "^7.0.6", "@octokit/plugin-paginate-rest": "^14.0.0", "@octokit/plugin-request-log": "^6.0.0", "@octokit/plugin-rest-endpoint-methods": "^17.0.0" } }, "sha512-Jzbhzl3CEexhnivb1iQ0KJ7s5vvjMWcmRtq5aUsKmKDrRW6z3r84ngmiFKFvpZjpiU/9/S6ITPFRpn5s/3uQJw=="], + + "@octokit/types": ["@octokit/types@16.0.0", "", { "dependencies": { "@octokit/openapi-types": "^27.0.0" } }, "sha512-sKq+9r1Mm4efXW1FCk7hFSeJo4QKreL/tTbR0rz/qx/r1Oa2VV83LTA/H/MuCOX7uCIJmQVRKBcbmWoySjAnSg=="], + + "@octokit/webhooks": ["@octokit/webhooks@14.2.0", "", { "dependencies": { "@octokit/openapi-webhooks-types": "12.1.0", "@octokit/request-error": "^7.0.0", "@octokit/webhooks-methods": "^6.0.0" } }, "sha512-da6KbdNCV5sr1/txD896V+6W0iamFWrvVl8cHkBSPT+YlvmT3DwXa4jxZnQc+gnuTEqSWbBeoSZYTayXH9wXcw=="], + + "@octokit/webhooks-methods": ["@octokit/webhooks-methods@6.0.0", "", {}, "sha512-MFlzzoDJVw/GcbfzVC1RLR36QqkTLUf79vLVO3D+xn7r0QgxnFoLZgtrzxiQErAjFUOdH6fas2KeQJ1yr/qaXQ=="], + "@opentelemetry/api": ["@opentelemetry/api@1.9.0", "", {}, "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg=="], "@oven/bun-darwin-aarch64": ["@oven/bun-darwin-aarch64@1.3.6", "", { "os": "darwin", "cpu": "arm64" }, "sha512-27rypIapNkYboOSylkf1tD9UW9Ado2I+P1NBL46Qz29KmOjTL6WuJ7mHDC5O66CYxlOkF5r93NPDAC3lFHYBXw=="], @@ -101,6 +160,8 @@ "@std-uritemplate/std-uritemplate": ["@std-uritemplate/std-uritemplate@2.0.8", "", {}, "sha512-8oj7jKksoTRxjdPkWKX9FyOKDS8ORBm+ZfTSHm0f3eYha8cI0O1d57gyduOIAX7Y2uUukNDNlxlvGb+FFkE7yQ=="], + "@types/aws-lambda": ["@types/aws-lambda@8.10.159", "", {}, "sha512-SAP22WSGNN12OQ8PlCzGzRCZ7QDCwI85dQZbmpz7+mAk+L7j+wI7qnvmdKh+o7A5LaOp6QnOZ2NJphAZQTTHQg=="], + "@types/bun": ["@types/bun@1.3.6", "", { "dependencies": { "bun-types": "1.3.6" } }, "sha512-uWCv6FO/8LcpREhenN1d1b6fcspAB+cefwD7uti8C8VffIv0Um08TKMn98FynpTiU38+y2dUO55T11NgDt8VAA=="], "@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="], @@ -151,6 +212,10 @@ "balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], + "before-after-hook": ["before-after-hook@4.0.0", "", {}, "sha512-q6tR3RPqIB1pMiTRMFcZwuG5T8vwp+vUvEG0vuI6B+Rikh5BfPp2fQ82c925FOs+b0lcFQ8CFrL+KbilfZFhOQ=="], + + "bottleneck": ["bottleneck@2.19.5", "", {}, "sha512-VHiNCbI1lKdl44tGrhNfU3lup0Tj/ZBMJB5/2ZbNXRCPuRCO7ed2mgcK4r17y+KB2EfuYuRaVlwNbAeaWGSpbw=="], + "brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="], "bun": ["bun@1.3.6", "", { "optionalDependencies": { "@oven/bun-darwin-aarch64": "1.3.6", "@oven/bun-darwin-x64": "1.3.6", "@oven/bun-darwin-x64-baseline": "1.3.6", "@oven/bun-linux-aarch64": "1.3.6", "@oven/bun-linux-aarch64-musl": "1.3.6", "@oven/bun-linux-x64": "1.3.6", "@oven/bun-linux-x64-baseline": "1.3.6", "@oven/bun-linux-x64-musl": "1.3.6", "@oven/bun-linux-x64-musl-baseline": "1.3.6", "@oven/bun-windows-x64": "1.3.6", "@oven/bun-windows-x64-baseline": "1.3.6" }, "os": [ "linux", "win32", "darwin", ], "cpu": [ "x64", "arm64", ], "bin": { "bun": "bin/bun.exe", "bunx": "bin/bunx.exe" } }, "sha512-Tn98GlZVN2WM7+lg/uGn5DzUao37Yc0PUz7yzYHdeF5hd+SmHQGbCUIKE4Sspdgtxn49LunK3mDNBC2Qn6GJjw=="], @@ -215,6 +280,8 @@ "esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="], + "fast-content-type-parse": ["fast-content-type-parse@3.0.0", "", {}, "sha512-ZvLdcY8P+N8mGQJahJV5G4U88CSvT1rP8ApL6uETe88MBXrBHAkZlSEySdUlyztF7ccb+Znos3TFqaepHxdhBg=="], + "fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="], "fast-json-stable-stringify": ["fast-json-stable-stringify@2.1.0", "", {}, "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="], @@ -305,6 +372,8 @@ "node-fetch": ["node-fetch@2.7.0", "", { "dependencies": { "whatwg-url": "^5.0.0" }, "peerDependencies": { "encoding": "^0.1.0" }, "optionalPeers": ["encoding"] }, "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A=="], + "octokit": ["octokit@5.0.5", "", { "dependencies": { "@octokit/app": "^16.1.2", "@octokit/core": "^7.0.6", "@octokit/oauth-app": "^8.0.3", "@octokit/plugin-paginate-graphql": "^6.0.0", "@octokit/plugin-paginate-rest": "^14.0.0", "@octokit/plugin-rest-endpoint-methods": "^17.0.0", "@octokit/plugin-retry": "^8.0.3", "@octokit/plugin-throttling": "^11.0.3", "@octokit/request-error": "^7.0.2", "@octokit/types": "^16.0.0", "@octokit/webhooks": "^14.0.0" } }, "sha512-4+/OFSqOjoyULo7eN7EA97DE0Xydj/PW5aIckxqQIoFjFwqXKuFCvXUJObyJfBF9Khu4RL/jlDRI9FPaMGfPnw=="], + "optionator": ["optionator@0.9.4", "", { "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", "word-wrap": "^1.2.5" } }, "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g=="], "p-limit": ["p-limit@3.1.0", "", { "dependencies": { "yocto-queue": "^0.1.0" } }, "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="], @@ -343,6 +412,8 @@ "tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="], + "toad-cache": ["toad-cache@3.7.0", "", {}, "sha512-/m8M+2BJUpoJdgAHoG+baCwBT+tf2VraSfkBgl0Y00qIWt41DJ8R5B8nsEw0I58YwF5IZH6z24/2TobDKnqSWw=="], + "tr46": ["tr46@0.0.3", "", {}, "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="], "ts-api-utils": ["ts-api-utils@2.4.0", "", { "peerDependencies": { "typescript": ">=4.8.4" } }, "sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA=="], @@ -359,6 +430,10 @@ "undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="], + "universal-github-app-jwt": ["universal-github-app-jwt@2.2.2", "", {}, "sha512-dcmbeSrOdTnsjGjUfAlqNDJrhxXizjAz94ija9Qw8YkZ1uu0d+GoZzyH+Jb9tIIqvGsadUfwg+22k5aDqqwzbw=="], + + "universal-user-agent": ["universal-user-agent@7.0.3", "", {}, "sha512-TmnEAEAsBJVZM/AADELsK76llnwcf9vMKuPz8JflO1frO8Lchitr0fNaN9d+Ap0BjKtqWqd/J17qeDnXh8CL2A=="], + "uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="], "uuid": ["uuid@11.1.0", "", { "bin": { "uuid": "dist/esm/bin/uuid" } }, "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A=="], diff --git a/.github/scripts/notion/commits/index.ts b/.github/scripts/notion/commits/index.ts new file mode 100644 index 000000000..9bab774e5 --- /dev/null +++ b/.github/scripts/notion/commits/index.ts @@ -0,0 +1 @@ +export async function _checkCommits(taskId: number) {} diff --git a/.github/scripts/notion/index.ts b/.github/scripts/notion/index.ts new file mode 100644 index 000000000..d265a996c --- /dev/null +++ b/.github/scripts/notion/index.ts @@ -0,0 +1,44 @@ +import { checkNotionPrAndGetTask } from "notion/pr"; + +export * from "./pr"; + +const notionPat = (() => { + const v = process.env.NOTION_PAT; + if (!v) { + throw new Error("NOTION_PAT is required"); + } + return v; +})(); + +const prId = (() => { + const v = process.env.PR_ID; + if (!v) { + throw new Error("PR_ID is required"); + } + const n = Number(v); + if (isNaN(n)) { + throw new Error("PR_ID is not a number"); + } + return n; +})(); + +const notionDbId = (() => { + const v = process.env.NOTION_TASK_DB_ID; + if (!v) { + throw new Error("NOTION_TASK_DB_ID is required"); + } + return v; +})(); + +async function main() { + const _ = checkNotionPrAndGetTask(notionPat, prId, notionDbId); +} + +main() + .then(() => { + process.exit(0); + }) + .catch((e) => { + console.error(e); + process.exit(1); + }); diff --git a/.github/scripts/notion/pr/index.ts b/.github/scripts/notion/pr/index.ts new file mode 100644 index 000000000..b21af5299 --- /dev/null +++ b/.github/scripts/notion/pr/index.ts @@ -0,0 +1,51 @@ +import type { NotionTaskObject } from "notion/pr/types"; + +import { Client, isFullPage } from "@notionhq/client"; +import { $ } from "bun"; +import { _fetchBlocks } from "notion/pr/utils"; +import { sendMessage } from "utils/send-message"; + +import { _getNotionTaskById } from "../task"; + +export async function checkNotionPrAndGetTask( + notionPat: string, + prId: number, + notionDbId: string, +): Promise<NotionTaskObject> { + const client = new Client({ + auth: notionPat, + }); + + const title = await (async () => { + const res = await $`gh pr view ${prId} --json title`.text(); + const json = JSON.parse(res) as { title?: string } | undefined; + + if (!json || !json.title) { + await sendMessage(prId, "Failed to parse Notion ID"); + process.exit(1); + } + + return json.title; + })(); + + const ticketNum = parseInt(title, 10); + + if (!Number.isNaN(ticketNum) && (ticketNum <= 0 || ticketNum > 999)) { + await sendMessage(prId, `No numeric prefix found in PR title: ${title}`); + process.exit(1); + } + + const task = await _getNotionTaskById(client, notionDbId, ticketNum); + + if (!isFullPage(task)) { + sendMessage(prId, "Notion task is not expected page type"); + process.exit(1); + } + + const blocks = await _fetchBlocks(client, task.id); + + return { + task, + taskContent: blocks.join("\n"), + }; +} diff --git a/.github/scripts/notion/pr/types.ts b/.github/scripts/notion/pr/types.ts new file mode 100644 index 000000000..eb735d3af --- /dev/null +++ b/.github/scripts/notion/pr/types.ts @@ -0,0 +1,6 @@ +import { type PageObjectResponse } from "@notionhq/client"; + +export type NotionTaskObject = { + task: PageObjectResponse; + taskContent: string; +}; diff --git a/.github/scripts/notion/pr/utils.ts b/.github/scripts/notion/pr/utils.ts new file mode 100644 index 000000000..bf16e2435 --- /dev/null +++ b/.github/scripts/notion/pr/utils.ts @@ -0,0 +1,92 @@ +import type { Client } from "@notionhq/client"; +import type { + BlockObjectResponse, + RichTextItemResponse, +} from "@notionhq/client/build/src/api-endpoints"; + +export async function _fetchBlocks( + client: Client, + blockId: string, + indent = "", +): Promise<string[]> { + const response = await client.blocks.children.list({ + block_id: blockId, + }); + + const lines: string[] = []; + + for (const block of response.results) { + if (!("type" in block)) continue; + + const blockResponse = block as BlockObjectResponse; + const markdown = blockToMarkdown(blockResponse, indent); + + if (markdown) { + lines.push(markdown); + } + + if (blockResponse.has_children) { + const childLines = await _fetchBlocks( + client, + blockResponse.id, + indent + " ", + ); + lines.push(...childLines); + } + } + + return lines; +} + +function extractPlainText(richText: RichTextItemResponse[]): string { + return richText.map((item) => item.plain_text).join(""); +} + +function blockToMarkdown(block: BlockObjectResponse, indent: string): string { + switch (block.type) { + case "paragraph": + return indent + extractPlainText(block.paragraph.rich_text); + + case "bulleted_list_item": + return ( + indent + "- " + extractPlainText(block.bulleted_list_item.rich_text) + ); + + case "numbered_list_item": + return ( + indent + "1. " + extractPlainText(block.numbered_list_item.rich_text) + ); + + case "heading_1": + return indent + "# " + extractPlainText(block.heading_1.rich_text); + + case "heading_2": + return indent + "## " + extractPlainText(block.heading_2.rich_text); + + case "heading_3": + return indent + "### " + extractPlainText(block.heading_3.rich_text); + + case "quote": + return indent + "> " + extractPlainText(block.quote.rich_text); + + case "to_do": { + const checked = block.to_do.checked ? "[x]" : "[ ]"; + return indent + checked + " " + extractPlainText(block.to_do.rich_text); + } + + case "toggle": + return indent + extractPlainText(block.toggle.rich_text); + + case "callout": + return indent + extractPlainText(block.callout.rich_text); + + case "code": { + const language = block.code.language || ""; + const code = extractPlainText(block.code.rich_text); + return `${indent}\`\`\`${language}\n${code}\n${indent}\`\`\``; + } + + default: + return ""; + } +} diff --git a/.github/scripts/notion/task/index.ts b/.github/scripts/notion/task/index.ts new file mode 100644 index 000000000..8443dfa91 --- /dev/null +++ b/.github/scripts/notion/task/index.ts @@ -0,0 +1,30 @@ +import { Client } from "@notionhq/client"; + +export async function _getNotionTaskById( + client: Client, + dbId: string, + id: number, +) { + try { + const { results } = await client.dataSources.query({ + filter: { + property: "ID", + unique_id: { + equals: id, + }, + }, + data_source_id: dbId, + }); + + const ticket = results[0]; + + if (!ticket) { + throw new Error("Ticket with ID does not exist."); + } + + return ticket; + } catch (e) { + console.error("Ticket cannot be retrieved\n", e); + process.exit(-100); + } +} diff --git a/.github/scripts/package.json b/.github/scripts/package.json index d7bdec17a..7095bfb90 100644 --- a/.github/scripts/package.json +++ b/.github/scripts/package.json @@ -8,7 +8,10 @@ "license": "MIT", "dependencies": { "@digitalocean/dots": "^1.7.0", - "bun": "^1.3.6" + "@notionhq/client": "^5.7.0", + "@octokit/rest": "^22.0.1", + "bun": "^1.3.6", + "octokit": "^5.0.5" }, "devDependencies": { "@eslint/js": "^9.39.2", diff --git a/.github/scripts/utils/run-backend-instance.ts b/.github/scripts/utils/run-backend-instance.ts index c35ae98b4..41bac45f2 100644 --- a/.github/scripts/utils/run-backend-instance.ts +++ b/.github/scripts/utils/run-backend-instance.ts @@ -1,5 +1,6 @@ import { $ } from "bun"; -import { cyan } from "./colors"; + +import { cyan } from "@/../utils/colors"; let be: Bun.Subprocess<"ignore", Bun.BunFile, "inherit"> | undefined; @@ -34,7 +35,9 @@ async function start() { ready = true; break; } - } catch (_) {} + } catch (_) { + /* empty */ + } console.log(`Waiting for backend... (${i}/${attempts})`); await Bun.sleep(2000); diff --git a/.github/scripts/utils/run-local-db.ts b/.github/scripts/utils/run-local-db.ts index 97298a6ba..3cc636549 100644 --- a/.github/scripts/utils/run-local-db.ts +++ b/.github/scripts/utils/run-local-db.ts @@ -1,5 +1,6 @@ import { $ } from "bun"; -import { brightGreen, brightMagenta } from "./colors"; + +import { brightMagenta } from "@/../utils/colors"; async function start() { try { diff --git a/.github/scripts/utils/send-message.ts b/.github/scripts/utils/send-message.ts new file mode 100644 index 000000000..f2f42fd56 --- /dev/null +++ b/.github/scripts/utils/send-message.ts @@ -0,0 +1,51 @@ +import { Octokit } from "@octokit/rest"; +import { RequestError } from "octokit"; + +const githubToken = (() => { + const v = process.env.GITHUB_TOKEN; + return v; +})(); + +const [owner, repo] = (() => { + const v = process.env.GITHUB_REPOSITORY; + if (!v) { + throw new Error("GITHUB_REPOSITORY is required"); + } + return v.split("/") as [string, string]; +})(); + +export async function sendMessage( + prId: number, + message: string, + token?: string, +) { + try { + if (!githubToken && !token) { + throw new Error("Some token should be set"); + } + + const client = new Octokit({ + auth: token ?? githubToken, + }); + + try { + await client.rest.issues.createComment({ + issue_number: prId, + owner, + repo, + body: JSON.stringify({ body: message }), + }); + } catch (e) { + let d: string; + if (e instanceof RequestError) { + d = JSON.stringify(e.response?.data); + } else { + d = String(e); + } + throw new Error(`GitHub API Error\n\n${d}`); + } + } catch (e) { + console.error("Failed to post GitHub error message\n", e); + process.exit(1); + } +} diff --git a/.github/workflows/pr-verifications.yml b/.github/workflows/pr-verifications.yml index e3350e0b2..9dc4b9073 100644 --- a/.github/workflows/pr-verifications.yml +++ b/.github/workflows/pr-verifications.yml @@ -22,16 +22,10 @@ jobs: - name: Checkout Repository uses: actions/checkout@v4 - - name: Run 'Check Notion PR' composite action - uses: ./.github/composite/check-notion-pr + - name: Run composite workflow + uses: ./.github/composite/notion-checks id: notion_check with: PR_ID: ${{ github.event.number }} GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }} GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} - - - name: Run 'Check Notion Commits' composite action - uses: ./.github/composite/check-notion-commits - with: - PR_ID: ${{ github.event.number }} - NOTION_ID: ${{ steps.notion_check.outputs.notion_id }} From ca450f4aab709a273fc612015d3b9e6303bdb614 Mon Sep 17 00:00:00 2001 From: Tahmid Ahmed <tahmid@tahmid.io> Date: Mon, 19 Jan 2026 00:21:21 -0500 Subject: [PATCH 051/101] 641: blebe --- .github/scripts/notion/index.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/scripts/notion/index.ts b/.github/scripts/notion/index.ts index d265a996c..2f8b981b0 100644 --- a/.github/scripts/notion/index.ts +++ b/.github/scripts/notion/index.ts @@ -2,10 +2,10 @@ import { checkNotionPrAndGetTask } from "notion/pr"; export * from "./pr"; -const notionPat = (() => { - const v = process.env.NOTION_PAT; +const notionSecret = (() => { + const v = process.env.NOTION_SECRET; if (!v) { - throw new Error("NOTION_PAT is required"); + throw new Error("NOTION_SECRET is required"); } return v; })(); @@ -31,7 +31,7 @@ const notionDbId = (() => { })(); async function main() { - const _ = checkNotionPrAndGetTask(notionPat, prId, notionDbId); + const _ = checkNotionPrAndGetTask(notionSecret, prId, notionDbId); } main() From 00868285866e8fb77566900b632348455f4c77dc Mon Sep 17 00:00:00 2001 From: Tahmid Ahmed <tahmid@tahmid.io> Date: Mon, 19 Jan 2026 00:22:28 -0500 Subject: [PATCH 052/101] 641: blebefewg --- .../composite/check-notion-commits/action.yml | 66 ---- .github/composite/check-notion-pr/action.yml | 291 ------------------ .github/composite/notion-checks/action.yml | 2 + 3 files changed, 2 insertions(+), 357 deletions(-) delete mode 100644 .github/composite/check-notion-commits/action.yml delete mode 100644 .github/composite/check-notion-pr/action.yml diff --git a/.github/composite/check-notion-commits/action.yml b/.github/composite/check-notion-commits/action.yml deleted file mode 100644 index 3b573178d..000000000 --- a/.github/composite/check-notion-commits/action.yml +++ /dev/null @@ -1,66 +0,0 @@ -name: "Check Notion Commits" -description: 'Ensure all commits in the PR match the Notion Task ID (run after "Check Notion PR")' -inputs: - PR_ID: - description: "PR ID" - required: true - NOTION_ID: - description: "The Notion ID parsed from the PR title" - required: true - -runs: - using: "composite" - steps: - - name: Verify Commit Messages - id: verify - shell: bash - continue-on-error: true - env: - GH_TOKEN: ${{ github.token }} - run: | - set -euo pipefail - - EXPECTED="${{ inputs.NOTION_ID }}" - FAILED_COMMITS="" - - COMMITS=$(gh pr view "${{ inputs.PR_ID }}" --json commits --jq '.commits[].messageHeadline') - - while IFS= read -r MESSAGE; do - if [[ ! "$MESSAGE" == "$EXPECTED"* ]]; then - echo "::warning::Invalid commit: $MESSAGE" - FAILED_COMMITS+="$MESSAGE"$'\n' - fi - done <<< "$COMMITS" - - if [ -n "$FAILED_COMMITS" ]; then - # strip last \n - FAILED_COMMITS="${FAILED_COMMITS%$'\n'}" - - echo "failed_list<<EOF" >> "$GITHUB_OUTPUT" - echo "$FAILED_COMMITS" >> "$GITHUB_OUTPUT" - echo "EOF" >> "$GITHUB_OUTPUT" - exit 1 - fi - - - name: Send PR Message on Failure - if: steps.verify.outcome == 'failure' - uses: ./.github/composite/send-message - with: - prId: ${{ inputs.PR_ID }} - message: | - ### Commit Validation Failed - The following commits do not start with the required Notion ID `${{ inputs.NOTION_ID }}`: - - ``` - ${{ steps.verify.outputs.failed_list }} - ``` - - Please rebase and update your commit messages. - All messages should be of the following format: `${{ inputs.NOTION_ID }}: Example commit` - - - name: Finalize Failure - if: steps.verify.outcome == 'failure' - shell: bash - run: | - echo "One or more commits do not match the Notion ID." - exit 1 diff --git a/.github/composite/check-notion-pr/action.yml b/.github/composite/check-notion-pr/action.yml deleted file mode 100644 index 101c2ed43..000000000 --- a/.github/composite/check-notion-pr/action.yml +++ /dev/null @@ -1,291 +0,0 @@ -name: "Check Notion PR" -description: "Run validation(s) on the given PR ID against Notion" - -inputs: - PR_ID: - description: "PR ID" - required: true - GPG_PRIVATE_KEY: - description: "GPG Private Key" - required: true - GPG_PASSPHRASE: - description: "GPG Passphrase" - required: true - -outputs: - notion_id: - description: "The parsed Notion ID" - value: ${{ steps.parse_id.outputs.id }} - context: - description: "The Notion task title and description combined" - value: ${{ steps.check_notion.outputs.context }} - -runs: - using: "composite" - steps: - - name: Disable man-db - uses: ./.github/composite/disable-mandb - - - name: Load secrets - uses: ./.github/composite/load-secrets - with: - GPG_PRIVATE_KEY: ${{ inputs.GPG_PRIVATE_KEY }} - GPG_PASSPHRASE: ${{ inputs.GPG_PASSPHRASE }} - UNLOAD_ENVIRONMENTS: ci - - - name: Parse the numerical string prefix from the PR title - id: parse_id - shell: bash - env: - GH_TOKEN: ${{ github.token }} - run: | - set -euo pipefail - - TITLE="$(gh pr view "${{ inputs.PR_ID }}" --json title -q .title)" - PREFIX="$(echo "$TITLE" | grep -oE '^-?[0-9]+' || true)" - - if [ -z "$PREFIX" ]; then - echo "No numeric prefix found in PR title: $TITLE" >&2 - echo "status=failed" >> "$GITHUB_OUTPUT" - exit 1 - fi - - echo "id=$PREFIX" >> "$GITHUB_OUTPUT" - - - name: Check if the Notion task ID exists - id: check_notion - shell: bash - run: | - set -euo pipefail - - ID="${{ steps.parse_id.outputs.id }}" - BODY='{ - "filter": { - "property": "ID", - "unique_id": { - "equals": '${ID}' - } - } - }' - - result=$(curl --request POST \ - --header "accept: application/json" \ - --header "Authorization: Bearer ${NOTION_SECRET}" \ - --header "Content-Type: application/json" \ - --header "Notion-Version: 2025-09-03" \ - --data "${BODY}" \ - https://api.notion.com/v1/data_sources/$NOTION_TASK_DB_ID/query \ - | jq '(.results[0] // null) | {id: .properties.ID.unique_id.number, public_url: .public_url, page_id: .id, title: ((.properties."Task name".title // []) | map(.plain_text) | join("")) }') - - if [ -z "$result" ] || [ "$result" = "null" ] || [ "$result" = "[]" ]; then - echo "found=false" >> "$GITHUB_OUTPUT" - echo "public_url=null" >> "$GITHUB_OUTPUT" - else - PARSED_ID=$(echo "$result" | jq -r '.id') - - if [ "$PARSED_ID" = "null" ]; then - # TODO: Replace this out, very hacky - PARSED_ID=-1 - fi; - - if [ "$PARSED_ID" -ne "$ID" ]; then - echo "found=false" >> "$GITHUB_OUTPUT" - echo "public_url=null" >> "$GITHUB_OUTPUT" - echo "notion_id=null" >> "$GITHUB_OUTPUT" - else - PUBLIC_URL=$(echo "$result" | jq -r '.public_url') - PAGE_ID=$(echo "$result" | jq -r '.page_id') - TITLE=$(echo "$result" | jq -r '.title // ""') - - fetch_blocks() { - local block_id=$1 - local indent=$2 - - local response=$(curl -s --request GET \ - --header "accept: application/json" \ - --header "Authorization: Bearer ${NOTION_SECRET}" \ - --header "Notion-Version: 2025-09-03" \ - "https://api.notion.com/v1/blocks/${block_id}/children") - - echo "$response" | jq -c '.results[]?' | while IFS= read -r block; do - echo "$block" | jq -r --arg indent "$indent" ' - if .type == "paragraph" and (.paragraph.rich_text? // null) != null then - $indent + ([.paragraph.rich_text[]? | .plain_text // ""] | join("")) - elif .type == "bulleted_list_item" and (.bulleted_list_item.rich_text? // null) != null then - $indent + "- " + ([.bulleted_list_item.rich_text[]? | .plain_text // ""] | join("")) - elif .type == "numbered_list_item" and (.numbered_list_item.rich_text? // null) != null then - $indent + "1. " + ([.numbered_list_item.rich_text[]? | .plain_text // ""] | join("")) - elif .type == "heading_1" and (.heading_1.rich_text? // null) != null then - $indent + "# " + ([.heading_1.rich_text[]? | .plain_text // ""] | join("")) - elif .type == "heading_2" and (.heading_2.rich_text? // null) != null then - $indent + "## " + ([.heading_2.rich_text[]? | .plain_text // ""] | join("")) - elif .type == "heading_3" and (.heading_3.rich_text? // null) != null then - $indent + "### " + ([.heading_3.rich_text[]? | .plain_text // ""] | join("")) - elif .type == "quote" and (.quote.rich_text? // null) != null then - $indent + "> " + ([.quote.rich_text[]? | .plain_text // ""] | join("")) - elif .type == "to_do" and (.to_do.rich_text? // null) != null then - $indent + (if .to_do.checked then "[x] " else "[ ] " end) + ([.to_do.rich_text[]? | .plain_text // ""] | join("")) - elif .type == "toggle" and (.toggle.rich_text? // null) != null then - $indent + ([.toggle.rich_text[]? | .plain_text // ""] | join("")) - elif .type == "callout" and (.callout.rich_text? // null) != null then - $indent + ([.callout.rich_text[]? | .plain_text // ""] | join("")) - elif .type == "code" and (.code.rich_text? // null) != null then - $indent + "```" + (.code.language // "") + "\n" + ([.code.rich_text[]? | .plain_text // ""] | join("")) + "\n" + $indent + "```" - else - empty - end' - - local has_children=$(echo "$block" | jq -r '.has_children // false') - local child_block_id=$(echo "$block" | jq -r '.id') - - if [ "$has_children" = "true" ] && [ -n "$child_block_id" ]; then - fetch_blocks "$child_block_id" "$indent " - fi - done - } - - DESCRIPTION=$(fetch_blocks "$PAGE_ID" "") - - CONTEXT="${TITLE}\n${DESCRIPTION}" - - echo "found=true" >> "$GITHUB_OUTPUT" - echo "public_url=$PUBLIC_URL" >> "$GITHUB_OUTPUT" - echo "notion_id=$PARSED_ID" >> "$GITHUB_OUTPUT" - { - echo "context<<EOF" - printf '%s\n' "$TITLE" - printf '%s\n' "$DESCRIPTION" - echo "EOF" - } >> "$GITHUB_OUTPUT" - fi - fi - - - name: Send a message to the PR if task does not exist - if: steps.check_notion.outputs.found == 'false' - uses: ./.github/composite/send-message - with: - prId: ${{ inputs.PR_ID }} - message: | - Task ID ${{ steps.parse_id.outputs.id || 'N/A'}} does not exist on Notion. Please set a valid Notion task ID to the start of the PR title. - - - name: Send a message to the PR if title does not have number prefix - if: failure() && steps.parse_id.outcome == 'failure' - uses: ./.github/composite/send-message - with: - prId: ${{ inputs.PR_ID }} - message: | - ## TITLE ID REQUIRED - - Please add the ID of your Notion ticket to the title of the PR. - - **Example:** `420: Foo Bar` - - - name: Fail if task does not exist - if: steps.check_notion.outputs.found == 'false' - shell: bash - run: | - echo "Task verification failed." - exit 1 - - - name: Update PR description with public link - shell: bash - env: - GH_TOKEN: ${{ github.token }} - run: | - set -euo pipefail - - PUBLIC_URL=${{ steps.check_notion.outputs.public_url }} - ID=${{ steps.parse_id.outputs.id }} - PR_ID=${{ inputs.PR_ID }} - - if [[ "$PUBLIC_URL" = "null" ]]; then - echo "Public URL set to null when it should not be. Exiting" - exit 1 - fi - - CURRENT_BODY="$(gh pr view "$PR_ID" --json body -q .body)" - - NEW_LINE="## [$ID]($PUBLIC_URL)" - - NEW_BODY=$(echo -e "$CURRENT_BODY" | awk -v link="$NEW_LINE" ' - FNR == 2 { - print link - } - FNR != 2 { - print $0 - } - ') - - gh pr edit "$PR_ID" --body "$NEW_BODY" - - - name: Update Notion task with PR link - shell: bash - env: - GH_TOKEN: ${{ github.token }} - run: | - set -euo pipefail - - PR_LINK=${{ github.server_url }}/${{ github.repository }}/pull/${{ inputs.PR_ID }} - PR_ID="${{ inputs.PR_ID }}" - NOTION_ID="${{ steps.parse_id.outputs.id }}" - - DATA_BODY='{ - "filter": { - "property": "ID", - "unique_id": { - "equals": '${NOTION_ID}' - } - } - }' - - notion_data=$(curl --request POST \ - --header "accept: application/json" \ - --header "Authorization: Bearer ${NOTION_SECRET}" \ - --header "Content-Type: application/json" \ - --header "Notion-Version: 2025-09-03" \ - --data "${DATA_BODY}" \ - https://api.notion.com/v1/data_sources/$NOTION_TASK_DB_ID/query | jq '.') - - NOTION_PAGE_ID=$(echo "$notion_data" | jq -r '.results[0].id') - - if [ "$NOTION_PAGE_ID" == "null" ] || [ -z "$NOTION_PAGE_ID" ]; then - echo "Error: Could not find Notion page for ID $NOTION_ID. Exiting." - exit 1 - fi - - prs_raw=$(echo "$notion_data" | jq -r '.results[0].properties."PRs (AUTO)".rich_text') - existing_prs=$(echo "$prs_raw" | jq -r '.[].plain_text') - - if echo "$existing_prs" | grep -Fq "$PR_LINK"; then - echo "PR link $PR_LINK is already present in Notion task. No update needed." - exit 0 - fi - - new_pr_obj=$(printf '{"text": {"content": "%s\\n"}}' "$PR_LINK") - new_prs_arr=$(echo "$prs_raw" | jq --arg new_obj_str "$new_pr_obj" '. + [$new_obj_str | fromjson]') - - PATCH_BODY='{ - "properties": { - "PRs (AUTO)": { - "rich_text": '$new_prs_arr' - } - } - }' - - status_code_output=$(curl -s -w "HTTPSTATUS:%{http_code}" \ - --request PATCH \ - --header "Authorization: Bearer ${NOTION_SECRET}" \ - --header "Content-Type: application/json" \ - --header "Notion-Version: 2025-09-03" \ - --data "$PATCH_BODY" \ - "https://api.notion.com/v1/pages/$NOTION_PAGE_ID") - - status_code=$(echo "$status_code_output" | sed -E 's/.*HTTPSTATUS:([0-9]{3}).*/\1/') - response_body=$(echo "$status_code_output" | sed -E 's/HTTPSTATUS:[0-9]{3}//') - - if [[ "$status_code" -ne "200" ]]; then - echo "Failed to update Notion task. Status code was $status_code." - exit 1 - fi - - echo "Notion task updated successfully with PR link: $PR_LINK." diff --git a/.github/composite/notion-checks/action.yml b/.github/composite/notion-checks/action.yml index 74429b0d9..7665a6b68 100644 --- a/.github/composite/notion-checks/action.yml +++ b/.github/composite/notion-checks/action.yml @@ -44,3 +44,5 @@ runs: - name: Run script shell: bash run: bun .github/scripts/notion + env: + PR_ID: ${{ inputs.PR_ID }} From 9292ab837e21ee8da3fd72dd8d0a37ded17b6e9a Mon Sep 17 00:00:00 2001 From: Tahmid Ahmed <tahmid@tahmid.io> Date: Mon, 19 Jan 2026 00:23:39 -0500 Subject: [PATCH 053/101] 641: blebefewgwgbewrg --- .github/scripts/notion/index.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/scripts/notion/index.ts b/.github/scripts/notion/index.ts index 2f8b981b0..13278ded2 100644 --- a/.github/scripts/notion/index.ts +++ b/.github/scripts/notion/index.ts @@ -31,7 +31,8 @@ const notionDbId = (() => { })(); async function main() { - const _ = checkNotionPrAndGetTask(notionSecret, prId, notionDbId); + const task = await checkNotionPrAndGetTask(notionSecret, prId, notionDbId); + console.log(task.taskContent); } main() From a874c12e6a0e62077ca5fcc860a27dcd84ace9ed Mon Sep 17 00:00:00 2001 From: Tahmid Ahmed <tahmid@tahmid.io> Date: Mon, 19 Jan 2026 00:25:30 -0500 Subject: [PATCH 054/101] 641: --- .github/composite/notion-checks/action.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/composite/notion-checks/action.yml b/.github/composite/notion-checks/action.yml index 7665a6b68..eb81a1a22 100644 --- a/.github/composite/notion-checks/action.yml +++ b/.github/composite/notion-checks/action.yml @@ -46,3 +46,4 @@ runs: run: bun .github/scripts/notion env: PR_ID: ${{ inputs.PR_ID }} + GH_TOKEN: ${{ github.token }} From dd978a6da90cea31e5510caf3623da3aff4ab862 Mon Sep 17 00:00:00 2001 From: Tahmid Ahmed <tahmid@tahmid.io> Date: Mon, 19 Jan 2026 00:26:37 -0500 Subject: [PATCH 055/101] 641: debug --- .github/scripts/notion/pr/index.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/scripts/notion/pr/index.ts b/.github/scripts/notion/pr/index.ts index b21af5299..a3662bc7d 100644 --- a/.github/scripts/notion/pr/index.ts +++ b/.github/scripts/notion/pr/index.ts @@ -30,6 +30,8 @@ export async function checkNotionPrAndGetTask( const ticketNum = parseInt(title, 10); + console.log(title, ticketNum); + if (!Number.isNaN(ticketNum) && (ticketNum <= 0 || ticketNum > 999)) { await sendMessage(prId, `No numeric prefix found in PR title: ${title}`); process.exit(1); From ba4babcdd3b1d9e26d493d81769d5add65ba198a Mon Sep 17 00:00:00 2001 From: Tahmid Ahmed <tahmid@tahmid.io> Date: Mon, 19 Jan 2026 00:27:19 -0500 Subject: [PATCH 056/101] 641: debug2 --- .github/scripts/notion/pr/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/scripts/notion/pr/index.ts b/.github/scripts/notion/pr/index.ts index a3662bc7d..60a943c44 100644 --- a/.github/scripts/notion/pr/index.ts +++ b/.github/scripts/notion/pr/index.ts @@ -30,8 +30,6 @@ export async function checkNotionPrAndGetTask( const ticketNum = parseInt(title, 10); - console.log(title, ticketNum); - if (!Number.isNaN(ticketNum) && (ticketNum <= 0 || ticketNum > 999)) { await sendMessage(prId, `No numeric prefix found in PR title: ${title}`); process.exit(1); @@ -44,6 +42,8 @@ export async function checkNotionPrAndGetTask( process.exit(1); } + console.log(task); + const blocks = await _fetchBlocks(client, task.id); return { From 82ade02205bf071cb511ec032b6aebf22fbb5d83 Mon Sep 17 00:00:00 2001 From: Tahmid Ahmed <tahmid@tahmid.io> Date: Mon, 19 Jan 2026 00:29:10 -0500 Subject: [PATCH 057/101] 641: debug3 --- .github/scripts/notion/pr/utils.ts | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/.github/scripts/notion/pr/utils.ts b/.github/scripts/notion/pr/utils.ts index bf16e2435..e37995ac5 100644 --- a/.github/scripts/notion/pr/utils.ts +++ b/.github/scripts/notion/pr/utils.ts @@ -1,9 +1,10 @@ -import type { Client } from "@notionhq/client"; import type { BlockObjectResponse, RichTextItemResponse, } from "@notionhq/client/build/src/api-endpoints"; +import { isFullBlock, type Client } from "@notionhq/client"; + export async function _fetchBlocks( client: Client, blockId: string, @@ -16,21 +17,16 @@ export async function _fetchBlocks( const lines: string[] = []; for (const block of response.results) { - if (!("type" in block)) continue; + if (!isFullBlock(block)) continue; - const blockResponse = block as BlockObjectResponse; - const markdown = blockToMarkdown(blockResponse, indent); + const markdown = blockToMarkdown(block, indent); if (markdown) { lines.push(markdown); } - if (blockResponse.has_children) { - const childLines = await _fetchBlocks( - client, - blockResponse.id, - indent + " ", - ); + if (block.has_children) { + const childLines = await _fetchBlocks(client, block.id, indent + " "); lines.push(...childLines); } } From add5062f5f7fdfafb79a3f2948972e14b2fa54fa Mon Sep 17 00:00:00 2001 From: Tahmid Ahmed <tahmid@tahmid.io> Date: Mon, 19 Jan 2026 00:29:24 -0500 Subject: [PATCH 058/101] 641: debug4 --- .github/scripts/notion/pr/utils.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/scripts/notion/pr/utils.ts b/.github/scripts/notion/pr/utils.ts index e37995ac5..9c51134b7 100644 --- a/.github/scripts/notion/pr/utils.ts +++ b/.github/scripts/notion/pr/utils.ts @@ -14,6 +14,8 @@ export async function _fetchBlocks( block_id: blockId, }); + console.log(response); + const lines: string[] = []; for (const block of response.results) { From fa170af7f6dbdc5ac3ae768653a61a7f6907c087 Mon Sep 17 00:00:00 2001 From: Tahmid Ahmed <tahmid@tahmid.io> Date: Mon, 19 Jan 2026 00:42:29 -0500 Subject: [PATCH 059/101] 641: gk3ng3g --- .github/scripts/notion/commits/index.ts | 49 ++++++++++++++++++++++++- .github/scripts/notion/index.ts | 4 ++ 2 files changed, 52 insertions(+), 1 deletion(-) diff --git a/.github/scripts/notion/commits/index.ts b/.github/scripts/notion/commits/index.ts index 9bab774e5..1fb0de205 100644 --- a/.github/scripts/notion/commits/index.ts +++ b/.github/scripts/notion/commits/index.ts @@ -1 +1,48 @@ -export async function _checkCommits(taskId: number) {} +import { $ } from "bun"; +import { sendMessage } from "utils/send-message"; + +export async function _checkCommits(taskId: number) { + const taskIdString = taskId.toString(); + + const res = await $`gh pr view ${taskId} --json commits`.text(); + const { commits } = JSON.parse(res) as { + commits?: { messageHeadline: string }[]; + }; + + if (!commits || commits.length === 0) { + console.log("No commits found in PR"); + return; + } + + const failedCommits = commits + .map((commit) => commit.messageHeadline) + .filter((message) => !message.startsWith(taskIdString)); + + if (failedCommits.length == 0) { + console.log("All commits are valid"); + return; + } + + for (const message of failedCommits) { + console.warn(`Invalid commit: ${message}`); + } + + const failedList = failedCommits.join("\n"); + + await sendMessage( + taskId, + ` +### Commit Validation Failed +The following commits do not start with the required Notion ID \`${taskIdString}\`: + +\`\`\` +${failedList} +\`\`\` + +Please rebase and update your commit messages. +All messages should be of the following format: \`${taskIdString}: Example commit\``.trim(), + ); + + console.error("One or more commits do not match the Notion ID."); + process.exit(1); +} diff --git a/.github/scripts/notion/index.ts b/.github/scripts/notion/index.ts index 13278ded2..d5029fe02 100644 --- a/.github/scripts/notion/index.ts +++ b/.github/scripts/notion/index.ts @@ -1,3 +1,4 @@ +import { _checkCommits } from "notion/commits"; import { checkNotionPrAndGetTask } from "notion/pr"; export * from "./pr"; @@ -32,7 +33,10 @@ const notionDbId = (() => { async function main() { const task = await checkNotionPrAndGetTask(notionSecret, prId, notionDbId); + console.log(task.taskContent); + + await _checkCommits(prId); } main() From 9d0ac62eefe913c336f7bd1a03e11f27434f4eef Mon Sep 17 00:00:00 2001 From: Tahmid Ahmed <tahmid@tahmid.io> Date: Mon, 19 Jan 2026 00:43:42 -0500 Subject: [PATCH 060/101] 641: gk3ng3g --- .github/scripts/utils/send-message.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/scripts/utils/send-message.ts b/.github/scripts/utils/send-message.ts index f2f42fd56..570a716d4 100644 --- a/.github/scripts/utils/send-message.ts +++ b/.github/scripts/utils/send-message.ts @@ -2,7 +2,7 @@ import { Octokit } from "@octokit/rest"; import { RequestError } from "octokit"; const githubToken = (() => { - const v = process.env.GITHUB_TOKEN; + const v = process.env.GH_TOKEN; return v; })(); From f99f5785343ccc32eece35d22ad1600fc254fccf Mon Sep 17 00:00:00 2001 From: Tahmid Ahmed <tahmid@tahmid.io> Date: Mon, 19 Jan 2026 00:44:37 -0500 Subject: [PATCH 061/101] 641: wgerg --- .github/scripts/utils/send-message.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/scripts/utils/send-message.ts b/.github/scripts/utils/send-message.ts index 570a716d4..25a70a981 100644 --- a/.github/scripts/utils/send-message.ts +++ b/.github/scripts/utils/send-message.ts @@ -33,7 +33,7 @@ export async function sendMessage( issue_number: prId, owner, repo, - body: JSON.stringify({ body: message }), + body: message, }); } catch (e) { let d: string; From dacd640ce14f078df1cfe6e1c5b7a5f4c0814dda Mon Sep 17 00:00:00 2001 From: Tahmid Ahmed <tahmid@tahmid.io> Date: Mon, 19 Jan 2026 00:46:53 -0500 Subject: [PATCH 062/101] 641: --- .github/scripts/notion/commits/index.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/scripts/notion/commits/index.ts b/.github/scripts/notion/commits/index.ts index 1fb0de205..bd0e31803 100644 --- a/.github/scripts/notion/commits/index.ts +++ b/.github/scripts/notion/commits/index.ts @@ -4,6 +4,8 @@ import { sendMessage } from "utils/send-message"; export async function _checkCommits(taskId: number) { const taskIdString = taskId.toString(); + console.log(taskIdString); + const res = await $`gh pr view ${taskId} --json commits`.text(); const { commits } = JSON.parse(res) as { commits?: { messageHeadline: string }[]; @@ -16,6 +18,7 @@ export async function _checkCommits(taskId: number) { const failedCommits = commits .map((commit) => commit.messageHeadline) + .map((s) => s.trim()) .filter((message) => !message.startsWith(taskIdString)); if (failedCommits.length == 0) { From b845ff21d12c3bbb88a26009eb854460ba6790f8 Mon Sep 17 00:00:00 2001 From: Tahmid Ahmed <tahmid@tahmid.io> Date: Mon, 19 Jan 2026 00:48:05 -0500 Subject: [PATCH 063/101] 641: --- .github/scripts/notion/index.ts | 10 +++++++--- .github/scripts/notion/pr/index.ts | 1 + .github/scripts/notion/pr/types.ts | 1 + 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/.github/scripts/notion/index.ts b/.github/scripts/notion/index.ts index d5029fe02..50fe22095 100644 --- a/.github/scripts/notion/index.ts +++ b/.github/scripts/notion/index.ts @@ -32,11 +32,15 @@ const notionDbId = (() => { })(); async function main() { - const task = await checkNotionPrAndGetTask(notionSecret, prId, notionDbId); + const { taskId, taskContent } = await checkNotionPrAndGetTask( + notionSecret, + prId, + notionDbId, + ); - console.log(task.taskContent); + console.log(taskContent); - await _checkCommits(prId); + await _checkCommits(taskId); } main() diff --git a/.github/scripts/notion/pr/index.ts b/.github/scripts/notion/pr/index.ts index 60a943c44..ce20f79b9 100644 --- a/.github/scripts/notion/pr/index.ts +++ b/.github/scripts/notion/pr/index.ts @@ -49,5 +49,6 @@ export async function checkNotionPrAndGetTask( return { task, taskContent: blocks.join("\n"), + taskId: ticketNum, }; } diff --git a/.github/scripts/notion/pr/types.ts b/.github/scripts/notion/pr/types.ts index eb735d3af..14f950cff 100644 --- a/.github/scripts/notion/pr/types.ts +++ b/.github/scripts/notion/pr/types.ts @@ -3,4 +3,5 @@ import { type PageObjectResponse } from "@notionhq/client"; export type NotionTaskObject = { task: PageObjectResponse; taskContent: string; + taskId: number; }; From b4d67fbc520cd7a12716ecfbb54676808e0d26ec Mon Sep 17 00:00:00 2001 From: Tahmid Ahmed <tahmid@tahmid.io> Date: Mon, 19 Jan 2026 00:50:00 -0500 Subject: [PATCH 064/101] 641: --- .github/scripts/notion/commits/index.ts | 6 ++---- .github/scripts/notion/index.ts | 2 +- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/.github/scripts/notion/commits/index.ts b/.github/scripts/notion/commits/index.ts index bd0e31803..258831f0d 100644 --- a/.github/scripts/notion/commits/index.ts +++ b/.github/scripts/notion/commits/index.ts @@ -1,12 +1,10 @@ import { $ } from "bun"; import { sendMessage } from "utils/send-message"; -export async function _checkCommits(taskId: number) { +export async function _checkCommits(taskId: number, prId: number) { const taskIdString = taskId.toString(); - console.log(taskIdString); - - const res = await $`gh pr view ${taskId} --json commits`.text(); + const res = await $`gh pr view ${prId} --json commits`.text(); const { commits } = JSON.parse(res) as { commits?: { messageHeadline: string }[]; }; diff --git a/.github/scripts/notion/index.ts b/.github/scripts/notion/index.ts index 50fe22095..c84f70c01 100644 --- a/.github/scripts/notion/index.ts +++ b/.github/scripts/notion/index.ts @@ -40,7 +40,7 @@ async function main() { console.log(taskContent); - await _checkCommits(taskId); + await _checkCommits(taskId, prId); } main() From 3ece3fef639d19872ed4fdb8bd65333621fc24e3 Mon Sep 17 00:00:00 2001 From: Tahmid Ahmed <tahmid@tahmid.io> Date: Mon, 19 Jan 2026 00:52:10 -0500 Subject: [PATCH 065/101] 641:fkbwefkew --- .github/scripts/utils/send-message.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/scripts/utils/send-message.ts b/.github/scripts/utils/send-message.ts index 25a70a981..78bf13580 100644 --- a/.github/scripts/utils/send-message.ts +++ b/.github/scripts/utils/send-message.ts @@ -29,12 +29,14 @@ export async function sendMessage( }); try { - await client.rest.issues.createComment({ + const res = await client.rest.issues.createComment({ issue_number: prId, owner, repo, body: message, }); + + console.log(res); } catch (e) { let d: string; if (e instanceof RequestError) { From 5c8eb1a46db318c74442b621276283776ac84f35 Mon Sep 17 00:00:00 2001 From: Tahmid Ahmed <tahmid@tahmid.io> Date: Mon, 19 Jan 2026 00:53:16 -0500 Subject: [PATCH 066/101] 641:fkbwefkew --- .github/scripts/notion/commits/index.ts | 2 +- .github/scripts/utils/send-message.ts | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/.github/scripts/notion/commits/index.ts b/.github/scripts/notion/commits/index.ts index 258831f0d..12bc8ec50 100644 --- a/.github/scripts/notion/commits/index.ts +++ b/.github/scripts/notion/commits/index.ts @@ -31,7 +31,7 @@ export async function _checkCommits(taskId: number, prId: number) { const failedList = failedCommits.join("\n"); await sendMessage( - taskId, + prId, ` ### Commit Validation Failed The following commits do not start with the required Notion ID \`${taskIdString}\`: diff --git a/.github/scripts/utils/send-message.ts b/.github/scripts/utils/send-message.ts index 78bf13580..25a70a981 100644 --- a/.github/scripts/utils/send-message.ts +++ b/.github/scripts/utils/send-message.ts @@ -29,14 +29,12 @@ export async function sendMessage( }); try { - const res = await client.rest.issues.createComment({ + await client.rest.issues.createComment({ issue_number: prId, owner, repo, body: message, }); - - console.log(res); } catch (e) { let d: string; if (e instanceof RequestError) { From 4681e9f96b152e798ddd16d4530dea8eec4b6015 Mon Sep 17 00:00:00 2001 From: Tahmid Ahmed <tahmid@tahmid.io> Date: Mon, 19 Jan 2026 01:01:59 -0500 Subject: [PATCH 067/101] 641: meowwwwww --- .github/composite/build-image/action.yml | 21 ++-------------- .github/composite/disable-mandb/action.yml | 8 ------ .github/composite/load-secrets/action.yml | 3 --- .github/composite/notion-checks/action.yml | 20 ++------------- .github/composite/redeploy/action.yml | 21 ++-------------- .github/composite/setup-ci/action.yml | 25 +++++++++++++++++++ .../test/backend-pre-test/action.yml | 20 ++------------- .../composite/test/backend-test/action.yml | 23 +++-------------- .../composite/test/frontend-test/action.yml | 21 ++-------------- 9 files changed, 38 insertions(+), 124 deletions(-) delete mode 100644 .github/composite/disable-mandb/action.yml create mode 100644 .github/composite/setup-ci/action.yml diff --git a/.github/composite/build-image/action.yml b/.github/composite/build-image/action.yml index cf82223c3..d105371e7 100644 --- a/.github/composite/build-image/action.yml +++ b/.github/composite/build-image/action.yml @@ -23,8 +23,8 @@ inputs: runs: using: "composite" steps: - - name: Disable man-db - uses: ./.github/composite/disable-mandb + - name: Setup CI + uses: ./.github/composite/setup-ci - name: Set up pnpm uses: pnpm/action-setup@master @@ -41,23 +41,6 @@ runs: java-version: "25" cache: "maven" - - name: Set up bun - uses: oven-sh/setup-bun@v2 - with: - bun-version: latest - - - name: Cache Bun dependencies - uses: actions/cache@v4 - with: - path: ~/.bun/install/cache - key: ${{ runner.os }}-bun-${{ hashFiles('.github/scripts/bun.lock') }} - restore-keys: | - ${{ runner.os }}-bun- - - - name: Install deps - shell: bash - run: bun install --cwd .github/scripts --frozen-lockfile - - name: Load secrets uses: ./.github/composite/load-secrets with: diff --git a/.github/composite/disable-mandb/action.yml b/.github/composite/disable-mandb/action.yml deleted file mode 100644 index 75d93dc65..000000000 --- a/.github/composite/disable-mandb/action.yml +++ /dev/null @@ -1,8 +0,0 @@ -name: "Disable man-db" -description: "Disable unnecessary man-db triggers (speed up CI time)" -runs: - using: "composite" - steps: - - name: Check GPG secret key & passphrase exists - shell: bash - run: sudo bash -ec 'echo "set man-db/auto-update false" | debconf-communicate; dpkg-reconfigure man-db' diff --git a/.github/composite/load-secrets/action.yml b/.github/composite/load-secrets/action.yml index 347c05178..7d5f3c558 100644 --- a/.github/composite/load-secrets/action.yml +++ b/.github/composite/load-secrets/action.yml @@ -12,9 +12,6 @@ inputs: runs: using: "composite" steps: - - name: Disable man-db - uses: ./.github/composite/disable-mandb - - name: Check GPG secret key & passphrase exists shell: bash run: | diff --git a/.github/composite/notion-checks/action.yml b/.github/composite/notion-checks/action.yml index eb81a1a22..43fffa73e 100644 --- a/.github/composite/notion-checks/action.yml +++ b/.github/composite/notion-checks/action.yml @@ -15,24 +15,8 @@ inputs: runs: using: "composite" steps: - - name: Disable man-db - uses: ./.github/composite/disable-mandb - - - uses: oven-sh/setup-bun@v2 - with: - bun-version: latest - - - name: Cache Bun dependencies - uses: actions/cache@v4 - with: - path: ~/.bun/install/cache - key: ${{ runner.os }}-bun-${{ hashFiles('.github/scripts/bun.lock') }} - restore-keys: | - ${{ runner.os }}-bun- - - - name: Install deps - shell: bash - run: bun install --cwd .github/scripts --frozen-lockfile + - name: Setup CI + uses: ./.github/composite/setup-ci - name: Load secrets uses: ./.github/composite/load-secrets diff --git a/.github/composite/redeploy/action.yml b/.github/composite/redeploy/action.yml index 609f133b0..5649b0dcc 100644 --- a/.github/composite/redeploy/action.yml +++ b/.github/composite/redeploy/action.yml @@ -19,8 +19,8 @@ inputs: runs: using: "composite" steps: - - name: Disable man-db - uses: ./.github/composite/disable-mandb + - name: Setup CI + uses: ./.github/composite/setup-ci - name: Set up pnpm uses: pnpm/action-setup@master @@ -37,23 +37,6 @@ runs: java-version: "25" cache: "maven" - - name: Set up bun - uses: oven-sh/setup-bun@v2 - with: - bun-version: latest - - - name: Cache Bun dependencies - uses: actions/cache@v4 - with: - path: ~/.bun/install/cache - key: ${{ runner.os }}-bun-${{ hashFiles('.github/scripts/bun.lock') }} - restore-keys: | - ${{ runner.os }}-bun- - - - name: Install deps - shell: bash - run: bun install --cwd .github/scripts --frozen-lockfile - - name: Load secrets uses: ./.github/composite/load-secrets with: diff --git a/.github/composite/setup-ci/action.yml b/.github/composite/setup-ci/action.yml new file mode 100644 index 000000000..398fc5276 --- /dev/null +++ b/.github/composite/setup-ci/action.yml @@ -0,0 +1,25 @@ +name: "Setup CI" +description: "Setup dependencies required to run CI (disable man-db)" + +runs: + using: "composite" + steps: + - name: Disable man-db + shell: bash + run: sudo bash -ec 'echo "set man-db/auto-update false" | debconf-communicate; dpkg-reconfigure man-db' + + - uses: oven-sh/setup-bun@v2 + with: + bun-version: latest + + - name: Cache Bun dependencies + uses: actions/cache@v4 + with: + path: ~/.bun/install/cache + key: ${{ runner.os }}-bun-${{ hashFiles('.github/scripts/bun.lock') }} + restore-keys: | + ${{ runner.os }}-bun- + + - name: Install deps + shell: bash + run: bun install --cwd .github/scripts --frozen-lockfile diff --git a/.github/composite/test/backend-pre-test/action.yml b/.github/composite/test/backend-pre-test/action.yml index 2cfa4c0b6..5c4311948 100644 --- a/.github/composite/test/backend-pre-test/action.yml +++ b/.github/composite/test/backend-pre-test/action.yml @@ -3,8 +3,8 @@ description: "Run backend linter & formatter checks + attempt to compile (NO TES runs: using: "composite" steps: - - name: Disable man-db - uses: ./.github/composite/disable-mandb + - name: Setup CI + uses: ./.github/composite/setup-ci - name: Set up OpenJDK 25 uses: actions/setup-java@v4 @@ -20,22 +20,6 @@ runs: javac -version echo "JAVA_HOME=$JAVA_HOME" - - uses: oven-sh/setup-bun@v2 - with: - bun-version: latest - - - name: Cache Bun dependencies - uses: actions/cache@v4 - with: - path: ~/.bun/install/cache - key: ${{ runner.os }}-bun-${{ hashFiles('.github/scripts/bun.lock') }} - restore-keys: | - ${{ runner.os }}-bun- - - - name: Install deps - shell: bash - run: bun install --cwd .github/scripts --frozen-lockfile - - name: Run script shell: bash run: bun .github/scripts/run-backend-compile-tests.ts diff --git a/.github/composite/test/backend-test/action.yml b/.github/composite/test/backend-test/action.yml index a240ac154..7368c2cff 100644 --- a/.github/composite/test/backend-test/action.yml +++ b/.github/composite/test/backend-test/action.yml @@ -16,8 +16,8 @@ inputs: runs: using: "composite" steps: - - name: Disable man-db - uses: ./.github/composite/disable-mandb + - name: Setup CI + uses: ./.github/composite/setup-ci - name: Set up OpenJDK 25 uses: actions/setup-java@v4 @@ -26,23 +26,6 @@ runs: java-version: "25" cache: "maven" - - name: Set up bun - uses: oven-sh/setup-bun@v2 - with: - bun-version: latest - - - name: Cache Bun dependencies - uses: actions/cache@v4 - with: - path: ~/.bun/install/cache - key: ${{ runner.os }}-bun-${{ hashFiles('.github/scripts/bun.lock') }} - restore-keys: | - ${{ runner.os }}-bun- - - - name: Install deps - shell: bash - run: bun install --cwd .github/scripts --frozen-lockfile - - name: Load secrets uses: ./.github/composite/load-secrets with: @@ -52,7 +35,7 @@ runs: - name: Run script shell: bash - run: bun .github/scripts/run-backend-tests.ts + run: bun .github/scripts/run-backend-tests - name: Upload JaCoCo HTML report uses: actions/upload-artifact@v4 diff --git a/.github/composite/test/frontend-test/action.yml b/.github/composite/test/frontend-test/action.yml index b2a1bb37d..48b7cc9e1 100644 --- a/.github/composite/test/frontend-test/action.yml +++ b/.github/composite/test/frontend-test/action.yml @@ -12,8 +12,8 @@ inputs: runs: using: "composite" steps: - - name: Disable man-db - uses: ./.github/composite/disable-mandb + - name: Setup CI + uses: ./.github/composite/setup-ci - name: Set up pnpm uses: pnpm/action-setup@master @@ -30,23 +30,6 @@ runs: java-version: "25" cache: "maven" - - name: Set up bun - uses: oven-sh/setup-bun@v2 - with: - bun-version: latest - - - name: Cache Bun dependencies - uses: actions/cache@v4 - with: - path: ~/.bun/install/cache - key: ${{ runner.os }}-bun-${{ hashFiles('.github/scripts/bun.lock') }} - restore-keys: | - ${{ runner.os }}-bun- - - - name: Install deps - shell: bash - run: bun install --cwd .github/scripts --frozen-lockfile - - name: Load secrets uses: ./.github/composite/load-secrets with: From 930558d62815b0ea9bbb5e85afa7660bc7269d5c Mon Sep 17 00:00:00 2001 From: Tahmid Ahmed <tahmid@tahmid.io> Date: Mon, 19 Jan 2026 01:43:26 -0500 Subject: [PATCH 068/101] 641: meowwwwwwwdfwbnrbwgw --- .github/composite/notion-checks/action.yml | 13 ----- .github/composite/redeploy/action.yml | 13 ----- .github/composite/setup-ci/action.yml | 9 ++++ .../test/backend-pre-test/action.yml | 7 --- .../composite/test/backend-test/action.yml | 13 ----- .../composite/test/frontend-test/action.yml | 15 ------ .github/scripts/load-secrets/env/load.ts | 17 +++++-- .github/scripts/notion/index.ts | 44 ++++++++++------- .github/scripts/redeploy/db/index.ts | 6 +-- .github/scripts/redeploy/do/index.ts | 6 +-- .github/scripts/redeploy/index.ts | 48 +++++++++++-------- .github/scripts/run-backend-tests.ts | 6 ++- .github/scripts/run-frontend-tests.ts | 8 +++- .github/scripts/utils/run-backend-instance.ts | 3 +- .github/scripts/utils/send-message.ts | 2 +- 15 files changed, 97 insertions(+), 113 deletions(-) diff --git a/.github/composite/notion-checks/action.yml b/.github/composite/notion-checks/action.yml index 43fffa73e..a02d1f256 100644 --- a/.github/composite/notion-checks/action.yml +++ b/.github/composite/notion-checks/action.yml @@ -5,12 +5,6 @@ inputs: PR_ID: description: "PR ID" required: true - GPG_PRIVATE_KEY: - description: "GPG Private Key" - required: true - GPG_PASSPHRASE: - description: "GPG Passphrase" - required: true runs: using: "composite" @@ -18,13 +12,6 @@ runs: - name: Setup CI uses: ./.github/composite/setup-ci - - name: Load secrets - uses: ./.github/composite/load-secrets - with: - GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }} - GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} - UNLOAD_ENVIRONMENTS: ci - - name: Run script shell: bash run: bun .github/scripts/notion diff --git a/.github/composite/redeploy/action.yml b/.github/composite/redeploy/action.yml index 5649b0dcc..36ebb49b8 100644 --- a/.github/composite/redeploy/action.yml +++ b/.github/composite/redeploy/action.yml @@ -2,12 +2,6 @@ name: "Re-Deploy to DigitalOcean" description: "Trigger a deployment to DigitalOcean and migrate the associated database." inputs: - GPG_PRIVATE_KEY: - description: "GPG Private Key" - required: true - GPG_PASSPHRASE: - description: "GPG Passphrase" - required: true ENVIRONMENT: description: '"staging" or "production"' required: false @@ -37,13 +31,6 @@ runs: java-version: "25" cache: "maven" - - name: Load secrets - uses: ./.github/composite/load-secrets - with: - GPG_PRIVATE_KEY: ${{ inputs.GPG_PRIVATE_KEY }} - GPG_PASSPHRASE: ${{ inputs.GPG_PASSPHRASE }} - UNLOAD_ENVIRONMENTS: ci - - name: Expose GitHub Runtime uses: crazy-max/ghaction-github-runtime@v3 diff --git a/.github/composite/setup-ci/action.yml b/.github/composite/setup-ci/action.yml index 398fc5276..01d85f69e 100644 --- a/.github/composite/setup-ci/action.yml +++ b/.github/composite/setup-ci/action.yml @@ -1,9 +1,18 @@ name: "Setup CI" description: "Setup dependencies required to run CI (disable man-db)" +inputs: + GH_TOKEN: + description: "GitHub token for sending messages. Set this to ${{ secrets.GITHUB_TOKEN }} for default logic." + required: false + runs: using: "composite" steps: + - name: Export GitHub Token + shell: bash + if: ${{ inputs.GH_TOKEN != "" }} + run: echo "GH_TOKEN=${{ inputs.GH_TOKEN }}" >> $GITHUB_ENV - name: Disable man-db shell: bash run: sudo bash -ec 'echo "set man-db/auto-update false" | debconf-communicate; dpkg-reconfigure man-db' diff --git a/.github/composite/test/backend-pre-test/action.yml b/.github/composite/test/backend-pre-test/action.yml index 5c4311948..5ef49f8fd 100644 --- a/.github/composite/test/backend-pre-test/action.yml +++ b/.github/composite/test/backend-pre-test/action.yml @@ -13,13 +13,6 @@ runs: java-version: "25" cache: "maven" - - name: Verify Java version - shell: bash - run: | - java -version - javac -version - echo "JAVA_HOME=$JAVA_HOME" - - name: Run script shell: bash run: bun .github/scripts/run-backend-compile-tests.ts diff --git a/.github/composite/test/backend-test/action.yml b/.github/composite/test/backend-test/action.yml index 7368c2cff..7ec9738b3 100644 --- a/.github/composite/test/backend-test/action.yml +++ b/.github/composite/test/backend-test/action.yml @@ -2,12 +2,6 @@ name: "Backend test" description: "Run backend tests" inputs: - GPG_PRIVATE_KEY: - description: "GPG Private Key" - required: true - GPG_PASSPHRASE: - description: "GPG Passphrase" - required: true UPLOAD_TEST_COV: description: "Boolean indicating whether tests should be uploaded to Codecov or not." required: false @@ -26,13 +20,6 @@ runs: java-version: "25" cache: "maven" - - name: Load secrets - uses: ./.github/composite/load-secrets - with: - GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }} - GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} - UNLOAD_ENVIRONMENTS: ci-app - - name: Run script shell: bash run: bun .github/scripts/run-backend-tests diff --git a/.github/composite/test/frontend-test/action.yml b/.github/composite/test/frontend-test/action.yml index 48b7cc9e1..0b8b53e92 100644 --- a/.github/composite/test/frontend-test/action.yml +++ b/.github/composite/test/frontend-test/action.yml @@ -1,14 +1,6 @@ name: "Frontend Test" description: "Run frontend tests" -inputs: - GPG_PRIVATE_KEY: - description: "GPG Private Key" - required: true - GPG_PASSPHRASE: - description: "GPG Passphrase" - required: true - runs: using: "composite" steps: @@ -30,13 +22,6 @@ runs: java-version: "25" cache: "maven" - - name: Load secrets - uses: ./.github/composite/load-secrets - with: - GPG_PRIVATE_KEY: ${{ inputs.GPG_PRIVATE_KEY }} - GPG_PASSPHRASE: ${{ inputs.GPG_PASSPHRASE }} - UNLOAD_ENVIRONMENTS: ci-app - - name: Run script shell: bash run: bun .github/scripts/run-frontend-tests.ts diff --git a/.github/scripts/load-secrets/env/load.ts b/.github/scripts/load-secrets/env/load.ts index 081d3a7ee..fcbe2278c 100644 --- a/.github/scripts/load-secrets/env/load.ts +++ b/.github/scripts/load-secrets/env/load.ts @@ -1,8 +1,10 @@ +import { $ } from "bun"; + +let isGitCryptUnlocked = false; + /** * @param environments - List of environment files to load. - * @param mask - Should variables be masked. Defaults to `true`. __NOTE: This will only work in a GitHub Action runner.__ - * - * __NOTE: Be very careful of setting `mask` to `false`.__ + * @param mask_PLZ_DO_NOT_TURN_OFF_UNLESS_YOU_KNOW_WHAT_UR_DOING - Should variables be masked. Defaults to `true`. __NOTE: This will only work in a GitHub Action runner.__ * * @returns a map of the loaded environments as a key and value inside of a map. * @@ -11,8 +13,13 @@ */ export async function getEnvVariables( environments: string[], - mask = true, + mask_PLZ_DO_NOT_TURN_OFF_UNLESS_YOU_KNOW_WHAT_UR_DOING = true, ): Promise<Map<string, string>> { + if (!isGitCryptUnlocked) { + await $`git-crypt unlock`; + isGitCryptUnlocked = true; + } + const loaded = new Map<string, string>(); for (const env of environments) { @@ -49,7 +56,7 @@ export async function getEnvVariables( } } - if (mask) { + if (mask_PLZ_DO_NOT_TURN_OFF_UNLESS_YOU_KNOW_WHAT_UR_DOING) { for (const [varName, value] of loaded.entries()) { console.log(`Masking ${varName}`); console.log(`::add-mask::${value}`); diff --git a/.github/scripts/notion/index.ts b/.github/scripts/notion/index.ts index c84f70c01..1fb648e72 100644 --- a/.github/scripts/notion/index.ts +++ b/.github/scripts/notion/index.ts @@ -1,16 +1,9 @@ +import { getEnvVariables } from "load-secrets/env/load"; import { _checkCommits } from "notion/commits"; import { checkNotionPrAndGetTask } from "notion/pr"; export * from "./pr"; -const notionSecret = (() => { - const v = process.env.NOTION_SECRET; - if (!v) { - throw new Error("NOTION_SECRET is required"); - } - return v; -})(); - const prId = (() => { const v = process.env.PR_ID; if (!v) { @@ -23,15 +16,11 @@ const prId = (() => { return n; })(); -const notionDbId = (() => { - const v = process.env.NOTION_TASK_DB_ID; - if (!v) { - throw new Error("NOTION_TASK_DB_ID is required"); - } - return v; -})(); - async function main() { + const { notionDbId, notionSecret } = parseCiEnv( + await getEnvVariables([".env.ci"]), + ); + const { taskId, taskContent } = await checkNotionPrAndGetTask( notionSecret, prId, @@ -43,6 +32,29 @@ async function main() { await _checkCommits(taskId, prId); } +function parseCiEnv(ciEnv: Map<string, string>) { + const notionDbId = (() => { + const v = ciEnv.get("NOTION_TASK_DB_ID"); + if (!v) { + throw new Error("Missing NOTION_TASK_DB_ID from .env.ci"); + } + return v; + })(); + + const notionSecret = (() => { + const v = ciEnv.get("NOTION_SECRET"); + if (!v) { + throw new Error("Missing NOTION_SECRET from .env.ci"); + } + return v; + })(); + + return { + notionDbId, + notionSecret, + }; +} + main() .then(() => { process.exit(0); diff --git a/.github/scripts/redeploy/db/index.ts b/.github/scripts/redeploy/db/index.ts index a8c5b78a6..b7121ddfe 100644 --- a/.github/scripts/redeploy/db/index.ts +++ b/.github/scripts/redeploy/db/index.ts @@ -4,11 +4,11 @@ import { $ } from "bun"; export async function _migrateDb({ environment, - envVariables, + env, sha, }: { environment: Environment; - envVariables: Map<string, string>; + env: Map<string, string>; sha?: string; }): Promise<void> { await $`git fetch origin main:main`; @@ -25,6 +25,6 @@ export async function _migrateDb({ } await $.env( - Object.fromEntries(envVariables), + Object.fromEntries(env), )`./mvnw flyway:migrate -Dflyway.locations=filesystem:./db/migration`; } diff --git a/.github/scripts/redeploy/do/index.ts b/.github/scripts/redeploy/do/index.ts index 0129e1cf1..c5614e71e 100644 --- a/.github/scripts/redeploy/do/index.ts +++ b/.github/scripts/redeploy/do/index.ts @@ -16,18 +16,18 @@ export async function _migrateDo({ token, environment, projectId, - envVariables, + env, }: { token: string; environment: Environment; projectId: string; - envVariables: Map<string, string>; + env: Map<string, string>; }): Promise<void> { const authProvider = new DigitalOceanApiKeyAuthenticationProvider(token); const adapter = new FetchRequestAdapter(authProvider); const client = createDigitalOceanClient(adapter); - const envs: App_variable_definition[] = envVariables + const envs: App_variable_definition[] = env .entries() .map(([key, value]) => { const env: App_variable_definition = { diff --git a/.github/scripts/redeploy/index.ts b/.github/scripts/redeploy/index.ts index 26e21ffc6..ae8fea8c8 100644 --- a/.github/scripts/redeploy/index.ts +++ b/.github/scripts/redeploy/index.ts @@ -4,22 +4,6 @@ import { getEnvVariables } from "load-secrets/env/load"; import { _migrateDb } from "redeploy/db"; import { _migrateDo } from "redeploy/do"; -const projectId = (() => { - const v = process.env.DIGITALOCEAN_PROJECT_ID; - if (!v) { - throw new Error("Project ID is required"); - } - return v; -})(); - -const token = (() => { - const v = process.env.DIGITALOCEAN_PAT; - if (!v) { - throw new Error("DigitalOcean PAT is required"); - } - return v; -})(); - const environment: Environment = (() => { const v = process.env.ENVIRONMENT; if (!v) { @@ -42,17 +26,18 @@ const sha = (() => { "SHA must be available in ENV if script is being run in staging environment.", ); } - return v; })(); async function main() { // should already be called // await $`git-crypt unlock`; - const envVariables = await getEnvVariables([environment]); + + const { token, projectId } = parseCiEnv(await getEnvVariables([".env.ci"])); + const appEnv = await getEnvVariables([environment]); await _migrateDb({ - envVariables, + env: appEnv, environment, sha, }); @@ -61,10 +46,33 @@ async function main() { projectId, token, environment, - envVariables, + env: appEnv, }); } +function parseCiEnv(ciEnv: Map<string, string>) { + const token = (() => { + const v = ciEnv.get("DIGITALOCEAN_PAT"); + if (!v) { + throw new Error("Missing DIGITALOCEAN_PAT from .env.ci"); + } + return v; + })(); + + const projectId = (() => { + const v = ciEnv.get("DIGITALOCEAN_PROJECT_ID"); + if (!v) { + throw new Error("Missing DIGITALOCEAN_PROJECT_ID from .env.ci"); + } + return v; + })(); + + return { + token, + projectId, + }; +} + main() .then(() => { process.exit(0); diff --git a/.github/scripts/run-backend-tests.ts b/.github/scripts/run-backend-tests.ts index 3f6d253ea..c86ff5c55 100644 --- a/.github/scripts/run-backend-tests.ts +++ b/.github/scripts/run-backend-tests.ts @@ -1,8 +1,12 @@ import { $ } from "bun"; +import { getEnvVariables } from "load-secrets/env/load"; import { db } from "utils/run-local-db"; async function main() { try { + const env = await getEnvVariables(["ci-app"]); + const $$ = $.env(Object.fromEntries(env)); + await db.start(); await $`./mvnw -B install -D skipTests --no-transfer-progress`; @@ -13,7 +17,7 @@ async function main() { await $`corepack enable pnpm`; await $`cd email && pnpm i --frozen-lockfile && ./email.sh && cd ..`; - await $`./mvnw clean verify -Dspring.profiles.active=ci`; + await $$`./mvnw clean verify -Dspring.profiles.active=ci`; } finally { await db.end(); } diff --git a/.github/scripts/run-frontend-tests.ts b/.github/scripts/run-frontend-tests.ts index 53d0c4a80..e3718dc60 100644 --- a/.github/scripts/run-frontend-tests.ts +++ b/.github/scripts/run-frontend-tests.ts @@ -1,15 +1,19 @@ import { $ } from "bun"; +import { getEnvVariables } from "load-secrets/env/load"; import { backend } from "utils/run-backend-instance"; import { db } from "utils/run-local-db"; async function main() { try { + const env = await getEnvVariables(["ci-app"]); + const $$ = $.env(Object.fromEntries(env)); + await db.start(); - await backend.start(); + await backend.start(env); await $`corepack enable pnpm`; await $`pnpm --dir js i --frozen-lockfile`; - await $`pnpm --dir js run generate`; + await $$`pnpm --dir js run generate`; await $`pnpm --dir js run test`; } finally { await backend.end(); diff --git a/.github/scripts/utils/run-backend-instance.ts b/.github/scripts/utils/run-backend-instance.ts index 41bac45f2..60dbe2c13 100644 --- a/.github/scripts/utils/run-backend-instance.ts +++ b/.github/scripts/utils/run-backend-instance.ts @@ -4,7 +4,7 @@ import { cyan } from "@/../utils/colors"; let be: Bun.Subprocess<"ignore", Bun.BunFile, "inherit"> | undefined; -async function start() { +async function start(env: Map<string, string>) { try { console.log("Starting backend instance..."); @@ -16,6 +16,7 @@ async function start() { be = Bun.spawn( ["./mvnw", "-Dspring-boot.run.profiles=ci", "spring-boot:run"], { + env: Object.fromEntries(env), stdout: logFile, }, ); diff --git a/.github/scripts/utils/send-message.ts b/.github/scripts/utils/send-message.ts index 25a70a981..e808a2dc1 100644 --- a/.github/scripts/utils/send-message.ts +++ b/.github/scripts/utils/send-message.ts @@ -20,7 +20,7 @@ export async function sendMessage( token?: string, ) { try { - if (!githubToken && !token) { + if (!token && !githubToken) { throw new Error("Some token should be set"); } From 8b7bf3093b18e1237d15456ba15e7ae9d7142739 Mon Sep 17 00:00:00 2001 From: Tahmid Ahmed <tahmid@tahmid.io> Date: Mon, 19 Jan 2026 01:44:56 -0500 Subject: [PATCH 069/101] 641: --- .github/scripts/utils/send-message.ts | 2 +- .github/workflows/pr-verifications.yml | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/scripts/utils/send-message.ts b/.github/scripts/utils/send-message.ts index e808a2dc1..a4b6f8103 100644 --- a/.github/scripts/utils/send-message.ts +++ b/.github/scripts/utils/send-message.ts @@ -2,7 +2,7 @@ import { Octokit } from "@octokit/rest"; import { RequestError } from "octokit"; const githubToken = (() => { - const v = process.env.GH_TOKEN; + const v = process.env.GITHUB_TOKEN; return v; })(); diff --git a/.github/workflows/pr-verifications.yml b/.github/workflows/pr-verifications.yml index 9dc4b9073..7840c271c 100644 --- a/.github/workflows/pr-verifications.yml +++ b/.github/workflows/pr-verifications.yml @@ -27,5 +27,3 @@ jobs: id: notion_check with: PR_ID: ${{ github.event.number }} - GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }} - GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} From 40ac2ed1f50eae04f9a41ead8b8f45801326f07a Mon Sep 17 00:00:00 2001 From: Tahmid Ahmed <tahmid@tahmid.io> Date: Mon, 19 Jan 2026 01:46:12 -0500 Subject: [PATCH 070/101] 641: --- .github/composite/setup-ci/action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/composite/setup-ci/action.yml b/.github/composite/setup-ci/action.yml index 01d85f69e..0dd79ff28 100644 --- a/.github/composite/setup-ci/action.yml +++ b/.github/composite/setup-ci/action.yml @@ -3,7 +3,7 @@ description: "Setup dependencies required to run CI (disable man-db)" inputs: GH_TOKEN: - description: "GitHub token for sending messages. Set this to ${{ secrets.GITHUB_TOKEN }} for default logic." + description: "GitHub token for sending messages. Set this to secrets.GITHUB_TOKEN for default logic." required: false runs: From 4dab6487d5bc7eb046e7067e23fd60e4b2f1d260 Mon Sep 17 00:00:00 2001 From: Tahmid Ahmed <tahmid@tahmid.io> Date: Mon, 19 Jan 2026 01:47:00 -0500 Subject: [PATCH 071/101] 641: --- .github/composite/setup-ci/action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/composite/setup-ci/action.yml b/.github/composite/setup-ci/action.yml index 0dd79ff28..485b0d644 100644 --- a/.github/composite/setup-ci/action.yml +++ b/.github/composite/setup-ci/action.yml @@ -11,7 +11,7 @@ runs: steps: - name: Export GitHub Token shell: bash - if: ${{ inputs.GH_TOKEN != "" }} + if: inputs.GH_TOKEN != "" run: echo "GH_TOKEN=${{ inputs.GH_TOKEN }}" >> $GITHUB_ENV - name: Disable man-db shell: bash From 88bb7cce21f46134cef893016f87150b47c86ef7 Mon Sep 17 00:00:00 2001 From: Tahmid Ahmed <tahmid@tahmid.io> Date: Mon, 19 Jan 2026 01:47:49 -0500 Subject: [PATCH 072/101] 641: --- .github/composite/setup-ci/action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/composite/setup-ci/action.yml b/.github/composite/setup-ci/action.yml index 485b0d644..b7cc821a9 100644 --- a/.github/composite/setup-ci/action.yml +++ b/.github/composite/setup-ci/action.yml @@ -11,7 +11,7 @@ runs: steps: - name: Export GitHub Token shell: bash - if: inputs.GH_TOKEN != "" + if: inputs.GH_TOKEN != '' run: echo "GH_TOKEN=${{ inputs.GH_TOKEN }}" >> $GITHUB_ENV - name: Disable man-db shell: bash From a93abed472ad7aec593611a20d6601a7bb0f83eb Mon Sep 17 00:00:00 2001 From: Tahmid Ahmed <tahmid@tahmid.io> Date: Mon, 19 Jan 2026 01:52:30 -0500 Subject: [PATCH 073/101] 641: --- .github/composite/build-image/action.yml | 10 +++------- .github/composite/load-secrets/action.yml | 2 ++ .github/composite/notion-checks/action.yml | 9 +++++++++ .github/composite/redeploy/action.yml | 9 +++++++++ .github/composite/setup-ci/action.yml | 17 +++++++++++++++++ .github/composite/test/backend-test/action.yml | 9 +++++++++ .github/composite/test/frontend-test/action.yml | 11 +++++++++++ 7 files changed, 60 insertions(+), 7 deletions(-) diff --git a/.github/composite/build-image/action.yml b/.github/composite/build-image/action.yml index d105371e7..56b1df033 100644 --- a/.github/composite/build-image/action.yml +++ b/.github/composite/build-image/action.yml @@ -25,6 +25,9 @@ runs: steps: - name: Setup CI uses: ./.github/composite/setup-ci + with: + GPG_PRIVATE_KEY: ${{ inputs.GPG_PRIVATE_KEY }} + GPG_PASSPHRASE: ${{ inputs.GPG_PASSPHRASE }} - name: Set up pnpm uses: pnpm/action-setup@master @@ -41,13 +44,6 @@ runs: java-version: "25" cache: "maven" - - name: Load secrets - uses: ./.github/composite/load-secrets - with: - GPG_PRIVATE_KEY: ${{ inputs.GPG_PRIVATE_KEY }} - GPG_PASSPHRASE: ${{ inputs.GPG_PASSPHRASE }} - UNLOAD_ENVIRONMENTS: ci,ci-app - - name: Expose GitHub Runtime uses: crazy-max/ghaction-github-runtime@v3 diff --git a/.github/composite/load-secrets/action.yml b/.github/composite/load-secrets/action.yml index 7d5f3c558..162822458 100644 --- a/.github/composite/load-secrets/action.yml +++ b/.github/composite/load-secrets/action.yml @@ -1,5 +1,6 @@ name: "Load Secrets" description: "Load secrets & mask all loaded secrets" + inputs: GPG_PRIVATE_KEY: description: "GPG Private Key" @@ -9,6 +10,7 @@ inputs: required: true UNLOAD_ENVIRONMENTS: description: "Comma separated string of what environment files to load" + runs: using: "composite" steps: diff --git a/.github/composite/notion-checks/action.yml b/.github/composite/notion-checks/action.yml index a02d1f256..907999558 100644 --- a/.github/composite/notion-checks/action.yml +++ b/.github/composite/notion-checks/action.yml @@ -5,12 +5,21 @@ inputs: PR_ID: description: "PR ID" required: true + GPG_PRIVATE_KEY: + description: "GPG Private Key" + required: true + GPG_PASSPHRASE: + description: "GPG Passphrase" + required: true runs: using: "composite" steps: - name: Setup CI uses: ./.github/composite/setup-ci + with: + GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }} + GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} - name: Run script shell: bash diff --git a/.github/composite/redeploy/action.yml b/.github/composite/redeploy/action.yml index 36ebb49b8..63e392579 100644 --- a/.github/composite/redeploy/action.yml +++ b/.github/composite/redeploy/action.yml @@ -2,6 +2,12 @@ name: "Re-Deploy to DigitalOcean" description: "Trigger a deployment to DigitalOcean and migrate the associated database." inputs: + GPG_PRIVATE_KEY: + description: "GPG Private Key" + required: true + GPG_PASSPHRASE: + description: "GPG Passphrase" + required: true ENVIRONMENT: description: '"staging" or "production"' required: false @@ -15,6 +21,9 @@ runs: steps: - name: Setup CI uses: ./.github/composite/setup-ci + with: + GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }} + GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} - name: Set up pnpm uses: pnpm/action-setup@master diff --git a/.github/composite/setup-ci/action.yml b/.github/composite/setup-ci/action.yml index b7cc821a9..c8ceccb92 100644 --- a/.github/composite/setup-ci/action.yml +++ b/.github/composite/setup-ci/action.yml @@ -5,6 +5,12 @@ inputs: GH_TOKEN: description: "GitHub token for sending messages. Set this to secrets.GITHUB_TOKEN for default logic." required: false + GPG_PASSPHRASE: + description: "GPG passphrase if secrets will be used." + required: false + GPG_PRIVATE_KEY: + description: "GPG private key if secrets will be used." + required: false runs: using: "composite" @@ -32,3 +38,14 @@ runs: - name: Install deps shell: bash run: bun install --cwd .github/scripts --frozen-lockfile + + - name: Load GPG secret key + uses: crazy-max/ghaction-import-gpg@v6 + if: inputs.GPG_PRIVATE_KEY != "" && inputs.GPG_PASSPHRASE != "" + with: + gpg_private_key: ${{ inputs.GPG_PRIVATE_KEY }} + passphrase: ${{ inputs.GPG_PASSPHRASE }} + + - name: Install git-crypt + if: inputs.GPG_PRIVATE_KEY != "" && inputs.GPG_PASSPHRASE != "" + uses: flydiverny/setup-git-crypt@v4 diff --git a/.github/composite/test/backend-test/action.yml b/.github/composite/test/backend-test/action.yml index 7ec9738b3..e4b12cd14 100644 --- a/.github/composite/test/backend-test/action.yml +++ b/.github/composite/test/backend-test/action.yml @@ -2,6 +2,12 @@ name: "Backend test" description: "Run backend tests" inputs: + GPG_PRIVATE_KEY: + description: "GPG Private Key" + required: true + GPG_PASSPHRASE: + description: "GPG Passphrase" + required: true UPLOAD_TEST_COV: description: "Boolean indicating whether tests should be uploaded to Codecov or not." required: false @@ -12,6 +18,9 @@ runs: steps: - name: Setup CI uses: ./.github/composite/setup-ci + with: + GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }} + GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} - name: Set up OpenJDK 25 uses: actions/setup-java@v4 diff --git a/.github/composite/test/frontend-test/action.yml b/.github/composite/test/frontend-test/action.yml index 0b8b53e92..93cb82c71 100644 --- a/.github/composite/test/frontend-test/action.yml +++ b/.github/composite/test/frontend-test/action.yml @@ -1,11 +1,22 @@ name: "Frontend Test" description: "Run frontend tests" +input: + GPG_PRIVATE_KEY: + description: "GPG Private Key" + required: true + GPG_PASSPHRASE: + description: "GPG Passphrase" + required: true + runs: using: "composite" steps: - name: Setup CI uses: ./.github/composite/setup-ci + with: + GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }} + GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} - name: Set up pnpm uses: pnpm/action-setup@master From c5076a32c38c0625cde3f0e8dbf86801217d3e74 Mon Sep 17 00:00:00 2001 From: Tahmid Ahmed <tahmid@tahmid.io> Date: Mon, 19 Jan 2026 01:53:47 -0500 Subject: [PATCH 074/101] 641: --- .github/composite/setup-ci/action.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/composite/setup-ci/action.yml b/.github/composite/setup-ci/action.yml index c8ceccb92..d4cdb8a80 100644 --- a/.github/composite/setup-ci/action.yml +++ b/.github/composite/setup-ci/action.yml @@ -41,11 +41,11 @@ runs: - name: Load GPG secret key uses: crazy-max/ghaction-import-gpg@v6 - if: inputs.GPG_PRIVATE_KEY != "" && inputs.GPG_PASSPHRASE != "" + if: inputs.GPG_PRIVATE_KEY != '' && inputs.GPG_PASSPHRASE != '' with: gpg_private_key: ${{ inputs.GPG_PRIVATE_KEY }} passphrase: ${{ inputs.GPG_PASSPHRASE }} - name: Install git-crypt - if: inputs.GPG_PRIVATE_KEY != "" && inputs.GPG_PASSPHRASE != "" + if: inputs.GPG_PRIVATE_KEY != '' && inputs.GPG_PASSPHRASE != '' uses: flydiverny/setup-git-crypt@v4 From c30feea8b5afcd7f65fd35d7c8081bbd2d180937 Mon Sep 17 00:00:00 2001 From: Tahmid Ahmed <tahmid@tahmid.io> Date: Mon, 19 Jan 2026 01:55:04 -0500 Subject: [PATCH 075/101] 641: --- .github/workflows/pr-verifications.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/pr-verifications.yml b/.github/workflows/pr-verifications.yml index 7840c271c..9dc4b9073 100644 --- a/.github/workflows/pr-verifications.yml +++ b/.github/workflows/pr-verifications.yml @@ -27,3 +27,5 @@ jobs: id: notion_check with: PR_ID: ${{ github.event.number }} + GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }} + GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} From 955aa15064ec9c147ab40981326349127cda3c04 Mon Sep 17 00:00:00 2001 From: Tahmid Ahmed <tahmid@tahmid.io> Date: Mon, 19 Jan 2026 01:57:51 -0500 Subject: [PATCH 076/101] 641: --- .github/scripts/notion/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/scripts/notion/index.ts b/.github/scripts/notion/index.ts index 1fb648e72..2edccb385 100644 --- a/.github/scripts/notion/index.ts +++ b/.github/scripts/notion/index.ts @@ -18,7 +18,7 @@ const prId = (() => { async function main() { const { notionDbId, notionSecret } = parseCiEnv( - await getEnvVariables([".env.ci"]), + await getEnvVariables(["ci"]), ); const { taskId, taskContent } = await checkNotionPrAndGetTask( From ee3f3b4254d745ac5ae845085089417c5b23c0d6 Mon Sep 17 00:00:00 2001 From: Tahmid Ahmed <tahmid@tahmid.io> Date: Mon, 19 Jan 2026 01:59:10 -0500 Subject: [PATCH 077/101] 641: test --- .github/scripts/utils/send-message.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/scripts/utils/send-message.ts b/.github/scripts/utils/send-message.ts index a4b6f8103..fc25fac2f 100644 --- a/.github/scripts/utils/send-message.ts +++ b/.github/scripts/utils/send-message.ts @@ -20,6 +20,7 @@ export async function sendMessage( token?: string, ) { try { + console.log(owner, repo); if (!token && !githubToken) { throw new Error("Some token should be set"); } From 007afdb6728473574f8791d98f5ec8d2f36b5a64 Mon Sep 17 00:00:00 2001 From: Tahmid Ahmed <tahmid@tahmid.io> Date: Mon, 19 Jan 2026 02:03:21 -0500 Subject: [PATCH 078/101] 641: test --- .github/composite/notion-checks/action.yml | 1 - .github/composite/setup-ci/action.yml | 4 ++++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/composite/notion-checks/action.yml b/.github/composite/notion-checks/action.yml index 907999558..c342fabf6 100644 --- a/.github/composite/notion-checks/action.yml +++ b/.github/composite/notion-checks/action.yml @@ -26,4 +26,3 @@ runs: run: bun .github/scripts/notion env: PR_ID: ${{ inputs.PR_ID }} - GH_TOKEN: ${{ github.token }} diff --git a/.github/composite/setup-ci/action.yml b/.github/composite/setup-ci/action.yml index d4cdb8a80..8c77c17de 100644 --- a/.github/composite/setup-ci/action.yml +++ b/.github/composite/setup-ci/action.yml @@ -19,6 +19,10 @@ runs: shell: bash if: inputs.GH_TOKEN != '' run: echo "GH_TOKEN=${{ inputs.GH_TOKEN }}" >> $GITHUB_ENV + - name: Export GitHub Token (default) + shell: bash + if: inputs.GH_TOKEN == '' + run: echo "GH_TOKEN=${{ github.token }}" >> $GITHUB_ENV - name: Disable man-db shell: bash run: sudo bash -ec 'echo "set man-db/auto-update false" | debconf-communicate; dpkg-reconfigure man-db' From 802418e1a3aec68cf79178b2e84e1e5339b03118 Mon Sep 17 00:00:00 2001 From: Tahmid Ahmed <tahmid@tahmid.io> Date: Mon, 19 Jan 2026 02:04:32 -0500 Subject: [PATCH 079/101] 641: test pt2 --- .github/composite/setup-ci/action.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/composite/setup-ci/action.yml b/.github/composite/setup-ci/action.yml index 8c77c17de..30a96f03e 100644 --- a/.github/composite/setup-ci/action.yml +++ b/.github/composite/setup-ci/action.yml @@ -19,10 +19,15 @@ runs: shell: bash if: inputs.GH_TOKEN != '' run: echo "GH_TOKEN=${{ inputs.GH_TOKEN }}" >> $GITHUB_ENV + - name: Export GitHub Token (default) shell: bash if: inputs.GH_TOKEN == '' run: echo "GH_TOKEN=${{ github.token }}" >> $GITHUB_ENV + + - name: Verify Token Length + run: echo "GH_TOKEN length is ${#GH_TOKEN}" + - name: Disable man-db shell: bash run: sudo bash -ec 'echo "set man-db/auto-update false" | debconf-communicate; dpkg-reconfigure man-db' From 193af7b95cebd7856ec9e1735c2a7e5117607d08 Mon Sep 17 00:00:00 2001 From: Tahmid Ahmed <tahmid@tahmid.io> Date: Mon, 19 Jan 2026 02:04:57 -0500 Subject: [PATCH 080/101] 641: test pt3 --- .github/composite/setup-ci/action.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/composite/setup-ci/action.yml b/.github/composite/setup-ci/action.yml index 30a96f03e..ef236fc26 100644 --- a/.github/composite/setup-ci/action.yml +++ b/.github/composite/setup-ci/action.yml @@ -26,6 +26,7 @@ runs: run: echo "GH_TOKEN=${{ github.token }}" >> $GITHUB_ENV - name: Verify Token Length + shell: bash run: echo "GH_TOKEN length is ${#GH_TOKEN}" - name: Disable man-db From c3596aba57e4832a6d5a3cdf08f4c14a31c2805c Mon Sep 17 00:00:00 2001 From: Tahmid Ahmed <tahmid@tahmid.io> Date: Mon, 19 Jan 2026 02:07:08 -0500 Subject: [PATCH 081/101] 641: test pt4 --- .github/composite/setup-ci/action.yml | 9 --------- .github/scripts/utils/send-message.ts | 2 +- 2 files changed, 1 insertion(+), 10 deletions(-) diff --git a/.github/composite/setup-ci/action.yml b/.github/composite/setup-ci/action.yml index ef236fc26..e13b7622d 100644 --- a/.github/composite/setup-ci/action.yml +++ b/.github/composite/setup-ci/action.yml @@ -2,9 +2,6 @@ name: "Setup CI" description: "Setup dependencies required to run CI (disable man-db)" inputs: - GH_TOKEN: - description: "GitHub token for sending messages. Set this to secrets.GITHUB_TOKEN for default logic." - required: false GPG_PASSPHRASE: description: "GPG passphrase if secrets will be used." required: false @@ -17,12 +14,6 @@ runs: steps: - name: Export GitHub Token shell: bash - if: inputs.GH_TOKEN != '' - run: echo "GH_TOKEN=${{ inputs.GH_TOKEN }}" >> $GITHUB_ENV - - - name: Export GitHub Token (default) - shell: bash - if: inputs.GH_TOKEN == '' run: echo "GH_TOKEN=${{ github.token }}" >> $GITHUB_ENV - name: Verify Token Length diff --git a/.github/scripts/utils/send-message.ts b/.github/scripts/utils/send-message.ts index fc25fac2f..b25f63a71 100644 --- a/.github/scripts/utils/send-message.ts +++ b/.github/scripts/utils/send-message.ts @@ -2,7 +2,7 @@ import { Octokit } from "@octokit/rest"; import { RequestError } from "octokit"; const githubToken = (() => { - const v = process.env.GITHUB_TOKEN; + const v = process.env.GH_TOKEN; return v; })(); From c305ae4b2d84aed78b038ad5a30a74a78870e0a4 Mon Sep 17 00:00:00 2001 From: Tahmid Ahmed <tahmid@tahmid.io> Date: Mon, 19 Jan 2026 02:16:34 -0500 Subject: [PATCH 082/101] 641: test pt5 --- .github/composite/setup-ci/action.yml | 4 --- .github/scripts/build-image.ts | 26 ++++++++++++++----- .github/scripts/load-secrets/index.ts | 6 ++--- .../index.ts} | 0 4 files changed, 22 insertions(+), 14 deletions(-) rename .github/scripts/utils/{send-message.ts => send-message/index.ts} (100%) diff --git a/.github/composite/setup-ci/action.yml b/.github/composite/setup-ci/action.yml index e13b7622d..c622d79b5 100644 --- a/.github/composite/setup-ci/action.yml +++ b/.github/composite/setup-ci/action.yml @@ -16,10 +16,6 @@ runs: shell: bash run: echo "GH_TOKEN=${{ github.token }}" >> $GITHUB_ENV - - name: Verify Token Length - shell: bash - run: echo "GH_TOKEN length is ${#GH_TOKEN}" - - name: Disable man-db shell: bash run: sudo bash -ec 'echo "set man-db/auto-update false" | debconf-communicate; dpkg-reconfigure man-db' diff --git a/.github/scripts/build-image.ts b/.github/scripts/build-image.ts index 9cac4e110..a7be89566 100644 --- a/.github/scripts/build-image.ts +++ b/.github/scripts/build-image.ts @@ -1,4 +1,5 @@ import { $ } from "bun"; +import { getEnvVariables } from "load-secrets/env/load"; import { backend } from "utils/run-backend-instance"; import { db } from "utils/run-local-db"; @@ -8,19 +9,18 @@ const tagPrefix = process.env.TAG_PREFIX || ""; const shouldDockerUpload = Boolean(process.env.DOCKER_UPLOAD) || false; const serverProfiles = process.env.SERVER_PROFILES || "prod"; -const dockerHubPat = process.env.DOCKER_HUB_PAT; -if (!dockerHubPat) { - throw new Error("DOCKER_HUB_PAT is required."); -} - async function main() { try { + const ciEnv = await getEnvVariables(["env"]); + const { dockerHubPat } = parseCiEnv(ciEnv); + const $$ = $.env(Object.fromEntries(ciEnv)); + await db.start(); - await backend.start(); + await backend.start(ciEnv); await $`corepack enable pnpm`; await $`pnpm --dir js i -D --frozen-lockfile`; - await $`pnpm --dir js run generate`; + await $$`pnpm --dir js run generate`; // copy old tz format from build-image.sh const timestamp = new Date() @@ -85,6 +85,18 @@ async function main() { } } +function parseCiEnv(ciEnv: Map<string, string>) { + const dockerHubPat = (() => { + const v = ciEnv.get("DOCKER_HUB_PAT"); + if (!v) { + throw new Error("Missing DOCKER_HUB_PAT from .env.ci"); + } + return v; + })(); + + return { dockerHubPat }; +} + main() .then(() => { process.exit(0); diff --git a/.github/scripts/load-secrets/index.ts b/.github/scripts/load-secrets/index.ts index 1c30e9c2d..e2bab15f1 100644 --- a/.github/scripts/load-secrets/index.ts +++ b/.github/scripts/load-secrets/index.ts @@ -1,4 +1,3 @@ -import { $ } from "bun"; import { getEnvVariables } from "load-secrets/env/load"; // UNLOAD_ENVIRONMENTS="prod,staging,dev" @@ -16,9 +15,10 @@ const excludedVars = [ "JAVA_HOME", ]; +/** + * @deprecated this is no longer usable. + */ async function main() { - await $`git-crypt unlock`; - const envs = unloadEnvironments .split(",") .map((e) => e.trim()) diff --git a/.github/scripts/utils/send-message.ts b/.github/scripts/utils/send-message/index.ts similarity index 100% rename from .github/scripts/utils/send-message.ts rename to .github/scripts/utils/send-message/index.ts From aede6582ca887dd8aec9476d51bc7bb0d1d76797 Mon Sep 17 00:00:00 2001 From: Tahmid Ahmed <tahmid@tahmid.io> Date: Mon, 19 Jan 2026 02:18:21 -0500 Subject: [PATCH 083/101] 641: test pt6 --- .github/scripts/notion/pr/index.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/scripts/notion/pr/index.ts b/.github/scripts/notion/pr/index.ts index ce20f79b9..a95ff889e 100644 --- a/.github/scripts/notion/pr/index.ts +++ b/.github/scripts/notion/pr/index.ts @@ -42,8 +42,6 @@ export async function checkNotionPrAndGetTask( process.exit(1); } - console.log(task); - const blocks = await _fetchBlocks(client, task.id); return { From db6f5585c25c0f52d80cf77fae5f06e0126f2986 Mon Sep 17 00:00:00 2001 From: Tahmid Ahmed <tahmid@tahmid.io> Date: Mon, 19 Jan 2026 02:24:20 -0500 Subject: [PATCH 084/101] 641: test pt67 --- .github/scripts/run-backend-tests.ts | 11 +++++++++-- .github/scripts/run-frontend-tests.ts | 6 +++--- .github/scripts/utils/run-local-db.ts | 16 ++++++++++------ 3 files changed, 22 insertions(+), 11 deletions(-) diff --git a/.github/scripts/run-backend-tests.ts b/.github/scripts/run-backend-tests.ts index c86ff5c55..ff3d48006 100644 --- a/.github/scripts/run-backend-tests.ts +++ b/.github/scripts/run-backend-tests.ts @@ -5,9 +5,16 @@ import { db } from "utils/run-local-db"; async function main() { try { const env = await getEnvVariables(["ci-app"]); - const $$ = $.env(Object.fromEntries(env)); + const localDbEnv = await db.start(); - await db.start(); + if (!localDbEnv) { + throw new Error("Local db empty when it should not be"); + } + + const $$ = $.env({ + ...Object.fromEntries(env), + ...localDbEnv, + }); await $`./mvnw -B install -D skipTests --no-transfer-progress`; diff --git a/.github/scripts/run-frontend-tests.ts b/.github/scripts/run-frontend-tests.ts index e3718dc60..83cb6dfaf 100644 --- a/.github/scripts/run-frontend-tests.ts +++ b/.github/scripts/run-frontend-tests.ts @@ -6,11 +6,11 @@ import { db } from "utils/run-local-db"; async function main() { try { const env = await getEnvVariables(["ci-app"]); - const $$ = $.env(Object.fromEntries(env)); - - await db.start(); + const localDbEnv = await db.start(); await backend.start(env); + const $$ = $.env({ ...Object.fromEntries(env), ...localDbEnv }); + await $`corepack enable pnpm`; await $`pnpm --dir js i --frozen-lockfile`; await $$`pnpm --dir js run generate`; diff --git a/.github/scripts/utils/run-local-db.ts b/.github/scripts/utils/run-local-db.ts index 3cc636549..0332d3589 100644 --- a/.github/scripts/utils/run-local-db.ts +++ b/.github/scripts/utils/run-local-db.ts @@ -42,17 +42,21 @@ async function start() { process.exit(1); } - process.env.DATABASE_HOST = "localhost"; - process.env.DATABASE_PORT = "5440"; - process.env.DATABASE_NAME = "codebloom"; - process.env.DATABASE_USER = "postgres"; - process.env.DATABASE_PASSWORD = "postgres"; + const env = { + DATABASE_HOST: "localhost", + DATABASE_PORT: "5440", + DATABASE_NAME: "codebloom", + DATABASE_USER: "postgres", + DATABASE_PASSWORD: "postgres", + }; console.log("postres started, running migrations..."); - await $`./mvnw flyway:migrate -Dflyway.locations=filesystem:./db`; + await $.env(env)`./mvnw flyway:migrate -Dflyway.locations=filesystem:./db`; console.log("postgres ready"); + + return env; } catch (e) { console.error(e); end(); From 3c20f15d298e5233fc372a2df09fb6bc6d6fde22 Mon Sep 17 00:00:00 2001 From: Tahmid Ahmed <tahmid@tahmid.io> Date: Mon, 19 Jan 2026 02:32:18 -0500 Subject: [PATCH 085/101] 641: test pt68 --- .github/scripts/redeploy/index.ts | 2 +- .github/scripts/run-backend-tests.ts | 1 + .github/scripts/run-frontend-tests.ts | 6 +++++- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/.github/scripts/redeploy/index.ts b/.github/scripts/redeploy/index.ts index ae8fea8c8..e3a96b32d 100644 --- a/.github/scripts/redeploy/index.ts +++ b/.github/scripts/redeploy/index.ts @@ -33,7 +33,7 @@ async function main() { // should already be called // await $`git-crypt unlock`; - const { token, projectId } = parseCiEnv(await getEnvVariables([".env.ci"])); + const { token, projectId } = parseCiEnv(await getEnvVariables(["ci"])); const appEnv = await getEnvVariables([environment]); await _migrateDb({ diff --git a/.github/scripts/run-backend-tests.ts b/.github/scripts/run-backend-tests.ts index ff3d48006..4475a9e3d 100644 --- a/.github/scripts/run-backend-tests.ts +++ b/.github/scripts/run-backend-tests.ts @@ -12,6 +12,7 @@ async function main() { } const $$ = $.env({ + ...process.env, ...Object.fromEntries(env), ...localDbEnv, }); diff --git a/.github/scripts/run-frontend-tests.ts b/.github/scripts/run-frontend-tests.ts index 83cb6dfaf..dfcdadbc7 100644 --- a/.github/scripts/run-frontend-tests.ts +++ b/.github/scripts/run-frontend-tests.ts @@ -9,7 +9,11 @@ async function main() { const localDbEnv = await db.start(); await backend.start(env); - const $$ = $.env({ ...Object.fromEntries(env), ...localDbEnv }); + const $$ = $.env({ + ...process.env, + ...Object.fromEntries(env), + ...localDbEnv, + }); await $`corepack enable pnpm`; await $`pnpm --dir js i --frozen-lockfile`; From fb6d316a33d3c9e5408c048cbf28b407ab566ac5 Mon Sep 17 00:00:00 2001 From: Tahmid Ahmed <tahmid@tahmid.io> Date: Mon, 19 Jan 2026 02:35:54 -0500 Subject: [PATCH 086/101] 641: test pt69 --- .github/scripts/build-image.ts | 11 ++++++++--- .github/scripts/load-secrets/env/load.ts | 2 +- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/.github/scripts/build-image.ts b/.github/scripts/build-image.ts index a7be89566..14b399bab 100644 --- a/.github/scripts/build-image.ts +++ b/.github/scripts/build-image.ts @@ -11,11 +11,16 @@ const serverProfiles = process.env.SERVER_PROFILES || "prod"; async function main() { try { - const ciEnv = await getEnvVariables(["env"]); + const ciEnv = await getEnvVariables(["ci"]); const { dockerHubPat } = parseCiEnv(ciEnv); - const $$ = $.env(Object.fromEntries(ciEnv)); + const localDbEnv = await db.start(); + + const $$ = $.env({ + ...process.env, + ...Object.fromEntries(ciEnv), + ...localDbEnv, + }); - await db.start(); await backend.start(ciEnv); await $`corepack enable pnpm`; diff --git a/.github/scripts/load-secrets/env/load.ts b/.github/scripts/load-secrets/env/load.ts index fcbe2278c..2497fc683 100644 --- a/.github/scripts/load-secrets/env/load.ts +++ b/.github/scripts/load-secrets/env/load.ts @@ -52,7 +52,7 @@ export async function getEnvVariables( } } } else { - console.warn(`Warning: ${envFile} not found`); + console.warn(`Warning: ${envFile.name} not found`); } } From 153176e3647510e1d148a49e3fa33d13c7fb2487 Mon Sep 17 00:00:00 2001 From: Tahmid Ahmed <tahmid@tahmid.io> Date: Mon, 19 Jan 2026 02:38:44 -0500 Subject: [PATCH 087/101] 641: test pt70 --- .github/scripts/utils/run-backend-instance.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/scripts/utils/run-backend-instance.ts b/.github/scripts/utils/run-backend-instance.ts index 60dbe2c13..505e816da 100644 --- a/.github/scripts/utils/run-backend-instance.ts +++ b/.github/scripts/utils/run-backend-instance.ts @@ -16,7 +16,7 @@ async function start(env: Map<string, string>) { be = Bun.spawn( ["./mvnw", "-Dspring-boot.run.profiles=ci", "spring-boot:run"], { - env: Object.fromEntries(env), + env: { ...process.env, ...Object.fromEntries(env) }, stdout: logFile, }, ); From 291cc7d908860665a3b3ff084cb2c11a1997820a Mon Sep 17 00:00:00 2001 From: Tahmid Ahmed <tahmid@tahmid.io> Date: Mon, 19 Jan 2026 02:43:24 -0500 Subject: [PATCH 088/101] 641: test pt80 --- .github/scripts/build-image.ts | 3 ++- .github/scripts/run-backend-tests.ts | 4 ++-- .github/scripts/run-frontend-tests.ts | 6 +++--- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/.github/scripts/build-image.ts b/.github/scripts/build-image.ts index 14b399bab..fff0b869c 100644 --- a/.github/scripts/build-image.ts +++ b/.github/scripts/build-image.ts @@ -14,10 +14,11 @@ async function main() { const ciEnv = await getEnvVariables(["ci"]); const { dockerHubPat } = parseCiEnv(ciEnv); const localDbEnv = await db.start(); + const ciAppEnv = await getEnvVariables(["ci-app"]); const $$ = $.env({ ...process.env, - ...Object.fromEntries(ciEnv), + ...Object.fromEntries(ciAppEnv), ...localDbEnv, }); diff --git a/.github/scripts/run-backend-tests.ts b/.github/scripts/run-backend-tests.ts index 4475a9e3d..04b249478 100644 --- a/.github/scripts/run-backend-tests.ts +++ b/.github/scripts/run-backend-tests.ts @@ -4,7 +4,7 @@ import { db } from "utils/run-local-db"; async function main() { try { - const env = await getEnvVariables(["ci-app"]); + const ciAppEnv = await getEnvVariables(["ci-app"]); const localDbEnv = await db.start(); if (!localDbEnv) { @@ -13,7 +13,7 @@ async function main() { const $$ = $.env({ ...process.env, - ...Object.fromEntries(env), + ...Object.fromEntries(ciAppEnv), ...localDbEnv, }); diff --git a/.github/scripts/run-frontend-tests.ts b/.github/scripts/run-frontend-tests.ts index dfcdadbc7..7e0b42226 100644 --- a/.github/scripts/run-frontend-tests.ts +++ b/.github/scripts/run-frontend-tests.ts @@ -5,13 +5,13 @@ import { db } from "utils/run-local-db"; async function main() { try { - const env = await getEnvVariables(["ci-app"]); + const ciAppEnv = await getEnvVariables(["ci-app"]); const localDbEnv = await db.start(); - await backend.start(env); + await backend.start(ciAppEnv); const $$ = $.env({ ...process.env, - ...Object.fromEntries(env), + ...Object.fromEntries(ciAppEnv), ...localDbEnv, }); From 1d4e61a9dee171257696856919d1766acc3784b4 Mon Sep 17 00:00:00 2001 From: Tahmid Ahmed <tahmid@tahmid.io> Date: Mon, 19 Jan 2026 02:48:44 -0500 Subject: [PATCH 089/101] 641: test pt81 --- .github/composite/validate-db/action.yml | 43 ++++++++++++++++++++++++ .github/scripts/build-image.ts | 6 ++++ 2 files changed, 49 insertions(+) create mode 100644 .github/composite/validate-db/action.yml diff --git a/.github/composite/validate-db/action.yml b/.github/composite/validate-db/action.yml new file mode 100644 index 000000000..777f7b13b --- /dev/null +++ b/.github/composite/validate-db/action.yml @@ -0,0 +1,43 @@ +name: "Validate DB" +description: "Validate DB against " + +inputs: + GPG_PRIVATE_KEY: + description: "GPG Private Key" + required: true + GPG_PASSPHRASE: + description: "GPG Passphrase" + required: true + +runs: + using: "composite" + steps: + - name: Setup CI + uses: ./.github/composite/setup-ci + with: + GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }} + GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} + + - name: Set up OpenJDK 25 + uses: actions/setup-java@v4 + with: + distribution: "temurin" + java-version: "25" + cache: "maven" + + - name: Run script + shell: bash + run: bun .github/scripts/run-backend-tests + + - name: Upload JaCoCo HTML report + uses: actions/upload-artifact@v4 + if: ${{ inputs.UPLOAD_TEST_COV == true }} + with: + name: jacoco-report + path: target/site/jacoco/ + + - name: Upload coverage reports to Codecov + uses: codecov/codecov-action@v5 + if: ${{ inputs.UPLOAD_TEST_COV == true }} + with: + token: ${{ secrets.CODECOV_TOKEN }} diff --git a/.github/scripts/build-image.ts b/.github/scripts/build-image.ts index fff0b869c..fb4ecd227 100644 --- a/.github/scripts/build-image.ts +++ b/.github/scripts/build-image.ts @@ -16,6 +16,12 @@ async function main() { const localDbEnv = await db.start(); const ciAppEnv = await getEnvVariables(["ci-app"]); + console.log({ + ...process.env, + ...Object.fromEntries(ciAppEnv), + ...localDbEnv, + }); + const $$ = $.env({ ...process.env, ...Object.fromEntries(ciAppEnv), From f089861656222bf2500755ff9eef0bb30e38823c Mon Sep 17 00:00:00 2001 From: Tahmid Ahmed <tahmid@tahmid.io> Date: Mon, 19 Jan 2026 02:55:10 -0500 Subject: [PATCH 090/101] 641: test pt82 --- .github/composite/validate-db/action.yml | 10 +++++++++- .github/scripts/build-image.ts | 9 +-------- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/.github/composite/validate-db/action.yml b/.github/composite/validate-db/action.yml index 777f7b13b..6a2bf5f49 100644 --- a/.github/composite/validate-db/action.yml +++ b/.github/composite/validate-db/action.yml @@ -1,5 +1,5 @@ name: "Validate DB" -description: "Validate DB against " +description: "Validate the current db/ folder at a current commit against a database" inputs: GPG_PRIVATE_KEY: @@ -8,6 +8,14 @@ inputs: GPG_PASSPHRASE: description: "GPG Passphrase" required: true + ENVIRONMENT: + description: '"staging" or "production"' + required: false + default: production + SHA: + description: "Current commit SHA" + required: false + default: ${{ github.sha }} runs: using: "composite" diff --git a/.github/scripts/build-image.ts b/.github/scripts/build-image.ts index fb4ecd227..e92ee8ae4 100644 --- a/.github/scripts/build-image.ts +++ b/.github/scripts/build-image.ts @@ -15,12 +15,7 @@ async function main() { const { dockerHubPat } = parseCiEnv(ciEnv); const localDbEnv = await db.start(); const ciAppEnv = await getEnvVariables(["ci-app"]); - - console.log({ - ...process.env, - ...Object.fromEntries(ciAppEnv), - ...localDbEnv, - }); + await backend.start(ciAppEnv); const $$ = $.env({ ...process.env, @@ -28,8 +23,6 @@ async function main() { ...localDbEnv, }); - await backend.start(ciEnv); - await $`corepack enable pnpm`; await $`pnpm --dir js i -D --frozen-lockfile`; await $$`pnpm --dir js run generate`; From 273a9539c00a2061948bd1425fdf2a90b5301bcc Mon Sep 17 00:00:00 2001 From: Tahmid Ahmed <tahmid@tahmid.io> Date: Mon, 19 Jan 2026 03:04:52 -0500 Subject: [PATCH 091/101] 641: test pt83 --- .github/composite/validate-db/action.yml | 17 --------- .github/scripts/redeploy/db/index.ts | 2 +- .github/scripts/redeploy/do/index.ts | 2 +- .github/scripts/redeploy/index.ts | 2 +- .github/scripts/{redeploy => }/types.ts | 0 .github/scripts/validate-db/index.ts | 44 ++++++++++++++++++++++++ .github/workflows/deploy-stg.yml | 36 ++----------------- 7 files changed, 50 insertions(+), 53 deletions(-) rename .github/scripts/{redeploy => }/types.ts (100%) create mode 100644 .github/scripts/validate-db/index.ts diff --git a/.github/composite/validate-db/action.yml b/.github/composite/validate-db/action.yml index 6a2bf5f49..c1b672c34 100644 --- a/.github/composite/validate-db/action.yml +++ b/.github/composite/validate-db/action.yml @@ -12,10 +12,6 @@ inputs: description: '"staging" or "production"' required: false default: production - SHA: - description: "Current commit SHA" - required: false - default: ${{ github.sha }} runs: using: "composite" @@ -36,16 +32,3 @@ runs: - name: Run script shell: bash run: bun .github/scripts/run-backend-tests - - - name: Upload JaCoCo HTML report - uses: actions/upload-artifact@v4 - if: ${{ inputs.UPLOAD_TEST_COV == true }} - with: - name: jacoco-report - path: target/site/jacoco/ - - - name: Upload coverage reports to Codecov - uses: codecov/codecov-action@v5 - if: ${{ inputs.UPLOAD_TEST_COV == true }} - with: - token: ${{ secrets.CODECOV_TOKEN }} diff --git a/.github/scripts/redeploy/db/index.ts b/.github/scripts/redeploy/db/index.ts index b7121ddfe..44384bfa8 100644 --- a/.github/scripts/redeploy/db/index.ts +++ b/.github/scripts/redeploy/db/index.ts @@ -1,4 +1,4 @@ -import type { Environment } from "redeploy/types"; +import type { Environment } from "types"; import { $ } from "bun"; diff --git a/.github/scripts/redeploy/do/index.ts b/.github/scripts/redeploy/do/index.ts index c5614e71e..bfe1618a3 100644 --- a/.github/scripts/redeploy/do/index.ts +++ b/.github/scripts/redeploy/do/index.ts @@ -1,4 +1,4 @@ -import type { Environment } from "redeploy/types"; +import type { Environment } from "types"; import { DigitalOceanApiKeyAuthenticationProvider, diff --git a/.github/scripts/redeploy/index.ts b/.github/scripts/redeploy/index.ts index e3a96b32d..ab35d747b 100644 --- a/.github/scripts/redeploy/index.ts +++ b/.github/scripts/redeploy/index.ts @@ -1,4 +1,4 @@ -import type { Environment } from "redeploy/types"; +import type { Environment } from "types"; import { getEnvVariables } from "load-secrets/env/load"; import { _migrateDb } from "redeploy/db"; diff --git a/.github/scripts/redeploy/types.ts b/.github/scripts/types.ts similarity index 100% rename from .github/scripts/redeploy/types.ts rename to .github/scripts/types.ts diff --git a/.github/scripts/validate-db/index.ts b/.github/scripts/validate-db/index.ts new file mode 100644 index 000000000..9b6b2aa10 --- /dev/null +++ b/.github/scripts/validate-db/index.ts @@ -0,0 +1,44 @@ +import type { Environment } from "types"; + +import { $ } from "bun"; +import { getEnvVariables } from "load-secrets/env/load"; + +const environment: Environment = (() => { + const v = process.env.ENVIRONMENT; + if (!v) { + throw new Error("Environment is required"); + } + + if (v !== "staging" && v !== "production") { + throw new Error( + 'Environment must be the string literal "staging" or "production"', + ); + } + + return v; +})(); + +export async function main() { + // make sure you checkout the repo first. + + const appEnv = await getEnvVariables([environment]); + + const onlyCheckPendingMigrationFlag = + environment === "staging" ? + '-Dflyway.ignoreMigrationPatterns="*:pending"' + : ""; + + await $.env({ + ...process.env, + ...Object.fromEntries(appEnv), + })`./mvnw flyway:validate -Dflyway.locations=filesystem:./db/migration ${onlyCheckPendingMigrationFlag}`; +} + +main() + .then(() => { + process.exit(0); + }) + .catch((e) => { + console.error(e); + process.exit(1); + }); diff --git a/.github/workflows/deploy-stg.yml b/.github/workflows/deploy-stg.yml index fb3be2d58..badeb1a88 100644 --- a/.github/workflows/deploy-stg.yml +++ b/.github/workflows/deploy-stg.yml @@ -191,42 +191,12 @@ jobs: ref: ${{ needs.getPRHead.outputs.sha }} fetch-depth: 0 - - name: Disable man-db - uses: ./.github/composite/disable-mandb - - - name: Set up OpenJDK 25 - uses: actions/setup-java@v4 - with: - distribution: "temurin" - java-version: "25" - cache: "maven" - - - name: Set up bun - uses: oven-sh/setup-bun@v2 - with: - bun-version: latest - - - name: Cache Bun dependencies - uses: actions/cache@v4 - with: - path: ~/.bun/install/cache - key: ${{ runner.os }}-bun-${{ hashFiles('.github/scripts/bun.lock') }} - restore-keys: | - ${{ runner.os }}-bun- - - - name: Install deps - shell: bash - run: bun install --cwd .github/scripts --frozen-lockfile - - - name: Load secrets - uses: ./.github/composite/load-secrets + - name: Run workflow + uses: ./.github/composite/validate-db with: GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }} GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} - UNLOAD_ENVIRONMENTS: staging - - - name: Validate DB Schema - run: ./mvnw flyway:validate -Dflyway.locations=filesystem:./db/migration -Dflyway.ignoreMigrationPatterns="*:pending" + ENVIRONMENT: staging buildImage: name: Build Docker Image & Upload to Registry From 3219f77c850773fbdaf2ca20d5e26eba8a07f2df Mon Sep 17 00:00:00 2001 From: Tahmid Ahmed <tahmid@tahmid.io> Date: Mon, 19 Jan 2026 03:07:34 -0500 Subject: [PATCH 092/101] 641: test pt84 --- .github/scripts/validate-db/index.ts | 4 +- .github/workflows/ci-cd.yml | 104 ++------------------------- 2 files changed, 7 insertions(+), 101 deletions(-) diff --git a/.github/scripts/validate-db/index.ts b/.github/scripts/validate-db/index.ts index 9b6b2aa10..366941a45 100644 --- a/.github/scripts/validate-db/index.ts +++ b/.github/scripts/validate-db/index.ts @@ -21,7 +21,9 @@ const environment: Environment = (() => { export async function main() { // make sure you checkout the repo first. - const appEnv = await getEnvVariables([environment]); + const appEnv = await getEnvVariables([ + environment === "staging" ? "staging" : "production-ro", + ]); const onlyCheckPendingMigrationFlag = environment === "staging" ? diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml index 44e73da23..ae17f60ff 100644 --- a/.github/workflows/ci-cd.yml +++ b/.github/workflows/ci-cd.yml @@ -86,31 +86,11 @@ jobs: - name: Checkout Repository uses: actions/checkout@v4 - - name: Disable man-db - uses: ./.github/composite/disable-mandb - - - name: Set up OpenJDK 25 - uses: actions/setup-java@v4 - with: - distribution: "temurin" - java-version: "25" - cache: "maven" - - - name: Verify Java version - run: | - java -version - javac -version - echo "JAVA_HOME=$JAVA_HOME" - - - name: Load secrets - uses: ./.github/composite/load-secrets + - name: Run workflow + uses: ./.github/composite/validate-db with: GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }} GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} - UNLOAD_ENVIRONMENTS: production-ro - - - name: Validate DB Schema - run: ./mvnw flyway:validate -Dflyway.locations=filesystem:./db/migration -Dflyway.ignoreMigrationPatterns="*:pending" buildImage: name: Build Docker Image & Upload to Registry @@ -142,84 +122,8 @@ jobs: - name: Checkout Repository uses: actions/checkout@v4 - - name: Disable man-db - uses: ./.github/composite/disable-mandb - - - name: Set up OpenJDK 25 - uses: actions/setup-java@v4 - with: - distribution: "temurin" - java-version: "25" - cache: "maven" - - - name: Verify Java version - run: | - java -version - javac -version - echo "JAVA_HOME=$JAVA_HOME" - - - name: Load secrets - uses: ./.github/composite/load-secrets + - name: Run workflow + uses: ./.github/composite/redeploy with: GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }} GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} - UNLOAD_ENVIRONMENTS: ci,production - - - name: Install doctl - uses: digitalocean/action-doctl@v2 - with: - token: ${{ env.DIGITAL_OCEAN_PAT }} - - - name: Migrate Production DB - run: ./mvnw flyway:migrate -Dflyway.locations=filesystem:./db/migration - - - name: Transform App Spec with secrets - run: bash .github/scripts/transform-yaml-with-env.sh .do/prod/app.yml .env.production - - - name: Update App Spec on DigitalOcean - run: | - OUTPUT=$(doctl apps update ${{ env.DIGITAL_OCEAN_APP_ID }} --spec .do/prod/app.yml --update-sources --output json) - - DEPLOYMENT_ID=$(echo "$OUTPUT" | jq -r '.[0].pending_deployment.id // .[0].in_progress_deployment.id // .[0].active_deployment.id // empty' | head -n 1) - - if [ -z "$DEPLOYMENT_ID" ] || [ "$DEPLOYMENT_ID" == "null" ]; then - echo "Failed to extract deployment ID from app update response" - exit 1 - fi - - echo "DEPLOYMENT_ID=$DEPLOYMENT_ID" >> $GITHUB_ENV - - - name: Poll Deployment Status - run: | - - if [ -z "$DEPLOYMENT_ID" ]; then - echo "Deployment ID is empty." - exit 1 - fi - - echo "Waiting for deployment to be promoted." - - for i in {1..60}; do - RESPONSE=$(curl -s -X GET \ - -H "Content-Type: application/json" \ - -H "Authorization: Bearer ${{ env.DIGITAL_OCEAN_PAT }}" \ - "https://api.digitalocean.com/v2/apps/${{ env.DIGITAL_OCEAN_APP_ID }}/deployments/$DEPLOYMENT_ID") - - PHASE=$(echo "$RESPONSE" | jq -r '.deployment.phase') - - echo "Deployment phase: $PHASE" - - if [ "$PHASE" == "ACTIVE" ]; then - echo "Deployment is active." - exit 0 - elif [ "$PHASE" == "SUPERSEDED" ] || [ "$PHASE" == "ERROR" ] || [ "$PHASE" == "CANCELED" ]; then - echo "Deployment failed with phase: $PHASE" - exit 1 - fi - - echo "Waiting for deployment to complete, sleep 10... ($i/60)" - sleep 10 - done - - echo "Deployment did not reach a valid state within 10 minutes." - exit 1 From c22dd061206a5547b0459fd2d3ed3f4d73ae47e7 Mon Sep 17 00:00:00 2001 From: Tahmid Ahmed <tahmid@tahmid.io> Date: Mon, 19 Jan 2026 03:10:36 -0500 Subject: [PATCH 093/101] 641: test pt85 --- .github/composite/validate-db/action.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/composite/validate-db/action.yml b/.github/composite/validate-db/action.yml index c1b672c34..1998a0d68 100644 --- a/.github/composite/validate-db/action.yml +++ b/.github/composite/validate-db/action.yml @@ -31,4 +31,6 @@ runs: - name: Run script shell: bash - run: bun .github/scripts/run-backend-tests + run: bun .github/scripts/validate-db + env: + ENVIRONMENT: ${{ inputs.ENVIRONMENT }} From 322e07d13bf25843c94073d75990339d29703714 Mon Sep 17 00:00:00 2001 From: Tahmid Ahmed <tahmid@tahmid.io> Date: Mon, 19 Jan 2026 03:13:55 -0500 Subject: [PATCH 094/101] 641: test pt86 --- .github/scripts/validate-db/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/scripts/validate-db/index.ts b/.github/scripts/validate-db/index.ts index 366941a45..7e4970600 100644 --- a/.github/scripts/validate-db/index.ts +++ b/.github/scripts/validate-db/index.ts @@ -27,7 +27,7 @@ export async function main() { const onlyCheckPendingMigrationFlag = environment === "staging" ? - '-Dflyway.ignoreMigrationPatterns="*:pending"' + "-Dflyway.ignoreMigrationPatterns=*:pending" : ""; await $.env({ From bdd3e47b195f2ab4f2dbecba3caeaeebcd5497b9 Mon Sep 17 00:00:00 2001 From: Tahmid Ahmed <tahmid@tahmid.io> Date: Mon, 19 Jan 2026 03:57:11 -0500 Subject: [PATCH 095/101] 641: testing 3 --- .github/composite/build-image/action.yml | 2 +- .github/composite/notion-checks/action.yml | 11 ++++++ .github/composite/send-message/action.yml | 21 +++++------ .github/composite/setup-ci/action.yml | 6 ++- .../test/backend-pre-test/action.yml | 2 +- .../composite/test/backend-test/action.yml | 6 +-- .../composite/test/frontend-test/action.yml | 6 +-- .../{build-image.ts => build-image/index.ts} | 0 .github/scripts/load-secrets/env/load.ts | 5 +++ .github/scripts/load-secrets/index.ts | 2 +- .github/scripts/notion/index.ts | 10 +++++ .../{ => test}/run-backend-compile-tests.ts | 0 .../scripts/{ => test}/run-backend-tests.ts | 0 .../scripts/{ => test}/run-frontend-tests.ts | 0 .github/scripts/utils/send-message/gha.ts | 37 +++++++++++++++++++ .github/scripts/utils/send-message/index.ts | 9 ++--- .github/workflows/ai-review.yml | 26 ++----------- 17 files changed, 94 insertions(+), 49 deletions(-) rename .github/scripts/{build-image.ts => build-image/index.ts} (100%) rename .github/scripts/{ => test}/run-backend-compile-tests.ts (100%) rename .github/scripts/{ => test}/run-backend-tests.ts (100%) rename .github/scripts/{ => test}/run-frontend-tests.ts (100%) create mode 100644 .github/scripts/utils/send-message/gha.ts diff --git a/.github/composite/build-image/action.yml b/.github/composite/build-image/action.yml index 56b1df033..b39b13319 100644 --- a/.github/composite/build-image/action.yml +++ b/.github/composite/build-image/action.yml @@ -49,7 +49,7 @@ runs: - name: Run script shell: bash - run: bun .github/scripts/build-image.ts + run: bun .github/scripts/build-image env: DOCKER_UPLOAD: ${{ inputs.DOCKER_UPLOAD }} TAG_PREFIX: ${{ inputs.TAG_PREFIX }} diff --git a/.github/composite/notion-checks/action.yml b/.github/composite/notion-checks/action.yml index c342fabf6..3631ff64f 100644 --- a/.github/composite/notion-checks/action.yml +++ b/.github/composite/notion-checks/action.yml @@ -11,6 +11,15 @@ inputs: GPG_PASSPHRASE: description: "GPG Passphrase" required: true + GET_GHA_OUTPUT: + description: "If true, will output an object with data that can be interfaced in GitHub Actions." + required: false + default: false + +outputs: + context: + description: "Notion task context for AI review" + value: ${{ steps.run_script.outputs.context }} runs: using: "composite" @@ -22,7 +31,9 @@ runs: GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} - name: Run script + id: run_script shell: bash run: bun .github/scripts/notion env: PR_ID: ${{ inputs.PR_ID }} + GET_GHA_OUTPUT: ${{ inputs.GET_GHA_OUTPUT }} diff --git a/.github/composite/send-message/action.yml b/.github/composite/send-message/action.yml index 53d70a293..ffa852ea8 100644 --- a/.github/composite/send-message/action.yml +++ b/.github/composite/send-message/action.yml @@ -20,17 +20,14 @@ inputs: runs: using: "composite" steps: - - name: Post PR Message - uses: actions/github-script@v7 + - name: Setup CI + uses: ./.github/composite/setup-ci with: - github-token: ${{ inputs.token }} - script: | - const prId = ${{ inputs.prId }}; - const message = ${{ toJSON(inputs.message) }}; + GITHUB_TOKEN: ${{ inputs.token }} - await github.rest.issues.createComment({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: parseInt(prId, 10), - body: message - }); + - name: Run script + shell: bash + run: bun .github/scripts/send-message/gha + env: + PR_ID: ${{ inputs.prId }} + MESSAGE: ${{ inputs.message }} diff --git a/.github/composite/setup-ci/action.yml b/.github/composite/setup-ci/action.yml index c622d79b5..2cd218dc6 100644 --- a/.github/composite/setup-ci/action.yml +++ b/.github/composite/setup-ci/action.yml @@ -8,13 +8,17 @@ inputs: GPG_PRIVATE_KEY: description: "GPG private key if secrets will be used." required: false + GITHUB_TOKEN: + description: "GitHub token used to send message" + required: false + default: ${{ github.token }} runs: using: "composite" steps: - name: Export GitHub Token shell: bash - run: echo "GH_TOKEN=${{ github.token }}" >> $GITHUB_ENV + run: echo "GH_TOKEN=${{ inputs.GITHUB_TOKEN }}" >> $GITHUB_ENV - name: Disable man-db shell: bash diff --git a/.github/composite/test/backend-pre-test/action.yml b/.github/composite/test/backend-pre-test/action.yml index 5ef49f8fd..45c9c39ee 100644 --- a/.github/composite/test/backend-pre-test/action.yml +++ b/.github/composite/test/backend-pre-test/action.yml @@ -15,4 +15,4 @@ runs: - name: Run script shell: bash - run: bun .github/scripts/run-backend-compile-tests.ts + run: bun .github/scripts/test/run-backend-compile-tests diff --git a/.github/composite/test/backend-test/action.yml b/.github/composite/test/backend-test/action.yml index e4b12cd14..5b61971af 100644 --- a/.github/composite/test/backend-test/action.yml +++ b/.github/composite/test/backend-test/action.yml @@ -19,8 +19,8 @@ runs: - name: Setup CI uses: ./.github/composite/setup-ci with: - GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }} - GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} + GPG_PRIVATE_KEY: ${{ inputs.GPG_PRIVATE_KEY }} + GPG_PASSPHRASE: ${{ inputs.GPG_PASSPHRASE }} - name: Set up OpenJDK 25 uses: actions/setup-java@v4 @@ -31,7 +31,7 @@ runs: - name: Run script shell: bash - run: bun .github/scripts/run-backend-tests + run: bun .github/scripts/test/run-backend-tests - name: Upload JaCoCo HTML report uses: actions/upload-artifact@v4 diff --git a/.github/composite/test/frontend-test/action.yml b/.github/composite/test/frontend-test/action.yml index 93cb82c71..5559d3ee0 100644 --- a/.github/composite/test/frontend-test/action.yml +++ b/.github/composite/test/frontend-test/action.yml @@ -15,8 +15,8 @@ runs: - name: Setup CI uses: ./.github/composite/setup-ci with: - GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }} - GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} + GPG_PRIVATE_KEY: ${{ inputs.GPG_PRIVATE_KEY }} + GPG_PASSPHRASE: ${{ inputs.GPG_PASSPHRASE }} - name: Set up pnpm uses: pnpm/action-setup@master @@ -35,4 +35,4 @@ runs: - name: Run script shell: bash - run: bun .github/scripts/run-frontend-tests.ts + run: bun .github/scripts/test/run-frontend-tests diff --git a/.github/scripts/build-image.ts b/.github/scripts/build-image/index.ts similarity index 100% rename from .github/scripts/build-image.ts rename to .github/scripts/build-image/index.ts diff --git a/.github/scripts/load-secrets/env/load.ts b/.github/scripts/load-secrets/env/load.ts index 2497fc683..b2cb5eae7 100644 --- a/.github/scripts/load-secrets/env/load.ts +++ b/.github/scripts/load-secrets/env/load.ts @@ -58,6 +58,11 @@ export async function getEnvVariables( if (mask_PLZ_DO_NOT_TURN_OFF_UNLESS_YOU_KNOW_WHAT_UR_DOING) { for (const [varName, value] of loaded.entries()) { + if (value === "true" || value === "false" || value === "") { + console.log(`Not masking ${varName}: true/false/empty value`); + continue; + } + console.log(`Masking ${varName}`); console.log(`::add-mask::${value}`); } diff --git a/.github/scripts/load-secrets/index.ts b/.github/scripts/load-secrets/index.ts index e2bab15f1..1800678bd 100644 --- a/.github/scripts/load-secrets/index.ts +++ b/.github/scripts/load-secrets/index.ts @@ -16,7 +16,7 @@ const excludedVars = [ ]; /** - * @deprecated this is no longer usable. + * @deprecated this is no longer a supported flow. */ async function main() { const envs = unloadEnvironments diff --git a/.github/scripts/notion/index.ts b/.github/scripts/notion/index.ts index 2edccb385..ea7ada566 100644 --- a/.github/scripts/notion/index.ts +++ b/.github/scripts/notion/index.ts @@ -16,6 +16,9 @@ const prId = (() => { return n; })(); +const getGhaOutput = process.env.GET_GHA_OUTPUT === "true"; +const githubOutputFile = process.env.GITHUB_OUTPUT; + async function main() { const { notionDbId, notionSecret } = parseCiEnv( await getEnvVariables(["ci"]), @@ -29,6 +32,13 @@ async function main() { console.log(taskContent); + if (getGhaOutput && githubOutputFile) { + const w = Bun.file(githubOutputFile).writer(); + await w.write(`context<<EOF\n${taskContent}\nEOF\n`); + await w.flush(); + await w.end(); + } + await _checkCommits(taskId, prId); } diff --git a/.github/scripts/run-backend-compile-tests.ts b/.github/scripts/test/run-backend-compile-tests.ts similarity index 100% rename from .github/scripts/run-backend-compile-tests.ts rename to .github/scripts/test/run-backend-compile-tests.ts diff --git a/.github/scripts/run-backend-tests.ts b/.github/scripts/test/run-backend-tests.ts similarity index 100% rename from .github/scripts/run-backend-tests.ts rename to .github/scripts/test/run-backend-tests.ts diff --git a/.github/scripts/run-frontend-tests.ts b/.github/scripts/test/run-frontend-tests.ts similarity index 100% rename from .github/scripts/run-frontend-tests.ts rename to .github/scripts/test/run-frontend-tests.ts diff --git a/.github/scripts/utils/send-message/gha.ts b/.github/scripts/utils/send-message/gha.ts new file mode 100644 index 000000000..c7c852215 --- /dev/null +++ b/.github/scripts/utils/send-message/gha.ts @@ -0,0 +1,37 @@ +import { sendMessage } from "utils/send-message"; + +const prId = (() => { + const v = process.env.PR_ID; + if (!v) { + throw new Error("PR_ID is required"); + } + const n = Number(v); + if (Number.isNaN(n)) { + throw new Error("PR_ID must be a number"); + } + return n; +})(); + +const message = (() => { + const v = process.env.MESSAGE; + if (!v) { + throw new Error("MESSAGE is required"); + } + return v; +})(); + +/** + * @deprecated usable but is not recommended. + */ +export async function main() { + sendMessage(prId, message); +} + +main() + .then(() => { + process.exit(0); + }) + .catch((e) => { + console.error(e); + process.exit(1); + }); diff --git a/.github/scripts/utils/send-message/index.ts b/.github/scripts/utils/send-message/index.ts index b25f63a71..7572db8bd 100644 --- a/.github/scripts/utils/send-message/index.ts +++ b/.github/scripts/utils/send-message/index.ts @@ -20,7 +20,6 @@ export async function sendMessage( token?: string, ) { try { - console.log(owner, repo); if (!token && !githubToken) { throw new Error("Some token should be set"); } @@ -37,13 +36,13 @@ export async function sendMessage( body: message, }); } catch (e) { - let d: string; + let s: string; if (e instanceof RequestError) { - d = JSON.stringify(e.response?.data); + s = JSON.stringify(e.response?.data); } else { - d = String(e); + s = String(e); } - throw new Error(`GitHub API Error\n\n${d}`); + throw new Error(`GitHub API Error\n\n${s}`); } } catch (e) { console.error("Failed to post GitHub error message\n", e); diff --git a/.github/workflows/ai-review.yml b/.github/workflows/ai-review.yml index 06bf9e4b3..67b6ddf43 100644 --- a/.github/workflows/ai-review.yml +++ b/.github/workflows/ai-review.yml @@ -37,7 +37,7 @@ jobs: repo: context.repo.repo, pull_number: parseInt(prId, 10) }); - return pr.head.sha; + return pr.head.sha; aiCodeReview: name: Run AI Code Review on the PR @@ -50,30 +50,12 @@ jobs: with: ref: ${{ needs.getPRHead.outputs.sha }} - - name: Load secrets - uses: ./.github/composite/load-secrets + - name: Setup composite workflow + uses: ./.github/composite/notion-checks with: + PR_ID: ${{ needs.getPRHead.outputs.sha }} GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }} GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} - UNLOAD_ENVIRONMENTS: ci - - - name: Run 'Check Notion PR' composite action - uses: ./.github/composite/check-notion-pr - id: notion_check - with: - PR_ID: ${{ needs.getPRHead.outputs.prId }} - GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }} - GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} - - - name: Echo Notion context - run: | - echo "Context: ${{ steps.notion_check.outputs.context }}" - - - name: Run 'Check Notion Commits' composite action - uses: ./.github/composite/check-notion-commits - with: - PR_ID: ${{ needs.getPRHead.outputs.prId }} - NOTION_ID: ${{ steps.notion_check.outputs.notion_id }} - name: Qodo PR-Agent uses: qodo-ai/pr-agent@main From 013a545c83ba16965f40bfa697fabe2b92a6fc5e Mon Sep 17 00:00:00 2001 From: Tahmid Ahmed <tahmid@tahmid.io> Date: Mon, 19 Jan 2026 04:02:32 -0500 Subject: [PATCH 096/101] 641: testingggg --- .github/workflows/ai-command.yml | 37 ++++++++++++++++++++++++++++++++ Justfile | 6 ++++++ 2 files changed, 43 insertions(+) diff --git a/.github/workflows/ai-command.yml b/.github/workflows/ai-command.yml index baa19d7fe..3394375b8 100644 --- a/.github/workflows/ai-command.yml +++ b/.github/workflows/ai-command.yml @@ -1,22 +1,59 @@ name: AI Command Handler run-name: Triggering AI review commands on PR #${{ github.event.client_payload.slash_command.args.named.prId }} +concurrency: + group: ai-command-${{ github.event.client_payload.slash_command.args.named.prId }} + cancel-in-progress: true + on: repository_dispatch: types: [ai-command] + # this should only be used for testing purposes. + workflow_dispatch: + inputs: + prId: + description: "PR to deploy" + required: true permissions: contents: read pull-requests: write jobs: + getPRHead: + name: Get PR head SHA + runs-on: ubuntu-latest + outputs: + sha: ${{ steps.pr-head.outputs.result }} + steps: + - name: Get PR head SHA + id: pr-head + uses: actions/github-script@v7 + with: + result-encoding: string + script: | + const prId = ${{ + (github.event_name == 'repository_dispatch' && github.event.client_payload.slash_command.args.named.prId) + || github.event.inputs.prId + }}; + const { data: pr } = await github.rest.pulls.get({ + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: parseInt(prId, 10) + }); + return pr.head.sha; + triggerAiReview: name: Trigger AI Review Commands runs-on: ubuntu-latest + needs: getPRHead + steps: - name: Checkout Repository uses: actions/checkout@v4 + with: + ref: ${{ needs.getPRHead.outputs.sha }} - name: Load secrets uses: ./.github/composite/load-secrets diff --git a/Justfile b/Justfile index c816b146c..ee5c54ce8 100644 --- a/Justfile +++ b/Justfile @@ -97,6 +97,12 @@ devd *args: ci-test-stg pr_name pr_id: gh workflow run .github/workflows/deploy-stg.yml --ref {{pr_name}} --field prId={{pr_id}} +# Triggers a deploy to staging command for the following environment. Must be authenticated on gh. +# NOTE: Triggering staging deployment this way can cause a buildup of tasks (broken concurrency group). +# As such, this should only be used in one branch at a time. +ci-test-ai pr_name pr_id: + gh workflow run .github/workflows/ai-command.yml --ref {{pr_name}} --field prId={{pr_id}} + # Interactive script to add a user's GPG public key so they can decrypt keys git-crypt-add-user: cd scripts && pnpm i && pnpm run git-crypt-add-user && clear && node git-crypt/add-user From 85d8229dcb00a1b59a02460ac7802ae0831be55c Mon Sep 17 00:00:00 2001 From: Tahmid Ahmed <tahmid@tahmid.io> Date: Mon, 19 Jan 2026 04:06:29 -0500 Subject: [PATCH 097/101] 641: fixes --- .github/workflows/ai-command.yml | 6 ++++++ .github/workflows/deploy-stg.yml | 6 ++++++ .github/workflows/slash.yml | 6 ++++++ 3 files changed, 18 insertions(+) diff --git a/.github/workflows/ai-command.yml b/.github/workflows/ai-command.yml index 3394375b8..b21753044 100644 --- a/.github/workflows/ai-command.yml +++ b/.github/workflows/ai-command.yml @@ -55,6 +55,12 @@ jobs: with: ref: ${{ needs.getPRHead.outputs.sha }} + - name: Setup CI + uses: ./.github/composite/setup-ci + with: + GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }} + GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} + - name: Load secrets uses: ./.github/composite/load-secrets with: diff --git a/.github/workflows/deploy-stg.yml b/.github/workflows/deploy-stg.yml index badeb1a88..87383eabc 100644 --- a/.github/workflows/deploy-stg.yml +++ b/.github/workflows/deploy-stg.yml @@ -105,6 +105,12 @@ jobs: with: ref: ${{ needs.getPRHead.outputs.sha }} + - name: Setup CI + uses: ./.github/composite/setup-ci + with: + GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }} + GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} + - name: Load secrets uses: ./.github/composite/load-secrets with: diff --git a/.github/workflows/slash.yml b/.github/workflows/slash.yml index dc997afba..dedbe7cbb 100644 --- a/.github/workflows/slash.yml +++ b/.github/workflows/slash.yml @@ -34,6 +34,12 @@ jobs: with: ref: ${{ steps.pr-head.outputs.result }} + - name: Setup CI + uses: ./.github/composite/setup-ci + with: + GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }} + GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} + - name: Load secrets uses: ./.github/composite/load-secrets with: From e8e796feef04170fe0caf111ab323b61ef9e4989 Mon Sep 17 00:00:00 2001 From: Tahmid Ahmed <tahmid@tahmid.io> Date: Mon, 19 Jan 2026 04:09:12 -0500 Subject: [PATCH 098/101] 641: fixes2 --- .github/composite/send-message/action.yml | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/.github/composite/send-message/action.yml b/.github/composite/send-message/action.yml index ffa852ea8..a1f4b849e 100644 --- a/.github/composite/send-message/action.yml +++ b/.github/composite/send-message/action.yml @@ -20,14 +20,10 @@ inputs: runs: using: "composite" steps: - - name: Setup CI - uses: ./.github/composite/setup-ci - with: - GITHUB_TOKEN: ${{ inputs.token }} - - name: Run script shell: bash run: bun .github/scripts/send-message/gha env: PR_ID: ${{ inputs.prId }} MESSAGE: ${{ inputs.message }} + GH_TOKEN: ${{ inputs.token }} From 2c271c2dbaf644760bf450e0e6f295f3f7915387 Mon Sep 17 00:00:00 2001 From: Tahmid Ahmed <tahmid@tahmid.io> Date: Mon, 19 Jan 2026 04:09:55 -0500 Subject: [PATCH 099/101] 641: fixes3 --- .github/composite/send-message/action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/composite/send-message/action.yml b/.github/composite/send-message/action.yml index a1f4b849e..06859d09c 100644 --- a/.github/composite/send-message/action.yml +++ b/.github/composite/send-message/action.yml @@ -22,7 +22,7 @@ runs: steps: - name: Run script shell: bash - run: bun .github/scripts/send-message/gha + run: bun .github/scripts/utils/send-message/gha env: PR_ID: ${{ inputs.prId }} MESSAGE: ${{ inputs.message }} From 04667c14055022c30ecad21f690f0747cb1885e7 Mon Sep 17 00:00:00 2001 From: Tahmid Ahmed <tahmid@tahmid.io> Date: Mon, 19 Jan 2026 04:12:34 -0500 Subject: [PATCH 100/101] 641: fixes4 --- .github/workflows/ai-command.yml | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ai-command.yml b/.github/workflows/ai-command.yml index b21753044..457fd571f 100644 --- a/.github/workflows/ai-command.yml +++ b/.github/workflows/ai-command.yml @@ -71,20 +71,29 @@ jobs: - name: Post /review command uses: ./.github/composite/send-message with: - prId: ${{ github.event.client_payload.slash_command.args.named.prId }} + prId: ${{ + (github.event_name == 'repository_dispatch' && github.event.client_payload.slash_command.args.named.prId) + || github.event.inputs.prId + }} message: "/review" token: ${{ env.GH_PAT }} - name: Post /describe command uses: ./.github/composite/send-message with: - prId: ${{ github.event.client_payload.slash_command.args.named.prId }} + prId: ${{ + (github.event_name == 'repository_dispatch' && github.event.client_payload.slash_command.args.named.prId) + || github.event.inputs.prId + }} message: "/describe" token: ${{ env.GH_PAT }} - name: Post /improve command uses: ./.github/composite/send-message with: - prId: ${{ github.event.client_payload.slash_command.args.named.prId }} + prId: ${{ + (github.event_name == 'repository_dispatch' && github.event.client_payload.slash_command.args.named.prId) + || github.event.inputs.prId + }} message: "/improve" token: ${{ env.GH_PAT }} From c12aa3c966b0dd7d34be725a9d4b9382c724af33 Mon Sep 17 00:00:00 2001 From: Tahmid Ahmed <tahmid@tahmid.io> Date: Mon, 19 Jan 2026 04:13:46 -0500 Subject: [PATCH 101/101] 641: fixes5 --- .github/scripts/utils/send-message/gha.ts | 2 +- .github/workflows/ai-command.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/scripts/utils/send-message/gha.ts b/.github/scripts/utils/send-message/gha.ts index c7c852215..5b41e015f 100644 --- a/.github/scripts/utils/send-message/gha.ts +++ b/.github/scripts/utils/send-message/gha.ts @@ -24,7 +24,7 @@ const message = (() => { * @deprecated usable but is not recommended. */ export async function main() { - sendMessage(prId, message); + await sendMessage(prId, message); } main() diff --git a/.github/workflows/ai-command.yml b/.github/workflows/ai-command.yml index 457fd571f..52aaa179b 100644 --- a/.github/workflows/ai-command.yml +++ b/.github/workflows/ai-command.yml @@ -2,7 +2,7 @@ name: AI Command Handler run-name: Triggering AI review commands on PR #${{ github.event.client_payload.slash_command.args.named.prId }} concurrency: - group: ai-command-${{ github.event.client_payload.slash_command.args.named.prId }} + group: ai-command-${{ github.event.client_payload.slash_command.args.named.prId || github.event.inputs.prId }} cancel-in-progress: true on: