The plugin publish workflow ran `git push --follow-tags` after `pnpm
version patch`. `--follow-tags` is non-atomic per ref: if a concurrent
publish run won the race, the branch fast-forward would be rejected
but the tag push would still land — leaving a dangling `vN+1` tag with
no matching version-bump commit on the branch. Every subsequent push
would then fail forever with `npm error fatal: tag 'vN+1' already
exists`, because `pnpm version patch` would re-derive the same tag
name from the unchanged `package.json`.
On 2026-04-08, a single churn day (badge fixes + Dependabot merges
firing back-to-back) put ~46 plugins into this state simultaneously.
Recovery required hand-bumping `package.json` past the dangling tag
on every affected repo, twice (a second wave appeared after the first
sweep finished, racing the next wave of publishes).
Fix: use `git push --atomic origin <branch> <tag>` so the branch
update and the tag update succeed or fail as a single server-side
transaction. A rejected branch push now also rejects the tag push,
the run aborts cleanly, and the next workflow tick can retry against
the up-to-date refs without leaving any orphaned tags.
Also derive the new tag name from `package.json` after the bump
(rather than parsing pnpm version's stdout, which has historically
varied) and pass it explicitly into the push.
Adds a backend regression test that asserts the workflow file uses
`--atomic`, does not contain a literal `git push --follow-tags`
command (ignoring the historical comment), and includes both the
branch ref and the freshly-bumped tag in the atomic push. The test
gates against accidental reverts.
This file is the source of truth that `bin/plugins/checkPlugin.ts`
propagates into every `ether/ep_*` plugin's `.github/workflows/`, so
the next `update-plugins` cron tick will roll the fix out across all
plugins automatically.
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>
* fix: re-apply retries: 0
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Enable globstar in backend-tests template for recursive test discovery
Without shopt -s globstar, bash ** doesn't recurse into subdirectories,
causing mocha to miss test files in paths like specs/api/exportHTML.ts.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Run pnpm update on plugins during autofix to bump all dependencies
to their latest compatible versions, reducing dependabot noise and
keeping plugins current.
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Prod mode enables rate limiting which causes frontend tests to fail
silently. Dev mode disables rate limiting for testing.
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Revert plugin workflow template changes
Reverts the following commits to replace with a single clean fix:
- e97e203d7 Fix backend-tests find pattern for versioned plugin paths
- 45fe8a310 Fix backend-tests find path for plugin test discovery
- 892c52ba2 Fix plugin backend-tests workflow pnpm 10 symlink error
- 7484d9ea6 Update deprecated GitHub Actions in plugin workflow templates
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Modernize plugin workflow templates for pnpm 10 and new plugin paths
- Bump actions/checkout v3 → v4, cache-apt-pkgs-action v1.4.2 → v1.6.0
- Replace pnpm link --global with pnpm run plugins i --path (fixes
pnpm 10 "symlink path same as target" error)
- Fix backend test discovery: plugins install to src/plugin_packages/
via live-plugin-manager, not node_modules/
- Run mocha directly from src/ against node_modules/ep_* symlinks
so tests resolve correctly
Tested and verified on ep_table_of_contents.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
live-plugin-manager installs to src/plugin_packages/ep_name@version/,
not src/plugin_packages/ep_name/. Update the glob to match the
versioned directory format.
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
plugins i --path installs to src/plugin_packages/, not node_modules/.
Update the find command to look in the correct location so backend
tests are actually discovered and run.
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Remove redundant pnpm link --global steps that conflict with
pnpm run plugins i --path on pnpm 10, causing "Symlink path is
the same as the target path" errors. The plugins i command handles
all linking internally via LinkInstaller.
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Bump actions/checkout v3 → v4 and awalsh128/cache-apt-pkgs-action v1.4.2 → v1.6.0
to fix CI failures caused by deprecated actions/upload-artifact v3 dependency.
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* 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
Also add symlinks from the old `bin/` and `tests/` locations to avoid
breaking scripts and other tools.
Motivations:
* Scripts and tests no longer have to do dubious things like:
require('ep_etherpad-lite/node_modules/foo')
to access packages installed as dependencies in
`src/package.json`.
* Plugins can access the backend test helper library in a non-hacky
way:
require('ep_etherpad-lite/tests/backend/common')
* We can delete the top-level `package.json` without breaking our
ability to lint the files in `bin/` and `tests/`.
Deleting the top-level `package.json` has downsides: It will cause
`npm` to print warnings whenever plugins are installed, npm will
no longer be able to enforce a plugin's peer dependency on
ep_etherpad-lite, and npm will keep deleting the
`node_modules/ep_etherpad-lite` symlink that points to `../src`.
But there are significant upsides to deleting the top-level
`package.json`: It will drastically speed up plugin installation
because `npm` doesn't have to recursively walk the dependencies in
`src/package.json`. Also, deleting the top-level `package.json`
avoids npm's horrible dependency hoisting behavior (where it moves
stuff from `src/node_modules/` to the top-level `node_modules/`
directory). Dependency hoisting causes numerous mysterious
problems such as silent failures in `npm outdated` and `npm
update`. Dependency hoisting also breaks plugins that do:
require('ep_etherpad-lite/node_modules/foo')
* bugfix, lint and refactor all bin scripts
* for squash: throw Error(message) rather than log(message); throw Error()
* for squash: Exit non-0 on unhandled Promise rejection
Many of the recent lint changes have converted normal functions to
async functions, and an error thrown in an async function does not
cause Node.js to exit by default.
* for squash: fix `require()` paths
* for squash: remove erroneous `Object.keys()` call
* for squash: fix missing `continue` statements
* for squash: Fix HTTP method for deleteSession
* for squash: delete erroneous throw
Throw is only for errors, not successful completion.
* for squash: redo migrateDirtyDBtoRealDB.js to fix async bugs
* for squash: fix erroneous use of `for..of`
* for squash: Add line break between statements
* for squash: put closing paren on same line as last arg
* for squash: Move `log()` back up where it was
to minimize the diff to develop
* for squash: indentation fixes
* for squash: typo fix
* for squash: wrap long lines
* for squash: use `util.callbackify` to silence promise/no-callback-in-promise warning
* for squash: use double quotes to improve readability
Co-authored-by: Richard Hansen <rhansen@rhansen.org>
This makes it possible to check plugins that were installed by
symlinking into `node_modules/` like this:
git clone git@github.com:ether/etherpad-lite.git
git clone git@github.com:ether/ep_example.git
cd etherpad-lite
npm i ep_example@file:../ep_example
node ./bin/checkPlugin.js ep_example
Rather than check for modifications and untracked files in one
command, use two commands: one for modifications and one for untracked
files. This makes the error messages easier to understand, and it
allows us to include `git status`-like output in the modifications
error message.