Ignore dormant apps: per-app and per-store 'ignored' in the app list; apps ignore/unignore, watch, ws and apps list skip them; MCP tools
This commit is contained in:
parent
75bf62e125
commit
13b91dd423
7 changed files with 278 additions and 30 deletions
27
cli/watch.ts
27
cli/watch.ts
|
|
@ -19,7 +19,7 @@ import { CONFIG_NAMES } from './lib/config';
|
|||
import { consoleIdsFromConfig } from './lib/console-links';
|
||||
import { profileEnv } from './lib/credentials';
|
||||
import { done, log, warn } from './lib/log';
|
||||
import { readRegistry } from './lib/registry';
|
||||
import { type IgnoreStore, ignoredStores, isIgnored, readRegistry } from './lib/registry';
|
||||
import { runCommand } from './lib/runner';
|
||||
import type { HealthSnapshot } from './lib/health';
|
||||
import {
|
||||
|
|
@ -36,7 +36,7 @@ const MAX_BACKOFF_MS = 4 * 60 * 60_000;
|
|||
const CHILD_TIMEOUT_MS = 5 * 60_000;
|
||||
const REVIEW_DAYS = 3;
|
||||
|
||||
interface Target extends PassTarget { dir: string; virtual: boolean; env: Record<string, string> }
|
||||
interface Target extends PassTarget { dir: string; virtual: boolean; env: Record<string, string>; /** store sides the registry ignores: not read, no events */ skip: IgnoreStore[] }
|
||||
|
||||
/** Runs a storebound child command; a hung one is cancelled after the timeout. */
|
||||
const exec = (env: Record<string, string>): Exec => async (argv, cwd) => {
|
||||
|
|
@ -46,26 +46,28 @@ const exec = (env: Record<string, string>): Exec => async (argv, cwd) => {
|
|||
};
|
||||
|
||||
/** The apps to watch: --dir, or the registry's folders and store-only apps (a store-only app that has a folder here is read once). */
|
||||
async function listTargets(args: Args): Promise<Target[]> {
|
||||
export async function listTargets(args: Args): Promise<Target[]> {
|
||||
const dir = args.get('dir');
|
||||
if (dir) {
|
||||
const d = resolve(dir);
|
||||
if (!CONFIG_NAMES.some(n => existsSync(join(d, n)))) throw new Error(`--dir: no storebound.config.ts in ${d}`);
|
||||
return [{ key: `dir:${d}`, name: d.split(/[\\/]/).pop() ?? d, dir: d, virtual: false, env: {} }];
|
||||
return [{ key: `dir:${d}`, name: d.split(/[\\/]/).pop() ?? d, dir: d, virtual: false, env: {}, skip: [] }];
|
||||
}
|
||||
const reg = readRegistry();
|
||||
const out: Target[] = [];
|
||||
for (const p of reg.projects) {
|
||||
if (isIgnored(p)) continue; // ignored apps are not watched at all (storebound apps ignore / unignore)
|
||||
if (!CONFIG_NAMES.some(n => existsSync(join(p.path, n)))) { warn(TAG, `${p.name}: no storebound.config.ts in ${p.path}, skipped (storebound apps remove ${p.path})`); continue; }
|
||||
out.push({ key: `dir:${p.path}`, name: p.name, dir: p.path, virtual: false, env: {} });
|
||||
out.push({ key: `dir:${p.path}`, name: p.name, dir: p.path, virtual: false, env: {}, skip: ignoredStores(p) });
|
||||
}
|
||||
if (reg.storeApps?.length) {
|
||||
const { checkVirtual, virtualDir } = await import('./serve');
|
||||
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 } }) });
|
||||
out.push({ key: `store:${s.id}`, name: s.name, dir: virtualDir(v), virtual: true, env: s.account ? profileEnv(s.account) : {} });
|
||||
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`); }
|
||||
}
|
||||
}
|
||||
|
|
@ -80,24 +82,25 @@ async function readApp(t: Target, prev: AppSnap | undefined, now: number): Promi
|
|||
const e = emptyStatus(t.dir);
|
||||
try { await fillAppStatus(e, t.dir, { live: true, virtual: true, exec: run }); } catch (err) { return { snap: null, errors: [(err as Error).message] }; }
|
||||
const s = e.summary!;
|
||||
const useApple = !!s.apple && !t.skip.includes('apple'), usePlay = !!s.play && !t.skip.includes('play');
|
||||
const snap: AppSnap = { name: s.name || t.name, ids: { appleAppId: s.apple?.appId ?? null } };
|
||||
if (s.play && !t.virtual && !prev?.ids.playDeveloperId) {
|
||||
if (usePlay && !t.virtual && !prev?.ids.playDeveloperId) {
|
||||
try { const { loadConfig } = await import('./lib/config'); Object.assign(snap.ids, consoleIdsFromConfig((await loadConfig(undefined, t.dir)).config)); } catch { /* links fall back to the console home */ }
|
||||
}
|
||||
const all = e.release.all ?? [];
|
||||
snap.releases = {};
|
||||
if (s.apple) { if (e.storeErrors.ios) errors.push(`App Store: ${e.storeErrors.ios}`); else snap.releases.apple = releaseMap(all, 'ios'); }
|
||||
if (s.play) { if (e.storeErrors.android) errors.push(`Google Play: ${e.storeErrors.android}`); else snap.releases.play = releaseMap(all, 'android'); }
|
||||
if (useApple) { if (e.storeErrors.ios) errors.push(`App Store: ${e.storeErrors.ios}`); else snap.releases.apple = releaseMap(all, 'ios'); }
|
||||
if (usePlay) { if (e.storeErrors.android) errors.push(`Google Play: ${e.storeErrors.android}`); else snap.releases.play = releaseMap(all, 'android'); }
|
||||
|
||||
const due = !prev?.healthAt || now - Date.parse(prev.healthAt) >= HEALTH_EVERY_MS;
|
||||
if (due && (s.apple || s.play)) {
|
||||
if (due && (useApple || usePlay)) {
|
||||
const h = await run(['health', '--json'], t.dir);
|
||||
if (h.data && typeof h.data === 'object') { snap.health = healthSnap(h.data as HealthSnapshot); snap.healthAt = new Date(now).toISOString(); }
|
||||
if (h.data && typeof h.data === 'object') { snap.health = healthSnap({ ...(h.data as HealthSnapshot), ...(useApple ? {} : { apple: undefined }), ...(usePlay ? {} : { play: undefined }) } as HealthSnapshot); snap.healthAt = new Date(now).toISOString(); }
|
||||
else errors.push(`health: ${lastLine(h.out) || `exit ${h.code}`}`);
|
||||
}
|
||||
|
||||
snap.reviews = {};
|
||||
for (const [store, on] of [['apple', !!s.apple], ['play', !!s.play]] as const) {
|
||||
for (const [store, on] of [['apple', useApple], ['play', usePlay]] as const) {
|
||||
if (!on) continue;
|
||||
const r = await run(['reviews', 'list', '--store', store, '--days', String(REVIEW_DAYS), '--json'], t.dir);
|
||||
if (r.code !== 0 || !Array.isArray(r.data)) { errors.push(`reviews ${store}: ${lastLine(r.out) || `exit ${r.code}`}`); continue; }
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue