openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
122 lines (121 loc) • 5.2 kB
JavaScript
import { f as withPluginCache, i as createPluginCache } from "./plugin-cache-DGWspMEc.js";
import { c as isRecord } from "./record-coerce-DItp3I4t.js";
import { r as defaultRuntime } from "./runtime-CF2WjnNZ.js";
import { w as root } from "./fs-safe-B6pvPGnf.js";
import { r as loadPluginManifest, s as resolvePackageExtensionEntries } from "./manifest-ByRdkf9X.js";
import { r as readPluginControlUiAssets } from "./control-ui-assets-CtXpBttK.js";
import { l as buildPluginBundle, r as collectPluginsValidationResult } from "./plugins-authoring-command-B5ijz98k.js";
import { createRequire } from "node:module";
import path from "node:path";
import fs from "node:fs/promises";
import os from "node:os";
import { createHash } from "node:crypto";
import { create } from "tar";
//#region src/cli/plugins-feature-artifact.ts
/** Produce one reviewable import: bundled code, immutable UI assets, no install scripts. */
async function packFeaturePlugin(opts) {
const rootDir = await fs.realpath(path.resolve(opts.root ?? process.cwd()));
const validation = await collectPluginsValidationResult({ root: rootDir });
if (!validation.valid) throw new Error(validation.errors.join("\n"));
const source = await root(rootDir, {
symlinks: "reject",
hardlinks: "reject"
});
const packageManifest = await source.readJson("package.json");
if (!isRecord(packageManifest) || !isRecord(packageManifest.openclaw)) throw new Error("Plugin package metadata is missing. Run openclaw plugins build.");
const extensions = resolvePackageExtensionEntries(packageManifest);
if (extensions.status !== "ok" || extensions.entries.length !== 1) throw new Error("Plugin artifacts require exactly one backend entrypoint.");
const entry = extensions.entries[0];
const loaded = withPluginCache(createPluginCache(), () => loadPluginManifest(rootDir, false));
if (!loaded.ok) throw new Error(loaded.error);
const builder = createRequire(path.join(rootDir, "package.json"))("esbuild");
const files = await buildPluginBundle(builder, {
absWorkingDir: rootDir,
entryPoints: [entry],
outfile: "dist/index.js",
platform: "node",
target: "node22",
external: ["openclaw", "openclaw/*"]
});
const pluginId = loaded.manifest.id;
const outputPath = path.resolve(opts.out ?? path.join(rootDir, `${pluginId}.tgz`));
if (!/\.(?:tgz|tar\.gz)$/u.test(outputPath)) throw new Error("Plugin artifact output must end in .tgz or .tar.gz.");
const staging = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-plugin-pack-"));
try {
await fs.mkdir(path.join(staging, "package"));
const destination = await root(path.join(staging, "package"), {
mkdir: true,
symlinks: "reject",
hardlinks: "reject"
});
await destination.ensureRoot();
const { controlUi: _source, ...openclaw } = packageManifest.openclaw;
const packedPackage = {
name: packageManifest.name,
version: packageManifest.version,
type: "module",
...typeof packageManifest.description === "string" ? { description: packageManifest.description } : {},
...typeof packageManifest.license === "string" ? { license: packageManifest.license } : {},
...isRecord(packageManifest.peerDependencies) && typeof packageManifest.peerDependencies.openclaw === "string" ? { peerDependencies: { openclaw: packageManifest.peerDependencies.openclaw } } : {},
openclaw: {
...openclaw,
extensions: ["./dist/index.js"]
}
};
await destination.create("package.json", `${JSON.stringify(packedPackage, null, 2)}\n`);
await destination.create("openclaw.plugin.json", await source.readBytes("openclaw.plugin.json"));
await destination.mkdir("dist");
await destination.create("dist/index.js", Buffer.from(files[0].contents));
const controlUi = loaded.manifest.controlUi;
if (controlUi) {
const { directory, assets } = await readPluginControlUiAssets(rootDir, controlUi);
for (const [name, asset] of assets) {
const relativePath = path.posix.join(directory, name);
await destination.mkdir(path.posix.dirname(relativePath));
await destination.create(relativePath, asset.body);
}
}
const archive = path.join(staging, "plugin.tgz");
await create({
cwd: staging,
file: archive,
gzip: true,
portable: true,
noMtime: true,
strict: true
}, ["package"]);
const bytes = await fs.readFile(archive);
if (bytes.length > 33554432) throw new Error("Plugin artifacts must be at most 32 MiB.");
await fs.writeFile(outputPath, bytes, {
flag: "wx",
mode: 384
});
const sha256 = createHash("sha256").update(bytes).digest("hex");
return {
path: outputPath,
sha256,
pluginId,
bytes: bytes.length,
activation: {
action: "plugin_activate_artifact",
path: outputPath,
sha256
}
};
} finally {
await fs.rm(staging, {
recursive: true,
force: true
});
}
}
async function runPluginsPackCommand(opts) {
const receipt = await packFeaturePlugin(opts);
if (opts.json) {
defaultRuntime.writeJson(receipt);
return;
}
defaultRuntime.log(`Packed ${receipt.pluginId}: ${receipt.path}\nSHA256: ${receipt.sha256}\nUse plugin_activate_artifact with this path and digest to request activation approval.`);
}
//#endregion
export { runPluginsPackCommand };