CLI Commands#
Installing composer require polidog/relayer puts these executables in vendor/bin/.
| Binary | Role |
|---|---|
vendor/bin/relayer | Project scaffolding/inspection (the framework) |
vendor/bin/rosanna-defer | Precompiling .psx and placing rosanna-defer.js (the PSX engine, Rosanna) |
vendor/bin/rosanna is installed too, but do not use its compile: it does not write the deferred manifest Relayer uses.
relayer#
With no arguments, -h, --help, or help it prints usage (no arguments exits with code 2; an explicit help exits 0). An unknown command prints usage and 2.
vendor/bin/relayer # usage (exit 2)
vendor/bin/relayer --help # usage (exit 0)-h / --help after a subcommand prints usage and exits 0; an unknown option (an argument starting with -) prints usage and exits 2. Both stop before any file is written.
relayer init#
Scaffolds the project structure into the current directory. Run it in a directory where composer require polidog/relayer has already been done.
- It does not touch existing files.
composer.jsonis patched additively, andextra.relayer.structure_versionis stamped (do not edit it by hand).initnever advances this marker, so the shape a project was generated against is always identifiable; advancing it isrelayer upgrade's job (below). - The generated artifacts include a FrankenPHP development container (
Dockerfile,php.ini,compose.yaml,.dockerignore). It starts withdocker compose up --buildwithout a host PHP (→ Getting Started). - It also generates convention files for AI agents.
RELAYER.md(the single source of truth for routing / theResponsecontract / CSRF / design policy), plus a two-lineAGENTS.md/CLAUDE.mdthat merely point to it (the file names that agent tooling and Claude Code each read automatically). They are bundled withpolidog/relayer, co-versioned with the framework, and skip-if-exists (they do not overwrite the user's existing files). - It also generates Claude Code tooling under
.claude/— arelayer-routingskill (.claude/skills/relayer-routing/SKILL.md) that passes the routing /Response/ CSRF contract on trigger, and arelayer-reviewersubagent (.claude/agents/relayer-reviewer.md) that reviews changes againstRELAYER.md. Both treatRELAYER.mdas the single source of truth and are likewise co-versioned and skip-if-exists. - It writes
.env.local(APP_ENV=dev) only into a new project: one with no.env, no marker, and none ofsrc/Pages,src/AppConfigurator.php,public/index.php, orpublic/worker.php. Before writing, it makes sure.gitignoreexcludes.env.local(adding/.env.localif missing) and fails if it cannot. It does not create the file when Git tracks.env.local. In an existing project or a fresh clone, runecho APP_ENV=dev >> .env.local(→ Environment Variables). - It rejects the case where the PSR-4 mapping of
App\points somewhere other thansrc/(becausepublic/index.phpisApp\AppConfigurator, which assumessrc/). - Exit codes:
0success /1I/O failure /2misuse (no composer.json, not JSON, etc.).
relayer upgrade#
Upgrading polidog/relayer can make a newer framework version add files to the generated scaffold. upgrade advances an existing project's structure up to the installed framework's structure. It is the counterpart to relayer init.
composer update polidog/relayer
vendor/bin/relayer upgrade
composer install- It reads the
extra.relayer.structure_versionmarker, applies the deltas between that value and the current version, then advances the marker. It is the only command that moves it. - New files are skip-if-exists. Edited files are kept as-is and reported as skipped (
=, non-destructive). - A version can also change the content of a file it shipped earlier, such as the
Dockerfilegaining a production target. That replacement is gated on the file being untouched:upgradereplaces it only when it is byte-for-byte what some framework version wrote (~). If you edited it at all, it is left exactly as-is and reported as kept (!). To take the new template, save your changes elsewhere, delete the file, and runrelayer init. - When it replaces an untouched
.env, it moves theAPP_ENV=devthere into the gitignored.env.local: it creates the file if missing, appends the line if the file does not setAPP_ENV, and leaves it alone if it does. An edited.envis kept and.env.localis not written, so checkAPP_ENVyourself. Before writing, it makes sure.gitignoreexcludes.env.local, and writes nothing if it cannot. If Git tracks.env.local, it fails and leaves.envand.env.localas they are; rungit rm --cached .env.local, commit, then re-run. - Scope is the structure deltas and the marker only. In composer scripts and autoload, it only replaces an asset publish script that points at a class that no longer exists with
Polidog\Rosanna\Defer\Installer\AssetInstaller::publish. Anything else missing is additive and safe, so re-runrelayer initfor it if needed. - Idempotent. If the project is already at the current version it reports that there is nothing to do and exits.
- A project with no marker was not generated by
relayer init, so stamp the current structure withinitfirst. - Exit codes:
0success / already current /1I/O or state error /2misuse (no composer.json, no marker, etc.).
relayer routes#
Lists the routes detected under src/Pages. They are sorted by path, with method, path, kind (page / api / native), and file lined up. A root middleware is shown at the end, marked (php) or (native).
vendor/bin/relayer routesroute.php(API routes) shows the declared HTTP methods. Pages showGET,POST. An unreadableroute.phpis not hidden — it is shown with?plus a warning line.- If
src/Pagesdoes not exist, it says so and exits.
relayer compile#
Builds every deploy artifact at once: .psx (src/Pages and src/Components, skipping a missing directory), routes:compile, then container:compile, stopping at the first failure.
vendor/bin/relayer compilerelayer native:build#
Compiles every route.go and middleware.go under src/Pages to var/cache/native/bin. It needs Go, so relayer compile does not include it (→ native routes).
relayer routes:compile#
Precompiles the route table and the dispatch pipeline. By default the router walks src/Pages/ on every request, and the dispatch listener chain is assembled from container tags on every boot. Running this writes two snapshots:
<projectRoot>/var/cache/routes/routes.php— the discovered route table.<projectRoot>/var/cache/routes/dispatcher.php— afinal class CompiledDispatcher implements DispatchListener. It receives the registered listeners in the constructor and forwards each hook in the same order. Open the file and the listener order is statically visible.
Run it at deploy time (→ Deployment).
vendor/bin/relayer routes:compile- Only their presence is the gate (the two files are independent). If
routes.phpexists, route scanning is skipped; ifdispatcher.phpexists, the dump is loaded. Missing files fall back to the matching live path. dev does not compile, so it always reflects the live tree and listener set and never goes stale. Withoutdispatcher.php, the samerelayer.dispatch_listenertag is fanned out throughRuntimeDispatcherin the same order, so the observable behavior is identical. - Scan-time ambiguities (a
pagevsroute.phpcollision, route-group URL collisions) fail in this command rather than on the first production request. dispatcher.phpbakes only the listener service IDs (class names). Constructor arguments are not baked. Listeners with env-derived constructor arguments are still resolved through the live container at request time (independent of the container:compile caveat).- The outputs are always the two paths above (auto-generated; do not edit by hand — regenerate with the same command). It is a separate step from
rosanna-defer compile(.psx); run both at build time. - Exit codes:
0success /1missing or unscannablesrc/Pages, or a write failure.
relayer container:compile#
Precompiles the DI container. By default Relayer::boot() builds a Symfony ContainerBuilder and runs compile() on every request — usually the largest avoidable per-request cost. Running this dumps it to a plain PHP class at <projectRoot>/var/cache/container/CompiledContainer.php (Polidog\Relayer\Generated\CompiledContainer), and production requires that file instead of rebuilding. Run it at deploy time (→ Deployment).
vendor/bin/relayer container:compile- Only its presence is the gate. If the file exists production uses it; if not, it falls back to a live build. dev does not compile, so it always reflects the latest service configuration and never goes stale.
- Bad service definitions (errors in
config/services.yamlorApp\AppConfigurator) fail in this command rather than on the first production request — same deploy-time failure property asrelayer routes:compile. - Regenerate after changing service configuration (re-run the same command). The output is always the path above (auto-generated; do not edit by hand). It is a separate step from
rosanna-defer compile(.psx) androutes:compile(the route table); run all three at build time. - Exit codes:
0success /1build, dump, or write failure.
relayer profiler:clear#
Deletes the dev profiler records (var/cache/profiler/*.json). See Profiler for details.
vendor/bin/relayer profiler:clearIdempotent, deleting only *.json (directories and other files remain). Empty still succeeds with 0; on a delete failure it reports and returns 1.
rosanna-defer compile#
A build step that precompiles .psx so there is no compilation at request time. Run it in production deployment (Deployment).
vendor/bin/rosanna-defer compile [options] [paths...]- A missing path exits 1 (a new project has no
src/Components, so userelayer compileto build everything). - The manifest is rewritten on every run. Pass
src/Componentsandsrc/Pagesin one command. - It scans each path (file/directory) and compiles
.psxinto the cache directory (named by the sha1 of the source's absolute path, so the source tree is not polluted). - It writes out
manifest.php(PascalCase component FQCN → generated path) anddeferred-manifest.php, which collects theDeferconfig (Defer Components auto-register via this). Lowercase-namedpage.psx/layout.psxare compiled but do not appear in the manifest and are loaded directly by path. - If the source hash matches
*.meta, recompilation is skipped (incremental). Whenpathsis omitted, the default is<cwd>/components, so in a setup like this site you specifysrcexplicitly.
| Option | Effect |
|---|---|
--check | Does not write; exits non-zero if anything is stale (good as a CI/build gate) |
--clean | Deletes the contents of the cache directory |
--watch | Recompiles on .psx change (stop with Ctrl+C) |
--cache=PATH | Cache destination (default <cwd>/var/cache/psx) |
An unknown option exits 1. --check and --watch cannot be combined.
vendor/bin/rosanna-defer compile src # precompile under src
vendor/bin/rosanna-defer compile --check src # CI: fail if not compiledvendor/bin/rosanna-defer publish places public/rosanna-defer.js. relayer init registers Polidog\Rosanna\Defer\Installer\AssetInstaller::publish in composer's post-install-cmd / post-update-cmd, so composer install normally does it for you.
This site's article-management CLI
bin/docs(migrate/list/new/edit/show/ rm/export/import) is this site's own tool, not relayer — it edits the Turso store directly. It is not a framework command, so treat it as something separate (→ Deployment).
Revisions (10)
- v1.1.0: usePHP→Rosanna(rosanna-defer)に合わせて更新
- v0.31.0: relayer compile、native:build、usephp compile の失敗条件を追記
- Reflect v0.26.0 relayer upgrade and routes:compile behavior
- v0.21.0 routes:compile dumps dispatcher.php; RuntimeDispatcher fallback
- v0.20.0 — add runtime secret bullet for container:compile
- Document the relayer container:compile command (v0.19.0)
- Sidebar reorg: order shift for the new Development category
- Document the relayer routes:compile command
- Add the relayer upgrade command (v0.18.0)
- Created