Repository navigation
feat: add runtime beta gating to sync-release-branches action - #62
Conversation
Ref: ED-25747 Implement runtime gating to prevent sync PRs to release/beta when beta is inactive. Beta is active if the X.Y version on release/beta > X.Y of latest published stable release. Changes: - Add check-beta-active.ts utility to determine beta status - Update sync-release-branches.sh to call beta check and conditionally include beta target - Add vitest tests covering all scenarios - Update README with beta gating explanation - Fail open (include beta) on errors or unavailable dependencies Co-authored-by: Davids Seveloff <davseve@users.noreply.github.com>
Wiz Scan Summary
To detect these findings earlier in the dev lifecycle, try the Wiz Code extension for VS Code, JetBrains, or Visual Studio. |
| pluginFile: string, | ||
| ): BetaCheckResult { | ||
| const betaVersion = getVersionFromBranch('release/beta', pluginFile); | ||
| if (!betaVersion) { |
There was a problem hiding this comment.
Please log this (e.g. console.warn) so a fail-open is visible in the run log. Today the reason only lives in the JSON, and the shell reads just .isActive. Note that the shell runs this with 2>&1, so stderr output would break the jq parse; drop 2>&1 there.
- Log fail-open reasons to stderr via console.warn for visibility - Remove 2>&1 from beta check invocation so stderr warnings don't break jq - Clarify 'latest published stable GitHub Release' definition in README - Document edge case where hotfix release can trigger harmless extra sync PR Addresses review comments: - #62 (comment) - #62 (comment) Co-authored-by: Davids Seveloff <davseve@users.noreply.github.com>
Ntnelbaba
left a comment
There was a problem hiding this comment.
Nit: action.yml's description still says release/stable merges open PRs to both release/beta and main; it should mention the beta gating.
Ref: ED-25747 Comprehensive fixes addressing all review comments from Netanel and David: HIGH PRIORITY FIXES: 1. Fixed stdout pollution (L60, L65): All diagnostic messages now go to stderr using >&2. Only CSV output goes to stdout from resolve_targets_csv. 2. Fixed npx tsx dependency issue: Rewrote entire solution in pure bash. - No npm dependencies needed - Uses git show + gh api for data fetching - Simpler, more reliable, works offline in composite action - Eliminated ERR_MODULE_NOT_FOUND errors 3. Fixed releases/latest logic for Pro compatibility: - List all releases via GitHub API with pagination - Exclude drafts and prereleases - Exclude automated build tags (-latest-, nightly) - Exclude pre-release version strings (-beta, -dev, -rc) - Take highest remaining semver by tag - Fixes issue where Pro's 4.4.0-latest-<ts> builds were treated as stable MEDIUM PRIORITY FIXES: 4. Tests now exercise real logic with bash test suite (check-beta-active.test.sh) - Tests extract_major_minor, version_gt, extract_version_from_file - All helper functions tested directly - 12 tests covering all scenarios 5. Empty/invalid JSON now fails open: - Check for empty result - Only exact 'true' or 'false' values decide - Any other value (empty, null, etc.) fails open with warning 6. README corrections: - Removed false claim that beta tags are always prereleases - Documented that Latest can be automated -latest- builds - Explained complete filtering logic - Noted edge case that hotfix on older line reopens the problem LOW PRIORITY FIXES: 7. No more fragile import.meta.url check (pure bash) 8. Version extraction now inline (can reuse plugin-config in future PR) 9. Missing origin/release/beta reported clearly as missing ref 10. Pure numeric major/minor comparison (no building X.Y.0 strings) 11. Removed unused @actions/core, @types/node, globals: true 12. Fixed grep -q substring match - now splits CSV and exact matches 13. Updated action.yml description to mention beta gating 14. Branch name variables used consistently ( not hardcoded) Changes: - NEW: check-beta-active.sh - Pure bash implementation - NEW: check-beta-active.test.sh - Bash test suite - DELETED: check-beta-active.ts, check-beta-active.test.ts (replaced with bash) - DELETED: tsconfig.json, vitest.config.ts (no longer needed) - MODIFIED: sync-release-branches.sh - Calls bash script, fixes stderr - MODIFIED: README.md - Complete filtering documentation - MODIFIED: action.yml - Updated description - MODIFIED: package.json - Simplified, runs bash tests All tests pass, lint passes. Co-authored-by: Davids Seveloff <davseve@users.noreply.github.com>
1c18dc2 to
9b2bc6e
Compare
- Remove 2>&1 from check-beta-active.sh call to prevent stderr pollution of JSON output - Remove misplaced 'local found_beta=false' variable outside function scope - Add BASH_SOURCE guard to check-beta-active.sh for safe sourcing in tests - Add git rev-parse checks to detect_plugin_file and get_beta_version to fail open when origin/release/beta ref is missing - Change ordering: release/stable merges now open main PR first, then check beta gating - Update resolve_targets_csv to only return main for stable branch - Add post-loop beta check for stable merges - Add comprehensive tests for get_latest_stable_release with gh stubbed - Add tests covering Pro -latest- tags, drafts, nightly/beta/dev/rc filtering, and highest semver selection - Add tests for check_beta_active true/false decision logic - Update README cascade table and reword hotfix edge case - Update action.yml description to reflect new ordering Co-authored-by: Davids Seveloff <davseve@users.noreply.github.com>
Ntnelbaba
left a comment
There was a problem hiding this comment.
Looks good after the bash rewrite, a few small notes inline.
|
|
||
| # Detect plugin file (elementor.php or elementor-pro.php) | ||
| detect_plugin_file() { | ||
| if ! git rev-parse --verify --quiet origin/release/beta >/dev/null; then |
There was a problem hiding this comment.
Low: this hard-codes origin/release/beta, but beta-branch is a configurable input. Pass BETA_BRANCH in (it's already in the step's env) and use it here, or the check reads the wrong branch when a caller overrides it.
| @@ -467,4 +502,14 @@ for target in "${TARGETS[@]}"; do | |||
| sync_to_target "$target" | |||
There was a problem hiding this comment.
Low: beta now syncs after the main loop. Under set -e, if sync_to_target returns non-zero for main (the push failure in create_success_pr comes back as return 1), the script exits and beta is never checked or synced. Before this change beta ran first. Consider sync_to_target "$target" || true, or tracking failures so beta still runs.
| get_latest_stable_release() { | ||
| local releases tag is_draft is_prerelease | ||
|
|
||
| if ! releases="$(gh api repos/"${REPO}"/releases --paginate --jq '.[] | {tag_name, draft, prerelease}' 2>&1)"; then |
There was a problem hiding this comment.
Low: --paginate downloads every release on each stable merge, which for Core is several pages, and 2>&1 mixes gh stderr into the data being parsed. Drop 2>&1 and send stderr to a separate variable or file. You could also stop after the first page or two, since the newest stable releases are on page 1.
| get_beta_version() { | ||
| local plugin_file content version | ||
|
|
||
| if ! git rev-parse --verify --quiet origin/release/beta >/dev/null; then |
There was a problem hiding this comment.
Nit: this repeats the git rev-parse check that detect_plugin_file already does on L49.
| fi | ||
|
|
||
| # Extract X.Y and compare | ||
| if major_minor="$(extract_major_minor "$tag")"; then |
There was a problem hiding this comment.
Nit: major_minor isn't declared local, unlike major/minor on L171.
|
|
||
| if [ "$plugin_file" = "elementor.php" ]; then | ||
| # Try header format first: * Version: X.Y.Z | ||
| if echo "$content" | grep -m1 '^\s*\*\s*Version:' | sed 's/^\s*\*\s*Version:\s*//' | grep -q '^[0-9]'; then |
There was a problem hiding this comment.
Nit: this header pipeline runs twice, once to test and once to print. Capture it once in a variable.
| IFS='.' read -r major minor <<< "$major_minor" | ||
|
|
||
| # Keep the highest version | ||
| if [ "$major" -gt "$latest_major" ] || \ |
There was a problem hiding this comment.
Nit: only a higher X.Y replaces the kept tag, so on a tie the first tag seen is kept. The log can then show 4.3.1 when 4.3.4 exists. The decision is unaffected, but the log is misleading.
| - Beta tags (e.g., `4.3.0-beta3`) can be published as non-prerelease in some repos | ||
| - The GitHub "Latest" release may be an automated build rather than a hand-published stable | ||
|
|
||
| By taking the highest semver tag instead of GitHub's "Latest" release, the action correctly handles cases where hotfix releases on older lines (e.g., `4.3.7`) are published after newer releases (e.g., `4.4.0`). |
There was a problem hiding this comment.
Nit: this says it takes 'the highest semver tag', but the code compares only X.Y (see L175). Suggest 'highest X.Y'.
Summary
Implements runtime gating for the
release/stable→release/betasync PR in thesync-release-branchescomposite action to prevent noisy sync PRs when no active beta cycle exists.All review comments from Netanel and David have been addressed. Rewrote the entire solution in pure bash for reliability and simplicity.
High Priority Fixes ✅
>&2). Only CSV output goes to stdout.git show+gh apidirectly. Works offline.-latest-*/nightly/-beta/-dev/-rc, takes highest remaining semver. Works for both Core and Pro.Medium Priority Fixes ✅
true/falsedecides, any other value fails open.Low Priority Fixes ✅
7-14. All addressed: no fragile import.meta.url (pure bash), clear missing ref error, numeric compare, removed unused deps, exact CSV match, updated action.yml description.
Context
Related: ED-25747 (release automation)
Problem: When a PR merges into
release/stable, the action opens downstream PRs to bothrelease/betaandmain. Therelease/betabranch always exists even when there is no active beta, resulting in noisy sync PRs that often conflict and are later closed (e.g., elementor/elementor#37578, #37522).Solution
The action now computes at runtime whether a beta is active:
release/beta> X.Y of the latest published stable release-latest-,nightly)-beta,-dev,-rc)resolve_targets_csvincluderelease/betafor merges intorelease/stablemainis targetedImplementation
Core Logic (
check-beta-active.sh)Pure bash script that:
elementor.php, Pro:elementor-pro.php) by checking which file exists onrelease/betarelease/betausinggit show origin/release/beta:<file>gh api repos/$REPO/releases --paginateBash Integration
Updated
sync-release-branches.sh:check_beta_active()function that calls the bash scriptresolve_targets_csv()to conditionally include beta target based on check resultTesting
Comprehensive bash test suite in
check-beta-active.test.sh:extract_major_minorwith various version formatsversion_gtfor major/minor comparisonextract_version_from_filefor Core header, Core constant, Pro constantScenarios validated:
-latest-tags → excluded from stable baselineWhy Pure Bash?
Chose bash over TypeScript for:
Open Points Resolved
1. Stable patch releases format
Confirmed: Stable patch releases (e.g., 4.3.6) are published as normal (non-prerelease) GitHub Releases. The filtering logic handles this: X.Y comparison ignores patch versions.
2. Why not file-vs-file (release/beta vs release/stable)?
Answer: Using the GitHub Releases API with proper filtering is more reliable because:
-latest-builds that pollute the "Latest" badge-beta,-dev,-rc)release/stableversion file is bumped before publish3. Automated
-latest-buildsVerified: Pro's published releases include many
4.4.0-latest-<timestamp>builds marked as non-prerelease. The new filtering logic explicitly excludes these, fixing the core issue where Pro beta gating never activated.Changes
actions/sync-release-branches/check-beta-active.sh- Pure bash implementation with filteringactions/sync-release-branches/check-beta-active.test.sh- Bash test suitecheck-beta-active.ts,check-beta-active.test.ts,tsconfig.json,vitest.config.ts(replaced with bash)sync-release-branches/sync-release-branches.sh- Calls bash script, all diagnostics to stderrsync-release-branches/README.md- Complete filtering documentationsync-release-branches/action.yml- Updated description to mention beta gatingsync-release-branches/package.json- Simplified, runs bash testsTesting Results
Edge Case
Hotfix on older line: If a hotfix release on an older stable line becomes "Latest" (e.g.,
4.3.7published while4.4.0already exists), it can lower the stable X.Y baseline and make beta appear active again, reopening the noisy sync PR issue this gating aims to prevent. This is noted in the README.Deployment Notes
Caller workflows: Both
elementor/elementorandelementor/elementor-propinsync-release-branches.ymlto@mainin their workflows. After this PR merges:release/stable@main)@maincommitVerification plan:
release/stablemerge