refactor(plugins): centralize npm runtime package planning

This commit is contained in:
Vincent Koc
2026-05-02 22:59:03 -07:00
parent fb6893cf48
commit 25ceffbf25
4 changed files with 86 additions and 94 deletions

View File

@@ -5,32 +5,11 @@ import path from "node:path";
import { pathToFileURL } from "node:url";
import {
buildPluginNpmRuntime,
listPluginNpmRuntimeBuildOutputs,
listPublishablePluginPackageDirs,
resolvePluginNpmRuntimeBuildPlan,
} from "./lib/plugin-npm-runtime-build.mjs";
function readJsonFile(filePath) {
return JSON.parse(fs.readFileSync(filePath, "utf8"));
}
function isPublishablePluginPackage(packageJson) {
return packageJson.openclaw?.release?.publishToNpm === true;
}
function listPublishablePluginPackageDirs(repoRoot) {
const extensionsRoot = path.join(repoRoot, "extensions");
return fs
.readdirSync(extensionsRoot, { withFileTypes: true })
.filter((entry) => entry.isDirectory())
.map((entry) => path.join("extensions", entry.name))
.filter((packageDir) => {
const packageJsonPath = path.join(repoRoot, packageDir, "package.json");
return (
fs.existsSync(packageJsonPath) && isPublishablePluginPackage(readJsonFile(packageJsonPath))
);
})
.toSorted((left, right) => left.localeCompare(right));
}
function parseArgs(argv) {
const packageDirs = [];
for (let index = 0; index < argv.length; index += 1) {
@@ -51,18 +30,12 @@ function parseArgs(argv) {
return { packageDirs };
}
function listMissingRuntimeOutputs(plan) {
return Object.keys(plan.entry)
.map((entryKey) => path.join(plan.outDir, `${entryKey}.js`))
.filter((filePath) => !fs.existsSync(filePath));
}
export async function checkPluginNpmRuntimeBuilds(params = {}) {
const repoRoot = path.resolve(params.repoRoot ?? ".");
const packageDirs =
params.packageDirs?.length > 0
? params.packageDirs
: listPublishablePluginPackageDirs(repoRoot);
: listPublishablePluginPackageDirs({ repoRoot });
const rows = [];
for (const packageDir of packageDirs) {
const plan = resolvePluginNpmRuntimeBuildPlan({ repoRoot, packageDir });
@@ -74,13 +47,12 @@ export async function checkPluginNpmRuntimeBuilds(params = {}) {
packageDir,
logLevel: params.logLevel ?? "warn",
});
const missing = listMissingRuntimeOutputs(result);
const missing = listPluginNpmRuntimeBuildOutputs(result).filter(
(runtimePath) =>
!fs.existsSync(path.join(result.packageDir, runtimePath.replace(/^\.\//u, ""))),
);
if (missing.length > 0) {
throw new Error(
`${packageDir} missing built runtime outputs: ${missing
.map((filePath) => path.relative(repoRoot, filePath))
.join(", ")}`,
);
throw new Error(`${packageDir} missing built runtime outputs: ${missing.join(", ")}`);
}
rows.push({
pluginDir: result.pluginDir,

View File

@@ -3,7 +3,10 @@ import fs from "node:fs";
import path from "node:path";
import { pathToFileURL } from "node:url";
import JSON5 from "json5";
import { resolvePluginNpmRuntimeBuildPlan } from "./plugin-npm-runtime-build.mjs";
import {
listPluginNpmRuntimeBuildOutputs,
resolvePluginNpmRuntimeBuildPlan,
} from "./plugin-npm-runtime-build.mjs";
const GENERATED_BUNDLED_CHANNEL_CONFIG_METADATA_PATH =
"src/config/bundled-channel-config-metadata.generated.ts";
@@ -28,34 +31,8 @@ function packageRelativePathExists(packageDir, relativePath) {
return fs.existsSync(path.join(packageDir, relativePath));
}
function mergePackageFiles(packageDir, files) {
const merged = new Set(
Array.isArray(files) ? files.filter((entry) => typeof entry === "string") : [],
);
merged.add("dist/**");
if (packageRelativePathExists(packageDir, "openclaw.plugin.json")) {
merged.add("openclaw.plugin.json");
}
if (packageRelativePathExists(packageDir, "README.md")) {
merged.add("README.md");
}
if (packageRelativePathExists(packageDir, "SKILL.md")) {
merged.add("SKILL.md");
}
if (packageRelativePathExists(packageDir, "skills")) {
merged.add("skills/**");
}
return [...merged];
}
function listRuntimeBuildOutputs(plan) {
return Object.keys(plan.entry)
.map((entryKey) => `./dist/${entryKey}.js`)
.toSorted((left, right) => left.localeCompare(right));
}
function assertPluginNpmRuntimeBuildExists(plan) {
const missing = listRuntimeBuildOutputs(plan).filter(
const missing = listPluginNpmRuntimeBuildOutputs(plan).filter(
(runtimePath) => !packageRelativePathExists(plan.packageDir, runtimePath.replace(/^\.\//u, "")),
);
if (missing.length > 0) {
@@ -98,7 +75,7 @@ export function resolveAugmentedPluginNpmPackageJson(params) {
const packageJson = {
...plan.packageJson,
files: mergePackageFiles(packageDir, plan.packageJson.files),
files: plan.packageFiles,
openclaw: {
...plan.packageJson.openclaw,
runtimeExtensions: plan.runtimeExtensions,

View File

@@ -16,6 +16,10 @@ function readJsonFile(filePath) {
return JSON.parse(fs.readFileSync(filePath, "utf8"));
}
export function isPublishablePluginPackage(packageJson) {
return packageJson.openclaw?.release?.publishToNpm === true;
}
function normalizePackageEntry(value) {
return typeof value === "string" ? value.trim().replaceAll("\\", "/") : "";
}
@@ -64,6 +68,54 @@ function resolvePackageDir(repoRoot, packageDir) {
return path.isAbsolute(packageDir) ? packageDir : path.resolve(repoRoot, packageDir);
}
function packageRelativePathExists(packageDir, relativePath) {
return fs.existsSync(path.join(packageDir, relativePath));
}
export function listPublishablePluginPackageDirs(params = {}) {
const repoRoot = path.resolve(params.repoRoot ?? ".");
const extensionsRoot = path.join(repoRoot, "extensions");
return fs
.readdirSync(extensionsRoot, { withFileTypes: true })
.filter((entry) => entry.isDirectory())
.map((entry) => path.join("extensions", entry.name))
.filter((packageDir) => {
const packageJsonPath = path.join(repoRoot, packageDir, "package.json");
return (
fs.existsSync(packageJsonPath) && isPublishablePluginPackage(readJsonFile(packageJsonPath))
);
})
.toSorted((left, right) => left.localeCompare(right));
}
export function listPluginNpmRuntimeBuildOutputs(plan) {
return Object.keys(plan.entry)
.map((entryKey) => `./dist/${entryKey}.js`)
.toSorted((left, right) => left.localeCompare(right));
}
export function resolvePluginNpmRuntimePackageFiles(plan) {
const merged = new Set(
Array.isArray(plan.packageJson.files)
? plan.packageJson.files.filter((entry) => typeof entry === "string")
: [],
);
merged.add("dist/**");
if (packageRelativePathExists(plan.packageDir, "openclaw.plugin.json")) {
merged.add("openclaw.plugin.json");
}
if (packageRelativePathExists(plan.packageDir, "README.md")) {
merged.add("README.md");
}
if (packageRelativePathExists(plan.packageDir, "SKILL.md")) {
merged.add("SKILL.md");
}
if (packageRelativePathExists(plan.packageDir, "skills")) {
merged.add("skills/**");
}
return [...merged];
}
export function resolvePluginNpmRuntimeBuildPlan(params) {
const repoRoot = path.resolve(params.repoRoot ?? ".");
const packageDir = resolvePackageDir(repoRoot, params.packageDir);
@@ -72,7 +124,7 @@ export function resolvePluginNpmRuntimeBuildPlan(params) {
return null;
}
const packageJson = readJsonFile(packageJsonPath);
if (packageJson.openclaw?.release?.publishToNpm !== true) {
if (!isPublishablePluginPackage(packageJson)) {
return null;
}
@@ -96,7 +148,7 @@ export function resolvePluginNpmRuntimeBuildPlan(params) {
]),
);
return {
const plan = {
repoRoot,
packageDir,
pluginDir,
@@ -115,6 +167,11 @@ export function resolvePluginNpmRuntimeBuildPlan(params) {
? toPackageRuntimeEntry(packageJson.openclaw.setupEntry)
: undefined,
};
return {
...plan,
runtimeBuildOutputs: listPluginNpmRuntimeBuildOutputs(plan),
packageFiles: resolvePluginNpmRuntimePackageFiles(plan),
};
}
export async function buildPluginNpmRuntime(params) {