Screenshot guardrails: name every screenshot not COMPLETE on 409 ASSET_IN_POST_PROCESSING (submit, validate; stuck after 5 min in UPLOAD_COMPLETE), apple screenshots --repair-stuck replaces only those in place, part uploads without Expect: 100-continue (failed part deletes the reservation, one retry, never committed), storebound api --confirm <operationId> for agents' plain writes (never owner-only)
This commit is contained in:
parent
164182012f
commit
00756e6908
19 changed files with 475 additions and 45 deletions
|
|
@ -1,6 +1,7 @@
|
|||
// Store screenshots of every locale on the editable App Store version (idempotent: a set whose files already match
|
||||
// by MD5 and order is left alone, otherwise it is emptied and re-uploaded).
|
||||
// storebound apple screenshots [--src store/renders] [--only en-US,tr] [--jobs 4] [--variant b] [--dry]
|
||||
// storebound apple screenshots --repair-stuck [--dry] (only the screenshots stuck outside COMPLETE, in place)
|
||||
// Source per language: <renders>/<language id>/ios69/NN.png (6.9", 1320 x 2868 -> APP_IPHONE_67),
|
||||
// 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
|
||||
|
|
@ -21,6 +22,7 @@ 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 { 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';
|
||||
|
||||
/** Render size key -> ASC screenshotDisplayType (iPhone sets). */
|
||||
|
|
@ -104,6 +106,8 @@ export async function run(ctx: Ctx): Promise<void> {
|
|||
const syncSet = (locId: string, sets: J[], type: string, files: string[], tag: string) =>
|
||||
syncScreenshotSet(c, { rel: 'appStoreVersionLocalization', type: 'appStoreVersionLocalizations', id: locId }, sets, type, files, tag, ctx.args.has('dry'));
|
||||
|
||||
if (ctx.args.has('repair-stuck')) { await repairStuck(ctx, c, root, locs, src, platform, only); return; }
|
||||
|
||||
const tasks: (() => Promise<void>)[] = [];
|
||||
for (const loc of locs) {
|
||||
const locale: string = loc.attributes.locale;
|
||||
|
|
@ -132,3 +136,35 @@ export async function run(ctx: Ctx): Promise<void> {
|
|||
}
|
||||
done('apple', bad ? `${bad} screenshots failed processing (re-run to retry)` : 'screenshots done');
|
||||
}
|
||||
|
||||
/** --repair-stuck: only the screenshots that are not COMPLETE and will not finish on their own (UPLOAD_COMPLETE for
|
||||
* over 5 min, FAILED, or a stranded AWAITING_UPLOAD reservation) are replaced at their own positions with the same
|
||||
* local file (same name in the renders). Everything else stays; still-processing ones are left to finish. --dry lists
|
||||
* the plan. Re-runnable: a run cut midway leaves the rest for the next run, which skips what is already COMPLETE. */
|
||||
async function repairStuck(ctx: Ctx, c: Asc, root: string, locs: J[], src: string, platform: string, only: string[]): Promise<void> {
|
||||
const dry = ctx.args.has('dry');
|
||||
const langOf = byAppleLocale(ctx.loaded.config.languages);
|
||||
const dirOf = Object.fromEntries(Object.entries(displaySets(ctx.loaded.config.apple, platform)).map(([d, t]) => [t, d] as const));
|
||||
let total = 0, held = 0;
|
||||
for (const loc of locs) {
|
||||
const locale: string = loc.attributes.locale;
|
||||
if (only.length && !only.includes(locale)) continue;
|
||||
for (const set of await c.all(`/v1/appStoreVersionLocalizations/${loc.id}/appScreenshotSets?limit=50`)) {
|
||||
const type: string = set.attributes.screenshotDisplayType;
|
||||
const rows = setRows(await c.all(`/v1/appScreenshotSets/${set.id}/appScreenshots?limit=50`), locale, type);
|
||||
const pending = pendingScreenshots(rows, updateLedger(root, rows), Date.now());
|
||||
held += pending.filter(p => p.kind === 'processing').length;
|
||||
const lang = langOf.get(locale);
|
||||
const dir = dirOf[type];
|
||||
const files = lang && dir ? numberedImages(join(src, lang.id, dir)).filter(f => f.endsWith('.png')) : [];
|
||||
const steps = repairPlan(rows, pending, files);
|
||||
if (!steps.length) continue;
|
||||
total += steps.length;
|
||||
const tag = `${locale} ${type}`;
|
||||
if (dry) { for (const st of steps) log('apple', `${tag}: would replace #${st.position} ${st.fileName} (${st.kind}) with ${st.file ?? 'NO LOCAL FILE'}`); continue; }
|
||||
await repairSet(c, set.id, steps, m => log('apple', `${tag} ${m}`));
|
||||
}
|
||||
}
|
||||
if (held) log('apple', `${held} screenshot(s) still processing (under 5 min): left alone; run validate again in a few minutes`);
|
||||
done('apple', total ? `${dry ? 'dry run: would replace' : 'replaced'} ${total} stuck screenshot(s)` : 'no stuck screenshots');
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue