Skip to content

Commit cb26116

Browse files
committed
feat: add release script for building, notarizing, and exporting MyMacCleaner
1 parent 542df91 commit cb26116

File tree

2 files changed

+204
-1
lines changed

2 files changed

+204
-1
lines changed

.env.example

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ APPLE_APP_SPECIFIC_PASSWORD=xxxx-xxxx-xxxx-xxxx
44
APPLE_TEAM_ID=XXXXXXXXXX
55

66
# Certificate name (from Keychain Access)
7-
MACOS_CERTIFICATE_NAME=Developer ID Application: Your Name (XXXXXXXXXX)
7+
MACOS_CERTIFICATE_NAME="Developer ID Application: Your Name (XXXXXXXXXX)"
88

99
# Sparkle EdDSA private key (base64)
1010
SPARKLE_PRIVATE_KEY=your-sparkle-private-key-here

scripts/release.sh

Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
#!/bin/bash
2+
set -e
3+
4+
# Load environment variables
5+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
6+
PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
7+
cd "$PROJECT_DIR"
8+
9+
if [ -f .env ]; then
10+
set -a
11+
source .env
12+
set +a
13+
else
14+
echo "Error: .env file not found"
15+
exit 1
16+
fi
17+
18+
# Configuration
19+
APP_NAME="MyMacCleaner"
20+
SCHEME="MyMacCleaner"
21+
VERSION="${1:-}"
22+
23+
if [ -z "$VERSION" ]; then
24+
echo "Usage: ./scripts/release.sh <version>"
25+
echo "Example: ./scripts/release.sh 1.1.0"
26+
exit 1
27+
fi
28+
29+
echo "=========================================="
30+
echo "Building MyMacCleaner v${VERSION}"
31+
echo "=========================================="
32+
33+
# Clean build directory
34+
rm -rf build
35+
mkdir -p build
36+
37+
# Step 1: Build and Archive
38+
echo ""
39+
echo "[1/7] Building and archiving..."
40+
xcodebuild archive \
41+
-project "${APP_NAME}.xcodeproj" \
42+
-scheme "${SCHEME}" \
43+
-archivePath "build/${APP_NAME}.xcarchive" \
44+
-configuration Release \
45+
CODE_SIGN_IDENTITY="${MACOS_CERTIFICATE_NAME}" \
46+
DEVELOPMENT_TEAM="${APPLE_TEAM_ID}" \
47+
CODE_SIGN_STYLE=Manual \
48+
OTHER_CODE_SIGN_FLAGS="--options runtime --timestamp" \
49+
ONLY_ACTIVE_ARCH=NO \
50+
| xcpretty || xcodebuild archive \
51+
-project "${APP_NAME}.xcodeproj" \
52+
-scheme "${SCHEME}" \
53+
-archivePath "build/${APP_NAME}.xcarchive" \
54+
-configuration Release \
55+
CODE_SIGN_IDENTITY="${MACOS_CERTIFICATE_NAME}" \
56+
DEVELOPMENT_TEAM="${APPLE_TEAM_ID}" \
57+
CODE_SIGN_STYLE=Manual \
58+
OTHER_CODE_SIGN_FLAGS="--options runtime --timestamp" \
59+
ONLY_ACTIVE_ARCH=NO
60+
61+
echo "Archive created successfully"
62+
63+
# Step 2: Export signed app
64+
echo ""
65+
echo "[2/7] Exporting signed app..."
66+
mkdir -p build/export
67+
68+
cat > build/ExportOptions.plist << EOF
69+
<?xml version="1.0" encoding="UTF-8"?>
70+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
71+
<plist version="1.0">
72+
<dict>
73+
<key>method</key>
74+
<string>developer-id</string>
75+
<key>teamID</key>
76+
<string>${APPLE_TEAM_ID}</string>
77+
<key>signingStyle</key>
78+
<string>manual</string>
79+
<key>signingCertificate</key>
80+
<string>Developer ID Application</string>
81+
</dict>
82+
</plist>
83+
EOF
84+
85+
xcodebuild -exportArchive \
86+
-archivePath "build/${APP_NAME}.xcarchive" \
87+
-exportPath build/export \
88+
-exportOptionsPlist build/ExportOptions.plist
89+
90+
echo "App exported successfully"
91+
92+
# Step 3: Notarize the app
93+
echo ""
94+
echo "[3/7] Notarizing app (this may take a few minutes)..."
95+
cd build/export
96+
ditto -c -k --keepParent "${APP_NAME}.app" "../notarization.zip"
97+
cd ..
98+
99+
xcrun notarytool submit notarization.zip \
100+
--keychain-profile "notary-profile" \
101+
--wait
102+
103+
echo "Stapling notarization ticket..."
104+
xcrun stapler staple "export/${APP_NAME}.app"
105+
xcrun stapler validate "export/${APP_NAME}.app"
106+
cd "$PROJECT_DIR"
107+
108+
echo "App notarized and stapled successfully"
109+
110+
# Step 4: Create DMG
111+
echo ""
112+
echo "[4/7] Creating DMG..."
113+
mkdir -p build/dmg-contents
114+
cp -R "build/export/${APP_NAME}.app" build/dmg-contents/
115+
ln -s /Applications build/dmg-contents/Applications
116+
117+
hdiutil create -volname "${APP_NAME}" \
118+
-srcfolder build/dmg-contents \
119+
-ov -format UDZO \
120+
"build/${APP_NAME}-v${VERSION}.dmg"
121+
122+
echo "DMG created successfully"
123+
124+
# Step 5: Sign the DMG
125+
echo ""
126+
echo "[5/7] Signing DMG..."
127+
codesign --force --sign "${MACOS_CERTIFICATE_NAME}" \
128+
--options runtime --timestamp \
129+
"build/${APP_NAME}-v${VERSION}.dmg"
130+
131+
echo "DMG signed successfully"
132+
133+
# Step 6: Notarize the DMG
134+
echo ""
135+
echo "[6/7] Notarizing DMG..."
136+
xcrun notarytool submit "build/${APP_NAME}-v${VERSION}.dmg" \
137+
--keychain-profile "notary-profile" \
138+
--wait
139+
140+
xcrun stapler staple "build/${APP_NAME}-v${VERSION}.dmg"
141+
142+
echo "DMG notarized and stapled successfully"
143+
144+
# Step 7: Create ZIP for Sparkle and sign
145+
echo ""
146+
echo "[7/7] Creating Sparkle update ZIP..."
147+
cd build/export
148+
zip -r -y "../${APP_NAME}-v${VERSION}.zip" "${APP_NAME}.app"
149+
cd "$PROJECT_DIR"
150+
151+
# Sign with Sparkle (if sign_update is available)
152+
echo "Signing update for Sparkle..."
153+
154+
# Try to find sign_update
155+
SPARKLE_SIGN=$(find ~/Library/Developer/Xcode/DerivedData -name "sign_update" -path "*/Sparkle.framework/*" 2>/dev/null | head -1)
156+
157+
if [ -z "$SPARKLE_SIGN" ]; then
158+
# Download Sparkle if not found
159+
echo "Downloading Sparkle tools..."
160+
curl -L -o /tmp/sparkle.tar.xz "https://github.com/sparkle-project/Sparkle/releases/download/2.6.4/Sparkle-2.6.4.tar.xz"
161+
mkdir -p /tmp/sparkle
162+
tar -xf /tmp/sparkle.tar.xz -C /tmp/sparkle
163+
SPARKLE_SIGN="/tmp/sparkle/bin/sign_update"
164+
fi
165+
166+
# Create temp file for private key
167+
echo "$SPARKLE_PRIVATE_KEY" > /tmp/sparkle_key
168+
SIGNATURE=$("$SPARKLE_SIGN" --ed-key-file /tmp/sparkle_key "build/${APP_NAME}-v${VERSION}.zip")
169+
rm -f /tmp/sparkle_key
170+
171+
echo ""
172+
echo "=========================================="
173+
echo "BUILD COMPLETE!"
174+
echo "=========================================="
175+
echo ""
176+
echo "Files created in build/:"
177+
ls -lh "build/${APP_NAME}-v${VERSION}.dmg" "build/${APP_NAME}-v${VERSION}.zip"
178+
echo ""
179+
echo "Sparkle signature:"
180+
echo "$SIGNATURE"
181+
echo ""
182+
FILE_SIZE=$(stat -f%z "build/${APP_NAME}-v${VERSION}.zip")
183+
echo "File size: $FILE_SIZE bytes"
184+
echo ""
185+
echo "Appcast XML snippet:"
186+
echo "----------------------------------------"
187+
cat << EOF
188+
<item>
189+
<title>Version ${VERSION}</title>
190+
<pubDate>$(date -R)</pubDate>
191+
<sparkle:version>${VERSION}</sparkle:version>
192+
<enclosure
193+
url="https://github.com/Prot10/MyMacCleaner/releases/download/v${VERSION}/${APP_NAME}-v${VERSION}.zip"
194+
${SIGNATURE}
195+
length="${FILE_SIZE}"
196+
type="application/octet-stream"/>
197+
</item>
198+
EOF
199+
echo "----------------------------------------"
200+
echo ""
201+
echo "Next steps:"
202+
echo "1. Create GitHub release: gh release create v${VERSION} build/${APP_NAME}-v${VERSION}.dmg build/${APP_NAME}-v${VERSION}.zip --title 'MyMacCleaner v${VERSION}'"
203+
echo "2. Update appcast.xml with the snippet above"

0 commit comments

Comments
 (0)