-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathjustfile
More file actions
353 lines (305 loc) Β· 16.6 KB
/
Copy pathjustfile
File metadata and controls
353 lines (305 loc) Β· 16.6 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
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
# Deck - Presentation Remote System
# Run `just` to see available commands
set shell := ["bash", "-uc"]
set dotenv-load
# Find the most recently modified DerivedData folder
derived_data := `ls -td ~/Library/Developer/Xcode/DerivedData/Deck-* 2>/dev/null | head -1`
mac_app := derived_data + "/Build/Products/Debug/Deck.app"
release_mac_app := derived_data + "/Build/Products/Release/Deck.app"
ios_app := derived_data + "/Build/Products/Debug-iphoneos/Deck.app"
ios_sim_app := derived_data + "/Build/Products/Debug-iphonesimulator/Deck.app"
# Version from Info.plist
version := `/usr/libexec/PlistBuddy -c "Print CFBundleShortVersionString" MacApp/Info.plist 2>/dev/null || echo "1.0.0"`
dmg_name := "Deck"
mac_logo := "public/logo/mac-logo.png"
notary_profile := "notarytool-profile"
signing_identity := "Developer ID Application: DOU Inc. (HD35YQ72U4)"
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# Development
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# Show available commands
default:
@just --list
# Generate Xcode project from project.yml
generate:
xcodegen generate
# Regenerate all app icons + marketing logos from deck-logo.png
# App icons are flattened onto white (no alpha β required by the App Store);
# public/logo/*.png keep transparency for the DMG/marketing use.
regen-icons:
#!/usr/bin/env bash
set -euo pipefail
command -v magick >/dev/null || { echo "β ImageMagick not found (brew install imagemagick)"; exit 1; }
SRC="deck-logo.png"
[ -f "$SRC" ] || { echo "β $SRC not found"; exit 1; }
opaque() { magick "$SRC" -resize "${1}x${1}" -background white -flatten -alpha off "$2"; echo " $2 (${1}px)"; }
alpha() { magick "$SRC" -resize "${1}x${1}" "$2"; echo " $2 (${1}px, alpha)"; }
# padded opaque: scale logo to a fraction of the canvas, center on white (watch face needs buffer)
padded() { local inner; inner=$(awk "BEGIN{printf \"%d\", $1*$3}"); \
magick "$SRC" -resize "${inner}x${inner}" -background white -gravity center -extent "${1}x${1}" -alpha off "$2"; \
echo " $2 (${1}px, ${3}x buffer)"; }
echo "πΌ Mac AppIcon:"
for s in 16 32 64 128 256 512 1024; do
opaque $s "MacApp/Assets.xcassets/AppIcon.appiconset/icon-$s.png"
done
echo "π± iPhone AppIcon:"
opaque 1024 "iPhoneApp/Assets.xcassets/AppIcon.appiconset/icon-1024.png"
echo "β Watch AppIcon (padded for circular face):"
padded 1024 "WatchApp/Assets.xcassets/AppIcon.appiconset/icon-1024.png" 0.70
echo "πΌ DeckLogo imageset (in-app, transparent):"
alpha 80 "iPhoneApp/Assets.xcassets/DeckLogo.imageset/deck-logo.png"
alpha 160 "iPhoneApp/Assets.xcassets/DeckLogo.imageset/[email protected]"
alpha 240 "iPhoneApp/Assets.xcassets/DeckLogo.imageset/[email protected]"
echo "π¨ Marketing logos:"
alpha 1024 "public/logo/mac-logo.png"
alpha 1024 "public/logo/ios-logo.png"
echo "β
Icons regenerated from $SRC"
# Build Mac menu bar app
build-mac:
xcodebuild -scheme DeckMac build
# Build iOS app for physical device
build-ios:
@if [ -z "${XCODE_DEVICE_ID:-}" ]; then \
echo "Error: XCODE_DEVICE_ID not set. Create .env file with XCODE_DEVICE_ID=your-udid"; \
exit 1; \
fi
xcodebuild -scheme DeckiOS -destination 'id={{ env("XCODE_DEVICE_ID", "") }}' build
# Build iOS app for simulator
build-sim:
xcodebuild -scheme DeckiOS -destination 'platform=iOS Simulator,name=iPhone 16,OS=18.2' build
# Build and run iOS app in simulator
run-sim: build-sim
xcrun simctl boot "iPhone 16" 2>/dev/null || true
xcrun simctl install booted "{{ ios_sim_app }}"
xcrun simctl launch booted com.dou.clicker-ios
# Build and run Mac app
run-mac: build-mac
#!/usr/bin/env bash
set -euo pipefail
# Resolve DerivedData at runtime (parse-time vars are empty on a fresh checkout)
derived=$(ls -td ~/Library/Developer/Xcode/DerivedData/Deck-* 2>/dev/null | head -1)
if [ -z "$derived" ]; then echo "β No Deck DerivedData folder found β did the build run?"; exit 1; fi
open "$derived/Build/Products/Debug/Deck.app"
# Build (Release) and install Mac app into /Applications
install-mac: build-mac-release
#!/usr/bin/env bash
set -euo pipefail
# Resolve DerivedData at runtime (parse-time vars are empty on a fresh checkout)
derived=$(ls -td ~/Library/Developer/Xcode/DerivedData/Deck-* 2>/dev/null | head -1)
if [ -z "$derived" ]; then echo "β No Deck DerivedData folder found β did the build run?"; exit 1; fi
app="$derived/Build/Products/Release/Deck.app"
if [ ! -d "$app" ]; then echo "β $app not found β did the build succeed?"; exit 1; fi
echo "π¦ Installing Deck.app β /Applications ..."
# Quit any running instance so the bundle can be replaced cleanly
osascript -e 'quit app "Deck"' 2>/dev/null || true
rm -rf "/Applications/Deck.app"
ditto "$app" "/Applications/Deck.app"
echo "β
Installed: /Applications/Deck.app"
open "/Applications/Deck.app"
# Build and install iOS app on device
install-ios: build-ios
@if [ -z "${DEVICECTL_ID:-}" ]; then \
echo "Error: DEVICECTL_ID not set. Run 'xcrun devicectl list devices' to find your device UUID"; \
exit 1; \
fi
xcrun devicectl device install app --device {{ env("DEVICECTL_ID", "") }} "{{ ios_app }}"
# Build, install, and launch iOS app on device
run-ios: install-ios
xcrun devicectl device process launch --device {{ env("DEVICECTL_ID", "") }} com.dou.clicker-ios
# Build and deploy iPhone + Watch apps to physical devices
deploy: build-ios
@echo "π± Installing iPhone + Watch apps on device..."
xcrun devicectl device install app --device {{ env("DEVICECTL_ID", "") }} "{{ ios_app }}"
@echo ""
@echo "β
Deployed! Watch app will sync to your paired Apple Watch automatically."
@echo " If it doesn't appear, open the Watch app on iPhone β My Watch β Installed Apps"
# Uninstall iOS app from device (removes app + all data)
uninstall-ios:
@if [ -z "${DEVICECTL_ID:-}" ]; then \
echo "Error: DEVICECTL_ID not set. Run 'xcrun devicectl list devices' to find your device UUID"; \
exit 1; \
fi
@echo "ποΈ Uninstalling Deck from device..."
xcrun devicectl device uninstall app --device {{ env("DEVICECTL_ID", "") }} com.dou.clicker-ios || echo "App not installed or already removed"
@echo "β
App and all data removed"
# Complete clean reinstall: uninstall, clean build, install fresh
clean-install-ios: uninstall-ios clean build-ios install-ios
@echo "β
Fresh installation complete"
# Build and run iOS app in screenshot mode (no trial banner)
screenshot:
xcodebuild -scheme DeckiOS \
-destination 'platform=iOS Simulator,name=iPhone 16,OS=18.2' \
SWIFT_ACTIVE_COMPILATION_CONDITIONS='DEBUG SCREENSHOT_MODE' \
build
xcrun simctl boot "iPhone 16" 2>/dev/null || true
xcrun simctl install booted "{{ ios_sim_app }}"
xcrun simctl launch booted com.dou.clicker-ios
# Clean build artifacts
clean:
xcodebuild -scheme DeckMac clean
xcodebuild -scheme DeckiOS clean
@echo "π§Ή Cleaning up mounted volumes and installed app (requires sudo)..."
-sudo umount -f /Volumes/Deck 2>/dev/null || true
-sudo rm -rf /Volumes/Deck 2>/dev/null || true
-sudo rm -rf /Applications/Deck.app 2>/dev/null || true
@echo "β
Clean complete"
# List connected iOS devices
list-devices:
xcrun xctrace list devices
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# iOS App Store Distribution
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# Archive iOS app for App Store
archive-ios:
xcodebuild archive \
-scheme DeckiOS \
-archivePath ./build/Deck.xcarchive \
-destination 'generic/platform=iOS'
# Export and upload iOS app to App Store Connect
upload-ios:
xcodebuild -exportArchive \
-archivePath ./build/Deck.xcarchive \
-exportPath ./build/export \
-exportOptionsPlist ExportOptions.plist
# Archive and upload iOS app to App Store Connect
release-ios: archive-ios upload-ios
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# Mac GitHub Distribution (Signed + Notarized DMG)
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# Build Mac app in Release configuration
build-mac-release:
xcodebuild -scheme DeckMac -configuration Release build
# Verify app is signed correctly for distribution
verify-signing: build-mac-release
@echo "π Verifying code signature..."
codesign --verify --deep --strict --verbose=2 "{{ release_mac_app }}"
@echo ""
@echo "π Checking signing identity..."
codesign -dvv "{{ release_mac_app }}" 2>&1 | grep "Authority"
@echo ""
@echo "π Checking Hardened Runtime..."
codesign -dvv "{{ release_mac_app }}" 2>&1 | grep -E "(flags|runtime)"
@echo ""
@echo "β
Signature verification complete"
# Create DMG for Mac app distribution (unsigned)
dmg: build-mac-release
#!/usr/bin/env bash
set -euo pipefail
mkdir -p ./build
rm -f "./build/{{ dmg_name }}-{{ version }}.dmg"
echo "Creating DMG with custom icon..."
# Create iconset from PNG
rm -rf ./build/dmg-icon.iconset
mkdir -p ./build/dmg-icon.iconset
sips -s format png -z 16 16 "{{ mac_logo }}" --out ./build/dmg-icon.iconset/icon_16x16.png
sips -s format png -z 32 32 "{{ mac_logo }}" --out ./build/dmg-icon.iconset/[email protected]
sips -s format png -z 32 32 "{{ mac_logo }}" --out ./build/dmg-icon.iconset/icon_32x32.png
sips -s format png -z 64 64 "{{ mac_logo }}" --out ./build/dmg-icon.iconset/[email protected]
sips -s format png -z 128 128 "{{ mac_logo }}" --out ./build/dmg-icon.iconset/icon_128x128.png
sips -s format png -z 256 256 "{{ mac_logo }}" --out ./build/dmg-icon.iconset/[email protected]
sips -s format png -z 256 256 "{{ mac_logo }}" --out ./build/dmg-icon.iconset/icon_256x256.png
sips -s format png -z 512 512 "{{ mac_logo }}" --out ./build/dmg-icon.iconset/[email protected]
sips -s format png -z 512 512 "{{ mac_logo }}" --out ./build/dmg-icon.iconset/icon_512x512.png
sips -s format png -z 1024 1024 "{{ mac_logo }}" --out ./build/dmg-icon.iconset/[email protected]
iconutil -c icns ./build/dmg-icon.iconset -o ./build/dmg-icon.icns
# Create temp folder for DMG contents
rm -rf ./build/dmg-temp
mkdir -p ./build/dmg-temp
cp -R "{{ release_mac_app }}" ./build/dmg-temp/
ln -s /Applications ./build/dmg-temp/Applications
# Create read-write DMG first
hdiutil create -volname "{{ dmg_name }}" \
-srcfolder ./build/dmg-temp \
-ov -format UDRW \
"./build/{{ dmg_name }}-rw.dmg"
# Mount, set volume icon, unmount
hdiutil attach "./build/{{ dmg_name }}-rw.dmg" -mountpoint "/Volumes/{{ dmg_name }}"
cp ./build/dmg-icon.icns "/Volumes/{{ dmg_name }}/.VolumeIcon.icns"
SetFile -a C "/Volumes/{{ dmg_name }}"
hdiutil detach "/Volumes/{{ dmg_name }}"
sleep 2
# Convert to compressed read-only DMG
hdiutil convert "./build/{{ dmg_name }}-rw.dmg" \
-format UDZO \
-o "./build/{{ dmg_name }}-{{ version }}.dmg"
# Cleanup
rm -rf ./build/dmg-temp ./build/dmg-icon.iconset ./build/dmg-icon.icns "./build/{{ dmg_name }}-rw.dmg"
echo "β
DMG created: ./build/{{ dmg_name }}-{{ version }}.dmg"
# Sign the DMG with Developer ID
sign-dmg: dmg
@echo "π Signing DMG..."
codesign --force --sign "{{ signing_identity }}" "./build/{{ dmg_name }}-{{ version }}.dmg"
@echo "β
DMG signed"
# Submit DMG for notarization and wait for result
notarize: sign-dmg
@echo "π€ Submitting for notarization..."
xcrun notarytool submit "./build/{{ dmg_name }}-{{ version }}.dmg" \
--keychain-profile {{ notary_profile }} \
--wait
@echo ""
@echo "π Stapling notarization ticket..."
xcrun stapler staple "./build/{{ dmg_name }}-{{ version }}.dmg"
@echo ""
@echo "β
Notarization complete!"
# Verify the DMG is properly notarized
verify-notarization:
@echo "π Verifying notarization..."
spctl --assess --type open --context context:primary-signature --verbose=2 "./build/{{ dmg_name }}-{{ version }}.dmg"
@echo ""
xcrun stapler validate "./build/{{ dmg_name }}-{{ version }}.dmg"
@echo "β
Notarization verified"
# Build, sign, notarize DMG and show GitHub release instructions
release-mac: notarize
@echo ""
@echo "π¦ Mac DMG ready for GitHub release!"
@echo " File: ./build/{{ dmg_name }}-{{ version }}.dmg"
@echo ""
@echo "To create a GitHub release:"
@echo " gh release create v{{ version }} ./build/{{ dmg_name }}-{{ version }}.dmg \\"
@echo " --title 'Deck v{{ version }}' \\"
@echo " --notes 'Release notes here'"
@echo ""
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# Notarization Setup
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# Check if Developer ID certificate is installed
check-signing:
@echo "π Looking for Developer ID Application certificate..."
@security find-identity -v -p codesigning | grep "Developer ID Application" || \
(echo "β No Developer ID Application certificate found!" && \
echo "" && \
echo "To create one:" && \
echo " 1. Go to https://developer.apple.com/account/resources/certificates/list" && \
echo " 2. Click + and select 'Developer ID Application'" && \
echo " 3. Follow the prompts to create and download" && \
echo " 4. Double-click the .cer file to install" && \
exit 1)
@echo "β
Developer ID certificate found"
# Store notarization credentials in keychain (interactive)
setup-notary:
@echo "π Setting up notarization credentials..."
@echo "You'll need:"
@echo " - Your Apple ID email"
@echo " - Team ID: HD35YQ72U4"
@echo " - An app-specific password from https://appleid.apple.com"
@echo ""
xcrun notarytool store-credentials {{ notary_profile }} --team-id HD35YQ72U4
# Show the log from the last notarization submission
notary-log:
@echo "π Recent notarization submissions:"
xcrun notarytool history --keychain-profile {{ notary_profile }}
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# Homebrew Tap
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# Refresh the deck cask in the shared douinc tap (github.com/douinc/homebrew-tap) after a release.
# The deck cask lives alongside clicker-remote-receiver in that repo; this triggers the tap's
# update-deck-cask workflow, which downloads the published DMG and recomputes the sha256.
# Requires the GitHub release (Deck-{{ version }}.dmg) to exist first, and `gh` auth with
# workflow access to douinc/homebrew-tap.
update-tap:
#!/usr/bin/env bash
set -euo pipefail
echo "πΊ Dispatching homebrew-tap to update deck β v{{ version }} ..."
gh workflow run update-deck-cask.yml -R douinc/homebrew-tap -f version="{{ version }}"
echo "β
Dispatched. Watch the run: gh run watch -R douinc/homebrew-tap"
echo " Install: brew tap douinc/tap && brew install --cask deck"