Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 95 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -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
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
16 changes: 10 additions & 6 deletions TODOS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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.
Expand Down
Loading