diff --git a/.github/dependabot.yml b/.github/dependabot.yml index bdf01e8..8ebcbc3 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -28,8 +28,8 @@ updates: prefix: "ci" labels: ["dependencies", "github_actions", "skip-changelog"] - # The digest-pinned base image the scaffold emits. These PRs fail the bump and changelog checks - # until `shared:docker`'s version in src/features/docker.ts is bumped and an entry added by hand. + # The digest-pinned base image the scaffold emits. The dependabot-bump workflow adds the + # `shared:docker` bump and a changelog line these PRs need. - package-ecosystem: docker directory: /packages/preset-nuxt4/templates/docker schedule: diff --git a/.github/workflows/dependabot-bump.yml b/.github/workflows/dependabot-bump.yml new file mode 100644 index 0000000..26d8aa1 --- /dev/null +++ b/.github/workflows/dependabot-bump.yml @@ -0,0 +1,66 @@ +name: Dependabot feature bump + +# A Dependabot PR that changes a template a scaffold emits (the Dockerfile's base image, for one) +# must bump that feature's version, or existing projects never get it on `battlestack pull`, and +# it needs a changelog line. This adds both to the PR, as a person would. +# +# `pull_request_target` runs with this repository's secrets, which a Dependabot-triggered +# `pull_request` run cannot read. The push uses RELEASE_PR_TOKEN because GitHub starts no +# workflows for a GITHUB_TOKEN push, so the required checks would never run on the new commit. +# Nothing from the PR is installed or built: the bump script needs only Node's own modules. + +on: + pull_request_target: + types: [opened, synchronize, reopened] + +permissions: + contents: read + +jobs: + bump: + name: Bump features whose templates changed + if: github.event.pull_request.user.login == 'dependabot[bot]' + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - name: Checkout the PR branch + uses: actions/checkout@v7 + with: + ref: ${{ github.event.pull_request.head.ref }} + fetch-depth: 0 + token: ${{ secrets.RELEASE_PR_TOKEN }} + + - name: Setup Node + uses: actions/setup-node@v7 + with: + node-version: '24' + + - name: Bump the features the version gate would fail on + env: + BUMP_CHECK_BASE: origin/${{ github.event.pull_request.base.ref }} + run: npx -y tsx@4.23.15 scripts/bump-check.ts --check --fix + + - name: Add a changelog line + env: + TITLE: ${{ github.event.pull_request.title }} + BASE: origin/${{ github.event.pull_request.base.ref }} + run: | + git diff --quiet -- packages && exit 0 + git diff --quiet "$BASE" -- CHANGELOG.md || exit 0 + # "fix(docker): bump node from a to b in /x" -> "Bump node from a to b in /x." + line="$(printf '%s' "$TITLE" | sed -E 's/^[a-z]+(\([^)]*\))?!?: //; s/^./\U&/; s/\.?$/./')" + node -e ' + const fs = require("fs") + const s = fs.readFileSync("CHANGELOG.md", "utf8") + const head = "## Unreleased\n\n" + if (!s.includes(head)) process.exit(1) + fs.writeFileSync("CHANGELOG.md", s.replace(head, `${head}- ${process.argv[1]}\n`)) + ' "$line" + + - name: Commit and push + run: | + git diff --quiet && { echo "No feature needed a bump."; exit 0; } + git config user.name "battlestack-bot" + git config user.email "battlestack-bot@users.noreply.github.com" + git commit -am "chore: bump the features this dependency update changes" + git push origin HEAD:${{ github.event.pull_request.head.ref }} diff --git a/scripts/bump-check.ts b/scripts/bump-check.ts index bc16cb3..9a15e5d 100644 --- a/scripts/bump-check.ts +++ b/scripts/bump-check.ts @@ -2,6 +2,7 @@ * Feature version gate. * * pnpm bump:check [baseRef] CI gate: fail when a changed feature was not bumped + * pnpm bump:check --fix patch-bump every feature the gate would fail on * pnpm bump --list every feature, version, status * pnpm bump --changed only features with changes * pnpm bump rewrite one version (patch|minor|major) @@ -104,6 +105,16 @@ async function runCheck(argv: string[]): Promise { return } + if (argv.includes('--fix')) { + const ids = new Set([...offenders.map((f) => f.id), ...writerOffenders.map((w) => w.writerId)]) + for (const id of ids) { + const target = features.find((f) => f.id === id) + if (!target) fail(`no feature with id "${id}"`) + await applyBump(target, 'patch') + } + return + } + for (const f of offenders) { console.log(`\nFAIL ${f.id} changed but is still ${f.version}`) console.log(` files: ${f.ownedPaths.map(rel).join(', ')}`)