apps folders add adds the folder (template note, refusals with reasons); MCP: .mcp.json never spawns a missing plugin path (sh fallback to PATH, npx), mcp/serve skip the self-update, spawned handshake test; store build minimums in one dated table: Play target API 36 and App Store Xcode 26 / SDK 26 blocked in scan, validate, play upload/release, apple upload; pitfalls 81-83

This commit is contained in:
CRTkafa 2026-10-20 12:15:00 +07:00
parent 9805582278
commit 9ab64bcbdf
16 changed files with 323 additions and 14 deletions

View file

@ -24,7 +24,7 @@ import { ascClient } from './apple/asc';
import { REPORTING_API } from './play/google';
import * as rt from './lib/rt';
import { existsSync, readFileSync } from 'node:fs';
import { join, resolve } from 'node:path';
import { basename, join, resolve } from 'node:path';
import { CONFIG_NAMES } from './lib/config';
import { addFolder, addStoreApp, readRegistry, registryFile, removeFolder, removeStoreApp } from './lib/registry';
@ -162,6 +162,10 @@ const tilde = (p: string) => (process.env.HOME && p.startsWith(process.env.HOME)
/** apps folders / add / remove: the machine's app folder list (cli/lib/registry.ts). */
async function folders(sub: string, args: Args): Promise<void> {
// `apps folders add [<dir>]` / `apps folders remove <dir>`: the same as `apps add` / `apps remove` (this form used
// to fall through to the list and add nothing, without a word)
if (sub === 'folders' && (args._[1] === 'add' || args._[1] === 'remove')) { sub = args._[1]; args._.splice(0, 1); }
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; }
@ -200,9 +204,13 @@ ${tilde(registryFile())}: the same list as the desktop app's App folders`);
}
const dir = resolve(args._[1] ?? '.');
if (sub === 'add') {
if (!CONFIG_NAMES.some(n => existsSync(join(dir, n)))) throw new Error(`${dir}: no storebound config here (storebound init starts one)`);
if (!existsSync(dir)) throw new Error(`${dir}: no such folder`);
const cfg = CONFIG_NAMES.find(n => existsSync(join(dir, n)));
if (!cfg) throw new Error(`${dir}: not added, no storebound config here (${CONFIG_NAMES.join(', ')}); run storebound init in it, then this again`);
const r = addFolder(dir);
log('apps', r.added ? `added ${tilde(r.project.path)} (it shows in the desktop app too)` : `${tilde(r.project.path)} is already in the list`);
const note = templateNote(join(dir, cfg));
if (note) warn('apps', note);
return;
}
if (!args._[1]) throw new Error('storebound apps remove <folder>');
@ -249,3 +257,11 @@ export async function run(args: Args): Promise<void> {
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>');
process.exit(2);
}
/** A note when the config still holds the template's placeholders (the folder is added anyway: it is the owner's). */
export function templateNote(file: string): string | null {
let t = '';
try { t = readFileSync(file, 'utf8'); } catch { return null; }
const left = [/['"]Example App['"]/.test(t) && "app.name is still the template's 'Example App'", /com\.example\./.test(t) && 'the bundle id / package is still com.example.*'].filter(Boolean);
return left.length ? `${left.join('; ')}: set your app's values in ${basename(file)} (storebound check lists the rest)` : null;
}