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:
CRTkafa 2026-10-14 02:18:38 +08:00
parent 49a73c90e4
commit 00be838b76
40 changed files with 1669 additions and 58 deletions

View file

@ -1,6 +1,6 @@
// Onboarding an app that is already live on a store (after `storebound init` and `listing pull`): reads what the
// stores and the code prove and puts it into the project, so the owner is asked only what nothing proves.
// storebound onboard [--store apple|play] [--write] [--dry] [--json] [--no-web] [--no-catalog]
// storebound onboard [--store apple|play] [--write] [--dry] [--json] [--no-web] [--no-catalog] [--no-art]
// Read-only on both stores (GETs; a Play edit that is opened, read and deleted, never committed). Without --write it
// prints the plan; --write (never with --dry) changes only local files:
// 1. products / subscriptions in storebound.config.ts from the live catalog of both stores: ids, type, listings per
@ -13,9 +13,13 @@
// account set on App Store Connect goes into .storebound/credentials.json reviewDemoAccount, never printed.
// 4. accounts.deletionUrl, when the app has accounts and none is set: a deletion page found on the app's own site
// (website, privacy policy links). Not found is reported as the gap it is, never invented.
// 5. the live store art (art pull): App Store screenshot sets, previews and icon, Play icon, feature graphic and
// screenshots into store/renders with pulled.json, so check / validate / uploads treat them like rendered art;
// local files that differ are kept (art pull --force replaces them).
// Then the owner questions that are left (one batch).
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
import { join } from 'node:path';
import { artPull, printPull } from './art';
import { appleItems, appleTexts, playItems, playTexts } from './catalog';
import { ascClient, type J } from './apple/asc';
import type { StoreItem } from './lib/catalog';
@ -171,6 +175,16 @@ export async function run(ctx: Ctx): Promise<void> {
} else if (config.play) questions.push('Account deletion page (Play requires a web page to request account deletion): none was found on the app\'s site; publish one, then set accounts.deletionUrl (account-deletion skill)');
}
report.deletion = deletion;
// 5. the store art (icon, screenshots, previews, feature graphic) from the live stores into store/renders, so
// nothing has to be rendered again for an app that is already live (art pull; upsert, local files kept)
if (!a.has('no-art')) {
try {
const art = await artPull(ctx, { store, write, force: false, quiet: json });
report.art = { summary: art.summary, conflicts: art.conflicts, unmapped: art.unmapped, written: art.written, stores: art.stores };
if (!json) printPull(art, write);
} catch (e) { warn(TAG, `store art: ${(e as Error).message.slice(0, 300)}`); }
}
report.questions = questions;
const changed = text !== before;