Add plugin installation script and documentation

The new `install-plugin` script automates plugin installation to Obsidian vaults
with support for both vault paths and direct plugin directory targets. It
includes options for skipping builds and dry runs.

Documentation in IMPLEMENTATION.md now includes usage examples for the new
installation method alongside the existing manual installation instructions.
This commit is contained in:
2026-05-31 17:17:51 +02:00
parent ff21ec921a
commit 2f14e8c1fa
3 changed files with 140 additions and 0 deletions
+133
View File
@@ -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 <path> Obsidian vault path. Installs to <vault>/.obsidian/plugins/<manifest id>
--plugin-dir <path> 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;
});