Google API errors as typed findings, never raw JSON: cli/lib/google-errors.ts parses SERVICE_DISABLED (api-disabled: service, Cloud project, console page), PERMISSION_DENIED (no-app-access: which app, Users and permissions), 401 / invalid_grant (auth-revoked) and 429 (quota); health / analytics print one line with the URL, raw text in detail and the log; desktop Health tab: the line, 'Turn it on in Google Cloud' and 'Check again', raw text in a collapsed Details; Portfolio shows a Cloud project blocking several apps once. Findings exit codes are results: the sidecar reports outcome (ok / findings / declined / failed) and the command's own error line; scan / check / validate / health exit 1 with findings show and cache the findings; no bare 'Exit code N' anywhere. tests/google-errors.test.ts, boot check errors cases (api-disabled, scan-findings, scan-failed) (pitfall 57)

This commit is contained in:
CRTkafa 2026-10-13 23:20:04 +08:00
parent f69c10d3a0
commit 89256f1e80
28 changed files with 666 additions and 47 deletions

View file

@ -8,6 +8,7 @@
// Download reports > Statistics > Copy Cloud Storage URI) are read with the service account (store listing visitors,
// acquisitions, daily user installs). compare lines the snapshots up with the git history of the store files (listing
// text, screenshots, config): the days before vs after each change. Read-only on both stores except `setup`.
import { storeReadError } from './lib/google-errors';
import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from 'node:fs';
import { join } from 'node:path';
import { createOnce } from './lib/upsert';
@ -120,11 +121,11 @@ export async function run(ctx: Ctx): Promise<void> {
const snap: Snapshot = { date: day(now), window: { start, end, days } };
if ((!store || store === 'apple') && config.apple) {
try { const r = await appleSnapshot(ctx, start, end); snap.apple = { daily: r.daily, totals: appleTotals(r.daily) }; }
catch (e) { snap.apple = { error: (e as Error).message.slice(0, 400) }; warn(TAG, `apple: ${snap.apple.error}`); }
catch (e) { snap.apple = { error: storeReadError((e as Error).message).error.slice(0, 400) }; warn(TAG, `apple: ${snap.apple.error}`); }
}
if ((!store || store === 'play') && config.play) {
try { const r = await playSnapshot(ctx, start, end); snap.play = { daily: r.daily, totals: playTotals(r.daily) }; }
catch (e) { snap.play = { error: (e as Error).message.slice(0, 400) }; warn(TAG, `play: ${snap.play.error}`); }
catch (e) { snap.play = { error: storeReadError((e as Error).message, { app: config.play.packageName }).error.slice(0, 400) }; warn(TAG, `play: ${snap.play.error}`); }
}
const rows: (string | number)[][] = [];
if (snap.apple && !('error' in snap.apple)) { const t = snap.apple.totals; rows.push(['apple', `${t.impressions} impressions`, `${t.pageViews} page views`, `${t.downloads} downloads`, pct(t.conversion ?? null)]); }