From d3190ecdec17fe45d821f4ba3a669127f4e9cf0f Mon Sep 17 00:00:00 2001 From: CRT Date: Fri, 23 Oct 2026 02:06:56 +0800 Subject: [PATCH] watch: read a missing App Store bundle id from the API instead of skipping the app --- CHANGELOG.md | 2 ++ cli/watch.ts | 19 +++++++++++++++++-- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 097f331..615a5b7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,8 @@ ## Unreleased +- `storebound watch`: a store-only App Store app added without a bundle id is no longer skipped; the bundle id is read from the API with the app's own key. + ## 0.8.35 ยท 2026-10-22 - Desktop v2 ships (0.8.33 and 0.8.34 stopped at the desktop boot check, which still expected the old markup). diff --git a/cli/watch.ts b/cli/watch.ts index 484113c..42528eb 100644 --- a/cli/watch.ts +++ b/cli/watch.ts @@ -65,8 +65,10 @@ export async function listTargets(args: Args): Promise { 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 --bundle-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 --bundle-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 { 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): Promise { + 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 { const run = exec(t.env);