Repository navigation
Update downloads tracking snapshot #12
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
| name: Update Releases Properties | |
| # This workflow runs in individual module repositories (e.g., module-php). | |
| # It updates releases.properties when a PR is opened, a release is edited | |
| # (files changed) or promoted to a full release, or run manually, and it always | |
| # notifies Bearsampp's quickpick dispatcher via a repository_dispatch event. | |
| # | |
| # PR trigger: auto-detects the latest prerelease from the repo's releases. | |
| # Manual trigger: requires release_tag and branch inputs; runs the full action. | |
| # Release event behavior: | |
| # - 'prereleased', or 'published' while still marked as a pre-release: | |
| # full action that adds/updates the new version in releases.properties | |
| # on the version PR's branch (then dispatches to quickpick). | |
| # - 'edited' (files changed): full action that rechecks the file URL and | |
| # updates the stored version/url when the file URL has changed, and | |
| # purges any entries whose files were removed from the release. | |
| # - 'released' (pre-release -> full "latest"): full action. NOTE: GitHub does | |
| # not reliably deliver a 'released'/'edited' event when an existing | |
| # release is converted to latest (known limitation), so the workflow's | |
| # heuristics never RELY on it. The initial 'published' full action | |
| # already keeps releases.properties current; use workflow_dispatch if a | |
| # recombine is ever needed after a conversion. | |
| # - 'deleted' (release removed, or its files removed): full action that | |
| # PURGES the release's version/url entries from releases.properties, | |
| # then dispatches so quickpick recombines without the removed version. | |
| # | |
| # TEST RE-RUN CONTRACT: | |
| # Updating the PR branch (git push) is what makes the module test workflows | |
| # re-run (via the PR "synchronize" event). Every full action — pre-release, | |
| # edited, released, manual — pushes the PR branch only when releases.properties | |
| # actually changed (has_changes == true). A new pre-release therefore adds its | |
| # entry (triggering a test re-run); cosmetic edits (title/notes only) leave | |
| # releases.properties unchanged and do NOT re-run the tests. | |
| on: | |
| pull_request: | |
| types: [opened] | |
| release: | |
| # 'released' covers pre-release -> full release ("latest") conversions. | |
| # 'prereleased' covers a newly created pre-release. | |
| # 'deleted' triggers a purge of the release's version/url entries. | |
| types: [published, prereleased, released, edited, deleted] | |
| workflow_dispatch: | |
| inputs: | |
| release_tag: | |
| description: 'Release tag to process (e.g., php-2025.10.31)' | |
| required: true | |
| branch: | |
| description: 'Branch to commit changes to' | |
| required: true | |
| concurrency: | |
| group: update-releases-${{ github.head_ref || github.event.inputs.branch || github.event.release.target_commitish || 'default' }} | |
| cancel-in-progress: false | |
| jobs: | |
| update-properties: | |
| runs-on: ubuntu-26.04 | |
| # Full action for every trigger. A pre-release (`prereleased`, or `published` | |
| # while still marked as a pre-release) updates releases.properties on the | |
| # version PR's branch too, so branch module tests can download the new build. | |
| if: | | |
| !endsWith(github.repository, '.settings') | |
| env: | |
| FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true | |
| steps: | |
| # --------------------------------------------------------------------- | |
| # Resolve WHICH prerelease to read and WHICH branch to update. | |
| # | |
| # The module workflow is: create a version branch (e.g. 9.7.2) + PR, | |
| # build + upload a prerelease (e.g. 2026.8.27), then merge. releases.properties | |
| # must be updated ON that branch so branch module tests (mysql/mariadb-test) | |
| # can download the new version. Ordering is not guaranteed: the PR may be | |
| # opened before or after the prerelease is published, so both triggers are | |
| # idempotent and defer gracefully instead of failing. | |
| # --------------------------------------------------------------------- | |
| - name: Resolve release tag and branch | |
| id: resolve | |
| run: | | |
| if [[ "${{ github.event_name }}" == "pull_request" ]]; then | |
| BRANCH="${{ github.head_ref }}" | |
| # Auto-detect the latest prerelease. If none exists yet (the | |
| # prerelease-before-merge is usually created after the PR), do NOT | |
| # fail — defer to the release event, which will update this branch. | |
| RELEASE_TAG=$(curl -sf -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \ | |
| "https://api.github.com/repos/${{ github.repository }}/releases?per_page=10" \ | |
| | python3 -c " | |
| import sys, json | |
| for r in json.load(sys.stdin): | |
| if r.get('prerelease'): | |
| print(r['tag_name']); break | |
| ") | |
| if [[ -z "$RELEASE_TAG" ]]; then | |
| echo "skip=true" >> $GITHUB_OUTPUT | |
| echo "::notice::No prerelease published yet; the release event will update this branch once 2026.*.* is published." | |
| echo "branch=$BRANCH" >> $GITHUB_OUTPUT | |
| exit 0 | |
| fi | |
| elif [[ "${{ github.event_name }}" == "release" ]]; then | |
| RELEASE_TAG="${{ github.event.release.tag_name }}" | |
| # The release has the data but no branch. If a version PR is open, | |
| # push the update to its branch so branch tests see the new version; | |
| # otherwise fall back to main. | |
| BRANCH=$(curl -sf -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \ | |
| "https://api.github.com/repos/${{ github.repository }}/pulls?state=open&per_page=100" \ | |
| | python3 -c " | |
| import sys, json | |
| for pr in json.load(sys.stdin): | |
| if pr['head']['ref'] != 'main': | |
| print(pr['head']['ref']); break | |
| ") | |
| BRANCH="${BRANCH:-main}" | |
| else | |
| RELEASE_TAG="${{ github.event.inputs.release_tag }}" | |
| BRANCH="${{ github.event.inputs.branch }}" | |
| fi | |
| echo "release_tag=$RELEASE_TAG" >> $GITHUB_OUTPUT | |
| echo "branch=$BRANCH" >> $GITHUB_OUTPUT | |
| echo "Release tag: $RELEASE_TAG" | |
| echo "Branch: $BRANCH" | |
| - name: Checkout repository | |
| if: steps.resolve.outputs.skip != 'true' | |
| uses: actions/checkout@v6 | |
| with: | |
| token: ${{ secrets.GH_PAT }} | |
| ref: ${{ steps.resolve.outputs.branch }} | |
| persist-credentials: false | |
| - name: Set up Python | |
| uses: actions/setup-python@v6 | |
| with: | |
| python-version: '3.13' | |
| - name: Install dependencies | |
| run: | | |
| pip install requests packaging | |
| - name: Extract module name from repository | |
| id: extract_module | |
| run: | | |
| # Extract module name from repo name (e.g., "module-php" -> "php") | |
| REPO_NAME="${{ github.event.repository.name }}" | |
| echo "Repository name: ${REPO_NAME}" | |
| # Remove "module-" prefix (case-insensitive) | |
| if [[ "${REPO_NAME}" == module-* ]]; then | |
| MODULE_NAME="${REPO_NAME#module-}" | |
| elif [[ "${REPO_NAME}" == Module-* ]]; then | |
| MODULE_NAME="${REPO_NAME#Module-}" | |
| else | |
| echo "ERROR: Repository name does not start with 'module-'" | |
| echo "Expected format: module-MODULENAME (e.g., module-php)" | |
| exit 1 | |
| fi | |
| # Convert to lowercase | |
| MODULE_NAME=$(echo "${MODULE_NAME}" | tr '[:upper:]' '[:lower:]') | |
| echo "module_name=${MODULE_NAME}" >> $GITHUB_OUTPUT | |
| echo "Module name: ${MODULE_NAME}" | |
| - name: Update releases properties | |
| if: steps.resolve.outputs.skip != 'true' | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| RELEASE_TAG: ${{ steps.resolve.outputs.release_tag }} | |
| REPO_OWNER: ${{ github.repository_owner }} | |
| REPO_NAME: ${{ github.event.repository.name }} | |
| MODULE_NAME: ${{ steps.extract_module.outputs.module_name }} | |
| EVENT_NAME: ${{ github.event_name }} | |
| RELEASE_ACTION: ${{ github.event.action }} | |
| RELEASE_RAW_TAG: ${{ github.event.release.tag_name }} | |
| run: | | |
| python << 'EOF' | |
| import os | |
| import re | |
| import requests | |
| from packaging import version | |
| from collections import OrderedDict | |
| # Get environment variables | |
| release_tag = os.environ['RELEASE_TAG'] | |
| repo_owner = os.environ['REPO_OWNER'] | |
| repo_name = os.environ['REPO_NAME'] | |
| module_name = os.environ['MODULE_NAME'] | |
| github_token = os.environ['GITHUB_TOKEN'] | |
| event_name = os.environ.get('EVENT_NAME', '') | |
| release_action = os.environ.get('RELEASE_ACTION', '') | |
| raw_release_tag = os.environ.get('RELEASE_RAW_TAG', '') | |
| is_delete = event_name == 'release' and release_action == 'deleted' | |
| # Strip module name prefix from tag if present (e.g., "postgresql-2025.11.22" -> "2025.11.22") | |
| if release_tag.lower().startswith(f"{module_name.lower()}-"): | |
| original_tag = release_tag | |
| release_tag = release_tag[len(module_name)+1:] | |
| print(f"Stripped module prefix from tag: {original_tag} -> {release_tag}") | |
| print(f"Module name: {module_name}") | |
| # Read existing properties file | |
| properties_file = "releases.properties" | |
| if not os.path.exists(properties_file): | |
| print(f"Properties file not found: {properties_file}") | |
| if is_delete: | |
| print(f"Nothing to purge (properties file missing)") | |
| exit(0) | |
| print(f"Creating new properties file...") | |
| with open(properties_file, 'w', encoding='utf-8') as f: | |
| f.write(f"# {module_name.upper()} Releases Properties\n") | |
| f.write(f"# Auto-generated and maintained by automation\n\n") | |
| with open(properties_file, 'r', encoding='utf-8') as f: | |
| lines = f.readlines() | |
| # Parse existing properties | |
| properties = OrderedDict() | |
| header_lines = [] | |
| in_header = True | |
| for line in lines: | |
| stripped = line.strip() | |
| if in_header and (stripped.startswith('#') or stripped == ''): | |
| header_lines.append(line) | |
| else: | |
| in_header = False | |
| if '=' in line and not stripped.startswith('#'): | |
| key, value = line.split('=', 1) | |
| properties[key.strip()] = value.strip() | |
| original_release_tag = release_tag | |
| match_tags = {raw_release_tag} if raw_release_tag else set() | |
| if original_release_tag: | |
| match_tags.add(original_release_tag) | |
| if release_tag and release_tag != original_release_tag: | |
| match_tags.add(release_tag) | |
| def purge_release_entries(): | |
| # Every stored URL embeds the release tag in the download path | |
| # (e.g. .../releases/download/2026.9.19/...), so matching on that | |
| # removes the version/url pair(s) that came from this release. | |
| removed = [] | |
| for key, url in list(properties.items()): | |
| if any(tag and f"/releases/download/{tag}/" in url for tag in match_tags): | |
| print(f"Removing: {key} = {url}") | |
| del properties[key] | |
| removed.append(key) | |
| return removed | |
| if is_delete: | |
| # The release itself was deleted (tag no longer exists): purge | |
| # every stored entry that shipped from it. | |
| print(f"Release deleted: {raw_release_tag} — purging its entries from releases.properties") | |
| purged = purge_release_entries() | |
| if purged: | |
| print(f"Purged {len(purged)} entries for deleted release") | |
| else: | |
| print(f"No stored entries referenced the deleted release tag") | |
| else: | |
| print(f"Processing release: {release_tag}") | |
| # Construct API URL | |
| api_url = f"https://api.github.com/repos/{repo_owner}/{repo_name}/releases/tags/{release_tag}" | |
| headers = { | |
| 'Authorization': f'token {github_token}', | |
| 'Accept': 'application/vnd.github.v3+json' | |
| } | |
| print(f"Fetching release information from: {api_url}") | |
| # Fetch release data | |
| response = requests.get(api_url, headers=headers) | |
| response.raise_for_status() | |
| release_data = response.json() | |
| # Extract assets that end with .7z only | |
| assets = [] | |
| # Use bearsampp-module format as the filename prefix (e.g., "bearsampp-postgresql") | |
| filename_prefix = f"bearsampp-{module_name}" | |
| print(f"Looking for .7z files with prefix: {filename_prefix}") | |
| print(f"Available assets:") | |
| for asset in release_data.get('assets', []): | |
| print(f" - {asset['name']}") | |
| for asset in release_data.get('assets', []): | |
| filename = asset['name'] | |
| # Check if file ends with .7z or .7z.exe | |
| if filename.endswith('.7z') or filename.endswith('.7z.exe'): | |
| download_url = asset['browser_download_url'] | |
| version_match = None | |
| # All modules use bearsampp-prefix format (e.g., "bearsampp-apache-2.4.66-2025.12.8.7z" -> "2.4.66") | |
| # Optional (?:-1)? handles revision-style versions like "bearsampp-ruby-4.0.6-1-2026.8.9.7z" -> "4.0.6-1". | |
| # Arbitrary build counters (e.g. Apache "-260617") are NOT captured as part of the version. | |
| # Optional (?:-?(?:RC|beta|alpha|dev)\d*)? preserves prerelease suffixes in the key, | |
| # both hyphenated (e.g., "bearsampp-mariadb-13.0.1-RC-2026.8.23.7z" -> "13.0.1-RC") | |
| # and no-separator (e.g., "bearsampp-php-8.6.0beta3-2026.9.13.7z" -> "8.6.0beta3"). | |
| # Legacy: also handle PortableGit format for older Git releases | |
| version_match = re.search( | |
| rf'{filename_prefix}-(\d+\.\d+(?:\.\d+)?(?:\.\d+)?(?:-1)?(?:-?(?:RC|beta|alpha|dev)\d*)?)(?:-\d+)?(?=-20\d{{2}}\.\d{{1,2}}\.\d{{1,2}}|\.7z|$)', | |
| filename, | |
| re.IGNORECASE | |
| ) | |
| if not version_match and module_name.lower() == 'git': | |
| # Fallback: legacy PortableGit format (e.g., "PortableGit-2.55.0.2-64-bit.7z.exe" -> "2.55.0.2") | |
| version_match = re.search( | |
| r'PortableGit-(\d+\.\d+(?:\.\d+)*)(?=-(?:\d+-)?bit|\.7z)', | |
| filename, | |
| re.IGNORECASE | |
| ) | |
| if version_match: | |
| ver = version_match.group(1) | |
| assets.append({ | |
| 'version': ver, | |
| 'url': download_url, | |
| 'filename': filename | |
| }) | |
| print(f"Found: {filename} -> Version: {ver}") | |
| else: | |
| print(f"Warning: Could not extract version from: {filename}") | |
| # Debug: print the filename for troubleshooting | |
| print(f" Filename: {filename}") | |
| if module_name.lower() == 'git': | |
| print(f" Expected PortableGit format") | |
| if not assets: | |
| if release_action == 'edited': | |
| # The release still exists but its files were removed | |
| # (e.g. the tag remains but the .7z assets are gone). | |
| print(f"Release edited: no .7z files remain (files removed) — purging its entries from releases.properties") | |
| purged = purge_release_entries() | |
| if purged: | |
| print(f"Purged {len(purged)} entries for the removed files") | |
| else: | |
| print(f"No stored entries referenced the removed files") | |
| else: | |
| print(f"No .7z assets found in release") | |
| exit(0) | |
| # Fix malformed version entries by re-extracting correct versions from URLs | |
| # This handles cases like "2025.7.2.7" or "18.1-2025" that should be "18.1" | |
| # NOTE: Existing entries are never removed — releases.properties is the complete history. | |
| # Stale/404 URLs are kept because the asset may still exist under a different URL | |
| # (e.g. re-hosted under a newer release or on the website's bin/archived/ path). | |
| fixed_properties = OrderedDict() | |
| for key, url in properties.items(): | |
| # Try to extract the correct version from the URL | |
| # URL format: .../bearsampp-apache-2.4.66-2025.12.8.7z (for apache) | |
| # URL format: .../bearsampp-postgresql-18.1-2025.12.8.7z (for other modules) | |
| # URL format: .../PortableGit-2.55.0.2-64-bit.7z.exe (for Git) | |
| url_version_match = re.search( | |
| rf'{filename_prefix}-(\d+\.\d+(?:\.\d+)?(?:\.\d+)?(?:-1)?(?:-?(?:RC|beta|alpha|dev)\d*)?)(?:-\d+)?(?=-20\d{{2}}\.\d{{1,2}}\.\d{{1,2}}|\.7z|/|$)', | |
| url, | |
| re.IGNORECASE | |
| ) | |
| if not url_version_match and module_name.lower() == 'git': | |
| url_version_match = re.search( | |
| r'PortableGit-(\d+\.\d+(?:\.\d+)*)(?=-(?:\d+-)?bit|\.7z)', | |
| url, | |
| re.IGNORECASE | |
| ) | |
| if url_version_match: | |
| correct_version = url_version_match.group(1) | |
| # If the key is malformed (date-like or has trailing year or has -64 suffix), fix it | |
| if re.match(r'^20\d{2}\.', key) or re.search(r'-20\d{2}$', key) or re.search(r'-\d+-bit$', key, re.IGNORECASE): | |
| print(f"Fixing malformed entry: {key} -> {correct_version}") | |
| fixed_properties[correct_version] = url | |
| else: | |
| # Keep valid entries as-is | |
| fixed_properties[key] = url | |
| else: | |
| # If we can't extract version from URL, keep the original entry | |
| fixed_properties[key] = url | |
| properties = fixed_properties | |
| # Repair stale URLs by searching all releases for replacement assets. | |
| # Old releases sometimes have their assets deleted/reorganized (e.g. assets | |
| # moved from 2022.12.03 to 2024.4.20). We search all releases to find a | |
| # working URL for any version whose current URL returns 404. | |
| print(f"\nChecking for stale URLs and searching for replacements...") | |
| version_url_map = {} | |
| page = 1 | |
| while True: | |
| releases_api_url = f"https://api.github.com/repos/{repo_owner}/{repo_name}/releases?per_page=100&page={page}" | |
| releases_response = requests.get(releases_api_url, headers=headers) | |
| releases_response.raise_for_status() | |
| releases_page = releases_response.json() | |
| if not releases_page: | |
| break | |
| for release in releases_page: | |
| for asset in release.get('assets', []): | |
| asset_name = asset['name'] | |
| if not (asset_name.endswith('.7z') or asset_name.endswith('.7z.exe')): | |
| continue | |
| m = re.search( | |
| rf'{filename_prefix}-(\d+\.\d+(?:\.\d+)?(?:\.\d+)?(?:-1)?(?:-?(?:RC|beta|alpha|dev)\d*)?)(?:-\d+)?(?=-20\d{{2}}\.\d{{1,2}}\.\d{{1,2}}|\.7z|$)', | |
| asset_name, re.IGNORECASE | |
| ) | |
| if not m and module_name.lower() == 'git': | |
| m = re.search(r'PortableGit-(\d+\.\d+(?:\.\d+)*)(?=-(?:\d+-)?bit|\.7z)', asset_name, re.IGNORECASE) | |
| if m: | |
| ver = m.group(1) | |
| if ver not in version_url_map: | |
| version_url_map[ver] = asset['browser_download_url'] | |
| if len(releases_page) < 100: | |
| break | |
| page += 1 | |
| print(f"Found {len(version_url_map)} versions across all releases") | |
| repaired_entries = 0 | |
| for key, url in list(properties.items()): | |
| try: | |
| response = requests.head(url, timeout=10, allow_redirects=True) | |
| if response.status_code == 404 and key in version_url_map: | |
| new_url = version_url_map[key] | |
| if new_url != url: | |
| print(f"Repairing stale URL: {key}") | |
| print(f" Old: {url}") | |
| print(f" New: {new_url}") | |
| properties[key] = new_url | |
| repaired_entries += 1 | |
| except requests.exceptions.RequestException: | |
| pass | |
| if repaired_entries > 0: | |
| print(f"\nRepaired {repaired_entries} stale URLs") | |
| else: | |
| print(f"\nNo stale URLs needed repair") | |
| # Add/update the versions from this release. On an 'edited' release | |
| # this rechecks the file URL: the stored pair is updated whenever the URL changed. | |
| for asset in assets: | |
| ver = asset['version'] | |
| url = asset['url'] | |
| # Create property key (just version number) | |
| key = ver | |
| if key in properties: | |
| if properties[key] == url: | |
| print(f"Skipped (URL unchanged): {key} = {url}") | |
| else: | |
| print(f"Updated URL: {key}") | |
| print(f" Old: {properties[key]}") | |
| print(f" New: {url}") | |
| properties[key] = url | |
| else: | |
| properties[key] = url | |
| print(f"Added: {key} = {url}") | |
| # Reconcile files removed from THIS release: drop any stored entry | |
| # that still points at this release's download path but whose version | |
| # is no longer among the release's current assets. | |
| asset_versions = {a['version'] for a in assets} | |
| removed_files = [] | |
| for key, url in list(properties.items()): | |
| if any(tag and f"/releases/download/{tag}/" in url for tag in match_tags): | |
| if key not in asset_versions: | |
| print(f"Removing (file removed from release): {key} = {url}") | |
| del properties[key] | |
| removed_files.append(key) | |
| if removed_files: | |
| print(f"Removed {len(removed_files)} entries whose files were removed from the release") | |
| # Sort properties by version (semver) | |
| def extract_version(key): | |
| # Parse version directly from key | |
| try: | |
| return version.parse(key) | |
| except: | |
| return version.parse("0.0.0") | |
| sorted_properties = OrderedDict( | |
| sorted(properties.items(), key=lambda x: extract_version(x[0]), reverse=True) | |
| ) | |
| # Write back to file | |
| with open(properties_file, 'w', encoding='utf-8') as f: | |
| # Write header | |
| for line in header_lines: | |
| f.write(line) | |
| # Write sorted properties with spaces around equals sign | |
| for key, value in sorted_properties.items(): | |
| f.write(f"{key} = {value}\n") | |
| print(f"\nSuccessfully updated {properties_file}") | |
| print(f"Total versions: {len(sorted_properties)}") | |
| EOF | |
| - name: Check for changes | |
| id: check_changes | |
| if: steps.resolve.outputs.skip != 'true' | |
| run: | | |
| if git diff --quiet releases.properties 2>/dev/null; then | |
| echo "has_changes=false" >> $GITHUB_OUTPUT | |
| echo "No changes to releases.properties" | |
| else | |
| echo "has_changes=true" >> $GITHUB_OUTPUT | |
| echo "Changes detected in releases.properties" | |
| fi | |
| - name: Commit and push changes | |
| # Commit on every event that produced changes. Pushes to the resolved | |
| # branch (the version PR branch) so branch tests see the new entry, and | |
| # mirrors to main so main stays consistent for the final release. | |
| if: > | |
| steps.resolve.outputs.skip != 'true' && | |
| steps.check_changes.outputs.has_changes == 'true' | |
| env: | |
| RELEASE_TAG: ${{ steps.resolve.outputs.release_tag }} | |
| BRANCH: ${{ steps.resolve.outputs.branch }} | |
| GH_TOKEN: ${{ secrets.GH_PAT }} | |
| run: | | |
| git config user.name "Bearsampp Bot" | |
| git config user.email "bot@bearsampp.com" | |
| git remote set-url origin "https://x-access-token:${GH_TOKEN}@github.com/${{ github.repository }}.git" | |
| git add releases.properties | |
| git commit -m "Update releases.properties | |
| Auto-generated from release ${RELEASE_TAG}" | |
| git push origin HEAD:${BRANCH} | |
| # Also update main branch directly without a PR | |
| if [[ "${BRANCH}" != "main" ]]; then | |
| COMMIT_SHA=$(git rev-parse HEAD) | |
| git fetch origin main | |
| git checkout main | |
| if git cherry-pick --no-edit $COMMIT_SHA; then | |
| git push origin main | |
| else | |
| echo "::warning::Cherry-pick to main failed, skipping main sync" | |
| git cherry-pick --abort 2>/dev/null || true | |
| fi | |
| git checkout ${BRANCH} || true | |
| fi | |
| dispatch: | |
| runs-on: ubuntu-26.04 | |
| needs: [update-properties] | |
| # Quickpick dispatcher. Fires on EVERY non-PR trigger, including release | |
| # deletion (a purged releases.properties must recombine so the removed | |
| # version disappears from the combined release) and triggers where | |
| # releases.properties did NOT change (commit/push skipped but the job still | |
| # succeeds) — quickpick combine is idempotent and must re-run so the | |
| # combined release reflects current data. Only a hard failure of the | |
| # properties job suppresses the dispatch. | |
| if: | | |
| always() && | |
| github.event_name != 'pull_request' && | |
| !endsWith(github.repository, '.settings') && | |
| needs.update-properties.result != 'failure' | |
| steps: | |
| - name: Invoke workflow in another repo (Quickpick Dispatcher) | |
| uses: peter-evans/repository-dispatch@v4 | |
| with: | |
| token: ${{ secrets.Gh_PAT }} | |
| repository: Bearsampp/Bearsampp | |
| event-type: module_release_created | |
| client-payload: '{"workflow": "combine_releases.yml"}' |