Repository navigation
Expand file tree
/
Copy pathjustfile
More file actions
161 lines (147 loc) · 7.29 KB
/
Copy pathjustfile
File metadata and controls
161 lines (147 loc) · 7.29 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
# Transcripts — pocket recorder for iOS.
# `brew install xcodegen just` to bootstrap.
export DEVELOPER_DIR := "/Applications/Xcode.app/Contents/Developer"
default: build
# The xcodeproj is not checked in — project.yml is the source of truth.
# Regenerate Transcripts.xcodeproj from project.yml.
project:
xcodegen generate
# Build for the iOS Simulator (no signing required).
build: project
xcodebuild build -project Transcripts.xcodeproj -scheme Transcripts \
-destination 'generic/platform=iOS Simulator' \
CODE_SIGNING_ALLOWED=NO | tail -5
# Re-shoot every image in the user guide from generated data, and rebuild the
# site that carries them. Needs no Screen Recording permission, no awake
# display and no live meeting — see scripts/guide-shots.py.
shots:
# Staged, not installed: the shots are taken with the build in .build/, so a
# re-shoot never replaces or restarts the copy of the app you are running.
NO_LAUNCH=1 NO_INSTALL=1 scripts/make-app.sh
python3 scripts/guide-shots.py
# App Store screenshots, iPhone 6.9" and iPad 13", from invented data, into
# dist/appstore/. Throwaway simulators, so the ones you use are left alone.
store-shots:
scripts/store-shots.sh
# Mac App Store screenshots, composed from the guide's images (so from the same
# invented data), into dist/appstore/mac/. Needs Pillow.
mac-store-shots:
python3 scripts/mac-store-shots.py
# Compose matched Mac/iPhone/iPad cards from the native captures above.
# Requires Playwright and Microsoft Edge; writes dist/store-artwork/.
store-artwork:
node scripts/store-artwork/render.cjs
# Replace the App Store screenshots on the version being prepared, from
# dist/appstore/. Refuses a version that is waiting for review or live.
# `just store-upload --dry-run` says what it would do.
store-upload *ARGS:
set -a; . ./.env.signing; set +a; python3 scripts/asc-screenshots.py {{ARGS}}
# Every screenshot the project publishes: the guide, then the App Store.
screenshots: shots store-shots
# Carries any live recording forward. Safe mid-session: the running app stops
# gracefully and the new one resumes the same meeting.
#
# Rebuild, reinstall to ~/Applications and relaunch.
reload:
scripts/make-app.sh
# Open in Xcode (for device runs, signing, archives).
open: project
open Transcripts.xcodeproj
# Build and install straight onto the paired iPhone and iPad, without waiting
# for TestFlight to process a build. Development signing, from the App Store
# Connect key in .env.signing — which is fine here and is exactly why this is
# not the path a store build takes; see `testflight`.
#
# Devices are addressed by devicectl identifier, never by name: this iPad is
# named with a Unicode right quote that does not survive shell round-tripping,
# and matching by name fails with CoreDeviceError 1000. `just devices` prints
# the identifiers.
#
# Build and install onto every paired iPhone and iPad.
devices: project
#!/usr/bin/env bash
set -euo pipefail
set -a; source .env.signing; set +a
KEY="$(eval echo "$ASC_KEY_PATH")"
echo "▶ Building for device"
xcodebuild -project Transcripts.xcodeproj -scheme Transcripts -configuration Debug \
-destination 'generic/platform=iOS' -derivedDataPath .build/ios-device \
DEVELOPMENT_TEAM=6Q9BX97LMS \
-allowProvisioningUpdates \
-authenticationKeyPath "$KEY" \
-authenticationKeyID "$ASC_KEY_ID" \
-authenticationKeyIssuerID "$ASC_ISSUER_ID" \
build > .build/ios-device.log 2>&1 \
|| { echo "✗ build failed — tail of .build/ios-device.log:" >&2; tail -25 .build/ios-device.log >&2; exit 1; }
APP=.build/ios-device/Build/Products/Debug-iphoneos/Transcripts.app
# Every paired physical device, so a new one is picked up without editing
# this recipe. A device that is locked or off refuses the install; that is
# reported per device rather than failing the whole run.
xcrun devicectl list devices --json-output .build/devices.json > /dev/null
IDS="$(python3 -c "
import json
d = json.load(open('.build/devices.json'))['result']['devices']
for x in d:
p = x.get('deviceProperties', {})
h = x.get('hardwareProperties', {})
# reality distinguishes a real device from a simulator; platform alone
# does not, and a shutdown simulator answers 'paired'.
if (h.get('platform') == 'iOS' and h.get('reality') == 'physical'
and x.get('connectionProperties', {}).get('pairingState') == 'paired'):
print(x['identifier'], p.get('name', '?').replace(' ', '_'))
")"
[[ -n "$IDS" ]] || { echo "✗ no paired iOS devices — connect or unlock them" >&2; exit 1; }
while read -r ID NAME; do
[[ -n "$ID" ]] || continue
echo "▶ Installing to ${NAME//_/ }"
# Retry: the two ways this fails in practice are both temporary. A locked
# device refuses outright, and the wireless tunnel drops the connection
# ("reset by peer") often enough to be worth riding out rather than
# reporting as a failure the user has to act on.
for attempt in 1 2 3 4 5; do
if xcrun devicectl device install app --device "$ID" "$APP" > .build/install-$ID.log 2>&1; then
echo " ✓ installed"
break
fi
if [[ $attempt -eq 5 ]]; then
echo " ✗ failed after 5 tries — see .build/install-$ID.log" >&2
grep -m1 -o 'NSLocalizedFailureReason = .*' .build/install-$ID.log >&2 || tail -3 .build/install-$ID.log >&2
else
sleep 5
fi
done
done <<< "$IDS"
# Upload a build to TestFlight and App Store Connect, by way of CI.
#
# This used to archive and upload from here, and that produced a build Apple
# rejected while the identical commit built by .github/workflows/testflight.yml
# was accepted. The local path signed with a *development* identity, and it ran
# none of the checks the workflow makes on the exported IPA: the assertion that
# no networking symbol reached the binary — which is the App Store privacy
# answer, verified rather than promised — and the ITMS-90626 Siri-description
# check, which matters because App Store processing applies that one *after*
# upload and then discards the build silently.
#
# Two upload paths meant one of them was always the untested one. So this is a
# thin wrapper now: push, dispatch the workflow, follow it. Slower than a local
# archive, and it produces builds that are accepted.
#
# Build and upload to TestFlight, by way of CI.
testflight:
#!/usr/bin/env bash
set -euo pipefail
if [[ -n "$(git status --porcelain)" ]]; then
echo "✗ Working tree is dirty. CI builds what you push, so commit first." >&2
exit 1
fi
REMOTE="$(git remote -v | awk '/github\.com.*\(push\)/{print $1; exit}')"
[[ -n "$REMOTE" ]] || { echo "✗ no git remote points at github.com" >&2; exit 1; }
echo "▶ Pushing to $REMOTE"
git push -q "$REMOTE" HEAD:main
VERSION="$(grep -o 'MARKETING_VERSION: "[^"]*"' project.yml | tail -1 | sed 's/.*"\(.*\)"/\1/')"
echo "▶ Dispatching the TestFlight workflow for $VERSION"
gh workflow run testflight.yml -f version="$VERSION"
sleep 8
RUN="$(gh run list --workflow=testflight.yml --limit 1 --json databaseId -q '.[0].databaseId')"
echo "▶ https://github.com/doughatcher/transcripts/actions/runs/$RUN"
gh run watch "$RUN" --exit-status