First in-app purchases left out of a review are caught (pitfall 70): firstProductGaps in validate / release plan (blocker) and the apple submit plan (refuses to leave a never-approved first product out); apple status (WARN + firstProductGaps in --json), release status and the desktop checks warn while a version waits for or is in review without it; iap-review says 'ready, not submitted yet' instead of 'complete'

This commit is contained in:
CRTkafa 2026-10-15 19:31:26 +08:00
parent 1c35a625e1
commit b830a99a5f
16 changed files with 288 additions and 18 deletions

View file

@ -16,6 +16,7 @@
import { templateBlockers } from './lib/template-answers';
import { ascClient, type J } from './apple/asc';
import { capabilityReport } from './apple/signing';
import { firstProductReport } from './apple/first-products';
import { requiredDisplayTypes } from './apple/screenshots';
import { type Loaded, playCategoryOf, playCodes, profileOf, sellsOn } from './lib/config';
import { reviewDemoAccount } from './lib/credentials';
@ -82,7 +83,10 @@ export async function collectApple(ctx: Ctx): Promise<AppleSnapshot> {
const sched = await c.maybe(`/v1/apps/${appId}/appPriceSchedule?include=manualPrices`);
const avail = await c.maybe(`/v1/apps/${appId}/appAvailabilityV2`);
const iaps: AppleSnapshot['iaps'] = [];
for (const p of await c.all(`/v1/apps/${appId}/inAppPurchasesV2?limit=200`)) {
const iapList = await c.all(`/v1/apps/${appId}/inAppPurchasesV2?limit=200`);
const configIds = [...(config.products ?? []).filter(p => sellsOn(config, p, 'apple')).map(p => p.id), ...allSubscriptions(config.subscriptions).filter(x => sellsOn(config, x.sub, 'apple')).map(x => x.sub.id)];
const firstProductGaps = configIds.length ? await firstProductReport(c, appId, { iaps: iapList, only: configIds, versionState: v ? (v.attributes.appVersionState ?? v.attributes.appStoreState) : undefined }) : [];
for (const p of iapList) {
const shot = (await c.maybe(`/v2/inAppPurchases/${p.id}/appStoreReviewScreenshot`))?.data;
iaps.push({ productId: p.attributes.productId, state: p.attributes.state, reviewScreenshot: !!shot });
}
@ -111,6 +115,7 @@ export async function collectApple(ctx: Ctx): Promise<AppleSnapshot> {
requiredDisplayTypes: requiredDisplayTypes(config.apple),
priceSchedule: !!sched?.included?.some((x: J) => x.type === 'appPrices'),
availability: !!avail?.data,
firstProductGaps,
iaps, expectedProducts: (config.products ?? []).filter(p => sellsOn(config, p, 'apple')).map(p => p.id),
privacyUrl: await checkUrl(config.app.privacyPolicyUrl),
capabilityMismatches: await capabilityReport(c, config, root).then(r => r.mismatches.map(m => ({ msg: m.msg, fix: m.fix })), e => [{ msg: `capabilities not compared: ${(e as Error).message.slice(0, 200)}`, fix: 'storebound apple signing status' }]),