storebound/cli/console/verify.ts
CRT 36a8bf9625 CLI runs on Node 20+: npx storebound (bunx keeps working)
- cli/lib/rt.ts: the runtime layer on node: modules (spawn / spawnSync with Bun's defaults, exit codes and
  "Executable not found", which with PATHEXT, sleep, serve, Glob, hasher, gunzip, stdin, SQLite, packageRoot,
  config imports with a type-stripping fallback for Node 20); every Bun.* use in cli/ goes through it
- tools/gen-embedded.ts -> cli/lib/embedded.ts: templates/, cli/shots/*.html and Play's Data safety form embedded
  (replaces Bun text imports; the bundle and the compiled sidecar carry them), checked fresh by bun run ci
- package.json: bin dist/storebound.mjs (Node ESM bundle, tools/build-node.ts, prepack), engines node >=20
- bun run ci: node bundle + node smoke (real node: --version, help, check same as Bun, scan, release plan --help,
  init, MCP handshake + tool call); Forgejo CI sets up Node 20; tests/node-compat.test.ts guards cli/
- install writes npx MCP commands when run with Node; bin/storebound falls back to Node
- docs: npx storebound init (Node 20+) or bunx storebound init
2026-10-13 04:03:28 +08:00

50 lines
3.2 KiB
TypeScript

// Checks a console form against the config after the agent (or the owner) filled it. Read-only.
// storebound console verify <form> [--from-text <file|->] [--json]
// apple-app-privacy: the page text of the App Privacy page (the Data Types section's per-type summaries; the App Store
// Connect API has no App Privacy endpoint). play-app-record: the JSON of the plan's capture snippet (input states) or
// the page text. play-content-rating, play-target-audience: the summary page text. play-app-content: the plan's
// capture JSON (cards still showing "Start <Name> declaration"); play-store-settings, the other App content cards and
// play-production-countries / -release: the page text after Save. apple-app-record: the App Store
// Connect API (the app by bundle id; a GET) or, with --from-text, the page text. apple-age-rating: the API.
// Exit 1 on any mismatch, and when nothing could be matched. Pure logic in cli/lib/console-verify.ts. MCP: console_verify.
import { readFileSync } from 'node:fs';
import type { Ctx } from '../lib/ctx';
import { isPlanForm, PLAN_FORM_IDS } from '../lib/console-answers';
import { verifyAgeRatingApi, verifyAppRecordApi, type VerifyResult, verifyTable, verifyText } from '../lib/console-verify';
import { printJson } from '../lib/log';
import * as rt from '../lib/rt';
async function readText(src: string): Promise<string> {
if (src === '-' || src === 'true') return rt.stdinText();
return readFileSync(src, 'utf8');
}
export async function run(ctx: Ctx): Promise<void> {
const { config, root } = ctx.loaded;
const args = ctx.args, json = args.has('json');
const form = args._[0];
if (!isPlanForm(form)) throw new Error(`console verify <form> [--from-text <file|->]: one of ${PLAN_FORM_IDS.join(', ')}`);
const from = args.get('from-text'), inline = args.get('text');
let r: VerifyResult;
if (from !== undefined || inline !== undefined) {
const text = inline ?? await readText(from!);
if (!text.trim()) throw new Error('console verify: the page text is empty');
r = verifyText(form, config, text);
} else if (form === 'apple-app-record' || form === 'apple-age-rating') {
const bundleId = config.apple?.bundleId;
if (!bundleId) throw new Error('console verify: apple.bundleId is not set');
const { ascClient } = await import('../apple/asc');
const c = await ascClient(root, bundleId, config.apple?.appId);
if (form === 'apple-app-record') {
const found = await c.asc('GET', `/v1/apps?filter[bundleId]=${encodeURIComponent(bundleId)}`);
const hit = (found.data as { attributes: { bundleId: string; name?: string; sku?: string; primaryLocale?: string } }[]).find(a => a.attributes.bundleId === bundleId);
r = verifyAppRecordApi(config, hit?.attributes ?? null);
} else {
const info = await c.editableAppInfo();
const age = await c.maybe(`/v1/appInfos/${info.id}/ageRatingDeclaration`);
r = verifyAgeRatingApi(config, (age?.data?.attributes ?? null) as Record<string, unknown> | null);
}
} else throw new Error(`console verify ${form}: pass the page text with --from-text <file|-> (run \`storebound console plan ${form}\` for what to capture)`);
if (json) printJson(r); else console.log(verifyTable(r));
if (!r.ok) process.exitCode = 1;
}