-
Notifications
You must be signed in to change notification settings - Fork 0
174 lines (153 loc) · 7.98 KB
/
Copy pathrelease.yml
File metadata and controls
174 lines (153 loc) · 7.98 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
# Release workflow — archives, signs, notarizes, and drafts a GitHub release
# for InterlinedList (Developer ID distribution, not App Store).
#
# Two scripts drive the heavy lifting:
# scripts/notarize-and-package.sh — archive → export → notarize .app → .pkg
# scripts/create-dmg.sh — notarized .app → signed, notarized .dmg
#
# Triggered by: a tag push matching v[0-9]* (e.g. v0.1.0, v1.2.3).
# The resulting GitHub release is always a DRAFT — a human publishes it.
#
# ------------------------------------------------------------------
# Secrets required (repo Settings → Secrets and variables → Actions):
#
# CERTIFICATES_P12 Base64-encoded .p12 bundle containing both the
# Developer ID Application and Developer ID Installer
# certificates (export from Keychain Access).
# Encode with: base64 -i Certificates.p12 | pbcopy
# CERTIFICATES_P12_PASSWORD Passphrase protecting the .p12.
# APPLE_ID Apple ID email used for notarization.
# APPLE_TEAM_ID 10-character Apple Developer Team ID.
# CODESIGN_IDENTITY Full certificate common name, e.g.:
# "Developer ID Application: Acme Corp (XXXXXXXXXX)"
# INSTALLER_IDENTITY Full certificate common name, e.g.:
# "Developer ID Installer: Acme Corp (XXXXXXXXXX)"
# NOTARIZATION_PASSWORD App-specific password for notarytool; generate at
# https://appleid.apple.com → App-Specific Passwords.
#
# Note: GITHUB_TOKEN is provided automatically by Actions. It does not need to
# be added as a secret — it is used by `gh` to create the draft release.
name: Release
on:
push:
tags:
- 'v[0-9]*'
# Never cancel in-progress releases — a release must always run to completion.
concurrency:
group: release-${{ github.ref }}
cancel-in-progress: false
env:
# Pin to the same toolchain used by ci.yml: Xcode 16.2 / Swift 6.0.3.
XCODE_VERSION: "16.2"
jobs:
release:
name: "Release: ${{ github.ref_name }}"
runs-on: macos-15
timeout-minutes: 90
permissions:
contents: write
steps:
- uses: actions/checkout@v4
- name: Select Xcode ${{ env.XCODE_VERSION }}
uses: maxim-lobanov/setup-xcode@v1
with:
xcode-version: ${{ env.XCODE_VERSION }}
# ─── Extract version from tag ─────────────────────────────────────────
# Strip the leading 'v': v0.1.0 → 0.1.0
- name: Extract version from tag
id: version
run: |
VERSION="${{ github.ref_name }}"
VERSION="${VERSION#v}"
echo "VERSION=$VERSION" >> "$GITHUB_ENV"
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
# ─── Import Developer ID certificates ────────────────────────────────
# Decode the .p12, create a short-lived keychain, and import the certs
# so that codesign and productbuild can sign without touching the runner's
# login keychain. The keychain is removed in the cleanup step below.
- name: Import Developer ID certificates
run: |
CERT_FILE="$(mktemp /tmp/certificates.XXXXXX.p12)"
echo "${{ secrets.CERTIFICATES_P12 }}" | base64 --decode > "$CERT_FILE"
KEYCHAIN_PATH="$RUNNER_TEMP/build.keychain"
KEYCHAIN_PASSWORD="$(openssl rand -hex 16)"
security create-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
security set-keychain-settings -lut 3600 "$KEYCHAIN_PATH"
security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
security import "$CERT_FILE" \
-k "$KEYCHAIN_PATH" \
-P "${{ secrets.CERTIFICATES_P12_PASSWORD }}" \
-T /usr/bin/codesign \
-T /usr/bin/productbuild \
-T /usr/bin/pkgbuild
rm -f "$CERT_FILE"
# Prepend the build keychain to the search list so codesign finds it.
EXISTING_KEYCHAINS=$(security list-keychains -d user | xargs)
security list-keychains -d user -s "$KEYCHAIN_PATH" $EXISTING_KEYCHAINS
# Allow codesign to access the private key without a UI prompt.
security set-key-partition-list \
-S apple-tool:,apple:,codesign: \
-s \
-k "$KEYCHAIN_PASSWORD" \
"$KEYCHAIN_PATH"
# Persist the path (not the password) for the cleanup step.
echo "KEYCHAIN_PATH=$KEYCHAIN_PATH" >> "$GITHUB_ENV"
# ─── Archive, sign, notarize, package → .pkg ──────────────────────────
# Also leaves the notarized, stapled .app at build/export/InterlinedList.app
# which the next step consumes.
- name: Build, sign, and package .pkg
env:
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
CODESIGN_IDENTITY: ${{ secrets.CODESIGN_IDENTITY }}
INSTALLER_IDENTITY: ${{ secrets.INSTALLER_IDENTITY }}
NOTARIZATION_PASSWORD: ${{ secrets.NOTARIZATION_PASSWORD }}
APP_VERSION: ${{ env.VERSION }}
run: ./scripts/notarize-and-package.sh
# ─── Sign, notarize, staple → .dmg ────────────────────────────────────
# Consumes build/export/InterlinedList.app left by the previous step.
- name: Build, sign, and notarize .dmg
env:
VERSION: ${{ env.VERSION }}
CODESIGN_IDENTITY: ${{ secrets.CODESIGN_IDENTITY }}
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
NOTARIZATION_PASSWORD: ${{ secrets.NOTARIZATION_PASSWORD }}
run: ./scripts/create-dmg.sh
# ─── Normalise artifact names ──────────────────────────────────────────
# notarize-and-package.sh uses the versioned name when APP_VERSION is set.
# This step handles the fallback where it produced an unversioned name, and
# confirms both artifacts are present before proceeding to the release step.
- name: Collect and verify artifacts
run: |
VERSION="${{ env.VERSION }}"
VERSIONED_PKG="build/InterlinedList-${VERSION}.pkg"
# Rename only when the versioned file is absent but the plain file exists.
if [[ -f "build/InterlinedList.pkg" && ! -f "$VERSIONED_PKG" ]]; then
mv "build/InterlinedList.pkg" "$VERSIONED_PKG"
fi
ls -lh "$VERSIONED_PKG" "build/InterlinedList-${VERSION}.dmg"
# ─── Draft GitHub release ─────────────────────────────────────────────
# A human reviews and publishes the draft via the GitHub UI or `gh`.
# Both artifacts are attached; the release notes can be expanded before
# publishing.
- name: Create draft GitHub release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
VERSION="${{ env.VERSION }}"
gh release create "v${VERSION}" \
--draft \
--title "InterlinedList ${VERSION}" \
--notes "Release ${VERSION}" \
"build/InterlinedList-${VERSION}.pkg" \
"build/InterlinedList-${VERSION}.dmg"
# ─── Cleanup temporary keychain (always runs) ─────────────────────────
# Runs even when earlier steps fail so the keychain is never left behind
# on the ephemeral runner.
- name: Cleanup temporary keychain
if: always()
run: |
security delete-keychain \
"${KEYCHAIN_PATH:-$RUNNER_TEMP/build.keychain}" \
2>/dev/null || true