Files
obsidian-remarkable/scripts/install.mjs
T
fegger 23ee2314b3 Add OCR dependency support to plugin installer
Adds new flags `--drawj2d-jar` and `--install-deps` to handle OCR dependencies
Automatically checks for required tools (unzip, Java, drawj2d.jar)
Installs missing system packages when `--install-deps` is used
Updates documentation with OCR setup examples

The implementation includes dependency checking that can be skipped with `--skip-deps`
when OCR functionality is not needed. The installer now copies and configures
drawj2d.jar if provided, and updates the plugin's data.json accordingly.
2026-05-31 18:00:01 +02:00

290 lines
8.1 KiB
JavaScript

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 <path> Obsidian vault path. Installs to <vault>/.obsidian/plugins/<manifest id>
--plugin-dir <path> Exact plugin install directory
--drawj2d-jar <path> 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;
});