Working on the server itself
For someone changing this repository rather than using it. The conventions are in AGENTS.md; these are the commands they rest on.
The upkeep CLI#
Everything this repository is kept in order by is one command — the requirement and decision files, the forward-run scenarios, the hint corpus, the bundled catalogs, and the core checkouts below. Run it with nothing and it says what it supports:
bin/cli # every command it carries, grouped by subject
bin/cli todo:next # the one todo that is due now, and nothing else
bin/cli repository:check # requirements, decisions, scenarios and the todos against their formats
bin/cli help <command> # what one command takes, and what each argument isbin/cli # every command it carries, grouped by subject
bin/cli todo:next # the one todo that is due now, and nothing else
bin/cli repository:check # requirements, decisions, scenarios and the todos against their formats
bin/cli help <command> # what one command takes, and what each argument is
bin/typo3-dev-companion is the server itself and carries none of this.
Core checkouts#
The knowledge is bound to TYPO3 versions, so writing it means checking a
statement on both sides of the boundary it claims. knowledge/versions.json
declares the lines that are covered, and one command turns them into checkouts
this repository owns:
bin/cli checkouts:update # create what is missing, update what is there
bin/cli checkouts:status # what exists, at which revisionbin/cli checkouts:update # create what is missing, update what is there
bin/cli checkouts:status # what exists, at which revision
They land below .checkouts/, which is gitignored — one treeless clone plus a
worktree per version, so four lines share one object store (under a gigabyte in
total). Nothing at runtime reads them: they are how the knowledge is verified,
not where the answers come from.
The same command keeps typo3/testing-framework there, because the harness a
project extension tests in releases on its own cycle and the core repository
does not contain it (D-KNW-002). Which release line belongs to which major
is not recorded anywhere: each covered branch pins it in its own
require-dev, and one worktree per pinned line is checked out at that line's
newest tag. So a statement about the harness is verified in
.checkouts/testing-framework/<line> the way a statement about the core is
verified in .checkouts/<branch>, and bin/cli catalog:check re-reads
both.
Scenario environments#
A case is only meaningful in the working directory it names, and two of the five
scenarios/readme.md defines are made by this checkout rather than found on
the machine:
bin/cli environment:status # which ones this checkout has, and which are missing
bin/cli environment:create E-SITE # a DDEV project with TYPO3 installed in it
bin/cli environment:create E-SITE 13.4 # the same, on another covered version
bin/cli environment:create E-NONE # a directory with no installation above itbin/cli environment:status # which ones this checkout has, and which are missing
bin/cli environment:create E-SITE # a DDEV project with TYPO3 installed in it
bin/cli environment:create E-SITE 13.4 # the same, on another covered version
bin/cli environment:create E-NONE # a directory with no installation above it
They land below .environments/, which is gitignored. E-SITE is six
ddev commands: a TYPO3 project, its containers, TYPO3's own base
distribution at the version asked for, the system extensions this server's
console path asks for, and the setup that writes the database, the admin user
and a site configuration. Minutes on a cold Composer cache, seconds on a warm
one, and running it again finishes one that stopped halfway.
There is one installation per covered version, each its own directory and its
own DDEV project, and the version named none is the covered stable one. Asked
for one that is already installed the command starts its containers rather than
building anything, so an environment is made once and kept — D-EVI-006,
which also has what one costs on disk.
The development line is one of them and is built differently: from the base
distribution's dev-main at a dev stability, on PHP 8.5, because that is what
its core declares. It is the only line on which this server's answers about the
next major can be seen at all, and it moves under the machine daily — nothing
re-makes it, so ddev delete and create again is what refreshes it.
What it is for is a directory in which ddev exec vendor/bin/typo3 … answers
— the half of this server that no test reaches, and where both D-DIS-007 and
R-DIS-018 were found by a real run instead. What it is not for is a recorded
forward review: a scaffold's defects are this repository's own, so a review
still runs in a real project. The other three environments say where they come
from when you ask for them, and the reasoning is D-EVI-004.
The published documentation#
This directory is published as a site, and nothing else in the repository is:
the site opens on the manual's own page and the checkout's
readme.md is a file it does not carry — D-DOC-026. What is generated is
a copy rather than these files.
This repository writes the copy and stops there — D-DOC-028. The
renderer is a build tool and none of it is committed here, so a deployment
prepares the copy and installs a renderer of its own. Locally that is one
command, which fetches the renderer into .site/renderer the first time and
reuses it after:
bin/cli documentation:preview # the whole site, into .site/html
php -S localhost:8000 -t .site/html # read it at http://localhost:8000/bin/cli documentation:preview # the whole site, into .site/html
php -S localhost:8000 -t .site/html # read it at http://localhost:8000/
bin/cli documentation:prepare is the first of its steps on its own — the
copy, into .site/source, with no renderer, no theme and no network. That is
what .github/workflows/documentation.yml runs before installing a renderer
into the runner's own temporary directory.
The order is not a choice. The renderer publishes the copy rather than these sources, so a render before a prepare renders the previous one; the theme's finish step reads the pages the renderer has just written, and it is what copies the stylesheet, the script and the faces beside them and writes the index the search bar fetches. The workflow spells the same three steps out, which is the one thing here written down twice.
Delete .site/ to render against the theme as it stands. The preview keeps
the renderer it fetched, and a deployment resolves it fresh on every run.
The site is read over a server rather than by opening .site/html/index.html:
the search fetches its index as a file beside the pages, and a browser refuses
that fetch over file://. Everything else on the page survives it, so a site
opened from disk looks whole and has no search.
87 of the links here point at a decision, a requirement or a class, and a
visitor of the site has none of those. The copy turns each of them into the file
on GitHub and leaves the rest as written, so these pages keep the paths a reader
of the checkout follows. It also publishes every readme.md as the
index.md a generator serves as the directory itself, and drops the heading a
link names in another page, which this renderer answers by discarding the link.
What that costs is D-DOC-017.
The renderer is phpDocumentor Guides, configured in guides.xml. Nothing here
requires it: one package is asked for, typo3/soul-guides-theme, and the
renderer comes with it. Resolved into this package's own require-dev it
would add 34 packages to every composer install, so it is fetched into a
directory of its own instead — D-DOC-028.
That configuration sits beside the pages, as documentation/guides.xml, which
is where a TYPO3 extension keeps its own and where -c documentation names it
on the render step. Everything else stays relative to the working directory —
the input and output it declares, the renderer, the finish step — which
is why both commands are run at the root. It is the one file below
documentation/ that is not published: D-DOC-027.
What the page looks like is not this repository's to invent, and no longer
this repository's to carry either. The design system publishes itself as a
theme for this renderer: the layout, the stylesheet, the script, the two
families, the icons and the search all ship inside that package, and what stood
here — a layout, a stylesheet, a script and an asset build of its own — is gone.
What is left here is guides.xml, and everything the bar, the tab and the
footer say is configured in it rather than by copying a template. D-DOC-024
is the move; D-DOC-023, which vendored the same system by hand, is revoked
with it.
The front page is the one page set in that theme's marketing layout —
D-DOC-030. :layout: marketing stands above the title, because the parser
takes a field list as metadata only while no title has been found, and what
follows is a run of band directives with the page's claims in them. What a
band, a grid, a card and a surface take is
the theme's own manual;
nothing here renders any of them.
Two things in guides.xml are load-bearing. theme="soul" selects a theme
that has to exist first, and the extension element below it is what makes it
exist. automatic-menu is the other: the rail and the trail are a toctree
in this renderer, which is a reStructuredText directive this markdown corpus
cannot write, and with it on the same tree is built out of the directories
instead. So every directory of this documentation needs its own
``readme.md`` — a page whose directory has none is attached to nothing and
lands in no menu at all, which
SiteTest::everyDirectoryOfTheDocumentationHasItsOwnPage is what stops.
The mark is this repository's own drawing and lives with the pages, as
images/signet-s.svg, -m and -l: a signet is redrawn per optical size
rather than scaled, and a browser picks between them by the slot it needs. Each
is written the way the system asks artwork to be written — one var() with a
hex fallback per shape, and the whole drawing under one id — so a mark
referenced into the page carries the page's own ink and the file still renders
on its own.
One thing the local preview cannot show is the type. The faces are
font-display: optional, so a browser uses one only where it is already
cached — which is what stops the wordmark being re-laid out on every navigation,
and what makes php -S render the whole site in the fallback, since it serves
no cache header. What is deployed does.
.github/workflows/documentation.yml runs all of it on every push to main
and deploys the result to
GitHub Pages. It needs
Settings → Pages → Source: GitHub Actions on the repository: a deployment
from a branch serves the root or /docs, and this directory is neither. Node
is there for the finish step alone, which is one bundled file and installs
nothing.
The drawings are the open half. A markdown image is an inline node, the theme
renders a figure for the reStructuredText directive alone, and a plain <img>
is a document of its own that cannot be told which mode the page is in. So a
reader in dark reads a light drawing, and a drawing is read at the width of the
column rather than at the size it was drawn at. What each would need is in
D-DOC-024.
Tests#
composer ci # lint, coding guidelines, static analysis, tests — what CI runs
composer test # phpunit only
composer stan # phpstan only
composer cgl # bring every PHP file to the guidelines; cgl:ci only reportscomposer ci # lint, coding guidelines, static analysis, tests — what CI runs
composer test # phpunit only
composer stan # phpstan only
composer cgl # bring every PHP file to the guidelines; cgl:ci only reports
composer ci lints, checks the coding guidelines, runs the static analysis,
and runs the test suite: the search and ranking logic, every tool against its
declared schemas and annotations, and the stdio entrypoint driven as a real
subprocess. CI runs the same command on every supported PHP version.
The guidelines are php-cs-fixer's, configured in .php-cs-fixer.dist.php and
nowhere else: PER-CS 3.0 plus the handful of rules this repository writes by —
strict types declared, imports sorted with global classes left unimported,
single quotes, trailing commas in multiline arrays. cgl rewrites the files
and cgl:ci reports what it would rewrite, which is the half ci runs
because a check may not change the code it is judging.