ship step: apple build plan keeps its version and build in --json; test fixtures pass the scrub (documentation IPs, neutral home paths, 10-char key ids)

This commit is contained in:
CRTkafa 2026-10-23 13:55:44 +08:00
parent 6abc1f65dd
commit 960e084bea
3 changed files with 11 additions and 11 deletions

View file

@ -141,7 +141,7 @@ export function fieldsFromDetail(key: string, d: unknown): Fields {
const dry = x.dry === true || x.dryRun === true;
switch (key) {
case 'play build': return { version: str(x.versionName), build: str(x.versionCode), state: x.aab ? 'built' : 'planned', ...(x.aab ? { artifact: String(x.aab) } : {}) };
case 'apple build': return { version: x.ipa ? str(x.version) : null, build: x.ipa ? str(x.build) : null, state: x.ipa ? 'built' : 'planned', ...(x.ipa ? { artifact: String(x.ipa) } : {}) };
case 'apple build': return { version: str(x.version), build: str(x.build), state: x.ipa ? 'built' : 'planned', ...(x.ipa ? { artifact: String(x.ipa) } : {}) };
case 'play upload': return { version: str(x.versionName), build: str(x.versionCode), track: str(x.track), state: x.uploaded ? str(x.status) : 'planned' };
case 'play release': {
const tracks = Array.isArray(x.tracks) ? x.tracks as { track: string; status: string }[] : [];

View file

@ -372,9 +372,9 @@ describe('credentials for builds', () => {
// another machine's paths and a keystore the stored password does not open are named by field
const m = dir('moved');
touch(m, 'keys/upload.jks', 'x');
touch(m, '.storebound/credentials.json', JSON.stringify({ ANDROID_KEYSTORE: 'keys/upload.jks', ANDROID_KEYSTORE_PASSWORD: 'wrong', ANDROID_KEY_ALIAS: 'upload', PLAY_SERVICE_ACCOUNT: '/Users/someone/keys/play.json' }));
touch(m, '.storebound/credentials.json', JSON.stringify({ ANDROID_KEYSTORE: 'keys/upload.jks', ANDROID_KEYSTORE_PASSWORD: 'wrong', ANDROID_KEY_ALIAS: 'upload', PLAY_SERVICE_ACCOUNT: '/Users/user/keys/play.json' }));
const issues = credentialFileIssues(m, () => ({ ok: false, out: 'keytool error: java.io.IOException: Keystore was tampered with, or password was incorrect' }));
expect(issues.some(i => i.startsWith('PLAY_SERVICE_ACCOUNT: /Users/someone/keys/play.json does not exist'))).toBe(true);
expect(issues.some(i => i.startsWith('PLAY_SERVICE_ACCOUNT: /Users/user/keys/play.json does not exist'))).toBe(true);
expect(issues.some(i => i.startsWith('ANDROID_KEYSTORE_PASSWORD does not open'))).toBe(true);
expect(credentialFileIssues(m, () => ({ ok: true, out: '' })).some(i => i.startsWith('ANDROID'))).toBe(false);
const { pem } = newSigningKey();
@ -498,7 +498,7 @@ describe('MCP', () => {
describe('apple build outside the GUI session (pitfall 101)', () => {
test('SSH or a non-Aqua launchd manager is named; the logged-in session passes', async () => {
const { remoteBuildSession } = await import('../cli/lib/xcode');
expect(remoteBuildSession({ SSH_CONNECTION: '10.0.0.2 5000 10.0.0.3 22' }, 'Aqua')).toBe('this is an SSH session');
expect(remoteBuildSession({ SSH_CONNECTION: '192.0.2.2 5000 192.0.2.3 22' }, 'Aqua')).toBe('this is an SSH session');
expect(remoteBuildSession({}, 'Background')).toContain('launchctl manager: Background');
expect(remoteBuildSession({}, 'Aqua')).toBeNull();
});
@ -509,15 +509,15 @@ describe('apple build outside the GUI session (pitfall 101)', () => {
const d = mkdtempSync(join(tmpdir(), 'sb-p8-'));
mkdirSync(join(d, '.storebound'));
const { generateKeyPairSync } = await import('node:crypto');
writeFileSync(join(d, '.storebound/AuthKey_KEYID00002.p8'), generateKeyPairSync('ec', { namedCurve: 'P-256' }).privateKey.export({ type: 'pkcs8', format: 'pem' }).toString());
writeFileSync(join(d, '.storebound/credentials.json'), JSON.stringify({ ASC_KEY_ID: 'KEYID00002', ASC_ISSUER_ID: '00000000-0000-0000-0000-000000000000', ASC_P8: '.storebound/AuthKey_KEYID00002.p8' }));
writeFileSync(join(d, '.storebound/AuthKey_KEYID0002.p8'), generateKeyPairSync('ec', { namedCurve: 'P-256' }).privateKey.export({ type: 'pkcs8', format: 'pem' }).toString());
writeFileSync(join(d, '.storebound/credentials.json'), JSON.stringify({ ASC_KEY_ID: 'KEYID00002', ASC_ISSUER_ID: '00000000-0000-0000-0000-000000000000', ASC_P8: '.storebound/AuthKey_KEYID0002.p8' }));
const saved = { ...process.env };
try {
for (const k of ['ASC_KEY_ID', 'ASC_ISSUER_ID', 'ASC_P8']) delete process.env[k];
const { ascKeyFile } = await import('../cli/lib/credentials');
const path = ascKeyFile(d).keyPath;
expect(isAbsolute(path)).toBe(true);
expect(path).toBe(join(d, '.storebound/AuthKey_KEYID00002.p8'));
expect(path).toBe(join(d, '.storebound/AuthKey_KEYID0002.p8'));
} finally { process.env = saved; }
});
});

View file

@ -22,10 +22,10 @@ describe('apps relink', () => {
expect(found.sort()).toEqual([game, front, renamed].sort());
expect(configAppName(front)).toBe('Studio');
const r = { version: 1 as const, projects: [
{ id: 'a', path: '/Users/old/Desktop/game-one', name: 'Game One', added: '' },
{ id: 'b', path: '/Users/old/Work/Studio/frontend', name: 'frontend', added: '' },
{ id: 'c', path: '/Users/old/Work/OldName/app', name: 'Old App', added: '' },
{ id: 'd', path: '/Users/old/gone', name: 'Gone', added: '' },
{ id: 'a', path: '/Users/name/Desktop/game-one', name: 'Game One', added: '' },
{ id: 'b', path: '/Users/name/Work/Studio/frontend', name: 'frontend', added: '' },
{ id: 'c', path: '/Users/name/Work/OldName/app', name: 'Old App', added: '' },
{ id: 'd', path: '/Users/name/gone', name: 'Gone', added: '' },
] };
const plan = relinkPlan(r, found, () => false, configAppName);
expect(plan.map(p => [p.id, p.to])).toEqual([['a', game], ['b', front], ['c', renamed]]);