94 lines
6.5 KiB
TypeScript
94 lines
6.5 KiB
TypeScript
// Checks a console form against the config after the agent (or the owner) filled it. Read-only.
|
|
// storebound console verify <form> [--from <file|->] [--from-text <file|->] [--json]
|
|
// --from takes the JSON of the plan's verify.capture snippet (input states: aria-checked, checked, aria-selected) or page text.
|
|
// storebound console verify play-app-record --url <url after Create app> [--write]
|
|
// storebound console verify apple-app-group [--bundle-id <id>] (API: the App Groups on the App ID's newest readable profile)
|
|
// 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.
|
|
// play-app-record also reads the Play Console app id from the URL Create app redirects to (/app/<appId>/app-dashboard;
|
|
// --url, or the capture JSON's url) and shows the play.appId change as a dry diff; --write saves it into the config (an
|
|
// upsert; the only write of this command, and only to the local config file).
|
|
// 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 { log, printJson } from '../lib/log';
|
|
import { appIdChange, appIdDiff, writePlayAppId } from '../lib/play-app-id';
|
|
import { basename } from 'node:path';
|
|
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') ?? args.get('from-text'), url = args.get('url');
|
|
const inline = args.get('text') ?? (url !== undefined ? url : undefined);
|
|
if (url !== undefined && form !== 'play-app-record') throw new Error('console verify: --url is for play-app-record (the URL Create app redirects to)');
|
|
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 === 'play-publish-changes') {
|
|
const { readyFromApi } = await import('../play/publish-ready');
|
|
const { verifyPublishChangesApi } = await import('../lib/console-verify');
|
|
r = verifyPublishChangesApi(await readyFromApi(root, config));
|
|
} else if (form === 'apple-app-group') {
|
|
const { discoverTargets, readGroupEvidence } = await import('../apple/signing');
|
|
const { appGroupVerdicts } = await import('../lib/targets');
|
|
const { verifyAppGroupsApi } = await import('../lib/console-verify');
|
|
const want = args.get('bundle-id');
|
|
const scan = discoverTargets(root, config);
|
|
const targets = [...(scan.main ? [scan.main] : []), ...scan.extensions].filter(t => t.appGroups.length && (!want || t.bundleId === want));
|
|
if (!targets.length) throw new Error(`console verify apple-app-group: no target${want ? ` "${want}"` : ''} with App Groups in the project's entitlements or apple.signing.extensions`);
|
|
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);
|
|
const style = config.apple?.signing?.style ?? 'automatic';
|
|
const evidence = await readGroupEvidence(c, targets, style, config.apple?.signing?.profileType ?? 'IOS_APP_STORE');
|
|
r = verifyAppGroupsApi(targets, appGroupVerdicts(targets, evidence));
|
|
} 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)`);
|
|
// play-app-record: the app id from the redirect URL -> play.appId (dry diff; --write saves it)
|
|
let appId: { change: ReturnType<typeof appIdChange>; written: boolean } | null = null;
|
|
if (r.playAppId && config.play) {
|
|
const change = appIdChange(config, r.playAppId);
|
|
const written = args.has('write') && !args.has('dry') && !!ctx.loaded.path ? writePlayAppId(ctx.loaded.path, config, r.playAppId) : false;
|
|
appId = { change, written };
|
|
}
|
|
if (json) printJson(appId ? { ...r, appId } : r); else {
|
|
console.log(verifyTable(r));
|
|
if (appId) {
|
|
const file = basename(ctx.loaded.path || 'storebound.config.ts');
|
|
console.log(`\n${appIdDiff(appId.change, file)}`);
|
|
if (appId.written) log('console', `wrote play.appId '${appId.change.to}' into ${file}`);
|
|
else if (appId.change.changed) console.log('dry: run the same command with --write to save it');
|
|
}
|
|
}
|
|
if (!r.ok) process.exitCode = 1;
|
|
}
|