storebound/cli/lib/http.ts
CRT 44e64cf9c6 apps / health / catalog / validate: fixes from a read-only run against live store accounts
- API error bodies on one line (errorBody): Google and App Store Connect pretty-print errors, which hid
  SERVICE_DISABLED from the Play account check (reported permission instead of api-disabled), broke tables
  and turned serve's apps.list error into a JSON fragment
- apps list: failure says the reason and the fix (same words as apps check)
- newest App Store version by createdDate, iOS first (the API groups versions by platform); all versions
  read so no platform is missed; per-app reads four at a time
- apps check (apple): team app count from meta.paging.total with a one-app read
- Play Money without units (prices under 1.00) read as 0.xx, not NaN (catalog diff, subscription diffs)
- health: App Store Connect 404 for perfPowerMetrics / diagnosticSignatures means no field data yet;
  Play vitals behind a disabled Reporting API say so with the fix
- validate: gracRatingClassificationNumber optional, socialMediaAgeRestricted only when socialMedia is yes,
  socialMedia unanswered is a warning
- tests with sanitized recorded responses (tests/store-real-shapes.test.ts)
2026-10-11 04:50:26 +08:00

25 lines
1.2 KiB
TypeScript

// Plain URL reachability check for public pages (privacy policy, app-ads.txt). No credentials are ever sent.
import type { UrlCheck } from './validate-rules';
export async function checkUrl(url: string, o: { body?: boolean; timeoutMs?: number } = {}): Promise<UrlCheck> {
try {
const r = await fetch(url, { redirect: 'follow', signal: AbortSignal.timeout(o.timeoutMs ?? 15_000), headers: { 'User-Agent': 'storebound-validate' } });
const body = o.body ? (await r.text()).slice(0, 200_000) : undefined;
if (!o.body) await r.body?.cancel();
return { url, status: r.status, body };
} catch (e) {
return { url, status: null, error: (e as Error).message };
}
}
/**
* An API error body for an Error message: JSON on one line (App Store Connect and Google pretty-print their errors,
* which broke tables, `lastLine` in serve and matching on the error reason), an HTML page named as such, else the text
* with its whitespace collapsed. At most `max` characters.
*/
export function errorBody(text: string, max = 1500): string {
if (/^\s*</.test(text)) return '(HTML error page)';
let s: string;
try { s = JSON.stringify(JSON.parse(text)); } catch { s = text.replace(/\s+/g, ' ').trim(); }
return s.length > max ? s.slice(0, max) + '…' : s;
}