# Development Guide (/docs/development)





OpenNOW's active desktop client lives in `opennow-stable/` in the [OpenCloudGaming/OpenNOW](https://github.com/OpenCloudGaming/OpenNOW) repository. This site is the canonical public documentation for that codebase.

## Prerequisites [#prerequisites]

* Node.js 22
* npm
* Git
* Rust/Cargo when working on the native streamer
* GStreamer development packages when building the Rust streamer with the `gstreamer` backend
* A GeForce NOW account for end-to-end session testing

Install from the Electron workspace:

```bash
git clone https://github.com/OpenCloudGaming/OpenNOW.git
cd OpenNOW/opennow-stable
npm install
```

## Scripts [#scripts]

Root scripts proxy into `opennow-stable/`:

| Root command               | Purpose                                                                        |
| -------------------------- | ------------------------------------------------------------------------------ |
| `npm run dev`              | Start Electron/Vite development mode                                           |
| `npm run build`            | Build the renderer and Electron bundles                                        |
| `npm run native:check`     | Run `cargo check` for `native/opennow-streamer`                                |
| `npm run native:build`     | Build the release Rust streamer and copy it into `native/opennow-streamer/bin` |
| `npm run typecheck`        | Run Node and renderer TypeScript checks                                        |
| `npm run locales:check`    | Validate renderer translation keys and locale JSON files                       |
| `npm run crowdin:upload`   | Upload `locales/en.json` source strings to Crowdin                             |
| `npm run crowdin:download` | Download translated locale JSON files from Crowdin                             |
| `npm run dist`             | Build app + native streamer, then package unsigned artifacts                   |
| `npm run dist:signed`      | Build app + native streamer, then package with signing enabled                 |

Workspace scripts available inside `opennow-stable/`:

| Workspace command       | Purpose                                                  |
| ----------------------- | -------------------------------------------------------- |
| `npm run dev`           | Electron/Vite dev server                                 |
| `npm run preview`       | Preview a production build                               |
| `npm run lint`          | Run `oxlint src`                                         |
| `npm test`              | Run the Node test harness                                |
| `npm run native:check`  | Cargo check the native streamer crate                    |
| `npm run native:build`  | Run `scripts/build-native-streamer.mjs`                  |
| `npm run typecheck`     | Type-check main/preload and renderer projects            |
| `npm run locales:check` | Validate renderer translation keys and locale JSON files |
| `npm run build`         | Production build only                                    |
| `npm run dist`          | Unsigned electron-builder packaging                      |
| `npm run dist:signed`   | Signed electron-builder packaging                        |

## Source layout [#source-layout]

```text
locales/                         Source and translated renderer locale JSON files

opennow-stable/
├── src/main/                 Electron main process
│   ├── gfn/                  Auth, service URLs, catalog, CloudMatch, NVST transport
│   ├── ipc/                  Account/catalog/cache, session, and media IPC handlers
│   ├── session/              Cloud G-Sync, session conflict, selection/lifecycle helpers
│   ├── signaling/            Signaling IPC coordinator and native-streamer routing
│   ├── media/                Screenshot/recording persistence and thumbnails
│   ├── services/             Cache services, PrintedWaste queue metadata, region ping, timeouts
│   ├── nativeStreamer/       Rust child-process lifecycle plus input/surface helpers
│   ├── discordRpc.ts         Discord Rich Presence integration
│   ├── appBuildInfo.ts       packaged version/build metadata
│   ├── updater.ts            electron-updater integration
│   ├── videoAcceleration.ts  Chromium video acceleration feature/switch selection
│   ├── settings.ts           settings.json defaults and migration
│   └── index.ts              app bootstrap, Chromium/WebRTC flags, windows, protocol/services wiring
├── src/preload/              contextBridge API
├── src/renderer/src/         React UI, stream view, settings, hooks, controller mode
├── src/shared/               Shared IPC, Settings, protocol, and GFN types
├── scripts/build-native-streamer.mjs
├── scripts/check-translations.mjs
└── package.json

native/opennow-streamer/       Rust native streamer process with modular GStreamer backend
```

## Localization [#localization]

Renderer UI strings live in `locales/en.json` as the English source file. Crowdin maps that file to `locales/%two_letters_code%.json` for translated locale files; current locale codes are `de`, `en`, `es`, `fr`, `ja`, `ko`, `nl`, `pl`, `ro`, `ru`, `tr`, and `zh`. The Settings UI defines friendly language names for every current locale code and falls back to uppercase codes for any newly loaded locale without a label. Run `npm run locales:check` before merging localization changes to verify renderer `t("...")` keys exist in English and that non-English locale JSON files parse cleanly.

At runtime the renderer loads `locales/*.json`, normalizes locale codes such as `en-US` or `en_US` to `en`, stores the selected app language in browser localStorage as `opennow.locale`, and falls back to English for missing files or keys.

## Native streamer builds [#native-streamer-builds]

`npm run native:build` runs Cargo in release mode through `opennow-stable/scripts/build-native-streamer.mjs`. By default it builds with the `gstreamer` feature. Set `OPENNOW_NATIVE_STREAMER_FEATURES=none` to build without optional media support, or set a comma-separated feature list to override. The script also supports `OPENNOW_NATIVE_STREAMER_TARGET`, `OPENNOW_NATIVE_STREAMER_PLATFORM_KEY`, and `OPENNOW_BUNDLE_GSTREAMER_RUNTIME`.

When the `gstreamer` feature is enabled, the build environment must provide GStreamer development files and plugins needed by `webrtcbin`. Release builders bundle a private GStreamer runtime on Windows x64 and macOS. Linux packages use distro GStreamer dependencies instead.

See [Native Streamer](/docs/reference/native-streamer/) for runtime behavior, environment variables, protocol, and platform decoder paths.

## CI and releases [#ci-and-releases]

PRs and pushes run the `auto-build.yml` CI workflow. The `select-builds` job builds a platform matrix on sponsored Blacksmith runners (`blacksmith-2vcpu-windows-2025`, `blacksmith-6vcpu-macos-15`, `blacksmith-2vcpu-ubuntu-2404`, and `blacksmith-2vcpu-ubuntu-2404-arm`). Pull requests that target `dev` validate the reduced Windows x64 and Linux x64 matrix; other events use the full Windows x64/ARM64, macOS x64/arm64, and Linux x64/arm64 matrix.

Each matrix job installs the Electron workspace dependencies with Node.js 22, then runs lint, typecheck, and tests. The Node test harness discovers `src/**/*.test.ts`, runs them through `tsx --test`, and appends a GitHub step summary with discovered files, pass/fail counts, skipped/todo/cancelled counts, and runner startup errors when present. The CI workflow also writes a compact lint/typecheck/test summary to the job summary.

The release workflow packages this matrix:

<ReleasePackageMatrixTable />

Windows ARM64 artifacts are release downloads only; the Windows updater feed remains the x64 `latest.yml` channel. Manual releases can update `opennow-stable/package.json` and lockfile to the requested version before tagging so source archives match the app version.
