From 09b192eb7e01ba4aed6d363288dd1277c054ff1d Mon Sep 17 00:00:00 2001 From: gamitej Date: Thu, 9 Apr 2026 17:39:18 +0530 Subject: [PATCH 1/9] ci(commented): stage file commented for skip now --- .github/workflows/stage.yml | 306 ++++++++++++++++++------------------ 1 file changed, 153 insertions(+), 153 deletions(-) diff --git a/.github/workflows/stage.yml b/.github/workflows/stage.yml index 5aa4cb8..3a172b1 100644 --- a/.github/workflows/stage.yml +++ b/.github/workflows/stage.yml @@ -1,153 +1,153 @@ -name: Staging Deploy - -# Triggers on pushes to the `develop` branch only. -# This is the full staging pipeline: test with an isolated service container DB, -# then deploy the whole stack to the staging VPS if tests pass. -# The staging VPS uses its own .env.staging file — completely isolated from -# the CI DB (ephemeral) and production DB. -on: - push: - branches: [develop] - -concurrency: - # Cancel any in-progress staging deploy if a new commit is pushed. - group: staging-deploy-${{ github.ref }} - cancel-in-progress: true - -jobs: - # ───────────────────────────────────────────────────────────────────────────── - # JOB 1: Test - # GitHub Actions service container provides an isolated, ephemeral Postgres. - # No staging or production DB credentials are used here. - # ───────────────────────────────────────────────────────────────────────────── - test: - name: 🧪 Test (Service Container DB) - runs-on: ubuntu-latest - - services: - postgres: - image: postgres:16-alpine - env: - POSTGRES_USER: ci_user - POSTGRES_PASSWORD: ci_password - POSTGRES_DB: ci_test_db - ports: - - 5432:5432 - options: >- - --health-cmd "pg_isready -U ci_user -d ci_test_db" - --health-interval 5s - --health-timeout 5s - --health-retries 10 - - env: - NODE_ENV: test - CI_TEST_DB_URL: postgresql://ci_user:ci_password@localhost:5432/ci_test_db - JWT_SECRET: ${{ secrets.CI_JWT_SECRET }} - LOG_LEVEL: error - PORT: 3000 - - steps: - - name: 🚚 Checkout - uses: actions/checkout@v4 - - - name: ⚡️ Setup Bun - uses: oven-sh/setup-bun@v2 - with: - bun-version: latest - - - name: 📦 Install Dependencies - run: bun install --frozen-lockfile - - - name: 🧹 Lint (Biome) - run: bun run lint - - - name: 🛡️ Type Check - run: bun run typecheck - - - name: 🗄️ Push Schema → Service Container DB - run: bunx drizzle-kit push --config=src/config/drizzle.config.ts --force - - - name: 🧪 Unit Tests - run: bun run test:unit - - - name: 🧪 Integration Tests - run: bun run test:integration - - - name: 🏗️ Build - run: bun run build - - # ───────────────────────────────────────────────────────────────────────────── - # JOB 2: Deploy Full Stack to Staging VPS - # Runs ONLY when the test job above passes. - # SSHes into the staging VPS and brings the entire docker compose stack up, - # including the backend, database, Loki, and Grafana services. - # The staging VPS reads .env.staging which holds STAGING_DATABASE_URL, - # Loki/Grafana credentials, etc. — none of these ever appear in this file. - # ───────────────────────────────────────────────────────────────────────────── - deploy-staging: - name: 🚀 Deploy to Staging VPS - needs: test - runs-on: ubuntu-latest - - environment: - name: staging - url: ${{ secrets.STAGING_APP_URL }} - - steps: - - name: 🔑 Deploy via SSH - uses: appleboy/ssh-action@v1.0.3 - with: - host: ${{ secrets.STAGING_VPS_HOST }} - username: ${{ secrets.STAGING_VPS_USER }} - key: ${{ secrets.STAGING_VPS_SSH_PRIVATE_KEY }} - port: ${{ secrets.STAGING_VPS_SSH_PORT }} - script: | - set -e - - echo "──────────────────────────────────────────" - echo " Deploying commit ${{ github.sha }} to staging" - echo "──────────────────────────────────────────" - - cd ${{ secrets.STAGING_APP_DIR }} - - # Pull latest code from develop branch - git fetch origin develop - git checkout develop - git pull origin develop - - # Install / update dependencies - bun install --frozen-lockfile - - # Apply any schema changes to the staging DB. - # NODE_ENV=production so drizzle.config.ts picks up PROD_DATABASE_URL, - # which in .env.staging points to the staging Postgres — not production. - NODE_ENV=production bunx drizzle-kit push \ - --config=src/config/drizzle.config.ts \ - --force - - # Bring up the full stack (backend + db + loki + grafana) - # --remove-orphans cleans up containers from old compose definitions - docker compose \ - -f docker-compose.staging.yml \ - --env-file .env.staging \ - up -d \ - --build \ - --remove-orphans - - echo "✅ Staging deploy complete" - - - name: 🩺 Health Check - # Wait up to 60 s for the app to respond before marking the deploy successful. - run: | - echo "Waiting for staging app to become healthy..." - for i in $(seq 1 12); do - STATUS=$(curl -s -o /dev/null -w "%{http_code}" "${{ secrets.STAGING_APP_URL }}/health" || true) - if [ "$STATUS" = "200" ]; then - echo "✅ Health check passed (HTTP 200)" - exit 0 - fi - echo " Attempt $i/12 — got HTTP $STATUS, retrying in 5s..." - sleep 5 - done - echo "❌ Health check failed after 60s" - exit 1 +# name: Staging Deploy + +# # Triggers on pushes to the `develop` branch only. +# # This is the full staging pipeline: test with an isolated service container DB, +# # then deploy the whole stack to the staging VPS if tests pass. +# # The staging VPS uses its own .env.staging file — completely isolated from +# # the CI DB (ephemeral) and production DB. +# on: +# push: +# branches: [develop] + +# concurrency: +# # Cancel any in-progress staging deploy if a new commit is pushed. +# group: staging-deploy-${{ github.ref }} +# cancel-in-progress: true + +# jobs: +# # ───────────────────────────────────────────────────────────────────────────── +# # JOB 1: Test +# # GitHub Actions service container provides an isolated, ephemeral Postgres. +# # No staging or production DB credentials are used here. +# # ───────────────────────────────────────────────────────────────────────────── +# test: +# name: 🧪 Test (Service Container DB) +# runs-on: ubuntu-latest + +# services: +# postgres: +# image: postgres:16-alpine +# env: +# POSTGRES_USER: ci_user +# POSTGRES_PASSWORD: ci_password +# POSTGRES_DB: ci_test_db +# ports: +# - 5432:5432 +# options: >- +# --health-cmd "pg_isready -U ci_user -d ci_test_db" +# --health-interval 5s +# --health-timeout 5s +# --health-retries 10 + +# env: +# NODE_ENV: test +# CI_TEST_DB_URL: postgresql://ci_user:ci_password@localhost:5432/ci_test_db +# JWT_SECRET: ${{ secrets.CI_JWT_SECRET }} +# LOG_LEVEL: error +# PORT: 3000 + +# steps: +# - name: 🚚 Checkout +# uses: actions/checkout@v4 + +# - name: ⚡️ Setup Bun +# uses: oven-sh/setup-bun@v2 +# with: +# bun-version: latest + +# - name: 📦 Install Dependencies +# run: bun install --frozen-lockfile + +# - name: 🧹 Lint (Biome) +# run: bun run lint + +# - name: 🛡️ Type Check +# run: bun run typecheck + +# - name: 🗄️ Push Schema → Service Container DB +# run: bunx drizzle-kit push --config=src/config/drizzle.config.ts --force + +# - name: 🧪 Unit Tests +# run: bun run test:unit + +# - name: 🧪 Integration Tests +# run: bun run test:integration + +# - name: 🏗️ Build +# run: bun run build + +# # ───────────────────────────────────────────────────────────────────────────── +# # JOB 2: Deploy Full Stack to Staging VPS +# # Runs ONLY when the test job above passes. +# # SSHes into the staging VPS and brings the entire docker compose stack up, +# # including the backend, database, Loki, and Grafana services. +# # The staging VPS reads .env.staging which holds STAGING_DATABASE_URL, +# # Loki/Grafana credentials, etc. — none of these ever appear in this file. +# # ───────────────────────────────────────────────────────────────────────────── +# deploy-staging: +# name: 🚀 Deploy to Staging VPS +# needs: test +# runs-on: ubuntu-latest + +# environment: +# name: staging +# url: ${{ secrets.STAGING_APP_URL }} + +# steps: +# - name: 🔑 Deploy via SSH +# uses: appleboy/ssh-action@v1.0.3 +# with: +# host: ${{ secrets.STAGING_VPS_HOST }} +# username: ${{ secrets.STAGING_VPS_USER }} +# key: ${{ secrets.STAGING_VPS_SSH_PRIVATE_KEY }} +# port: ${{ secrets.STAGING_VPS_SSH_PORT }} +# script: | +# set -e + +# echo "──────────────────────────────────────────" +# echo " Deploying commit ${{ github.sha }} to staging" +# echo "──────────────────────────────────────────" + +# cd ${{ secrets.STAGING_APP_DIR }} + +# # Pull latest code from develop branch +# git fetch origin develop +# git checkout develop +# git pull origin develop + +# # Install / update dependencies +# bun install --frozen-lockfile + +# # Apply any schema changes to the staging DB. +# # NODE_ENV=production so drizzle.config.ts picks up PROD_DATABASE_URL, +# # which in .env.staging points to the staging Postgres — not production. +# NODE_ENV=production bunx drizzle-kit push \ +# --config=src/config/drizzle.config.ts \ +# --force + +# # Bring up the full stack (backend + db + loki + grafana) +# # --remove-orphans cleans up containers from old compose definitions +# docker compose \ +# -f docker-compose.staging.yml \ +# --env-file .env.staging \ +# up -d \ +# --build \ +# --remove-orphans + +# echo "✅ Staging deploy complete" + +# - name: 🩺 Health Check +# # Wait up to 60 s for the app to respond before marking the deploy successful. +# run: | +# echo "Waiting for staging app to become healthy..." +# for i in $(seq 1 12); do +# STATUS=$(curl -s -o /dev/null -w "%{http_code}" "${{ secrets.STAGING_APP_URL }}/health" || true) +# if [ "$STATUS" = "200" ]; then +# echo "✅ Health check passed (HTTP 200)" +# exit 0 +# fi +# echo " Attempt $i/12 — got HTTP $STATUS, retrying in 5s..." +# sleep 5 +# done +# echo "❌ Health check failed after 60s" +# exit 1 From f5fe7782aa50313aab6f5a769e00196099d28cfb Mon Sep 17 00:00:00 2001 From: gamitej Date: Thu, 9 Apr 2026 17:59:52 +0530 Subject: [PATCH 2/9] fix(ci): ci issue fix --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b0235f1..d347365 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -28,7 +28,7 @@ jobs: ports: - 5432:5432 options: >- - --health-cmd "pg_isready -U ${{ secrets.POSTGRES_USER }} -d ${{ secrets.POSTGRES_DB }}" + --health-cmd pg_isready --health-interval 5s --health-timeout 5s --health-retries 10 From 5850f44982397aa586b729c9ec4dab6f1413c725 Mon Sep 17 00:00:00 2001 From: gamitej Date: Thu, 9 Apr 2026 18:27:57 +0530 Subject: [PATCH 3/9] fix(package): version mismatch issue fixed --- .github/workflows/ci.yml | 4 +++- package.json | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d347365..c4d9e53 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -41,6 +41,8 @@ jobs: JWT_SECRET: ${{ secrets.JWT_SECRET }} LOG_LEVEL: error PORT: 3000 + # Opt into Node.js 24 for all actions to avoid Node.js 20 deprecation warnings. + FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true steps: - name: 🚚 Checkout Repository @@ -49,7 +51,7 @@ jobs: - name: ⚡️ Setup Bun uses: oven-sh/setup-bun@v2 with: - bun-version: latest + bun-version: 1.3.11 - name: 📦 Install Dependencies run: bun install --frozen-lockfile diff --git a/package.json b/package.json index f92d25f..d66730b 100644 --- a/package.json +++ b/package.json @@ -35,7 +35,7 @@ "dependencies": { "@hono/zod-openapi": "0.18.3", "@hono/zod-validator": "^0.4.2", - "drizzle-orm": "^0.33.0", + "drizzle-orm": "0.33.0", "hono": "^4.6.1", "pino": "^9.4.0", "pino-loki": "^3.0.0", From 07d002c310bf36394dbc1df7290c7505960d1919 Mon Sep 17 00:00:00 2001 From: gamitej Date: Thu, 9 Apr 2026 18:40:41 +0530 Subject: [PATCH 4/9] fix(test): test fail issue fixed --- package.json | 2 +- src/database/index.ts | 9 +++++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index d66730b..f92d25f 100644 --- a/package.json +++ b/package.json @@ -35,7 +35,7 @@ "dependencies": { "@hono/zod-openapi": "0.18.3", "@hono/zod-validator": "^0.4.2", - "drizzle-orm": "0.33.0", + "drizzle-orm": "^0.33.0", "hono": "^4.6.1", "pino": "^9.4.0", "pino-loki": "^3.0.0", diff --git a/src/database/index.ts b/src/database/index.ts index 6cddb97..cc280eb 100644 --- a/src/database/index.ts +++ b/src/database/index.ts @@ -19,8 +19,13 @@ export function getDb(): PostgresJsDatabase { } export const db = new Proxy({} as PostgresJsDatabase, { - get(_target, prop, receiver) { - return Reflect.get(getDb(), prop, receiver); + get(_target, prop) { + const dbInstance = getDb(); + const value = Reflect.get(dbInstance, prop, dbInstance); + if (typeof value === "function") { + return value.bind(dbInstance); + } + return value; }, }); From d6f10c35f1e8ed96642367dbb087029f880d4b43 Mon Sep 17 00:00:00 2001 From: gamitej Date: Thu, 9 Apr 2026 18:54:03 +0530 Subject: [PATCH 5/9] fix(test): test integration fail issue fixed --- src/database/index.ts | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/src/database/index.ts b/src/database/index.ts index cc280eb..8dfc411 100644 --- a/src/database/index.ts +++ b/src/database/index.ts @@ -18,16 +18,8 @@ export function getDb(): PostgresJsDatabase { return database; } -export const db = new Proxy({} as PostgresJsDatabase, { - get(_target, prop) { - const dbInstance = getDb(); - const value = Reflect.get(dbInstance, prop, dbInstance); - if (typeof value === "function") { - return value.bind(dbInstance); - } - return value; - }, -}); +export let db: PostgresJsDatabase = + null as unknown as PostgresJsDatabase; export const startDatabase = async (): Promise => { queryClient = isProd @@ -45,6 +37,7 @@ export const startDatabase = async (): Promise => { }); database = drizzle(queryClient, { schema }); + db = database; await queryClient`SELECT 1`; logger.info(`Database connected [${env.NODE_ENV.toUpperCase()}]`); @@ -59,6 +52,7 @@ export const stopDatabase = async (): Promise => { await queryClient.end(); queryClient = null; database = null; + db = null as unknown as PostgresJsDatabase; logger.info("Database connection closed"); } catch (error) { logger.error({ err: error }, "Error closing database connection"); From bb033d926f1cf573c17c9fe37999cff51ce0d247 Mon Sep 17 00:00:00 2001 From: gamitej Date: Thu, 9 Apr 2026 19:11:50 +0530 Subject: [PATCH 6/9] fix(test): test integration fail issue --- .github/workflows/ci.yml | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c4d9e53..30864b1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -35,13 +35,11 @@ jobs: env: NODE_ENV: test - # Points to the service container above — never touches the VPS. - DATABASE_URL: ${{ secrets.DATABASE_URL }} - # JWT_SECRET must be ≥32 chars; using a fixed CI-only value is safe here. + # Built from individual secrets so the port is always 5432 (service container). + DATABASE_URL: postgresql://${{ secrets.POSTGRES_USER }}:${{ secrets.POSTGRES_PASSWORD }}@localhost:5432/${{ secrets.POSTGRES_DB }} JWT_SECRET: ${{ secrets.JWT_SECRET }} LOG_LEVEL: error PORT: 3000 - # Opt into Node.js 24 for all actions to avoid Node.js 20 deprecation warnings. FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true steps: From 44064936e25908c0c6fe79ea29bf8ea460c237f7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 9 Apr 2026 13:56:59 +0000 Subject: [PATCH 7/9] Initial plan From b05f3431d70fcaba4ef7ce943f449629c057ddc1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 9 Apr 2026 13:59:32 +0000 Subject: [PATCH 8/9] fix: use getDb() instead of stale db binding in integration tests Agent-Logs-Url: https://github.com/gamitej/Learning-Bun/sessions/5fef3187-d099-4c24-b642-213c205b9cc2 Co-authored-by: gamitej <55736105+gamitej@users.noreply.github.com> --- tests/integration/helpers/index.ts | 8 ++++---- tests/integration/todo.test.ts | 18 +++++++++--------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/tests/integration/helpers/index.ts b/tests/integration/helpers/index.ts index 73c8d86..9f59c42 100644 --- a/tests/integration/helpers/index.ts +++ b/tests/integration/helpers/index.ts @@ -1,11 +1,11 @@ -import { db } from "@/database"; +import { getDb } from "@/database"; import { UserHelpers, todos, users } from "@/database/schema"; import { signJwt } from "@/utils/auth"; export async function setupTestContext(username: string) { - await db.delete(users); + await getDb().delete(users); - const user = await UserHelpers.create(db, { + const user = await UserHelpers.create(getDb(), { username, passwordRaw: "test-password", }); @@ -57,7 +57,7 @@ export async function createTestTodo( userId: number, overrides: Partial<{ title: string; completed: boolean }> = {}, ) { - const [todo] = await db + const [todo] = await getDb() .insert(todos) .values({ title: "Test todo", diff --git a/tests/integration/todo.test.ts b/tests/integration/todo.test.ts index 6400163..d983737 100644 --- a/tests/integration/todo.test.ts +++ b/tests/integration/todo.test.ts @@ -13,7 +13,7 @@ mock.module("@/utils/logger", () => { // ── Imports ─────────────────────────────────────────────────────────────── import { createApp } from "@/app"; -import { db, startDatabase, stopDatabase } from "@/database"; +import { getDb, startDatabase, stopDatabase } from "@/database"; import { todos, users } from "@/database/schema"; import { Errors } from "@/utils/errors"; import { createTestTodo, json, jsonReq, setupTestContext } from "./helpers"; @@ -30,7 +30,7 @@ let authed: Awaited>["authed"]; beforeAll(async () => { await startDatabase(); - await db.delete(todos); + await getDb().delete(todos); const ctx = await setupTestContext("alice"); userId = ctx.user.id; authHeader = ctx.authHeader; @@ -38,8 +38,8 @@ beforeAll(async () => { }); afterAll(async () => { - await db.delete(todos); - await db.delete(users); + await getDb().delete(todos); + await getDb().delete(users); await stopDatabase(); }); @@ -69,7 +69,7 @@ describe("GET /api/todos", () => { }); it("returns 200 with all todos belonging to the authenticated user", async () => { - await db.insert(todos).values([ + await getDb().insert(todos).values([ { title: "Buy milk", completed: false, user_id: userId }, { title: "Walk dog", completed: true, user_id: userId }, ]); @@ -83,7 +83,7 @@ describe("GET /api/todos", () => { expect(body.data[0].title).toBe("Buy milk"); expect(body.data[1].title).toBe("Walk dog"); - await db.delete(todos); + await getDb().delete(todos); }); }); @@ -149,7 +149,7 @@ describe("GET /api/todos/:id", () => { }); afterAll(async () => { - await db.delete(todos); + await getDb().delete(todos); }); it("returns 401 when not authenticated", async () => { @@ -196,7 +196,7 @@ describe("PUT /api/todos/:id", () => { }); afterAll(async () => { - await db.delete(todos); + await getDb().delete(todos); }); it("returns 401 when not authenticated", async () => { @@ -266,7 +266,7 @@ describe("DELETE /api/todos/:id", () => { }); afterAll(async () => { - await db.delete(todos); + await getDb().delete(todos); }); it("returns 401 when not authenticated", async () => { From b12ffa1e0a5b3ff43479cceb374389b7c33fa288 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 9 Apr 2026 14:12:31 +0000 Subject: [PATCH 9/9] fix: move auth.test.ts to unit tests to prevent mock.module leak into integration tests Agent-Logs-Url: https://github.com/gamitej/Learning-Bun/sessions/57c202ba-7a88-43f3-936d-50676de30428 Co-authored-by: gamitej <55736105+gamitej@users.noreply.github.com> --- tests/{integration => unit}/auth.test.ts | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename tests/{integration => unit}/auth.test.ts (100%) diff --git a/tests/integration/auth.test.ts b/tests/unit/auth.test.ts similarity index 100% rename from tests/integration/auth.test.ts rename to tests/unit/auth.test.ts