// App Store pre-orders through the app availability API. // storebound apple preorder status [--json] // storebound apple preorder enable --release-date YYYY-MM-DD [--territories USA,DEU] [--dry] [--yes] [--json] // storebound apple preorder end [--territories USA,DEU] [--dry] [--yes] [--json] // status: per territory available, release date, pre-order on, publish date, content statuses. enable puts the // territories (default: all the app is available in and not yet released) on pre-order with that release date; end // stops the pre-order and releases the app there at once. Both are public, so they show the dry run and ask first; // neither is an MCP tool. Apple requires an approved version first; the release date is 2-180 days ahead for a new // app (up to 365 days for an existing app in a new region). Google Play pre-registration has no API: Play Console > // Grow users > Pre-registration (see the app-store-promotion skill). Pure parts and sources: cli/lib/offers.ts. import type { Ctx } from '../lib/ctx'; import { confirm, need } from '../lib/ctx'; import { done, printJson, warn } from '../lib/log'; import { endPreOrderReq, planPreOrder, territoryRows } from '../lib/offers'; import { textTable } from '../lib/table'; import { ascClient, type J } from './asc'; import { describeReq, execute } from './jsonapi'; const TAG = 'preorder'; const USAGE = 'usage: storebound apple preorder status | enable --release-date YYYY-MM-DD [--territories USA,DEU] | end [--territories USA]'; export async function run(ctx: Ctx): Promise { const { config, root } = ctx.loaded; const a = ctx.args; const sub = a._[0]; if (!sub || !['status', 'enable', 'end'].includes(sub)) throw new Error(USAGE); const dry = a.has('dry'); const c = await ascClient(root, need(config.apple?.bundleId, 'apple.bundleId'), config.apple?.appId); const appId = await c.app(); const avail = (await c.maybe(`/v1/apps/${appId}/appAvailabilityV2`))?.data; if (!avail) throw new Error('the app has no availability yet: set territories first (storebound apple appinfo, or Pricing and Availability in App Store Connect)'); const pagesOut: J[] = []; for (let url: string | null = `/v2/appAvailabilities/${avail.id}/territoryAvailabilities?include=territory&limit=200`; url;) { const p: J = await c.asc('GET', url); pagesOut.push(p); url = p.links?.next ?? null; } const rows = territoryRows(pagesOut); const territories = a.list('territories').length ? a.list('territories') : null; if (sub === 'status') { const pre = rows.filter(r => r.preOrderEnabled); if (a.has('json')) { printJson({ availableInNewTerritories: avail.attributes?.availableInNewTerritories ?? null, territories: rows }); return; } console.log(textTable(['territory', 'available', 'release date', 'pre-order', 'pre-order published', 'status'], rows.filter(r => !territories || territories.includes(r.territory)).map(r => [r.territory, r.available ? 'yes' : 'no', r.releaseDate ?? '', r.preOrderEnabled ? 'on' : '', r.preOrderPublishDate ?? '', (r.contentStatuses ?? []).join(' ')]))); done(TAG, `${rows.length} territories, ${pre.length} on pre-order`); return; } const plan = sub === 'enable' ? planPreOrder(rows, need(a.get('release-date'), '--release-date YYYY-MM-DD'), territories) : { reqs: [endPreOrderReq(rows, territories)].filter(r => r !== null), problems: [] as string[], warnings: [] as string[] }; for (const w of plan.warnings) warn(TAG, w); if (plan.problems.length) throw new Error(plan.problems.join('; ')); if (a.has('json') && dry) { printJson({ dryRun: true, requests: plan.reqs.map(r => describeReq(r)), warnings: plan.warnings }); return; } if (!plan.reqs.length) { done(TAG, sub === 'enable' ? 'pre-order already set up like that' : 'no territory is on pre-order'); return; } if (dry) { await execute(c.asc, plan.reqs, { dry, tag: TAG }); done(TAG, 'dry run: nothing changed'); return; } const q = sub === 'enable' ? `Put the app on pre-order in ${plan.reqs.length} territor${plan.reqs.length === 1 ? 'y' : 'ies'} with release date ${a.get('release-date')}? The pre-order page goes public once you release an approved version.` : `End the pre-order and release the app now (${plan.reqs[0]!.note})?`; if (!(await confirm(ctx, q))) return; await execute(c.asc, plan.reqs, { dry: false, tag: TAG }); if (a.has('json')) printJson({ dryRun: false, requests: plan.reqs.map(r => describeReq(r)) }); done(TAG, sub === 'enable' ? 'pre-order set (storebound apple preorder status)' : 'pre-order ended; the app is released in those territories'); }