cross-store text: other platforms named in App Store text (2.3.10: Android, Google Play, ...) or Play text (Metadata: iOS, App Store, Game Center, ...) are refused by every text write (apple / play listing, play release / upload notes, listing set, whats-new, storebound edit, desktop editor), blockers in check / validate, scan other-platforms / play-other-platforms; listing.allowPlatformMentions for the owner's functional mentions; whats-new splits [apple] / [play] lines per store; storebound api --yes without a TTY is refused (exit 1, stderr) instead of a silent dry run; pitfalls 77, 78

This commit is contained in:
CRTkafa 2026-10-20 02:57:25 +07:00
parent 3e4015b686
commit edabbec874
32 changed files with 613 additions and 53 deletions

View file

@ -15,6 +15,7 @@
// from the config of an app folder).
import { appleLocaleIssues, type FieldIssue, playLangIssues } from './lib/listing-rules';
import type { AppleCopy } from './lib/copy';
import type { PlatformAllow } from './lib/cross-store';
import type { Ctx } from './lib/ctx';
import { confirm, need } from './lib/ctx';
import { done, log, printJson, warn } from './lib/log';
@ -39,7 +40,7 @@ export interface FieldPlan { field: string; from: string | null; to: string }
export interface SetPlan { store: 'play' | 'apple'; lang: string; create: boolean; changes: FieldPlan[]; same: string[]; issues: FieldIssue[]; refused: string | null; target?: string }
/** Old -> new for the given fields of one language (an empty and a missing value are the same). Pure. */
export function setPlan(store: 'play' | 'apple', lang: string, current: Record<string, string> | null, want: Record<string, string>, o: { appName: string; editable?: boolean }): SetPlan {
export function setPlan(store: 'play' | 'apple', lang: string, current: Record<string, string> | null, want: Record<string, string>, o: { appName: string; editable?: boolean; allow?: readonly PlatformAllow[] }): SetPlan {
const changes: FieldPlan[] = [];
const same: string[] = [];
for (const [f, v] of Object.entries(want)) {
@ -50,8 +51,8 @@ export function setPlan(store: 'play' | 'apple', lang: string, current: Record<s
// only the fields this run sends are judged (a store value it leaves alone is not this run's problem)
const sent = new Set(Object.keys(want));
const all = store === 'play'
? playLangIssues(lang, { title: merged.title || undefined, short: merged.short ?? '', full: merged.full ?? '' }, o.appName)
: appleLocaleIssues(lang, merged as AppleCopy, o.appName);
? playLangIssues(lang, { title: merged.title || undefined, short: merged.short ?? '', full: merged.full ?? '' }, o.appName, o.allow)
: appleLocaleIssues(lang, merged as AppleCopy, o.appName, o.allow);
const issues = all.filter(i => sent.has(i.field) && !(i.level === 'error' && /is required$/.test(i.msg) && !(want[i.field] !== undefined && !want[i.field]!.trim())));
let refused: string | null = null;
if (store === 'apple' && o.editable === false && changes.some(c => c.field !== 'promotionalText' && (APPLE_VERSION_FIELDS as readonly string[]).includes(c.field)))
@ -138,7 +139,7 @@ async function set(ctx: Ctx): Promise<void> {
const playName = config.play?.name ?? config.app.name;
const now = await playLive(ctx);
const cur = now?.find(l => l.language === lang) ?? null;
const p = setPlan('play', lang, cur ? { title: cur.title, short: cur.short, full: cur.full } : null, want, { appName: playName });
const p = setPlan('play', lang, cur ? { title: cur.title, short: cur.short, full: cur.full } : null, want, { appName: playName, allow: config.listing?.allowPlatformMentions });
show(ctx, p);
const errors = p.issues.filter(i => i.level === 'error').length;
if (a.has('json')) printJson({ ...p, dry, written: false });
@ -167,7 +168,7 @@ async function set(ctx: Ctx): Promise<void> {
promotionalText: vl?.attributes.promotionalText ?? '', whatsNew: vl?.attributes.whatsNew ?? '',
} : null;
const appName = config.apple?.name ?? config.app.name;
const p = { ...setPlan('apple', lang, cur, want, { appName, editable: r.editable }), target: r.version ? `version ${r.version.attributes.versionString}, ${r.version.attributes.appVersionState ?? r.version.attributes.appStoreState}` : 'no version' };
const p = { ...setPlan('apple', lang, cur, want, { appName, editable: r.editable, allow: config.listing?.allowPlatformMentions }), target: r.version ? `version ${r.version.attributes.versionString}, ${r.version.attributes.appVersionState ?? r.version.attributes.appStoreState}` : 'no version' };
show(ctx, p);
const errors = p.issues.filter(i => i.level === 'error').length;
if (a.has('json')) printJson({ ...p, dry, written: false });