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); } }