storebound/apps/desktop/src/dev/submit-fixtures.ts

105 lines
8.2 KiB
TypeScript

// 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<string, unknown>) {
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 <form> --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: [],
};