ITMS-91064: NSPrivacyTracking and NSPrivacyTrackingDomains must agree in every privacy manifest (true needs domains, domains need true); check, scan and privacy-manifest fail on either mismatch with the exact ITMS text; App Privacy tracking no longer demands the app manifest's true: SDK manifests that declare tracking (Pods, frameworks, SwiftPM, Carthage, plugins, the built IPA) are info, known tracking SDKs info, else a warning; privacy-manifest --propose writes true only with appPrivacy.trackingDomains; apple upload checks every manifest in the IPA (XML or binary) before any network call, apple build after the export. Pitfall 29

This commit is contained in:
CRTkafa 2026-10-12 10:27:21 +08:00
parent b7bfe89151
commit c8213be2b6
19 changed files with 631 additions and 44 deletions

View file

@ -2,7 +2,9 @@
// storebound apple upload [<app.ipa>] [--via api|altool] [--no-wait] [--timeout 60m] [--dry] [--yes] [--json]
// Default IPA: the newest in <apple.build.out or build/ios>/export. The bundle id, version and build number come from
// the IPA's Info.plist; the upload is refused when that build number is already on App Store Connect for the app
// (`apple next-build` / `apple build` pick a free one).
// (`apple next-build` / `apple build` pick a free one). Before any network call every PrivacyInfo.xcprivacy in the IPA
// (app root, Frameworks/*.framework, bundles; XML or binary) is checked: NSPrivacyTracking and NSPrivacyTrackingDomains
// must agree (ITMS-91064, Invalid Binary a minute after the upload), else nothing is uploaded.
// --via api (default): the App Store Connect API build upload (Build Uploads, API 4.1+): POST /v1/buildUploads,
// reserve /v1/buildUploadFiles, PUT the parts, commit with the MD5, then poll the upload state. Same API key, no
// Xcode needed, works on any OS.
@ -19,6 +21,7 @@ import { confirm, need } from '../lib/ctx';
import { done, log, printJson, warn } from '../lib/log';
import { compareBuild, nextBuildNumber, nextPollDelay, parseDuration, WAIT_EXIT } from '../lib/release';
import { altoolArgs, buildUploadReqs, ipaInfo, showCmd, UPLOAD_DONE, uploadProblems } from '../lib/xcode';
import { bundleTrackingIssues, ipaManifests } from '../lib/privacy-files';
import { BUILD_WAIT_EXIT } from '../lib/build-ops';
import { type Asc, ascClient, type J } from './asc';
import { waitForBuild, waitMessage } from './testflight';
@ -207,6 +210,10 @@ export async function run(ctx: Ctx): Promise<void> {
} else ipa = resolve(ipa);
const info = ipaInfo(ipa);
if (info.bundleId !== bundleId) throw new Error(`${relative(root, ipa)} is ${info.bundleId}, the config says ${bundleId}`);
// every PrivacyInfo.xcprivacy in the app (root, Frameworks, bundles): Apple refuses the build minutes after the
// upload when NSPrivacyTracking and NSPrivacyTrackingDomains disagree (ITMS-91064), so stop before any network call
const privacy = bundleTrackingIssues(ipaManifests(ipa));
if (privacy.length) throw new Error(`${relative(root, ipa)}: not uploaded, Apple would refuse it as Invalid Binary:\n${privacy.map(x => ` ${x}`).join('\n')}\nFix the manifest, rebuild (storebound apple build) and upload the new IPA.`);
const size = statSync(ipa).size;
const dry = a.has('dry');
const json = a.has('json');