fix(ci): verify bundled plugin runtime deps

This commit is contained in:
Vincent Koc
2026-04-13 10:47:50 +01:00
parent 9e2ac8a1cb
commit 21ca387eda
7 changed files with 175 additions and 2 deletions

View File

@@ -45,6 +45,18 @@ function collectPackageJsonPaths(rootDir) {
.toSorted((left, right) => left.localeCompare(right));
}
function usesStagedRuntimeDependencies(packageJson) {
return packageJson?.openclaw?.bundle?.stageRuntimeDependencies === true;
}
function dependencySentinelPath(packageRoot, dependencyName) {
return path.join(packageRoot, "node_modules", ...dependencyName.split("/"), "package.json");
}
function pluginIdFromPackageJsonPath(packageJsonPath) {
return path.basename(path.dirname(packageJsonPath));
}
export function collectBundledPluginRuntimeDependencySpecs(bundledPluginsDir) {
const specs = new Map();
@@ -68,6 +80,30 @@ export function collectBundledPluginRuntimeDependencySpecs(bundledPluginsDir) {
return specs;
}
export function collectBuiltBundledPluginStagedRuntimeDependencyErrors(params) {
const errors = [];
for (const packageJsonPath of collectPackageJsonPaths(params.bundledPluginsDir)) {
const packageJson = readJson(packageJsonPath);
if (!usesStagedRuntimeDependencies(packageJson)) {
continue;
}
const pluginId = pluginIdFromPackageJsonPath(packageJsonPath);
const pluginRoot = path.dirname(packageJsonPath);
for (const [dependencyName, spec] of collectRuntimeDependencySpecs(packageJson)) {
if (!fs.existsSync(dependencySentinelPath(pluginRoot, dependencyName))) {
const specText = String(spec);
errors.push(
`built bundled plugin '${pluginId}' is missing staged runtime dependency '${dependencyName}: ${specText}' under dist/extensions/${pluginId}/node_modules.`,
);
}
}
}
return errors.toSorted((left, right) => left.localeCompare(right));
}
function walkJavaScriptFiles(rootDir) {
const files = [];
if (!fs.existsSync(rootDir)) {

View File

@@ -12,6 +12,7 @@ import {
} from "./lib/bundled-extension-manifest.ts";
import { listBundledPluginPackArtifacts } from "./lib/bundled-plugin-build-entries.mjs";
import {
collectBuiltBundledPluginStagedRuntimeDependencyErrors,
collectBundledPluginRootRuntimeMirrorErrors,
collectBundledPluginRuntimeDependencySpecs,
collectRootDistBundledRuntimeMirrors,
@@ -22,6 +23,7 @@ import { sparkleBuildFloorsFromShortVersion, type SparkleBuildFloors } from "./s
export { collectBundledExtensionManifestErrors } from "./lib/bundled-extension-manifest.ts";
export {
collectBuiltBundledPluginStagedRuntimeDependencyErrors,
collectBundledPluginRootRuntimeMirrorErrors,
collectRootDistBundledRuntimeMirrors,
packageNameFromSpecifier,
@@ -109,7 +111,10 @@ function checkBundledExtensionMetadata() {
requiredRootMirrors,
rootPackageJson: rootPackage,
});
const errors = [...manifestErrors, ...rootMirrorErrors];
const builtArtifactErrors = collectBuiltBundledPluginStagedRuntimeDependencyErrors({
bundledPluginsDir: resolve("dist/extensions"),
});
const errors = [...manifestErrors, ...rootMirrorErrors, ...builtArtifactErrors];
if (errors.length > 0) {
console.error("release-check: bundled extension manifest validation failed:");
for (const error of errors) {

View File

@@ -0,0 +1,63 @@
import assert from "node:assert/strict";
import fs from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";
import {
collectBuiltBundledPluginStagedRuntimeDependencyErrors,
collectBundledPluginRootRuntimeMirrorErrors,
collectBundledPluginRuntimeDependencySpecs,
collectRootDistBundledRuntimeMirrors,
} from "./lib/bundled-plugin-root-runtime-mirrors.mjs";
function parseArgs(argv) {
let packageRoot = process.env.OPENCLAW_BUNDLED_RUNTIME_DEPS_ROOT;
for (let index = 0; index < argv.length; index += 1) {
const arg = argv[index];
if (arg === "--package-root") {
packageRoot = argv[index + 1];
index += 1;
continue;
}
if (arg?.startsWith("--package-root=")) {
packageRoot = arg.slice("--package-root=".length);
continue;
}
throw new Error(`unknown argument: ${arg}`);
}
return {
packageRoot: path.resolve(
packageRoot ?? path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".."),
),
};
}
const { packageRoot } = parseArgs(process.argv.slice(2));
const rootPackageJsonPath = path.join(packageRoot, "package.json");
const builtPluginsDir = path.join(packageRoot, "dist", "extensions");
assert.ok(fs.existsSync(rootPackageJsonPath), `package.json missing from ${packageRoot}`);
assert.ok(fs.existsSync(builtPluginsDir), `built bundled plugins missing from ${builtPluginsDir}`);
const rootPackageJson = JSON.parse(fs.readFileSync(rootPackageJsonPath, "utf8"));
const bundledRuntimeDependencySpecs = collectBundledPluginRuntimeDependencySpecs(
path.join(packageRoot, "extensions"),
);
const requiredRootMirrors = collectRootDistBundledRuntimeMirrors({
bundledRuntimeDependencySpecs,
distDir: path.join(packageRoot, "dist"),
});
const errors = [
...collectBundledPluginRootRuntimeMirrorErrors({
bundledRuntimeDependencySpecs,
requiredRootMirrors,
rootPackageJson,
}),
...collectBuiltBundledPluginStagedRuntimeDependencyErrors({
bundledPluginsDir: builtPluginsDir,
}),
];
assert.deepEqual(errors, [], errors.join("\n"));
process.stdout.write(
`[build-smoke] bundled runtime dependency smoke passed packageRoot=${packageRoot}\n`,
);