store art from the live stores: storebound art pull (App Store screenshot sets and previews per locale and display type from imageAsset.templateUrl at the original size, the App Store icon from the newest build or the public artwork; per Play language icon, feature graphic, phone / 7" / 10" / TV / Wear screenshots as the =s0 originals checked against Play's sha256) into store/renders with pulled.json (sha256, store, URL host, store checksum); upsert (a re-run changes nothing, differing local files kept unless --force, the upload's file of a slot compared: Play .jpg, App Store .png, the App Store icon as a picture); onboard runs it; apple screenshots / previews and play images see unchanged pulled files as the store's own; check: render sources are notes with pulled art; local renders check: pulled files pass, store-empty sets not demanded; art live / thumb / replace; desktop Store art tab: live thumbnails per store, language and set (same / differs / not pulled), Pull from stores, store-only apps read-only, Replace with a file (image_pick, art.replace) in the lightbox and the art editor; boot check store art case; tests/art-pull.test.ts; console parity 76 rows (57 complete, 19 with the reason); store-screenshots skill (pull first for live apps), pitfall 59
This commit is contained in:
parent
49a73c90e4
commit
00be838b76
40 changed files with 1669 additions and 58 deletions
20
cli/check.ts
20
cli/check.ts
|
|
@ -25,6 +25,7 @@ import { detectProject } from './lib/scan/project';
|
|||
import { reviewShotProblem } from './apple/iap-review';
|
||||
import { allSubscriptions } from './lib/subscriptions';
|
||||
import { type CopyFile, validateCopy } from './shots/lib';
|
||||
import { posix, readPulled } from './lib/art-pull';
|
||||
import { type CapProject, capacitorProject, prebuildFor, staleMessage, webState } from './lib/web-assets';
|
||||
|
||||
/** The newest .ipa in a folder (apple build's export), or null. */
|
||||
|
|
@ -159,7 +160,18 @@ export async function run(ctx: Ctx): Promise<void> {
|
|||
const bad = reviewShotProblem(readFileSync(file));
|
||||
if (bad) problems.push(`products[].reviewScreenshot ${f}: ${bad}`);
|
||||
}
|
||||
if (config.shots) tryDo('shots copy', () => {
|
||||
// store art pulled from the live stores (art pull): the render sources (screenshot captions, icon artwork, logo)
|
||||
// are needed only to render again, so their gaps are notes, not problems
|
||||
const pulled = readPulled(resolve(config.renders ?? config.shots?.out ?? 'store/renders'));
|
||||
const fromStores = !!pulled && Object.keys(pulled.files).length > 0;
|
||||
if (fromStores) log('check', `store art: ${Object.keys(pulled!.files).length} file(s) pulled from the stores (${posix(relative(ctx.loaded.root, resolve(config.renders ?? config.shots?.out ?? 'store/renders')))}/pulled.json); render sources are optional`);
|
||||
if (config.shots && fromStores) {
|
||||
try {
|
||||
const copy = JSON.parse(readFileSync(resolve(config.shots.copy), 'utf8')) as CopyFile;
|
||||
const e = validateCopy(copy, config.shots, config.languages.map(l => l.id));
|
||||
if (e.length) note(`shots copy (only needed to render again; the store art is pulled): ${e.join('; ')}`);
|
||||
} catch (e) { note(`shots copy (only needed to render again; the store art is pulled): ${(e as Error).message}`); }
|
||||
} else if (config.shots) tryDo('shots copy', () => {
|
||||
const copy = JSON.parse(readFileSync(resolve(config.shots!.copy), 'utf8')) as CopyFile;
|
||||
const e = validateCopy(copy, config.shots!, config.languages.map(l => l.id));
|
||||
if (e.length) throw new Error(e.join('; '));
|
||||
|
|
@ -198,7 +210,11 @@ export async function run(ctx: Ctx): Promise<void> {
|
|||
if (subs.length) log('check', `subscriptions: ${subs.length} in ${config.subscriptions!.length} group(s)`);
|
||||
const files = [config.icons?.composite, config.icons?.foreground, config.icons?.background, config.shots?.logo,
|
||||
...(config.products ?? []).map(p => p.reviewScreenshot), ...subs.map(x => x.sub.reviewScreenshot)].filter((f): f is string => !!f);
|
||||
for (const f of new Set(files)) if (!existsSync(resolve(f))) problems.push(`missing file: ${f}`);
|
||||
const renderSources = new Set([config.icons?.composite, config.icons?.foreground, config.icons?.background, config.shots?.logo].filter(Boolean));
|
||||
for (const f of new Set(files)) if (!existsSync(resolve(f))) {
|
||||
if (fromStores && renderSources.has(f)) note(`render source not there: ${f} (only needed to render icons / screenshots again; the store art is pulled)`);
|
||||
else problems.push(`missing file: ${f}`);
|
||||
}
|
||||
if (existsSync(resolve('.gitignore')) && !/^\/?\.storebound\/?$/m.test(readFileSync(resolve('.gitignore'), 'utf8')))
|
||||
problems.push('.gitignore does not ignore .storebound/ (credentials live there)');
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue