creative-assets: the dry run plans one upload per file, not per slot (a shared file used by 13 locales read as 26 uploads)
This commit is contained in:
parent
7e69883dfc
commit
98d3bf4410
3 changed files with 24 additions and 4 deletions
|
|
@ -12,7 +12,8 @@
|
|||
twice, is not uploaded again; an archived copy of the same file is unarchived instead of uploaded. A live app without
|
||||
an editable version uses the live version: approved assets are selected there, new ones are uploaded and wait for a
|
||||
review of their own. Art without words needs no copy per locale: `<renders>/creative/header.png` / `search.*`
|
||||
fill every language without its own file. Pitfall 76 rewritten.
|
||||
fill every language without its own file, uploaded once and placed per locale (the dry run counts files, not slots).
|
||||
Pitfall 76 rewritten.
|
||||
|
||||
## 0.8.13 · 2026-10-18
|
||||
|
||||
|
|
|
|||
|
|
@ -90,7 +90,7 @@ const reason = (a: J): string => {
|
|||
/** Bounded wait for processing (every wait has a timeout and a heartbeat); tests set waitMs 0 / timeoutMs 0. */
|
||||
export const processingTiming = { pollMs: 3000, timeoutMs: 120_000, heartbeatMs: 20_000, sleep: (ms: number): Promise<void> => (ms > 0 ? rt.sleep(ms) : Promise.resolve()) };
|
||||
|
||||
export interface Row { locale: string; placement: Placement; file: string; action: 'keep' | 'upload' | 'wait' | 'select' | 'replace' | 'approve' | 'refuse'; state?: string; asset?: J }
|
||||
export interface Row { locale: string; placement: Placement; file: string; action: 'keep' | 'upload' | 'reuse' | 'wait' | 'select' | 'replace' | 'approve' | 'refuse'; state?: string; asset?: J }
|
||||
|
||||
/** Local files per App Store locale, checked; throws on any error before a network call. */
|
||||
export function localCreative(ctx: Ctx): { src: string; local: Map<string, CreativeFile[]> } {
|
||||
|
|
@ -158,6 +158,7 @@ export async function run(ctx: Ctx): Promise<void> {
|
|||
if (verify) return verifySlots();
|
||||
|
||||
const rows: Row[] = [];
|
||||
const planned = new Set<string>(); // dry run: MD5s already planned for upload
|
||||
async function upload(f: CreativeFile, sum: string): Promise<J> {
|
||||
const buf = readFileSync(f.file);
|
||||
const type = f.kind === 'image' ? 'appAssetLibraryImages' : 'appAssetLibraryVideos';
|
||||
|
|
@ -202,7 +203,12 @@ export async function run(ctx: Ctx): Promise<void> {
|
|||
if (pick.refuse) { warn(TAG, `${tag}: ${basename(f.file)} ${pick.refuse}`); row('refuse'); continue; }
|
||||
let asset = pick.asset;
|
||||
if (!asset) {
|
||||
if (dry) { log(TAG, `${tag}: would upload ${basename(f.file)} to the Asset Library and select it ${onLive ? 'on the live version once approved' : 'once processed'}`); row('upload'); continue; }
|
||||
if (dry) {
|
||||
// one upload per file: a later locale with the same file reuses it (the real run finds it by MD5)
|
||||
const first = !planned.has(sum); planned.add(sum);
|
||||
log(TAG, `${tag}: would ${first ? 'upload' : 'reuse'} ${basename(f.file)}${first ? ' to the Asset Library' : ' (uploaded once for an earlier locale)'} and select it ${onLive ? 'on the live version once approved' : 'once processed'}`);
|
||||
row(first ? 'upload' : 'reuse'); continue;
|
||||
}
|
||||
log(TAG, `${tag}: uploading ${basename(f.file)} to the Asset Library`);
|
||||
asset = await upload(f, sum);
|
||||
assets = await library();
|
||||
|
|
@ -261,7 +267,7 @@ export async function run(ctx: Ctx): Promise<void> {
|
|||
log(TAG, `${added} creative asset(s) added to draft review submission ${sid}; nothing was submitted`);
|
||||
}
|
||||
}
|
||||
const summary = `creative assets: ${n(['keep'])} up to date, ${n(['select', 'replace'])} ${dry ? 'to select' : 'selected'}, ${n(['upload'])} to upload, ${n(['wait'])} still processing, ${approve ? `${approve} waiting for App Review (live version), ` : ''}${n(['refuse'])} refused${off ? `, ${off} did not read back` : ''}`;
|
||||
const summary = `creative assets: ${n(['keep'])} up to date, ${n(['select', 'replace'])} ${dry ? 'to select' : 'selected'}, ${n(['upload'])} file(s) to upload${n(['reuse']) ? ` (used by ${n(['upload', 'reuse'])} slots)` : ''}, ${n(['wait'])} still processing, ${approve ? `${approve} waiting for App Review (live version), ` : ''}${n(['refuse'])} refused${off ? `, ${off} did not read back` : ''}`;
|
||||
if (unreviewed.size) {
|
||||
log(TAG, forReview
|
||||
? `owner step: Submit for Review with the version (or alone once the app has an approved version): ${consoleLink('asc.app-review')}`
|
||||
|
|
|
|||
|
|
@ -194,6 +194,19 @@ describe('apple creative-assets against the fake App Store Connect', () => {
|
|||
expect((await go(p, ['--verify'])).out).toContain('every slot shows the local file');
|
||||
});
|
||||
|
||||
test('one shared file for many locales: the dry run plans one upload, the run uploads once and places it per locale', async () => {
|
||||
const two = { ...CFG, languages: [{ id: 'en', apple: 'en-US', play: 'en-US' }, { id: 'tr', apple: 'tr', play: 'tr-TR' }] };
|
||||
s.asc.put({ type: 'appAssetLibraries', id: 'lib-1', attributes: {}, rels: { app: APP } });
|
||||
s.asc.put({ type: 'appStoreVersions', id: 'ver-1', attributes: { versionString: '1.0.0', appVersionState: 'PREPARE_FOR_SUBMISSION', platform: 'IOS' }, rels: { app: APP } });
|
||||
for (const l of ['en-US', 'tr']) s.asc.put({ type: 'appStoreVersionLocalizations', id: `loc-${l}`, attributes: { locale: l }, rels: { appStoreVersion: 'ver-1' } });
|
||||
const p = project(two, { 'store/renders/creative/header.png': png(3840, 1646, false, 4) });
|
||||
const dry = await go(p, ['--dry']);
|
||||
expect(dry.out).toContain('1 file(s) to upload (used by 2 slots)');
|
||||
const r = await go(p);
|
||||
expect(r.w.filter(x => x === 'POST /v1/appAssetLibraryImages')).toHaveLength(1);
|
||||
expect(r.w.filter(x => x === 'POST /v1/appAssetLibraryPlacements')).toHaveLength(2);
|
||||
});
|
||||
|
||||
test('a bad file stops the run before any call', async () => {
|
||||
const p = setup({ 'store/renders/en/creative/header.png': png(3840, 1600, true) });
|
||||
const a = s.calls.length;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue