// Made-up store answers for the send-for-review cards (Releases tab), in the shapes `apple status --json` and // `play status --json` print: one App Store app ready to submit (an editable version, VALID and processing builds, a // harmless empty draft) and one back from App Review (UNRESOLVED_ISSUES with a REJECTED version item), plus Play // tracks. Used by the dev fixture mode (fixtures.ts), tests/submit.test.ts and the boot check's live case // (tools/desktop-boot-check.ts). Placeholder ids only; nothing here is real store data. const version = (id: string, versionString: string, state: string, build: string | null) => ({ id, type: 'appStoreVersions', attributes: { versionString, appVersionState: state, platform: 'IOS' }, relationships: { build: { data: build ? { type: 'builds', id: build } : null } } }); const build = (id: string, number: string, state: string, uploaded: string, pre: string) => ({ id, type: 'builds', attributes: { version: number, processingState: state, uploadedDate: uploaded }, relationships: { preReleaseVersion: { data: { type: 'preReleaseVersions', id: pre } } } }); const BUILDS = [build('b-53', '53', 'PROCESSING', '2026-10-12T08:10:00Z', 'pre-240'), build('b-52', '52', 'VALID', '2026-10-11T16:40:00Z', 'pre-240'), build('b-48', '48', 'VALID', '2026-09-30T09:00:00Z', 'pre-230')]; export const APPLE_STATUS_READY = { app: { id: '1000000001', attributes: { name: 'Prism Puddle', bundleId: 'com.example.prismpuddle' } }, versions: [version('ver-240', '2.4.0', 'PREPARE_FOR_SUBMISSION', null), version('ver-230', '2.3.0', 'READY_FOR_SALE', 'b-48')], builds: BUILDS, attachedBuilds: [], buildVersions: { 'b-53': '2.4.0', 'b-52': '2.4.0', 'b-48': '2.3.0' }, iaps: [], phased: {}, subs: [{ id: 'rs-empty-1', attributes: { state: 'READY_FOR_REVIEW', submittedDate: null } }, { id: 'rs-old-1', attributes: { state: 'COMPLETE', submittedDate: '2026-09-30T12:00:00Z' } }], unresolved: [], emptyDrafts: ['rs-empty-1'], }; export const APPLE_STATUS_UNRESOLVED = { ...APPLE_STATUS_READY, versions: [version('ver-240', '2.4.0', 'REJECTED', 'b-52'), version('ver-230', '2.3.0', 'READY_FOR_SALE', 'b-48')], subs: [{ id: 'rs-back-1', attributes: { state: 'UNRESOLVED_ISSUES', submittedDate: '2026-10-11T18:00:00Z' } }], unresolved: [{ id: 'rs-back-1', items: [ { id: 'it-ver-1', kind: 'appStoreVersion', versionId: 'ver-240', state: 'REJECTED' }, { id: 'it-coins-1', kind: 'inAppPurchaseVersion', versionId: 'iapv-coins', state: 'READY_FOR_REVIEW' }, ] }], emptyDrafts: [], }; const res = (method: string, command: string, output: string, data?: unknown, ok = true) => ({ method, ok, exitCode: ok ? 0 : 1, dryRun: true, command, output, ...(data === undefined ? {} : { data }) }); /** Dry runs of the owner actions, with the plan tables the CLI prints (cli/lib/review-submit.ts formatRows). */ export function ownerDry(method: string, args: Record) { const b = String(args.build ?? ''); if (method === 'owner.apple_submit' || method === 'owner.apple_resubmit') { const resub = method === 'owner.apple_resubmit'; const table = resub ? [ 'STEP WHAT NOW ACTION', `build build ${b} attached: 52 attach build ${b} instead of 52`, 'submission rs-back-1 UNRESOLVED_ISSUES reuse (resubmit, no new draft)', 'item app version 2.4.0 REJECTED in it already; mark resolved', 'item coins_small READY_FOR_REVIEW in it already', 'submit PATCH submitted: true - owner step (--yes); retried while Apple says "not ready yet" and no item is left REJECTED', ] : [ 'STEP WHAT NOW ACTION', `build build ${b} none attached attach build ${b}`, 'submission new draft - create (no open submission)', 'item app version 2.4.0 - add', 'item coins_small (consumable, version 1) - add', 'submit PATCH submitted: true - owner step (--yes); retried while Apple says "not ready yet" and no item is left REJECTED', ]; const tag = resub ? 'resubmit' : 'submit'; return res(method, `storebound apple ${tag} --build ${b} --dry --json`, [...table, `12:00:02 [${tag}] dry run: nothing sent`].join('\n'), { dryRun: true, version: { versionString: '2.4.0', state: resub ? 'REJECTED' : 'PREPARE_FOR_SUBMISSION' }, build: { action: 'attach', number: b }, blockers: [] }); } if (method === 'owner.play_production') { return res(method, `storebound play release --version-code ${String(args.version_code)} --production ${String(args.status ?? 'completed')} --dry`, [`12:00:01 [play] production: versionCode ${String(args.version_code)} as ${String(args.status ?? 'completed')}`, '12:00:01 [play] release notes: en-US, tr-TR', '12:00:01 [play] WARN managed publishing is off by default (play.managedPublishing not set): the release goes live automatically once Google approves it', '12:00:01 [play] dry run: edit not committed'].join('\n')); } if (method.startsWith('owner.prices_') && method !== 'owner.prices_import') { return res(method, `storebound ${method.replace('owner.prices_', '').replace('_', ' ')} --dry`, ['ID REGION NOW WANT ACTION', 'coins_small US USD 0.99 USD 1.49 owner (price change: needs --allow-price-changes)', 'coins_small DE EUR 0.99 EUR 1.49 owner (price change: needs --allow-price-changes)', '12:00:01 [play] dry run: nothing written'].join('\n')); } return res(method, `storebound ${method.replace('owner.', '').replace('_', ' ')} --dry`, `12:00:01 [${method}] dry run (fixture)`); } /** `console plan
--json`, shortened (made up from the demo configs). */ export function consolePlan(form: string) { const play = form.startsWith('play'); return { form, store: play ? 'play' : 'apple', title: form === 'play-production-release' ? 'Play Console: send the first production release for review' : `Console plan: ${form}`, start: { url: play ? 'https://play.google.com/console' : 'https://appstoreconnect.apple.com/apps', deepLink: false, reach: play ? ['the app in the app list', 'Test and release > Production'] : ['the app', 'General > App Information'] }, steps: [ { n: 1, text: 'Open the page above (sign in yourself the first time).' }, { n: 2, text: 'Press Edit release, then Next: the review page lists errors and warnings.', click: ['Edit release', 'Next'] }, { n: 3, text: 'Press Save (nothing is sent yet).', click: ['Save'] }, { n: 4, text: 'Stop. The owner presses "Send changes for review" in Publishing overview.' }, ], answers: [ { id: 'managedPublishing', question: 'Managed publishing on?', text: 'No', status: 'owner-check', note: 'Managed publishing is off by default: the release goes live once Google approves it.' }, { id: 'countries', question: 'Countries / regions', text: 'All countries / regions', status: 'certain' }, ], askOwner: ['Managed publishing on?: No (owner check)'], buttons: { neverPress: ['Send changes for review', 'Publish', 'Submit', 'Create', 'Start rollout to Production'], final: 'Send changes for review' }, }; } export function consoleVerify(form: string) { return { form, source: 'page-text', rows: [{ item: 'Production release', expected: '2.4.0 (24)', seen: '2.4.0 (24)', status: 'match' }, { item: 'Countries', expected: 'all', seen: '177 countries / regions', status: 'match' }], mismatches: 0, matched: 2, ok: true, notes: [] }; } export const PLAY_STATUS = { details: { defaultLanguage: 'en-US' }, category: { config: 'Puzzle', source: 'play.category', api: false, plan: 'storebound console plan play-store-settings' }, tracks: [ { track: 'production', releases: [{ name: '2.3.0 (23)', versionCodes: ['23'], status: 'completed' }] }, { track: 'beta', releases: [{ name: '2.4.0 (24)', versionCodes: ['24'], status: 'completed' }] }, { track: 'internal', releases: [{ name: '2.4.0 (24)', versionCodes: ['24'], status: 'completed' }, { name: '2.3.1 (22)', versionCodes: ['22'], status: 'completed' }] }, ], listings: [], products: [], };