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:
parent
d2ce185441
commit
e5f148e534
55 changed files with 1648 additions and 158 deletions
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue