Signed AAB guardrails, injected signing args, IPA version train check, Play notes lint at build

This commit is contained in:
CRTkafa 2026-10-23 08:49:43 +08:00
parent 707db2ebc6
commit 7960063e0e
12 changed files with 181 additions and 14 deletions

View file

@ -23,7 +23,7 @@ import { parseMobileprovision, toXmlPlist } from '../lib/plist';
import { nextBuildNumber } from '../lib/release';
import { exportOptions, profileDirs } from '../lib/signing';
import { capacitorProject, webStep } from '../lib/web-assets';
import { archiveArgs, type AscAuth, distributionErrors, exportArgs, foreignRsync, remoteBuildSession, xcodePath, flutterConfigArgs, ipaInfo, pickXcode, scanXcode, showCmd } from '../lib/xcode';
import { archiveArgs, type AscAuth, distributionErrors, exportArgs, foreignRsync, remoteBuildSession, xcodePath, flutterConfigArgs, ipaInfo, ipaVersionProblem, pickXcode, scanXcode, showCmd } from '../lib/xcode';
import { bundleTrackingIssues, ipaManifests } from '../lib/privacy-files';
import { ascClient } from './asc';
import { findBundleId } from './signing';
@ -189,6 +189,8 @@ export async function run(ctx: Ctx): Promise<void> {
const ipa = readdirSync(exportPath).find(f => f.endsWith('.ipa'))!;
const info = ipaInfo(join(exportPath, ipa));
if (info.bundleId !== bundleId) warn(TAG, `the IPA's bundle id is ${info.bundleId}, the config says ${bundleId}`);
const vwhy = ipaVersionProblem(info.version, version, 'the requested --version');
if (vwhy) { if (a.has('allow-version-mismatch')) warn(TAG, vwhy); else throw new Error(`${relative(root, join(exportPath, ipa))}: ${vwhy}`); }
if (info.build !== build) warn(TAG, `the IPA has build ${info.build}, not ${build}: the project sets CFBundleVersion itself (use $(CURRENT_PROJECT_VERSION) in Info.plist)`);
// ITMS-91064 (Invalid Binary after the upload): NSPrivacyTracking / NSPrivacyTrackingDomains of every manifest in the IPA
const privacy = bundleTrackingIssues(ipaManifests(join(exportPath, ipa)));

View file

@ -5,7 +5,9 @@
// An IPA whose build number is already on App Store Connect fails with exit 1 (a stale IPA left by a failed
// `apple build`, pitfall 53); only --allow-existing (flows that resume after a finished upload) re-checks it instead. 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.
// must agree (ITMS-91064, Invalid Binary a minute after the upload), else nothing is uploaded. The IPA's version
// (CFBundleShortVersionString) must be the editable App Store version's (else the build lands in the wrong train);
// --allow-version-mismatch goes on anyway.
// --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.
@ -22,7 +24,7 @@ import type { Ctx } from '../lib/ctx';
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, exportedIpas, ipaInfo, showCmd, UPLOAD_DONE, uploadProblems } from '../lib/xcode';
import { altoolArgs, buildUploadReqs, exportedIpas, ipaInfo, ipaVersionProblem, 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';
@ -246,6 +248,11 @@ export async function run(ctx: Ctx): Promise<void> {
const c = await ascClient(root, bundleId, config.apple?.appId);
const appId = await c.app();
// the IPA must belong to the version train being prepared (the editable App Store version): MARKETING_VERSION 1.0
// from a project that ignored the intended version otherwise lands in the wrong train
const editable = await c.editableVersion().catch(() => null);
const trainWhy = ipaVersionProblem(info.version, editable?.attributes?.versionString as string | undefined, 'the editable App Store version');
if (trainWhy) { if (a.has('allow-version-mismatch')) warn(TAG, trainWhy); else throw new Error(`${relative(root, ipa)}: not uploaded, ${trainWhy}`); }
const builds = await c.all(`/v1/builds?filter[app]=${appId}&limit=200&fields[builds]=version,processingState`);
const numbers = builds.map(b => String(b.attributes.version));
const next = nextBuildNumber(numbers);