CLI: skip runner rebuilds when dist is fresh (#1231)

Co-authored-by: mukhtharcm <mukhtharcm@users.noreply.github.com>
This commit is contained in:
Shadow
2026-01-19 13:12:33 -06:00
parent 754494d1a0
commit 39dfdccf6c
3 changed files with 115 additions and 15 deletions

View File

@@ -17,6 +17,7 @@ Docs: https://docs.clawd.bot
### Fixes ### Fixes
- Tests: stabilize Windows gateway/CLI tests by skipping sidecars, normalizing argv, and extending timeouts. - Tests: stabilize Windows gateway/CLI tests by skipping sidecars, normalizing argv, and extending timeouts.
- CLI: skip runner rebuilds when dist is fresh. (#1231) — thanks @mukhtharcm, @thewilloftheshadow.
## 2026.1.19-1 ## 2026.1.19-1

View File

@@ -1,27 +1,89 @@
#!/usr/bin/env node #!/usr/bin/env node
import { spawn } from "node:child_process"; import { spawn } from "node:child_process";
import fs from "node:fs";
import path from "node:path";
import process from "node:process"; import process from "node:process";
const args = process.argv.slice(2); const args = process.argv.slice(2);
const env = { ...process.env }; const env = { ...process.env };
const cwd = process.cwd(); const cwd = process.cwd();
const build = spawn("pnpm", ["exec", "tsc", "-p", "tsconfig.json"], { const distRoot = path.join(cwd, "dist");
cwd, const distEntry = path.join(distRoot, "entry.js");
env, const buildStampPath = path.join(distRoot, ".buildstamp");
stdio: "inherit", const srcRoot = path.join(cwd, "src");
}); const configFiles = [path.join(cwd, "tsconfig.json"), path.join(cwd, "package.json")];
build.on("exit", (code, signal) => { const statMtime = (filePath) => {
if (signal) { try {
process.exit(1); return fs.statSync(filePath).mtimeMs;
return; } catch {
return null;
} }
if (code !== 0 && code !== null) { };
process.exit(code);
return; const isExcludedSource = (filePath) => {
const relativePath = path.relative(srcRoot, filePath);
if (relativePath.startsWith("..")) return false;
return (
relativePath.endsWith(".test.ts") ||
relativePath.endsWith(".test.tsx") ||
relativePath.endsWith(`test-helpers.ts`)
);
};
const findLatestMtime = (dirPath, shouldSkip) => {
let latest = null;
const queue = [dirPath];
while (queue.length > 0) {
const current = queue.pop();
if (!current) continue;
let entries = [];
try {
entries = fs.readdirSync(current, { withFileTypes: true });
} catch {
continue;
}
for (const entry of entries) {
const fullPath = path.join(current, entry.name);
if (entry.isDirectory()) {
queue.push(fullPath);
continue;
}
if (!entry.isFile()) continue;
if (shouldSkip?.(fullPath)) continue;
const mtime = statMtime(fullPath);
if (mtime == null) continue;
if (latest == null || mtime > latest) {
latest = mtime;
}
}
}
return latest;
};
const shouldBuild = () => {
if (env.CLAWDBOT_FORCE_BUILD === "1") return true;
const stampMtime = statMtime(buildStampPath);
if (stampMtime == null) return true;
if (statMtime(distEntry) == null) return true;
for (const filePath of configFiles) {
const mtime = statMtime(filePath);
if (mtime != null && mtime > stampMtime) return true;
} }
const srcMtime = findLatestMtime(srcRoot, isExcludedSource);
if (srcMtime != null && srcMtime > stampMtime) return true;
return false;
};
const logRunner = (message) => {
if (env.CLAWDBOT_RUNNER_LOG === "0") return;
process.stderr.write(`[clawdbot] ${message}\n`);
};
const runNode = () => {
const nodeProcess = spawn(process.execPath, ["dist/entry.js", ...args], { const nodeProcess = spawn(process.execPath, ["dist/entry.js", ...args], {
cwd, cwd,
env, env,
@@ -35,4 +97,39 @@ build.on("exit", (code, signal) => {
} }
process.exit(exitCode ?? 1); process.exit(exitCode ?? 1);
}); });
}); };
const writeBuildStamp = () => {
try {
fs.mkdirSync(distRoot, { recursive: true });
fs.writeFileSync(buildStampPath, `${Date.now()}\n`);
} catch (error) {
// Best-effort stamp; still allow the runner to start.
logRunner(`Failed to write build stamp: ${error?.message ?? "unknown error"}`);
}
};
if (!shouldBuild()) {
logRunner("Skipping build; dist is fresh.");
runNode();
} else {
logRunner("Building TypeScript (dist is stale).");
const build = spawn("pnpm", ["exec", "tsc", "-p", "tsconfig.json"], {
cwd,
env,
stdio: "inherit",
});
build.on("exit", (code, signal) => {
if (signal) {
process.exit(1);
return;
}
if (code !== 0 && code !== null) {
process.exit(code);
return;
}
writeBuildStamp();
runNode();
});
}

View File

@@ -102,8 +102,10 @@ const normalizePluginsConfig = (config?: ClawdbotConfig["plugins"]): NormalizedP
const resolvePluginSdkAlias = (): string | null => { const resolvePluginSdkAlias = (): string | null => {
try { try {
const preferDist = process.env.VITEST || process.env.NODE_ENV === "test"; const modulePath = fileURLToPath(import.meta.url);
let cursor = path.dirname(fileURLToPath(import.meta.url)); const isDistRuntime = modulePath.split(path.sep).includes("dist");
const preferDist = process.env.VITEST || process.env.NODE_ENV === "test" || isDistRuntime;
let cursor = path.dirname(modulePath);
for (let i = 0; i < 6; i += 1) { for (let i = 0; i < 6; i += 1) {
const srcCandidate = path.join(cursor, "src", "plugin-sdk", "index.ts"); const srcCandidate = path.join(cursor, "src", "plugin-sdk", "index.ts");
const distCandidate = path.join(cursor, "dist", "plugin-sdk", "index.js"); const distCandidate = path.join(cursor, "dist", "plugin-sdk", "index.js");