ci: draft-then-publish releases to close the empty public window #100
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: Go | |
| on: | |
| push: | |
| branches: [ "main" ] | |
| tags: [ "v*" ] | |
| pull_request: | |
| branches: [ "main" ] | |
| jobs: | |
| build: | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - name: Set up Go | |
| uses: actions/setup-go@v6 | |
| with: | |
| go-version-file: 'go.mod' | |
| - name: Cache Go build artifacts | |
| uses: actions/cache@v5 | |
| with: | |
| path: | | |
| ~/.cache/go-build | |
| ~/go/pkg/mod | |
| key: ${{ runner.os }}-go-${{ hashFiles('**/go.mod') }} | |
| restore-keys: | | |
| ${{ runner.os }}-go- | |
| - name: Verify dependencies | |
| run: go mod verify | |
| - name: Build | |
| run: go build -v -buildvcs=false ./... | |
| - name: Test | |
| run: go test -v -race ./... | |
| - name: Run golangci-lint | |
| uses: golangci/golangci-lint-action@v9 | |
| with: | |
| version: latest | |
| # Single job that owns release creation. It runs once (not a matrix), so | |
| # exactly one job ever creates the release for the tag. The upload-assets | |
| # matrix below then only adds files to this already-existing release and can | |
| # never race to create it (the race that made action-gh-release v3 fail with | |
| # "already_exists (tag_name)"). | |
| # | |
| # The release is created as a draft so it is not publicly visible while the | |
| # upload matrix is still attaching assets. The publish-release job flips it to | |
| # published only after all assets are present, so the public never sees a | |
| # partial or empty release. | |
| create-release: | |
| needs: build | |
| if: startsWith(github.ref, 'refs/tags/v') | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write | |
| steps: | |
| - name: Create draft release for tag | |
| uses: softprops/action-gh-release@v3 | |
| with: | |
| draft: true | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| upload-assets: | |
| needs: create-release | |
| if: startsWith(github.ref, 'refs/tags/v') | |
| permissions: | |
| contents: write | |
| strategy: | |
| matrix: | |
| include: | |
| - os: ubuntu-latest | |
| goos: linux | |
| goarch: amd64 | |
| artifact_name: subenum | |
| asset_name: subenum-linux-amd64 | |
| - os: windows-latest | |
| goos: windows | |
| goarch: amd64 | |
| artifact_name: subenum.exe | |
| asset_name: subenum-windows-amd64.exe | |
| - os: macos-latest | |
| goos: darwin | |
| goarch: arm64 | |
| artifact_name: subenum | |
| asset_name: subenum-macos-arm64 | |
| - os: ubuntu-latest | |
| goos: darwin | |
| goarch: amd64 | |
| artifact_name: subenum | |
| asset_name: subenum-macos-amd64 | |
| - os: ubuntu-latest | |
| goos: linux | |
| goarch: arm64 | |
| artifact_name: subenum | |
| asset_name: subenum-linux-arm64 | |
| runs-on: ${{ matrix.os }} | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - name: Set up Go | |
| uses: actions/setup-go@v6 | |
| with: | |
| go-version-file: 'go.mod' | |
| - name: Cache Go build artifacts | |
| uses: actions/cache@v5 | |
| with: | |
| path: | | |
| ~/.cache/go-build | |
| ~/go/pkg/mod | |
| key: ${{ runner.os }}-go-${{ hashFiles('**/go.mod') }} | |
| restore-keys: | | |
| ${{ runner.os }}-go- | |
| - name: Build | |
| env: | |
| GOOS: ${{ matrix.goos }} | |
| GOARCH: ${{ matrix.goarch }} | |
| run: go build -v -buildvcs=false -o ${{ matrix.artifact_name }} | |
| - name: Rename for release | |
| shell: bash | |
| run: | | |
| if [ "${{ matrix.artifact_name }}" != "${{ matrix.asset_name }}" ]; then | |
| cp ${{ matrix.artifact_name }} ${{ matrix.asset_name }} | |
| fi | |
| - name: Generate checksum | |
| shell: bash | |
| run: sha256sum ${{ matrix.asset_name }} > ${{ matrix.asset_name }}.sha256 | |
| # The release already exists (created by create-release), so each matrix leg | |
| # only adds its binary and checksum. No matrix job creates the release, which | |
| # avoids the concurrent-create race entirely. draft: true is set explicitly | |
| # so these update calls keep the release in draft (the action's draft input | |
| # defaults to false and would otherwise publish it early). | |
| - name: Upload assets to draft release | |
| uses: softprops/action-gh-release@v3 | |
| with: | |
| draft: true | |
| files: | | |
| ${{ matrix.asset_name }} | |
| ${{ matrix.asset_name }}.sha256 | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| # Single job that publishes the release once, only after the full asset set is | |
| # attached. Flipping draft to false here is the moment the release becomes | |
| # public, so there is no window where a partial or empty release is visible. | |
| publish-release: | |
| needs: upload-assets | |
| if: startsWith(github.ref, 'refs/tags/v') | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write | |
| steps: | |
| - name: Publish release | |
| uses: softprops/action-gh-release@v3 | |
| with: | |
| draft: false | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} |