Reusable Forgejo Actions workflows — fast-gate + full-suite for Haskytech repos
Find a file
John Ang c3512cf98a Add composite action implementations
Follow-up to pivot commit which staged only the workflow deletions — the
new .forgejo/actions/ tree was untracked and missed the previous commit.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 19:34:54 +08:00
.forgejo/actions Add composite action implementations 2026-04-13 19:34:54 +08:00
README.md Add composite action implementations 2026-04-13 19:34:54 +08:00

ci-workflows

Centralized composite actions for Haskytech repos. Consumer repos call these via uses: at the step level to avoid duplicating lint/test logic.

Why composite actions (not reusable workflows)

Forgejo 14.0.3 (Gitea 1.22.0 lineage) does not support workflow_call — reusable-workflow runs get stuck in the scheduler with no jobs queued. Composite actions work on all current Forgejo versions and provide step-level reuse, which covers ~90% of the centralization benefit.

If a future Forgejo upgrade adds reusable-workflow support, we can add .forgejo/workflows/*.yml files alongside without removing the composite actions.

Tagging

  • @v1 — floating major tag, auto-propagates fixes to all consumers
  • @main — only for temporary pinning during refactors
  • @<sha> — for explicit pinning during incident response

Update v1 by retagging from main:

git tag -f v1 main && git push -f origin v1

Any consumer using @v1 picks up the change on its next run — no consumer-side PR needed.

Actions

Path Purpose Notes
backend-fast-gate Python pip — ruff + mypy No DB. Target <2 min.
backend-test Python pip — alembic + pytest + coverage Consumer declares postgres service.
frontend-fast-gate Next.js — ESLint + tsc Optional Prisma generate.
frontend-build Next.js — next build Optional Prisma migrate.
node-service-unit Node service — lint + vitest unit For asales-style services.
node-service-int Node service — vitest integration + contract Consumer declares postgres service.
shellcheck Bash scripts — shellcheck For ae, ate, parts of gantry.
python-ruff Standalone ruff lint For Python scripts without tests.

Canonical consumer pattern — fast-gate / full-suite split

This is the shape that rolls out to all 8 client repos. PR gates are cheap, full suite runs post-merge.

name: CI

on:
  pull_request:
    branches: [dev]
  push:
    branches: [dev, 'staging/**']

jobs:
  # Fast gate — runs on every PR, blocks merge. Target: <5 min.
  backend-fast-gate:
    name: Backend Fast Gate
    runs-on: ci
    steps:
      - uses: actions/checkout@v4
      - uses: haskytech/ci-workflows/.forgejo/actions/backend-fast-gate@v1
        with:
          working-directory: backend
          strict-lint: "true"

  frontend-fast-gate:
    name: Frontend Fast Gate
    runs-on: ci
    steps:
      - uses: actions/checkout@v4
      - uses: haskytech/ci-workflows/.forgejo/actions/frontend-fast-gate@v1
        with:
          working-directory: frontend
          type-check-cmd: "npm run type-check"

  # Full suite — runs only on push to dev (post-merge). Can be 15-30 min.
  backend-full-suite:
    name: Backend Full Suite
    if: github.event_name == 'push' && github.ref == 'refs/heads/dev'
    runs-on: ci
    services:
      postgres:
        image: postgres:16
        env:
          POSTGRES_USER: postgres
          POSTGRES_PASSWORD: postgres
          POSTGRES_DB: myapp_test
        options: >-
          --health-cmd pg_isready
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5
    env:
      DATABASE_URL: "postgresql+asyncpg://postgres:postgres@postgres:5432/myapp_test"
      TEST_DATABASE_URL: "postgresql+asyncpg://postgres:postgres@postgres:5432/myapp_test"
      SECRET_KEY: "ci-test-secret-key-not-for-production"
    steps:
      - uses: actions/checkout@v4
      - uses: haskytech/ci-workflows/.forgejo/actions/backend-test@v1
        with:
          working-directory: backend
          coverage-threshold: "50"

  frontend-build:
    name: Frontend Build
    if: github.event_name == 'push' && github.ref == 'refs/heads/dev'
    runs-on: ci
    steps:
      - uses: actions/checkout@v4
      - uses: haskytech/ci-workflows/.forgejo/actions/frontend-build@v1
        with:
          working-directory: frontend

Composite action constraints to know

These are limitations of the Gitea/Forgejo Actions composite-action model, not bugs:

  1. Consumer declares runs-on: and services: — composite actions only bundle steps, not job-level config. Postgres sidecar services must live in the consumer job.
  2. Boolean inputs are stringsstrict-lint: "true" not strict-lint: true. Compare with == 'true' inside the action.
  3. shell: bash required on every run: step — composite actions don't inherit the default shell.
  4. working-directory per step — no top-level defaults: in composite actions; each step that needs it declares it.
  5. continue-on-error accepts expressionscontinue-on-error: ${{ inputs.strict-lint != 'true' }} works.