From fcb674700ac6af1ca26e57ae49ccfb7fb7243684 Mon Sep 17 00:00:00 2001 From: lvbt-bot Date: Mon, 28 Sep 2026 11:01:12 -0700 Subject: [PATCH] chore: update LVBT repository standard to v0.5.2 Apply the reviewed v0.5.2 release with its own updater. --- .claude/settings.json | 2 +- .lvbt/web-platform.json | 6 +- .../examples/with-astro/.claude/settings.json | 2 +- .../with-astro/apps/site/package.json | 8 +- .../examples/with-astro/package.json | 4 +- .../with-vite-react/.claude/settings.json | 2 +- .../with-vite-react/apps/app/package.json | 8 +- .../examples/with-vite-react/package.json | 4 +- .lvbt/web-platform/packages/cli/package.json | 2 +- .../.claude-plugin/plugin.json | 2 +- .../.codex-plugin/plugin.json | 2 +- .../packages/eslint-config/package.json | 2 +- .../packages/playwright-config/package.json | 2 +- .../packages/prettier-config/package.json | 2 +- .../packages/typescript-config/package.json | 2 +- .../packages/vitest-config/package.json | 2 +- .../packages/web-platform/package.json | 2 +- .lvbt/web-platform/standards/propagate.ts | 4 + .lvbt/web-platform/standards/propose.ts | 86 +++++++++++++++++-- .lvbt/web-platform/standards/status.ts | 8 +- 20 files changed, 114 insertions(+), 38 deletions(-) diff --git a/.claude/settings.json b/.claude/settings.json index 22dbad4..39b3306 100644 --- a/.claude/settings.json +++ b/.claude/settings.json @@ -4,7 +4,7 @@ "source": { "source": "github", "repo": "LasVegasForTransit/repository-tooling", - "ref": "v0.5.0" + "ref": "v0.5.2" } } }, diff --git a/.lvbt/web-platform.json b/.lvbt/web-platform.json index af928ed..499c61a 100644 --- a/.lvbt/web-platform.json +++ b/.lvbt/web-platform.json @@ -1,9 +1,9 @@ { "formatVersion": 1, "preset": "lvbt-web", - "release": "v0.5.0", - "commit": "cc7458f97c1699ea48633810298e4982d308f0ad", - "contentHash": "13d5ae98e03b50cb5419af95a197efc540d4af3daa0eefed0856806068273895", + "release": "v0.5.2", + "commit": "878cea0fd76851186e802eb36dede1d7a93d4776", + "contentHash": "2f05abb3a7998eb15c8fe816f7510bfbbb01c9bcfda07d706ef37c864e4d7bf9", "executables": [ "examples/with-astro/.githooks/commit-msg", "examples/with-astro/.githooks/pre-commit", diff --git a/.lvbt/web-platform/examples/with-astro/.claude/settings.json b/.lvbt/web-platform/examples/with-astro/.claude/settings.json index 22dbad4..39b3306 100644 --- a/.lvbt/web-platform/examples/with-astro/.claude/settings.json +++ b/.lvbt/web-platform/examples/with-astro/.claude/settings.json @@ -4,7 +4,7 @@ "source": { "source": "github", "repo": "LasVegasForTransit/repository-tooling", - "ref": "v0.5.0" + "ref": "v0.5.2" } } }, diff --git a/.lvbt/web-platform/examples/with-astro/apps/site/package.json b/.lvbt/web-platform/examples/with-astro/apps/site/package.json index 19ac71d..8f18b97 100644 --- a/.lvbt/web-platform/examples/with-astro/apps/site/package.json +++ b/.lvbt/web-platform/examples/with-astro/apps/site/package.json @@ -21,10 +21,10 @@ }, "devDependencies": { "@astrojs/check": "catalog:", - "@lasvegasfortransit/eslint-config": "0.5.0", - "@lasvegasfortransit/playwright-config": "0.5.0", - "@lasvegasfortransit/typescript-config": "0.5.0", - "@lasvegasfortransit/vitest-config": "0.5.0", + "@lasvegasfortransit/eslint-config": "0.5.2", + "@lasvegasfortransit/playwright-config": "0.5.2", + "@lasvegasfortransit/typescript-config": "0.5.2", + "@lasvegasfortransit/vitest-config": "0.5.2", "@playwright/test": "catalog:", "@types/node": "catalog:", "eslint": "catalog:", diff --git a/.lvbt/web-platform/examples/with-astro/package.json b/.lvbt/web-platform/examples/with-astro/package.json index 50671a2..1c75559 100644 --- a/.lvbt/web-platform/examples/with-astro/package.json +++ b/.lvbt/web-platform/examples/with-astro/package.json @@ -23,8 +23,8 @@ "*": "prettier --write --ignore-unknown" }, "devDependencies": { - "@lasvegasfortransit/cli": "0.5.0", - "@lasvegasfortransit/prettier-config": "0.5.0", + "@lasvegasfortransit/cli": "0.5.2", + "@lasvegasfortransit/prettier-config": "0.5.2", "lint-staged": "catalog:", "markdownlint-cli2": "catalog:", "markdownlint-rule-relative-links": "catalog:", diff --git a/.lvbt/web-platform/examples/with-vite-react/.claude/settings.json b/.lvbt/web-platform/examples/with-vite-react/.claude/settings.json index 22dbad4..39b3306 100644 --- a/.lvbt/web-platform/examples/with-vite-react/.claude/settings.json +++ b/.lvbt/web-platform/examples/with-vite-react/.claude/settings.json @@ -4,7 +4,7 @@ "source": { "source": "github", "repo": "LasVegasForTransit/repository-tooling", - "ref": "v0.5.0" + "ref": "v0.5.2" } } }, diff --git a/.lvbt/web-platform/examples/with-vite-react/apps/app/package.json b/.lvbt/web-platform/examples/with-vite-react/apps/app/package.json index dc885d2..9e209af 100644 --- a/.lvbt/web-platform/examples/with-vite-react/apps/app/package.json +++ b/.lvbt/web-platform/examples/with-vite-react/apps/app/package.json @@ -17,10 +17,10 @@ "react-dom": "catalog:" }, "devDependencies": { - "@lasvegasfortransit/eslint-config": "0.5.0", - "@lasvegasfortransit/playwright-config": "0.5.0", - "@lasvegasfortransit/typescript-config": "0.5.0", - "@lasvegasfortransit/vitest-config": "0.5.0", + "@lasvegasfortransit/eslint-config": "0.5.2", + "@lasvegasfortransit/playwright-config": "0.5.2", + "@lasvegasfortransit/typescript-config": "0.5.2", + "@lasvegasfortransit/vitest-config": "0.5.2", "@playwright/test": "catalog:", "@tailwindcss/vite": "catalog:", "@types/node": "catalog:", diff --git a/.lvbt/web-platform/examples/with-vite-react/package.json b/.lvbt/web-platform/examples/with-vite-react/package.json index 2199229..dc1bbf2 100644 --- a/.lvbt/web-platform/examples/with-vite-react/package.json +++ b/.lvbt/web-platform/examples/with-vite-react/package.json @@ -23,8 +23,8 @@ "*": "prettier --write --ignore-unknown" }, "devDependencies": { - "@lasvegasfortransit/cli": "0.5.0", - "@lasvegasfortransit/prettier-config": "0.5.0", + "@lasvegasfortransit/cli": "0.5.2", + "@lasvegasfortransit/prettier-config": "0.5.2", "lint-staged": "catalog:", "markdownlint-cli2": "catalog:", "markdownlint-rule-relative-links": "catalog:", diff --git a/.lvbt/web-platform/packages/cli/package.json b/.lvbt/web-platform/packages/cli/package.json index 500ac05..54bc966 100644 --- a/.lvbt/web-platform/packages/cli/package.json +++ b/.lvbt/web-platform/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "@lasvegasfortransit/cli", - "version": "0.5.0", + "version": "0.5.2", "description": "The lvbt command every LVBT repository runs for bootstrap, preflight, and deploy, plus the production platform setup, the shared git hooks, and the lvbt-contributions agent plugin.", "license": "MIT", "type": "module", diff --git a/.lvbt/web-platform/packages/cli/plugins/lvbt-contributions/.claude-plugin/plugin.json b/.lvbt/web-platform/packages/cli/plugins/lvbt-contributions/.claude-plugin/plugin.json index 18c099d..ce879c6 100644 --- a/.lvbt/web-platform/packages/cli/plugins/lvbt-contributions/.claude-plugin/plugin.json +++ b/.lvbt/web-platform/packages/cli/plugins/lvbt-contributions/.claude-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "lvbt-contributions", - "version": "0.5.0", + "version": "0.5.2", "description": "Create readable LVBT GitHub issues and pull requests through the organization workflow.", "author": { "name": "Las Vegans for Better Transit", diff --git a/.lvbt/web-platform/packages/cli/plugins/lvbt-contributions/.codex-plugin/plugin.json b/.lvbt/web-platform/packages/cli/plugins/lvbt-contributions/.codex-plugin/plugin.json index 5c68dc0..f18002b 100644 --- a/.lvbt/web-platform/packages/cli/plugins/lvbt-contributions/.codex-plugin/plugin.json +++ b/.lvbt/web-platform/packages/cli/plugins/lvbt-contributions/.codex-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "lvbt-contributions", - "version": "0.5.0", + "version": "0.5.2", "description": "Create readable LVBT GitHub issues and pull requests through the organization workflow.", "author": { "name": "Las Vegans for Better Transit", diff --git a/.lvbt/web-platform/packages/eslint-config/package.json b/.lvbt/web-platform/packages/eslint-config/package.json index 50b0c72..d1d9c1b 100644 --- a/.lvbt/web-platform/packages/eslint-config/package.json +++ b/.lvbt/web-platform/packages/eslint-config/package.json @@ -1,6 +1,6 @@ { "name": "@lasvegasfortransit/eslint-config", - "version": "0.5.0", + "version": "0.5.2", "description": "The ESLint configurations every LVBT repository uses.", "license": "MIT", "type": "module", diff --git a/.lvbt/web-platform/packages/playwright-config/package.json b/.lvbt/web-platform/packages/playwright-config/package.json index f2108b1..2d45257 100644 --- a/.lvbt/web-platform/packages/playwright-config/package.json +++ b/.lvbt/web-platform/packages/playwright-config/package.json @@ -1,6 +1,6 @@ { "name": "@lasvegasfortransit/playwright-config", - "version": "0.5.0", + "version": "0.5.2", "description": "The Playwright configuration every LVBT repository spreads into its own: end-to-end tests under tests/e2e, desktop and mobile projects, traces on failure.", "license": "MIT", "type": "module", diff --git a/.lvbt/web-platform/packages/prettier-config/package.json b/.lvbt/web-platform/packages/prettier-config/package.json index 6428c0f..8502cee 100644 --- a/.lvbt/web-platform/packages/prettier-config/package.json +++ b/.lvbt/web-platform/packages/prettier-config/package.json @@ -1,6 +1,6 @@ { "name": "@lasvegasfortransit/prettier-config", - "version": "0.5.0", + "version": "0.5.2", "description": "The Prettier configuration every LVBT repository uses.", "license": "MIT", "type": "module", diff --git a/.lvbt/web-platform/packages/typescript-config/package.json b/.lvbt/web-platform/packages/typescript-config/package.json index da0e97c..a39afe9 100644 --- a/.lvbt/web-platform/packages/typescript-config/package.json +++ b/.lvbt/web-platform/packages/typescript-config/package.json @@ -1,6 +1,6 @@ { "name": "@lasvegasfortransit/typescript-config", - "version": "0.5.0", + "version": "0.5.2", "description": "TypeScript configurations every LVBT repository extends.", "license": "MIT", "repository": { diff --git a/.lvbt/web-platform/packages/vitest-config/package.json b/.lvbt/web-platform/packages/vitest-config/package.json index 35ab091..f3edfbe 100644 --- a/.lvbt/web-platform/packages/vitest-config/package.json +++ b/.lvbt/web-platform/packages/vitest-config/package.json @@ -1,6 +1,6 @@ { "name": "@lasvegasfortransit/vitest-config", - "version": "0.5.0", + "version": "0.5.2", "description": "The Vitest configuration every LVBT repository spreads into its own.", "license": "MIT", "type": "module", diff --git a/.lvbt/web-platform/packages/web-platform/package.json b/.lvbt/web-platform/packages/web-platform/package.json index bff470a..e7ed44a 100644 --- a/.lvbt/web-platform/packages/web-platform/package.json +++ b/.lvbt/web-platform/packages/web-platform/package.json @@ -1,6 +1,6 @@ { "name": "@lasvegasfortransit/web-platform", - "version": "0.5.0", + "version": "0.5.2", "description": "Provider-neutral deployment, provisioning, and validation primitives for LVBT web repositories.", "license": "MIT", "type": "module", diff --git a/.lvbt/web-platform/standards/propagate.ts b/.lvbt/web-platform/standards/propagate.ts index a892a27..5ed8e7d 100644 --- a/.lvbt/web-platform/standards/propagate.ts +++ b/.lvbt/web-platform/standards/propagate.ts @@ -276,6 +276,10 @@ export async function applyRelease(options: { 'user.name=lvbt-bot', '-c', 'user.email=noreply@lasvegasfortransit.org', + // The repository's own hooks are for people; installing dependencies can switch them on, + // and prepare-commit-msg runs even with --no-verify. + '-c', + 'core.hooksPath=/dev/null', 'commit', '--quiet', '--no-verify', diff --git a/.lvbt/web-platform/standards/propose.ts b/.lvbt/web-platform/standards/propose.ts index f2b5720..d5acfd1 100644 --- a/.lvbt/web-platform/standards/propose.ts +++ b/.lvbt/web-platform/standards/propose.ts @@ -29,7 +29,12 @@ function remoteBranchState( branch: string, runner: Runner, ): 'absent' | 'current' | 'stale' | 'edited' { - if (!runner('git', ['ls-remote', '--heads', 'origin', branch], target)) return 'absent'; + if (!runner('git', ['ls-remote', '--heads', 'origin', branch], target)) { + // A tracking ref left from a branch GitHub has since deleted would make --force-with-lease + // refuse the push, so forget it. + runner('git', ['update-ref', '-d', `refs/remotes/origin/${branch}`], target); + return 'absent'; + } runner( 'git', ['fetch', '--quiet', 'origin', `+refs/heads/${branch}:refs/remotes/origin/${branch}`], @@ -73,7 +78,19 @@ function openUpdates(target: string, runner: Runner): OpenUpdate[] { function pushUpdateBranch(target: string, name: string, branch: string, runner: Runner): boolean { const state = remoteBranchState(target, branch, runner); if (state === 'absent' || state === 'stale') { - runner('git', ['push', '--force-with-lease', '--set-upstream', 'origin', branch], target); + runner( + 'git', + [ + '-c', + 'core.hooksPath=/dev/null', + 'push', + '--force-with-lease', + '--set-upstream', + 'origin', + branch, + ], + target, + ); return true; } if (state === 'edited') { @@ -136,6 +153,63 @@ function createOrEdit(options: { return (JSON.parse(runner('node', args, target)) as { number: number }).number; } +/** The release the repository's default branch vendors, read from GitHub rather than the checkout. */ +function defaultBranchRelease(target: string, runner: Runner): string | null { + const manifest = runner( + 'gh', + [ + 'api', + 'repos/{owner}/{repo}/contents/.lvbt/web-platform.json', + '-H', + 'Accept: application/vnd.github.raw', + ], + target, + ); + return (JSON.parse(manifest) as { release: string | null }).release; +} + +/** + * GitHub holds the workflow runs of a pull request that a workflow's own token opened until someone + * with write access approves them. Approve the ones this update started so `Validate` runs on it. + */ +function approveHeldRuns(target: string, branch: string, runner: Runner): void { + const held = () => + ( + JSON.parse( + runner( + 'gh', + ['run', 'list', '--branch', branch, '--json', 'databaseId,conclusion', '--limit', '20'], + target, + ), + ) as { databaseId: number; conclusion: string }[] + ) + .filter(({ conclusion }) => conclusion === 'action_required') + .map(({ databaseId }) => databaseId); + const approved = new Set(); + // Runs appear a few seconds after the pull request opens; keep looking briefly after the first. + for (let attempt = 0, quiet = 0; attempt < 12 && quiet < 3; attempt += 1) { + const found = held().filter((id) => !approved.has(id)); + for (const id of found) { + try { + runner( + 'gh', + ['api', '-X', 'POST', `repos/{owner}/{repo}/actions/runs/${id}/approve`], + target, + ); + approved.add(id); + } catch { + process.stderr.write( + `Could not approve workflow run ${id} on ${branch}. A maintainer approves it on the pull request so Validate runs.\n`, + ); + process.exitCode = 1; + return; + } + } + quiet = approved.size > 0 && found.length === 0 ? quiet + 1 : 0; + runner('sleep', ['5'], target); + } +} + /** * Pushes the update branch, opens or refreshes its pull request, enables auto-merge, and closes * update pull requests for older releases. @@ -164,10 +238,10 @@ export async function proposeRelease(options: { const pushed = pushUpdateBranch(target, entry.name, updateBranch(tag), runner); number = await openPullRequest({ ...options, automerge, runner }); if (automerge) runner('gh', ['pr', 'merge', String(number), '--auto', '--rebase'], target); - // A push made with a repository's own GITHUB_TOKEN starts no workflow, but a dispatch always - // does, and its Validate check lands on the branch's head commit. - if (pushed) runner('gh', ['workflow', 'run', 'ci.yml', '--ref', updateBranch(tag)], target); - } else { + if (pushed) approveHeldRuns(target, updateBranch(tag), runner); + } else if (defaultBranchRelease(target, runner) === tag) { + // Only a default branch that already carries the release makes its update pull request moot; + // a checkout that happens to be on the update branch does not. superseded.push(...open.filter(({ headRefName }) => headRefName === updateBranch(tag))); } diff --git a/.lvbt/web-platform/standards/status.ts b/.lvbt/web-platform/standards/status.ts index 05ba85c..9e040e9 100644 --- a/.lvbt/web-platform/standards/status.ts +++ b/.lvbt/web-platform/standards/status.ts @@ -22,7 +22,7 @@ export interface RepositoryState { name: string; release: string | null; pluginRef: string | null; - /** The repository runs `Standard update`, and `ci.yml` accepts the dispatch it sends. */ + /** The repository runs `Standard update`. */ selfUpdating: boolean; rulesets: string[]; updates: { number: number; headRefName: string; failing: boolean }[]; @@ -76,7 +76,7 @@ export function findings(state: RepositoryState, releases: Release[], now: numbe if (!state.selfUpdating) add( 'self-update', - 'it cannot update itself: copy .github/workflows/standard-update.yml from the example and give ci.yml a workflow_dispatch trigger.', + 'it cannot update itself: copy .github/workflows/standard-update.yml from the example.', ); if (!state.rulesets.includes('org-standard')) add('ruleset', 'the org-standard ruleset is missing.'); @@ -186,9 +186,7 @@ function readState(entry: RegistryEntry): RepositoryState { name: entry.name, release, pluginRef: pluginRef(readRaw(entry.name, '.claude/settings.json')), - selfUpdating: - readRaw(entry.name, '.github/workflows/standard-update.yml') !== null && - /^\s{2}workflow_dispatch:/m.test(readRaw(entry.name, '.github/workflows/ci.yml') ?? ''), + selfUpdating: readRaw(entry.name, '.github/workflows/standard-update.yml') !== null, rulesets, updates, };