storebound/CONTRIBUTING.md
CRT 7746a984ec Merge remote-tracking branch 'origin/main' into shots-liga
# Conflicts:
#	CHANGELOG.md
#	cli/index.ts
2026-10-12 05:24:19 +08:00

5.5 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. fetch against the official store APIs, node: built-ins, external tools (ffmpeg, xcrun, adb) only through argv builders. Playwright is resolved from the user's project.
  • Pure where possible. Rules, argv builders, plans and parsers are pure functions with tests in tests/; the IO stays in the command's run. Tests never need a store account, a simulator or a network.
  • Every write command takes --dry and is idempotent. New MCP write tools default to dry_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.md or docs/agents-core.md, then run bun tools/gen-adapters.ts; never edit AGENTS.md, GEMINI.md, the plugin manifests or the Cursor / Copilot files by hand. Frontmatter is name + description only.
  • 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 in cli/commands.ts); a flag read with a computed name goes into DYNAMIC in that tool. tests/cli-help.test.ts fails on a stale table, on a command whose --help does 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.com and obviously fake values. bun run ci runs the scrub.
  • No hand-written SVG. Charts come from visx, icons from Phosphor (@phosphor-icons/react in 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 by bun tools/site-icons.ts into site/img/icons/ and apps/desktop/src/ui/store-marks/, byte-equal to the packages), diagrams from Mermaid (docs/diagrams/*.mmd, rendered by bun tools/diagrams.ts), the desktop fixtures' art from bun tools/fixture-art.ts. The logo files (app icons, favicons, header marks, site/og.png) are PNG / ICO / ICNS from tools/brand-assets.py (brand/README.md). tests/no-handmade-svg.test.ts fails on new inline <svg> / <path> drawing in apps/desktop/src and site/*.html and 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.md and to skills/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 --dry run prints the plan (dryPlan in cli/lib/dry.ts), not only a count; tests/pitfalls.test.ts enforces it.
  • Plain English in docs and messages, short sentences, no marketing words.

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).