Plugins are switched on and off in a Plugins tab, the server keeps the state in data/plugins/plugins.json, and switched-off plugins are never delivered (#114)
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
57a97ba94f
commit
c17b2b5426
17 files changed
+994
-129
No files matched your search
@@ -0,0 +1,66 @@
|
||||
import {
|
||||
chownSync,
|
||||
copyFileSync,
|
||||
existsSync,
|
||||
mkdirSync,
|
||||
readdirSync,
|
||||
readFileSync,
|
||||
statSync,
|
||||
writeFileSync,
|
||||
} from 'node:fs';
|
||||
import { resolve } from 'node:path';
|
||||
|
||||
/**
|
||||
* Puts the plugins that ship with the repository (`plugins/*.js`) where the editor loads them from,
|
||||
* `data/plugins/`, and switches them on in `plugins.json` (#114) – a plugin put there by hand stays
|
||||
* off until someone switches it on in the Plugins tab. Used by the build and by the e2e seeder.
|
||||
*
|
||||
* `force` is the seeder's: the checks need the demo plugin on, whatever a person did with the
|
||||
* switch. The build only switches on what the state does not name yet, so a plugin switched off
|
||||
* on the server stays off across deployments.
|
||||
*
|
||||
* The API writes `plugins.json` as the web server's user. When this runs as root (the build on the
|
||||
* host), the folder and what it wrote are handed to the owner of `data/`, which is that user.
|
||||
*/
|
||||
export function publishPlugins(root, { force = false } = {}) {
|
||||
const target = resolve(root, 'data/plugins');
|
||||
mkdirSync(target, { recursive: true });
|
||||
const written = [target];
|
||||
const shipped = readdirSync(resolve(root, 'plugins')).filter((f) => f.endsWith('.js'));
|
||||
for (const file of shipped) {
|
||||
copyFileSync(resolve(root, 'plugins', file), resolve(target, file));
|
||||
written.push(resolve(target, file));
|
||||
}
|
||||
|
||||
const statePath = resolve(target, 'plugins.json');
|
||||
let state = {};
|
||||
if (existsSync(statePath)) {
|
||||
try {
|
||||
state = JSON.parse(readFileSync(statePath, 'utf8'));
|
||||
} catch {
|
||||
console.warn(`${statePath} is not readable JSON – left as it is, no plugin switched on`);
|
||||
state = null;
|
||||
}
|
||||
}
|
||||
if (state && typeof state === 'object') {
|
||||
let changed = false;
|
||||
for (const file of shipped) {
|
||||
if (!force && Object.hasOwn(state, file)) continue;
|
||||
if (state[file]?.active === true) continue;
|
||||
state[file] = { active: true };
|
||||
changed = true;
|
||||
}
|
||||
if (changed) {
|
||||
const sorted = Object.fromEntries(
|
||||
Object.entries(state).sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0)),
|
||||
);
|
||||
writeFileSync(statePath, `${JSON.stringify(sorted, null, 4)}\n`);
|
||||
}
|
||||
written.push(statePath);
|
||||
}
|
||||
|
||||
if (process.getuid?.() === 0) {
|
||||
const { uid, gid } = statSync(resolve(root, 'data'));
|
||||
for (const path of written) if (existsSync(path)) chownSync(path, uid, gid);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user