@taraxvoid/voidflow 0.14.0 → 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 +209 -362
  2. package/package.json +1 -1
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)
104
-
105
- A bun site can set a security scanner in `bunfig.toml`, which checks every
106
- package on install:
107
-
108
- ```toml
109
- [install.security]
110
- scanner = "@socketsecurity/bun-security-scanner"
111
- ```
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.
112
126
 
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.
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.
120
129
 
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.
139
+ ## Tool CI
142
140
 
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
150
-
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,74 +160,89 @@ 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
- ### Unlighthouse (performance / SEO budgets)
168
+ ## Deploys
183
169
 
184
- `site-ci.yml` runs `bun run test:e2e:lighthouse` on PRs into `live`. Sites
185
- implement that script with the shared runner here, which serves the built
186
- static directory on a free port and runs
187
- [Unlighthouse](https://unlighthouse.dev/) against it, reusing Playwright's
188
- Chromium. It exits non-zero if a category budget in the site's config fails.
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.
189
174
 
190
- Add `@taraxvoid/voidflow` as a devDependency and call the bin from the site's
191
- `package.json`:
175
+ ```yaml
176
+ jobs:
177
+ resolve-env:
178
+ needs: validate
179
+ if: github.event_name == 'push'
180
+ uses: taraxvoid/voidflow/.github/workflows/resolve-env.yml@<sha> # vX.Y.Z
192
181
 
193
- ```json
194
- "test:e2e:lighthouse": "bun run build && unlighthouse-runner --dir dist"
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 }}
195
194
  ```
196
195
 
197
- Options: `--dir` (default `dist`, use `dist/client` for Cloudflare adapter
198
- builds), `--config` (default `unlighthouse.config.ts`), `--version` (pinned
199
- Unlighthouse version). Start from
200
- [`scripts/unlighthouse/unlighthouse.config.example.ts`](scripts/unlighthouse/unlighthouse.config.example.ts)
201
- and add `.unlighthouse/` to the site's `.gitignore`.
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.
202
204
 
203
- ### License policy
205
+ ## npm package
204
206
 
205
- `site-ci.yml` runs `bun run check:licenses` when the lockfile or `scripts/`
206
- change. Sites implement that script with the shared check, which runs
207
- `license-checker-rseidelsohn` in the site's repo root and fails on GPL, AGPL,
208
- SSPL and similar copyleft licenses (LGPL is allowed). Voidflow pins the
209
- checker to an exact version as its own dependency, so sites don't need to
210
- install it and every site runs the same version (sites that added
211
- `license-checker-rseidelsohn` as a devDependency for 0.12.2 can drop it).
207
+ ```sh
208
+ bun add -d @taraxvoid/voidflow # or: pnpm add -D @taraxvoid/voidflow
209
+ ```
212
210
 
213
- With `@taraxvoid/voidflow` as a devDependency, call the bin from the site's
214
- `package.json`:
211
+ Also published unscoped as `voidflow` with identical contents.
212
+
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.
216
+
217
+ ### Unlighthouse budgets
215
218
 
216
219
  ```json
217
- "check:licenses": "check-licenses"
220
+ "test:e2e:lighthouse": "bun run build && unlighthouse-runner --dir dist"
218
221
  ```
219
222
 
220
- Don't use `bunx --package @taraxvoid/voidflow@<version> ...` for these. It
221
- duplicates the version pin outside the lockfile and Renovate, and `bunx` reads
222
- the global `~/.bunfig.toml` rather than the site's, so a global
223
- `minimumReleaseAge` that doesn't exclude `@taraxvoid/voidflow` blocks it
224
- locally (including in the pre-commit hook) for 3 days after every release.
225
- The devDependency's bins are installed under the site's own `bunfig.toml`.
223
+ Serves the built directory and runs [Unlighthouse](https://unlighthouse.dev/)
224
+ with Playwright's Chromium. Fails if a category budget fails.
226
225
 
227
- Both bins (`check-licenses`, `unlighthouse-runner`) are TypeScript run by Bun
228
- (`#!/usr/bin/env bun`), so `bun` must be on `PATH` even when the site uses
229
- npm or pnpm. The `@taraxvoid/voidflow/playwright` export is plain ESM and runs
230
- on Node 22+ without Bun.
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
231
230
 
232
- ### Playwright preview config
231
+ Add `.unlighthouse/` to `.gitignore`.
233
232
 
234
- Shared `playwright.config` for Astro sites that run e2e against
235
- `astro preview`. It picks a free port outside CI, sets
236
- `ASTRO_PREVIEW_BACKGROUND=false` (Astro backgrounds `preview` in agent
237
- environments, which makes Playwright think the server exited), and drops the
238
- `webServer` when `PLAYWRIGHT_BASE_URL` is set.
233
+ ### License check
239
234
 
240
- ```sh
241
- bun add -d @taraxvoid/voidflow # or: pnpm add -D @taraxvoid/voidflow
235
+ ```json
236
+ "check:licenses": "check-licenses"
242
237
  ```
243
238
 
239
+ Fails on GPL, AGPL, SSPL and similar copyleft licenses. LGPL is allowed.
240
+
241
+ ### Playwright config
242
+
243
+ For Astro sites that run e2e against `astro preview`. Requires
244
+ `@playwright/test`. Runs on Node 22+ without Bun.
245
+
244
246
  ```js
245
247
  // playwright.config.js
246
248
  import { definePreviewConfig } from '@taraxvoid/voidflow/playwright'
@@ -248,23 +250,14 @@ import { definePreviewConfig } from '@taraxvoid/voidflow/playwright'
248
250
  export default definePreviewConfig({ ciPort: 4141 })
249
251
  ```
250
252
 
251
- The same release is also published unscoped as `voidflow` (identical code and
252
- version, with a short pointer README), so `bun add -d voidflow` and
253
- `import ... from 'voidflow/playwright'` work too. `@taraxvoid/voidflow` is the
254
- canonical name.
255
-
256
- Options: `ciPort` (required in CI), `runner` (default `bun`, e.g. `pnpm`),
257
- `timeout` (default 30000), `projects` (default Pixel 7 and Desktop Chrome),
258
- `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`).
259
257
 
260
- ## Shared Biome config
258
+ ### Biome
261
259
 
262
- [`biome/base.json`](biome/base.json) is the house style for the fleet: 4-space
263
- indent, LF, width 80, single quotes, semicolons only where needed, trailing
264
- commas, the `recommended` rule preset, organized imports, CSS lint off, and
265
- unused-import/variable rules off in `.astro` files (Biome can't see template
266
- usage). With `@taraxvoid/voidflow` as a devDependency, the site's `biome.json`
267
- extends it and adds only what is specific to the site:
260
+ Needs Biome 2. In the site's `biome.json`:
268
261
 
269
262
  ```json
270
263
  {
@@ -276,16 +269,12 @@ extends it and adds only what is specific to the site:
276
269
  }
277
270
  ```
278
271
 
279
- Lists such as `files.includes` and `overrides` combine with the base's rather
280
- than replace them; single values set in the site win. The base has no ignore
281
- lists, so each site keeps its own. It needs Biome 2: the pre-commit hook and
282
- `site-ci.yml` print a warning (they don't fail) when a site has Biome 1.x
283
- installed.
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.
284
276
 
285
- ## Dependency updates (Renovate)
286
-
287
- [`default.json`](default.json) is the shared Renovate preset. Sites add a
288
- `renovate.json`:
277
+ ## Renovate
289
278
 
290
279
  ```json
291
280
  {
@@ -294,220 +283,78 @@ installed.
294
283
  }
295
284
  ```
296
285
 
297
- It builds on `config:best-practices` (which pins GitHub Action digests), holds
298
- updates for 3 days like `minimumReleaseAge` in `bunfig.toml` (except our own
299
- package and security alerts), uses `chore(deps):` Conventional Commits, groups
300
- actions, astro, biome and playwright, and runs on Monday mornings. Custom
301
- managers keep three things current that Dependabot cannot see: the
302
- `@taraxvoid/voidflow@x.y.z` pin inside `package.json` scripts, a lefthook
303
- remote's `ref` tag, and tool versions in a `justfile` (annotate the line above
304
- with `# renovate: datasource=... depName=...`). Requires the Renovate GitHub App
305
- on the repo.
306
-
307
- ## Git hooks (lefthook)
308
-
309
- [`lefthook/base.yml`](lefthook/base.yml) is the shared hook set for any bun
310
- (or pnpm) repo, sites and tools alike, consumed as a lefthook remote. It calls
311
- the repo's own `package.json` scripts (`<pm> run --if-present`, so a missing
312
- script is skipped; `<pm>` is pnpm when the repo has a `pnpm-lock.yaml`, bun
313
- otherwise):
314
-
315
- - `pre-commit`: dependency guard, Biome 1.x warning, `format` (formatted files are re-staged),
316
- `lint:actions`, `check`, `test:unit`, and `check:licenses` / `audit` when the
317
- lockfile changed.
318
- - `commit-msg`: advisory Conventional Commits hint (never blocks).
319
- - `pre-push`: bail if the remote branch is ahead, then `test:push`; skipped when
320
- the push carries no commit the remote lacks (branch deletes, and tags or
321
- branches at already-pushed commits).
322
-
323
- Before `test:unit` and `test:push`, the hooks unset the variables git exports to
324
- hooks (`GIT_DIR`, `GIT_INDEX_FILE`, ...; the list comes from
325
- `git rev-parse --local-env-vars`). Without that, a test that runs
326
- `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`
327
301
 
328
302
  ```yaml
329
- # lefthook.yml in the repo
303
+ # lefthook.yml
330
304
  remotes:
331
305
  - git_url: https://github.com/taraxvoid/voidflow
332
- 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
333
307
  configs:
334
308
  - lefthook/base.yml
335
-
336
309
  ```
337
310
 
338
- Upgrading from `lefthook/site.yml` (v0.13 and earlier): change the `configs`
339
- entry to `lefthook/base.yml` when you bump `ref`. The hooks are otherwise the
340
- same. `lefthook/site.yml` is now a stub whose `pre-commit` and `pre-push` fail
341
- with that instruction, so a `ref` bump (for example from Renovate) that leaves
342
- `configs` alone stops commits instead of turning the hooks off silently.
343
-
344
- Lefthook merges `lefthook.yml`, then remotes, then `lefthook-local.yml`, so a
345
- repo's own `lefthook.yml` can add commands (for example image compression) but
346
- cannot override ones defined here. Differences go through `package.json`
347
- scripts: define `test:unit:precommit` (for example excluding tests that need a
348
- built `dist/`) and the pre-commit unit step runs it instead of `test:unit`.
349
- Likewise, define `check:precommit` and the typecheck step runs it instead of
350
- `check`. CI keeps calling the full scripts, so these only narrow what runs
351
- locally. Common uses:
352
-
353
- - bun: `"test:unit:precommit": "bun test --changed"` runs only the test files
354
- whose imports reach an uncommitted change.
355
- - pnpm workspace: `pnpm -r --filter '...[HEAD]' run <script>` runs only the
356
- changed packages and their dependents. Fall back to the full run when a
357
- root-level file (lockfile, shared tsconfig) changed, since those don't
358
- belong to any package. If the root `package.json` defines the same script
359
- as a recursive run, add `--filter '!{.}'` so it doesn't recurse.
360
-
361
- Hook-level options merge too. Don't set `parallel: true` under `pre-commit:` in
362
- the repo's `lefthook.yml`: it applies to the merged hook, so every step,
363
- including the shared ones, starts at once and their `priority` order is ignored
364
- (tests that share a database or build output can then time out).
365
-
366
- Install the binaries through mise. A starter `mise.toml` for a repo using
367
- these hooks and voidflow CI:
311
+ Install the binaries with mise:
368
312
 
369
313
  ```toml
314
+ # mise.toml
370
315
  [tools]
371
316
  lefthook = "2.2.1"
372
317
  gitleaks = "8.30.1"
373
318
  jactionlint = "2.1.0"
374
319
  ```
375
320
 
376
- Renovate bumps `mise.toml` natively. Set the `prepare` script so `bun install`
377
- wires the hooks, but make it tolerate a missing binary:
321
+ and wire the hooks on install without failing where lefthook is absent (CI):
378
322
 
379
323
  ```json
380
324
  "prepare": "lefthook install || echo 'lefthook not installed (run mise install); git hooks skipped'"
381
325
  ```
382
326
 
383
- A bare `lefthook install` fails the whole install step (exit 127) on any runner
384
- without the `lefthook` binary, which includes GitHub-hosted and self-hosted CI
385
- images, and CI never runs git hooks anyway. `ref` cannot be a commit SHA; pin a
386
- release tag.
387
-
388
- ## Branch rulesets as code
389
-
390
- Branch protection for the fleet lives in [`rulesets/index.ts`](rulesets/index.ts):
391
- one canonical `main` / `next` / `live` definition (plus `prerelease` and `maintenance`
392
- tiers for repos with release channels) and a small per-repo table
393
- (Netlify or not, check names, CODEOWNERS, extra bypass actors). Sites use
394
- `site()` and tools use `tool()` (main only, no Netlify, requires
395
- `validate / Validate` from `tool-ci.yml`). Private repos are left out: on a
396
- plan without rulesets for private repos, the API returns 403. Hand-edited
397
- rulesets drift, and drift is how a `next` ruleset ends up requiring a check
398
- name that no workflow reports any more.
399
-
400
- ```sh
401
- just rulesets-plan # read-only: diff every repo against the config
402
- just rulesets-plan --repo soundry
403
- just rulesets-plan --check # exit 1 on drift, for CI
404
- just rulesets-apply # create/update rulesets to match
405
- just rulesets-apply --prune # also delete rulesets a repo lists under `retire`
406
- ```
407
-
408
- Single-environment sites (a stable `main` that deploys to prod) manage only the
409
- `main` tier; their old `next`/`live` rulesets are listed under `retire`. voidflow itself
410
- manages `main`, `prerelease` (`next`) and `maintenance` (`release/*`) and sets
411
- `linearHistory: false` so `next` can be promoted into `main` with a merge commit.
412
- Uses the `gh` CLI for auth (needs admin on the repos). Rulesets that are not in
413
- the config are reported and left alone; only `retire` entries are ever deleted,
414
- and only with `--prune`. Private repos on a plan without rulesets (GitHub answers
415
- 403, e.g. a free personal account) are skipped and listed at the end rather than
416
- failing the run, so `--check` stays green and they pick up their rulesets once the
417
- repo goes public or the plan changes. It is repo tooling, not part of the published
418
- package.
419
-
420
- ## Deployments
421
-
422
- Example deployment job added to your workflow above. `resolve-env` maps the
423
- branch to dev/staging/prod once, up front, so its output can be used both to
424
- drive the deploy and to label the job (`Deploy [dev]`, `Deploy [staging]`,
425
- `Deploy [prod]`). A job's `name:` can reference `needs.<job>.outputs.*` but
426
- not a step output from within itself, hence the split:
427
-
428
- ```yaml
429
- jobs:
430
- resolve-env:
431
- needs: validate
432
- if: github.event_name == 'push'
433
- uses: taraxvoid/voidflow/.github/workflows/resolve-env.yml@15caea203066d42cb7be021eedb6356fac6ffdec # v0.8.1
434
-
435
- deploy:
436
- name: Deploy [${{ needs.resolve-env.outputs.env }}]
437
- needs: resolve-env
438
- if: github.event_name == 'push'
439
- environment: ${{ needs.resolve-env.outputs.env }}
440
- steps:
441
- - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
442
- - uses: taraxvoid/voidflow/actions/cloudflare-deploy@15caea203066d42cb7be021eedb6356fac6ffdec # v0.8.1
443
- with:
444
- env: ${{ needs.resolve-env.outputs.env }}
445
- cloudflare-api-token: ${{ secrets.CLOUDFLARE_API_TOKEN }}
446
- ```
447
-
448
- Sites with a single production environment and per-PR previews (no `next` or
449
- `live` branch) pass `with: { single-environment: true }` to `resolve-env.yml`
450
- (or the same input on `actions/branch-env-map`). `main` then maps to `prod` and
451
- `pull_request` events map to `preview`; any other ref fails.
452
-
453
- ### Versioning
454
-
455
- Semver, released with [release-please](https://github.com/googleapis/release-please)
456
- ([`release-please.yml`](.github/workflows/release-please.yml)) on three branch-based channels
457
- (full flow in [`RELEASING.md`](RELEASING.md)):
458
-
459
- | Channel | Branch | Example | npm dist-tag |
460
- |---|---|---|---|
461
- | stable | `main` | `0.10.0` | `latest` |
462
- | prerelease | `next` | `0.10.0-next`, `0.10.0-next.1` | `next` |
463
- | maintenance | `release/N.x` | `0.9.4` | `release-N.x` |
464
-
465
- release-please keeps a standing release PR open on each channel's branch, updated as commits land.
466
- Merging it bumps `package.json` (and [`CHANGELOG.md`](CHANGELOG.md) on stable), tags `vX.Y.Z` (`vX.Y.Z-next[.N]` on `next`),
467
- creates the GitHub release and publishes to npm and JSR under the channel's dist-tag. The PR is
468
- opened with the org's release-bot GitHub App token (`RELEASE_BOT_CLIENT_ID` /
469
- `RELEASE_BOT_APP_PRIVATE_KEY`) so CI runs on it.
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:
470
329
 
471
- Feature PRs target `next`; promotion to `main` is a merge-commit PR (`just promote`).
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)
472
333
 
473
- Commit messages should follow [Conventional Commits](https://www.conventionalcommits.org),
474
- enforced loosely, as an advisory `commit-msg` hint (see
475
- [`scripts/check-commit-msg.sh`](scripts/check-commit-msg.sh)), not a blocking check. They
476
- drive the version bump and generated changelog.
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.
477
337
 
478
- Ways to consume a release:
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.
479
340
 
480
- - **Floating major tag**: `@v0` follows the newest stable 0.x release. Easiest to track, and
481
- mutable like any floating tag.
482
- - **SHA pin with a version comment**, e.g. `@15caea2... # v0.8.1`: what I use. Same hardening
483
- as any third-party action, and Renovate or Dependabot can bump it.
484
- - **Try unreleased changes**: pin the commit SHA of a `vX.Y.Z-next` / `vX.Y.Z-next.N` prerelease tag.
485
-
486
- Treat `@main` and `@next` as unstable.
487
-
488
- ## Self-validation
489
-
490
- This repo validates its own workflows and composite actions (`.github/workflows/ci.yml`):
341
+ ## Used by
491
342
 
492
- - **jactionlint**: workflow schema/logic; also shellchecks `run:` steps in `.github/workflows/*.yml` automatically (`shellcheck` ships on `ubuntu-latest`)
493
- - **check-jsonschema**: schema-checks `actions/**/action.yml` and the workflow files against [SchemaStore](https://www.schemastore.org/)'s `github-action.json` / `github-workflow.json`
494
- - **shellcheck** (`scripts/shellcheck-actions.ts`): checks `run:` steps inside `actions/**/action.yml`, which jactionlint doesn't reach
495
- - **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).
496
346
 
497
- ### Local dev setup
347
+ Optional badge for your README (follows light/dark mode):
498
348
 
499
- ```sh
500
- mise install # tools pinned in mise.toml
501
- 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>
502
356
  ```
503
357
 
504
- - `just validate`: run everything CI runs, locally
505
- - `just lint-workflows` / `lint-actions` / `lint-shell` / `security`: run one check at a time
506
- - `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`
507
- - lefthook runs the fast checks (`lint-workflows`, `lint-actions`, `lint-shell`) on `pre-commit`, and `zizmor` on `pre-push`
508
-
509
- **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.
510
-
511
- **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
512
359
 
513
- 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",
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": [