apple build stops before the archive in an SSH / non-GUI session (errSecInternalComponent) with how to run it in the logged-in session, --allow-ssh to skip; relative credential paths resolve from the project and xcodebuild gets an absolute ASC key path; tests, pitfall 101

This commit is contained in:
CRTkafa 2026-10-22 02:38:45 +08:00
parent 467bacc00a
commit b97f23701f
7 changed files with 72 additions and 6 deletions

View file

@ -189,7 +189,13 @@ function value(root: string, name: string): string | undefined {
}
/** A path or the inline content itself. */
export const inline = (v: string, marker: RegExp): string => (marker.test(v) ? v : readFileSync(v.replace(/^~(?=\/)/, homedir()), 'utf8'));
export const inline = (v: string, marker: RegExp, root?: string): string => (marker.test(v) ? v : readFileSync(credPath(v, root), 'utf8'));
/** A credential path: ~ expanded; a relative one is taken from the project folder, not the working directory. */
export function credPath(v: string, root?: string): string {
const h = v.replace(/^~(?=[/\\])/, homedir());
return isAbsolute(h) || !root ? h : resolve(root, h);
}
export function parseServiceAccount(text: string): ServiceAccount {
const j = JSON.parse(text) as Partial<ServiceAccount>;
@ -201,7 +207,7 @@ export function parseServiceAccount(text: string): ServiceAccount {
export function playServiceAccount(root: string): ServiceAccount {
const v = value(root, 'PLAY_SERVICE_ACCOUNT');
if (!v) throw new Error('PLAY_SERVICE_ACCOUNT is not set (env, the credential profile in ~/.storebound/credentials, or .storebound/credentials.json): path to the service account JSON key; `storebound accounts` lists the profiles');
return parseServiceAccount(inline(v, /^\s*\{/));
return parseServiceAccount(inline(v, /^\s*\{/, root));
}
export function ascKey(root: string): AscKey {
@ -209,8 +215,8 @@ export function ascKey(root: string): AscKey {
const issuerId = value(root, 'ASC_ISSUER_ID');
if (!keyId || !issuerId) throw new Error('ASC_KEY_ID and ASC_ISSUER_ID are not set (env, the credential profile in ~/.storebound/credentials, or .storebound/credentials.json); `storebound accounts` lists the profiles');
const p8 = value(root, 'ASC_P8') ?? join(homedir(), '.appstoreconnect/private_keys', `AuthKey_${keyId}.p8`);
if (!/BEGIN PRIVATE KEY/.test(p8) && !existsSync(p8.replace(/^~(?=\/)/, homedir()))) throw new Error(`App Store Connect key not found: ${p8} (set ASC_P8)`);
return { keyId, issuerId, pem: inline(p8, /BEGIN PRIVATE KEY/) };
if (!/BEGIN PRIVATE KEY/.test(p8) && !existsSync(credPath(p8, root))) throw new Error(`App Store Connect key not found: ${p8} (set ASC_P8)`);
return { keyId, issuerId, pem: inline(p8, /BEGIN PRIVATE KEY/, root) };
}
/** The keys and nestings a review contact is read from (first hit): the documented one, then shapes seen in real files. */
@ -306,7 +312,9 @@ export const maskSecret = (s: string): string => (s ? '********' : '(empty)');
export function ascKeyFile(root: string): { keyPath: string; keyId: string; issuerId: string; cleanup: () => void } {
const k = ascKey(root);
const raw = value(root, 'ASC_P8') ?? join(homedir(), '.appstoreconnect/private_keys', `AuthKey_${k.keyId}.p8`);
if (!/BEGIN PRIVATE KEY/.test(raw)) return { keyPath: raw.replace(/^~(?=\/)/, homedir()), keyId: k.keyId, issuerId: k.issuerId, cleanup: () => {} };
// xcodebuild refuses a relative -authenticationKeyPath ("must be an absolute path"): resolve it from the project
const asPath = (p: string) => { const h = p.replace(/^~(?=[/\\])/, homedir()); return isAbsolute(h) ? h : resolve(root, h); };
if (!/BEGIN PRIVATE KEY/.test(raw)) return { keyPath: asPath(raw), keyId: k.keyId, issuerId: k.issuerId, cleanup: () => {} };
const dir = mkdtempSync(join(tmpdir(), 'storebound-key-'));
chmodSync(dir, 0o700);
const keyPath = join(dir, `AuthKey_${k.keyId}.p8`);