play build: find the Android SDK like Android Studio (ANDROID_HOME, ANDROID_SDK_ROOT, sdk.dir, default folder), pass it in the env, stop before Gradle when missing; doctor shows it. apple upload: size, per-part lines, 20 s heartbeat (api, altool, processing wait), per-part and per-call timeouts, --timeout exits 7 with the resume hint. Pitfall 25

This commit is contained in:
CRTkafa 2026-10-12 08:48:02 +08:00
parent 366362d62b
commit fc524cd2d4
15 changed files with 460 additions and 34 deletions

View file

@ -20,6 +20,7 @@ import { pump } from '../lib/runner';
import { aabInfo, findBundles, gradleArgs, pickGradle, signingEnv } from '../lib/gradle';
import { nextVersionCode } from '../lib/release';
import { capacitorProject, webStep } from '../lib/web-assets';
import { androidSdk, androidSdkEnv, androidSdkMissing } from '../lib/tools';
import { playClient } from './client';
import { usedVersionCodes } from './next-version-code';
@ -57,20 +58,26 @@ export async function run(ctx: Ctx): Promise<void> {
const hasWrapper = existsSync(join(t.dir, 'gradlew'));
const argv = gradleArgs(t, { versionCode: code || undefined, versionName, flavor, hasWrapper });
const cwd = t.framework === 'flutter' ? root : t.dir;
// the SDK as Android Studio finds it (ANDROID_HOME is often unset in non-interactive shells and local.properties is
// gitignored): handed to Gradle through the environment, local.properties is never written
const sdk = androidSdk([t.dir]);
const plan = { framework: t.framework, dir: relative(root, t.dir) || '.', module: t.module, task: t.framework === 'flutter' ? 'flutter build appbundle' : t.task,
versionCode: code || null, versionName: versionName ?? null, signing: signing ? `upload key from ${process.env.ANDROID_KEYSTORE ? 'env' : '.storebound/credentials.json'} (alias ${signing.keyAlias})` : 'the project\'s own signingConfig',
command: argv.join(' '), out: relative(root, out) || '.', notes: t.notes };
androidSdk: sdk.path ? `${sdk.path} (${sdk.source})` : null, command: argv.join(' '), out: relative(root, out) || '.', notes: t.notes };
const webOpts = { config, root, cap, store: 'play' as const, tag: TAG, noPrebuild: a.has('no-prebuild'), allowStale: a.has('allow-stale-web') };
if (dry) {
const web = await webStep({ ...webOpts, dry: true, quiet: json });
if (json) { printJson({ ...plan, web }); return; }
log(TAG, `${t.framework}: ${plan.dir} ${plan.task}, versionCode ${code || '?'}${versionName ? `, versionName ${versionName}` : ''}`);
log(TAG, `signing: ${plan.signing}`);
if (sdk.path) log(TAG, `Android SDK: ${sdk.path} (from ${sdk.source})`); else warn(TAG, androidSdkMissing(sdk));
for (const n of t.notes) log(TAG, n);
log(TAG, `would run (in ${relative(root, cwd) || '.'}): ${plan.command}`);
done(TAG, 'build planned (dry run)');
return;
}
if (!sdk.path) throw new Error(androidSdkMissing(sdk));
log(TAG, `Android SDK: ${sdk.path} (from ${sdk.source})`);
const web = await webStep({ ...webOpts, dry: false });
if (signing && !existsSync(signing.storeFile)) throw new Error('ANDROID_KEYSTORE: the keystore file does not exist');
if (!signing) warn(TAG, 'no ANDROID_KEYSTORE: the project\'s own release signingConfig must sign, else Play refuses the bundle');
@ -78,7 +85,7 @@ export async function run(ctx: Ctx): Promise<void> {
log(TAG, `$ ${plan.command}`);
const started = Date.now();
mkdirSync(out, { recursive: true });
const p = Bun.spawn(argv, { cwd, stdout: 'pipe', stderr: 'pipe', env: { ...process.env, ...signingEnv(signing) } });
const p = Bun.spawn(argv, { cwd, stdout: 'pipe', stderr: 'pipe', env: { ...process.env, ...androidSdkEnv(sdk), ...signingEnv(signing) } });
const lines: string[] = [];
const each = (line: string): void => {
lines.push(line);