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

@ -12,6 +12,7 @@ import { log } from './lib/log';
import { textTable } from './lib/table';
import { androidSdk, type AndroidSdkLookup, detectTools, findPlaywright, installCommands, type ToolStatus } from './lib/tools';
import * as rt from './lib/rt';
import { foreignRsync } from './lib/xcode';
export interface DoctorRow {
id: ToolStatus['id'] | 'playwright';
@ -32,6 +33,8 @@ export interface DoctorResult {
/** Store credentials of the project folder (env, .storebound/credentials.json, profile): set, missing or
* placeholder (a template value, treated as missing). Never the values. */
credentials: { key: string; status: 'set' | 'missing' | 'placeholder' }[];
/** macOS: a non-Apple rsync first on PATH (Homebrew 3.x breaks Xcode's exportArchive, pitfall 54), else null. */
foreignRsync: string | null;
}
export function doctor(cwd: string, env: Record<string, string | undefined> = process.env): DoctorResult {
@ -42,7 +45,8 @@ export function doctor(cwd: string, env: Record<string, string | undefined> = pr
usedFor: 'screenshot renders and web captures from a terminal (the desktop app brings its own)', install: pw ? [] : installCommands('playwright'), url: 'https://playwright.dev/docs/intro' });
const project = CONFIG_NAMES.some(n => existsSync(join(cwd, n))) ? cwd : null;
const sdk = androidSdk([join(cwd, 'android'), cwd], env);
return { storebound: pkg.version, bun: rt.bunVersion, runtime: `${rt.runtime.name} ${rt.runtime.version}`, platform: `${process.platform}-${process.arch}`, project, tools, missing: tools.filter(t => !t.found).length, androidSdk: sdk, credentials: credentialStatus(cwd) };
return { storebound: pkg.version, bun: rt.bunVersion, runtime: `${rt.runtime.name} ${rt.runtime.version}`, platform: `${process.platform}-${process.arch}`, project, tools, missing: tools.filter(t => !t.found).length, androidSdk: sdk, credentials: credentialStatus(cwd),
foreignRsync: process.platform === 'darwin' ? foreignRsync(env.PATH) : null };
}
export async function run(args: Args): Promise<void> {
@ -54,6 +58,7 @@ export async function run(args: Args): Promise<void> {
: `Android SDK: not found (play build needs it). Checked: ${r.androidSdk.checked.join('; ')}`);
const set = r.credentials.filter(c => c.status === 'set').map(c => c.key), ph = r.credentials.filter(c => c.status === 'placeholder').map(c => c.key);
log('doctor', `credentials: ${set.length ? set.join(', ') : 'none set'}${ph.length ? ` · placeholders (treated as missing): ${ph.join(', ')}` : ''}`);
if (r.foreignRsync) log('doctor', `rsync: ${r.foreignRsync} is first on PATH and breaks Xcode exportArchive ("Copy failed") outside storebound; storebound apple build puts /usr/bin first. Fix for everything else: brew unlink rsync, or put /usr/bin before it`);
const missing = r.tools.filter(t => !t.found);
if (!missing.length) { log('doctor', 'everything storebound can use is here'); return; }
console.log('\nTo install what is missing (pick one per tool; storebound never runs these for you):');