// Store double for the teaser's CLI scenes (tests/teaser.test.ts): loaded with `preload` from a bunfig.toml in a temp // project, so every `bun cli/index.ts …` started there (and the children `release plan` starts) answers from here and // never reaches the network. It answers in the shapes App Store Connect and Google Play use (sanitized: placeholder // ids only) for a brand-new app record of the placeholder project: // TEASER_STORE=new the app record exists, nothing else: no version, no build, no listings, no products // TEASER_STORE=catalog the two products of the teaser project exist on both stores, priced in every region // Regions: the App Store's 175 storefronts (ALPHA3 in cli/lib/money.ts); Google Play the same list without mainland // China (Play is not offered there), 174 regions, the count Play answered in a recorded read-only run // (tests/play-post-reads.test.ts). Pages under https://example.com/ (the template's privacy policy and // delete-account URLs) answer 200; any other request answers 599 with its URL. import { ALPHA3, toMoney } from '../../cli/lib/money'; import { CURRENCY } from '../../cli/lib/ppp'; const STATE = process.env.TEASER_STORE ?? 'new'; const APP = '6400000101'; const ASC = 'https://api.appstoreconnect.apple.com'; const json = (o: unknown, status = 200): Response => new Response(JSON.stringify(o), { status, headers: { 'content-type': 'application/json' } }); const list = (data: unknown[], included: unknown[] = []): Response => json({ data, included, links: {}, meta: { paging: { total: data.length, limit: 200 } } }); /** The teaser project's products (the config in tests/teaser.test.ts): id, Apple type, US price. */ const PRODUCTS = [ { id: 'com.example.app.no_ads', type: 'NON_CONSUMABLE', usd: 2.99 }, { id: 'com.example.app.gems_100', type: 'CONSUMABLE', usd: 0.99 }, ]; const APPLE_REGIONS = Object.keys(ALPHA3); const PLAY_REGIONS = APPLE_REGIONS.filter(r => r !== 'CN'); /** One price per product everywhere (the values are not shown; the same price on both stores, so no parity gaps). */ const currency = (r: string): string => CURRENCY[r] ?? 'USD'; function apple(m: string, p: string): Response { if (/^\/v1\/apps\?filter\[bundleId\]=/.test(p)) return list([{ type: 'apps', id: APP, attributes: { name: 'Example App', bundleId: decodeURIComponent(p.split('=')[1]!), primaryLocale: 'en-US', sku: 'EXAMPLE' } }]); if (p.startsWith(`/v1/apps/${APP}/appInfos`)) return list([{ type: 'appInfos', id: 'info-1', attributes: { appStoreState: 'PREPARE_FOR_SUBMISSION', state: 'PREPARE_FOR_SUBMISSION' } }]); if (STATE === 'catalog') { if (p.startsWith(`/v1/apps/${APP}/inAppPurchasesV2`)) return list(PRODUCTS.map((x, i) => ({ type: 'inAppPurchases', id: `iap-${i}`, attributes: { productId: x.id, name: x.id.split('.').pop(), inAppPurchaseType: x.type, state: 'READY_TO_SUBMIT' } }))); const loc = p.match(/^\/v2\/inAppPurchases\/iap-\d\/inAppPurchaseLocalizations/); if (loc) return list(['en-US', 'tr'].map((locale, i) => ({ type: 'inAppPurchaseLocalizations', id: `loc-${i}`, attributes: { locale, name: 'Example', description: 'Example.' } }))); const sched = p.match(/^\/v2\/inAppPurchases\/(iap-\d)\/iapPriceSchedule/); if (sched) return json({ data: { type: 'inAppPurchasePriceSchedules', id: sched[1] } }); const prices = p.match(/^\/v1\/inAppPurchasePriceSchedules\/iap-(\d)\/(manualPrices|automaticPrices)/); if (prices) { const x = PRODUCTS[Number(prices[1])]!; // US is the base territory (manual); every other storefront is equalized by Apple (automatic) const regions = prices[2] === 'manualPrices' ? ['US'] : APPLE_REGIONS.filter(r => r !== 'US'); const data = regions.map(r => ({ type: 'inAppPurchasePrices', id: `price-${r}`, attributes: { startDate: null, endDate: null, manual: r === 'US' }, relationships: { inAppPurchasePricePoint: { data: { type: 'inAppPurchasePricePoints', id: `point-${r}` } }, territory: { data: { type: 'territories', id: ALPHA3[r] } } } })); const included = regions.flatMap(r => [ { type: 'inAppPurchasePricePoints', id: `point-${r}`, attributes: { customerPrice: String(x.usd), proceeds: String(x.usd * 0.85) } }, { type: 'territories', id: ALPHA3[r], attributes: { currency: currency(r) } }, ]); return list(data, included); } if (/^\/v1\/inAppPurchasePriceSchedules\/iap-\d\/baseTerritory/.test(p)) return json({ data: { type: 'territories', id: 'USA', attributes: { currency: 'USD' } } }); } if (m === 'GET') return list([]); return json({ errors: [{ status: '599', detail: `teaser store double: no route for ${m} ${p}` }] }, 599); } function play(m: string, url: string): Response { if (m === 'POST' && /\/edits$/.test(url)) return json({ id: 'edit-1', expiryTimeSeconds: '9999999999' }); if (m === 'DELETE') return new Response(null, { status: 204 }); if (/\/edits\/edit-1\/tracks$/.test(url)) return json({ kind: 'androidpublisher#tracksListResponse', tracks: [] }); if (/\/edits\/edit-1\/listings$/.test(url)) return json({ kind: 'androidpublisher#listingsListResponse' }); if (m === 'POST' && /\/pricing:convertRegionPrices$/.test(url)) return json({ convertedRegionPrices: Object.fromEntries(PLAY_REGIONS.map(r => [r, { regionCode: r, price: toMoney(currency(r), 1.99), taxAmount: toMoney(currency(r), 0) }])), convertedOtherRegionsPrice: { usdPrice: toMoney('USD', 1.99), eurPrice: toMoney('EUR', 1.99) }, regionVersion: { version: '2022/02' }, }); const product = (x: (typeof PRODUCTS)[number]) => ({ packageName: 'com.example.app', productId: x.id, listings: [{ languageCode: 'en-US', title: 'Example', description: 'Example.' }, { languageCode: 'tr-TR', title: 'Example', description: 'Example.' }], purchaseOptions: [{ purchaseOptionId: 'buy', state: 'ACTIVE', buyOption: { legacyCompatible: true }, regionalPricingAndAvailabilityConfigs: PLAY_REGIONS.map(r => ({ regionCode: r, availability: 'AVAILABLE', price: toMoney(currency(r), x.usd) })) }], }); if (/\/oneTimeProducts\?pageSize=/.test(url)) return json(STATE === 'catalog' ? { oneTimeProducts: PRODUCTS.map(product) } : {}); const one = url.match(/\/oneTimeProducts\/([^/?]+)$/); if (one) { const x = PRODUCTS.find(p => p.id === decodeURIComponent(one[1]!)); return x && STATE === 'catalog' ? json(product(x)) : json({ error: { code: 404, message: 'not found', status: 'NOT_FOUND' } }, 404); } if (/\/subscriptions\?pageSize=/.test(url)) return json({}); if (m === 'GET') return json({}); return json({ error: { code: 599, message: `teaser store double: no route for ${m} ${url}` } }, 599); } globalThis.fetch = (async (input: string | URL | Request, init?: RequestInit) => { const url = String(input instanceof Request ? input.url : input); const m = (init?.method ?? (input instanceof Request ? input.method : 'GET')).toUpperCase(); if (url.startsWith('https://oauth2.googleapis.com/token')) return json({ access_token: 'ya29.example', expires_in: 3600 }); if (url.startsWith(ASC)) return apple(m, url.slice(ASC.length)); if (url.startsWith('https://androidpublisher.googleapis.com/')) return play(m, url); if (url.startsWith('https://example.com/')) return new Response('
Example page.
', { status: 200, headers: { 'content-type': 'text/html' } }); return new Response(`teaser store double: no route for ${m} ${url}`, { status: 599 }); }) as typeof fetch;