Skip to content

Development

Daniel Kueppermann edited this page Mar 19, 2026 · 13 revisions

Development

Guide for setting up a development environment and contributing to decky-romm-sync.

Prerequisites

  • mise — manages Node, pnpm, and Python versions
  • Git
  • A Steam Deck or Linux PC with Decky Loader installed (for testing)

Setup

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 dependencies

This creates a Python virtual environment (auto-activated by mise via _.python.venv in mise.toml) and installs all npm packages.

Building

pnpm build            # Rollup -> dist/index.js

The frontend is bundled with Rollup into a single dist/index.js file that Decky Loader serves.

Testing

python -m pytest tests/ -q     # run all 1260+ backend tests
mise run test                   # same thing via mise

To run with coverage:

python -m pytest tests/ -q --cov=py_modules --cov=main --cov-report=term --cov-branch

Tests 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

Dev Reload

mise run dev          # builds frontend + restarts plugin_loader

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

Deploying to Device

For development, symlink the repo into the plugins directory:

sudo ln -sf "$(pwd)" ~/homebrew/plugins/decky-romm-sync
sudo systemctl restart plugin_loader

This way, rebuilds take effect immediately after a Decky restart.

Linting

PYTHONPATH=py_modules lint-imports   # check service/adapter layer rules
mise run lint                        # same via mise

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

Code Quality

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

Project Structure

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.

Clone this wiki locally