analytics and reports for a collector: store-only configs, --json snapshot, App Usage and Play install stock numbers, per-day sales ranges for both stores

This commit is contained in:
CRTkafa 2026-10-26 10:20:27 +07:00
parent b59665c661
commit 5833776a4b
15 changed files with 651 additions and 60 deletions

View file

@ -658,6 +658,7 @@ export interface ShipConfig {
billingPrograms?: ('user-choice-billing' | 'alternative-billing' | 'external-offers' | 'external-content-links')[];
};
apple?: {
/** Absent / null only in a store-only config with apple.appId (analytics and reports reads); every write needs it. */
bundleId: string; appId?: string; primaryLocale?: string; listing?: string; info?: AppleAppInfo; reviewShots?: string;
/** App Store name in the primary locale (at most 30 characters) when it differs from app.name, e.g. because the plain
* name is taken on the App Store ("STAR DASH: Stone Age to 2100"). Default app.name. A `### name` block in the
@ -1189,7 +1190,9 @@ export function validateConfig(c: unknown): string[] {
if (cfg.play?.billingPrograms !== undefined && !(Array.isArray(cfg.play.billingPrograms) && cfg.play.billingPrograms.every(b => (PLAY_BILLING_PROGRAMS as readonly string[]).includes(b))))
e.push(`play.billingPrograms: a list of ${PLAY_BILLING_PROGRAMS.join(' | ')}`);
if (cfg.apple) {
if (!ID_RE.test(cfg.apple.bundleId ?? '')) e.push('apple.bundleId: not a bundle id (com.example.game)');
// a store-only config for reads (analytics, reports) may name the app by apple.appId alone: bundleId null / absent
const idOnly = cfg.apple.bundleId == null && /^\d+$/.test(cfg.apple.appId ?? '');
if (!idOnly && !ID_RE.test(cfg.apple.bundleId ?? '')) e.push('apple.bundleId: not a bundle id (com.example.game)');
const an = cfg.apple.name;
if (an !== undefined && (typeof an !== 'string' || !an.trim())) e.push('apple.name: the App Store name (text), or leave it out to use app.name');
else if (an !== undefined && [...an].length > APPLE_NAME_MAX) e.push(`apple.name: ${[...an].length} characters > ${APPLE_NAME_MAX} (App Store limit)`);