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

@ -56,7 +56,7 @@ import { consoleLink } from '../console-links';
import { PLAY_AGE_GROUPS } from '../config';
import { allSubscriptions } from '../subscriptions';
import { grep, type Hit, type ProjectFacts } from './project';
import type { ScanFinding, ScanInput } from './rules';
import { platformHits, type ScanFinding, type ScanInput } from './rules';
import { versionAtLeast } from './sdks';
const PLAY = 'https://support.google.com/googleplay/android-developer/answer/';
@ -347,6 +347,11 @@ export function playRules(inp: ScanInput, x: PlayCtx): void {
// ---- Play listing metadata ----
if (c?.play || inp.listings.play) {
const listing = inp.listings.play;
// Apple platforms or services named in Play text (Metadata policy; the mirror of App Store 2.3.10, pitfall 77)
const apple = platformHits(inp, 'play');
if (apple.length)
add({ rule: 'play-other-platforms', guideline: 'Play: Metadata', severity: 'high', title: 'Apple platforms or stores (iOS, iPhone, App Store, Game Center, TestFlight) named in the Play listing text', evidence: apple.slice(0, 6),
ref: PLAY_REFS.metadata, skill: 'aso-basics', fix: 'Remove them from store/listing.md (release notes shared with the App Store: tag the line [apple]); a functional mention ("Sign in with Apple") goes into listing.allowPlatformMentions (owner\'s call). Every Play text write refuses these.' });
const titles: Hit[] = fieldBlocks(listing, /\btitle\b/i).map(b => ({ ...b.at, text: b.text.trim() }));
if (!titles.length && c?.app.name) titles.push(...cfg(/\bname\s*:/, c.app.name).map(h => ({ ...h, text: c.app.name })));
const long = titles.filter(t => [...t.text].length > 30);

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)