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> |
||
|---|---|---|
| .forgejo/actions | ||
| README.md | ||
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:
- Consumer declares
runs-on:andservices:— composite actions only bundle steps, not job-level config. Postgres sidecar services must live in the consumer job. - Boolean inputs are strings —
strict-lint: "true"notstrict-lint: true. Compare with== 'true'inside the action. shell: bashrequired on everyrun:step — composite actions don't inherit the default shell.working-directoryper step — no top-leveldefaults:in composite actions; each step that needs it declares it.continue-on-erroraccepts expressions —continue-on-error: ${{ inputs.strict-lint != 'true' }}works.