Skip to content

fix: promote develop → main#80

Merged
flywheel-build[bot] merged 13 commits intomainfrom
develop
May 6, 2026
Merged

fix: promote develop → main#80
flywheel-build[bot] merged 13 commits intomainfrom
develop

Conversation

@flywheel-build
Copy link
Copy Markdown
Contributor

@flywheel-build flywheel-build Bot commented May 6, 2026

Promote developmain

Pending commits (9 total):

chore

fix

  • aggregate Closes refs into promotion PR body (f445ee6)
  • split bypass scope so App can't auto-delete stream branches (c1d0157)
  • leave promotion PRs to runPromotion (use MERGE, not SQUASH) (0ad50df)

Status:flywheel:auto-mergefix is in auto_merge list for main

point-source and others added 6 commits May 6, 2026 12:39
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>
## [1.0.1-dev.2](v1.0.1-dev.1...v1.0.1-dev.2) (2026-05-06)

### Bug Fixes

* **pr-flow:** leave promotion PRs to runPromotion (use MERGE, not SQUASH) ([#79](#79)) ([0ad50df](0ad50df)), closes [#74](#74) [#77](#77)
@flywheel-build
Copy link
Copy Markdown
Contributor Author

flywheel-build Bot commented May 6, 2026

🎉 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>
@flywheel-build flywheel-build Bot enabled auto-merge May 6, 2026 14:33
## [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)
@flywheel-build
Copy link
Copy Markdown
Contributor Author

flywheel-build Bot commented May 6, 2026

🎉 This PR is included in version 1.0.1-dev.3 🎉

The release is available on GitHub release

Your semantic-release bot 📦🚀

point-source and others added 3 commits May 6, 2026 14:45
)

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>
@flywheel-build
Copy link
Copy Markdown
Contributor Author

flywheel-build Bot commented May 6, 2026

🎉 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>
@flywheel-build flywheel-build Bot merged commit 2a521a4 into main May 6, 2026
10 of 13 checks passed
@flywheel-build
Copy link
Copy Markdown
Contributor Author

flywheel-build Bot commented May 6, 2026

🎉 This PR is included in version 1.0.2-dev.1 🎉

The release is available on GitHub release

Your semantic-release bot 📦🚀

@flywheel-build
Copy link
Copy Markdown
Contributor Author

flywheel-build Bot commented May 6, 2026

🎉 This PR is included in version 1.0.2 🎉

The release is available on GitHub release

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants