fix: complete lint toolchain reuse in worktrees (#111467)

* fix(scripts): complete worktree lint toolchain

* fix(scripts): prepare complete lint pipeline
This commit is contained in:
Peter Steinberger
2026-07-19 08:58:43 -07:00
committed by GitHub
parent f8e35afecc
commit 87fbc19fe3
13 changed files with 226 additions and 13 deletions

View File

@@ -7,6 +7,7 @@ import {
acquireLocalHeavyCheckLockSync,
applyLocalOxlintPolicy,
applyLocalTsgoPolicy,
ensureRepoToolNodeModulesLink,
resolveLocalHeavyCheckEnv,
resolveRepoToolBinPath,
shouldAcquireLocalHeavyCheckLockForOxlint,
@@ -64,6 +65,51 @@ describe("local-heavy-check-runtime", () => {
).toBe(localPath);
});
it("links dependency-less worktrees to the selected checkout's modules", () => {
const primaryRoot = createTempDir("openclaw-primary-toolchain-");
const cwd = path.join(primaryRoot, ".codex", "worktrees", "task", "openclaw");
const commonDir = path.join(primaryRoot, ".git");
const primaryTsgo = path.join(primaryRoot, "node_modules", ".bin", "tsgo");
const primaryNodeModules = path.join(primaryRoot, "node_modules");
const localNodeModules = path.join(cwd, "node_modules");
fs.mkdirSync(path.dirname(primaryTsgo), { recursive: true });
fs.mkdirSync(cwd, { recursive: true });
expect(
ensureRepoToolNodeModulesLink(primaryTsgo, {
cwd,
resolveCommonDir: () => commonDir,
}),
).toBe(localNodeModules);
expect(fs.realpathSync(localNodeModules)).toBe(fs.realpathSync(primaryNodeModules));
// The stable link is idempotent for concurrent and later local runners.
expect(
ensureRepoToolNodeModulesLink(primaryTsgo, {
cwd,
resolveCommonDir: () => commonDir,
}),
).toBe(localNodeModules);
});
it("leaves existing worktree node_modules directories locally owned", () => {
const primaryRoot = createTempDir("openclaw-primary-toolchain-");
const commonDir = path.join(primaryRoot, ".git");
const primaryTsgo = path.join(primaryRoot, "node_modules", ".bin", "tsgo");
const cwd = path.join(primaryRoot, "worktree");
const localNodeModules = path.join(cwd, "node_modules");
fs.mkdirSync(path.dirname(primaryTsgo), { recursive: true });
fs.mkdirSync(localNodeModules, { recursive: true });
ensureRepoToolNodeModulesLink(primaryTsgo, {
cwd,
resolveCommonDir: () => commonDir,
});
expect(fs.lstatSync(localNodeModules).isDirectory()).toBe(true);
expect(fs.lstatSync(localNodeModules).isSymbolicLink()).toBe(false);
});
it("reenables local heavy-check policy for local wrapper entrypoints", () => {
expect(resolveLocalHeavyCheckEnv({ OPENCLAW_LOCAL_CHECK: "0", PATH: "/usr/bin" })).toEqual({
OPENCLAW_LOCAL_CHECK: "1",

View File

@@ -16,6 +16,7 @@ import {
parseMode,
resolveBoundaryEntryShimRequiredOutputs,
resolveBoundaryRootShimsTimeoutMs,
resolveTsxImportSpecifier,
runNodeStep,
runNodeSteps,
runNodeStepsInParallel,
@@ -100,6 +101,33 @@ async function waitForProcessExit(
}
describe("prepare-extension-package-boundary-artifacts", () => {
it("resolves the tsx loader from the selected checkout toolchain", () => {
const tsxBinPath = "/primary/node_modules/.bin/tsx";
const loaderPath = "/primary/node_modules/tsx/dist/loader.mjs";
expect(
resolveTsxImportSpecifier({
resolveTool: (toolName) => {
expect(toolName).toBe("tsx");
return tsxBinPath;
},
ensureToolchain: (toolPath) => {
expect(toolPath).toBe(tsxBinPath);
return "/worktree/node_modules";
},
createRequireFrom: (filename) => {
expect(filename).toBe(tsxBinPath);
return {
resolve(packageName) {
expect(packageName).toBe("tsx");
return loaderPath;
},
};
},
}),
).toBe(pathToFileURL(loaderPath).href);
});
it("prefixes each completed line and flushes the trailing partial line", () => {
let output = "";
const writer = createPrefixedOutputWriter("boundary", {

View File

@@ -69,9 +69,7 @@ describe("run-oxlint", () => {
const shardedLintRunner = readFileSync("scripts/run-oxlint-shards.mjs", "utf8");
expect(packageJson.scripts.check).toBe("node scripts/check.mjs");
expect(packageJson.scripts.lint).toBe(
"pnpm lint:ui:i18n && node scripts/run-oxlint-shards.mjs",
);
expect(packageJson.scripts.lint).toBe("node scripts/run-lint.mjs");
expect(packageJson.scripts["lint:core"]).toBe(
"node scripts/run-oxlint-shards.mjs --only=core --split-core",
);
@@ -82,6 +80,22 @@ describe("run-oxlint", () => {
expect(shardedLintRunner).toContain('OPENCLAW_OXLINT_SKIP_PREPARE: "1"');
});
it("prepares the worktree toolchain before the complete lint pre-step", () => {
const packageJson = JSON.parse(readFileSync("package.json", "utf8")) as {
scripts: Record<string, string>;
};
const lintRunner = readFileSync("scripts/run-lint.mjs", "utf8");
expect(packageJson.scripts.lint).toBe("node scripts/run-lint.mjs");
expect(lintRunner.indexOf("ensureRepoToolNodeModulesLink(")).toBeGreaterThan(-1);
expect(
lintRunner.indexOf('path.resolve("scripts", "control-ui-i18n-verify.ts")'),
).toBeGreaterThan(lintRunner.indexOf("ensureRepoToolNodeModulesLink("));
expect(lintRunner.indexOf('path.resolve("scripts", "run-oxlint-shards.mjs")')).toBeGreaterThan(
lintRunner.indexOf('path.resolve("scripts", "control-ui-i18n-verify.ts")'),
);
});
it("holds one parent heavy-check lock for sharded lint runs", () => {
const shardedLintRunner = readFileSync("scripts/run-oxlint-shards.mjs", "utf8");
const skipLockIndex = shardedLintRunner.indexOf('env.OPENCLAW_OXLINT_SKIP_LOCK === "1"');