@sigit / siti / commits / 4b68c8f

Add build script

paydii committed Jul 15, 2026 at 23:02 UTC 4b68c8fee542bdf24ce5257d52acf53608b27e59
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`.