watch: read a missing App Store bundle id from the API instead of skipping the app

This commit is contained in:
CRTkafa 2026-10-23 02:06:56 +08:00
parent 3ff1da10c6
commit d3190ecdec
2 changed files with 19 additions and 2 deletions

View file

@ -65,8 +65,10 @@ export async function listTargets(args: Args): Promise<Target[]> {
for (const s of reg.storeApps) {
if (isIgnored(s)) continue;
try {
if (s.store === 'apple' && !s.bundleId) throw new Error('no bundle id (storebound apps add --store apple --app-id <id> --bundle-id <id>)');
const v = checkVirtual({ name: s.name, ...(s.store === 'apple' ? { apple: { bundleId: s.bundleId, appId: s.appId } } : { play: { packageName: s.appId } }) });
// a store-only App Store app added without --bundle-id: read it from the API with the app's own key
const bundleId = s.store === 'apple' ? (s.bundleId || await appleBundleId(s.appId, s.account ? profileEnv(s.account) : {})) : null;
if (s.store === 'apple' && !bundleId) throw new Error('no bundle id (storebound apps add --store apple --app-id <id> --bundle-id <id>)');
const v = checkVirtual({ name: s.name, ...(s.store === 'apple' ? { apple: { bundleId: bundleId!, appId: s.appId } } : { play: { packageName: s.appId } }) });
out.push({ key: `store:${s.id}`, name: s.name, dir: virtualDir(v), virtual: true, env: s.account ? profileEnv(s.account) : {}, skip: [] });
} catch (e) { warn(TAG, `${s.name}: ${(e as Error).message}, skipped`); }
}
@ -74,6 +76,19 @@ export async function listTargets(args: Args): Promise<Target[]> {
return out;
}
/** The bundle id of an App Store app by its numeric id, read with the given credential env; null when it cannot be read. */
async function appleBundleId(appId: string, env: Record<string, string>): Promise<string | null> {
const saved = Object.fromEntries(Object.keys(env).map(k => [k, process.env[k]]));
Object.assign(process.env, env);
try {
const { ascClient } = await import('./apple/asc');
const r = await (await ascClient(process.cwd(), '')).asc('GET', `/v1/apps/${appId}?fields[apps]=bundleId`) as { data?: { attributes?: { bundleId?: string } } };
return r.data?.attributes?.bundleId ?? null;
} catch { return null; } finally {
for (const [k, v] of Object.entries(saved)) { if (v === undefined) delete process.env[k]; else process.env[k] = v; }
}
}
/** Reads one app: releases of both stores, health when due, new reviews. A part that cannot be read is left out. */
async function readApp(t: Target, prev: AppSnap | undefined, now: number): Promise<AppRead> {
const run = exec(t.env);