apple submit/resubmit --platform macos: apple.macos.noIap leaves IAPs out, no Game Center switch without apple.macos.gameCenter; pitfalls #128

This commit is contained in:
CRTkafa 2026-11-06 21:07:22 +07:00
parent 36ef9c2c5c
commit d05294ebcd
6 changed files with 875 additions and 2 deletions

View file

@ -20,7 +20,7 @@
// `storebound apple submit --build <n>` (cli/apple/submit.ts) runs the same steps for a first submission: the version
// is --version, else the one an open submission holds, else the editable one (Prepare for Submission or rejected).
// Under `serve` (the desktop's owner action) the plan table is printed next to the --json result.
import { sellsOn } from '../lib/config';
import { macosSubmitSkips, sellsOn } from '../lib/config';
import { changeText, RELEASE_TYPE_EDITABLE, releaseTypeChanges, wantedRelease } from '../lib/release-type';
import type { Ctx } from '../lib/ctx';
import { need } from '../lib/ctx';
@ -86,6 +86,10 @@ export async function submitVersion(ctx: Ctx, mode: 'submit' | 'resubmit'): Prom
&& ((mode === 'submit' ? SUBMITTABLE_APP : REJECTED_APP).has(stateOf(x)) || back.has(platformOf(x)));
platform = pickPlatform(every, fits, undefined, mode === 'submit' ? `a version to submit${want ? ` (${want})` : ''}` : `a rejected version${want ? ` (${want})` : ''}`);
}
// the Mac app of a two-platform record may sell nothing and have no Game Center (apple.macos): its submission then
// carries the version and build only
const macSkips = macosSubmitSkips(config, platform);
if (macSkips.iaps && ids.length) { log(TAG, `${ids.length} in-app purchase(s) left out: ${macSkips.iaps}`); ids.length = 0; }
const versions = await c.all(versionsPath(appId, platform));
const open = await openSubmissions(c, appId, undefined, platform);
const held = new Set(open.filter(s => s.state === 'UNRESOLVED_ISSUES').flatMap(s => s.items.filter(i => i.kind === 'appStoreVersion').map(i => i.versionId)));
@ -125,7 +129,8 @@ export async function submitVersion(ctx: Ctx, mode: 'submit' | 'resubmit'): Prom
// Game Center: a build with the entitlement needs the app's Game Center detail and the version's switch, else the
// submission answers 409 STATE_ERROR.BUILD_INDICATES_GAME_CENTER_ENABLED (seen live); the same upsert as apple game-center
const gcUse = gameCenterInUse(config, root);
const gc = gcUse.inUse ? await readGcSwitch(c, appId, v) : null;
if (gcUse.inUse && macSkips.gameCenter) log(TAG, `Game Center switch left alone: ${macSkips.gameCenter}`);
const gc = gcUse.inUse && !macSkips.gameCenter ? await readGcSwitch(c, appId, v) : null;
const gcPlan = gc ? gcSwitchPlan(gc) : [];
if (a.has('json')) {
printJson({ dryRun: dry, releaseKind: kind.kind, releaseType: relChanges.length ? { changes: relChanges } : null, gameCenter: gcPlan, version: { id: v.id, versionString, platform, state: stateOf(v) }, build, submission: state.choice ?? null, resolve: marks, rows, skipped: plan.skipped, blockers });

View file

@ -1161,6 +1161,14 @@ export const appleName = (c: Pick<ShipConfig, 'app' | 'apple'>): string => c.app
export const platformsOf = (c: Pick<ShipConfig, 'app' | 'apple' | 'play'>): Platform[] =>
c.app.platforms ?? [...(c.apple ? ['ios' as const] : []), ...(c.play ? ['android' as const] : [])];
/** What a macOS submission leaves out per apple.macos (noIap: the in-app purchases; no gameCenter: the version's Game
* Center switch), with why; nulls for every other platform. Pure. */
export function macosSubmitSkips(c: Pick<ShipConfig, 'apple'>, platform: string): { iaps: string | null; gameCenter: string | null } {
if (platform !== 'MAC_OS') return { iaps: null, gameCenter: null };
return { iaps: c.apple?.macos?.noIap ? 'apple.macos.noIap (the Mac app sells nothing)' : null,
gameCenter: c.apple?.macos?.gameCenter ? null : 'the Mac app has no Game Center (apple.macos.gameCenter is not true)' };
}
/** Is a catalog item (product / subscription) sold on this store? Pure. */
export const sellsOn = (c: Pick<ShipConfig, 'app' | 'apple' | 'play'>, item: { platforms?: Platform[] }, store: 'apple' | 'play'): boolean => {
const p: Platform = store === 'apple' ? 'ios' : 'android';