storebound/templates/profiles/subscription-app/storebound.config.ts

176 lines
9.5 KiB
TypeScript

// storebound config for one app (profile "subscription-app": accounts plus auto-renewable subscriptions, an
// analytics SDK, no ads). Every answer below is a starting point: edit it to match your app. Credentials NEVER go
// here (env vars or .storebound/credentials.json, gitignored). Paths are relative to this file.
// The type import is optional: it only works when storebound is installed in the project (bun add -d storebound).
import type { ShipConfig } from 'storebound';
const config: ShipConfig = {
// Every compliance answer below is the TEMPLATE's, not yours. While an area is listed here, check / validate block
// and nothing sends it to a store. Derive the answers from the code (storebound scan --write; for an app that is
// already live: storebound onboard --declarations), answer the rest, then remove the area from this list.
unanswered: ['dataSafety', 'appPrivacy', 'reviewNotes', 'ageRating', 'accounts', 'reviewAccess'],
app: {
name: 'Example App',
profile: 'subscription-app',
copyright: '2026 Example Studio',
privacyPolicyUrl: 'https://example.com/privacy',
termsUrl: 'https://example.com/terms', // Terms of Use (EULA): in the app and the metadata (Apple 3.1.2)
supportUrl: 'https://example.com/support',
marketingUrl: 'https://example.com',
website: 'https://example.com',
contactEmail: 'support@example.com',
},
// Accounts (account-deletion skill). Remove this block if the app has no sign-up / sign-in at all.
// Apple 5.1.1(v): deletion must start inside the app. Google Play: in the app AND on a public web page.
accounts: {
enabled: true,
deletionUrl: 'https://example.com/delete-account', // public page, no login needed to request deletion
inApp: 'Settings > Account > Delete account', // goes into the App Review notes
// How people sign up: 'username' (a name picked in the app, no password, no email), 'username-password',
// 'email-password', 'email-link', 'phone', 'oauth', 'other'. `storebound scan` proposes the account data and
// dataSafety.accountCreation from it; only the 'email-*' methods mean an email address.
signUp: ['email-password'],
},
// Sign-in for store reviewers (review-access skill). The demo account itself lives ONLY in
// .storebound/credentials.json ("reviewDemoAccount") or REVIEW_DEMO_USER / REVIEW_DEMO_PASSWORD.
reviewAccess: {
signInRequired: true, // false if every feature works without signing in
instructions: 'Sign in with the demo account. Sample data is already there; nothing needs a second device.',
},
// id = your folder / copy key; play = Play language code(s); apple = App Store Connect locale
languages: [
{ id: 'en', play: 'en-US', apple: 'en-US' },
{ id: 'tr', play: 'tr-TR', apple: 'tr' },
],
play: {
packageName: 'com.example.app',
defaultLanguage: 'en-US',
listing: 'store/listing.md',
icon: 'store/icons/play-512.png',
},
apple: {
bundleId: 'com.example.app',
// name: 'Example: Short Tagline', // App Store name (max 30) when app.name is taken there ("already being used"); default app.name
listing: 'store/appstore.md',
// Export compliance (export-compliance skill): decide it, then uncomment. false ONLY if the app uses no encryption
// beyond the OS (HTTPS through the system, standard sign-in).
// usesNonExemptEncryption: false,
// ipad: true,
info: {
categories: { primary: 'PRODUCTIVITY', secondary: 'UTILITIES' },
ageRating: {}, // every content question NONE / false; change what applies (user-generated content, web access ...)
contentRights: 'DOES_NOT_USE_THIRD_PARTY_CONTENT',
free: true,
reviewNotes: [
'Example App keeps lists and notes in sync across devices. Pro (yearly with a 1-week free trial, or monthly) unlocks unlimited lists, sharing and themes.',
'Subscriptions: Settings > Pro (or the paywall after the third list). Restore Purchases is on the paywall and in Settings > Pro.',
'Privacy policy and Terms of Use: on the paywall and in Settings > About.',
// With a remote paywall or feature flags (storebound scan warns, 2.3.1) you may add a line like:
// 'Paywall designs and the prices shown are tested remotely with <SDK>; what Pro unlocks does not change after review.',
// Google Play has no review-notes field: put the same line in reviewAccess.instructions (Play App content >
// App access, 'Any other information') when the app has a sign-in, and say in the full description what needs
// a paid subscription (Play: Deceptive behavior, Payments).
].join('\n\n'),
},
},
// Auto-renewable subscriptions (store-subscriptions skill): `storebound apple subscriptions`, `storebound play
// subscriptions`. Apple: one group, levels rank the plans (1 = highest). Play: one subscription per entry with one
// auto-renewing base plan. Prices: USD base, Apple equalizes from the USA, Play converts per region; overrides set a
// region by hand. The disclosure (price per period, renewal, how to cancel, Terms + Privacy links) belongs on the
// purchase screen and in the descriptions.
subscriptions: [
{
name: 'Example Pro',
listings: { en: { name: 'Example Pro' }, tr: { name: 'Example Pro' } },
subscriptions: [
{
id: 'com.example.app.pro_yearly', period: 'P1Y', level: 1, usd: 29.99,
listings: {
en: { title: 'Pro yearly', description: 'Unlimited lists, sharing and themes.', benefits: ['Unlimited lists', 'Shared lists', 'All themes'] },
tr: { title: 'Pro yıllık', description: 'Sınırsız liste, paylaşım ve tema.', benefits: ['Sınırsız liste', 'Paylaşılan listeler', 'Tüm temalar'] },
},
intro: { type: 'free', duration: 'P1W' }, // 1 week free trial for new subscribers
reviewScreenshot: 'store/iap-review/paywall.png',
},
{
id: 'com.example.app.pro_monthly', period: 'P1M', level: 2, usd: 3.99,
overrides: [{ region: 'TR', currency: 'TRY', price: 99.99 }],
listings: {
en: { title: 'Pro monthly', description: 'Unlimited lists, sharing and themes.', benefits: ['Unlimited lists', 'Shared lists', 'All themes'] },
tr: { title: 'Pro aylık', description: 'Sınırsız liste, paylaşım ve tema.', benefits: ['Sınırsız liste', 'Paylaşılan listeler', 'Tüm temalar'] },
},
reviewScreenshot: 'store/iap-review/paywall.png',
},
],
},
],
// Google Play Data safety. Types not listed = not collected. Typical app with accounts + an analytics SDK acting as
// your processor (collected, not shared). Edit to match what your app and its SDKs really do.
dataSafety: {
collectsData: true,
encryptedInTransit: true,
// Sign-up methods. Google does not publish these ids: compare with Play Console > App content > Data safety >
// Export to CSV before the first import. The delete-account URL (PSL_ACCOUNT_DELETION_URL) comes from
// accounts.deletionUrl; `storebound check` names any other row Play requires.
accountCreation: ['PSL_ACM_USER_ID_PASSWORD'],
// Can people delete their data without deleting the account (a data-only page)? true needs partialDeletionUrl.
// Play's "can users request deletion?" is answered yes by accounts.deletionUrl. Ask the owner.
partialDeletion: false,
data: {
PSL_EMAIL: { shared: false, collect: ['functionality', 'account'] },
PSL_USER_ACCOUNT: { shared: false, collect: ['functionality', 'account'] },
PSL_USER_GENERATED_CONTENT: { shared: false, collect: ['functionality'] },
PSL_USER_INTERACTION: { shared: false, collect: ['analytics'] },
PSL_CRASH_LOGS: { shared: false, collect: ['analytics'] },
PSL_PERFORMANCE_DIAGNOSTICS: { shared: false, collect: ['analytics'] },
PSL_DEVICE_ID: { shared: false, collect: ['analytics'] },
PSL_PURCHASE_HISTORY: { shared: false, collect: ['functionality'] }, // subscription status tied to the account
},
},
renders: 'store/renders',
shots: {
style: 'app', // calm gradient, device frame, headline + subline (game: sparkles and badges)
copy: 'store/shots/copy.json',
frames: 'store/shots/frames', // <lang>/phone/<name>.png, <lang>/tablet/<name>.png
out: 'store/renders',
// fonts: { title: 'Inter', body: 'Inter' }, // the app style default
scenes: [
{ id: 'home', palette: 'mist', frames: [{ src: 'home.png' }] },
{ id: 'detail', palette: 'mint', frames: [{ src: 'detail.png' }] },
{ id: 'share', palette: 'sky', frames: [{ src: 'share.png' }] },
{ id: 'privacy', palette: 'slate', frames: [{ src: 'settings.png' }] },
],
feature: { frame: 'home.png', palette: 'mist' },
// Raw frames from a web build, the iOS Simulator (ios-sim) or an Android emulator (android-emu): sign in to a demo
// account with realistic sample data, never real user data (store-screenshots skill).
capture: {
baseUrl: 'http://127.0.0.1:5173',
query: 'lang={lang}&demo=1',
shots: {
'home': { path: '/', delayMs: 1000, device: 'both' },
'detail': { path: '/lists/1', delayMs: 1000, device: 'both' },
'share': { path: '/lists/1/share', delayMs: 1000, device: 'both' },
'settings': { path: '/settings', delayMs: 1000, device: 'both' },
},
},
},
icons: {
composite: 'store/icon/icon.png',
foreground: 'store/icon/foreground.png',
background: 'store/icon/background.png',
out: 'store/icons',
},
};
export default config;