mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-12 21:53:00 -06:00
fix(gateway): restore runtime postbuild freshness
This commit is contained in:
committed by
Peter Steinberger
parent
0a13bd5841
commit
17643e549f
+171
-2
@@ -4,6 +4,10 @@ import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import process from "node:process";
|
||||
import { pathToFileURL } from "node:url";
|
||||
import {
|
||||
collectBundledPluginBuildEntries,
|
||||
NON_PACKAGED_BUNDLED_PLUGIN_DIRS,
|
||||
} from "./lib/bundled-plugin-build-entries.mjs";
|
||||
import {
|
||||
BUNDLED_PLUGIN_PATH_PREFIX,
|
||||
BUNDLED_PLUGIN_ROOT_DIR,
|
||||
@@ -67,6 +71,11 @@ const resolvePrivateQaRequiredDistEntries = (distRoot) => [
|
||||
path.join(distRoot, "plugin-sdk", "qa-lab.js"),
|
||||
path.join(distRoot, "plugin-sdk", "qa-runtime.js"),
|
||||
];
|
||||
const shouldIncludePrivateQaBundledOutputs = (env = process.env) =>
|
||||
env.OPENCLAW_BUILD_PRIVATE_QA === "1";
|
||||
|
||||
const shouldRequireBundledPluginRuntimeOutput = (pluginId, env = process.env) =>
|
||||
shouldIncludePrivateQaBundledOutputs(env) || !NON_PACKAGED_BUNDLED_PLUGIN_DIRS.has(pluginId);
|
||||
|
||||
const isExcludedSource = (filePath, sourceRoot, sourceRootName) => {
|
||||
const relativePath = normalizePath(path.relative(sourceRoot, filePath));
|
||||
@@ -146,7 +155,13 @@ const hasDirtySourceTree = (deps) => {
|
||||
if (output === null) {
|
||||
return null;
|
||||
}
|
||||
return parseGitStatusPaths(output).some((repoPath) => isBuildRelevantRunNodePath(repoPath));
|
||||
return parseGitStatusPaths(output).some((repoPath) => {
|
||||
const normalizedPath = normalizePath(repoPath).replace(/^\.\/+/, "");
|
||||
return (
|
||||
isBuildRelevantRunNodePath(normalizedPath) ||
|
||||
isDirtyBundledPluginPackageEntryChangeWithoutBuiltOutputs(normalizedPath, deps)
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
const isRuntimePostBuildRelevantPath = (repoPath) => {
|
||||
@@ -167,7 +182,8 @@ const isRuntimePostBuildRelevantPath = (repoPath) => {
|
||||
return false;
|
||||
}
|
||||
const pluginRelativePath = normalizedPath.slice(BUNDLED_PLUGIN_PATH_PREFIX.length);
|
||||
if (pluginRelativePath.startsWith("skills/")) {
|
||||
const pluginLocalPath = pluginRelativePath.split("/").slice(1).join("/");
|
||||
if (pluginLocalPath === "skills" || pluginLocalPath.startsWith("skills/")) {
|
||||
return true;
|
||||
}
|
||||
return extensionRestartMetadataFiles.has(path.posix.basename(pluginRelativePath));
|
||||
@@ -257,6 +273,143 @@ const hasRuntimePostBuildInputMtimeChanged = (stampMtime, deps) => {
|
||||
return latestInputMtime != null && latestInputMtime > stampMtime;
|
||||
};
|
||||
|
||||
const resolveRuntimePostBuildDistRoot = (deps) => deps.distRoot ?? path.join(deps.cwd, "dist");
|
||||
const resolveRuntimePostBuildRuntimeRoot = (deps) => path.join(deps.cwd, "dist-runtime");
|
||||
|
||||
const collectRunNodeBundledPluginBuildEntries = (deps) => {
|
||||
if (!deps.fs.existsSync(path.join(deps.cwd, BUNDLED_PLUGIN_ROOT_DIR))) {
|
||||
return [];
|
||||
}
|
||||
return collectBundledPluginBuildEntries({ cwd: deps.cwd, env: deps.env });
|
||||
};
|
||||
|
||||
const resolveBuiltBundledPluginRuntimeEntryPath = (distRoot, pluginId, sourceEntry) =>
|
||||
path.join(
|
||||
distRoot,
|
||||
"extensions",
|
||||
pluginId,
|
||||
sourceEntry.replace(/^\.\//, "").replace(/\.[^.]+$/u, ".js"),
|
||||
);
|
||||
|
||||
const listBundledPluginRuntimeEntryPaths = (pluginEntry, deps) => {
|
||||
const distRoot = resolveRuntimePostBuildDistRoot(deps);
|
||||
return pluginEntry.sourceEntries
|
||||
.map((sourceEntry) =>
|
||||
resolveBuiltBundledPluginRuntimeEntryPath(distRoot, pluginEntry.id, sourceEntry),
|
||||
)
|
||||
.toSorted((left, right) => left.localeCompare(right));
|
||||
};
|
||||
|
||||
const isDirtyBundledPluginPackageEntryChangeWithoutBuiltOutputs = (normalizedPath, deps) => {
|
||||
if (!normalizedPath.startsWith("extensions/") || !normalizedPath.endsWith("/package.json")) {
|
||||
return false;
|
||||
}
|
||||
const [, pluginId] = normalizedPath.split("/");
|
||||
if (!pluginId || !shouldRequireBundledPluginRuntimeOutput(pluginId, deps.env)) {
|
||||
return false;
|
||||
}
|
||||
const pluginEntry = collectRunNodeBundledPluginBuildEntries(deps).find(
|
||||
(entry) => entry.id === pluginId,
|
||||
);
|
||||
if (!pluginEntry) {
|
||||
return false;
|
||||
}
|
||||
return listBundledPluginRuntimeEntryPaths(pluginEntry, deps).some(
|
||||
(filePath) => !deps.fs.existsSync(filePath),
|
||||
);
|
||||
};
|
||||
|
||||
const hasMissingBuiltBundledPluginRuntimeEntryOutput = (deps) => {
|
||||
return collectRunNodeBundledPluginBuildEntries(deps)
|
||||
.filter(({ id }) => shouldRequireBundledPluginRuntimeOutput(id, deps.env))
|
||||
.some((pluginEntry) => {
|
||||
const entryPaths = listBundledPluginRuntimeEntryPaths(pluginEntry, deps);
|
||||
return entryPaths.some((filePath) => !deps.fs.existsSync(filePath));
|
||||
});
|
||||
};
|
||||
|
||||
const listBuiltBundledPluginEntries = (deps) => {
|
||||
return collectRunNodeBundledPluginBuildEntries(deps)
|
||||
.filter(({ id }) => shouldRequireBundledPluginRuntimeOutput(id, deps.env))
|
||||
.filter((pluginEntry) =>
|
||||
listBundledPluginRuntimeEntryPaths(pluginEntry, deps).some((filePath) =>
|
||||
deps.fs.existsSync(filePath),
|
||||
),
|
||||
)
|
||||
.toSorted((left, right) => left.id.localeCompare(right.id));
|
||||
};
|
||||
|
||||
const listRequiredBundledPluginMetadataOutputs = (pluginEntries, deps) =>
|
||||
pluginEntries.flatMap(({ id, hasManifest, hasPackageJson }) => {
|
||||
const builtPluginDir = path.join(resolveRuntimePostBuildDistRoot(deps), "extensions", id);
|
||||
const requiredPaths = [];
|
||||
if (hasPackageJson) {
|
||||
requiredPaths.push(path.join(builtPluginDir, "package.json"));
|
||||
}
|
||||
if (hasManifest) {
|
||||
requiredPaths.push(path.join(builtPluginDir, "openclaw.plugin.json"));
|
||||
}
|
||||
return requiredPaths;
|
||||
});
|
||||
|
||||
const listRuntimeOverlaySourcePaths = (sourceDir, deps) => {
|
||||
const paths = [];
|
||||
const queue = [sourceDir];
|
||||
while (queue.length > 0) {
|
||||
const current = queue.pop();
|
||||
if (!current) {
|
||||
continue;
|
||||
}
|
||||
let entries = [];
|
||||
try {
|
||||
entries = deps.fs.readdirSync(current, { withFileTypes: true });
|
||||
} catch {
|
||||
continue;
|
||||
}
|
||||
for (const entry of entries) {
|
||||
if (entry.name === "node_modules") {
|
||||
continue;
|
||||
}
|
||||
const entryPath = path.join(current, entry.name);
|
||||
if (entry.isDirectory()) {
|
||||
queue.push(entryPath);
|
||||
continue;
|
||||
}
|
||||
if (entry.isFile() || entry.isSymbolicLink()) {
|
||||
paths.push(entryPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
return paths.toSorted((left, right) => left.localeCompare(right));
|
||||
};
|
||||
|
||||
const listRequiredBundledPluginRuntimeOverlayOutputs = (pluginEntries, deps) => {
|
||||
const distRoot = resolveRuntimePostBuildDistRoot(deps);
|
||||
const runtimeRoot = resolveRuntimePostBuildRuntimeRoot(deps);
|
||||
const runtimePaths = [];
|
||||
for (const pluginEntry of pluginEntries) {
|
||||
const distPluginDir = path.join(distRoot, "extensions", pluginEntry.id);
|
||||
const runtimePluginDir = path.join(runtimeRoot, "extensions", pluginEntry.id);
|
||||
for (const sourcePath of listRuntimeOverlaySourcePaths(distPluginDir, deps)) {
|
||||
runtimePaths.push(path.join(runtimePluginDir, path.relative(distPluginDir, sourcePath)));
|
||||
}
|
||||
}
|
||||
return [...new Set(runtimePaths)].toSorted((left, right) => left.localeCompare(right));
|
||||
};
|
||||
|
||||
const listRequiredRuntimePostBuildOutputs = (deps) => {
|
||||
const builtPluginEntries = listBuiltBundledPluginEntries(deps);
|
||||
return [
|
||||
...listRequiredBundledPluginMetadataOutputs(builtPluginEntries, deps),
|
||||
...listRequiredBundledPluginRuntimeOverlayOutputs(builtPluginEntries, deps),
|
||||
];
|
||||
};
|
||||
|
||||
const hasMissingRequiredRuntimePostBuildOutput = (deps) =>
|
||||
listRequiredRuntimePostBuildOutputs(deps).some(
|
||||
(filePath) => statMtime(filePath, deps.fs) == null,
|
||||
);
|
||||
|
||||
export const resolveBuildRequirement = (deps) => {
|
||||
if (deps.env.OPENCLAW_FORCE_BUILD === "1") {
|
||||
return { shouldBuild: true, reason: "force_build" };
|
||||
@@ -297,10 +450,17 @@ export const resolveBuildRequirement = (deps) => {
|
||||
return { shouldBuild: true, reason: "dirty_watched_tree" };
|
||||
}
|
||||
if (dirty === false) {
|
||||
if (hasMissingBuiltBundledPluginRuntimeEntryOutput(deps)) {
|
||||
return { shouldBuild: true, reason: "missing_bundled_plugin_dist_entry" };
|
||||
}
|
||||
return { shouldBuild: false, reason: "clean" };
|
||||
}
|
||||
}
|
||||
|
||||
if (hasMissingBuiltBundledPluginRuntimeEntryOutput(deps)) {
|
||||
return { shouldBuild: true, reason: "missing_bundled_plugin_dist_entry" };
|
||||
}
|
||||
|
||||
if (hasSourceMtimeChanged(stamp.mtime, deps)) {
|
||||
return { shouldBuild: true, reason: "source_mtime_newer" };
|
||||
}
|
||||
@@ -338,6 +498,9 @@ export const resolveRuntimePostBuildRequirement = (deps) => {
|
||||
return { shouldSync: true, reason: "dirty_runtime_postbuild_inputs" };
|
||||
}
|
||||
if (dirty === false) {
|
||||
if (hasMissingRequiredRuntimePostBuildOutput(deps)) {
|
||||
return { shouldSync: true, reason: "missing_runtime_postbuild_output" };
|
||||
}
|
||||
return { shouldSync: false, reason: "clean" };
|
||||
}
|
||||
}
|
||||
@@ -346,6 +509,10 @@ export const resolveRuntimePostBuildRequirement = (deps) => {
|
||||
return { shouldSync: true, reason: "runtime_postbuild_input_mtime_newer" };
|
||||
}
|
||||
|
||||
if (hasMissingRequiredRuntimePostBuildOutput(deps)) {
|
||||
return { shouldSync: true, reason: "missing_runtime_postbuild_output" };
|
||||
}
|
||||
|
||||
return { shouldSync: false, reason: "clean" };
|
||||
};
|
||||
|
||||
@@ -357,6 +524,7 @@ const BUILD_REASON_LABELS = {
|
||||
build_stamp_missing_head: "build stamp missing git head",
|
||||
git_head_changed: "git head changed",
|
||||
dirty_watched_tree: "dirty watched source tree",
|
||||
missing_bundled_plugin_dist_entry: "bundled plugin dist entry missing",
|
||||
source_mtime_newer: "source mtime newer than build stamp",
|
||||
missing_private_qa_dist: "private QA dist entry missing",
|
||||
clean: "clean",
|
||||
@@ -364,6 +532,7 @@ const BUILD_REASON_LABELS = {
|
||||
|
||||
const RUNTIME_POSTBUILD_REASON_LABELS = {
|
||||
force_runtime_postbuild: "forced by OPENCLAW_FORCE_RUNTIME_POSTBUILD",
|
||||
missing_runtime_postbuild_output: "required runtime postbuild output missing",
|
||||
missing_runtime_postbuild_stamp: "runtime postbuild stamp missing",
|
||||
missing_build_stamp: "build stamp missing",
|
||||
build_stamp_newer: "build stamp newer than runtime postbuild stamp",
|
||||
|
||||
Reference in New Issue
Block a user