From 206decab9d07533d155c5409e6b7483cb996b915 Mon Sep 17 00:00:00 2001 From: Sid Jain Date: Fri, 19 Jun 2026 20:23:06 +0000 Subject: [PATCH] fix(ci): allow release PR merge commits --- .github/scripts/check-release-intent.sh | 22 ++++++--- .github/workflows/release.yml | 5 ++ .../assets/generated/asset-inputs.sha256 | 2 +- tools/release/sync_release_pr.py | 48 +++++++++++++++++++ 4 files changed, 70 insertions(+), 7 deletions(-) diff --git a/.github/scripts/check-release-intent.sh b/.github/scripts/check-release-intent.sh index 4ba0aab7..8556ed7d 100755 --- a/.github/scripts/check-release-intent.sh +++ b/.github/scripts/check-release-intent.sh @@ -15,8 +15,16 @@ release_pattern='^((feat|fix|perf|refactor|revert)(\([a-z0-9][a-z0-9._/-]*\))?(! release_pr_pattern='^chore\(release\): .+' is_release_pr=false -if [[ "${subject}" =~ ${release_pr_pattern} && "${head_branch}" == release/* ]]; then - is_release_pr=true +is_generated_release_branch=false +case "${head_branch}" in + release/* | release-please--branches--*) + is_generated_release_branch=true + ;; +esac +if [[ "${subject}" =~ ${release_pr_pattern} ]]; then + if [[ "${is_generated_release_branch}" == true || "${head_branch}" == "main" ]]; then + is_release_pr=true + fi fi package_versions_from_ref() { @@ -113,8 +121,9 @@ Package and release-please manifest version bumps are release owned. Run the Release workflow with prepare-release-pr and merge the generated release PR instead of changing versions in a feature/fix PR. -Generated release PRs are allowed only from release/* branches and -when their title starts with chore(release):. +Generated release PRs are allowed only from generated release branches, and +their main merge commits are allowed only when the subject starts with +chore(release):. Received: ${subject} @@ -167,8 +176,9 @@ Use one of these Conventional Commit types in the PR title: Breaking changes may use any type with !, for example: chore!: remove a deprecated API -Generated release PRs are exempt only from release/* branches and when -their title starts with chore(release):. +Generated release PRs are exempt only from generated release branches, and +their main merge commits are exempt only when the subject starts with +chore(release):. Docs, README, CI, tests, examples, xtask-only maintenance, source-checkout scripts, and other repository-only changes can keep non-release types such as diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 3772b758..3aa529ff 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -81,6 +81,11 @@ jobs: - name: Set up Moon uses: ./.github/actions/setup-moon + - name: Set up Rust + uses: ./.github/actions/setup-rust + with: + cache-save-if: "true" + - name: Validate release metadata run: | tools/release/release.py check diff --git a/src/runtimes/liboliphaunt/wasix/assets/generated/asset-inputs.sha256 b/src/runtimes/liboliphaunt/wasix/assets/generated/asset-inputs.sha256 index 773c0b08..0b1c1a4f 100644 --- a/src/runtimes/liboliphaunt/wasix/assets/generated/asset-inputs.sha256 +++ b/src/runtimes/liboliphaunt/wasix/assets/generated/asset-inputs.sha256 @@ -1 +1 @@ -7496ad92c25f4d01d0cc3db01ca83c2841f5bdeb1dd8c45b152458dea6cb0bc3 +7b87d6512c9f965d4147dd3027f20ead7b21b9dc34cc704583a1ce675df5d18e diff --git a/tools/release/sync_release_pr.py b/tools/release/sync_release_pr.py index 5def8cf8..ee4c1ecc 100755 --- a/tools/release/sync_release_pr.py +++ b/tools/release/sync_release_pr.py @@ -6,6 +6,7 @@ import argparse import json import re +import subprocess import sys import tomllib from dataclasses import dataclass @@ -35,6 +36,10 @@ TOML_TABLE_RE = re.compile(r"^\s*\[([A-Za-z0-9_.-]+)\]\s*(?:#.*)?$") PNPM_NODE_DIRECT_KEY_RE = re.compile(r"^(\s*)'(@oliphaunt/node-direct-[^']+)':\s*$") PNPM_SPECIFIER_RE = re.compile(r"^(\s*specifier:\s*)(\S+)(\s*)$") +ASSET_INPUT_FINGERPRINT_PATH = ROOT / "src/runtimes/liboliphaunt/wasix/assets/generated/asset-inputs.sha256" +ASSET_INPUT_FINGERPRINT_MISMATCH_RE = re.compile( + r"committed asset input fingerprint must be '([0-9a-f]+)', got '([0-9a-f]+)'" +) @dataclass(frozen=True) @@ -427,6 +432,48 @@ def sync_lockfiles(changes: list[Change], *, write: bool) -> None: sync_lockfile(lockfile, versions, changes, write=write) +def read_optional_text(path: Path) -> str | None: + if not path.exists(): + return None + return path.read_text(encoding="utf-8") + + +def command_output_for_error(result: subprocess.CompletedProcess[str]) -> str: + parts = [part.strip() for part in (result.stdout, result.stderr) if part.strip()] + return "\n".join(parts) or f"exit {result.returncode}" + + +def sync_asset_input_fingerprint(changes: list[Change], *, write: bool) -> None: + command = ["cargo", "run", "-p", "xtask", "--", "assets", "input-fingerprint"] + if write: + command.append("--write") + + before = read_optional_text(ASSET_INPUT_FINGERPRINT_PATH) + result = subprocess.run(command, cwd=ROOT, text=True, capture_output=True, check=False) + output = command_output_for_error(result) + + if result.returncode != 0: + mismatch = ASSET_INPUT_FINGERPRINT_MISMATCH_RE.search(output) + if not write and mismatch is not None: + changes.append( + Change( + ASSET_INPUT_FINGERPRINT_PATH, + f"{mismatch.group(1)} -> {mismatch.group(2)}", + ) + ) + return + fail(f"`{' '.join(command)}` failed:\n{output}") + + if not write: + return + + after = read_optional_text(ASSET_INPUT_FINGERPRINT_PATH) + if before != after: + old = before.strip() if before is not None else "" + new = after.strip() if after is not None else "" + changes.append(Change(ASSET_INPUT_FINGERPRINT_PATH, f"{old} -> {new}")) + + def main() -> int: parser = argparse.ArgumentParser(description=__doc__) parser.add_argument("--check", action="store_true", help="fail instead of writing updates") @@ -439,6 +486,7 @@ def main() -> int: sync_pnpm_node_direct_optional_specifiers(changes, write=write) sync_cargo_path_dependency_pins(changes, write=write) sync_lockfiles(changes, write=write) + sync_asset_input_fingerprint(changes, write=write) if not changes: print("release PR derived files are in sync")