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

@ -36,7 +36,8 @@
import { consoleLink, consoleUrl } from '../console-links';
import type { FixAction } from '../fix-actions';
import { templateBlockers } from '../template-answers';
import { BETA_WORDS, competitorRe, OTHER_PLATFORMS, PLACEHOLDER_MARKERS, PLACEHOLDER_TEXT, PRICE_CLAIMS } from '../listing-rules';
import { fileMentions, GUIDELINE, type PlatformHit, textMentions } from '../cross-store';
import { BETA_WORDS, competitorRe, PLACEHOLDER_MARKERS, PLACEHOLDER_TEXT, PRICE_CLAIMS } from '../listing-rules';
import type { ShipConfig } from '../config';
import { allSubscriptions } from '../subscriptions';
import { sdkOfProjectPath } from '../privacy-files';
@ -186,6 +187,17 @@ export function rewardNearReviewApi(sources: { path: string; text: string }[], m
}
/** Text lines (inside fenced blocks of a listing file, or JSON string values) that match `re`. */
/** Other platforms named in one store's listing file (and, for the App Store, the screenshot captions), with
* locale, field and line (pitfall 77). */
export function platformHits(inp: Pick<ScanInput, 'listings' | 'config'>, store: 'apple' | 'play'): Hit[] {
const allow = inp.config?.listing?.allowPlatformMentions;
const file = store === 'apple' ? inp.listings.apple : inp.listings.play;
const hits: PlatformHit[] = file ? fileMentions(store, file.path, file.text, allow) : [];
const cap = store === 'apple' ? inp.listings.captions : undefined;
if (cap) hits.push(...textMentions(cap.text, { store, locale: 'captions', field: 'screenshot captions', allow }).map(h => ({ ...h, file: cap.path, fileLine: h.line })));
return hits.map(h => ({ file: h.file!, line: h.fileLine!, text: `${h.locale} ${h.field}: "${h.term}" in ${h.text.slice(0, 120)}` }));
}
function listingHits(file: { path: string; text: string } | undefined, re: RegExp, o: { fenced?: boolean; field?: RegExp } = {}): Hit[] {
if (!file) return [];
const out: Hit[] = [];
@ -439,10 +451,10 @@ export function scanRules(inp: ScanInput): ScanFinding[] {
// ---- metadata (iOS listing + screenshot captions) ----
if (apple) {
const other = [...listingHits(inp.listings.apple, OTHER_PLATFORMS), ...listingHits(inp.listings.captions, OTHER_PLATFORMS, { fenced: false })];
const other = platformHits(inp, 'apple');
if (other.length)
add({ rule: 'other-platforms', guideline: '2.3.10', store: 'apple', severity: 'high', title: 'Other mobile platforms or stores named in the App Store metadata or screenshot captions', evidence: other.slice(0, 4),
fix: 'Remove "Android", "Google Play" and other platforms or stores from the App Store text and screenshots (Play listing text is separate: store/listing.md).', skill: 'aso-basics' });
add({ rule: 'other-platforms', guideline: GUIDELINE.apple, store: 'apple', severity: 'high', title: 'Other mobile platforms or stores named in the App Store metadata or screenshot captions', evidence: other.slice(0, 6),
fix: 'Remove "Android", "Google Play" and other platforms or stores from the App Store text and screenshots (a What\'s New shared with Play: tag the line [play]); a functional mention goes into listing.allowPlatformMentions (owner\'s call). Every App Store text write refuses these.', skill: 'aso-basics' });
const words = competitorRe();
const kw = [...listingHits(inp.listings.apple, words, { field: /^(keywords|name|subtitle)$/ })];
if (kw.length)