132 lines
5.6 KiB
JavaScript
132 lines
5.6 KiB
JavaScript
import {
|
|
existsSync,
|
|
mkdirSync,
|
|
mkdtempSync,
|
|
readFileSync,
|
|
renameSync,
|
|
rmSync,
|
|
writeFileSync,
|
|
} from "node:fs";
|
|
import path from "node:path";
|
|
import { fileURLToPath } from "node:url";
|
|
|
|
const root = fileURLToPath(new URL("../", import.meta.url));
|
|
const args = process.argv.slice(2);
|
|
const [id, destination, option, template = "ui"] = args;
|
|
if (
|
|
args.length < 2 ||
|
|
args.length > 4 ||
|
|
(option && option !== "--template") ||
|
|
(option && args.length !== 4) ||
|
|
!["ui", "rust"].includes(template)
|
|
) {
|
|
process.stderr.write(
|
|
"Usage: pnpm plugin:create <publisher.plugin> <directory> [--template ui|rust]\n",
|
|
);
|
|
process.exit(1);
|
|
}
|
|
if (
|
|
!/^[a-z0-9][a-z0-9._-]{0,127}$/.test(id) ||
|
|
!id.includes(".") ||
|
|
id.includes("..")
|
|
) {
|
|
throw new Error(
|
|
"Use a namespaced lowercase plugin ID, for example example.hello",
|
|
);
|
|
}
|
|
const target = path.resolve(destination);
|
|
if (existsSync(target))
|
|
throw new Error(`Destination already exists: ${target}`);
|
|
const { version } = JSON.parse(
|
|
readFileSync(path.join(root, "package.json"), "utf8"),
|
|
);
|
|
const sdkPath = path
|
|
.relative(target, path.join(root, "plugins/sdk/rust/nyaterm-plugin-sdk"))
|
|
.split(path.sep)
|
|
.join("/");
|
|
const manifest = {
|
|
manifestVersion: 1,
|
|
id,
|
|
name: id,
|
|
version: "1.0.0",
|
|
description: "A NyaTerm plugin starter",
|
|
publisher: id.split(".")[0],
|
|
engine: `>=${version}, <2.0.0`,
|
|
permissions: template === "rust" ? ["native"] : ["session.read"],
|
|
contributions: {
|
|
panels: [{ id: "main", title: id, entry: "ui/index.html" }],
|
|
commands: [
|
|
{
|
|
id: "hello",
|
|
title: `Open ${id}`,
|
|
panel: "main",
|
|
menus: ["terminal", "connection"],
|
|
},
|
|
],
|
|
},
|
|
};
|
|
if (template === "rust") {
|
|
manifest.backend = { transport: "stdio-jsonl", executables: {} };
|
|
manifest.contributions.commands.push({
|
|
id: "run",
|
|
title: "Run native hello",
|
|
method: "command/hello",
|
|
menus: ["terminal", "connection"],
|
|
});
|
|
}
|
|
mkdirSync(path.dirname(target), { recursive: true });
|
|
const stage = mkdtempSync(path.join(path.dirname(target), ".nyaterm-plugin-"));
|
|
try {
|
|
const put = (file, content) => {
|
|
const output = path.join(stage, file);
|
|
mkdirSync(path.dirname(output), { recursive: true });
|
|
writeFileSync(output, content);
|
|
};
|
|
put("manifest.json", `${JSON.stringify(manifest, null, 2)}\n`);
|
|
put(".gitignore", "target/\nbin/\n*.nyap\n");
|
|
put(
|
|
"ui/index.html",
|
|
readFileSync(path.join(root, "plugins/templates/ui/index.html"), "utf8"),
|
|
);
|
|
put(
|
|
"ui/main.js",
|
|
template === "rust"
|
|
? 'document.getElementById("run").onclick = async () => {\n const output = document.getElementById("output");\n try { await NyaTerm.ready; output.textContent = JSON.stringify(await NyaTerm.backend("ui/hello"), null, 2); }\n catch (error) { output.textContent = error.message; }\n};\n'
|
|
: 'document.getElementById("run").onclick = async () => {\n const output = document.getElementById("output");\n try { await NyaTerm.ready; output.textContent = JSON.stringify(await NyaTerm.session(), null, 2); }\n catch (error) { output.textContent = error.message; }\n};\n',
|
|
);
|
|
put(
|
|
"ui/nyaterm-sdk.js",
|
|
readFileSync(path.join(root, "plugins/sdk/nyaterm.js")),
|
|
);
|
|
put(
|
|
"ui/nyaterm-sdk.d.ts",
|
|
readFileSync(path.join(root, "plugins/sdk/nyaterm.d.ts")),
|
|
);
|
|
if (template === "rust") {
|
|
put(
|
|
"Cargo.toml",
|
|
`[package]\nname = "nyaterm-plugin-starter"\nversion = "1.0.0"\nedition = "2024"\nrust-version = "1.94"\n\n[dependencies]\nnyaterm-plugin-sdk = { path = ${JSON.stringify(sdkPath)} }\ntokio = { version = "1", features = ["rt", "macros"] }\n`,
|
|
);
|
|
put(
|
|
"src/main.rs",
|
|
readFileSync(path.join(root, "plugins/templates/rust/main.rs")),
|
|
);
|
|
put(
|
|
"build.mjs",
|
|
readFileSync(path.join(root, "plugins/templates/rust/build.mjs")),
|
|
);
|
|
}
|
|
const packer = path.join(root, "scripts/package-plugin.mjs");
|
|
put(
|
|
"README.md",
|
|
`# ${id}\n\nGenerated from the NyaTerm ${template} template.\n\n${template === "rust" ? "1. Install Rust 1.94 or newer, then run `node build.mjs` here. This builds the SDK backend and fills the current platform executable in manifest.json.\n2." : "1."} Package from this directory:\n\n\`\`\`sh\nnode ${JSON.stringify(packer)} . ../${id}.nyap\n\`\`\`\n\nInstall the .nyap from NyaTerm's Plugins panel, review permissions, enable it and open its panel. ${template === "rust" ? "The native command reuses the process; check status/logs in Plugins. Stop the backend to reset its counter. Never print protocol data to stdout: use Context::log or stderr. The Rust SDK is a relative local dependency; distribute this source with the SDK or change its path when moving the project. For framed transport change backend.transport to stdio-framed; the SDK selects the transport from the host environment. Add other-platform binaries under bin/ and their executable entries before packing." : "Select a connected terminal before inspecting its session. The UI runs in an isolated iframe; use the SDK instead of Tauri APIs or direct file/network access."}\n\nEdit manifest.json to add permissions and contributions. UI scripts and styles must be local; use textContent for host/plugin data. Packaging includes only manifest.json, ui/, assets/ and bin/; target and source are excluded. The packager injects the current UI SDK. To update an installed plugin, bump manifest.version (and Cargo version for Rust) because released versions are immutable.\n`,
|
|
);
|
|
renameSync(stage, target);
|
|
} catch (error) {
|
|
rmSync(stage, { recursive: true, force: true });
|
|
throw error;
|
|
}
|
|
process.stdout.write(
|
|
`Created ${template} plugin ${id} at ${target}\nSee README.md for build and packaging instructions.\n`,
|
|
);
|