init --profile app|game|subscription-app (default app): per-profile config, declarations, App Privacy, Data safety, listing and screenshot copy
This commit is contained in:
parent
44699eac8c
commit
7f0bb7c2dd
15 changed files with 872 additions and 5 deletions
30
cli/init.ts
30
cli/init.ts
|
|
@ -1,12 +1,18 @@
|
||||||
// Copies the templates into the current project without overwriting anything, and makes sure .storebound/ is ignored.
|
// Copies the templates into the current project without overwriting anything, and makes sure .storebound/ is ignored.
|
||||||
// storebound init [--dir .]
|
// storebound init [--dir .] [--profile app|game|subscription-app]
|
||||||
|
// Profiles (default app): app = utility / productivity app with an optional account and analytics, no ads;
|
||||||
|
// game = free-to-play game with ads and in-app purchases (the templates storebound shipped before 0.5.0);
|
||||||
|
// subscription-app = accounts plus auto-renewable subscriptions. Each profile has its own config, declarations,
|
||||||
|
// App Privacy answers, Data safety section, listing copy and screenshot copy; every answer is a starting point to edit.
|
||||||
|
// Lookup per file: templates/profiles/<profile>/<file>, then the profile it builds on, then templates/<file>.
|
||||||
import { appendFileSync, copyFileSync, existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
import { appendFileSync, copyFileSync, existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
||||||
import { dirname, join, resolve } from 'node:path';
|
import { dirname, join, resolve } from 'node:path';
|
||||||
import type { Args } from './lib/args';
|
import type { Args } from './lib/args';
|
||||||
|
import { type AppProfile, PROFILES } from './lib/config';
|
||||||
import { log } from './lib/log';
|
import { log } from './lib/log';
|
||||||
|
|
||||||
const T = join(import.meta.dir, '..', 'templates');
|
const T = join(import.meta.dir, '..', 'templates');
|
||||||
const FILES: [string, string][] = [
|
export const FILES: [string, string][] = [
|
||||||
['storebound.config.ts', 'storebound.config.ts'],
|
['storebound.config.ts', 'storebound.config.ts'],
|
||||||
['listing.md', 'store/listing.md'],
|
['listing.md', 'store/listing.md'],
|
||||||
['appstore.md', 'store/appstore.md'],
|
['appstore.md', 'store/appstore.md'],
|
||||||
|
|
@ -18,13 +24,29 @@ const FILES: [string, string][] = [
|
||||||
['testers.csv', '.storebound/testers.csv'],
|
['testers.csv', '.storebound/testers.csv'],
|
||||||
];
|
];
|
||||||
|
|
||||||
|
/** Template folders a profile reads from, most specific first. game = the root templates. */
|
||||||
|
const CHAIN: Record<AppProfile, string[]> = {
|
||||||
|
'game': [T],
|
||||||
|
'app': [join(T, 'profiles', 'app'), T],
|
||||||
|
'subscription-app': [join(T, 'profiles', 'subscription-app'), join(T, 'profiles', 'app'), T],
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Source file of one template for a profile. Pure apart from existsSync. */
|
||||||
|
export function templateFor(profile: AppProfile, name: string): string {
|
||||||
|
for (const dir of CHAIN[profile]) if (existsSync(join(dir, name))) return join(dir, name);
|
||||||
|
throw new Error(`template ${name} not found`);
|
||||||
|
}
|
||||||
|
|
||||||
export async function run(args: Args): Promise<void> {
|
export async function run(args: Args): Promise<void> {
|
||||||
const root = resolve(args.str('dir', '.'));
|
const root = resolve(args.str('dir', '.'));
|
||||||
|
const profile = args.str('profile', 'app') as AppProfile;
|
||||||
|
if (!PROFILES.includes(profile)) throw new Error(`--profile ${profile}: one of ${PROFILES.join(', ')}`);
|
||||||
|
log('init', `profile ${profile} (templates are starting points: edit every answer to match your app)`);
|
||||||
for (const [from, to] of FILES) {
|
for (const [from, to] of FILES) {
|
||||||
const dest = join(root, to);
|
const dest = join(root, to);
|
||||||
if (existsSync(dest)) { log('init', `keep ${to} (exists)`); continue; }
|
if (existsSync(dest)) { log('init', `keep ${to} (exists)`); continue; }
|
||||||
mkdirSync(dirname(dest), { recursive: true });
|
mkdirSync(dirname(dest), { recursive: true });
|
||||||
copyFileSync(join(T, from), dest);
|
copyFileSync(templateFor(profile, from), dest);
|
||||||
log('init', `create ${to}`);
|
log('init', `create ${to}`);
|
||||||
}
|
}
|
||||||
const gi = join(root, '.gitignore');
|
const gi = join(root, '.gitignore');
|
||||||
|
|
@ -34,5 +56,5 @@ export async function run(args: Args): Promise<void> {
|
||||||
else writeFileSync(gi, '# storebound: credentials, fonts, temp files\n.storebound/\n');
|
else writeFileSync(gi, '# storebound: credentials, fonts, temp files\n.storebound/\n');
|
||||||
log('init', '.gitignore: added .storebound/');
|
log('init', '.gitignore: added .storebound/');
|
||||||
}
|
}
|
||||||
log('init', 'next: edit storebound.config.ts and .storebound/credentials.json, then `storebound check`');
|
log('init', 'next: edit storebound.config.ts, store/*.md and .storebound/credentials.json, then `storebound check`');
|
||||||
}
|
}
|
||||||
|
|
|
||||||
29
templates/profiles/app/app-privacy.md
Normal file
29
templates/profiles/app/app-privacy.md
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
# Example App · App Store "App Privacy" answers
|
||||||
|
|
||||||
|
App Store Connect > your app > App Privacy. **Web UI only, there is no API.** Fill it from this table, then press
|
||||||
|
**Publish** (saving alone does not publish it). Keep it consistent with the privacy policy and the Play Data safety
|
||||||
|
answers (`dataSafety` in `storebound.config.ts`). **Edit to match your app.**
|
||||||
|
|
||||||
|
Typical utility / productivity app: optional account (email), content synced to your backend, an analytics SDK
|
||||||
|
acting as your processor, no ads, no tracking.
|
||||||
|
|
||||||
|
**Do you or your third-party partners collect data from this app?** Yes
|
||||||
|
|
||||||
|
**Tracking**: No (no ad SDK, no data broker, nothing combined with other companies' data for advertising). If that
|
||||||
|
changes, add the App Tracking Transparency prompt first.
|
||||||
|
|
||||||
|
| data type | collected | used to track | linked to the user | purposes |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| Contact Info > Email Address | yes (account) | no | yes | App Functionality |
|
||||||
|
| Identifiers > User ID | yes (account id) | no | yes | App Functionality, Analytics |
|
||||||
|
| User Content > Other User Content (lists, notes) | yes (synced) | no | yes | App Functionality |
|
||||||
|
| Usage Data > Product Interaction | yes (analytics) | no | yes if tied to the account, else no | Analytics |
|
||||||
|
| Diagnostics > Crash Data | yes | no | no | App Functionality, Analytics |
|
||||||
|
| Diagnostics > Performance Data | yes | no | no | Analytics |
|
||||||
|
|
||||||
|
"Linked to you" is **Yes** for what you store under the account. Not collected (typical): Location, Health and
|
||||||
|
Fitness, Financial Info, Contacts, Photos or Videos (unless people attach them), Browsing and Search History,
|
||||||
|
Purchases (until you sell something), Sensitive Info.
|
||||||
|
|
||||||
|
Per data type the UI asks, in order: purposes > "Is this data linked to the user's identity?" > "Do you or your
|
||||||
|
third-party partners use this data for tracking purposes?".
|
||||||
67
templates/profiles/app/appstore.md
Normal file
67
templates/profiles/app/appstore.md
Normal file
|
|
@ -0,0 +1,67 @@
|
||||||
|
# Example App · App Store listing copy
|
||||||
|
|
||||||
|
Source for `storebound apple listing`. One `## <ASC locale>` section per locale (the first word is the locale code:
|
||||||
|
`en-US`, `tr`, `de-DE`, `ja`, `zh-Hans`, `ar-SA`, ...), one fenced block per `### <field>`, field names exactly:
|
||||||
|
`name` (optional, defaults to `app.name`), `subtitle` (30), `promotionalText` (170), `keywords` (100 bytes, comma
|
||||||
|
separated, no spaces after commas, do not repeat the app name), `description` (4000), `whatsNew` (optional; not allowed
|
||||||
|
on the first version). Shared values (privacy policy, support and marketing URL) come from `storebound.config.ts`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## en-US · English (U.S.)
|
||||||
|
|
||||||
|
### subtitle
|
||||||
|
```
|
||||||
|
Lists and notes, in sync
|
||||||
|
```
|
||||||
|
|
||||||
|
### promotionalText
|
||||||
|
```
|
||||||
|
One or two sentences you can change any time without a new version.
|
||||||
|
```
|
||||||
|
|
||||||
|
### keywords
|
||||||
|
```
|
||||||
|
todo,checklist,groceries,planner,notes,shared,family,organizer,tasks,sync
|
||||||
|
```
|
||||||
|
|
||||||
|
### description
|
||||||
|
```
|
||||||
|
First paragraph: the job the app does and why it is easier with it.
|
||||||
|
|
||||||
|
FEATURES
|
||||||
|
• Lists and notes that sync across your devices
|
||||||
|
• Share a list with family or friends
|
||||||
|
• Export your data or delete your account in Settings, any time
|
||||||
|
|
||||||
|
Free to use. An account is optional and only needed for sync and sharing.
|
||||||
|
```
|
||||||
|
|
||||||
|
## tr · Türkçe
|
||||||
|
|
||||||
|
### subtitle
|
||||||
|
```
|
||||||
|
Listeler ve notlar, eşitli
|
||||||
|
```
|
||||||
|
|
||||||
|
### promotionalText
|
||||||
|
```
|
||||||
|
Yeni sürüm göndermeden istediğin zaman değiştirebileceğin bir iki cümle.
|
||||||
|
```
|
||||||
|
|
||||||
|
### keywords
|
||||||
|
```
|
||||||
|
yapılacaklar,kontrol,market,planlayıcı,not,paylaşım,aile,düzen,görev
|
||||||
|
```
|
||||||
|
|
||||||
|
### description
|
||||||
|
```
|
||||||
|
İlk paragraf: uygulamanın gördüğü iş ve onunla neden daha kolay olduğu.
|
||||||
|
|
||||||
|
ÖZELLİKLER
|
||||||
|
• Tüm cihazlarında eşitlenen listeler ve notlar
|
||||||
|
• Bir listeyi ailen ya da arkadaşlarınla paylaş
|
||||||
|
• Verilerini dışa aktar ya da hesabını Ayarlar'dan istediğin an sil
|
||||||
|
|
||||||
|
Ücretsiz. Hesap isteğe bağlıdır; yalnızca eşitleme ve paylaşım için gerekir.
|
||||||
|
```
|
||||||
57
templates/profiles/app/declarations.md
Normal file
57
templates/profiles/app/declarations.md
Normal file
|
|
@ -0,0 +1,57 @@
|
||||||
|
# Example App · store declarations (answers to copy into the consoles)
|
||||||
|
|
||||||
|
Starting answers for a typical **utility / productivity app**: an optional account (email + password, or sign in
|
||||||
|
with a provider), an analytics SDK acting as your processor, data synced to your own backend, no ads, no chat with
|
||||||
|
strangers, general audience. **Edit every line to match your app**: these are your statements to the stores. Everything
|
||||||
|
marked **UI** has no API: Play Console > your app > Policy and programs > **App content**, and App Store Connect for
|
||||||
|
Apple. Data safety (Play) has an API: `storebound play data-safety`.
|
||||||
|
|
||||||
|
## Google Play · App content (UI)
|
||||||
|
|
||||||
|
| declaration | answer |
|
||||||
|
|---|---|
|
||||||
|
| Privacy policy | your policy URL (same one in the listing and inside the app) |
|
||||||
|
| App access | "All or some functionality is restricted": add the demo account (username, password) and the steps from `reviewAccess.instructions`; credentials must work at all times, no 2FA / OTP (review-access skill) |
|
||||||
|
| Ads | No, my app does not contain ads |
|
||||||
|
| Content rating | IARC questionnaire, below |
|
||||||
|
| Target audience and content | 18 and over (or 13+ if teens use it); "Could your app unintentionally appeal to children?" No (only if true) |
|
||||||
|
| News app | No |
|
||||||
|
| Data safety | `storebound play data-safety`, then check the summary; with accounts: the delete-account URL (`accounts.deletionUrl`) |
|
||||||
|
| Government apps | No |
|
||||||
|
| Financial features | My app doesn't provide any financial features |
|
||||||
|
| Health apps | No health features |
|
||||||
|
| Advertising ID | No (unless an SDK in your app reads it: check the SDK docs and your merged manifest) |
|
||||||
|
| Store settings | App, category (Productivity, Tools, ...); contact email + website |
|
||||||
|
|
||||||
|
## Account deletion (account-deletion skill)
|
||||||
|
|
||||||
|
- In the app: Settings > Account > Delete account deletes the account and its data (not only "deactivate").
|
||||||
|
- On the web: `accounts.deletionUrl` lets people request deletion without the app; Play asks for it in Data safety.
|
||||||
|
- With subscriptions: say that deleting the account does not cancel the store subscription, and how to cancel.
|
||||||
|
|
||||||
|
## IARC content rating (Play UI; Apple has its own form, `storebound apple appinfo`)
|
||||||
|
|
||||||
|
Category: not a game (e.g. Utility, Productivity, Communication, or Social Networking if people share content with
|
||||||
|
others). Typical answers for a lists / notes app:
|
||||||
|
|
||||||
|
| topic | answer |
|
||||||
|
|---|---|
|
||||||
|
| Violence, sexuality, language, controlled substances | No |
|
||||||
|
| Gambling (simulated / real money) | No |
|
||||||
|
| Users interact or exchange content | Yes only if people can share with others outside their own invitations; then also moderation and reporting |
|
||||||
|
| Shares location with other users | No |
|
||||||
|
| Digital purchases | No (Yes once you sell IAPs or subscriptions) |
|
||||||
|
| Unrestricted internet / browser | No |
|
||||||
|
|
||||||
|
Expected: PEGI 3, ESRB Everyone, IARC 3+. Apple age rating 4+ (all content questions None) unless users share
|
||||||
|
content publicly.
|
||||||
|
|
||||||
|
## App Store (UI-only parts)
|
||||||
|
|
||||||
|
- App record: App Store Connect > Apps > + > New App. No API.
|
||||||
|
- App Privacy: `app-privacy.md`, then **Publish**.
|
||||||
|
- App Review information: `storebound apple review-details` sends the contact, the demo account (from
|
||||||
|
`.storebound/credentials.json`, masked in the log) and the notes.
|
||||||
|
- Export compliance (export-compliance skill): `ITSAppUsesNonExemptEncryption = NO` only if true.
|
||||||
|
- Guideline 5.1.1: the privacy policy link must also be reachable **inside the app**; 5.1.1(v): account deletion
|
||||||
|
inside the app.
|
||||||
61
templates/profiles/app/listing.md
Normal file
61
templates/profiles/app/listing.md
Normal file
|
|
@ -0,0 +1,61 @@
|
||||||
|
# Example App · Google Play listing copy
|
||||||
|
|
||||||
|
Source for `storebound play listing` (title, short + full description) and `storebound play release` (release notes).
|
||||||
|
Rules the parser follows:
|
||||||
|
- every `## ...` section with a `<!-- play: <codes> -->` line is a listing; one section can serve several Play codes
|
||||||
|
- under it, `### ...` headings that contain **title** (optional, defaults to `app.name`), **short**, **full** and
|
||||||
|
**what's new**, each followed by one fenced block
|
||||||
|
- limits (checked before anything is sent): title 30, short 80, full 4000, what's new 500 characters
|
||||||
|
Only claim what the app really does. No prices, no "best / #1", no other apps' names.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## English (en-US)
|
||||||
|
<!-- play: en-US -->
|
||||||
|
|
||||||
|
### Play short description (max 80)
|
||||||
|
```
|
||||||
|
What the app does for people, in one plain line
|
||||||
|
```
|
||||||
|
|
||||||
|
### Play full description (max 4000)
|
||||||
|
```
|
||||||
|
First paragraph: the job the app does and why it is easier with it, in two or three sentences.
|
||||||
|
|
||||||
|
FEATURES
|
||||||
|
• Lists and notes that sync across your devices
|
||||||
|
• Share a list with family or friends
|
||||||
|
• Export your data or delete your account in Settings, any time
|
||||||
|
|
||||||
|
Free to use. An account is optional and only needed for sync and sharing.
|
||||||
|
```
|
||||||
|
|
||||||
|
### What's new (max 500)
|
||||||
|
```
|
||||||
|
First release. Thanks for trying it!
|
||||||
|
```
|
||||||
|
|
||||||
|
## Türkçe (tr-TR)
|
||||||
|
<!-- play: tr-TR -->
|
||||||
|
|
||||||
|
### Play kısa açıklama · short (max 80)
|
||||||
|
```
|
||||||
|
Uygulamanın insanlar için ne yaptığını anlatan sade bir cümle
|
||||||
|
```
|
||||||
|
|
||||||
|
### Play tam açıklama · full (max 4000)
|
||||||
|
```
|
||||||
|
İlk paragraf: uygulamanın gördüğü iş ve onunla neden daha kolay olduğu, iki üç cümlede.
|
||||||
|
|
||||||
|
ÖZELLİKLER
|
||||||
|
• Tüm cihazlarında eşitlenen listeler ve notlar
|
||||||
|
• Bir listeyi ailen ya da arkadaşlarınla paylaş
|
||||||
|
• Verilerini dışa aktar ya da hesabını Ayarlar'dan istediğin an sil
|
||||||
|
|
||||||
|
Ücretsiz. Hesap isteğe bağlıdır; yalnızca eşitleme ve paylaşım için gerekir.
|
||||||
|
```
|
||||||
|
|
||||||
|
### Yenilikler · what's new (max 500)
|
||||||
|
```
|
||||||
|
İlk sürüm. Denediğin için teşekkürler!
|
||||||
|
```
|
||||||
88
templates/profiles/app/privacy-policy.html
Normal file
88
templates/profiles/app/privacy-policy.html
Normal file
|
|
@ -0,0 +1,88 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>Privacy Policy · Example Studio</title>
|
||||||
|
<!--
|
||||||
|
One privacy policy for your apps (profile "app": accounts, synced content, analytics, no ads), with a per-app
|
||||||
|
services table. Host it at a stable URL (https://example.com/privacy) and use that URL in both store listings, in
|
||||||
|
storebound.config.ts and inside each app (Settings > Privacy; App Store guideline 5.1.1). Replace every "Example"
|
||||||
|
value and remove services you do not use. Update the date and the table whenever an app starts collecting something
|
||||||
|
new, then the store declarations. This is a template, not legal advice.
|
||||||
|
-->
|
||||||
|
<style>
|
||||||
|
:root { color-scheme: light dark; --fg: #1b1c20; --bg: #ffffff; --muted: #5d6170; --line: #e3e5ea; --accent: #2563eb; }
|
||||||
|
@media (prefers-color-scheme: dark) { :root { --fg: #eceef3; --bg: #15161a; --muted: #a3a8b6; --line: #2b2d35; --accent: #7aa2ff; } }
|
||||||
|
* { box-sizing: border-box; }
|
||||||
|
body { margin: 0; background: var(--bg); color: var(--fg); font: 16px/1.6 system-ui, -apple-system, Segoe UI, Roboto, sans-serif; }
|
||||||
|
main { max-width: 760px; margin: 0 auto; padding: 32px 16px 64px; }
|
||||||
|
h1 { font-size: 1.8rem; margin: 0 0 4px; } h2 { font-size: 1.2rem; margin-top: 2em; }
|
||||||
|
p.meta { color: var(--muted); margin: 0 0 24px; }
|
||||||
|
a { color: var(--accent); }
|
||||||
|
.table { overflow-x: auto; }
|
||||||
|
table { border-collapse: collapse; width: 100%; font-size: .95rem; }
|
||||||
|
th, td { text-align: left; vertical-align: top; padding: 8px 10px; border-bottom: 1px solid var(--line); }
|
||||||
|
th { font-weight: 600; }
|
||||||
|
nav a { margin-right: 12px; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<main>
|
||||||
|
<nav><a href="#en">English</a><a href="#tr">Türkçe</a></nav>
|
||||||
|
|
||||||
|
<section id="en">
|
||||||
|
<h1>Privacy Policy</h1>
|
||||||
|
<p class="meta">Example Studio · applies to all our apps · last updated 2026-01-01</p>
|
||||||
|
|
||||||
|
<p>We collect what an app needs to work for you, never sell personal data and show no ads. This page explains what
|
||||||
|
each app collects, why, and what you can do about it.</p>
|
||||||
|
|
||||||
|
<h2>What our apps use</h2>
|
||||||
|
<div class="table"><table>
|
||||||
|
<tr><th>App</th><th>Service</th><th>What and why</th><th>Your choice</th></tr>
|
||||||
|
<tr><td>Example App</td><td>Account (our servers)</td><td>Email address, account id and the lists and notes you create: to sign you in and sync your content between your devices.</td><td>The account is optional; export or delete it in Settings > Account</td></tr>
|
||||||
|
<tr><td>Example App</td><td>Analytics (e.g. a privacy-friendly analytics SDK)</td><td>Random install id, screens used, device model, OS version: to improve the app.</td><td>Settings > Analytics off</td></tr>
|
||||||
|
<tr><td>Example App</td><td>Crash reporting</td><td>Crash logs and device model: to fix crashes.</td><td>-</td></tr>
|
||||||
|
<tr><td>Example App</td><td>App stores</td><td>Purchases and subscriptions are handled by Google Play / the App Store; we never see payment details.</td><td>-</td></tr>
|
||||||
|
</table></div>
|
||||||
|
|
||||||
|
<h2>Deleting your account</h2>
|
||||||
|
<p>In the app: Settings > Account > Delete account. Without the app: <a href="https://example.com/delete-account">example.com/delete-account</a>.
|
||||||
|
We delete your account and its content within 30 days; backups roll over within 90 days. A store subscription is
|
||||||
|
not cancelled by deleting the account: cancel it in your Google Play or App Store subscriptions.</p>
|
||||||
|
|
||||||
|
<h2>Children</h2>
|
||||||
|
<p>Our apps are not directed at children under 13. We do not knowingly collect personal data from children. If you
|
||||||
|
believe a child sent us data, contact us and we will delete it.</p>
|
||||||
|
|
||||||
|
<h2>Retention and your rights</h2>
|
||||||
|
<p>We keep account data while the account exists and analytics data for up to 24 months. You can ask for a copy or
|
||||||
|
the deletion of your data by writing to <a href="mailto:privacy@example.com">privacy@example.com</a>; we answer
|
||||||
|
within 30 days.</p>
|
||||||
|
|
||||||
|
<h2>Security</h2>
|
||||||
|
<p>All network traffic of our apps uses HTTPS; passwords are stored only as salted hashes.</p>
|
||||||
|
|
||||||
|
<h2>Changes</h2>
|
||||||
|
<p>When an app starts collecting something new we update this page and its date first.</p>
|
||||||
|
|
||||||
|
<h2>Contact</h2>
|
||||||
|
<p>Example Studio · <a href="mailto:privacy@example.com">privacy@example.com</a></p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section id="tr" lang="tr">
|
||||||
|
<h1>Gizlilik Politikası</h1>
|
||||||
|
<p class="meta">Example Studio · tüm uygulamalarımız için geçerlidir · son güncelleme 2026-01-01</p>
|
||||||
|
<p>Yalnızca uygulamanın sizin için çalışması gereken verileri topluyoruz, kişisel verileri asla satmıyoruz ve reklam
|
||||||
|
göstermiyoruz. Yukarıdaki tablo her uygulamanın neyi neden topladığını gösterir.</p>
|
||||||
|
<h2>Hesabınızı silmek</h2>
|
||||||
|
<p>Uygulamada: Ayarlar > Hesap > Hesabı sil. Uygulama olmadan: <a href="https://example.com/delete-account">example.com/delete-account</a>.
|
||||||
|
Mağaza aboneliği hesap silinince iptal olmaz; Google Play veya App Store aboneliklerinizden iptal edin.</p>
|
||||||
|
<h2>Haklarınız</h2>
|
||||||
|
<p>Verilerinizin bir kopyasını veya silinmesini <a href="mailto:privacy@example.com">privacy@example.com</a> adresine
|
||||||
|
yazarak isteyebilirsiniz; 30 gün içinde yanıtlıyoruz.</p>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
14
templates/profiles/app/shots-copy.json
Normal file
14
templates/profiles/app/shots-copy.json
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
{
|
||||||
|
"en": {
|
||||||
|
"home": { "eyebrow": "Example App", "title": ["Everything in order,", "in one calm place"], "sub": "Lists and notes, synced on all your devices." },
|
||||||
|
"detail": { "eyebrow": "Get it done", "title": ["See what is left", "at a glance"], "sub": "Tap to tick, swipe to reorder." },
|
||||||
|
"share": { "eyebrow": "Together", "title": ["Share a list,", "plan together"], "sub": "Invite people by link and edit live." },
|
||||||
|
"privacy": { "eyebrow": "Your data", "title": ["Private by default,", "yours to export"], "sub": "Export your data or delete your account in Settings." }
|
||||||
|
},
|
||||||
|
"tr": {
|
||||||
|
"home": { "eyebrow": "Example App", "title": ["Her şey düzenli,", "tek sakin bir yerde"], "sub": "Listeler ve notlar, tüm cihazlarında eşitlenir." },
|
||||||
|
"detail": { "eyebrow": "Bitir", "title": ["Kalanları", "bir bakışta gör"], "sub": "Dokun işaretle, sürükle sırala." },
|
||||||
|
"share": { "eyebrow": "Birlikte", "title": ["Listeyi paylaş,", "birlikte planla"], "sub": "Bağlantıyla davet et, canlı düzenleyin." },
|
||||||
|
"privacy": { "eyebrow": "Verilerin", "title": ["Varsayılan olarak gizli,", "dışa aktarması senin"], "sub": "Verilerini dışa aktar ya da hesabını Ayarlar'dan sil." }
|
||||||
|
}
|
||||||
|
}
|
||||||
130
templates/profiles/app/storebound.config.ts
Normal file
130
templates/profiles/app/storebound.config.ts
Normal file
|
|
@ -0,0 +1,130 @@
|
||||||
|
// 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
|
||||||
|
},
|
||||||
|
|
||||||
|
// 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',
|
||||||
|
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 question that follows: add it under `extra` with
|
||||||
|
// the exact id from that export, e.g. { question: '<id from the export>', value: 'https://example.com/delete-account' }.
|
||||||
|
accountCreation: ['PSL_ACM_USER_ID_PASSWORD'],
|
||||||
|
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;
|
||||||
30
templates/profiles/subscription-app/app-privacy.md
Normal file
30
templates/profiles/subscription-app/app-privacy.md
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
# Example App · App Store "App Privacy" answers
|
||||||
|
|
||||||
|
App Store Connect > your app > App Privacy. **Web UI only, there is no API.** Fill it from this table, then press
|
||||||
|
**Publish** (saving alone does not publish it). Keep it consistent with the privacy policy and the Play Data safety
|
||||||
|
answers (`dataSafety` in `storebound.config.ts`). **Edit to match your app.**
|
||||||
|
|
||||||
|
Typical subscription app: account (email), content synced to your backend, auto-renewable subscriptions checked by
|
||||||
|
your server, an analytics SDK acting as your processor, no ads, no tracking.
|
||||||
|
|
||||||
|
**Do you or your third-party partners collect data from this app?** Yes
|
||||||
|
|
||||||
|
**Tracking**: No (no ad SDK, no data broker, nothing combined with other companies' data for advertising). If that
|
||||||
|
changes, add the App Tracking Transparency prompt first.
|
||||||
|
|
||||||
|
| data type | collected | used to track | linked to the user | purposes |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| Contact Info > Email Address | yes (account) | no | yes | App Functionality |
|
||||||
|
| Identifiers > User ID | yes (account id) | no | yes | App Functionality, Analytics |
|
||||||
|
| User Content > Other User Content (lists, notes) | yes (synced) | no | yes | App Functionality |
|
||||||
|
| Usage Data > Product Interaction | yes (analytics) | no | yes if tied to the account, else no | Analytics |
|
||||||
|
| Purchases > Purchase History (subscription status) | yes | no | yes | App Functionality |
|
||||||
|
| Diagnostics > Crash Data | yes | no | no | App Functionality, Analytics |
|
||||||
|
| Diagnostics > Performance Data | yes | no | no | Analytics |
|
||||||
|
|
||||||
|
"Linked to you" is **Yes** for what you store under the account. Not collected (typical): Location, Health and
|
||||||
|
Fitness, Financial Info, Contacts, Photos or Videos (unless people attach them), Browsing and Search History,
|
||||||
|
Sensitive Info.
|
||||||
|
|
||||||
|
Per data type the UI asks, in order: purposes > "Is this data linked to the user's identity?" > "Do you or your
|
||||||
|
third-party partners use this data for tracking purposes?".
|
||||||
73
templates/profiles/subscription-app/appstore.md
Normal file
73
templates/profiles/subscription-app/appstore.md
Normal file
|
|
@ -0,0 +1,73 @@
|
||||||
|
# Example App · App Store listing copy
|
||||||
|
|
||||||
|
Source for `storebound apple listing`. One `## <ASC locale>` section per locale (the first word is the locale code:
|
||||||
|
`en-US`, `tr`, `de-DE`, `ja`, `zh-Hans`, `ar-SA`, ...), one fenced block per `### <field>`, field names exactly:
|
||||||
|
`name` (optional, defaults to `app.name`), `subtitle` (30), `promotionalText` (170), `keywords` (100 bytes, comma
|
||||||
|
separated, no spaces after commas, do not repeat the app name), `description` (4000), `whatsNew` (optional; not allowed
|
||||||
|
on the first version). Shared values (privacy policy, support and marketing URL) come from `storebound.config.ts`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## en-US · English (U.S.)
|
||||||
|
|
||||||
|
### subtitle
|
||||||
|
```
|
||||||
|
Lists and notes, in sync
|
||||||
|
```
|
||||||
|
|
||||||
|
### promotionalText
|
||||||
|
```
|
||||||
|
One or two sentences you can change any time without a new version.
|
||||||
|
```
|
||||||
|
|
||||||
|
### keywords
|
||||||
|
```
|
||||||
|
todo,checklist,groceries,planner,notes,shared,family,organizer,tasks,sync
|
||||||
|
```
|
||||||
|
|
||||||
|
### description
|
||||||
|
```
|
||||||
|
First paragraph: the job the app does and why it is easier with it.
|
||||||
|
|
||||||
|
FEATURES
|
||||||
|
• Lists and notes that sync across your devices
|
||||||
|
• Share a list with family or friends
|
||||||
|
• Export your data or delete your account in Settings, any time
|
||||||
|
|
||||||
|
Free to use with up to 3 lists. Example Pro unlocks unlimited lists, sharing and themes: yearly with a 1-week free trial, or monthly. Payment is charged to your store account; the subscription renews automatically unless cancelled at least 24 hours before the end of the period, and you can manage or cancel it in your account settings.
|
||||||
|
|
||||||
|
Terms of Use: https://example.com/terms
|
||||||
|
Privacy Policy: https://example.com/privacy
|
||||||
|
```
|
||||||
|
|
||||||
|
## tr · Türkçe
|
||||||
|
|
||||||
|
### subtitle
|
||||||
|
```
|
||||||
|
Listeler ve notlar, eşitli
|
||||||
|
```
|
||||||
|
|
||||||
|
### promotionalText
|
||||||
|
```
|
||||||
|
Yeni sürüm göndermeden istediğin zaman değiştirebileceğin bir iki cümle.
|
||||||
|
```
|
||||||
|
|
||||||
|
### keywords
|
||||||
|
```
|
||||||
|
yapılacaklar,kontrol,market,planlayıcı,not,paylaşım,aile,düzen,görev
|
||||||
|
```
|
||||||
|
|
||||||
|
### description
|
||||||
|
```
|
||||||
|
İlk paragraf: uygulamanın gördüğü iş ve onunla neden daha kolay olduğu.
|
||||||
|
|
||||||
|
ÖZELLİKLER
|
||||||
|
• Tüm cihazlarında eşitlenen listeler ve notlar
|
||||||
|
• Bir listeyi ailen ya da arkadaşlarınla paylaş
|
||||||
|
• Verilerini dışa aktar ya da hesabını Ayarlar'dan istediğin an sil
|
||||||
|
|
||||||
|
3 listeye kadar ücretsiz. Example Pro sınırsız liste, paylaşım ve tema açar: 1 hafta ücretsiz denemeli yıllık ya da aylık. Ödeme mağaza hesabınızdan alınır; abonelik, dönem bitmeden en az 24 saat önce iptal edilmezse otomatik yenilenir; hesap ayarlarınızdan yönetebilir ya da iptal edebilirsiniz.
|
||||||
|
|
||||||
|
Kullanım Koşulları: https://example.com/terms
|
||||||
|
Gizlilik Politikası: https://example.com/privacy
|
||||||
|
```
|
||||||
69
templates/profiles/subscription-app/declarations.md
Normal file
69
templates/profiles/subscription-app/declarations.md
Normal file
|
|
@ -0,0 +1,69 @@
|
||||||
|
# Example App · store declarations (answers to copy into the consoles)
|
||||||
|
|
||||||
|
Starting answers for a typical **subscription app**: auto-renewable subscriptions (free trial on the yearly plan), an account (email + password, or sign in
|
||||||
|
with a provider), an analytics SDK acting as your processor, data synced to your own backend, no ads, no chat with
|
||||||
|
strangers, general audience. **Edit every line to match your app**: these are your statements to the stores. Everything
|
||||||
|
marked **UI** has no API: Play Console > your app > Policy and programs > **App content**, and App Store Connect for
|
||||||
|
Apple. Data safety (Play) has an API: `storebound play data-safety`.
|
||||||
|
|
||||||
|
## Google Play · App content (UI)
|
||||||
|
|
||||||
|
| declaration | answer |
|
||||||
|
|---|---|
|
||||||
|
| Privacy policy | your policy URL (same one in the listing and inside the app) |
|
||||||
|
| App access | "All or some functionality is restricted": add the demo account (username, password) and the steps from `reviewAccess.instructions`; credentials must work at all times, no 2FA / OTP (review-access skill) |
|
||||||
|
| Ads | No, my app does not contain ads |
|
||||||
|
| Content rating | IARC questionnaire, below |
|
||||||
|
| Target audience and content | 18 and over (or 13+ if teens use it); "Could your app unintentionally appeal to children?" No (only if true) |
|
||||||
|
| News app | No |
|
||||||
|
| Data safety | `storebound play data-safety`, then check the summary; with accounts: the delete-account URL (`accounts.deletionUrl`) |
|
||||||
|
| Government apps | No |
|
||||||
|
| Financial features | My app doesn't provide any financial features |
|
||||||
|
| Health apps | No health features |
|
||||||
|
| Advertising ID | No (unless an SDK in your app reads it: check the SDK docs and your merged manifest) |
|
||||||
|
| Store settings | App, category (Productivity, Tools, ...); contact email + website |
|
||||||
|
|
||||||
|
## Account deletion (account-deletion skill)
|
||||||
|
|
||||||
|
- In the app: Settings > Account > Delete account deletes the account and its data (not only "deactivate").
|
||||||
|
- On the web: `accounts.deletionUrl` lets people request deletion without the app; Play asks for it in Data safety.
|
||||||
|
- With subscriptions: say that deleting the account does not cancel the store subscription, and how to cancel.
|
||||||
|
|
||||||
|
## IARC content rating (Play UI; Apple has its own form, `storebound apple appinfo`)
|
||||||
|
|
||||||
|
Category: not a game (e.g. Utility, Productivity, Communication, or Social Networking if people share content with
|
||||||
|
others). Typical answers for a lists / notes app:
|
||||||
|
|
||||||
|
| topic | answer |
|
||||||
|
|---|---|
|
||||||
|
| Violence, sexuality, language, controlled substances | No |
|
||||||
|
| Gambling (simulated / real money) | No |
|
||||||
|
| Users interact or exchange content | Yes only if people can share with others outside their own invitations; then also moderation and reporting |
|
||||||
|
| Shares location with other users | No |
|
||||||
|
| Digital purchases | Yes (subscriptions) |
|
||||||
|
| Unrestricted internet / browser | No |
|
||||||
|
|
||||||
|
Expected: PEGI 3, ESRB Everyone, IARC 3+. Apple age rating 4+ (all content questions None) unless users share
|
||||||
|
content publicly.
|
||||||
|
|
||||||
|
## App Store (UI-only parts)
|
||||||
|
|
||||||
|
- App record: App Store Connect > Apps > + > New App. No API.
|
||||||
|
- App Privacy: `app-privacy.md`, then **Publish**.
|
||||||
|
- App Review information: `storebound apple review-details` sends the contact, the demo account (from
|
||||||
|
`.storebound/credentials.json`, masked in the log) and the notes.
|
||||||
|
- Export compliance (export-compliance skill): `ITSAppUsesNonExemptEncryption = NO` only if true.
|
||||||
|
- Guideline 5.1.1: the privacy policy link must also be reachable **inside the app**; 5.1.1(v): account deletion
|
||||||
|
inside the app.
|
||||||
|
|
||||||
|
## Subscriptions (store-subscriptions skill)
|
||||||
|
|
||||||
|
- Purchase screen (both stores): plan name, length, the full renewal price per period (most prominent), what is
|
||||||
|
included, free trial length and what is charged after it, how to cancel, Restore Purchases, links to the Terms of Use
|
||||||
|
and the Privacy Policy. Apple 3.1.2; Google Play subscriptions policy.
|
||||||
|
- Metadata: the App Store description (or a custom EULA in App Information > License Agreement) links the Terms of
|
||||||
|
Use; the Privacy Policy URL field is set by `storebound apple listing`.
|
||||||
|
- First subscriptions are submitted together with an app version (App Store Connect: add them to the version on its
|
||||||
|
page before Submit for Review), like first in-app purchases.
|
||||||
|
- App Review notes say where the paywall is and that sandbox purchases work with the demo account.
|
||||||
|
- Play: base plans and offers must be active before people can buy; new subscribers see the converted regional prices.
|
||||||
67
templates/profiles/subscription-app/listing.md
Normal file
67
templates/profiles/subscription-app/listing.md
Normal file
|
|
@ -0,0 +1,67 @@
|
||||||
|
# Example App · Google Play listing copy
|
||||||
|
|
||||||
|
Source for `storebound play listing` (title, short + full description) and `storebound play release` (release notes).
|
||||||
|
Rules the parser follows:
|
||||||
|
- every `## ...` section with a `<!-- play: <codes> -->` line is a listing; one section can serve several Play codes
|
||||||
|
- under it, `### ...` headings that contain **title** (optional, defaults to `app.name`), **short**, **full** and
|
||||||
|
**what's new**, each followed by one fenced block
|
||||||
|
- limits (checked before anything is sent): title 30, short 80, full 4000, what's new 500 characters
|
||||||
|
Only claim what the app really does. No prices, no "best / #1", no other apps' names.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## English (en-US)
|
||||||
|
<!-- play: en-US -->
|
||||||
|
|
||||||
|
### Play short description (max 80)
|
||||||
|
```
|
||||||
|
What the app does for people, in one plain line
|
||||||
|
```
|
||||||
|
|
||||||
|
### Play full description (max 4000)
|
||||||
|
```
|
||||||
|
First paragraph: the job the app does and why it is easier with it, in two or three sentences.
|
||||||
|
|
||||||
|
FEATURES
|
||||||
|
• Lists and notes that sync across your devices
|
||||||
|
• Share a list with family or friends
|
||||||
|
• Export your data or delete your account in Settings, any time
|
||||||
|
|
||||||
|
Free to use with up to 3 lists. Example Pro unlocks unlimited lists, sharing and themes: yearly with a 1-week free trial, or monthly. Payment is charged to your store account; the subscription renews automatically unless cancelled at least 24 hours before the end of the period, and you can manage or cancel it in your account settings.
|
||||||
|
|
||||||
|
Terms of Use: https://example.com/terms
|
||||||
|
Privacy Policy: https://example.com/privacy
|
||||||
|
```
|
||||||
|
|
||||||
|
### What's new (max 500)
|
||||||
|
```
|
||||||
|
First release. Thanks for trying it!
|
||||||
|
```
|
||||||
|
|
||||||
|
## Türkçe (tr-TR)
|
||||||
|
<!-- play: tr-TR -->
|
||||||
|
|
||||||
|
### Play kısa açıklama · short (max 80)
|
||||||
|
```
|
||||||
|
Uygulamanın insanlar için ne yaptığını anlatan sade bir cümle
|
||||||
|
```
|
||||||
|
|
||||||
|
### Play tam açıklama · full (max 4000)
|
||||||
|
```
|
||||||
|
İlk paragraf: uygulamanın gördüğü iş ve onunla neden daha kolay olduğu, iki üç cümlede.
|
||||||
|
|
||||||
|
ÖZELLİKLER
|
||||||
|
• Tüm cihazlarında eşitlenen listeler ve notlar
|
||||||
|
• Bir listeyi ailen ya da arkadaşlarınla paylaş
|
||||||
|
• Verilerini dışa aktar ya da hesabını Ayarlar'dan istediğin an sil
|
||||||
|
|
||||||
|
3 listeye kadar ücretsiz. Example Pro sınırsız liste, paylaşım ve tema açar: 1 hafta ücretsiz denemeli yıllık ya da aylık. Ödeme mağaza hesabınızdan alınır; abonelik, dönem bitmeden en az 24 saat önce iptal edilmezse otomatik yenilenir; hesap ayarlarınızdan yönetebilir ya da iptal edebilirsiniz.
|
||||||
|
|
||||||
|
Kullanım Koşulları: https://example.com/terms
|
||||||
|
Gizlilik Politikası: https://example.com/privacy
|
||||||
|
```
|
||||||
|
|
||||||
|
### Yenilikler · what's new (max 500)
|
||||||
|
```
|
||||||
|
İlk sürüm. Denediğin için teşekkürler!
|
||||||
|
```
|
||||||
159
templates/profiles/subscription-app/storebound.config.ts
Normal file
159
templates/profiles/subscription-app/storebound.config.ts
Normal file
|
|
@ -0,0 +1,159 @@
|
||||||
|
// 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 = {
|
||||||
|
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
|
||||||
|
},
|
||||||
|
|
||||||
|
// 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',
|
||||||
|
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.',
|
||||||
|
].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,
|
||||||
|
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 question that follows: add it under `extra` with
|
||||||
|
// the exact id from that export, e.g. { question: '<id from the export>', value: 'https://example.com/delete-account' }.
|
||||||
|
accountCreation: ['PSL_ACM_USER_ID_PASSWORD'],
|
||||||
|
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;
|
||||||
|
|
@ -6,6 +6,7 @@ import type { ShipConfig } from 'storebound';
|
||||||
const config: ShipConfig = {
|
const config: ShipConfig = {
|
||||||
app: {
|
app: {
|
||||||
name: 'Example Game',
|
name: 'Example Game',
|
||||||
|
profile: 'game',
|
||||||
copyright: '2026 Example Studio',
|
copyright: '2026 Example Studio',
|
||||||
privacyPolicyUrl: 'https://example.com/privacy',
|
privacyPolicyUrl: 'https://example.com/privacy',
|
||||||
supportUrl: 'https://example.com',
|
supportUrl: 'https://example.com',
|
||||||
|
|
|
||||||
|
|
@ -12,5 +12,5 @@
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"resolveJsonModule": true
|
"resolveJsonModule": true
|
||||||
},
|
},
|
||||||
"include": ["cli", "tests", "tools", "templates/storebound.config.ts", "examples/demo-game/storebound.config.ts", "examples/demo-game/serve.ts", "media/teaser/render.ts"]
|
"include": ["cli", "tests", "tools", "templates/storebound.config.ts", "examples/demo-game/storebound.config.ts", "examples/demo-game/serve.ts", "examples/demo-app/storebound.config.ts", "examples/demo-app/serve.ts", "templates/profiles/*/storebound.config.ts", "media/teaser/render.ts"]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue