diff --git a/IMPLEMENTATION.md b/IMPLEMENTATION.md index 5d5e03a..be36ccd 100644 --- a/IMPLEMENTATION.md +++ b/IMPLEMENTATION.md @@ -265,6 +265,12 @@ Run `npm run build`, then copy the plugin folder containing `manifest.json` and - Linux: ~/.config/obsidian/plugins/ - macOS: ~/Library/Application Support/obsidian/plugins/ - Windows: %APPDATA%\obsidian\plugins\ + +# Or install directly into a vault +npm run install-plugin -- --vault /path/to/vault + +# Exact target directory also works +npm run install-plugin -- --plugin-dir /path/to/vault/.obsidian/plugins/obsidian-remarkable ``` --- diff --git a/package.json b/package.json index afd5347..055b8aa 100644 --- a/package.json +++ b/package.json @@ -6,6 +6,7 @@ "scripts": { "build": "node esbuild.config.mjs", "dev": "node esbuild.config.mjs --watch", + "install-plugin": "node scripts/install.mjs", "test": "node tests/run-tests.mjs" }, "devDependencies": { diff --git a/scripts/install.mjs b/scripts/install.mjs new file mode 100644 index 0000000..2f4ca03 --- /dev/null +++ b/scripts/install.mjs @@ -0,0 +1,133 @@ +import { copyFile, mkdir, readFile } from "fs/promises"; +import { 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"]; + +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 + --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`); +} + +function parseArgs(argv) { + const options = { + vault: process.env.OBSIDIAN_VAULT || "", + pluginDir: process.env.OBSIDIAN_PLUGIN_DIR || "", + build: true, + dryRun: false, + help: 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 === "--vault") { + options.vault = argv[++i] || ""; + } else if (arg === "--plugin-dir") { + options.pluginDir = 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}`); + } +} + +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(); + } + + 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); + } + } + + console.log("Install complete."); +} + +install().catch((error) => { + console.error(error instanceof Error ? error.message : error); + process.exitCode = 1; +});