Development uses the root pnpm workspace. You need Node.js 24.16 or newer and the pnpm version pinned in the root packageManager field. Install that version through npm; Corepack cannot launch pnpm 12’s native executable.
git clone https://github.com/techsquidtv/cliparr.git
cd cliparr
cp .env.example .env
npm install --global "$(node -p 'require("./package.json").packageManager')"
pnpm install
pnpm devgit clone https://github.com/techsquidtv/cliparr.git
Set-Location cliparr
Copy-Item .env.example .env
npm install --global (node -p 'require("./package.json").packageManager')
pnpm install
pnpm devThe frontend runs on http://localhost:5173. The API server runs on http://localhost:3000 and redirects auth callback pages back to the Vite frontend during development.
Optional Docker dev stack
The optional Docker dev stack seeds Plex and Jellyfin with Sintel, a Blender open movie with embedded subtitle tracks.
Run pnpm docker:dev:build after Dockerfile or dependency changes, then pnpm docker:dev:up to start the stack.
Portable provider tests
Ordinary tests need neither Docker nor a provider account. Run pnpm --filter @cliparr/server test
and pnpm test:plex-sdk with Node 24. Shared fixtures in
apps/server/src/test/providerFixtures.ts use plex.example.test and
jellyfin.example.test, fabricated identifiers and tokens, and DNS results from
RFC 5737 documentation ranges. Install the fixture hooks in every test file that
uses those endpoints; unexpected DNS and HTTP requests fail, and mocks restore
after each test. Restore temporary environment changes with the test cleanup hook.
Private address boundaries belong only in the labelled networkPolicyFixtures.ts
classifier fixtures. Socket tests bind loopback with port 0 (an OS-assigned port)
and explicitly enable the relevant provider’s loopback opt-in for that test.
Never copy addresses, device identifiers, responses, tokens, or paths from a real
installation into a fixture.
For integration checks, use the Docker dev stack above with its seeded Sintel
library, service names plex and jellyfin, and documented development credentials.
Use a unique Compose project and fresh volumes; remove only resources created by
your validation run. Personal provider accounts and libraries are unnecessary.
pnpm privacy:check runs in preflight and CI without an additional dependency.
It reports file, line, and category without printing matched values. Exceptions
are limited to documentation ranges, unsafe network-policy inputs, the explicit
development network, and hash-verified public Plex specification examples. Review
changed upstream snapshots against their published source before changing a hash;
update generated contracts through pnpm plex:sdk:update, never by hand.
The check is a fixture policy, not a complete credential detector. Review new captures visually and inspect embedded metadata before committing. Use the public seed media for screenshots and recordings, with fabricated account/device labels. Intentional project attribution and documented development credentials are public.
Live session architecture
While dashboards are subscribed, the Cliparr server maintains shared provider notification connections: SSE for Plex and WebSockets for Jellyfin. It loads initial session snapshots, prepares playback metadata and session-scoped media handles, handles reconnects and permission changes, and forwards updates to browsers through authenticated SSE at /api/media/live. Browsers do not poll provider sessions.
For manual validation, connect a provider and check playback start, pause, stop, and recovery after a connection loss. The initial connection may take longer while Cliparr finds a reachable server and prepares playback details. See Providers for supported Jellyfin versions and Configuration for streaming requirements.
Pull request checks
Before opening a pull request, run:
pnpm preflightUse a Conventional Commit pull request title, such as feat: add subtitle presets, fix: preserve Jellyfin session ids, or ci: update release automation.
Cliparr uses squash merges with the pull request title as the squash commit title. Release automation uses merged pull request titles to choose the next SemVer version and generate release notes.
Release-impacting title types:
featcreates a minor release.fix,perf,security, andbuild(deps)create a patch release.- A breaking
!creates a major release. docs,ci,chore,refactor,test,style, and otherbuildchanges are included in notes but do not trigger a release by themselves.
Releases
GitHub Releases are the canonical changelog. The manual Release workflow runs from main, publishes Docker images to GHCR, creates the GitHub Release, and triggers a Cloudflare Pages rebuild so /changelog mirrors the latest release notes.
Use the workflow’s dry-run mode before publishing. Real releases require the CLOUDFLARE_PAGES_DEPLOY_HOOK_URL repository secret. Cloudflare Pages builds require a read-only GITHUB_TOKEN or GH_TOKEN environment variable so the changelog mirror does not hit unauthenticated GitHub API rate limits.
Do not include Plex tokens, Jellyfin credentials, server URLs, local media paths, or other private account details in issues, logs, screenshots, or pull requests.