- 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
6.9 KiB
Contributing
Thanks for wanting to help. storebound is my own toolchain that I open-sourced, so it stays small and opinionated, but fixes, store rule updates and new engines / AI tools are very welcome.
Setup
bun install # Bun 1.1+; playwright is a dev dependency (renders, web capture)
bun run ci:quick # inner loop: typecheck + the tests your change touches + scrub (seconds)
bun run ci # full gate before pushing: every check and test (adapters, API tables, typecheck,
# desktop build and boot check, Rust host when it changed, scrub)
Which one when. Run bun run ci:quick while you work: it diffs against origin/main (committed, staged,
unstaged and new files), maps the changed files to the tests that import them (directly, through other modules, or
by naming a fixture or page) and runs those, plus tsc (incremental) and the scrub. A change to package.json,
bun.lock, bunfig.toml, tsconfig.json or the test preload runs every test. Run the full bun run ci before every
push and check its exit code: it is the gate, ci:quick is not.
tools/ci.ts runs the steps as a task graph (independent steps at the same time, output prefixed with the step
name, the first failure stops the rest, a time-per-step summary at the end). The tests run as parallel shards
(CPU cores - 1, at most 8; STOREBOUND_CI_SHARDS=n overrides) balanced by the durations of the last run, kept in
node_modules/.cache/storebound-ci/; the browser suites get a shard each. So tests must be shard-safe: temp folders
from mkdtempSync, servers on port 0, no fixed sleeps (wait for the event), no state shared through files in the
checkout. The desktop Rust host (cargo test, clippy) runs only when apps/desktop/src-tauri/ or
tools/build-sidecars.ts changed against origin/main; CI=full bun run ci always runs it (Forgejo CI does).
Try the screenshot and video pipeline on the demo game without any store account: see examples/demo-game/README.md.
Rules of the house
- Bun + TypeScript strict, zero runtime dependencies.
fetchagainst the official store APIs,node:built-ins, external tools (ffmpeg,xcrun,adb) only through argv builders. Playwright is resolved from the user's project. - Runs on Node 20+ and Bun. Development, tests and CI use Bun; users run the npm bundle with Node (
npx) or Bun (bunx). The CLI never touches theBunglobal,bun:modules,import.meta.diror text imports: everything goes throughcli/lib/rt.ts(spawn, which, sleep, serve, glob, hashing, stdin, the package folder, config imports), and text files the CLI ships are embedded bybun tools/gen-embedded.ts(templates/,cli/shots/*.html, Play's form export: run it after changing one).tests/node-compat.test.tsfails on a direct Bun use;bun run cibuilds the bundle (tools/build-node.ts) and runs it with the realnode(tools/node-smoke.ts). - Pure where possible. Rules, argv builders, plans and parsers are pure functions with tests in
tests/; the IO stays in the command'srun. Tests never need a store account, a simulator or a network. - Every write command takes
--dryand is idempotent. New MCP write tools default todry_run: true. Nothing public or irreversible (submit, release, rollout resume / complete, cancel, delete) goes into MCP. - Store facts need a source. When you change a size, limit or rule, cite the official page in the code comment and the skill (Apple Developer / App Store Connect Help, Play Console Help, API references) with the date you checked it.
- Skills are the single source. Edit
skills/*/SKILL.mdordocs/agents-core.md, then runbun tools/gen-adapters.ts; never editAGENTS.md,GEMINI.md, the plugin manifests or the Cursor / Copilot files by hand. Frontmatter isname+descriptiononly. - New or renamed flags: run
bun tools/gen-flags.ts. The CLI rejects a flag a command does not read (cli/data/flags.json, generated from the commands and their imports incli/commands.ts); a flag read with a computed name goes intoDYNAMICin that tool.tests/cli-help.test.tsfails on a stale table, on a command whose--helpdoes anything but print its usage, and on an MCP / desktop tool that passes a flag its command does not take. - No secrets, no real ids, no personal data in code, tests, fixtures or examples: use
com.example.*,example.comand obviously fake values.bun run ciruns the scrub. - No hand-written SVG. Charts come from visx, icons from Phosphor (
@phosphor-icons/reactin the desktop app, one per-icon import each; the site's icons are Phosphor's own files from@phosphor-icons/core), the App Store and Google Play marks from simple-icons (vendored with the licences bybun tools/site-icons.tsintosite/img/icons/andapps/desktop/src/ui/store-marks/, byte-equal to the packages), diagrams from Mermaid (docs/diagrams/*.mmd, rendered bybun tools/diagrams.ts), the desktop fixtures' art frombun tools/fixture-art.ts. The logo files (app icons, favicons, header marks,site/og.png) are PNG / ICO / ICNS fromtools/brand-assets.py(brand/README.md).tests/no-handmade-svg.test.tsfails on new inline<svg>/<path>drawing inapps/desktop/srcandsite/*.htmland on SVG files that are not vendored or rendered. These libraries are desktop dependencies (bundled by Vite) or devDependencies; the CLI package keeps zero runtime dependencies. - Every bug found in real use becomes a guardrail. Add an entry to
docs/pitfalls.mdand toskills/store-pitfalls/SKILL.md(symptom, cause, what to do, what guards it) and, where possible, a check or a test with recorded store answers that fails without the fix. A--dryrun prints the plan (dryPlanincli/lib/dry.ts), not only a count;tests/pitfalls.test.tsenforces it. - Plain English in docs and messages, short sentences, no marketing words.
Releasing the npm package
npm pack and npm publish run prepack, which builds dist/storebound.mjs (the bin, #!/usr/bin/env node,
gitignored) with bun tools/build-node.ts. Before publishing: bun run ci green (it smoke-tests the bundle with
Node), npm pack --dry-run lists dist/storebound.mjs, the privacy scan on the tree and on the bundle, and a run of
the packed tarball in an empty folder (npx --yes ./storebound-<version>.tgz --version, then init).
The npm package and the desktop app are one release with one version (bun tools/desktop-version.ts --set <version>
writes it everywhere): publish the desktop artifacts of that version first (docs/desktop-release.md), then
npm publish, so npx storebound desktop of the new CLI finds its own app.
Pull requests
One topic per PR, bun run ci green, a line in CHANGELOG.md under an "Unreleased" heading, and for anything
that talks to a store: how you verified it (--dry output, a test app, or "not verified" said plainly).