mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 06:50:43 +00:00
refactor(plugins): declare static runtime assets in package metadata
This commit is contained in:
@@ -28,7 +28,21 @@
|
||||
"pluginApi": ">=2026.5.3"
|
||||
},
|
||||
"build": {
|
||||
"openclawVersion": "2026.5.3"
|
||||
"openclawVersion": "2026.5.3",
|
||||
"staticAssets": [
|
||||
{
|
||||
"source": "./src/runtime-internals/mcp-proxy.mjs",
|
||||
"output": "mcp-proxy.mjs"
|
||||
},
|
||||
{
|
||||
"source": "./src/runtime-internals/error-format.mjs",
|
||||
"output": "error-format.mjs"
|
||||
},
|
||||
{
|
||||
"source": "./src/runtime-internals/mcp-command-line.mjs",
|
||||
"output": "mcp-command-line.mjs"
|
||||
}
|
||||
]
|
||||
},
|
||||
"release": {
|
||||
"publishToClawHub": true,
|
||||
|
||||
@@ -33,7 +33,13 @@
|
||||
"pluginApi": ">=2026.5.3"
|
||||
},
|
||||
"build": {
|
||||
"openclawVersion": "2026.5.3"
|
||||
"openclawVersion": "2026.5.3",
|
||||
"staticAssets": [
|
||||
{
|
||||
"source": "./assets/viewer-runtime.js",
|
||||
"output": "assets/viewer-runtime.js"
|
||||
}
|
||||
]
|
||||
},
|
||||
"release": {
|
||||
"publishToClawHub": true,
|
||||
|
||||
@@ -1,42 +1,88 @@
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
|
||||
/**
|
||||
* Static, non-transpiled runtime assets referenced by built extension code.
|
||||
*
|
||||
* `dest` is the root-package dist path. Package-local runtime builds rewrite it
|
||||
* under the plugin package's own dist directory.
|
||||
*/
|
||||
export const STATIC_EXTENSION_ASSETS = [
|
||||
{
|
||||
src: "extensions/acpx/src/runtime-internals/mcp-proxy.mjs",
|
||||
dest: "dist/extensions/acpx/mcp-proxy.mjs",
|
||||
},
|
||||
{
|
||||
src: "extensions/acpx/src/runtime-internals/error-format.mjs",
|
||||
dest: "dist/extensions/acpx/error-format.mjs",
|
||||
},
|
||||
{
|
||||
src: "extensions/acpx/src/runtime-internals/mcp-command-line.mjs",
|
||||
dest: "dist/extensions/acpx/mcp-command-line.mjs",
|
||||
},
|
||||
{
|
||||
src: "extensions/diffs/assets/viewer-runtime.js",
|
||||
dest: "dist/extensions/diffs/assets/viewer-runtime.js",
|
||||
},
|
||||
];
|
||||
function toPosixPath(value) {
|
||||
return String(value ?? "").replaceAll("\\", "/");
|
||||
}
|
||||
|
||||
function readJsonFile(filePath, fsImpl) {
|
||||
return JSON.parse(fsImpl.readFileSync(filePath, "utf8"));
|
||||
}
|
||||
|
||||
function normalizePackageRelativePath(value) {
|
||||
const normalized = toPosixPath(value)
|
||||
.trim()
|
||||
.replace(/^\.\/+/u, "");
|
||||
if (!normalized || normalized.startsWith("../") || normalized.includes("/../")) {
|
||||
return "";
|
||||
}
|
||||
return normalized;
|
||||
}
|
||||
|
||||
function listExtensionPackageDirs(rootDir, fsImpl) {
|
||||
const extensionsRoot = path.join(rootDir, "extensions");
|
||||
if (!fsImpl.existsSync(extensionsRoot)) {
|
||||
return [];
|
||||
}
|
||||
return fsImpl
|
||||
.readdirSync(extensionsRoot, { withFileTypes: true })
|
||||
.filter((entry) => entry.isDirectory())
|
||||
.map((entry) => ({
|
||||
dirName: entry.name,
|
||||
packageDir: path.join(extensionsRoot, entry.name),
|
||||
}))
|
||||
.toSorted((left, right) => left.dirName.localeCompare(right.dirName));
|
||||
}
|
||||
|
||||
function readPackageStaticAssetEntries(packageJson) {
|
||||
const entries = packageJson.openclaw?.build?.staticAssets;
|
||||
return Array.isArray(entries) ? entries : [];
|
||||
}
|
||||
|
||||
export function discoverStaticExtensionAssets(params = {}) {
|
||||
const rootDir = params.rootDir ?? process.cwd();
|
||||
const fsImpl = params.fs ?? fs;
|
||||
const assets = [];
|
||||
for (const { dirName, packageDir } of listExtensionPackageDirs(rootDir, fsImpl)) {
|
||||
const packageJsonPath = path.join(packageDir, "package.json");
|
||||
if (!fsImpl.existsSync(packageJsonPath)) {
|
||||
continue;
|
||||
}
|
||||
const packageJson = readJsonFile(packageJsonPath, fsImpl);
|
||||
for (const entry of readPackageStaticAssetEntries(packageJson)) {
|
||||
const source = normalizePackageRelativePath(entry?.source);
|
||||
const output = normalizePackageRelativePath(entry?.output);
|
||||
if (!source || !output) {
|
||||
continue;
|
||||
}
|
||||
assets.push({
|
||||
pluginDir: dirName,
|
||||
src: toPosixPath(path.posix.join("extensions", dirName, source)),
|
||||
dest: toPosixPath(path.posix.join("dist", "extensions", dirName, output)),
|
||||
});
|
||||
}
|
||||
}
|
||||
return assets.toSorted((left, right) => left.dest.localeCompare(right.dest));
|
||||
}
|
||||
|
||||
export function listStaticExtensionAssetOutputs(params = {}) {
|
||||
const assets = params.assets ?? STATIC_EXTENSION_ASSETS;
|
||||
const assets = params.assets ?? discoverStaticExtensionAssets(params);
|
||||
return assets
|
||||
.map(({ dest }) => dest.replace(/\\/g, "/"))
|
||||
.toSorted((left, right) => left.localeCompare(right));
|
||||
}
|
||||
|
||||
export function listStaticExtensionAssetSources(params = {}) {
|
||||
const assets = params.assets ?? discoverStaticExtensionAssets(params);
|
||||
return assets
|
||||
.map(({ src }) => src.replace(/\\/g, "/"))
|
||||
.toSorted((left, right) => left.localeCompare(right));
|
||||
}
|
||||
|
||||
export function copyStaticExtensionAssets(params = {}) {
|
||||
const rootDir = params.rootDir ?? process.cwd();
|
||||
const assets = params.assets ?? STATIC_EXTENSION_ASSETS;
|
||||
const fsImpl = params.fs ?? fs;
|
||||
const assets = params.assets ?? discoverStaticExtensionAssets({ rootDir, fs: fsImpl });
|
||||
const warn = params.warn ?? console.warn;
|
||||
for (const { src, dest } of assets) {
|
||||
const srcPath = path.join(rootDir, src);
|
||||
@@ -52,8 +98,8 @@ export function copyStaticExtensionAssets(params = {}) {
|
||||
|
||||
export function copyStaticExtensionAssetsForPackage(params) {
|
||||
const rootDir = params.rootDir ?? process.cwd();
|
||||
const assets = params.assets ?? STATIC_EXTENSION_ASSETS;
|
||||
const fsImpl = params.fs ?? fs;
|
||||
const assets = params.assets ?? discoverStaticExtensionAssets({ rootDir, fs: fsImpl });
|
||||
const packagePrefix = `extensions/${params.pluginDir}/`;
|
||||
const rootDistPrefix = `dist/extensions/${params.pluginDir}/`;
|
||||
const copied = [];
|
||||
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
writeBuildStamp as writeDistBuildStamp,
|
||||
writeRuntimePostBuildStamp as writeDistRuntimePostBuildStamp,
|
||||
} from "./lib/local-build-metadata.mjs";
|
||||
import { listStaticExtensionAssetSources } from "./lib/static-extension-assets.mjs";
|
||||
import { runRuntimePostBuild } from "./runtime-postbuild.mjs";
|
||||
|
||||
const buildScript = "scripts/tsdown-build.mjs";
|
||||
@@ -46,10 +47,7 @@ const ignoredRunNodeRepoPaths = new Set([
|
||||
const runtimePostBuildScriptPaths = new Set(
|
||||
runtimePostBuildWatchedPaths.filter((entry) => entry.startsWith("scripts/")),
|
||||
);
|
||||
const runtimePostBuildStaticAssetPaths = new Set([
|
||||
"extensions/acpx/src/runtime-internals/mcp-proxy.mjs",
|
||||
"extensions/diffs/assets/viewer-runtime.js",
|
||||
]);
|
||||
const runtimePostBuildStaticAssetPaths = new Set(listStaticExtensionAssetSources());
|
||||
const extensionSourceFilePattern = /\.(?:[cm]?[jt]sx?)$/;
|
||||
const extensionRestartMetadataFiles = new Set(["openclaw.plugin.json", "package.json"]);
|
||||
|
||||
|
||||
@@ -24,6 +24,10 @@ function collectRootPackageExcludedRuntimeSidecarPluginDirs(rootDir: string): Se
|
||||
if (typeof entry !== "string") {
|
||||
continue;
|
||||
}
|
||||
// The root package intentionally excludes externalized official plugin
|
||||
// runtime trees. Do not put their runtime sidecars in the root package
|
||||
// baseline: packaged installs must load those files from the plugin's own
|
||||
// npm package-local dist directory instead.
|
||||
const match = /^!dist\/extensions\/([^/]+)\/\*\*$/u.exec(entry);
|
||||
if (match?.[1]) {
|
||||
excluded.add(match[1]);
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import bundledRuntimeSidecarPaths from "../../scripts/lib/bundled-runtime-sidecar-paths.json" with { type: "json" };
|
||||
|
||||
// Keep this JSON as the root package's runtime sidecar inventory only. Official
|
||||
// plugin packages that are excluded from root package files must ship their
|
||||
// sidecars from their own npm package-local dist directory instead.
|
||||
export function assertUniqueValues<T extends string>(
|
||||
values: readonly T[],
|
||||
label: string,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import fs from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { discoverStaticExtensionAssets } from "../../scripts/lib/static-extension-assets.mjs";
|
||||
import {
|
||||
copyStaticExtensionAssets,
|
||||
listStaticExtensionAssetOutputs,
|
||||
@@ -23,6 +24,37 @@ describe("runtime postbuild static assets", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("discovers static assets from plugin package metadata", async () => {
|
||||
const rootDir = createTempDir("openclaw-runtime-postbuild-");
|
||||
const packageDir = path.join(rootDir, "extensions", "demo");
|
||||
await fs.mkdir(packageDir, { recursive: true });
|
||||
await fs.writeFile(
|
||||
path.join(packageDir, "package.json"),
|
||||
JSON.stringify({
|
||||
name: "@openclaw/demo",
|
||||
openclaw: {
|
||||
build: {
|
||||
staticAssets: [
|
||||
{
|
||||
source: "./assets/runtime.js",
|
||||
output: "assets/runtime.js",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
}),
|
||||
"utf8",
|
||||
);
|
||||
|
||||
expect(discoverStaticExtensionAssets({ rootDir })).toEqual([
|
||||
{
|
||||
pluginDir: "demo",
|
||||
src: "extensions/demo/assets/runtime.js",
|
||||
dest: "dist/extensions/demo/assets/runtime.js",
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it("copies declared static assets into dist", async () => {
|
||||
const rootDir = createTempDir("openclaw-runtime-postbuild-");
|
||||
const src = "extensions/acpx/src/runtime-internals/mcp-proxy.mjs";
|
||||
|
||||
Reference in New Issue
Block a user