apple iap-submit: DEVELOPER_REJECTED IAP / subscription / group versions (left by a canceled submission) are submitted as they are, plan says 'add (developer rejected, re-submitted as is)', --new-version only for APPROVED / REJECTED, empty draft reused; fake ASC models cancel and re-add. play build / check: warn before Gradle when defaultConfig does not read -PversionCode / -PversionName, with the exact Groovy / Kotlin line (built with AGP 8.13); android.injected.version.code does not override bundleRelease; post-build versionCode mismatch is a hard stop. play-production-release plan: 'Do you want to restart your review?' dialog, Restart review is never-press. Pitfalls 31-33
This commit is contained in:
parent
41d2ffcc24
commit
0b1451800c
18 changed files with 399 additions and 26 deletions
|
|
@ -33,8 +33,15 @@ const APPROVED_APP = new Set(['READY_FOR_DISTRIBUTION', 'READY_FOR_SALE', 'PENDI
|
|||
'PROCESSING_FOR_APP_STORE', 'REPLACED_WITH_NEW_VERSION', 'ACCEPTED', 'DEVELOPER_REMOVED_FROM_SALE', 'REMOVED_FROM_SALE']);
|
||||
/** Product states (InAppPurchaseState / Subscription.state) that mean the product was approved once. */
|
||||
const APPROVED_PRODUCT = new Set(['APPROVED', 'DEVELOPER_REMOVED_FROM_SALE', 'REMOVED_FROM_SALE']);
|
||||
/** Version states that can still go into a draft submission. */
|
||||
const SUBMITTABLE = new Set(['PREPARE_FOR_SUBMISSION', 'READY_FOR_REVIEW']);
|
||||
/** Version states that can still go into a draft submission. DEVELOPER_REJECTED (the owner canceled the submission
|
||||
* it was in) goes in as it is: seen live (2026-10-12), POST reviewSubmissionItems with that same in-app purchase version
|
||||
* answers 201 READY_FOR_REVIEW and the submit 200 WAITING_FOR_REVIEW; no new version is needed (--new-version is for
|
||||
* APPROVED or REJECTED products whose metadata changed). */
|
||||
const SUBMITTABLE = new Set(['PREPARE_FOR_SUBMISSION', 'READY_FOR_REVIEW', 'DEVELOPER_REJECTED']);
|
||||
/** What the plan says about a DEVELOPER_REJECTED version it adds. */
|
||||
export const DEV_REJECTED_NOTE = 'developer rejected, re-submitted as is';
|
||||
/** Product version states for which --new-version makes a new draft version first. */
|
||||
export const NEW_VERSION_FROM = new Set(['APPROVED', 'REJECTED']);
|
||||
const IN_REVIEW = new Set(['WAITING_FOR_REVIEW', 'IN_REVIEW']);
|
||||
|
||||
export type ItemKind = 'inAppPurchaseVersion' | 'subscriptionVersion' | 'subscriptionGroupVersion' | 'appStoreVersion';
|
||||
|
|
@ -111,7 +118,7 @@ export function chooseSubmission(open: OpenSub[], o: { appVersionId?: string } =
|
|||
return { action: 'create', why: 'no open review submission: a new draft' };
|
||||
}
|
||||
|
||||
export interface PlanItem { kind: ItemKind; versionId: string; label: string }
|
||||
export interface PlanItem { kind: ItemKind; versionId: string; label: string; /** a DEVELOPER_REJECTED version added again as it is */ devRejected?: boolean }
|
||||
export interface SubmitPlan {
|
||||
items: PlanItem[];
|
||||
/** Product ids that are left out, with the reason. */
|
||||
|
|
@ -156,9 +163,9 @@ export function planIapSubmission(s: SubmitState, want: string[], o: { withVersi
|
|||
const v = latestVersion(versions);
|
||||
if (!v) { plan.skipped.push({ id, why: 'no version on App Store Connect yet (create the product first: storebound apple iap / apple subscriptions)' }); return; }
|
||||
if (IN_REVIEW.has(v.state)) { plan.skipped.push({ id, why: `already ${v.state}` }); return; }
|
||||
if (!SUBMITTABLE.has(v.state)) { plan.skipped.push({ id, why: `latest version is ${v.state}: change it in App Store Connect (or --new-version) to send a new version` }); return; }
|
||||
if (!SUBMITTABLE.has(v.state)) { plan.skipped.push({ id, why: `latest version is ${v.state}: ${NEW_VERSION_FROM.has(v.state) ? 'add --new-version to send a new version' : 'change it in App Store Connect to send a new version'}` }); return; }
|
||||
if (already.has(v.id)) { plan.skipped.push({ id, why: 'already in the draft submission' }); return; }
|
||||
plan.items.push({ kind, versionId: v.id, label: `${id} (${typeLabel(type)}, version ${v.version ?? '?'})` });
|
||||
plan.items.push({ kind, versionId: v.id, label: `${id} (${typeLabel(type)}, version ${v.version ?? '?'})`, ...(v.state === 'DEVELOPER_REJECTED' ? { devRejected: true } : {}) });
|
||||
if (!approvedTypes.has(type)) firstTypes.add(type);
|
||||
};
|
||||
|
||||
|
|
@ -180,7 +187,7 @@ export function planIapSubmission(s: SubmitState, want: string[], o: { withVersi
|
|||
if (!v) { plan.blockers.push(`subscription group "${g.name}" has no version to submit`); continue; }
|
||||
if (IN_REVIEW.has(v.state) || already.has(v.id)) continue;
|
||||
if (!SUBMITTABLE.has(v.state)) { plan.blockers.push(`subscription group "${g.name}": version is ${v.state}`); continue; }
|
||||
plan.items.push({ kind: 'subscriptionGroupVersion', versionId: v.id, label: `subscription group "${g.name}" (not approved yet, goes with its subscriptions)` });
|
||||
plan.items.push({ kind: 'subscriptionGroupVersion', versionId: v.id, label: `subscription group "${g.name}" (not approved yet, goes with its subscriptions)`, ...(v.state === 'DEVELOPER_REJECTED' ? { devRejected: true } : {}) });
|
||||
}
|
||||
plan.firstOfType = [...firstTypes].map(typeLabel);
|
||||
const forced = o.appVersionId ? s.appVersions.find(v => v.id === o.appVersionId) : undefined;
|
||||
|
|
@ -192,7 +199,7 @@ export function planIapSubmission(s: SubmitState, want: string[], o: { withVersi
|
|||
: 'the app has no approved version yet: in-app purchases go together with an app version';
|
||||
if (!o.withVersion && !forced) plan.blockers.push(`${why}. Add --with-version (the editable version ${editable ? editable.versionString : '(none yet: storebound apple listing --version x.y.z --build N)'} goes into the same submission; submitting it submits the app)`);
|
||||
else if (!editable) plan.blockers.push(`${why}, and there is no editable app version: storebound apple listing --version x.y.z --build N first`);
|
||||
else if (!already.has(editable.id)) { plan.appVersion = editable; plan.items.unshift({ kind: 'appStoreVersion', versionId: editable.id, label: `app version ${editable.versionString}` }); }
|
||||
else if (!already.has(editable.id)) { plan.appVersion = editable; plan.items.unshift({ kind: 'appStoreVersion', versionId: editable.id, label: `app version ${editable.versionString}`, ...(editable.state === 'DEVELOPER_REJECTED' ? { devRejected: true } : {}) }); }
|
||||
}
|
||||
if (plan.items.length + (s.draft?.itemVersionIds.length ?? 0) > MAX_SUBMISSION_ITEMS) plan.blockers.push(`more than ${MAX_SUBMISSION_ITEMS} items in one submission: use --only for a part`);
|
||||
const sid = s.draft?.id ?? '${submission}';
|
||||
|
|
@ -203,6 +210,9 @@ export function planIapSubmission(s: SubmitState, want: string[], o: { withVersi
|
|||
return plan;
|
||||
}
|
||||
|
||||
/** The plan's word for an item it adds. Pure. */
|
||||
export const addAction = (it: PlanItem): string => (it.devRejected ? `add (${DEV_REJECTED_NOTE})` : 'add');
|
||||
|
||||
/** Whether the run ends with a submit: new items, or a submission back from App Review that only needs resubmitting. Pure. */
|
||||
export const willSubmit = (s: SubmitState, plan: SubmitPlan): boolean => plan.items.length > 0 || s.draft?.state === 'UNRESOLVED_ISSUES';
|
||||
|
||||
|
|
@ -255,7 +265,7 @@ export function resubmitRows(s: SubmitState, plan: SubmitPlan, build: BuildStep,
|
|||
for (const it of ch.sub.items) rows.push({ step: 'item', what: labelOf(s, it.versionId, it.kind), now: it.state,
|
||||
action: marked.has(it) ? 'in it already; mark resolved' : it.state === 'REJECTED' ? `REJECTED, left: ${resolveHint(it.kind)}` : 'in it already' });
|
||||
}
|
||||
for (const it of plan.items) rows.push({ step: 'item', what: it.label, now: '-', action: 'add' });
|
||||
for (const it of plan.items) rows.push({ step: 'item', what: it.label, now: it.devRejected ? 'DEVELOPER_REJECTED' : '-', action: addAction(it) });
|
||||
if (ch?.action === 'reuse') for (const d of ch.strayDrafts) rows.push({ step: 'stray draft', what: d.id, now: `${d.state}, ${d.items.length} item(s)`, action: d.items.length ? 'left as it is (named)' : `left as it is: ${EMPTY_DRAFT}` });
|
||||
if (ch?.action !== 'busy') rows.push({ step: 'submit', what: 'PATCH submitted: true', now: '-', action: 'owner step (--yes); retried while Apple says "not ready yet" and no item is left REJECTED' });
|
||||
return rows;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue