diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 3494469..2f9108c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -25,16 +25,18 @@ jobs: runs-on: windows-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 - - uses: actions/setup-python@v5 + - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5 with: python-version: "3.12" - name: Take the version from the tag, or from the dispatch input + env: + DISPATCH_VERSION: ${{ inputs.version }} run: | if ($env:GITHUB_EVENT_NAME -eq 'workflow_dispatch') { - $v = "${{ inputs.version }}" + $v = $env:DISPATCH_VERSION } else { $v = $env:GITHUB_REF_NAME -replace '^v', '' } @@ -53,7 +55,7 @@ jobs: - name: Install claude-batch-runner and PyInstaller run: | python -m pip install --upgrade pip - python -m pip install . pyinstaller + python -m pip install . pyinstaller==6.21.0 - name: Build the one-file executable # The entry point is a module function, so PyInstaller is aimed at @@ -69,7 +71,7 @@ jobs: - name: Install Inno Setup # Installed, not assumed: a runner image that quietly drops a # preinstalled tool would break releases with no warning. - run: choco install innosetup -y --no-progress + run: choco install innosetup -y --no-progress --version=6.7.1 - name: Build the installer run: | @@ -78,13 +80,27 @@ jobs: if (-not (Test-Path $iscc)) { throw "ISCC.exe not found at $iscc - Inno Setup's install layout changed" } & $iscc /DAppVersion=$env:VERSION installer\claude-batch-runner.iss + - name: Checksum the installer + # A binary nobody can verify is a binary nobody should trust. The hash is + # written beside the exe, shipped as a release asset, and repeated in the + # release notes so a download can be checked without fetching the asset. + run: | + $exe = "dist\claude-batch-runner-setup-$env:VERSION.exe" + if (-not (Test-Path $exe)) { throw "no installer at $exe" } + $leaf = Split-Path $exe -Leaf + $hash = (Get-FileHash $exe -Algorithm SHA256).Hash.ToLower() + "$hash $leaf" | Out-File -FilePath dist\SHA256SUMS -Encoding ascii + "INSTALLER_SHA256=$hash" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8 + - name: Keep the installer as an artifact (dry run only) # A dry run proves the build; it deliberately publishes nothing. if: github.event_name == 'workflow_dispatch' - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: name: claude-batch-runner-setup-${{ env.VERSION }} - path: dist/claude-batch-runner-setup-${{ env.VERSION }}.exe + path: | + dist/claude-batch-runner-setup-${{ env.VERSION }}.exe + dist/SHA256SUMS if-no-files-found: error - name: Attach the installer to the release @@ -97,10 +113,27 @@ jobs: run: | $ErrorActionPreference = 'Continue' $exe = "dist\claude-batch-runner-setup-$env:VERSION.exe" + $leaf = Split-Path $exe -Leaf + # Notes are rebuilt from scratch every run, so re-running a release cannot + # append a second checksum block to notes that already carry one. + $generated = gh api "repos/$env:GITHUB_REPOSITORY/releases/generate-notes" -f tag_name=$env:GITHUB_REF_NAME -q .body + if ($LASTEXITCODE -ne 0) { throw "could not generate release notes for $env:GITHUB_REF_NAME" } + $notes = "dist\release-notes.md" + @( + $generated + "" + "## Verify the download" + "" + '```' + "$env:INSTALLER_SHA256 $leaf" + '```' + ) | Set-Content -Path $notes -Encoding utf8 gh release view $env:GITHUB_REF_NAME --json tagName | Out-Null if ($LASTEXITCODE -eq 0) { - gh release upload $env:GITHUB_REF_NAME $exe --clobber + gh release upload $env:GITHUB_REF_NAME $exe dist\SHA256SUMS --clobber + if ($LASTEXITCODE -ne 0) { throw "could not upload $exe to $env:GITHUB_REF_NAME" } + gh release edit $env:GITHUB_REF_NAME --notes-file $notes } else { - gh release create $env:GITHUB_REF_NAME --verify-tag --generate-notes $exe + gh release create $env:GITHUB_REF_NAME --verify-tag --notes-file $notes $exe dist\SHA256SUMS } if ($LASTEXITCODE -ne 0) { throw "could not publish $exe to $env:GITHUB_REF_NAME" } diff --git a/installer/claude-batch-runner.iss b/installer/claude-batch-runner.iss index 2e5c7a8..a5c704a 100644 --- a/installer/claude-batch-runner.iss +++ b/installer/claude-batch-runner.iss @@ -7,6 +7,9 @@ ; ISCC.exe /DAppVersion=0.1.0 installer\claude-batch-runner.iss ; ; See .github/workflows/release.yml for the whole sequence. +; +; Keep this file pure ASCII. Inno reads a BOM-less script as ANSI, so an em +; dash or a curly quote pasted in from prose becomes mojibake in the wizard. #define AppName "claude-batch-runner" #define AppURL "https://github.com/Back-Road-Creative/claude-batch-runner"