release #32
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: release | |
| # Manually-triggered stable release. Pick a bump (patch/minor/major) — | |
| # the next vX.Y.Z tag is computed from the latest stable tag. The `notes` | |
| # input is used verbatim as the release body and is also attached to the | |
| # release as `CHANGELOG.md`. An ephemeral release commit (with the built | |
| # ui/dist/ force-added past .gitignore) is created on a detached HEAD and | |
| # the new tag points to *that* commit, so `go install ...@vX.Y.Z` resolves | |
| # a tree containing the embedded UI assets. Main itself is never modified | |
| # — only the tag is pushed to origin. | |
| on: | |
| workflow_dispatch: | |
| inputs: | |
| bump: | |
| description: "Semver bump (patch/minor/major)" | |
| required: true | |
| type: choice | |
| default: patch | |
| options: | |
| - patch | |
| - minor | |
| - major | |
| notes: | |
| description: "Release notes in Markdown. Becomes the release body and the attached CHANGELOG.md asset. Use \\n for newlines if passing via CLI." | |
| required: true | |
| type: string | |
| permissions: read-all | |
| concurrency: | |
| group: release-manual | |
| cancel-in-progress: false | |
| jobs: | |
| tag: | |
| name: compute next tag | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| outputs: | |
| tag: ${{ steps.ver.outputs.tag }} | |
| steps: | |
| - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 | |
| with: | |
| fetch-depth: 0 | |
| - name: Compute next stable tag | |
| id: ver | |
| env: | |
| BUMP: ${{ inputs.bump }} | |
| run: | | |
| set -eu | |
| latest=$(git tag -l 'v[0-9]*.[0-9]*.[0-9]*' \ | |
| | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$' \ | |
| | sort -V \ | |
| | tail -1) | |
| if [ -z "$latest" ]; then | |
| latest="v0.0.0" | |
| fi | |
| ver="${latest#v}" | |
| major="${ver%%.*}" | |
| rest="${ver#*.}" | |
| minor="${rest%%.*}" | |
| patch="${rest##*.}" | |
| case "$BUMP" in | |
| major) major=$((major+1)); minor=0; patch=0 ;; | |
| minor) minor=$((minor+1)); patch=0 ;; | |
| patch) patch=$((patch+1)) ;; | |
| *) echo "unknown bump: $BUMP" >&2; exit 1 ;; | |
| esac | |
| next="v${major}.${minor}.${patch}" | |
| echo "tag=$next" >> "$GITHUB_OUTPUT" | |
| echo "Next tag: $next (from $latest, bump=$BUMP)" | |
| ui: | |
| name: build ui | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| steps: | |
| - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 | |
| - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6 | |
| with: | |
| node-version: '22' | |
| cache: 'npm' | |
| cache-dependency-path: ui/package-lock.json | |
| - run: npm --prefix ui ci | |
| - run: npm --prefix ui run build | |
| - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 | |
| with: | |
| name: ui-dist | |
| path: ui/dist | |
| retention-days: 1 | |
| if-no-files-found: error | |
| build: | |
| name: build ${{ matrix.goos }}-${{ matrix.goarch }} | |
| needs: [tag, ui] | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - runner: ubuntu-latest | |
| goos: linux | |
| goarch: amd64 | |
| - runner: macos-latest | |
| goos: darwin | |
| goarch: arm64 | |
| runs-on: ${{ matrix.runner }} | |
| permissions: | |
| contents: read | |
| env: | |
| CGO_ENABLED: "1" | |
| GOOS: ${{ matrix.goos }} | |
| GOARCH: ${{ matrix.goarch }} | |
| steps: | |
| - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 | |
| - uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6 | |
| with: | |
| go-version-file: go.mod | |
| - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 | |
| with: | |
| name: ui-dist | |
| path: ui/dist | |
| - name: Build binary | |
| run: | | |
| set -eu | |
| tag="${{ needs.tag.outputs.tag }}" | |
| sha="${{ github.sha }}" | |
| date="$(date -u +%Y-%m-%dT%H:%M:%SZ)" | |
| pkg="github.com/RandomCodeSpace/docsiq/cmd" | |
| ldflags="-s -w -X ${pkg}.Version=${tag} -X ${pkg}.Commit=${sha} -X ${pkg}.Date=${date}" | |
| go build -tags sqlite_fts5 -trimpath -ldflags="${ldflags}" -o docsiq ./ | |
| - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 | |
| with: | |
| name: binary-${{ matrix.goos }}-${{ matrix.goarch }} | |
| path: docsiq | |
| retention-days: 1 | |
| if-no-files-found: error | |
| release: | |
| name: publish release | |
| needs: [tag, build] | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write | |
| id-token: write # cosign keyless | |
| attestations: write # SLSA provenance | |
| steps: | |
| - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 | |
| with: | |
| fetch-depth: 0 | |
| - uses: sigstore/cosign-installer@cad07c2e89fa2edd6e2d7bab4c1aa38e53f76003 # v4.1.1 | |
| with: | |
| # v2.x — v3 broke our sign-blob flag compatibility. | |
| cosign-release: 'v2.6.3' | |
| - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 | |
| with: | |
| pattern: binary-* | |
| path: downloaded/ | |
| - name: Prepare release notes | |
| id: notes | |
| env: | |
| TAG: ${{ needs.tag.outputs.tag }} | |
| NOTES: ${{ inputs.notes }} | |
| run: | | |
| set -eu | |
| if ! printf '%s' "$NOTES" | grep -Eq '[^[:space:]]'; then | |
| echo "::error::The 'notes' workflow input is empty. Fire release.yml again with curated Markdown notes." | |
| exit 1 | |
| fi | |
| mkdir -p dist | |
| # CHANGELOG.md attached to the release as-is (same bytes as the body). | |
| printf '# %s\n\n%s\n' "$TAG" "$NOTES" > dist/CHANGELOG.md | |
| # Emit the body to GITHUB_OUTPUT for the next step. The Verify | |
| # footer is appended in the create-release step. | |
| { | |
| echo 'body<<__RN_EOF__' | |
| printf '%s' "$NOTES" | |
| echo | |
| echo '__RN_EOF__' | |
| } >> "$GITHUB_OUTPUT" | |
| - name: Assemble versioned binaries + SHA256SUMS | |
| env: | |
| TAG: ${{ needs.tag.outputs.tag }} | |
| run: | | |
| set -eu | |
| mkdir -p dist | |
| for dir in downloaded/binary-*; do | |
| rest=$(basename "$dir" | sed 's/^binary-//') | |
| goos="${rest%-*}" | |
| goarch="${rest##*-}" | |
| out="dist/docsiq-${TAG}-${goos}-${goarch}" | |
| cp "$dir/docsiq" "$out" | |
| chmod +x "$out" | |
| done | |
| (cd dist && sha256sum docsiq-* > SHA256SUMS) | |
| ls -la dist/ | |
| - name: Sign artifacts with cosign (keyless) | |
| run: | | |
| set -eu | |
| cd dist | |
| for f in docsiq-* SHA256SUMS; do | |
| cosign sign-blob --yes \ | |
| --output-signature="${f}.sig" \ | |
| --output-certificate="${f}.pem" \ | |
| "$f" | |
| done | |
| ls -la | |
| - name: Download ui-dist for release commit | |
| uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 | |
| with: | |
| name: ui-dist | |
| path: ui/dist | |
| - name: Bake ui/dist into release commit and tag it | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| TAG: ${{ needs.tag.outputs.tag }} | |
| run: | | |
| set -eu | |
| # ui/dist/ is gitignored on main, so a `go install ...@TAG` | |
| # against the main tree would embed only a placeholder | |
| # index.html and 404 on every /assets/* request. Create an | |
| # ephemeral commit on a detached HEAD that force-includes the | |
| # built ui/dist/, tag that commit as $TAG, and push the tag | |
| # only — main itself is never modified. | |
| git config user.name 'github-actions[bot]' | |
| git config user.email '41898282+github-actions[bot]@users.noreply.github.com' | |
| git checkout --detach | |
| git add -f ui/dist | |
| git commit -m "release: bake ui/dist for ${TAG}" | |
| git tag "$TAG" | |
| git push origin "$TAG" | |
| - name: Create GitHub release and upload assets | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| TAG: ${{ needs.tag.outputs.tag }} | |
| NOTES_BODY: ${{ steps.notes.outputs.body }} | |
| run: | | |
| set -eu | |
| { | |
| printf '%s\n\n' "$NOTES_BODY" | |
| printf '### Verify\n\n' | |
| printf 'All artifacts are signed with [cosign](https://github.com/sigstore/cosign) keyless via Sigstore.\n\n' | |
| printf '```sh\n' | |
| printf 'cosign verify-blob \\\n' | |
| printf " --certificate-identity-regexp 'https://github.com/RandomCodeSpace/docsiq/\\\\.github/workflows/release\\\\.yml.*' \\\\\n" | |
| printf " --certificate-oidc-issuer 'https://token.actions.githubusercontent.com' \\\\\n" | |
| printf ' --certificate docsiq-%s-linux-amd64.pem \\\n' "$TAG" | |
| printf ' --signature docsiq-%s-linux-amd64.sig \\\n' "$TAG" | |
| printf ' docsiq-%s-linux-amd64\n' "$TAG" | |
| printf '```\n' | |
| } > release-notes.md | |
| gh release create "$TAG" \ | |
| --title "$TAG" \ | |
| --notes-file release-notes.md \ | |
| dist/docsiq-* dist/SHA256SUMS dist/SHA256SUMS.sig dist/SHA256SUMS.pem dist/CHANGELOG.md | |
| - name: Generate SLSA build provenance | |
| id: attest | |
| uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 # v4.1.0 | |
| with: | |
| subject-path: | | |
| dist/docsiq-*-linux-amd64 | |
| dist/docsiq-*-darwin-arm64 | |
| dist/SHA256SUMS | |
| - name: Upload provenance to release | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| TAG: ${{ needs.tag.outputs.tag }} | |
| run: | | |
| set -eu | |
| cp "${{ steps.attest.outputs.bundle-path }}" "docsiq-${TAG}.intoto.jsonl" | |
| gh release upload "$TAG" "docsiq-${TAG}.intoto.jsonl" |