@taraxvoid/voidflow 0.14.0-next.1 → 0.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/README.md +201 -386
  2. package/package.json +3 -3
package/README.md CHANGED
@@ -1,37 +1,37 @@
1
1
  # voidflow
2
2
 
3
- Shared CI and deploy pipeline for a small fleet of Astro sites on Cloudflare Workers, and the bun tools around them. One set of reusable GitHub Actions workflows and composite actions keeps lint, type checks, license policy, Playwright and axe-core accessibility tests, Unlighthouse budgets, and deploys identical across every site.
3
+ Shared CI, deploy and repo config for Astro sites on Cloudflare Workers or
4
+ Netlify, and for bun tools and libraries. Defaults assume bun, Astro and
5
+ Playwright; pnpm is supported.
6
+
7
+ | Piece | What it does |
8
+ |---|---|
9
+ | [`site-ci.yml`](#site-ci) | Lint, typecheck, build, unit, license, Playwright, axe and Unlighthouse for sites |
10
+ | [`tool-ci.yml`](#tool-ci) | gitleaks, lint, typecheck, build, test, workflow audit for bun tools |
11
+ | [`resolve-env.yml`, deploy actions](#deploys) | Map branch to environment, deploy to Cloudflare or Netlify |
12
+ | [`unlighthouse-runner`, `check-licenses`](#npm-package) | Performance budgets and copyleft license check (npm bins) |
13
+ | [`@taraxvoid/voidflow/playwright`](#playwright-config) | Playwright config for `astro preview` |
14
+ | [`@taraxvoid/voidflow/biome`](#biome) | Shared Biome config |
15
+ | [`github>taraxvoid/voidflow`](#renovate) | Shared Renovate preset |
16
+ | [`lefthook/base.yml`](#git-hooks) | Shared git hooks |
17
+
18
+ ## Pinning
19
+
20
+ Pin to a release commit SHA with the version in a comment, like any
21
+ third-party action. Renovate and Dependabot can bump it.
4
22
 
5
- - **SHA-pinned.** Every third-party action in this repo is pinned to a full commit SHA with a version comment.
6
- - **Audited.** Workflows are checked with [zizmor](https://github.com/zizmorcore/zizmor), [jactionlint](https://github.com/jdx/jactionlint), check-jsonschema and shellcheck on every change (see [Self-validation](#self-validation)).
7
- - **In production.** It runs the pipelines for the sites listed under [Used by](#used-by), including the marketing site for [RavenFlight Industries, LLC](https://rvnflt.com/github).
8
-
9
- It is semi-opinionated: the defaults match how my own sites are built (bun, Astro, Playwright).
10
-
11
- ## Used by
12
-
13
- - [taraxvoid.net](https://taraxvoid.net), my portfolio
14
- - [Queer Omaha](https://queeromaha.net), [Synth Omaha](https://synthomaha.net) and [Soundry](https://soundryomaha.org), free community sites for Omaha's queer and music scenes
15
- - [ravenflight.io](https://ravenflight.io), RavenFlight's marketing site
16
-
17
- ### Show you use it
18
-
19
- Optional. Paste this into your site's README. It follows light and dark mode.
20
-
21
- ```html
22
- <a href="https://github.com/taraxvoid/voidflow">
23
- <picture>
24
- <source media="(prefers-color-scheme: dark)" srcset="https://img.shields.io/badge/CI-voidflow-8b8fd9?labelColor=2b2d3a&style=flat">
25
- <img alt="CI: voidflow" src="https://img.shields.io/badge/CI-voidflow-5a5fb8?labelColor=3a3d4d&style=flat">
26
- </picture>
27
- </a>
23
+ ```yaml
24
+ uses: taraxvoid/voidflow/.github/workflows/site-ci.yml@<sha> # vX.Y.Z
28
25
  ```
29
26
 
30
- ## Usage
27
+ `@v0` follows the newest stable 0.x release if you'd rather float. To try
28
+ unreleased changes, pin the SHA of a `vX.Y.Z-next.N` tag. Don't use `@main` or
29
+ `@next`. Release channels are described in [`RELEASING.md`](RELEASING.md).
31
30
 
32
- Point `uses` in your configuration at a workflow, e.g. in your `.github/workflows/ci.yml`. Pin to a release commit SHA with the version in a comment, the same way you would any third-party action (see [Versioning](#versioning)).
31
+ ## Site CI
33
32
 
34
33
  ```yaml
34
+ # .github/workflows/ci.yml
35
35
  name: CI
36
36
  on:
37
37
  pull_request:
@@ -41,26 +41,50 @@ on:
41
41
 
42
42
  jobs:
43
43
  validate:
44
- uses: taraxvoid/voidflow/.github/workflows/site-ci.yml@15caea203066d42cb7be021eedb6356fac6ffdec # v0.8.1
44
+ uses: taraxvoid/voidflow/.github/workflows/site-ci.yml@<sha> # vX.Y.Z
45
45
  ```
46
46
 
47
- ### Non-GitHub / self-hosted runners
48
-
49
- Pass `runner` (defaults to GitHub runner `ubuntu-latest`)
50
-
51
- ```yaml
52
- jobs:
53
- validate:
54
- uses: taraxvoid/voidflow/.github/workflows/site-ci.yml@15caea203066d42cb7be021eedb6356fac6ffdec # v0.8.1
55
- with:
56
- runner: my-hosted-runner
57
- ```
47
+ It runs whichever of these `package.json` scripts the site defines (missing
48
+ ones are skipped with a notice):
49
+
50
+ | Script | When |
51
+ |---|---|
52
+ | `lint`, `check`, `build`, `test:unit` | always |
53
+ | `check:licenses` | lockfile or `scripts/` changed |
54
+ | `test:e2e:full` | PRs into `main` |
55
+ | `test:e2e:all`, `test:e2e:a11y` | PRs into `next` and `live` |
56
+ | `test:e2e:lighthouse` | PRs into `live` |
57
+
58
+ Inputs:
59
+
60
+ | Input | Default | |
61
+ |---|---|---|
62
+ | `runner` | `ubuntu-latest` | Runner label |
63
+ | `package-manager` | `bun` | `bun`, `pnpm` or `aube` (see below) |
64
+ | `single-environment` | `false` | See below |
65
+ | `build` | `true` | Run `build` as its own step. Pass `false` when the e2e scripts already build |
66
+ | `build-env` | `{}` | JSON object of build env vars. Visible in logs, so no secrets |
67
+ | `code-paths` | everything except Markdown, `docs/`, `LICENSE*`, issue templates | JSON array of globs (`!` excludes) that count as code |
68
+ | `e2e-scripts` | `[]` | JSON array of extra e2e scripts (see below) |
69
+ | `e2e-extra-paths` | `["wrangler.jsonc", "site.vars.json"]` | JSON array of globs that also trigger e2e |
70
+ | `socket-scanner` | `false` | Run the bun install security scanner in CI (see below) |
71
+ | `playwright-install-with-deps` | `true` | Install Playwright system deps |
72
+
73
+ Pull requests outside `code-paths` skip install, lint and tests but still pass
74
+ `validate / Validate`. Pushes and manual runs always run everything.
75
+
76
+ ### pnpm and aube
77
+
78
+ pnpm takes its version from `packageManager` and Node from `volta` or
79
+ `engines`. aube needs the site's `mise.toml` to pin it (`aube = "<version>"`),
80
+ then follows `packageManager` and takes Node from `devEngines.runtime`. Both
81
+ use `pnpm-lock.yaml`.
58
82
 
59
83
  ### Single-environment sites
60
84
 
61
- Sites with one production environment and no `next`/`live` branches (`main`
62
- deploys to prod, pull requests get previews) pass `single-environment: true`,
63
- the same flag `resolve-env` and `branch-env-map` take:
85
+ For sites where `main` deploys to prod and PRs get previews (no `next`/`live`),
86
+ pass `single-environment: true` and add a `push` trigger so direct pushes to
87
+ `main` are validated:
64
88
 
65
89
  ```yaml
66
90
  on:
@@ -73,90 +97,53 @@ on:
73
97
 
74
98
  jobs:
75
99
  validate:
76
- uses: taraxvoid/voidflow/.github/workflows/site-ci.yml@<sha> # <version>
100
+ uses: taraxvoid/voidflow/.github/workflows/site-ci.yml@<sha> # vX.Y.Z
77
101
  with:
78
102
  single-environment: true
79
103
  ```
80
104
 
81
- `main` then gets the production test tier (`test:e2e:all`, `test:e2e:a11y`,
82
- `test:e2e:lighthouse`) instead of only `test:e2e:full`. Include the `push`
83
- trigger so a direct push to `main` is validated too; the e2e steps resolve the
84
- branch from the pushed ref.
105
+ `main` then runs the production tier (`test:e2e:all`, `test:e2e:a11y`,
106
+ `test:e2e:lighthouse`).
107
+
108
+ ### Extra e2e scripts
85
109
 
86
- ### pnpm sites, build env, extra e2e paths
110
+ Scripts listed in `e2e-scripts` run in order after the built-in tiers whenever
111
+ e2e-relevant files changed. Unlike the built-in tiers, a missing one fails the
112
+ job.
87
113
 
88
114
  ```yaml
89
- jobs:
90
- validate:
91
- uses: taraxvoid/voidflow/.github/workflows/site-ci.yml@<sha> # <version>
92
115
  with:
93
- package-manager: pnpm # default: bun
94
- e2e-extra-paths: '["wrangler.jsonc", "site.vars.json"]' # JSON array of globs
95
- build-env: ${{ format('{{"PUBLIC_SITE_URL":"https://example.com","GIT_SHA":"{0}"}}', github.sha) }}
116
+ e2e-scripts: '["test:e2e", "test:e2e:gtm", "test:e2e:editor"]'
117
+ build: false
96
118
  ```
97
119
 
98
- With `pnpm`, the pnpm version comes from `packageManager` and Node from
99
- `engines` in `package.json`. The lockfile (`bun.lock` or `pnpm-lock.yaml`)
100
- follows the package manager. `build-env` is a JSON object, visible in logs, so
101
- no secrets.
120
+ ### Socket install scanner
102
121
 
103
- ### Install-time security scanner (Socket)
122
+ If `bunfig.toml` sets `install.security.scanner`, CI installs with that line
123
+ removed (everything else still applies) and leaves a notice, so a flaky
124
+ third-party API can't fail the build. Local installs still run it. Pass
125
+ `socket-scanner: true` to run it in CI too.
104
126
 
105
- A bun site can set a security scanner in `bunfig.toml`, which checks every
106
- package on install:
127
+ Write it as a plain `scanner = "..."` line or dotted `security.scanner`. An
128
+ inline table can't be removed cleanly, so the install fails instead.
107
129
 
108
- ```toml
109
- [install.security]
110
- scanner = "@socketsecurity/bun-security-scanner"
111
- ```
112
-
113
- CI skips it by default. The scanner calls a third-party API on every install,
114
- so a network blip or an API change could fail a build that otherwise passes,
115
- and rerunning the same commit could give a different result. `site-ci.yml`
116
- installs with a copy of `bunfig.toml` minus the scanner line (every other
117
- setting, e.g. `minimumReleaseAge`, still applies) and leaves a notice in the
118
- run. Local `bun install` reads `bunfig.toml` as is, so the scanner still runs
119
- on your machine.
120
-
121
- To run it in CI too, pass `socket-scanner: true` to `site-ci.yml`. A workflow
122
- that installs on its own (outside `site-ci.yml`) gets the same default from
123
- the composite action:
130
+ Workflows that install outside `site-ci.yml` get the same behavior from the
131
+ composite action:
124
132
 
125
133
  ```yaml
126
134
  - uses: oven-sh/setup-bun@<sha> # <version>
127
- - uses: taraxvoid/voidflow/actions/bun-install@<sha> # <version>
135
+ - uses: taraxvoid/voidflow/actions/bun-install@<sha> # vX.Y.Z
128
136
  # with: { socket-scanner: 'true' }
129
137
  ```
130
138
 
131
- The copy is checked by parsing both files: it must equal your `bunfig.toml`
132
- with only `install.security.scanner` removed. Set the scanner as a plain
133
- `scanner = "..."` line (or the dotted `security.scanner` form); if it can't be
134
- removed cleanly, e.g. an inline table, the install fails rather than run or
135
- skip the scanner silently. pnpm sites are unaffected.
136
-
137
- ## Workflow Types
138
-
139
- ### Validation / Checks
140
-
141
- Runs lint, typecheck, check for GPL licenses, e2e with Playwright.
142
-
143
- `site-ci.yml` runs these `package.json` scripts: `lint`, `check`, `build`,
144
- `test:unit`, `check:licenses`, `test:e2e:full` (PRs into `main`),
145
- `test:e2e:all` (PRs into `next` and `live`), `test:e2e:a11y` (`next` and
146
- `live`), and `test:e2e:lighthouse` (`live`). A script the site does not define
147
- is skipped with a notice, so none of them is required.
148
-
149
- ### Tool validation
139
+ ## Tool CI
150
140
 
151
- `tool-ci.yml` is the validation job for bun tools and libraries (CLIs, TUIs,
152
- packages): gitleaks over the full history, install (with the same Socket
153
- scanner handling as `site-ci.yml`), tools from the repo's `mise.toml` when it
154
- has one, then whichever of `lint`, `check`, `build` and `test` `package.json`
155
- defines, and `check:licenses` when dependencies or `scripts/` changed. When
156
- workflows change it also runs jactionlint (same profile handling as
157
- `site-ci.yml`) and
158
- [zizmor](https://github.com/zizmorcore/zizmor). The bun version comes from
159
- `.bun-version` when present.
141
+ For bun CLIs, TUIs and libraries. Runs gitleaks over full history, installs
142
+ (same scanner handling), installs tools from `mise.toml` if present, then
143
+ whichever of `lint`, `check`, `build` and `test` exist, plus `check:licenses`
144
+ when dependencies or `scripts/` change. When workflows change it also runs
145
+ jactionlint and [zizmor](https://github.com/zizmorcore/zizmor). Bun version
146
+ comes from `.bun-version` if present. Inputs: `runner`, `socket-scanner`.
160
147
 
161
148
  ```yaml
162
149
  name: CI
@@ -173,105 +160,88 @@ permissions:
173
160
 
174
161
  jobs:
175
162
  validate:
176
- uses: taraxvoid/voidflow/.github/workflows/tool-ci.yml@<release SHA> # vX.Y.Z
163
+ uses: taraxvoid/voidflow/.github/workflows/tool-ci.yml@<sha> # vX.Y.Z
177
164
  ```
178
165
 
179
- The job reports as `validate / Validate`, the check the `tool()` entry in
180
- [`rulesets/index.ts`](#branch-rulesets-as-code) requires.
166
+ The required status check is `validate / Validate`.
181
167
 
182
- ### Code scanning (SARIF)
168
+ ## Deploys
183
169
 
184
- `site-ci.yml` and `tool-ci.yml` write gitleaks and zizmor results as SARIF
185
- and keep them as the `voidflow-sarif` artifact (7 days). gitleaks still fails
186
- the job on a finding; the zizmor SARIF run scans every workflow and never
187
- fails the job (the zizmor gate in `tool-ci.yml` is unchanged). Both steps run
188
- after a failure too, so the findings that failed a run are the ones uploaded.
189
-
190
- `upload-sarif.yml` sends them to code scanning, which is what voidlight
191
- counts. It is a separate workflow because it needs `security-events: write`,
192
- and a reusable workflow can't ask for more than its caller grants: adding it to
193
- the validate workflows would break every caller that doesn't. Add a second job:
170
+ `resolve-env.yml` maps the branch to an environment: `main` to `dev`, `next`
171
+ to `staging`, `live` to `prod`. With `single-environment: true`, `main` maps to
172
+ `prod`, pull requests to `preview`, and any other ref fails. It's a separate
173
+ job so the deploy job's `name:` can show the environment.
194
174
 
195
175
  ```yaml
196
176
  jobs:
197
- validate:
198
- uses: taraxvoid/voidflow/.github/workflows/site-ci.yml@<sha> # <version>
199
- sarif:
177
+ resolve-env:
200
178
  needs: validate
201
- if: ${{ !cancelled() && needs.validate.result != 'skipped' }}
202
- permissions:
203
- contents: read
204
- actions: read
205
- security-events: write
206
- uses: taraxvoid/voidflow/.github/workflows/upload-sarif.yml@<sha> # <version>
179
+ if: github.event_name == 'push'
180
+ uses: taraxvoid/voidflow/.github/workflows/resolve-env.yml@<sha> # vX.Y.Z
181
+
182
+ deploy:
183
+ name: Deploy [${{ needs.resolve-env.outputs.env }}]
184
+ needs: resolve-env
185
+ if: github.event_name == 'push'
186
+ runs-on: ubuntu-latest
187
+ environment: ${{ needs.resolve-env.outputs.env }}
188
+ steps:
189
+ - uses: actions/checkout@<sha> # <version>
190
+ - uses: taraxvoid/voidflow/actions/cloudflare-deploy@<sha> # vX.Y.Z
191
+ with:
192
+ env: ${{ needs.resolve-env.outputs.env }}
193
+ cloudflare-api-token: ${{ secrets.CLOUDFLARE_API_TOKEN }}
207
194
  ```
208
195
 
209
- Each tool uploads under its own category (`gitleaks`, `zizmor`), so a later
210
- run closes only that tool's fixed alerts. gitleaks SARIF is redacted.
211
- Code scanning is free on public repositories; private ones need GitHub Code
212
- Security, without which the upload step fails.
196
+ - `actions/cloudflare-deploy`: `wrangler deploy` for the environment, with an
197
+ optional D1 migration first (`migrate-d1`). Other inputs:
198
+ `cloudflare-account-id`, `working-directory`, `command`.
199
+ - `actions/netlify-deploy`: `prod` is a production deploy, `dev`/`staging` are
200
+ alias deploys. Inputs: `env`, `publish-dir`, `netlify-auth-token`,
201
+ `netlify-site-id`, `working-directory`, `github-token`. Turn off Netlify's
202
+ own GitHub auto-deploy for the site, or the two will double-deploy.
203
+ - `actions/branch-env-map`: the mapping step on its own, for use inside a job.
204
+
205
+ ## npm package
206
+
207
+ ```sh
208
+ bun add -d @taraxvoid/voidflow # or: pnpm add -D @taraxvoid/voidflow
209
+ ```
213
210
 
214
- ### Unlighthouse (performance / SEO budgets)
211
+ Also published unscoped as `voidflow` with identical contents.
215
212
 
216
- `site-ci.yml` runs `bun run test:e2e:lighthouse` on PRs into `live`. Sites
217
- implement that script with the shared runner here, which serves the built
218
- static directory on a free port and runs
219
- [Unlighthouse](https://unlighthouse.dev/) against it, reusing Playwright's
220
- Chromium. It exits non-zero if a category budget in the site's config fails.
213
+ Call the bins from `package.json` scripts rather than `bunx --package ...`, so
214
+ the version stays in the lockfile and the site's `bunfig.toml` applies. Both
215
+ bins need `bun` on `PATH`, even in npm or pnpm projects.
221
216
 
222
- Add `@taraxvoid/voidflow` as a devDependency and call the bin from the site's
223
- `package.json`:
217
+ ### Unlighthouse budgets
224
218
 
225
219
  ```json
226
220
  "test:e2e:lighthouse": "bun run build && unlighthouse-runner --dir dist"
227
221
  ```
228
222
 
229
- Options: `--dir` (default `dist`, use `dist/client` for Cloudflare adapter
230
- builds), `--config` (default `unlighthouse.config.ts`), `--version` (pinned
231
- Unlighthouse version). Start from
232
- [`scripts/unlighthouse/unlighthouse.config.example.ts`](scripts/unlighthouse/unlighthouse.config.example.ts)
233
- and add `.unlighthouse/` to the site's `.gitignore`.
223
+ Serves the built directory and runs [Unlighthouse](https://unlighthouse.dev/)
224
+ with Playwright's Chromium. Fails if a category budget fails.
234
225
 
235
- ### License policy
226
+ - `--dir`: default `dist`; use `dist/client` for the Cloudflare adapter
227
+ - `--config`: default `unlighthouse.config.ts`; start from
228
+ [the example](scripts/unlighthouse/unlighthouse.config.example.ts)
229
+ - `--version`: Unlighthouse version
236
230
 
237
- `site-ci.yml` runs `bun run check:licenses` when the lockfile or `scripts/`
238
- change. Sites implement that script with the shared check, which runs
239
- `license-checker-rseidelsohn` in the site's repo root and fails on GPL, AGPL,
240
- SSPL and similar copyleft licenses (LGPL is allowed). Voidflow pins the
241
- checker to an exact version as its own dependency, so sites don't need to
242
- install it and every site runs the same version (sites that added
243
- `license-checker-rseidelsohn` as a devDependency for 0.12.2 can drop it).
231
+ Add `.unlighthouse/` to `.gitignore`.
244
232
 
245
- With `@taraxvoid/voidflow` as a devDependency, call the bin from the site's
246
- `package.json`:
233
+ ### License check
247
234
 
248
235
  ```json
249
236
  "check:licenses": "check-licenses"
250
237
  ```
251
238
 
252
- Don't use `bunx --package @taraxvoid/voidflow@<version> ...` for these. It
253
- duplicates the version pin outside the lockfile and Renovate, and `bunx` reads
254
- the global `~/.bunfig.toml` rather than the site's, so a global
255
- `minimumReleaseAge` that doesn't exclude `@taraxvoid/voidflow` blocks it
256
- locally (including in the pre-commit hook) for 3 days after every release.
257
- The devDependency's bins are installed under the site's own `bunfig.toml`.
239
+ Fails on GPL, AGPL, SSPL and similar copyleft licenses. LGPL is allowed.
258
240
 
259
- Both bins (`check-licenses`, `unlighthouse-runner`) are TypeScript run by Bun
260
- (`#!/usr/bin/env bun`), so `bun` must be on `PATH` even when the site uses
261
- npm or pnpm. The `@taraxvoid/voidflow/playwright` export is plain ESM and runs
262
- on Node 22+ without Bun.
241
+ ### Playwright config
263
242
 
264
- ### Playwright preview config
265
-
266
- Shared `playwright.config` for Astro sites that run e2e against
267
- `astro preview`. It picks a free port outside CI, sets
268
- `ASTRO_PREVIEW_BACKGROUND=false` (Astro backgrounds `preview` in agent
269
- environments, which makes Playwright think the server exited), and drops the
270
- `webServer` when `PLAYWRIGHT_BASE_URL` is set.
271
-
272
- ```sh
273
- bun add -d @taraxvoid/voidflow # or: pnpm add -D @taraxvoid/voidflow
274
- ```
243
+ For Astro sites that run e2e against `astro preview`. Requires
244
+ `@playwright/test`. Runs on Node 22+ without Bun.
275
245
 
276
246
  ```js
277
247
  // playwright.config.js
@@ -280,23 +250,14 @@ import { definePreviewConfig } from '@taraxvoid/voidflow/playwright'
280
250
  export default definePreviewConfig({ ciPort: 4141 })
281
251
  ```
282
252
 
283
- The same release is also published unscoped as `voidflow` (identical code and
284
- version, with a short pointer README), so `bun add -d voidflow` and
285
- `import ... from 'voidflow/playwright'` work too. `@taraxvoid/voidflow` is the
286
- canonical name.
287
-
288
- Options: `ciPort` (required in CI), `runner` (default `bun`, e.g. `pnpm`),
289
- `timeout` (default 30000), `projects` (default Pixel 7 and Desktop Chrome),
290
- `testDir` (default `./test/e2e`). Requires `@playwright/test` in the site.
253
+ Picks a free port locally and skips `webServer` when `PLAYWRIGHT_BASE_URL` is
254
+ set. Options: `ciPort` (required in CI), `runner` (default `bun`), `timeout`
255
+ (default 30000), `projects` (default Pixel 7 and Desktop Chrome), `testDir`
256
+ (default `./test/e2e`).
291
257
 
292
- ## Shared Biome config
258
+ ### Biome
293
259
 
294
- [`biome/base.json`](biome/base.json) is the house style for the fleet: 4-space
295
- indent, LF, width 80, single quotes, semicolons only where needed, trailing
296
- commas, the `recommended` rule preset, organized imports, CSS lint off, and
297
- unused-import/variable rules off in `.astro` files (Biome can't see template
298
- usage). With `@taraxvoid/voidflow` as a devDependency, the site's `biome.json`
299
- extends it and adds only what is specific to the site:
260
+ Needs Biome 2. In the site's `biome.json`:
300
261
 
301
262
  ```json
302
263
  {
@@ -308,16 +269,12 @@ extends it and adds only what is specific to the site:
308
269
  }
309
270
  ```
310
271
 
311
- Lists such as `files.includes` and `overrides` combine with the base's rather
312
- than replace them; single values set in the site win. The base has no ignore
313
- lists, so each site keeps its own. It needs Biome 2: the pre-commit hook and
314
- `site-ci.yml` print a warning (they don't fail) when a site has Biome 1.x
315
- installed.
316
-
317
- ## Dependency updates (Renovate)
272
+ The base sets formatting (4-space indent, width 80, single quotes, semicolons
273
+ as needed), the `recommended` rules, and turns off unused-import/variable rules
274
+ in `.astro` files. It has no ignore lists. Lists like `files.includes` and
275
+ `overrides` merge with the base; single values in the site win.
318
276
 
319
- [`default.json`](default.json) is the shared Renovate preset. Sites add a
320
- `renovate.json`:
277
+ ## Renovate
321
278
 
322
279
  ```json
323
280
  {
@@ -326,220 +283,78 @@ installed.
326
283
  }
327
284
  ```
328
285
 
329
- It builds on `config:best-practices` (which pins GitHub Action digests), holds
330
- updates for 3 days like `minimumReleaseAge` in `bunfig.toml` (except our own
331
- package and security alerts), uses `chore(deps):` Conventional Commits, groups
332
- actions, astro, biome and playwright, and runs on Monday mornings. Custom
333
- managers keep three things current that Dependabot cannot see: the
334
- `@taraxvoid/voidflow@x.y.z` pin inside `package.json` scripts, a lefthook
335
- remote's `ref` tag, and tool versions in a `justfile` (annotate the line above
336
- with `# renovate: datasource=... depName=...`). Requires the Renovate GitHub App
337
- on the repo.
338
-
339
- ## Git hooks (lefthook)
340
-
341
- [`lefthook/base.yml`](lefthook/base.yml) is the shared hook set for any bun
342
- (or pnpm) repo, sites and tools alike, consumed as a lefthook remote. It calls
343
- the repo's own `package.json` scripts (`<pm> run --if-present`, so a missing
344
- script is skipped; `<pm>` is pnpm when the repo has a `pnpm-lock.yaml`, bun
345
- otherwise):
346
-
347
- - `pre-commit`: dependency guard, Biome 1.x warning, `format` (formatted files are re-staged),
348
- `lint:actions`, `check`, `test:unit`, and `check:licenses` / `audit` when the
349
- lockfile changed.
350
- - `commit-msg`: advisory Conventional Commits hint (never blocks).
351
- - `pre-push`: bail if the remote branch is ahead, then `test:push`; skipped when
352
- the push carries no commit the remote lacks (branch deletes, and tags or
353
- branches at already-pushed commits).
354
-
355
- Before `test:unit` and `test:push`, the hooks unset the variables git exports to
356
- hooks (`GIT_DIR`, `GIT_INDEX_FILE`, ...; the list comes from
357
- `git rev-parse --local-env-vars`). Without that, a test that runs
358
- `git -C <tmpdir> commit` operates on, and commits into, the repo being hooked.
286
+ Requires the Renovate GitHub App. Based on `config:best-practices`: 3-day
287
+ release age (except voidflow and security fixes), `chore(deps):` commits,
288
+ grouped updates, Monday mornings. It also bumps the lefthook remote `ref`, and
289
+ `justfile` tool versions annotated with
290
+ `# renovate: datasource=... depName=...` on the line above.
291
+
292
+ ## Git hooks
293
+
294
+ Shared lefthook hooks for bun or pnpm repos. Each step runs the repo's own
295
+ `package.json` script and is skipped if the script is missing.
296
+
297
+ - `pre-commit`: dependency guard, `format` (re-staged), `lint:actions`,
298
+ `check`, `test:unit`, and `check:licenses` / `audit` when the lockfile changed
299
+ - `commit-msg`: Conventional Commits hint (never blocks)
300
+ - `pre-push`: stop if the remote is ahead, then `test:push`
359
301
 
360
302
  ```yaml
361
- # lefthook.yml in the repo
303
+ # lefthook.yml
362
304
  remotes:
363
305
  - git_url: https://github.com/taraxvoid/voidflow
364
- ref: v0.10.0 # a tag or branch; lefthook passes it to `git clone --branch`
306
+ ref: vX.Y.Z # a tag, not a SHA
365
307
  configs:
366
308
  - lefthook/base.yml
367
-
368
309
  ```
369
310
 
370
- Upgrading from `lefthook/site.yml` (v0.13 and earlier): change the `configs`
371
- entry to `lefthook/base.yml` when you bump `ref`. The hooks are otherwise the
372
- same. `lefthook/site.yml` is now a stub whose `pre-commit` and `pre-push` fail
373
- with that instruction, so a `ref` bump (for example from Renovate) that leaves
374
- `configs` alone stops commits instead of turning the hooks off silently.
375
-
376
- Lefthook merges `lefthook.yml`, then remotes, then `lefthook-local.yml`, so a
377
- repo's own `lefthook.yml` can add commands (for example image compression) but
378
- cannot override ones defined here. Differences go through `package.json`
379
- scripts: define `test:unit:precommit` (for example excluding tests that need a
380
- built `dist/`) and the pre-commit unit step runs it instead of `test:unit`.
381
- Likewise, define `check:precommit` and the typecheck step runs it instead of
382
- `check`. CI keeps calling the full scripts, so these only narrow what runs
383
- locally. Common uses:
384
-
385
- - bun: `"test:unit:precommit": "bun test --changed"` runs only the test files
386
- whose imports reach an uncommitted change.
387
- - pnpm workspace: `pnpm -r --filter '...[HEAD]' run <script>` runs only the
388
- changed packages and their dependents. Fall back to the full run when a
389
- root-level file (lockfile, shared tsconfig) changed, since those don't
390
- belong to any package. If the root `package.json` defines the same script
391
- as a recursive run, add `--filter '!{.}'` so it doesn't recurse.
392
-
393
- Hook-level options merge too. Don't set `parallel: true` under `pre-commit:` in
394
- the repo's `lefthook.yml`: it applies to the merged hook, so every step,
395
- including the shared ones, starts at once and their `priority` order is ignored
396
- (tests that share a database or build output can then time out).
397
-
398
- Install the binaries through mise. A starter `mise.toml` for a repo using
399
- these hooks and voidflow CI:
311
+ Install the binaries with mise:
400
312
 
401
313
  ```toml
314
+ # mise.toml
402
315
  [tools]
403
316
  lefthook = "2.2.1"
404
317
  gitleaks = "8.30.1"
405
318
  jactionlint = "2.1.0"
406
319
  ```
407
320
 
408
- Renovate bumps `mise.toml` natively. Set the `prepare` script so `bun install`
409
- wires the hooks, but make it tolerate a missing binary:
321
+ and wire the hooks on install without failing where lefthook is absent (CI):
410
322
 
411
323
  ```json
412
324
  "prepare": "lefthook install || echo 'lefthook not installed (run mise install); git hooks skipped'"
413
325
  ```
414
326
 
415
- A bare `lefthook install` fails the whole install step (exit 127) on any runner
416
- without the `lefthook` binary, which includes GitHub-hosted and self-hosted CI
417
- images, and CI never runs git hooks anyway. `ref` cannot be a commit SHA; pin a
418
- release tag.
327
+ To run less locally, define `test:unit:precommit` or `check:precommit`;
328
+ pre-commit runs those instead. CI still runs the full scripts. For example:
419
329
 
420
- ## Branch rulesets as code
330
+ - bun: `"test:unit:precommit": "bun test --changed"`
331
+ - pnpm workspace: `pnpm -r --filter '...[HEAD]' run <script>` (add
332
+ `--filter '!{.}'` if the root defines the same script recursively)
421
333
 
422
- Branch protection for the fleet lives in [`rulesets/index.ts`](rulesets/index.ts):
423
- one canonical `main` / `next` / `live` definition (plus `prerelease` and `maintenance`
424
- tiers for repos with release channels) and a small per-repo table
425
- (Netlify or not, check names, CODEOWNERS, extra bypass actors). Sites use
426
- `site()` and tools use `tool()` (main only, no Netlify, requires
427
- `validate / Validate` from `tool-ci.yml`). Private repos are left out: on a
428
- plan without rulesets for private repos, the API returns 403. Hand-edited
429
- rulesets drift, and drift is how a `next` ruleset ends up requiring a check
430
- name that no workflow reports any more.
431
-
432
- ```sh
433
- just rulesets-plan # read-only: diff every repo against the config
434
- just rulesets-plan --repo soundry
435
- just rulesets-plan --check # exit 1 on drift, for CI
436
- just rulesets-apply # create/update rulesets to match
437
- just rulesets-apply --prune # also delete rulesets a repo lists under `retire`
438
- ```
334
+ Your `lefthook.yml` can add commands but not override shared ones. Don't set
335
+ `parallel: true` on `pre-commit`; it applies to the shared steps too and breaks
336
+ their ordering.
439
337
 
440
- Single-environment sites (a stable `main` that deploys to prod) manage only the
441
- `main` tier; their old `next`/`live` rulesets are listed under `retire`. voidflow itself
442
- manages `main`, `prerelease` (`next`) and `maintenance` (`release/*`) and sets
443
- `linearHistory: false` so `next` can be promoted into `main` with a merge commit.
444
- Uses the `gh` CLI for auth (needs admin on the repos). Rulesets that are not in
445
- the config are reported and left alone; only `retire` entries are ever deleted,
446
- and only with `--prune`. Private repos on a plan without rulesets (GitHub answers
447
- 403, e.g. a free personal account) are skipped and listed at the end rather than
448
- failing the run, so `--check` stays green and they pick up their rulesets once the
449
- repo goes public or the plan changes. It is repo tooling, not part of the published
450
- package.
451
-
452
- ## Deployments
453
-
454
- Example deployment job added to your workflow above. `resolve-env` maps the
455
- branch to dev/staging/prod once, up front, so its output can be used both to
456
- drive the deploy and to label the job (`Deploy [dev]`, `Deploy [staging]`,
457
- `Deploy [prod]`). A job's `name:` can reference `needs.<job>.outputs.*` but
458
- not a step output from within itself, hence the split:
338
+ Upgrading from `lefthook/site.yml` (v0.13 and earlier): change `configs` to
339
+ `lefthook/base.yml`. The old file now fails commits with that instruction.
459
340
 
460
- ```yaml
461
- jobs:
462
- resolve-env:
463
- needs: validate
464
- if: github.event_name == 'push'
465
- uses: taraxvoid/voidflow/.github/workflows/resolve-env.yml@15caea203066d42cb7be021eedb6356fac6ffdec # v0.8.1
466
-
467
- deploy:
468
- name: Deploy [${{ needs.resolve-env.outputs.env }}]
469
- needs: resolve-env
470
- if: github.event_name == 'push'
471
- environment: ${{ needs.resolve-env.outputs.env }}
472
- steps:
473
- - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
474
- - uses: taraxvoid/voidflow/actions/cloudflare-deploy@15caea203066d42cb7be021eedb6356fac6ffdec # v0.8.1
475
- with:
476
- env: ${{ needs.resolve-env.outputs.env }}
477
- cloudflare-api-token: ${{ secrets.CLOUDFLARE_API_TOKEN }}
478
- ```
479
-
480
- Sites with a single production environment and per-PR previews (no `next` or
481
- `live` branch) pass `with: { single-environment: true }` to `resolve-env.yml`
482
- (or the same input on `actions/branch-env-map`). `main` then maps to `prod` and
483
- `pull_request` events map to `preview`; any other ref fails.
484
-
485
- ### Versioning
486
-
487
- Semver, released with [release-please](https://github.com/googleapis/release-please)
488
- ([`release-please.yml`](.github/workflows/release-please.yml)) on three branch-based channels
489
- (full flow in [`RELEASING.md`](RELEASING.md)):
490
-
491
- | Channel | Branch | Example | npm dist-tag |
492
- |---|---|---|---|
493
- | stable | `main` | `0.10.0` | `latest` |
494
- | prerelease | `next` | `0.10.0-next`, `0.10.0-next.1` | `next` |
495
- | maintenance | `release/N.x` | `0.9.4` | `release-N.x` |
496
-
497
- release-please keeps a standing release PR open on each channel's branch, updated as commits land.
498
- Merging it bumps `package.json` (and [`CHANGELOG.md`](CHANGELOG.md) on stable), tags `vX.Y.Z` (`vX.Y.Z-next[.N]` on `next`),
499
- creates the GitHub release and publishes to npm and JSR under the channel's dist-tag. The PR is
500
- opened with the org's release-bot GitHub App token (`RELEASE_BOT_CLIENT_ID` /
501
- `RELEASE_BOT_APP_PRIVATE_KEY`) so CI runs on it.
502
-
503
- Feature PRs target `next`; promotion to `main` is a merge-commit PR (`just promote`).
504
-
505
- Commit messages should follow [Conventional Commits](https://www.conventionalcommits.org),
506
- enforced loosely, as an advisory `commit-msg` hint (see
507
- [`scripts/check-commit-msg.sh`](scripts/check-commit-msg.sh)), not a blocking check. They
508
- drive the version bump and generated changelog.
509
-
510
- Ways to consume a release:
511
-
512
- - **Floating major tag**: `@v0` follows the newest stable 0.x release. Easiest to track, and
513
- mutable like any floating tag.
514
- - **SHA pin with a version comment**, e.g. `@15caea2... # v0.8.1`: what I use. Same hardening
515
- as any third-party action, and Renovate or Dependabot can bump it.
516
- - **Try unreleased changes**: pin the commit SHA of a `vX.Y.Z-next` / `vX.Y.Z-next.N` prerelease tag.
517
-
518
- Treat `@main` and `@next` as unstable.
519
-
520
- ## Self-validation
521
-
522
- This repo validates its own workflows and composite actions (`.github/workflows/ci.yml`):
341
+ ## Used by
523
342
 
524
- - **jactionlint**: workflow schema/logic; also shellchecks `run:` steps in `.github/workflows/*.yml` automatically (`shellcheck` ships on `ubuntu-latest`)
525
- - **check-jsonschema**: schema-checks `actions/**/action.yml` and the workflow files against [SchemaStore](https://www.schemastore.org/)'s `github-action.json` / `github-workflow.json`
526
- - **shellcheck** (`scripts/shellcheck-actions.ts`): checks `run:` steps inside `actions/**/action.yml`, which jactionlint doesn't reach
527
- - **zizmor**: security audit (unpinned refs, script injection via `${{ }}` in `run:`, excess permissions, etc.)
343
+ [taraxvoid.net](https://taraxvoid.net), [Queer Omaha](https://queeromaha.net),
344
+ [Synth Omaha](https://synthomaha.net), [Soundry](https://soundryomaha.org),
345
+ [ravenflight.io](https://ravenflight.io).
528
346
 
529
- ### Local dev setup
347
+ Optional badge for your README (follows light/dark mode):
530
348
 
531
- ```sh
532
- mise install # tools pinned in mise.toml
533
- bun install # its prepare script runs `lefthook install`
349
+ ```html
350
+ <a href="https://github.com/taraxvoid/voidflow">
351
+ <picture>
352
+ <source media="(prefers-color-scheme: dark)" srcset="https://img.shields.io/badge/CI-voidflow-8b8fd9?labelColor=2b2d3a&style=flat">
353
+ <img alt="CI: voidflow" src="https://img.shields.io/badge/CI-voidflow-5a5fb8?labelColor=3a3d4d&style=flat">
354
+ </picture>
355
+ </a>
534
356
  ```
535
357
 
536
- - `just validate`: run everything CI runs, locally
537
- - `just lint-workflows` / `lint-actions` / `lint-shell` / `security`: run one check at a time
538
- - `just dry-run`: full local run of `ci.yml` via [`act`](https://github.com/nektos/act) (needs Docker running); pass extra args, e.g. `just dry-run -j validation`
539
- - lefthook runs the fast checks (`lint-workflows`, `lint-actions`, `lint-shell`) on `pre-commit`, and `zizmor` on `pre-push`
540
-
541
- **Colima users:** `.actrc` already disables the docker-socket mount (`--container-daemon-socket -`). `act` binds `/var/run/docker.sock` by default, which doesn't exist under Colima and fails with `operation not supported`. None of these workflow steps need docker-in-docker, so this is safe.
542
-
543
- **Run `just dry-run` from a normal checkout, not a linked git worktree.** `act` copies the working tree via `docker cp` rather than a real clone, so a worktree's `.git` pointer back to the parent repo doesn't resolve inside the container, so `git ls-files`-based steps (schema validation, `scripts/shellcheck-actions.ts`) silently see zero files instead of erroring. Verified clean end-to-end (`🏁 Job succeeded`) from a plain clone.
358
+ ## Contributing
544
359
 
545
- Provided as is under the [MIT license](LICENSE). Issues and pull requests are welcome.
360
+ See [`CONTRIBUTING.md`](CONTRIBUTING.md). MIT licensed.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@taraxvoid/voidflow",
3
- "version": "0.14.0-next.1",
3
+ "version": "0.15.0",
4
4
  "description": "Enter a flow state of security, stability, and developer ergonomics - batteries-included Biome, vitest, Astro, local hooks, CI and deployments on Netlify and Cloudflare support with GitHub rulesets, branch environments, releases and security checks.",
5
5
  "license": "MIT",
6
6
  "keywords": [
@@ -61,8 +61,8 @@
61
61
  }
62
62
  },
63
63
  "devDependencies": {
64
- "@biomejs/biome": "2.5.14",
65
- "@playwright/test": "1.64.0",
64
+ "@biomejs/biome": "2.5.15",
65
+ "@playwright/test": "1.64.1",
66
66
  "@socketsecurity/bun-security-scanner": "1.1.3"
67
67
  },
68
68
  "dependencies": {