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:
CRTkafa 2026-10-23 00:57:42 +08:00
parent 75bf62e125
commit 13b91dd423
7 changed files with 278 additions and 30 deletions

View file

@ -27,7 +27,7 @@ import * as rt from './lib/rt';
import { existsSync, readFileSync } from 'node:fs';
import { basename, join, resolve } from 'node:path';
import { CONFIG_NAMES } from './lib/config';
import { addFolder, addStoreApp, applyRelink, configAppName, configFolders, readRegistry, registryFile, relinkPlan, removeFolder, removeStoreApp } from './lib/registry';
import { activeProjects, activeStoreApps, addFolder, withIgnore, addStoreApp, applyRelink, configAppName, configFolders, type IgnoreStore, ignoredStoreIds, ignoredStores, isIgnored, readRegistry, registryFile, relinkPlan, removeFolder, removeStoreApp, setIgnore } from './lib/registry';
import { homedir } from 'node:os';
type Store = 'apple' | 'play';
@ -170,13 +170,27 @@ async function folders(sub: string, args: Args): Promise<void> {
if (sub === 'folders' && args._[1]) throw new Error(`storebound apps folders ${args._[1]}: unknown; to add a folder: storebound apps add [<folder>] (or apps folders add [<folder>]), to list: storebound apps folders`);
if (sub === 'folders') {
const r = readRegistry();
if (args.has('json')) { printJson({ file: registryFile(), projects: r.projects.map(p => ({ ...p, missing: !existsSync(p.path) })), storeApps: r.storeApps ?? [] }); return; }
if (r.storeApps?.length) {
console.log(textTable(['store app', 'store', 'app id', 'profile', ''], r.storeApps.map(a => [a.name, a.store, a.appId, a.account ?? '(default)', 'no local project'])));
const ignoredCount = r.projects.filter(p => ignoredStores(p).length).length + (r.storeApps ?? []).filter(a => isIgnored(a)).length;
if (args.has('json')) {
printJson({ file: registryFile(), projects: r.projects.map(p => ({ ...p, missing: !existsSync(p.path), ignored: ignoredStores(p), allIgnored: isIgnored(p) })), storeApps: r.storeApps ?? [] });
return;
}
const liveStore = activeStoreApps(r);
if (liveStore.length) {
console.log(textTable(['store app', 'store', 'app id', 'profile', ''], liveStore.map(a => [a.name, a.store, a.appId, a.account ?? '(default)', 'no local project'])));
console.log('');
}
if (!r.projects.length) { console.log(`no app folders yet (${tilde(registryFile())}): storebound apps add, in an app folder (or storebound desktop there)`); return; }
console.log(textTable(['app', 'folder', ''], r.projects.map(p => [p.name, tilde(p.path), existsSync(p.path) ? '' : 'folder not found'])));
const live = activeProjects(r);
if (!r.projects.length && !liveStore.length) { console.log(`no app folders yet (${tilde(registryFile())}): storebound apps add, in an app folder (or storebound desktop there)`); return; }
if (live.length) console.log(textTable(['app', 'folder', ''], live.map(p => [p.name, tilde(p.path), [existsSync(p.path) ? '' : 'folder not found', ignoredStores(p).length ? `${ignoredStores(p).join('+')} ignored` : ''].filter(Boolean).join(', ')])));
if (ignoredCount) {
console.log(`
ignored (${ignoredCount}; storebound apps unignore <name|id> [--store apple|play] brings them back):`);
console.log(textTable(['app', 'where', 'ignored'], [
...r.projects.filter(p => isIgnored(p)).map(p => [p.name, tilde(p.path), 'apple+play']),
...(r.storeApps ?? []).filter(a => isIgnored(a)).map(a => [a.name, `${a.store}:${a.appId}`, a.store]),
]));
}
const missing = r.projects.filter(p => !existsSync(p.path)).length;
if (missing) console.log(`\n${missing} folder(s) not found (moved to another computer or folder?): storebound apps relink --dry finds them by path and app name`);
console.log(`
@ -222,6 +236,21 @@ ${tilde(registryFile())}: the same list as the desktop app's App folders`);
log('apps', removed ? `removed ${tilde(removed.path)} from the list (the folder and its files stay)` : `${tilde(dir)} is not in the list`);
}
/** apps ignore / unignore <name|id|folder|store:app id> [--store apple|play]: dormant apps, whole or one store side. */
function ignoreCmd(on: boolean, args: Args): void {
const key = args._[1];
const store = args.get('store') as IgnoreStore | undefined;
if (!key) throw new Error(`storebound apps ${on ? 'ignore' : 'unignore'} <name|id|folder|apple:<id>|play:<package>> [--store apple|play]`);
if (store !== undefined && store !== 'apple' && store !== 'play') throw new Error('--store: apple or play');
const { entry, changed } = args.has('dry') ? (({ entry, changed }) => ({ entry, changed }))(withIgnore(readRegistry(), key, store, on)) : setIgnore(key, store, on);
if (args.has('dry')) { log('apps', `dry run: ${changed ? `would ${on ? 'ignore' : 'restore'} ${store ? `the ${store} side of ` : ''}${entry.name}` : `${entry.name}: nothing to change`}`); return; }
const now = ignoredStores(entry);
const where = store ? `the ${store} side of ` : '';
if (args.has('json')) { printJson({ name: entry.name, id: entry.id, ignored: now, changed }); return; }
log('apps', changed ? `${on ? 'ignoring' : 'restored'} ${where}${entry.name}${now.length ? ` (ignored now: ${now.join(', ')})` : ''}: watch, events and portfolio reads ${on ? 'skip' : 'read'} it. Storebound only: the store listing is untouched`
: `${entry.name} was already ${on ? 'ignored' : 'not ignored'}${store ? ` on ${store}` : ''}`);
}
/** apps relink: point "folder not found" entries at the same app on this machine (after a move); ids stay. */
function relink(args: Args): void {
const home = homedir();
@ -242,6 +271,7 @@ export async function run(args: Args): Promise<void> {
const json = args.has('json');
if (sub === 'relink') return relink(args);
if (sub === 'folders' || sub === 'add' || sub === 'remove') return folders(sub, args);
if (sub === 'ignore' || sub === 'unignore') return ignoreCmd(sub === 'ignore', args);
if (sub === 'list') {
const out: AppsListResult[] = [];
for (const s of stores(args, root)) {
@ -253,10 +283,21 @@ export async function run(args: Args): Promise<void> {
warn('apps', `${s}: ${why}`);
}
}
if (json) { printJson(args.get('store') ? out[0] : out); return; }
// ignored apps (registry: whole app or this store's side) are listed apart, not with the live ones; --all keeps them in
const ign = ignoredStoreIds(readRegistry());
const sideOf = (r: AppsListResult) => (r.store === 'ios' ? ign.apple : ign.play);
const isIgn = (r: AppsListResult, a: { id: string; bundleId: string }) => sideOf(r).has(a.id) || sideOf(r).has(a.bundleId);
if (json) {
const marked = out.map(r => ({ ...r, apps: r.apps.map(a => (isIgn(r, a) ? { ...a, ignored: true } : a)) }));
printJson(args.get('store') ? marked[0] : marked);
return;
}
for (const r of out) {
log('apps', `${r.store === 'ios' ? 'App Store Connect' : 'Google Play'}: ${r.apps.length} apps`);
console.log(textTable(['name', 'id', 'bundle / package', 'platforms', 'newest'], r.apps.map(a => [a.name, a.id, a.bundleId, a.platforms.join(' '), a.latest ? `${a.latest.version} ${a.latest.state}` : ''])));
const mine = r.apps.filter(a => !isIgn(r, a)), hidden = r.apps.filter(a => isIgn(r, a));
const rows = (l: typeof r.apps) => l.map(a => [a.name, a.id, a.bundleId, a.platforms.join(' '), a.latest ? `${a.latest.version} ${a.latest.state}` : '']);
log('apps', `${r.store === 'ios' ? 'App Store Connect' : 'Google Play'}: ${mine.length} apps${hidden.length ? ` (+${hidden.length} ignored)` : ''}`);
console.log(textTable(['name', 'id', 'bundle / package', 'platforms', 'newest'], rows(mine)));
if (hidden.length) { console.log(`ignored (storebound apps unignore):`); console.log(textTable(['name', 'id', 'bundle / package', 'platforms', 'newest'], rows(hidden))); }
for (const w of r.warnings) warn('apps', w);
}
return;
@ -273,7 +314,7 @@ export async function run(args: Args): Promise<void> {
if (out.some(c => c.status !== 'ok')) process.exitCode = 1;
return;
}
console.error('usage: storebound apps list [--store apple|play] [--icons] [--json] | apps check [--store apple|play] [--json] | apps folders [--json] | apps add [<folder>] | apps add --store apple|play --app-id <id> [--account <profile>] | apps remove <folder> | apps remove apple:<id>');
console.error('usage: storebound apps list [--store apple|play] [--icons] [--json] | apps check [--store apple|play] [--json] | apps folders [--json] | apps ignore|unignore <name|id> [--store apple|play] | apps add [<folder>] | apps add --store apple|play --app-id <id> [--account <profile>] | apps remove <folder> | apps remove apple:<id>');
process.exit(2);
}