/
raiden
/
obsidian-git-encrypt
Обзор
Документация
Войти
/
raiden
/
obsidian-git-encrypt
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
scripts/build-plugin.mjs
226 строк
7 KB
Robert Kuzhin
fix: accept custom Obsidian plugin directory
09 авг 2026, 14:29
09 авг 2026, 14:29
cb77430
Код
Авторство
О чём код?
import { unwatchFile, watchFile } from "node:fs"; import { builtinModules } from "node:module"; import { copyFile, mkdir, readFile, rm, stat } from "node:fs/promises"; import path from "node:path"; import { fileURLToPath } from "node:url"; import { Script } from "node:vm"; import esbuild from "esbuild"; const arguments_ = process.argv.slice(2); assertSupportedArguments(arguments_); const production = arguments_.includes("--production"); const watching = arguments_.includes("--watch"); if (production && watching) { throw new Error("Production and watch modes cannot be enabled together"); } const builtins = builtinModules.flatMap((moduleName) => [moduleName, `node:${moduleName}`]); const distribution = new URL("../dist/", import.meta.url); const output = new URL("main.js", distribution); const deploymentTarget = await resolveDeploymentTarget(arguments_); await mkdir(distribution, { recursive: true }); await rm(new URL("main.js.map", distribution), { force: true }); const buildOptions = { entryPoints: ["src/main.ts"], bundle: true, external: ["obsidian", "electron", ...builtins], format: "cjs", platform: "browser", target: "es2022", outfile: fileURLToPath(output), sourcemap: production ? false : "inline", minify: production, treeShaking: true, logLevel: "info", }; if (watching) { await watchPlugin(); } else { await esbuild.build(buildOptions); await finalizeBundle(); } async function watchPlugin() { let deploymentChain = Promise.resolve(); const scheduleDeployment = () => { const deploy = () => finalizeBundle(); deploymentChain = deploymentChain.then(deploy, deploy); return deploymentChain.catch((error) => { console.error("[encrypted-git] Plugin deployment failed:", error); }); }; const context = await esbuild.context({ ...buildOptions, plugins: [ { name: "deploy-obsidian-plugin", setup(build) { build.onEnd((result) => { if (result.errors.length > 0) return; return scheduleDeployment(); }); }, }, ], }); const watchedAssets = [ fileURLToPath(new URL("../manifest.json", import.meta.url)), fileURLToPath(new URL("../styles.css", import.meta.url)), ]; for (const asset of watchedAssets) { watchFile(asset, { interval: 300 }, (current, previous) => { if (current.mtimeMs !== previous.mtimeMs || current.size !== previous.size) { void scheduleDeployment(); } }); } try { await context.watch(); console.log("[encrypted-git] Watching source files; press Ctrl+C to stop."); if (deploymentTarget === undefined) { console.log("[encrypted-git] Updating dist/ only; pass --vault or --plugin-dir to deploy."); } await waitForTermination(); } finally { for (const asset of watchedAssets) unwatchFile(asset); await context.dispose(); } } async function finalizeBundle() { await Promise.all([ copyFile(new URL("../manifest.json", import.meta.url), new URL("manifest.json", distribution)), copyFile(new URL("../styles.css", import.meta.url), new URL("styles.css", distribution)), ]); const bundle = await readFile(output, "utf8"); try { new Script(bundle, { filename: "main.js" }); } catch (error) { throw new Error("Obsidian plugin bundle cannot be parsed as a CommonJS script", { cause: error, }); } if ( !bundle.includes("module.exports") || !bundle.includes('require("obsidian")') || /^\s*import\s/mu.test(bundle) || (production && bundle.includes("sourceMappingURL")) ) { throw new Error("Obsidian plugin bundle is not a self-contained CommonJS artifact"); } if (deploymentTarget !== undefined) { await mkdir(deploymentTarget, { recursive: true }); await Promise.all( ["main.js", "manifest.json", "styles.css"].map((filename) => copyFile(new URL(filename, distribution), path.join(deploymentTarget, filename)), ), ); console.log(`[encrypted-git] Updated ${deploymentTarget}`); } } /** * @param {string[]} arguments_ * @returns {Promise<string | undefined>} */ async function resolveDeploymentTarget(arguments_) { const vault = readOption(arguments_, "--vault"); const explicitPluginDirectory = readOption(arguments_, "--plugin-dir"); if (vault !== undefined && explicitPluginDirectory !== undefined) { throw new Error("Pass either --vault or --plugin-dir, not both"); } if (vault !== undefined) { const vaultRoot = validateAbsolutePath(vault, "Vault path"); await assertDirectoryExists(vaultRoot, "Vault path"); return path.join(vaultRoot, ".obsidian", "plugins", "encrypted-git"); } const configuredPluginDirectory = explicitPluginDirectory ?? (arguments_.includes("--watch") ? process.env.OBSIDIAN_PLUGIN_DIR?.trim() : undefined); if (configuredPluginDirectory === undefined || configuredPluginDirectory === "") { return undefined; } const pluginDirectory = validateAbsolutePath( configuredPluginDirectory, "Obsidian plugin directory", ); await assertDirectoryExists(path.dirname(pluginDirectory), "Obsidian plugins directory"); return pluginDirectory; } /** @param {string[]} arguments_ */ function assertSupportedArguments(arguments_) { for (let index = 0; index < arguments_.length; index += 1) { const argument = arguments_[index]; if (argument === "--" || argument === "--production" || argument === "--watch") continue; if (argument === "--vault" || argument === "--plugin-dir") { index += 1; continue; } throw new Error(`Unsupported plugin build option: ${argument}`); } } /** * @param {string[]} arguments_ * @param {string} name * @returns {string | undefined} */ function readOption(arguments_, name) { const matches = arguments_ .map((argument, index) => (argument === name ? index : -1)) .filter((index) => index >= 0); if (matches.length > 1) throw new Error(`${name} may be passed only once`); const index = matches[0]; if (index === undefined) return undefined; const value = arguments_[index + 1]; if (value === undefined || value.startsWith("--")) { throw new Error(`${name} requires an absolute path`); } return value; } /** * @param {string} candidate * @param {string} label * @returns {string} */ function validateAbsolutePath(candidate, label) { if (!path.isAbsolute(candidate) || candidate.includes("\0")) { throw new Error(`${label} must be an absolute filesystem path`); } const normalized = path.normalize(candidate); if (normalized === path.parse(normalized).root) { throw new Error(`${label} cannot be a filesystem root`); } return normalized; } /** * @param {string} candidate * @param {string} label * @returns {Promise<void>} */ async function assertDirectoryExists(candidate, label) { const metadata = await stat(candidate).catch(() => undefined); if (metadata?.isDirectory() !== true) { throw new Error(`${label} does not exist or is not a directory: ${candidate}`); } } /** @returns {Promise<void>} */ function waitForTermination() { return new Promise((resolve) => { process.once("SIGINT", resolve); process.once("SIGTERM", resolve); }); }