fix: promote develop → main#80
Merged
flywheel-build[bot] merged 13 commits intomainfrom May 6, 2026
Merged
Conversation
The bootstrap window in scripts/init.sh enabled delete_branch_on_merge
before applying the managed-branches ruleset, leaving long-lived stream
branches (develop, customer-acme, etc.) unprotected from auto-deletion
when a PR merged. Reorder the two steps so the ruleset's {type: deletion}
rule lands first and the global delete-on-merge setting only flips on
top of an already-protected stream surface.
Add src/rulesets.ts with a TypeScript syncRulesets() that reconciles the
managed-branches and tag-namespace rulesets' include arrays with the
current .flywheel.yml. Hook it into the push event handler in src/main.ts
so any push that touches .flywheel.yml on a stream branch re-aligns the
rulesets — adopters who add or remove streams no longer need to re-run
scripts/apply-rulesets.sh manually for the ruleset to track the config.
Bundles roadmap §"Multi-stream tag namespace not fully protected": the
tag-namespace ruleset's include now covers both refs/tags/v* (primary
stream) and refs/tags/*/v* (stream-prefixed tags emitted by secondary
streams via src/release-rc.ts). syncRulesets writes both patterns and
the static JSON template matches.
Skip silently with a warning when the App lacks repository administration
scope (the rulesets API returns 403/404). Bootstrap path via
apply-rulesets.sh is unchanged and remains the recovery tool when a
ruleset goes missing entirely.
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Squash-commit messages often contain @-prefixed identifiers (@semantic-release/exec, @v1 floating tag, @octokit/auth-app, etc.) that GitHub's release-page renderer parses as user mentions and surfaces in the release's "Contributors" sidebar — adding bogus entries like a "v1" contributor on the v1.0.0 release page. Add a flywheel-push.yml step that runs after semantic-release publishes: fetches the new release body, wraps any @-prefixed identifier whose @ is at line-start or follows whitespace / opening bracket in backticks, and PATCHes the release if the body changed. Identifiers preceded by a word char (email-like user@host) or an existing backtick are left alone. Both the adopter template (scripts/templates/flywheel-push.yml) and the dogfood copy (.github/workflows/flywheel-push.yml) get the same step so workflow-template-parity stays green. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
After a release back-merge, develop fast-forwards to main's tip.
The next push-flow run on develop computes pending commits via the
existing strategies, which can return a non-empty list (Strategy A's
last-promotion regex misses merge-commit titles, and Strategy B's
title set-difference picks up commits whose normalized titles differ
slightly). The follow-up createPR call then 422s with "No commits
between main and develop" — semantic-release worked correctly, but
the action exits red.
Two changes covering both symptom and root cause:
1. Fast-path equal source/target tips in computePendingCommits — if
sourceCommits[0].sha === targetCommits[0].sha, return [] before
either strategy runs. Aligns pending detection with GitHub's
compare view for the post-back-merge state.
2. Catch 422 "No commits between" in runPromotion's createPR call
and return a new {kind: "in-sync"} outcome. Defensive guard for
races and any other strategy/GitHub disagreements that slip past
the fast path. Other 422 / 5xx errors still propagate.
Adds two regression tests in tests/promotion.test.ts: the equal-tip
fast-path returns [], and a stubbed createPR 422 funnels into the
in-sync outcome with no PR created.
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
## [1.0.1-dev.1](v1.0.0...v1.0.1-dev.1) (2026-05-06) ### Bug Fixes * prevent stream-branch deletion + auto-sync rulesets ([#60](#60)) ([#72](#72)) ([8942f18](8942f18)) * **promotion:** handle equal-tip promote 422 gracefully ([#71](#71)) ([#75](#75)) ([79bdf6e](79bdf6e)) * sanitize @-mentions in release body ([#70](#70)) ([#73](#73)) ([6cf2c1a](6cf2c1a))
Bundles four small, independent items that don't warrant their own PR: - semantic-release plugin majors: pin every @semantic-release/* plugin in scripts/templates/flywheel-push.yml and the dogfood copy. Floating majors meant every adopter pipeline picked up a plugin major release on the next push and could break unexpectedly. Pinned versions: commit-analyzer@13, release-notes-generator@14, changelog@6, exec@7, git@10, github@11. - listBranchCommits pagination: src/github.ts now uses octokit.paginate for the repo-listCommits call (mirroring the pattern already used at src/github.ts:170,211). Drops the unused perPage parameter from the GitHubClient interface and the two callers in src/promotion.ts. Long- lived branches with >200 commits between promotions previously lost Strategy A's cutoff and could double-list already-promoted commits. - Fork PRs: action.yml's app-private-key is now optional. src/main.ts detects an empty key (the fork-PR signal — GitHub doesn't pass repo secrets to fork PR workflows) and exits with a notice instead of failing. Other misconfigurations still surface via mintInstallationToken. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ASH) (#79) When runPromotion opens the develop→main PR it sets native auto-merge to MERGE method, since squashing on a promotion edge collapses every upstream feature commit into a single squash on the production branch and the v*.* release notes only see that one commit. The pull_request event for the same PR then fired runPrFlow, which has no notion of "promotion PR", matched fix in main.auto_merge, and re-enabled native auto-merge with SQUASH — overwriting the MERGE choice. PR #74 went out as a squash, so the v1.0.1 release notes contained only one "promote develop → main" entry instead of the per-fix list visible on the v1.0.1-dev.1 prerelease. Closes #78. Add isPromotionPR(config, headRef, baseRef, title) in src/promotion.ts that returns true only when the head/base pair is a configured promotion edge AND the title matches the runPromotion title shape (reuses the existing buildPromotionTitleRegex). Right after the title check in runPrFlow, short-circuit on isPromotionPR: post the flywheel/conventional-commit success check (so adopters can require it in branch protection without stalling the promotion PR) and return a new "promotion-pr" outcome. Skipping the rest of pr-flow also stops the body from flapping between formatPromotionBody (push event) and renderBody (pull_request event), which would otherwise clobber the Closes refs that #77 plans to aggregate into the body. tests/pr-flow.test.ts adds two cases: the canonical promotion-PR short-circuit (title check posted, no updatePR, no enableAutoMerge, no addLabels) and a negative case where head/base look promotion-y but the title is a plain fix — must fall through to the normal flow. tests/promotion.test.ts adds direct isPromotionPR coverage for the edge-and-title combinations: non-adjacent branches, reversed edges, single-branch streams, and non-promotion titles all return false; any conventional-commit type prefix on a configured edge returns true. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Contributor
Author
|
🎉 This PR is included in version 1.0.1-dev.2 🎉 The release is available on GitHub release Your semantic-release bot 📦🚀 |
…nches (#83) The single managed-branches ruleset bundled deletion + non_fast_forward + pull_request under one bypass_actors list. The App needs bypass on pull_request to push semantic-release's chore(release) commit and the back-merge directly to develop / main, so the App ended up with bypass_mode "always" — which also bypassed the deletion rule. When the App auto-merged a promotion or feature PR and GitHub's delete_branch_on_merge fired against the head branch, the deletion ran under the App's identity and was waved through. Result: develop got auto-deleted after the v1.0.1 promotion (PR #74) — see #81 for the full activity-log trace. Closes #81. GitHub rulesets only support actor-level bypass, not per-rule. The fix is to split the protections into two rulesets so the App's bypass scope is correctly narrow: Flywheel managed branches — destruction. Rules: deletion + non_fast_forward. bypass_actors: []. Even the App is blocked from deleting these branches or rewriting their history. Flywheel managed branches — review. Rules: pull_request (+ optional required_status_checks). App on bypass with mode "always" so semantic-release's direct pushes still work. Both rulesets cover the same ref_name include set. The destruction ruleset keeps the existing name "Flywheel managed branches" — the review ruleset is new — so PUT-in-place against the existing ruleset transparently sheds the pull_request rule and the bypass on the next apply-rulesets.sh re-run. Adopters who don't re-run the script keep working (their old combined ruleset still enforces everything correctly, just with the bypass-too-wide flaw); the review ruleset's absence is logged as a warning pointing back at #81. scripts/rulesets/managed-branches.json now contains only deletion + non_fast_forward and an empty bypass_actors. New scripts/rulesets/managed-branches-review.json holds the pull_request rule. scripts/apply-rulesets.sh applies both, gating the App-bypass injection on the review payload only — required-status-checks still goes onto review (where pull_request lives), as before. src/rulesets.ts adds MANAGED_BRANCHES_REVIEW_RULESET_NAME and extends syncRulesets to reconcile the review ruleset's include alongside the destruction one (both must track .flywheel.yml's branch list). SyncRulesetsResult gains reviewUpdated to surface which side drifted; main.ts still ignores the result so no caller-side change. tests/rulesets.test.ts gets a new makeReviewRuleset helper, and the existing fixtures stop putting App bypass on the destruction ruleset (it now reflects the post-#81 shape). New cases: both rulesets reconcile in sync when the include array drifts; pre-#81 adopters with only the legacy combined ruleset get a warning that points at #81 and apply-rulesets.sh, but their destruction ruleset's include still gets reconciled so they aren't blocked. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
## [1.0.1-dev.3](v1.0.1-dev.2...v1.0.1-dev.3) (2026-05-06) ### Bug Fixes * **rulesets:** split bypass scope so App can't auto-delete stream branches ([#83](#83)) ([c1d0157](c1d0157)), closes [#74](#74) [#81](#81) [post-#81](https://github.com/post-/issues/81) [pre-#81](https://github.com/pre-/issues/81) [#81](#81)
Contributor
Author
|
🎉 This PR is included in version 1.0.1-dev.3 🎉 The release is available on GitHub release Your semantic-release bot 📦🚀 |
) Of the six items in the roadmap, four shipped (fork-PR neutral exit, listBranchCommits pagination, multi-stream tag namespace, semantic-release plugin pinning) and the two remaining open items moved to issues: - #84 — Reusable workflow for the adopter surface - #85 — Simplify computePendingCommits now that ancestry is preserved The roadmap doc no longer carries information that isn't tracked elsewhere. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Issues referenced via Closes/Fixes/Resolves keywords in sub-PR descriptions stay open after the develop→main promotion lands. GitHub auto-closes an issue only when the PR/commit referencing it merges into the default branch. In this flow sub-PRs land on develop (non-default), and the default squash-commit message body on develop concatenates commit messages rather than the PR body, so the keyword references never reach the production branch. The promotion PR is the vehicle that does merge into main, but its body is generated from the pending-commits list and never contained Closes refs. Closes #77. In runPromotion, between computing pending and building the body, extract the trailing (#NN) PR number from each squash-merged commit title (the last (#NN) — original PR titles can already contain (#NN) issue links typed by the author), fetch each PR's description via a new gh.getPullBody(prNumber) helper that swallows 404s, and aggregate Closes/Fixes/Resolves #N matches via a case-insensitive regex that also accepts the colon variant ("Closes: #N"). Drop self-references — a sub-PR whose body says "closes #<itself>" is either a typo or a future-numbered issue GitHub already linked separately. Cross-repo refs (owner/repo#N) are intentionally skipped: the issue lives elsewhere and propagating it from a flywheel-owned PR risks closing unrelated issues. The aggregated set is sorted, deduped, and emitted as one "Closes #N" line per ref so GitHub's auto-close recognizes each one independently when the merge to the production branch lands. Two follow-on prerequisites for this to actually auto-close: 1. The promotion PR must merge into the default branch with a keyword still in the body. GitHub auto-close runs from the PR description, not the merge commit message — so this works regardless of merge method (squash/merge/rebase) — but pr-flow used to rewrite the promotion PR body via renderBody, which would have stripped the new Closes block. #78 already fixed that rewrite by short-circuiting promotion PRs in pr-flow. 2. main must be a configured production branch in .flywheel.yml. Already the case for the dogfood repo; no change needed. tests/promotion.test.ts adds runPromotion coverage for: refs aggregation across three sub-PRs (sorted/normalized), intra-body and cross-body deduplication, self-reference stripping, no-Closes case (no block emitted), 404 from getPullBody (sub-PR hard-deleted — promotion still upserts), and direct-pushed commits without a trailing (#NN) skipping the fetch entirely. A second describe block exercises extractClosesRefs directly: case-insensitive keyword set, colon variant, encounter-order duplicates, cross-repo skip, word-boundary enforcement, and null/empty body returning []. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
## [1.0.1-dev.4](v1.0.1-dev.3...v1.0.1-dev.4) (2026-05-06) ### Bug Fixes * **promotion:** aggregate Closes refs into promotion PR body ([#82](#82)) ([f445ee6](f445ee6)), closes [#NN](https://github.com/point-source/flywheel/issues/NN) [#NN](https://github.com/point-source/flywheel/issues/NN) [#NN](https://github.com/point-source/flywheel/issues/NN) [#N](https://github.com/point-source/flywheel/issues/N) [owner/repo#N](https://github.com/owner/repo/issues/N) [#78](#78) [#NN](https://github.com/point-source/flywheel/issues/NN)
Contributor
Author
|
🎉 This PR is included in version 1.0.1-dev.4 🎉 The release is available on GitHub release Your semantic-release bot 📦🚀 |
Restores the main → develop ancestry that didn't get re-established after PR #74 (the develop → main promotion). The post-promotion back-merge step in flywheel-push.yml ran while develop was being auto-deleted by delete_branch_on_merge (#81 / fixed in #83), so the git fetch origin develop:develop call exited non-zero and the merge never happened. After develop was restored, four more commits piled up (#76, #79, #82, #83) without main's chore(release): 1.0.1 ever landing on develop, leaving PR #80 in a CONFLICTING state. Resolution: - src/{github,promotion,rulesets}.ts and tests/{promotion,rulesets} .test.ts: take develop's content. Develop is a strict superset of main here — it contains the same #72/#73/#75 source that PR #74's squash carried into main, plus #79/#82/#83's evolutions on the same regions, so taking ours loses nothing. - CHANGELOG.md: keep develop's 1.0.1-dev.{2,3,4} entries and insert main's 1.0.1 release entry between dev.2 and dev.1, matching the publish chronology. semantic-release will rewrite this on the next release anyway. - dist/index.cjs: matches develop's already-committed bundle since the resolved src is identical to develop's. After this lands on develop, PR #80's three-way merge cleanly takes develop's superset and main's history merges in as a real two-parent commit (PR #79 fixed the SQUASH-on-promotion-PR bug that broke #74). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Contributor
Author
|
🎉 This PR is included in version 1.0.2-dev.1 🎉 The release is available on GitHub release Your semantic-release bot 📦🚀 |
Contributor
Author
|
🎉 This PR is included in version 1.0.2 🎉 The release is available on GitHub release Your semantic-release bot 📦🚀 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Promote
develop→mainPending commits (9 total):
chore
fix
Status: ✅
flywheel:auto-merge—fixis in auto_merge list formain