diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index cde15da..ed5c9c6 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -62,22 +62,8 @@ jobs: with: fetch-depth: 0 - - name: Detect resumable release - if: github.event_name == 'workflow_dispatch' && !inputs.dry_run - id: resumable-release - env: - GH_TOKEN: ${{ github.token }} - RELEASE_CHANNEL: ${{ inputs.release_channel }} - run: bash .github/workflows/scripts/detect-resumable-release.sh - - - name: Checkout resumable release target - if: github.event_name == 'workflow_dispatch' && !inputs.dry_run && steps.resumable-release.outputs.resume == 'true' - env: - TARGET: ${{ steps.resumable-release.outputs.target }} - run: git checkout --detach "$TARGET" - - name: Determine minimum version - if: github.event_name == 'workflow_dispatch' && (inputs.dry_run || steps.resumable-release.outputs.resume != 'true') + if: github.event_name == 'workflow_dispatch' id: minimum-version shell: bash run: | @@ -93,7 +79,7 @@ jobs: echo "version=${minimum_major_minor}.0" >> "$GITHUB_OUTPUT" - name: Calculate release version - if: github.event_name == 'workflow_dispatch' && (inputs.dry_run || steps.resumable-release.outputs.resume != 'true') + if: github.event_name == 'workflow_dispatch' id: calculated-version uses: Kralizek/github-actions/actions/calculate-next-version@v0.4 with: @@ -101,32 +87,6 @@ jobs: channel: ${{ inputs.release_channel != 'stable' && inputs.release_channel || '' }} minimum-version: ${{ steps.minimum-version.outputs.version }} - - name: Resolve manual release version - if: github.event_name == 'workflow_dispatch' - id: manual-release - env: - RESUME: ${{ steps.resumable-release.outputs.resume }} - RESUMED_VERSION: ${{ steps.resumable-release.outputs.version }} - RESUMED_TAG: ${{ steps.resumable-release.outputs.tag }} - CALCULATED_VERSION: ${{ steps.calculated-version.outputs.version }} - CALCULATED_TAG: ${{ steps.calculated-version.outputs.tag }} - CALCULATED_RELEASE_NOTES_START_TAG: ${{ steps.calculated-version.outputs['release-notes-start-tag'] }} - shell: bash - run: | - set -euo pipefail - - if [[ "$RESUME" == "true" ]]; then - echo "resume=true" >> "$GITHUB_OUTPUT" - echo "version=$RESUMED_VERSION" >> "$GITHUB_OUTPUT" - echo "tag=$RESUMED_TAG" >> "$GITHUB_OUTPUT" - echo "release-notes-start-tag=" >> "$GITHUB_OUTPUT" - else - echo "resume=false" >> "$GITHUB_OUTPUT" - echo "version=$CALCULATED_VERSION" >> "$GITHUB_OUTPUT" - echo "tag=$CALCULATED_TAG" >> "$GITHUB_OUTPUT" - echo "release-notes-start-tag=$CALCULATED_RELEASE_NOTES_START_TAG" >> "$GITHUB_OUTPUT" - fi - - name: Validate published release if: github.event_name == 'release' id: published-release @@ -138,14 +98,14 @@ jobs: - name: Build and test uses: Kralizek/github-actions/actions/dotnet-build@v0.3 env: - MinVerVersionOverride: ${{ github.event_name == 'workflow_dispatch' && steps.manual-release.outputs.version || '' }} + MinVerVersionOverride: ${{ github.event_name == 'workflow_dispatch' && steps.calculated-version.outputs.version || '' }} with: checkout: false global_json_file: global.json - name: Pack env: - MinVerVersionOverride: ${{ github.event_name == 'workflow_dispatch' && steps.manual-release.outputs.version || '' }} + MinVerVersionOverride: ${{ github.event_name == 'workflow_dispatch' && steps.calculated-version.outputs.version || '' }} run: dotnet pack --configuration $CONFIGURATION --no-build --output ./artifacts/packages - name: Read package version @@ -169,7 +129,7 @@ jobs: - name: Validate package version env: PACKAGE_VERSION: ${{ steps.package-version.outputs.version }} - EXPECTED_VERSION: ${{ github.event_name == 'release' && steps.published-release.outputs.version || steps.manual-release.outputs.version }} + EXPECTED_VERSION: ${{ github.event_name == 'release' && steps.published-release.outputs.version || steps.calculated-version.outputs.version }} shell: bash run: | set -euo pipefail @@ -196,15 +156,39 @@ jobs: echo "Release channel: ${{ inputs.release_channel }}" echo "Package version: ${{ steps.package-version.outputs.version }}" - - name: Create or reuse draft GitHub release + - name: Ensure draft GitHub release + if: github.event_name == 'workflow_dispatch' && !inputs.dry_run + id: ensured-release + uses: Kralizek/github-actions/actions/ensure-github-release@v0.5 + with: + tag: ${{ steps.calculated-version.outputs.tag }} + target: ${{ github.sha }} + prerelease: ${{ steps.calculated-version.outputs.prerelease }} + release-notes-start-tag: ${{ steps.calculated-version.outputs['release-notes-start-tag'] }} + draft: 'true' + + - name: Validate resumable draft release if: github.event_name == 'workflow_dispatch' && !inputs.dry_run env: GH_TOKEN: ${{ github.token }} - TAG: ${{ steps.manual-release.outputs.tag }} - PREVIOUS_TAG: ${{ steps.manual-release.outputs['release-notes-start-tag'] }} - RELEASE_CHANNEL: ${{ inputs.release_channel }} - RESUME: ${{ steps.manual-release.outputs.resume }} - run: bash .github/workflows/scripts/create-or-reuse-draft-release.sh + TAG: ${{ steps.ensured-release.outputs.tag }} + EXPECTED_PRERELEASE: ${{ steps.calculated-version.outputs.prerelease }} + shell: bash + run: | + set -euo pipefail + release=$(gh api "repos/${{ github.repository }}/releases/tags/$TAG") + if [[ "$(jq -r .draft <<< "$release")" != true ]]; then + echo "::error::Release $TAG is already published; it cannot be resumed as a draft." + exit 1 + fi + if [[ "$(jq -r .author.login <<< "$release")" != "github-actions[bot]" ]]; then + echo "::error::Draft release $TAG was not created by GitHub Actions." + exit 1 + fi + if [[ "$(jq -r .prerelease <<< "$release")" != "$EXPECTED_PRERELEASE" ]]; then + echo "::error::Draft release $TAG prerelease classification differs from calculated version (expected $EXPECTED_PRERELEASE)." + exit 1 + fi - name: Configure GitHub Packages source if: github.event_name == 'release' || (github.event_name == 'workflow_dispatch' && !inputs.dry_run) @@ -245,7 +229,7 @@ jobs: if: github.event_name == 'release' || (github.event_name == 'workflow_dispatch' && !inputs.dry_run) env: GH_TOKEN: ${{ github.token }} - RELEASE_TAG: ${{ github.event_name == 'release' && github.event.release.tag_name || steps.manual-release.outputs.tag }} + RELEASE_TAG: ${{ github.event_name == 'release' && github.event.release.tag_name || steps.calculated-version.outputs.tag }} run: >- gh release upload "$RELEASE_TAG" ./artifacts/packages/*.nupkg @@ -256,19 +240,19 @@ jobs: - name: Fetch manual release tag if: github.event_name == 'workflow_dispatch' && !inputs.dry_run env: - TAG: ${{ steps.manual-release.outputs.tag }} + TAG: ${{ steps.calculated-version.outputs.tag }} run: git fetch origin "refs/tags/$TAG:refs/tags/$TAG" - name: Validate manual release tag if: github.event_name == 'workflow_dispatch' && !inputs.dry_run uses: Kralizek/github-actions/actions/validate-release@v0.3 with: - tag: ${{ steps.manual-release.outputs.tag }} - prerelease: ${{ inputs.release_channel != 'stable' }} + tag: ${{ steps.calculated-version.outputs.tag }} + prerelease: ${{ steps.calculated-version.outputs.prerelease }} - name: Publish GitHub release if: github.event_name == 'workflow_dispatch' && !inputs.dry_run env: GH_TOKEN: ${{ github.token }} - TAG: ${{ steps.manual-release.outputs.tag }} + TAG: ${{ steps.calculated-version.outputs.tag }} run: gh release edit "$TAG" --repo "${{ github.repository }}" --draft=false diff --git a/.github/workflows/scripts/create-or-reuse-draft-release.sh b/.github/workflows/scripts/create-or-reuse-draft-release.sh deleted file mode 100644 index da9445d..0000000 --- a/.github/workflows/scripts/create-or-reuse-draft-release.sh +++ /dev/null @@ -1,33 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -: "${GH_TOKEN:?GH_TOKEN must be set}" -: "${GITHUB_REPOSITORY:?GITHUB_REPOSITORY must be set}" -: "${GITHUB_SHA:?GITHUB_SHA must be set}" -: "${TAG:?TAG must be set}" -: "${RELEASE_CHANNEL:?RELEASE_CHANNEL must be set}" -: "${RESUME:?RESUME must be set}" - -if [[ "$RESUME" == "true" ]]; then - echo "Reusing draft release $TAG." - exit 0 -fi - -args=( - "$TAG" - --repo "$GITHUB_REPOSITORY" - --target "$GITHUB_SHA" - --title "$TAG" - --generate-notes - --draft -) - -if [[ -n "${PREVIOUS_TAG:-}" ]]; then - args+=(--notes-start-tag "$PREVIOUS_TAG") -fi - -if [[ "$RELEASE_CHANNEL" != "stable" ]]; then - args+=(--prerelease) -fi - -gh release create "${args[@]}" diff --git a/.github/workflows/scripts/detect-resumable-release.sh b/.github/workflows/scripts/detect-resumable-release.sh deleted file mode 100644 index f6d6a3b..0000000 --- a/.github/workflows/scripts/detect-resumable-release.sh +++ /dev/null @@ -1,49 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -: "${GH_TOKEN:?GH_TOKEN must be set}" -: "${GITHUB_REPOSITORY:?GITHUB_REPOSITORY must be set}" -: "${GITHUB_SHA:?GITHUB_SHA must be set}" -: "${RELEASE_CHANNEL:?RELEASE_CHANNEL must be set}" -: "${GITHUB_OUTPUT:?GITHUB_OUTPUT must be set}" - -releases=$(gh api --paginate "repos/${GITHUB_REPOSITORY}/releases?per_page=100") - -if [[ "$RELEASE_CHANNEL" == "stable" ]]; then - pattern='^v(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$' -else - pattern="^v(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)-${RELEASE_CHANNEL}\\.[0-9]+$" -fi - -mapfile -t matching_releases < <( - jq -rc \ - --arg pattern "$pattern" \ - --arg sha "$GITHUB_SHA" \ - '.[] - | select(.draft == true) - | select(.author.login == "github-actions[bot]") - | select(.target_commitish == $sha) - | select(.tag_name | test($pattern)) - | {tag: .tag_name, target: .target_commitish}' \ - <<< "$releases" -) - -if (( ${#matching_releases[@]} > 1 )); then - tags=$(printf '%s\n' "${matching_releases[@]}" | jq -r .tag | paste -sd ' ' -) - echo "Multiple resumable draft releases match channel $RELEASE_CHANNEL: $tags" >&2 - exit 1 -fi - -if (( ${#matching_releases[@]} == 1 )); then - release="${matching_releases[0]}" - tag=$(jq -r .tag <<< "$release") - target=$(jq -r .target <<< "$release") - - echo "Resuming draft release $tag at $target." - echo "resume=true" >> "$GITHUB_OUTPUT" - echo "tag=$tag" >> "$GITHUB_OUTPUT" - echo "version=${tag#v}" >> "$GITHUB_OUTPUT" - echo "target=$target" >> "$GITHUB_OUTPUT" -else - echo "resume=false" >> "$GITHUB_OUTPUT" -fi