mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-12 21:53:00 -06:00
perf(checks): accelerate fresh changed gates (#121805)
This commit is contained in:
@@ -45,7 +45,9 @@ jobs:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
with:
|
||||
fetch-depth: ${{ github.event_name == 'pull_request' && '2' || '0' }}
|
||||
# Dispatch hydrates the pinned workflow ref; changed-gate sync later
|
||||
# reconstructs its exact base and final tree. PR validation keeps both commits.
|
||||
fetch-depth: ${{ github.event_name == 'pull_request' && '2' || '1' }}
|
||||
persist-credentials: false
|
||||
- name: Setup Node environment
|
||||
uses: ./.github/actions/setup-node-env
|
||||
@@ -65,7 +67,7 @@ jobs:
|
||||
- name: Prepare Testbox shell
|
||||
uses: ./.github/actions/prepare-testbox-shell
|
||||
with:
|
||||
base-ref: ${{ github.event.pull_request.base.sha || 'refs/remotes/origin/main' }}
|
||||
base-ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.base.sha || 'HEAD' }}
|
||||
|
||||
- name: Hydrate Testbox provider env helper
|
||||
shell: bash
|
||||
|
||||
@@ -705,6 +705,8 @@ Docker, package lanes, E2E, live proof, and CI parity. Trusted maintainer heavy
|
||||
proof defaults to `blacksmith-testbox`, and `.crabbox.yaml` now defaults to it. Its configured
|
||||
workflow hydrates provider and agent credentials, so untrusted contributor or
|
||||
fork code must use secretless fork CI or sanitized direct AWS Crabbox instead.
|
||||
The check workflow hydrates its pinned dispatch commit with a depth-1 checkout;
|
||||
the changed gate later reconstructs the exact merge base and synced final tree.
|
||||
Sanitized AWS runs set `CRABBOX_ENV_ALLOW=CI`, pass
|
||||
`--no-hydrate`, and use a fresh temporary remote `HOME`; this prevents the repo
|
||||
`OPENCLAW_*` allowlist and existing auth profiles from reaching untrusted code.
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
resolveRepoToolBinPath,
|
||||
shouldAcquireLocalHeavyCheckLockForOxlint,
|
||||
} from "./lib/local-heavy-check-runtime.mts";
|
||||
import { shouldPrepareExtensionPackageBoundaryArtifacts } from "./run-oxlint.mts";
|
||||
|
||||
const DEFAULT_WINDOWS_EXTENSION_CHUNK_SIZE = 8;
|
||||
const DEFAULT_SHARD_HEARTBEAT_MS = 30_000;
|
||||
@@ -283,23 +284,28 @@ export async function main(
|
||||
const selectedShards = filterOxlintShards(shards, shardArgs.only);
|
||||
|
||||
ensureRepoToolNodeModulesLink(resolveRepoToolBinPath("oxlint"));
|
||||
const prepareResult = spawnSync(
|
||||
process.execPath,
|
||||
[
|
||||
"--import",
|
||||
"tsx",
|
||||
path.resolve("scripts", "prepare-extension-package-boundary-artifacts.mts"),
|
||||
],
|
||||
{
|
||||
stdio: "inherit",
|
||||
env,
|
||||
},
|
||||
);
|
||||
const prepareResult = shouldPrepareExtensionPackageBoundaryArtifactsForShards(
|
||||
selectedShards,
|
||||
shardArgs.oxlintArgs,
|
||||
)
|
||||
? spawnSync(
|
||||
process.execPath,
|
||||
[
|
||||
"--import",
|
||||
"tsx",
|
||||
path.resolve("scripts", "prepare-extension-package-boundary-artifacts.mts"),
|
||||
],
|
||||
{
|
||||
stdio: "inherit",
|
||||
env,
|
||||
},
|
||||
)
|
||||
: undefined;
|
||||
|
||||
if (prepareResult.error) {
|
||||
if (prepareResult?.error) {
|
||||
throw prepareResult.error;
|
||||
}
|
||||
if ((prepareResult.status ?? 1) !== 0) {
|
||||
if (prepareResult && (prepareResult.status ?? 1) !== 0) {
|
||||
process.exitCode = prepareResult.status ?? 1;
|
||||
} else {
|
||||
const shardConcurrency = resolveOxlintShardConcurrency({
|
||||
@@ -398,6 +404,15 @@ export function filterOxlintShards<T extends { name: string }>(shards: T[], only
|
||||
);
|
||||
}
|
||||
|
||||
export function shouldPrepareExtensionPackageBoundaryArtifactsForShards(
|
||||
shards: readonly OxlintShard[],
|
||||
extraArgs: readonly string[] = [],
|
||||
) {
|
||||
return shards.some((shard) =>
|
||||
shouldPrepareExtensionPackageBoundaryArtifacts([...shard.args, ...extraArgs]),
|
||||
);
|
||||
}
|
||||
|
||||
function requireShardSelector(value: string | undefined) {
|
||||
if (!value || value.startsWith("-")) {
|
||||
throw new Error("--only requires a shard name");
|
||||
|
||||
+21
-1
@@ -43,13 +43,33 @@ const OXLINT_VALUE_FLAGS = new Set([
|
||||
"--tsconfig",
|
||||
"--warn",
|
||||
]);
|
||||
const OXLINT_BOUNDARY_FREE_TS_CONFIGS = new Set([
|
||||
"config/tsconfig/oxlint.core.json",
|
||||
"config/tsconfig/oxlint.scripts.json",
|
||||
]);
|
||||
const OPENCLAW_FOCUSED_CONFIG_FLAG = "--openclaw-focused-config";
|
||||
|
||||
/**
|
||||
* Returns whether oxlint args need package-boundary declaration artifacts first.
|
||||
*/
|
||||
export function shouldPrepareExtensionPackageBoundaryArtifacts(args: string[]) {
|
||||
return !args.some((arg) => OXLINT_PREPARE_SKIP_FLAGS.has(arg));
|
||||
if (args.some((arg) => OXLINT_PREPARE_SKIP_FLAGS.has(arg))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const tsconfigs = args.flatMap((arg, index) => {
|
||||
if (arg === "--tsconfig") {
|
||||
const value = args[index + 1];
|
||||
return value === undefined ? [] : [value];
|
||||
}
|
||||
return arg.startsWith("--tsconfig=") ? [arg.slice("--tsconfig=".length)] : [];
|
||||
});
|
||||
// Core and script lint resolve workspace sources through the root tsconfig;
|
||||
// generated plugin package declarations are only an extension-lint input.
|
||||
return (
|
||||
tsconfigs.length === 0 ||
|
||||
tsconfigs.some((tsconfig) => !OXLINT_BOUNDARY_FREE_TS_CONFIGS.has(tsconfig))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -4372,28 +4372,48 @@ NODE
|
||||
}
|
||||
});
|
||||
|
||||
it("prepares all Testbox checkouts from one maintained owner", () => {
|
||||
it("prepares Testbox checkouts with one maintained owner and scoped history", () => {
|
||||
const workflowPaths = [
|
||||
".github/workflows/ci-check-testbox.yml",
|
||||
".github/workflows/ci-check-arm-testbox.yml",
|
||||
".github/workflows/ci-build-artifacts-testbox.yml",
|
||||
];
|
||||
[
|
||||
".github/workflows/ci-check-testbox.yml",
|
||||
"1",
|
||||
"${{ github.event_name == 'pull_request' && github.event.pull_request.base.sha || 'HEAD' }}",
|
||||
],
|
||||
[
|
||||
".github/workflows/ci-check-arm-testbox.yml",
|
||||
"0",
|
||||
"${{ github.event.pull_request.base.sha || 'refs/remotes/origin/main' }}",
|
||||
],
|
||||
[
|
||||
".github/workflows/ci-build-artifacts-testbox.yml",
|
||||
"0",
|
||||
"${{ github.event.pull_request.base.sha || 'refs/remotes/origin/main' }}",
|
||||
],
|
||||
] as const;
|
||||
|
||||
for (const workflowPath of workflowPaths) {
|
||||
for (const [workflowPath, dispatchFetchDepth, baseRef] of workflowPaths) {
|
||||
const workflow = parse(readFileSync(workflowPath, "utf8"));
|
||||
const job = Object.values(workflow.jobs)[0] as { steps: WorkflowStep[] };
|
||||
const checkoutStep = job.steps.find((step) => step.name === "Checkout");
|
||||
const prepareStep = job.steps.find((step) => step.name === "Prepare Testbox shell");
|
||||
|
||||
expect(checkoutStep?.uses, workflowPath).toBe(CHECKOUT_V6);
|
||||
expect(checkoutStep?.with, workflowPath).toEqual({
|
||||
"fetch-depth": "${{ github.event_name == 'pull_request' && '2' || '0' }}",
|
||||
"persist-credentials": false,
|
||||
});
|
||||
expect(checkoutStep?.with?.["persist-credentials"], workflowPath).toBe(false);
|
||||
for (const [eventName, expectedDepth] of [
|
||||
["pull_request", "2"],
|
||||
["workflow_dispatch", dispatchFetchDepth],
|
||||
] as const) {
|
||||
expect(
|
||||
evaluateWorkflowExpression(checkoutStep?.with?.["fetch-depth"], {
|
||||
eventName,
|
||||
repository: "openclaw/openclaw",
|
||||
runAttempt: 1,
|
||||
}),
|
||||
`${workflowPath} ${eventName}`,
|
||||
).toBe(expectedDepth);
|
||||
}
|
||||
expect(prepareStep?.uses, workflowPath).toBe("./.github/actions/prepare-testbox-shell");
|
||||
expect(prepareStep?.with?.["base-ref"], workflowPath).toBe(
|
||||
"${{ github.event.pull_request.base.sha || 'refs/remotes/origin/main' }}",
|
||||
);
|
||||
expect(prepareStep?.with?.["base-ref"], workflowPath).toBe(baseRef);
|
||||
const ensureBaseStep = job.steps.find(
|
||||
(step: WorkflowStep) => step.name === "Ensure Testbox base commit",
|
||||
);
|
||||
|
||||
@@ -16,6 +16,7 @@ import {
|
||||
resolveOxlintShardConcurrency,
|
||||
resolveWindowsExtensionChunkSize,
|
||||
runShard,
|
||||
shouldPrepareExtensionPackageBoundaryArtifactsForShards,
|
||||
shouldRunOxlintShardsSerial,
|
||||
} from "../../scripts/run-oxlint-shards.mts";
|
||||
import {
|
||||
@@ -192,6 +193,27 @@ describe("run-oxlint", () => {
|
||||
expect(shouldPrepareExtensionPackageBoundaryArtifacts([])).toBe(true);
|
||||
expect(shouldPrepareExtensionPackageBoundaryArtifacts(["src/index.ts"])).toBe(true);
|
||||
expect(shouldPrepareExtensionPackageBoundaryArtifacts(["--type-aware"])).toBe(true);
|
||||
expect(
|
||||
shouldPrepareExtensionPackageBoundaryArtifacts([
|
||||
"--tsconfig",
|
||||
"config/tsconfig/oxlint.extensions.json",
|
||||
"extensions/telegram/src/index.ts",
|
||||
]),
|
||||
).toBe(true);
|
||||
expect(
|
||||
shouldPrepareExtensionPackageBoundaryArtifacts([
|
||||
"--tsconfig=config/tsconfig/oxlint.core.json",
|
||||
"--tsconfig=config/tsconfig/oxlint.extensions.json",
|
||||
]),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it.each([
|
||||
["--tsconfig", "config/tsconfig/oxlint.core.json", "src/index.ts"],
|
||||
["--tsconfig=config/tsconfig/oxlint.core.json", "src/index.ts"],
|
||||
["--tsconfig", "config/tsconfig/oxlint.scripts.json", "scripts/check-changed.mts"],
|
||||
])("skips extension artifacts for an exact source-backed config: %s", (...args) => {
|
||||
expect(shouldPrepareExtensionPackageBoundaryArtifacts(args)).toBe(false);
|
||||
});
|
||||
|
||||
it("skips artifact preparation for metadata-only oxlint commands", () => {
|
||||
@@ -201,6 +223,15 @@ describe("run-oxlint", () => {
|
||||
expect(shouldPrepareExtensionPackageBoundaryArtifacts(["--rules"])).toBe(false);
|
||||
});
|
||||
|
||||
it("prepares shard artifacts only when a selected config consumes them", () => {
|
||||
const core = oxlintShard("core", "core", "src");
|
||||
const scripts = oxlintShard("scripts", "scripts", "scripts");
|
||||
const extensions = oxlintShard("extensions", "extensions", "extensions");
|
||||
|
||||
expect(shouldPrepareExtensionPackageBoundaryArtifactsForShards([core, scripts])).toBe(false);
|
||||
expect(shouldPrepareExtensionPackageBoundaryArtifactsForShards([core, extensions])).toBe(true);
|
||||
});
|
||||
|
||||
it("does not run package-boundary artifact prep twice in pnpm check", () => {
|
||||
const packageJson = JSON.parse(readFileSync("package.json", "utf8")) as {
|
||||
scripts: Record<string, string>;
|
||||
|
||||
Reference in New Issue
Block a user