apple screenshots: iPhone Duo through the App Asset Library (IPHONE_DUO placement group, APP_SCREENSHOT), pitfall 130

This commit is contained in:
CRTkafa 2026-11-08 14:19:12 +07:00
parent b1043f5d79
commit 73a962fbdc
10 changed files with 174 additions and 32 deletions

View file

@ -6,7 +6,7 @@
// ios65/NN.png (6.5", 1284 x 2778 -> APP_IPHONE_65) and, only with apple.ipad, ipad13/NN.png (13" iPad, 2064 x 2752 ->
// APP_IPAD_PRO_3GEN_129: the API has no "13 inch" value, the 13" display set keeps the 3rd gen 12.9" name). With
// --platform macos (or when only the macOS version is editable) and apple.mac: mac/NN.png (2880 x 1800 -> APP_DESKTOP)
// to the macOS version. iPhone Duo sets are skipped with the console deep link (no API display type yet). Up to 10
// to the macOS version. iPhone Duo (apple.iphoneDuo: iphone-duo-outer/ then iphone-duo-inner/) goes through the App Asset Library. Up to 10
// per set. --variant <id> uploads <renders>/<id>/<lang>/... instead (an A/B variant from shots.variants, e.g. for a
// Product Page Optimization treatment you then set up in App Store Connect). Apple processes uploads asynchronously; failed
// ones are reported at the end. Long: run in the background with a log file.
@ -20,7 +20,8 @@ import { variantRoot } from '../shots/lib';
import { findOrCreate } from '../lib/upsert';
import { pulledChecksum } from '../lib/art-pull';
import { consoleLink } from '../lib/console-links';
import { IPHONE_DUO_DISPLAY, IPHONE_DUO_NOT_IN_API } from '../shots/spec';
import { IPHONE_DUO_DISPLAY } from '../shots/spec';
import { duoGroup, syncDuo } from './duo-library';
import { type Asc, ascClient, EDITABLE, keptPrefix, md5, setUpToDate, type J } from './asc';
import { pendingScreenshots, repairPlan, repairSet, setRows, updateLedger } from './screenshot-health';
import { allVersions, parsePlatform, pickPlatform, platformLabel, stateOf, versionLabel } from './platform';
@ -42,12 +43,9 @@ export const displaySets = (apple: { ipad?: boolean; iphoneMedium?: boolean; iph
platform === 'MAC_OS' ? (apple?.mac ? MAC_DISPLAY : {}) : ({ ...DISPLAY, ...(apple?.iphoneMedium ? MEDIUM_DISPLAY : {}), ...(apple?.ipad ? IPAD_DISPLAY : {}),
...(apple?.iphoneDuo ? Object.fromEntries(Object.entries(IPHONE_DUO_DISPLAY).filter((e): e is [string, string] => !!e[1])) : {}) });
/** iPhone Duo size keys `apple screenshots` skips (apple.iphoneDuo on, no API display type yet), with the message. */
export function skippedDuoSets(apple: { iphoneDuo?: boolean; appId?: string } | undefined): { dirs: string[]; msg: string } | null {
const dirs = apple?.iphoneDuo ? Object.entries(IPHONE_DUO_DISPLAY).filter(([, t]) => !t).map(([k]) => k) : [];
// the console takes them (its own "iPhone Duo" tab): send the owner straight to the version page
return dirs.length ? { dirs, msg: `${dirs.join(', ')}: ${IPHONE_DUO_NOT_IN_API}. Add them in ${consoleLink('asc.version', '> App Previews and Screenshots > iPhone Duo', { appleAppId: apple?.appId })}` } : null;
}
/** iPhone Duo size keys (render folders) `apple screenshots` uploads through the App Asset Library (no
* ScreenshotDisplayType exists for Duo: cli/apple/duo-library.ts, pitfall 130), outer display first. */
export const duoDirs = (apple: { iphoneDuo?: boolean } | undefined): string[] => (apple?.iphoneDuo ? Object.keys(IPHONE_DUO_DISPLAY) : []);
/** Display types `validate` requires on every locale: apple.screenshotTypes, else the classes App Store Connect's
* "App Previews and Screenshots" tab lists (October 2026): iPhone with Dynamic Island (medium display), filled by
@ -98,8 +96,9 @@ export async function run(ctx: Ctx): Promise<void> {
const version = await c.editableVersion(platform);
if (!version) throw new Error(`no editable ${platformLabel(platform)} App Store version (run apple listing --version x.y.z first)`);
log('apple', `screenshots for version ${versionLabel(version)}`);
const duoSkip = platform === 'MAC_OS' ? null : skippedDuoSets(config.apple);
if (duoSkip) warn('apple', duoSkip.msg);
const duo = platform === 'MAC_OS' ? [] : duoDirs(config.apple);
const duoGroupId = duo.length ? await duoGroup(c) : '';
const appId = duo.length ? await c.app() : '';
if (platform === 'MAC_OS' && !config.apple?.mac) throw new Error('macOS version: set apple.mac: true and render the mac size (storebound shots render --size mac) first');
if (platform !== 'MAC_OS' && config.apple?.mac) log('apple', 'Mac screenshots (mac/, APP_DESKTOP) go to the macOS version: run apple screenshots --platform macos');
const langOf = byAppleLocale(config.languages);
@ -127,6 +126,11 @@ export async function run(ctx: Ctx): Promise<void> {
if (!existsSync(d) || !files.length) { warn('apple', `${locale}: nothing in ${d}, skipped`); continue; }
await syncSet(loc.id, sets, type, files, `${locale} ${type}`);
}
if (duo.length) {
const files = duo.flatMap(dir => numberedImages(join(src, lang.id, dir)).filter(f => f.endsWith('.png')));
if (!files.length) warn('apple', `${locale}: nothing in ${duo.map(d => join(src, lang.id, d)).join(' or ')}, iPhone Duo skipped`);
else await syncDuo(c, { appId, group: duoGroupId, loc, files, dry: ctx.args.has('dry') });
}
});
}
log('apple', `${tasks.length} locales, ${jobs} in parallel`);