From 2b78fcf731e7f80c77f5bed6242027539eef589b Mon Sep 17 00:00:00 2001 From: Flea Flicker Date: Fri, 22 May 2026 02:16:49 +0000 Subject: [PATCH 1/4] fix(ci): build all service images + upgrade Node 22 + pin packageManager (GRO-1522) - Upgrade CI jobs (lint-typecheck, test, build) to Node 22 - Dockerfile uses node:22-alpine for base and runner stages - Root package.json gets packageManager field for corepack pin - Docker build already targets all 4 stages (api/migrate/seed/reset) Co-Authored-By: Paperclip --- .github/workflows/ci.yml | 6 +++--- Dockerfile | 4 ++-- package.json | 1 + 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3881905..0fb65b7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -25,7 +25,7 @@ jobs: - uses: actions/setup-node@v4 with: - node-version: 20 + node-version: 22 cache: pnpm - name: Install dependencies @@ -49,7 +49,7 @@ jobs: - uses: actions/setup-node@v4 with: - node-version: 20 + node-version: 22 cache: pnpm - name: Install dependencies @@ -71,7 +71,7 @@ jobs: - uses: actions/setup-node@v4 with: - node-version: 20 + node-version: 22 cache: pnpm - name: Install dependencies diff --git a/Dockerfile b/Dockerfile index 662ad85..ada297f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM node:20-alpine AS base +FROM node:22-alpine AS base RUN corepack enable && corepack prepare pnpm@9.15.4 --activate WORKDIR /app @@ -12,7 +12,7 @@ RUN mkdir -p /home/node/.cache/node/corepack COPY apps/api/ apps/api/ RUN pnpm --filter @groombook/api build -FROM node:20-alpine AS runner +FROM node:22-alpine AS runner RUN corepack enable && corepack prepare pnpm@9.15.4 --activate WORKDIR /app ENV NODE_ENV=production diff --git a/package.json b/package.json index d2e71b5..871b4bd 100644 --- a/package.json +++ b/package.json @@ -3,5 +3,6 @@ "version": "0.0.1", "private": true, "type": "module", + "packageManager": "pnpm@9.15.4", "license": "AGPL-3.0-only" } From 59893908e29fee91a850b0a5a7cf161a9a506520 Mon Sep 17 00:00:00 2001 From: Flea Flicker Date: Fri, 22 May 2026 02:23:05 +0000 Subject: [PATCH 2/4] fix: resolve pre-existing TypeScript errors for CI compliance - petsExtendedFields.test.ts: import and/eq/exists/or from db/index.js (avoids mock scope collision with TypeScript closures) - petsExtendedFields.test.ts: add non-null assertion on petRows[0] in makeDeleteChainable (petRows always has at least one element) - factories.ts buildPet: add missing extended pet fields to defaults (coatType, temperamentScore, temperamentFlags, medicalAlerts, preferredCuts) so the inferred PetRow type is satisfied Co-Authored-By: Paperclip --- apps/api/src/__tests__/petsExtendedFields.test.ts | 3 ++- apps/api/src/db/factories.ts | 5 +++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/apps/api/src/__tests__/petsExtendedFields.test.ts b/apps/api/src/__tests__/petsExtendedFields.test.ts index a013d88..e07850e 100644 --- a/apps/api/src/__tests__/petsExtendedFields.test.ts +++ b/apps/api/src/__tests__/petsExtendedFields.test.ts @@ -2,6 +2,7 @@ import { describe, it, expect, vi, beforeEach } from "vitest"; import { Hono } from "hono"; import type { AppEnv, StaffRow } from "../middleware/rbac.js"; import { petsRouter } from "../routes/pets.js"; +import { and, eq, exists, or } from "../db/index.js"; // ─── Mock staff fixtures ────────────────────────────────────────────────────── @@ -134,7 +135,7 @@ function makeDeleteChainable(): unknown { } if (prop === "returning") { return () => { - const row = petRows[0]; + const row = petRows[0]!; deletedId = row.id as string; return [row]; }; diff --git a/apps/api/src/db/factories.ts b/apps/api/src/db/factories.ts index 9f801e2..da36fe6 100644 --- a/apps/api/src/db/factories.ts +++ b/apps/api/src/db/factories.ts @@ -103,6 +103,11 @@ export function buildPet(overrides: Partial & { clientId: string }): Pet photoKey: null, photoUploadedAt: null, image: null, + coatType: null, + temperamentScore: null, + temperamentFlags: [], + medicalAlerts: [], + preferredCuts: [], createdAt: new Date("2025-01-01T00:00:00Z"), updatedAt: new Date("2025-01-01T00:00:00Z"), }; From ce9fcfb362dade3bd457e0861a95dc70bca45c40 Mon Sep 17 00:00:00 2001 From: Flea Flicker Date: Fri, 22 May 2026 02:36:18 +0000 Subject: [PATCH 3/4] fix: resolve pre-existing test and TypeScript errors for CI compliance - Fix CLIENT_ID/PET_ID in petsExtendedFields.test.ts to valid UUIDs so createPetSchema validation (z.string().uuid()) passes in tests - Replace top-level imports of and/eq/exists/or with vi.fn() stubs in petsExtendedFields.test.ts mock to avoid vi.mock hoisting ReferenceError - Add impersonationAuditLogs proxy + insert() chain to portal.test.ts mock to fix audit-log write failures - Add 5 missing extended fields to buildPet factory defaults - Add non-null assertion on petRows[0] in makeDeleteChainable Co-Authored-By: Claude Sonnet 4.6 --- apps/api/src/__tests__/petsExtendedFields.test.ts | 13 ++++++------- apps/api/src/__tests__/portal.test.ts | 9 +++++++++ 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/apps/api/src/__tests__/petsExtendedFields.test.ts b/apps/api/src/__tests__/petsExtendedFields.test.ts index e07850e..33e0698 100644 --- a/apps/api/src/__tests__/petsExtendedFields.test.ts +++ b/apps/api/src/__tests__/petsExtendedFields.test.ts @@ -2,7 +2,6 @@ import { describe, it, expect, vi, beforeEach } from "vitest"; import { Hono } from "hono"; import type { AppEnv, StaffRow } from "../middleware/rbac.js"; import { petsRouter } from "../routes/pets.js"; -import { and, eq, exists, or } from "../db/index.js"; // ─── Mock staff fixtures ────────────────────────────────────────────────────── @@ -22,8 +21,8 @@ const MANAGER: StaffRow = { // ─── Mutable mock state ─────────────────────────────────────────────────────── -const CLIENT_ID = "client-uuid-extended"; -const PET_ID = "pet-uuid-extended"; +const CLIENT_ID = "550e8400-e29b-41d4-a716-446655440001"; +const PET_ID = "660e8400-e29b-41d4-a716-446655440002"; let petRows: Record[] = []; let appointmentRows: Record[] = []; @@ -164,10 +163,10 @@ vi.mock("../db", () => { }), pets, appointments, - and, - eq, - exists, - or, + and: vi.fn(), + eq: vi.fn(), + exists: vi.fn(), + or: vi.fn(), }; }); diff --git a/apps/api/src/__tests__/portal.test.ts b/apps/api/src/__tests__/portal.test.ts index 2388943..bbeacbd 100644 --- a/apps/api/src/__tests__/portal.test.ts +++ b/apps/api/src/__tests__/portal.test.ts @@ -67,6 +67,11 @@ vi.mock("../db", () => { { get: (t, p) => (p === "_name" ? "impersonationSessions" : { table: "impersonationSessions", column: p }) } ); + const impersonationAuditLogs = new Proxy( + { _name: "impersonationAuditLogs" }, + { get: (t, p) => (p === "_name" ? "impersonationAuditLogs" : { table: "impersonationAuditLogs", column: p }) } + ); + const appointments = new Proxy( { _name: "appointments" }, { get: (t, p) => (p === "_name" ? "appointments" : { table: "appointments", column: p }) } @@ -99,8 +104,12 @@ vi.mock("../db", () => { }), }), }), + insert: () => ({ + values: () => ({ returning: () => [{}] }), + }), }), impersonationSessions, + impersonationAuditLogs, appointments, eq: vi.fn(), and: vi.fn(), From da913d600fa8b76dc4082db1f35a66ad3a60c9ef Mon Sep 17 00:00:00 2001 From: The Dogfather <20+gb_dogfather@noreply.git.farh.net> Date: Fri, 22 May 2026 02:56:20 +0000 Subject: [PATCH 4/4] fix(ci): add Gitea CI workflow with all 4 image targets + Node 22 (GRO-1522) Co-Authored-By: Paperclip --- .gitea/workflows/ci.yml | 139 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 139 insertions(+) create mode 100644 .gitea/workflows/ci.yml diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml new file mode 100644 index 0000000..e16d7e5 --- /dev/null +++ b/.gitea/workflows/ci.yml @@ -0,0 +1,139 @@ +name: CI + +on: + push: + branches: [main, dev] + pull_request: + branches: [main, dev] + workflow_dispatch: + inputs: + ref: + description: "Branch or ref to run CI against" + required: false + default: "main" + +jobs: + lint-typecheck: + name: Lint & Typecheck + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: pnpm/action-setup@v4 + with: + version: '9.15.4' + + - uses: actions/setup-node@v4 + with: + node-version: 22 + cache: pnpm + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Typecheck + run: pnpm typecheck + + - name: Lint + run: pnpm lint + + test: + name: Test + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: pnpm/action-setup@v4 + with: + version: '9.15.4' + + - uses: actions/setup-node@v4 + with: + node-version: 22 + cache: pnpm + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Run tests + run: pnpm test + + docker: + name: Build & Push Docker Images + runs-on: ubuntu-latest + needs: [lint-typecheck, test] + steps: + - uses: actions/checkout@v4 + + - name: Generate image tag + id: version + run: | + if [ "${{ github.event_name }}" = "pull_request" ]; then + TAG="pr-${{ github.event.pull_request.number }}-${GITHUB_SHA::7}" + else + TAG="$(date -u +%Y.%m.%d)-${GITHUB_SHA::7}" + fi + echo "tag=$TAG" >> "$GITHUB_OUTPUT" + echo "Image tag: $TAG" + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to Gitea Container Registry + uses: docker/login-action@v3 + with: + registry: git.farh.net + username: ${{ gitea.actor }} + password: ${{ secrets.REGISTRY_TOKEN }} + + - name: Build and push API image + uses: docker/build-push-action@v6 + with: + context: . + file: Dockerfile + target: runner + push: true + tags: | + git.farh.net/groombook/api:${{ steps.version.outputs.tag }} + ${{ github.ref == 'refs/heads/main' && 'git.farh.net/groombook/api:latest' || '' }} + cache-from: type=registry,ref=git.farh.net/groombook/cache:api + cache-to: type=registry,ref=git.farh.net/groombook/cache:api,mode=max + + - name: Build and push Migrate image + uses: docker/build-push-action@v6 + with: + context: . + file: Dockerfile + target: migrate + push: true + tags: | + git.farh.net/groombook/migrate:${{ steps.version.outputs.tag }} + ${{ github.ref == 'refs/heads/main' && 'git.farh.net/groombook/migrate:latest' || '' }} + cache-from: type=registry,ref=git.farh.net/groombook/cache:migrate + cache-to: type=registry,ref=git.farh.net/groombook/cache:migrate,mode=max + + - name: Build and push Seed image + uses: docker/build-push-action@v6 + with: + context: . + file: Dockerfile + target: seed + push: true + tags: | + git.farh.net/groombook/seed:${{ steps.version.outputs.tag }} + ${{ github.ref == 'refs/heads/main' && 'git.farh.net/groombook/seed:latest' || '' }} + cache-from: type=registry,ref=git.farh.net/groombook/cache:seed + cache-to: type=registry,ref=git.farh.net/groombook/cache:seed,mode=max + + - name: Build and push Reset image + uses: docker/build-push-action@v6 + with: + context: . + file: Dockerfile + target: reset + push: true + tags: | + git.farh.net/groombook/reset:${{ steps.version.outputs.tag }} + ${{ github.ref == 'refs/heads/main' && 'git.farh.net/groombook/reset:latest' || '' }} + cache-from: type=registry,ref=git.farh.net/groombook/cache:reset + cache-to: type=registry,ref=git.farh.net/groombook/cache:reset,mode=max