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:
parent
366362d62b
commit
fc524cd2d4
15 changed files with 460 additions and 34 deletions
47
cli/lib/progress.ts
Normal file
47
cli/lib/progress.ts
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
// Progress for long blocking steps (uploads, store processing waits, external tools): a heartbeat line every few
|
||||
// seconds so a run is never silent for minutes, and the size / elapsed formats those lines use. The timer is
|
||||
// injectable (Ticker), so tests drive beats by hand instead of sleeping.
|
||||
|
||||
/** Calls fn every ms until the returned stop function is called. */
|
||||
export type Ticker = (fn: () => void, ms: number) => () => void;
|
||||
|
||||
export const realTicker: Ticker = (fn, ms) => {
|
||||
const t = setInterval(fn, ms);
|
||||
(t as { unref?: () => void }).unref?.();
|
||||
return () => clearInterval(t);
|
||||
};
|
||||
|
||||
/** Default heartbeat interval of uploads and waits. */
|
||||
export const HEARTBEAT_MS = 20_000;
|
||||
|
||||
/** 4.2 MB, 312 MB (whole MB from 10 MB up). */
|
||||
export const mb = (bytes: number): string => `${(bytes / 1048576).toFixed(bytes < 10 * 1048576 ? 1 : 0)} MB`;
|
||||
|
||||
/** 45s, 4m30s, 1h02m. */
|
||||
export function elapsed(ms: number): string {
|
||||
const s = Math.max(0, Math.round(ms / 1000));
|
||||
if (s < 60) return `${s}s`;
|
||||
const m = Math.floor(s / 60);
|
||||
if (m < 60) return `${m}m${String(s % 60).padStart(2, '0')}s`;
|
||||
return `${Math.floor(m / 60)}h${String(m % 60).padStart(2, '0')}m`;
|
||||
}
|
||||
|
||||
/** "312/890 MB (35%)". */
|
||||
export const share = (done: number, total: number): string =>
|
||||
`${(done / 1048576).toFixed(total < 10 * 1048576 ? 1 : 0)}/${mb(total)} (${total ? Math.floor((done / total) * 100) : 100}%)`;
|
||||
|
||||
export interface HeartbeatOptions { everyMs?: number; ticker?: Ticker; now?: () => number }
|
||||
|
||||
/** Runs `say(elapsedMs)` every everyMs while a step runs; stop() ends it. */
|
||||
export function heartbeat(say: (elapsedMs: number) => void, o: HeartbeatOptions = {}): { stop: () => void } {
|
||||
const now = o.now ?? Date.now;
|
||||
const started = now();
|
||||
const stop = (o.ticker ?? realTicker)(() => say(now() - started), o.everyMs ?? HEARTBEAT_MS);
|
||||
return { stop };
|
||||
}
|
||||
|
||||
/** Runs fn with a heartbeat; the heartbeat stops however fn ends. */
|
||||
export async function withHeartbeat<T>(say: (elapsedMs: number) => void, fn: () => Promise<T>, o: HeartbeatOptions = {}): Promise<T> {
|
||||
const h = heartbeat(say, o);
|
||||
try { return await fn(); } finally { h.stop(); }
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue