diff --git a/CHANGELOG.md b/CHANGELOG.md index 580a6e1..a19ab85 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ ## Unreleased +- `apple listing` refused by an approved version waiting for a manual release now names the way out: + `storebound apple release --manual-release --version `. `play release --dry` shows the staged rollout share. + - **Desktop: a new home.** Store-style app cards (your rendered screenshots, or the icon on its tint), a release pipeline per store (Build -> Testing -> Review -> Live -> Rollout), the crash curve and one next-step button per app; below, What changed and Fix first. The table view stays one switch away. diff --git a/cli/lib/listing-plan.ts b/cli/lib/listing-plan.ts index 895fa64..7e18bed 100644 --- a/cli/lib/listing-plan.ts +++ b/cli/lib/listing-plan.ts @@ -97,7 +97,11 @@ export function planVersion(rows: { id: string; attributes: Record c.field).join(', ')}` : `version ${want} (${editable.state}) is up to date` }; } const busy = others.find(v => IN_PROGRESS.has(v.state)); - if (busy) return refuse(`version ${busy.versionString} is ${busy.state}; App Store Connect allows one version in progress (wait for its release or remove it from review)`); + // the way out, named (Acme 2026-10-22: an approved manual-release version blocked the next one and only the console + // was suggested): an approved version is released with apple release --manual-release + if (busy) return refuse(`version ${busy.versionString} is ${busy.state}; App Store Connect allows one version in progress (${busy.state === 'PENDING_DEVELOPER_RELEASE' + ? `release it first: storebound apple release --manual-release --version ${busy.versionString} --dry, then without --dry` + : busy.state === 'WAITING_FOR_REVIEW' || busy.state === 'IN_REVIEW' ? 'wait for the review, or remove it from review in App Store Connect' : 'wait for its release or remove it from review'})`); const attributes = { platform, versionString: want, copyright: attrs.copyright, releaseType: attrs.releaseType }; return { ...base, action: 'create', reason: `create version ${want} (no editable ${PLATFORM_LABEL[platform] ?? platform} version)`, changes: Object.entries(attributes).map(([field, to]) => ({ field, from: null, to })), attributes }; } diff --git a/cli/play/release.ts b/cli/play/release.ts index 24c3d8f..7d4f204 100644 --- a/cli/play/release.ts +++ b/cli/play/release.ts @@ -91,7 +91,8 @@ export async function run(ctx: Ctx): Promise { } if (!changed && !uploaded) ctl.unchanged(); }, { dry: a.has('dry'), retries: aab ? 0 : 3, notSentForReview: a.has('not-sent-for-review'), - summary: `release ${a.get('name') ?? (aab ?? `versionCode ${fixed}`)}: ${tracks.map(t => `${t.track} ${t.status}`).join(', ')}` }); + // the staged rollout share is part of the plan (it was applied but not shown in the dry run) + summary: `release ${a.get('name') ?? (aab ?? `versionCode ${fixed}`)}: ${tracks.map(t => `${t.track} ${t.status}${t.status === 'inProgress' ? ` ${Math.round(Number(fraction ?? 0.1) * 100)}%` : ''}`).join(', ')}` }); } catch (e) { if (/draft app|status draft/i.test(String(e))) throw new Error(`${(e as Error).message}\n\nThis app has never been published: the API may only create DRAFT releases. Re-run with --production draft, then send it from the Console (skill play-first-release).`); diff --git a/tests/listing-plan-busy.test.ts b/tests/listing-plan-busy.test.ts new file mode 100644 index 0000000..f8afbf1 --- /dev/null +++ b/tests/listing-plan-busy.test.ts @@ -0,0 +1,9 @@ +// A version waiting for a manual release blocks the next one: the refusal names the command that releases it. +import { expect, test } from 'bun:test'; +import * as plan from '../cli/lib/listing-plan'; + +test('PENDING_DEVELOPER_RELEASE refusal names apple release --manual-release', () => { + const fn = Object.values(plan).find(f => typeof f === 'function' && /one version in progress/.test(String(f))) as ((...a: unknown[]) => { action: string; reason: string }) | undefined; + expect(fn).toBeDefined(); + expect(String(fn)).toContain('apple release --manual-release --version'); +});