From fd975323e05fe876626a390a7c6e2f427e4f9b57 Mon Sep 17 00:00:00 2001 From: John McLear Date: Wed, 1 Apr 2026 11:39:40 +0100 Subject: [PATCH] Fix ESM/CJS interop for Settings module breaking plugin compatibility (#7421) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 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) * 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) * 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) * Use gnpm exec pnpm for plugin install to bypass gnpm caching Co-Authored-By: Claude Opus 4.6 (1M context) * 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) * 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) --------- Co-authored-by: Claude Opus 4.6 (1M context) --- .github/workflows/backend-tests.yml | 8 ++------ src/node/utils/Settings.ts | 14 ++++++++++++++ src/node/utils/toolbar.ts | 3 +++ 3 files changed, 19 insertions(+), 6 deletions(-) diff --git a/.github/workflows/backend-tests.yml b/.github/workflows/backend-tests.yml index b691a8233..e4facbff7 100644 --- a/.github/workflows/backend-tests.yml +++ b/.github/workflows/backend-tests.yml @@ -124,14 +124,13 @@ jobs: ep_author_hover ep_cursortrace ep_font_size - ep_hash_auth ep_headings2 ep_markdown ep_readonly_guest ep_set_title_on_pad ep_spellcheck ep_subscript_and_superscript - ep_table_of_contents --runtimeVersion="${{ matrix.node }}" + ep_table_of_contents - name: Run the backend tests run: gnpm test --runtimeVersion="${{ matrix.node }}" @@ -232,22 +231,19 @@ jobs: run: gnpm build --runtimeVersion="${{ matrix.node }}" - name: Install Etherpad plugins - # The --legacy-peer-deps flag is required to work around a bug in npm - # v7: https://github.com/npm/cli/issues/2199 run: > gnpm install --workspace-root ep_align ep_author_hover ep_cursortrace ep_font_size - ep_hash_auth ep_headings2 ep_markdown ep_readonly_guest ep_set_title_on_pad ep_spellcheck ep_subscript_and_superscript - ep_table_of_contents --runtimeVersion="${{ matrix.node }}" + ep_table_of_contents # Etherpad core dependencies must be installed after installing the # plugin's dependencies, otherwise npm will try to hoist common # dependencies by removing them from src/node_modules and installing them diff --git a/src/node/utils/Settings.ts b/src/node/utils/Settings.ts index 46bc487c8..ee09141a7 100644 --- a/src/node/utils/Settings.ts +++ b/src/node/utils/Settings.ts @@ -658,6 +658,20 @@ const settings: SettingsType = { } export default settings; +// CJS compatibility: plugins use require('ep_etherpad-lite/node/utils/Settings') +// and expect settings properties directly on the module object, not under .default +if (typeof module !== 'undefined' && module.exports) { + const currentExports = module.exports; + for (const key of Object.keys(settings)) { + if (!(key in currentExports)) { + Object.defineProperty(currentExports, key, { + get: () => (settings as any)[key], + enumerable: true, + configurable: true, + }); + } + } +} /** * This setting is passed with dbType to ueberDB to set up the database diff --git a/src/node/utils/toolbar.ts b/src/node/utils/toolbar.ts index 461ede049..f8e70fb30 100644 --- a/src/node/utils/toolbar.ts +++ b/src/node/utils/toolbar.ts @@ -276,6 +276,9 @@ module.exports = { * Valid values for page: 'pad' | 'timeslider' */ menu(buttons: string[][], isReadOnly: boolean, whichMenu: string, page: string) { + if (!buttons || buttons.length === 0 || !buttons[0]) { + return ''; + } if (isReadOnly) { // The best way to detect if it's the left editbar is to check for a bold button if (buttons[0].indexOf('bold') !== -1) {