0.7.0: version (package, plugin and extension manifests), changelog section with summary; check no longer counts owner-only App content steps (IARC Terms, demo account typing) as missing config answers; the check shot shows a ready project (init plus icons, apple.name, App content answers, demo account): all good

This commit is contained in:
CRTkafa 2026-10-13 04:19:39 +08:00
parent 36a8bf9625
commit ba8cfd2594
11 changed files with 69 additions and 19 deletions

View file

@ -13,7 +13,7 @@
// bun tools/site-term-shots.ts --text only the .txt files (no browser)
// bun tools/site-term-shots.ts scan check only these shots
import { generateKeyPairSync } from 'node:crypto';
import { existsSync, mkdirSync, mkdtempSync, readFileSync, realpathSync, rmSync, statSync, writeFileSync } from 'node:fs';
import { copyFileSync, existsSync, mkdirSync, mkdtempSync, readFileSync, realpathSync, rmSync, statSync, writeFileSync } from 'node:fs';
import { homedir, tmpdir } from 'node:os';
import { join } from 'node:path';
import { closeBrowser, launch } from '../cli/shots/browser';
@ -74,6 +74,38 @@ const PRODUCTS = ` products: [
],
dataSafety: {`;
/** What the owner adds to a fresh `storebound init` project before the first release, so that `check` passes:
* the icon artwork (examples/demo-game's real icon files), the App Store name with a suffix, the Play App content
* answers (the batch `console plan play-app-content` asks for) and the App Review demo account in
* .storebound/credentials.json (placeholder values). Returns a function that puts the project back as init wrote it. */
export function makeReady(dir: string): () => void {
const cfg = join(dir, 'storebound.config.ts'), creds = join(dir, '.storebound', 'credentials.json');
const src = readFileSync(cfg, 'utf8'), credSrc = readFileSync(creds, 'utf8');
for (const anchor of [" listing: 'store/appstore.md',\n", ' renders: ']) if (!src.includes(anchor)) throw new Error(`the init config has no ${JSON.stringify(anchor)} line`);
writeFileSync(cfg, src
.replace(" listing: 'store/appstore.md',\n", " listing: 'store/appstore.md',\n name: 'Example App: Lists & Notes',\n")
.replace(' renders: ', `${READY_CONSOLE}\n renders: `));
const c = JSON.parse(credSrc) as Record<string, unknown>;
writeFileSync(creds, JSON.stringify({ ...c, reviewDemoAccount: { username: 'demo@example.com', password: 'placeholder-demo-password' } }, null, 2));
const icons = join(dir, 'store', 'icon');
mkdirSync(icons, { recursive: true });
for (const f of ['icon.png', 'foreground.png', 'background.png']) copyFileSync(join(DEMO, 'store', 'icon', f), join(icons, f));
return () => { writeFileSync(cfg, src); writeFileSync(creds, credSrc); rmSync(icons, { recursive: true, force: true }); };
}
/** The Play App content answers of a plain utility app without ads, for everyone 18+ (config `console`). */
const READY_CONSOLE = ` console: {
contentRating: {
category: 'other',
answers: { violence: false, fear: false, sexuality: false, language: false, controlledSubstances: false, crudeHumor: false, gambling: false,
simulatedGambling: false, lootBoxes: false, userInteraction: false, sharesLocation: false, digitalPurchases: false, unrestrictedInternet: false },
details: { miscellaneous: { naziSymbols: false, koreaHistory: false, terrorism: false, crimes: false } },
},
targetAudience: { ages: ['18-over'], appealsToChildren: false, containsAds: false },
appContent: { government: false, financialFeatures: false, healthFeatures: false },
},
`;
// --- cleaning --------------------------------------------------------------------------------------------------------
const ANSI = /\x1b\[[0-9;?]*[ -/]*[@-~]/g;
@ -171,8 +203,12 @@ const SHOTS: Shot[] = [
},
{
name: 'check', cmd: 'storebound check', title: 'my-app',
codes: [1], markers: ['config ok', /^error: \d+ problems?$/],
run: async p => ({ ...await run(['check'], p.dir), paths: [[p.dir, '']] }),
codes: [0], markers: ['config ok', 'all good'],
run: async p => {
const restore = makeReady(p.dir);
try { return { ...await run(['check'], p.dir, { REVIEW_DEMO_USER: '', REVIEW_DEMO_PASSWORD: '' }), paths: [[p.dir, '']] }; }
finally { restore(); }
},
image: lines => head(lines.map(short), 24, 'lines'),
},
{