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

138 lines
6.8 KiB
TypeScript

// storebound config for one app (profile "app": a utility / productivity app with an optional account and 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 = {
app: {
name: 'Example App',
profile: 'app',
copyright: '2026 Example Studio',
privacyPolicyUrl: 'https://example.com/privacy',
// termsUrl: 'https://example.com/terms', // Terms of Use; required once you sell subscriptions
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. An account is optional on the first screen.',
'Privacy policy and Terms: Settings > About.',
].join('\n\n'),
},
},
// In-app purchases (optional): a one-time "Pro" unlock, for example. Subscriptions: see the subscription-app profile
// (storebound init --profile subscription-app) or the store-subscriptions skill.
// products: [{ id: 'com.example.app.pro', type: 'non_consumable', usd: 4.99,
// listings: { en: { title: 'Pro', description: 'Unlimited lists and themes.' } }, reviewScreenshot: 'store/iap-review/pro.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,
deletionRequest: 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'],
// Play asks this once there are accounts (PSL_SUPPORT_DATA_DELETION_BY_USER): can people delete some or all of their
// data without deleting the account? Ask the owner. true also needs a page: partialDeletionUrl (default accounts.deletionUrl).
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'] },
},
},
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;