price changes of existing products need --allow-price-changes (play products, play/apple subscriptions, prices import): --yes and flow run --apply --yes confirm creates, first prices and other updates only; without the flag the run writes the rest, names each held change and exits 3 (declined), dry runs mark them owner, flows refuse the flag; lib/price-gate.ts, tests/price-gate.test.ts. CLI wording: console fill leaves what it cannot open or fill to the calling agent's browser tool (console plan / verify) instead of 'yourself'; other 'by hand' output points at the config; do-it-dont-ask test scans cli/ output
This commit is contained in:
parent
b5b8f2da9d
commit
e01a27d7b7
33 changed files with 414 additions and 117 deletions
|
|
@ -3,12 +3,16 @@
|
|||
// one-time products and (unless --no-subscriptions) auto-renewable subscriptions: Apple's current and scheduled
|
||||
// subscription prices per territory, Play's regional prices of the base plan. Subscription rows use the same
|
||||
// columns; `apple subscriptions --csv` / `play subscriptions --csv` read them back (prices import skips them).
|
||||
// storebound prices import [--csv store/prices.csv] [--store apple|play] [--only ids] [--reconvert] [--all] [--dry] [--yes]
|
||||
// storebound prices import [--csv store/prices.csv] [--store apple|play] [--only ids] [--reconvert] [--all] [--dry] [--yes] [--allow-price-changes]
|
||||
// storebound prices ppp [--base <USD>] [...] purchasing power price sheet (prices-ppp.ts)
|
||||
// CSV columns: product, store (apple | play | both), region (ISO alpha-2), currency, price, start_date (App Store
|
||||
// only, a future YYYY-MM-DD = scheduled change), kind (manual | auto; auto rows are Apple's equalized prices, exported
|
||||
// for reference and ignored on import).
|
||||
// import: reads the current prices, prints the diff table, and (not --dry) asks before writing. Apple: one new price
|
||||
// import: reads the current prices, prints the diff table, and (not --dry) asks before writing. A product that already
|
||||
// has prices on a store is a price change: the owner's call. In a terminal (no --yes) the one question covers it;
|
||||
// --yes (also from `flow run --apply --yes`) only sets first prices and never changes existing ones, which need
|
||||
// --allow-price-changes: without it the run writes the first prices, prints "price change on <id> (<from> -> <to>)
|
||||
// needs the owner" per product and exits 3 (declined). The dry run marks those pairs "owner". Apple: one new price
|
||||
// schedule per changed product (base territory + every manual price; territories without one are equalized by Apple;
|
||||
// an increase cannot be undone once it starts). Play: the purchase option's regional prices are replaced: CSV regions
|
||||
// on top of the current ones; regions without a price get convertRegionPrices from the USD base (CSV US row, else the
|
||||
|
|
@ -18,6 +22,7 @@ import { ascClient, type Asc, type J } from './apple/asc';
|
|||
import type { Ctx } from './lib/ctx';
|
||||
import { confirm, need } from './lib/ctx';
|
||||
import { done, log, printJson, step, warn } from './lib/log';
|
||||
import { allowPriceChanges, type HeldPrice, holdPriceChanges } from './lib/price-gate';
|
||||
import { toMoney } from './lib/money';
|
||||
import {
|
||||
appleSchedule, appleScheduleBody, applePriceRows, diffPrices, diffTable, parsePriceCsv, type PlayProduct, playPatchBody, playPriceRows, playRegions,
|
||||
|
|
@ -143,8 +148,26 @@ export async function run(ctx: Ctx): Promise<void> {
|
|||
console.log(diffTable(diff, { all: a.has('all') }));
|
||||
if (!changed.size) { done(TAG, 'prices up to date'); return; }
|
||||
log(TAG, `${changed.size} product / store pair(s) to update`);
|
||||
// a pair that has prices on the store already is a price change of a live product: the owner's call
|
||||
const live = [...changed].filter(k => current.some(r => `${r.product}|${r.store}` === k && r.kind !== 'auto'));
|
||||
const first = [...changed].filter(k => !live.includes(k));
|
||||
if (live.length) log(TAG, `owner: price change of ${live.length} existing product / store pair(s) (--allow-price-changes): ${live.map(k => k.replace('|', ' ')).join(', ')}`);
|
||||
if (dry) { done(TAG, 'dry run: nothing written'); return; }
|
||||
if (!(await confirm(ctx, `Write the new prices for ${changed.size} product / store pair(s)? Users see them as soon as the stores apply them; an App Store price increase cannot be undone once it starts.`))) return;
|
||||
let liveOk: boolean;
|
||||
if (process.stdin.isTTY && !a.has('yes') && process.env.STOREBOUND_SERVE !== '1') {
|
||||
if (!(await confirm(ctx, `Write the new prices for ${changed.size} product / store pair(s)? Users see them as soon as the stores apply them; an App Store price increase cannot be undone once it starts.`))) return;
|
||||
liveOk = true;
|
||||
} else {
|
||||
if (first.length && !(await confirm(ctx, `Set the first prices of ${first.length} product / store pair(s) (${first.map(k => k.replace('|', ' ')).join(', ')})?`))) return;
|
||||
liveOk = await allowPriceChanges(ctx, live.length, `Change the prices of ${live.length} existing product / store pair(s) (${live.map(k => k.replace('|', ' ')).join(', ')})? Users see them as soon as the stores apply them; an App Store price increase cannot be undone once it starts.`);
|
||||
}
|
||||
const held: HeldPrice[] = [];
|
||||
if (!liveOk) for (const k of live) {
|
||||
changed.delete(k);
|
||||
const rows = diff.filter(d => `${d.product}|${d.store}` === k && d.change !== 'same');
|
||||
held.push({ id: k.replace('|', ' '), changes: rows.length ? rows.map(d => ({ region: d.region, from: d.from === null ? '-' : `${d.currency} ${d.from}`, to: `${d.currency} ${d.to}${d.startDate ? ` from ${d.startDate}` : ''}` }))
|
||||
: [{ region: 'every region', from: 'current', to: 'reconverted' }] });
|
||||
}
|
||||
|
||||
for (const p of products) {
|
||||
const wanted = wantedAll.filter(r => r.product === p.id);
|
||||
|
|
@ -180,5 +203,6 @@ export async function run(ctx: Ctx): Promise<void> {
|
|||
log(TAG, `${p.id} play: ${regions.length} regions set`);
|
||||
}
|
||||
}
|
||||
done(TAG, 'prices imported (storebound prices export shows the stores\' view)');
|
||||
holdPriceChanges(held);
|
||||
done(TAG, held.length ? `${first.length ? 'first prices imported' : 'nothing written'}, price changes left for the owner` : 'prices imported (storebound prices export shows the stores\' view)');
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue