Shared CI/CD, security checks and repo config for TypeScript projects.
- Reusable workflows for sites (
site-ci.yml) and bun tools (tool-ci.yml), plus SARIF upload - Deploy actions for Netlify and Cloudflare Workers, and branch-to-environment mapping
- npm package with Lighthouse budgets, a copyleft license check, and Playwright and Biome configs
- Renovate preset and lefthook hooks
Works with bun (default), pnpm and aube. CI runs whichever
package.json scripts your repo defines (lint, test:unit, test:e2e:all,
...) and skips the rest.
Pin to a release SHA with the version in a comment. Renovate keeps it current.
uses: taraxvoid/voidflow/.github/workflows/site-ci.yml@<sha> # vX.Y.ZCanary releases are tagged vX.Y.Z-next.N. See RELEASING.md.
STABILITY.md says what minors, patches and majors may change
and how deprecations reach you before a removal.
# .github/workflows/ci.yml
name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
types: [opened, synchronize, reopened, ready_for_review]
workflow_dispatch:
jobs:
validate:
uses: taraxvoid/voidflow/.github/workflows/site-ci.yml@<sha> # vX.Y.Zmain deploys to prod and PRs get previews; there are no other environment
branches. The push trigger validates direct pushes to main.
Scripts it runs, if defined:
| Script | When |
|---|---|
lint, check, build, test:unit |
always |
check:licenses |
lockfile or scripts/ changed |
test:e2e:all (or test:e2e:full if there's no :all) |
e2e-relevant files changed; always on merge_group, schedule and workflow_dispatch |
test:e2e:a11y, test:e2e:lighthouse |
schedule and workflow_dispatch only |
Draft PRs skip the job.
Inputs:
| Input | Default | |
|---|---|---|
runner |
ubuntu-latest |
Runner label |
package-manager |
bun |
bun, pnpm or aube |
build |
true |
false if your e2e scripts already build |
build-env |
{} |
JSON object of build env vars (not for secrets) |
code-paths |
all but Markdown, docs/, LICENSE*, issue templates |
JSON array of globs that count as code |
e2e-scripts |
[] |
JSON array of extra e2e scripts, run in order. Missing ones fail |
e2e-extra-paths |
["wrangler.jsonc", "site.vars.json"] |
JSON array of globs that also trigger e2e |
socket-scanner |
false |
Run the bun install security scanner in CI |
playwright-install-with-deps |
true |
Install Playwright system deps |
PRs that only touch non-code paths skip the work but still pass. The required
check is validate / Validate.
pnpm / aube: pnpm reads its version from packageManager and Node from
engines. aube must be pinned in mise.toml and reads Node from
devEngines.runtime. Both use pnpm-lock.yaml.
Migrating from main/next/live: drop single-environment from callers
(GitHub rejects unknown inputs). branch-env-map takes no inputs and fails on
next, live and pushes to other branches. a11y and Lighthouse no longer run
on PRs; add a schedule (below) to keep them.
Merge queue: org-owned repos can use one (personal accounts can't). Set
mergeQueue: true on the repo's ruleset config and add merge_group: to
on: so the required check reports on the queue's branch.
Scheduled a11y and Lighthouse: these run only on a schedule or a manual dispatch. The cron is the caller's; drop it to stop running them:
on:
schedule:
- cron: '17 6 * * *'Socket scanner: CI strips install.security.scanner from bunfig.toml
so a flaky API can't break the build; local installs still use it. Write it as
a plain scanner = "..." line; inline tables fail. Outside site-ci.yml, use
the composite action:
- uses: oven-sh/setup-bun@<sha> # <version>
- uses: taraxvoid/voidflow/actions/bun-install@<sha> # vX.Y.ZFor bun CLIs, TUIs and libraries: gitleaks, lint, check, build, test,
check:licenses, and jactionlint + zizmor
when workflows change. Reads tools from mise.toml and bun from
.bun-version. Inputs: runner, socket-scanner.
name: CI
on:
pull_request:
types: [opened, synchronize, reopened, ready_for_review]
push:
branches: [main]
workflow_dispatch:
permissions:
contents: read
pull-requests: read
jobs:
validate:
uses: taraxvoid/voidflow/.github/workflows/tool-ci.yml@<sha> # vX.Y.ZBoth CI workflows save gitleaks and zizmor results as a voidflow-sarif
artifact. To upload them to GitHub code scanning, add a job (it needs
security-events: write, so it's separate):
sarif:
needs: validate
if: ${{ !cancelled() && needs.validate.result != 'skipped' }}
permissions:
contents: read
actions: read
security-events: write
uses: taraxvoid/voidflow/.github/workflows/upload-sarif.yml@<sha> # vX.Y.ZPrivate repos need GitHub Code Security.
actions/branch-env-map maps main → prod and PRs → preview; any other
ref fails. Run it as the first step of the deploy job:
jobs:
deploy:
name: Deploy
needs: validate
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@<sha> # <version>
- id: env
uses: taraxvoid/voidflow/actions/branch-env-map@<sha> # vX.Y.Z
- uses: taraxvoid/voidflow/actions/cloudflare-deploy@<sha> # vX.Y.Z
with:
env: ${{ steps.env.outputs.env }}
cloudflare-api-token: ${{ secrets.CLOUDFLARE_API_TOKEN }}A job's name: and environment: can't read its own step outputs. For
GitHub deployment environments, use one deploy job for main and one for PRs,
each gated with an if:.
cloudflare-deploy runs wrangler deploy --env <env>, so preview needs a
matching env block in the wrangler config, or pass command to deploy previews
another way. netlify-deploy makes prod a production deploy and anything else
an alias deploy.
| Action | Inputs |
|---|---|
actions/cloudflare-deploy |
env, cloudflare-api-token, cloudflare-account-id, migrate-d1, working-directory, command, token-fallback (details) |
actions/netlify-deploy |
env, publish-dir, netlify-auth-token, netlify-site-id, working-directory, github-token |
For Netlify, turn off the site's own GitHub auto-deploy or it will deploy twice.
bun add -d @taraxvoid/voidflow # or: pnpm add -D @taraxvoid/voidflowAlso published as voidflow. The bins need bun on PATH.
Lighthouse budgets - runs Unlighthouse against
the build and fails on a missed budget. Options: --dir (default dist, use
dist/client for the Cloudflare adapter), --config (default
unlighthouse.config.ts, see example),
--version. Add .unlighthouse/ to .gitignore.
"test:e2e:lighthouse": "bun run build && unlighthouse-runner --dir dist"License check - fails on GPL, AGPL, SSPL and similar. LGPL is allowed.
"check:licenses": "check-licenses"Playwright - for Astro sites testing against astro preview. Needs
@playwright/test. Options: ciPort (required in CI), runner (bun),
timeout (30000), projects (Pixel 7, Desktop Chrome), testDir
(./test/e2e). Skips webServer when PLAYWRIGHT_BASE_URL is set.
// playwright.config.js
import { definePreviewConfig } from '@taraxvoid/voidflow/playwright'
export default definePreviewConfig({ ciPort: 4141 })Biome (v2) - 4-space indent, width 80, single quotes, recommended rules.
Lists merge with the base; scalar values in your config win.
{
"$schema": "https://biomejs.dev/schemas/2.5.14/schema.json",
"extends": ["@taraxvoid/voidflow/biome"],
"files": {
"includes": ["**", "!**/bun.lock", "!public/admin/**"]
}
}Requires the Renovate GitHub App. Extends config:best-practices with a 3-day
release age, grouped chore(deps): updates on Monday mornings, and bumps for
lefthook ref and annotated justfile versions.
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": ["github>taraxvoid/voidflow"]
}Shared lefthook hooks. Each step runs your package.json script if it exists.
pre-commit: dependency guard,format,lint:actions,check,test:unit, pluscheck:licenses/auditwhen the lockfile changedcommit-msg: Conventional Commits hint (non-blocking)pre-push: stops if the remote is ahead, thentest:push
# lefthook.yml
remotes:
- git_url: https://github.com/taraxvoid/voidflow
ref: vX.Y.Z # a tag, not a SHA
configs:
- lefthook/base.yml# mise.toml
[tools]
lefthook = "2.2.1"
gitleaks = "8.30.1"
jactionlint = "2.1.0""prepare": "lefthook install || echo 'lefthook not installed (run mise install); git hooks skipped'"Define test:unit:precommit or check:precommit (e.g. bun test --changed)
to run less on commit. Don't set parallel: true on pre-commit; it breaks
the shared steps' ordering.
Upgrading from lefthook/site.yml (≤ v0.13): switch configs to
lefthook/base.yml.
taraxvoid.net, Queer Omaha, Synth Omaha, Soundry.
Badge:
<a href="https://github.com/taraxvoid/voidflow">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://img.shields.io/badge/CI-voidflow-8b8fd9?labelColor=2b2d3a&style=flat">
<img alt="CI: voidflow" src="https://img.shields.io/badge/CI-voidflow-5a5fb8?labelColor=3a3d4d&style=flat">
</picture>
</a>See CONTRIBUTING.md. MIT licensed.