--json prints only JSON on stdout (withJsonOutput around every command: logs, tables and prompts to stderr; printJson everywhere; test runs every --json command against the fake stores and parses stdout); prices ppp compares like with like: par = the store's own US-equivalent price (Play convertRegionPrices, Apple equalizations; offline base x fx x (1 + VAT) rounded up) scaled by the PPP price level; defaults only lower, only price levels <= 0.5, World Bank high-income at par, CN at par on Apple and skipped on Play, never above par (--only-decreases, --max-ratio, --min-ratio, --max-level, --include-high-income; --floor / --ceiling kept), top-10 revenue market drops > 20 % warned (sales reports, else a static list), unchanged regions out of the sheet; price changes of existing products only with --allow-price-changes, TTY or not (a terminal yes covered them), then their own question listing each old -> new, held section in dry runs (play products, prices import, play / apple subscriptions); pitfalls 61-63; tests

This commit is contained in:
CRTkafa 2026-10-14 17:36:00 +08:00
parent bab3f63227
commit 357319a775
30 changed files with 865 additions and 209 deletions

View file

@ -26,6 +26,7 @@ import { verify } from './lib/minisign';
import { parsePlist } from './lib/plist';
import { addFolder } from './lib/registry';
import * as rt from './lib/rt';
import { printJson, promptOut } from './lib/log';
import { stdinIsTTY } from './lib/tty';
type Fetch = (url: string) => Promise<Response>;
@ -128,7 +129,7 @@ function canWrite(dir: string): boolean {
}
async function askYes(text: string): Promise<boolean> {
process.stdout.write(text);
promptOut().write(text);
const line = await rt.readLine();
return line !== null && !/^n(o)?$/i.test(line.trim());
}
@ -158,7 +159,7 @@ export async function run(args: Args): Promise<void> {
if (manifest && !chosen && !error) error = `version ${manifest.version} has no ${args.get('format') ?? 'installer'} for ${platform} ${process.arch} yet`;
if (args.has('download')) {
if (json) { console.log(JSON.stringify({ cli, installed, installedVersion, page: PAGE, version: manifest?.version ?? null, download: chosen ? { kind: chosen[0], ...chosen[1] } : null, error: error || null }, null, 2)); return; }
if (json) { printJson({ cli, installed, installedVersion, page: PAGE, version: manifest?.version ?? null, download: chosen ? { kind: chosen[0], ...chosen[1] } : null, error: error || null }); return; }
if (!chosen) throw new Error(`${error}; see ${PAGE}`);
const [kind, d] = chosen;
if (dry) { console.log(`dry run: would download ${d.file} (${(d.size / 1e6).toFixed(1)} MB, ${DOWNLOAD_LABEL[kind]}) into ${tilde(downloadsDir())} and check it`); return; }
@ -179,12 +180,12 @@ export async function run(args: Args): Promise<void> {
}) : null;
if (json) {
console.log(JSON.stringify({
printJson({
cli, installed, installedVersion, decision, link, folder: isApp ? dir : null,
download: chosen && changing ? { kind: chosen[0], ...chosen[1] } : null,
plan: plan ? { target: plan.target, place: plan.place, manual: plan.manual ?? null, steps: plan.steps.map(s => s.what) } : null,
command: installed && !changing ? openCommand(platform, installed, link) : null,
}, null, 2));
});
return;
}
for (const n of decision.notes) console.log(`storebound desktop: ${n}`);