mirror of
https://github.com/ether/etherpad-lite.git
synced 2026-05-05 20:26:49 +02:00
* fix(plugins): updatePlugins.sh actually updates installed plugins (#6670) bin/updatePlugins.sh detected outdated plugins by running `pnpm --filter ep_etherpad-lite outdated --depth=0`, but installed plugins are not registered in src/package.json — bin/plugins.ts adds them via linkInstaller.installPlugin which writes to src/plugin_packages/.versions/<name>@<version>/ and tracks the result in var/installed_plugins.json. pnpm has no view of them, so `outdated` returns empty and the script always reported "All plugins are up-to-date" even when newer versions existed on the registry. PR #7468 fixed npm→pnpm and install→update but kept the same broken detection mechanism, which is why the issue stayed open after that PR landed. Read the plugin list from var/installed_plugins.json instead, then re-invoke linkInstaller.installPlugin(name) for each entry. Calling the installer without a version pin resolves the registry-latest and overwrites the existing pinned copy, so an outdated plugin is brought to head while plugins already at latest are no-ops apart from the pnpm cache hit. Add an `update`/`up` action to bin/plugins.ts so users can also run `pnpm run plugins update` directly, mirroring the existing install/remove/list actions. updatePlugins.sh becomes a one-line wrapper for backwards compatibility. Reproduction (verified): pnpm run install-plugins ep_markdown@11.0.5 # latest is 11.0.18 ./bin/updatePlugins.sh # → 11.0.18 Edge cases tested: no plugins installed, missing installed_plugins.json, already-at-latest re-run. Closes #6670. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(plugins): validate ep_ prefix and dedupe + add regression test Qodo flagged two issues on the original update() addition: 1. Security — update() trusted every name in var/installed_plugins.json, so a corrupted or hand-edited manifest could coerce the script into installing arbitrary npm packages. pluginfw/plugins.getPackages already gates on the ep_ prefix; mirror that gate here. 2. Reliability — no automated regression test, so a future refactor could silently bring back the broken behaviour. Extract the safe-name filter to filterUpdatablePluginNames in bin/commonPlugins.ts (pure, side-effect-free, prefix configurable, also de-duplicates repeats so a duplicated entry installs once). Use it from plugins.ts update(). Add src/tests/backend/specs/filterUpdatablePluginNames.ts covering: keep prefixed names, drop ep_etherpad-lite, reject non-prefixed entries, de-dupe repeats, tolerate missing/null/non-string name fields, empty input, custom prefix. Manually verified end-to-end on a live install: an installed_plugins.json containing ep_markdown@11.0.5, a duplicate ep_markdown, and a "malicious-package" entry runs `Updating plugins to latest from registry: ep_markdown` (only) and ep_markdown ends up at 11.0.18 — the bad entries are silently filtered out. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
38 lines
1.6 KiB
TypeScript
38 lines
1.6 KiB
TypeScript
import {PackageData} from "ep_etherpad-lite/node/types/PackageInfo";
|
|
import {writeFileSync} from "fs";
|
|
import {installedPluginsPath} from "ep_etherpad-lite/static/js/pluginfw/installer";
|
|
const pluginsModule = require('ep_etherpad-lite/static/js/pluginfw/plugins');
|
|
|
|
// Pure helper used by `pnpm run plugins update` to whittle the contents of
|
|
// var/installed_plugins.json down to names safe to re-install. Mirrors the
|
|
// gate inside pluginfw/plugins.getPackages: only entries that start with the
|
|
// plugin prefix (ep_) are real Etherpad plugins; ep_etherpad-lite is the
|
|
// vendored core, never installed via the plugin path. De-duplicates so a
|
|
// corrupted manifest with repeated entries triggers one install per name.
|
|
// Exported and kept side-effect-free so backend tests can exercise it.
|
|
export const filterUpdatablePluginNames = (
|
|
entries: ReadonlyArray<{name?: unknown} | null | undefined>,
|
|
prefix: string = pluginsModule.prefix as string,
|
|
): string[] => {
|
|
const names = entries
|
|
.map((e) => (e == null ? undefined : e.name))
|
|
.filter(
|
|
(n): n is string =>
|
|
typeof n === 'string' && n.startsWith(prefix) && n !== 'ep_etherpad-lite',
|
|
);
|
|
return Array.from(new Set(names));
|
|
};
|
|
|
|
export const persistInstalledPlugins = async () => {
|
|
const plugins:PackageData[] = []
|
|
const installedPlugins = {plugins: plugins};
|
|
for (const pkg of Object.values(await pluginsModule.getPackages()) as PackageData[]) {
|
|
installedPlugins.plugins.push({
|
|
name: pkg.name,
|
|
version: pkg.version,
|
|
});
|
|
}
|
|
installedPlugins.plugins = [...new Set(installedPlugins.plugins)];
|
|
writeFileSync(installedPluginsPath, JSON.stringify(installedPlugins));
|
|
};
|