11 Commits

Author SHA1 Message Date
TehRiehlDeal 6a6d629bcf Replace pnpm/action-setup with corepack to actually pin the pnpm version
CI / secrets-scan (push) Successful in 6s
CI / sast (push) Successful in 15s
CI / vuln-scan (push) Successful in 18s
CI / test (push) Successful in 27s
CI / lint (push) Failing after 30s
CI / build-images (push) Has been skipped
CI / image-scan (push) Has been skipped
CI / push (push) Has been skipped
pnpm/action-setup@v4 was ignoring its `version` input on this runner and
installing pnpm 10.x no matter what value we passed. That's why every
attempt to land an onlyBuiltDependencies allowlist failed —
ERR_PNPM_IGNORED_BUILDS kept blocking the install.

Switch to corepack, which Node 22 ships with, and `corepack prepare
pnpm@9.14.4 --activate`. Same mechanism the Dockerfiles use. Adds an
explicit `pnpm --version` line so future CI runs make the actual
installed version visible.

Dropped `cache: pnpm` from actions/setup-node@v4 since pnpm isn't on
PATH yet at that step — the pnpm store cache wasn't doing much for us
on first runs anyway.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 14:49:18 -07:00
TehRiehlDeal a79ee5f479 Pin CI to pnpm 9.14.4 to dodge the strict ERR_PNPM_IGNORED_BUILDS gate
CI / secrets-scan (push) Successful in 6s
CI / sast (push) Successful in 14s
CI / vuln-scan (push) Successful in 17s
CI / lint (push) Failing after 28s
CI / test (push) Failing after 28s
CI / build-images (push) Has been skipped
CI / image-scan (push) Has been skipped
CI / push (push) Has been skipped
The Install dependencies step has been failing with
ERR_PNPM_IGNORED_BUILDS no matter where I put the onlyBuiltDependencies
allowlist (package.json#pnpm, pnpm-workspace.yaml, project .npmrc) and
no matter which pnpm 10.x is installed. The strict build-script gate was
introduced in pnpm 9.15 / 10.0; pnpm 9.14.4 predates it and just runs
postinstall scripts the way pnpm has for years — matching what the
Dockerfiles already do via corepack `pnpm@9`.

Also reverts the short-lived `--ignore-scripts` install workaround,
which skipped @prisma/client's postinstall and left runtime files
missing so `prisma generate` couldn't complete.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 14:47:08 -07:00
TehRiehlDeal aefa5abf95 Pin pnpm 10.33.0 in CI so onlyBuiltDependencies is honored
CI / secrets-scan (push) Successful in 6s
CI / sast (push) Successful in 13s
CI / vuln-scan (push) Successful in 17s
CI / test (push) Failing after 26s
CI / lint (push) Failing after 26s
CI / build-images (push) Has been skipped
CI / image-scan (push) Has been skipped
CI / push (push) Has been skipped
pnpm/action-setup@v4 with `version: 9` was actually resolving to pnpm
10.x in CI (confirmed by the pnpm-10-only WARN about the package.json
"pnpm" field and by the "Verifying lockfile against supply-chain
policies" step). pnpm 10 reads onlyBuiltDependencies from
pnpm-workspace.yaml — our config has been correct there since the first
fix — but whichever 10.x the action picked apparently didn't, so every
install failed with ERR_PNPM_IGNORED_BUILDS.

Pin to 10.33.0 explicitly. That's the version where I verified locally
that pnpm-workspace.yaml's onlyBuiltDependencies is read correctly and
the install completes cleanly.

Dockerfiles still pin pnpm@9 via corepack, which reads the legacy
package.json#pnpm.onlyBuiltDependencies (still in place), so production
image builds remain unaffected.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 14:42:11 -07:00
TehRiehlDeal 4c84d2fb96 Bump Node 20 → 22 for native WebSocket support
CI / test (push) Successful in 31s
CI / lint (push) Successful in 27s
CI / secrets-scan (push) Successful in 5s
CI / vuln-scan (push) Successful in 13s
CI / sast (push) Successful in 9s
CI / build-images (push) Successful in 1m51s
CI / image-scan (push) Successful in 44s
CI / push (push) Successful in 32s
The deployed backend was crashing at startup with `Node.js 20 detected
without native WebSocket support` from @supabase/realtime-js. Native
WebSocket landed in Node 22.4 — bumping the base image is cleaner than
shimming `ws` as a transport (no extra dep, no constructor wrapper).

Bumped in three places to keep everything aligned:
- tehriehlbudget-backend/Dockerfile (runtime + build stages)
- tehriehlbudget-frontend/Dockerfile (build stage; nginx runtime
  unaffected)
- .gitea/workflows/ci.yml (test + lint jobs use the same Node)

@types/node is already on ^22.10.7, so no type-side changes needed.

Bump backend and frontend to 0.1.6 (frontend forced by per-service
push gate; no functional change).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 18:19:35 -07:00
TehRiehlDeal 573c1ec7df Sign pushed images with cosign
After the Harbor push, install cosign and sign each image's digest with
a key stored in COSIGN_PRIVATE_KEY / COSIGN_PASSWORD secrets. Cosign
resolves the SHA tag to the underlying digest, so a single signature
covers every tag (version, sha, latest) pointing at the same image.
Harbor looks up signatures by digest and will display "signed" status
once the signature artifact lands alongside the image.

Cosign is curl-installed at v2.4.1 and uses the existing docker login
for registry auth — no extra credentials needed beyond the cosign key.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 16:42:07 -07:00
TehRiehlDeal 6f626d0c22 Make Trivy image-scan failures readable in the job log
CI / test (push) Successful in 25s
CI / lint (push) Successful in 27s
CI / vuln-scan (push) Successful in 12s
CI / sast (push) Successful in 10s
CI / build-images (push) Successful in 2m19s
CI / secrets-scan (push) Successful in 5s
CI / image-scan (push) Failing after 1m3s
CI / push (push) Has been skipped
The previous gate step wrote findings only to a SARIF file, so when the
scan exited 1 the job log showed nothing — no way to see what was
flagged without downloading the artifact, which itself failed to upload
because the frontend scan never ran and upload-artifact@v3 errors out
when a listed path is missing.

Run Trivy twice per service: first with --format table (no exit-code
gate) so the finding list lands in stdout, then with --format sarif and
--exit-code 1 for the actual gate and the artifact. The DB is cached
between the two runs so the second invocation is fast.

Also add `if-no-files-found: warn` to the artifact upload and `if:
always()` to the frontend scan step so partial reports still upload.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 16:18:25 -07:00
TehRiehlDeal 8c10124272 Build, scan, and push images to Harbor on every main push
CI / test (push) Successful in 25s
CI / lint (push) Successful in 28s
CI / secrets-scan (push) Successful in 5s
CI / vuln-scan (push) Successful in 12s
CI / sast (push) Successful in 10s
CI / build-images (push) Failing after 51s
CI / image-scan (push) Has been skipped
CI / push (push) Has been skipped
Wires up the CD half of the pipeline. New jobs build multi-stage Docker
images for the frontend and backend, run a Trivy image scan that fails
on HIGH/CRITICAL findings, and push to harbor.tehriehldeal.com on main
only. Each push tags <version> (from package.json), <sha>, and latest;
a pre-push existence check refuses to overwrite a version tag that
already points at a different digest, forcing a real bump.

The Vite frontend now reads runtime config from window.__RUNTIME_CONFIG__,
populated by /config.js which nginx renders from container env vars at
startup via envsubst. A getConfig() helper falls back to import.meta.env
for `pnpm dev` and Vitest, so existing test scaffolding keeps working.
PWA workbox excludes /config.js from precache and serves it NetworkOnly
to keep stale config from surviving a container restart.

Bumps frontend 0.0.0→0.1.0 and backend 0.0.1→0.1.0 (production
deployment is a meaningful new capability for both packages).

Also fixes four pre-existing tsc -b errors that the new vite build step
in the frontend Dockerfile would otherwise hit: global.fetch →
globalThis.fetch in three test files, null-guard in Activity.tsx
account filter, type cast on Recharts Pie onClick in Dashboard.tsx,
typed callback signature on the auth.test.ts onAuthStateChange mock.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 15:49:01 -07:00
TehRiehlDeal e67447dfed Fix CI scan jobs for Gitea Actions runner
CI / test (push) Successful in 24s
CI / lint (push) Failing after 22s
CI / secrets-scan (push) Successful in 14s
CI / vuln-scan (push) Successful in 15s
CI / sast (push) Successful in 10s
vuln-scan: replace aquasecurity/trivy-action (tag 0.28.0 was
unresolvable on the runner) with a direct docker run aquasec/trivy
call — same pattern the sast job already uses for Semgrep, no
third-party action to track.

secrets-scan, vuln-scan, sast: pin actions/upload-artifact to v3.
v4 uses a GitHub-specific Twirp protocol that this Gitea Actions
runner does not implement, so uploads were failing with a bare
exitcode 1.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 16:03:23 -07:00
TehRiehlDeal a314908c7b Wire security scans into the CI pipeline
CI / test (push) Successful in 25s
CI / lint (push) Failing after 22s
CI / secrets-scan (push) Failing after 13s
CI / vuln-scan (push) Failing after 9s
CI / sast (push) Successful in 20s
Replace the test-only workflow with a parallel five-job pipeline:
tests, lint+format, gitleaks, Trivy (fs scan + CycloneDX SBOM), and
Semgrep SAST. Security scans are report-only initially so the team
can baseline findings before flipping the gates to blocking. Adds
.gitleaks.toml allowlists for the known dev/test placeholders so the
secret scan starts at zero noise. Future build-image / image-scan /
push-to-harbor stages are sketched in comments at the bottom of
ci.yml.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 15:58:56 -07:00
TehRiehlDeal 1fafad4e69 Hand the CI runner placeholder Supabase env vars
Tests / test (push) Successful in 24s
lib/supabase.ts calls createClient at module load, and Supabase's URL
validator throws when VITE_SUPABASE_URL is undefined — which it is on
the Gitea runner. AccountDetail and Transactions tests transitively
import TransactionForm → lib/supabase and trip on it; tests that mock
lib/supabase directly slip past. Set explicit placeholder values for
the Vitest step so the import succeeds; real auth calls are stubbed
inside the tests, so the placeholders are never sent over the wire.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 16:14:56 -07:00
TehRiehlDeal 5f1918bbce Add Gitea Actions workflow to run tests on every push
Tests / test (push) Successful in 1m1s
Self-hosted Gitea runner picks up `.gitea/workflows/test.yml`. The job
installs deps with the committed pnpm-lock, regenerates the Prisma
client, then runs the backend Jest suite and the frontend Vitest suite
as separate steps so a failure points at the right package. Tests mock
Prisma so no database is needed in CI.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 11:45:52 -07:00