Files
feuerwerk/tools/lib/plugins.mjs
T
2026-09-17 20:15:51 +02:00

67 lines
2.3 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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);
}
}