* 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>
* 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>
* ci(docs): build on PRs and pin Node 22 (Qodo follow-up to #7640)
Qodo flagged two reliability gaps on the oxc-minify fix that landed in
#7640:
1. The Deploy Docs to GitHub Pages workflow only ran on push to
develop, so a PR that broke `pnpm run docs:build` was not caught
until after merge — exactly how the dead-link regression in #7546
escaped. Add a pull_request trigger that runs the same build but
skips the deploy/upload steps via `if: github.event_name ==
'push'`. Also include the workflow file itself in the path filter
so changes to it are exercised on PR.
2. oxc-minify@0.128.0 requires Node ^20.19.0 || >=22.12.0, but the
workflow did not pin Node and the repo declared engines.node
>=22.0.0 with engineStrict: true — a runner image (or local dev)
on Node 22.0–22.11 would refuse to install. Pin Node 22 in the
docs workflow with actions/setup-node@v6 (matching the rest of
CI), and bump engines.node to >=22.12.0 so the project's
engineStrict gate matches the actual minimum.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* ci(docs): split build and deploy so PR runs do not hit pages env protection
The previous attempt put `if: github.event_name == 'push'` on individual
deploy steps but kept the single job's `environment: github-pages`
binding. Environment protection rules reject any non-develop ref
(including `refs/pull/N/merge`), so the runner failed the entire job
at creation time before any step could execute:
Branch "refs/pull/7645/merge" is not allowed to deploy to
github-pages due to environment protection rules.
Split into two jobs: `build` runs on every trigger (PR + push) and
uploads the artifact only on push, `deploy` depends on `build`,
runs only on push, and is the only job bound to the github-pages
environment. Standard GHA pages-deploy pattern; PR builds never
attempt to enter the protected environment.
* docs: align Node minimum references with bumped engines.node (Qodo round 2 on #7645)
Qodo flagged that engines.node moved from >=22.0.0 to >=22.12.0 in
this PR but documentation still claimed the old requirement. Sync the
three places that pinned a specific minimum:
- README.md installation requirements (>= 22 → >= 22.12)
- doc/npm-trusted-publishing.md publish prerequisites
(>=22.0.0 → >=22.12.0, with oxc-minify cited as the driver)
- CHANGELOG.md 2.7.3 breaking-changes entry (22 → 22.12, with the
same oxc-minify justification)
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix: setup-trusted-publishers.sh works with real npm trust CLI
Two issues found when running the script for the first time after #7490:
1. `npm trust github --file` wants ONLY the workflow filename basename
(e.g. `test-and-release.yml`), not the full
`.github/workflows/test-and-release.yml` path. npm errors out with
"GitHub Actions workflow must be just a file not a path" otherwise.
Constants updated.
2. `npm trust github` requires 2FA on accounts that have it enabled,
and there is no way to disable that requirement. Add a `--otp <code>`
pass-through flag and forward it to every call so a maintainer can
batch-process multiple packages within a single TOTP window.
Documented the limitation in the script header.
Also reword the call site so the npm command line is built without
shell-string round-tripping (passing $CMD through `$( $CMD )` was
unrelated to this bug but was bad practice).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: setup-trusted-publishers.sh recognizes 409 as already-configured
When --skip-existing is set, treat HTTP 409 Conflict from
POST /-/package/<name>/trust as 'already configured' so re-runs of
the bulk script don't fail on packages that were configured in a
previous run.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* test: cover setup-trusted-publishers.sh, harden against set -e, document --otp
Addresses qodo review on #7491:
- Add backend regression test that shims `npm` on PATH and asserts
`--file` is given the workflow basename (never a path), `--otp` is
forwarded to every `npm trust github` call when supplied, and the
loop survives a non-zero exit so `--skip-existing` can absorb 409
Conflict responses from the registry.
- Wrap the `npm trust github` invocation in `set +e` / `set -e`. The
`if configure_one` already shields the function from errexit in
practice, but a future refactor moving the call site out of an `if`
would silently reintroduce the bug — the explicit shim makes intent
obvious and survives such refactors.
- Document `--otp` and the 2FA / TOTP-expiry workflow in
doc/npm-trusted-publishing.md so maintainers don't follow the docs
and hit EOTP.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: migrate npm publish to OIDC trusted publishing (#7401)
Replaces NPM_TOKEN-based publishing with npm Trusted Publishing over
OIDC for both etherpad-lite core and the shared plugin publish
template. Tokens no longer expire every 90 days; each publish
authenticates via a short-lived OIDC token issued to the GitHub
Actions runner.
Changes:
- bin/plugins/lib/npmpublish.yml: the reusable workflow propagated to
every ether/ep_* plugin via the update-plugins cron. Now bumps Node
to 22, upgrades npm to >=11.5.1, declares id-token: write, drops
NODE_AUTH_TOKEN, and calls `npm publish --provenance --access public`
directly (not via pnpm/gnpm wrappers, which obscure the npm CLI
version requirement).
- bin/plugins/lib/test-and-release.yml: the parent workflow that calls
npmpublish.yml as a reusable workflow. Top-level and release-job
permissions now grant id-token: write so the OIDC token can flow
into the called workflow.
- .github/workflows/releaseEtherpad.yml: core's own publish workflow
for the ep_etherpad package. Same OIDC migration; keeps the gnpm
install + rename steps but switches the final publish to npm.
- doc/npm-trusted-publishing.md: explains how trusted publishing
works, the one-time per-package setup that has to happen on
npmjs.com, requirements (Node 22.14+, npm 11.5.1+, cloud runners),
and common errors.
The next update-plugins cron run will propagate the new template to
every plugin. Once that lands and the trusted publisher is configured
on npmjs.com per package, the NPM_TOKEN secret can be removed.
Closes#7401
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: add bin/setup-trusted-publishers.sh for bulk OIDC config (#7401)
Adds a script that automates the per-package trusted-publisher setup
that previously had to be done by clicking through npmjs.com once for
each of the 80+ ep_* plugins. Uses the new `npm trust github` CLI
(npm >= 11.5.1) so the whole org can be configured in one shot:
npm login
bin/setup-trusted-publishers.sh
The script:
- Discovers every non-archived ether/ep_* repo via `gh repo list`
- Maps ep_etherpad to the etherpad-lite repo / releaseEtherpad.yml,
and every plugin to its same-named repo / test-and-release.yml
- Runs `npm trust github <pkg> --repository <org>/<repo> --file
<workflow> --yes` for each package
- Supports --dry-run, --packages <comma list>, and --skip-existing
- Verifies npm >= 11.5.1 and that the user is logged in before doing
anything destructive
Doc updated to feature the script as the recommended setup path,
with manual web-UI steps kept as a fallback.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: don't bump CI Node version to 22 for OIDC
npm 11.5.1 (the version that ships trusted publishing) actually
requires '^20.17.0 || >=22.9.0', not Node 22.14+. The npm docs
recommend Node 22 but only because that's what bundles a recent
enough npm — installing 'npm@latest' on top of Node 20.17+ works
just as well.
The repo already requires Node >= 20.0.0 in engines.node and the
setup-node@v6 'version: 20' input resolves to the latest 20.x
(currently 20.20+), which satisfies npm 11's range. Revert the CI
publish workflows from node-version: 22 back to 20 so this PR does
not raise the Node bar at all.
Doc updated to explain the actual constraint.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>