play images: tablet, TV and Wear sets are optional; play.tablets switch (default: tablet renders exist); a missing optional set never deletes Play's images without --prune

A phone-only project aborted with 'no sevenInchScreenshots'. Play requires the 512 icon, the 1024x500 feature graphic and at least 2 screenshots (Play Console Help answer 9866151). play images uploads what exists and prints an info line for missing optional sets; play.tablets true makes missing tablet sets an error in shots render/check/gallery, validate and play images. Pitfall 19.
This commit is contained in:
CRTkafa 2026-10-12 08:21:03 +08:00
parent 18fde37b9b
commit a9261a8052
18 changed files with 364 additions and 83 deletions

View file

@ -4,7 +4,8 @@
// Out: <shots.out>/<lang>/<size>/NN.png (24-bit RGB: Play rejects alpha / palette PNGs), <lang>/feature/1024x500.png,
// <lang>/sheet.png (contact sheet of every size packed into a grid, for a quick review) and <shots.out>/report.json (text overflow flags
// per image, read by `shots check` and `shots gallery`). Variants go to <shots.out>/<variant>/<lang>/...
// Default sizes: defaultSizes() (ipad13 only with apple.ipad). Needs playwright + ffmpeg.
// Default sizes: defaultSizes() (ipad13 only with apple.ipad; tab7 / tab10 only with play.tablets, default: tablet renders
// already exist). Needs playwright + ffmpeg.
// Fonts: the per-language stacks from .storebound/fonts/fonts.json (`shots fonts`). Before rendering, every character
// of the copy is checked against the cmaps of the downloaded faces (stale fonts fail with the language and the
// characters); while rendering, the page measures which face drew every character, and report.json records it per
@ -25,7 +26,7 @@ import { defaultConcurrency, pool, progressLine } from './capture-web';
import { shotsStyle } from '../lib/config';
import { type CopyFile, SIZES, copyText, defaultSizes, frameDims, mergeCopy, palette, renderDir, scriptFont, sheetLayout, validateCopy, variantScenes } from './lib';
import { faceFilePath, fontsRoute, resolveShotFonts, staleLocalFonts } from './local-fonts';
import { type RenderReport, mergeReport, readReport } from './report';
import { type RenderReport, mergeReport, readReport, resolvedTablets } from './report';
const TYPES: Record<string, string> = { html: 'text/html; charset=utf-8', png: 'image/png', jpg: 'image/jpeg', jpeg: 'image/jpeg',
webp: 'image/webp', woff2: 'font/woff2', woff: 'font/woff', ttf: 'font/ttf', otf: 'font/otf', css: 'text/css', svg: 'image/svg+xml' };
@ -35,7 +36,10 @@ export async function run(ctx: Ctx): Promise<void> {
const shots = need(config.shots, 'shots');
const a = ctx.args;
const langs = a.list('lang').length ? a.list('lang') : config.languages.map(l => l.id);
const sizes = a.list('size').length ? a.list('size') : defaultSizes(config);
const tablets = resolvedTablets(config, resolve(shots.out));
const sizes = a.list('size').length ? a.list('size') : defaultSizes(config, tablets);
if (config.play && !tablets && !a.list('size').length)
log('shots', `Play tablet screenshots: not rendered (${config.play.tablets === false ? 'play.tablets is false' : 'no tablet renders yet; set play.tablets: true to render tab7 / tab10'}; optional on Play)`);
for (const s of sizes) if (!SIZES[s]) throw new Error(`unknown size ${s} (${Object.keys(SIZES).join(', ')})`);
const variants: (string | undefined)[] = a.has('variants') ? [undefined, ...Object.keys(shots.variants ?? {})] : [a.get('variant')];
const baseCopy = JSON.parse(readFileSync(resolve(shots.copy), 'utf8')) as CopyFile;