apple build exports each build into build/ios/export/<version>-<build>/ (cleaned first), runs xcodebuild with Apple's tool folders first on PATH (Homebrew rsync 3.x broke Xcode's IPA step: exportArchive Copy failed), shows the IDEDistribution log's real error lines and exits non-zero on any export failure; apple upload fails with exit 1 when the IPA's build number is already on App Store Connect (stale IPA after a failed build), --allow-existing re-checks, --build N picks that build's IPA, release and beta flows use both; doctor flags a non-Apple rsync first on PATH; desktop publish calls rsync by absolute path without --chmod (publishSteps); site download notice per OS (macOS signed and notarized, Windows unsigned for now, Linux unsigned); game-report credential tests hermetic against a machine default profile (pitfalls 53, 54)

This commit is contained in:
CRTkafa 2026-10-13 09:45:48 +08:00
parent 8fd4ac741e
commit 0e4f579899
20 changed files with 338 additions and 48 deletions

View file

@ -186,3 +186,46 @@ export function uploadProblems(state: { errors?: { code?: string; message?: stri
const f = (x: { code?: string; message?: string; description?: string }) => [x.code, x.message ?? x.description].filter(Boolean).join(': ');
return { errors: (state?.errors ?? []).map(f), warnings: (state?.warnings ?? []).map(f) };
}
/** Apple's own tool folders, first on PATH for xcodebuild. */
export const APPLE_PATH = ['/usr/bin', '/bin', '/usr/sbin', '/sbin'];
/**
* PATH for xcodebuild: Xcode's developer usr/bin (when known) and Apple's folders first. Xcode's IPA packaging
* (IDEDistributionCreateIPAStep) runs `rsync` from PATH and fails with "rsync error: syntax or usage error (code 1)",
* shown as "exportArchive Copy failed", when Homebrew's rsync 3.x comes first (pitfall 54).
*/
export function xcodePath(path: string | undefined, developerDir?: string): string {
const first = [...(developerDir ? [join(developerDir, 'usr', 'bin')] : []), ...APPLE_PATH];
const rest = (path ?? '').split(':').filter(p => p && !first.includes(p));
return [...first, ...rest].join(':');
}
/** The first rsync on PATH when it is not Apple's (/usr/bin, /bin): Homebrew, MacPorts, ...; else null. */
export function foreignRsync(path: string | undefined, exists: (p: string) => boolean = existsSync): string | null {
for (const d of (path ?? '').split(':').filter(Boolean)) {
const p = join(d, 'rsync');
if (exists(p)) return d === '/usr/bin' || d === '/bin' ? null : p;
}
return null;
}
/**
* The real reason behind "exportArchive Copy failed": the error lines of the newest IDEDistribution*.log in the
* .xcdistributionlogs folder xcodebuild names in its output, else the newest such folder in the temp folder.
*/
export function distributionErrors(output: string, tmp: string = process.env.TMPDIR ?? '/tmp'): { log: string; lines: string[] } | null {
let dir = (output.match(/\/[^\s"']+\.xcdistributionlogs/g) ?? []).filter(p => existsSync(p)).at(-1);
if (!dir && existsSync(tmp)) {
dir = readdirSync(tmp).filter(f => f.endsWith('.xcdistributionlogs')).map(f => join(tmp, f))
.sort((a, b) => statSync(b).mtimeMs - statSync(a).mtimeMs)[0];
}
if (!dir) return null;
const d = dir;
const logs = readdirSync(d).filter(f => /^IDEDistribution.*\.log$/.test(f)).map(f => join(d, f)).sort((a, b) => statSync(b).mtimeMs - statSync(a).mtimeMs);
for (const l of logs) {
const lines = readFileSync(l, 'utf8').split('\n').filter(x => /rsync error|\berror:/i.test(x)).map(x => x.trim().slice(0, 300));
if (lines.length) return { log: l, lines: [...new Set(lines)].slice(-10) };
}
return logs[0] ? { log: logs[0], lines: [] } : null;
}