Summary
#7421 added property getters on module.exports so that plugins using
require('ep_etherpad-lite/node/utils/Settings') can read settings directly.
That works for built-in settings such as toolbar, but not for plugin-specific
settings hashes (ep_* keys) supplied via settings.json.
The mirror is built from a snapshot of Object.keys(settings) taken at
module-evaluation time, which happens before reloadSettings() parses
settings.json and adds the plugin keys. A plugin reading its own configuration
the documented way therefore always sees undefined.
Observed on 3.3.3, and the same ordering is present in 2.7.3.
Root cause
In src/node/utils/Settings.ts:
- line ~912 — the CJS compatibility block from Fix ESM/CJS interop for Settings module breaking plugin compatibility #7421:
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, set, ... });
}
}
}
- line ~1464 — reloadSettings() is called, which runs storeSettings() and
adds ep_* keys from settings.json.
Object.keys(settings) at line 912 contains only the defaults declared in the
module. ep_comments_page (or any ep_* hash) is not a default — it appears 550
lines later — so no getter is ever defined for it. The getters themselves are
live, so existing keys track correctly; the problem is purely which keys get
mirrored.
Reproduction
- Add a plugin settings hash to settings.json:
{ "ep_comments_page": { "allowReadonlyComments": true } }
- In any plugin, log what the documented accessor returns:
const mod = require('ep_etherpad-lite/node/utils/Settings');
console.log(mod.ep_comments_page); // undefined
console.log(mod.default && mod.default.ep_comments_page); // the object
console.log(Object.keys(mod).filter(k => k.startsWith('ep_'))); // []
console.log(Object.keys(mod.default).filter(k => k.startsWith('ep_'))); // ['ep_comments_page']
Actual probe output from a stock 3.3.3 container with the above settings.json:
{"moduleType":"object","hasDefault":true,
"default_ep_comments_page":{"allowReadonlyComments":true,"displayCommentAsIcon":true},
"argsSettings_ep":{"allowReadonlyComments":true,"displayCommentAsIcon":true},
"ep_keys_on_module":[],
"ep_keys_on_default":["ep_comments_page"]}
No Unknown Setting warning is logged — storeSettings() accepts ep_* keys
correctly. The value is loaded; it just isn't reachable through the CJS mirror.
The same happens via the EP__ep_comments_page__allowReadonlyComments=true
environment variable, so it isn't specific to how the setting is supplied.
Impact
Any plugin that reads its own configuration with
require('ep_etherpad-lite/node/utils/Settings').ep_myplugin silently sees
undefined, so every configurable option of that plugin falls back to its
default with no error or warning. This is the pattern #7421 was intended to keep
working.
Concretely: ep_comments_page's allowReadonlyComments and
displayCommentAsIcon cannot be enabled at all on a stock install.
Suggested fix
Either move the CJS mirror block to after reloadSettings(), or replace the
key-by-key snapshot with a Proxy that forwards property access to settings,
so keys added later are covered without depending on evaluation order.
Workarounds that do work today, for reference: read .default (or use
.default || mod), or take args.settings from the loadSettings hook.
Summary
#7421 added property getters on module.exports so that plugins using
require('ep_etherpad-lite/node/utils/Settings') can read settings directly.
That works for built-in settings such as toolbar, but not for plugin-specific
settings hashes (ep_* keys) supplied via settings.json.
The mirror is built from a snapshot of Object.keys(settings) taken at
module-evaluation time, which happens before reloadSettings() parses
settings.json and adds the plugin keys. A plugin reading its own configuration
the documented way therefore always sees undefined.
Observed on 3.3.3, and the same ordering is present in 2.7.3.
Root cause
In src/node/utils/Settings.ts:
adds ep_* keys from settings.json.
Object.keys(settings) at line 912 contains only the defaults declared in the
module. ep_comments_page (or any ep_* hash) is not a default — it appears 550
lines later — so no getter is ever defined for it. The getters themselves are
live, so existing keys track correctly; the problem is purely which keys get
mirrored.
Reproduction
{ "ep_comments_page": { "allowReadonlyComments": true } }Actual probe output from a stock 3.3.3 container with the above settings.json:
{"moduleType":"object","hasDefault":true, "default_ep_comments_page":{"allowReadonlyComments":true,"displayCommentAsIcon":true}, "argsSettings_ep":{"allowReadonlyComments":true,"displayCommentAsIcon":true}, "ep_keys_on_module":[], "ep_keys_on_default":["ep_comments_page"]}No Unknown Setting warning is logged — storeSettings() accepts ep_* keys
correctly. The value is loaded; it just isn't reachable through the CJS mirror.
The same happens via the EP__ep_comments_page__allowReadonlyComments=true
environment variable, so it isn't specific to how the setting is supplied.
Impact
Any plugin that reads its own configuration with
require('ep_etherpad-lite/node/utils/Settings').ep_myplugin silently sees
undefined, so every configurable option of that plugin falls back to its
default with no error or warning. This is the pattern #7421 was intended to keep
working.
Concretely: ep_comments_page's allowReadonlyComments and
displayCommentAsIcon cannot be enabled at all on a stock install.
Suggested fix
Either move the CJS mirror block to after reloadSettings(), or replace the
key-by-key snapshot with a Proxy that forwards property access to settings,
so keys added later are covered without depending on evaluation order.
Workarounds that do work today, for reference: read .default (or use
.default || mod), or take args.settings from the loadSettings hook.