This commit is contained in:
@@ -0,0 +1,45 @@
|
||||
name: CI
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches: [main]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
# Only the latest run per branch/PR matters; cancel older ones so private-repo
|
||||
# macOS minutes (billed at 10x) aren't spent on superseded commits.
|
||||
concurrency:
|
||||
group: ci-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
test:
|
||||
name: test
|
||||
runs-on: macos-14
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
|
||||
# Package.swift declares swift-tools-version 6.0, which needs Xcode 16+.
|
||||
- uses: maxim-lobanov/setup-xcode@v1
|
||||
with:
|
||||
xcode-version: latest-stable
|
||||
|
||||
- name: Swift version
|
||||
run: swift --version
|
||||
|
||||
# Cache the SwiftPM build (deps + compiled objects) keyed on the resolved
|
||||
# dependency graph, so green runs don't recompile SwiftMath etc. each time.
|
||||
- name: Cache .build
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
path: .build
|
||||
# v2: the repo was renamed md -> Edmund, which changed the checkout
|
||||
# path and invalidated absolute paths baked into the cached module
|
||||
# cache. Bump this token to discard any pre-rename .build cache.
|
||||
key: spm-v2-${{ runner.os }}-${{ hashFiles('Package.resolved') }}
|
||||
restore-keys: spm-v2-${{ runner.os }}-
|
||||
|
||||
- name: Test
|
||||
run: swift test
|
||||
@@ -0,0 +1,133 @@
|
||||
name: Release
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*'
|
||||
|
||||
jobs:
|
||||
release:
|
||||
name: Build & publish
|
||||
runs-on: macos-14
|
||||
permissions:
|
||||
contents: write # create releases and commit appcast
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
# Fetch full history so we can push the appcast commit back.
|
||||
fetch-depth: 0
|
||||
# Admin PAT so the appcast commit can be pushed to the protected
|
||||
# `main` branch. The default GITHUB_TOKEN/bot is not an admin and is
|
||||
# blocked by the required `test` status check.
|
||||
token: ${{ secrets.RELEASE_TOKEN }}
|
||||
|
||||
- uses: maxim-lobanov/setup-xcode@v1
|
||||
with:
|
||||
xcode-version: latest-stable
|
||||
|
||||
- name: Cache .build
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
path: .build
|
||||
key: spm-v2-${{ runner.os }}-${{ hashFiles('Package.resolved') }}
|
||||
restore-keys: spm-v2-${{ runner.os }}-
|
||||
|
||||
- name: Build app bundle
|
||||
run: ./scripts/build-app.sh
|
||||
|
||||
# create-dmg 8.x requires Node >= 20; pin it so the runner's default node
|
||||
# version can't break the release.
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
|
||||
- name: Install create-dmg
|
||||
# sindresorhus/create-dmg is the npm package (NOT the homebrew
|
||||
# create-dmg/create-dmg tool, which has an incompatible CLI).
|
||||
run: npm install --global create-dmg
|
||||
|
||||
- name: Create DMG
|
||||
run: |
|
||||
VERSION="$(/usr/libexec/PlistBuddy -c 'Print CFBundleShortVersionString' Info.plist)"
|
||||
echo "VERSION=$VERSION" >> "$GITHUB_ENV"
|
||||
# create-dmg exits 2 when it can't Developer-ID-sign / notarize the
|
||||
# image (we ship ad-hoc only) but still produces the .dmg — so don't
|
||||
# let that non-zero status fail the job.
|
||||
create-dmg build/Edmund.app build/ || true
|
||||
# It names the output "Edmund <version>.dmg"; normalize to a
|
||||
# hyphenated, URL-friendly name and fail loudly if none was produced.
|
||||
DMG_SRC="$(ls build/Edmund*.dmg 2>/dev/null | head -1)"
|
||||
if [ -z "$DMG_SRC" ]; then
|
||||
echo "create-dmg produced no .dmg" >&2
|
||||
exit 1
|
||||
fi
|
||||
mv "$DMG_SRC" "build/Edmund-${VERSION}.dmg"
|
||||
|
||||
- name: Sign archive (EdDSA)
|
||||
env:
|
||||
SPARKLE_ED_PRIVATE_KEY: ${{ secrets.SPARKLE_ED_PRIVATE_KEY }}
|
||||
run: |
|
||||
SIGN_UPDATE="$(find .build -name sign_update -type f | head -1)"
|
||||
# Pass the key on stdin via --ed-key-file -. The deprecated `-s <key>`
|
||||
# is fatal for newly generated keys ("no longer supported").
|
||||
SIG_OUTPUT="$(echo "$SPARKLE_ED_PRIVATE_KEY" | "$SIGN_UPDATE" --ed-key-file - "build/Edmund-${VERSION}.dmg")"
|
||||
echo "ED_SIG=$(echo "$SIG_OUTPUT" | grep -o 'sparkle:edSignature="[^"]*"')" >> "$GITHUB_ENV"
|
||||
echo "LENGTH=$(echo "$SIG_OUTPUT" | grep -o 'length="[^"]*"' | head -1)" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Create GitHub Release
|
||||
id: create_release
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
awk "BEGIN{p=0} /^## \[${VERSION}\]/{p=1;next} p && /^## \[/{exit} p{print}" \
|
||||
CHANGELOG.md > release-notes.txt
|
||||
if [ ! -s release-notes.txt ]; then
|
||||
echo "See CHANGELOG for details." > release-notes.txt
|
||||
fi
|
||||
gh release create "v${VERSION}" "build/Edmund-${VERSION}.dmg" \
|
||||
--title "Edmund ${VERSION}" \
|
||||
--notes-file release-notes.txt \
|
||||
--latest
|
||||
|
||||
- name: Update appcast.xml
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
REPO="${{ github.repository }}"
|
||||
ASSET_URL="https://github.com/${REPO}/releases/download/v${VERSION}/Edmund-${VERSION}.dmg"
|
||||
PUB_DATE="$(date -u '+%a, %d %b %Y %H:%M:%S +0000')"
|
||||
BUILD="$(/usr/libexec/PlistBuddy -c 'Print CFBundleVersion' Info.plist)"
|
||||
|
||||
# Release notes for Sparkle's update dialog (scrollable pane), built
|
||||
# from this version's CHANGELOG section. Omitted if the section is missing.
|
||||
# Built with printf (not a literal multi-line string) so this step's
|
||||
# `run: |` block scalar doesn't contain an unindented line, which
|
||||
# breaks YAML parsing of the whole workflow file.
|
||||
# $(...) strips trailing newlines, so DESC_BLOCK can't carry its own
|
||||
# trailing separator — NEW_ITEM's printf adds it explicitly instead.
|
||||
DESC_HTML="$(python3 scripts/changelog-to-html.py "$VERSION")"
|
||||
DESC_BLOCK=""
|
||||
if [ -n "$DESC_HTML" ]; then
|
||||
DESC_BLOCK="$(printf ' <description><![CDATA[\n%s\n]]></description>' "$DESC_HTML")"
|
||||
fi
|
||||
|
||||
# Built with printf (one logical YAML line) rather than a literal
|
||||
# multi-line string: an unindented line inside this value would
|
||||
# break this run block's YAML indentation (as DESC_BLOCK did above).
|
||||
NEW_ITEM="$(printf ' <item>\n <title>Edmund %s</title>\n <pubDate>%s</pubDate>\n%s\n <enclosure url="%s"\n sparkle:version="%s"\n sparkle:shortVersionString="%s"\n %s\n %s\n type="application/x-apple-diskimage"/>\n </item>' "$VERSION" "$PUB_DATE" "$DESC_BLOCK" "$ASSET_URL" "$BUILD" "$VERSION" "$ED_SIG" "$LENGTH")"
|
||||
|
||||
python3 - "$NEW_ITEM" <<'PYEOF'
|
||||
import sys
|
||||
new_item = sys.argv[1]
|
||||
with open("appcast.xml", "r") as f:
|
||||
content = f.read()
|
||||
content = content.replace(" </channel>", new_item + "\n </channel>")
|
||||
with open("appcast.xml", "w") as f:
|
||||
f.write(content)
|
||||
PYEOF
|
||||
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "github-actions[bot]@users.noreply.github.com"
|
||||
git add appcast.xml
|
||||
git commit -m "appcast: add Edmund ${VERSION}"
|
||||
git push origin HEAD:main
|
||||
Reference in New Issue
Block a user