Skip to content

Development

Mikhail Deynekin edited this page Nov 14, 2026 · 4 revisions

Development

Commands

composer test              # PHPUnit — 214 tests, 679 assertions
composer stan              # PHPStan level 6, strict types
composer check             # both
php tools/build-manifest.php   # regenerate config/manifest.json (release step only)

Tests live in tests/Unit and tests/Feature and run against a throw-away project root (tests/TestCase.php); no test touches real storage or content. Security behaviours (path traversal, XSS vectors, CSRF) and the historic v2 bugs (Unicode filenames, em-dashes, fence-length nesting) have dedicated regression tests.

The conventions checker

pwsh -NoProfile -File tools/Invoke-ProjectDoctrine.ps1

Checks over the project's own conventions (version carriers, manifest freshness, sanitizer allowlist shape, cache key content, admin page registration, wikilink contract and more). Baseline at 3.5.0 on PHP 8.5.12: 0 errors, 0 warnings (12 checks). A red run is a real finding — but compare with the baseline before blaming your change.

Continuous integration

.github/workflows/ci.yml runs on every push: PHP 8.5 only — lint, PHPStan, PHPUnit and the smoke requests; plus a frontend build reproducibility job (a difference is reported, not fatal).

Release checklist

  1. Bump src/Core/Version.php (VERSION, CODENAME).
  2. cd tools/frontend && node build.mjs --vendor — refresh assets.
  3. composer check green and tools/Invoke-ProjectDoctrine.ps1 at the baseline.
  4. Build the release tree from the tag in a clean copy, then composer install --no-dev --optimize-autoloader — the archive ships vendor/ so an install needs no Composer on the host.
  5. php tools/build-manifest.php in that same tree and copy config/manifest.json back into the repository. The updater verifies every file in the archive against the manifest embedded in it and refuses the whole update on the first mismatch.
  6. Zip the tree (everything except storage/* contents, tools/frontend/node_modules and .md.ini) as MD.Scribe-<version>.zip.
  7. Update CHANGELOG.md and the README badges; commit.
  8. Tag v<version>, create the GitHub release and attach the archive — the updater downloads the release asset, not a branch tarball.
  9. Deploy the release to a real host and run the checks in Getting started and Updates before telling anyone it works.

Two traps worth knowing

  • vendor/composer/installed.php records the git reference of the build, so rebuilding an old archive from git archive (no .git) changes that one file's hash. Build releases from a checkout, not from an extracted archive.
  • The manifest must be regenerated after the version bump and in the tree that will be zipped; a manifest regenerated in the working tree silently describes files that no longer match the archive.

Code outside static analysis

bin/md-scribe-render (the standalone CLI renderer:

bin/md-scribe-render README.md --html --toc > page.html
bin/md-scribe-render - --json < notes.md

) is deliberately not in PHPStan's paths and not in the CI lint list. The price is paid by hand: php -l bin/md-scribe-render before editing, and a smoke run after. Everything else — src, public, templates, tools, tests and the three root scripts — is linted, and src is analysed at level 6.

Adding an .md.ini key

  1. Add it to config/defaults.php with type, default, section, label and description.
  2. Read it through Config (bool(), int(), str()) — never read .md.ini directly.
  3. Add the smoke expectation to the doctrine checker if the key changes a contract.

Clone this wiki locally