|
1
|
#!/usr/bin/env bash |
|
2
|
# build-visionos.sh |
|
3
|
# |
|
4
|
# Full App Store pipeline for the native SitiVision visionOS app (xcode/): |
|
5
|
# 1. Generate — xcodegen generate (SitiVision.xcodeproj is not checked in) |
|
6
|
# 2. Archive — xcodebuild archive (automatic signing, Splitfire AB team) |
|
7
|
# 3. Export — xcodebuild -exportArchive (method app-store-connect → .ipa) |
|
8
|
# 4. Fixplist — rewrite the beta host-OS stamp (BuildMachineOSBuild) + Mach-O |
|
9
|
# LC_BUILD_VERSION, then re-sign — see do_fixplist for why |
|
10
|
# 5. Upload — xcrun altool (TestFlight / App Store Connect submission) |
|
11
|
# |
|
12
|
# Unlike build-ios.sh (Tauri + cargo-tauri), SitiVision is a plain XcodeGen |
|
13
|
# project, so this pipeline is xcodebuild/xcodegen only — no cargo/pnpm steps. |
|
14
|
# The fixplist step follows the same workaround as the sibling splitfire repo's |
|
15
|
# Scripts/build_karokowe_appstore.sh (native tvOS/visionOS Xcode projects) and |
|
16
|
# this repo's own build-ios.sh (Tauri iOS) — see do_fixplist for the mechanism. |
|
17
|
# |
|
18
|
# Every configurable value has a hardcoded default that can be overridden by |
|
19
|
# setting the corresponding environment variable before invoking the script. |
|
20
|
# |
|
21
|
# Usage: |
|
22
|
# ./scripts/build-visionos.sh # full pipeline |
|
23
|
# ./scripts/build-visionos.sh generate # xcodegen generate only |
|
24
|
# ./scripts/build-visionos.sh archive # archive only (assumes project generated) |
|
25
|
# ./scripts/build-visionos.sh export # export .ipa only (assumes archive already built) |
|
26
|
# ./scripts/build-visionos.sh fixplist # patch beta host-OS stamp + re-sign |
|
27
|
# ./scripts/build-visionos.sh upload # upload only (assumes .ipa already exported) |
|
28
|
# |
|
29
|
# make asvision # full pipeline via Makefile |
|
30
|
# make asvision-generate |
|
31
|
# make asvision-archive |
|
32
|
# make asvision-export |
|
33
|
# make asvision-fixplist |
|
34
|
# make asvision-upload |
|
35
|
|
|
36
|
set -euo pipefail |
|
37
|
|
|
38
|
# ── Paths ───────────────────────────────────────────────────────────────────── |
|
39
|
|
|
40
|
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" |
|
41
|
XCODE_DIR="${REPO_ROOT}/xcode" |
|
42
|
PROJECT="${XCODE_DIR}/SitiVision.xcodeproj" |
|
43
|
BUILD_DIR="${XCODE_DIR}/build" |
|
44
|
ARCHIVE_PATH="${BUILD_DIR}/SitiVision.xcarchive" |
|
45
|
EXPORT_DIR="${BUILD_DIR}/export" |
|
46
|
EXPORT_OPTIONS="${BUILD_DIR}/ExportOptions.plist" |
|
47
|
IPA_PATH="${EXPORT_DIR}/SitiVision.ipa" |
|
48
|
|
|
49
|
# Auto-load the untracked .env.appstore (signing identities + ASC creds) so |
|
50
|
# `make asvision` works without the caller exporting them first. See |
|
51
|
# .env.appstore.example for the expected keys. |
|
52
|
if [[ -f "${REPO_ROOT}/.env.appstore" ]]; then |
|
53
|
set -a |
|
54
|
# shellcheck disable=SC1091 |
|
55
|
. "${REPO_ROOT}/.env.appstore" |
|
56
|
set +a |
|
57
|
fi |
|
58
|
|
|
59
|
# Xcode toolchain. App Store Connect rejects binaries built with a beta Xcode or |
|
60
|
# beta SDK, so pin a release Xcode here regardless of the global `xcode-select`. |
|
61
|
# Override: DEVELOPER_DIR=/Applications/Xcode.app/Contents/Developer ./scripts/build-visionos.sh |
|
62
|
pick_release_xcode() { |
|
63
|
local active; active="$(xcode-select -p 2>/dev/null)" |
|
64
|
if [[ -n "$active" && "$active" != *[Bb]eta* ]]; then |
|
65
|
printf '%s\n' "$active"; return |
|
66
|
fi |
|
67
|
local app |
|
68
|
for app in /Applications/Xcode*.app; do |
|
69
|
case "$app" in *[Bb]eta*) continue ;; esac |
|
70
|
[[ -d "$app/Contents/Developer" ]] && { printf '%s\n' "$app/Contents/Developer"; return; } |
|
71
|
done |
|
72
|
} |
|
73
|
DEVELOPER_DIR="${DEVELOPER_DIR:-$(pick_release_xcode)}" |
|
74
|
export DEVELOPER_DIR |
|
75
|
|
|
76
|
# ── Overridable config ───────────────────────────────────────────────────────── |
|
77
|
|
|
78
|
# Apple Developer team (Splitfire AB — same team as the Tauri app's iOS/macOS |
|
79
|
# builds; see project.yml's DEVELOPMENT_TEAM and ../src-tauri/tauri.conf.json). |
|
80
|
TEAM_ID="${TEAM_ID:-2TQF86ZACD}" |
|
81
|
|
|
82
|
# App Store Connect API credentials. Used both for -allowProvisioningUpdates |
|
83
|
# (so archive/export can fetch/create the distribution profile headlessly, |
|
84
|
# without a signed-in Xcode account) and for the altool upload. |
|
85
|
# ASC_API_KEY — Key ID shown in App Store Connect → Users & Access → Keys. |
|
86
|
# ASC_ISSUER_ID — Issuer UUID on the same page. |
|
87
|
# The private key (.p8) must be present at: |
|
88
|
# ~/.appstoreconnect/private_keys/AuthKey_<ASC_API_KEY>.p8 (Xcode's default), or |
|
89
|
# ~/private_keys/AuthKey_<ASC_API_KEY>.p8 (ASC_PRIVATE_KEY_PATH override) |
|
90
|
ASC_API_KEY="${ASC_API_KEY:-L84N624YQH}" |
|
91
|
ASC_ISSUER_ID="${ASC_ISSUER_ID:-b4e8d369-8b7d-4538-8435-643b73237575}" |
|
92
|
ASC_PRIVATE_KEY_PATH="${ASC_PRIVATE_KEY_PATH:-${HOME}/private_keys/AuthKey_${ASC_API_KEY}.p8}" |
|
93
|
|
|
94
|
# Re-sign identity used by the fixplist step (distribution cert for the same |
|
95
|
# team as TEAM_ID). Not secret — safe to commit once known. |
|
96
|
SIGN_IDENTITY="${SIGN_IDENTITY:-Apple Distribution: Splitfire AB (2TQF86ZACD)}" |
|
97
|
|
|
98
|
# A shipping macOS build number to stamp for BuildMachineOSBuild during |
|
99
|
# fixplist (matches the constant already used by build-ios.sh and the |
|
100
|
# sibling splitfire repo's Scripts/build_karokowe_appstore.sh). |
|
101
|
RELEASE_OS_BUILD="${RELEASE_OS_BUILD:-25A354}" |
|
102
|
|
|
103
|
# ── Helpers ─────────────────────────────────────────────────────────────────── |
|
104
|
|
|
105
|
step() { echo ""; echo "── $* ──────────────────────────────────────────────"; } |
|
106
|
ok() { echo "✅ $*"; } |
|
107
|
fail() { printf "❌ %b\n" "$*" >&2; exit 1; } |
|
108
|
|
|
109
|
# True when an Apple build number is a pre-release (beta) seed. Apple stamps |
|
110
|
# seeds with a trailing lowercase letter (e.g. 26A5353q); shipping builds |
|
111
|
# never carry one (e.g. 25A354). |
|
112
|
is_beta_build() { |
|
113
|
[[ "${1:-}" =~ [a-z]$ ]] |
|
114
|
} |
|
115
|
|
|
116
|
require_release_xcode() { |
|
117
|
[[ -n "${DEVELOPER_DIR}" && -d "${DEVELOPER_DIR}" ]] \ |
|
118
|
|| fail "No release Xcode found.\n Install a release Xcode, or set DEVELOPER_DIR to its Contents/Developer dir." |
|
119
|
case "${DEVELOPER_DIR}" in |
|
120
|
*[Bb]eta*) |
|
121
|
fail "DEVELOPER_DIR points at a beta Xcode:\n ${DEVELOPER_DIR}\n App Store Connect rejects beta-built binaries. Point DEVELOPER_DIR at a release Xcode." ;; |
|
122
|
esac |
|
123
|
echo "🛠 Xcode → $(xcodebuild -version 2>/dev/null | head -1) [${DEVELOPER_DIR}]" |
|
124
|
} |
|
125
|
|
|
126
|
require_asc_key() { |
|
127
|
[[ -f "${ASC_PRIVATE_KEY_PATH}" ]] \ |
|
128
|
|| fail "ASC API private key not found: ${ASC_PRIVATE_KEY_PATH}\n Download it from App Store Connect → Users & Access → Integrations → App Store Connect API (once — Apple won't let you re-download), or set ASC_PRIVATE_KEY_PATH." |
|
129
|
} |
|
130
|
|
|
131
|
# Release toolchain stamps used by fixplist to rewrite a bundle's Info.plist |
|
132
|
# so App Store Connect reads it as a release-Xcode/SDK build. Derived from the |
|
133
|
# pinned release Xcode (DEVELOPER_DIR); every value is overridable via env. |
|
134
|
derive_release_toolchain() { |
|
135
|
local xv sv xver xbuild sdkver sdkbuild d |
|
136
|
xv="$(xcodebuild -version 2>/dev/null)" |
|
137
|
sv="$(xcodebuild -version -sdk xros 2>/dev/null)" |
|
138
|
xver="$(printf '%s\n' "$xv" | awk '/^Xcode/{print $2}')" |
|
139
|
xbuild="$(printf '%s\n' "$xv" | awk '/Build version/{print $3}')" |
|
140
|
sdkver="$(printf '%s\n' "$sv" | awk -F': ' '/SDKVersion/{print $2; exit}')" |
|
141
|
sdkbuild="$(printf '%s\n' "$sv" | awk -F': ' '/ProductBuildVersion/{print $2; exit}')" |
|
142
|
d="${xver//./}"; while [[ -n "$d" && ${#d} -lt 4 ]]; do d="${d}0"; done |
|
143
|
REL_DTXCODE="${RELEASE_DTXCODE:-$d}" |
|
144
|
REL_XCODE_BUILD="${RELEASE_XCODE_BUILD:-$xbuild}" |
|
145
|
REL_SDK_VERSION="${RELEASE_SDK_VERSION:-$sdkver}" |
|
146
|
REL_SDK_NAME="${RELEASE_SDK_NAME:-xros${sdkver}}" |
|
147
|
REL_SDK_BUILD="${RELEASE_SDK_BUILD:-$sdkbuild}" |
|
148
|
[[ -n "$REL_DTXCODE" && -n "$REL_XCODE_BUILD" && -n "$REL_SDK_BUILD" ]] \ |
|
149
|
|| fail "Could not determine release toolchain stamps from ${DEVELOPER_DIR}." |
|
150
|
} |
|
151
|
|
|
152
|
patch_bundle_plist() { |
|
153
|
local plist="$1" |
|
154
|
pl() { /usr/libexec/PlistBuddy -c "Print :$1" "$plist" 2>/dev/null || echo ""; } |
|
155
|
setpl() { /usr/libexec/PlistBuddy -c "Set :$1 $2" "$plist" 2>/dev/null \ |
|
156
|
|| /usr/libexec/PlistBuddy -c "Add :$1 string $2" "$plist"; } |
|
157
|
setpl BuildMachineOSBuild "${RELEASE_OS_BUILD}" |
|
158
|
setpl DTXcode "${REL_DTXCODE}"; setpl DTXcodeBuild "${REL_XCODE_BUILD}" |
|
159
|
setpl DTSDKName "${REL_SDK_NAME}"; setpl DTSDKBuild "${REL_SDK_BUILD}" |
|
160
|
setpl DTPlatformBuild "${REL_SDK_BUILD}"; setpl DTPlatformVersion "${REL_SDK_VERSION}" |
|
161
|
} |
|
162
|
|
|
163
|
# ── Steps ───────────────────────────────────────────────────────────────────── |
|
164
|
|
|
165
|
do_generate() { |
|
166
|
step "Generate" |
|
167
|
|
|
168
|
command -v xcodegen >/dev/null || fail "xcodegen not found.\n Install with: brew install xcodegen" |
|
169
|
|
|
170
|
( cd "${XCODE_DIR}" && xcodegen generate ) |
|
171
|
[[ -d "$PROJECT" ]] || fail "xcodegen ran but ${PROJECT} was not created" |
|
172
|
ok "Xcode project generated → ${PROJECT}" |
|
173
|
} |
|
174
|
|
|
175
|
do_archive() { |
|
176
|
step "Archive" |
|
177
|
|
|
178
|
require_release_xcode |
|
179
|
require_asc_key |
|
180
|
[[ -d "$PROJECT" ]] || fail "Xcode project not found at ${PROJECT}\n Run 'generate' step first." |
|
181
|
|
|
182
|
# CURRENT_PROJECT_VERSION: unix-seconds build number. |
|
183
|
# Strictly increasing across builds → satisfies App Store Connect's |
|
184
|
# "each build must have a higher build number than the last" requirement. |
|
185
|
BUILD_NUMBER="$(date -u +"%s")" |
|
186
|
|
|
187
|
mkdir -p "${BUILD_DIR}" |
|
188
|
rm -rf "${ARCHIVE_PATH}" |
|
189
|
|
|
190
|
echo "🏷 buildNumber → ${BUILD_NUMBER}" |
|
191
|
echo "👥 team → ${TEAM_ID}" |
|
192
|
echo "" |
|
193
|
|
|
194
|
xcodebuild archive \ |
|
195
|
-project "${PROJECT}" \ |
|
196
|
-scheme SitiVision \ |
|
197
|
-destination "generic/platform=visionOS" \ |
|
198
|
-archivePath "${ARCHIVE_PATH}" \ |
|
199
|
-configuration Release \ |
|
200
|
-allowProvisioningUpdates \ |
|
201
|
-authenticationKeyPath "${ASC_PRIVATE_KEY_PATH}" \ |
|
202
|
-authenticationKeyID "${ASC_API_KEY}" \ |
|
203
|
-authenticationKeyIssuerID "${ASC_ISSUER_ID}" \ |
|
204
|
CURRENT_PROJECT_VERSION="${BUILD_NUMBER}" \ |
|
205
|
DEVELOPMENT_TEAM="${TEAM_ID}" |
|
206
|
|
|
207
|
[[ -d "$ARCHIVE_PATH" ]] || fail "Archive succeeded but not found at: $ARCHIVE_PATH" |
|
208
|
ok "Archive complete → ${ARCHIVE_PATH}" |
|
209
|
} |
|
210
|
|
|
211
|
do_export() { |
|
212
|
step "Export" |
|
213
|
|
|
214
|
require_release_xcode |
|
215
|
require_asc_key |
|
216
|
[[ -d "$ARCHIVE_PATH" ]] || fail "Archive not found: ${ARCHIVE_PATH}\n Run 'archive' step first." |
|
217
|
|
|
218
|
# exportOptionsPlist: automatic signing re-resolves to a *distribution* |
|
219
|
# certificate + App Store profile for method app-store-connect, even though |
|
220
|
# the archive step itself signs with a development identity (expected — |
|
221
|
# Xcode always archives with the dev cert; export re-signs per method). |
|
222
|
cat > "${EXPORT_OPTIONS}" <<PLIST |
|
223
|
<?xml version="1.0" encoding="UTF-8"?> |
|
224
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> |
|
225
|
<plist version="1.0"> |
|
226
|
<dict> |
|
227
|
<key>method</key> |
|
228
|
<string>app-store-connect</string> |
|
229
|
<key>teamID</key> |
|
230
|
<string>${TEAM_ID}</string> |
|
231
|
<key>signingStyle</key> |
|
232
|
<string>automatic</string> |
|
233
|
<key>destination</key> |
|
234
|
<string>export</string> |
|
235
|
</dict> |
|
236
|
</plist> |
|
237
|
PLIST |
|
238
|
|
|
239
|
rm -rf "${EXPORT_DIR}" |
|
240
|
|
|
241
|
xcodebuild -exportArchive \ |
|
242
|
-archivePath "${ARCHIVE_PATH}" \ |
|
243
|
-exportPath "${EXPORT_DIR}" \ |
|
244
|
-exportOptionsPlist "${EXPORT_OPTIONS}" \ |
|
245
|
-allowProvisioningUpdates \ |
|
246
|
-authenticationKeyPath "${ASC_PRIVATE_KEY_PATH}" \ |
|
247
|
-authenticationKeyID "${ASC_API_KEY}" \ |
|
248
|
-authenticationKeyIssuerID "${ASC_ISSUER_ID}" |
|
249
|
|
|
250
|
[[ -f "$IPA_PATH" ]] || fail "Export succeeded but .ipa not found at: $IPA_PATH" |
|
251
|
ok "Export complete → ${IPA_PATH}" |
|
252
|
} |
|
253
|
|
|
254
|
# Why this is needed even with a release Xcode pinned via DEVELOPER_DIR: |
|
255
|
# xcodebuild fully respects DEVELOPER_DIR, so DTXcode/DTXcodeBuild/DTSDKBuild |
|
256
|
# and every Mach-O's embedded LC_BUILD_VERSION sdk field all come out |
|
257
|
# clean/release automatically. But `BuildMachineOSBuild` reflects the literal |
|
258
|
# host macOS build — this Mac itself, regardless of which Xcode.app performs |
|
259
|
# the build. When the host OS is a beta seed (a build number with a trailing |
|
260
|
# lowercase letter, e.g. `26A5353q`), that field alone is enough to make App |
|
261
|
# Store Connect's REVIEW-SUBMISSION validation — a separate, stricter pass |
|
262
|
# than TestFlight build processing — reject the binary as "Invalid Binary" |
|
263
|
# with no further detail, even though TestFlight processing itself reports |
|
264
|
# the build as valid. Mirrors build-ios.sh's fixplist step and the sibling |
|
265
|
# splitfire repo's Scripts/build_karokowe_appstore.sh, adapted for a plain |
|
266
|
# xcodebuild archive/export instead of `cargo tauri ios build`. |
|
267
|
do_fixplist() { |
|
268
|
step "Fixplist (beta host OS → release)" |
|
269
|
|
|
270
|
[[ -f "$IPA_PATH" ]] || fail ".ipa not found: ${IPA_PATH}\n Run 'export' step first." |
|
271
|
command -v /usr/libexec/PlistBuddy >/dev/null || fail "PlistBuddy not found" |
|
272
|
|
|
273
|
local work app_dir os_cur |
|
274
|
work="$(mktemp -d)" |
|
275
|
trap 'rm -rf "$work"' RETURN |
|
276
|
|
|
277
|
unzip -q "$IPA_PATH" -d "$work" |
|
278
|
app_dir="$(find "$work/Payload" -maxdepth 1 -name '*.app' -print -quit)" |
|
279
|
[[ -n "$app_dir" && -d "$app_dir" ]] || fail "No .app found inside ${IPA_PATH}" |
|
280
|
|
|
281
|
os_cur="$(/usr/libexec/PlistBuddy -c 'Print :BuildMachineOSBuild' "$app_dir/Info.plist" 2>/dev/null || echo "")" |
|
282
|
if [[ -z "${FORCE_FIXPLIST:-}" ]] && ! is_beta_build "$os_cur"; then |
|
283
|
ok "BuildMachineOSBuild (${os_cur}) is already a release seed — no patch needed." |
|
284
|
return 0 |
|
285
|
fi |
|
286
|
|
|
287
|
require_release_xcode |
|
288
|
derive_release_toolchain |
|
289
|
echo "🛠 xcode → ${REL_DTXCODE}/${REL_XCODE_BUILD} sdk → ${REL_SDK_NAME}/${REL_SDK_BUILD}" |
|
290
|
echo "🔁 BuildMachineOSBuild ${os_cur:-<unset>} → ${RELEASE_OS_BUILD}" |
|
291
|
echo "🔏 identity → ${SIGN_IDENTITY}" |
|
292
|
echo "" |
|
293
|
|
|
294
|
# Patch every bundle's Info.plist: the main .app and any nested extensions. |
|
295
|
local bundles=("$app_dir") |
|
296
|
while IFS= read -r appex; do bundles+=("$appex"); done \ |
|
297
|
< <(find "$app_dir" -name '*.appex' -type d) |
|
298
|
local bundle |
|
299
|
for bundle in "${bundles[@]}"; do |
|
300
|
[[ -f "$bundle/Info.plist" ]] && patch_bundle_plist "$bundle/Info.plist" |
|
301
|
done |
|
302
|
|
|
303
|
# Critical: App Store Connect's review-stage validation reads the `sdk` |
|
304
|
# baked into each executable's Mach-O LC_BUILD_VERSION, not just the |
|
305
|
# Info.plist DTSDK* keys. Patch every Mach-O in the bundle; minos is |
|
306
|
# preserved, only the sdk is (re)set to the release SDK. |
|
307
|
local macho minos_cur |
|
308
|
while IFS= read -r macho; do |
|
309
|
[[ -z "$macho" ]] && continue |
|
310
|
minos_cur="$(xcrun vtool -show-build "$macho" 2>/dev/null | awk '/minos/{print $2; exit}')" |
|
311
|
minos_cur="${minos_cur:-2.0}" |
|
312
|
echo "🔁 Mach-O sdk → ${REL_SDK_VERSION} (minos ${minos_cur}): ${macho#"$app_dir"/}" |
|
313
|
xcrun vtool -set-build-version visionos "${minos_cur}" "${REL_SDK_VERSION}" \ |
|
314
|
-replace -output "${macho}.vt" "$macho" >/dev/null 2>&1 \ |
|
315
|
&& mv "${macho}.vt" "$macho" \ |
|
316
|
|| fail "vtool failed to patch LC_BUILD_VERSION on ${macho#"$app_dir"/}" |
|
317
|
done < <(find "$app_dir" -type f -exec sh -c 'file "$1" | grep -q Mach-O' _ {} \; -print) |
|
318
|
|
|
319
|
# Re-sign nested code deepest-first, then the .app last — editing the |
|
320
|
# plist/Mach-O invalidates the original signature. |
|
321
|
local nested ent |
|
322
|
while IFS= read -r nested; do |
|
323
|
[[ -z "$nested" ]] && continue |
|
324
|
ent="${work}/$(basename "$nested" | tr '/' '_').entitlements" |
|
325
|
echo "✍️ sign nested → ${nested#"$app_dir"/}" |
|
326
|
if codesign -d --entitlements :"$ent" "$nested" 2>/dev/null && [[ -s "$ent" ]]; then |
|
327
|
codesign --force --timestamp=none --sign "$SIGN_IDENTITY" --entitlements "$ent" "$nested" |
|
328
|
else |
|
329
|
codesign --force --timestamp=none --sign "$SIGN_IDENTITY" "$nested" |
|
330
|
fi |
|
331
|
done < <(find "$app_dir" \( -name '*.framework' -o -name '*.dylib' -o -name '*.appex' \) -print \ |
|
332
|
| awk '{ print length, $0 }' | sort -rn | cut -d' ' -f2-) |
|
333
|
|
|
334
|
ent="${work}/app.entitlements" |
|
335
|
if codesign -d --entitlements :"$ent" "$app_dir" 2>/dev/null && [[ -s "$ent" ]]; then |
|
336
|
codesign --force --timestamp=none --sign "$SIGN_IDENTITY" --entitlements "$ent" "$app_dir" |
|
337
|
else |
|
338
|
codesign --force --timestamp=none --sign "$SIGN_IDENTITY" "$app_dir" |
|
339
|
fi |
|
340
|
codesign --verify --deep --strict "$app_dir" || fail "Re-signed app failed signature verification" |
|
341
|
echo "🔁 now: DTXcodeBuild=$(/usr/libexec/PlistBuddy -c 'Print :DTXcodeBuild' "$app_dir/Info.plist" 2>/dev/null) BuildMachineOSBuild=$(/usr/libexec/PlistBuddy -c 'Print :BuildMachineOSBuild' "$app_dir/Info.plist" 2>/dev/null)" |
|
342
|
|
|
343
|
rm -f "$IPA_PATH" |
|
344
|
( cd "$work" && zip -qry "$IPA_PATH" Payload ) |
|
345
|
[[ -f "$IPA_PATH" ]] || fail "Repack failed: $IPA_PATH" |
|
346
|
|
|
347
|
ok "Patched + re-signed → ${IPA_PATH}" |
|
348
|
} |
|
349
|
|
|
350
|
do_upload() { |
|
351
|
step "Upload" |
|
352
|
|
|
353
|
[[ -f "$IPA_PATH" ]] || fail ".ipa not found: ${IPA_PATH}\n Run 'export' step first." |
|
354
|
|
|
355
|
echo "📤 file → ${IPA_PATH}" |
|
356
|
echo "🔑 apiKey → ${ASC_API_KEY}" |
|
357
|
echo "🏢 issuer → ${ASC_ISSUER_ID}" |
|
358
|
echo "" |
|
359
|
|
|
360
|
xcrun altool \ |
|
361
|
--upload-app \ |
|
362
|
--type visionos \ |
|
363
|
--file "${IPA_PATH}" \ |
|
364
|
--apiKey "${ASC_API_KEY}" \ |
|
365
|
--apiIssuer "${ASC_ISSUER_ID}" |
|
366
|
|
|
367
|
ok "Uploaded to App Store Connect" |
|
368
|
} |
|
369
|
|
|
370
|
# ── Entry point ─────────────────────────────────────────────────────────────── |
|
371
|
|
|
372
|
STEP="${1:-all}" |
|
373
|
|
|
374
|
case "$STEP" in |
|
375
|
generate) do_generate ;; |
|
376
|
archive) do_archive ;; |
|
377
|
export) do_export ;; |
|
378
|
fixplist) do_fixplist ;; |
|
379
|
upload) do_upload ;; |
|
380
|
all) |
|
381
|
do_generate |
|
382
|
do_archive |
|
383
|
do_export |
|
384
|
do_fixplist |
|
385
|
do_upload |
|
386
|
echo "" |
|
387
|
echo "🎉 Pipeline complete: built and uploaded SitiVision to App Store Connect." |
|
388
|
;; |
|
389
|
*) |
|
390
|
echo "Usage: $0 [generate|archive|export|fixplist|upload|all]" >&2 |
|
391
|
exit 1 |
|
392
|
;; |
|
393
|
esac |