From 600b6893e5c7d92f77ce96753135cd0974fd945a Mon Sep 17 00:00:00 2001 From: CRT Date: Thu, 22 Oct 2026 03:33:30 +0800 Subject: [PATCH] play listing uploads the hi-res icon (and the feature graphic) for a new Play language in the same edit (403 no high resolution icon set; play images skipped it), pitfall 102; script traces skip test files --- CHANGELOG.md | 4 ++++ cli/lib/fastlane-traces.ts | 3 ++- cli/play/images.ts | 12 ++++++++++++ cli/play/listing.ts | 10 ++++++++++ skills/store-pitfalls/SKILL.md | 10 ++++++++++ tests/fastlane-traces.test.ts | 9 +++++++++ 6 files changed, 47 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7e71c73..24629af 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +- **A new Play language in one run.** `play listing` uploads the hi-res icon (and the feature graphic when rendered) for a + language Play does not have yet, in the same edit (Play refused the commit with 403 otherwise). Pitfall 102. +- Script traces skip test files (`*.test.*`, `*.spec.*`). + - **CI-friendly Play output.** `storebound aab --json` reads packageName, versionCode, versionName, SDKs and sha256 offline (no config, no upload); `play release ... --json` prints {versionCode, sha256, name, tracks[{track, status, userFraction}]}. `play status --json` already lists each track's releases. diff --git a/cli/lib/fastlane-traces.ts b/cli/lib/fastlane-traces.ts index fc847de..2a80a8f 100644 --- a/cli/lib/fastlane-traces.ts +++ b/cli/lib/fastlane-traces.ts @@ -68,7 +68,8 @@ export function findFastlaneTraces(root: string): FastlaneTrace[] { try { for (const n of readdirSync(dir)) if (SCRIPT_EXT.test(n)) files.push(join(dir, n)); } catch { /* none */ } } try { for (const n of readdirSync(root)) if (/\.(sh|bash|ps1)$/i.test(n)) files.push(join(root, n)); } catch { /* unreadable */ } - for (const f of new Set(files)) { + // test files quote the tools in their assertions; they upload nothing + for (const f of [...new Set(files)].filter(x => !/\.(test|spec)\.[cm]?[jt]sx?$/i.test(x))) { lines(f).forEach((l, i) => { const m = CLI_CALL.exec(l); if (m) out.push({ file: rel(f), line: i + 1, text: l.trim().slice(0, 200), store: storeOf(m[1]!) }); diff --git a/cli/play/images.ts b/cli/play/images.ts index 8ee5940..de181b2 100644 --- a/cli/play/images.ts +++ b/cli/play/images.ts @@ -72,6 +72,18 @@ export function missingChange(type: string, remote: number, prune: boolean): Ima /** What one language uploads: the sets with files, the optional types without, and errors (missing required sets). */ export interface LangImages { sets: [string, string[]][]; missing: string[]; errors: string[] } +/** The graphics a NEW Play language needs before its edit can commit (403 "no high resolution icon set for language"): + * the hi-res icon and, when rendered, the feature graphic. `play listing` uploads them in the same edit as the text, + * so listing and images never wait for each other (pitfall 102). */ +export function firstGraphics(ctx: Ctx, code: string): [string, string][] { + const { config, resolve } = ctx.loaded; + const icon = resolve(config.play?.icon ?? join(config.icons?.out ?? 'store/icons', 'play-512.png')); + const l = byPlayCode(config.languages).get(code); + const src = resolve(config.renders ?? config.shots?.out ?? 'store/renders'); + const li = langImages(code, l ? join(src, l.id) : join(src, '__none__'), icon, false); + return li.sets.filter(([t]) => t === 'icon' || t === 'featureGraphic').map(([t, files]) => [t, files[0]!] as [string, string]); +} + /** The image sets of one language folder (`tablets` = resolved play.tablets). Reads the folder only. */ export function langImages(code: string, dir: string, icon: string, tablets: boolean): LangImages { const out: LangImages = { sets: [], missing: [], errors: [] }; diff --git a/cli/play/listing.ts b/cli/play/listing.ts index 2d86894..fc0ded4 100644 --- a/cli/play/listing.ts +++ b/cli/play/listing.ts @@ -20,6 +20,7 @@ import type { Ctx } from '../lib/ctx'; import { confirm, need } from '../lib/ctx'; import { done, log, printJson, step, warn } from '../lib/log'; import { playClient } from './client'; +import { firstGraphics } from './images'; import { stdinIsTTY } from '../lib/tty'; /** Play release notes that name iOS, iPhone, App Store, ... are refused before any edit, dry runs included (pitfall 77). */ @@ -167,6 +168,15 @@ export async function run(ctx: Ctx): Promise { for (const [i, b] of todo.entries()) { await play.call('PUT', `${play.API}/edits/${id}/listings/${b.language}`, b); log('play', step(i + 1, todo.length, b.language!)); + // a language Play does not have yet: its edit only commits with a hi-res icon (pitfall 102) + if (!now.some(n => n.language === b.language)) { + const graphics = firstGraphics(ctx, b.language!); + if (!graphics.some(([t]) => t === 'icon')) throw new Error(`${b.language}: a new Play language needs the 512x512 icon in the same edit (play.icon or store/icons/play-512.png): storebound icons makes it`); + for (const [type, f] of graphics) { + await play.call('POST', `${play.UPLOAD}/edits/${id}/listings/${b.language}/${type}?uploadType=media`, new Uint8Array(readFileSync(f)), { 'Content-Type': /\.png$/i.test(f) ? 'image/png' : 'image/jpeg' }); + log('play', `${b.language}: ${type} uploaded with the new listing`); + } + } } }, { notSentForReview: ctx.args.has('not-sent-for-review'), summary: 'listing text' }); done('play', sent ? 'listing done' : 'listing already up to date (the edit was deleted, nothing committed)'); diff --git a/skills/store-pitfalls/SKILL.md b/skills/store-pitfalls/SKILL.md index d74c179..ec8c872 100644 --- a/skills/store-pitfalls/SKILL.md +++ b/skills/store-pitfalls/SKILL.md @@ -1233,3 +1233,13 @@ that fails without the fix (see CONTRIBUTING.md). - **Guard:** `remoteBuildSession` in `cli/lib/xcode.ts` stops `apple build` before the archive; `credPath` in `cli/lib/credentials.ts` resolves relative credential paths from the project and `ascKeyFile` hands xcodebuild an absolute path (`tests/build-sign.test.ts`). + +## 102. A new Play language: `play listing` 403 "no high resolution icon set for language", `play images` skips it +- **Symptom:** adding a language (tr-TR) to an existing app, `play listing --yes` failed at the edit commit with 403 + "This app has no high resolution icon set for language tr-TR", while `play images` said "skipped tr-TR: no Play + listing yet": each waited for the other. +- **Cause:** Play commits a new language only when its hi-res icon is in the same edit. +- **Do:** update storebound; `play listing` now uploads the icon (and the feature graphic when rendered) for a new + language in the same edit. Then run `play images` for the screenshots. +- **Guard:** `firstGraphics` in `cli/play/images.ts`, used by `cli/play/listing.ts` for languages Play does not have yet; + it stops with the fix when the 512x512 icon is missing. diff --git a/tests/fastlane-traces.test.ts b/tests/fastlane-traces.test.ts index 7d03491..1879ed5 100644 --- a/tests/fastlane-traces.test.ts +++ b/tests/fastlane-traces.test.ts @@ -65,3 +65,12 @@ describe('direct store API traces stay short', () => { expect(findFastlaneTraces(dir)).toEqual([expect.objectContaining({ file: 'tools/ship.mjs', line: 5, via: 'api' })]); }); }); + +describe('test files are not traces', () => { + test('a test that quotes altool is skipped', () => { + const dir = mkdtempSync(join(tmpdir(), 'sb-fl3-')); + mkdirSync(join(dir, 'tools')); + writeFileSync(join(dir, 'tools/ship.test.mjs'), "test('calls altool', () => { run(['xcrun', 'altool']); });\n"); + expect(findFastlaneTraces(dir)).toEqual([]); + }); +});