|
1
|
#!/usr/bin/env bash |
|
2
|
# build-ios.sh |
|
3
|
# |
|
4
|
# Full App Store pipeline for Siti AI (iOS): |
|
5
|
# 1. Clean — rm -rf src-tauri/gen/apple |
|
6
|
# 2. Init — cargo tauri ios init (local tauri CLI, prod iOS config) |
|
7
|
# 3. Icons — pnpm gim |
|
8
|
# 4. Override — copy gen-override/apple into gen/apple |
|
9
|
# 5. Build — cargo tauri ios build (app-store-connect, datetime CFBundleVersion) |
|
10
|
# 6. Fixplist — rewrite beta toolchain stamps → release values, re-sign |
|
11
|
# 7. Upload — xcrun altool (App Store Connect submission) |
|
12
|
# |
|
13
|
# Every configurable value has a hardcoded default that can be overridden by |
|
14
|
# setting the corresponding environment variable before invoking the script. |
|
15
|
# |
|
16
|
# Usage: |
|
17
|
# ./scripts/build-ios.sh # full pipeline |
|
18
|
# ./scripts/build-ios.sh clean # rm gen/apple only |
|
19
|
# ./scripts/build-ios.sh init # tauri ios init only |
|
20
|
# ./scripts/build-ios.sh icons # pnpm gim only |
|
21
|
# ./scripts/build-ios.sh override # copy gen-override/apple only |
|
22
|
# ./scripts/build-ios.sh build # build only (assumes init already done) |
|
23
|
# ./scripts/build-ios.sh fixplist # patch beta toolchain stamps + re-sign |
|
24
|
# ./scripts/build-ios.sh upload # upload only (assumes .ipa already built) |
|
25
|
# |
|
26
|
# make asios # full pipeline via Makefile |
|
27
|
# make asios-clean |
|
28
|
# make asios-init |
|
29
|
# make asios-icons |
|
30
|
# make asios-override |
|
31
|
# make asios-build |
|
32
|
# make asios-fixplist |
|
33
|
# make asios-upload |
|
34
|
|
|
35
|
set -euo pipefail |
|
36
|
|
|
37
|
# ── Paths ───────────────────────────────────────────────────────────────────── |
|
38
|
|
|
39
|
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" |
|
40
|
IOS_CONFIG="${REPO_ROOT}/src-tauri/tauri.prod-ios.conf.json" |
|
41
|
GEN_APPLE="${REPO_ROOT}/src-tauri/gen/apple" |
|
42
|
GEN_OVERRIDE="${REPO_ROOT}/src-tauri/gen-override/apple" |
|
43
|
|
|
44
|
# Auto-load the untracked .env.appstore (signing identities + ASC creds) so |
|
45
|
# `make asios` works without the caller exporting them first. See |
|
46
|
# .env.appstore.example for the expected keys. |
|
47
|
if [[ -f "${REPO_ROOT}/.env.appstore" ]]; then |
|
48
|
set -a |
|
49
|
# shellcheck disable=SC1091 |
|
50
|
. "${REPO_ROOT}/.env.appstore" |
|
51
|
set +a |
|
52
|
fi |
|
53
|
|
|
54
|
# Xcode toolchain. App Store Connect rejects binaries built with a beta Xcode or |
|
55
|
# beta SDK. `cargo tauri ios build` resolves the toolchain from the system |
|
56
|
# xcode-select (it ignores DEVELOPER_DIR), so the fixplist step below rewrites |
|
57
|
# the beta stamps instead — but a pinned release Xcode is still needed to derive |
|
58
|
# the release values to stamp in. |
|
59
|
# Override: DEVELOPER_DIR=/Applications/Xcode.app/Contents/Developer ./scripts/build-ios.sh |
|
60
|
pick_release_xcode() { |
|
61
|
local active; active="$(xcode-select -p 2>/dev/null)" |
|
62
|
if [[ -n "$active" && "$active" != *[Bb]eta* ]]; then |
|
63
|
printf '%s\n' "$active"; return |
|
64
|
fi |
|
65
|
local app |
|
66
|
for app in /Applications/Xcode*.app; do |
|
67
|
case "$app" in *[Bb]eta*) continue ;; esac |
|
68
|
[[ -d "$app/Contents/Developer" ]] && { printf '%s\n' "$app/Contents/Developer"; return; } |
|
69
|
done |
|
70
|
} |
|
71
|
DEVELOPER_DIR="${DEVELOPER_DIR:-$(pick_release_xcode)}" |
|
72
|
export DEVELOPER_DIR |
|
73
|
|
|
74
|
# ── Overridable config ───────────────────────────────────────────────────────── |
|
75
|
# |
|
76
|
# Path to a local Tauri source checkout. The ios init command is run via |
|
77
|
# `cargo run` against this manifest so the CLI version matches the workspace. |
|
78
|
# Override: TAURI_CLI_MANIFEST=/path/to/tauri/Cargo.toml ./scripts/build-ios.sh |
|
79
|
TAURI_CLI_MANIFEST="${TAURI_CLI_MANIFEST:-${HOME}/Repositories/tauri/Cargo.toml}" |
|
80
|
|
|
81
|
# App Store Connect API credentials. |
|
82
|
# ASC_API_KEY — Key ID shown in App Store Connect → Users & Access → Keys. |
|
83
|
# ASC_ISSUER_ID — Issuer UUID on the same page. |
|
84
|
# The private key (.p8) must be present at: |
|
85
|
# ~/.appstoreconnect/private_keys/AuthKey_<ASC_API_KEY>.p8 |
|
86
|
ASC_API_KEY="${ASC_API_KEY:-L84N624YQH}" |
|
87
|
ASC_ISSUER_ID="${ASC_ISSUER_ID:-b4e8d369-8b7d-4538-8435-643b73237575}" |
|
88
|
|
|
89
|
# Beta-macOS submission workaround (non-sensitive; safe default). |
|
90
|
# |
|
91
|
# App Store Connect rejects binaries whose Info.plist reports a *beta* |
|
92
|
# BuildMachineOSBuild (e.g. macOS 27.0 build 26A5353q → "Invalid Binary"). |
|
93
|
# The fixplist step rewrites that key to a shipping macOS build and re-signs |
|
94
|
# the .app so the code-signature seal stays valid. |
|
95
|
# RELEASE_OS_BUILD — a shipping macOS build number. Default 25A354 = macOS 26.0. |
|
96
|
RELEASE_OS_BUILD="${RELEASE_OS_BUILD:-25A354}" |
|
97
|
|
|
98
|
# Distribution identity used to re-sign after the fixplist patch |
|
99
|
# (e.g. "Apple Distribution: <Your Org> (<TEAM_ID>)"). Required by fixplist only. |
|
100
|
# SIGN_IDENTITY — env-only, not committed (set it in .env.appstore). |
|
101
|
|
|
102
|
# App name — must match the productName in tauri.prod-ios.conf.json. |
|
103
|
APP_NAME="${APP_NAME:-Siti AI}" |
|
104
|
|
|
105
|
# Derived IPA path produced by `cargo tauri ios build`. |
|
106
|
IPA_PATH="${REPO_ROOT}/src-tauri/gen/apple/build/arm64/${APP_NAME}.ipa" |
|
107
|
|
|
108
|
# ── Helpers ─────────────────────────────────────────────────────────────────── |
|
109
|
|
|
110
|
step() { echo ""; echo "── $* ──────────────────────────────────────────────"; } |
|
111
|
ok() { echo "✅ $*"; } |
|
112
|
fail() { printf "❌ %b\n" "$*" >&2; exit 1; } |
|
113
|
|
|
114
|
# Release toolchain stamps used by the fixplist step to rewrite a beta-built |
|
115
|
# binary's Info.plist so App Store Connect reads it as a release-Xcode build. |
|
116
|
# Auto-derived from the pinned release Xcode (DEVELOPER_DIR, from pick_release_xcode); |
|
117
|
# every value is overridable via env. |
|
118
|
# RELEASE_DTXCODE e.g. 2660 (Xcode 26.6 → "2660") |
|
119
|
# RELEASE_XCODE_BUILD e.g. 17F113 (Xcode product build) |
|
120
|
# RELEASE_SDK_VERSION e.g. 26.5 |
|
121
|
# RELEASE_SDK_NAME e.g. iphoneos26.5 |
|
122
|
# RELEASE_SDK_BUILD e.g. 23F81a (iPhoneOS SDK product build) |
|
123
|
derive_release_toolchain() { |
|
124
|
local dd="${DEVELOPER_DIR}" |
|
125
|
if [[ -z "$dd" || ! -d "$dd" || "$dd" == *[Bb]eta* ]]; then |
|
126
|
[[ -n "${RELEASE_DTXCODE:-}${RELEASE_XCODE_BUILD:-}" ]] \ |
|
127
|
|| fail "No release Xcode found to derive release stamps.\n Install a release Xcode, or set RELEASE_DTXCODE / RELEASE_XCODE_BUILD / RELEASE_SDK_* by hand." |
|
128
|
fi |
|
129
|
local xv sv xver xbuild sdkver sdkbuild d |
|
130
|
xv="$(DEVELOPER_DIR="$dd" xcodebuild -version 2>/dev/null)" |
|
131
|
sv="$(DEVELOPER_DIR="$dd" xcodebuild -version -sdk iphoneos 2>/dev/null)" |
|
132
|
xver="$(printf '%s\n' "$xv" | awk '/^Xcode/{print $2}')" |
|
133
|
xbuild="$(printf '%s\n' "$xv" | awk '/Build version/{print $3}')" |
|
134
|
sdkver="$(printf '%s\n' "$sv" | awk -F': ' '/SDKVersion/{print $2; exit}')" |
|
135
|
sdkbuild="$(printf '%s\n' "$sv" | awk -F': ' '/ProductBuildVersion/{print $2; exit}')" |
|
136
|
d="${xver//./}"; while [[ -n "$d" && ${#d} -lt 4 ]]; do d="${d}0"; done |
|
137
|
REL_DTXCODE="${RELEASE_DTXCODE:-$d}" |
|
138
|
REL_XCODE_BUILD="${RELEASE_XCODE_BUILD:-$xbuild}" |
|
139
|
REL_SDK_VERSION="${RELEASE_SDK_VERSION:-$sdkver}" |
|
140
|
REL_SDK_NAME="${RELEASE_SDK_NAME:-iphoneos${sdkver}}" |
|
141
|
REL_SDK_BUILD="${RELEASE_SDK_BUILD:-$sdkbuild}" |
|
142
|
[[ -n "$REL_DTXCODE" && -n "$REL_XCODE_BUILD" && -n "$REL_SDK_BUILD" ]] \ |
|
143
|
|| fail "Could not determine release toolchain stamps from ${dd:-<unset>}." |
|
144
|
} |
|
145
|
|
|
146
|
# True when an Apple build number is a pre-release (beta) seed. Apple stamps |
|
147
|
# seeds with a trailing lowercase letter (e.g. 26A5353q); shipping builds never |
|
148
|
# carry one (e.g. 25A354). Used to decide whether the Info.plist actually needs |
|
149
|
# patching, so the workaround is a no-op on a release macOS. |
|
150
|
is_beta_build() { |
|
151
|
[[ "${1:-}" =~ [a-z]$ ]] |
|
152
|
} |
|
153
|
|
|
154
|
# ── Steps ───────────────────────────────────────────────────────────────────── |
|
155
|
|
|
156
|
do_clean() { |
|
157
|
step "Clean" |
|
158
|
rm -rf "${GEN_APPLE}" |
|
159
|
ok "Removed ${GEN_APPLE}" |
|
160
|
} |
|
161
|
|
|
162
|
do_init() { |
|
163
|
step "Init" |
|
164
|
|
|
165
|
[[ -f "$IOS_CONFIG" ]] || fail "Config not found: $IOS_CONFIG" |
|
166
|
[[ -f "$TAURI_CLI_MANIFEST" ]] || fail "Tauri CLI manifest not found: $TAURI_CLI_MANIFEST\n Set TAURI_CLI_MANIFEST to the path of your local tauri/Cargo.toml" |
|
167
|
|
|
168
|
echo "📱 config → ${IOS_CONFIG}" |
|
169
|
echo "🦀 cli → ${TAURI_CLI_MANIFEST}" |
|
170
|
echo "" |
|
171
|
|
|
172
|
cargo run --release \ |
|
173
|
--manifest-path "${TAURI_CLI_MANIFEST}" \ |
|
174
|
-p tauri-cli \ |
|
175
|
-- tauri ios init \ |
|
176
|
-c "${IOS_CONFIG}" |
|
177
|
|
|
178
|
ok "Xcode project generated → ${GEN_APPLE}" |
|
179
|
} |
|
180
|
|
|
181
|
do_icons() { |
|
182
|
step "Icons" |
|
183
|
pnpm gim |
|
184
|
ok "Icons generated" |
|
185
|
} |
|
186
|
|
|
187
|
do_override() { |
|
188
|
step "Override" |
|
189
|
|
|
190
|
if [[ -d "${GEN_OVERRIDE}" ]]; then |
|
191
|
cp -r "${GEN_OVERRIDE}/." "${GEN_APPLE}/" |
|
192
|
ok "Copied ${GEN_OVERRIDE} → ${GEN_APPLE}" |
|
193
|
else |
|
194
|
echo "ℹ️ No overrides found at ${GEN_OVERRIDE}, skipping." |
|
195
|
fi |
|
196
|
} |
|
197
|
|
|
198
|
do_build() { |
|
199
|
step "Build" |
|
200
|
|
|
201
|
[[ -f "$IOS_CONFIG" ]] || fail "Config not found: $IOS_CONFIG" |
|
202
|
[[ -d "$GEN_APPLE" ]] || fail "Xcode project not found at ${GEN_APPLE}\n Run 'init' step first." |
|
203
|
|
|
204
|
# CFBundleVersion: YYYYMMDD.HHMM (UTC) |
|
205
|
# Two period-separated integers → valid for Apple |
|
206
|
# Monotonically increasing → satisfies App Store requirement |
|
207
|
BUNDLE_VERSION="$(date -u +"%Y%m%d.%H%M")" |
|
208
|
BUNDLE_VERSION_JSON="{\"bundle\":{\"iOS\":{\"bundleVersion\":\"${BUNDLE_VERSION}\"}}}" |
|
209
|
|
|
210
|
echo "🏷 bundleVersion → ${BUNDLE_VERSION}" |
|
211
|
echo "📦 app config → ${IOS_CONFIG}" |
|
212
|
echo "" |
|
213
|
|
|
214
|
# `cargo tauri ios build` resolves the toolchain from the system xcode-select |
|
215
|
# (it ignores DEVELOPER_DIR), so on a beta-Xcode machine it builds with the |
|
216
|
# beta Xcode. That is fine: the fixplist step rewrites the beta toolchain |
|
217
|
# stamps (DTXcode, DTSDKBuild, …) to release values and re-signs, so the |
|
218
|
# uploaded binary reads as a release-Xcode build. App Store Connect accepts it. |
|
219
|
cargo tauri ios build \ |
|
220
|
--export-method app-store-connect \ |
|
221
|
--config "${IOS_CONFIG}" \ |
|
222
|
--config "${BUNDLE_VERSION_JSON}" |
|
223
|
|
|
224
|
[[ -f "$IPA_PATH" ]] || fail "Build succeeded but .ipa not found at: $IPA_PATH" |
|
225
|
ok "Build complete → ${IPA_PATH}" |
|
226
|
} |
|
227
|
|
|
228
|
do_fixplist() { |
|
229
|
step "Fixplist (beta toolchain → release)" |
|
230
|
|
|
231
|
[[ -f "$IPA_PATH" ]] || fail ".ipa not found: ${IPA_PATH}\n Run 'build' step first." |
|
232
|
|
|
233
|
command -v /usr/libexec/PlistBuddy >/dev/null || fail "PlistBuddy not found" |
|
234
|
: "${SIGN_IDENTITY:?set SIGN_IDENTITY (distribution identity for the re-sign)}" |
|
235
|
|
|
236
|
WORK="$(mktemp -d)" |
|
237
|
trap 'rm -rf "$WORK"' RETURN |
|
238
|
|
|
239
|
unzip -q "$IPA_PATH" -d "$WORK" |
|
240
|
|
|
241
|
local app_dir plist ent os_cur xcb_cur sdk_cur |
|
242
|
app_dir="$(find "$WORK/Payload" -maxdepth 1 -name '*.app' -print -quit)" |
|
243
|
[[ -n "$app_dir" && -d "$app_dir" ]] || fail "No .app found inside ${IPA_PATH}" |
|
244
|
plist="$app_dir/Info.plist" |
|
245
|
[[ -f "$plist" ]] || fail "Info.plist not found in $app_dir" |
|
246
|
|
|
247
|
pl() { /usr/libexec/PlistBuddy -c "Print :$1" "$plist" 2>/dev/null || echo ""; } |
|
248
|
setpl() { /usr/libexec/PlistBuddy -c "Set :$1 $2" "$plist" 2>/dev/null \ |
|
249
|
|| /usr/libexec/PlistBuddy -c "Add :$1 string $2" "$plist"; } |
|
250
|
|
|
251
|
os_cur="$(pl BuildMachineOSBuild)" # set by the build *machine's* OS (beta macOS) |
|
252
|
xcb_cur="$(pl DTXcodeBuild)" # set by the *Xcode* used (beta Xcode) |
|
253
|
sdk_cur="$(pl DTSDKBuild)" # set by the *SDK* used (beta SDK) |
|
254
|
|
|
255
|
# Patch when ANY toolchain stamp is a pre-release seed (trailing lowercase |
|
256
|
# letter): a beta macOS (BuildMachineOSBuild), a beta Xcode (DTXcodeBuild) or a |
|
257
|
# beta SDK (DTSDKBuild) all make App Store Connect reject the upload. Override |
|
258
|
# with FORCE_FIXPLIST=1 to patch regardless, or SKIP_FIXPLIST=1 to never patch. |
|
259
|
if [[ -n "${SKIP_FIXPLIST:-}" ]]; then |
|
260
|
ok "SKIP_FIXPLIST set — leaving ${IPA_PATH} untouched (DTXcodeBuild=${xcb_cur:-<unset>})." |
|
261
|
return 0 |
|
262
|
fi |
|
263
|
if [[ -z "${FORCE_FIXPLIST:-}" ]] \ |
|
264
|
&& ! is_beta_build "$os_cur" && ! is_beta_build "$xcb_cur" && ! is_beta_build "$sdk_cur"; then |
|
265
|
ok "Toolchain stamps are already release seeds — no patch needed, leaving .ipa untouched." |
|
266
|
return 0 |
|
267
|
fi |
|
268
|
|
|
269
|
derive_release_toolchain |
|
270
|
echo "📦 ipa → ${IPA_PATH}" |
|
271
|
echo "🔏 identity → ${SIGN_IDENTITY}" |
|
272
|
echo "🛠 xcode → ${REL_DTXCODE} / ${REL_XCODE_BUILD} sdk → ${REL_SDK_NAME} / ${REL_SDK_BUILD}" |
|
273
|
echo "" |
|
274
|
|
|
275
|
# Capture the entitlements the binary was actually signed with, so the |
|
276
|
# re-sign reproduces them exactly (matches the embedded provisioning profile). |
|
277
|
ent="$WORK/entitlements.plist" |
|
278
|
if ! codesign -d --entitlements :"$ent" "$app_dir" 2>/dev/null || [[ ! -s "$ent" ]]; then |
|
279
|
echo "ℹ️ Falling back to gen-override entitlements" |
|
280
|
cp "${GEN_OVERRIDE}/sitiai_iOS/sitiai_iOS.entitlements" "$ent" |
|
281
|
fi |
|
282
|
|
|
283
|
# Rewrite every beta toolchain stamp to the release Xcode's values. |
|
284
|
echo "🔁 BuildMachineOSBuild: ${os_cur:-<unset>} → ${RELEASE_OS_BUILD}" |
|
285
|
echo "🔁 DTXcode/DTXcodeBuild: ${xcb_cur:-<unset>} → ${REL_DTXCODE}/${REL_XCODE_BUILD}" |
|
286
|
echo "🔁 DTSDK*: ${sdk_cur:-<unset>} → ${REL_SDK_NAME}/${REL_SDK_BUILD}" |
|
287
|
setpl BuildMachineOSBuild "${RELEASE_OS_BUILD}" |
|
288
|
setpl DTXcode "${REL_DTXCODE}" |
|
289
|
setpl DTXcodeBuild "${REL_XCODE_BUILD}" |
|
290
|
setpl DTSDKName "${REL_SDK_NAME}" |
|
291
|
setpl DTSDKBuild "${REL_SDK_BUILD}" |
|
292
|
setpl DTPlatformBuild "${REL_SDK_BUILD}" |
|
293
|
setpl DTPlatformVersion "${REL_SDK_VERSION}" |
|
294
|
|
|
295
|
# Rewrite the Mach-O LC_BUILD_VERSION load command on every binary in the |
|
296
|
# bundle. This is the critical step: App Store Connect's *review-stage* |
|
297
|
# validation reads the `sdk` field baked into the executable's load command — |
|
298
|
# NOT the Info.plist DTSDK* keys above — so a beta-SDK build (e.g. sdk 27.0) |
|
299
|
# is accepted at upload, processes fine, then flags "Invalid Binary" only |
|
300
|
# after it is submitted for review. Patching Info.plist alone does not fix it; |
|
301
|
# vtool rewrites the binary itself. Each slice's `minos` is preserved; only |
|
302
|
# the beta `sdk` is lowered to the release SDK (REL_SDK_VERSION). The re-sign |
|
303
|
# below re-seals the modified Mach-O. |
|
304
|
local macho |
|
305
|
while IFS= read -r macho; do |
|
306
|
[[ -z "$macho" ]] && continue |
|
307
|
local minos_cur |
|
308
|
minos_cur="$(xcrun vtool -show-build "$macho" 2>/dev/null | awk '/minos/{print $2; exit}')" |
|
309
|
minos_cur="${minos_cur:-16.0}" |
|
310
|
echo "🔁 Mach-O sdk → ${REL_SDK_VERSION} (minos ${minos_cur}): ${macho#$app_dir/}" |
|
311
|
xcrun vtool -set-build-version ios "${minos_cur}" "${REL_SDK_VERSION}" \ |
|
312
|
-replace -output "${macho}.vt" "$macho" >/dev/null 2>&1 \ |
|
313
|
&& mv "${macho}.vt" "$macho" \ |
|
314
|
|| fail "vtool failed to patch LC_BUILD_VERSION on ${macho#$app_dir/}" |
|
315
|
done < <(find "$app_dir" -type f -exec sh -c 'file "$1" | grep -q Mach-O' _ {} \; -print) |
|
316
|
|
|
317
|
# Re-sign nested code first (deepest path → shallowest), then the .app itself. |
|
318
|
# Editing Info.plist / the Mach-O load command invalidates the original |
|
319
|
# signature; without this the upload is rejected for a broken seal. |
|
320
|
local nested |
|
321
|
while IFS= read -r nested; do |
|
322
|
[[ -z "$nested" ]] && continue |
|
323
|
echo "✍️ sign nested → ${nested#$app_dir/}" |
|
324
|
codesign --force --timestamp=none --sign "$SIGN_IDENTITY" "$nested" |
|
325
|
done < <(find "$app_dir" \( -name '*.framework' -o -name '*.dylib' -o -name '*.appex' \) -print | awk '{ print length, $0 }' | sort -rn | cut -d' ' -f2-) |
|
326
|
|
|
327
|
codesign --force --timestamp=none \ |
|
328
|
--sign "$SIGN_IDENTITY" \ |
|
329
|
--entitlements "$ent" \ |
|
330
|
"$app_dir" |
|
331
|
|
|
332
|
codesign --verify --deep --strict "$app_dir" \ |
|
333
|
|| fail "Re-signed app failed signature verification" |
|
334
|
echo "🔁 now: DTXcodeBuild=$(pl DTXcodeBuild) DTSDKBuild=$(pl DTSDKBuild) BuildMachineOSBuild=$(pl BuildMachineOSBuild)" |
|
335
|
|
|
336
|
# Repack the .ipa in place (zip from the extraction root so Payload/ is at top). |
|
337
|
rm -f "$IPA_PATH" |
|
338
|
( cd "$WORK" && zip -qry "$IPA_PATH" Payload ) |
|
339
|
[[ -f "$IPA_PATH" ]] || fail "Repack failed: $IPA_PATH" |
|
340
|
|
|
341
|
ok "Patched + re-signed → ${IPA_PATH}" |
|
342
|
} |
|
343
|
|
|
344
|
do_upload() { |
|
345
|
step "Upload" |
|
346
|
|
|
347
|
[[ -f "$IPA_PATH" ]] || fail ".ipa not found: ${IPA_PATH}\n Run 'build' step first." |
|
348
|
|
|
349
|
echo "📤 file → ${IPA_PATH}" |
|
350
|
echo "🔑 apiKey → ${ASC_API_KEY}" |
|
351
|
echo "🏢 issuer → ${ASC_ISSUER_ID}" |
|
352
|
echo "" |
|
353
|
|
|
354
|
xcrun altool \ |
|
355
|
--upload-app \ |
|
356
|
--type ios \ |
|
357
|
--file "${IPA_PATH}" \ |
|
358
|
--apiKey "${ASC_API_KEY}" \ |
|
359
|
--apiIssuer "${ASC_ISSUER_ID}" |
|
360
|
|
|
361
|
ok "Uploaded to App Store Connect" |
|
362
|
} |
|
363
|
|
|
364
|
# ── Entry point ─────────────────────────────────────────────────────────────── |
|
365
|
|
|
366
|
STEP="${1:-all}" |
|
367
|
|
|
368
|
case "$STEP" in |
|
369
|
clean) do_clean ;; |
|
370
|
init) do_init ;; |
|
371
|
icons) do_icons ;; |
|
372
|
override) do_override ;; |
|
373
|
build) do_build ;; |
|
374
|
fixplist) do_fixplist ;; |
|
375
|
upload) do_upload ;; |
|
376
|
all) |
|
377
|
do_clean |
|
378
|
do_init |
|
379
|
do_icons |
|
380
|
do_override |
|
381
|
do_build |
|
382
|
do_fixplist |
|
383
|
do_upload |
|
384
|
echo "" |
|
385
|
echo "🎉 Pipeline complete: built and uploaded ${APP_NAME} to App Store Connect." |
|
386
|
;; |
|
387
|
*) |
|
388
|
echo "Usage: $0 [clean|init|icons|override|build|fixplist|upload|all]" >&2 |
|
389
|
exit 1 |
|
390
|
;; |
|
391
|
esac |