Files
XC_VM/CONTRIBUTING.md
T
Divarion_D 734f4751c9 chore(tests): move phpunit.phar into tests/ and repoint references
The committed PHPUnit runner lived at tools/.bin/phpunit.phar, away from
the suite it runs. Move it next to the tests it drives —
tests/phpunit.phar — and update every invocation to
`php tests/phpunit.phar -c tests/phpunit.xml.dist`:

- CI workflows (ci, build-release, build_pre-release) + the ci.yml header,
- CLAUDE.md, CONTRIBUTING.md, tools/README.md, the qa-lead-reviewer agent,
- docs/en (dev-workflow, updates_checklist, phpunit-phar, refactoring).

docs/ru is generated from docs/en (make docs-translate) and is left for
the next regeneration, per the docs workflow.
2026-09-13 22:04:10 +03:00

14 KiB

Contributing to XC_VM

Thank you for considering contributing! XC_VM is an open-source, Xtream-Codes-style IPTV management panel (PHP 8.1+, AGPL-3.0) — a modular monolith migrated to Composer PSR-4. This guide covers the layout, the local dev loop, the checks CI enforces, and how to open a good pull request.

📌 General Guidelines

  • Minimally use AI; you own every line you submit and must understand it.
  • Follow the project's coding style and best practices.
  • Keep pull requests focused on a single change.
  • Write meaningful commit messages (Conventional Commits, English).
  • Document behaviour that isn't obvious from the code.
  • If you refactor and aren't sure code is unused elsewhere, comment it out with a note instead of deleting — it can be removed after the next release.

🧭 Repository Layout (read this first)

The trap for new contributors: the application lives under src/, not the repo root.

  • src/ is the application root and the deploy root. composer.json, vendor/, bootstrap.php, console.php all live in src/. It maps 1:1 to the install root on a server (src/Core/X.php → /home/xc_vm/Core/X.php).
  • PSR-4: XcVm\ → src/ (e.g. XcVm\Core\Database\DatabaseHandler = src/Core/Database/DatabaseHandler.php).
  • The repo root holds build & CI tooling only: Makefile, tools/, tests/, install/, lb_configs/, build/, docs/.

Two build flavours are produced from the same tree: the full MAIN panel and a stripped Load Balancer (LB) archive (restreaming only — admin/reseller/ player code, installers, and privileged commands removed). Modules are MAIN-only. Deeper architecture notes live in docs/en/development/ and the repo CLAUDE.md.

⚠️ Hard Constraints

  • src/vendor/ is committed and PRODUCTION-ONLY. Never run composer install on a deploy path. To change the autoload map, run composer dump-autoload from src/. After changing dependencies, re-commit a composer install --no-dev vendor tree plus the updated composer.lock.
  • Modules own their DB schema via file migrations (src/Modules/<name>/migrations/<semver>.up.sql/.down.sql). Do not add module tables to src/bin/install/database.sql, and core must not touch module-owned tables directly — dispatch an event and let the module clean up.
  • Bundled binaries are regular Git objects (not Git LFS). The ffmpeg/ffprobe, redis-server, yt-dlp, MaxMind DBs, login-bg.mp4, etc. are committed as plain binary blobs (marked -text -diff in .gitattributes) — a normal git clone fetches them whole, no git lfs pull needed. A verify_no_lfs_pointers build gate still guards against stray legacy LFS pointer stubs.
  • Outbound HTTPS from PHP must use cURL — file_get_contents() over https does not work in this environment.

🛠️ Local Setup

You need PHP 8.1+, git, make, and Composer.

git clone https://github.com/Vateron-Media/XC_VM.git
cd XC_VM

make dev-tools    # composer install in src/ — adds PHPStan +
                  # phpcs (Slevomat) + unused-public to src/vendor

make dev-tools installs the require-dev toolchain (PHPStan 2.2.9, PHP_ CodeSniffer + Slevomat, Tomas Votruba's unused-public) into src/vendor. These are not in the committed vendor tree, so run it before the checks below. When you're done, make dev-clean prunes src/vendor back to production-only.

🔁 The Dev Loop: Deploying to a Live Box

Prerequisite — you need a working XC_VM install to sync onto. sync-dev.sh does not provision a server; it pushes changed code onto an existing install root (/home/xc_vm/ by default). So first stand up a throwaway/test box the normal way — install the panel from a release (see Quick Install in the README: download XC_VM.zip, unzip, sudo python3 install). Only once the panel is installed and running does sync-dev.sh become useful for pushing your local edits onto it.

Once you have that box, use tools/sync-dev.sh to iterate against it without building a release. It works out which files under src/ your commits changed and copies their full current content to the server's install root. Files your commits deleted are removed on the server; renames delete the old path and copy the new one.

It is a developer convenience — it never runs DB migrations, ships binaries, or edits per-server config. For a real upgrade use the release archive + panel updater.

# Preview the copy/delete plan — never contacts the server; always safe:
DEV_SERVER=<ip> tools/sync-dev.sh <RANGE> --dry-run

# Push uncommitted edits to tracked files (fastest inner loop):
DEV_SERVER=<ip> tools/sync-dev.sh --working --restart

# Push committed work since the last sync (watermark in .dev-sync-state):
DEV_SERVER=<ip> tools/sync-dev.sh --restart

# Push everything since a tag / branch / sha:
DEV_SERVER=<ip> tools/sync-dev.sh 2.4.1 --restart

Here <RANGE> is usually the version tag of the panel currently installed on the box (e.g. 2.4.1). The box was installed from that release, so syncing <that version>..HEAD pushes exactly the changes your branch adds on top of it, nothing more.

  • --working — also include uncommitted changes to tracked files (diff vs HEAD).
  • --restart — restart the panel so OPcache reloads new PHP code. Slow (>1 min); run in the background under a short timeout. Pure CSS/JS/template changes are served statically and usually need only a hard refresh, not this.
  • --cache — rebuild the settings cache (for settings-shaped changes).

RANGE is a git range A..B, or a single ref REF (treated as REF..HEAD). Omitted, it continues from .dev-sync-state (or HEAD~1..HEAD on first run). SSH auth uses your keys by default; set DEV_SSH_USER / DEV_SSH_PASS (needs sshpass) to override. All SSH multiplexes over one ControlMaster socket so repeated syncs don't trip fail2ban. See the header of tools/sync-dev.sh (or --help) for the full reference.

✅ Pre-Commit Checks

Always run make cs-fix first. It auto-formats your changes to the coding standard (K&R braces, tab indentation, spacing, import order) via phpcbf, so your diff matches what CI expects. Then run the checks below — CI runs the exact same set and will reject a PR that fails any of them:

make cs-fix        # run FIRST — auto-format to the coding standard (phpcbf)
make cs            # verify code style: PSR-12 base + K&R braces + tab indentation
make phpstan       # static analysis, level 5 (also catches syntax errors)
make gates         # PSR-4 regression gates (see below)
php tests/phpunit.phar -c tests/phpunit.xml.dist   # unit tests

make cs-fix applies everything auto-fixable; whatever make cs still reports afterwards (e.g. a missing parameter type hint) you fix by hand.

make gates runs three CI blockers:

  • check-procedural-use — every procedural/view file must use-import the XcVm\ classes it references at the top (PHP use is positional; a bare short name faults at runtime).
  • verify-lb-archive — the load-balancer build must contain no privileged code (admin/reseller/player UI, user/device domain, install/root crons).
  • check-vendor-prod-only — the committed src/vendor/ must stay production-only (no dev package tracked in the git index).

Quick single-file syntax check while editing (no DB needed):

php -l path/to/File.php

🔬 Static Analysis (PHPStan)

The project is analysed at level 5 with the config in build/phpstan.dist.neon. PHPStan is a require-dev Composer package installed by make dev-tools (into src/vendor/bin/phpstan); a bootstrap in tools/phpstan/ defines the ~131 runtime define() constants static scanning can't see.

make phpstan       # must report [OK] No errors

How the gate works:

  • A committed baseline (build/phpstan-baseline.neon) freezes pre-existing findings, so CI fails only on new issues your change introduces — fix those.
  • The baseline is not a list of accepted bugs (most entries are false positives from dynamic DB-row shapes or templates). Do not grow it to hide a real problem in new code.
  • If you clear a batch of existing findings, regenerate the smaller baseline: make phpstan-baseline.
  • After runtime constants change, regenerate the stub with make phpstan-stub; after editing PHPDoc types, clear the cache first: php src/vendor/bin/phpstan clear-result-cache.

✨ Code Style

The PHP standard (build/phpcs.xml.dist) is PSR-12 with project overrides — run make cs-fix to auto-format to it before committing (make cs only reports). Key points:

  • K&R brace style (opening brace on the same line), not PSR-12's Allman.
  • Tabs for indentation (one tab per level), not spaces.
  • Give every function parameter a type hint (make cs flags untyped ones; this one is not auto-fixable — add the type yourself).
  • The ruleset excludes view templates (Public/Views/, Modules/*/views/), so short-tag templates (<?, <?=) need no special handling there.
  • All code comments and docblocks in English.
  • Prefer inverting empty-else guards: if (!$c) { body } over if ($c) {} else { body }.
  • Avoid unused functions and redundant code.
  • Follow best practices for Python and Bash tooling under tools/.

🧪 Tests

Unit tests (PHPUnit 10.5, config tests/phpunit.xml.dist, suite "Unit"):

php tests/phpunit.phar -c tests/phpunit.xml.dist                    # all
php tests/phpunit.phar -c tests/phpunit.xml.dist --filter SomeTest  # one

On an installed server, use the bundled interpreter instead of system PHP: /home/xc_vm/bin/php/bin/php tests/phpunit.phar ... (see docs/en/guides/phpunit-phar.md). On a dev machine, plain php is fine.

Guidelines:

  • Add PHP tests under tests/Unit/, named after the class under test (e.g. GitHubReleasesTest.php).
  • Prefer focused tests for the file you changed over broad project-wide mocks.
  • Cover valid inputs, invalid inputs, edge cases, and side effects.
  • If code writes to stdout, capture it in the test so PHPUnit output stays clean.

End-to-end tests (Playwright, in tests/e2e/) exercise the running panel UI:

make e2e-install   # one-time: npm ci + playwright install chromium
make e2e           # run the suite
make e2e-ui        # interactive UI mode

🧰 Developer Tooling (tools/)

Nothing in tools/ ships to production. Highlights useful while developing — see tools/README.md for the full list:

Tool Purpose
sync-dev.sh Incremental code deploy to a live box (see the dev loop above).
test_player_api.sh <url> <user> <pass> HTTP smoke-test of every Player API endpoint (status / content-type / JSON shape).
stream-check/ Dependency-free MPEG-TS + HLS stream integrity checker (stream_queue_check.py) with an SVG grapher (stream_graph.py).
test-stream-generator/ Generates a synthetic moving test pattern (stopwatch + wall-clock) as an HTTP "live" source; no input file.
test-install/ Docker end-to-end install test of the built release archive.

📦 Building a Release (maintainers)

make main    # full panel archive: dist/xc_vm.tar.gz + XC_VM.zip installer
make lb      # load-balancer archive (privileged code stripped; MAIN-only modules excluded)
make new     # wipe dist/

Builds copy only git-tracked files and run verify_no_lfs_pointers.

📚 Documentation

Docs are a MkDocs Material site under docs/.

  • Edit only docs/en/ — English is the single source of truth.
  • Never hand-edit docs/ru/ (or any other language tree): it is generated from docs/en by tools/docs/translate.py and overwritten on the next run.
  • After editing English docs, verify with make docs-build (strict — fails on broken links/anchors). Preview locally with make docs-serve.
  • docs/ru is regenerated locally before a release (make docs-translate), not in CI.

🔥 Submitting a Pull Request

  1. Fork and branch (see naming conventions below):

    git checkout -b feature/your-feature
    
  2. Make your changes; run the pre-commit checks; commit with a Conventional Commits message:

    git commit -m "feat: short description of the change"
    
  3. Push and open a pull request on GitHub:

    git push origin feature/your-feature
    

Keep the PR focused on a single change and describe what and why.

Branch Naming Conventions

Type Template Example
Features feature/<short-description> feature/user-authentication
Bug Fixes fix/<short-description> fix/login-bug
Hotfixes hotfix/<short-description> hotfix/critical-error
Refactoring refactor/<short-description> refactor/code-cleanup
Testing test/<short-description> test/api-endpoints
Documentation docs/<short-description> docs/documentation-api

👀 Code Reviews

  • All PRs must be reviewed by at least 2 maintainers.
  • Address review comments before merging.

🚀 Reporting Issues

  • Use GitHub Issues to report bugs and suggest features.
  • Provide clear steps to reproduce, plus relevant logs or error messages.

🌟 Recognition

Your GitHub profile will be added to CONTRIBUTORS.md.

Thank you for contributing! 🎉