--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

@ -712,3 +712,31 @@ that fails without the fix (see CONTRIBUTING.md).
one owner question list. Remove an area from `unanswered` only after the owner answered it.
- **Guard:** `cli/lib/template-answers.ts`, `cli/lib/onboard-declarations.ts`, the `template-answers` scan rule,
`tests/template-answers.test.ts` (init blocks, data-safety --dry refuses, onboard on auth code without an ad SDK).
## 61. `prices ppp --json` printed log lines on stdout before the JSON
- **Do:** with `--json`, read stdout as one JSON document and stderr as the log. Every command run with `--json` goes
through `withJsonOutput` (cli/lib/log.ts, wrapped in cli/index.ts): log / done / tables go to stderr, `printJson` is
the only writer to stdout, prompts go to stderr. Never print a result with `console.log(JSON.stringify(...))`.
- **Guard:** `tests/json-stdout.test.ts` runs every command whose module reads `--json` against the fake stores and
`JSON.parse`s its stdout, plus one real CLI run (`prices ppp --offline --dry --json`).
## 62. PPP prices cut Germany to EUR 3.59 for a USD 4.99 product (store price EUR 5.99)
- **Do:** trust `prices ppp` defaults: only lower prices, only economies with a price level at most 50 % of the US,
World Bank high-income economies and mainland China keep the store's own US-equivalent price, never above it; a
top-10 revenue market dropping more than 20 % is warned about. The old math set a net price (base x PPP level x
exchange rate) against VAT-inclusive store prices and cut every region. Now par = the store's own conversion (Play
convertRegionPrices, Apple equalizations; offline base x fx x (1 + VAT), rounded up), share = clamp(level, 0.3, 1).
Regions that keep their price are left out of the sheet (`kept` in `--json`), so `prices import` leaves the store's
price there. `--include-high-income`, `--max-level`, `--min-ratio` / `--max-ratio`, `--no-only-decreases` widen it,
only after the owner asks.
- **Guard:** `shareOf`, `offlinePar`, `planRows`, `topMarketDrops` (cli/lib/ppp.ts), `tests/ppp.test.ts` (Germany
keeps EUR 5.99, India and Turkey drop, CN skipped on Play).
## 63. Creating new IAPs also changed live prices of existing ones (a terminal "y" covered them)
- **Do:** never rely on a confirmation for price changes. Creating products never changes an existing product's price
unless `--allow-price-changes` is passed, TTY or not; the terminal question of `play products`, `prices import`,
`play subscriptions` and `apple subscriptions` covers creates and other updates only. With the flag, price changes
get their own question listing each old -> new (in a terminal even with `--yes`). Dry runs list them in a separate
"price changes on existing products (held)" section. `apple iap` never changes an existing schedule.
- **Guard:** `allowPriceChanges` / `heldSection` (cli/lib/price-gate.ts), `tests/price-gate.test.ts` (TTY and non-TTY:
a "y" creates the new product and holds the live price; the flag's question names each change).