* test(ci): remove the silent-ELIFECYCLE investigation scaffolding
The Windows backend flake is root-caused (server.ts handler gate for the
in-process process.exit path; Windows pinned to Node 24.16.0 for the libuv
TCP-connect overrun, tracked upstream as nodejs/node#63620). Remove the
temporary diagnostics added while hunting it:
- delete src/tests/backend/diagnostics.ts (per-test heartbeat + node-report
snapshots) and its `--require` from the backend `test` script;
- drop the `--report-on-fatalerror`/`-on-signal`/`-uncaught-exception`
`NODE_OPTIONS` and the "Upload Node diagnostic reports" steps;
- drop the Windows OS-level netstat/tasklist sidecar watcher.
Kept: the real fixes — the log-only unhandledRejection guard in
tests/backend/common.ts, the lowerCasePadIds socket-teardown tracking (comment
de-referenced from the deleted file), and `pnpm test -- --exit` on Windows.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* test: drop the obsolete report-on-fatalerror NODE_OPTIONS guard assertion
The backend-tests-flake-mitigation source-lint guard required every backend
step to keep the --report-on-fatalerror NODE_OPTIONS + node-report upload. Those
diagnostics are removed in this PR now that the flake is root-caused, so drop
that assertion. Retain the Windows-only `--exit` checks (still a live invariant)
and reframe the file around the resolved root cause.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(test): stop a single leaked promise rejection from killing the whole backend suite
Root cause of the long-standing Windows backend-test "silent ELIFECYCLE"
flake (~22% of runs, rotating across random spec files, no mocha summary,
no JS-handler trace, bypassing --report-on-fatalerror / Defender / Windows
event log / AeDebug). Found by capturing a full-memory dump of the dying
node.exe with Sysinternals ProcDump (-t dump-on-terminate) and symbolizing
it against Node 24.15.0's node.pdb. The dying thread's stack:
exit_or_terminate_process / common_exit (CRT exit)
node::Exit
node::DefaultProcessExitHandlerInternal
node::Environment::Exit
node::ReallyExit (process.exit binding)
... v8 MicrotaskQueue::RunMicrotasks ...
node::InternalCallbackScope::Close
No exception stream — a *clean* ExitProcess, not a crash. The job log
pinned the trigger:
[INFO] server - Exiting...
AssertionError at tests/backend/specs/SessionStore.ts:235
at process.processTicksAndRejections
Mechanism: a timing-fragile test (SessionStore touch/expiry specs use real
setTimeout against a 200ms-expiry session; socket.io delay-race specs are
similar) gets timed out and abandoned by mocha, but its async body keeps
running. When its trailing assertion later throws, it surfaces as an ORPHAN
unhandled rejection belonging to no awaited test. Three handlers then
escalated that into a whole-process exit:
- server.ts installed process-global uncaughtException/unhandledRejection
handlers that call exports.exit() → process.reallyExit() (production
graceful-shutdown behaviour, catastrophic in-process under mocha)
- common.ts (PR #7663) and diagnostics.ts (PR #7838) rethrew the rejection
and process.exit(1)
Because it's a deliberate, clean exit it bypassed every forensic layer; it
rotated across files because the orphan rejection lands during whatever test
is running; it's Windows-mostly because event-loop timing makes the abandoned
test's assertion fire in a *later* test's window more often there.
Fix (two halves):
1. server.ts: gate the process-global uncaughtException / unhandledRejection
/ signal handlers behind `require.main === module`. They are correct for
a real Etherpad process but must not fire when server.start() is called
in-process by a test runner — mocha owns process-level error handling
there. Mirrors the existing `if (require.main === module) exports.start()`
idiom; production (node server.js) is unchanged.
2. common.ts + diagnostics.ts: the backend-test bootstraps now LOG unhandled
rejections instead of rethrowing / exiting. Orphan rejections cannot be
cleanly attributed to a test, so rethrowing only yields an
ERR_MOCHA_MULTIPLE_DONE abort. Real failures are unaffected — an assertion
in a test's own awaited path rejects that test's promise and mocha fails
it normally, never reaching this global handler.
Verified locally: a spec that leaks a delayed rejection during a later test
now reports `3 passing` / exit 0 with the rejection logged, instead of
aborting the run.
Follow-ups (separate PRs): harden the SessionStore / socket.io timing specs
to not leak (fake timers); remove the now-unneeded diagnostic scaffolding
(diagnostics.ts heartbeat/node-report, the #7846 OS sidecar) now that the
cause is known.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(ci): run Windows backend tests on Node 25 to dodge the libuv connect overrun
Node 24.x's bundled libuv has a stack buffer overrun in the Windows TCP-connect
path (uv__tcp_connect / uv__tcp_try_connect), proven by a SilentProcessExit
full-memory dump of the dying mocha process: the main thread executes
__fastfail(FAST_FAIL_STACK_COOKIE_CHECK_FAILURE) from __report_gsfailure with
TCPWrap::Connect -> uv_tcp_connect on the stack. It fires under the backend
suite's heavy localhost connection churn, is address-family independent (occurs
on both sockaddr_in and sockaddr_in6, so an IPv4 pin does NOT help), and -- being
memory corruption -- bypasses all JS/Node observability, rotating across tests
as the "silent ELIFECYCLE" flake (~22% of Windows runs).
Empirically: Node 25 = 16/16 green; Node 24 (even with an IPv4 pin) = ~39% fail.
Node 25's newer bundled libuv does not overrun. Linux stays on Node 24 LTS (the
bug is Windows-specific). Revisit once the libuv fix is backported to 24.x.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(ci): pin Windows backend to Node 24.16.0 (libuv fix) instead of 25
Bisect (standalone repro) pinpointed the fix to Node 24.16.0 (libuv 1.52.1):
24.15.0 (libuv 1.51.0) crashes the connect overrun 4/4 on 127.0.0.1, while
24.16.0 is clean 0/8. 24.16.0 stays on the Node 24 "Krypton" LTS line, so prefer
it over Node 25 (non-LTS). Pinned explicitly because setup-node's default
check-latest:false reuses the runner's pre-cached 24.15.0 for a bare "24".
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* docs(ci): reference upstream nodejs/node#63620 in the Windows Node-pin comment
Links the explicit 24.16.0 pin to the filed upstream issue so the pin can be
dropped back to plain "24" once the libuv connect-overrun fix is across the
supported 24.x baseline.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
In-process diagnostics (diagnostics.ts heartbeat at 5 Hz + node-report
snapshots on every beforeEach and heartbeat tick) merged in #7838 and
#7842 reach a hard ceiling: during every captured death window the V8
main isolate is event-loop-starved for 200-400 ms before the process
is externally terminated, so any timer-driven probe (heartbeat,
setTimeout, --report-on-signal handler) never gets serviced and we
have zero JS-visible state from the actual moment of death.
To capture state during the starvation window we need a probe whose
own scheduling does not depend on the dying process's libuv event
loop. This commit adds a tiny bash background loop to the Windows
backend-test steps (both with- and without-plugins). Every 500 ms it
appends:
- netstat.log: localhost TCP socket state — surfaces TIME_WAIT /
CLOSE_WAIT accumulation or ephemeral-port exhaustion that the
in-process libuv handle list can't see (libuv only shows handles
Node currently knows about; the kernel may hold many more sockets
in disposal states).
- tasklist.log: node.exe process state from the Windows OS view
(handle count, working set, CPU time), independent of whether V8
is responsive.
Both files land in $GITHUB_WORKSPACE/node-report/ which is already
the artifact-upload target on failure, so they ride for free on
existing infrastructure. The watcher is killed cleanly after `pnpm
test` returns so it never holds the runner open.
On the next captured silent ELIFECYCLE we'll have, for the first
time, a 500 ms-resolution external observation of TCP and process
state across the death window.
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ep_readonly_guest is archived (read-only on GitHub) and its
authenticate hook unconditionally swaps req.session.user with a
read-only guest, even when the request carries an HTTP Authorization
header. That silently demoted admin login attempts and stalled the
anonymizeAuthorSocket tests for 14 min/run on every with-plugins CI
matrix (#7795). The pre-fix theory blamed ep_hash_auth.handleMessage;
the actual hook trace is a red herring — handleMessage only fires on
the /pad namespace and never on /settings.
ep_guest is the maintained successor (same authors, same purpose).
1.0.72 on npm already includes the "defer to basic auth / admin
paths" fix backported to ep_readonly_guest by intent here. Swapping
the matrix unblocks the anonymizeAuthorSocket suite on Linux,
Windows, and the upgrade-from-latest-release workflow.
The runtime probe added in #7796 stays — it still catches any other
authenticate-hook plugin that rejects the test's plain-text
credentials (e.g. a future ep_hash_auth-style hashed-only plugin).
Reattribute its comment so future readers don't chase ep_hash_auth.
Closes#7795.
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Roll Node.js floor back to >= 24 (Active LTS)
Closes#7779.
#7779 originally proposed bumping past the Node 25 stop-gap to Node 26.
After re-checking the release schedule, the cleaner LTS target is
actually Node 24:
- Node 24 (Krypton) is currently in Active LTS, supported until ~May 2028.
- Node 25 hit end-of-life on April 10 2026 — the floor merged in
#7752 / #7749 / #7754 a day ago ships an already-EOL major.
- Node 26 was released May 5 2026 and does not enter Active LTS until
October 2026.
So this PR reverts the Node 25 ratchet from those three PRs and lands
on Node 24 — Etherpad's runtime floor stays on a supported LTS for the
next ~2 years.
Runtime / infra
- `package.json` + `src/package.json`: `engines.node` `>=25.0.0` -> `>=24.0.0`
- `bin/functions.sh`, `bin/installer.sh`, `bin/installer.ps1`:
`REQUIRED_NODE_MAJOR` 25 -> 24
- `Dockerfile`: `node:25-alpine` -> `node:24-alpine` (both stages).
Corepack-via-npm workaround is intentionally kept: it works on
Node 24 (which still ships corepack) and on Node 25+ (which doesn't),
so the same recipe survives the next LTS bump without churn. Comments
reworded accordingly.
- `snap/snapcraft.yaml`: pinned `NODE_VERSION` 25.9.0 -> 24.15.0; design
notes + corepack comment adjusted
- `packaging/nfpm.yaml`: `nodejs (>= 25)` -> `nodejs (>= 24)` in
top-level depends + deb/rpm overrides
- `packaging/bin/etherpad`: comment matches the new pin
- `packaging/README.md`: build prereqs + apt install snippet point at
`node_24.x`; the long-stale "engines.node floor is 20" line is fixed
while we're here
- `.github/workflows/*.yml`: setup-node `node-version` 25 -> 24 across
every workflow; backend / frontend-admin / upgrade matrices
`[25]` -> `[24]`
- `.github/workflows/deb-package.yml`: `NODE_MAJOR=25` + `node_25.x`
smoke-test installer -> 24
- `bin/plugins/lib/npmpublish.yml`: 25 -> 24 (template propagates to
the ~80 ether/* plugins via update-plugins workflow)
Docs
- `README.md`: install one-liner + Requirements -> Node.js >= 24
- `doc/npm-trusted-publishing.md`: runner requirement -> Node 24
- `doc/plugins.md` / `doc/plugins.adoc`: plugin metadata example
`engines.node` -> `">=24.0.0"`
@types/node is left at ^25.8.0 — newer type definitions cover Node 24
runtime fine and avoid an unnecessary lockfile churn.
Companion homepage one-liner change to follow on ether/ether.github.com.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* docs(plugins): example engines.node = ">=22.0.0", not core's floor
Plugin code is overwhelmingly ace-hook glue and rarely uses Node-version-
specific APIs, so plugin engines.node should reflect the plugin's own
requirements, not track core. Showing core's 24-floor in the example
encouraged plugin authors to blindly copy a tighter pin than necessary
and locked plugins out of being installable on older Etherpad/Node
deployments. Use the most-recent Node LTS that has actually reached EOL
(20 -> EOL April 2026) as the example floor, i.e. >=22.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The workspace root declares `engines.node: >=25.0.0` and
`engineStrict: true`, so anyone running Node 22 or 24 is hard-blocked
by pnpm at install time. Three places still referenced the old
floor and were testing / supporting a configuration no real user can
reach:
1. `.github/workflows/backend-tests.yml` Windows matrices (both
`withoutpluginsWindows` and `withpluginsWindows`) still had
`node: [22, 24, 25]`. The Linux jobs were already collapsed to
`[25]`; this collapses the Windows side to match. Drops 4
Windows CI cells per develop push (Node 22 × 2 + Node 24 × 2)
that were exercising an unsupported runtime.
2. `src/package.json` engines.node was `>=22.13.0`. Bumped to
`>=25.0.0` to match the workspace root. `pnpm` floor bumped
from `>=11.0.0` to `>=11.1.2` so the inner package agrees with
the root packageManager pin.
3. `.github/workflows/deb-package.yml` setup-node was pinned to
`node-version: '24'`. Every other setup-node call in the
workflows folder is already on 25; this brings the deb job in
line.
Side benefit: the Windows + Node 24 flake addressed in #7748 is now
moot for develop CI — Node 24 isn't tested at all. The `--exit`
mitigation and node-diagnostic-report capture remain in place on
Windows Node 25 as defence in depth in case the same native-crash
class shows up on a different Node line.
Verified locally:
- cd src && pnpm exec vitest run tests/backend-new/specs/backend-tests-flake-mitigation.test.ts
→ 3 passed (3). The mitigation test counts step blocks (4) and
Windows --exit invocations (2), not matrix dimensions, so the
contract is unchanged.
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* docs: bump documented Node.js minimum to 25
Etherpad is moving its supported Node.js floor to >= 25 (CI matrix is
already pinned to 25 across all workflows on the node25-corepack-pnpm11
work). Sync the user-facing documentation so the install instructions,
requirements section, and plugin metadata example all reflect the new
minimum instead of Node 22 / 12.17.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore: require Node.js >= 25 (engines, installers, Dockerfile, snap, CI)
#7747 added Node 25 *support* but left the floor at Node 22. This
commit completes the cutover so the runtime requirement matches the
documentation bumped in the previous commit.
- package.json: engines.node ">=22.13.0" → ">=25.0.0"
- bin/functions.sh, bin/installer.sh, bin/installer.ps1: REQUIRED_NODE
bumped to 25 (controls the error message users see when they invoke
the installer or pnpm scripts on an older Node)
- Dockerfile: base image node:22-alpine → node:25-alpine (×2). Corepack
comment updated: Node 25 no longer ships corepack at all, so we
install it from npm rather than refreshing a stale signing-key list
- snap/snapcraft.yaml: pinned NODE_VERSION 22.22.2 → 25.9.0 and the
surrounding design notes rewritten to reflect Node 25 instead of 22
- .github/workflows/*.yml: matrix dropped from [22, 24, 25] to just
[25] (anything older now fails engines anyway). Stale comments in
build-and-deploy-docs.yml referencing vite 8's 22.12 floor cleaned up
- bin/plugins/lib/npmpublish.yml: setup-node 22 → 25 so the plugin
template propagated to every ether/* plugin matches the new minimum
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(docker): install pnpm directly on Node 25 (no corepack)
node:25-alpine doesn't ship corepack but does pre-install yarn at
/usr/local/bin/yarn, so `npm install -g corepack@latest` fails with
EEXIST trying to register its yarn shim. Per #7747, end-users install
pnpm via plain `npm install -g pnpm` on Node 25 — use the same flow in
the Dockerfile (and remove the unused yarn binary so it doesn't sit on
PATH inside the image). Drops COREPACK_HOME and the related
issue-7687 cache-sharing tweak since there's no corepack shim to share.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Bumps the workflow Node version (PR matrix → [25], full push matrix
stays at [22, 24, 25]) and the pinned pnpm to 11.1.2 with a matching
`engines.pnpm` minimum. End-users install pnpm the same way they
always have (`npm install -g pnpm` works on Node 25 — only Corepack
was dropped from the official Node 25 distribution).
Also includes two workflow fixes that were entangled with the
Node-version edits in the same files:
- `upgrade-from-latest-release.yml` now actually checks out the
latest release tag instead of `ref: develop #FIXME`, so the job
finally exercises what its name implies.
- `installer-test.yml` resolves `ETHERPAD_REPO` / `ETHERPAD_BRANCH`
from the PR head when running on a fork, so the smoke test exercises
the PR branch rather than the base.
Verified end-to-end against `node:25-bookworm-slim` (no corepack):
`npm install -g pnpm` → `pnpm i` → `pnpm run build:etherpad` →
`pnpm run prod` boots and listens on 9001.
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* test(ci): kill Windows + Node 24 backend-test flake; capture native crashes
The Backend tests suite has a ~22% silent-failure rate on Windows + Node
24 specifically (Linux 22/24/25 ✓, Windows 22/25 ✓). Two prior PRs
instrumented the failure — common.ts handlers (#7663), then an unconditional
diagnostics.ts (#7665) — and confirmed it's a hard kill:
diagnostics.ts:23-27 documents the matrix, and every recurrence (run
25279692065, 25754938013, 25906496503) shows only `[diag +0ms]
diagnostics loaded`, no beforeExit / exit / unhandledRejection /
uncaughtException / signal handlers. Process dies 700–900 ms after the
last passing test, in a varying spec each time. That's a native crash in
V8 / libuv / the tsx loader, not anything reachable from JS.
This PR ships two independent attacks at the failure:
1. Mitigation — add --exit to the mocha command in src/package.json.
Mocha's default (--exit=false) waits for the event loop to drain
after tests complete. The hard kill happens during that drain or the
inter-spec transition. With --exit, mocha calls process.exit(failures)
directly once the run finishes, closing the cleanup-race window.
Linux/Windows-22/25 are green today, so the natural-drain path is
not surfacing real leaks worth preserving. Verified locally:
`cd src && pnpm test` -> 1121 passing, 0 failing, 23s.
2. Capture — set NODE_OPTIONS in each Backend tests step to
--report-on-fatalerror, --report-uncaught-exception,
--report-on-signal, --report-compact, plus
--report-directory=${{ github.workspace }}/node-report. If Node
crashes at the C++ level (segfault, V8 abort, libuv panic) the
runtime writes a JSON diagnostic report with the V8 stack, libuv
handle table, JS heap state, and OS info. A new "Upload Node
diagnostic reports on failure" step (actions/upload-artifact@v7,
`if: failure()`, `if-no-files-found: ignore`) uploads that directory
as an artifact per matrix cell — the data we have been unable to
capture from JS instrumentation alone.
If (1) eliminates the flake on the next push to develop, great. If not,
(2) finally gives us the crash dump and we can fix the root cause.
Touches all four Backend tests jobs (Linux × 2, Windows × 2). The
Windows steps now also set `shell: bash` so the same `mkdir -p ...`
line works under git-bash; the existing `working-directory: src` is
preserved. NODE_OPTIONS is scoped to the test step only, so the
existing vitest step is unchanged.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* test(ci): address Qodo review on PR #7748
Three findings from Qodo's review of the Windows + Node 24 flake fix:
1. (bug) "mocha --exit masks handle leaks". --exit removes the post-suite
event-loop drain that surfaces leaked timers / sockets — exactly the
class of regression that lowerCasePadIds.ts notes is otherwise visible.
Linux/local runs are currently green on natural drain, so dropping
that signal everywhere would silence real leaks to fix a Windows-only
flake.
Fix: scope --exit to Windows only.
- Remove --exit from src/package.json's "test" script (shared with
local dev + Linux CI; both keep natural-drain behaviour).
- Append `pnpm test -- --exit` in just the two Windows backend-test
steps so the mitigation only runs where the flake actually lives.
2. (observability) "diagnostics exit-matrix misleading". With --exit on
Windows, "only exit fires" becomes the EXPECTED pattern there, not a
sign of unexpected process.exit(). Update the matrix comment in
tests/backend/diagnostics.ts to spell out: clean drain on
Linux/local → beforeExit + exit; Windows under --exit → only exit;
"only exit" elsewhere still implies an unexpected process.exit
somewhere.
3. (rule violation) "no regression test for the mitigation". Repo
convention (see admin-i18n-source-lint.test.ts) is to pin
policy-bearing config with a source-lint spec so a future refactor
can't silently revert it.
Add src/tests/backend-new/specs/backend-tests-flake-mitigation.test.ts:
- Asserts every "Run the backend tests" step sets NODE_OPTIONS with
the report-on-fatalerror diag flags AND is followed by an Upload
Node diagnostic reports step (4 of each in the current matrix).
- Asserts exactly 2 Windows jobs invoke `pnpm test -- --exit`.
- Asserts the shared mocha "test" script in src/package.json does
NOT bake in --exit globally.
Verified locally:
- cd src && pnpm exec vitest run tests/backend-new/specs/backend-tests-flake-mitigation.test.ts
→ 3 passed (3).
- Stream.ts spec without --exit → 31 passing, diag prints
"beforeExit code=0" + "exit code=0" (clean drain restored).
- Existing admin-i18n-source-lint suite still passes.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(docker): clear most CVEs in published image — npm/pnpm/uuid + drop curl
Cuts published-image vulnerabilities from 18 (4H/13M/1L) across 8 packages
to 12 (2H/9M/1L) across 3 packages. The remaining three (curl/libcurl,
git, busybox) are all upstream Alpine 3.23 packages with "not fixed"
status — libcurl is pulled in transitively by git and cannot be
removed independently.
Changes:
- Provision pnpm via corepack instead of `npm install -g pnpm`, then
remove the bundled npm. The base image's npm@10.9.7 ships old
transitives (picomatch 4.0.3 → CVE-2026-33671/33672, brace-expansion
2.0.2 → CVE-2026-33750) that we don't otherwise need at runtime;
corepack handles pnpm directly without npm. Fixes 1H + 1M.
- Bump PnpmVersion 10.28.2 → 10.33.2 to align with the rest of the
workflow and pull in pnpm's patched bundled brace-expansion (5.0.5
vs 5.0.4). Fixes 1M.
- Add `uuid@<14.0.0` → `>=14.0.0` to pnpm.overrides
(GHSA-w5hq-g745-h8pq). Fixes 1M.
- Drop `curl` from the runtime apk add list and switch HEALTHCHECK to
wget (busybox built-in). curl was only invoked by the healthcheck and
by dev/CI scripts that don't run in the container. Removes the curl
CLI binary; libcurl remains as a git transitive dep, so the
`apk/alpine/curl` advisories scout reports against libcurl persist
but aren't reachable from any code we ship. As a side-effect this
also clears nghttp2 (CVE-2026-27135) which was a curl-CLI dep.
- Switch HEALTHCHECK URL from `localhost` to `127.0.0.1` — alpine/musl
resolves localhost to ::1 first and Etherpad only binds IPv4.
Verified locally: docker build → docker run → healthy → docker scout
cves shows 12 CVEs / 3 packages.
* fix(docker): refresh corepack before preparing pnpm (Qodo)
Node 22's bundled corepack ships a stale signing-key list and can reject
newer pnpm releases (nodejs/corepack#612), which would fail the image
build at `corepack prepare`. Mirror the snap/snapcraft.yaml workaround:
`npm install -g corepack@latest` before activating pnpm, in both
adminbuild and build stages. npm is still removed afterwards.
* docs(changelog): note docker image dropping curl/npm/npx (Qodo)
Address Qodo's "backwards-incompatible change without mitigation" rule
violations by documenting the removal in the 2.7.3 breaking-changes
section. Operators who exec into the container can apk add curl on
demand or use the busybox wget / pnpm already present.
* chore: pnpm
* chore: pnpm
* chore: pnpm
* chore: pnpm
* chore: pnpm
* chore: pnpm
* chore: pnpm
* chore: pnpm
* chore: pnpm
---------
Co-authored-by: SamTV12345 <40429738+samtv12345@users.noreply.github.com>
* ci: add ep_font_color and ep_hash_auth to plugin test matrix
These are the #12 and #14 most-installed Etherpad plugins on npm
(last 30d) and were the only top-15 plugins not exercised by the
withpluginsLinux / withpluginsWindows / Playwright with-plugins
jobs. Adding them broadens coverage of the plugin loader against
two real-world hooks: aceEditorCSS / aceAttribsToClasses
(ep_font_color) and authenticate / handleMessage (ep_hash_auth).
ep_hash_auth's authenticate hook is a no-op unless a Basic auth
header is sent and a matching settings.users[user].hash exists,
so it falls through cleanly with the default test settings.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* test(change_user_color): close users popup before opening chat
The "Own user color is shown when you enter a chat" spec leaves the
users popup open after picking a color, then calls showChat(). In the
with-plugins matrix the popup overlaps #chaticon and intercepts pointer
events, so the click in showChat() is retried until the 90s timeout
(× 5 retries ≈ 7m), failing both Firefox and Chrome with-plugins jobs.
Toggle the users button off and wait for popup-show to drop before
clicking the chat icon, matching the close pattern used in
a11y_dialogs.spec.ts.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
PRs now run a minimal test matrix; full matrix runs on push to develop.
Changes:
- Backend tests: PRs test on Node 24 only (Linux). Windows tests only
run on push to develop. Reduces from 12 to 2 jobs for PRs.
- Upgrade-from-latest-release: PRs test on Node 24 only (1 job vs 3).
- Frontend admin tests: PRs test on Node 24 only (1 job vs 3).
This reduces PR CI from ~25 jobs to ~10, preventing runner exhaustion
when multiple PRs are merged in succession. The full matrix (3 Node
versions × Linux + Windows) still runs on every push to develop.
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Pin plugins to last-known-good versions in backend tests
Pin ep_font_size@0.4.65, ep_headings2@0.2.76, ep_markdown@10.0.1
to the versions that passed on March 31. The newer versions cause
a template crash: Cannot read properties of undefined (reading
'indexOf') at pad.html:67 in toolbar.menu().
This will help narrow down which plugin update is the culprit.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Unpin ep_markdown, 1.0.8 is latest and code-identical to 10.0.1
Only ep_font_size@0.4.65 and ep_headings2@0.2.76 remain pinned to
narrow down which plugin update causes the toolbar template crash.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Use pnpm instead of gnpm for plugin install in backend tests
gnpm ignores version pins — it reports installing the pinned version
but the plugin loader picks up the latest from its store. Switching
to pnpm for the plugin install step so version pins actually work.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Use gnpm exec pnpm for plugin install to bypass gnpm caching
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Remove ep_hash_auth from backend test plugin list
ep_hash_auth blocks unauthenticated requests, causing 28 backend tests
to get 500 Internal Server Error when accessing pads. The tests don't
provide credentials, so any auth plugin will break them.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Fix ESM/CJS interop for Settings module and harden toolbar
Plugins use require('ep_etherpad-lite/node/utils/Settings') (CJS) but
Settings.ts uses export default (ESM). With tsx, CJS require puts the
default export under .default, so settings.toolbar is undefined and
ep_font_size crashes with "Cannot read properties of undefined
(reading 'indexOf')" when rendering pad.html.
Two fixes:
- Settings.ts: add property getters on module.exports so CJS consumers
can access settings properties directly
- toolbar.ts: guard against undefined buttons array to prevent crashes
if Settings interop doesn't propagate through gnpm's plugin_packages
Tested locally: 735 passing, 0 failing with all plugins.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: use gnpm
* chore: fixed pnpm
* chore: fixed gnpm
* chore: fixed jq
* chore: use 0.0.7
* chore: use flag for building
* chore: fixed all backend tests
* chore: continue with porting things
* chore: fixed path
* chore: fixed backend tests
* chore: upgraded all to gnpm
* chore: updated to gnpm 0.0.9
* chore: echo current env with debug logs
* chore: install with frozen lockfile
* chore: use 0.0.10
* chore: use 0.0.11
* chore: use 0.0.12 globally
* chore: reworked handleRelease workflow to be up to date and depend on other workflows
* Added vitest tests.
* Added Settings tests to vitest - not working
* Added attributes and attributemap to vitest.
* Added more tests.
* Also run the vitest tests.
* Also run withoutPlugins
* Fixed pnpm lock
* Converted tests to typescript.
* Run all tests.
* Fixed tests.
* Removed cypress from every installation.
* Use cache for libreoffice.
* Fixed cypress install.
* Fixed cypress install.
* fix bin folder and workflows as far its possible
cleanup of dockerfile
changed paths of scripts
add lock file
fix working directory for workflows
fix windows bin
fix travis (is travis used anyway?)
fix package refs
remove pnpm-lock file in root as these conflicts with the docker volume setup
optimize comments
use install again
refactor prod image call to run
fix --workspace can only be used inside a workspace
correct comment
try fix pipeline
try fix pipeline for upgrade-from-latest-release
install all deps
smaller adjustments
save
update dockerfile
remove workspace command
fix run test command
start repair latest release workflow
start repair latest release workflow
start repair latest release workflow
further repairs
* remove test plugin from docker compose