-
Notifications
You must be signed in to change notification settings - Fork 0
Development
Guide for setting up a development environment and contributing to decky-romm-sync.
- mise — manages Node, pnpm, and Python versions
- Git
- A Steam Deck or Linux PC with Decky Loader installed (for testing)
git clone https://github.com/danielcopper/decky-romm-sync.git
cd decky-romm-sync
mise install # installs Node LTS, pnpm, Python
mise run setup # installs JS + Python dependenciesThis creates a Python virtual environment (auto-activated by mise via _.python.venv in mise.toml) and installs all npm packages.
pnpm build # Rollup -> dist/index.jsThe frontend is bundled with Rollup into a single dist/index.js file that Decky Loader serves.
python -m pytest tests/ -q # run all 1260+ backend tests
mise run test # same thing via miseTo run with coverage:
python -m pytest tests/ -q --cov=py_modules --cov=main --cov-report=term --cov-branchTests are split per backend module in tests/test_*.py with shared mocks in tests/conftest.py. The conftest.py provides a mock decky module so tests run without Decky Loader.
Every backend feature or callable where testing makes sense should have unit tests covering:
- Happy path — normal successful operation
- Bad path — invalid input, missing data, API errors, network failures
- Edge cases — empty strings, None values, boundary conditions
mise run dev # builds frontend + restarts plugin_loaderThis runs pnpm build and then sudo systemctl restart plugin_loader to pick up changes. For backend-only changes, restarting the plugin loader is sufficient without rebuilding.
For development, symlink the repo into the plugins directory:
sudo ln -sf "$(pwd)" ~/homebrew/plugins/decky-romm-sync
sudo systemctl restart plugin_loaderThis way, rebuilds take effect immediately after a Decky restart.
PYTHONPATH=py_modules lint-imports # check service/adapter layer rules
mise run lint # same via miseThe .importlinter config enforces five layer boundary contracts:
- Services must not import concrete adapter implementations (Protocols are allowed)
- Adapters must not import services
- Utilities (
lib/) must not import services, adapters, or domain - Domain must not import services, adapters, or lib
- Services must be independent of each other (no cross-service imports)
See Backend Architecture for details.
- SonarCloud — CI-based analysis on every PR and push to main. Quality Gate enforces 80% coverage on new code, 0 bugs, 0 vulnerabilities.
- Ruff — Python linting in CI. Expanded ruleset includes B (bugbear), SIM (simplify), UP (pyupgrade), RUF (ruff-specific), and ARG (unused arguments) in addition to the base E/F rules.
- basedpyright — Type checking in CI. Checks all source files including the test suite (tests/ is not excluded).
- import-linter — Layer boundary enforcement in CI (see Linting section above).
- pytest-cov — Branch coverage reported to SonarCloud.
main.py # Plugin entry point, callable routing, bootstrap
py_modules/
bootstrap.py # Composition root — wires adapters and services
services/ # Business logic (14 services)
protocols.py # Protocol interfaces (14+ typed Protocols — adapter, cross-service, infrastructure)
library.py # LibraryService — sync engine
saves.py # SaveService — save file sync, conflict detection
playtime.py # PlaytimeService — tracking via RomM notes
downloads.py # DownloadService — ROM downloads
firmware.py # FirmwareService — BIOS management
steamgrid.py # SteamGridService — SteamGridDB artwork
metadata.py # MetadataService — ROM metadata caching
achievements.py # AchievementsService — RetroAchievements
migration.py # MigrationService — RetroDECK path migration
game_detail.py # GameDetailService — game detail page data aggregation
rom_removal.py # RomRemovalService — ROM file deletion and state cleanup
artwork.py # ArtworkService — cover art download, staging, cleanup
shortcut_removal.py # ShortcutRemovalService — shortcut removal and state cleanup
_util.py # Shared service utilities
adapters/ # I/O boundaries (~860 lines)
persistence.py # PersistenceAdapter — settings/state/cache JSON I/O
steam_config.py # SteamConfigAdapter — VDF, grid dir, Steam Input
romm/
http.py # RommHttpAdapter — HTTP client
version_router.py # VersionRouter — SaveApi version proxy (v46/v47)
save_api/
v46.py # SaveApiV46 — RomM 4.6.1 save adapter
v47.py # SaveApiV47 — RomM 4.7.0 save adapter
domain/ # Domain logic (ES-DE config, RetroDECK paths)
es_de_config.py # CoreResolver + GamelistXmlEditor
retrodeck_config.py # RetroDECK path resolution (roms, saves, BIOS)
state_migrations.py # Schema migration functions for state files
bios.py # BIOS status formatting and computation
save_conflicts.py # Save file conflict detection and resolution logic
rom_files.py # ROM file format logic (M3U generation, launch file detection)
shortcut_data.py # Shortcut data building (registry entries, shortcut dicts)
sync_state.py # SyncState enum for sync lifecycle
models/ # Domain dataclasses (types inlined in services)
lib/ # Utilities (errors)
src/ # Frontend TypeScript
index.tsx # Plugin entry, event listeners, QAM router
components/ # React components (QAM pages, game detail UI)
patches/ # Route and store patches
api/backend.ts # callable() wrappers (typed)
types/ # TypeScript interfaces and Steam API declarations
utils/ # Shortcut CRUD, sync, downloads, collections
bin/romm-launcher # Bash launcher for RetroDECK
defaults/config.json # 149 platform slug -> RetroDECK system mappings
tests/ # Backend unit tests (1260+ tests)
See Backend Architecture for the service/adapter design, dependency diagram, and layer enforcement rules.