iPhone Duo: limit from Apple's reference data instead of a cut to 10, --duo-only; shared asset library helpers in their own module
This commit is contained in:
parent
5cc3da486d
commit
108b362cd9
7 changed files with 102 additions and 75 deletions
|
|
@ -8,18 +8,18 @@
|
|||
// 3. POST appAssetLibraryPlacements (placementType APP_SCREENSHOT, placementGroup, the version localization).
|
||||
// Screenshots are reviewed with the version, so a processed asset is placed at once (no approval wait).
|
||||
// Upsert: a group whose placements already show the local files in order is left alone; otherwise its placements are
|
||||
// removed and the files placed in order (outer display files first, then inner; at most 10). Read back at the end.
|
||||
// removed and the files placed in order (outer display files first, then inner). Never truncated: more files than
|
||||
// the limit Apple publishes for the group (refData groupLimits) changes nothing and says so. Read back at the end.
|
||||
import { readFileSync } from 'node:fs';
|
||||
import { basename } from 'node:path';
|
||||
import { log, warn } from '../lib/log';
|
||||
import { elapsed } from '../lib/progress';
|
||||
import { classify, pickAsset, processingTiming, REF, selectable } from './creative-assets';
|
||||
import { classify, pickAsset, processingTiming, REF, selectable } from './asset-library';
|
||||
import { type Asc, md5, type J } from './asc';
|
||||
import { partHeaders } from './screenshot-health';
|
||||
|
||||
const TAG = 'apple';
|
||||
export const DUO_CATEGORY = 'APP_SCREENSHOTS_AND_PREVIEWS';
|
||||
export const DUO_MAX = 10;
|
||||
const sumOf = (a: J | undefined): string | undefined => /^storebound ([0-9a-f]{32})$/.exec(String(a?.attributes?.referenceName ?? ''))?.[1];
|
||||
|
||||
/** iPhone placement profile group ids (platform IOS or IPHONE_APP_STORE, as the live API answers) of the IPHONE_DUO display class in an appAssetLibraryRefData response. Pure. */
|
||||
|
|
@ -30,26 +30,44 @@ export function duoGroups(refData: J[]): string[] {
|
|||
return [...ids];
|
||||
}
|
||||
|
||||
/** Apple's screenshot limit for a placement group on a version page, or null when refData publishes none. Pure. */
|
||||
export function duoLimit(refData: J[], group: string): number | null {
|
||||
for (const r of refData) for (const f of (r?.attributes?.features ?? []) as J[]) {
|
||||
if (f?.featureId !== 'APP_STORE_VERSIONS') continue;
|
||||
for (const p of (f.placementPolicies ?? []) as J[]) {
|
||||
if (p?.placementType !== 'APP_SCREENSHOT') continue;
|
||||
for (const l of (p.groupLimits ?? []) as J[]) if ((l?.groupIds ?? []).includes(group) && typeof l.maxCount === 'number') return l.maxCount;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/** Whether a group's current placements (asset MD5s in order) already show `want`. Pure. */
|
||||
export const duoUpToDate = (shown: (string | undefined)[], want: string[]): boolean => shown.length === want.length && shown.every((s, i) => s === want[i]);
|
||||
|
||||
export interface DuoResult { locale: string; action: 'keep' | 'place' | 'wait' | 'refuse' | 'plan'; files: number }
|
||||
|
||||
/** Resolves the Duo placement group once per run; throws with the guardrail text when the API lists none. */
|
||||
export async function duoGroup(c: Asc): Promise<string> {
|
||||
export async function duoGroup(c: Asc): Promise<{ id: string; limit: number | null }> {
|
||||
const ref = await c.all('/v1/appAssetLibraryRefData?filter[placementTypes]=APP_SCREENSHOT');
|
||||
const g = duoGroups(ref);
|
||||
if (!g.length) throw new Error('appAssetLibraryRefData lists no iOS IPHONE_DUO placement group for APP_SCREENSHOT: report it (pitfall 130); the console version page > App Previews and Screenshots > iPhone Duo takes them meanwhile');
|
||||
if (g.length > 1) log(TAG, `iPhone Duo: ${g.length} placement groups (${g.join(', ')}), using ${g[0]}`);
|
||||
return g[0]!;
|
||||
const limit = duoLimit(ref, g[0]!);
|
||||
log(TAG, `iPhone Duo: placement group ${g[0]}, limit ${limit ?? 'not published'}`);
|
||||
return { id: g[0]!, limit };
|
||||
}
|
||||
|
||||
/** Makes the iPhone Duo screenshots of one version localization show `files` in order. */
|
||||
export async function syncDuo(c: Asc, o: { appId: string; group: string; loc: J; files: string[]; dry: boolean }): Promise<DuoResult> {
|
||||
export async function syncDuo(c: Asc, o: { appId: string; group: string; limit: number | null; loc: J; files: string[]; dry: boolean }): Promise<DuoResult> {
|
||||
const locale: string = o.loc.attributes.locale;
|
||||
const tag = `${locale} IPHONE_DUO`;
|
||||
const files = o.files.slice(0, DUO_MAX);
|
||||
if (o.files.length > DUO_MAX) warn(TAG, `${tag}: ${o.files.length} files, only the first ${DUO_MAX} are used`);
|
||||
const files = o.files;
|
||||
if (o.limit !== null && files.length > o.limit) {
|
||||
warn(TAG, `${tag}: ${files.length} files (outer + inner) but Apple allows ${o.limit} in this group: nothing changed, none dropped. Keep at most ${o.limit} across iphone-duo-outer/ and iphone-duo-inner/`);
|
||||
process.exitCode = 1;
|
||||
return { locale, action: 'refuse', files: files.length };
|
||||
}
|
||||
const lib = (await c.asc('GET', `/v1/apps/${o.appId}/assetLibrary`)).data as J;
|
||||
const library = () => c.all(`/v1/appAssetLibraries/${lib.id}/images?filter[category]=${DUO_CATEGORY}&limit=200`);
|
||||
let assets = await library();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue