Onboarding live apps without owner questions for what the stores and the code prove: storebound onboard imports the live products and subscriptions with USD / base prices (catalog diff clean), contact values, the App Review contact and demo account (credentials.json, masked), sign-in for review from Apple's review detail and auth code, and the account deletion page from the app's site; App Store keywords count characters (live fields up to 265 bytes accepted); validate accepts the 6.5-inch iPhone set and Play's default-language graphics fallback; Play Reporting API off gives the project's enable link and apps list falls back to local apps; accounts default <profile>; store-only apps (apps add --store --app-id) shown read-only in the desktop (pitfalls 43-52)

This commit is contained in:
CRTkafa 2026-10-13 07:25:01 +08:00
parent d2ce185441
commit e5f148e534
55 changed files with 1648 additions and 158 deletions

View file

@ -90,7 +90,9 @@ class Sidecar {
// The mocked host: what src-tauri/src/commands.rs answers for one saved state
interface Project { id: string; path: string; name: string; playAccount: string | null; appleAccount: string | null; added: string }
interface HostFixture { projects_list: Project[]; accounts_list: { id: string }[]; workspace_get: { overrides: { ignored: string[] } } & Record<string, unknown> }
interface HostFixture { projects_list: Project[]; accounts_list: { id: string }[]; workspace_get: { overrides: { ignored: string[] } } & Record<string, unknown>;
/** the host's answer before any store read: the machine's store-only apps (projects.json storeApps) under their account */
store_apps_cached?: { account: string; entry: unknown }[] }
const isDir = (p: string): boolean => { try { return statSync(p).isDirectory(); } catch { return false; } };
@ -115,7 +117,7 @@ function host(fx: HostFixture, sidecar: Sidecar, unmocked: Set<string>, logged:
case 'projects_list': return projects();
case 'accounts_list': return fx.accounts_list;
case 'workspace_get': return fx.workspace_get;
case 'store_apps_cached': return fx.accounts_list.map(x => ({ account: x.id, entry: null }));
case 'store_apps_cached': return fx.store_apps_cached ?? fx.accounts_list.map(x => ({ account: x.id, entry: null }));
case 'cache_get': {
const st = live && STORE_READS[String(a.method)];
return st ? { value: toolResult(String(a.method), live![st]), at: new Date(Date.now() - 5 * 60_000).toISOString() } : null;
@ -328,6 +330,17 @@ async function runCase(browser: Browser, name: string, sidecar: Sidecar, o: { br
notes.push(`Accounts: ${accts.map(a => a.profile ?? '(keychain)').join(', ')}`);
} else problems.push('no Accounts entry in the sidebar');
}
// store-only apps of the machine list (projects.json storeApps): in the portfolio, opened as "Store only", no folder
for (const e of fx.store_apps_cached ?? []) for (const app of ((e.entry as { value?: { apps?: { name: string; registered?: boolean }[] } } | null)?.value?.apps ?? []).filter(x => x.registered)) {
const item = page.locator('nav.side .nav-app', { hasText: app.name }).first();
if (!(await item.count())) { problems.push(`${app.name} (store-only entry): not in the sidebar`); continue; }
await item.click({ timeout: WAIT_MS });
await settle(page);
const text = await page.locator('#content').innerText();
await check(app.name);
if (!/Store only/i.test(text)) problems.push(`${app.name}: not shown as "Store only" (${text.replace(/\s+/g, ' ').slice(0, 200)})`);
else notes.push(`${app.name}: store-only entry, no local project`);
}
// a folder that pins another storebound version says so on its app page
for (const p of fx.projects_list.filter(x => (x as { storeboundPin?: string }).storeboundPin && isDir(x.path))) {
const pin = (p as { storeboundPin?: string }).storeboundPin!;