Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 49 additions & 0 deletions .github/workflows/pre-commit.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,11 @@ jobs:
uses: actions/setup-node@v6
with:
node-version: "24"
# Cache the npm download cache keyed on the frontend lockfile.
# Saves ~30-40s per run vs an uncached `npm ci`. Same pattern
# already used in frontend-build-sentinel.yml.
cache: "npm"
cache-dependency-path: frontend/package-lock.json

- name: Set up Terraform
# Required by the terraform_fmt + terraform_validate pre-commit hooks.
Expand Down Expand Up @@ -63,13 +68,30 @@ jobs:
"https://raw.githubusercontent.com/terraform-linters/tflint/${TFLINT_VERSION}/install_linux.sh"
bash /tmp/tflint-install.sh

# Cache the installed tool binaries (gosec, gocyclo). Keyed on the
# pinned version strings so a tool-version bump still triggers a
# fresh install. Both binaries land in ~/go/bin which setup-go@v6
# already adds to PATH. Restored BEFORE the install steps so the
# `if: cache-hit != 'true'` guards below can short-circuit them on
# cache-hit runs (the `go install` invocations cost ~3-5s each
# even when the module cache is warm; skipping them on cache-hit
# is worth the extra `if`).
- name: Cache Go-installed tools (gosec, gocyclo)
id: cache-go-tools
uses: actions/cache@v4
with:
path: ~/go/bin
key: go-tools-${{ runner.os }}-gosec-v2.22.4-gocyclo-v0.6.0

- name: Install gosec
if: steps.cache-go-tools.outputs.cache-hit != 'true'
# Pinned to the same version ci.yml's `securego/gosec` Action uses,
# so an upstream gosec release with rule changes can't silently
# downgrade the gate between the two workflows.
run: go install github.com/securego/gosec/v2/cmd/gosec@v2.22.4

- name: Install gocyclo
if: steps.cache-go-tools.outputs.cache-hit != 'true'
# Pinned to match ci.yml — security tool installs must not use
# @latest; that's exactly the supply-chain weakness this PR is
# closing for Dockerfile FROMs.
Expand Down Expand Up @@ -115,6 +137,33 @@ jobs:
- name: Install pre-commit
run: pip install 'pre-commit==4.0.1'

# Cache pre-commit's per-hook environments (Go, Python, Node, etc.
# virtualenvs it builds on first run). Keyed on the hook config
# because pre-commit will rebuild any env whose pinned rev changes.
# Saves ~30-60s per cache-hit run; safe because pre-commit verifies
# env integrity on use.
- name: Cache pre-commit environments
uses: actions/cache@v4
with:
path: ~/.cache/pre-commit
key: pre-commit-${{ runner.os }}-${{ hashFiles('.pre-commit-config.yaml') }}
restore-keys: |
pre-commit-${{ runner.os }}-

# Cache the Go build cache so `go vet`, `gosec`, and any other
# Go-compiling hooks reuse compiled object files instead of
# rebuilding from source. setup-go@v6 caches ~/go/pkg/mod
# (modules) but NOT ~/.cache/go-build (compiled output) — this
# step covers the latter. Keyed on go.sum so a dep upgrade still
# invalidates the cache and gets clean builds.
- name: Cache Go build cache
uses: actions/cache@v4
with:
path: ~/.cache/go-build
key: go-build-${{ runner.os }}-${{ hashFiles('**/go.sum') }}
restore-keys: |
go-build-${{ runner.os }}-

- name: Install frontend deps
run: |
if [ -f frontend/package-lock.json ]; then
Expand Down
36 changes: 32 additions & 4 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -152,29 +152,57 @@ repos:
pass_filenames: false
files: ^internal/database/postgres/migrations/

# Test execution
# Heavy test execution: pre-push stage only.
#
# These three hooks rebuild + run the full Go and frontend test suites,
# which is ~6-7 min of work and the bulk of the CI pre-commit job's
# runtime. They are *redundant in CI* — the same suites are run by
# dedicated workflows that PRs and pushes already trigger:
#
# - go-test (-short -race ./...) : ci.yml `unit-tests` runs the same
# suite with -race AND an integration
# pass with -tags=integration.
# - frontend-build (npm run build): frontend-build.yml runs npm run
# typecheck + npm run build on PRs;
# frontend-build-sentinel.yml runs
# the build on every push to main /
# feat/**.
# - frontend-test (jest) : frontend-build-sentinel.yml runs
# `npx jest --no-coverage --silent`
# on every push to feat/** (which
# fires on every PR-branch update).
#
# Moving them to the pre-push stage keeps the local safety net (devs
# who run `pre-commit install --hook-type pre-push` still get these
# tests on `git push`) while letting the CI pre-commit workflow stay
# focused on style/security/syntax. Pre-commit's default stage filter
# is `pre-commit`, so the CI workflow's `pre-commit run --all-files`
# skips these hooks automatically.
- repo: local
hooks:
- id: go-test
name: Run Go tests
name: Run Go tests (pre-push only; CI covers via ci.yml)
entry: bash -c 'go test -short -race ./...'
language: system
pass_filenames: false
files: \.go$
stages: [pre-push]

- id: frontend-build
name: Build frontend
name: Build frontend (pre-push only; CI covers via frontend-build.yml)
entry: bash -c 'cd frontend && npm run build'
language: system
pass_filenames: false
files: ^frontend/src/
stages: [pre-push]

- id: frontend-test
name: Run frontend tests
name: Run frontend tests (pre-push only; CI covers via frontend-build-sentinel.yml)
entry: bash -c 'cd frontend && npx jest --no-coverage --silent'
language: system
pass_filenames: false
files: ^frontend/src/
stages: [pre-push]

# Global configuration
default_stages: [pre-commit, pre-push]
Expand Down
Loading