desktop reads everything by itself: one background read scheduler (4 per store, starts spaced per account, App Store Connect X-Rate-Limit slows it below 20 % and pauses at 100 left, backoff with jitter on 429 / RESOURCE_EXHAUSTED, visible app first, one read per key), per-method TTLs over the saved reads, Settings auto-read (on) and interval (15 min, paused while hidden), Refresh all, a refresh icon and 'read 3 min ago' / 'couldn't refresh' per card, writes invalidate, status line with progress and quota; quota reaches the window through ToolResult.quota (cli/lib/quota.ts); Health top issues open with versions, sample stack (health --issue / --signature, read health_issue), Open in Play Console, Copy for AI and likely-cause hints (cli/lib/crash-hints.ts); tests/scheduler.test.ts, tests/quota-crash-hints.test.ts, boot check auto-read case
This commit is contained in:
parent
89256f1e80
commit
0fb4ce0001
46 changed files with 1531 additions and 141 deletions
|
|
@ -4,6 +4,7 @@ import { createHash, createPrivateKey, sign } from 'node:crypto';
|
|||
import { readFileSync } from 'node:fs';
|
||||
import { ascKey } from '../lib/credentials';
|
||||
import { errorBody } from '../lib/http';
|
||||
import { noteAsc } from '../lib/quota';
|
||||
import { AmbiguousWriteError, retrySafe } from '../lib/upsert';
|
||||
import * as rt from '../lib/rt';
|
||||
|
||||
|
|
@ -77,6 +78,7 @@ export async function ascClient(root: string, bundleId: string, appIdHint?: stri
|
|||
if (!safe) throw new AmbiguousWriteError(`${method} ${path} -> no answer (${(e as Error).message})`);
|
||||
throw e;
|
||||
}
|
||||
noteAsc(r);
|
||||
const t = await r.text();
|
||||
if ((r.status === 429 || (r.status >= 500 && safe)) && attempt < 5) { await rt.sleep(2000 * attempt); return asc(method, path, body, attempt + 1); }
|
||||
if (r.status >= 500 && !safe) throw new AmbiguousWriteError(`${method} ${path} -> ${r.status} ${errorBody(t)}`, r.status);
|
||||
|
|
@ -89,6 +91,7 @@ export async function ascClient(root: string, bundleId: string, appIdHint?: stri
|
|||
async function getAs(path: string, accept: string): Promise<J> {
|
||||
for (let attempt = 1; ; attempt++) {
|
||||
const r = await fetch('https://api.appstoreconnect.apple.com' + path, { headers: { Authorization: 'Bearer ' + jwt(), Accept: accept } });
|
||||
noteAsc(r);
|
||||
const t = await r.text();
|
||||
if ((r.status >= 500 || r.status === 429) && attempt < 5) { await rt.sleep(2000 * attempt); continue; }
|
||||
if (!r.ok) throw new Error(`GET ${path} -> ${r.status} ${errorBody(t, 800)}`);
|
||||
|
|
@ -101,6 +104,7 @@ export async function ascClient(root: string, bundleId: string, appIdHint?: stri
|
|||
async function bytes(path: string, accept = 'application/a-gzip'): Promise<Uint8Array | null> {
|
||||
for (let attempt = 1; ; attempt++) {
|
||||
const r = await fetch('https://api.appstoreconnect.apple.com' + path, { headers: { Authorization: 'Bearer ' + jwt(), Accept: accept } });
|
||||
noteAsc(r);
|
||||
if ((r.status >= 500 || r.status === 429) && attempt < 5) { await r.body?.cancel(); await rt.sleep(2000 * attempt); continue; }
|
||||
if (r.status === 404) { await r.body?.cancel(); return null; }
|
||||
if (!r.ok) throw new Error(`GET ${path.replace(/\?.*$/, '')} -> ${r.status} ${errorBody(await r.text(), 600)}`);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue