mirror of
https://github.com/projectsend/projectsend.git
synced 2026-09-16 16:45:07 +00:00
main
5 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
4469648d82 |
Stop the update tests emptying bootstrap/cache for every other worker
`UpdateWelcomeTest > staff who may not read system information are not
interrupted` fails on a parallel run roughly one time in six, with
BindingResolutionException: Target [Inertia\Ssr\Gateway] is not
instantiable
in a file that has nothing to do with updates. Run alone it is green
every time. The cause is not in that file.
`clear-compiled` deletes bootstrap/cache/packages.php and
bootstrap/cache/services.php. There is one of each for the whole
checkout, and `pest --parallel` gives eight worker processes the same
one. Instrumented over three full runs, the real command ran 12 times per
run -- 11 from UpdateCommandTest, 1 from StaleCodeNoticeTest -- and the
other workers observed the package manifest missing at boot 46 times.
What that costs is in PackageManifest::getManifest():
if (! is_file($this->manifestPath)) {
$this->build();
}
return $this->manifest = is_file($this->manifestPath) ?
$this->files->getRequire($this->manifestPath) : [];
A worker that loses the second is_file() to another worker's unlink gets
`[]`: no discovered packages, so no package service providers, so
Inertia's is never registered and `Inertia\Ssr\Gateway` is never bound.
The next page it renders dies in the compiled root view, where
`@inertia` resolves that interface. Any test in any file, whichever one
happened to be booting.
Both halves measured. Building the manifest with inertia-laravel in
`dont-discover` reproduces the reported failure exactly -- same test,
same exception, same frame (`app('Inertia\Ssr\Gateway')` from the
compiled app.blade.php). And 12 real `clear-compiled` calls per run is
the count above.
UpdateCommandTest already owns a double for this, and says why in its own
docblock: the artisan call is a seam. Nine of its tests and one in
StaleCodeNoticeTest simply do not use it. None of them asserts that a
command ran -- they assert EnsureSystemRoles, the settings writes, the
activity log and the welcome marker, and the double touches none of
those. So the seam now covers the file, through a beforeEach rather than
per test, because the next test added here should not have to know any of
this.
The double moves to tests/Support and its helper to tests/Helpers.php,
for the reason that file documents: Pest hands whole files to workers, so
a class declared in one test file does not exist for another.
Not changed: UpdateInstallation. `clear-compiled` belongs in a real
update. Also not changed: giving each worker its own bootstrap/cache
through APP_PACKAGES_CACHE and friends. That would make the destruction
cheap rather than remove it, and nothing in the suite needs those
commands to run at all.
One new test, on the files rather than on the recorded call list -- a
future double that forgot to intercept one command would still satisfy a
call-list assertion. Counter-checked: with the beforeEach removed it goes
red on both manifests being gone (1 failed / 22 passed).
Eight consecutive parallel runs green after the change; the manifests'
mtimes are untouched by a full run, where before they were rewritten
every time. Full suite passes (2049 passed / 2 skipped). PHPStan level 8
clean -- it analyses `app` only, so it does not cover this change.
Pre-existing and left alone: pint reports `ordered_imports` on
UpdateCommandTest.php. Its import block is misordered on main too.
|
||
|
|
6ddfc1aa5d |
Greet the administrator once, on the first visit after an update
An update finished and nothing said so. The dashboard looked identical to yesterday's, and whatever the release brought was in a file nobody opens. The first time the installation's administrator opens ProjectSend after an update, they now land on a page that names the version they are on, invites them to the Discord — the same invitation update.sh prints, made again where they are actually looking — and then lays out what the release brought. The notes come from CHANGELOG.md inside the release, not from GitHub: the one moment this page exists for is the moment after an update, possibly on a server with no outbound access, describing code already on disk. Parsed rather than rendered, so nothing in it can become HTML. Once, and to one person. The update happened to the installation, so greeting five staff members — each having to dismiss a page they did not ask for — would turn a pleasant moment into a support question. It goes to the oldest active administrator, which on any installation that went through setup is whoever set it up. No owner flag was invented for this: administrators are equal in authority, and changing that for a greeting is not a trade worth making. Only forwards, and only for a real update. A fresh install has nothing to catch up on, a container reboot has not updated anything, and somebody restoring an older release is dealing with a problem rather than celebrating. Managed installations never see it at all — nobody signed in there performed the update it thanks them for, which is the same gate the System card and About's environment block already carry. The redirect is attached to the dashboard alone, not the web group: it catches a login and the sidebar logo both, without ever interrupting a download to congratulate somebody. Reading the page clears the marker, but the address keeps working — closing it by accident should not be unrecoverable — and About now links to it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
dad8d21dc8 |
Translate the update strings into all sixteen locales (#1632)
The English for the updater, the stale-code banner and the activity-log entry had settled, so this is their pass: 208 lines across sixteen catalogues, every :placeholder intact, no existing entry reordered. The activity row and its filter label were modelled on each catalogue's own "Installed ProjectSend" / "ProjectSend was installed" pair, so the new rows read like the ones beside them rather than like a different translator wrote them. One code change belongs to this pass rather than to the feature: the update entry recorded "an unrecorded version" when the previous version was unknown, and context values are substituted into the template verbatim — never translated — so that sentence fragment would have sat inside an otherwise Japanese or Polish row forever. It is a dash now. Verified on a real install with the interface switched to Spanish: "Sistema — Actualizó ProjectSend a 2.0.3, desde 2.0.2", and the action filter reading "ProjectSend fue actualizado a una versión nueva". Scan reports 0 missing in every locale; the Locale tests pass. Co-authored-by: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
0b994aebe2 |
Put the update in the activity log (#1631)
The activity log is where an administrator goes to answer "what changed on this installation, and when" — and the largest change of all was not in it. A new version arrived, the schema moved, behaviour changed, and the log said nothing. `projectsend:update` now records it as a system action, naming both versions: "Updated ProjectSend to 2.1.0, from 2.0.1". It appears in the log's own action filter without further work, since that list is built from the enum. Only a real version change is written. The container entrypoint runs this command on every boot, so logging unconditionally would bury the log under an entry per restart, and a first boot is an installation rather than an update — SetupCompleted already covers that. "First boot" is decided by whether any migration had run before this one, not by whether a version was recorded: the first update of any installation older than this command finds no recorded version, and that update is exactly the one worth logging. It says "from an unrecorded version", once, ever. Writing the entry cannot fail the update: an update that worked must not report failure because its own paperwork did. Verified on a real manual install — the row renders as "Updated ProjectSend to 2.0.3, from 2.0.2", attributed to the system. Co-authored-by: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
ed0d36de25 |
Reduce a manual update to one command that asks first (#1628)
Updating a server install cost nine artisan invocations plus a PHP-FPM
reload, written out in three places that had already drifted apart. One
of those steps is silently fatal to skip: with opcache.validate_timestamps
off — what production guides recommend and what our own image ships — the
database moves to the new version while every visitor keeps being served
the old code, and artisan reports the new version throughout.
`sudo ./update.sh` is now the whole procedure. It asks whether to check
GitHub, asks whether to download the release and verifies the checksum
published beside it, and asks whether there is a backup — offering to dump
the database when the answer is no. Then it takes the site down, replaces
the files, runs the update, reloads PHP-FPM, restarts the worker and
brings the site back. The application still has no self-updater: nothing
is fetched or applied unless somebody runs this and answers yes.
Underneath it is `php artisan projectsend:update`, which is everything an
update does that needs no root — and now the only definition of it. Both
container entrypoints call it instead of carrying their own copy of the
sequence, so the two paths cannot drift again.
Three findings worth keeping in the record, all from rehearsing rather
than reasoning:
- queue:restart has to come last. It writes its signal into the cache,
so clearing the cache afterwards deletes it and the worker runs old
code forever.
- optimize:clear is not safe to recommend. It runs cache:clear, which
on Redis is FLUSHDB — harmless on the default two-database layout,
but on a single-database Redis it takes the sessions and the queue
with it. The compiled caches are cleared individually instead.
- update.sh overwrites itself mid-run, because the zip contains it and
bash reads its own script lazily by byte offset. It re-execs from a
temporary copy before touching anything.
And when the reload is skipped anyway, the application now says so:
projectsend:update records the version it applied, and any staff page
compares that with what the running process actually compiled. The same
check catches the mirror image — new files in place, update never run.
Rehearsed end to end against real installs: a container upgrade (69 to 73
migrations, key and data intact, healthy), a scripted update on a real
nginx + php-fpm install with OPcache pinned (web process moved 2.1.0 to
2.1.1), the skipped-reload case (banner appears naming both versions, and
clears on reload), the refusals (downgrade, non-release zip, truncated
zip, URL passed to --zip, non-root), a database taken down mid-update
(site comes back out of maintenance mode by itself), and a real download
of the published 2.0.0 zip with its checksum verified.
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
|