From 8aa155185d41f8de84639d6a0be55b8076fc374e Mon Sep 17 00:00:00 2001 From: Nikolaus Heger Date: Fri, 12 Jun 2026 21:41:34 +0800 Subject: [PATCH 1/4] Automate miner release versioning Release proposal workflow computes the next miner-v version (patch/minor/major/custom) from tags and opens a version-bump PR; merging it tags, builds all platforms, and publishes the release. --- .github/workflows/create_miner_build.yml | 120 ++---------- .../create_miner_release_proposal.yml | 179 ++++++++++++++++++ .github/workflows/publish_miner_release.yml | 134 +++++++++++++ 3 files changed, 326 insertions(+), 107 deletions(-) create mode 100644 .github/workflows/create_miner_release_proposal.yml create mode 100644 .github/workflows/publish_miner_release.yml diff --git a/.github/workflows/create_miner_build.yml b/.github/workflows/create_miner_build.yml index a3939011a..f12b26b09 100644 --- a/.github/workflows/create_miner_build.yml +++ b/.github/workflows/create_miner_build.yml @@ -1,15 +1,15 @@ name: Create Miner Build on: - workflow_dispatch: # Manual trigger only + workflow_dispatch: # Manual test builds (no release) + workflow_call: # Reused by publish_miner_release.yml inputs: - create_release: - description: 'Create a new release' + ref: + description: 'Git ref (tag/branch/SHA) to build' required: false - default: false - type: boolean + type: string permissions: - contents: write + contents: read jobs: build-macos: @@ -20,6 +20,8 @@ jobs: steps: - name: Checkout repository uses: actions/checkout@v4 + with: + ref: ${{ inputs.ref || github.ref }} - name: Set up Flutter uses: subosito/flutter-action@v2 @@ -193,6 +195,8 @@ jobs: steps: - name: Checkout repository uses: actions/checkout@v4 + with: + ref: ${{ inputs.ref || github.ref }} - name: Set up Flutter uses: subosito/flutter-action@v2 @@ -247,6 +251,8 @@ jobs: steps: - name: Checkout repository uses: actions/checkout@v4 + with: + ref: ${{ inputs.ref || github.ref }} - name: Set up Flutter uses: subosito/flutter-action@v2 @@ -278,104 +284,4 @@ jobs: uses: actions/upload-artifact@v4 with: name: quantus_miner-windows - path: miner-app/build/windows/x64/runner/Release/quantus_miner_windows.zip - - create-release: - name: Create Release - if: ${{ inputs.create_release }} - needs: [build-macos, build-linux, build-windows] - runs-on: ubuntu-latest - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Read version from pubspec.yaml - id: get_version - run: | - VERSION=$(grep '^version:' miner-app/pubspec.yaml | head -1 | sed 's/version:[[:space:]]*//') - echo "version=$VERSION" >> "$GITHUB_OUTPUT" - echo "tag=miner-v$VERSION" >> "$GITHUB_OUTPUT" - - - name: Download all artifacts - uses: actions/download-artifact@v4 - with: - path: ./artifacts - - - name: List downloaded artifacts - run: | - # For debugging only; artifact already contains the final ZIP we want - find ./artifacts -type f \( -name "*.zip" -o -name "*.tar.gz" \) -o -type d -name "*.app" | sort - - - name: Create and push git tag - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - TAG="${{ steps.get_version.outputs.tag }}" - echo "Tag to create: $TAG" - - if git ls-remote --tags origin "$TAG" | grep -q "$TAG"; then - echo "Error: Tag $TAG already exists on remote." - echo "Please update the version in pubspec.yaml before creating a new release." - exit 1 - else - git config user.name "github-actions[bot]" - git config user.email "github-actions[bot]@users.noreply.github.com" - git tag "$TAG" "$GITHUB_SHA" - git push origin "$TAG" - fi - - - name: Create Release - id: create_release - uses: actions/create-release@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - tag_name: ${{ steps.get_version.outputs.tag }} - release_name: Quantus Miner ${{ steps.get_version.outputs.version }} - body: | - ## Quantus Miner Release - This release includes binaries for: - - 🍎 macOS (Intel/Apple Silicon) - - 🐧 Linux (x64) - - 🪟 Windows (x64) - ### Installation - **macOS**: Download the `.zip` file, extract it, and run the `Quantus Miner.app` - **Linux**: Download the `.tar.gz` file, extract it, and run the `quantus_miner` executable - **Windows**: Download the `.zip` file, extract it, and run the `quantus_miner.exe` - ### What's Changed - - Cross-platform mining support - - External miner integration - - Improved UI and stability - Built from commit: ${{ github.sha }} - draft: false - prerelease: false - - - name: Upload macOS Asset - uses: actions/upload-release-asset@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - upload_url: ${{ steps.create_release.outputs.upload_url }} - asset_path: ./artifacts/quantus_miner-macos/quantus_miner_macos.zip - asset_name: quantus_miner_macos.zip - asset_content_type: application/zip - - - name: Upload Linux Asset - uses: actions/upload-release-asset@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - upload_url: ${{ steps.create_release.outputs.upload_url }} - asset_path: ./artifacts/quantus_miner-linux/quantus_miner_linux.tar.gz - asset_name: quantus_miner_linux.tar.gz - asset_content_type: application/gzip - - - name: Upload Windows Asset - uses: actions/upload-release-asset@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - upload_url: ${{ steps.create_release.outputs.upload_url }} - asset_path: ./artifacts/quantus_miner-windows/quantus_miner_windows.zip - asset_name: quantus_miner_windows.zip - asset_content_type: application/zip \ No newline at end of file + path: miner-app/build/windows/x64/runner/Release/quantus_miner_windows.zip \ No newline at end of file diff --git a/.github/workflows/create_miner_release_proposal.yml b/.github/workflows/create_miner_release_proposal.yml new file mode 100644 index 000000000..2b8e1e8fc --- /dev/null +++ b/.github/workflows/create_miner_release_proposal.yml @@ -0,0 +1,179 @@ +name: Miner - Create Release Proposal + +on: + workflow_dispatch: + inputs: + target_branch: + description: 'Target branch for the PR (default: main)' + required: false + type: string + default: 'main' + version_type: + description: 'Type of version bump (major, minor, patch) or specify custom version' + required: true + default: 'patch' + type: choice + options: + - patch + - minor + - major + - custom + custom_version: + description: 'Custom version string (e.g., 0.5.0). Only used if version_type is "custom". Do NOT include "miner-v" prefix' + required: false + is_draft: + description: 'Is this a draft release?' + required: true + type: boolean + default: false + +jobs: + calculate-next-version: + name: 🧮 Calculate Next Miner Version + runs-on: ubuntu-latest + outputs: + new_version: ${{ steps.versioner.outputs.new_version }} + source_branch: ${{ steps.vars.outputs.source_branch }} + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + fetch-tags: true + + - name: Get current branch + id: vars + run: | + echo "source_branch=$(git rev-parse --abbrev-ref HEAD)" >> "$GITHUB_OUTPUT" + + - name: Get latest miner tag + id: latest_tag + run: | + latest_miner_tag=$(git tag -l "miner-v[0-9]*.[0-9]*.[0-9]*" | sort -V | tail -n 1) + + if [ -z "$latest_miner_tag" ]; then + latest_miner_tag="miner-v0.0.0" + fi + + echo "latest_tag_found=$latest_miner_tag" >> "$GITHUB_OUTPUT" + echo "Latest miner tag found: $latest_miner_tag" + + - name: Calculate new version + id: versioner + env: + LATEST_TAG: ${{ steps.latest_tag.outputs.latest_tag_found }} + VERSION_TYPE: ${{ github.event.inputs.version_type }} + CUSTOM_VERSION: ${{ github.event.inputs.custom_version }} + run: | + current_version=${LATEST_TAG#miner-v} + + if [[ "$VERSION_TYPE" == "custom" ]]; then + if [[ -z "$CUSTOM_VERSION" ]]; then + echo "Error: Custom version is selected but no custom_version string provided." + exit 1 + fi + if [[ ! "$CUSTOM_VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + echo "Error: Custom version string must be in format X.Y.Z (e.g., 0.5.0)." + exit 1 + fi + new_version="miner-v$CUSTOM_VERSION" + else + IFS='.' read -r major minor patch <<< "$current_version" + + if [[ "$VERSION_TYPE" == "major" ]]; then + major=$((major + 1)) + minor=0 + patch=0 + elif [[ "$VERSION_TYPE" == "minor" ]]; then + minor=$((minor + 1)) + patch=0 + elif [[ "$VERSION_TYPE" == "patch" ]]; then + patch=$((patch + 1)) + else + echo "Error: Invalid version_type: $VERSION_TYPE" + exit 1 + fi + new_version="miner-v$major.$minor.$patch" + fi + + echo "New version: $new_version" + echo "new_version=$new_version" >> "$GITHUB_OUTPUT" + + update-version: + name: 📝 Update Version & Open PR + needs: calculate-next-version + runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Create version bump branch and PR + env: + NEW_VERSION: ${{ needs.calculate-next-version.outputs.new_version }} + GITHUB_TOKEN: ${{ secrets.PAT_TOKEN }} + SOURCE_BRANCH: ${{ needs.calculate-next-version.outputs.source_branch }} + TARGET_BRANCH: ${{ github.event.inputs.target_branch }} + run: | + set -ex + new_pubspec_version=${NEW_VERSION#miner-v} + branch_name="release/${NEW_VERSION}" + + if git tag -l | grep -q "^${NEW_VERSION}$"; then + echo "Error: Tag $NEW_VERSION already exists" + exit 1 + fi + + git checkout "$SOURCE_BRANCH" + git checkout -b "$branch_name" + + echo "Updating miner-app/pubspec.yaml to version: $new_pubspec_version" + sed -i -E "s/^version:[[:space:]]*.+$/version: $new_pubspec_version/" miner-app/pubspec.yaml + + if ! grep -q "^version: $new_pubspec_version$" miner-app/pubspec.yaml; then + echo "Error: Failed to update version in miner-app/pubspec.yaml" + exit 1 + fi + + git config user.name "${{ github.actor }}" + git config user.email "${{ github.actor }}@users.noreply.github.com" + + git add miner-app/pubspec.yaml + # --allow-empty: pubspec may already be at the target version (manual pre-bump) + git commit --allow-empty -m "ci: Miner version bump to $NEW_VERSION" + git push origin "$branch_name" + + gh label create "automated" --force --color "ededed" --description "Created by CI" + gh label create "miner-release-proposal" --force --color "0e8a16" --description "Miner release proposal PR" + gh label create "draft-release" --force --color "d4c5f9" --description "Release will be created as draft" + + PR_TITLE="ci: Miner version bump to $NEW_VERSION" + PR_LABELS="automated,miner-release-proposal" + if [[ "${{ github.event.inputs.is_draft }}" == "true" ]]; then + PR_LABELS="$PR_LABELS,draft-release" + fi + + cat > pr_body.md << EOF + ## Miner Release Proposal + + This PR proposes releasing **Quantus Miner** version \`${new_pubspec_version}\` (tag \`${NEW_VERSION}\`). + + Merging this PR will automatically tag the merge commit, build macOS/Linux/Windows binaries, and publish a GitHub release. + + ### Changes + - Updated \`miner-app/pubspec.yaml\` version to \`${new_pubspec_version}\` + + Triggered by workflow run: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + Bump type: ${{ github.event.inputs.version_type }} + EOF + + gh pr create \ + --title "$PR_TITLE" \ + --body-file pr_body.md \ + --base "$TARGET_BRANCH" \ + --head "$branch_name" \ + --label "$PR_LABELS" diff --git a/.github/workflows/publish_miner_release.yml b/.github/workflows/publish_miner_release.yml new file mode 100644 index 000000000..d79168871 --- /dev/null +++ b/.github/workflows/publish_miner_release.yml @@ -0,0 +1,134 @@ +name: Miner - Publish Release + +on: + pull_request: + types: [closed] + branches: + - main + +permissions: + contents: read + +jobs: + create-tag: + name: Create Miner Tag + if: github.event.pull_request.merged == true && contains(github.event.pull_request.labels.*.name, 'miner-release-proposal') + runs-on: ubuntu-latest + permissions: + contents: write + outputs: + tag: ${{ steps.extract_version.outputs.tag }} + version: ${{ steps.extract_version.outputs.version }} + is_draft: ${{ steps.extract_version.outputs.is_draft }} + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Extract version from PR title + id: extract_version + env: + PR_TITLE: ${{ github.event.pull_request.title }} + HAS_DRAFT_LABEL: ${{ contains(github.event.pull_request.labels.*.name, 'draft-release') }} + run: | + # PR title format: "ci: Miner version bump to miner-vX.Y.Z" + TAG=$(echo "$PR_TITLE" | grep -o 'miner-v[0-9]\+\.[0-9]\+\.[0-9]\+') + if [ -z "$TAG" ]; then + echo "Error: Could not extract miner version from PR title: $PR_TITLE" + exit 1 + fi + { + echo "tag=$TAG" + echo "version=${TAG#miner-v}" + echo "is_draft=$HAS_DRAFT_LABEL" + } >> "$GITHUB_OUTPUT" + echo "Extracted tag: $TAG" + + - name: Verify pubspec version matches tag + env: + MERGE_COMMIT: ${{ github.event.pull_request.merge_commit_sha }} + VERSION: ${{ steps.extract_version.outputs.version }} + run: | + PUBSPEC_VERSION=$(git show "$MERGE_COMMIT:miner-app/pubspec.yaml" | grep '^version:' | head -1 | sed 's/version:[[:space:]]*//') + if [ "$PUBSPEC_VERSION" != "$VERSION" ]; then + echo "Error: pubspec version ($PUBSPEC_VERSION) does not match release version ($VERSION)" + exit 1 + fi + + - name: Create and push tag + env: + TAG: ${{ steps.extract_version.outputs.tag }} + MERGE_COMMIT: ${{ github.event.pull_request.merge_commit_sha }} + run: | + git config user.name "${{ github.actor }}" + git config user.email "${{ github.actor }}@users.noreply.github.com" + git tag -a "$TAG" "$MERGE_COMMIT" -m "Quantus Miner release $TAG" + git push origin "$TAG" + + build: + name: Build Miner + needs: create-tag + uses: ./.github/workflows/create_miner_build.yml + with: + ref: ${{ needs.create-tag.outputs.tag }} + secrets: inherit + + create-release: + name: 🚀 Create GitHub Release + needs: [create-tag, build] + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - name: Checkout code at tag + uses: actions/checkout@v4 + with: + ref: ${{ needs.create-tag.outputs.tag }} + fetch-depth: 0 + fetch-tags: true + + - name: Download all artifacts + uses: actions/download-artifact@v4 + with: + path: ./artifacts + + - name: Create GitHub Release with assets + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + TAG: ${{ needs.create-tag.outputs.tag }} + VERSION: ${{ needs.create-tag.outputs.version }} + IS_DRAFT: ${{ needs.create-tag.outputs.is_draft }} + run: | + set -eo pipefail + + NOTES="## Quantus Miner Release + This release includes binaries for: + - 🍎 macOS (Intel/Apple Silicon) + - 🐧 Linux (x64) + - 🪟 Windows (x64) + ### Installation + **macOS**: Download the \`.zip\` file, extract it, and run the \`Quantus Miner.app\` + **Linux**: Download the \`.tar.gz\` file, extract it, and run the \`quantus_miner\` executable + **Windows**: Download the \`.zip\` file, extract it, and run the \`quantus_miner.exe\`" + + EXTRA_ARGS=() + + # Generated notes must diff against the previous miner tag, not a wallet-v* tag + PREV_TAG=$(git tag -l "miner-v[0-9]*.[0-9]*.[0-9]*" | sort -V | grep -x -B1 "$TAG" | head -n 1) + if [ -n "$PREV_TAG" ] && [ "$PREV_TAG" != "$TAG" ]; then + EXTRA_ARGS+=(--notes-start-tag "$PREV_TAG") + fi + + if [[ "$IS_DRAFT" == "true" ]]; then + EXTRA_ARGS+=(--draft) + fi + + gh release create "$TAG" \ + --title "Quantus Miner $VERSION" \ + --notes "$NOTES" \ + --generate-notes \ + "${EXTRA_ARGS[@]}" \ + ./artifacts/quantus_miner-macos/quantus_miner_macos.zip \ + ./artifacts/quantus_miner-linux/quantus_miner_linux.tar.gz \ + ./artifacts/quantus_miner-windows/quantus_miner_windows.zip From 8b0e6bb34de3858c147883005a78a6cef18dbc02 Mon Sep 17 00:00:00 2001 From: Nikolaus Heger Date: Fri, 14 Aug 2026 09:18:34 +0800 Subject: [PATCH 2/4] updates to build --- .github/workflows/create_miner_build.yml | 4 +- .../create_miner_release_proposal.yml | 47 +++++++++++++++--- .github/workflows/publish_miner_release.yml | 49 +++++++++++++++---- .../xcshareddata/swiftpm/Package.resolved | 14 ++++++ 4 files changed, 95 insertions(+), 19 deletions(-) create mode 100644 miner-app/macos/Runner.xcworkspace/xcshareddata/swiftpm/Package.resolved diff --git a/.github/workflows/create_miner_build.yml b/.github/workflows/create_miner_build.yml index f12b26b09..46f1dacb5 100644 --- a/.github/workflows/create_miner_build.yml +++ b/.github/workflows/create_miner_build.yml @@ -235,7 +235,7 @@ jobs: - name: Package Linux App run: | cd miner-app/build/linux/x64/release/bundle/ - tar -czvf quantus_miner_linux.tar.gz * + tar -czvf quantus_miner_linux.tar.gz -- * - name: Upload Linux Artifact uses: actions/upload-artifact@v4 @@ -284,4 +284,4 @@ jobs: uses: actions/upload-artifact@v4 with: name: quantus_miner-windows - path: miner-app/build/windows/x64/runner/Release/quantus_miner_windows.zip \ No newline at end of file + path: miner-app/build/windows/x64/runner/Release/quantus_miner_windows.zip diff --git a/.github/workflows/create_miner_release_proposal.yml b/.github/workflows/create_miner_release_proposal.yml index 2b8e1e8fc..f0d40c36d 100644 --- a/.github/workflows/create_miner_release_proposal.yml +++ b/.github/workflows/create_miner_release_proposal.yml @@ -27,6 +27,9 @@ on: type: boolean default: false +permissions: + contents: read + jobs: calculate-next-version: name: 🧮 Calculate Next Miner Version @@ -46,26 +49,39 @@ jobs: run: | echo "source_branch=$(git rev-parse --abbrev-ref HEAD)" >> "$GITHUB_OUTPUT" - - name: Get latest miner tag - id: latest_tag + - name: Get current versions + id: current_versions run: | + set -euo pipefail latest_miner_tag=$(git tag -l "miner-v[0-9]*.[0-9]*.[0-9]*" | sort -V | tail -n 1) if [ -z "$latest_miner_tag" ]; then latest_miner_tag="miner-v0.0.0" fi + pubspec_version=$(grep -E '^version:' miner-app/pubspec.yaml | head -n 1 | sed -E 's/^version:[[:space:]]*//' | cut -d+ -f1) + if [[ ! "$pubspec_version" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + echo "Error: could not parse version from miner-app/pubspec.yaml (got: '$pubspec_version')" + exit 1 + fi + echo "latest_tag_found=$latest_miner_tag" >> "$GITHUB_OUTPUT" - echo "Latest miner tag found: $latest_miner_tag" + echo "pubspec_version=$pubspec_version" >> "$GITHUB_OUTPUT" + echo "Latest miner tag found: $latest_miner_tag (pubspec: $pubspec_version)" - name: Calculate new version id: versioner env: - LATEST_TAG: ${{ steps.latest_tag.outputs.latest_tag_found }} + LATEST_TAG: ${{ steps.current_versions.outputs.latest_tag_found }} + PUBSPEC_VERSION: ${{ steps.current_versions.outputs.pubspec_version }} VERSION_TYPE: ${{ github.event.inputs.version_type }} CUSTOM_VERSION: ${{ github.event.inputs.custom_version }} run: | - current_version=${LATEST_TAG#miner-v} + set -euo pipefail + tag_version=${LATEST_TAG#miner-v} + # Bump from whichever is higher: the last released tag or a manually pre-bumped pubspec + current_version=$(printf '%s\n%s\n' "$tag_version" "$PUBSPEC_VERSION" | sort -V | tail -n 1) + echo "Base version: $current_version (tag: $tag_version, pubspec: $PUBSPEC_VERSION)" if [[ "$VERSION_TYPE" == "custom" ]]; then if [[ -z "$CUSTOM_VERSION" ]]; then @@ -76,6 +92,11 @@ jobs: echo "Error: Custom version string must be in format X.Y.Z (e.g., 0.5.0)." exit 1 fi + lower=$(printf '%s\n%s\n' "$CUSTOM_VERSION" "$current_version" | sort -V | head -n 1) + if [[ "$lower" == "$CUSTOM_VERSION" && "$CUSTOM_VERSION" != "$current_version" ]]; then + echo "Error: custom version $CUSTOM_VERSION is lower than current version $current_version (latest tag / pubspec)." + exit 1 + fi new_version="miner-v$CUSTOM_VERSION" else IFS='.' read -r major minor patch <<< "$current_version" @@ -118,12 +139,15 @@ jobs: GITHUB_TOKEN: ${{ secrets.PAT_TOKEN }} SOURCE_BRANCH: ${{ needs.calculate-next-version.outputs.source_branch }} TARGET_BRANCH: ${{ github.event.inputs.target_branch }} + VERSION_TYPE: ${{ github.event.inputs.version_type }} + IS_DRAFT: ${{ github.event.inputs.is_draft }} run: | set -ex new_pubspec_version=${NEW_VERSION#miner-v} branch_name="release/${NEW_VERSION}" - if git tag -l | grep -q "^${NEW_VERSION}$"; then + # Check the remote directly so the guard doesn't depend on local tag state + if git ls-remote --exit-code --tags origin "refs/tags/$NEW_VERSION" >/dev/null; then echo "Error: Tag $NEW_VERSION already exists" exit 1 fi @@ -131,6 +155,13 @@ jobs: git checkout "$SOURCE_BRANCH" git checkout -b "$branch_name" + current_pubspec=$(grep -E '^version:' miner-app/pubspec.yaml | head -n 1 | sed -E 's/^version:[[:space:]]*//' | cut -d+ -f1) + lower=$(printf '%s\n%s\n' "$new_pubspec_version" "$current_pubspec" | sort -V | head -n 1) + if [[ "$lower" == "$new_pubspec_version" && "$new_pubspec_version" != "$current_pubspec" ]]; then + echo "Error: refusing to downgrade miner-app/pubspec.yaml from $current_pubspec to $new_pubspec_version" + exit 1 + fi + echo "Updating miner-app/pubspec.yaml to version: $new_pubspec_version" sed -i -E "s/^version:[[:space:]]*.+$/version: $new_pubspec_version/" miner-app/pubspec.yaml @@ -153,7 +184,7 @@ jobs: PR_TITLE="ci: Miner version bump to $NEW_VERSION" PR_LABELS="automated,miner-release-proposal" - if [[ "${{ github.event.inputs.is_draft }}" == "true" ]]; then + if [[ "$IS_DRAFT" == "true" ]]; then PR_LABELS="$PR_LABELS,draft-release" fi @@ -168,7 +199,7 @@ jobs: - Updated \`miner-app/pubspec.yaml\` version to \`${new_pubspec_version}\` Triggered by workflow run: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} - Bump type: ${{ github.event.inputs.version_type }} + Bump type: ${VERSION_TYPE} EOF gh pr create \ diff --git a/.github/workflows/publish_miner_release.yml b/.github/workflows/publish_miner_release.yml index d79168871..d55418957 100644 --- a/.github/workflows/publish_miner_release.yml +++ b/.github/workflows/publish_miner_release.yml @@ -9,6 +9,10 @@ on: permissions: contents: read +concurrency: + group: miner-publish-release + cancel-in-progress: false + jobs: create-tag: name: Create Miner Tag @@ -21,10 +25,10 @@ jobs: version: ${{ steps.extract_version.outputs.version }} is_draft: ${{ steps.extract_version.outputs.is_draft }} steps: - - name: Checkout code + - name: Checkout merge commit uses: actions/checkout@v4 with: - fetch-depth: 0 + ref: ${{ github.event.pull_request.merge_commit_sha }} - name: Extract version from PR title id: extract_version @@ -33,7 +37,7 @@ jobs: HAS_DRAFT_LABEL: ${{ contains(github.event.pull_request.labels.*.name, 'draft-release') }} run: | # PR title format: "ci: Miner version bump to miner-vX.Y.Z" - TAG=$(echo "$PR_TITLE" | grep -o 'miner-v[0-9]\+\.[0-9]\+\.[0-9]\+') + TAG=$(echo "$PR_TITLE" | grep -o 'miner-v[0-9]\+\.[0-9]\+\.[0-9]\+' | head -n 1) if [ -z "$TAG" ]; then echo "Error: Could not extract miner version from PR title: $PR_TITLE" exit 1 @@ -47,10 +51,9 @@ jobs: - name: Verify pubspec version matches tag env: - MERGE_COMMIT: ${{ github.event.pull_request.merge_commit_sha }} VERSION: ${{ steps.extract_version.outputs.version }} run: | - PUBSPEC_VERSION=$(git show "$MERGE_COMMIT:miner-app/pubspec.yaml" | grep '^version:' | head -1 | sed 's/version:[[:space:]]*//') + PUBSPEC_VERSION=$(grep '^version:' miner-app/pubspec.yaml | head -n 1 | sed 's/version:[[:space:]]*//') if [ "$PUBSPEC_VERSION" != "$VERSION" ]; then echo "Error: pubspec version ($PUBSPEC_VERSION) does not match release version ($VERSION)" exit 1 @@ -61,6 +64,17 @@ jobs: TAG: ${{ steps.extract_version.outputs.tag }} MERGE_COMMIT: ${{ github.event.pull_request.merge_commit_sha }} run: | + set -euo pipefail + # Idempotent: a re-run after a failed build/release must not die here + if git fetch origin "refs/tags/$TAG:refs/tags/$TAG" 2>/dev/null; then + EXISTING=$(git rev-parse "$TAG^{commit}") + if [ "$EXISTING" = "$MERGE_COMMIT" ]; then + echo "Tag $TAG already points at $MERGE_COMMIT; skipping tag creation" + exit 0 + fi + echo "Error: tag $TAG already exists at $EXISTING, not at merge commit $MERGE_COMMIT" + exit 1 + fi git config user.name "${{ github.actor }}" git config user.email "${{ github.actor }}@users.noreply.github.com" git tag -a "$TAG" "$MERGE_COMMIT" -m "Quantus Miner release $TAG" @@ -102,6 +116,25 @@ jobs: run: | set -eo pipefail + ASSETS=( + ./artifacts/quantus_miner-macos/quantus_miner_macos.zip + ./artifacts/quantus_miner-linux/quantus_miner_linux.tar.gz + ./artifacts/quantus_miner-windows/quantus_miner_windows.zip + ) + for ASSET in "${ASSETS[@]}"; do + if [ ! -f "$ASSET" ]; then + echo "Error: expected build artifact missing: $ASSET" + exit 1 + fi + done + + # Idempotent: a re-run after a partial failure re-uploads assets + if gh release view "$TAG" >/dev/null 2>&1; then + echo "Release $TAG already exists; re-uploading assets" + gh release upload "$TAG" --clobber "${ASSETS[@]}" + exit 0 + fi + NOTES="## Quantus Miner Release This release includes binaries for: - 🍎 macOS (Intel/Apple Silicon) @@ -115,7 +148,7 @@ jobs: EXTRA_ARGS=() # Generated notes must diff against the previous miner tag, not a wallet-v* tag - PREV_TAG=$(git tag -l "miner-v[0-9]*.[0-9]*.[0-9]*" | sort -V | grep -x -B1 "$TAG" | head -n 1) + PREV_TAG=$(git tag -l "miner-v[0-9]*.[0-9]*.[0-9]*" | sort -V | grep -Fx -B1 "$TAG" | head -n 1 || true) if [ -n "$PREV_TAG" ] && [ "$PREV_TAG" != "$TAG" ]; then EXTRA_ARGS+=(--notes-start-tag "$PREV_TAG") fi @@ -129,6 +162,4 @@ jobs: --notes "$NOTES" \ --generate-notes \ "${EXTRA_ARGS[@]}" \ - ./artifacts/quantus_miner-macos/quantus_miner_macos.zip \ - ./artifacts/quantus_miner-linux/quantus_miner_linux.tar.gz \ - ./artifacts/quantus_miner-windows/quantus_miner_windows.zip + "${ASSETS[@]}" diff --git a/miner-app/macos/Runner.xcworkspace/xcshareddata/swiftpm/Package.resolved b/miner-app/macos/Runner.xcworkspace/xcshareddata/swiftpm/Package.resolved new file mode 100644 index 000000000..9ab534d83 --- /dev/null +++ b/miner-app/macos/Runner.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -0,0 +1,14 @@ +{ + "pins" : [ + { + "identity" : "swiftsdk", + "kind" : "remoteSourceControl", + "location" : "https://github.com/TelemetryDeck/SwiftSDK", + "state" : { + "revision" : "ad4a03ec7ea7416a4081370f21c86e55b02a5b88", + "version" : "2.14.1" + } + } + ], + "version" : 2 +} From d904d8a8c7fdaaa4bad97a7daf21ceff9a29708a Mon Sep 17 00:00:00 2001 From: Nikolaus Heger Date: Fri, 14 Aug 2026 10:57:08 +0800 Subject: [PATCH 3/4] fix(ci): pin miner release build and release checkouts to the verified merge SHA actions/checkout resolves bare refs as branches before tags, so a branch named miner-vX.Y.Z could shadow the release tag and get different code signed and published. Build and release now check out the merge commit SHA that the pubspec verification and tag creation already ran against. --- .github/workflows/publish_miner_release.yml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/.github/workflows/publish_miner_release.yml b/.github/workflows/publish_miner_release.yml index d55418957..7178799e2 100644 --- a/.github/workflows/publish_miner_release.yml +++ b/.github/workflows/publish_miner_release.yml @@ -24,6 +24,7 @@ jobs: tag: ${{ steps.extract_version.outputs.tag }} version: ${{ steps.extract_version.outputs.version }} is_draft: ${{ steps.extract_version.outputs.is_draft }} + sha: ${{ github.event.pull_request.merge_commit_sha }} steps: - name: Checkout merge commit uses: actions/checkout@v4 @@ -85,7 +86,9 @@ jobs: needs: create-tag uses: ./.github/workflows/create_miner_build.yml with: - ref: ${{ needs.create-tag.outputs.tag }} + # The verified merge SHA, never the bare tag name: checkout resolves + # branches before tags, so a same-named branch could shadow the release + ref: ${{ needs.create-tag.outputs.sha }} secrets: inherit create-release: @@ -95,10 +98,10 @@ jobs: permissions: contents: write steps: - - name: Checkout code at tag + - name: Checkout code at release commit uses: actions/checkout@v4 with: - ref: ${{ needs.create-tag.outputs.tag }} + ref: ${{ needs.create-tag.outputs.sha }} fetch-depth: 0 fetch-tags: true From 745b10e36f16e2f6214c71c6dc81794b22a56030 Mon Sep 17 00:00:00 2001 From: Nikolaus Heger Date: Fri, 14 Aug 2026 11:10:21 +0800 Subject: [PATCH 4/4] chore: drop Package.resolved, xcshareddata is gitignored --- .../xcshareddata/swiftpm/Package.resolved | 14 -------------- 1 file changed, 14 deletions(-) delete mode 100644 miner-app/macos/Runner.xcworkspace/xcshareddata/swiftpm/Package.resolved diff --git a/miner-app/macos/Runner.xcworkspace/xcshareddata/swiftpm/Package.resolved b/miner-app/macos/Runner.xcworkspace/xcshareddata/swiftpm/Package.resolved deleted file mode 100644 index 9ab534d83..000000000 --- a/miner-app/macos/Runner.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ /dev/null @@ -1,14 +0,0 @@ -{ - "pins" : [ - { - "identity" : "swiftsdk", - "kind" : "remoteSourceControl", - "location" : "https://github.com/TelemetryDeck/SwiftSDK", - "state" : { - "revision" : "ad4a03ec7ea7416a4081370f21c86e55b02a5b88", - "version" : "2.14.1" - } - } - ], - "version" : 2 -}