6 files changed
+310
-6
.env.appstore.example
new
+30
@@ -0,0 +1,30 @@
1
+# App Store build configuration for Siti AI (macOS + iOS).
2
+#
3
+# No account, credential, or signing detail is committed. Copy this file to an
4
+# untracked `.env.appstore`, fill in the real values, and the build scripts
5
+# auto-load it:
6
+#
7
+# cp .env.appstore.example .env.appstore # then edit
8
+# make asmacos # or: make asios
9
+#
10
+# Find values with:
11
+# security find-identity -v -p codesigning # signing identities
12
+# App Store Connect → Users & Access → Integrations → App Store Connect API
13
+
14
+# ── macOS (make asmacos) ───────────────────────────────────────────────────
15
+# Installer cert ("3rd Party Mac Developer Installer: …" or its SHA-1).
16
+# NOTE: this is the *Installer* cert used by productbuild — distinct from the
17
+# app codesigning identity in tauri.prod-macos-appstore.conf.json.
18
+SIGNING_IDENTITY=
19
+
20
+# ── iOS (make asios) ───────────────────────────────────────────────────────
21
+# Distribution identity used by the fixplist re-sign, required only when the
22
+# build machine is on a beta macOS/Xcode toolchain (see scripts/build-ios.sh
23
+# `fixplist` step). ("Apple Distribution: …" or its SHA-1 fingerprint.)
24
+SIGN_IDENTITY=
25
+
26
+# ── Optional overrides (committed defaults exist in the scripts) ───────────
27
+# ASC_API_KEY=
28
+# ASC_ISSUER_ID=
29
+# DEVELOPER_DIR=/Applications/Xcode.app/Contents/Developer
30
+# RELEASE_OS_BUILD=25A354
.gitignore
+1
@@ -48,6 +48,7 @@ keystore.properties
48
.env
49
.env.*
50
!.env.example
51
+!.env.appstore.example
52
53
# Local Apple signing configs — contain cert SHA-1 identities and local
54
# provisioning-profile paths. Copy the .example files, fill in your own, and
Makefile
+6
-2
@@ -7,12 +7,13 @@
7
# make asmacos-upload — upload the .pkg to App Store Connect
8
#
9
# ── iOS App Store ──────────────────────────────────────────────────────────
10
-# make asios — full pipeline: clean → init → icons → override → build → upload
10
+# make asios — full pipeline: clean → init → icons → override → build → fixplist → upload
11
# make asios-clean — rm -rf src-tauri/gen/apple
12
# make asios-init — cargo tauri ios init
13
# make asios-icons — pnpm gim
14
# make asios-override — copy gen-override/apple into gen/apple
15
# make asios-build — cargo tauri ios build
16
+# make asios-fixplist — rewrite beta toolchain stamps (Info.plist + Mach-O) to release values, re-sign
17
# make asios-upload — xcrun altool upload
18
#
19
# ── Android Play Store ─────────────────────────────────────────────────────
@@ -34,7 +35,7 @@
35
36
.PHONY: dev build \
37
asmacos asmacos-build asmacos-sign asmacos-upload \
37
- asios asios-clean asios-init asios-icons asios-override asios-build asios-upload \
38
+ asios asios-clean asios-init asios-icons asios-override asios-build asios-fixplist asios-upload \
39
psmobile psmobile-clean psmobile-init psmobile-icons psmobile-override psmobile-keystore psmobile-build psmobile-open \
40
web-dev web-build web-deploy \
41
fmt lint clean
@@ -73,6 +74,9 @@ asios-override:
74
asios-build:
75
./scripts/build-ios.sh build
76
77
+asios-fixplist:
78
+ ./scripts/build-ios.sh fixplist
79
+
80
asios-upload:
81
./scripts/build-ios.sh upload
82
scripts/build-appstore.sh
+54
-1
@@ -27,6 +27,35 @@ set -euo pipefail
27
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
28
APPSTORE_CONFIG="${REPO_ROOT}/src-tauri/tauri.prod-macos-appstore.conf.json"
29
30
+# Auto-load the untracked .env.appstore (signing identities + ASC creds) so
31
+# `make asmacos` works without the caller exporting them first. See
32
+# .env.appstore.example for the expected keys.
33
+if [[ -f "${REPO_ROOT}/.env.appstore" ]]; then
34
+ set -a
35
+ # shellcheck disable=SC1091
36
+ . "${REPO_ROOT}/.env.appstore"
37
+ set +a
38
+fi
39
+
40
+# Xcode toolchain. App Store Connect rejects binaries built with a beta Xcode or
41
+# beta SDK, so pin a release Xcode here regardless of the global `xcode-select`.
42
+# Default: the active xcode-select if it is a release, otherwise the newest
43
+# non-beta Xcode under /Applications.
44
+# Override: DEVELOPER_DIR=/Applications/Xcode.app/Contents/Developer ./scripts/build-appstore.sh
45
+pick_release_xcode() {
46
+ local active; active="$(xcode-select -p 2>/dev/null)"
47
+ if [[ -n "$active" && "$active" != *[Bb]eta* ]]; then
48
+ printf '%s\n' "$active"; return
49
+ fi
50
+ local app
51
+ for app in /Applications/Xcode*.app; do
52
+ case "$app" in *[Bb]eta*) continue ;; esac
53
+ [[ -d "$app/Contents/Developer" ]] && { printf '%s\n' "$app/Contents/Developer"; return; }
54
+ done
55
+}
56
+DEVELOPER_DIR="${DEVELOPER_DIR:-$(pick_release_xcode)}"
57
+export DEVELOPER_DIR
58
+
59
# ── Overridable config ─────────────────────────────────────────────────────────
60
#
61
# Installer signing identity: SHA-1 fingerprint of the "3rd Party Mac Developer
@@ -60,13 +89,37 @@ PKG_PATH="${REPO_ROOT}/${APP_NAME}.pkg"
89
90
step() { echo ""; echo "── $* ──────────────────────────────────────────────"; }
91
ok() { echo "✅ $*"; }
63
-fail() { echo "❌ $*" >&2; exit 1; }
92
+fail() { printf "❌ %b\n" "$*" >&2; exit 1; }
93
+
94
+# Refuse to build with a beta toolchain — App Store Connect rejects beta-built
95
+# binaries (the cause of an "Invalid Binary" after upload). DEVELOPER_DIR is
96
+# pinned to a release Xcode above; `cargo tauri build` resolves the macOS
97
+# linker/SDK via xcrun, which honors DEVELOPER_DIR.
98
+require_release_xcode() {
99
+ [[ -n "${DEVELOPER_DIR}" && -d "${DEVELOPER_DIR}" ]] \
100
+ || fail "No release Xcode found.\n Install a release Xcode, or set DEVELOPER_DIR to its Contents/Developer dir."
101
+ case "${DEVELOPER_DIR}" in
102
+ *[Bb]eta*)
103
+ 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." ;;
104
+ esac
105
+ echo "🛠 Xcode → $(xcodebuild -version 2>/dev/null | head -1) [${DEVELOPER_DIR}]"
106
+}
107
+
108
+# A genuinely full disk during a large release build writes truncated/corrupt
109
+# proc-macro dylibs, which fail with the same "can't find crate" errors as the
110
+# beta-macOS strip bug — easy to conflate. Guard against it up front.
111
+preflight_disk() {
112
+ local free_gb; free_gb=$(df -g / | awk 'NR==2 {print $4}')
113
+ [[ "${free_gb:-0}" -ge 20 ]] || fail "Only ${free_gb} GB free on / — a universal release build needs ~20 GB+. Free space and retry."
114
+}
115
116
# ── Steps ─────────────────────────────────────────────────────────────────────
117
118
do_build() {
119
step "1/3 Build"
120
121
+ require_release_xcode
122
+ preflight_disk
123
[[ -f "$APPSTORE_CONFIG" ]] || fail "Config not found: $APPSTORE_CONFIG"
124
125
# CFBundleVersion: YYYYMMDD.HHMM (UTC)
scripts/build-ios.sh
+212
-3
@@ -7,7 +7,8 @@
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. Upload — xcrun altool (App Store Connect submission)
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.
@@ -19,6 +20,7 @@
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,6 +29,7 @@
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
@@ -38,6 +41,36 @@ 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
@@ -53,6 +86,19 @@ TAURI_CLI_MANIFEST="${TAURI_CLI_MANIFEST:-${HOME}/Repositories/tauri/Cargo.toml}
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
@@ -63,7 +109,47 @@ IPA_PATH="${REPO_ROOT}/src-tauri/gen/apple/build/arm64/${APP_NAME}.ipa"
109
110
step() { echo ""; echo "── $* ──────────────────────────────────────────────"; }
111
ok() { echo "✅ $*"; }
66
-fail() { echo "❌ $*" >&2; exit 1; }
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
@@ -125,6 +211,11 @@ do_build() {
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}" \
@@ -134,6 +225,122 @@ do_build() {
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
@@ -164,6 +371,7 @@ case "$STEP" in
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
@@ -171,12 +379,13 @@ case "$STEP" in
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
*)
179
- echo "Usage: $0 [clean|init|icons|override|build|upload|all]" >&2
388
+ echo "Usage: $0 [clean|init|icons|override|build|fixplist|upload|all]" >&2
389
exit 1
390
;;
391
esac
src-tauri/Cargo.toml
+7
@@ -55,6 +55,13 @@ lto = "thin"
55
codegen-units = 1
56
strip = "debuginfo"
57
58
+# Do NOT strip proc-macro / build-script dylibs. On beta macOS (Darwin 26/27)
59
+# `strip = "debuginfo"` corrupts their Mach-O LINKEDIT ("mis-aligned LINKEDIT
60
+# string pool"), so rustc can't dlopen them mid-build ("can't find crate for
61
+# thiserror_impl"). The shipped app binary stays stripped via the profile above.
62
+[profile.release.build-override]
63
+strip = false
64
+
65
# ── Local Onde SDK development ─────────────────────────────────────────────────
66
# Uncomment to build Siti against a local checkout of the Onde engine (sibling
67
# directory) instead of the published crate. Keep this commented on `main`.