Console pages as absolute deep links: cli/lib/console-links.ts builds the URL of every App Store Connect / Play Console page from apple.appId, play.developerId and play.appId (app list / developer chooser when unknown); every plain 'App Store Connect > ...' / 'Play Console > ...' direction in cli/ and templates/ now carries it, validate / scan / declarations / release plan JSON carry url, the desktop shows an Open button; tests/console-links.test.ts guards it; AGENTS rule 11, pitfall 71

This commit is contained in:
CRTkafa 2026-10-15 19:55:37 +08:00
parent f898d642d0
commit e500e88e40
50 changed files with 381 additions and 92 deletions

View file

@ -9,6 +9,7 @@
// fetched and fetches nothing. Apple needs a team API key (not an individual key) with access to reports and the vendor
// number (ASC_VENDOR_NUMBER in env or .storebound/credentials.json; --vendor). Play needs play.reportsBucket and the
// service account with "View financial data". Read-only on both stores. Pure parts and sources: cli/lib/reports.ts.
import { consoleLink } from './lib/console-links';
import { mkdirSync, writeFileSync } from 'node:fs';
import { dirname, join } from 'node:path';
import { ascClient } from './apple/asc';
@ -51,7 +52,7 @@ function save(dir: string, name: string, raw: string | Uint8Array, rows: Summary
/** What a failed Sales and Trends / finance download usually means. Seen live: a wrong vendor number gets a 500
* (after the retries) from salesReports and a 400 PARAMETER_ERROR.INVALID from financeReports. Pure. */
export function appleReportHint(msg: string): string | null {
if (/salesReports -> 500|financeReports -> 400 .*vendor number/i.test(msg)) return 'check the vendor number (App Store Connect > Payments and Financial Reports, the number next to the legal entity name); reports also need a team key with Sales or Finance access';
if (/salesReports -> 500|financeReports -> 400 .*vendor number/i.test(msg)) return `check the vendor number (${consoleLink('asc.payments')}, the number next to the legal entity name); reports also need a team key with Sales or Finance access`;
if (/-> 403/.test(msg)) return 'the API key may not read reports: it needs the Admin, Finance or Sales role (Users and Access > Integrations)';
return null;
}
@ -67,7 +68,7 @@ export async function run(ctx: Ctx): Promise<void> {
if (sub === 'apple-sales' || sub === 'apple-finance') {
const vendor = a.get('vendor') ?? ascVendorNumber(root);
if (!vendor || !/^\d{5,12}$/.test(vendor)) throw new Error('vendor number missing: ASC_VENDOR_NUMBER in env, .storebound/credentials.json or the credential profile (--account), or --vendor (App Store Connect > Payments and Financial Reports)');
if (!vendor || !/^\d{5,12}$/.test(vendor)) throw new Error(`vendor number missing: ASC_VENDOR_NUMBER in env, .storebound/credentials.json or the credential profile (--account), or --vendor (${consoleLink('asc.payments')})`);
let path: string, name: string;
if (sub === 'apple-sales') {
const type = a.str('type', 'SALES').toUpperCase();
@ -100,7 +101,7 @@ export async function run(ctx: Ctx): Promise<void> {
if (sub === 'play-earnings' || sub === 'play-sales') {
const bucket = config.play?.reportsBucket;
if (!bucket) throw new Error('play.reportsBucket is not set (Play Console > Download reports > Financial > Copy Cloud Storage URI)');
if (!bucket) throw new Error(`play.reportsBucket is not set (${consoleLink('play.reports-financial', '> Copy Cloud Storage URI')})`);
const month = need(a.get('month'), '--month YYYY-MM');
const kind = sub === 'play-earnings' ? 'earnings' : 'sales';
const prefix = playReportPrefix(kind, month);