Files
vac-optimizer/.gitea/workflows/ci.yml
T
nessarandClaude Opus 5.5 ac69a41fc8 ci: run development, quality checks and CI on Bun 1.4.3
Pin Bun 1.4.3 with packageManager, and force every bun run script onto
the Bun runtime through bunfig so ng, ESLint and Prettier never fall
back to an installed Node. Type checking uses bun check for the
application and test projects, next to the Angular build.

CI jobs install the pinned release from a checksum-verified download
instead of npm and run project steps in a shell without Node on PATH,
which stays available only for the checkout and upload actions. The
image test's fake docker runs on Bun.

Refs #290, #293

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 14:25:46 +02:00

238 lines
9.4 KiB
YAML

name: CI
on:
push:
pull_request:
types: [opened, reopened, synchronize, edited]
workflow_dispatch:
permissions:
contents: read
# Gitea's checkout and upload-artifact actions are JavaScript actions that the runner
# executes with the job container's own `node`, so those containers keep Node. Project
# commands never use it: the jobs that run them install the pinned Bun with
# scripts/install-bun.sh and run `run:` steps under a shell whose PATH has Bun but no Node.
concurrency:
group: ci-${{ github.event_name }}-${{ github.ref }}
cancel-in-progress: ${{ !startsWith(github.ref, 'refs/tags/') }}
jobs:
policy:
name: policy
runs-on: ubuntu-latest
container: node:24.21.0
defaults:
run:
shell: env PATH=/opt/bun/bin:/usr/bin:/bin bash -e {0}
outputs:
slug: ${{ steps.policy.outputs.slug }}
tag: ${{ steps.policy.outputs.tag }}
steps:
- uses: https://gitea.com/actions/checkout@v4
with:
fetch-depth: 0
persist-credentials: false
- name: Install Bun
run: sh scripts/install-bun.sh
- name: Policy regression tests
run: bun scripts/ci-policy.test.mjs && bun scripts/ci-image.test.mjs
- name: Validate event and commit
id: policy
run: bun scripts/ci-policy.mjs
validate:
name: validate
runs-on: ubuntu-latest
container: node:24.21.0
defaults:
run:
shell: env PATH=/opt/bun/bin:/usr/bin:/bin bash -e {0}
steps:
- uses: https://gitea.com/actions/checkout@v4
with:
persist-credentials: false
- name: Install Bun
run: sh scripts/install-bun.sh
- run: bun install --frozen-lockfile
- run: bun run lint
- run: bun run format:check
- run: bun run typecheck
test:
name: test
runs-on: ubuntu-latest
container: node:24.21.0
defaults:
run:
shell: env PATH=/opt/bun/bin:/usr/bin:/bin bash -e {0}
steps:
- uses: https://gitea.com/actions/checkout@v4
with:
persist-credentials: false
- name: Install Bun
run: sh scripts/install-bun.sh
- run: bun install --frozen-lockfile
- run: bun scripts/generate-langs.js
- name: Functional coverage
run: bun test --config=bunfig.coverage.toml --isolate --path-ignore-patterns='**/benchmark/**'
- name: Coverage report
if: ${{ always() }}
run: bun scripts/coverage-reports.mjs
- name: Upload coverage
if: ${{ always() }}
uses: https://gitea.com/actions/upload-artifact@v3
with:
name: coverage
path: coverage/
retention-days: 7
if-no-files-found: error
benchmark:
name: benchmark
runs-on: ubuntu-latest
container:
image: node:24.21.0
options: --memory=3g
defaults:
run:
shell: env PATH=/opt/bun/bin:/usr/bin:/bin bash -e {0}
steps:
- uses: https://gitea.com/actions/checkout@v4
with:
persist-credentials: false
- name: Install Bun
run: sh scripts/install-bun.sh
- run: bun install --frozen-lockfile
- run: bun scripts/generate-langs.js
- name: Benchmark protocol with relative gate
run: timeout 600s bun test src/app/benchmark --timeout=600000
build:
name: build
runs-on: ubuntu-latest
container: node:24.21.0
defaults:
run:
shell: env PATH=/opt/bun/bin:/usr/bin:/bin bash -e {0}
steps:
- uses: https://gitea.com/actions/checkout@v4
with:
persist-credentials: false
- name: Install Bun
run: sh scripts/install-bun.sh
- run: bun install --frozen-lockfile
- run: bun run build -- --configuration production
- uses: https://gitea.com/actions/upload-artifact@v3
with:
name: dist
path: dist/
retention-days: 7
if-no-files-found: error
security:
name: security
runs-on: ubuntu-latest
container: node:24.21.0
steps:
- uses: https://gitea.com/actions/checkout@v4
with:
persist-credentials: false
- name: Install scanners
run: |
apt-get update
apt-get install --yes --no-install-recommends python3-venv
python3 -m venv /tmp/semgrep
/tmp/semgrep/bin/pip install semgrep==1.180.0
case "$(uname -m)" in
x86_64) trivy_arch=64bit ;;
aarch64) trivy_arch=ARM64 ;;
*) echo 'Unsupported Trivy architecture' >&2; exit 1 ;;
esac
archive="trivy_0.75.0_Linux-${trivy_arch}.tar.gz"
curl --fail --location --silent --show-error "https://github.com/aquasecurity/trivy/releases/download/v0.75.0/${archive}" --output "/tmp/${archive}"
curl --fail --location --silent --show-error https://github.com/aquasecurity/trivy/releases/download/v0.75.0/trivy_0.75.0_checksums.txt --output /tmp/trivy-checksums.txt
(cd /tmp && sha256sum --check --ignore-missing trivy-checksums.txt)
tar -xzf "/tmp/${archive}" -C /usr/local/bin trivy
- name: JavaScript and TypeScript SAST
run: /tmp/semgrep/bin/semgrep scan --config p/javascript --config p/typescript --metrics off --sarif --output semgrep-results.sarif src scripts
- name: Dependency and secret scan
if: ${{ !cancelled() }}
run: trivy fs --db-repository public.ecr.aws/aquasecurity/trivy-db:2 --format sarif --output trivy-results.sarif --skip-dirs node_modules,dist,coverage .
- name: Security reports
if: ${{ always() }}
uses: https://gitea.com/actions/upload-artifact@v3
with:
name: security
path: '*-results.sarif'
retention-days: 7
if-no-files-found: error
images:
name: images
needs: [policy, validate, test, benchmark, build, security]
if: >-
success() &&
(github.event_name == 'push' ||
(github.event_name == 'pull_request' &&
github.event.pull_request.head.repo.full_name == github.repository))
runs-on: ubuntu-latest
container: docker:29.8.2
concurrency:
group: image-${{ needs.policy.outputs.tag != '' && 'stable' || needs.policy.outputs.slug }}
cancel-in-progress: false
services:
docker:
image: docker:29.8.2-dind
env:
DOCKER_TLS_CERTDIR: ''
options: >-
--privileged
--health-cmd "docker info"
--health-interval 5s
--health-timeout 5s
--health-retries 12
env:
DOCKER_HOST: tcp://docker:2375
DOCKER_TLS_CERTDIR: ''
defaults:
run:
shell: sh
steps:
# Alpine's nodejs package is for the checkout action only (it shares /usr/bin
# with git); Bun needs libgcc and libstdc++ and the installer needs curl.
- name: Install checkout runtime and tools
run: apk add --no-cache nodejs git curl libgcc libstdc++
- uses: https://gitea.com/actions/checkout@v4
with:
fetch-depth: 0
persist-credentials: false
- name: Install Bun
run: sh scripts/install-bun.sh
- name: Build branch images or promote a release
env:
REGISTRY_USERNAME: ${{ secrets.REGISTRY_USERNAME }}
REGISTRY_PASSWORD: ${{ secrets.REGISTRY_PASSWORD }}
run: /opt/bun/bin/bun scripts/ci-image.mjs
required:
name: required
if: ${{ always() }}
needs: [policy, validate, test, benchmark, build, security, images]
runs-on: ubuntu-latest
container: node:24.21.0
env:
CHECK_RESULTS: ${{ needs.policy.result }} ${{ needs.validate.result }} ${{ needs.test.result }} ${{ needs.benchmark.result }} ${{ needs.build.result }} ${{ needs.security.result }}
IMAGE_RESULT: ${{ needs.images.result }}
IMAGE_EXPECTED: ${{ github.event_name == 'push' || (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository) }}
steps:
- name: Require successful validation and expected publication
run: |
test "$CHECK_RESULTS" = 'success success success success success success' || exit 1
if [ "$IMAGE_EXPECTED" = 'true' ]; then
test "$IMAGE_RESULT" = 'success'
else
test "$IMAGE_RESULT" = 'skipped'
fi