mirror of
https://github.com/andy-stack/vaultkeeper-ai.git
synced 2026-07-22 06:42:03 +00:00
Switch PDF extraction to Obsidian's bundled PDF.js via loadPdfJs() instead of unpdf. Replace officeparser with mammoth for DOCX and custom fflate-based ZIP parsers for PPTX/XLSX/ODF formats. Eliminates heavy dependencies, removes all dynamic eval and .wasm references, and simplifies the esbuild plugin configuration.
235 lines
No EOL
7.8 KiB
JavaScript
235 lines
No EOL
7.8 KiB
JavaScript
import esbuild from "esbuild";
|
|
import process from "process";
|
|
import { builtinModules as builtins } from "node:module";
|
|
import { copyFileSync, mkdirSync, existsSync, readdirSync, statSync, readFileSync, writeFileSync, unlinkSync, watch as fsWatch } from "fs";
|
|
import { join } from "path";
|
|
import esbuildSvelte from "esbuild-svelte";
|
|
import { sveltePreprocess } from "svelte-preprocess";
|
|
|
|
const banner =
|
|
`/*
|
|
THIS IS A GENERATED/BUNDLED FILE BY ESBUILD
|
|
if you want to view the source, please visit the github repository of this plugin
|
|
*/
|
|
`;
|
|
|
|
const prod = (process.argv[2] === "production");
|
|
|
|
// Clean up build artifacts that aren"t needed (main.css, main.js, font files)
|
|
const CLEANUP_BUILD_ARTIFACTS = true; // Set to false if you need to debug these files
|
|
|
|
// Function to copy directory recursively
|
|
function copyDir(src, dest) {
|
|
if (!existsSync(dest)) {
|
|
mkdirSync(dest, { recursive: true });
|
|
}
|
|
|
|
const files = readdirSync(src);
|
|
|
|
for (const file of files) {
|
|
const srcPath = join(src, file);
|
|
const destPath = join(dest, file);
|
|
|
|
if (statSync(srcPath).isDirectory()) {
|
|
copyDir(srcPath, destPath);
|
|
} else {
|
|
copyFileSync(srcPath, destPath);
|
|
console.log(`📁 Copied: ${srcPath} → ${destPath}`);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Neutralises `new Function(...)` literals in the bundled output.
|
|
//
|
|
// All such literals come from dependencies, never from our own source, and every
|
|
// one is dead code in our usage:
|
|
// - bluebird (transitive via mammoth): 7x promise-method JIT compilers, ALL
|
|
// gated behind `canEvaluate = typeof navigator == "undefined"`. Obsidian always
|
|
// defines `navigator`, so `canEvaluate` is false and bluebird takes its pure-JS
|
|
// fallbacks — the `new Function` branches are never entered. (call_get.js x2,
|
|
// join.js x3, promisify.js x2.)
|
|
// - diff2html/@profoundlogic/hogan: 2x template compilers reachable only via
|
|
// Hogan.compile(), which diff2html invokes only for runtime `rawTemplates`.
|
|
// We use diff2html's precompiled defaults, so these never run.
|
|
//
|
|
// We rewrite them to a throwing stub so the bundle contains no `new Function(`
|
|
// literal, which clears the Obsidian scanner's "Dynamic Code Execution" warning. The
|
|
// build FAILS if the count drifts from EXPECTED_NEW_FUNCTION_COUNT, so a dependency
|
|
// change can never silently reintroduce a live (or un-neutralised) dynamic-eval path.
|
|
//
|
|
// 9 = 7 bluebird (canEvaluate-gated) + 2 Hogan.
|
|
const EXPECTED_NEW_FUNCTION_COUNT = 9;
|
|
const NEW_FUNCTION_STUB = "VKBlockedDynamicFn";
|
|
function neutraliseDynamicEval(outfile) {
|
|
if (!existsSync(outfile)) return;
|
|
let contents = readFileSync(outfile, "utf-8");
|
|
const matches = contents.match(/new Function\s*\(/g) || [];
|
|
if (matches.length !== EXPECTED_NEW_FUNCTION_COUNT) {
|
|
throw new Error(
|
|
`neutraliseDynamicEval: expected ${EXPECTED_NEW_FUNCTION_COUNT} ` +
|
|
`\`new Function(\` literals in ${outfile} but found ${matches.length}. ` +
|
|
`A dependency changed — re-audit which library introduced/removed the ` +
|
|
`call and confirm it is unreachable before updating EXPECTED_NEW_FUNCTION_COUNT.`,
|
|
);
|
|
}
|
|
// Throwing stub: callable as `new VKBlockedDynamicFn(...)` with any args.
|
|
const stubDecl =
|
|
`function ${NEW_FUNCTION_STUB}(){throw new Error("Dynamic code execution is disabled in this build.")}`;
|
|
contents = contents.replace(/new Function\s*\(/g, `new ${NEW_FUNCTION_STUB}(`);
|
|
// Insert the stub right after the generated banner comment (a hoisted function
|
|
// declaration is in scope for the whole module regardless of position).
|
|
const bannerEnd = contents.indexOf("*/");
|
|
if (bannerEnd !== -1) {
|
|
const insertAt = contents.indexOf("\n", bannerEnd) + 1;
|
|
contents = contents.slice(0, insertAt) + stubDecl + "\n" + contents.slice(insertAt);
|
|
} else {
|
|
contents = `${stubDecl}\n${contents}`;
|
|
}
|
|
writeFileSync(outfile, contents);
|
|
console.log(
|
|
`🛡️ Neutralised ${matches.length} \`new Function(\` literal(s) in ${outfile}`,
|
|
);
|
|
}
|
|
|
|
// Runs the dynamic-eval neutralisation on every build and rebuild, for both dev and
|
|
// production, so the dev bundle matches what ships and the post-build step is
|
|
// exercised continuously rather than only at release time. Registered last so it
|
|
// transforms the finalised main.js.
|
|
const dynamicEvalPlugin = {
|
|
name: "neutralise-dynamic-eval",
|
|
setup(build) {
|
|
build.onEnd(() => {
|
|
neutraliseDynamicEval(build.initialOptions.outfile);
|
|
});
|
|
},
|
|
};
|
|
|
|
// Plugin to merge CSS files into styles.css and cleanup build artifacts
|
|
const cssMergerPlugin = {
|
|
name: "css-merger",
|
|
setup(build) {
|
|
build.onEnd(() => {
|
|
// esbuild outputs CSS as main.css (based on outfile: main.js)
|
|
const generatedCss = "main.css";
|
|
const customCssDir = "Styles";
|
|
const outputCss = "styles.css";
|
|
|
|
let mergedCss = "";
|
|
|
|
// Read generated CSS from dependencies if it exists
|
|
if (existsSync(generatedCss)) {
|
|
mergedCss += readFileSync(generatedCss, "utf-8");
|
|
mergedCss += "\n\n/* Custom Styles */\n\n";
|
|
}
|
|
|
|
// Append custom CSS files from styles directory
|
|
if (existsSync(customCssDir)) {
|
|
const cssFiles = readdirSync(customCssDir)
|
|
.filter(file => file.endsWith(".css"))
|
|
.sort();
|
|
|
|
for (const cssFile of cssFiles) {
|
|
const cssPath = join(customCssDir, cssFile);
|
|
mergedCss += readFileSync(cssPath, "utf-8");
|
|
mergedCss += "\n\n";
|
|
console.log(`📦 Merged: ${cssPath}`);
|
|
}
|
|
}
|
|
|
|
// Write merged CSS to styles.css
|
|
writeFileSync(outputCss, mergedCss);
|
|
console.log(`✅ Generated: ${outputCss}`);
|
|
|
|
// Clean up build artifacts if enabled
|
|
if (CLEANUP_BUILD_ARTIFACTS) {
|
|
// Remove main.css (intermediate CSS file)
|
|
if (existsSync(generatedCss)) {
|
|
unlinkSync(generatedCss);
|
|
console.log(`🗑️ Removed: ${generatedCss}`);
|
|
}
|
|
|
|
// Remove KaTeX font files
|
|
let anyRemoved = false;
|
|
const fontExtensions = [".woff", ".woff2", ".ttf"];
|
|
const files = readdirSync(".");
|
|
for (const file of files) {
|
|
const ext = file.substring(file.lastIndexOf("."));
|
|
if (fontExtensions.includes(ext)) {
|
|
unlinkSync(file);
|
|
anyRemoved = true;
|
|
}
|
|
}
|
|
if (anyRemoved) {
|
|
console.log("🗑️ Removed KaTeX Font Files");
|
|
}
|
|
}
|
|
});
|
|
}
|
|
};
|
|
|
|
const buildOptions = {
|
|
plugins: [
|
|
esbuildSvelte({
|
|
compilerOptions: { css: "injected" },
|
|
preprocess: sveltePreprocess(),
|
|
}),
|
|
cssMergerPlugin,
|
|
dynamicEvalPlugin,
|
|
],
|
|
banner: {
|
|
js: banner,
|
|
},
|
|
entryPoints: ["main.ts"],
|
|
bundle: true,
|
|
define: {
|
|
"process.env.NODE_ENV": JSON.stringify(prod ? "production" : "development"),
|
|
},
|
|
external: [
|
|
"obsidian",
|
|
"electron",
|
|
"@codemirror/autocomplete",
|
|
"@codemirror/collab",
|
|
"@codemirror/commands",
|
|
"@codemirror/language",
|
|
"@codemirror/lint",
|
|
"@codemirror/search",
|
|
"@codemirror/state",
|
|
"@codemirror/view",
|
|
"@lezer/common",
|
|
"@lezer/highlight",
|
|
"@lezer/lr",
|
|
...builtins],
|
|
format: "cjs",
|
|
target: "es2022",
|
|
logLevel: "info",
|
|
sourcemap: prod ? false : "inline",
|
|
treeShaking: true,
|
|
outfile: "main.js",
|
|
minify: prod,
|
|
loader: {
|
|
".css": "css",
|
|
".svg": "text",
|
|
".png": "dataurl",
|
|
".ttf": "file",
|
|
".woff": "file",
|
|
".woff2": "file",
|
|
},
|
|
};
|
|
|
|
if (prod) {
|
|
await esbuild.build(buildOptions);
|
|
console.log("✅ Production build complete!");
|
|
} else {
|
|
const ctx = await esbuild.context(buildOptions);
|
|
await ctx.watch();
|
|
|
|
// Watch Styles directory for CSS changes
|
|
if (existsSync("Styles")) {
|
|
fsWatch("Styles", { recursive: true }, (_eventType, filename) => {
|
|
if (filename && filename.endsWith(".css")) {
|
|
console.log(`🔄 CSS file changed: ${filename} - Rebuilding...`);
|
|
ctx.rebuild();
|
|
}
|
|
});
|
|
}
|
|
} |