69 lines
2.8 KiB
JavaScript
69 lines
2.8 KiB
JavaScript
// site-sync: Saltcorn plugin for one-way DESIRED-STATE synchronization.
|
|
//
|
|
// Snapshot a source site's current design state (via Saltcorn's own
|
|
// backup.create_pack_json), diff it against a target, preview a plan, and apply
|
|
// the difference so the target converges to the source. No edit log. User data is
|
|
// protected by per-table data modes; the users table is hard-locked.
|
|
|
|
const { PLUGIN_NAME, PLUGIN_VERSION } = require("./lib/constants");
|
|
const { createAllTables } = require("./lib/schema");
|
|
const { initEnvIfMissing } = require("./lib/env");
|
|
const { createDashboardWorkflow } = require("./lib/common/dashboardWorkflow");
|
|
const { ADMIN_URLS } = require("./lib/render");
|
|
const { routes } = require("./lib/routes");
|
|
const { API_PREFIX } = require("./lib/transport");
|
|
const mcpTools = require("./lib/mcpTools");
|
|
|
|
|
|
const log = (msg) => {
|
|
// eslint-disable-next-line no-console
|
|
console.log(`[${PLUGIN_NAME}] ${msg}`);
|
|
};
|
|
|
|
|
|
// Register the machine-API prefix in Saltcorn's disable_csrf_routes so peers can
|
|
// POST to it with the custom Content-Type. Belt-and-suspenders: the api routes
|
|
// ALSO carry noCsrf:true on the route object.
|
|
const ensureCsrfBypass = async () => {
|
|
try {
|
|
const { getState } = require("@saltcorn/data/db/state");
|
|
const current = getState().getConfig("disable_csrf_routes", "");
|
|
const entries = current.split(",").map((s) => s.trim()).filter(Boolean);
|
|
if (!entries.includes(API_PREFIX)) {
|
|
entries.push(API_PREFIX);
|
|
await getState().setConfig("disable_csrf_routes", entries.join(","));
|
|
}
|
|
} catch (e) {
|
|
// eslint-disable-next-line no-console
|
|
console.error(`[${PLUGIN_NAME}] failed to register csrf bypass:`, e);
|
|
}
|
|
};
|
|
|
|
|
|
const onLoad = async (cfg) => {
|
|
try {
|
|
await createAllTables();
|
|
const env = await initEnvIfMissing();
|
|
await ensureCsrfBypass();
|
|
log(`v${PLUGIN_VERSION} loaded env_id=${env.env_id}`);
|
|
} catch (err) {
|
|
// eslint-disable-next-line no-console
|
|
console.error(`[${PLUGIN_NAME}] onLoad failed:`, err);
|
|
throw err;
|
|
}
|
|
};
|
|
|
|
|
|
module.exports = {
|
|
sc_plugin_api_version: 1,
|
|
plugin_name: PLUGIN_NAME,
|
|
// The plugin gear (Settings -> Plugins) bounces straight to the dashboard.
|
|
configuration_workflow: () => createDashboardWorkflow({ name: PLUGIN_NAME, url: ADMIN_URLS.DASHBOARD }),
|
|
onLoad: onLoad,
|
|
// With configuration_workflow present, Saltcorn invokes capability keys as
|
|
// (cfg)=>value (state.ts withCfg), so routes must be a function, not the array.
|
|
routes: () => routes,
|
|
// Tools for the saltcorn-mcp server (its docs/pluginTools.md): an MCP client
|
|
// drives site-sync as an admin drives the UI (lib/mcpTools.js).
|
|
mcp_tools: () => mcpTools.tools()
|
|
};
|