shots: render refuses missing frames and fails on load errors; shots check errors on renders with failed loads (pitfall 93)
This commit is contained in:
parent
00756e6908
commit
88d1b58f1f
7 changed files with 78 additions and 2 deletions
|
|
@ -305,3 +305,20 @@ export function boxOutside(b: DeviceBox, W: number, H: number, tol = 1): string[
|
|||
if (b.x + b.w > W + tol) out.push(`right ${Math.round(b.x + b.w - W)} px`);
|
||||
return out;
|
||||
}
|
||||
|
||||
/** Frame files (relative to shots.frames: <lang>/<phone|tablet|landscape>/<src>) a render of these scenes and sizes
|
||||
* loads. A missing one renders an image with no app or game content (only a 404 in the page), so `shots render`
|
||||
* refuses to start without them. Landscape layouts use each scene's first frame; the feature graphic a phone frame. */
|
||||
export function framesForRender(scenes: { frames: { src: string }[] }[], sizes: string[], langs: string[],
|
||||
shots: Pick<ShotsConfig, 'landscape'> & { feature?: { frame?: string } }): string[] {
|
||||
const out = new Set<string>();
|
||||
for (const lang of langs) for (const size of sizes) {
|
||||
const s = SIZES[size];
|
||||
if (!s) continue;
|
||||
if (s.feature) { const f = shots.feature?.frame ?? scenes[0]?.frames[0]?.src; if (f) out.add(`${lang}/phone/${f}`); continue; }
|
||||
const d = renderDims(size, shots);
|
||||
const kind = d.landscape ? 'landscape' : s.device ? 'tablet' : 'phone';
|
||||
for (const sc of scenes) for (const f of d.landscape ? sc.frames.slice(0, 1) : sc.frames) out.add(`${lang}/${kind}/${f.src}`);
|
||||
}
|
||||
return [...out].sort();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ import { done, log, warn } from '../lib/log';
|
|||
import { closeBrowser, launch } from './browser';
|
||||
import { defaultConcurrency, pool, progressLine } from './capture-web';
|
||||
import { shotsStyle } from '../lib/config';
|
||||
import { type CopyFile, type DeviceBox, type TextBox, SIZES, renderDims, sheetTimeoutMs, featureLayout, copyText, defaultSizes, frameDims, mergeCopy, palette, renderDir, scriptFont, sheetLayout, validateCopy, variantScenes } from './lib';
|
||||
import { type CopyFile, type DeviceBox, type TextBox, SIZES, renderDims, sheetTimeoutMs, featureLayout, copyText, defaultSizes, frameDims, framesForRender, mergeCopy, palette, renderDir, scriptFont, sheetLayout, validateCopy, variantScenes } from './lib';
|
||||
import { bgCss, bgTone, type ContrastSample, contrastIssues, parseColour, photoBackground, photoCss, type RGB, worstContrast } from './palette';
|
||||
import { type WeightSample, weightIssues } from './weights';
|
||||
import type { Palette } from '../lib/config';
|
||||
|
|
@ -70,6 +70,12 @@ export async function run(ctx: Ctx): Promise<void> {
|
|||
if (bad.length) throw new Error(`--scenes: unknown scene id${bad.length > 1 ? 's' : ''} ${bad.join(', ')}${v ? ` (variant ${v})` : ''}; scene ids: ${job.scenes.map(s => s.id).join(', ')}`);
|
||||
}
|
||||
const framesDir = resolve(shots.frames), outDir = resolve(shots.out);
|
||||
// every frame the scenes load must exist: a missing one renders a store image with no app or game in it
|
||||
const gone = [...jobs.values()].flatMap(job => {
|
||||
const sel = a.list('scenes').length ? job.scenes.filter(s => a.list('scenes').includes(s.id)) : job.scenes;
|
||||
return framesForRender(sel, sizes, langs, shots);
|
||||
}).filter((f, i, all) => all.indexOf(f) === i && !existsSync(join(framesDir, f)));
|
||||
if (gone.length) throw new Error(`shots render: ${gone.length} frame file${gone.length > 1 ? 's' : ''} missing under ${relative(root, framesDir) || '.'} (run \`storebound shots capture\` first, or restore the frames folder; it is often git-ignored):\n ${gone.slice(0, 20).join('\n ')}${gone.length > 20 ? `\n ... ${gone.length - 20} more` : ''}`);
|
||||
if (a.has('dry')) {
|
||||
let n = 0;
|
||||
for (const [v, job] of jobs) for (const lang of langs) {
|
||||
|
|
@ -290,6 +296,8 @@ export async function run(ctx: Ctx): Promise<void> {
|
|||
server.stop(true);
|
||||
if (Object.keys(report).length) writeFileSync(join(outDir, 'report.json'), JSON.stringify(mergeReport(readReport(outDir), report), null, 1) + '\n');
|
||||
}
|
||||
const broken = Object.entries(report).filter(([, r]) => r.missing?.length);
|
||||
if (broken.length) throw new Error(`shots render: ${broken.length} image${broken.length > 1 ? 's' : ''} rendered with files that failed to load (frame, logo or background; the image has holes): ${broken.slice(0, 5).map(([k, r]) => `${k} (${r.missing!.join(', ')})`).join('; ')}${broken.length > 5 ? ' ...' : ''}`);
|
||||
const flagged = Object.values(report).filter(r => r.overflow.length).length;
|
||||
if (flagged) warn('shots', `${flagged} images with text overflow flags: storebound shots check / shots gallery`);
|
||||
done('shots', `render done${warnings ? `, ${warnings} with warnings (missing frames or fonts?)` : ''}`);
|
||||
|
|
|
|||
|
|
@ -88,6 +88,7 @@ export function scanRenders(dir: string, o: { langs: string[]; variants?: string
|
|||
const fromStore = !variant && !!pulledEntry(pulled, dir, file, bytes);
|
||||
const issues = fromStore ? [] : checkImage(info, size);
|
||||
const entry = report[rel.replace(/\.jpe?g$/i, '.png')];
|
||||
if (entry?.missing?.length) issues.push({ level: 'error', code: 'frame-missing', msg: `rendered while ${entry.missing.length} file${entry.missing.length > 1 ? 's' : ''} failed to load (${entry.missing.join(', ')}): the image lacks its frame / logo / background; restore the frames (shots capture) and render again` });
|
||||
for (const msg of entry?.overflow ?? []) issues.push({ level: 'warn', code: 'overflow', msg });
|
||||
issues.push(...fontIssues(entry?.fonts));
|
||||
issues.push(...deviceIssues(entry, size, info.w, info.h));
|
||||
|
|
|
|||
|
|
@ -122,7 +122,7 @@ export function imageInfo(b: Uint8Array): ImageInfo {
|
|||
}
|
||||
|
||||
export type IssueLevel = 'error' | 'warn';
|
||||
export interface Issue { level: IssueLevel; code: 'size' | 'alpha' | 'format' | 'bytes' | 'count' | 'overflow' | 'video' | 'font-fallback' | 'glyph-missing' | 'device-crop' | 'fonts' | 'margin' | 'font-weight' | 'contrast' | 'background'; msg: string }
|
||||
export interface Issue { level: IssueLevel; code: 'size' | 'alpha' | 'format' | 'bytes' | 'count' | 'overflow' | 'video' | 'font-fallback' | 'glyph-missing' | 'device-crop' | 'fonts' | 'margin' | 'font-weight' | 'contrast' | 'background' | 'frame-missing'; msg: string }
|
||||
|
||||
/** Problems with one image for a size key. */
|
||||
export function checkImage(info: ImageInfo, sizeKey: string): Issue[] {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue