diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..2f9617c --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,95 @@ +# Release: builds the universal Go engine binary + Swift app, assembles the +# .app bundle exactly as `make bundle` does, zips it, and publishes it (with +# checksums) to a GitHub Release with auto-generated notes. +# +# Triggered by pushing a 3-part semver tag (vX.Y.Z). The Go toolchain used by +# this project rejects 4-part versions, so tags with a 4th segment (or any +# pre-release/build suffix) are rejected before any build work happens. +# +# The resulting app is ad-hoc signed only, not notarized (no Apple Developer ID is +# configured in this repo yet) — see the release PR for the follow-up. + +name: release + +on: + push: + tags: + - "v*" + +permissions: + contents: read + +jobs: + build: + runs-on: macos-latest + permissions: + contents: write # to publish the GitHub Release + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + + - uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0 + with: + go-version-file: engine/go.mod + + - name: Derive and validate version from tag + id: version + run: | + tag="${GITHUB_REF#refs/tags/}" + version="${tag#v}" + if [[ ! "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + echo "::error::Tag '$tag' is not a 3-part semver (vX.Y.Z). Go tooling in this repo rejects 4-part versions; retag with e.g. v${version%%.*}.0.0." >&2 + exit 1 + fi + # The bundle ships VERSION/Info.plist as-is, so a mismatched tag would + # publish an app that reports the wrong version. Fail instead. + file_version="$(tr -d '[:space:]' < VERSION)" + plist_version="$(/usr/libexec/PlistBuddy -c 'Print :CFBundleShortVersionString' ChoosePaste/Resources/Info.plist)" + if [[ "$file_version" != "$version" || "$plist_version" != "$version" ]]; then + echo "::error::Tag version ($version) must match VERSION ($file_version) and Info.plist CFBundleShortVersionString ($plist_version). Bump them before tagging." >&2 + exit 1 + fi + echo "tag=$tag" >> "$GITHUB_OUTPUT" + echo "version=$version" >> "$GITHUB_OUTPUT" + + - name: Bundle (engine + app) + run: make bundle + + # Without a bundle-level signature the .app fails `codesign -v` ("code has + # no resources but signature indicates they must be present"), which + # Gatekeeper reports as "damaged" for downloaded copies. Ad-hoc sign it + # until Developer ID signing + notarization exist. + - name: Ad-hoc sign bundle + run: | + codesign --force --deep --sign - build/ChoosePaste.app + codesign --verify --strict --deep build/ChoosePaste.app + + - name: Package artifacts + id: package + env: + TAG: ${{ steps.version.outputs.tag }} + run: | + set -euo pipefail + cd build + zip_name="ChoosePaste-${TAG}-macos.zip" + zip -r -X -q "$zip_name" ChoosePaste.app + shasum -a 256 "$zip_name" > SHA256SUMS.txt + echo "zip_name=$zip_name" >> "$GITHUB_OUTPUT" + + # Re-run safe: if the release already exists (earlier run, or a partial + # upload), replace its assets instead of failing on `gh release create`. + - name: Publish GitHub Release + env: + GH_TOKEN: ${{ github.token }} + TAG: ${{ steps.version.outputs.tag }} + ZIP_NAME: ${{ steps.package.outputs.zip_name }} + run: | + set -euo pipefail + assets=("build/$ZIP_NAME" "build/SHA256SUMS.txt") + if gh release view "$TAG" --repo "$GITHUB_REPOSITORY" >/dev/null 2>&1; then + gh release upload "$TAG" "${assets[@]}" --repo "$GITHUB_REPOSITORY" --clobber + else + gh release create "$TAG" "${assets[@]}" \ + --repo "$GITHUB_REPOSITORY" \ + --title "$TAG" \ + --generate-notes + fi diff --git a/CHANGELOG.md b/CHANGELOG.md index 90c34e4..03f9fef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,13 @@ All notable changes to this project will be documented in this file. ## [Unreleased] +### Added +- CI: release workflow (`.github/workflows/release.yml`) — on a `vX.Y.Z` tag push, + builds the universal Go engine + Swift release app, zips the .app bundle, and + publishes it with SHA256 checksums to a GitHub Release with generated notes. + The tag must match `VERSION` and Info.plist; the bundle is ad-hoc signed (not + notarized) and re-running the workflow replaces the release assets + ### Security - Bump `golang.org/x/net` to v0.58.0 and Go to 1.26.6 (fixes reachable HTML-parser DoS/correctness issues; the engine parses arbitrary clipboard HTML) diff --git a/TODOS.md b/TODOS.md index 1c70777..8c06fe8 100644 --- a/TODOS.md +++ b/TODOS.md @@ -14,17 +14,17 @@ **Priority:** P1 **Depends on:** Alpha build available with benchmarkable transform flow -### Add GitHub Actions release workflow +### Developer ID signing and notarization for release builds -**What:** Create `.github/workflows/release.yml` to build the universal Go engine binary and assemble the .app bundle on push to main or tag creation. +**What:** Extend `.github/workflows/release.yml` to sign the app (and the embedded `choosepaste-engine`) with a Developer ID Application certificate, notarize with `notarytool`, and staple the ticket before zipping. -**Why:** Currently the Makefile handles local builds, but there's no CI/CD pipeline to produce release artifacts automatically. Users and contributors can't download a pre-built binary after merge. +**Why:** Releases are currently ad-hoc signed only. Downloaded copies pass `codesign --verify` but Gatekeeper still blocks first launch ("Apple could not verify..."), so users must approve the app in System Settings. -**Context:** Deferred from /ship pre-flight check. The Makefile already handles the full build process (Go universal binary + Swift app + bundle assembly), so the workflow mainly needs to replicate those steps on GitHub-hosted macOS runners. +**Context:** Split out of the release-workflow PR (#12): no Apple Developer ID or signing secrets exist in this repo yet. Needs an Apple Developer account, repository secrets for the certificate/notary credentials, and a hardened-runtime entitlements check for the `Process()` launch of the engine. -**Effort:** S +**Effort:** M **Priority:** P2 -**Depends on:** None +**Depends on:** Apple Developer Program membership and signing secrets configured ## Design Review @@ -54,6 +54,10 @@ ## Completed +### Add GitHub Actions release workflow + +**Completed:** 2026-09-25. `.github/workflows/release.yml` added: triggers on `v*` tags, validates 3-part semver (rejects 4-part), builds the universal Go engine binary + Swift release app via `make bundle`, zips the .app, generates SHA256 checksums, and publishes a GitHub Release with auto-generated notes. Hardened in review: tag must match VERSION and Info.plist, the bundle is ad-hoc signed and verified, and publishing is re-run safe. Developer ID signing + notarization tracked above. + ### Create Markdown fixture corpus from real-world sources **Completed:** v0.1.0 (2026-04-08). 5 fixtures created in engine/testdata/fixtures/ covering Claude Code, ChatGPT, browser articles, nested lists/tables, and rich text mixed content.