From 4ffb12e489f4cd5c764d3fb2c7ec669364a73938 Mon Sep 17 00:00:00 2001 From: Tara X V01D <1711810+taraxvoid@users.noreply.github.com> Date: Fri, 2 Oct 2026 14:16:55 -0500 Subject: [PATCH 01/21] docs: drop stray conflict marker, fix lefthook remote ref comment Co-Authored-By: Claude Sonnet 5.5 --- README.md | 1 - lefthook/site.yml | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index 9f63a60..3d9226e 100644 --- a/README.md +++ b/README.md @@ -182,7 +182,6 @@ remote's `ref` tag, and tool versions in a `justfile` (annotate the line above with `# renovate: datasource=... depName=...`). Requires the Renovate GitHub App on the repo. -||||||| 4041a59 ## Git hooks (lefthook) [`lefthook/site.yml`](lefthook/site.yml) is the shared hook set for the site diff --git a/lefthook/site.yml b/lefthook/site.yml index b1a9c94..728ed62 100644 --- a/lefthook/site.yml +++ b/lefthook/site.yml @@ -5,7 +5,7 @@ # # lefthook.yml in the site # remotes: # - git_url: https://github.com/taraxvoid/voidflow -# ref: # pin; bump deliberately +# ref: # a tag or branch (not a SHA); bump deliberately # configs: # - lefthook/site.yml # From cc14d77befdb4532e8ba448d37b0f4087ca99e1d Mon Sep 17 00:00:00 2001 From: "Tara \\X/ Void" <1711810+taraxvoid@users.noreply.github.com> Date: Fri, 2 Oct 2026 14:22:51 -0500 Subject: [PATCH 02/21] fix(resolve-env): inline the env mapping so the reusable workflow works cross-repo (#118) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `resolve-env.yml` does `actions/checkout` (which checks out the **caller's** repo) and then `uses: ./actions/branch-env-map`, a path that only exists in voidflow's own repo. Every cross-repo caller therefore fails at that step with the action not found. Found rolling resolve-env into taraxvoid.net (`resolve-env / Resolve env: Run ./actions/branch-env-map` failed on the PR run). Fix: inline the mapping in the workflow (no checkout, no local action). A reusable workflow has no way to reference its own repo's composite action at a matching ref, so the script is duplicated; `test/branch-env-map.test.ts` asserts the two scripts are byte-identical and runs the mapping for every ref/event case in both modes (default `main/next/live` to `dev/staging/prod`, and `single-environment`), against both copies. Also asserts the workflow has no `uses` steps, so this cannot regress. Validating on a real runner via taraxvoid.net#20 pointed at this branch; result to follow here. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Sonnet 5.5 --- .github/workflows/resolve-env.yml | 37 ++++++++++++---- test/branch-env-map.test.ts | 71 +++++++++++++++++++++++++++++++ 2 files changed, 100 insertions(+), 8 deletions(-) create mode 100644 test/branch-env-map.test.ts diff --git a/.github/workflows/resolve-env.yml b/.github/workflows/resolve-env.yml index 7d20fc3..49af8e6 100644 --- a/.github/workflows/resolve-env.yml +++ b/.github/workflows/resolve-env.yml @@ -1,8 +1,9 @@ name: Resolve env # Resolves the deploy environment (dev/staging/prod) for the current branch -# via the branch-env-map action, exposing it as a job output so callers can -# reference it in a downstream job's `name:` (e.g. `Deploy [${{ needs.resolve-env.outputs.env }}]`). +# using the same mapping as the branch-env-map action, exposing it as a job +# output so callers can reference it in a downstream job's `name:` (e.g. +# `Deploy [${{ needs.resolve-env.outputs.env }}]`). # Job names may use the `needs` context, but not composite action outputs # directly, so this indirection is what makes that possible. @@ -34,10 +35,30 @@ jobs: outputs: env: ${{ steps.map.outputs.env }} steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - with: - persist-credentials: false + # Same mapping as actions/branch-env-map (a test keeps them identical). + # Inlined because a reusable workflow can't reach `./actions/...` in its + # own repo: checkout gives it the caller's tree, not voidflow's. - id: map - uses: ./actions/branch-env-map - with: - single-environment: ${{ inputs.single-environment }} + shell: bash + env: + REF_NAME: ${{ github.ref_name }} + EVENT_NAME: ${{ github.event_name }} + SINGLE: ${{ inputs.single-environment }} + run: | + if [[ "$SINGLE" == "true" ]]; then + if [[ "$EVENT_NAME" == pull_request* ]]; then + echo "env=preview" >> "$GITHUB_OUTPUT" + elif [[ "$REF_NAME" == main ]]; then + echo "env=prod" >> "$GITHUB_OUTPUT" + else + echo "::error::Refusing to deploy unknown ref: $REF_NAME (expected main or a pull request)" >&2 + exit 1 + fi + exit 0 + fi + case "$REF_NAME" in + main) echo "env=dev" >> "$GITHUB_OUTPUT" ;; + next) echo "env=staging" >> "$GITHUB_OUTPUT" ;; + live) echo "env=prod" >> "$GITHUB_OUTPUT" ;; + *) echo "::error::Refusing to deploy unknown branch: $REF_NAME (expected main, next, or live)" >&2; exit 1 ;; + esac diff --git a/test/branch-env-map.test.ts b/test/branch-env-map.test.ts new file mode 100644 index 0000000..6f0fa7a --- /dev/null +++ b/test/branch-env-map.test.ts @@ -0,0 +1,71 @@ +import { describe, expect, test } from 'bun:test' +import { mkdtempSync, readFileSync } from 'node:fs' +import { tmpdir } from 'node:os' +import { join } from 'node:path' + +const read = (p: string) => readFileSync(new URL(`../${p}`, import.meta.url), 'utf8') + +const actionScript: string = Bun.YAML.parse(read('actions/branch-env-map/action.yml')).runs.steps.find( + (s: any) => s.id === 'map', +).run +const workflowScript: string = Bun.YAML.parse(read('.github/workflows/resolve-env.yml')).jobs[ + 'resolve-env' +].steps.find((s: any) => s.id === 'map').run + +async function map(script: string, ref: string, event: string, single: boolean) { + const out = join(mkdtempSync(join(tmpdir(), 'voidflow-env-')), 'out') + await Bun.write(out, '') + const proc = Bun.spawn(['bash', '-c', script], { + env: { + PATH: process.env.PATH, + REF_NAME: ref, + EVENT_NAME: event, + SINGLE: String(single), + GITHUB_OUTPUT: out, + }, + stdout: 'pipe', + stderr: 'pipe', + }) + const code = await proc.exited + return { code, env: readFileSync(out, 'utf8').trim() } +} + +describe('resolve-env.yml carries the same mapping as branch-env-map', () => { + test('scripts are identical (no drift)', () => { + expect(workflowScript).toBe(actionScript) + }) + + test('resolve-env.yml has no `uses` steps (a local ./actions path or checkout cannot work cross-repo)', () => { + const steps = Bun.YAML.parse(read('.github/workflows/resolve-env.yml')).jobs['resolve-env'].steps + expect(steps.filter((s: any) => s.uses)).toEqual([]) + }) +}) + +describe.each([ + ['action', actionScript], + ['workflow', workflowScript], +])('%s script', (_name, script) => { + test.each([ + ['main', 'push', 'env=dev'], + ['next', 'push', 'env=staging'], + ['live', 'push', 'env=prod'], + ])('default mode: %s on %s', async (ref, event, expected) => { + expect(await map(script, ref, event, false)).toEqual({ code: 0, env: expected }) + }) + + test('default mode rejects unknown branches', async () => { + expect((await map(script, 'feature/x', 'push', false)).code).toBe(1) + }) + + test.each([ + ['main', 'push', 'env=prod'], + ['123/merge', 'pull_request', 'env=preview'], + ['feature/x', 'pull_request', 'env=preview'], + ])('single-environment: %s on %s', async (ref, event, expected) => { + expect(await map(script, ref, event, true)).toEqual({ code: 0, env: expected }) + }) + + test('single-environment rejects non-main pushes', async () => { + expect((await map(script, 'feature/x', 'push', true)).code).toBe(1) + }) +}) From 8bcb8a18656717e98718ea6a47f8a1bdcd92ef49 Mon Sep 17 00:00:00 2001 From: "Tara \\X/ Void" <1711810+taraxvoid@users.noreply.github.com> Date: Tue, 6 Oct 2026 11:18:40 -0500 Subject: [PATCH 03/21] docs: tell sites to run voidflow bins from the devDependency, not bunx (#122) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The README told sites to wire `check:licenses` and `test:e2e:lighthouse` through `bunx --package @taraxvoid/voidflow@ …`. `bunx` reads the **global** `~/.bunfig.toml`, so a global `minimumReleaseAge` that doesn't exclude voidflow blocks it locally (pre-commit hook included) for 3 days after each release, and the pinned version lives outside the lockfile and Renovate. Sites already have voidflow as a devDependency, so the bins are in `node_modules/.bin`. queeromaha and soundry already call them directly; synthomaha is switching in taraxvoid/synthomaha#100. This updates the README to match and drops the stale 0.8.1 pin. Not verified: why synthomaha CI run 37051879514 failed with `No version matching "0.11.1"` — I couldn't reproduce it now that 0.11.1 is past the age gate. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Sonnet 5.5 --- README.md | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 3d9226e..fa8ca3d 100644 --- a/README.md +++ b/README.md @@ -107,10 +107,11 @@ static directory on a free port and runs [Unlighthouse](https://unlighthouse.dev/) against it, reusing Playwright's Chromium. It exits non-zero if a category budget in the site's config fails. -In the site's `package.json` (pin the tag): +Add `@taraxvoid/voidflow` as a devDependency and call the bin from the site's +`package.json`: ```json -"test:e2e:lighthouse": "bun run build && bunx --package @taraxvoid/voidflow@0.8.1 unlighthouse-runner --dir dist" +"test:e2e:lighthouse": "bun run build && unlighthouse-runner --dir dist" ``` Options: `--dir` (default `dist`, use `dist/client` for Cloudflare adapter @@ -126,12 +127,20 @@ change. Sites implement that script with the shared check, which runs `license-checker` in the site's repo root and fails on GPL, AGPL, SSPL and similar copyleft licenses (LGPL is allowed). -In the site's `package.json` (pin the tag): +With `@taraxvoid/voidflow` as a devDependency, call the bin from the site's +`package.json`: ```json -"check:licenses": "bunx --package @taraxvoid/voidflow@ check-licenses" +"check:licenses": "check-licenses" ``` +Don't use `bunx --package @taraxvoid/voidflow@ ...` for these. It +duplicates the version pin outside the lockfile and Renovate, and `bunx` reads +the global `~/.bunfig.toml` rather than the site's, so a global +`minimumReleaseAge` that doesn't exclude `@taraxvoid/voidflow` blocks it +locally (including in the pre-commit hook) for 3 days after every release. +The devDependency's bins are installed under the site's own `bunfig.toml`. + ### Playwright preview config Shared `playwright.config` for Astro sites that run e2e against From fc33ffd2c3314ed8e4a3be6a2d006789b832d1fd Mon Sep 17 00:00:00 2001 From: "Tara \\X/ Void" <1711810+taraxvoid@users.noreply.github.com> Date: Tue, 6 Oct 2026 11:20:07 -0500 Subject: [PATCH 04/21] feat(lefthook): pick bun or pnpm from the lockfile in the shared site hooks (#119) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The shared site hooks hardcoded `bun run --if-present`, which blocks the pnpm sites (rvnflt, voidsite) from adopting them. A top-level lefthook `templates:` entry `pm` resolves to `pnpm` when the repo has a `pnpm-lock.yaml`, else `bun`, and every command uses `{pm} run --if-present`. The install hints use `{pm} install` too. Also fixes the header comment (`ref` is a tag or branch, not a SHA). Tested as a `file://` remote in throwaway repos: the bun repo runs `bun run` for each step, the pnpm repo (with a stub `pnpm` on PATH) runs `pnpm run --if-present` for each step. `lefthook validate` passes and the unit test now asserts the lockfile detection and that no `bun run` remains. Note: `bun run --if-present audit` falls through to macOS's `/usr/sbin/audit` when a site defines no `audit` script. Pre-existing and harmless for sites that define one. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Sonnet 5.5 --- README.md | 3 ++- lefthook/site.yml | 37 ++++++++++++++++++++++++------------- test/lefthook-site.test.ts | 16 ++++++++++++++-- 3 files changed, 40 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index fa8ca3d..5dccc88 100644 --- a/README.md +++ b/README.md @@ -195,7 +195,8 @@ on the repo. [`lefthook/site.yml`](lefthook/site.yml) is the shared hook set for the site stack, consumed as a lefthook remote. It calls the site's own `package.json` -scripts (`bun run --if-present`, so a missing script is skipped): +scripts (` run --if-present`, so a missing script is skipped; `` is pnpm +when the repo has a `pnpm-lock.yaml`, bun otherwise): - `pre-commit`: dependency guard, `format` (formatted files are re-staged), `lint:actions`, `check`, `test:unit`, and `check:licenses` / `audit` when the diff --git a/lefthook/site.yml b/lefthook/site.yml index 728ed62..47dc8e6 100644 --- a/lefthook/site.yml +++ b/lefthook/site.yml @@ -1,6 +1,7 @@ -# Shared git hooks for the Astro/bun site stack. Sites pull this in as a -# lefthook remote and call their own package.json scripts, so a script a site -# does not define is skipped (`bun run --if-present`). +# Shared git hooks for the Astro site stack (bun, or pnpm when the repo has a +# pnpm-lock.yaml). Sites pull this in as a lefthook remote and call their own +# package.json scripts, so a script a site does not define is skipped +# (` run --if-present`). # # # lefthook.yml in the site # remotes: @@ -14,6 +15,10 @@ # one defined here. Differences go through package.json scripts instead, e.g. # `test:unit:precommit` replaces `test:unit` for the pre-commit unit step. +templates: + # Package manager: pnpm when the repo is a pnpm project, bun otherwise. + pm: "$(if [ -f pnpm-lock.yaml ]; then echo pnpm; else echo bun; fi)" + pre-commit: skip: [merge, rebase] commands: @@ -22,39 +27,45 @@ pre-commit: run: | if [ ! -d node_modules ]; then echo "pre-commit: 'node_modules' is missing, dependencies aren't installed." - echo " Run 'bun install' before committing, then commit again." + echo " Run '{pm} install' before committing, then commit again." exit 1 fi format: priority: 2 - run: bun run --if-present format + run: "{pm} run --if-present format" stage_fixed: true lint-actions: priority: 3 - run: bun run --if-present lint:actions + run: "{pm} run --if-present lint:actions" typecheck: priority: 3 - run: bun run --if-present check + run: "{pm} run --if-present check" unit: priority: 3 # A site that can't run its whole suite pre-commit (e.g. tests that need # a built dist/) defines `test:unit:precommit`; otherwise test:unit runs. run: | if grep -q '"test:unit:precommit"' package.json; then - bun run test:unit:precommit + {pm} run test:unit:precommit else - bun run --if-present test:unit + {pm} run --if-present test:unit fi licenses: priority: 3 glob: "{package.json,bun.lock,pnpm-lock.yaml}" - run: bun run --if-present check:licenses + run: "{pm} run --if-present check:licenses" audit: # Local only: hits a remote vulnerability database and can hang offline, # so it runs when dependencies changed and fails closed. priority: 3 glob: "{package.json,bun.lock,pnpm-lock.yaml}" - run: bun run --if-present audit + # Checked by hand: under bun, `--if-present` falls through to + # the system `audit` binary (macOS /usr/sbin/audit) when the site has no + # audit script, and that exits non-zero. + run: | + if grep -q '"audit"' package.json; then + {pm} run audit + fi commit-msg: commands: @@ -109,8 +120,8 @@ pre-push: if [ ! -d node_modules ]; then echo "pre-push: 'node_modules' is missing, dependencies aren't installed." - echo " Run 'bun install' before pushing, then push again." + echo " Run '{pm} install' before pushing, then push again." exit 1 fi - bun run --if-present test:push + {pm} run --if-present test:push diff --git a/test/lefthook-site.test.ts b/test/lefthook-site.test.ts index 574c31d..f44c796 100644 --- a/test/lefthook-site.test.ts +++ b/test/lefthook-site.test.ts @@ -28,7 +28,19 @@ describe('lefthook/site.yml', () => { test('pre-commit unit step prefers test:unit:precommit when a site defines it', async () => { const text = await Bun.file(config).text() expect(text).toContain('grep -q \'"test:unit:precommit"\' package.json') - expect(text).toContain('bun run test:unit:precommit') - expect(text).toContain('bun run --if-present test:unit') + expect(text).toContain('{pm} run test:unit:precommit') + expect(text).toContain('{pm} run --if-present test:unit') + }) + + test('picks the package manager from the lockfile instead of hardcoding bun', async () => { + const text = await Bun.file(config).text() + expect(text).toContain('pnpm-lock.yaml ]; then echo pnpm; else echo bun') + expect(text).not.toMatch(/\bbun run\b/) + }) + + test('audit step only runs when the site defines an audit script', async () => { + const text = await Bun.file(config).text() + expect(text).toContain('grep -q \'"audit"\' package.json') + expect(text).not.toContain('--if-present audit') }) }) From e7b76d040cba8d5c28925b8de81532de6537583d Mon Sep 17 00:00:00 2001 From: "ravenflight-releases[bot]" <330165707+ravenflight-releases[bot]@users.noreply.github.com> Date: Tue, 6 Oct 2026 16:20:33 +0000 Subject: [PATCH 05/21] chore(main): release 0.12.0 --- .release-please/main-manifest.json | 2 +- CHANGELOG.md | 12 ++++++++++++ jsr.json | 2 +- package.json | 2 +- 4 files changed, 15 insertions(+), 3 deletions(-) diff --git a/.release-please/main-manifest.json b/.release-please/main-manifest.json index ddfa3e3..8032c17 100644 --- a/.release-please/main-manifest.json +++ b/.release-please/main-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.11.1" + ".": "0.12.0" } diff --git a/CHANGELOG.md b/CHANGELOG.md index 76b724b..f48bbfc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,18 @@ All notable changes to this project are documented here, generated by [release-please](https://github.com/googleapis/release-please) from [Conventional Commits](https://www.conventionalcommits.org). +## [0.12.0](https://github.com/taraxvoid/voidflow/compare/v0.11.1...v0.12.0) (2026-10-06) + + +### Features + +* **lefthook:** pick bun or pnpm from the lockfile in the shared site hooks ([#119](https://github.com/taraxvoid/voidflow/issues/119)) ([fc33ffd](https://github.com/taraxvoid/voidflow/commit/fc33ffd2c3314ed8e4a3be6a2d006789b832d1fd)) + + +### Bug Fixes + +* **resolve-env:** inline the env mapping so the reusable workflow works cross-repo ([#118](https://github.com/taraxvoid/voidflow/issues/118)) ([cc14d77](https://github.com/taraxvoid/voidflow/commit/cc14d77befdb4532e8ba448d37b0f4087ca99e1d)) + ## [0.11.1](https://github.com/taraxvoid/voidflow/compare/v0.11.0...v0.11.1) (2026-10-02) diff --git a/jsr.json b/jsr.json index 085437a..336260e 100644 --- a/jsr.json +++ b/jsr.json @@ -1,6 +1,6 @@ { "name": "@taraxvoid/voidflow", - "version": "0.11.1", + "version": "0.12.0", "license": "MIT", "exports": { "./playwright": "./playwright/index.mjs" diff --git a/package.json b/package.json index be4a64d..d9c4048 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@taraxvoid/voidflow", - "version": "0.11.1", + "version": "0.12.0", "description": "Shared workflows, actions and tooling for @taraxvoid sites", "license": "MIT", "keywords": [ From 63a1b6f3a7827a9292f27f27468da6bb8e802c37 Mon Sep 17 00:00:00 2001 From: "Tara \\X/ Void" <1711810+taraxvoid@users.noreply.github.com> Date: Tue, 6 Oct 2026 12:58:50 -0500 Subject: [PATCH 06/21] fix(renovate): drop Renovate security PRs in favor of Dependabot security updates (#124) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Security PRs now come from GitHub Dependabot security updates (repo setting, no config file; enabled on both accounts). Renovate's `vulnerabilityAlerts` reads the same GitHub alerts, so leaving it on would open duplicate PRs. Version updates are unchanged. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Sonnet 5.5 --- default.json | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/default.json b/default.json index 003e4e0..a0dab61 100644 --- a/default.json +++ b/default.json @@ -15,9 +15,7 @@ "schedule": ["before 9am on the first day of the month"] }, "vulnerabilityAlerts": { - "minimumReleaseAge": null, - "schedule": ["at any time"], - "labels": ["security"] + "enabled": false }, "packageRules": [ { From 7d2ec9b10afe34051211e0ba231c5feaf668895e Mon Sep 17 00:00:00 2001 From: "Tara \\X/ Void" <1711810+taraxvoid@users.noreply.github.com> Date: Tue, 6 Oct 2026 14:43:16 -0500 Subject: [PATCH 07/21] docs: state how to install lefthook (#127) Co-Authored-By: Claude Sonnet 5.5 ## What and why Co-authored-by: Claude Sonnet 5.5 --- README.md | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 5dccc88..b2f2904 100644 --- a/README.md +++ b/README.md @@ -222,8 +222,17 @@ scripts: define `test:unit:precommit` (for example excluding tests that need a built `dist/`) and the pre-commit unit step runs it instead of `test:unit`. Install the binary through mise (`lefthook = ""` in `mise.toml`) and -set `"prepare": "lefthook install"` so `bun install` wires the hooks. `ref` -cannot be a commit SHA; pin a release tag. +set the `prepare` script so `bun install` wires the hooks, but make it tolerate a +missing binary: + +```json +"prepare": "lefthook install || echo 'lefthook not installed (run mise install); git hooks skipped'" +``` + +A bare `lefthook install` fails the whole install step (exit 127) on any runner +without the `lefthook` binary, which includes GitHub-hosted and self-hosted CI +images, and CI never runs git hooks anyway. `ref` cannot be a commit SHA; pin a +release tag. ## Branch rulesets as code From 94e3b04b5e5537f031f19648da15b5d88a3a1290 Mon Sep 17 00:00:00 2001 From: "ravenflight-releases[bot]" <330165707+ravenflight-releases[bot]@users.noreply.github.com> Date: Wed, 7 Oct 2026 21:55:40 -0700 Subject: [PATCH 08/21] chore(main): release 0.12.1 (#125) :robot: I have created a release *beep* *boop* --- ## [0.12.1](https://github.com/taraxvoid/voidflow/compare/v0.12.0...v0.12.1) (2026-10-06) ### Bug Fixes * **renovate:** drop Renovate security PRs in favor of Dependabot security updates ([#124](https://github.com/taraxvoid/voidflow/issues/124)) ([63a1b6f](https://github.com/taraxvoid/voidflow/commit/63a1b6f3a7827a9292f27f27468da6bb8e802c37)) --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please). Co-authored-by: ravenflight-releases[bot] <330165707+ravenflight-releases[bot]@users.noreply.github.com> --- .release-please/main-manifest.json | 2 +- CHANGELOG.md | 7 +++++++ jsr.json | 2 +- package.json | 2 +- 4 files changed, 10 insertions(+), 3 deletions(-) diff --git a/.release-please/main-manifest.json b/.release-please/main-manifest.json index 8032c17..38acea6 100644 --- a/.release-please/main-manifest.json +++ b/.release-please/main-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.12.0" + ".": "0.12.1" } diff --git a/CHANGELOG.md b/CHANGELOG.md index f48bbfc..f6dc846 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,13 @@ All notable changes to this project are documented here, generated by [release-please](https://github.com/googleapis/release-please) from [Conventional Commits](https://www.conventionalcommits.org). +## [0.12.1](https://github.com/taraxvoid/voidflow/compare/v0.12.0...v0.12.1) (2026-10-06) + + +### Bug Fixes + +* **renovate:** drop Renovate security PRs in favor of Dependabot security updates ([#124](https://github.com/taraxvoid/voidflow/issues/124)) ([63a1b6f](https://github.com/taraxvoid/voidflow/commit/63a1b6f3a7827a9292f27f27468da6bb8e802c37)) + ## [0.12.0](https://github.com/taraxvoid/voidflow/compare/v0.11.1...v0.12.0) (2026-10-06) diff --git a/jsr.json b/jsr.json index 336260e..aca48ad 100644 --- a/jsr.json +++ b/jsr.json @@ -1,6 +1,6 @@ { "name": "@taraxvoid/voidflow", - "version": "0.12.0", + "version": "0.12.1", "license": "MIT", "exports": { "./playwright": "./playwright/index.mjs" diff --git a/package.json b/package.json index d9c4048..6f8a41e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@taraxvoid/voidflow", - "version": "0.12.0", + "version": "0.12.1", "description": "Shared workflows, actions and tooling for @taraxvoid sites", "license": "MIT", "keywords": [ From d829c18b1603a35a132048082eaab6d7ffd10f13 Mon Sep 17 00:00:00 2001 From: "Tara \\X/ Void" <1711810+taraxvoid@users.noreply.github.com> Date: Sun, 11 Oct 2026 21:30:29 -0700 Subject: [PATCH 09/21] docs: add optional 'uses voidflow' badge snippet (#129) Co-Authored-By: Claude Sonnet 5.5 Co-authored-by: Claude Sonnet 5.5 --- README.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/README.md b/README.md index b2f2904..b348c50 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,19 @@ It is semi-opinionated: the defaults match how my own sites are built (bun, Astr - [Queer Omaha](https://queeromaha.net), [Synth Omaha](https://synthomaha.net) and [Soundry](https://soundryomaha.net), free community sites for Omaha's queer and music scenes - [ravenflight.io](https://ravenflight.io), RavenFlight's marketing site +### Show you use it + +Optional. Paste this into your site's README. It follows light and dark mode. + +```html + + + + CI: voidflow + + +``` + ## Usage 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)). From 23ed5e4d2a098e12b85fa185e0fe9be20a7a5e18 Mon Sep 17 00:00:00 2001 From: "Tara \\X/ Void" <1711810+taraxvoid@users.noreply.github.com> Date: Sun, 11 Oct 2026 21:30:39 -0700 Subject: [PATCH 10/21] docs: make lefthook prepare script tolerate a missing binary in CI (#131) Co-Authored-By: Claude Sonnet 5.5 Co-authored-by: Claude Sonnet 5.5 From 4eef1177dfb8e0d50b56b3d48e952ae6e647133b Mon Sep 17 00:00:00 2001 From: "Tara \\X/ Void" <1711810+taraxvoid@users.noreply.github.com> Date: Sun, 11 Oct 2026 21:30:46 -0700 Subject: [PATCH 11/21] docs: tell sites to run voidflow bins from the devDependency, not bunx (#132) The README told sites to wire check:licenses and test:e2e:lighthouse through bunx --package @taraxvoid/voidflow@. bunx reads the global ~/.bunfig.toml, so a global minimumReleaseAge that does not exclude voidflow blocks it locally for 3 days after every release, and the pinned version lives outside the lockfile and Renovate. Sites already depend on voidflow as a devDependency, so the bins are in node_modules/.bin. Co-Authored-By: Claude Sonnet 5.5 Co-authored-by: Claude Sonnet 5.5 From 183877f5f4eb091a7df6e09c333ffa609eaf8a81 Mon Sep 17 00:00:00 2001 From: "Tara \\X/ Void" <1711810+taraxvoid@users.noreply.github.com> Date: Sun, 11 Oct 2026 21:30:51 -0700 Subject: [PATCH 12/21] fix(renovate): drop Renovate security PRs in favor of Dependabot security updates (#133) Security PRs now come from GitHub Dependabot security updates (a repo setting, no config file). Renovate's vulnerabilityAlerts reads the same GitHub alerts, so leaving it on would open duplicate PRs. Co-Authored-By: Claude Sonnet 5.5 Co-authored-by: Claude Sonnet 5.5 From bbc620ba9d54b5a3ed4d4d81e7502e805da31791 Mon Sep 17 00:00:00 2001 From: "Tara \\X/ Void" <1711810+taraxvoid@users.noreply.github.com> Date: Sun, 11 Oct 2026 21:31:02 -0700 Subject: [PATCH 13/21] feat/bun install socket scanner (#134) - **feat(ci): skip the bunfig security scanner in CI by default** - **fix(bun-install): verify the scanner-free bunfig by parsing it, not grep** --------- Co-authored-by: Claude Opus 5.5 --- .github/workflows/ci.yml | 2 +- .github/workflows/site-ci.yml | 64 +++++++++- README.md | 34 ++++++ actions/bun-install/action.yml | 64 ++++++++++ test/bun-install.test.ts | 205 +++++++++++++++++++++++++++++++++ 5 files changed, 366 insertions(+), 3 deletions(-) create mode 100644 actions/bun-install/action.yml create mode 100644 test/bun-install.test.ts diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 85fe459..f0d75eb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -72,6 +72,6 @@ jobs: - uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0 - - run: bun install --frozen-lockfile + - uses: ./actions/bun-install - run: bun test diff --git a/.github/workflows/site-ci.yml b/.github/workflows/site-ci.yml index 1bec857..3a3c934 100644 --- a/.github/workflows/site-ci.yml +++ b/.github/workflows/site-ci.yml @@ -58,6 +58,15 @@ on: (inputs are visible in logs). type: string default: '{}' + socket-scanner: + description: > + Run the bunfig.toml install-time security scanner (`[install.security] + scanner`, e.g. Socket's) during the CI install. Defaults to false: it + calls a third-party API on every install and can fail a build for + reasons outside the repo. It still runs on local installs. Bun only; + ignored for pnpm. + type: boolean + default: false secrets: GITLEAKS_LICENSE: description: > @@ -187,8 +196,59 @@ jobs: restore-keys: | ${{ runner.os }}-playwright- - - name: Install NPM Dependencies - run: '"$PM" install --frozen-lockfile' + # Same script as actions/bun-install (a test keeps them identical). + # Inlined because a reusable workflow can't reach `./actions/...` in its + # own repo: checkout gives it the caller's tree, not voidflow's. + - name: Install NPM Dependencies (bun) + id: install + if: inputs.package-manager == 'bun' + shell: bash + env: + SOCKET_SCANNER: ${{ inputs.socket-scanner }} + run: | + # Removes the scanner line as text, then checks the result with a real + # TOML parse: it must equal the original minus install.security.scanner. + # Anything that can't be removed that way fails the install instead of + # running the scanner, or skipping it, silently. + # shellcheck disable=SC2016 # JS source; the ${...} in it is for bun, not bash + strip_scanner=' + const src = await Bun.file("bunfig.toml").text() + const parse = (text) => { try { return Bun.TOML.parse(text) } catch (e) { console.log(`::error file=bunfig.toml::Could not parse bunfig.toml: ${e.message}`); process.exit(1) } } + const original = parse(src) + if (original.install?.security?.scanner === undefined) process.exit(10) + const key = /^\s*((install\.)?security\.)?("scanner"|scanner)\s*=/ + const stripped = src.split("\n").filter((line) => !key.test(line)).join("\n") + // empty tables left behind by the removal count as absent + const prune = (cfg) => { + delete cfg.install?.security?.scanner + if (cfg.install?.security && Object.keys(cfg.install.security).length === 0) delete cfg.install.security + if (cfg.install && Object.keys(cfg.install).length === 0) delete cfg.install + return cfg + } + const after = parse(stripped) + if (after.install?.security?.scanner !== undefined || !Bun.deepEquals(prune(after), prune(structuredClone(original)))) { + console.log("::error file=bunfig.toml::Could not remove the security scanner from bunfig.toml cleanly; set it as `scanner = \"...\"` under [install.security].") + process.exit(1) + } + await Bun.write(process.env.BUNFIG_OUT, stripped) + ' + if [[ "$SOCKET_SCANNER" != "true" ]] && [[ -f bunfig.toml ]]; then + config="$RUNNER_TEMP/bunfig.toml" + rc=0 + BUNFIG_OUT="$config" bun --config=/dev/null -e "$strip_scanner" || rc=$? + if [[ $rc -eq 0 ]]; then + echo "::notice file=bunfig.toml::Security scanner skipped in CI. Pass socket-scanner: true to run it here; it still runs on local installs." + bun install --frozen-lockfile --config="$config" + exit + elif [[ $rc -ne 10 ]]; then + exit "$rc" + fi + fi + bun install --frozen-lockfile + + - name: Install NPM Dependencies (pnpm) + if: inputs.package-manager == 'pnpm' + run: pnpm install --frozen-lockfile - name: Detect package scripts id: scripts diff --git a/README.md b/README.md index b348c50..9b968dd 100644 --- a/README.md +++ b/README.md @@ -100,6 +100,40 @@ or `engines` in `package.json`. The lockfile (`bun.lock` or `pnpm-lock.yaml`) follows the package manager. `build-env` is a JSON object, visible in logs, so no secrets. +### Install-time security scanner (Socket) + +A bun site can set a security scanner in `bunfig.toml`, which checks every +package on install: + +```toml +[install.security] +scanner = "@socketsecurity/bun-security-scanner" +``` + +CI skips it by default. The scanner calls a third-party API on every install, +so a network blip or an API change could fail a build that otherwise passes, +and rerunning the same commit could give a different result. `site-ci.yml` +installs with a copy of `bunfig.toml` minus the scanner line (every other +setting, e.g. `minimumReleaseAge`, still applies) and leaves a notice in the +run. Local `bun install` reads `bunfig.toml` as is, so the scanner still runs +on your machine. + +To run it in CI too, pass `socket-scanner: true` to `site-ci.yml`. A workflow +that installs on its own (outside `site-ci.yml`) gets the same default from +the composite action: + +```yaml +- uses: oven-sh/setup-bun@ # +- uses: taraxvoid/voidflow/actions/bun-install@ # + # with: { socket-scanner: 'true' } +``` + +The copy is checked by parsing both files: it must equal your `bunfig.toml` +with only `install.security.scanner` removed. Set the scanner as a plain +`scanner = "..."` line (or the dotted `security.scanner` form); if it can't be +removed cleanly, e.g. an inline table, the install fails rather than run or +skip the scanner silently. pnpm sites are unaffected. + ## Workflow Types ### Validation / Checks diff --git a/actions/bun-install/action.yml b/actions/bun-install/action.yml new file mode 100644 index 0000000..bea786b --- /dev/null +++ b/actions/bun-install/action.yml @@ -0,0 +1,64 @@ +name: Bun install +description: > + Runs `bun install --frozen-lockfile`. By default it leaves out the + install-time security scanner set in bunfig.toml (`[install.security] + scanner`, e.g. Socket's), which calls a third-party API on every install and + can fail a build for reasons outside the repo. The scanner still runs on + local installs; set socket-scanner to 'true' to run it in CI as well. + +inputs: + socket-scanner: + description: > + 'true' runs the bunfig.toml security scanner during the CI install. + Default 'false' installs with a copy of bunfig.toml minus the scanner + (every other setting, e.g. minimumReleaseAge, still applies). + required: false + default: 'false' + +runs: + using: composite + steps: + - id: install + shell: bash + env: + SOCKET_SCANNER: ${{ inputs.socket-scanner }} + run: | + # Removes the scanner line as text, then checks the result with a real + # TOML parse: it must equal the original minus install.security.scanner. + # Anything that can't be removed that way fails the install instead of + # running the scanner, or skipping it, silently. + # shellcheck disable=SC2016 # JS source; the ${...} in it is for bun, not bash + strip_scanner=' + const src = await Bun.file("bunfig.toml").text() + const parse = (text) => { try { return Bun.TOML.parse(text) } catch (e) { console.log(`::error file=bunfig.toml::Could not parse bunfig.toml: ${e.message}`); process.exit(1) } } + const original = parse(src) + if (original.install?.security?.scanner === undefined) process.exit(10) + const key = /^\s*((install\.)?security\.)?("scanner"|scanner)\s*=/ + const stripped = src.split("\n").filter((line) => !key.test(line)).join("\n") + // empty tables left behind by the removal count as absent + const prune = (cfg) => { + delete cfg.install?.security?.scanner + if (cfg.install?.security && Object.keys(cfg.install.security).length === 0) delete cfg.install.security + if (cfg.install && Object.keys(cfg.install).length === 0) delete cfg.install + return cfg + } + const after = parse(stripped) + if (after.install?.security?.scanner !== undefined || !Bun.deepEquals(prune(after), prune(structuredClone(original)))) { + console.log("::error file=bunfig.toml::Could not remove the security scanner from bunfig.toml cleanly; set it as `scanner = \"...\"` under [install.security].") + process.exit(1) + } + await Bun.write(process.env.BUNFIG_OUT, stripped) + ' + if [[ "$SOCKET_SCANNER" != "true" ]] && [[ -f bunfig.toml ]]; then + config="$RUNNER_TEMP/bunfig.toml" + rc=0 + BUNFIG_OUT="$config" bun --config=/dev/null -e "$strip_scanner" || rc=$? + if [[ $rc -eq 0 ]]; then + echo "::notice file=bunfig.toml::Security scanner skipped in CI. Pass socket-scanner: true to run it here; it still runs on local installs." + bun install --frozen-lockfile --config="$config" + exit + elif [[ $rc -ne 10 ]]; then + exit "$rc" + fi + fi + bun install --frozen-lockfile diff --git a/test/bun-install.test.ts b/test/bun-install.test.ts new file mode 100644 index 0000000..bb9da24 --- /dev/null +++ b/test/bun-install.test.ts @@ -0,0 +1,205 @@ +import { describe, expect, test } from 'bun:test' +import { chmodSync, existsSync, mkdirSync, mkdtempSync, readFileSync, writeFileSync } from 'node:fs' +import { tmpdir } from 'node:os' +import { join } from 'node:path' + +const read = (p: string) => readFileSync(new URL(`../${p}`, import.meta.url), 'utf8') + +const actionScript: string = Bun.YAML.parse(read('actions/bun-install/action.yml')).runs.steps.find( + (s: any) => s.id === 'install', +).run +const siteCiSteps: any[] = Bun.YAML.parse(read('.github/workflows/site-ci.yml')).jobs.validate.steps +const workflowScript: string = siteCiSteps.find((s: any) => s.id === 'install').run + +const socketBunfig = `[install] +minimumReleaseAge = 259200 # 3 days + +[install.security] +scanner = "@socketsecurity/bun-security-scanner" +` + +// Runs the script in a temp repo with a stub `bun` that records its args and +// the config file it was pointed at. +async function install(script: string, opts: { bunfig?: string; scanner: boolean }) { + const root = mkdtempSync(join(tmpdir(), 'voidflow-bun-install-')) + const repo = join(root, 'repo') + const runnerTemp = join(root, 'runner-temp') + const bin = join(root, 'bin') + for (const d of [repo, runnerTemp, bin]) mkdirSync(d) + if (opts.bunfig !== undefined) writeFileSync(join(repo, 'bunfig.toml'), opts.bunfig) + const log = join(root, 'bun-args') + writeFileSync( + join(bin, 'bun'), + `#!/usr/bin/env bash +# the script's own \`bun -e\` (the TOML check) runs on the real bun; --config=/dev/null keeps the repo bunfig.toml out of it +if [[ " $* " == *" -e "* ]]; then exec ${JSON.stringify(process.execPath)} "$@"; fi +printf '%s\\n' "$@" > ${JSON.stringify(log)} +for a in "$@"; do [[ "$a" == --config=* ]] && cp "\${a#--config=}" ${JSON.stringify(log + '.config')}; done +exit 0 +`, + ) + chmodSync(join(bin, 'bun'), 0o755) + const proc = Bun.spawn(['bash', '-c', script], { + cwd: repo, + env: { PATH: `${bin}:${process.env.PATH}`, RUNNER_TEMP: runnerTemp, SOCKET_SCANNER: String(opts.scanner) }, + stdout: 'pipe', + stderr: 'pipe', + }) + const code = await proc.exited + const stdout = await new Response(proc.stdout).text() + const stderr = await new Response(proc.stderr).text() + return { + code, + stdout, + stderr, + args: existsSync(log) ? readFileSync(log, 'utf8').trim().split('\n') : null, + config: existsSync(log + '.config') ? readFileSync(log + '.config', 'utf8') : null, + bunfigAfter: opts.bunfig !== undefined ? readFileSync(join(repo, 'bunfig.toml'), 'utf8') : null, + } +} + +describe('site-ci.yml carries the same install script as actions/bun-install', () => { + test('scripts are identical (no drift)', () => { + expect(workflowScript).toBe(actionScript) + }) + + test('the inlined bun step only runs for bun; pnpm has its own install', () => { + expect(siteCiSteps.find((s: any) => s.id === 'install').if).toBe("inputs.package-manager == 'bun'") + const pnpm = siteCiSteps.find((s: any) => s.if === "inputs.package-manager == 'pnpm'" && /pnpm install/.test(s.run ?? '')) + expect(pnpm).toBeDefined() + }) + + test('socket-scanner defaults to off in both', () => { + expect(Bun.YAML.parse(read('actions/bun-install/action.yml')).inputs['socket-scanner'].default).toBe('false') + const wf = Bun.YAML.parse(read('.github/workflows/site-ci.yml')) + expect(wf.on.workflow_call.inputs['socket-scanner'].default).toBe(false) + }) + + test("voidflow's own ci.yml installs through the action", () => { + const steps = Bun.YAML.parse(read('.github/workflows/ci.yml')).jobs['unit-tests'].steps + expect(steps.some((s: any) => s.uses === './actions/bun-install')).toBe(true) + expect(steps.some((s: any) => /bun install/.test(s.run ?? ''))).toBe(false) + }) +}) + +describe.each([ + ['action', actionScript], + ['workflow', workflowScript], +])('%s script', (_name, script) => { + test('off by default: installs with a scanner-free copy of bunfig.toml, other settings kept', async () => { + const r = await install(script, { bunfig: socketBunfig, scanner: false }) + expect(r.code).toBe(0) + expect(r.args?.[0]).toBe('install') + expect(r.args).toContain('--frozen-lockfile') + expect(r.args?.some((a) => a.startsWith('--config='))).toBe(true) + expect(Bun.TOML.parse(r.config!)).toEqual({ install: { minimumReleaseAge: 259200, security: {} } }) + expect(r.stdout).toContain('::notice') + // the checkout's bunfig.toml is left untouched + expect(r.bunfigAfter).toBe(socketBunfig) + }) + + test('on: plain install, scanner left in place', async () => { + const r = await install(script, { bunfig: socketBunfig, scanner: true }) + expect(r).toMatchObject({ code: 0, args: ['install', '--frozen-lockfile'] }) + expect(r.stdout).not.toContain('::notice') + }) + + test.each([ + ['no bunfig.toml', undefined], + ['bunfig.toml without a scanner', '[install]\nminimumReleaseAge = 259200\n'], + ['a commented-out scanner', '[install.security]\n# scanner = "@socketsecurity/bun-security-scanner"\n'], + ])('off with %s: plain install, no notice', async (_label, bunfig) => { + const r = await install(script, { bunfig, scanner: false }) + expect(r).toMatchObject({ code: 0, args: ['install', '--frozen-lockfile'] }) + expect(r.stdout).not.toContain('::notice') + }) + + test.each([ + ['[install] security.scanner', '[install]\nsecurity.scanner = "x"\n'], + ['top-level install.security.scanner', 'install.security.scanner = "x"\n'], + ])('off strips the dotted form: %s', async (_label, bunfig) => { + const r = await install(script, { bunfig, scanner: false }) + expect(r.code).toBe(0) + expect(Bun.TOML.parse(r.config!).install?.security?.scanner).toBeUndefined() + }) + + test.each([ + ['an inline table', '[install]\nsecurity = { scanner = "x" }\n'], + ['a `scanner` key in another table too', '[install.security]\nscanner = "x"\n\n[other]\nscanner = "y"\n'], + ['invalid TOML', '[install.security\nscanner = "x"\n'], + ])('off fails closed when it cannot remove the scanner cleanly: %s', async (_label, bunfig) => { + const r = await install(script, { bunfig, scanner: false }) + expect(r.code).toBe(1) + expect(r.stdout).toContain('::error') + expect(r.args).toBeNull() + }) + + test('off only touches the scanner key, not other values that mention "scanner"', async () => { + const bunfig = `[install] +minimumReleaseAge = 259200 +minimumReleaseAgeExcludes = ["foo-scanner"] + +[install.security] +scanner = "@socketsecurity/bun-security-scanner" + +[other] +tool = { scanner = "y" } +` + const r = await install(script, { bunfig, scanner: false }) + expect(r.code).toBe(0) + expect(Bun.TOML.parse(r.config!)).toEqual({ + install: { minimumReleaseAge: 259200, minimumReleaseAgeExcludes: ['foo-scanner'], security: {} }, + other: { tool: { scanner: 'y' } }, + }) + }) +}) + +// End-to-end against the real bun: `--config=` must replace (not merge with) +// the repo's bunfig.toml, or the scanner would still run. A local fake scanner +// and a file: dependency keep this offline. +describe('real bun', () => { + function fixture(bunfig: string) { + const repo = mkdtempSync(join(tmpdir(), 'voidflow-bun-real-')) + mkdirSync(join(repo, 'node_modules/fake-scanner'), { recursive: true }) + mkdirSync(join(repo, 'dep')) + writeFileSync( + join(repo, 'node_modules/fake-scanner/package.json'), + '{"name":"fake-scanner","version":"1.0.0","type":"module","main":"index.js"}', + ) + writeFileSync( + join(repo, 'node_modules/fake-scanner/index.js'), + 'export const scanner = { version: "1", async scan() { console.error("FAKE SCANNER RAN"); return [] } }', + ) + writeFileSync(join(repo, 'dep/package.json'), '{"name":"dep","version":"1.0.0"}') + writeFileSync(join(repo, 'package.json'), '{"name":"fixture","dependencies":{"dep":"file:./dep"}}') + writeFileSync(join(repo, 'bunfig.toml'), bunfig) + // lockfile for --frozen-lockfile + Bun.spawnSync(['bun', 'install', '--config=/dev/null'], { cwd: repo }) + return repo + } + + async function run(scanner: boolean) { + const repo = fixture('[install.security]\nscanner = "fake-scanner"\n') + const runnerTemp = mkdtempSync(join(tmpdir(), 'voidflow-runner-temp-')) + const proc = Bun.spawn(['bash', '-c', actionScript], { + cwd: repo, + env: { PATH: process.env.PATH, HOME: process.env.HOME, RUNNER_TEMP: runnerTemp, SOCKET_SCANNER: String(scanner) }, + stdout: 'pipe', + stderr: 'pipe', + }) + const code = await proc.exited + return { code, output: (await new Response(proc.stdout).text()) + (await new Response(proc.stderr).text()) } + } + + test('off: the scanner does not run', async () => { + const r = await run(false) + expect(r.code).toBe(0) + expect(r.output).not.toContain('FAKE SCANNER RAN') + }) + + test('on: the scanner runs', async () => { + const r = await run(true) + expect(r.code).toBe(0) + expect(r.output).toContain('FAKE SCANNER RAN') + }) +}) From f84a0f63d2e7f2da6efe85dfaca5de7c676d3ca1 Mon Sep 17 00:00:00 2001 From: "Tara \\X/ Void" <1711810+taraxvoid@users.noreply.github.com> Date: Sun, 11 Oct 2026 21:31:25 -0700 Subject: [PATCH 14/21] feat!: run license-checker-rseidelsohn in check-licenses (#130) Settle on license-checker-rseidelsohn 5.x (final stable) instead of the 0.x @lizenz/checker. Its bin is `license-checker-rseidelsohn`, not `license-checker`, so check-licenses now invokes that name. BREAKING CHANGE: sites must replace their `@lizenz/checker` (or `license-checker`) devDependency with `license-checker-rseidelsohn`. Co-Authored-By: Claude Opus 5.5 Co-authored-by: Claude Opus 5.5 --- README.md | 6 ++++-- scripts/check-licenses.ts | 6 +++--- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 9b968dd..2deaca5 100644 --- a/README.md +++ b/README.md @@ -171,8 +171,10 @@ and add `.unlighthouse/` to the site's `.gitignore`. `site-ci.yml` runs `bun run check:licenses` when the lockfile or `scripts/` change. Sites implement that script with the shared check, which runs -`license-checker` in the site's repo root and fails on GPL, AGPL, SSPL and -similar copyleft licenses (LGPL is allowed). +`license-checker-rseidelsohn` in the site's repo root and fails on GPL, AGPL, +SSPL and similar copyleft licenses (LGPL is allowed). The site needs +`license-checker-rseidelsohn` as a devDependency so the bin resolves from its +own `node_modules`. With `@taraxvoid/voidflow` as a devDependency, call the bin from the site's `package.json`: diff --git a/scripts/check-licenses.ts b/scripts/check-licenses.ts index 3ded0dc..5758c92 100755 --- a/scripts/check-licenses.ts +++ b/scripts/check-licenses.ts @@ -2,7 +2,7 @@ /** * Shared license policy check for the Astro/bun site stack: fail on GPL kudzu. * - * Runs `license-checker` against the current working directory (the caller's + * Runs `license-checker-rseidelsohn` against the current working directory (the caller's * repo root) and exits non-zero if any installed package carries a blocked * license. * @@ -43,13 +43,13 @@ export const FAIL_ON = [ const result = spawnSync( 'bunx', - ['license-checker', '--summary', '--failOn', FAIL_ON], + ['license-checker-rseidelsohn', '--summary', '--failOn', FAIL_ON], { cwd: process.cwd(), stdio: 'inherit' }, ) if (result.error) { console.error( - `\ncheck:licenses failed - could not run license-checker: ${result.error.message}`, + `\ncheck:licenses failed - could not run license-checker-rseidelsohn: ${result.error.message}`, ) process.exit(1) } From 56239c2cb5885b41ee56530ca9d64e09b2b74f4b Mon Sep 17 00:00:00 2001 From: "ravenflight-releases[bot]" <330165707+ravenflight-releases[bot]@users.noreply.github.com> Date: Sun, 11 Oct 2026 21:31:43 -0700 Subject: [PATCH 15/21] chore(main): release 0.12.2 (#137) :robot: I have created a release *beep* *boop* --- ## [0.12.2](https://github.com/taraxvoid/voidflow/compare/v0.12.1...v0.12.2) (2026-10-12) ### Bug Fixes * **renovate:** drop Renovate security PRs in favor of Dependabot security updates ([#133](https://github.com/taraxvoid/voidflow/issues/133)) ([183877f](https://github.com/taraxvoid/voidflow/commit/183877f5f4eb091a7df6e09c333ffa609eaf8a81)) --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please). Co-authored-by: ravenflight-releases[bot] <330165707+ravenflight-releases[bot]@users.noreply.github.com> --- .release-please/main-manifest.json | 2 +- CHANGELOG.md | 7 +++++++ jsr.json | 2 +- package.json | 2 +- 4 files changed, 10 insertions(+), 3 deletions(-) diff --git a/.release-please/main-manifest.json b/.release-please/main-manifest.json index 38acea6..18eef3d 100644 --- a/.release-please/main-manifest.json +++ b/.release-please/main-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.12.1" + ".": "0.12.2" } diff --git a/CHANGELOG.md b/CHANGELOG.md index f6dc846..bee5ddd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,13 @@ All notable changes to this project are documented here, generated by [release-please](https://github.com/googleapis/release-please) from [Conventional Commits](https://www.conventionalcommits.org). +## [0.12.2](https://github.com/taraxvoid/voidflow/compare/v0.12.1...v0.12.2) (2026-10-12) + + +### Bug Fixes + +* **renovate:** drop Renovate security PRs in favor of Dependabot security updates ([#133](https://github.com/taraxvoid/voidflow/issues/133)) ([183877f](https://github.com/taraxvoid/voidflow/commit/183877f5f4eb091a7df6e09c333ffa609eaf8a81)) + ## [0.12.1](https://github.com/taraxvoid/voidflow/compare/v0.12.0...v0.12.1) (2026-10-06) diff --git a/jsr.json b/jsr.json index aca48ad..d5d2cef 100644 --- a/jsr.json +++ b/jsr.json @@ -1,6 +1,6 @@ { "name": "@taraxvoid/voidflow", - "version": "0.12.1", + "version": "0.12.2", "license": "MIT", "exports": { "./playwright": "./playwright/index.mjs" diff --git a/package.json b/package.json index 6f8a41e..514dd69 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@taraxvoid/voidflow", - "version": "0.12.1", + "version": "0.12.2", "description": "Shared workflows, actions and tooling for @taraxvoid sites", "license": "MIT", "keywords": [ From a0d8dd18a3d3efad3c33983d6ce587fd288c910f Mon Sep 17 00:00:00 2001 From: "Tara \\X/ Void" <1711810+taraxvoid@users.noreply.github.com> Date: Sun, 11 Oct 2026 21:40:08 -0700 Subject: [PATCH 16/21] fix: pin license-checker-rseidelsohn as a voidflow dependency (#138) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit check-licenses ran `bunx license-checker-rseidelsohn`, so the checker version was decided by each site, or fetched as the unpinned `latest` when the site lacked the devDependency. - Add `license-checker-rseidelsohn` `5.0.1` (final stable; `6.0.0-rc.1` is prerelease) as an exact-pinned dependency. - Resolve its bin from voidflow's install and run it with `process.execPath` instead of `bunx`. - Test guarding the exact pin and the absence of `bunx`. - README: sites no longer need their own devDependency. Note: 5.0.1's `--version` prints `4.4.2` (hardcoded upstream); the installed package is 5.0.1. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 5.5 --- README.md | 7 +- bun.lock | 277 ++++++++++++++++++++++++++++++++++++ package.json | 3 + scripts/check-licenses.ts | 21 ++- test/check-licenses.test.ts | 12 +- 5 files changed, 313 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 2deaca5..bacfe7c 100644 --- a/README.md +++ b/README.md @@ -172,9 +172,10 @@ and add `.unlighthouse/` to the site's `.gitignore`. `site-ci.yml` runs `bun run check:licenses` when the lockfile or `scripts/` change. Sites implement that script with the shared check, which runs `license-checker-rseidelsohn` in the site's repo root and fails on GPL, AGPL, -SSPL and similar copyleft licenses (LGPL is allowed). The site needs -`license-checker-rseidelsohn` as a devDependency so the bin resolves from its -own `node_modules`. +SSPL and similar copyleft licenses (LGPL is allowed). Voidflow pins the +checker to an exact version as its own dependency, so sites don't need to +install it and every site runs the same version (sites that added +`license-checker-rseidelsohn` as a devDependency for 0.12.2 can drop it). With `@taraxvoid/voidflow` as a devDependency, call the bin from the site's `package.json`: diff --git a/bun.lock b/bun.lock index d377394..78a63aa 100644 --- a/bun.lock +++ b/bun.lock @@ -4,6 +4,9 @@ "workspaces": { "": { "name": "@taraxvoid/voidflow", + "dependencies": { + "license-checker-rseidelsohn": "5.0.1", + }, "devDependencies": { "@playwright/test": "^1.40.0", "@socketsecurity/bun-security-scanner": "^1.1.3", @@ -14,12 +17,286 @@ }, }, "packages": { + "@gar/promise-retry": ["@gar/promise-retry@1.0.3", "", {}, "sha512-GmzA9ckNokPypTg10pgpeHNQe7ph+iIKKmhKu3Ob9ANkswreCx7R3cKmY781K8QK3AqVL3xVh9A42JvIAbkkSA=="], + + "@isaacs/fs-minipass": ["@isaacs/fs-minipass@4.0.1", "", { "dependencies": { "minipass": "^7.0.4" } }, "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w=="], + + "@isaacs/string-locale-compare": ["@isaacs/string-locale-compare@1.1.0", "", {}, "sha512-SQ7Kzhh9+D+ZW9MA0zkYv3VXhIDNx+LzM6EJ+/65I3QY+enU6Itte7E5XX7EWrqLW2FN4n06GWzBnPoC3th2aQ=="], + + "@npmcli/agent": ["@npmcli/agent@4.0.2", "", { "dependencies": { "agent-base": "^7.1.0", "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.1", "lru-cache": "^11.2.1", "socks-proxy-agent": "^8.0.3" } }, "sha512-EUEuWAxnL07Sp5/iC/1X6Xj+XThUvnbei9zfRWZdEXa7lss9RTHMhAHBeg+MZ5To9s/gGaSI+UwZTPdYMvKSeg=="], + + "@npmcli/arborist": ["@npmcli/arborist@9.6.0", "", { "dependencies": { "@gar/promise-retry": "^1.0.0", "@isaacs/string-locale-compare": "^1.1.0", "@npmcli/fs": "^5.0.0", "@npmcli/installed-package-contents": "^4.0.0", "@npmcli/map-workspaces": "^5.0.0", "@npmcli/metavuln-calculator": "^9.0.2", "@npmcli/name-from-folder": "^4.0.0", "@npmcli/node-gyp": "^5.0.0", "@npmcli/package-json": "^7.0.0", "@npmcli/query": "^5.0.0", "@npmcli/redact": "^4.0.0", "@npmcli/run-script": "^10.0.0", "bin-links": "^6.0.0", "cacache": "^20.0.1", "common-ancestor-path": "^2.0.0", "hosted-git-info": "^9.0.0", "json-stringify-nice": "^1.1.4", "lru-cache": "^11.2.1", "minimatch": "^10.0.3", "nopt": "^9.0.0", "npm-install-checks": "^8.0.0", "npm-package-arg": "^13.0.0", "npm-pick-manifest": "^11.0.1", "npm-registry-fetch": "^19.0.0", "pacote": "^21.0.2", "parse-conflict-json": "^5.0.1", "proc-log": "^6.0.0", "proggy": "^4.0.0", "promise-all-reject-late": "^1.0.0", "promise-call-limit": "^3.0.1", "semver": "^7.3.7", "ssri": "^13.0.0", "treeverse": "^3.0.0", "walk-up-path": "^4.0.0" }, "bin": { "arborist": "bin/index.js" } }, "sha512-Dku9UWbrrX+UCu8rQ1obGKaQAL4kwdt3hHCNXrd0n0R/4B8oq3CzloUAShwFjfsAGM6KY27gPuNftOUEZ4nhOw=="], + + "@npmcli/fs": ["@npmcli/fs@5.0.0", "", { "dependencies": { "semver": "^7.3.5" } }, "sha512-7OsC1gNORBEawOa5+j2pXN9vsicaIOH5cPXxoR6fJOmH6/EXpJB2CajXOu1fPRFun2m1lktEFX11+P89hqO/og=="], + + "@npmcli/git": ["@npmcli/git@7.0.2", "", { "dependencies": { "@gar/promise-retry": "^1.0.0", "@npmcli/promise-spawn": "^9.0.0", "ini": "^6.0.0", "lru-cache": "^11.2.1", "npm-pick-manifest": "^11.0.1", "proc-log": "^6.0.0", "semver": "^7.3.5", "which": "^6.0.0" } }, "sha512-oeolHDjExNAJAnlYP2qzNjMX/Xi9bmu78C9dIGr4xjobrSKbuMYCph8lTzn4vnW3NjIqVmw/f8BCfouqyJXlRg=="], + + "@npmcli/installed-package-contents": ["@npmcli/installed-package-contents@4.0.0", "", { "dependencies": { "npm-bundled": "^5.0.0", "npm-normalize-package-bin": "^5.0.0" }, "bin": { "installed-package-contents": "bin/index.js" } }, "sha512-yNyAdkBxB72gtZ4GrwXCM0ZUedo9nIbOMKfGjt6Cu6DXf0p8y1PViZAKDC8q8kv/fufx0WTjRBdSlyrvnP7hmA=="], + + "@npmcli/map-workspaces": ["@npmcli/map-workspaces@5.0.3", "", { "dependencies": { "@npmcli/name-from-folder": "^4.0.0", "@npmcli/package-json": "^7.0.0", "glob": "^13.0.0", "minimatch": "^10.0.3" } }, "sha512-o2grssXo1e774E5OtEwwrgoszYRh0lqkJH+Pb9r78UcqdGJRDRfhpM8DvZPjzNLLNYeD/rNbjOKM3Ss5UABROw=="], + + "@npmcli/metavuln-calculator": ["@npmcli/metavuln-calculator@9.0.3", "", { "dependencies": { "cacache": "^20.0.0", "json-parse-even-better-errors": "^5.0.0", "pacote": "^21.0.0", "proc-log": "^6.0.0", "semver": "^7.3.5" } }, "sha512-94GLSYhLXF2t2LAC7pDwLaM4uCARzxShyAQKsirmlNcpidH89VA4/+K1LbJmRMgz5gy65E/QBBWQdUvGLe2Frg=="], + + "@npmcli/name-from-folder": ["@npmcli/name-from-folder@4.0.0", "", {}, "sha512-qfrhVlOSqmKM8i6rkNdZzABj8MKEITGFAY+4teqBziksCQAOLutiAxM1wY2BKEd8KjUSpWmWCYxvXr0y4VTlPg=="], + + "@npmcli/node-gyp": ["@npmcli/node-gyp@5.0.0", "", {}, "sha512-uuG5HZFXLfyFKqg8QypsmgLQW7smiRjVc45bqD/ofZZcR/uxEjgQU8qDPv0s9TEeMUiAAU/GC5bR6++UdTirIQ=="], + + "@npmcli/package-json": ["@npmcli/package-json@7.0.5", "", { "dependencies": { "@npmcli/git": "^7.0.0", "glob": "^13.0.0", "hosted-git-info": "^9.0.0", "json-parse-even-better-errors": "^5.0.0", "proc-log": "^6.0.0", "semver": "^7.5.3", "spdx-expression-parse": "^4.0.0" } }, "sha512-iVuTlG3ORq2iaVa1IWUxAO/jIp77tUKBhoMjuzYW2kL4MLN1bi/ofqkZ7D7OOwh8coAx1/S2ge0rMdGv8sLSOQ=="], + + "@npmcli/promise-spawn": ["@npmcli/promise-spawn@9.0.1", "", { "dependencies": { "which": "^6.0.0" } }, "sha512-OLUaoqBuyxeTqUvjA3FZFiXUfYC1alp3Sa99gW3EUDz3tZ3CbXDdcZ7qWKBzicrJleIgucoWamWH1saAmH/l2Q=="], + + "@npmcli/query": ["@npmcli/query@5.0.0", "", { "dependencies": { "postcss-selector-parser": "^7.0.0" } }, "sha512-8TZWfTQOsODpLqo9SVhVjHovmKXNpevHU0gO9e+y4V4fRIOneiXy0u0sMP9LmS71XivrEWfZWg50ReH4WRT4aQ=="], + + "@npmcli/redact": ["@npmcli/redact@4.0.0", "", {}, "sha512-gOBg5YHMfZy+TfHArfVogwgfBeQnKbbGo3pSUyK/gSI0AVu+pEiDVcKlQb0D8Mg1LNRZILZ6XG8I5dJ4KuAd9Q=="], + + "@npmcli/run-script": ["@npmcli/run-script@10.0.4", "", { "dependencies": { "@npmcli/node-gyp": "^5.0.0", "@npmcli/package-json": "^7.0.0", "@npmcli/promise-spawn": "^9.0.0", "node-gyp": "^12.1.0", "proc-log": "^6.0.0" } }, "sha512-mGUWr1uMnf0le2TwfOZY4SFxZGXGfm4Jtay/nwAa2FLNAKXUoUwaGwBMNH36UHPtinWfTSJ3nqFQr0091CxVGg=="], + "@playwright/test": ["@playwright/test@1.63.0", "", { "dependencies": { "playwright": "1.63.0" }, "bin": { "playwright": "cli.js" } }, "sha512-oxMK4vllB9RK5NQ2l1pq1IfOf2AvnEuj/vYGDj0H2nMtmtZpKtCwt/l00GEO6xjGfpBNAvjovvYdCm50dRQkpQ=="], + "@sigstore/bundle": ["@sigstore/bundle@4.0.0", "", { "dependencies": { "@sigstore/protobuf-specs": "^0.5.0" } }, "sha512-NwCl5Y0V6Di0NexvkTqdoVfmjTaQwoLM236r89KEojGmq/jMls8S+zb7yOwAPdXvbwfKDlP+lmXgAL4vKSQT+A=="], + + "@sigstore/core": ["@sigstore/core@3.2.1", "", {}, "sha512-qRsxPnCrbC/puegGxKuynfnxgLiHqWStrSjxkoB4YKqq3Z3s4cyZyj42ZdWFAEblNP65C+rBH8EuREHIXoi83g=="], + + "@sigstore/protobuf-specs": ["@sigstore/protobuf-specs@0.5.2", "", {}, "sha512-SQqvFMt4V78fdjcDdYX6HbiVSOR4QK3ZgwCa2KOsopAgPIHy1rU5UDUmzLl02r5oyyaYcYHR1hpwDRk/yUe+Mw=="], + + "@sigstore/sign": ["@sigstore/sign@4.1.1", "", { "dependencies": { "@gar/promise-retry": "^1.0.2", "@sigstore/bundle": "^4.0.0", "@sigstore/core": "^3.2.0", "@sigstore/protobuf-specs": "^0.5.0", "make-fetch-happen": "^15.0.4", "proc-log": "^6.1.0" } }, "sha512-Hf4xglukg0XXQ2RiD5vSoLjdPe8OBUPA8XeVjUObheuDcWdYWrnH/BNmxZCzkAy68MzmNCxXLeurJvs6hcP2OQ=="], + + "@sigstore/tuf": ["@sigstore/tuf@4.0.2", "", { "dependencies": { "@sigstore/protobuf-specs": "^0.5.0", "tuf-js": "^4.1.0" } }, "sha512-TCAzTy0xzdP79EnxSjq9KQ3eaR7+FmudLC6eRKknVKZbV7ZNlGLClAAQb/HMNJ5n2OBNk2GT1tEmU0xuPr+SLQ=="], + + "@sigstore/verify": ["@sigstore/verify@3.1.1", "", { "dependencies": { "@sigstore/bundle": "^4.0.0", "@sigstore/core": "^3.2.1", "@sigstore/protobuf-specs": "^0.5.0" } }, "sha512-qv7+G3J2cc6wwFj3yKvXOamzqhMwSk1ogPGmhpS8iXllcPrJaIIBA+4HbttlHVu1pqWTdmaCH/WE7UOC51kdoA=="], + "@socketsecurity/bun-security-scanner": ["@socketsecurity/bun-security-scanner@1.1.3", "", {}, "sha512-etUBYn5rx/ubI2JaAplX/Dr48X4uufcXVxvcVp68DZFne+ROlUQTcEw7pJFgq6UY4PWbChvhWnoNQBnFWwXyEA=="], + "@tufjs/canonical-json": ["@tufjs/canonical-json@2.0.0", "", {}, "sha512-yVtV8zsdo8qFHe+/3kw81dSLyF7D576A5cCFCi4X7B39tWT7SekaEFUnvnWJHz+9qO7qJTah1JbrDjWKqFtdWA=="], + + "@tufjs/models": ["@tufjs/models@4.1.0", "", { "dependencies": { "@tufjs/canonical-json": "2.0.0", "minimatch": "^10.1.1" } }, "sha512-Y8cK9aggNRsqJVaKUlEYs4s7CvQ1b1ta2DVPyAimb0I2qhzjNk+A+mxvll/klL0RlfuIUei8BF7YWiua4kQqww=="], + + "abbrev": ["abbrev@2.0.0", "", {}, "sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ=="], + + "agent-base": ["agent-base@7.1.4", "", {}, "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ=="], + + "ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], + + "array-find-index": ["array-find-index@1.0.2", "", {}, "sha512-M1HQyIXcBGtVywBt8WVdim+lrNaK7VHp99Qt5pSNziXznKHViIBbXWtfRTpEFpF/c4FdfxNAsCCwPp5phBYJtw=="], + + "balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="], + + "bin-links": ["bin-links@6.0.2", "", { "dependencies": { "cmd-shim": "^8.0.0", "npm-normalize-package-bin": "^5.0.0", "proc-log": "^6.0.0", "read-cmd-shim": "^6.0.0", "write-file-atomic": "^7.0.0" } }, "sha512-frE1t78WOwJ45PKV2cF2tNPjTcs9L1J9s6VkrV59wanRP4GlaomuxYPVma7BwthMg8WnfSory4w5PTE6FZZ81w=="], + + "brace-expansion": ["brace-expansion@5.0.12", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ=="], + + "cacache": ["cacache@20.0.4", "", { "dependencies": { "@npmcli/fs": "^5.0.0", "fs-minipass": "^3.0.0", "glob": "^13.0.0", "lru-cache": "^11.1.0", "minipass": "^7.0.3", "minipass-collect": "^2.0.1", "minipass-flush": "^1.0.5", "minipass-pipeline": "^1.2.4", "p-map": "^7.0.2", "ssri": "^13.0.0" } }, "sha512-M3Lab8NPYlZU2exsL3bMVvMrMqgwCnMWfdZbK28bn3pK6APT/Te/I8hjRPNu1uwORY9a1eEQoifXbKPQMfMTOA=="], + + "chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], + + "chownr": ["chownr@3.0.0", "", {}, "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g=="], + + "cmd-shim": ["cmd-shim@8.0.0", "", {}, "sha512-Jk/BK6NCapZ58BKUxlSI+ouKRbjH1NLZCgJkYoab+vEHUY3f6OzpNBN9u7HFSv9J6TRDGs4PLOHezoKGaFRSCA=="], + + "color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="], + + "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], + + "common-ancestor-path": ["common-ancestor-path@2.0.0", "", {}, "sha512-dnN3ibLeoRf2HNC+OlCiNc5d2zxbLJXOtiZUudNFSXZrNSydxcCsSpRzXwfu7BBWCIfHPw+xTayeBvJCP/D8Ng=="], + + "content-type": ["content-type@2.1.0", "", {}, "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag=="], + + "cssesc": ["cssesc@3.0.0", "", { "bin": { "cssesc": "bin/cssesc" } }, "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg=="], + + "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" }, "peerDependencies": { "supports-color": "*" }, "optionalPeers": ["supports-color"] }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], + + "env-paths": ["env-paths@2.2.1", "", {}, "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A=="], + + "exponential-backoff": ["exponential-backoff@3.1.3", "", {}, "sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA=="], + + "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="], + + "fs-minipass": ["fs-minipass@3.0.3", "", { "dependencies": { "minipass": "^7.0.3" } }, "sha512-XUBA9XClHbnJWSfBzjkm6RvPsyg3sryZt06BEQoXcF7EK/xpGaQYJgQKDJSUH5SGZ76Y7pFx1QBnXz09rU5Fbw=="], + + "glob": ["glob@13.0.6", "", { "dependencies": { "minimatch": "^10.2.2", "minipass": "^7.1.3", "path-scurry": "^2.0.2" } }, "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw=="], + + "graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="], + + "has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="], + + "hosted-git-info": ["hosted-git-info@9.0.3", "", { "dependencies": { "lru-cache": "^11.1.0" } }, "sha512-Hc+ghLoSt6QaYZUv0WBiIvmMDZuZZ7oaDvdH8MbfOO4lOsxdXLEvuC6ePoGs9H1X9oCLyq6+NVN0MKqD+ydxyg=="], + + "http-cache-semantics": ["http-cache-semantics@4.3.0", "", {}, "sha512-M5t5LlJpS1UHMjvwRQVdFHvPISGeLAxNcrWuJkeGh0KxsqCHZ1O3NXZU/8x7cD0BDcGW8kapxMKTvwlqrNkHkA=="], + + "http-proxy-agent": ["http-proxy-agent@7.0.2", "", { "dependencies": { "agent-base": "^7.1.0", "debug": "^4.3.4" } }, "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig=="], + + "https-proxy-agent": ["https-proxy-agent@7.0.6", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "4" } }, "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw=="], + + "iconv-lite": ["iconv-lite@0.7.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ=="], + + "ignore-walk": ["ignore-walk@8.0.0", "", { "dependencies": { "minimatch": "^10.0.3" } }, "sha512-FCeMZT4NiRQGh+YkeKMtWrOmBgWjHjMJ26WQWrRQyoyzqevdaGSakUaJW5xQYmjLlUVk2qUnCjYVBax9EKKg8A=="], + + "ini": ["ini@6.0.0", "", {}, "sha512-IBTdIkzZNOpqm7q3dRqJvMaldXjDHWkEDfrwGEQTs5eaQMWV+djAhR+wahyNNMAa+qpbDUhBMVt4ZKNwpPm7xQ=="], + + "ip-address": ["ip-address@10.7.3", "", {}, "sha512-A1kdq/tSb5QjvKvAMgIoEvDBIgL7qaqVP/jkvSwYYRZ9iEzvPpopxp2wQfu3SuZRHtpHNxMn8Fs0bS+gf5Xmwg=="], + + "isexe": ["isexe@4.0.0", "", {}, "sha512-FFUtZMpoZ8RqHS3XeXEmHWLA4thH+ZxCv2lOiPIn1Xc7CxrqhWzNSDzD+/chS/zbYezmiwWLdQC09JdQKmthOw=="], + + "json-parse-even-better-errors": ["json-parse-even-better-errors@5.0.0", "", {}, "sha512-ZF1nxZ28VhQouRWhUcVlUIN3qwSgPuswK05s/HIaoetAoE/9tngVmCHjSxmSQPav1nd+lPtTL0YZ/2AFdR/iYQ=="], + + "json-stringify-nice": ["json-stringify-nice@1.1.4", "", {}, "sha512-5Z5RFW63yxReJ7vANgW6eZFGWaQvnPE3WNmZoOJrSkGju2etKA2L5rrOa1sm877TVTFt57A80BH1bArcmlLfPw=="], + + "jsonparse": ["jsonparse@1.3.1", "", {}, "sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg=="], + + "just-diff": ["just-diff@6.0.2", "", {}, "sha512-S59eriX5u3/QhMNq3v/gm8Kd0w8OS6Tz2FS1NG4blv+z0MuQcBRJyFWjdovM0Rad4/P4aUPFtnkNjMjyMlMSYA=="], + + "just-diff-apply": ["just-diff-apply@5.5.0", "", {}, "sha512-OYTthRfSh55WOItVqwpefPtNt2VdKsq5AnAK6apdtR6yCH8pr0CmSr710J0Mf+WdQy7K/OzMy7K2MgAfdQURDw=="], + + "license-checker-rseidelsohn": ["license-checker-rseidelsohn@5.0.1", "", { "dependencies": { "@npmcli/arborist": "9.6.0", "@npmcli/package-json": "7.0.5", "chalk": "4.1.2", "debug": "^4.3.4", "lodash.clonedeep": "^4.5.0", "mkdirp": "^1.0.4", "nopt": "^7.2.0", "semver": "^7.3.5", "spdx-correct": "^3.2.0", "spdx-expression-parse": "^4.0.0", "spdx-satisfies": "^6.0.0", "treeify": "^1.1.0" }, "bin": { "license-checker-rseidelsohn": "bin/license-checker-rseidelsohn.js" } }, "sha512-9X+ikKxt9Hy3zOrOZzW1dXL4St5akoYjLt63Am9JZVzU6aTdN+xfDvqySpnJT+gF/h5RmtMk2waW6TDNNCKbqQ=="], + + "lodash.clonedeep": ["lodash.clonedeep@4.5.0", "", {}, "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ=="], + + "lru-cache": ["lru-cache@11.5.3", "", {}, "sha512-U4N8FgzmWxc8k1VH8Kr6lQg18U7Fjvby6wXHVRX/ZZ7IwWbRMgrRbP0Wrb5q5NVinryp4SQampHKdvtecItxUg=="], + + "make-fetch-happen": ["make-fetch-happen@15.0.6", "", { "dependencies": { "@gar/promise-retry": "^1.0.0", "@npmcli/agent": "^4.0.0", "@npmcli/redact": "^4.0.0", "cacache": "^20.0.1", "http-cache-semantics": "^4.1.1", "minipass": "^7.0.2", "minipass-fetch": "^5.0.0", "minipass-flush": "^1.0.5", "minipass-pipeline": "^1.2.4", "negotiator": "^1.0.0", "proc-log": "^6.0.0", "ssri": "^13.0.0" } }, "sha512-Je0fLJ0F5atA7F+eIlLzk+Wkcl57JDf4kf+EW8xiP5E31xOQxkIxTbgf1Oi1Lw9tRI9UEMRdI5Vz2xTzoNU1Jw=="], + + "minimatch": ["minimatch@10.2.6", "", { "dependencies": { "brace-expansion": "^5.0.8" } }, "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A=="], + + "minipass": ["minipass@7.1.3", "", {}, "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A=="], + + "minipass-collect": ["minipass-collect@2.0.1", "", { "dependencies": { "minipass": "^7.0.3" } }, "sha512-D7V8PO9oaz7PWGLbCACuI1qEOsq7UKfLotx/C0Aet43fCUB/wfQ7DYeq2oR/svFJGYDHPr38SHATeaj/ZoKHKw=="], + + "minipass-fetch": ["minipass-fetch@5.0.2", "", { "dependencies": { "minipass": "^7.0.3", "minipass-sized": "^2.0.0", "minizlib": "^3.0.1" }, "optionalDependencies": { "iconv-lite": "^0.7.2" } }, "sha512-2d0q2a8eCi2IRg/IGubCNRJoYbA1+YPXAzQVRFmB45gdGZafyivnZ5YSEfo3JikbjGxOdntGFvBQGqaSMXlAFQ=="], + + "minipass-flush": ["minipass-flush@1.0.7", "", { "dependencies": { "minipass": "^3.0.0" } }, "sha512-TbqTz9cUwWyHS2Dy89P3ocAGUGxKjjLuR9z8w4WUTGAVgEj17/4nhgo2Du56i0Fm3Pm30g4iA8Lcqctc76jCzA=="], + + "minipass-pipeline": ["minipass-pipeline@1.2.4", "", { "dependencies": { "minipass": "^3.0.0" } }, "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A=="], + + "minipass-sized": ["minipass-sized@2.0.0", "", { "dependencies": { "minipass": "^7.1.2" } }, "sha512-zSsHhto5BcUVM2m1LurnXY6M//cGhVaegT71OfOXoprxT6o780GZd792ea6FfrQkuU4usHZIUczAQMRUE2plzA=="], + + "minizlib": ["minizlib@3.1.0", "", { "dependencies": { "minipass": "^7.1.2" } }, "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw=="], + + "mkdirp": ["mkdirp@1.0.4", "", { "bin": { "mkdirp": "bin/cmd.js" } }, "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw=="], + + "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + + "negotiator": ["negotiator@1.1.0", "", { "dependencies": { "content-type": "^2.1.0" } }, "sha512-NMPBRMJgiQHjbd8phG3Vebdx4kZ1H121rbl5IkMqeOsahptB9BKo/d7oJ3zTXqTgagn2bWlNSXkh0QUGM31RYg=="], + + "node-gyp": ["node-gyp@12.4.0", "", { "dependencies": { "env-paths": "^2.2.0", "exponential-backoff": "^3.1.1", "graceful-fs": "^4.2.6", "nopt": "^9.0.0", "proc-log": "^6.0.0", "semver": "^7.3.5", "tar": "^7.5.4", "tinyglobby": "^0.2.12", "undici": "^6.25.0", "which": "^6.0.0" }, "bin": { "node-gyp": "bin/node-gyp.js" } }, "sha512-OMcPNvqTCFUnNaBlmdgq+lfNqY7gTiSmNRDjY3uAXRyudeKZEZxu3CLtjMQrx4zZxCX2b/mpNqTtwuCJgXhHkw=="], + + "nopt": ["nopt@7.2.1", "", { "dependencies": { "abbrev": "^2.0.0" }, "bin": { "nopt": "bin/nopt.js" } }, "sha512-taM24ViiimT/XntxbPyJQzCG+p4EKOpgD3mxFwW38mGjVUrfERQOeY4EDHjdnptttfHuHQXFx+lTP08Q+mLa/w=="], + + "npm-bundled": ["npm-bundled@5.0.0", "", { "dependencies": { "npm-normalize-package-bin": "^5.0.0" } }, "sha512-JLSpbzh6UUXIEoqPsYBvVNVmyrjVZ1fzEFbqxKkTJQkWBO3xFzFT+KDnSKQWwOQNbuWRwt5LSD6HOTLGIWzfrw=="], + + "npm-install-checks": ["npm-install-checks@8.0.0", "", { "dependencies": { "semver": "^7.1.1" } }, "sha512-ScAUdMpyzkbpxoNekQ3tNRdFI8SJ86wgKZSQZdUxT+bj0wVFpsEMWnkXP0twVe1gJyNF5apBWDJhhIbgrIViRA=="], + + "npm-normalize-package-bin": ["npm-normalize-package-bin@5.0.0", "", {}, "sha512-CJi3OS4JLsNMmr2u07OJlhcrPxCeOeP/4xq67aWNai6TNWWbTrlNDgl8NcFKVlcBKp18GPj+EzbNIgrBfZhsag=="], + + "npm-package-arg": ["npm-package-arg@13.0.2", "", { "dependencies": { "hosted-git-info": "^9.0.0", "proc-log": "^6.0.0", "semver": "^7.3.5", "validate-npm-package-name": "^7.0.0" } }, "sha512-IciCE3SY3uE84Ld8WZU23gAPPV9rIYod4F+rc+vJ7h7cwAJt9Vk6TVsK60ry7Uj3SRS3bqRRIGuTp9YVlk6WNA=="], + + "npm-packlist": ["npm-packlist@10.0.4", "", { "dependencies": { "ignore-walk": "^8.0.0", "proc-log": "^6.0.0" } }, "sha512-uMW73iajD8hiH4ZBxEV3HC+eTnppIqwakjOYuvgddnalIw2lJguKviK1pcUJDlIWm1wSJkchpDZDSVVsZEYRng=="], + + "npm-pick-manifest": ["npm-pick-manifest@11.0.3", "", { "dependencies": { "npm-install-checks": "^8.0.0", "npm-normalize-package-bin": "^5.0.0", "npm-package-arg": "^13.0.0", "semver": "^7.3.5" } }, "sha512-buzyCfeoGY/PxKqmBqn1IUJrZnUi1VVJTdSSRPGI60tJdUhUoSQFhs0zycJokDdOznQentgrpf8LayEHyyYlqQ=="], + + "npm-registry-fetch": ["npm-registry-fetch@19.1.1", "", { "dependencies": { "@npmcli/redact": "^4.0.0", "jsonparse": "^1.3.1", "make-fetch-happen": "^15.0.0", "minipass": "^7.0.2", "minipass-fetch": "^5.0.0", "minizlib": "^3.0.1", "npm-package-arg": "^13.0.0", "proc-log": "^6.0.0" } }, "sha512-TakBap6OM1w0H73VZVDf44iFXsOS3h+L4wVMXmbWOQroZgFhMch0juN6XSzBNlD965yIKvWg2dfu7NSiaYLxtw=="], + + "p-map": ["p-map@7.0.8", "", {}, "sha512-MitaVsCuCFIvOLLPIU7NnfrZvS9H9h7kwMUkDo+T2pEISaJD48IV9S8iIdXB7PsvvdxyYcsSTTrr90XKsbulNw=="], + + "pacote": ["pacote@21.5.1", "", { "dependencies": { "@gar/promise-retry": "^1.0.0", "@npmcli/git": "^7.0.0", "@npmcli/installed-package-contents": "^4.0.0", "@npmcli/package-json": "^7.0.0", "@npmcli/promise-spawn": "^9.0.0", "@npmcli/run-script": "^10.0.0", "cacache": "^20.0.0", "fs-minipass": "^3.0.0", "minipass": "^7.0.2", "npm-package-arg": "^13.0.0", "npm-packlist": "^10.0.1", "npm-pick-manifest": "^11.0.1", "npm-registry-fetch": "^19.0.0", "proc-log": "^6.0.0", "sigstore": "^4.0.0", "ssri": "^13.0.0", "tar": "^7.4.3" }, "bin": { "pacote": "bin/index.js" } }, "sha512-KvcJ9iy3crysCsgqc4+PknH/w6jkrp8JN36mpZBPwNaDRwTfMZD37YzRazNstiZUOhuF5pno9f78n9mEJBavwg=="], + + "parse-conflict-json": ["parse-conflict-json@5.0.1", "", { "dependencies": { "json-parse-even-better-errors": "^5.0.0", "just-diff": "^6.0.0", "just-diff-apply": "^5.2.0" } }, "sha512-ZHEmNKMq1wyJXNwLxyHnluPfRAFSIliBvbK/UiOceROt4Xh9Pz0fq49NytIaeaCUf5VR86hwQ/34FCcNU5/LKQ=="], + + "path-scurry": ["path-scurry@2.0.2", "", { "dependencies": { "lru-cache": "^11.0.0", "minipass": "^7.1.2" } }, "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg=="], + + "picomatch": ["picomatch@4.0.7", "", {}, "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA=="], + "playwright": ["playwright@1.63.0", "", { "dependencies": { "playwright-core": "1.63.0" }, "bin": { "playwright": "cli.js" } }, "sha512-+7ziBLidS4NaNCdt57SUDT+wYmmd5fmiQejUic/kb+YsYSCPyOOE9sebzMjNmQrsnNpDJqd4WHvV/8lfKfUDUg=="], "playwright-core": ["playwright-core@1.63.0", "", { "bin": { "playwright-core": "cli.js" } }, "sha512-rYCsBF/M5HjUch52bbtVONEFjv6Xu8sm8h72dNlR5bzIE1fvC/bxgspzkjSfU+MweEMmPM8KJebG6nnyxo5mCg=="], + + "postcss-selector-parser": ["postcss-selector-parser@7.1.6", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-7qASPzhKF2l2KLboRZux8CCTRMdGiV08vWmyKzPz22qZ7ZjQBOeY7rNzNoCLSUiftJ7HUq0GERHmxw/t0dCdMw=="], + + "proc-log": ["proc-log@6.1.0", "", {}, "sha512-iG+GYldRf2BQ0UDUAd6JQ/RwzaQy6mXmsk/IzlYyal4A4SNFw54MeH4/tLkF4I5WoWG9SQwuqWzS99jaFQHBuQ=="], + + "proggy": ["proggy@4.0.0", "", {}, "sha512-MbA4R+WQT76ZBm/5JUpV9yqcJt92175+Y0Bodg3HgiXzrmKu7Ggq+bpn6y6wHH+gN9NcyKn3yg1+d47VaKwNAQ=="], + + "promise-all-reject-late": ["promise-all-reject-late@1.0.1", "", {}, "sha512-vuf0Lf0lOxyQREH7GDIOUMLS7kz+gs8i6B+Yi8dC68a2sychGrHTJYghMBD6k7eUcH0H5P73EckCA48xijWqXw=="], + + "promise-call-limit": ["promise-call-limit@3.0.2", "", {}, "sha512-mRPQO2T1QQVw11E7+UdCJu7S61eJVWknzml9sC1heAdj1jxl0fWMBypIt9ZOcLFf8FkG995ZD7RnVk7HH72fZw=="], + + "read-cmd-shim": ["read-cmd-shim@6.0.0", "", {}, "sha512-1zM5HuOfagXCBWMN83fuFI/x+T/UhZ7k+KIzhrHXcQoeX5+7gmaDYjELQHmmzIodumBHeByBJT4QYS7ufAgs7A=="], + + "safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="], + + "semver": ["semver@7.8.5", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA=="], + + "signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="], + + "sigstore": ["sigstore@4.1.1", "", { "dependencies": { "@sigstore/bundle": "^4.0.0", "@sigstore/core": "^3.2.1", "@sigstore/protobuf-specs": "^0.5.0", "@sigstore/sign": "^4.1.1", "@sigstore/tuf": "^4.0.2", "@sigstore/verify": "^3.1.1" } }, "sha512-endqECJkfhozrXMK5ngu/UAA0xVcVEFdnHJCElGaExypjW+HK5i6zu3NteLoaX/iFbRUbC3+DjttQs0GARr+5w=="], + + "smart-buffer": ["smart-buffer@4.2.0", "", {}, "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg=="], + + "socks": ["socks@2.8.10", "", { "dependencies": { "ip-address": "^10.1.1", "smart-buffer": "^4.2.0" } }, "sha512-e0VyvkVTwVYViNovRkZ9aodhxVlyoMn7eJhVUPxZ+eK9P/7CBkxvvsBOHqFPEH416726W8tLXXXjKwqgTErrCQ=="], + + "socks-proxy-agent": ["socks-proxy-agent@8.0.5", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "^4.3.4", "socks": "^2.8.3" } }, "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw=="], + + "spdx-compare": ["spdx-compare@1.0.0", "", { "dependencies": { "array-find-index": "^1.0.2", "spdx-expression-parse": "^3.0.0", "spdx-ranges": "^2.0.0" } }, "sha512-C1mDZOX0hnu0ep9dfmuoi03+eOdDoz2yvK79RxbcrVEG1NO1Ph35yW102DHWKN4pk80nwCgeMmSY5L25VE4D9A=="], + + "spdx-correct": ["spdx-correct@3.2.0", "", { "dependencies": { "spdx-expression-parse": "^3.0.0", "spdx-license-ids": "^3.0.0" } }, "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA=="], + + "spdx-exceptions": ["spdx-exceptions@2.5.0", "", {}, "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w=="], + + "spdx-expression-parse": ["spdx-expression-parse@4.0.0", "", { "dependencies": { "spdx-exceptions": "^2.1.0", "spdx-license-ids": "^3.0.0" } }, "sha512-Clya5JIij/7C6bRR22+tnGXbc4VKlibKSVj2iHvVeX5iMW7s1SIQlqu699JkODJJIhh/pUu8L0/VLh8xflD+LQ=="], + + "spdx-license-ids": ["spdx-license-ids@3.0.24", "", {}, "sha512-cLS9TtWkIQFyLkJ3/5aFQAOHOSKTlOs/7WDut/XPSdjom1fJhUdkepeJAKXa9Y05+FabHd0sti+Et559vDtkpQ=="], + + "spdx-ranges": ["spdx-ranges@2.1.1", "", {}, "sha512-mcdpQFV7UDAgLpXEE/jOMqvK4LBoO0uTQg0uvXUewmEFhpiZx5yJSZITHB8w1ZahKdhfZqP5GPEOKLyEq5p8XA=="], + + "spdx-satisfies": ["spdx-satisfies@6.0.0", "", { "dependencies": { "spdx-compare": "^1.0.0", "spdx-expression-parse": "^3.0.0", "spdx-ranges": "^2.0.0" } }, "sha512-oOWQocnRbFVtBnBITfFgzjhnOklHossTvI+6C1hB2slvp3HgTsfru5wuo8HY2rQpwSm5JuIhNzIuqOfR5IuojQ=="], + + "ssri": ["ssri@13.0.1", "", { "dependencies": { "minipass": "^7.0.3" } }, "sha512-QUiRf1+u9wPTL/76GTYlKttDEBWV1ga9ZXW8BG6kfdeyyM8LGPix9gROyg9V2+P0xNyF3X2Go526xKFdMZrHSQ=="], + + "supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], + + "tar": ["tar@7.5.22", "", { "dependencies": { "@isaacs/fs-minipass": "^4.0.0", "chownr": "^3.0.0", "minipass": "^7.1.2", "minizlib": "^3.1.0", "yallist": "^5.0.0" } }, "sha512-MFO/QzvtAOmJbkhOaCTvbGcFN9L9b+JunIsDwaKljSOdcLMea3NJ1k9Usz/rjdfSXTq4dfzfeS7W4p4YOAAHeA=="], + + "tinyglobby": ["tinyglobby@0.2.17", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.4" } }, "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g=="], + + "treeify": ["treeify@1.1.0", "", {}, "sha512-1m4RA7xVAJrSGrrXGs0L3YTwyvBs2S8PbRHaLZAkFw7JR8oIFwYtysxlBZhYIa7xSyiYJKZ3iGrrk55cGA3i9A=="], + + "treeverse": ["treeverse@3.0.0", "", {}, "sha512-gcANaAnd2QDZFmHFEOF4k7uc1J/6a6z3DJMd/QwEyxLoKGiptJRwid582r7QIsFlFMIZ3SnxfS52S4hm2DHkuQ=="], + + "tuf-js": ["tuf-js@4.1.0", "", { "dependencies": { "@tufjs/models": "4.1.0", "debug": "^4.4.3", "make-fetch-happen": "^15.0.1" } }, "sha512-50QV99kCKH5P/Vs4E2Gzp7BopNV+KzTXqWeaxrfu5IQJBOULRsTIS9seSsOVT8ZnGXzCyx55nYWAi4qJzpZKEQ=="], + + "undici": ["undici@6.29.0", "", {}, "sha512-R+RODBqp6i2pPflGdq+xIOUkl+RNfGgHwoinecKu/JCuf2uO06cOKoDbI2P7Dn6KcswdKwrczbU6IYJ6K8X+wg=="], + + "util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="], + + "validate-npm-package-name": ["validate-npm-package-name@7.0.2", "", {}, "sha512-hVDIBwsRruT73PbK7uP5ebUt+ezEtCmzZz3F59BSr2F6OVFnJ/6h8liuvdLrQ88Xmnk6/+xGGuq+pG9WwTuy3A=="], + + "walk-up-path": ["walk-up-path@4.0.0", "", {}, "sha512-3hu+tD8YzSLGuFYtPRb48vdhKMi0KQV5sn+uWr8+7dMEq/2G/dtLrdDinkLjqq5TIbIBjYJ4Ax/n3YiaW7QM8A=="], + + "which": ["which@6.0.1", "", { "dependencies": { "isexe": "^4.0.0" }, "bin": { "node-which": "bin/which.js" } }, "sha512-oGLe46MIrCRqX7ytPUf66EAYvdeMIZYn3WaocqqKZAxrBpkqHfL/qvTyJ/bTk5+AqHCjXmrv3CEWgy368zhRUg=="], + + "write-file-atomic": ["write-file-atomic@7.0.1", "", { "dependencies": { "signal-exit": "^4.0.1" } }, "sha512-OTIk8iR8/aCRWBqvxrzxR0hgxWpnYBblY1S5hDWBQfk/VFmJwzmJgQFN3WsoUKHISv2eAwe+PpbUzyL1CKTLXg=="], + + "yallist": ["yallist@5.0.0", "", {}, "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw=="], + + "@npmcli/arborist/nopt": ["nopt@9.0.0", "", { "dependencies": { "abbrev": "^4.0.0" }, "bin": { "nopt": "bin/nopt.js" } }, "sha512-Zhq3a+yFKrYwSBluL4H9XP3m3y5uvQkB/09CwDruCiRmR/UJYnn9W4R48ry0uGC70aeTPKLynBtscP9efFFcPw=="], + + "minipass-flush/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="], + + "minipass-pipeline/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="], + + "node-gyp/nopt": ["nopt@9.0.0", "", { "dependencies": { "abbrev": "^4.0.0" }, "bin": { "nopt": "bin/nopt.js" } }, "sha512-Zhq3a+yFKrYwSBluL4H9XP3m3y5uvQkB/09CwDruCiRmR/UJYnn9W4R48ry0uGC70aeTPKLynBtscP9efFFcPw=="], + + "spdx-compare/spdx-expression-parse": ["spdx-expression-parse@3.0.1", "", { "dependencies": { "spdx-exceptions": "^2.1.0", "spdx-license-ids": "^3.0.0" } }, "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q=="], + + "spdx-correct/spdx-expression-parse": ["spdx-expression-parse@3.0.1", "", { "dependencies": { "spdx-exceptions": "^2.1.0", "spdx-license-ids": "^3.0.0" } }, "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q=="], + + "spdx-satisfies/spdx-expression-parse": ["spdx-expression-parse@3.0.1", "", { "dependencies": { "spdx-exceptions": "^2.1.0", "spdx-license-ids": "^3.0.0" } }, "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q=="], + + "@npmcli/arborist/nopt/abbrev": ["abbrev@4.0.0", "", {}, "sha512-a1wflyaL0tHtJSmLSOVybYhy22vRih4eduhhrkcjgrWGnRfrZtovJ2FRjxuTtkkj47O/baf0R86QU5OuYpz8fA=="], + + "minipass-flush/minipass/yallist": ["yallist@4.0.0", "", {}, "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="], + + "minipass-pipeline/minipass/yallist": ["yallist@4.0.0", "", {}, "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="], + + "node-gyp/nopt/abbrev": ["abbrev@4.0.0", "", {}, "sha512-a1wflyaL0tHtJSmLSOVybYhy22vRih4eduhhrkcjgrWGnRfrZtovJ2FRjxuTtkkj47O/baf0R86QU5OuYpz8fA=="], } } diff --git a/package.json b/package.json index 514dd69..2d34a51 100644 --- a/package.json +++ b/package.json @@ -52,5 +52,8 @@ "devDependencies": { "@playwright/test": "^1.40.0", "@socketsecurity/bun-security-scanner": "^1.1.3" + }, + "dependencies": { + "license-checker-rseidelsohn": "5.0.1" } } diff --git a/scripts/check-licenses.ts b/scripts/check-licenses.ts index 5758c92..d97412d 100755 --- a/scripts/check-licenses.ts +++ b/scripts/check-licenses.ts @@ -4,12 +4,16 @@ * * Runs `license-checker-rseidelsohn` against the current working directory (the caller's * repo root) and exits non-zero if any installed package carries a blocked - * license. + * license. The checker is voidflow's own exact-pinned dependency, resolved from + * voidflow's install rather than `bunx`, so every site runs the same version + * and nothing is fetched at check time. * * Usage: bun check-licenses.ts */ import { spawnSync } from 'node:child_process' +import { readFileSync } from 'node:fs' +import { dirname, join } from 'node:path' export const FAIL_ON = [ 'GPL-1.0', @@ -41,9 +45,20 @@ export const FAIL_ON = [ // modifying/statically linking the library, not on depending on it as-is. ].join(';') +const checkerPkg = Bun.resolveSync( + 'license-checker-rseidelsohn/package.json', + import.meta.dir, +) +const checkerBin = join( + dirname(checkerPkg), + JSON.parse(readFileSync(checkerPkg, 'utf8')).bin[ + 'license-checker-rseidelsohn' + ], +) + const result = spawnSync( - 'bunx', - ['license-checker-rseidelsohn', '--summary', '--failOn', FAIL_ON], + process.execPath, + [checkerBin, '--summary', '--failOn', FAIL_ON], { cwd: process.cwd(), stdio: 'inherit' }, ) diff --git a/test/check-licenses.test.ts b/test/check-licenses.test.ts index 67ec1ed..354f79b 100644 --- a/test/check-licenses.test.ts +++ b/test/check-licenses.test.ts @@ -1,5 +1,5 @@ import { describe, expect, test } from 'bun:test' -import { mkdirSync, mkdtempSync, writeFileSync } from 'node:fs' +import { mkdirSync, mkdtempSync, readFileSync, writeFileSync } from 'node:fs' import { tmpdir } from 'node:os' import { join } from 'node:path' @@ -42,6 +42,16 @@ async function run(cwd: string) { } describe('check-licenses', () => { + test('runs an exact-pinned checker from voidflow, not bunx', () => { + const pkg = JSON.parse( + readFileSync(new URL('../package.json', import.meta.url), 'utf8'), + ) + expect(pkg.dependencies['license-checker-rseidelsohn']).toMatch( + /^\d+\.\d+\.\d+$/, + ) + expect(readFileSync(script, 'utf8')).not.toContain("'bunx'") + }) + test('passes when every dependency is permissively licensed', async () => { const { code, stdout } = await run(project('MIT')) expect(code).toBe(0) From 00765b8c7eea5edd7345c6c042c193093d1949f8 Mon Sep 17 00:00:00 2001 From: "ravenflight-releases[bot]" <330165707+ravenflight-releases[bot]@users.noreply.github.com> Date: Sun, 11 Oct 2026 21:42:31 -0700 Subject: [PATCH 17/21] chore(main): release 0.12.3 (#139) :robot: I have created a release *beep* *boop* --- ## [0.12.3](https://github.com/taraxvoid/voidflow/compare/v0.12.2...v0.12.3) (2026-10-12) ### Bug Fixes * pin license-checker-rseidelsohn as a voidflow dependency ([#138](https://github.com/taraxvoid/voidflow/issues/138)) ([a0d8dd1](https://github.com/taraxvoid/voidflow/commit/a0d8dd18a3d3efad3c33983d6ce587fd288c910f)) --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please). Co-authored-by: ravenflight-releases[bot] <330165707+ravenflight-releases[bot]@users.noreply.github.com> --- .release-please/main-manifest.json | 2 +- CHANGELOG.md | 7 +++++++ jsr.json | 2 +- package.json | 2 +- 4 files changed, 10 insertions(+), 3 deletions(-) diff --git a/.release-please/main-manifest.json b/.release-please/main-manifest.json index 18eef3d..e1a55a3 100644 --- a/.release-please/main-manifest.json +++ b/.release-please/main-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.12.2" + ".": "0.12.3" } diff --git a/CHANGELOG.md b/CHANGELOG.md index bee5ddd..819c90e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,13 @@ All notable changes to this project are documented here, generated by [release-please](https://github.com/googleapis/release-please) from [Conventional Commits](https://www.conventionalcommits.org). +## [0.12.3](https://github.com/taraxvoid/voidflow/compare/v0.12.2...v0.12.3) (2026-10-12) + + +### Bug Fixes + +* pin license-checker-rseidelsohn as a voidflow dependency ([#138](https://github.com/taraxvoid/voidflow/issues/138)) ([a0d8dd1](https://github.com/taraxvoid/voidflow/commit/a0d8dd18a3d3efad3c33983d6ce587fd288c910f)) + ## [0.12.2](https://github.com/taraxvoid/voidflow/compare/v0.12.1...v0.12.2) (2026-10-12) diff --git a/jsr.json b/jsr.json index d5d2cef..aad039f 100644 --- a/jsr.json +++ b/jsr.json @@ -1,6 +1,6 @@ { "name": "@taraxvoid/voidflow", - "version": "0.12.2", + "version": "0.12.3", "license": "MIT", "exports": { "./playwright": "./playwright/index.mjs" diff --git a/package.json b/package.json index 2d34a51..7ccd647 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@taraxvoid/voidflow", - "version": "0.12.2", + "version": "0.12.3", "description": "Shared workflows, actions and tooling for @taraxvoid sites", "license": "MIT", "keywords": [ From 9dad79ba1f630738c8e06cc79c4de229fbd2081f Mon Sep 17 00:00:00 2001 From: "Tara \\X/ Void" <1711810+taraxvoid@users.noreply.github.com> Date: Sun, 11 Oct 2026 21:53:53 -0700 Subject: [PATCH 18/21] fix(release): check the tag against the branch tip, not the run's checkout (#141) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Why 0.12.2 never reached npm: 1. Run 38204136468 (push of #130, f84a0f6) was in flight when release PR #137 merged (56239c2). 2. release-please reads the branch through the API, so that run created v0.12.2 on 56239c2 and set `release_created=true`. 3. The publish job checked `git merge-base --is-ancestor v0.12.2 HEAD`, but HEAD was the run's own checkout (f84a0f6), so it failed with "Tag v0.12.2 is not on branch main". 4. #137's own run (38204156854) found v0.12.2 already released, so `release_created` was false and publish was skipped. Fix: compare the tag against `refs/remotes/origin/`, which the job's `fetch-depth: 0` checkout fetches after release-please has finished. This keeps the same guarantee (the tag is on this channel's branch) and works no matter which push's run creates the release. 0.12.3 replaced 0.12.2 on npm, so I'm not republishing 0.12.2. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 5.5 --- .github/workflows/release-please.yml | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml index 497697c..2579f44 100644 --- a/.github/workflows/release-please.yml +++ b/.github/workflows/release-please.yml @@ -141,7 +141,12 @@ jobs: # channel: its version fits the channel (a -next tag on main, a stable # tag on next, or another line's tag on release/N.x are rejected) and # the tagged commit is on this branch (so a maintenance tag cannot be - # republished from main, or the reverse). + # republished from main, or the reverse). Compare against the branch tip + # (origin/, fetched by the checkout above), not HEAD: release-please + # tags the branch as it is when it runs, so if the release PR merges while + # an earlier push's run is in flight, that run tags a commit newer than its + # own checkout. Checking HEAD rejected it, and the release PR's own run then + # saw the release as already created and skipped publishing (v0.12.2). - name: Check tag belongs to this channel env: BRANCH: ${{ github.ref_name }} @@ -149,7 +154,7 @@ jobs: run: | set -euo pipefail bun scripts/release-channel.ts "$BRANCH" "${TAG#v}" > /dev/null - if ! git merge-base --is-ancestor "refs/tags/${TAG}^{commit}" HEAD; then + if ! git merge-base --is-ancestor "refs/tags/${TAG}^{commit}" "refs/remotes/origin/${BRANCH}"; then echo "::error::Tag ${TAG} is not on branch ${BRANCH}; run this workflow from the branch that owns the tag." >&2 exit 1 fi From a40fe21a2d32a0e90d3a01bc72a5ec307dc3d32e Mon Sep 17 00:00:00 2001 From: "Tara \\X/ Void" <1711810+taraxvoid@users.noreply.github.com> Date: Sun, 11 Oct 2026 21:54:04 -0700 Subject: [PATCH 19/21] fix: make the @playwright/test peer optional (#142) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two consumer-facing items reported from trailguide: - **`@playwright/test` peer marked optional** (`peerDependenciesMeta`). Only the `./playwright` export imports it. As a required peer, bun auto-installed it in every consumer, including repos that only use the bins. I checked by packing this branch and installing the tarball into an empty project: no `@playwright/*` in `node_modules`, and the installed `check-licenses` bin runs and passes there. - **README: don't set `parallel: true` under `pre-commit:`.** A site's hook-level `parallel: true` applies to the merged hook, so the shared steps all start at once and their `priority` order is ignored (in trailguide, SQLite-backed tests timed out). `lefthook/site.yml`'s comment is left alone because next changed those same lines, and editing them here would cause another back-merge conflict. Squash-merging folds both into one changelog entry under the PR title; a merge commit keeps them as two. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 --- README.md | 5 +++++ package.json | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/README.md b/README.md index bacfe7c..55e2521 100644 --- a/README.md +++ b/README.md @@ -271,6 +271,11 @@ cannot override ones defined here. Differences go through `package.json` scripts: define `test:unit:precommit` (for example excluding tests that need a built `dist/`) and the pre-commit unit step runs it instead of `test:unit`. +Hook-level options merge too. Don't set `parallel: true` under `pre-commit:` in +the site's `lefthook.yml`: it applies to the merged hook, so every step, +including the shared ones, starts at once and their `priority` order is ignored +(tests that share a database or build output can then time out). + Install the binary through mise (`lefthook = ""` in `mise.toml`) and set the `prepare` script so `bun install` wires the hooks, but make it tolerate a missing binary: diff --git a/package.json b/package.json index 7ccd647..adb3bed 100644 --- a/package.json +++ b/package.json @@ -49,6 +49,11 @@ "peerDependencies": { "@playwright/test": ">=1.40" }, + "peerDependenciesMeta": { + "@playwright/test": { + "optional": true + } + }, "devDependencies": { "@playwright/test": "^1.40.0", "@socketsecurity/bun-security-scanner": "^1.1.3" From 4f92bef155df48bc89dd620d58110c5201e2d22d Mon Sep 17 00:00:00 2001 From: "ravenflight-releases[bot]" <330165707+ravenflight-releases[bot]@users.noreply.github.com> Date: Sun, 11 Oct 2026 21:55:16 -0700 Subject: [PATCH 20/21] chore(main): release 0.12.4 (#144) :robot: I have created a release *beep* *boop* --- ## [0.12.4](https://github.com/taraxvoid/voidflow/compare/v0.12.3...v0.12.4) (2026-10-12) ### Bug Fixes * make the @playwright/test peer optional ([#142](https://github.com/taraxvoid/voidflow/issues/142)) ([a40fe21](https://github.com/taraxvoid/voidflow/commit/a40fe21a2d32a0e90d3a01bc72a5ec307dc3d32e)) * **release:** check the tag against the branch tip, not the run's checkout ([#141](https://github.com/taraxvoid/voidflow/issues/141)) ([9dad79b](https://github.com/taraxvoid/voidflow/commit/9dad79ba1f630738c8e06cc79c4de229fbd2081f)) --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please). Co-authored-by: ravenflight-releases[bot] <330165707+ravenflight-releases[bot]@users.noreply.github.com> --- .release-please/main-manifest.json | 2 +- CHANGELOG.md | 8 ++++++++ jsr.json | 2 +- package.json | 2 +- 4 files changed, 11 insertions(+), 3 deletions(-) diff --git a/.release-please/main-manifest.json b/.release-please/main-manifest.json index e1a55a3..e939560 100644 --- a/.release-please/main-manifest.json +++ b/.release-please/main-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.12.3" + ".": "0.12.4" } diff --git a/CHANGELOG.md b/CHANGELOG.md index 819c90e..01723a8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,14 @@ All notable changes to this project are documented here, generated by [release-please](https://github.com/googleapis/release-please) from [Conventional Commits](https://www.conventionalcommits.org). +## [0.12.4](https://github.com/taraxvoid/voidflow/compare/v0.12.3...v0.12.4) (2026-10-12) + + +### Bug Fixes + +* make the @playwright/test peer optional ([#142](https://github.com/taraxvoid/voidflow/issues/142)) ([a40fe21](https://github.com/taraxvoid/voidflow/commit/a40fe21a2d32a0e90d3a01bc72a5ec307dc3d32e)) +* **release:** check the tag against the branch tip, not the run's checkout ([#141](https://github.com/taraxvoid/voidflow/issues/141)) ([9dad79b](https://github.com/taraxvoid/voidflow/commit/9dad79ba1f630738c8e06cc79c4de229fbd2081f)) + ## [0.12.3](https://github.com/taraxvoid/voidflow/compare/v0.12.2...v0.12.3) (2026-10-12) diff --git a/jsr.json b/jsr.json index aad039f..636d704 100644 --- a/jsr.json +++ b/jsr.json @@ -1,6 +1,6 @@ { "name": "@taraxvoid/voidflow", - "version": "0.12.3", + "version": "0.12.4", "license": "MIT", "exports": { "./playwright": "./playwright/index.mjs" diff --git a/package.json b/package.json index adb3bed..28db73f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@taraxvoid/voidflow", - "version": "0.12.3", + "version": "0.12.4", "description": "Shared workflows, actions and tooling for @taraxvoid sites", "license": "MIT", "keywords": [ From 56fbbffb17e9d0d9415f0aee164e482dbc0d965a Mon Sep 17 00:00:00 2001 From: "Tara \\X/ Void" <1711810+taraxvoid@users.noreply.github.com> Date: Sun, 11 Oct 2026 22:12:33 -0700 Subject: [PATCH 21/21] fix(release): auto-merge back-merge PRs with a merge commit (#149) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The `back-merge` job opens the main to next PR but left the merge method to whoever merged it. The `next` ruleset allows squash (feature PRs need it for release-please), so back-merges #123, #128, #140 and #145 were all squashed. A squash leaves main's commits out of next's history, so each following back-merge re-conflicts on the same lines (this just happened merging v0.12.4 into next). - `release-please.yml`: after creating (or finding) the back-merge PR, run `gh pr merge --auto --merge`, so it lands as a merge commit once required checks pass. - `RELEASING.md`: document the auto-merge and why a manual merge must never be a squash. `just promote` is unchanged: auto-merging a promotion would skip the review before a stable release. actionlint passes; `bun test` 116 pass. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 5.5 --- .github/workflows/release-please.yml | 11 ++++++++--- RELEASING.md | 5 ++++- bun.lock | 3 +++ 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml index 2579f44..892ccbb 100644 --- a/.github/workflows/release-please.yml +++ b/.github/workflows/release-please.yml @@ -286,8 +286,8 @@ jobs: # Stable releases only: bring the release's version bump and changelog from # main into next so the branches do not drift. Only version bookkeeping files # may conflict and scripts/back-merge.ts resolves those; anything else fails - # the job loudly so a person resolves it (see RELEASING.md). The PR must be - # merged with a merge commit, not squashed. + # the job loudly so a person resolves it (see RELEASING.md). The PR is set to + # auto-merge with a merge commit, never a squash, so the branches share history. back-merge: name: Open main to next back-merge PR needs: [channel, release-please] @@ -347,6 +347,11 @@ jobs: else gh pr create --repo "$GITHUB_REPOSITORY" --base next --head "$BRANCH" \ --title "chore: back-merge ${TAG} into next" \ - --body "Brings the ${TAG} version bump and changelog from main into next. Merge with a merge commit, not a squash, so the branches share history." + --body "Brings the ${TAG} version bump and changelog from main into next. Auto-merges with a merge commit once checks pass; do not squash it, or main's commits never become ancestors of next and the next back-merge conflicts on the same lines again." fi + # The next ruleset allows squash for feature PRs, so a person clicking + # merge here would default to squash (v0.12.0 to v0.12.4 all were). + # Auto-merge pins the method to a real merge commit. + gh pr merge "$BRANCH" --repo "$GITHUB_REPOSITORY" --auto --merge + diff --git a/RELEASING.md b/RELEASING.md index 1fcc860..62a92cc 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -40,7 +40,10 @@ gitGraph 3. release-please opens or updates the stable release PR on `main`. Merging it publishes under `latest`, moves the floating `v0` tag, and opens a `main` to `next` back-merge PR. -4. Merge the back-merge PR with a merge commit too. +4. The back-merge PR auto-merges with a merge commit once checks pass. If you + ever merge one by hand (e.g. after resolving conflicts), use a merge commit, + never squash: a squash leaves main's commits out of next's history, so the + following back-merge conflicts on the same lines again. ## Hotfix diff --git a/bun.lock b/bun.lock index 78a63aa..d490979 100644 --- a/bun.lock +++ b/bun.lock @@ -14,6 +14,9 @@ "peerDependencies": { "@playwright/test": ">=1.40", }, + "optionalPeers": [ + "@playwright/test", + ], }, }, "packages": {