From b18d480d6552945aa4ee8a512aa77d806b43a497 Mon Sep 17 00:00:00 2001 From: CRT Date: Sun, 8 Nov 2026 15:48:58 +0700 Subject: [PATCH 01/13] iPhone Duo: accept platform IPHONE_APP_STORE from appAssetLibraryRefData; a Duo failure warns, other sets continue --- CHANGELOG.md | 2 ++ cli/apple/duo-library.ts | 4 ++-- cli/apple/screenshots.ts | 7 ++++--- tests/ppo.test.ts | 1 + 4 files changed, 9 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3362120..e3ef69b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,8 @@ ## Unreleased +- **iPhone Duo:** the live API names the platform IPHONE_APP_STORE (not IOS); accepted. A Duo problem now warns and the other screenshot sets still upload. + ## 0.8.72 · 2026-11-08 - **iPhone Duo screenshots upload.** `apple screenshots` now sends them through the App Asset Library (display class diff --git a/cli/apple/duo-library.ts b/cli/apple/duo-library.ts index 86c620e..8e090fc 100644 --- a/cli/apple/duo-library.ts +++ b/cli/apple/duo-library.ts @@ -22,11 +22,11 @@ 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]; -/** iOS placement profile group ids of the IPHONE_DUO display class in an appAssetLibraryRefData response. Pure. */ +/** 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. */ export function duoGroups(refData: J[]): string[] { const ids = new Set(); for (const r of refData) for (const g of (r?.attributes?.placementProfileGroups ?? []) as J[]) - if (g?.displayClassId === 'IPHONE_DUO' && (!g.platform || g.platform === 'IOS') && g.placementProfileGroupId) ids.add(String(g.placementProfileGroupId)); + if (g?.displayClassId === 'IPHONE_DUO' && (!g.platform || /IOS|IPHONE/.test(String(g.platform))) && g.placementProfileGroupId) ids.add(String(g.placementProfileGroupId)); return [...ids]; } diff --git a/cli/apple/screenshots.ts b/cli/apple/screenshots.ts index fc2adb6..de2f3fe 100644 --- a/cli/apple/screenshots.ts +++ b/cli/apple/screenshots.ts @@ -96,8 +96,9 @@ export async function run(ctx: Ctx): Promise { const version = await c.editableVersion(platform); if (!version) throw new Error(`no editable ${platformLabel(platform)} App Store version (run apple listing --version x.y.z first)`); log('apple', `screenshots for version ${versionLabel(version)}`); - const duo = platform === 'MAC_OS' ? [] : duoDirs(config.apple); - const duoGroupId = duo.length ? await duoGroup(c) : ''; + let duo = platform === 'MAC_OS' ? [] : duoDirs(config.apple); + // a Duo problem never stops the other sets + const duoGroupId = duo.length ? await duoGroup(c).catch((e: Error) => { warn('apple', `iPhone Duo skipped: ${e.message}`); duo = []; return ''; }) : ''; const appId = duo.length ? await c.app() : ''; if (platform === 'MAC_OS' && !config.apple?.mac) throw new Error('macOS version: set apple.mac: true and render the mac size (storebound shots render --size mac) first'); if (platform !== 'MAC_OS' && config.apple?.mac) log('apple', 'Mac screenshots (mac/, APP_DESKTOP) go to the macOS version: run apple screenshots --platform macos'); @@ -129,7 +130,7 @@ export async function run(ctx: Ctx): Promise { if (duo.length) { const files = duo.flatMap(dir => numberedImages(join(src, lang.id, dir)).filter(f => f.endsWith('.png'))); if (!files.length) warn('apple', `${locale}: nothing in ${duo.map(d => join(src, lang.id, d)).join(' or ')}, iPhone Duo skipped`); - else await syncDuo(c, { appId, group: duoGroupId, loc, files, dry: ctx.args.has('dry') }); + else await syncDuo(c, { appId, group: duoGroupId, loc, files, dry: ctx.args.has('dry') }).catch((e: Error) => { warn('apple', `${locale} iPhone Duo: ${e.message}`); process.exitCode = 1; }); } }); } diff --git a/tests/ppo.test.ts b/tests/ppo.test.ts index 3770a30..0d147fa 100644 --- a/tests/ppo.test.ts +++ b/tests/ppo.test.ts @@ -104,6 +104,7 @@ describe('iPhone Duo (foldable) sizes', () => { { placementProfileGroupId: 'g-mac', platform: 'MAC_OS', displayClassId: 'IPHONE_DUO' }] } }]; expect(duoGroups(ref)).toEqual(['g-duo']); expect(duoGroups([])).toEqual([]); + expect(duoGroups([{ attributes: { placementProfileGroups: [{ placementProfileGroupId: 'IPHONE_DUO_PROFILE', platform: 'IPHONE_APP_STORE', displayClassId: 'IPHONE_DUO' }] } }])).toEqual(['IPHONE_DUO_PROFILE']); expect(duoUpToDate(['a', 'b'], ['a', 'b'])).toBe(true); expect(duoUpToDate(['b', 'a'], ['a', 'b'])).toBe(false); expect(duoUpToDate(['a', undefined], ['a', 'b'])).toBe(false); From b361e226310774a58b31f7c40f8e599cfc7f07c9 Mon Sep 17 00:00:00 2001 From: CRT Date: Sun, 8 Nov 2026 15:50:43 +0700 Subject: [PATCH 02/13] ci: re-run (bun download 504) From 96458bba5cb3d37749c615984f1c5d3c79ecb2da Mon Sep 17 00:00:00 2001 From: CRT Date: Sun, 8 Nov 2026 16:17:34 +0700 Subject: [PATCH 03/13] ci: re-run (GitHub download outage) From 3a22d7f4550af417a3a743069118cb3d47e72941 Mon Sep 17 00:00:00 2001 From: CRT Date: Sun, 8 Nov 2026 16:23:15 +0700 Subject: [PATCH 04/13] ci: install bun from the npm registry (GitHub release assets answer 504 now and then), bun.sh with retries as fallback --- .forgejo/actions/linux-setup/action.yml | 2 +- .forgejo/workflows/acceptance.yml | 4 ++-- .forgejo/workflows/ci.yml | 6 +++--- .forgejo/workflows/release.yml | 2 +- tools/ci-install-bun.sh | 14 ++++++++++++++ 5 files changed, 21 insertions(+), 7 deletions(-) create mode 100644 tools/ci-install-bun.sh diff --git a/.forgejo/actions/linux-setup/action.yml b/.forgejo/actions/linux-setup/action.yml index a16be63..ec2f37f 100644 --- a/.forgejo/actions/linux-setup/action.yml +++ b/.forgejo/actions/linux-setup/action.yml @@ -35,7 +35,7 @@ runs: shell: bash run: | P="${FORGEJO_PATH:-$GITHUB_PATH}" - curl -fsSL https://bun.sh/install | bash -s "bun-v${BUN_VERSION}" >/dev/null + bash tools/ci-install-bun.sh echo "$HOME/.bun/bin" >> "$P" command -v rustup || curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --profile minimal echo "$HOME/.cargo/bin" >> "$P" diff --git a/.forgejo/workflows/acceptance.yml b/.forgejo/workflows/acceptance.yml index cc5cbb2..e6a4b22 100644 --- a/.forgejo/workflows/acceptance.yml +++ b/.forgejo/workflows/acceptance.yml @@ -33,7 +33,7 @@ jobs: if: ${{ matrix.platform == 'Linux' }} shell: bash run: | - curl -fsSL https://bun.sh/install | bash -s "bun-v${BUN_VERSION}" >/dev/null + bash tools/ci-install-bun.sh echo "$HOME/.bun/bin" >> "${FORGEJO_PATH:-$GITHUB_PATH}" - name: Acceptance matrix (Linux) if: ${{ matrix.platform == 'Linux' }} @@ -97,7 +97,7 @@ jobs: path: acceptance-runs/Windows-bun - name: Matrix (markdown), red cells fail the job run: | - curl -fsSL https://bun.sh/install | bash -s "bun-v${BUN_VERSION}" >/dev/null + bash tools/ci-install-bun.sh export PATH="$HOME/.bun/bin:$PATH" V=$(bun tools/desktop-version.ts --print) mkdir -p out diff --git a/.forgejo/workflows/ci.yml b/.forgejo/workflows/ci.yml index d0d95da..ff134a6 100644 --- a/.forgejo/workflows/ci.yml +++ b/.forgejo/workflows/ci.yml @@ -102,7 +102,7 @@ jobs: if: ${{ matrix.platform == 'Linux' }} shell: bash run: | - curl -fsSL https://bun.sh/install | bash -s "bun-v${BUN_VERSION}" >/dev/null + bash tools/ci-install-bun.sh echo "$HOME/.bun/bin" >> "${FORGEJO_PATH:-$GITHUB_PATH}" - name: Acceptance matrix (Linux) if: ${{ matrix.platform == 'Linux' }} @@ -371,7 +371,7 @@ jobs: - uses: actions/checkout@v4 - name: Tools run: | - curl -fsSL https://bun.sh/install | bash -s "bun-v${BUN_VERSION}" >/dev/null + bash tools/ci-install-bun.sh echo "$HOME/.bun/bin" >> "${FORGEJO_PATH:-$GITHUB_PATH}" - uses: actions/download-artifact@v3 with: @@ -406,7 +406,7 @@ jobs: # no apt: the installers were scanned with the private terms in their own jobs; this job only records - name: Tools run: | - curl -fsSL https://bun.sh/install | bash -s "bun-v${BUN_VERSION}" >/dev/null + bash tools/ci-install-bun.sh echo "$HOME/.bun/bin" >> "${FORGEJO_PATH:-$GITHUB_PATH}" [ -n "$FORGEJO_TOKEN" ] || { echo "secret RELEASE_TOKEN is missing (repo Settings > Actions > Secrets)" >&2; exit 1; } - uses: actions/download-artifact@v3 diff --git a/.forgejo/workflows/release.yml b/.forgejo/workflows/release.yml index ee88ef0..e77a9fd 100644 --- a/.forgejo/workflows/release.yml +++ b/.forgejo/workflows/release.yml @@ -61,7 +61,7 @@ jobs: - name: Tag, package.json version, every version copy and the CHANGELOG agree id: check run: | - curl -fsSL https://bun.sh/install | bash -s "bun-v${BUN_VERSION}" >/dev/null + bash tools/ci-install-bun.sh echo "$HOME/.bun/bin" >> "${FORGEJO_PATH:-$GITHUB_PATH}" export PATH="$HOME/.bun/bin:$PATH" bun tools/desktop-version.ts diff --git a/tools/ci-install-bun.sh b/tools/ci-install-bun.sh new file mode 100644 index 0000000..8071050 --- /dev/null +++ b/tools/ci-install-bun.sh @@ -0,0 +1,14 @@ +#!/usr/bin/env bash +# Installs bun $1 (or $BUN_VERSION) into ~/.bun/bin on a Linux CI runner from the npm registry (@oven/bun-linux-), +# because GitHub's oven-sh/bun release assets answer 504 now and then; bun.sh/install with retries is the fallback. +set -u +V="${1:-${BUN_VERSION:?BUN_VERSION}}" +case "$(uname -m)" in aarch64|arm64) A=aarch64 ;; *) A=x64 ;; esac +D="$HOME/.bun/bin"; mkdir -p "$D"; T=$(mktemp -d) +for i in 1 2 3; do + if curl -fsSL --max-time 120 "https://registry.npmjs.org/@oven/bun-linux-$A/-/bun-linux-$A-$V.tgz" | tar -xz -C "$T" \ + && install -m 755 "$T/package/bin/bun" "$D/bun"; then ln -sf bun "$D/bunx"; "$D/bun" --version; exit 0; fi + sleep 10 +done +for i in 1 2 3; do curl -fsSL https://bun.sh/install | bash -s "bun-v$V" >/dev/null && { "$D/bun" --version; exit 0; }; sleep 20; done +echo "bun $V: npm registry and GitHub both failed" >&2; exit 1 From f637c5e4e7f3fdd02e8fd8228b6b8f491a06f3cc Mon Sep 17 00:00:00 2001 From: CRT Date: Sun, 8 Nov 2026 16:26:03 +0700 Subject: [PATCH 05/13] storebound 0.8.73: version in package.json and every copy, CHANGELOG 0.8.73 section --- .claude-plugin/marketplace.json | 2 +- .claude-plugin/plugin.json | 2 +- .codex-plugin/plugin.json | 2 +- CHANGELOG.md | 2 ++ apps/desktop/package.json | 2 +- apps/desktop/src-tauri/Cargo.lock | 2 +- apps/desktop/src-tauri/Cargo.toml | 2 +- apps/desktop/src-tauri/tauri.conf.json | 2 +- gemini-extension.json | 2 +- package.json | 2 +- 10 files changed, 11 insertions(+), 9 deletions(-) diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index f60726c..62a1df1 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -9,7 +9,7 @@ "name": "storebound", "source": ".", "description": "Take a mobile app or game from nothing to Submit for review on both stores: skills for every store step and questionnaire plus the storebound CLI and MCP server.", - "version": "0.8.72", + "version": "0.8.73", "category": "development", "tags": [ "mobile", diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json index 1c86081..11502d2 100644 --- a/.claude-plugin/plugin.json +++ b/.claude-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "storebound", - "version": "0.8.72", + "version": "0.8.73", "description": "Take a mobile app or game from nothing to Submit for review on Google Play and the App Store: app records, listings in every language, in-app products and subscriptions with regional prices, every questionnaire (Data safety, App access and demo accounts, account deletion, IARC, target audience, ads, App Privacy, age rating, export compliance), privacy, icons, screenshots and previews, validate, TestFlight, release management, and after launch: crash vitals, review replies, price sheets, store analytics, Product Page Optimization. Agent Skills plus a Bun CLI and MCP server (storebound); you press Submit. CRTkafa's own toolchain, open-sourced.", "author": { "name": "CRTkafa" diff --git a/.codex-plugin/plugin.json b/.codex-plugin/plugin.json index a2817a3..ae6a14b 100644 --- a/.codex-plugin/plugin.json +++ b/.codex-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "storebound", - "version": "0.8.72", + "version": "0.8.73", "description": "Take a mobile app or game from nothing to Submit for review on Google Play and the App Store: app records, listings in every language, in-app products and subscriptions with regional prices, every questionnaire (Data safety, App access and demo accounts, account deletion, IARC, target audience, ads, App Privacy, age rating, export compliance), privacy, icons, screenshots and previews, validate, TestFlight, release management, and after launch: crash vitals, review replies, price sheets, store analytics, Product Page Optimization. Agent Skills plus a Bun CLI and MCP server (storebound); you press Submit. CRTkafa's own toolchain, open-sourced.", "author": { "name": "CRTkafa" diff --git a/CHANGELOG.md b/CHANGELOG.md index e3ef69b..99b9fd8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,8 @@ ## Unreleased +## 0.8.73 · 2026-11-08 + - **iPhone Duo:** the live API names the platform IPHONE_APP_STORE (not IOS); accepted. A Duo problem now warns and the other screenshot sets still upload. ## 0.8.72 · 2026-11-08 diff --git a/apps/desktop/package.json b/apps/desktop/package.json index ff6c949..c410b0b 100644 --- a/apps/desktop/package.json +++ b/apps/desktop/package.json @@ -1,6 +1,6 @@ { "name": "@storebound/desktop", - "version": "0.8.72", + "version": "0.8.73", "private": true, "license": "MIT", "type": "module", diff --git a/apps/desktop/src-tauri/Cargo.lock b/apps/desktop/src-tauri/Cargo.lock index 01c201b..dd4cc4a 100644 --- a/apps/desktop/src-tauri/Cargo.lock +++ b/apps/desktop/src-tauri/Cargo.lock @@ -4096,7 +4096,7 @@ checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] name = "storebound-desktop" -version = "0.8.72" +version = "0.8.73" dependencies = [ "base64 0.22.1", "getrandom 0.3.4", diff --git a/apps/desktop/src-tauri/Cargo.toml b/apps/desktop/src-tauri/Cargo.toml index ea83d16..349c6f1 100644 --- a/apps/desktop/src-tauri/Cargo.toml +++ b/apps/desktop/src-tauri/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "storebound-desktop" -version = "0.8.72" +version = "0.8.73" description = "storebound desktop: every game's releases on both stores in one window" license = "MIT" edition = "2021" diff --git a/apps/desktop/src-tauri/tauri.conf.json b/apps/desktop/src-tauri/tauri.conf.json index fcc5157..d4e2779 100644 --- a/apps/desktop/src-tauri/tauri.conf.json +++ b/apps/desktop/src-tauri/tauri.conf.json @@ -2,7 +2,7 @@ "$schema": "https://schema.tauri.app/config/2", "productName": "storebound", "mainBinaryName": "storebound-desktop", - "version": "0.8.72", + "version": "0.8.73", "identifier": "dev.storebound.desktop", "build": { "frontendDist": "../dist", diff --git a/gemini-extension.json b/gemini-extension.json index b971bd0..40ed0e8 100644 --- a/gemini-extension.json +++ b/gemini-extension.json @@ -1,6 +1,6 @@ { "name": "storebound", - "version": "0.8.72", + "version": "0.8.73", "description": "Take a mobile app or game from nothing to Submit for review on Google Play and the App Store: app records, listings in every language, in-app products and subscriptions with regional prices, every questionnaire (Data safety, App access and demo accounts, account deletion, IARC, target audience, ads, App Privacy, age rating, export compliance), privacy, icons, screenshots and previews, validate, TestFlight, release management, and after launch: crash vitals, review replies, price sheets, store analytics, Product Page Optimization. Agent Skills plus a Bun CLI and MCP server (storebound); you press Submit. CRTkafa's own toolchain, open-sourced.", "contextFileName": "GEMINI.md", "mcpServers": { diff --git a/package.json b/package.json index 1da9c55..c63e1d6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "storebound", - "version": "0.8.72", + "version": "0.8.73", "description": "Take a mobile app or game from nothing to Submit for review on Google Play and the App Store: app records, listings in every language, in-app products and subscriptions with regional prices, every store questionnaire (incl. account deletion and App Review demo accounts), privacy, icons, screenshots and previews, TestFlight. After launch: crash vitals, review replies, price sheets, analytics and Product Page Optimization. One config, a CLI (Node 20+ or Bun), an MCP server and Agent Skills for your AI coding agent.", "license": "MIT", "author": "CRTkafa", From c5173e4caee3d1aa178817b36b9a0e1f01ae079f Mon Sep 17 00:00:00 2001 From: CRT Date: Sun, 8 Nov 2026 16:55:29 +0700 Subject: [PATCH 06/13] ci: cache ~/.cache/tauri and retry the Linux bundle (GitHub release 504s); CHANGELOG note for 0.8.73 --- .forgejo/actions/linux-setup/action.yml | 1 + .forgejo/workflows/ci.yml | 5 +++-- CHANGELOG.md | 2 ++ 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/.forgejo/actions/linux-setup/action.yml b/.forgejo/actions/linux-setup/action.yml index ec2f37f..2f0d06c 100644 --- a/.forgejo/actions/linux-setup/action.yml +++ b/.forgejo/actions/linux-setup/action.yml @@ -26,6 +26,7 @@ runs: with: path: | ~/.bun/install/cache + ~/.cache/tauri ~/.cargo/registry/index ~/.cargo/registry/cache ~/.cargo/git/db diff --git a/.forgejo/workflows/ci.yml b/.forgejo/workflows/ci.yml index ff134a6..b411154 100644 --- a/.forgejo/workflows/ci.yml +++ b/.forgejo/workflows/ci.yml @@ -173,7 +173,8 @@ jobs: working-directory: apps/desktop run: | rm -rf "$CARGO_TARGET_DIR/release/bundle" - bun ../../tools/desktop-release.ts exec -- bunx tauri build --bundles appimage,deb --ci + # the AppImage tools come from GitHub releases, which answer 504 now and then (cached in ~/.cache/tauri) + for i in 1 2 3; do bun ../../tools/desktop-release.ts exec -- bunx tauri build --bundles appimage,deb --ci && break; [ $i = 3 ] && exit 1; sleep 30; done - name: Self-test the exact artifacts run: | V=$(bun tools/desktop-version.ts --print) @@ -237,7 +238,7 @@ jobs: bun tools/build-sidecars.ts --target bun-linux-x64 patchelf --set-rpath '$ORIGIN/../lib' apps/desktop/src-tauri/binaries/storebound-x86_64-unknown-linux-gnu B="$CARGO_TARGET_DIR/release/bundle"; rm -rf "$B" - (cd apps/desktop && bun ../../tools/desktop-release.ts exec -- bunx tauri build --bundles appimage,deb --ci) + for i in 1 2 3; do (cd apps/desktop && bun ../../tools/desktop-release.ts exec -- bunx tauri build --bundles appimage,deb --ci) && break; [ $i = 3 ] && exit 1; sleep 30; done mkdir -p dev && cp "$B/appimage/storebound_${V}_amd64.AppImage" "$B/deb/storebound_${V}_amd64.deb" dev/ chmod +x dev/*.AppImage (cd /tmp && rm -rf squashfs-root && "$GITHUB_WORKSPACE/dev/storebound_${V}_amd64.AppImage" --appimage-extract >/dev/null && squashfs-root/usr/bin/storebound-desktop --self-test) diff --git a/CHANGELOG.md b/CHANGELOG.md index 99b9fd8..efab712 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,8 @@ ## Unreleased +- 0.8.73 was tagged but not published (a GitHub 504 in the Linux AppImage bundling); 0.8.74 ships its section below. CI caches the AppImage tools and retries the Linux bundle. + ## 0.8.73 · 2026-11-08 - **iPhone Duo:** the live API names the platform IPHONE_APP_STORE (not IOS); accepted. A Duo problem now warns and the other screenshot sets still upload. From ab97d6d72f80a151404401c58b43c34dc1094c60 Mon Sep 17 00:00:00 2001 From: CRT Date: Sun, 8 Nov 2026 16:58:18 +0700 Subject: [PATCH 07/13] storebound 0.8.74: version in package.json and every copy, CHANGELOG 0.8.74 section --- .claude-plugin/marketplace.json | 2 +- .claude-plugin/plugin.json | 2 +- .codex-plugin/plugin.json | 2 +- CHANGELOG.md | 2 ++ apps/desktop/package.json | 2 +- apps/desktop/src-tauri/Cargo.lock | 2 +- apps/desktop/src-tauri/Cargo.toml | 2 +- apps/desktop/src-tauri/tauri.conf.json | 2 +- gemini-extension.json | 2 +- package.json | 2 +- 10 files changed, 11 insertions(+), 9 deletions(-) diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index 62a1df1..826be18 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -9,7 +9,7 @@ "name": "storebound", "source": ".", "description": "Take a mobile app or game from nothing to Submit for review on both stores: skills for every store step and questionnaire plus the storebound CLI and MCP server.", - "version": "0.8.73", + "version": "0.8.74", "category": "development", "tags": [ "mobile", diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json index 11502d2..1437a74 100644 --- a/.claude-plugin/plugin.json +++ b/.claude-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "storebound", - "version": "0.8.73", + "version": "0.8.74", "description": "Take a mobile app or game from nothing to Submit for review on Google Play and the App Store: app records, listings in every language, in-app products and subscriptions with regional prices, every questionnaire (Data safety, App access and demo accounts, account deletion, IARC, target audience, ads, App Privacy, age rating, export compliance), privacy, icons, screenshots and previews, validate, TestFlight, release management, and after launch: crash vitals, review replies, price sheets, store analytics, Product Page Optimization. Agent Skills plus a Bun CLI and MCP server (storebound); you press Submit. CRTkafa's own toolchain, open-sourced.", "author": { "name": "CRTkafa" diff --git a/.codex-plugin/plugin.json b/.codex-plugin/plugin.json index ae6a14b..34ffbd3 100644 --- a/.codex-plugin/plugin.json +++ b/.codex-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "storebound", - "version": "0.8.73", + "version": "0.8.74", "description": "Take a mobile app or game from nothing to Submit for review on Google Play and the App Store: app records, listings in every language, in-app products and subscriptions with regional prices, every questionnaire (Data safety, App access and demo accounts, account deletion, IARC, target audience, ads, App Privacy, age rating, export compliance), privacy, icons, screenshots and previews, validate, TestFlight, release management, and after launch: crash vitals, review replies, price sheets, store analytics, Product Page Optimization. Agent Skills plus a Bun CLI and MCP server (storebound); you press Submit. CRTkafa's own toolchain, open-sourced.", "author": { "name": "CRTkafa" diff --git a/CHANGELOG.md b/CHANGELOG.md index efab712..cdc352a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,8 @@ ## Unreleased +## 0.8.74 · 2026-11-08 + - 0.8.73 was tagged but not published (a GitHub 504 in the Linux AppImage bundling); 0.8.74 ships its section below. CI caches the AppImage tools and retries the Linux bundle. ## 0.8.73 · 2026-11-08 diff --git a/apps/desktop/package.json b/apps/desktop/package.json index c410b0b..d06554f 100644 --- a/apps/desktop/package.json +++ b/apps/desktop/package.json @@ -1,6 +1,6 @@ { "name": "@storebound/desktop", - "version": "0.8.73", + "version": "0.8.74", "private": true, "license": "MIT", "type": "module", diff --git a/apps/desktop/src-tauri/Cargo.lock b/apps/desktop/src-tauri/Cargo.lock index dd4cc4a..ac3365e 100644 --- a/apps/desktop/src-tauri/Cargo.lock +++ b/apps/desktop/src-tauri/Cargo.lock @@ -4096,7 +4096,7 @@ checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] name = "storebound-desktop" -version = "0.8.73" +version = "0.8.74" dependencies = [ "base64 0.22.1", "getrandom 0.3.4", diff --git a/apps/desktop/src-tauri/Cargo.toml b/apps/desktop/src-tauri/Cargo.toml index 349c6f1..e88f770 100644 --- a/apps/desktop/src-tauri/Cargo.toml +++ b/apps/desktop/src-tauri/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "storebound-desktop" -version = "0.8.73" +version = "0.8.74" description = "storebound desktop: every game's releases on both stores in one window" license = "MIT" edition = "2021" diff --git a/apps/desktop/src-tauri/tauri.conf.json b/apps/desktop/src-tauri/tauri.conf.json index d4e2779..3f984da 100644 --- a/apps/desktop/src-tauri/tauri.conf.json +++ b/apps/desktop/src-tauri/tauri.conf.json @@ -2,7 +2,7 @@ "$schema": "https://schema.tauri.app/config/2", "productName": "storebound", "mainBinaryName": "storebound-desktop", - "version": "0.8.73", + "version": "0.8.74", "identifier": "dev.storebound.desktop", "build": { "frontendDist": "../dist", diff --git a/gemini-extension.json b/gemini-extension.json index 40ed0e8..73e5d42 100644 --- a/gemini-extension.json +++ b/gemini-extension.json @@ -1,6 +1,6 @@ { "name": "storebound", - "version": "0.8.73", + "version": "0.8.74", "description": "Take a mobile app or game from nothing to Submit for review on Google Play and the App Store: app records, listings in every language, in-app products and subscriptions with regional prices, every questionnaire (Data safety, App access and demo accounts, account deletion, IARC, target audience, ads, App Privacy, age rating, export compliance), privacy, icons, screenshots and previews, validate, TestFlight, release management, and after launch: crash vitals, review replies, price sheets, store analytics, Product Page Optimization. Agent Skills plus a Bun CLI and MCP server (storebound); you press Submit. CRTkafa's own toolchain, open-sourced.", "contextFileName": "GEMINI.md", "mcpServers": { diff --git a/package.json b/package.json index c63e1d6..d782149 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "storebound", - "version": "0.8.73", + "version": "0.8.74", "description": "Take a mobile app or game from nothing to Submit for review on Google Play and the App Store: app records, listings in every language, in-app products and subscriptions with regional prices, every store questionnaire (incl. account deletion and App Review demo accounts), privacy, icons, screenshots and previews, TestFlight. After launch: crash vitals, review replies, price sheets, analytics and Product Page Optimization. One config, a CLI (Node 20+ or Bun), an MCP server and Agent Skills for your AI coding agent.", "license": "MIT", "author": "CRTkafa", From 793c322f99062e5e296bedba01832404ece84ac8 Mon Sep 17 00:00:00 2001 From: CRT Date: Sun, 8 Nov 2026 17:37:34 +0700 Subject: [PATCH 08/13] CHANGELOG: 0.8.74 was not published (GitHub 504) --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index cdc352a..4a2b9ee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,8 @@ ## Unreleased +- 0.8.74 was tagged but not published (GitHub release downloads answered 504 during the Linux AppImage bundling); 0.8.75 ships its section. + ## 0.8.74 · 2026-11-08 - 0.8.73 was tagged but not published (a GitHub 504 in the Linux AppImage bundling); 0.8.74 ships its section below. CI caches the AppImage tools and retries the Linux bundle. From c1bed9440e616a3b1bd65e43168cf00363cbc424 Mon Sep 17 00:00:00 2001 From: CRT Date: Sun, 8 Nov 2026 17:40:26 +0700 Subject: [PATCH 09/13] storebound 0.8.75: version in package.json and every copy, CHANGELOG 0.8.75 section --- .claude-plugin/marketplace.json | 2 +- .claude-plugin/plugin.json | 2 +- .codex-plugin/plugin.json | 2 +- CHANGELOG.md | 2 ++ apps/desktop/package.json | 2 +- apps/desktop/src-tauri/Cargo.lock | 2 +- apps/desktop/src-tauri/Cargo.toml | 2 +- apps/desktop/src-tauri/tauri.conf.json | 2 +- gemini-extension.json | 2 +- package.json | 2 +- 10 files changed, 11 insertions(+), 9 deletions(-) diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index 826be18..e532be7 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -9,7 +9,7 @@ "name": "storebound", "source": ".", "description": "Take a mobile app or game from nothing to Submit for review on both stores: skills for every store step and questionnaire plus the storebound CLI and MCP server.", - "version": "0.8.74", + "version": "0.8.75", "category": "development", "tags": [ "mobile", diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json index 1437a74..3dea39a 100644 --- a/.claude-plugin/plugin.json +++ b/.claude-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "storebound", - "version": "0.8.74", + "version": "0.8.75", "description": "Take a mobile app or game from nothing to Submit for review on Google Play and the App Store: app records, listings in every language, in-app products and subscriptions with regional prices, every questionnaire (Data safety, App access and demo accounts, account deletion, IARC, target audience, ads, App Privacy, age rating, export compliance), privacy, icons, screenshots and previews, validate, TestFlight, release management, and after launch: crash vitals, review replies, price sheets, store analytics, Product Page Optimization. Agent Skills plus a Bun CLI and MCP server (storebound); you press Submit. CRTkafa's own toolchain, open-sourced.", "author": { "name": "CRTkafa" diff --git a/.codex-plugin/plugin.json b/.codex-plugin/plugin.json index 34ffbd3..0c0927e 100644 --- a/.codex-plugin/plugin.json +++ b/.codex-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "storebound", - "version": "0.8.74", + "version": "0.8.75", "description": "Take a mobile app or game from nothing to Submit for review on Google Play and the App Store: app records, listings in every language, in-app products and subscriptions with regional prices, every questionnaire (Data safety, App access and demo accounts, account deletion, IARC, target audience, ads, App Privacy, age rating, export compliance), privacy, icons, screenshots and previews, validate, TestFlight, release management, and after launch: crash vitals, review replies, price sheets, store analytics, Product Page Optimization. Agent Skills plus a Bun CLI and MCP server (storebound); you press Submit. CRTkafa's own toolchain, open-sourced.", "author": { "name": "CRTkafa" diff --git a/CHANGELOG.md b/CHANGELOG.md index 4a2b9ee..37bd801 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,8 @@ ## Unreleased +## 0.8.75 · 2026-11-08 + - 0.8.74 was tagged but not published (GitHub release downloads answered 504 during the Linux AppImage bundling); 0.8.75 ships its section. ## 0.8.74 · 2026-11-08 diff --git a/apps/desktop/package.json b/apps/desktop/package.json index d06554f..2131b76 100644 --- a/apps/desktop/package.json +++ b/apps/desktop/package.json @@ -1,6 +1,6 @@ { "name": "@storebound/desktop", - "version": "0.8.74", + "version": "0.8.75", "private": true, "license": "MIT", "type": "module", diff --git a/apps/desktop/src-tauri/Cargo.lock b/apps/desktop/src-tauri/Cargo.lock index ac3365e..ce49174 100644 --- a/apps/desktop/src-tauri/Cargo.lock +++ b/apps/desktop/src-tauri/Cargo.lock @@ -4096,7 +4096,7 @@ checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] name = "storebound-desktop" -version = "0.8.74" +version = "0.8.75" dependencies = [ "base64 0.22.1", "getrandom 0.3.4", diff --git a/apps/desktop/src-tauri/Cargo.toml b/apps/desktop/src-tauri/Cargo.toml index e88f770..e147fe4 100644 --- a/apps/desktop/src-tauri/Cargo.toml +++ b/apps/desktop/src-tauri/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "storebound-desktop" -version = "0.8.74" +version = "0.8.75" description = "storebound desktop: every game's releases on both stores in one window" license = "MIT" edition = "2021" diff --git a/apps/desktop/src-tauri/tauri.conf.json b/apps/desktop/src-tauri/tauri.conf.json index 3f984da..b510d03 100644 --- a/apps/desktop/src-tauri/tauri.conf.json +++ b/apps/desktop/src-tauri/tauri.conf.json @@ -2,7 +2,7 @@ "$schema": "https://schema.tauri.app/config/2", "productName": "storebound", "mainBinaryName": "storebound-desktop", - "version": "0.8.74", + "version": "0.8.75", "identifier": "dev.storebound.desktop", "build": { "frontendDist": "../dist", diff --git a/gemini-extension.json b/gemini-extension.json index 73e5d42..a269708 100644 --- a/gemini-extension.json +++ b/gemini-extension.json @@ -1,6 +1,6 @@ { "name": "storebound", - "version": "0.8.74", + "version": "0.8.75", "description": "Take a mobile app or game from nothing to Submit for review on Google Play and the App Store: app records, listings in every language, in-app products and subscriptions with regional prices, every questionnaire (Data safety, App access and demo accounts, account deletion, IARC, target audience, ads, App Privacy, age rating, export compliance), privacy, icons, screenshots and previews, validate, TestFlight, release management, and after launch: crash vitals, review replies, price sheets, store analytics, Product Page Optimization. Agent Skills plus a Bun CLI and MCP server (storebound); you press Submit. CRTkafa's own toolchain, open-sourced.", "contextFileName": "GEMINI.md", "mcpServers": { diff --git a/package.json b/package.json index d782149..586da92 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "storebound", - "version": "0.8.74", + "version": "0.8.75", "description": "Take a mobile app or game from nothing to Submit for review on Google Play and the App Store: app records, listings in every language, in-app products and subscriptions with regional prices, every store questionnaire (incl. account deletion and App Review demo accounts), privacy, icons, screenshots and previews, TestFlight. After launch: crash vitals, review replies, price sheets, analytics and Product Page Optimization. One config, a CLI (Node 20+ or Bun), an MCP server and Agent Skills for your AI coding agent.", "license": "MIT", "author": "CRTkafa", From 7e362ef962880afa6ecda72d7ff6624363374f0e Mon Sep 17 00:00:00 2001 From: CRT Date: Sun, 8 Nov 2026 18:20:36 +0700 Subject: [PATCH 10/13] ci: AppImage tools from the Forgejo mirror (sha256-checked) into ~/.cache/tauri; GitHub release assets fail too often --- .forgejo/workflows/ci.yml | 2 ++ CHANGELOG.md | 2 ++ tools/ci-tauri-tools.sh | 13 +++++++++++++ 3 files changed, 17 insertions(+) create mode 100755 tools/ci-tauri-tools.sh diff --git a/.forgejo/workflows/ci.yml b/.forgejo/workflows/ci.yml index b411154..656ac95 100644 --- a/.forgejo/workflows/ci.yml +++ b/.forgejo/workflows/ci.yml @@ -174,6 +174,7 @@ jobs: run: | rm -rf "$CARGO_TARGET_DIR/release/bundle" # the AppImage tools come from GitHub releases, which answer 504 now and then (cached in ~/.cache/tauri) + bash ../../tools/ci-tauri-tools.sh for i in 1 2 3; do bun ../../tools/desktop-release.ts exec -- bunx tauri build --bundles appimage,deb --ci && break; [ $i = 3 ] && exit 1; sleep 30; done - name: Self-test the exact artifacts run: | @@ -238,6 +239,7 @@ jobs: bun tools/build-sidecars.ts --target bun-linux-x64 patchelf --set-rpath '$ORIGIN/../lib' apps/desktop/src-tauri/binaries/storebound-x86_64-unknown-linux-gnu B="$CARGO_TARGET_DIR/release/bundle"; rm -rf "$B" + bash tools/ci-tauri-tools.sh for i in 1 2 3; do (cd apps/desktop && bun ../../tools/desktop-release.ts exec -- bunx tauri build --bundles appimage,deb --ci) && break; [ $i = 3 ] && exit 1; sleep 30; done mkdir -p dev && cp "$B/appimage/storebound_${V}_amd64.AppImage" "$B/deb/storebound_${V}_amd64.deb" dev/ chmod +x dev/*.AppImage diff --git a/CHANGELOG.md b/CHANGELOG.md index 37bd801..695db9b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,8 @@ ## Unreleased +- 0.8.75 was tagged but not published (GitHub 502/504 in the Linux AppImage bundling). CI now takes the AppImage tools from a checked mirror instead of GitHub. + ## 0.8.75 · 2026-11-08 - 0.8.74 was tagged but not published (GitHub release downloads answered 504 during the Linux AppImage bundling); 0.8.75 ships its section. diff --git a/tools/ci-tauri-tools.sh b/tools/ci-tauri-tools.sh new file mode 100755 index 0000000..63f6841 --- /dev/null +++ b/tools/ci-tauri-tools.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env bash +# Puts the AppImage tools Tauri's bundler would download from GitHub releases (which answer 502/504 now and then) into +# ~/.cache/tauri from our Forgejo package mirror, checked against its SHA256SUMS. Tauri skips the download when they exist. +set -eu +B="${TAURI_TOOLS_MIRROR:-https://git.crt.fyi/api/packages/CRT/generic/tauri-linux-tools/2026.11.08}" +D="$HOME/.cache/tauri"; mkdir -p "$D"; cd "$D" +curl -fsSL --retry 3 --max-time 60 -o SHA256SUMS "$B/SHA256SUMS" +while read -r sum f; do + f="${f#\*}" + if ! echo "$sum $f" | sha256sum -c --status 2>/dev/null; then curl -fsSL --retry 3 --max-time 300 -o "$f" "$B/$f"; echo "$sum $f" | sha256sum -c --status; fi + chmod +x "$f" +done < SHA256SUMS +rm -f SHA256SUMS; ls "$D" From 5cc3da486da95ff84d6317fe2c1007f63d2080a7 Mon Sep 17 00:00:00 2001 From: CRT Date: Sun, 8 Nov 2026 18:24:15 +0700 Subject: [PATCH 11/13] storebound 0.8.76: version in package.json and every copy, CHANGELOG 0.8.76 section --- .claude-plugin/marketplace.json | 2 +- .claude-plugin/plugin.json | 2 +- .codex-plugin/plugin.json | 2 +- CHANGELOG.md | 2 ++ apps/desktop/package.json | 2 +- apps/desktop/src-tauri/Cargo.lock | 2 +- apps/desktop/src-tauri/Cargo.toml | 2 +- apps/desktop/src-tauri/tauri.conf.json | 2 +- gemini-extension.json | 2 +- package.json | 2 +- 10 files changed, 11 insertions(+), 9 deletions(-) diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index e532be7..05ef60d 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -9,7 +9,7 @@ "name": "storebound", "source": ".", "description": "Take a mobile app or game from nothing to Submit for review on both stores: skills for every store step and questionnaire plus the storebound CLI and MCP server.", - "version": "0.8.75", + "version": "0.8.76", "category": "development", "tags": [ "mobile", diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json index 3dea39a..9d027bf 100644 --- a/.claude-plugin/plugin.json +++ b/.claude-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "storebound", - "version": "0.8.75", + "version": "0.8.76", "description": "Take a mobile app or game from nothing to Submit for review on Google Play and the App Store: app records, listings in every language, in-app products and subscriptions with regional prices, every questionnaire (Data safety, App access and demo accounts, account deletion, IARC, target audience, ads, App Privacy, age rating, export compliance), privacy, icons, screenshots and previews, validate, TestFlight, release management, and after launch: crash vitals, review replies, price sheets, store analytics, Product Page Optimization. Agent Skills plus a Bun CLI and MCP server (storebound); you press Submit. CRTkafa's own toolchain, open-sourced.", "author": { "name": "CRTkafa" diff --git a/.codex-plugin/plugin.json b/.codex-plugin/plugin.json index 0c0927e..654fc45 100644 --- a/.codex-plugin/plugin.json +++ b/.codex-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "storebound", - "version": "0.8.75", + "version": "0.8.76", "description": "Take a mobile app or game from nothing to Submit for review on Google Play and the App Store: app records, listings in every language, in-app products and subscriptions with regional prices, every questionnaire (Data safety, App access and demo accounts, account deletion, IARC, target audience, ads, App Privacy, age rating, export compliance), privacy, icons, screenshots and previews, validate, TestFlight, release management, and after launch: crash vitals, review replies, price sheets, store analytics, Product Page Optimization. Agent Skills plus a Bun CLI and MCP server (storebound); you press Submit. CRTkafa's own toolchain, open-sourced.", "author": { "name": "CRTkafa" diff --git a/CHANGELOG.md b/CHANGELOG.md index 695db9b..d9b4dab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,8 @@ ## Unreleased +## 0.8.76 · 2026-11-08 + - 0.8.75 was tagged but not published (GitHub 502/504 in the Linux AppImage bundling). CI now takes the AppImage tools from a checked mirror instead of GitHub. ## 0.8.75 · 2026-11-08 diff --git a/apps/desktop/package.json b/apps/desktop/package.json index 2131b76..fe04e9d 100644 --- a/apps/desktop/package.json +++ b/apps/desktop/package.json @@ -1,6 +1,6 @@ { "name": "@storebound/desktop", - "version": "0.8.75", + "version": "0.8.76", "private": true, "license": "MIT", "type": "module", diff --git a/apps/desktop/src-tauri/Cargo.lock b/apps/desktop/src-tauri/Cargo.lock index ce49174..5c1bdec 100644 --- a/apps/desktop/src-tauri/Cargo.lock +++ b/apps/desktop/src-tauri/Cargo.lock @@ -4096,7 +4096,7 @@ checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] name = "storebound-desktop" -version = "0.8.75" +version = "0.8.76" dependencies = [ "base64 0.22.1", "getrandom 0.3.4", diff --git a/apps/desktop/src-tauri/Cargo.toml b/apps/desktop/src-tauri/Cargo.toml index e147fe4..4d0f9db 100644 --- a/apps/desktop/src-tauri/Cargo.toml +++ b/apps/desktop/src-tauri/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "storebound-desktop" -version = "0.8.75" +version = "0.8.76" description = "storebound desktop: every game's releases on both stores in one window" license = "MIT" edition = "2021" diff --git a/apps/desktop/src-tauri/tauri.conf.json b/apps/desktop/src-tauri/tauri.conf.json index b510d03..cee5333 100644 --- a/apps/desktop/src-tauri/tauri.conf.json +++ b/apps/desktop/src-tauri/tauri.conf.json @@ -2,7 +2,7 @@ "$schema": "https://schema.tauri.app/config/2", "productName": "storebound", "mainBinaryName": "storebound-desktop", - "version": "0.8.75", + "version": "0.8.76", "identifier": "dev.storebound.desktop", "build": { "frontendDist": "../dist", diff --git a/gemini-extension.json b/gemini-extension.json index a269708..451ed18 100644 --- a/gemini-extension.json +++ b/gemini-extension.json @@ -1,6 +1,6 @@ { "name": "storebound", - "version": "0.8.75", + "version": "0.8.76", "description": "Take a mobile app or game from nothing to Submit for review on Google Play and the App Store: app records, listings in every language, in-app products and subscriptions with regional prices, every questionnaire (Data safety, App access and demo accounts, account deletion, IARC, target audience, ads, App Privacy, age rating, export compliance), privacy, icons, screenshots and previews, validate, TestFlight, release management, and after launch: crash vitals, review replies, price sheets, store analytics, Product Page Optimization. Agent Skills plus a Bun CLI and MCP server (storebound); you press Submit. CRTkafa's own toolchain, open-sourced.", "contextFileName": "GEMINI.md", "mcpServers": { diff --git a/package.json b/package.json index 586da92..b78b422 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "storebound", - "version": "0.8.75", + "version": "0.8.76", "description": "Take a mobile app or game from nothing to Submit for review on Google Play and the App Store: app records, listings in every language, in-app products and subscriptions with regional prices, every store questionnaire (incl. account deletion and App Review demo accounts), privacy, icons, screenshots and previews, TestFlight. After launch: crash vitals, review replies, price sheets, analytics and Product Page Optimization. One config, a CLI (Node 20+ or Bun), an MCP server and Agent Skills for your AI coding agent.", "license": "MIT", "author": "CRTkafa", From 108b362cd9efa78f18aca46f8e0e3f88210935b4 Mon Sep 17 00:00:00 2001 From: CRT Date: Mon, 9 Nov 2026 06:35:00 +0700 Subject: [PATCH 12/13] iPhone Duo: limit from Apple's reference data instead of a cut to 10, --duo-only; shared asset library helpers in their own module --- CHANGELOG.md | 2 ++ cli/apple/asset-library.ts | 57 ++++++++++++++++++++++++++++++++++++ cli/apple/creative-assets.ts | 56 ++--------------------------------- cli/apple/duo-library.ts | 34 ++++++++++++++++----- cli/apple/screenshots.ts | 10 +++---- cli/data/flags.json | 13 ++++---- tests/ppo.test.ts | 5 +++- 7 files changed, 102 insertions(+), 75 deletions(-) create mode 100644 cli/apple/asset-library.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index d9b4dab..5d1e2ed 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,8 @@ ## Unreleased +- **iPhone Duo:** outer and inner files are no longer cut to the first 10: the limit comes from Apple's reference data, and over it nothing changes and nothing is dropped. `apple screenshots --duo-only` writes only the Duo set. `apple cpp` no longer lists creative-assets flags. + ## 0.8.76 · 2026-11-08 - 0.8.75 was tagged but not published (GitHub 502/504 in the Linux AppImage bundling). CI now takes the AppImage tools from a checked mirror instead of GitHub. diff --git a/cli/apple/asset-library.ts b/cli/apple/asset-library.ts new file mode 100644 index 0000000..a484865 --- /dev/null +++ b/cli/apple/asset-library.ts @@ -0,0 +1,57 @@ +// App Asset Library states and upsert rules shared by creative assets and iPhone Duo screenshots. No command flags here. +import * as rt from '../lib/rt'; +import type { J } from './asc'; + +export const REF = (sum: string): string => `storebound ${sum}`; +export const sumOf = (a: J): string | undefined => /^storebound ([0-9a-f]{32})$/.exec(String(a?.attributes?.referenceName ?? ''))?.[1]; + +/** Asset states (AppAssetLibraryAssetState, API 4.5.1) by what storebound may do with them. */ +export const ASSET_STATES = { + /** still uploading / processing: wait (bounded), then select */ + processing: ['AWAITING_UPLOAD', 'UPLOAD_COMPLETE'], + /** processed and usable in a slot; not reviewed yet: goes into a review submission */ + unreviewed: ['COMPLETE', 'PREPARE_FOR_SUBMISSION'], + /** in a submission or reviewed: usable in a slot, nothing to add */ + reviewing: ['READY_FOR_REVIEW', 'WAITING_FOR_REVIEW', 'IN_REVIEW', 'ACCEPTED', 'APPROVED'], +} as const; +export type AssetClass = 'processing' | 'unreviewed' | 'reviewing' | 'failed' | 'rejected' | 'archived' | 'unknown'; + +/** What a library asset's state means for storebound. Pure. */ +export function classify(state: string | undefined): AssetClass { + const s = String(state ?? ''); + for (const [k, v] of Object.entries(ASSET_STATES)) if ((v as readonly string[]).includes(s)) return k as AssetClass; + if (s === 'FAILED') return 'failed'; + if (s === 'REJECTED') return 'rejected'; + if (s === 'ARCHIVED') return 'archived'; + return s ? 'unknown' : 'processing'; +} + +/** Selectable for a slot: processed and not failed / rejected / archived (Prepare for Submission is enough). */ +export const selectable = (c: AssetClass): boolean => c === 'unreviewed' || c === 'reviewing'; + +/** Stop re-uploading a file whose earlier uploads failed processing this many times. */ +export const MAX_FAILED = 2; + +/** The library asset to use for one MD5: a live one first (newest state wins: reviewing > unreviewed > processing), + * else an archived one (unarchive it), else none; plus why not to upload (a rejected file, repeated failures). Pure. */ +export function pickAsset(assets: J[], sum: string): { asset?: J; unarchive?: boolean; refuse?: string } { + const mine = assets.filter(x => sumOf(x) === sum); + const rank: Record = { reviewing: 0, unreviewed: 1, processing: 2, unknown: 3, archived: 4, rejected: 5, failed: 6 }; + const by = (c: AssetClass) => mine.filter(x => classify(x.attributes?.state) === c); + const live = mine.filter(x => rank[classify(x.attributes?.state)] <= 3).sort((a, b) => rank[classify(a.attributes?.state)] - rank[classify(b.attributes?.state)]); + if (live[0]) return { asset: live[0] }; + if (by('archived')[0]) return { asset: by('archived')[0], unarchive: true }; + const rej = by('rejected')[0]; + if (rej) return { refuse: `rejected by App Review${reason(rej)}: uploading the same file again changes nothing; change the file` }; + if (by('failed').length >= MAX_FAILED) return { refuse: `${by('failed').length} earlier uploads of this file failed processing${reason(by('failed')[0]!)}: check the file (format, size) before trying again` }; + return {}; +} + +export const reason = (a: J): string => { + const d = ((a.attributes?.stateDetails ?? []) as J[]).map(x => x?.code ?? x?.message ?? x?.description).filter(Boolean); + return d.length ? ` (${d.join(', ')})` : ''; +}; + +/** 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 => (ms > 0 ? rt.sleep(ms) : Promise.resolve()) }; + diff --git a/cli/apple/creative-assets.ts b/cli/apple/creative-assets.ts index 7539022..78866a3 100644 --- a/cli/apple/creative-assets.ts +++ b/cli/apple/creative-assets.ts @@ -29,61 +29,17 @@ import { need } from '../lib/ctx'; import { consoleLink } from '../lib/console-links'; import { done, log, warn } from '../lib/log'; import { elapsed } from '../lib/progress'; -import * as rt from '../lib/rt'; import { ffprobe } from '../shots/report'; import { imageInfo } from '../shots/spec'; import { checkCreativeImage, checkCreativeVideo, type CreativeFile, creativeFiles, PLACEMENT_TYPE, type Placement } from '../shots/creative'; import { type Asc, ascClient, md5, type J } from './asc'; +import { type AssetClass, ASSET_STATES, classify, MAX_FAILED, pickAsset, processingTiming, reason, REF, selectable, sumOf } from './asset-library'; +export { type AssetClass, ASSET_STATES, classify, MAX_FAILED, pickAsset, processingTiming, REF, selectable }; import { partHeaders } from './screenshot-health'; import { AmbiguousWriteError } from '../lib/upsert'; import { addItem, ensureDraft, OtherSubmissionError } from './submission'; const TAG = 'apple'; -export const REF = (sum: string): string => `storebound ${sum}`; -const sumOf = (a: J): string | undefined => /^storebound ([0-9a-f]{32})$/.exec(String(a?.attributes?.referenceName ?? ''))?.[1]; - -/** Asset states (AppAssetLibraryAssetState, API 4.5.1) by what storebound may do with them. */ -export const ASSET_STATES = { - /** still uploading / processing: wait (bounded), then select */ - processing: ['AWAITING_UPLOAD', 'UPLOAD_COMPLETE'], - /** processed and usable in a slot; not reviewed yet: goes into a review submission */ - unreviewed: ['COMPLETE', 'PREPARE_FOR_SUBMISSION'], - /** in a submission or reviewed: usable in a slot, nothing to add */ - reviewing: ['READY_FOR_REVIEW', 'WAITING_FOR_REVIEW', 'IN_REVIEW', 'ACCEPTED', 'APPROVED'], -} as const; -export type AssetClass = 'processing' | 'unreviewed' | 'reviewing' | 'failed' | 'rejected' | 'archived' | 'unknown'; - -/** What a library asset's state means for storebound. Pure. */ -export function classify(state: string | undefined): AssetClass { - const s = String(state ?? ''); - for (const [k, v] of Object.entries(ASSET_STATES)) if ((v as readonly string[]).includes(s)) return k as AssetClass; - if (s === 'FAILED') return 'failed'; - if (s === 'REJECTED') return 'rejected'; - if (s === 'ARCHIVED') return 'archived'; - return s ? 'unknown' : 'processing'; -} - -/** Selectable for a slot: processed and not failed / rejected / archived (Prepare for Submission is enough). */ -export const selectable = (c: AssetClass): boolean => c === 'unreviewed' || c === 'reviewing'; - -/** Stop re-uploading a file whose earlier uploads failed processing this many times. */ -export const MAX_FAILED = 2; - -/** The library asset to use for one MD5: a live one first (newest state wins: reviewing > unreviewed > processing), - * else an archived one (unarchive it), else none; plus why not to upload (a rejected file, repeated failures). Pure. */ -export function pickAsset(assets: J[], sum: string): { asset?: J; unarchive?: boolean; refuse?: string } { - const mine = assets.filter(x => sumOf(x) === sum); - const rank: Record = { reviewing: 0, unreviewed: 1, processing: 2, unknown: 3, archived: 4, rejected: 5, failed: 6 }; - const by = (c: AssetClass) => mine.filter(x => classify(x.attributes?.state) === c); - const live = mine.filter(x => rank[classify(x.attributes?.state)] <= 3).sort((a, b) => rank[classify(a.attributes?.state)] - rank[classify(b.attributes?.state)]); - if (live[0]) return { asset: live[0] }; - if (by('archived')[0]) return { asset: by('archived')[0], unarchive: true }; - const rej = by('rejected')[0]; - if (rej) return { refuse: `rejected by App Review${reason(rej)}: uploading the same file again changes nothing; change the file` }; - if (by('failed').length >= MAX_FAILED) return { refuse: `${by('failed').length} earlier uploads of this file failed processing${reason(by('failed')[0]!)}: check the file (format, size) before trying again` }; - return {}; -} - /** "uploaded 2026-10-19, waiting for Apple's approval (IN_REVIEW); run `storebound apple creative-assets` again later". * The version page's header / search slots offer only approved assets ("Browse approved assets"), so an asset that * is not APPROVED waits; a later run places it. Pure. */ @@ -107,14 +63,6 @@ export async function creativeWaiting(c: Asc, appId: string, local: Map { - const d = ((a.attributes?.stateDetails ?? []) as J[]).map(x => x?.code ?? x?.message ?? x?.description).filter(Boolean); - return d.length ? ` (${d.join(', ')})` : ''; -}; - -/** 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 => (ms > 0 ? rt.sleep(ms) : Promise.resolve()) }; - 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. */ diff --git a/cli/apple/duo-library.ts b/cli/apple/duo-library.ts index 8e090fc..76c2383 100644 --- a/cli/apple/duo-library.ts +++ b/cli/apple/duo-library.ts @@ -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 { +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 { +export async function syncDuo(c: Asc, o: { appId: string; group: string; limit: number | null; loc: J; files: string[]; dry: boolean }): Promise { 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(); diff --git a/cli/apple/screenshots.ts b/cli/apple/screenshots.ts index de2f3fe..f2439fd 100644 --- a/cli/apple/screenshots.ts +++ b/cli/apple/screenshots.ts @@ -1,6 +1,6 @@ // Store screenshots of every locale on the editable App Store version (idempotent: a set whose files already match // by MD5 and order is left alone, otherwise it is emptied and re-uploaded). -// storebound apple screenshots [--src store/renders] [--only en-US,tr] [--jobs 4] [--variant b] [--dry] +// storebound apple screenshots [--src store/renders] [--only en-US,tr] [--jobs 4] [--variant b] [--duo-only] [--dry] // storebound apple screenshots --repair-stuck [--dry] (only the screenshots stuck outside COMPLETE, in place) // Source per language: //ios69/NN.png (6.9", 1320 x 2868 -> APP_IPHONE_67), // ios65/NN.png (6.5", 1284 x 2778 -> APP_IPHONE_65) and, only with apple.ipad, ipad13/NN.png (13" iPad, 2064 x 2752 -> @@ -98,7 +98,7 @@ export async function run(ctx: Ctx): Promise { log('apple', `screenshots for version ${versionLabel(version)}`); let duo = platform === 'MAC_OS' ? [] : duoDirs(config.apple); // a Duo problem never stops the other sets - const duoGroupId = duo.length ? await duoGroup(c).catch((e: Error) => { warn('apple', `iPhone Duo skipped: ${e.message}`); duo = []; return ''; }) : ''; + const duoGroupId = duo.length ? await duoGroup(c).catch((e: Error) => { warn('apple', `iPhone Duo skipped: ${e.message}`); duo = []; return null; }) : null; const appId = duo.length ? await c.app() : ''; if (platform === 'MAC_OS' && !config.apple?.mac) throw new Error('macOS version: set apple.mac: true and render the mac size (storebound shots render --size mac) first'); if (platform !== 'MAC_OS' && config.apple?.mac) log('apple', 'Mac screenshots (mac/, APP_DESKTOP) go to the macOS version: run apple screenshots --platform macos'); @@ -121,16 +121,16 @@ export async function run(ctx: Ctx): Promise { if (!lang) { warn('apple', `${locale}: no language with apple: "${locale}", skipped`); continue; } tasks.push(async () => { const sets = await c.all(`/v1/appStoreVersionLocalizations/${loc.id}/appScreenshotSets`); - for (const [dir, type] of Object.entries(displaySets(config.apple, platform))) { + for (const [dir, type] of Object.entries(ctx.args.has('duo-only') ? {} : displaySets(config.apple, platform))) { const d = join(src, lang.id, dir); const files = numberedImages(d).filter(f => f.endsWith('.png')).slice(0, 10); if (!existsSync(d) || !files.length) { warn('apple', `${locale}: nothing in ${d}, skipped`); continue; } await syncSet(loc.id, sets, type, files, `${locale} ${type}`); } - if (duo.length) { + if (duo.length && duoGroupId) { const files = duo.flatMap(dir => numberedImages(join(src, lang.id, dir)).filter(f => f.endsWith('.png'))); if (!files.length) warn('apple', `${locale}: nothing in ${duo.map(d => join(src, lang.id, d)).join(' or ')}, iPhone Duo skipped`); - else await syncDuo(c, { appId, group: duoGroupId, loc, files, dry: ctx.args.has('dry') }).catch((e: Error) => { warn('apple', `${locale} iPhone Duo: ${e.message}`); process.exitCode = 1; }); + else await syncDuo(c, { appId, group: duoGroupId.id, limit: duoGroupId.limit, loc, files, dry: ctx.args.has('dry') }).catch((e: Error) => { warn('apple', `${locale} iPhone Duo: ${e.message}`); process.exitCode = 1; }); } }); } diff --git a/cli/data/flags.json b/cli/data/flags.json index b8058b6..fcb31d3 100644 --- a/cli/data/flags.json +++ b/cli/data/flags.json @@ -106,7 +106,7 @@ "name" ], "apple cpp": [ - "add-for-review", + "duo-only", "include-drafts", "jobs", "name", @@ -117,7 +117,6 @@ "repair-stuck", "src", "variant", - "verify", "write" ], "apple creative-assets": [ @@ -206,7 +205,7 @@ "write" ], "apple ppo": [ - "add-for-review", + "duo-only", "experiment", "include-drafts", "jobs", @@ -218,7 +217,6 @@ "src", "traffic", "variant", - "verify", "write" ], "apple preorder": [ @@ -274,7 +272,7 @@ "platform" ], "apple screenshots": [ - "add-for-review", + "duo-only", "include-drafts", "jobs", "not-sent-for-review", @@ -283,7 +281,6 @@ "repair-stuck", "src", "variant", - "verify", "write" ], "apple signing": [ @@ -1119,6 +1116,7 @@ "cordova-android", "cordova-ios", "days", + "duo-only", "expo", "flutter", "godot", @@ -1250,6 +1248,7 @@ "device", "dir", "download", + "duo-only", "enable", "end", "entitlements", @@ -1594,7 +1593,7 @@ "storebound apple review-details [--platform ios|macos|tvos|visionos] [--contact-from ] [--dry]" ], "apple screenshots": [ - "storebound apple screenshots [--src store/renders] [--only en-US,tr] [--jobs 4] [--variant b] [--dry]", + "storebound apple screenshots [--src store/renders] [--only en-US,tr] [--jobs 4] [--variant b] [--duo-only] [--dry]", "storebound apple screenshots --repair-stuck [--dry] (only the screenshots stuck outside COMPLETE, in place)" ], "apple signing": [ diff --git a/tests/ppo.test.ts b/tests/ppo.test.ts index 0d147fa..b63e096 100644 --- a/tests/ppo.test.ts +++ b/tests/ppo.test.ts @@ -1,6 +1,6 @@ import { describe, expect, test } from 'bun:test'; import { displaySets, duoDirs } from '../cli/apple/screenshots'; -import { duoGroups, duoUpToDate } from '../cli/apple/duo-library'; +import { duoGroups, duoLimit, duoUpToDate } from '../cli/apple/duo-library'; import { appleRules } from '../cli/lib/validate-rules'; import { checkCreate, createExperimentReq, defaultName, experimentFromApi, pickExperiment, planTreatmentLocales, planTreatments, startStopReq } from '../cli/lib/ppo'; import { validateConfig } from '../cli/lib/config'; @@ -105,6 +105,9 @@ describe('iPhone Duo (foldable) sizes', () => { expect(duoGroups(ref)).toEqual(['g-duo']); expect(duoGroups([])).toEqual([]); expect(duoGroups([{ attributes: { placementProfileGroups: [{ placementProfileGroupId: 'IPHONE_DUO_PROFILE', platform: 'IPHONE_APP_STORE', displayClassId: 'IPHONE_DUO' }] } }])).toEqual(['IPHONE_DUO_PROFILE']); + const lim = [{ attributes: { features: [{ featureId: 'IN_APP_EVENTS', placementPolicies: [{ placementType: 'APP_SCREENSHOT', groupLimits: [{ groupIds: ['g'], maxCount: 1 }] }] }, { featureId: 'APP_STORE_VERSIONS', placementPolicies: [{ placementType: 'APP_SCREENSHOT', groupLimits: [{ groupIds: ['g'], maxCount: 20 }] }] }] } }]; + expect(duoLimit(lim, 'g')).toBe(20); + expect(duoLimit(lim, 'other')).toBeNull(); expect(duoUpToDate(['a', 'b'], ['a', 'b'])).toBe(true); expect(duoUpToDate(['b', 'a'], ['a', 'b'])).toBe(false); expect(duoUpToDate(['a', undefined], ['a', 'b'])).toBe(false); From 73e4614b34b7349f58f1fdd77cafa232a00da599 Mon Sep 17 00:00:00 2001 From: CRT Date: Mon, 9 Nov 2026 06:37:51 +0700 Subject: [PATCH 13/13] storebound 0.8.77: version in package.json and every copy, CHANGELOG 0.8.77 section --- .claude-plugin/marketplace.json | 2 +- .claude-plugin/plugin.json | 2 +- .codex-plugin/plugin.json | 2 +- CHANGELOG.md | 2 ++ apps/desktop/package.json | 2 +- apps/desktop/src-tauri/Cargo.lock | 2 +- apps/desktop/src-tauri/Cargo.toml | 2 +- apps/desktop/src-tauri/tauri.conf.json | 2 +- gemini-extension.json | 2 +- package.json | 2 +- 10 files changed, 11 insertions(+), 9 deletions(-) diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index 05ef60d..fcc4c0d 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -9,7 +9,7 @@ "name": "storebound", "source": ".", "description": "Take a mobile app or game from nothing to Submit for review on both stores: skills for every store step and questionnaire plus the storebound CLI and MCP server.", - "version": "0.8.76", + "version": "0.8.77", "category": "development", "tags": [ "mobile", diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json index 9d027bf..4b980c5 100644 --- a/.claude-plugin/plugin.json +++ b/.claude-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "storebound", - "version": "0.8.76", + "version": "0.8.77", "description": "Take a mobile app or game from nothing to Submit for review on Google Play and the App Store: app records, listings in every language, in-app products and subscriptions with regional prices, every questionnaire (Data safety, App access and demo accounts, account deletion, IARC, target audience, ads, App Privacy, age rating, export compliance), privacy, icons, screenshots and previews, validate, TestFlight, release management, and after launch: crash vitals, review replies, price sheets, store analytics, Product Page Optimization. Agent Skills plus a Bun CLI and MCP server (storebound); you press Submit. CRTkafa's own toolchain, open-sourced.", "author": { "name": "CRTkafa" diff --git a/.codex-plugin/plugin.json b/.codex-plugin/plugin.json index 654fc45..397705c 100644 --- a/.codex-plugin/plugin.json +++ b/.codex-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "storebound", - "version": "0.8.76", + "version": "0.8.77", "description": "Take a mobile app or game from nothing to Submit for review on Google Play and the App Store: app records, listings in every language, in-app products and subscriptions with regional prices, every questionnaire (Data safety, App access and demo accounts, account deletion, IARC, target audience, ads, App Privacy, age rating, export compliance), privacy, icons, screenshots and previews, validate, TestFlight, release management, and after launch: crash vitals, review replies, price sheets, store analytics, Product Page Optimization. Agent Skills plus a Bun CLI and MCP server (storebound); you press Submit. CRTkafa's own toolchain, open-sourced.", "author": { "name": "CRTkafa" diff --git a/CHANGELOG.md b/CHANGELOG.md index 5d1e2ed..a0bcd5e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,8 @@ ## Unreleased +## 0.8.77 · 2026-11-08 + - **iPhone Duo:** outer and inner files are no longer cut to the first 10: the limit comes from Apple's reference data, and over it nothing changes and nothing is dropped. `apple screenshots --duo-only` writes only the Duo set. `apple cpp` no longer lists creative-assets flags. ## 0.8.76 · 2026-11-08 diff --git a/apps/desktop/package.json b/apps/desktop/package.json index fe04e9d..395fdfd 100644 --- a/apps/desktop/package.json +++ b/apps/desktop/package.json @@ -1,6 +1,6 @@ { "name": "@storebound/desktop", - "version": "0.8.76", + "version": "0.8.77", "private": true, "license": "MIT", "type": "module", diff --git a/apps/desktop/src-tauri/Cargo.lock b/apps/desktop/src-tauri/Cargo.lock index 5c1bdec..be06ab9 100644 --- a/apps/desktop/src-tauri/Cargo.lock +++ b/apps/desktop/src-tauri/Cargo.lock @@ -4096,7 +4096,7 @@ checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] name = "storebound-desktop" -version = "0.8.76" +version = "0.8.77" dependencies = [ "base64 0.22.1", "getrandom 0.3.4", diff --git a/apps/desktop/src-tauri/Cargo.toml b/apps/desktop/src-tauri/Cargo.toml index 4d0f9db..95dca43 100644 --- a/apps/desktop/src-tauri/Cargo.toml +++ b/apps/desktop/src-tauri/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "storebound-desktop" -version = "0.8.76" +version = "0.8.77" description = "storebound desktop: every game's releases on both stores in one window" license = "MIT" edition = "2021" diff --git a/apps/desktop/src-tauri/tauri.conf.json b/apps/desktop/src-tauri/tauri.conf.json index cee5333..ab2239f 100644 --- a/apps/desktop/src-tauri/tauri.conf.json +++ b/apps/desktop/src-tauri/tauri.conf.json @@ -2,7 +2,7 @@ "$schema": "https://schema.tauri.app/config/2", "productName": "storebound", "mainBinaryName": "storebound-desktop", - "version": "0.8.76", + "version": "0.8.77", "identifier": "dev.storebound.desktop", "build": { "frontendDist": "../dist", diff --git a/gemini-extension.json b/gemini-extension.json index 451ed18..112ed09 100644 --- a/gemini-extension.json +++ b/gemini-extension.json @@ -1,6 +1,6 @@ { "name": "storebound", - "version": "0.8.76", + "version": "0.8.77", "description": "Take a mobile app or game from nothing to Submit for review on Google Play and the App Store: app records, listings in every language, in-app products and subscriptions with regional prices, every questionnaire (Data safety, App access and demo accounts, account deletion, IARC, target audience, ads, App Privacy, age rating, export compliance), privacy, icons, screenshots and previews, validate, TestFlight, release management, and after launch: crash vitals, review replies, price sheets, store analytics, Product Page Optimization. Agent Skills plus a Bun CLI and MCP server (storebound); you press Submit. CRTkafa's own toolchain, open-sourced.", "contextFileName": "GEMINI.md", "mcpServers": { diff --git a/package.json b/package.json index b78b422..a62d97e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "storebound", - "version": "0.8.76", + "version": "0.8.77", "description": "Take a mobile app or game from nothing to Submit for review on Google Play and the App Store: app records, listings in every language, in-app products and subscriptions with regional prices, every store questionnaire (incl. account deletion and App Review demo accounts), privacy, icons, screenshots and previews, TestFlight. After launch: crash vitals, review replies, price sheets, analytics and Product Page Optimization. One config, a CLI (Node 20+ or Bun), an MCP server and Agent Skills for your AI coding agent.", "license": "MIT", "author": "CRTkafa",