play images: same languages as play listing (drafts skipped unless --include-drafts); a language with no Play listing (404) is skipped with an info line, never fatal
This commit is contained in:
parent
147cee9721
commit
720cb2a13a
6 changed files with 71 additions and 7 deletions
|
|
@ -1063,7 +1063,7 @@
|
|||
"storebound play data-safety [--csv out.csv] [--dry] [--force]"
|
||||
],
|
||||
"play images": [
|
||||
"storebound play images [--dry] [--only en-US,de-DE] [--src store/renders] [--variant b] [--prune]"
|
||||
"storebound play images [--dry] [--only en-US,de-DE] [--src store/renders] [--variant b] [--prune] [--include-drafts]"
|
||||
],
|
||||
"play listing": [
|
||||
"storebound play listing [--dry] [--json] [--only en-US,de-DE] [--include-drafts]",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,9 @@
|
|||
// Play store graphics per language, one edit per language (a Console save in parallel then only costs a retry of that
|
||||
// language, not of the whole upload).
|
||||
// storebound play images [--dry] [--only en-US,de-DE] [--src store/renders] [--variant b] [--prune]
|
||||
// storebound play images [--dry] [--only en-US,de-DE] [--src store/renders] [--variant b] [--prune] [--include-drafts]
|
||||
// Languages: the ones `play listing` pushes (a status=draft or status=translate section is skipped unless
|
||||
// --include-drafts) and that have a listing on Play. A language Play has no listing for yet (404 "Listing for language
|
||||
// ... not found") is skipped with an info line, never an error: run `play listing` first.
|
||||
// Per Play language, from <renders>/<language id>/: icon (play.icon, 512x512), featureGraphic (feature/1024x500.*) and
|
||||
// phoneScreenshots (play/NN.*, at least 2) are required (Play Console Help answer 9866151). Optional, uploaded only
|
||||
// when present: sevenInchScreenshots (tab7/NN.*), tenInchScreenshots (tab10/NN.*), tvScreenshots (tv/NN.*),
|
||||
|
|
@ -19,6 +22,7 @@ import { existsSync, readdirSync, readFileSync } from 'node:fs';
|
|||
import { join } from 'node:path';
|
||||
import type { Ctx } from '../lib/ctx';
|
||||
import { byPlayCode, need } from '../lib/ctx';
|
||||
import { draftReason, draftSections } from '../lib/listing-plan';
|
||||
import { done, log, step } from '../lib/log';
|
||||
import { playClient } from './client';
|
||||
import { readPlayCopy } from './listing';
|
||||
|
|
@ -106,6 +110,9 @@ export function describePlan(plan: ImageChange[]): string {
|
|||
return parts.join('; ');
|
||||
}
|
||||
|
||||
/** A Play 404 for a language with no store listing ("Listing for language 'de-DE' not found."). Pure. */
|
||||
export const noListing = (e: unknown): boolean => /-> 404\b/.test(String((e as Error)?.message ?? e)) && /listing/i.test(String((e as Error)?.message ?? e));
|
||||
|
||||
const sha256 = (f: string): string => createHash('sha256').update(readFileSync(f)).digest('hex');
|
||||
|
||||
export async function run(ctx: Ctx): Promise<void> {
|
||||
|
|
@ -116,7 +123,15 @@ export async function run(ctx: Ctx): Promise<void> {
|
|||
if (ctx.args.get('variant')) log('play', `variant ${ctx.args.get('variant')}: ${src}`);
|
||||
const icon = resolve(config.play?.icon ?? join(config.icons?.out ?? 'store/icons', 'play-512.png'));
|
||||
const langOf = byPlayCode(config.languages);
|
||||
const codes = readPlayCopy(ctx).map(c => c.lang);
|
||||
// the languages `play listing` pushes: a draft or untranslated section has no Play listing yet (unless --include-drafts)
|
||||
const drafts = draftSections(readFileSync(resolve(config.play?.listing ?? 'store/listing.md'), 'utf8'), 'play');
|
||||
const skipped: string[] = [];
|
||||
let codes = readPlayCopy(ctx).map(c => c.lang).filter(code => {
|
||||
const st = drafts.get(code);
|
||||
if (!st || ctx.args.has('include-drafts')) return true;
|
||||
skipped.push(`skipped ${code}: no Play listing yet (${draftReason(st)})`);
|
||||
return false;
|
||||
});
|
||||
const tablets = resolvedTablets(config, src);
|
||||
|
||||
const sets = new Map<string, LangImages>();
|
||||
|
|
@ -128,6 +143,7 @@ export async function run(ctx: Ctx): Promise<void> {
|
|||
errors.push(...li.errors);
|
||||
sets.set(code, li);
|
||||
}
|
||||
for (const line of skipped) log('play', line);
|
||||
if (errors.length) throw new Error(errors.join('\n'));
|
||||
const first = [...sets.values()][0];
|
||||
log('play', `${codes.length} languages: ${first?.sets.map(([t, f]) => `${t} (${f.length})`).join(', ') ?? 'nothing'}`);
|
||||
|
|
@ -139,15 +155,25 @@ export async function run(ctx: Ctx): Promise<void> {
|
|||
(await play.call<{ images?: { sha256?: string }[] }>('GET', `${play.API}/edits/${id}/listings/${code}/${type}`)).images ?? [];
|
||||
// one read-only edit: what Play has now (sha256 per image, in order) against the local files, per language and type;
|
||||
// for an optional type with no local files only how many Play has (kept, or deleted with --prune)
|
||||
// a language with no listing on Play yet answers 404: skipped (play listing creates it), never fatal
|
||||
const plan = new Map<string, ImageChange[]>();
|
||||
const unlisted: string[] = [];
|
||||
await play.read(async id => {
|
||||
for (const code of codes) {
|
||||
const li = sets.get(code)!, out: ImageChange[] = [];
|
||||
for (const [type, files] of li.sets) out.push(imageChange(type, await remoteOf(id, code, type), files.map(sha256)));
|
||||
for (const type of li.missing) out.push(missingChange(type, (await remoteOf(id, code, type)).length, prune));
|
||||
try {
|
||||
for (const [type, files] of li.sets) out.push(imageChange(type, await remoteOf(id, code, type), files.map(sha256)));
|
||||
for (const type of li.missing) out.push(missingChange(type, (await remoteOf(id, code, type)).length, prune));
|
||||
} catch (e) {
|
||||
if (!noListing(e)) throw e;
|
||||
unlisted.push(code);
|
||||
continue;
|
||||
}
|
||||
plan.set(code, out);
|
||||
}
|
||||
});
|
||||
for (const code of unlisted) log('play', `skipped ${code}: no Play listing yet (run storebound play listing first; a draft section needs review or --include-drafts)`);
|
||||
codes = codes.filter(c => plan.has(c));
|
||||
const writes = (code: string) => plan.get(code)!.filter(c => c.change === 'replace' || c.change === 'delete');
|
||||
if (ctx.args.has('dry')) {
|
||||
let changed = 0;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue