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

@ -6,7 +6,8 @@
//
// Which profile a project uses (first hit): --account <name> / env STOREBOUND_PROFILE (set but empty = none), the
// config's `account`, "account" in <project>/.storebound/credentials.json (written by `storebound accounts import`),
// else the default: the only profile on this machine, or the one named "default".
// else the default: the one `storebound accounts default <name>` set (the pointer file <credentials>/default-profile),
// else the only profile on this machine, else the one named "default".
//
// One value, first hit wins:
// 1. environment: PLAY_SERVICE_ACCOUNT (path to the JSON key, or the JSON itself), ASC_KEY_ID, ASC_ISSUER_ID,
@ -22,7 +23,7 @@
// A profile file: { "ASC_KEY_ID", "ASC_ISSUER_ID", "ASC_P8", "PLAY_SERVICE_ACCOUNT", "ASC_VENDOR_NUMBER", "label"? };
// key paths relative to the credentials folder (`accounts import` puts the key files in <name>/, mode 0600), or env
// STOREBOUND_PROFILE_<NAME>_<KEY> (NAME upper case, other characters as _). storebound.workspace.json names profiles too.
import { chmodSync, existsSync, mkdtempSync, readdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
import { chmodSync, existsSync, mkdirSync, mkdtempSync, readdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
import { homedir, tmpdir } from 'node:os';
import { isAbsolute, join, resolve } from 'node:path';
@ -78,8 +79,31 @@ export function profileNames(): string[] {
return names.filter(n => PROFILE_RE.test(n)).sort();
}
/** The default profile: the only one on this machine, else the one named "default"; null when that is not clear. */
export function defaultProfile(names = profileNames()): string | null {
/** The pointer file `storebound accounts default <name>` writes: one profile name, nothing else (never a key). */
export const defaultPointerFile = (): string => join(profileDir(), 'default-profile');
/** The profile the pointer file names, when it is a valid name; null without one. Not checked against the folder. */
export function defaultPointer(): string | null {
try {
const n = readFileSync(defaultPointerFile(), 'utf8').trim();
return PROFILE_RE.test(n) ? n : null;
} catch { return null; }
}
/** Sets (or with null clears) the default profile pointer. Throws on a name that is not a profile on this machine. */
export function setDefaultPointer(name: string | null): string {
const f = defaultPointerFile();
if (name === null) { rmSync(f, { force: true }); return f; }
if (!profileNames().includes(name)) throw new Error(`no profile "${name}" on this machine (${profileNames().join(', ') || 'none yet'}; storebound accounts lists them)`);
mkdirSync(profileDir(), { recursive: true, mode: 0o700 });
writeFileSync(f, name + '\n', { mode: 0o600 });
return f;
}
/** The default profile: the one the pointer names (when it exists), else the only one on this machine, else the one
* named "default"; null when that is not clear. */
export function defaultProfile(names = profileNames(), pointer = defaultPointer()): string | null {
if (pointer && names.includes(pointer)) return pointer;
return names.length === 1 ? names[0]! : names.includes('default') ? 'default' : null;
}