* feat(admin): show "requires newer Etherpad" when installing incompatible plugin (#7763) Old admins on out-of-date Etherpad installations get no feedback when they click Install on a plugin that needs a newer core. live-plugin-manager doesn't honor engines.node, and the admin UI dropped the error payload that adminplugins.ts already emits. This wires up an end-to-end signal: - pluginEngineCheck.ts: pure helper comparing a plugin's engines.node range against process.version, with a stable error code (PLUGIN_REQUIRES_NEWER_ETHERPAD) and a message that avoids leaking the Node-version implementation detail. Unparseable ranges fall through as compatible so the preflight is opportunistic, not a gate. 8 unit tests cover the happy + edge paths. - installer.ts: install() now best-effort fetches the published plugin's engines.node from npmjs.org, runs the preflight, and short-circuits with the typed error before invoking live-plugin-manager. Also wraps the body in try/catch so the error reaches the socket callback (it was silently dropped on every install failure today, including network errors). - HomePage.tsx: surfaces finished:install.error as a toast, using a dedicated i18n key when the code is PLUGIN_REQUIRES_NEWER_ETHERPAD and a generic fallback otherwise. - en.json: two new strings, parameterized by {{plugin}} and {{error}}. The message admins see is intentionally about Etherpad, not Node — upgrading Etherpad pulls the Node requirement along with it. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(plugins): don't restart server when every install in the batch failed Qodo PR review on #7771 caught a side effect introduced earlier in this PR. Before this PR, install() never invoked its callback on error, so a failed install left the task counter inflated and onAllTasksFinished() never ran — masking, but not fixing, the bug. Once install() correctly propagates errors to its cb, the counter hits zero on failure too, and onAllTasksFinished() fires hooks.aCallAll('restartServer'). A no-op preflight rejection (EngineIncompatibleError) would then disconnect every connected pad. Fix: extract wrapTaskCb + task state into InstallerTaskQueue and track whether at least one task in the current batch succeeded. Only fire the "all finished" side effect when something actually changed. Failed-only batches do nothing. Seven unit tests cover the matrix: single success, single failure (the regression), mixed batch (still restarts), all-failed batch, batch reset, null cb, two-task drain. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(plugins): time-bound the engines preflight registry fetch Qodo PR review on #7771 flagged that fetchPluginEnginesNode awaits fetch() with no timeout. A stalled DNS lookup or hung connection to registry.npmjs.org would block install() forever — the finished:install socket event would never fire and the admin UI would stay spinning with no error to surface. Wrap the fetch in AbortSignal.timeout(5000). On any failure (network, HTTP error, abort) fetchPluginEnginesNode returns undefined, which the preflight then treats as "no engines info → compatible," so a slow registry never blocks an install that would otherwise succeed. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|---|---|---|
| .. | ||
| public | ||
| scripts | ||
| src | ||
| .eslintrc.cjs | ||
| .gitignore | ||
| index.html | ||
| package.json | ||
| README.md | ||
| tsconfig.json | ||
| tsconfig.node.json | ||
| vite.config.ts | ||
Admin UI
Vite + React 19 single-page app served at /admin. Talks to the backend over
socket.io for the existing settings / plugins / pads pages, and (when
endpoints are added to the OpenAPI spec) over a typed REST client.
Scripts
| Script | What it does |
|---|---|
pnpm dev |
gen:api + Vite dev server (expects backend on :9001). |
pnpm gen:api |
Regenerates src/api/{schema.d.ts,version.ts} from the OpenAPI spec. |
pnpm build |
gen:api + tsc + vite build. |
pnpm build-copy |
Same, but writes into ../src/templates/admin. |
pnpm test |
gen:api + smoke tests for the API client wiring. |
pnpm lint |
ESLint. |
Typed API client
The admin uses openapi-typescript to generate types from
src/node/hooks/express/openapi.ts, openapi-fetch for typed requests, and
openapi-react-query for TanStack Query bindings.
Generated files
admin/src/api/schema.d.ts and admin/src/api/version.ts are generated by
gen:api and gitignored — never commit them. They are produced by:
pnpm --filter admin gen:api
admin/scripts/gen-api.mjs loads src/node/hooks/express/openapi.ts, calls
generateDefinitionForVersion for the latest API version, pipes the JSON
through openapi-typescript to produce schema.d.ts, and emits a runtime
constant LATEST_API_VERSION (read from info.version in the spec) to
version.ts so client.ts can build the right /api/<version>/ baseUrl.
gen:api runs as the first step of dev, build, build-copy, and
test, so a fresh checkout produces the generated files automatically when
any of those scripts is invoked. After modifying any of the following, the
next pnpm <dev|build|test> will refresh the generated files; you can also
run gen:api directly:
src/node/hooks/express/openapi.tssrc/node/handler/APIHandler.ts(changes tolatestApiVersion)- the resource definitions referenced by
openapi.ts
Using the client
import { $api } from './api/client';
const SettingsPanel = () => {
const { data } = $api.useQuery('get', '/admin/settings'); // example
return <pre>{JSON.stringify(data, null, 2)}</pre>;
};
The admin endpoints are not yet present in the OpenAPI spec — this client is in place to support upcoming work (see issue #7638 follow-up). For now, it is exercised only by the smoke test.