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

@ -5,7 +5,7 @@ import { existsSync, mkdirSync, mkdtempSync, readdirSync, readFileSync, statSync
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { applyImport, planImport, useProfile } from '../cli/lib/accounts';
import { activeProfile, androidSigning, ascKey, defaultProfile, playServiceAccount, reviewContact, setConfigAccount } from '../cli/lib/credentials';
import { activeProfile, androidSigning, ascKey, defaultPointer, defaultProfile, playServiceAccount, reviewContact, setConfigAccount, setDefaultPointer } from '../cli/lib/credentials';
import { compareVersions, consent, decide, installPlan, isRunning, question, runningCheck } from '../cli/lib/desktop-install';
import { addFolder, folderId, parseRegistry, readRegistry, removeFolder, withFolder, withoutFolder } from '../cli/lib/registry';
import { desktopProjectId } from '../cli/lib/workspace';
@ -46,6 +46,43 @@ function project(base: string, name: string, o: { keyId?: string; email?: string
}
describe('credentials: one machine, many projects', () => {
test('accounts default <name>: a pointer file; env > project-named > project file > pointer > single profile', () => machine(({ creds, base }) => {
mkdirSync(creds, { recursive: true });
for (const n of ['teamA', 'teamB']) writeFileSync(join(creds, `${n}.json`), JSON.stringify({ ASC_KEY_ID: `${n.toUpperCase()}00000`.slice(0, 10), ASC_ISSUER_ID: 'iss', ASC_P8: PEM('x') }));
const bare = join(base, 'bare');
mkdirSync(bare);
// two profiles, none named default, no pointer: no default
expect(defaultProfile()).toBeNull();
expect(activeProfile(bare)).toBeNull();
expect(() => setDefaultPointer('nope')).toThrow(/no profile "nope"/);
setDefaultPointer('teamB');
expect(readFileSync(join(creds, 'default-profile'), 'utf8')).toBe('teamB\n');
expect(statSync(join(creds, 'default-profile')).mode & 0o777).toBe(0o600);
expect(defaultPointer()).toBe('teamB');
expect(defaultProfile()).toBe('teamB');
expect(activeProfile(bare)).toEqual({ name: 'teamB', from: 'default' });
expect(ascKey(bare).keyId).toBe('TEAMB00000');
// a pointer beats a profile named "default" and the single-profile rule
writeFileSync(join(creds, 'default.json'), JSON.stringify({ ASC_KEY_ID: 'DEFAULT000', ASC_ISSUER_ID: 'iss', ASC_P8: PEM('x') }));
expect(defaultProfile()).toBe('teamB');
// the project's own keys beat the pointer; a profile the project names beats both; env beats all
const a = project(base, 'Star Dash');
expect(ascKey(a).keyId).toBe('ABCDE12345');
const file = join(a, '.storebound', 'credentials.json');
writeFileSync(file, JSON.stringify({ ...JSON.parse(readFileSync(file, 'utf8')), account: 'teamA' }));
expect(ascKey(a).keyId).toBe('TEAMA00000');
process.env.ASC_KEY_ID = 'ENV0000000';
expect(ascKey(a).keyId).toBe('ENV0000000');
delete process.env.ASC_KEY_ID;
// a pointer to a profile that is gone falls back to the old rule
writeFileSync(join(creds, 'default-profile'), 'gone\n');
expect(defaultProfile()).toBe('default');
setDefaultPointer(null);
expect(existsSync(join(creds, 'default-profile'))).toBe(false);
// the pointer file is not a profile
expect(readdirSync(creds).filter(n => n.endsWith('.json')).sort()).toEqual(['default.json', 'teamA.json', 'teamB.json']);
}));
test('project-only values from the project, store keys from the profile, env over everything', () => machine(({ creds, base }) => {
const a = project(base, 'Star Dash');
// the project's own keys work without any profile