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

@ -10,10 +10,14 @@
// contact and demo account stay in the project
// storebound accounts use <profile> [--dir <project>] this project uses that profile ("account" in its
// .storebound/credentials.json)
// storebound accounts default <profile> | --clear the machine's default profile (a pointer file in the
// credentials folder). Order: env > a profile the project
// names > the project's own keys > this default > the only
// profile (or the one named "default")
import { resolve } from 'node:path';
import type { Args } from './lib/args';
import { applyImport, describeProfiles, planImport } from './lib/accounts';
import { activeProfile, profileDir } from './lib/credentials';
import { activeProfile, defaultPointer, defaultProfile, profileDir, setDefaultPointer } from './lib/credentials';
import { confirm, type Ctx } from './lib/ctx';
import { textTable } from './lib/table';
@ -26,14 +30,17 @@ export async function run(args: Args): Promise<void> {
const rows = describeProfiles();
let active = null;
try { active = activeProfile(dir); } catch { /* unreadable project file */ }
if (args.has('json')) { console.log(JSON.stringify({ folder: tilde(profileDir()), profiles: rows, thisFolder: active }, null, 2)); return; }
const def = defaultProfile();
const pointer = defaultPointer();
if (args.has('json')) { console.log(JSON.stringify({ folder: tilde(profileDir()), profiles: rows.map(r => ({ ...r, default: r.profile === def })), default: def, defaultFrom: def ? (def === pointer ? 'pointer' : rows.length === 1 ? 'only' : 'named-default') : null, thisFolder: active }, null, 2)); return; }
if (!rows.length) {
console.log(`no store accounts on this machine yet (${tilde(profileDir())}).\n From a project that has its keys in .storebound/credentials.json: storebound accounts import\n Or add them in the desktop app (Accounts).`);
return;
}
console.log(textTable(['profile', 'App Store Connect', 'Google Play', 'vendor no.'], rows.map(r => [r.profile, r.error ? `unreadable: ${r.error}` : r.apple ? 'key' : '-', r.play ? r.playAccount ?? 'service account' : '-', r.vendor ? 'yes' : '-'])));
console.log(textTable(['profile', 'App Store Connect', 'Google Play', 'vendor no.', 'default'], rows.map(r => [r.profile, r.error ? `unreadable: ${r.error}` : r.apple ? 'key' : '-', r.play ? r.playAccount ?? 'service account' : '-', r.vendor ? 'yes' : '-', r.profile === def ? (def === pointer ? 'yes (accounts default)' : 'yes') : ''])));
if (pointer && pointer !== def) console.log(`note: the default pointer names "${pointer}", which is not a profile here (storebound accounts default <profile>)`);
console.log(`\n${tilde(profileDir())}: shared by the CLI and the desktop app`);
console.log(active ? `this folder uses "${active.name}" (${{ env: '--account / STOREBOUND_PROFILE', config: 'account in the config', project: '"account" in .storebound/credentials.json', default: 'the default profile' }[active.from]})` : 'this folder uses no profile (several profiles and none named "default": storebound accounts use <profile>)');
console.log(active ? `this folder uses "${active.name}" (${{ env: '--account / STOREBOUND_PROFILE', config: 'account in the config', project: '"account" in .storebound/credentials.json', default: 'the default profile' }[active.from]})` : 'this folder uses no profile (several profiles and no default: storebound accounts default <profile>, or accounts use <profile> in the project)');
return;
}
if (sub === 'import') {
@ -61,5 +68,13 @@ export async function run(args: Args): Promise<void> {
console.log(`${tilde(f)}: "account": "${name}"`);
return;
}
throw new Error('usage: storebound accounts [list | import [--dir <project>] [--name <profile>] [--dry] [--yes] | use <profile>]');
if (sub === 'default') {
if (args.has('clear')) { setDefaultPointer(null); console.log('default profile pointer removed (default: the only profile, or the one named "default")'); return; }
const name = args._[1];
if (!name) { const d = defaultProfile(); console.log(d ? `default profile: ${d}${d === defaultPointer() ? ' (accounts default)' : ''}` : 'no default profile (storebound accounts default <profile>)'); return; }
const f = setDefaultPointer(name);
console.log(`${tilde(f)}: default profile "${name}" (used by every folder that names no profile and has no keys of its own)`);
return;
}
throw new Error('usage: storebound accounts [list | import [--dir <project>] [--name <profile>] [--dry] [--yes] | use <profile> | default <profile> | default --clear]');
}