История коммитов

.
refactor(forum): drop the legacy link converter
forum:normalize-message-links converted the posts of a site moving from 9.8 to 9.9:
it unwrapped the /redirect/ interstitial around the site's own links and rewrote the
query-string links of the older forum. 10.0 can only be reached from 9.9, where that
work is behind everyone.

ForumMessageLinkNormalizer goes with it - it existed to serve that command and nothing
else called it. The static analysis baseline loses the entries of both, and the
documentation of the upgrade no longer promises that migrate fills in slugs.
.
refactor(database): drop the slug migrations, the conversion belongs to 9.9
They could never do anything. The installer of 9.9 already created the slug columns and
their unique keys, and a site that came to 9.9 from 9.8 had them filled by the very
commands this replaced - and 10.0 can only be reached from 9.9. On a fresh installation
the baseline creates the columns and there are no rows; on an upgrading one everything
is already there.

The commands stay deleted: they converted 9.8 to 9.9, which is a path 10.0 no longer
has, the same reason the pre-9.9 install scripts were dropped.

The parity test keeps running only the baseline of a source. The fixture is a record of
what the old installer built, and the first migration written after the baseline would
otherwise break it with a confusing failure.
.
docs(database): write down the migration rules and bump the documentation
A guide for whoever writes the next migration: where the files live, what a migration
is given and what it must never reach for, and why - it is a historical record that has
to run unchanged on a site upgrading years from now. The module scaffold gains its
migrations directory, and the documentation submodule follows.
.
refactor(database): turn the slug converters into migrations
library:generate-slugs and downloads:generate-slugs were one-time commands an
administrator had to know about, find in the changelog and run by hand. They are
migrations of their modules now: the upgrade runs them along with everything else, and
the journal is what remembers they are done - not a flag in a local config file that a
restored dump knows nothing about.

They fill only what is empty, where the commands regenerated everything. A slug is the
address a page already has, so one that is set - by the old command or by hand - is
kept, and the ones generated around it reserve it and step out of its way.

forum:normalize-message-links stays a command. It asks which hosts count as internal,
falling back to the configured homeurl, and it works through two services of the module
and a model. A migration may depend on none of those: it has to run unchanged years
from now, when all three have moved on.

The parity test now runs the baseline of a source rather than everything in it. The
fixture is a record of what the old installer built, and a migration written after the
baseline is meant to take the schema somewhere that code never went.
.
feat(admin): say when the database is behind the code
An administrator who unpacked a new version had no way of learning that the database
still had to be brought up to it, short of reading the changelog and reaching for a
terminal. The admin panel now says so on every page, and offers the page that puts it
right.

The notice is shown only to whoever may open the maintenance page - to anybody else it
would be about something they cannot act on - and only while something is actually
waiting. What it counts is cached for a minute and dropped the moment a run finishes,
so it goes away with the migration rather than with the expiry, and a page of the panel
does not pay for a directory listing and a query.

A migration nobody can read is logged and counted as none: this is a notice on a page,
and taking down every page of the panel with it would hide the problem behind a worse
one.

migrate itself joins the maintenance page as a background task, so a site with no shell
can update its database from the browser. Background because on a large site the schema
takes longer than a web server waits, and a queued task cannot be started twice by a
reloaded page.

The language templates are regenerated, which also catches up with the line references
the schema move left behind.
.
refactor(database): move the schema into baseline migrations
The installer no longer builds the tables itself. A fresh installation and an
existing site upgrading to 10.0 now take the same path: the migrations of the core
and of every module. The four places the schema used to be described in - the
installer, AuthSchema, MailSchema and the install() of each module - are gone.

The baseline is a snapshot rather than a step, and the one place idempotence is
allowed: it creates what is missing and leaves the rest alone, so a site upgrading
from 9.9 arrives at the schema a fresh one is built with. Every migration after it is
a plain step forward.

BaselineSchemaParityTest holds the old code to account: the fixture is the exact MySQL
the installer and the module installers produced on a fresh database, recorded from
that code before it was deleted, and the migrations have to produce the same, table by
table. Verified against a real server as well - a database built from the migrations
and the one built by the old installer have the same 60 tables, with the same columns,
keys and options; five tables list their keys in a different order, which MySQL does
not care about.

The port grew two questions the baseline needs to be able to skip its own work:
hasForeignKey(), because a database may satisfy a foreign key with an index it already
has and then no index carries the name of the constraint, and fullText(), the word
index the forum and the library search with - left out on a database that has no such
thing.

AuthSchema and MailSchema also held the names of their tables, which models and queries
spell with them; those names live on in AuthTables and MailTables.

Tests build their schema by running migrations (Tests\Support\RunsMigrations) instead
of calling a second description of the same tables. A test may ask for single
migrations of a source, and for the core it has to: its index names come from 9.x,
where several tables name an index user_id, which MySQL keeps per table and SQLite per
database.
.
feat(database): add the migration engine
Migrations live in system/migrations and modules/<module>/migrations, one file per
step returning an anonymous class extending Migration. A migration is given a schema
and a connection and nothing else: it has to keep running unchanged on a site
upgrading years from now, and a model or a service it reached for will have moved on.

The journal is a table of the database it describes, so it travels with a restored
dump instead of being left behind in a config file. A row is identified by its source
and version rather than by a file name: two modules may well reach the same minute,
and the words after the timestamp are a description an author may correct.

Sources come from MigrationSourceProviderInterface, tagged by the container, so a
package that ships tables of its own joins the run without a line changed here. The
core is asked first and the modules after it - a module may point at the tables of the
core and the core points at nobody's.

A run takes a lock, applies one migration at a time and journals each before calling
it done. A failure stops the run and is not journalled, and the message says the step
may have carried out part of its work: MySQL does not roll back DDL.

Commands: migrate, migrate:status, migrate:rollback and make:migration. Rolling back
is a tool of development - it undoes a step the data has already been through, so a
migration that says nothing refuses instead, and a site not in debug mode has to pass
--force.
.
feat(database): add the connection and schema ports
Two contracts for the code that has to outlive the ORM: ConnectionInterface for
running a query, and SchemaInterface with a neutral table description for creating
and altering tables. Neither names the library underneath.

The description covers what the CMS actually uses - 19 column types, five modifiers,
four kinds of key - and deliberately leaves out platform-only idioms such as column
positioning, which no portable adapter could carry out.

Behind the ports, IlluminateSchema and BlueprintCompiler translate to the Eloquent
schema builder: the same builder that created the tables of every existing
installation, so a table built through the port is identical to the one there now.
MysqlDdlTest pins that DDL statement by statement, against a connection double that
cannot reach a server.

The description is excluded from the directory load of the container: its value
objects carry a column name and a length, and autowiring them breaks the
compilation.
.
fix(admin): autowire the config repositories writing local files
FileCaptchaConfigRepository and FileSystemAuthConfigRepository take a LocalConfigWriter, but both were registered with set() without autowire(), so the constructor argument was never injected and the captcha settings page ended in an ArgumentCountError.
.
Merge remote-tracking branch 'origin/10.x' into 10.x
# Conflicts:
# composer.lock
# docs
# system/src/Container/PSRContainerFactory.php