release #28
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`. Nothing is committed back to the repo. | |
| 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: Create + push tag | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| TAG: ${{ needs.tag.outputs.tag }} | |
| run: | | |
| set -eu | |
| 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" |