mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 10:30:44 +00:00
fix: skip clean run-node runtime restaging
This commit is contained in:
@@ -17,10 +17,32 @@ const compilerArgs = [buildScript, "--no-clean"];
|
||||
const runNodeSourceRoots = ["src", BUNDLED_PLUGIN_ROOT_DIR];
|
||||
const runNodeConfigFiles = ["tsconfig.json", "package.json", "tsdown.config.ts"];
|
||||
export const runNodeWatchedPaths = [...runNodeSourceRoots, ...runNodeConfigFiles];
|
||||
const runtimePostBuildStampFile = ".runtime-postbuildstamp";
|
||||
const runtimePostBuildWatchedPaths = [
|
||||
"scripts/copy-bundled-plugin-metadata.mjs",
|
||||
"scripts/copy-plugin-sdk-root-alias.mjs",
|
||||
"scripts/lib",
|
||||
"scripts/npm-runner.mjs",
|
||||
"scripts/runtime-postbuild-shared.mjs",
|
||||
"scripts/runtime-postbuild.mjs",
|
||||
"scripts/stage-bundled-plugin-runtime-deps.mjs",
|
||||
"scripts/stage-bundled-plugin-runtime.mjs",
|
||||
"scripts/windows-cmd-helpers.mjs",
|
||||
"scripts/write-official-channel-catalog.mjs",
|
||||
"src/plugin-sdk/root-alias.cjs",
|
||||
BUNDLED_PLUGIN_ROOT_DIR,
|
||||
];
|
||||
const ignoredRunNodeRepoPaths = new Set([
|
||||
"src/canvas-host/a2ui/.bundle.hash",
|
||||
"src/canvas-host/a2ui/a2ui.bundle.js",
|
||||
]);
|
||||
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 extensionSourceFilePattern = /\.(?:[cm]?[jt]sx?)$/;
|
||||
const extensionRestartMetadataFiles = new Set(["openclaw.plugin.json", "package.json"]);
|
||||
|
||||
@@ -130,11 +152,11 @@ const findLatestMtime = (dirPath, shouldSkip, deps) => {
|
||||
return latest;
|
||||
};
|
||||
|
||||
const readGitStatus = (deps) => {
|
||||
const readGitStatus = (deps, paths = runNodeWatchedPaths) => {
|
||||
try {
|
||||
const result = deps.spawnSync(
|
||||
"git",
|
||||
["status", "--porcelain", "--untracked-files=normal", "--", ...runNodeWatchedPaths],
|
||||
["status", "--porcelain", "--untracked-files=normal", "--", ...paths],
|
||||
{
|
||||
cwd: deps.cwd,
|
||||
encoding: "utf8",
|
||||
@@ -165,6 +187,38 @@ const hasDirtySourceTree = (deps) => {
|
||||
return parseGitStatusPaths(output).some((repoPath) => isBuildRelevantRunNodePath(repoPath));
|
||||
};
|
||||
|
||||
const isRuntimePostBuildRelevantPath = (repoPath) => {
|
||||
const normalizedPath = normalizePath(repoPath).replace(/^\.\/+/, "");
|
||||
if (normalizedPath === "src/plugin-sdk/root-alias.cjs") {
|
||||
return true;
|
||||
}
|
||||
if (runtimePostBuildStaticAssetPaths.has(normalizedPath)) {
|
||||
return true;
|
||||
}
|
||||
if (
|
||||
normalizedPath.startsWith("scripts/") &&
|
||||
(runtimePostBuildScriptPaths.has(normalizedPath) || normalizedPath.startsWith("scripts/lib/"))
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
if (!normalizedPath.startsWith(BUNDLED_PLUGIN_PATH_PREFIX)) {
|
||||
return false;
|
||||
}
|
||||
const pluginRelativePath = normalizedPath.slice(BUNDLED_PLUGIN_PATH_PREFIX.length);
|
||||
if (pluginRelativePath.startsWith("skills/")) {
|
||||
return true;
|
||||
}
|
||||
return extensionRestartMetadataFiles.has(path.posix.basename(pluginRelativePath));
|
||||
};
|
||||
|
||||
const hasDirtyRuntimePostBuildInputs = (deps) => {
|
||||
const output = readGitStatus(deps, runtimePostBuildWatchedPaths);
|
||||
if (output === null) {
|
||||
return null;
|
||||
}
|
||||
return parseGitStatusPaths(output).some((repoPath) => isRuntimePostBuildRelevantPath(repoPath));
|
||||
};
|
||||
|
||||
const readBuildStamp = (deps) => {
|
||||
const mtime = statMtime(deps.buildStampPath, deps.fs);
|
||||
if (mtime == null) {
|
||||
@@ -183,6 +237,24 @@ const readBuildStamp = (deps) => {
|
||||
}
|
||||
};
|
||||
|
||||
const readRuntimePostBuildStamp = (deps) => {
|
||||
const mtime = statMtime(deps.runtimePostBuildStampPath, deps.fs);
|
||||
if (mtime == null) {
|
||||
return { mtime: null, head: null };
|
||||
}
|
||||
try {
|
||||
const raw = deps.fs.readFileSync(deps.runtimePostBuildStampPath, "utf8").trim();
|
||||
if (!raw.startsWith("{")) {
|
||||
return { mtime, head: null };
|
||||
}
|
||||
const parsed = JSON.parse(raw);
|
||||
const head = typeof parsed?.head === "string" && parsed.head.trim() ? parsed.head.trim() : null;
|
||||
return { mtime, head };
|
||||
} catch {
|
||||
return { mtime, head: null };
|
||||
}
|
||||
};
|
||||
|
||||
const hasSourceMtimeChanged = (stampMtime, deps) => {
|
||||
let latestSourceMtime = null;
|
||||
for (const sourceRoot of deps.sourceRoots) {
|
||||
@@ -198,6 +270,43 @@ const hasSourceMtimeChanged = (stampMtime, deps) => {
|
||||
return latestSourceMtime != null && latestSourceMtime > stampMtime;
|
||||
};
|
||||
|
||||
const findLatestRuntimePostBuildInputMtime = (absolutePath, relativePath, deps) => {
|
||||
const normalizedRelativePath = normalizePath(relativePath);
|
||||
const statsMtime = statMtime(absolutePath, deps.fs);
|
||||
if (statsMtime == null) {
|
||||
return null;
|
||||
}
|
||||
let stat;
|
||||
try {
|
||||
stat = deps.fs.statSync(absolutePath);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
if (!stat.isDirectory()) {
|
||||
return isRuntimePostBuildRelevantPath(normalizedRelativePath) ? statsMtime : null;
|
||||
}
|
||||
return findLatestMtime(
|
||||
absolutePath,
|
||||
(candidate) => {
|
||||
const candidateRelativePath = path.relative(deps.cwd, candidate);
|
||||
return !isRuntimePostBuildRelevantPath(candidateRelativePath);
|
||||
},
|
||||
deps,
|
||||
);
|
||||
};
|
||||
|
||||
const hasRuntimePostBuildInputMtimeChanged = (stampMtime, deps) => {
|
||||
let latestInputMtime = null;
|
||||
for (const relativePath of runtimePostBuildWatchedPaths) {
|
||||
const absolutePath = path.join(deps.cwd, relativePath);
|
||||
const inputMtime = findLatestRuntimePostBuildInputMtime(absolutePath, relativePath, deps);
|
||||
if (inputMtime != null && (latestInputMtime == null || inputMtime > latestInputMtime)) {
|
||||
latestInputMtime = inputMtime;
|
||||
}
|
||||
}
|
||||
return latestInputMtime != null && latestInputMtime > stampMtime;
|
||||
};
|
||||
|
||||
export const resolveBuildRequirement = (deps) => {
|
||||
if (deps.env.OPENCLAW_FORCE_BUILD === "1") {
|
||||
return { shouldBuild: true, reason: "force_build" };
|
||||
@@ -248,6 +357,48 @@ export const resolveBuildRequirement = (deps) => {
|
||||
return { shouldBuild: false, reason: "clean" };
|
||||
};
|
||||
|
||||
export const resolveRuntimePostBuildRequirement = (deps) => {
|
||||
if (deps.env.OPENCLAW_FORCE_RUNTIME_POSTBUILD === "1") {
|
||||
return { shouldSync: true, reason: "force_runtime_postbuild" };
|
||||
}
|
||||
|
||||
const stamp = readRuntimePostBuildStamp(deps);
|
||||
if (stamp.mtime == null) {
|
||||
return { shouldSync: true, reason: "missing_runtime_postbuild_stamp" };
|
||||
}
|
||||
|
||||
const buildStamp = readBuildStamp(deps);
|
||||
if (buildStamp.mtime == null) {
|
||||
return { shouldSync: true, reason: "missing_build_stamp" };
|
||||
}
|
||||
if (buildStamp.mtime > stamp.mtime) {
|
||||
return { shouldSync: true, reason: "build_stamp_newer" };
|
||||
}
|
||||
|
||||
const currentHead = resolveGitHead(deps);
|
||||
if (currentHead && !stamp.head) {
|
||||
return { shouldSync: true, reason: "runtime_postbuild_stamp_missing_head" };
|
||||
}
|
||||
if (currentHead && stamp.head && currentHead !== stamp.head) {
|
||||
return { shouldSync: true, reason: "git_head_changed" };
|
||||
}
|
||||
if (currentHead) {
|
||||
const dirty = hasDirtyRuntimePostBuildInputs(deps);
|
||||
if (dirty === true) {
|
||||
return { shouldSync: true, reason: "dirty_runtime_postbuild_inputs" };
|
||||
}
|
||||
if (dirty === false) {
|
||||
return { shouldSync: false, reason: "clean" };
|
||||
}
|
||||
}
|
||||
|
||||
if (hasRuntimePostBuildInputMtimeChanged(stamp.mtime, deps)) {
|
||||
return { shouldSync: true, reason: "runtime_postbuild_input_mtime_newer" };
|
||||
}
|
||||
|
||||
return { shouldSync: false, reason: "clean" };
|
||||
};
|
||||
|
||||
const BUILD_REASON_LABELS = {
|
||||
force_build: "forced by OPENCLAW_FORCE_BUILD",
|
||||
missing_build_stamp: "build stamp missing",
|
||||
@@ -261,7 +412,20 @@ const BUILD_REASON_LABELS = {
|
||||
clean: "clean",
|
||||
};
|
||||
|
||||
const RUNTIME_POSTBUILD_REASON_LABELS = {
|
||||
force_runtime_postbuild: "forced by OPENCLAW_FORCE_RUNTIME_POSTBUILD",
|
||||
missing_runtime_postbuild_stamp: "runtime postbuild stamp missing",
|
||||
missing_build_stamp: "build stamp missing",
|
||||
build_stamp_newer: "build stamp newer than runtime postbuild stamp",
|
||||
runtime_postbuild_stamp_missing_head: "runtime postbuild stamp missing git head",
|
||||
git_head_changed: "git head changed",
|
||||
dirty_runtime_postbuild_inputs: "dirty runtime postbuild inputs",
|
||||
runtime_postbuild_input_mtime_newer: "runtime postbuild input mtime newer than stamp",
|
||||
clean: "clean",
|
||||
};
|
||||
|
||||
const formatBuildReason = (reason) => BUILD_REASON_LABELS[reason] ?? reason;
|
||||
const formatRuntimePostBuildReason = (reason) => RUNTIME_POSTBUILD_REASON_LABELS[reason] ?? reason;
|
||||
|
||||
const SIGNAL_EXIT_CODES = {
|
||||
SIGINT: 130,
|
||||
@@ -565,6 +729,38 @@ const syncRuntimeArtifacts = async (deps) => {
|
||||
return true;
|
||||
};
|
||||
|
||||
const writeRuntimePostBuildStamp = (deps) => {
|
||||
try {
|
||||
deps.fs.mkdirSync(path.dirname(deps.runtimePostBuildStampPath), { recursive: true });
|
||||
const head = resolveGitHead(deps);
|
||||
deps.fs.writeFileSync(
|
||||
deps.runtimePostBuildStampPath,
|
||||
`${JSON.stringify(
|
||||
{
|
||||
syncedAt: Date.now(),
|
||||
...(head ? { head } : {}),
|
||||
},
|
||||
null,
|
||||
2,
|
||||
)}\n`,
|
||||
"utf8",
|
||||
);
|
||||
} catch (error) {
|
||||
logRunner(
|
||||
`Failed to write runtime postbuild stamp: ${error?.message ?? "unknown error"}`,
|
||||
deps,
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const syncRuntimeArtifactsAndStamp = async (deps) => {
|
||||
const synced = await syncRuntimeArtifacts(deps);
|
||||
if (synced) {
|
||||
writeRuntimePostBuildStamp(deps);
|
||||
}
|
||||
return synced;
|
||||
};
|
||||
|
||||
const writeBuildStamp = (deps) => {
|
||||
try {
|
||||
writeDistBuildStamp({
|
||||
@@ -598,6 +794,7 @@ export async function runNodeMain(params = {}) {
|
||||
deps.distRoot = path.join(deps.cwd, "dist");
|
||||
deps.distEntry = path.join(deps.distRoot, "/entry.js");
|
||||
deps.buildStampPath = path.join(deps.distRoot, ".buildstamp");
|
||||
deps.runtimePostBuildStampPath = path.join(deps.distRoot, runtimePostBuildStampFile);
|
||||
deps.sourceRoots = runNodeSourceRoots.map((sourceRoot) => ({
|
||||
name: sourceRoot,
|
||||
path: path.join(deps.cwd, sourceRoot),
|
||||
@@ -615,12 +812,22 @@ export async function runNodeMain(params = {}) {
|
||||
const buildRequirement = resolveBuildRequirement(deps);
|
||||
if (!buildRequirement.shouldBuild) {
|
||||
if (!shouldSkipCleanWatchRuntimeSync(deps)) {
|
||||
const synced = await withRunNodeBuildLock(
|
||||
deps,
|
||||
async () => await syncRuntimeArtifacts(deps),
|
||||
);
|
||||
if (!synced) {
|
||||
return await closeRunNodeOutputTee(deps, 1);
|
||||
const runtimePostBuildRequirement = resolveRuntimePostBuildRequirement(deps);
|
||||
if (runtimePostBuildRequirement.shouldSync) {
|
||||
const synced = await withRunNodeBuildLock(deps, async () => {
|
||||
const lockedRuntimePostBuildRequirement = resolveRuntimePostBuildRequirement(deps);
|
||||
if (!lockedRuntimePostBuildRequirement.shouldSync) {
|
||||
return true;
|
||||
}
|
||||
logRunner(
|
||||
`Syncing runtime artifacts (${lockedRuntimePostBuildRequirement.reason} - ${formatRuntimePostBuildReason(lockedRuntimePostBuildRequirement.reason)}).`,
|
||||
deps,
|
||||
);
|
||||
return await syncRuntimeArtifactsAndStamp(deps);
|
||||
});
|
||||
if (!synced) {
|
||||
return await closeRunNodeOutputTee(deps, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
exitCode = await runOpenClaw(deps);
|
||||
@@ -630,7 +837,15 @@ export async function runNodeMain(params = {}) {
|
||||
const buildExitCode = await withRunNodeBuildLock(deps, async () => {
|
||||
const lockedBuildRequirement = resolveBuildRequirement(deps);
|
||||
if (!lockedBuildRequirement.shouldBuild) {
|
||||
return (await syncRuntimeArtifacts(deps)) ? 0 : 1;
|
||||
const runtimePostBuildRequirement = resolveRuntimePostBuildRequirement(deps);
|
||||
if (!runtimePostBuildRequirement.shouldSync) {
|
||||
return 0;
|
||||
}
|
||||
logRunner(
|
||||
`Syncing runtime artifacts (${runtimePostBuildRequirement.reason} - ${formatRuntimePostBuildReason(runtimePostBuildRequirement.reason)}).`,
|
||||
deps,
|
||||
);
|
||||
return (await syncRuntimeArtifactsAndStamp(deps)) ? 0 : 1;
|
||||
}
|
||||
|
||||
logRunner(
|
||||
@@ -658,6 +873,7 @@ export async function runNodeMain(params = {}) {
|
||||
return 1;
|
||||
}
|
||||
writeBuildStamp(deps);
|
||||
writeRuntimePostBuildStamp(deps);
|
||||
return 0;
|
||||
});
|
||||
if (buildExitCode !== 0) {
|
||||
|
||||
Reference in New Issue
Block a user