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

@ -14,6 +14,7 @@
// listing file: shows the diff, writes only with --write.
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
import { type AppleCopy, parseAppStoreListing } from '../lib/copy';
import { fieldsMentions, hitLine, type PlatformHit, refuseMentions } from '../lib/cross-store';
import { compactDiff } from '../lib/diff';
import { mergeAppleListing, overLimits } from '../lib/listing-pull';
import { counts, draftReason, draftSections, type ItemPlan, itemLines, planItem, planLocales, planVersion, short, type Skipped, type VersionPlan } from '../lib/listing-plan';
@ -86,6 +87,8 @@ export interface AppleListingPlan {
skipped: Skipped[];
notes: string[];
changes: number;
/** Android, Google Play, ... in the texts this run sends (Guideline 2.3.10): the run is refused (pitfall 77) */
mentions: PlatformHit[];
}
type Asc = Awaited<ReturnType<typeof ascClient>>;
@ -133,7 +136,10 @@ async function planListing(ctx: Ctx, c: Asc, appId: string, version: string): Pr
const changes = (versionPlan.action === 'create' || versionPlan.action === 'update' ? 1 : 0) + (build?.action === 'attach' ? 1 : 0)
+ infoLocs.filter(x => x.action !== 'same').length + (versionLocales ? versionLocales.filter(x => x.action !== 'same').length : Object.keys(wanted).length);
return { version, versionPlan, build, appInfo: { id: info.id, state: String(info.attributes?.state ?? info.attributes?.appStoreState ?? ''), locales: infoLocs },
const allow = config.listing?.allowPlatformMentions;
const mentions = Object.entries(listing).flatMap(([l, f]) => fieldsMentions('apple', l, { name: f.name, subtitle: f.subtitle, promotionalText: f.promotionalText,
keywords: f.keywords, description: f.description, whatsNew: versionPlan.first ? undefined : f.whatsNew }, allow));
return { mentions, version, versionPlan, build, appInfo: { id: info.id, state: String(info.attributes?.state ?? info.attributes?.appStoreState ?? ''), locales: infoLocs },
versionLocales, wanted, skipped, notes, changes };
}
@ -156,6 +162,7 @@ export function listingPlanLines(p: AppleListingPlan): string[] {
}
for (const s of p.skipped) out.push(`Skipped ${s.key}: ${s.reason}`);
for (const n of p.notes) out.push(`Note: ${n}`);
for (const h of p.mentions ?? []) out.push(`REFUSE: ${hitLine(h)}`);
return out;
}
@ -187,7 +194,8 @@ export async function run(ctx: Ctx): Promise<void> {
const c = await ascClient(root, need(config.apple?.bundleId, 'apple.bundleId'), config.apple?.appId);
const appId = await c.app();
const p = await planListing(ctx, c, appId, version);
const refused = [p.versionPlan.action === 'refuse' ? p.versionPlan.reason : '', p.build?.action === 'refuse' ? p.build.reason : ''].filter(Boolean);
const refused = [p.versionPlan.action === 'refuse' ? p.versionPlan.reason : '', p.build?.action === 'refuse' ? p.build.reason : '',
(() => { try { refuseMentions(p.mentions, 'apple listing'); return ''; } catch (e) { return (e as Error).message; } })()].filter(Boolean);
if (a.has('json')) printJson({ dry, ...p, refused });
else for (const line of listingPlanLines(p)) console.log(line);
if (refused.length) { if (dry) { process.exitCode = 1; warn('apple', `dry run: would refuse: ${refused.join('; ')}`); return; } throw new Error(refused.join('; ')); }