name: CI on: push: branches: [main] pull_request: branches: [main] concurrency: group: ci-${{ github.ref }} cancel-in-progress: true permissions: contents: read # All third-party Actions are pinned to a 40-char commit SHA with a trailing # '# vX.Y.Z' comment so a compromised maintainer or moved tag cannot silently # execute attacker code in CI. Bump the SHA + comment together when updating. jobs: go: name: Go runs-on: ubuntu-latest steps: - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 - uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5.6.0 with: go-version: "1.26" - name: Create web build placeholder for embed run: mkdir -p web/build && echo "placeholder" > web/build/.gitkeep - name: Run go vet run: go vet ./... - name: Run golangci-lint # only-new-issues: false means CI fails on ANY linter finding, # not just findings on PR-changed lines. The IDEA-732 cleanup # (PRs #247/#249/#251/#252) cleared the existing findings under # the configured linter set in .golangci.yml — staticcheck SA*, # govet, ineffassign, gofmt, and the standalone `unused` linter # (which reports U1000). Flipping the gate now prevents # regression drift going forward. # v2 of golangci-lint is required because v1 is capped at older # Go releases that we no longer support. uses: golangci/golangci-lint-action@1e7e51e771db61008b38414a730f564565cf7c20 # v9.2.0 with: version: v2.11.4 args: --timeout=5m only-new-issues: false - name: Run govulncheck # Fails the build on any known vulnerability in a package we # actually reach via the call graph. Net-positive: catches CVEs # in indirect deps early, without the noise of hitting every # stale entry in our dependency tree. # # Pinned to a specific govulncheck release. Track upstream in # Pad's workspace; bump intentionally so an upstream behavior # change can't break unrelated PRs. Update via: # go install golang.org/x/vuln/cmd/govulncheck@ run: | go install golang.org/x/vuln/cmd/govulncheck@v1.2.0 "$(go env GOPATH)/bin/govulncheck" ./... - name: Run tests run: go test ./... - name: Run tests with race detector if: github.ref == 'refs/heads/main' # Default 10m is tight: the full server-package suite under -race # measures ~13m locally on a developer laptop after BUG-851 (the # ipRateLimiter goroutine drain). The PLAN-866 attachment work # (image decode/encode/resize across thumbnail + transform tests) # pushes total race-step runtime past 20m on the GitHub-hosted # runner — bumped to 30m to give headroom without papering over # an actual hang. Genuine deadlocks would still hit this and # produce the goroutine-dump panic. run: go test -race -timeout=30m ./... - name: Build binary run: go build -o pad ./cmd/pad - name: Verify binary runs run: ./pad --help go-postgres: name: Go (PostgreSQL) runs-on: ubuntu-latest services: postgres: image: postgres:17-alpine env: POSTGRES_USER: pad POSTGRES_PASSWORD: pad POSTGRES_DB: pad ports: - 5432:5432 options: >- --health-cmd "pg_isready -U pad" --health-interval 5s --health-timeout 3s --health-retries 10 steps: - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 - uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5.6.0 with: go-version: "1.26" - name: Create web build placeholder for embed run: mkdir -p web/build && echo "placeholder" > web/build/.gitkeep - name: Run tests against PostgreSQL env: PAD_TEST_POSTGRES_URL: "postgres://pad:pad@localhost:5432/pad?sslmode=disable" run: go test ./... -count=1 - name: Run tests with race detector against PostgreSQL if: github.ref == 'refs/heads/main' env: PAD_TEST_POSTGRES_URL: "postgres://pad:pad@localhost:5432/pad?sslmode=disable" # See SQLite race-detector step: 30m headroom over the default 10m. # PostgreSQL adds latency on every CREATE/DROP and bcrypt under # -race is ~3s per call — TestSessionIPChange-style tests that # bootstrap a fresh user pay the full cost each time. The PLAN-866 # attachment work pushed the cumulative wall over 20m. run: go test -race -timeout=30m ./... -count=1 web: name: Web runs-on: ubuntu-latest defaults: run: working-directory: web steps: - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 with: node-version: "22" cache: "npm" cache-dependency-path: web/package-lock.json - name: Install dependencies run: npm ci - name: Audit npm dependencies (production, high+) # Fail the build on any HIGH or CRITICAL advisory in production deps. # Dev-only advisories are treated as informational — they don't ship # and fixing them can require waiting on upstream maintainers. run: npm audit --audit-level=high --omit=dev - name: Build run: npm run build - name: Type check (svelte-check) run: npm run check e2e: name: E2E (Playwright) runs-on: ubuntu-latest # Build the binary + UI once and reuse across Playwright projects. # The suite is small (<10s at the time of writing — see TASK-733 for # follow-up coverage); the `timeout-minutes` cap is a sanity check. timeout-minutes: 10 steps: - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 - uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5.6.0 with: go-version: "1.26" - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 with: node-version: "22" cache: "npm" cache-dependency-path: web/package-lock.json - name: Install web dependencies working-directory: web run: npm ci - name: Build web UI working-directory: web run: npm run build - name: Build pad binary # Web build output is embedded via //go:embed; it must exist before # the Go build. The CI `go` job above builds against a placeholder, # which is fine for tests — for e2e we need the real embedded UI. run: go build -o pad ./cmd/pad - name: Install Playwright browsers working-directory: web # --with-deps pulls in the Ubuntu libraries Playwright needs # (libatk, libnss, libcups, …). Scoped to chromium to cut download # time — the suite's mobile project uses Pixel 7, which defaults to # Chromium, so we don't need WebKit. run: npx playwright install --with-deps chromium - name: Run Playwright working-directory: web env: CI: "1" run: npx playwright test - name: Upload Playwright report on failure if: failure() uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: name: playwright-report path: web/playwright-report/ retention-days: 14