perf(test): restore scoped vitest routing

This commit is contained in:
Peter Steinberger
2026-04-06 15:16:09 +01:00
parent ac6f696baa
commit 878c208844
11 changed files with 245 additions and 32 deletions

View File

@@ -24,7 +24,7 @@ Most days:
- Full gate (expected before push): `pnpm build && pnpm check && pnpm test`
- Faster local full-suite run on a roomy machine: `pnpm test:max`
- Direct Vitest watch loop (modern projects config): `pnpm test:watch`
- Direct Vitest watch loop: `pnpm test:watch`
- Direct file targeting now routes extension/channel paths too: `pnpm test extensions/discord/src/monitor/message-handler.preflight.test.ts`
When you touch tests or want extra confidence:
@@ -57,8 +57,9 @@ Think of the suites as “increasing realism” (and increasing flakiness/cost):
- No real keys required
- Should be fast and stable
- Projects note:
- `pnpm test`, `pnpm test:watch`, and `pnpm test:changed` all use the same native Vitest root `projects` config now.
- Direct file filters route natively through the root project graph, so `pnpm test extensions/discord/src/monitor/message-handler.preflight.test.ts` works without a custom wrapper.
- Untargeted `pnpm test` still uses the native Vitest root `projects` config.
- `pnpm test`, `pnpm test:watch`, and `pnpm test:perf:imports` route explicit file/directory targets through scoped lanes first, so `pnpm test extensions/discord/src/monitor/message-handler.preflight.test.ts` avoids paying the full root project startup tax.
- `pnpm test:changed` expands changed git paths into the same scoped lanes when the diff only touches routable source/test files; config/setup edits still fall back to the broad root-project rerun.
- Embedded runner note:
- When you change message-tool discovery inputs or compaction runtime context,
keep both levels of coverage.
@@ -77,8 +78,8 @@ Think of the suites as “increasing realism” (and increasing flakiness/cost):
- `pnpm test` inherits the same `threads` + `isolate: false` defaults from the root `vitest.config.ts` projects config.
- The shared `scripts/run-vitest.mjs` launcher now also adds `--no-maglev` for Vitest child Node processes by default to reduce V8 compile churn during big local runs. Set `OPENCLAW_VITEST_ENABLE_MAGLEV=1` if you need to compare against stock V8 behavior.
- Fast-local iteration note:
- `pnpm test:changed` runs the native projects config with `--changed origin/main`.
- `pnpm test:max` and `pnpm test:changed:max` keep the same native projects config, just with a higher worker cap.
- `pnpm test:changed` routes through scoped lanes when the changed paths map cleanly to a smaller suite.
- `pnpm test:max` and `pnpm test:changed:max` keep the same routing behavior, just with a higher worker cap.
- Local worker auto-scaling is intentionally conservative now and also backs off when the host load average is already high, so multiple concurrent Vitest runs do less damage by default.
- The base Vitest config marks the projects/config files as `forceRerunTriggers` so changed-mode reruns stay correct when test wiring changes.
- The config keeps `OPENCLAW_VITEST_FS_MODULE_CACHE` enabled on supported hosts; set `OPENCLAW_VITEST_FS_MODULE_CACHE_PATH=/abs/path` if you want one explicit cache location for direct profiling.

View File

@@ -12,13 +12,13 @@ title: "Tests"
- `pnpm test:force`: Kills any lingering gateway process holding the default control port, then runs the full Vitest suite with an isolated gateway port so server tests dont collide with a running instance. Use this when a prior gateway run left port 18789 occupied.
- `pnpm test:coverage`: Runs the unit suite with V8 coverage (via `vitest.unit.config.ts`). Global thresholds are 70% lines/branches/functions/statements. Coverage excludes integration-heavy entrypoints (CLI wiring, gateway/telegram bridges, webchat static server) to keep the target focused on unit-testable logic.
- `pnpm test:coverage:changed`: Runs unit coverage only for files changed since `origin/main`.
- `pnpm test:changed`: runs the native Vitest projects config with `--changed origin/main`. The base config treats the projects/config files as `forceRerunTriggers` so wiring changes still rerun broadly when needed.
- `pnpm test`: runs the native Vitest root projects config directly. File filters work natively across the configured projects.
- `pnpm test:changed`: expands changed git paths into scoped Vitest lanes when the diff only touches routable source/test files. Config/setup changes still fall back to the native root projects run so wiring edits rerun broadly when needed.
- `pnpm test`: routes explicit file/directory targets through scoped Vitest lanes, but still falls back to the native root projects run when you do a full untargeted sweep.
- Base Vitest config now defaults to `pool: "threads"` and `isolate: false`, with the shared non-isolated runner enabled across the repo configs.
- `pnpm test:channels` runs `vitest.channels.config.ts`.
- `pnpm test:extensions` runs `vitest.extensions.config.ts`.
- `pnpm test:extensions`: runs extension/plugin suites.
- `pnpm test:perf:imports`: enables Vitest import-duration + import-breakdown reporting for the native root projects run.
- `pnpm test:perf:imports`: enables Vitest import-duration + import-breakdown reporting, while still using scoped lane routing for explicit file/directory targets.
- `pnpm test:perf:imports:changed`: same import profiling, but only for files changed since `origin/main`.
- `pnpm test:perf:profile:main`: writes a CPU profile for the Vitest main thread (`.artifacts/vitest-main-profile`).
- `pnpm test:perf:profile:runner`: writes CPU + heap profiles for the unit runner (`.artifacts/vitest-runner-profile`).

View File

@@ -1120,13 +1120,13 @@
"runtime-sidecars:gen": "node --import tsx scripts/generate-runtime-sidecar-paths-baseline.ts --write",
"stage:bundled-plugin-runtime-deps": "node scripts/stage-bundled-plugin-runtime-deps.mjs",
"start": "node scripts/run-node.mjs",
"test": "node scripts/run-vitest.mjs run --config vitest.config.ts",
"test": "node scripts/test-projects.mjs",
"test:all": "pnpm lint && pnpm build && pnpm test && pnpm test:e2e && pnpm test:live && pnpm test:docker:all",
"test:auth:compat": "node scripts/run-vitest.mjs run --config vitest.gateway.config.ts src/gateway/server.auth.compat-baseline.test.ts src/gateway/client.test.ts src/gateway/reconnect-gating.test.ts src/gateway/protocol/connect-error-details.test.ts",
"test:build:singleton": "node scripts/test-built-plugin-singleton.mjs",
"test:bundled": "node scripts/run-vitest.mjs run --config vitest.bundled.config.ts",
"test:changed": "node scripts/run-vitest.mjs run --config vitest.config.ts --changed origin/main",
"test:changed:max": "OPENCLAW_VITEST_MAX_WORKERS=8 node scripts/run-vitest.mjs run --config vitest.config.ts --changed origin/main",
"test:changed": "node scripts/test-projects.mjs --changed origin/main",
"test:changed:max": "OPENCLAW_VITEST_MAX_WORKERS=8 node scripts/test-projects.mjs --changed origin/main",
"test:channels": "node scripts/run-vitest.mjs run --config vitest.channels.config.ts",
"test:contracts": "pnpm test:contracts:channels && pnpm test:contracts:plugins",
"test:contracts:channels": "node scripts/run-vitest.mjs run --config vitest.contracts.config.ts --maxWorkers=1 src/channels/plugins/contracts",
@@ -1165,15 +1165,15 @@
"test:live:cache": "bun scripts/check-live-cache.ts",
"test:live:gateway-profiles": "node scripts/test-live.mjs -- src/gateway/gateway-models.profiles.live.test.ts",
"test:live:models-profiles": "node scripts/test-live.mjs -- src/agents/models.profiles.live.test.ts",
"test:max": "OPENCLAW_VITEST_MAX_WORKERS=8 node scripts/run-vitest.mjs run --config vitest.config.ts",
"test:max": "OPENCLAW_VITEST_MAX_WORKERS=8 node scripts/test-projects.mjs",
"test:parallels:linux": "bash scripts/e2e/parallels-linux-smoke.sh",
"test:parallels:macos": "bash scripts/e2e/parallels-macos-smoke.sh",
"test:parallels:npm-update": "bash scripts/e2e/parallels-npm-update-smoke.sh",
"test:parallels:windows": "bash scripts/e2e/parallels-windows-smoke.sh",
"test:perf:budget": "node scripts/test-perf-budget.mjs",
"test:perf:hotspots": "node scripts/test-hotspots.mjs",
"test:perf:imports": "OPENCLAW_VITEST_IMPORT_DURATIONS=1 OPENCLAW_VITEST_PRINT_IMPORT_BREAKDOWN=1 node scripts/run-vitest.mjs run --config vitest.config.ts",
"test:perf:imports:changed": "OPENCLAW_VITEST_IMPORT_DURATIONS=1 OPENCLAW_VITEST_PRINT_IMPORT_BREAKDOWN=1 node scripts/run-vitest.mjs run --config vitest.config.ts --changed origin/main",
"test:perf:imports": "OPENCLAW_VITEST_IMPORT_DURATIONS=1 OPENCLAW_VITEST_PRINT_IMPORT_BREAKDOWN=1 node scripts/test-projects.mjs",
"test:perf:imports:changed": "OPENCLAW_VITEST_IMPORT_DURATIONS=1 OPENCLAW_VITEST_PRINT_IMPORT_BREAKDOWN=1 node scripts/test-projects.mjs --changed origin/main",
"test:perf:profile:main": "node scripts/run-vitest-profile.mjs main",
"test:perf:profile:runner": "node scripts/run-vitest-profile.mjs runner",
"test:sectriage": "node scripts/run-vitest.mjs run --config vitest.gateway.config.ts && node scripts/run-vitest.mjs run --config vitest.unit.config.ts --exclude src/daemon/launchd.integration.test.ts --exclude src/process/exec.test.ts",
@@ -1186,7 +1186,7 @@
"test:startup:memory": "node scripts/check-cli-startup-memory.mjs",
"test:ui": "pnpm ui:i18n:check && pnpm lint:ui:no-raw-window-open && pnpm --dir ui test",
"test:voicecall:closedloop": "node scripts/test-voicecall-closedloop.mjs",
"test:watch": "node scripts/run-vitest.mjs --config vitest.config.ts",
"test:watch": "node scripts/test-projects.mjs --watch",
"tool-display:check": "node --import tsx scripts/tool-display.ts --check",
"tool-display:write": "node --import tsx scripts/tool-display.ts --write",
"ts-topology": "node --import tsx scripts/ts-topology.ts",

View File

@@ -1,9 +1,11 @@
import fs from "node:fs";
import { acquireLocalHeavyCheckLockSync } from "./lib/local-heavy-check-runtime.mjs";
import { spawnPnpmRunner } from "./pnpm-runner.mjs";
import { resolveVitestCliEntry, resolveVitestNodeArgs } from "./run-vitest.mjs";
import {
createVitestRunSpecs,
parseTestProjectsArgs,
resolveChangedTargetArgs,
writeVitestIncludeFile,
} from "./test-projects.test-support.mjs";
@@ -66,7 +68,9 @@ function createRootVitestRunSpec(args) {
includePatterns: null,
pnpmArgs: [
"exec",
"vitest",
"node",
...resolveVitestNodeArgs(process.env),
resolveVitestCliEntry(),
...(watchMode ? [] : ["run"]),
"--config",
"vitest.config.ts",
@@ -79,8 +83,10 @@ function createRootVitestRunSpec(args) {
async function main() {
const args = process.argv.slice(2);
const { targetArgs } = parseTestProjectsArgs(args, process.cwd());
const changedTargetArgs =
targetArgs.length === 0 ? resolveChangedTargetArgs(args, process.cwd()) : null;
const runSpecs =
targetArgs.length === 0
targetArgs.length === 0 && changedTargetArgs === null
? [createRootVitestRunSpec(args)]
: createVitestRunSpecs(args, {
baseEnv: process.env,

View File

@@ -23,7 +23,17 @@ export function parseTestProjectsArgs(
watchMode: boolean;
};
export function buildVitestRunPlans(args: string[], cwd?: string): VitestRunPlan[];
export function buildVitestRunPlans(
args: string[],
cwd?: string,
listChangedPaths?: (baseRef: string, cwd: string) => string[],
): VitestRunPlan[];
export function resolveChangedTargetArgs(
args: string[],
cwd?: string,
listChangedPaths?: (baseRef: string, cwd: string) => string[],
): string[] | null;
export function createVitestRunSpecs(
args: string[],

View File

@@ -1,3 +1,4 @@
import { execFileSync } from "node:child_process";
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
@@ -18,6 +19,7 @@ import { isVoiceCallExtensionRoot } from "../vitest.extension-voice-call-paths.m
import { isWhatsAppExtensionRoot } from "../vitest.extension-whatsapp-paths.mjs";
import { isZaloExtensionRoot } from "../vitest.extension-zalo-paths.mjs";
import { isBoundaryTestFile, isBundledPluginDependentUnitTestFile } from "../vitest.unit-paths.mjs";
import { resolveVitestCliEntry, resolveVitestNodeArgs } from "./run-vitest.mjs";
const DEFAULT_VITEST_CONFIG = "vitest.unit.config.ts";
const AGENTS_VITEST_CONFIG = "vitest.agents.config.ts";
@@ -68,6 +70,15 @@ const UI_VITEST_CONFIG = "vitest.ui.config.ts";
const UTILS_VITEST_CONFIG = "vitest.utils.config.ts";
const WIZARD_VITEST_CONFIG = "vitest.wizard.config.ts";
const INCLUDE_FILE_ENV_KEY = "OPENCLAW_VITEST_INCLUDE_FILE";
const CHANGED_ARGS_PATTERN = /^--changed(?:=(.+))?$/u;
const BROAD_CHANGED_RERUN_PATTERNS = [
/^package\.json$/u,
/^pnpm-lock\.yaml$/u,
/^test\/setup(?:\.shared|\.extensions|-openclaw-runtime)?\.ts$/u,
/^vitest(?:\..+)?\.(?:config\.ts|paths\.mjs)$/u,
/^scripts\/run-vitest\.mjs$/u,
/^scripts\/test-projects(?:\.test-support)?\.mjs$/u,
];
function normalizePathPattern(value) {
return value.replaceAll("\\", "/");
@@ -93,6 +104,10 @@ function isFileLikeTarget(arg) {
return /\.(?:test|spec)\.[cm]?[jt]sx?$/u.test(arg);
}
function isLikelyFileTarget(arg) {
return /(?:^|\/)[^/]+\.[A-Za-z0-9]+$/u.test(arg);
}
function isPathLikeTargetArg(arg, cwd) {
if (!arg || arg === "--" || arg.startsWith("-")) {
return false;
@@ -113,13 +128,86 @@ function toScopedIncludePattern(arg, cwd) {
if (isGlobTarget(relative) || isFileLikeTarget(relative)) {
return relative;
}
if (isExistingFileTarget(arg, cwd)) {
if (isExistingFileTarget(arg, cwd) || isLikelyFileTarget(relative)) {
const directory = normalizePathPattern(path.posix.dirname(relative));
return directory === "." ? "**/*.test.ts" : `${directory}/**/*.test.ts`;
}
return `${relative.replace(/\/+$/u, "")}/**/*.test.ts`;
}
function listChangedPathsFromGit(baseRef, cwd) {
return execFileSync("git", ["diff", "--name-only", `${baseRef}...HEAD`], {
cwd,
encoding: "utf8",
stdio: ["ignore", "pipe", "pipe"],
})
.split("\n")
.map((line) => normalizePathPattern(line.trim()))
.filter((line) => line.length > 0);
}
function extractChangedBaseRef(args) {
for (let index = 0; index < args.length; index += 1) {
const arg = args[index];
const match = arg.match(CHANGED_ARGS_PATTERN);
if (!match) {
continue;
}
if (match[1]) {
return match[1];
}
const nextArg = args[index + 1];
return nextArg && nextArg !== "--" && !nextArg.startsWith("-") ? nextArg : "HEAD";
}
return null;
}
function stripChangedArgs(args) {
const strippedArgs = [];
for (let index = 0; index < args.length; index += 1) {
const arg = args[index];
const match = arg.match(CHANGED_ARGS_PATTERN);
if (!match) {
strippedArgs.push(arg);
continue;
}
if (!match[1]) {
const nextArg = args[index + 1];
if (nextArg && nextArg !== "--" && !nextArg.startsWith("-")) {
index += 1;
}
}
}
return strippedArgs;
}
function shouldKeepBroadChangedRun(changedPaths) {
return changedPaths.some((changedPath) =>
BROAD_CHANGED_RERUN_PATTERNS.some((pattern) => pattern.test(changedPath)),
);
}
function isRoutableChangedTarget(changedPath) {
return /^(?:src|test|extensions|ui|packages|apps)(?:\/|$)/u.test(changedPath);
}
export function resolveChangedTargetArgs(
args,
cwd = process.cwd(),
listChangedPaths = listChangedPathsFromGit,
) {
const baseRef = extractChangedBaseRef(args);
if (!baseRef) {
return null;
}
const changedPaths = listChangedPaths(baseRef, cwd);
if (changedPaths.length === 0 || shouldKeepBroadChangedRun(changedPaths)) {
return null;
}
const routablePaths = changedPaths.filter(isRoutableChangedTarget);
return routablePaths.length > 0 ? [...new Set(routablePaths)] : null;
}
function classifyTarget(arg, cwd) {
const relative = toRepoRelativeTarget(arg, cwd);
if (relative.endsWith(".e2e.test.ts")) {
@@ -278,7 +366,9 @@ function classifyTarget(arg, cwd) {
function createVitestArgs(params) {
return [
"exec",
"vitest",
"node",
...resolveVitestNodeArgs(params.env),
resolveVitestCliEntry(),
...(params.watchMode ? [] : ["run"]),
"--config",
params.config,
@@ -308,13 +398,21 @@ export function parseTestProjectsArgs(args, cwd = process.cwd()) {
return { forwardedArgs, targetArgs, watchMode };
}
export function buildVitestRunPlans(args, cwd = process.cwd()) {
export function buildVitestRunPlans(
args,
cwd = process.cwd(),
listChangedPaths = listChangedPathsFromGit,
) {
const { forwardedArgs, targetArgs, watchMode } = parseTestProjectsArgs(args, cwd);
if (targetArgs.length === 0) {
const changedTargetArgs =
targetArgs.length === 0 ? resolveChangedTargetArgs(args, cwd, listChangedPaths) : null;
const activeTargetArgs = changedTargetArgs ?? targetArgs;
const activeForwardedArgs = changedTargetArgs ? stripChangedArgs(forwardedArgs) : forwardedArgs;
if (activeTargetArgs.length === 0) {
return [
{
config: DEFAULT_VITEST_CONFIG,
forwardedArgs,
forwardedArgs: activeForwardedArgs,
includePatterns: null,
watchMode,
},
@@ -322,7 +420,7 @@ export function buildVitestRunPlans(args, cwd = process.cwd()) {
}
const groupedTargets = new Map();
for (const targetArg of targetArgs) {
for (const targetArg of activeTargetArgs) {
const kind = classifyTarget(targetArg, cwd);
const current = groupedTargets.get(kind) ?? [];
current.push(targetArg);
@@ -335,7 +433,7 @@ export function buildVitestRunPlans(args, cwd = process.cwd()) {
);
}
const nonTargetArgs = forwardedArgs.filter((arg) => !targetArgs.includes(arg));
const nonTargetArgs = activeForwardedArgs.filter((arg) => !activeTargetArgs.includes(arg));
const orderedKinds = [
"default",
"boundary",
@@ -502,11 +600,14 @@ export function buildVitestRunPlans(args, cwd = process.cwd()) {
"extension"
? EXTENSIONS_VITEST_CONFIG
: DEFAULT_VITEST_CONFIG;
const includePatterns =
kind === "default" || kind === "e2e"
? null
: grouped.map((targetArg) => toScopedIncludePattern(targetArg, cwd));
const scopedTargetArgs = kind === "default" || kind === "e2e" ? grouped : [];
const useCliTargetArgs =
kind === "e2e" ||
(kind === "default" &&
grouped.every((targetArg) => isFileLikeTarget(toRepoRelativeTarget(targetArg, cwd))));
const includePatterns = useCliTargetArgs
? null
: grouped.map((targetArg) => toScopedIncludePattern(targetArg, cwd));
const scopedTargetArgs = useCliTargetArgs ? grouped : [];
plans.push({
config,
forwardedArgs: [...nonTargetArgs, ...scopedTargetArgs],

View File

@@ -0,0 +1,70 @@
import { describe, expect, it } from "vitest";
import {
buildVitestRunPlans,
resolveChangedTargetArgs,
} from "../../scripts/test-projects.test-support.mjs";
describe("scripts/test-projects changed-target routing", () => {
it("maps changed source files into scoped lane targets", () => {
expect(
resolveChangedTargetArgs(["--changed", "origin/main"], process.cwd(), () => [
"src/shared/string-normalization.ts",
"src/utils/provider-utils.ts",
]),
).toEqual(["src/shared/string-normalization.ts", "src/utils/provider-utils.ts"]);
});
it("keeps the broad changed run for Vitest wiring edits", () => {
expect(
resolveChangedTargetArgs(["--changed", "origin/main"], process.cwd(), () => [
"vitest.shared.config.ts",
"src/utils/provider-utils.ts",
]),
).toBeNull();
});
it("ignores changed files that cannot map to test lanes", () => {
expect(
resolveChangedTargetArgs(["--changed", "origin/main"], process.cwd(), () => [
"docs/help/testing.md",
]),
).toBeNull();
});
it("narrows default-lane changed source files to include globs", () => {
const plans = buildVitestRunPlans(["--changed", "origin/main"], process.cwd(), () => [
"packages/sdk/src/index.ts",
]);
expect(plans).toEqual([
{
config: "vitest.unit.config.ts",
forwardedArgs: [],
includePatterns: ["packages/sdk/src/**/*.test.ts"],
watchMode: false,
},
]);
});
it("routes changed utils and shared files to their light scoped lanes", () => {
const plans = buildVitestRunPlans(["--changed", "origin/main"], process.cwd(), () => [
"src/shared/string-normalization.ts",
"src/utils/provider-utils.ts",
]);
expect(plans).toEqual([
{
config: "vitest.shared-core.config.ts",
forwardedArgs: [],
includePatterns: ["src/shared/**/*.test.ts"],
watchMode: false,
},
{
config: "vitest.utils.config.ts",
forwardedArgs: [],
includePatterns: ["src/utils/**/*.test.ts"],
watchMode: false,
},
]);
});
});

View File

@@ -101,6 +101,25 @@ describe("createScopedVitestConfig", () => {
expect(config.test?.passWithNoTests).toBe(true);
});
it("loads scoped include overrides from OPENCLAW_VITEST_INCLUDE_FILE", () => {
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-vitest-scoped-"));
try {
const includeFile = path.join(tempDir, "include.json");
fs.writeFileSync(includeFile, JSON.stringify(["src/utils/utils-misc.test.ts"]), "utf8");
const config = createScopedVitestConfig(["src/utils/**/*.test.ts"], {
dir: "src",
env: {
OPENCLAW_VITEST_INCLUDE_FILE: includeFile,
},
});
expect(config.test?.include).toEqual(["utils/utils-misc.test.ts"]);
} finally {
fs.rmSync(tempDir, { recursive: true, force: true });
}
});
it("overrides setup files when a scoped config requests them", () => {
const config = createScopedVitestConfig(["src/example.test.ts"], {
env: {},
@@ -508,6 +527,7 @@ describe("scoped vitest configs", () => {
it("normalizes shared-core include patterns relative to the scoped dir", () => {
expect(defaultSharedCoreConfig.test?.dir).toBe("src");
expect(defaultSharedCoreConfig.test?.include).toEqual(["shared/**/*.test.ts"]);
expect(defaultSharedCoreConfig.test?.setupFiles).toEqual(["test/setup.ts"]);
});
it("normalizes process include patterns relative to the scoped dir", () => {
@@ -585,5 +605,6 @@ describe("scoped vitest configs", () => {
it("normalizes utils include patterns relative to the scoped dir", () => {
expect(defaultUtilsConfig.test?.dir).toBe("src");
expect(defaultUtilsConfig.test?.include).toEqual(["utils/**/*.test.ts"]);
expect(defaultUtilsConfig.test?.setupFiles).toEqual(["test/setup.ts"]);
});
});

View File

@@ -1,5 +1,5 @@
import { defineConfig } from "vitest/config";
import { narrowIncludePatternsForCli } from "./vitest.pattern-file.ts";
import { loadPatternListFromEnv, narrowIncludePatternsForCli } from "./vitest.pattern-file.ts";
import { sharedVitestConfig } from "./vitest.shared.config.ts";
function normalizePathPattern(value: string): string {
@@ -55,6 +55,8 @@ export function createScopedVitestConfig(
const base = sharedVitestConfig as Record<string, unknown>;
const baseTest = sharedVitestConfig.test ?? {};
const scopedDir = options?.dir;
const env = options?.env;
const includeFromEnv = loadPatternListFromEnv("OPENCLAW_VITEST_INCLUDE_FILE", env);
const cliInclude = narrowIncludePatternsForCli(include, options?.argv);
const exclude = relativizeScopedPatterns(
[...(baseTest.exclude ?? []), ...(options?.exclude ?? [])],
@@ -82,7 +84,7 @@ export function createScopedVitestConfig(
...(runner ? { runner } : { runner: undefined }),
setupFiles,
...(scopedDir ? { dir: scopedDir } : {}),
include: relativizeScopedPatterns(cliInclude ?? include, scopedDir),
include: relativizeScopedPatterns(includeFromEnv ?? cliInclude ?? include, scopedDir),
exclude,
...(options?.pool ? { pool: options.pool } : {}),
...(options?.passWithNoTests !== undefined || cliInclude !== null

View File

@@ -4,6 +4,7 @@ export function createSharedCoreVitestConfig(env?: Record<string, string | undef
return createScopedVitestConfig(["src/shared/**/*.test.ts"], {
dir: "src",
env,
includeOpenClawRuntimeSetup: false,
name: "shared-core",
passWithNoTests: true,
});

View File

@@ -4,6 +4,7 @@ export function createUtilsVitestConfig(env?: Record<string, string | undefined>
return createScopedVitestConfig(["src/utils/**/*.test.ts"], {
dir: "src",
env,
includeOpenClawRuntimeSetup: false,
name: "utils",
passWithNoTests: true,
});