- one version: package.json's version is the desktop app's too (desktopVersion removed); tools/desktop-version.ts keeps the plugin manifests and the app's copies equal, desktop-release publish refuses another version, release order app first then npm publish; the unpublished desktop 0.1.0 candidate is noted in the docs and CHANGELOG - storebound desktop installs (per user, no admin: macOS DMG -> /Applications or ~/Applications, Windows NSIS /S, Linux AppImage + .desktop with the storebound:// handler), updates an older app, notes a newer one (downgrade only with --force), asks once ([Y/n], --yes, exit 3 without a terminal), opens on the folder; --dry / --json / --download; the install steps are pure plans (cli/lib/desktop-install.ts); the version's own manifest dl/<version>/latest.json - credentials: one store, the CLI's credential profiles in ~/.storebound/credentials; the desktop writes and reads them (profiles.rs, files 0600), moves old keychain accounts into profiles on first start, shows profiles the CLI made; precedence env > named profile > project file > default profile; config `account`, "account" in the project's credentials.json; storebound accounts list | import (dedup by key id / service account e-mail, key files copied 0600, project file keeps keystore and review contact, backup, --dry, never prints values) | use - app folders: one list ~/.storebound/projects.json shared by the desktop (registry.rs), storebound desktop, apps folders | add | remove, ws init and ws without a workspace file - desktop: notice when a folder pins another storebound version; storebound-desktop --version - tests: version drift, desktop decisions and per-OS plans, credential precedence and import, registry (TS + Rust), keychain migration and profile round trip (Rust), boot check with old keychain state and the new shared state
8.4 KiB
Many apps, studios, release trains and workflows
Many apps and studios
A studio with twenty games, or a developer with apps under two store accounts, works from one folder above the app
folders. storebound ws init writes storebound.workspace.json:
{
"apps": ["games/*", { "path": "apps/notes", "apple": "solo-apple" }],
"studios": [
{ "name": "Acme Games", "color": "indigo", "apps": ["games/*"],
"accounts": [{ "store": "apple", "profile": "acme-apple" }, { "store": "play", "profile": "acme-play" }] }
],
"accounts": [{ "store": "apple", "profile": "solo-apple", "label": "Solo" }],
"ignore": ["games/old-*"],
"overrides": { "pairs": [], "unpairs": [], "links": [], "appStudio": [] },
"concurrency": 4
}
- apps: folders or globs (only folders with a
storebound.config.*count;node_modulesand dot folders are skipped). An object entry names the folder's own credential profiles. - studios: name, colour (the desktop's set), the store accounts and the app folders (globs) that belong to it.
- accounts: store accounts without a studio. An account is a store and a credential profile name, never a key.
- ignore: folder globs,
app:<store app key>andaccount:<profile>, as in the desktop's ignore list. - overrides: the desktop's pairing corrections (
pairs,unpairs,linksof a store app to a folder,appStudio), with folders named by their path. Astorebound.workspace.tswithexport default { ... }works too.
Which keys an app uses: its own entry's profiles, else the single account of that store in its studio, else the
single account of that store in the workspace, else the folder's own credentials (env or .storebound/).
Credential profiles keep many accounts apart on one machine: ~/.storebound/credentials/<profile>.json with the
account names of .storebound/credentials.json (PLAY_SERVICE_ACCOUNT, ASC_KEY_ID, ASC_ISSUER_ID, ASC_P8,
ASC_VENDOR_NUMBER; the Android upload keystore belongs to the app and stays in the project;
relative key paths are relative to that folder, which STOREBOUND_CREDENTIALS_DIR can move), or env
STOREBOUND_PROFILE_<NAME>_<KEY> (for CI). Any command takes --account <profile>. ws list shows per account whether
its profile has keys; no command prints a key.
ws status, ws check, ws validate and ws run read the apps a few at a time (concurrency, 1-8, or
--concurrency), each app with its own keys handed to its child process only. ws run takes any read command and
the MCP tool set; write commands get --dry unless --apply, and submit, release, rollout, send, start / stop,
testers and --yes are refused: those stay with the owner in the app folder.
ws apps import --account <profile> lists the apps a store account sees (as apps list) into
.storebound/workspace/ and pairs them with the folders by the desktop's rules (cli/lib/identity.ts, the same code):
a folder takes the store apps its config names, the remaining iOS and Android apps pair by id, close id or name,
unpairs keep two apart, and the rest are store-only apps that ws status reads too.
The desktop app keeps the same model (studios, accounts, folders, overrides). ws desktop import turns its
state.json (or --state <file>) into a workspace file; its accounts name the credential profiles the app and the
CLI share (~/.storebound/credentials), so the keys are already there. ws desktop export prints the workspace as a
desktop state (--out <file>), and --merge --apply adds it to the desktop's state.json while the app is closed (a
backup is kept; nothing is removed). Without a workspace file, ws works over the machine's app folder list
(~/.storebound/projects.json, the desktop's App folders).
MCP: workspace_list (offline) and workspace_status (read-only).
Release trains
One version, both stores, one track: build > testing > review > live > rollout. The CLI and the desktop's Releases
tab share the code (cli/lib/train.ts, cli/lib/portfolio.ts), so both show the same stage for the same store state.
$ storebound release status
app version train App Store Google Play
------ ------- ------- ---------------------- -----------------------------
Puzzle 2.3.0 current rollout 20% (build 45) rollout 10% (build 230)
Puzzle 2.4.0 next build testing (build 240, internal)
release plan <version> lists what is left for that version on each store in fix order: config problems (check),
the version on the store (how to create it when it is missing), store blockers (validate, while nothing is
submitted), the next move and the warnings. Owner steps (submit for review, promote to production, release an approved
version, raise or complete a rollout) are named with where the owner does them; storebound does not do them. A store
it cannot read is "unknown", never "not there". Both commands work in an app folder (that app) and in a workspace
(every app, or --app a,b); --json gives the structured result. MCP: release_status, release_plan (read-only).
Repo-local workflows
Your release routine as a file in the repo, run step by step. storebound init writes two to start from
(store/workflows/release.yml, beta.yml; app and game versions):
# store/workflows/release.yml (app profile, shortened)
params:
VERSION: "1.0.0"
steps:
- id: next-build
if: apple
run: apple next-build
capture: BUILD
- id: build
if: apple
run: apple build --version ${VERSION} --build-number ${BUILD}
- id: upload
if: apple
run: apple upload --no-wait
# wait, play-build, play-upload, whats-new, listing, validate, scan, plan ...
- id: submit
owner: "App Store Connect: Submit for Review (version ${VERSION}, build ${BUILD}) ..."
The plan of that file (--dry: nothing runs; without it the reads run and ${BUILD} is filled in):
$ storebound flow run release VERSION=1.2.0 --dry
[flow] 1/12 next-build: storebound apple next-build (plan only)
[flow] 2/12 build: storebound apple build --version 1.2.0 --build-number ${BUILD} --dry (plan only)
[flow] 3/12 upload: storebound apple upload --no-wait --dry (plan only)
[flow] 4/12 wait: storebound apple testflight wait --build ${BUILD} --version 1.2.0 --dry (plan only)
[flow] 5/12 play-build: storebound play build --version-name 1.2.0 --dry (plan only)
[flow] 6/12 play-upload: storebound play upload --track internal --dry (plan only)
[flow] 7/12 whats-new: storebound whats-new apply (if exists store/whats-new: false)
[flow] 8/12 listing: storebound apple listing --version 1.2.0 --build ${BUILD} --dry (plan only)
[flow] 9/12 validate: storebound validate (plan only)
[flow] 10/12 scan: storebound scan (plan only)
[flow] 11/12 plan: storebound release plan 1.2.0 (plan only)
[flow] 12/12 submit: YOUR CLICK App Store Connect: Submit for Review (version 1.2.0, build ${BUILD}). Play Console: promote the internal release and Send changes for review
- Read steps (check, validate, scan, release plan, next-build, status) run; write steps run with
--dryunless--apply, and even then each command asks its own confirmation; wait steps check once;sh:steps (your own build commands) run only with--apply.--dryprints the plan without running anything.--apply --yesis your approval of every write step of the run (no terminal needed); without it, a step whose confirmation cannot be answered stops the flow as "declined", never "ok". - Owner steps (submit, release, phased release, rollout, review replies, price imports, tester invitations, Beta App
Review) are never executed, also when written as
run:; the flow ends with the list of your clicks. capture:turns a step's last output line into a variable (${BUILD}),if:skips a step (apple,play,exists <path>,${VAR} == x),flow:reuses another flow (private: truesub-flows),before/after/onErrorare hooks,continueOnErrorkeeps going.- A failed step stops the flow;
--resumestarts again at that step with the captured variables (.storebound/flows/<name>.json),--from <step>anywhere.flow validatechecks commands, variables, duplicate ids and sub-flow cycles. Several flows can also live instorebound.workflows.yml|json|ts. - MCP:
flow_listandflow_run(the dry mode only;--applystays a terminal command). Skill:release-flows.