CI Guard PR titles against changesets so Release workflow is not skipped
- Dominant language
- JavaScript
- Stars
- 1.1k
- Forks
- 397
- Avg merge
- 1d 7h
- Merged PRs (30d)
- 25
Description
## Problem
In #2189 we merged hook bundling work with a squash-merge title `fix(hooks): …` and a changeset for `@asyncapi/generator-hooks`. That title is valid under Conventional Commits and passes central `lint-pr-title.yml` (semantic-pull-request allows scopes), but **Release** only runs when the commit on `master` starts with an **unscoped** prefix:
- `fix:`, `fix!:`, `feat:`, `feat!:`, or `chore(release):`
See `.github/workflows/release-with-changesets.yml` (`startsWith` on `github.event.commits[0].message`).
Squash merges use the **PR title** as that message (`MERGE_COMMIT_MESSAGE: "{pullRequest.title} (#{pullRequest.number})"` in automerge). Result: Release was **skipped**, the changeset stayed on `master`, and `@asyncapi/generator-hooks` was not published until a follow-up `fix:` PR.
## Proposal
Add a **generator-local** required check (similar in spirit to `verify-ai-disclosure.yml`), without changing centrally managed `lint-pr-title.yml` from `asyncapi/.github`.
### Rule A — changeset ⇒ release-trigger title
If the PR **adds or modifies** any `.changeset/*.md` file (excluding `config.json`), the PR title must use a Release-compatible **unscoped** prefix (same list as Release workflow). This would have blocked #2189 (`fix(hooks):` + changeset).
### Rule B — release title + publishable code ⇒ changeset
If the PR title **will trigger Release** and the diff touches **publishable package source paths** (e.g. `apps/hooks/src/`, `packages/components/src/`, baked-in `packages/templates/`, etc., excluding tests/snapshots), require either:
- a changeset file in the PR diff, **or**
- pending changesets already on the branch (`npx -p @changesets/cli@2.27.7 changeset status` reports packages to bump).
**Wake-up PRs** (`fix: trigger release…` with no publishable path changes) should **not** fail Rule B.
### Bot exemptions
Skip the check for known bots, aligned with existing workflows:
- `asyncapi-bot` / `asyncapi-bot-eve` when title starts with `chore(release):` or `ci: update of files from global .github repo`
- `dependabot[bot]` / `dependabot-preview[bot]`
- `allcontributors[bot]` when title starts with `docs: add`
### Implementation sketch
- Small Node module under `.github/workflows/scripts/release-pr-guard/` with unit tests (`node:test`).
- Workflow: e.g. `verify-changeset-release-alignment.yml` on `pull_request`.
- Docs: `Development.md` + `.github/pr-review-checklist.md` — scoped `fix(hooks):` does **not** trigger Release.
- Maintainers: add the new check to branch protection after merge.
### Non-goals (optional follow-ups)
- Changing Release `if:` to accept scoped prefixes (policy decision).
- Org-wide changes to `lint-pr-title.yml`.
## Related
- Merged PR: #2189
- Issue: #2144 (hook bundling)
## Acceptance criteria
- [ ] PR with changeset + `fix(hooks):` title fails CI with a clear message.
- [ ] PR with changeset + `fix:` title passes.
- [ ] `fix:` PR changing `apps/hooks/src/` without changeset fails (unless pending changeset on branch).
- [ ] `fix:` empty/doc-only wake-up PR passes.
- [ ] Bot release PRs (`chore(release):`) are exempt.
Contributor guide
Assessment
This issue has not been assessed yet.