import { access, copyFile, mkdir, readFile, writeFile } from "fs/promises"; import { constants } from "fs"; import { basename, dirname, join, resolve } from "path"; import { fileURLToPath } from "url"; import { spawnSync } from "child_process"; const root = dirname(dirname(fileURLToPath(import.meta.url))); const runtimeFiles = ["manifest.json", "main.js"]; const drawj2dTargetName = "drawj2d.jar"; function printUsage() { console.log(`Usage: npm run install-plugin -- --vault /path/to/vault npm run install-plugin -- --plugin-dir /path/to/vault/.obsidian/plugins/obsidian-remarkable Options: --vault Obsidian vault path. Installs to /.obsidian/plugins/ --plugin-dir Exact plugin install directory --drawj2d-jar Copy a local drawj2d.jar into the plugin directory and configure it --install-deps Try to install missing system packages such as unzip --skip-deps Do not check OCR dependencies --no-build Skip npm run build before copying files --dry-run Print actions without writing files --help Show this help Environment: OBSIDIAN_VAULT Same as --vault OBSIDIAN_PLUGIN_DIR Same as --plugin-dir DRAWJ2D_JAR Same as --drawj2d-jar`); } function parseArgs(argv) { const options = { vault: process.env.OBSIDIAN_VAULT || "", pluginDir: process.env.OBSIDIAN_PLUGIN_DIR || "", drawj2dJar: process.env.DRAWJ2D_JAR || "", build: true, dryRun: false, help: false, installDeps: false, skipDeps: false, }; for (let i = 0; i < argv.length; i++) { const arg = argv[i]; if (arg === "--help" || arg === "-h") { options.help = true; } else if (arg === "--no-build") { options.build = false; } else if (arg === "--dry-run") { options.dryRun = true; } else if (arg === "--install-deps") { options.installDeps = true; } else if (arg === "--skip-deps") { options.skipDeps = true; } else if (arg === "--vault") { options.vault = argv[++i] || ""; } else if (arg === "--plugin-dir") { options.pluginDir = argv[++i] || ""; } else if (arg === "--drawj2d-jar") { options.drawj2dJar = argv[++i] || ""; } else { throw new Error(`Unknown option: ${arg}`); } } return options; } async function getManifestId() { const manifestPath = join(root, "manifest.json"); const manifest = JSON.parse(await readFile(manifestPath, "utf-8")); if (!manifest.id || typeof manifest.id !== "string") { throw new Error("manifest.json is missing a string id"); } return manifest.id; } function runBuild() { const result = spawnSync("npm", ["run", "build"], { cwd: root, stdio: "inherit", }); if (result.error) { throw result.error; } if (result.status !== 0) { throw new Error(`npm run build failed with exit code ${result.status}`); } } function run(command, args, options = {}) { return spawnSync(command, args, { cwd: root, stdio: options.stdio || "pipe", encoding: "utf-8", }); } function commandExists(command, args) { const result = run(command, args); return !result.error && result.status === 0; } function detectPackageInstallCommand(packageName) { if (commandExists("apt-get", ["--version"])) { return ["sudo", ["apt-get", "install", "-y", packageName]]; } if (commandExists("dnf", ["--version"])) { return ["sudo", ["dnf", "install", "-y", packageName]]; } if (commandExists("pacman", ["--version"])) { return ["sudo", ["pacman", "-S", "--needed", "--noconfirm", packageName]]; } if (commandExists("brew", ["--version"])) { return ["brew", ["install", packageName]]; } return null; } function installPackage(packageName, dryRun) { const command = detectPackageInstallCommand(packageName); if (!command) { return false; } const [binary, args] = command; console.log(`${dryRun ? "Would run" : "Running"} ${binary} ${args.join(" ")}`); if (dryRun) { return true; } const result = spawnSync(binary, args, { stdio: "inherit" }); if (result.error) { throw result.error; } return result.status === 0; } async function fileExists(path) { try { await access(path, constants.R_OK); return true; } catch { return false; } } async function checkDependencies(options, targetDir) { const missing = []; if (!commandExists("unzip", ["-v"])) { if (options.installDeps && installPackage("unzip", options.dryRun)) { console.log(options.dryRun ? "unzip would be installed." : "Installed unzip."); } else { missing.push("unzip"); } } if (!commandExists("java", ["-version"])) { missing.push("java"); } const targetDrawj2dPath = join(targetDir, drawj2dTargetName); const sourceDrawj2dPath = options.drawj2dJar ? resolve(options.drawj2dJar) : ""; const hasSourceDrawj2d = sourceDrawj2dPath ? await fileExists(sourceDrawj2dPath) : false; const hasInstalledDrawj2d = await fileExists(targetDrawj2dPath); if (sourceDrawj2dPath && !hasSourceDrawj2d) { missing.push(`drawj2d jar at ${sourceDrawj2dPath}`); } else if (!hasSourceDrawj2d && !hasInstalledDrawj2d) { missing.push("drawj2d.jar"); } if (missing.length > 0) { throw new Error( [ `Missing dependencies: ${missing.join(", ")}`, "Install unzip and Java with your system package manager.", "Download drawj2d.jar, then rerun with --drawj2d-jar /path/to/drawj2d.jar or set DRAWJ2D_JAR.", "Use --skip-deps only if you intentionally do not want OCR dependency checks.", ].join("\n"), ); } return sourceDrawj2dPath && hasSourceDrawj2d ? sourceDrawj2dPath : ""; } async function installDrawj2dJar(sourcePath, targetDir, dryRun) { if (!sourcePath) { return ""; } if (basename(sourcePath) !== drawj2dTargetName) { console.log(`Installing ${basename(sourcePath)} as ${drawj2dTargetName}`); } const destination = join(targetDir, drawj2dTargetName); console.log(`${dryRun ? "Would copy" : "Copying"} ${drawj2dTargetName}`); if (!dryRun) { await copyFile(sourcePath, destination); } return destination; } async function updatePluginData(targetDir, drawj2dPath, dryRun) { if (!drawj2dPath) { return; } const dataPath = join(targetDir, "data.json"); let data = {}; if (await fileExists(dataPath)) { data = JSON.parse(await readFile(dataPath, "utf-8")); } data.drawj2dPath = drawj2dPath; console.log(`${dryRun ? "Would update" : "Updating"} data.json drawj2dPath`); if (!dryRun) { await writeFile(dataPath, `${JSON.stringify(data, null, 2)}\n`); } } async function install() { const options = parseArgs(process.argv.slice(2)); if (options.help) { printUsage(); return; } if (options.vault && options.pluginDir) { throw new Error("Use either --vault or --plugin-dir, not both"); } const manifestId = await getManifestId(); const targetDir = options.pluginDir ? resolve(options.pluginDir) : options.vault ? join(resolve(options.vault), ".obsidian", "plugins", manifestId) : ""; if (!targetDir) { printUsage(); throw new Error("Missing install target. Provide --vault, --plugin-dir, OBSIDIAN_VAULT, or OBSIDIAN_PLUGIN_DIR"); } if (options.build) { console.log("Building plugin..."); runBuild(); } const drawj2dJarToInstall = options.skipDeps ? "" : await checkDependencies(options, targetDir); console.log(`Installing to ${targetDir}`); if (!options.dryRun) { await mkdir(targetDir, { recursive: true }); } for (const file of runtimeFiles) { const source = join(root, file); const destination = join(targetDir, file); console.log(`${options.dryRun ? "Would copy" : "Copying"} ${file}`); if (!options.dryRun) { await copyFile(source, destination); } } const installedDrawj2dPath = await installDrawj2dJar(drawj2dJarToInstall, targetDir, options.dryRun); await updatePluginData(targetDir, installedDrawj2dPath || (!options.skipDeps ? join(targetDir, drawj2dTargetName) : ""), options.dryRun); console.log("Install complete."); } install().catch((error) => { console.error(error instanceof Error ? error.message : error); process.exitCode = 1; });