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
|
tmp
|
||||||
**/dist
|
**/dist
|
||||||
packages/bridge/main.js
|
packages/bridge/main.js
|
||||||
apps/ignis-server/server/plugins/*/obsidian/main.js
|
|
||||||
|
|||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -4,6 +4,5 @@ investigation/
|
|||||||
vaults/
|
vaults/
|
||||||
packages/*/dist/
|
packages/*/dist/
|
||||||
packages/bridge/main.js
|
packages/bridge/main.js
|
||||||
apps/ignis-server/server/plugins/*/obsidian/main.js
|
|
||||||
demo-vaults/
|
demo-vaults/
|
||||||
data/
|
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/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/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/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
|
RUN chmod +x /app/apps/ignis-server/scripts/entrypoint.sh
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,11 @@ const {
|
|||||||
BRIDGE_PLUGIN_ID,
|
BRIDGE_PLUGIN_ID,
|
||||||
migratePluginsFromAllVaults,
|
migratePluginsFromAllVaults,
|
||||||
} = require("./bridge-plugin");
|
} = require("./bridge-plugin");
|
||||||
const { initPlugins, shutdownPlugins } = require("./plugin-system/manager");
|
const {
|
||||||
|
initPlugins,
|
||||||
|
shutdownPlugins,
|
||||||
|
getBundledPluginDirs,
|
||||||
|
} = require("./plugin-system/manager");
|
||||||
const pluginRoutes = require("./routes/plugins");
|
const pluginRoutes = require("./routes/plugins");
|
||||||
writeCoalescer.configure({ writeCoalesceMs: config.writeCoalesceMs });
|
writeCoalescer.configure({ writeCoalesceMs: config.writeCoalesceMs });
|
||||||
const { flushAll } = writeCoalescer;
|
const { flushAll } = writeCoalescer;
|
||||||
@@ -173,8 +177,19 @@ const server = app.listen(config.port, async () => {
|
|||||||
console.log(`[ignis] Vault root: ${config.vaultRoot}`);
|
console.log(`[ignis] Vault root: ${config.vaultRoot}`);
|
||||||
console.log(`[ignis] Vaults: ${Object.keys(config.vaults).join(", ")}`);
|
console.log(`[ignis] Vaults: ${Object.keys(config.vaults).join(", ")}`);
|
||||||
|
|
||||||
await migratePluginsFromAllVaults(config.vaultRoot, [BRIDGE_PLUGIN_ID]);
|
|
||||||
await initPlugins({ app, config, wss, watcher });
|
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
|
bootstrapRoutes
|
||||||
.warmUp()
|
.warmUp()
|
||||||
.catch((e) => console.warn("[bootstrap] warm-up error:", e.message));
|
.catch((e) => console.warn("[bootstrap] warm-up error:", e.message));
|
||||||
|
|||||||
@@ -40,17 +40,16 @@ function discoverPlugins(pluginsDir) {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let bundledPluginId = null;
|
let bundledManifest = null;
|
||||||
|
|
||||||
if (plugin.obsidianPlugin) {
|
if (plugin.obsidianPlugin) {
|
||||||
try {
|
try {
|
||||||
const manifest = JSON.parse(
|
bundledManifest = JSON.parse(
|
||||||
fs.readFileSync(
|
fs.readFileSync(
|
||||||
path.join(plugin.obsidianPlugin, "manifest.json"),
|
path.join(plugin.obsidianPlugin, "manifest.json"),
|
||||||
"utf-8",
|
"utf-8",
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
bundledPluginId = manifest.id;
|
|
||||||
} catch {
|
} catch {
|
||||||
// No valid bundled plugin manifest
|
// No valid bundled plugin manifest
|
||||||
}
|
}
|
||||||
@@ -61,7 +60,8 @@ function discoverPlugins(pluginsDir) {
|
|||||||
name: plugin.name,
|
name: plugin.name,
|
||||||
description: plugin.description || "",
|
description: plugin.description || "",
|
||||||
obsidianPlugin: plugin.obsidianPlugin || null,
|
obsidianPlugin: plugin.obsidianPlugin || null,
|
||||||
bundledPluginId,
|
bundledPluginId: bundledManifest ? bundledManifest.id : null,
|
||||||
|
bundledManifest,
|
||||||
module: plugin,
|
module: plugin,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -3,10 +3,6 @@ const path = require("path");
|
|||||||
const express = require("express");
|
const express = require("express");
|
||||||
const { discoverPlugins } = require("./discovery");
|
const { discoverPlugins } = require("./discovery");
|
||||||
const configStore = require("./config-store");
|
const configStore = require("./config-store");
|
||||||
const {
|
|
||||||
installObsidianPlugin,
|
|
||||||
removeObsidianPlugin,
|
|
||||||
} = require("./obsidian-plugin");
|
|
||||||
|
|
||||||
let discoveredPlugins = new Map();
|
let discoveredPlugins = new Map();
|
||||||
const loadedPlugins = new Map();
|
const loadedPlugins = new Map();
|
||||||
@@ -50,18 +46,6 @@ async function initPlugins(ctx) {
|
|||||||
continue;
|
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);
|
const loaded = loadedPlugins.get(pluginId);
|
||||||
|
|
||||||
if (loaded?.module?.onVaultEnabled) {
|
if (loaded?.module?.onVaultEnabled) {
|
||||||
@@ -182,25 +166,6 @@ async function enablePluginForVault(pluginId, vaultId) {
|
|||||||
await loadPlugin(pluginId);
|
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);
|
const loaded = loadedPlugins.get(pluginId);
|
||||||
|
|
||||||
if (loaded?.module?.onVaultEnabled) {
|
if (loaded?.module?.onVaultEnabled) {
|
||||||
@@ -227,25 +192,6 @@ async function disablePluginForVault(pluginId, vaultId) {
|
|||||||
await loaded.module.onVaultDisabled(vaultId, vaultPath);
|
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 enabledVaults = configStore.getEnabledVaults(pluginConfig, pluginId);
|
||||||
const updated = enabledVaults.filter((id) => id !== vaultId);
|
const updated = enabledVaults.filter((id) => id !== vaultId);
|
||||||
configStore.setEnabledVaults(pluginConfig, pluginId, updated);
|
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() {
|
function getDiscoveredPlugins() {
|
||||||
const result = [];
|
const result = [];
|
||||||
|
|
||||||
@@ -280,4 +267,6 @@ module.exports = {
|
|||||||
enablePluginForVault,
|
enablePluginForVault,
|
||||||
disablePluginForVault,
|
disablePluginForVault,
|
||||||
getDiscoveredPlugins,
|
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 path = require("path");
|
||||||
const zlib = require("zlib");
|
const zlib = require("zlib");
|
||||||
const config = require("../config");
|
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();
|
const router = express.Router();
|
||||||
|
|
||||||
@@ -135,6 +139,7 @@ async function buildEntry(vaultId) {
|
|||||||
tree,
|
tree,
|
||||||
// In demo mode, hide server-side plugins from the client.
|
// In demo mode, hide server-side plugins from the client.
|
||||||
plugins: config.demoMode ? [] : getDiscoveredPlugins(),
|
plugins: config.demoMode ? [] : getDiscoveredPlugins(),
|
||||||
|
virtualPlugins: getVirtualPluginsForVault(vaultId, getVersion()),
|
||||||
};
|
};
|
||||||
|
|
||||||
const jsonBuf = Buffer.from(JSON.stringify(response));
|
const jsonBuf = Buffer.from(JSON.stringify(response));
|
||||||
|
|||||||
59
build.js
59
build.js
@@ -1,6 +1,17 @@
|
|||||||
const esbuild = require("esbuild");
|
const esbuild = require("esbuild");
|
||||||
|
const fs = require("fs");
|
||||||
const path = require("path");
|
const path = require("path");
|
||||||
|
|
||||||
|
const headlessSyncDir = path.join(
|
||||||
|
__dirname,
|
||||||
|
"apps",
|
||||||
|
"ignis-server",
|
||||||
|
"server",
|
||||||
|
"plugins",
|
||||||
|
"headless-sync",
|
||||||
|
"obsidian",
|
||||||
|
);
|
||||||
|
|
||||||
Promise.all([
|
Promise.all([
|
||||||
// Build shim-loader.js (delegated to packages/shim)
|
// Build shim-loader.js (delegated to packages/shim)
|
||||||
require("./packages/shim/build.js"),
|
require("./packages/shim/build.js"),
|
||||||
@@ -9,35 +20,21 @@ Promise.all([
|
|||||||
require("./packages/ui/build.js"),
|
require("./packages/ui/build.js"),
|
||||||
|
|
||||||
// Build headless-sync bundled plugin
|
// Build headless-sync bundled plugin
|
||||||
esbuild.build({
|
esbuild
|
||||||
entryPoints: [
|
.build({
|
||||||
path.join(
|
entryPoints: [path.join(headlessSyncDir, "src", "main.js")],
|
||||||
__dirname,
|
bundle: true,
|
||||||
"apps",
|
outfile: path.join(headlessSyncDir, "dist", "ignis-headless-sync.js"),
|
||||||
"ignis-server",
|
format: "cjs",
|
||||||
"server",
|
platform: "browser",
|
||||||
"plugins",
|
target: ["chrome90"],
|
||||||
"headless-sync",
|
external: ["obsidian", "fs"],
|
||||||
"obsidian",
|
logLevel: "info",
|
||||||
"src",
|
})
|
||||||
"main.js",
|
.then(() => {
|
||||||
),
|
fs.copyFileSync(
|
||||||
],
|
path.join(headlessSyncDir, "styles.css"),
|
||||||
bundle: true,
|
path.join(headlessSyncDir, "dist", "ignis-headless-sync.css"),
|
||||||
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",
|
|
||||||
}),
|
|
||||||
]).catch(() => process.exit(1));
|
]).catch(() => process.exit(1));
|
||||||
|
|||||||
@@ -232,6 +232,7 @@ export function initialize() {
|
|||||||
autoTrustDemoVaults(bootstrap.vaultList);
|
autoTrustDemoVaults(bootstrap.vaultList);
|
||||||
applyTree(bootstrap.tree);
|
applyTree(bootstrap.tree);
|
||||||
applyCoreSyncGuard(bootstrap.plugins);
|
applyCoreSyncGuard(bootstrap.plugins);
|
||||||
|
window.__ignisVirtualPlugins = bootstrap.virtualPlugins || [];
|
||||||
|
|
||||||
// Race the indexer: batch-fetch text content into ContentCache so
|
// Race the indexer: batch-fetch text content into ContentCache so
|
||||||
// Obsidian's startup indexing reads hit the cache instead of the network.
|
// 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 { initialize } from "./init.js";
|
||||||
import { fsShim } from "./fs/index.js";
|
import { fsShim } from "./fs/index.js";
|
||||||
import { registerUI } from "./ui-registry.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.
|
// __IGNIS_VERSION__ is replaced at build time from package.json.
|
||||||
window.__ignis = { version: __IGNIS_VERSION__ };
|
window.__ignis = { version: __IGNIS_VERSION__ };
|
||||||
@@ -48,6 +51,15 @@ extractObsidianModule()
|
|||||||
const bridge = new IgnisBridgePlugin(window.app, BRIDGE_MANIFEST);
|
const bridge = new IgnisBridgePlugin(window.app, BRIDGE_MANIFEST);
|
||||||
await bridge.onload();
|
await bridge.onload();
|
||||||
console.log("[ignis] bridge loaded");
|
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));
|
.catch((e) => console.error("[ignis] bridge load failed:", e));
|
||||||
|
|
||||||
|
|||||||
@@ -103,3 +103,42 @@ export async function extractObsidianModule() {
|
|||||||
console.log("[ignis] obsidian module captured");
|
console.log("[ignis] obsidian module captured");
|
||||||
return 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