Ship-step contract for pipelines: uniform exit codes (0/1/2/3/4/7) in cli/lib/ship-step.ts, one final --json line, --result-file (atomic, also on failure), --progress json, --env-file with secret redaction, --version-from; idempotent play upload / apple upload / play release / apple submit re-runs end with exit 7; play upload takes --track production and --version-code

This commit is contained in:
CRTkafa 2026-10-23 13:13:21 +08:00
parent 4eac8e8cd8
commit 06a0b424f5
14 changed files with 928 additions and 24 deletions

View file

@ -28,6 +28,7 @@ import { bundleTrackingIssues, ipaManifests } from '../lib/privacy-files';
import { ascClient } from './asc';
import { findBundleId } from './signing';
import * as rt from '../lib/rt';
import { shipPhase } from '../lib/ship-step';
const TAG = 'build';
@ -172,6 +173,7 @@ export async function run(ctx: Ctx): Promise<void> {
// (Embed Pods Frameworks). Stop before the web build and the archive (pitfall 101).
const remote = remoteBuildSession(process.env, rt.spawnSync(['launchctl', 'managername'], { timeout: 5000 }).stdout.toString().trim());
if (remote && !a.has('allow-ssh')) throw new Error(`apple build: ${remote}; codesign there fails with errSecInternalComponent (locked keychain). Run it in the Mac's logged-in session: a Jenkins / Forgejo agent started in that session, or from SSH: write the command into a .command file and \`open -a Terminal <file>\` (it runs in the GUI session; unset SSH_CONNECTION SSH_CLIENT SSH_TTY inside). --allow-ssh skips this check after \`security unlock-keychain\`.`);
shipPhase('prebuild', 'web build / prebuild');
const web = await webStep({ ...webOpts, dry: false });
for (const n of target.notes) log(TAG, n);
const rsync = foreignRsync(process.env.PATH);
@ -182,12 +184,15 @@ export async function run(ctx: Ctx): Promise<void> {
const realAuth: AscAuth | null = key ? { keyPath: key.keyPath, keyId: key.keyId, issuerId: key.issuerId } : null;
try {
if (target.framework === 'flutter') await runLogged(flutterConfigArgs(build!, version), root, join(out, 'flutter-config.log'), showCmd(flutterConfigArgs(build!, version), null));
shipPhase('build', `xcodebuild archive, build ${build}`);
const arch = archiveArgs({ target, root, configuration, archivePath, version, build: build!, teamId: teamId ?? undefined, auth: realAuth });
await runLogged(arch, root, join(out, 'xcodebuild-archive.log'), showCmd(arch, realAuth));
shipPhase('sign', 'xcodebuild -exportArchive (signing)');
await exportIpa({ root, out, archivePath, exportPath, auth: realAuth });
} finally { key?.cleanup(); }
const ipa = readdirSync(exportPath).find(f => f.endsWith('.ipa'))!;
const info = ipaInfo(join(exportPath, ipa));
shipPhase('verify', `${info.version} (${info.build})`);
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}`); }