docs: add release process and notarization script
This commit is contained in:
parent
d88c59321d
commit
9258c16278
85
.github/workflows/release.yml
vendored
Normal file
85
.github/workflows/release.yml
vendored
Normal file
@ -0,0 +1,85 @@
|
||||
name: release
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag:
|
||||
description: "Tag to (re)release (e.g. v0.1.0)"
|
||||
required: true
|
||||
type: string
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
release:
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Determine tag
|
||||
id: tag
|
||||
shell: bash
|
||||
run: |
|
||||
if [[ "${GITHUB_EVENT_NAME}" == "workflow_dispatch" ]]; then
|
||||
echo "tag=${{ inputs.tag }}" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "tag=${GITHUB_REF_NAME}" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
- name: Checkout release tag
|
||||
if: ${{ github.event_name == 'workflow_dispatch' }}
|
||||
run: git checkout ${{ inputs.tag }}
|
||||
|
||||
- name: Resolve packages
|
||||
run: swift package resolve
|
||||
|
||||
- name: Sync version
|
||||
run: scripts/generate-version.sh
|
||||
|
||||
- name: Build
|
||||
run: swift build -c release --product remindctl
|
||||
|
||||
- name: Codesign
|
||||
run: codesign --force --sign - --identifier com.steipete.remindctl .build/release/remindctl
|
||||
|
||||
- name: Package artifact
|
||||
run: |
|
||||
mkdir -p dist
|
||||
cp .build/release/remindctl dist/remindctl
|
||||
(
|
||||
cd dist
|
||||
zip -r remindctl-macos.zip remindctl
|
||||
)
|
||||
|
||||
- name: Publish release assets
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
files: dist/remindctl-macos.zip
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Update GitHub release notes from CHANGELOG
|
||||
shell: bash
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
TAG: ${{ steps.tag.outputs.tag }}
|
||||
run: |
|
||||
version="${TAG#v}"
|
||||
notes_file="/tmp/release-notes.md"
|
||||
|
||||
awk -v v="$version" '
|
||||
$0 ~ ("^## " v "($|[[:space:]]-)") { in_section=1; next }
|
||||
in_section && $0 ~ "^## " { exit }
|
||||
in_section { print }
|
||||
' CHANGELOG.md > "$notes_file"
|
||||
|
||||
if ! grep -q '[^[:space:]]' "$notes_file"; then
|
||||
echo "No CHANGELOG.md section found for version $version" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
gh release edit "$TAG" --notes-file "$notes_file"
|
||||
38
docs/RELEASING.md
Normal file
38
docs/RELEASING.md
Normal file
@ -0,0 +1,38 @@
|
||||
# Releasing
|
||||
|
||||
## Release notes source
|
||||
- GitHub Release notes come from `CHANGELOG.md` for the matching version section (`## X.Y.Z - YYYY-MM-DD`).
|
||||
|
||||
## Steps
|
||||
1. Update changelog and version
|
||||
- Ensure `CHANGELOG.md` has `## 0.1.0 - YYYY-MM-DD` with final notes.
|
||||
- Update `version.env` to `0.1.0` (already set for the first release).
|
||||
- Run `scripts/generate-version.sh` (refreshes `Sources/remindctl/Version.swift` + embedded Info.plist).
|
||||
2. Ensure checks are green
|
||||
- `make check`
|
||||
3. Build, sign, and notarize (local)
|
||||
- Requires `APP_STORE_CONNECT_API_KEY_P8`, `APP_STORE_CONNECT_KEY_ID`, `APP_STORE_CONNECT_ISSUER_ID`.
|
||||
- `scripts/sign-and-notarize.sh` (outputs `/tmp/remindctl-macos.zip` by default).
|
||||
4. Tag, push, and publish
|
||||
- `git tag -a v0.1.0 -m "v0.1.0"`
|
||||
- `git push origin v0.1.0`
|
||||
- Extract release notes:
|
||||
```sh
|
||||
version=0.1.0
|
||||
notes_file=/tmp/release-notes.txt
|
||||
awk -v v="$version" '
|
||||
$0 ~ ("^## " v "($|[[:space:]]-)") { in_section=1; next }
|
||||
in_section && $0 ~ "^## " { exit }
|
||||
in_section { print }
|
||||
' CHANGELOG.md > "$notes_file"
|
||||
```
|
||||
- Create GitHub release:
|
||||
```sh
|
||||
gh release create v0.1.0 /tmp/remindctl-macos.zip -t "v0.1.0" -F /tmp/release-notes.txt
|
||||
```
|
||||
5. Homebrew tap
|
||||
- Update `../homebrew-tap/Formula/remindctl.rb` to point at the GitHub release asset.
|
||||
|
||||
## What happens in CI
|
||||
- Release signing + notarization are done locally via `scripts/sign-and-notarize.sh`.
|
||||
- `.github/workflows/release.yml` is only for manual rebuilds, not the primary release path.
|
||||
73
scripts/sign-and-notarize.sh
Executable file
73
scripts/sign-and-notarize.sh
Executable file
@ -0,0 +1,73 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
ROOT=$(cd "$(dirname "$0")/.." && pwd)
|
||||
source "$ROOT/version.env"
|
||||
|
||||
APP_NAME="remindctl"
|
||||
CODESIGN_IDENTITY=${CODESIGN_IDENTITY:-"Developer ID Application: Peter Steinberger (Y5PE65HELJ)"}
|
||||
ENTITLEMENTS="${ROOT}/Resources/remindctl.entitlements"
|
||||
OUTPUT_DIR=${OUTPUT_DIR:-/tmp}
|
||||
ZIP_PATH="${OUTPUT_DIR}/remindctl-macos.zip"
|
||||
ARCHES_VALUE=${ARCHES:-"arm64 x86_64"}
|
||||
ARCH_LIST=( ${ARCHES_VALUE} )
|
||||
DIST_DIR="$(mktemp -d "/tmp/${APP_NAME}-dist.XXXXXX")"
|
||||
API_KEY_FILE="$(mktemp "/tmp/${APP_NAME}-notary.XXXXXX.p8")"
|
||||
|
||||
cleanup() {
|
||||
rm -f "$API_KEY_FILE"
|
||||
rm -rf "$DIST_DIR"
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
if [[ -z "${APP_STORE_CONNECT_API_KEY_P8:-}" || -z "${APP_STORE_CONNECT_KEY_ID:-}" || -z "${APP_STORE_CONNECT_ISSUER_ID:-}" ]]; then
|
||||
echo "Missing APP_STORE_CONNECT_* env vars (API key, key id, issuer id)." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "$APP_STORE_CONNECT_API_KEY_P8" | sed 's/\\n/\n/g' > "$API_KEY_FILE"
|
||||
|
||||
"$ROOT/scripts/generate-version.sh"
|
||||
|
||||
for ARCH in "${ARCH_LIST[@]}"; do
|
||||
swift build -c release --product remindctl --arch "$ARCH"
|
||||
done
|
||||
|
||||
BINARIES=()
|
||||
for ARCH in "${ARCH_LIST[@]}"; do
|
||||
BINARIES+=("$ROOT/.build/${ARCH}-apple-macosx/release/remindctl")
|
||||
done
|
||||
|
||||
lipo -create "${BINARIES[@]}" -output "$DIST_DIR/remindctl"
|
||||
|
||||
if [[ -f "$ENTITLEMENTS" ]]; then
|
||||
codesign --force --timestamp --options runtime --sign "$CODESIGN_IDENTITY" \
|
||||
--entitlements "$ENTITLEMENTS" \
|
||||
"$DIST_DIR/remindctl"
|
||||
else
|
||||
codesign --force --timestamp --options runtime --sign "$CODESIGN_IDENTITY" \
|
||||
"$DIST_DIR/remindctl"
|
||||
fi
|
||||
|
||||
chmod -R u+rw "$DIST_DIR"
|
||||
xattr -cr "$DIST_DIR"
|
||||
find "$DIST_DIR" -name '._*' -delete
|
||||
|
||||
DITTO_BIN=${DITTO_BIN:-/usr/bin/ditto}
|
||||
(
|
||||
cd "$DIST_DIR"
|
||||
"$DITTO_BIN" --norsrc -c -k . "$ZIP_PATH"
|
||||
)
|
||||
|
||||
xcrun notarytool submit "$ZIP_PATH" \
|
||||
--key "$API_KEY_FILE" \
|
||||
--key-id "$APP_STORE_CONNECT_KEY_ID" \
|
||||
--issuer "$APP_STORE_CONNECT_ISSUER_ID" \
|
||||
--wait
|
||||
|
||||
codesign --verify --strict --verbose=4 "$DIST_DIR/remindctl"
|
||||
if ! spctl -a -t exec -vv "$DIST_DIR/remindctl"; then
|
||||
echo "spctl check failed (CLI binaries often report 'not an app')." >&2
|
||||
fi
|
||||
|
||||
echo "Done: $ZIP_PATH"
|
||||
Loading…
Reference in New Issue
Block a user