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

@ -17,6 +17,7 @@ import { done, log, warn } from '../lib/log';
import { reportMissing } from '../lib/store-lag';
import { IMAGE_SPECS, imageInfo } from '../shots/spec';
import { ascClient, md5 } from './asc';
import { productStateText } from '../lib/review-submit';
import * as rt from '../lib/rt';
/** Every iPhone / iPad screenshot size App Store Connect accepts (portrait and landscape). */
@ -79,9 +80,11 @@ export async function run(ctx: Ctx): Promise<void> {
else log('apple', `${sku}: screenshot uploaded (${state})`);
}
// what keeps a product in MISSING_METADATA (Apple's rule: a localization, a price, availability and the screenshot)
for (const p of await c.all(`/v1/apps/${appId}/inAppPurchasesV2?limit=200`)) {
const all = await c.all(`/v1/apps/${appId}/inAppPurchasesV2?limit=200`);
const approvedTypes = new Set(all.filter((x: { attributes: { state: string } }) => /APPROVED|REMOVED_FROM_SALE/.test(x.attributes.state)).map((x: { attributes: { inAppPurchaseType: string } }) => x.attributes.inAppPurchaseType));
for (const p of all) {
const state: string = p.attributes.state;
if (state !== 'MISSING_METADATA') { log('apple', `${p.attributes.productId}: ${state}`); continue; }
if (state !== 'MISSING_METADATA') { log('apple', `${p.attributes.productId}: ${productStateText(state, approvedTypes.has(p.attributes.inAppPurchaseType))}`); continue; }
const lack: string[] = [];
const locs = await c.maybe(`/v2/inAppPurchases/${p.id}/inAppPurchaseLocalizations?limit=50`);
if (!locs?.data?.length) lack.push('a localization (name + description)');
@ -97,5 +100,5 @@ export async function run(ctx: Ctx): Promise<void> {
}
if (dry) console.log(dryPlan('apple iap-review', rows));
if (problems) process.exitCode = 1;
done('apple', dry ? 'iap-review dry run (nothing uploaded)' : 'iap-review done (READY_TO_SUBMIT = complete; first IAPs go to review with a version, from the IAP page)');
done('apple', dry ? 'iap-review dry run (nothing uploaded)' : 'iap-review done (READY_TO_SUBMIT = ready, not submitted yet; the first in-app purchase of a type goes to review with the next app version: storebound apple submit adds it)');
}