diff --git a/.github/workflows/release-desktop.yml b/.github/workflows/release-desktop.yml index 3fb05515..78e7f0bf 100644 --- a/.github/workflows/release-desktop.yml +++ b/.github/workflows/release-desktop.yml @@ -15,6 +15,11 @@ on: required: false default: true type: boolean + publish_draft_release: + description: 'Publish manual draft artifacts to GitHub Releases' + required: false + default: false + type: boolean permissions: actions: read @@ -700,6 +705,7 @@ jobs: if-no-files-found: error publish-release: + if: github.event_name == 'push' || inputs.draft == false || inputs.publish_draft_release == true needs: build runs-on: ubuntu-latest @@ -724,6 +730,31 @@ jobs: exit 1 fi + - name: Refuse to overwrite an existing published release + if: github.event_name == 'workflow_dispatch' && inputs.draft == true + shell: bash + env: + GH_TOKEN: ${{ github.token }} + run: | + error_file="${RUNNER_TEMP}/release-view-error.txt" + set +e + release_state=$(gh api "repos/${GITHUB_REPOSITORY}/releases/tags/v${{ steps.version.outputs.value }}" --jq '.draft' 2>"$error_file") + status=$? + set -e + + if [ "$status" -ne 0 ]; then + if grep -q 'HTTP 404' "$error_file"; then + exit 0 + fi + cat "$error_file" >&2 + exit "$status" + fi + + if [ "$release_state" = "false" ]; then + echo "::error::Refusing to overwrite published release v${{ steps.version.outputs.value }} with manual draft artifacts." + exit 1 + fi + - name: Load release notes id: release_notes shell: bash diff --git a/scripts/pr/release-workflow.test.ts b/scripts/pr/release-workflow.test.ts index 3d442820..8327cd03 100644 --- a/scripts/pr/release-workflow.test.ts +++ b/scripts/pr/release-workflow.test.ts @@ -402,6 +402,19 @@ describe('release desktop workflow', () => { expect(buildJob).not.toContain('Load release notes') }) + test('manual draft validation cannot overwrite an existing published release', () => { + const workflow = readReleaseWorkflow() + const publishJob = extractJob(workflow, 'publish-release') + + expect(workflow).toContain('publish_draft_release:') + expect(workflow).toContain("description: 'Publish manual draft artifacts to GitHub Releases'") + expect(publishJob).toContain("if: github.event_name == 'push' || inputs.draft == false || inputs.publish_draft_release == true") + expect(publishJob).toContain('Refuse to overwrite an existing published release') + expect(publishJob).toContain("if: github.event_name == 'workflow_dispatch' && inputs.draft == true") + expect(publishJob).toContain('Refusing to overwrite published release v${{ steps.version.outputs.value }} with manual draft artifacts') + expect(publishJob.indexOf('Refuse to overwrite an existing published release')).toBeLessThan(publishJob.indexOf('Publish complete GitHub release')) + }) + test('release workflow publishes all release assets only after all matrix builds pass', () => { const workflow = readReleaseWorkflow() const publishJob = extractJob(workflow, 'publish-release')