mirror of
https://github.com/Nystik-gh/ignis.git
synced 2026-06-17 04:35:53 +00:00
load bundled plugins via virtual-plugin loader
This commit is contained in:
@@ -12,4 +12,3 @@ data
|
||||
tmp
|
||||
**/dist
|
||||
packages/bridge/main.js
|
||||
apps/ignis-server/server/plugins/*/obsidian/main.js
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -4,6 +4,5 @@ investigation/
|
||||
vaults/
|
||||
packages/*/dist/
|
||||
packages/bridge/main.js
|
||||
apps/ignis-server/server/plugins/*/obsidian/main.js
|
||||
demo-vaults/
|
||||
data/
|
||||
|
||||
@@ -58,7 +58,7 @@ COPY packages/bridge/styles.css ./packages/bridge/
|
||||
COPY --from=build /app/packages/shim/dist/shim-loader.js ./packages/shim/dist/shim-loader.js
|
||||
COPY --from=build /app/packages/ui/dist/ignis-ui.js ./packages/ui/dist/ignis-ui.js
|
||||
COPY --from=build /app/packages/bridge/main.js ./packages/bridge/main.js
|
||||
COPY --from=build /app/apps/ignis-server/server/plugins/headless-sync/obsidian/main.js ./apps/ignis-server/server/plugins/headless-sync/obsidian/main.js
|
||||
COPY --from=build /app/apps/ignis-server/server/plugins/headless-sync/obsidian/dist/ ./apps/ignis-server/server/plugins/headless-sync/obsidian/dist/
|
||||
|
||||
RUN chmod +x /app/apps/ignis-server/scripts/entrypoint.sh
|
||||
|
||||
|
||||
@@ -13,7 +13,11 @@ const {
|
||||
BRIDGE_PLUGIN_ID,
|
||||
migratePluginsFromAllVaults,
|
||||
} = require("./bridge-plugin");
|
||||
const { initPlugins, shutdownPlugins } = require("./plugin-system/manager");
|
||||
const {
|
||||
initPlugins,
|
||||
shutdownPlugins,
|
||||
getBundledPluginDirs,
|
||||
} = require("./plugin-system/manager");
|
||||
const pluginRoutes = require("./routes/plugins");
|
||||
writeCoalescer.configure({ writeCoalesceMs: config.writeCoalesceMs });
|
||||
const { flushAll } = writeCoalescer;
|
||||
@@ -173,8 +177,19 @@ const server = app.listen(config.port, async () => {
|
||||
console.log(`[ignis] Vault root: ${config.vaultRoot}`);
|
||||
console.log(`[ignis] Vaults: ${Object.keys(config.vaults).join(", ")}`);
|
||||
|
||||
await migratePluginsFromAllVaults(config.vaultRoot, [BRIDGE_PLUGIN_ID]);
|
||||
await initPlugins({ app, config, wss, watcher });
|
||||
|
||||
const bundledPluginDirs = getBundledPluginDirs();
|
||||
|
||||
for (const { distDir } of bundledPluginDirs) {
|
||||
app.use(express.static(distDir));
|
||||
}
|
||||
|
||||
await migratePluginsFromAllVaults(config.vaultRoot, [
|
||||
BRIDGE_PLUGIN_ID,
|
||||
...bundledPluginDirs.map((d) => d.bundledPluginId),
|
||||
]);
|
||||
|
||||
bootstrapRoutes
|
||||
.warmUp()
|
||||
.catch((e) => console.warn("[bootstrap] warm-up error:", e.message));
|
||||
|
||||
@@ -40,17 +40,16 @@ function discoverPlugins(pluginsDir) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let bundledPluginId = null;
|
||||
let bundledManifest = null;
|
||||
|
||||
if (plugin.obsidianPlugin) {
|
||||
try {
|
||||
const manifest = JSON.parse(
|
||||
bundledManifest = JSON.parse(
|
||||
fs.readFileSync(
|
||||
path.join(plugin.obsidianPlugin, "manifest.json"),
|
||||
"utf-8",
|
||||
),
|
||||
);
|
||||
bundledPluginId = manifest.id;
|
||||
} catch {
|
||||
// No valid bundled plugin manifest
|
||||
}
|
||||
@@ -61,7 +60,8 @@ function discoverPlugins(pluginsDir) {
|
||||
name: plugin.name,
|
||||
description: plugin.description || "",
|
||||
obsidianPlugin: plugin.obsidianPlugin || null,
|
||||
bundledPluginId,
|
||||
bundledPluginId: bundledManifest ? bundledManifest.id : null,
|
||||
bundledManifest,
|
||||
module: plugin,
|
||||
});
|
||||
|
||||
|
||||
@@ -3,10 +3,6 @@ const path = require("path");
|
||||
const express = require("express");
|
||||
const { discoverPlugins } = require("./discovery");
|
||||
const configStore = require("./config-store");
|
||||
const {
|
||||
installObsidianPlugin,
|
||||
removeObsidianPlugin,
|
||||
} = require("./obsidian-plugin");
|
||||
|
||||
let discoveredPlugins = new Map();
|
||||
const loadedPlugins = new Map();
|
||||
@@ -50,18 +46,6 @@ async function initPlugins(ctx) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const discovered = discoveredPlugins.get(pluginId);
|
||||
|
||||
if (discovered.obsidianPlugin) {
|
||||
try {
|
||||
await installObsidianPlugin(discovered.obsidianPlugin, vaultPath);
|
||||
} catch (e) {
|
||||
console.error(
|
||||
`[plugins] Failed to verify bundled plugin for ${pluginId} in ${vaultId}: ${e.message}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const loaded = loadedPlugins.get(pluginId);
|
||||
|
||||
if (loaded?.module?.onVaultEnabled) {
|
||||
@@ -182,25 +166,6 @@ async function enablePluginForVault(pluginId, vaultId) {
|
||||
await loadPlugin(pluginId);
|
||||
}
|
||||
|
||||
if (discovered.obsidianPlugin) {
|
||||
try {
|
||||
const result = await installObsidianPlugin(
|
||||
discovered.obsidianPlugin,
|
||||
vaultPath,
|
||||
);
|
||||
|
||||
if (result.installed) {
|
||||
console.log(
|
||||
`[plugins] Installed bundled Obsidian plugin for ${pluginId} in vault: ${vaultId}`,
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(
|
||||
`[plugins] Failed to install bundled plugin for ${pluginId}: ${e.message}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const loaded = loadedPlugins.get(pluginId);
|
||||
|
||||
if (loaded?.module?.onVaultEnabled) {
|
||||
@@ -227,25 +192,6 @@ async function disablePluginForVault(pluginId, vaultId) {
|
||||
await loaded.module.onVaultDisabled(vaultId, vaultPath);
|
||||
}
|
||||
|
||||
if (discovered.obsidianPlugin) {
|
||||
try {
|
||||
const result = await removeObsidianPlugin(
|
||||
discovered.obsidianPlugin,
|
||||
vaultPath,
|
||||
);
|
||||
|
||||
if (result.removed) {
|
||||
console.log(
|
||||
`[plugins] Removed bundled Obsidian plugin for ${pluginId} from vault: ${vaultId}`,
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(
|
||||
`[plugins] Failed to remove bundled plugin for ${pluginId}: ${e.message}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const enabledVaults = configStore.getEnabledVaults(pluginConfig, pluginId);
|
||||
const updated = enabledVaults.filter((id) => id !== vaultId);
|
||||
configStore.setEnabledVaults(pluginConfig, pluginId, updated);
|
||||
@@ -256,6 +202,47 @@ async function disablePluginForVault(pluginId, vaultId) {
|
||||
}
|
||||
}
|
||||
|
||||
function getBundledPluginDirs() {
|
||||
const dirs = [];
|
||||
|
||||
for (const [, discovered] of discoveredPlugins) {
|
||||
if (discovered.obsidianPlugin && discovered.bundledPluginId) {
|
||||
dirs.push({
|
||||
bundledPluginId: discovered.bundledPluginId,
|
||||
distDir: path.join(discovered.obsidianPlugin, "dist"),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return dirs;
|
||||
}
|
||||
|
||||
function getVirtualPluginsForVault(vaultId, version) {
|
||||
const v = version ? `?v=${version}` : "";
|
||||
const result = [];
|
||||
|
||||
for (const [pluginId, discovered] of discoveredPlugins) {
|
||||
if (!discovered.obsidianPlugin || !discovered.bundledPluginId) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const enabledVaults = configStore.getEnabledVaults(pluginConfig, pluginId);
|
||||
|
||||
if (!enabledVaults.includes(vaultId)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
result.push({
|
||||
id: discovered.bundledPluginId,
|
||||
scriptUrl: `/${discovered.bundledPluginId}.js${v}`,
|
||||
cssUrl: `/${discovered.bundledPluginId}.css${v}`,
|
||||
manifest: discovered.bundledManifest,
|
||||
});
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function getDiscoveredPlugins() {
|
||||
const result = [];
|
||||
|
||||
@@ -280,4 +267,6 @@ module.exports = {
|
||||
enablePluginForVault,
|
||||
disablePluginForVault,
|
||||
getDiscoveredPlugins,
|
||||
getBundledPluginDirs,
|
||||
getVirtualPluginsForVault,
|
||||
};
|
||||
|
||||
7
apps/ignis-server/server/routes/bootstrap.js
vendored
7
apps/ignis-server/server/routes/bootstrap.js
vendored
@@ -9,7 +9,11 @@ const fsp = fs.promises;
|
||||
const path = require("path");
|
||||
const zlib = require("zlib");
|
||||
const config = require("../config");
|
||||
const { getDiscoveredPlugins } = require("../plugin-system/manager");
|
||||
const {
|
||||
getDiscoveredPlugins,
|
||||
getVirtualPluginsForVault,
|
||||
} = require("../plugin-system/manager");
|
||||
const { getVersion } = require("../version");
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
@@ -135,6 +139,7 @@ async function buildEntry(vaultId) {
|
||||
tree,
|
||||
// In demo mode, hide server-side plugins from the client.
|
||||
plugins: config.demoMode ? [] : getDiscoveredPlugins(),
|
||||
virtualPlugins: getVirtualPluginsForVault(vaultId, getVersion()),
|
||||
};
|
||||
|
||||
const jsonBuf = Buffer.from(JSON.stringify(response));
|
||||
|
||||
59
build.js
59
build.js
@@ -1,6 +1,17 @@
|
||||
const esbuild = require("esbuild");
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
|
||||
const headlessSyncDir = path.join(
|
||||
__dirname,
|
||||
"apps",
|
||||
"ignis-server",
|
||||
"server",
|
||||
"plugins",
|
||||
"headless-sync",
|
||||
"obsidian",
|
||||
);
|
||||
|
||||
Promise.all([
|
||||
// Build shim-loader.js (delegated to packages/shim)
|
||||
require("./packages/shim/build.js"),
|
||||
@@ -9,35 +20,21 @@ Promise.all([
|
||||
require("./packages/ui/build.js"),
|
||||
|
||||
// Build headless-sync bundled plugin
|
||||
esbuild.build({
|
||||
entryPoints: [
|
||||
path.join(
|
||||
__dirname,
|
||||
"apps",
|
||||
"ignis-server",
|
||||
"server",
|
||||
"plugins",
|
||||
"headless-sync",
|
||||
"obsidian",
|
||||
"src",
|
||||
"main.js",
|
||||
),
|
||||
],
|
||||
bundle: true,
|
||||
outfile: path.join(
|
||||
__dirname,
|
||||
"apps",
|
||||
"ignis-server",
|
||||
"server",
|
||||
"plugins",
|
||||
"headless-sync",
|
||||
"obsidian",
|
||||
"main.js",
|
||||
),
|
||||
format: "cjs",
|
||||
platform: "browser",
|
||||
target: ["chrome90"],
|
||||
external: ["obsidian", "fs"], //using fs shim
|
||||
logLevel: "info",
|
||||
}),
|
||||
esbuild
|
||||
.build({
|
||||
entryPoints: [path.join(headlessSyncDir, "src", "main.js")],
|
||||
bundle: true,
|
||||
outfile: path.join(headlessSyncDir, "dist", "ignis-headless-sync.js"),
|
||||
format: "cjs",
|
||||
platform: "browser",
|
||||
target: ["chrome90"],
|
||||
external: ["obsidian", "fs"],
|
||||
logLevel: "info",
|
||||
})
|
||||
.then(() => {
|
||||
fs.copyFileSync(
|
||||
path.join(headlessSyncDir, "styles.css"),
|
||||
path.join(headlessSyncDir, "dist", "ignis-headless-sync.css"),
|
||||
);
|
||||
}),
|
||||
]).catch(() => process.exit(1));
|
||||
|
||||
@@ -232,6 +232,7 @@ export function initialize() {
|
||||
autoTrustDemoVaults(bootstrap.vaultList);
|
||||
applyTree(bootstrap.tree);
|
||||
applyCoreSyncGuard(bootstrap.plugins);
|
||||
window.__ignisVirtualPlugins = bootstrap.virtualPlugins || [];
|
||||
|
||||
// Race the indexer: batch-fetch text content into ContentCache so
|
||||
// Obsidian's startup indexing reads hit the cache instead of the network.
|
||||
|
||||
@@ -4,7 +4,10 @@ import { installCssOverrides } from "./css-overrides.js";
|
||||
import { initialize } from "./init.js";
|
||||
import { fsShim } from "./fs/index.js";
|
||||
import { registerUI } from "./ui-registry.js";
|
||||
import { extractObsidianModule } from "./virtual-plugin-loader.js";
|
||||
import {
|
||||
extractObsidianModule,
|
||||
loadVirtualPlugin,
|
||||
} from "./virtual-plugin-loader.js";
|
||||
|
||||
// __IGNIS_VERSION__ is replaced at build time from package.json.
|
||||
window.__ignis = { version: __IGNIS_VERSION__ };
|
||||
@@ -48,6 +51,15 @@ extractObsidianModule()
|
||||
const bridge = new IgnisBridgePlugin(window.app, BRIDGE_MANIFEST);
|
||||
await bridge.onload();
|
||||
console.log("[ignis] bridge loaded");
|
||||
|
||||
for (const vp of window.__ignisVirtualPlugins || []) {
|
||||
try {
|
||||
await loadVirtualPlugin(vp);
|
||||
console.log(`[ignis] virtual plugin loaded: ${vp.id}`);
|
||||
} catch (e) {
|
||||
console.error(`[ignis] virtual plugin load failed: ${vp.id}`, e);
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch((e) => console.error("[ignis] bridge load failed:", e));
|
||||
|
||||
|
||||
@@ -103,3 +103,42 @@ export async function extractObsidianModule() {
|
||||
console.log("[ignis] obsidian module captured");
|
||||
return captured;
|
||||
}
|
||||
|
||||
export async function loadVirtualPlugin(entry) {
|
||||
if (entry.cssUrl) {
|
||||
const link = document.createElement("link");
|
||||
link.rel = "stylesheet";
|
||||
link.href = entry.cssUrl;
|
||||
link.setAttribute("data-ignis-virtual-plugin", entry.id);
|
||||
document.head.appendChild(link);
|
||||
}
|
||||
|
||||
const res = await fetch(entry.scriptUrl);
|
||||
|
||||
if (!res.ok) {
|
||||
throw new Error(
|
||||
`fetch ${entry.scriptUrl} -> ${res.status} ${res.statusText}`,
|
||||
);
|
||||
}
|
||||
|
||||
const src =
|
||||
(await res.text()) + `\n//# sourceURL=ignis-virtual/${entry.id}.js`;
|
||||
|
||||
const module = { exports: {} };
|
||||
const localRequire = (name) =>
|
||||
name === "obsidian" ? window.__obsidian : window.require(name);
|
||||
|
||||
new Function("module", "exports", "require", src)(
|
||||
module,
|
||||
module.exports,
|
||||
localRequire,
|
||||
);
|
||||
|
||||
const PluginClass = module.exports.default || module.exports;
|
||||
const instance = new PluginClass(window.app, entry.manifest);
|
||||
|
||||
await instance.onload();
|
||||
|
||||
window.__ignis.plugins = window.__ignis.plugins || {};
|
||||
window.__ignis.plugins[entry.id] = { instance, manifest: entry.manifest };
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user