apple.macos listing / review notes per platform + iOS-only claim warnings; apple upload of a .pkg: bounded pkgutil, macOS processing wait, heartbeats; release 409 pattern

This commit is contained in:
CRTkafa 2026-11-03 23:30:59 +07:00
parent 53d57544bf
commit 1e47c2ee60
18 changed files with 417 additions and 63 deletions

View file

@ -72,7 +72,10 @@ export interface AppleSnapshot {
build: { number: string; processingState: string; usesNonExemptEncryption: boolean | null; declarationState: string | null } | null;
/** Locales the config expects (apple: entries of `languages`). */
expectedLocales: string[];
locales: { locale: string; name?: string; subtitle?: string; description?: string; keywords?: string; screenshots: Record<string, number> }[];
locales: { locale: string; name?: string; subtitle?: string; description?: string; keywords?: string; promotionalText?: string; whatsNew?: string; screenshots: Record<string, number> }[];
/** apple.macos facts for the macOS claim check (validate fills it): the config's macOS review notes (what `apple review-details
* --platform macos` would send) and the gameCenter / noIap switches. */
macos?: { notes?: string; gameCenter?: boolean; noIap?: boolean };
requiredDisplayTypes: string[];
/** apple.iphoneDuo (iPhone Duo renders on) and whether the project has a play section, for the Duo note. */
iphoneDuo?: boolean;
@ -208,6 +211,35 @@ export function copyrightRules(s: Pick<AppleSnapshot, 'version' | 'configCopyrig
const PLATFORM_NAMES: Record<string, string> = { IOS: 'iOS', MAC_OS: 'macOS', TV_OS: 'tvOS', VISION_OS: 'visionOS' };
const MACOS_CLAIMS: { kind: string; re: RegExp; needs?: 'noGameCenter' | 'noIap'; what: string }[] = [
{ kind: 'game-center', re: /game\s*center/i, needs: 'noGameCenter', what: 'Game Center (set apple.macos.gameCenter: true when the Mac app has it)' },
{ kind: 'touch', re: /\b(tap|taps|tapping|swipe|swipes|swiping|pinch|pinches)\b|touch[\s-]?screen/i, what: 'touch words (tap, swipe, pinch, touch screen)' },
{ kind: 'ios-device', re: /\b(iphone|ipad)s?\b/i, what: 'an iPhone / iPad mention' },
{ kind: 'iap', re: /in[\s-]app[\s-]purchases?|\biaps?\b|restore purchases/i, needs: 'noIap', what: 'an in-app purchase line (apple.macos.noIap is set)' },
];
/**
* A macOS version's texts (description, promotional text, what's new, keywords, subtitle) and review notes (live, and
* the config's apple.macos.reviewNotes / apple.info.reviewNotes) that carry iOS-only claims. Warnings only. Game Center
* is flagged unless apple.macos.gameCenter is true; in-app purchase lines only when apple.macos.noIap is set. Pure.
*/
export function macosClaimRules(s: Pick<AppleSnapshot, 'version' | 'locales' | 'reviewDetail' | 'macos'>): Finding[] {
if (s.version?.platform !== 'MAC_OS') return [];
const texts: { where: string; text: string }[] = [];
for (const l of s.locales) for (const k of ['subtitle', 'description', 'promotionalText', 'whatsNew', 'keywords'] as const) if (l[k]) texts.push({ where: `${l.locale} ${k}`, text: l[k]! });
if (s.reviewDetail?.notes) texts.push({ where: 'review notes (on the store)', text: s.reviewDetail.notes });
if (s.macos?.notes) texts.push({ where: 'review notes (config)', text: s.macos.notes });
const out: Finding[] = [];
for (const c of MACOS_CLAIMS) {
if (c.needs === 'noGameCenter' && s.macos?.gameCenter) continue;
if (c.needs === 'noIap' && !s.macos?.noIap) continue;
const hits = [...new Set(texts.filter(t => c.re.test(t.text)).map(t => t.where))];
if (hits.length) out.push({ store: 'apple', level: 'warn', code: 'macos-claims', msg: `macOS version ${s.version.versionString}: ${c.what} in ${hits.slice(0, 6).join(', ')}${hits.length > 6 ? ` (+${hits.length - 6} more)` : ''}`,
fix: 'a separate macOS listing file (apple.macos.listing, then storebound apple listing --platform macos) and apple.macos.reviewNotes (storebound apple review-details --platform macos)' });
}
return out;
}
export function appleRules(s: AppleSnapshot): Finding[] {
const f: Finding[] = [];
const add = (level: Level, code: string, msg: string, fix?: string, action?: FixAction, url?: string) => f.push({ store: 'apple', level, code, msg, fix, ...(action ? { action } : {}), ...(url ? { url } : {}) });
@ -232,6 +264,7 @@ export function appleRules(s: AppleSnapshot): Finding[] {
add('warn', 'version-platform', `version ${o.versionString} (${name}) is ${o.state} too: this report covers ${vl || 'iOS'} only`, `storebound validate --platform ${name.toLowerCase()}; storebound apple status lists every platform`);
}
f.push(...copyrightRules(s));
f.push(...macosClaimRules(s));
if (!s.build) add('blocker', 'build', 'no build attached to the version', 'storebound apple listing --version x.y.z --build N (pick the build number you just uploaded)');
else {