refactor bridge plugin loading

This commit is contained in:
Nystik
2026-03-26 22:27:50 +01:00
parent f55a015b64
commit 80bf7436d9
6 changed files with 216 additions and 141 deletions

7
.prettierrc Normal file
View File

@@ -0,0 +1,7 @@
{
"semi": true,
"singleQuote": false,
"tabWidth": 2,
"trailingComma": "all",
"printWidth": 80
}

70
server/bridge-plugin.js Normal file
View File

@@ -0,0 +1,70 @@
const fs = require("fs");
const path = require("path");
const {
installObsidianPlugin,
isObsidianPluginInstalled,
} = require("./plugin-system/obsidian-plugin");
const BRIDGE_PLUGIN_ID = "ignis-bridge";
const BRIDGE_PLUGIN_DIR = path.join(__dirname, "..", "plugin");
// .ignis metadata helpers
async function getIgnisMeta(vaultPath) {
const metaFile = path.join(vaultPath, ".ignis", "meta.json");
try {
const content = await fs.promises.readFile(metaFile, "utf-8");
return JSON.parse(content);
} catch {
return {};
}
}
async function setIgnisMeta(vaultPath, data) {
const ignisDir = path.join(vaultPath, ".ignis");
const metaFile = path.join(ignisDir, "meta.json");
await fs.promises.mkdir(ignisDir, { recursive: true });
await fs.promises.writeFile(metaFile, JSON.stringify(data, null, 2));
}
// Bridge plugin install/check
async function isBridgePluginInstalled(vaultPath) {
return isObsidianPluginInstalled(BRIDGE_PLUGIN_ID, vaultPath);
}
async function installBridgePlugin(vaultPath) {
const result = await installObsidianPlugin(BRIDGE_PLUGIN_DIR, vaultPath);
return result.installed;
}
async function updateBridgePluginInAllVaults(vaultRoot) {
if (!(await fs.promises.stat(vaultRoot).catch(() => null))) {
return;
}
const entries = await fs.promises.readdir(vaultRoot, { withFileTypes: true });
for (const entry of entries) {
if (!entry.isDirectory()) {
continue;
}
const vaultPath = path.join(vaultRoot, entry.name);
const installed = await installBridgePlugin(vaultPath);
if (installed) {
console.log(`[ignis] Installed bridge plugin in vault: ${entry.name}`);
}
}
}
module.exports = {
installBridgePlugin,
updateBridgePluginInAllVaults,
isBridgePluginInstalled,
getIgnisMeta,
setIgnisMeta,
};

View File

@@ -3,7 +3,8 @@ const path = require("path");
const compression = require("compression");
const config = require("./config");
const { setupWebSocket } = require("./ws");
const { installPluginInAllVaults } = require("./install-plugin");
const watcher = require("./watcher");
const { updateBridgePluginInAllVaults } = require("./bridge-plugin");
const ANSI_RED = "\x1b[31m";
const ANSI_YELLOW = "\x1b[33m";
@@ -97,7 +98,26 @@ const server = app.listen(config.port, async () => {
console.log(`[ignis] Vault root: ${config.vaultRoot}`);
console.log(`[ignis] Vaults: ${Object.keys(config.vaults).join(", ")}`);
await installPluginInAllVaults(config.vaultRoot);
await updateBridgePluginInAllVaults(config.vaultRoot);
});
setupWebSocket(server);
const wss = setupWebSocket(server);
async function gracefulShutdown(signal) {
console.log(`\n[ignis] Received ${signal}, shutting down gracefully...`);
await shutdownPlugins();
server.close(() => {
console.log("[ignis] Server closed");
process.exit(0);
});
setTimeout(() => {
console.error("[ignis] Forced shutdown after timeout");
process.exit(1);
}, 10000);
}
process.on("SIGTERM", () => gracefulShutdown("SIGTERM"));
process.on("SIGINT", () => gracefulShutdown("SIGINT"));

View File

@@ -1,109 +0,0 @@
const fs = require("fs");
const path = require("path");
// .ignis metadata helpers
async function getIgnisMeta(vaultPath) {
const ignisDir = path.join(vaultPath, ".ignis");
const metaFile = path.join(ignisDir, "meta.json");
try {
const content = await fs.promises.readFile(metaFile, "utf-8");
return JSON.parse(content);
} catch (e) {
return {};
}
}
async function setIgnisMeta(vaultPath, data) {
const ignisDir = path.join(vaultPath, ".ignis");
const metaFile = path.join(ignisDir, "meta.json");
await fs.promises.mkdir(ignisDir, { recursive: true });
await fs.promises.writeFile(metaFile, JSON.stringify(data, null, 2));
}
async function checkPluginInstalled(vaultPath) {
const pluginDir = path.join(
vaultPath,
".obsidian",
"plugins",
"ignis-bridge",
);
const manifestPath = path.join(pluginDir, "manifest.json");
const mainPath = path.join(pluginDir, "main.js");
try {
await fs.promises.access(manifestPath);
await fs.promises.access(mainPath);
return true;
} catch (e) {
return false;
}
}
async function installPluginInVault(vaultPath) {
const obsidianDir = path.join(vaultPath, ".obsidian");
const pluginDir = path.join(obsidianDir, "plugins", "ignis-bridge");
if (!(await fs.promises.stat(obsidianDir).catch(() => null))) {
return false;
}
await fs.promises.mkdir(pluginDir, { recursive: true });
const pluginSrcDir = path.join(__dirname, "..", "plugin");
await fs.promises.copyFile(
path.join(pluginSrcDir, "manifest.json"),
path.join(pluginDir, "manifest.json"),
);
await fs.promises.copyFile(
path.join(pluginSrcDir, "main.js"),
path.join(pluginDir, "main.js"),
);
const pluginsConfig = path.join(obsidianDir, "community-plugins.json");
let plugins = [];
if (await fs.promises.stat(pluginsConfig).catch(() => null)) {
try {
plugins = JSON.parse(await fs.promises.readFile(pluginsConfig, "utf8"));
} catch (e) {
plugins = [];
}
}
if (!plugins.includes("ignis-bridge")) {
plugins.push("ignis-bridge");
await fs.promises.writeFile(pluginsConfig, JSON.stringify(plugins));
return true;
}
return false;
}
async function installPluginInAllVaults(vaultRoot) {
if (!(await fs.promises.stat(vaultRoot).catch(() => null))) {
return;
}
const entries = await fs.promises.readdir(vaultRoot, { withFileTypes: true });
for (const entry of entries) {
if (entry.isDirectory()) {
const vaultPath = path.join(vaultRoot, entry.name);
const installed = await installPluginInVault(vaultPath);
if (installed) {
console.log(`[ignis] Installed plugin in vault: ${entry.name}`);
}
}
}
}
module.exports = {
installPluginInVault,
installPluginInAllVaults,
getIgnisMeta,
setIgnisMeta,
checkPluginInstalled,
};

View File

@@ -0,0 +1,110 @@
const fs = require("fs");
const path = require("path");
async function readManifestId(sourceDir) {
const manifestPath = path.join(sourceDir, "manifest.json");
const content = await fs.promises.readFile(manifestPath, "utf-8");
const manifest = JSON.parse(content);
if (!manifest.id) {
throw new Error(`No "id" in manifest.json at ${sourceDir}`);
}
return manifest.id;
}
async function installObsidianPlugin(sourceDir, vaultPath) {
const pluginId = await readManifestId(sourceDir);
const obsidianDir = path.join(vaultPath, ".obsidian");
try {
await fs.promises.access(obsidianDir);
} catch {
return { installed: false, pluginId };
}
const targetDir = path.join(obsidianDir, "plugins", pluginId);
await fs.promises.mkdir(targetDir, { recursive: true });
const files = await fs.promises.readdir(sourceDir);
for (const file of files) {
const srcPath = path.join(sourceDir, file);
const stat = await fs.promises.stat(srcPath);
if (stat.isFile()) {
await fs.promises.copyFile(srcPath, path.join(targetDir, file));
}
}
const pluginsConfigFile = path.join(obsidianDir, "community-plugins.json");
let plugins = [];
try {
const content = await fs.promises.readFile(pluginsConfigFile, "utf-8");
plugins = JSON.parse(content);
} catch {
plugins = [];
}
if (!plugins.includes(pluginId)) {
plugins.push(pluginId);
await fs.promises.writeFile(pluginsConfigFile, JSON.stringify(plugins));
}
return { installed: true, pluginId };
}
async function removeObsidianPlugin(sourceDir, vaultPath) {
const pluginId = await readManifestId(sourceDir);
const obsidianDir = path.join(vaultPath, ".obsidian");
try {
await fs.promises.access(obsidianDir);
} catch {
return { removed: false, pluginId };
}
const targetDir = path.join(obsidianDir, "plugins", pluginId);
try {
await fs.promises.rm(targetDir, { recursive: true });
} catch {
// Already gone
}
const pluginsConfigFile = path.join(obsidianDir, "community-plugins.json");
try {
const content = await fs.promises.readFile(pluginsConfigFile, "utf-8");
let plugins = JSON.parse(content);
plugins = plugins.filter((id) => id !== pluginId);
await fs.promises.writeFile(pluginsConfigFile, JSON.stringify(plugins));
} catch {
// No config file or parse error - nothing to remove from
}
return { removed: true, pluginId };
}
async function isObsidianPluginInstalled(pluginId, vaultPath) {
const pluginDir = path.join(vaultPath, ".obsidian", "plugins", pluginId);
const manifestPath = path.join(pluginDir, "manifest.json");
const mainPath = path.join(pluginDir, "main.js");
try {
await fs.promises.access(manifestPath);
await fs.promises.access(mainPath);
return true;
} catch {
return false;
}
}
module.exports = {
installObsidianPlugin,
removeObsidianPlugin,
isObsidianPluginInstalled,
};

View File

@@ -3,11 +3,11 @@ const fs = require("fs");
const config = require("../config");
const path = require("path");
const {
checkPluginInstalled,
isBridgePluginInstalled,
getIgnisMeta,
setIgnisMeta,
installPluginInVault,
} = require("../install-plugin");
installBridgePlugin,
} = require("../bridge-plugin");
const router = express.Router();
@@ -33,7 +33,7 @@ router.get("/info", async (req, res) => {
return res.status(404).json({ error: "Vault not found", id: vaultId });
}
const pluginInstalled = await checkPluginInstalled(vaultPath);
const pluginInstalled = await isBridgePluginInstalled(vaultPath);
const ignisMeta = await getIgnisMeta(vaultPath);
res.json({
@@ -65,30 +65,7 @@ router.post("/create", async (req, res) => {
recursive: false,
});
// Install ignis-bridge plugin
const pluginDir = path.join(
vaultPath,
".obsidian",
"plugins",
"ignis-bridge",
);
await fs.promises.mkdir(pluginDir, { recursive: true });
const pluginSrcDir = path.join(__dirname, "..", "..", "plugin");
await fs.promises.copyFile(
path.join(pluginSrcDir, "manifest.json"),
path.join(pluginDir, "manifest.json"),
);
await fs.promises.copyFile(
path.join(pluginSrcDir, "main.js"),
path.join(pluginDir, "main.js"),
);
// Enable the plugin
await fs.promises.writeFile(
path.join(vaultPath, ".obsidian", "community-plugins.json"),
JSON.stringify(["ignis-bridge"]),
);
await installBridgePlugin(vaultPath);
config.refreshVaults();
@@ -182,7 +159,7 @@ router.post("/install-plugin", async (req, res) => {
return res.json({ ok: true, prompted: true });
} else {
// User wants to install the plugin
const installed = await installPluginInVault(vaultPath);
const installed = await installBridgePlugin(vaultPath);
meta.pluginPrompted = true;
await setIgnisMeta(vaultPath, meta);