mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 05:40:44 +00:00
refactor(plugins): centralize npm runtime package planning
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -1,37 +1,15 @@
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { resolvePluginNpmRuntimeBuildPlan } from "../scripts/lib/plugin-npm-runtime-build.mjs";
|
||||
import {
|
||||
listPublishablePluginPackageDirs,
|
||||
resolvePluginNpmRuntimeBuildPlan,
|
||||
} from "../scripts/lib/plugin-npm-runtime-build.mjs";
|
||||
|
||||
const repoRoot = path.resolve(import.meta.dirname, "..");
|
||||
|
||||
function readJsonFile(filePath: string): Record<string, unknown> {
|
||||
return JSON.parse(fs.readFileSync(filePath, "utf8")) as Record<string, unknown>;
|
||||
}
|
||||
|
||||
function isPublishablePluginPackage(packageJson: Record<string, unknown>): boolean {
|
||||
const openclaw = packageJson.openclaw as { release?: { publishToNpm?: unknown } } | undefined;
|
||||
return openclaw?.release?.publishToNpm === true;
|
||||
}
|
||||
|
||||
function listPublishablePluginPackageDirs(): string[] {
|
||||
const extensionsRoot = path.join(repoRoot, "extensions");
|
||||
return fs
|
||||
.readdirSync(extensionsRoot, { withFileTypes: true })
|
||||
.filter((dirent) => dirent.isDirectory())
|
||||
.map((dirent) => path.join(extensionsRoot, dirent.name))
|
||||
.filter((packageDir) => {
|
||||
const packageJsonPath = path.join(packageDir, "package.json");
|
||||
return (
|
||||
fs.existsSync(packageJsonPath) && isPublishablePluginPackage(readJsonFile(packageJsonPath))
|
||||
);
|
||||
})
|
||||
.toSorted((left, right) => left.localeCompare(right));
|
||||
}
|
||||
|
||||
describe("plugin npm runtime build planning", () => {
|
||||
it("plans package-local runtime entries for every publishable plugin package", () => {
|
||||
const packageDirs = listPublishablePluginPackageDirs();
|
||||
const packageDirs = listPublishablePluginPackageDirs({ repoRoot });
|
||||
expect(packageDirs.length).toBeGreaterThan(0);
|
||||
|
||||
const plans = packageDirs.map((packageDir) =>
|
||||
@@ -46,6 +24,8 @@ describe("plugin npm runtime build planning", () => {
|
||||
for (const plan of plans) {
|
||||
expect(plan?.outDir).toBe(path.join(plan?.packageDir ?? "", "dist"));
|
||||
expect(plan?.runtimeExtensions.every((entry) => entry.startsWith("./dist/"))).toBe(true);
|
||||
expect(plan?.runtimeBuildOutputs.every((entry) => entry.startsWith("./dist/"))).toBe(true);
|
||||
expect(plan?.packageFiles).toContain("dist/**");
|
||||
}
|
||||
});
|
||||
|
||||
@@ -75,5 +55,11 @@ describe("plugin npm runtime build planning", () => {
|
||||
"runtime-api": path.join(repoRoot, "extensions", "diffs", "runtime-api.ts"),
|
||||
}),
|
||||
);
|
||||
expect(diffsPlan?.packageFiles).toEqual([
|
||||
"dist/**",
|
||||
"openclaw.plugin.json",
|
||||
"README.md",
|
||||
"skills/**",
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user