apple: macOS (tvOS, visionOS) versions are no longer invisible: every version and review submission lookup is per platform; status, listing, listing pull, resubmit, submit, release and screenshots take --platform and otherwise pick the platform that has the version they need (refuse naming both when several do); status, validate, release status, the report and the desktop label versions with their platform; MCP and owner actions get platform; pitfall 77
This commit is contained in:
parent
1736300784
commit
47dfd372fe
30 changed files with 423 additions and 127 deletions
|
|
@ -1,5 +1,6 @@
|
|||
// Resubmit an App Store version after a rejection (Invalid Binary, Guideline 2.1, ...) with a new build.
|
||||
// storebound apple resubmit --build <n> [--version 1.0.0] [--resolve-iaps] [--dry] [--yes] [--json]
|
||||
// storebound apple resubmit --build <n> [--version 1.0.0] [--platform ios|macos|tvos|visionos] [--resolve-iaps] [--dry] [--yes] [--json]
|
||||
// The platform: --platform, else the one with a rejected version or an UNRESOLVED_ISSUES submission (both: asks).
|
||||
// 1. The rejected version (--version, else the one in the submission back from App Review, else the one in a
|
||||
// rejected state) gets build n attached, by explicit number (never "the latest"); it must be VALID.
|
||||
// 2. The open review submission is reused: the one with UNRESOLVED_ISSUES (it holds the version and its in-app
|
||||
|
|
@ -25,6 +26,7 @@ import { allSubscriptions } from '../lib/subscriptions';
|
|||
import { ascClient, type J } from './asc';
|
||||
import { readSubmitState, submitPlanned } from './iap-submit';
|
||||
import { openSubmissions } from './submission';
|
||||
import { allVersions, parsePlatform, pickPlatform, platformLabel, platformOf, versionsPath } from './platform';
|
||||
|
||||
/** App Store version states a first submission may send: the editable version (Prepare for Submission, a rejected
|
||||
* one, or one already Ready for Review in a draft). */
|
||||
|
|
@ -43,23 +45,33 @@ export async function submitVersion(ctx: Ctx, mode: 'submit' | 'resubmit'): Prom
|
|||
const appId = await c.app();
|
||||
const ids = [...(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)];
|
||||
|
||||
// the version to resubmit
|
||||
const versions = await c.all(`/v1/apps/${appId}/appStoreVersions?filter[platform]=IOS&limit=50`);
|
||||
// the platform: --platform, else the one whose version (or UNRESOLVED_ISSUES submission) this needs; both -> ask
|
||||
const stateOf = (v: J): string => v.attributes.appVersionState ?? v.attributes.appStoreState;
|
||||
const open = await openSubmissions(c, appId);
|
||||
const held = new Set(open.filter(s => s.state === 'UNRESOLVED_ISSUES').flatMap(s => s.items.filter(i => i.kind === 'appStoreVersion').map(i => i.versionId)));
|
||||
const want = a.get('version');
|
||||
const flag = parsePlatform(a.get('platform'));
|
||||
let platform = flag;
|
||||
if (!platform) {
|
||||
const every = await allVersions(c, appId);
|
||||
const back = new Set((await c.all(`/v1/apps/${appId}/reviewSubmissions?filter[state]=UNRESOLVED_ISSUES&limit=50`)).map((s: J) => String(s.attributes?.platform ?? 'IOS')));
|
||||
const fits = (x: J) => (!want || x.attributes.versionString === want)
|
||||
&& ((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})` : ''}`);
|
||||
}
|
||||
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)));
|
||||
const pl = platformLabel(platform);
|
||||
const v: J | undefined = want ? versions.find((x: J) => x.attributes.versionString === want)
|
||||
: versions.find((x: J) => held.has(x.id)) ?? versions.find((x: J) => (mode === 'submit' ? SUBMITTABLE_APP : REJECTED_APP).has(stateOf(x)));
|
||||
if (!v) throw new Error(want ? `no App Store version ${want} on iOS`
|
||||
: mode === 'submit' ? 'no App Store version to submit (none in Prepare for Submission or rejected): storebound apple listing --version x.y.z first'
|
||||
: 'no rejected App Store version and no review submission with UNRESOLVED_ISSUES: nothing to resubmit (storebound apple status)');
|
||||
if (mode === 'submit' && !SUBMITTABLE_APP.has(stateOf(v)) && !held.has(v.id)) throw new Error(`App Store version ${String(v.attributes.versionString)} is ${stateOf(v)}: only a version in Prepare for Submission (or a rejected one) can be submitted`);
|
||||
if (!v) throw new Error(want ? `no App Store version ${want} on ${pl}`
|
||||
: mode === 'submit' ? `no ${pl} App Store version to submit (none in Prepare for Submission or rejected): storebound apple listing --version x.y.z first`
|
||||
: `no rejected ${pl} App Store version and no ${pl} review submission with UNRESOLVED_ISSUES: nothing to resubmit (storebound apple status; another platform: --platform)`);
|
||||
if (mode === 'submit' && !SUBMITTABLE_APP.has(stateOf(v)) && !held.has(v.id)) throw new Error(`App Store version ${String(v.attributes.versionString)} (${pl}) is ${stateOf(v)}: only a version in Prepare for Submission (or a rejected one) can be submitted`);
|
||||
const versionString = String(v.attributes.versionString);
|
||||
if (!a.has('json')) log(TAG, `app version ${versionString}: ${stateOf(v)}`);
|
||||
if (!a.has('json')) log(TAG, `app version ${versionString} (${pl}): ${stateOf(v)}`);
|
||||
|
||||
// the build, by its number
|
||||
const b = (await c.all(`/v1/builds?filter[app]=${appId}&filter[version]=${number}&filter[preReleaseVersion.version]=${versionString}&filter[preReleaseVersion.platform]=IOS`))[0];
|
||||
const b = (await c.all(`/v1/builds?filter[app]=${appId}&filter[version]=${number}&filter[preReleaseVersion.version]=${versionString}&filter[preReleaseVersion.platform]=${platform}`))[0];
|
||||
const cur = (await c.maybe(`/v1/appStoreVersions/${v.id}/build`))?.data;
|
||||
const current = cur ? String(cur.attributes?.version ?? cur.id) : null;
|
||||
const build: BuildStep = !b ? { number, id: null, current, action: 'refuse', reason: `build ${versionString} (${number}) not found (still uploading or processing? storebound apple status)` }
|
||||
|
|
@ -68,8 +80,8 @@ export async function submitVersion(ctx: Ctx, mode: 'submit' | 'resubmit'): Prom
|
|||
: { number, id: b.id, current, action: 'attach', reason: `attach build ${number}${current ? ` instead of ${current}` : ''}` };
|
||||
if (b && b.attributes.usesNonExemptEncryption == null) warn(TAG, `build ${number} has no export compliance answer yet: storebound apple encryption (Apple refuses the submission without it)`);
|
||||
|
||||
const state = await readSubmitState(c, appId, ids, { appVersionId: v.id });
|
||||
const plan = planIapSubmission(state, ids, { withVersion: true, appVersionId: v.id });
|
||||
const state = await readSubmitState(c, appId, ids, { appVersionId: v.id, platform });
|
||||
const plan = planIapSubmission(state, ids, { withVersion: true, appVersionId: v.id, platform });
|
||||
// --build is a fixed build for the version: its REJECTED item is marked resolved; IAP items only on --resolve-iaps
|
||||
const resolve: ResolveScope = { version: true, iaps: a.has('resolve-iaps') };
|
||||
const rows = resubmitRows(state, plan, build, resolve);
|
||||
|
|
@ -77,7 +89,7 @@ export async function submitVersion(ctx: Ctx, mode: 'submit' | 'resubmit'): Prom
|
|||
const blockers = [...(build.action === 'refuse' ? [build.reason] : []), ...plan.blockers];
|
||||
const dry = a.has('dry') || !a.has('yes');
|
||||
if (a.has('json')) {
|
||||
printJson({ dryRun: dry, version: { id: v.id, versionString, state: stateOf(v) }, build, submission: state.choice ?? null, resolve: marks, rows, skipped: plan.skipped, blockers });
|
||||
printJson({ dryRun: dry, version: { id: v.id, versionString, platform, state: stateOf(v) }, build, submission: state.choice ?? null, resolve: marks, rows, skipped: plan.skipped, blockers });
|
||||
// the desktop's owner dialog shows the output text: the plan table goes with the result there
|
||||
if (process.env.STOREBOUND_MACHINE === '1') { console.log(formatRows(rows)); for (const x of blockers) warn(TAG, x); }
|
||||
if (blockers.length) process.exitCode = 1;
|
||||
|
|
@ -98,5 +110,5 @@ export async function submitVersion(ctx: Ctx, mode: 'submit' | 'resubmit'): Prom
|
|||
await c.asc('PATCH', `/v1/appStoreVersions/${v.id}/relationships/build`, { data: { type: 'builds', id: build.id } });
|
||||
log(TAG, `build ${number} attached to ${versionString}`);
|
||||
}
|
||||
await submitPlanned(ctx, c, { appId, ids, state, tag: TAG, withVersion: true, appVersionId: v.id, quiet: true, resolve });
|
||||
await submitPlanned(ctx, c, { appId, ids, state, tag: TAG, withVersion: true, appVersionId: v.id, platform, quiet: true, resolve });
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue