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

@ -13,7 +13,9 @@
// naming what is missing where.
import type { Ctx } from '../lib/ctx';
import { confirm, need } from '../lib/ctx';
import { log, printJson } from '../lib/log';
import { log, printJson, warn } from '../lib/log';
import type { FirstProductGap } from '../lib/review-submit';
import { firstProductReport } from './first-products';
import { classifyReviewState, nextPollDelay, parseDuration, WAIT_EXIT } from '../lib/release';
import { EMPTY_DRAFT } from '../lib/review-submit';
import { type Asc, ascClient, type J } from './asc';
@ -79,6 +81,11 @@ export async function run(ctx: Ctx): Promise<void> {
// an empty draft: App Store Connect keeps it and refuses to cancel it; harmless
const empty = new Set<string>();
for (const s of subs.filter((x: J) => x.attributes?.state === 'READY_FOR_REVIEW')) if (!(await readItems(c, s.id)).length) empty.add(s.id);
// first products of a type that are not in the review (pitfall 70): not sellable after the version is approved
const vstate = (v: J): string => v.attributes.appVersionState ?? v.attributes.appStoreState;
const reviewed = versions.find((v: J) => /WAITING_FOR_REVIEW|IN_REVIEW/.test(vstate(v))) ?? versions[0];
let gaps: FirstProductGap[] = [];
try { gaps = await firstProductReport(c, appId, { iaps, versionState: reviewed ? vstate(reviewed) : undefined }); } catch (e) { log('apple', `first in-app purchases not compared with the review: ${(e as Error).message.slice(0, 200)}`); }
const itemLabel = (kind: string, id: string): string => {
const v = versions.find((x: J) => x.id === id);
return v ? `app version ${v.attributes.versionString}` : `${kind} ${id}`;
@ -102,7 +109,7 @@ export async function run(ctx: Ctx): Promise<void> {
const v = pre.get(b.relationships?.preReleaseVersion?.data?.id ?? '');
if (v) buildVersions[b.id] = v;
}
printJson({ app, versions, builds: builds.data, attachedBuilds, buildVersions, iaps, subs, phased, unresolved, emptyDrafts: [...empty], capabilityCheck: caps });
printJson({ app, versions, builds: builds.data, attachedBuilds, buildVersions, iaps, subs, phased, unresolved, emptyDrafts: [...empty], firstProductGaps: gaps, capabilityCheck: caps });
return;
}
@ -118,6 +125,7 @@ export async function run(ctx: Ctx): Promise<void> {
const newest = builds.data[0]?.attributes?.version;
console.log(` resubmit with: storebound apple resubmit --build <n>${newest ? ` (newest uploaded build: ${newest})` : ''}`);
}
for (const g of gaps) warn('apple', `${g.msg}; ${g.fix}`);
if ('error' in caps) log('apple', `capabilities not compared: ${caps.error}`);
else printCapabilityReport(caps, 'apple');
}