67 lines
2.3 KiB
JavaScript
67 lines
2.3 KiB
JavaScript
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);
|
||
}
|
||
}
|