mirror of
https://github.com/openclaw/openclaw.git
synced 2026-07-21 02:06:43 +00:00
improve(ci): persist warm Vitest and Node caches (#109425)
* perf(ci): harden persistent test caches * ci: document trusted cache cleanup trigger * ci: harden composite cache inputs * perf(ci): isolate Node build bytecode cache
This commit is contained in:
@@ -35,10 +35,26 @@ inputs:
|
||||
alongside.
|
||||
required: false
|
||||
default: "false"
|
||||
runtime-cache-sticky-disk:
|
||||
description: Whether runtime caches should use Blacksmith sticky disks instead of actions/cache.
|
||||
required: false
|
||||
default: "false"
|
||||
vitest-fs-cache:
|
||||
description: Whether to persist Vitest's experimental filesystem module cache.
|
||||
required: false
|
||||
default: "false"
|
||||
node-compile-cache:
|
||||
description: Whether to persist Node's on-disk V8 compile cache.
|
||||
required: false
|
||||
default: "false"
|
||||
node-compile-cache-scope:
|
||||
description: Cache namespace for isolating workloads with different writer ownership.
|
||||
required: false
|
||||
default: "test"
|
||||
save-node-compile-cache:
|
||||
description: Whether this job may save the Node compile cache.
|
||||
required: false
|
||||
default: "false"
|
||||
save-vitest-fs-cache:
|
||||
description: >
|
||||
Whether this job may save the shared Vitest filesystem module cache.
|
||||
@@ -103,46 +119,123 @@ runs:
|
||||
# detection, so an incomplete first hydration cannot seed this key.
|
||||
commit: if-missing
|
||||
|
||||
- name: Mount Vitest transform cache sticky disk
|
||||
if: inputs.vitest-fs-cache == 'true' && inputs.sticky-disk == 'true' && runner.os != 'Windows'
|
||||
- name: Mount protected Vitest transform seed
|
||||
if: inputs.vitest-fs-cache == 'true' && (inputs.sticky-disk == 'true' || inputs.runtime-cache-sticky-disk == 'true') && github.event_name == 'pull_request' && runner.os != 'Windows'
|
||||
uses: useblacksmith/stickydisk@5b350170ae4ef55b536b548ef5f5896e76a6b54f # v1.4.0
|
||||
with:
|
||||
# Match dependency-cache trust scoping. Vitest content-addresses module
|
||||
# entries; the lockfile key rotates dependency/plugin generations.
|
||||
key: ${{ github.repository }}-vitest-fs-v1-${{ github.event_name == 'pull_request' && format('pr-{0}', github.event.pull_request.number) || 'protected' }}-${{ runner.os }}-${{ runner.arch }}-node-${{ inputs.node-version }}-${{ hashFiles('pnpm-lock.yaml') }}
|
||||
# Same-repository PRs read the protected snapshot but never write it.
|
||||
# Content-addressed misses stay in the PR-scoped overlay below.
|
||||
key: ${{ github.repository }}-vitest-fs-v2-protected-${{ runner.os }}-${{ runner.arch }}-node-${{ inputs.node-version }}
|
||||
path: /var/tmp/openclaw-vitest-fs-protected-seed
|
||||
commit: false
|
||||
|
||||
- name: Mount Vitest transform cache sticky disk
|
||||
if: inputs.vitest-fs-cache == 'true' && (inputs.sticky-disk == 'true' || inputs.runtime-cache-sticky-disk == 'true') && runner.os != 'Windows'
|
||||
uses: useblacksmith/stickydisk@5b350170ae4ef55b536b548ef5f5896e76a6b54f # v1.4.0
|
||||
with:
|
||||
# One stable disk per trust scope. The generation guard below clears
|
||||
# incompatible transform inputs without leaking one disk per lockfile.
|
||||
key: ${{ github.repository }}-vitest-fs-v2-${{ github.event_name == 'pull_request' && format('pr-{0}', github.event.pull_request.number) || 'protected' }}-${{ runner.os }}-${{ runner.arch }}-node-${{ inputs.node-version }}
|
||||
path: /var/tmp/openclaw-vitest-fs-cache
|
||||
commit: ${{ inputs.save-vitest-fs-cache == 'true' && 'on-change' || 'false' }}
|
||||
# One semantic writer only. Explicit true avoids stickydisk's allocated-
|
||||
# byte heuristic missing equal-size replacements or prune-balanced writes.
|
||||
commit: ${{ inputs.save-vitest-fs-cache == 'true' && 'true' || 'false' }}
|
||||
|
||||
- name: Restore and save Vitest transform cache
|
||||
if: inputs.vitest-fs-cache == 'true' && inputs.sticky-disk != 'true' && inputs.save-vitest-fs-cache == 'true' && runner.os != 'Windows'
|
||||
if: inputs.vitest-fs-cache == 'true' && inputs.sticky-disk != 'true' && inputs.runtime-cache-sticky-disk != 'true' && inputs.save-vitest-fs-cache == 'true' && runner.os != 'Windows'
|
||||
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5
|
||||
with:
|
||||
path: /var/tmp/openclaw-vitest-fs-cache
|
||||
# actions/cache entries are immutable. One matrix job writes a new
|
||||
# run key; every job restores the newest cache through coarse prefixes.
|
||||
key: ${{ github.repository }}-vitest-fs-v1-${{ github.event_name == 'pull_request' && format('pr-{0}', github.event.pull_request.number) || 'protected' }}-${{ runner.os }}-${{ runner.arch }}-node-${{ inputs.node-version }}-${{ hashFiles('pnpm-lock.yaml') }}-${{ github.run_id }}
|
||||
key: ${{ github.repository }}-vitest-fs-v2-${{ github.event_name == 'pull_request' && format('pr-{0}', github.event.pull_request.number) || 'protected' }}-${{ runner.os }}-${{ runner.arch }}-node-${{ inputs.node-version }}-${{ hashFiles('pnpm-lock.yaml', 'pnpm-workspace.yaml', '**/package.json', '**/tsconfig*.json', 'vitest.config.*', 'test/vitest/**') }}-${{ github.run_id }}-${{ github.run_attempt }}
|
||||
restore-keys: |
|
||||
${{ github.repository }}-vitest-fs-v1-${{ github.event_name == 'pull_request' && format('pr-{0}', github.event.pull_request.number) || 'protected' }}-${{ runner.os }}-${{ runner.arch }}-node-${{ inputs.node-version }}-${{ hashFiles('pnpm-lock.yaml') }}-
|
||||
${{ github.repository }}-vitest-fs-v2-${{ github.event_name == 'pull_request' && format('pr-{0}', github.event.pull_request.number) || 'protected' }}-${{ runner.os }}-${{ runner.arch }}-node-${{ inputs.node-version }}-${{ hashFiles('pnpm-lock.yaml', 'pnpm-workspace.yaml', '**/package.json', '**/tsconfig*.json', 'vitest.config.*', 'test/vitest/**') }}-
|
||||
|
||||
- name: Restore Vitest transform cache
|
||||
if: inputs.vitest-fs-cache == 'true' && inputs.sticky-disk != 'true' && inputs.save-vitest-fs-cache != 'true' && runner.os != 'Windows'
|
||||
if: inputs.vitest-fs-cache == 'true' && inputs.sticky-disk != 'true' && inputs.runtime-cache-sticky-disk != 'true' && inputs.save-vitest-fs-cache != 'true' && runner.os != 'Windows'
|
||||
uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5
|
||||
with:
|
||||
path: /var/tmp/openclaw-vitest-fs-cache
|
||||
key: ${{ github.repository }}-vitest-fs-v1-${{ github.event_name == 'pull_request' && format('pr-{0}', github.event.pull_request.number) || 'protected' }}-${{ runner.os }}-${{ runner.arch }}-node-${{ inputs.node-version }}-${{ hashFiles('pnpm-lock.yaml') }}-${{ github.run_id }}
|
||||
key: ${{ github.repository }}-vitest-fs-v2-${{ github.event_name == 'pull_request' && format('pr-{0}', github.event.pull_request.number) || 'protected' }}-${{ runner.os }}-${{ runner.arch }}-node-${{ inputs.node-version }}-${{ hashFiles('pnpm-lock.yaml', 'pnpm-workspace.yaml', '**/package.json', '**/tsconfig*.json', 'vitest.config.*', 'test/vitest/**') }}-${{ github.run_id }}-${{ github.run_attempt }}
|
||||
restore-keys: |
|
||||
${{ github.repository }}-vitest-fs-v1-${{ github.event_name == 'pull_request' && format('pr-{0}', github.event.pull_request.number) || 'protected' }}-${{ runner.os }}-${{ runner.arch }}-node-${{ inputs.node-version }}-${{ hashFiles('pnpm-lock.yaml') }}-
|
||||
${{ github.repository }}-vitest-fs-v2-${{ github.event_name == 'pull_request' && format('pr-{0}', github.event.pull_request.number) || 'protected' }}-${{ runner.os }}-${{ runner.arch }}-node-${{ inputs.node-version }}-${{ hashFiles('pnpm-lock.yaml', 'pnpm-workspace.yaml', '**/package.json', '**/tsconfig*.json', 'vitest.config.*', 'test/vitest/**') }}-
|
||||
|
||||
- name: Configure Vitest transform cache
|
||||
if: inputs.vitest-fs-cache == 'true' && runner.os != 'Windows'
|
||||
env:
|
||||
CACHE_GENERATION: ${{ hashFiles('pnpm-lock.yaml', 'pnpm-workspace.yaml', '**/package.json', '**/tsconfig*.json', 'vitest.config.*', 'test/vitest/**') }}
|
||||
CACHE_WRITER: ${{ inputs.save-vitest-fs-cache == 'true' && '1' || '0' }}
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
cache_root=/var/tmp/openclaw-vitest-fs-cache
|
||||
seed_root=/var/tmp/openclaw-vitest-fs-protected-seed
|
||||
generation_file="$cache_root/.openclaw-transform-generation"
|
||||
mkdir -p "$cache_root"
|
||||
cache_generation=""
|
||||
if [[ -f "$generation_file" ]]; then
|
||||
cache_generation="$(<"$generation_file")"
|
||||
fi
|
||||
cache_entry="$(find "$cache_root" -mindepth 1 -maxdepth 1 -print -quit)"
|
||||
if [[ -n "$cache_entry" ]] && [[ "$cache_generation" != "$CACHE_GENERATION" ]]; then
|
||||
echo "Vitest transform inputs changed; clearing incompatible cache generation"
|
||||
find "$cache_root" -mindepth 1 -maxdepth 1 -exec rm -rf -- {} +
|
||||
fi
|
||||
seed_generation_file="$seed_root/.openclaw-transform-generation"
|
||||
if [[ -f "$seed_generation_file" ]] && [[ "$(<"$seed_generation_file")" == "$CACHE_GENERATION" ]]; then
|
||||
echo "Merging matching protected Vitest transform seed"
|
||||
cp -a --no-clobber "$seed_root"/. "$cache_root"/
|
||||
elif [[ -f "$seed_generation_file" ]]; then
|
||||
echo "Ignoring protected Vitest transform seed from an incompatible generation"
|
||||
fi
|
||||
printf '%s\n' "$CACHE_GENERATION" > "$generation_file"
|
||||
# The shard runner treats this as a persistent root and assigns one
|
||||
# isolated subdirectory per concurrent Vitest worker.
|
||||
echo "OPENCLAW_VITEST_FS_MODULE_CACHE_PATH=$cache_root" >> "$GITHUB_ENV"
|
||||
echo "OPENCLAW_VITEST_FS_MODULE_CACHE_WRITER=$CACHE_WRITER" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Mount Node compile cache sticky disk
|
||||
if: inputs.node-compile-cache == 'true' && (inputs.sticky-disk == 'true' || inputs.runtime-cache-sticky-disk == 'true') && runner.os != 'Windows'
|
||||
uses: useblacksmith/stickydisk@5b350170ae4ef55b536b548ef5f5896e76a6b54f # v1.4.0
|
||||
with:
|
||||
# Trusted PRs read the protected dependency bytecode seed but cannot
|
||||
# publish feature-branch code into it. Node isolates exact versions and
|
||||
# invalidates bytecode when module contents change.
|
||||
key: ${{ github.repository }}-node-compile-v2-${{ inputs.node-compile-cache-scope }}-protected-${{ runner.os }}-${{ runner.arch }}-node-${{ inputs.node-version }}
|
||||
path: /var/tmp/openclaw-node-compile-cache
|
||||
commit: ${{ inputs.save-node-compile-cache == 'true' && github.event_name != 'pull_request' && 'true' || 'false' }}
|
||||
|
||||
- name: Restore and save Node compile cache
|
||||
if: inputs.node-compile-cache == 'true' && inputs.sticky-disk != 'true' && inputs.runtime-cache-sticky-disk != 'true' && inputs.save-node-compile-cache == 'true' && runner.os != 'Windows'
|
||||
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5
|
||||
with:
|
||||
path: /var/tmp/openclaw-node-compile-cache
|
||||
key: ${{ github.repository }}-node-compile-v2-${{ inputs.node-compile-cache-scope }}-${{ github.event_name == 'pull_request' && format('pr-{0}', github.event.pull_request.number) || 'protected' }}-${{ runner.os }}-${{ runner.arch }}-node-${{ inputs.node-version }}-${{ github.run_id }}-${{ github.run_attempt }}
|
||||
restore-keys: |
|
||||
${{ github.repository }}-node-compile-v2-${{ inputs.node-compile-cache-scope }}-${{ github.event_name == 'pull_request' && format('pr-{0}', github.event.pull_request.number) || 'protected' }}-${{ runner.os }}-${{ runner.arch }}-node-${{ inputs.node-version }}-
|
||||
|
||||
- name: Restore Node compile cache
|
||||
if: inputs.node-compile-cache == 'true' && inputs.sticky-disk != 'true' && inputs.runtime-cache-sticky-disk != 'true' && inputs.save-node-compile-cache != 'true' && runner.os != 'Windows'
|
||||
uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5
|
||||
with:
|
||||
path: /var/tmp/openclaw-node-compile-cache
|
||||
key: ${{ github.repository }}-node-compile-v2-${{ inputs.node-compile-cache-scope }}-${{ github.event_name == 'pull_request' && format('pr-{0}', github.event.pull_request.number) || 'protected' }}-${{ runner.os }}-${{ runner.arch }}-node-${{ inputs.node-version }}-${{ github.run_id }}-${{ github.run_attempt }}
|
||||
restore-keys: |
|
||||
${{ github.repository }}-node-compile-v2-${{ inputs.node-compile-cache-scope }}-${{ github.event_name == 'pull_request' && format('pr-{0}', github.event.pull_request.number) || 'protected' }}-${{ runner.os }}-${{ runner.arch }}-node-${{ inputs.node-version }}-
|
||||
|
||||
- name: Configure Node compile cache
|
||||
if: inputs.node-compile-cache == 'true' && runner.os != 'Windows'
|
||||
env:
|
||||
CACHE_WRITER: ${{ inputs.save-node-compile-cache == 'true' && ((inputs.sticky-disk != 'true' && inputs.runtime-cache-sticky-disk != 'true') || github.event_name != 'pull_request') && '1' || '0' }}
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
cache_root=/var/tmp/openclaw-node-compile-cache
|
||||
mkdir -p "$cache_root"
|
||||
echo "NODE_COMPILE_CACHE=$cache_root" >> "$GITHUB_ENV"
|
||||
echo "NODE_COMPILE_CACHE_PORTABLE=1" >> "$GITHUB_ENV"
|
||||
echo "OPENCLAW_NODE_COMPILE_CACHE_WRITER=$CACHE_WRITER" >> "$GITHUB_ENV"
|
||||
|
||||
# Post actions run last-in-first-out. Register after stickydisk so this bind
|
||||
# is gone before stickydisk flushes, unmounts, and snapshots its filesystem.
|
||||
|
||||
+28
-11
@@ -567,14 +567,23 @@ jobs:
|
||||
runNodeFull &&
|
||||
changedScopeHasQaImpact &&
|
||||
(!frozenTarget || existsSync("extensions/qa-lab/src/ci-smoke-plan.ts"));
|
||||
const nodeTestShards = runNodeFull
|
||||
? (changedNodeTestShards
|
||||
? changedNodeTestShards
|
||||
: createNodeTestPlan({
|
||||
includeReleaseOnlyPluginShards: false,
|
||||
compact: compactPullRequest,
|
||||
})
|
||||
).map((shard) => ({
|
||||
const rawNodeTestShards = runNodeFull
|
||||
? changedNodeTestShards
|
||||
? changedNodeTestShards
|
||||
: createNodeTestPlan({
|
||||
includeReleaseOnlyPluginShards: false,
|
||||
compact: compactPullRequest,
|
||||
})
|
||||
: [];
|
||||
const assignVitestFsCacheWriter =
|
||||
typeof nodeTestPlan.assignVitestFsCacheWriter === "function"
|
||||
? nodeTestPlan.assignVitestFsCacheWriter
|
||||
: (shards) =>
|
||||
shards.map((shard, index) => ({
|
||||
...shard,
|
||||
saveVitestFsCache: index === 0,
|
||||
}));
|
||||
const nodeTestShards = assignVitestFsCacheWriter(rawNodeTestShards).map((shard) => ({
|
||||
check_name: shard.checkName,
|
||||
runtime: "node",
|
||||
task: "test-shard",
|
||||
@@ -587,12 +596,12 @@ jobs:
|
||||
runner: shard.runner,
|
||||
timeout_minutes: shard.timeoutMinutes,
|
||||
plan_concurrency: shard.planConcurrency,
|
||||
save_vitest_fs_cache: shard.saveVitestFsCache,
|
||||
targets: shard.targets,
|
||||
requires_go:
|
||||
shard.shardName === "core-tooling" ||
|
||||
shard.groups?.some((group) => group.shard_name.startsWith("core-tooling")),
|
||||
}))
|
||||
: [];
|
||||
}));
|
||||
const nodeTestNonDistShards = nodeTestShards.filter((shard) => !shard.requires_dist);
|
||||
const nodeTestDistShards = nodeTestShards.filter((shard) => shard.requires_dist);
|
||||
// Targeted jobs cannot discover repository-scanning boundary tests
|
||||
@@ -951,6 +960,10 @@ jobs:
|
||||
uses: ./.github/actions/setup-node-env
|
||||
with:
|
||||
install-bun: "false"
|
||||
node-compile-cache: "true"
|
||||
node-compile-cache-scope: "build"
|
||||
runtime-cache-sticky-disk: ${{ github.event_name != 'workflow_dispatch' && github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw') && 'true' || 'false' }}
|
||||
save-node-compile-cache: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' && 'true' || 'false' }}
|
||||
|
||||
- name: Restore build-all step cache
|
||||
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5
|
||||
@@ -1716,11 +1729,15 @@ jobs:
|
||||
# Fork PRs must keep actions/cache: sticky snapshots are writable,
|
||||
# repository-global state and must never be produced by fork code.
|
||||
sticky-disk: ${{ github.event_name != 'workflow_dispatch' && github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw') && 'true' || 'false' }}
|
||||
runtime-cache-sticky-disk: ${{ github.event_name != 'workflow_dispatch' && github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw') && 'true' || 'false' }}
|
||||
use-actions-cache: ${{ github.event_name != 'workflow_dispatch' && github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw') && 'false' || 'true' }}
|
||||
vitest-fs-cache: "true"
|
||||
node-compile-cache: "true"
|
||||
node-compile-cache-scope: "test"
|
||||
# One matrix job commits the shared warm seed. Other jobs restore it
|
||||
# read-only, avoiding concurrent sticky-disk or archive writers.
|
||||
save-vitest-fs-cache: ${{ strategy.job-index == 0 && 'true' || 'false' }}
|
||||
save-vitest-fs-cache: ${{ matrix.save_vitest_fs_cache && 'true' || 'false' }}
|
||||
save-node-compile-cache: ${{ matrix.save_vitest_fs_cache && 'true' || 'false' }}
|
||||
|
||||
- name: Setup Go for docs i18n
|
||||
if: matrix.requires_go == true
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
name: PR Cache Cleanup
|
||||
|
||||
on:
|
||||
pull_request_target: # zizmor: ignore[dangerous-triggers] trusted base workflow only; no checkout or untrusted PR code execution
|
||||
types: [closed]
|
||||
|
||||
permissions:
|
||||
actions: write
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
cleanup:
|
||||
if: github.repository == 'openclaw/openclaw'
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 5
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
PR_CACHE_REF: refs/pull/${{ github.event.pull_request.number }}/merge
|
||||
steps:
|
||||
- name: Delete closed PR caches
|
||||
run: gh cache delete --repo "$GITHUB_REPOSITORY" --all --ref "$PR_CACHE_REF" --succeed-on-no-caches
|
||||
@@ -0,0 +1,61 @@
|
||||
name: Vitest Cache Warm
|
||||
|
||||
on:
|
||||
repository_dispatch:
|
||||
types: [vitest-cache-warm]
|
||||
schedule:
|
||||
- cron: "17 8 * * *"
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: vitest-cache-warm
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
warm:
|
||||
if: github.repository == 'openclaw/openclaw'
|
||||
runs-on: blacksmith-8vcpu-ubuntu-2404
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
|
||||
|
||||
- name: Setup Node environment
|
||||
uses: ./.github/actions/setup-node-env
|
||||
with:
|
||||
install-bun: "false"
|
||||
node-compile-cache: "true"
|
||||
node-compile-cache-scope: "test"
|
||||
runtime-cache-sticky-disk: "true"
|
||||
save-node-compile-cache: "true"
|
||||
save-vitest-fs-cache: "true"
|
||||
sticky-disk: "true"
|
||||
use-actions-cache: "false"
|
||||
vitest-fs-cache: "true"
|
||||
|
||||
- name: Select broad cache seed
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
node --input-type=module <<'EOF'
|
||||
import { appendFileSync } from "node:fs";
|
||||
import { createNodeTestShards } from "./scripts/lib/ci-node-test-plan.mjs";
|
||||
|
||||
const shard = createNodeTestShards().find((candidate) => candidate.shardName === "core-unit-fast");
|
||||
if (!shard) {
|
||||
throw new Error("core-unit-fast cache seed shard is missing");
|
||||
}
|
||||
appendFileSync(
|
||||
process.env.GITHUB_ENV,
|
||||
[
|
||||
`OPENCLAW_NODE_TEST_CONFIGS_JSON=${JSON.stringify(shard.configs)}`,
|
||||
`OPENCLAW_VITEST_SHARD_NAME=${shard.shardName}`,
|
||||
"OPENCLAW_NODE_TEST_PLAN_CONCURRENCY=1",
|
||||
].join("\n") + "\n",
|
||||
);
|
||||
EOF
|
||||
|
||||
- name: Warm transform and compile caches
|
||||
run: node scripts/ci-run-node-test-shard.mjs
|
||||
+2
-1
@@ -114,7 +114,8 @@ The slowest Node test families are split or balanced so each job stays small wit
|
||||
- Pull requests on the canonical repository reuse the changed-test resolver against the synthetic merged-tree diff. Precise changes run one targeted Node job; each selected test file gets its own process so stateful suite isolation remains intact. The planner combines sibling tests with import-graph dependents and falls back to the existing 14-job compact full-suite plan for workspace package, package/lockfile, shared harness, split-config, renamed, or deleted changes, public extension-contract changes, tests with special shard setup, partially resolved or empty targets, oversized path or target plans, and planner errors. Targeted plans always retain the full built-artifact boundary gate because its repository scanners cannot be derived from imports. `main` pushes, manual dispatches, and release gates retain the full matrix because canceled superseded `main` runs make a single-push diff insufficient as integration proof.
|
||||
- The full Node matrix admits the consistently slow serial tooling and auto-reply command shards first. This keeps the 28-job cap while preventing short alphabetical groups from pushing critical-path work into a later wave.
|
||||
- Broad browser, QA, media, and miscellaneous plugin tests use their dedicated Vitest configs instead of the shared plugin catch-all. Include-pattern shards record timing entries using the CI shard name, so `.artifacts/vitest-shard-timings.json` can distinguish a whole config from a filtered shard.
|
||||
- Linux Node shard jobs persist Vitest's experimental filesystem module cache. Trusted Blacksmith jobs use the same PR/protected trust scoping as dependency sticky disks; GitHub-hosted and fork jobs use an `actions/cache` fallback with coarse restore prefixes. One deterministic matrix job commits the warm seed while the other jobs restore it read-only, avoiding competing snapshots. Every job receives an isolated cache clone, and the shard runner gives each concurrent Vitest worker its own stable subdirectory so serial plans reuse transforms without sharing live writers. The runner prunes oldest transform entries to 75% after the cache exceeds 2 GiB; lockfile changes also rotate the backing key.
|
||||
- Linux Node shard jobs persist Vitest's experimental filesystem module cache. Trusted Blacksmith jobs use PR-scoped writable overlays seeded from the protected snapshot only when their transform-input generations match; GitHub-hosted and fork jobs use an `actions/cache` fallback with coarse restore prefixes. The planner marks the broad `core-unit-fast` graph as the single writer without coupling cache ownership to matrix order, while every other job restores a private read-only clone. Concurrent Vitest workers retain separate live directories. A transform-input fingerprint clears incompatible lockfile, package, tsconfig, and Vitest-config generations inside stable sticky keys. Only the writer scans and prunes the cache to 75% after it exceeds 2 GiB. A non-cancelling daily or default-branch repository-dispatch warmer prevents rapid `main` pushes from starving the protected seed, and closed PR cache archives are deleted.
|
||||
- Node shard and build-artifact jobs also restore Node's portable on-disk compile cache. Independent `test` and `build` namespaces prevent their writers from replacing each other's snapshots: the scheduled test warmer owns the protected test seed, while `build-artifacts` publishes the protected build seed only from trusted `main` pushes. PR jobs read protected snapshots without publishing feature-branch bytecode; fallback archives remain PR-scoped. This reuses V8 bytecode for Node-loaded orchestration, build tooling, and external dependencies across different checkout paths, including when only part of the source graph changes. Vitest child processes disable an inherited compile cache because coverage can be enabled inside dynamic configs and V8 coverage can lose source-position precision when scripts are deserialized from bytecode.
|
||||
- `check-additional-*` stripes the supplemental boundary guard list (`scripts/run-additional-boundary-checks.mjs`) into one prompt-heavy shard (`check-additional-boundaries-a`, which includes the Codex prompt snapshot drift check) and one combined shard for the remaining stripes (`check-additional-boundaries-bcd`), each running independent guards concurrently and printing per-check timings. Package-boundary compile/canary work stays together, and runtime topology architecture runs separately from the gateway watch coverage embedded in `build-artifacts`.
|
||||
- Gateway watch, channel tests, and the core support-boundary shard run concurrently inside `build-artifacts` after `dist/` and `dist-runtime/` are already built.
|
||||
|
||||
|
||||
+2
-2
@@ -2104,7 +2104,7 @@
|
||||
"@types/web-push": "3.6.4",
|
||||
"@types/ws": "8.18.1",
|
||||
"@typescript/native-preview": "7.0.0-dev.20260707.2",
|
||||
"@vitest/coverage-v8": "4.1.9",
|
||||
"@vitest/coverage-v8": "4.1.10",
|
||||
"acorn": "8.17.0",
|
||||
"esbuild": "0.28.1",
|
||||
"fast-glob": "3.3.3",
|
||||
@@ -2122,7 +2122,7 @@
|
||||
"tsx": "4.22.4",
|
||||
"unrun": "0.3.1",
|
||||
"vite": "8.1.3",
|
||||
"vitest": "4.1.9"
|
||||
"vitest": "4.1.10"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"sqlite-vec": "0.1.9"
|
||||
|
||||
Generated
+102
-102
@@ -238,7 +238,7 @@ importers:
|
||||
version: 0.10.1(signal-polyfill@0.2.2)
|
||||
'@copilotkit/aimock':
|
||||
specifier: 1.35.0
|
||||
version: 1.35.0(vitest@4.1.9)
|
||||
version: 1.35.0(vitest@4.1.10)
|
||||
'@lit-labs/signals':
|
||||
specifier: 0.3.0
|
||||
version: 0.3.0
|
||||
@@ -288,8 +288,8 @@ importers:
|
||||
specifier: 7.0.0-dev.20260707.2
|
||||
version: 7.0.0-dev.20260707.2
|
||||
'@vitest/coverage-v8':
|
||||
specifier: 4.1.9
|
||||
version: 4.1.9(@vitest/browser@4.1.9)(vitest@4.1.9)
|
||||
specifier: 4.1.10
|
||||
version: 4.1.10(@vitest/browser@4.1.10)(vitest@4.1.10)
|
||||
acorn:
|
||||
specifier: 8.17.0
|
||||
version: 8.17.0
|
||||
@@ -342,8 +342,8 @@ importers:
|
||||
specifier: 8.1.3
|
||||
version: 8.1.3(@types/node@26.1.0)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.22.4)(yaml@2.9.0)
|
||||
vitest:
|
||||
specifier: 4.1.9
|
||||
version: 4.1.9(@opentelemetry/api@1.9.1)(@types/node@26.1.0)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(jsdom@29.1.1(@noble/hashes@2.2.0))(vite@8.1.3(@types/node@26.1.0)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.22.4)(yaml@2.9.0))
|
||||
specifier: 4.1.10
|
||||
version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.0)(@vitest/browser-playwright@4.1.10)(@vitest/coverage-v8@4.1.10)(jsdom@29.1.1(@noble/hashes@2.2.0))(vite@8.1.3(@types/node@26.1.0)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.22.4)(yaml@2.9.0))
|
||||
optionalDependencies:
|
||||
sqlite-vec:
|
||||
specifier: 0.1.9
|
||||
@@ -1490,7 +1490,7 @@ importers:
|
||||
dependencies:
|
||||
'@copilotkit/aimock':
|
||||
specifier: 1.35.0
|
||||
version: 1.35.0(vitest@4.1.9)
|
||||
version: 1.35.0(vitest@4.1.10)
|
||||
'@modelcontextprotocol/sdk':
|
||||
specifier: 1.29.0
|
||||
version: 1.29.0(zod@4.4.3)
|
||||
@@ -2283,11 +2283,11 @@ importers:
|
||||
specifier: 14.1.2
|
||||
version: 14.1.2
|
||||
'@vitest/browser':
|
||||
specifier: 4.1.9
|
||||
version: 4.1.9(vite@8.1.3(@types/node@24.13.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.22.4)(yaml@2.9.0))(vitest@4.1.9)
|
||||
specifier: 4.1.10
|
||||
version: 4.1.10(vite@8.1.3(@types/node@24.13.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.22.4)(yaml@2.9.0))(vitest@4.1.10)
|
||||
'@vitest/browser-playwright':
|
||||
specifier: 4.1.9
|
||||
version: 4.1.9(playwright@1.61.1)(vite@8.1.3(@types/node@24.13.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.22.4)(yaml@2.9.0))(vitest@4.1.9)
|
||||
specifier: 4.1.10
|
||||
version: 4.1.10(playwright@1.61.1)(vite@8.1.3(@types/node@24.13.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.22.4)(yaml@2.9.0))(vitest@4.1.10)
|
||||
jsdom:
|
||||
specifier: 29.1.1
|
||||
version: 29.1.1(@noble/hashes@2.2.0)
|
||||
@@ -2301,8 +2301,8 @@ importers:
|
||||
specifier: 8.1.3
|
||||
version: 8.1.3(@types/node@24.13.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.22.4)(yaml@2.9.0)
|
||||
vitest:
|
||||
specifier: 4.1.9
|
||||
version: 4.1.9(@opentelemetry/api@1.9.1)(@types/node@24.13.2)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(jsdom@29.1.1(@noble/hashes@2.2.0))(vite@8.1.3(@types/node@24.13.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.22.4)(yaml@2.9.0))
|
||||
specifier: 4.1.10
|
||||
version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@24.13.2)(@vitest/browser-playwright@4.1.10)(@vitest/coverage-v8@4.1.10)(jsdom@29.1.1(@noble/hashes@2.2.0))(vite@8.1.3(@types/node@24.13.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.22.4)(yaml@2.9.0))
|
||||
|
||||
packages:
|
||||
|
||||
@@ -4956,31 +4956,31 @@ packages:
|
||||
resolution: {integrity: sha512-N8/FHc/lmlMDCumMuTXyRHCxlov5KZY6unmJ9QR2GOw+OpROZMBsXYGwE+ZMtvN21ql9+Xb8KhGNBj08IrG3Wg==}
|
||||
engines: {node: '>=16', npm: '>=8'}
|
||||
|
||||
'@vitest/browser-playwright@4.1.9':
|
||||
resolution: {integrity: sha512-Bq1rOGf9waevzG3EOkO/dene6bvKTUsZMVg8S1i+WH3JcMjuXEjiahP9rAqZRELUqjBySOJsvvSWqK/B3wjKQw==}
|
||||
'@vitest/browser-playwright@4.1.10':
|
||||
resolution: {integrity: sha512-nMoXGEiRpT7m3W7NsbvrM2aKNwiNHZf+zEpUCvMteGjZFvfT96Q9fh7QyB98dvDWXiKvrLxA7bJ1mCOOv+JQPw==}
|
||||
peerDependencies:
|
||||
playwright: '*'
|
||||
vitest: 4.1.9
|
||||
vitest: 4.1.10
|
||||
|
||||
'@vitest/browser@4.1.9':
|
||||
resolution: {integrity: sha512-j1BKtWmPcqpMhmx/L9EPLgAJpCb0zKfwoWLmqBbxaogCXHjOwHFSEoHCBfnGtx93xKQwilZ26m+UOsHqHMkRNg==}
|
||||
'@vitest/browser@4.1.10':
|
||||
resolution: {integrity: sha512-UDwuWGwXj646CBx/bQHOaJSX7np0I8JL/UKQYa1e4QrVHH8VdWtx8eaOuf8sy0ShwDgR6NjJAsp5eF6vjF6qng==}
|
||||
peerDependencies:
|
||||
vitest: 4.1.9
|
||||
vitest: 4.1.10
|
||||
|
||||
'@vitest/coverage-v8@4.1.9':
|
||||
resolution: {integrity: sha512-G9/lgqibheLVBDRuya45EbsEXTYcWoSG+TLg7i2axuzx0Eq62eXn+aWXyaVdV5vKvFSWd6ywcX8hA7la9Pvu8g==}
|
||||
'@vitest/coverage-v8@4.1.10':
|
||||
resolution: {integrity: sha512-IM49HmthevbgAO4anp1hwtoT9wYe59w0LR00gr+eagHE+ZJ5lK4sLPeO0ubgoJcwLk6dehU3R24N+FbEEKDc8g==}
|
||||
peerDependencies:
|
||||
'@vitest/browser': 4.1.9
|
||||
vitest: 4.1.9
|
||||
'@vitest/browser': 4.1.10
|
||||
vitest: 4.1.10
|
||||
peerDependenciesMeta:
|
||||
'@vitest/browser':
|
||||
optional: true
|
||||
|
||||
'@vitest/expect@4.1.9':
|
||||
resolution: {integrity: sha512-vl/rYsUKcBr3SnQn166+XR5ZQcgMx3DQhFWdfli/cWpLnLUmbxZvyrJZotLFUryib+LtArYMSTJ5RbQ57ZqrlA==}
|
||||
'@vitest/expect@4.1.10':
|
||||
resolution: {integrity: sha512-YsCn+qAk1GWjQOWFEsEcL2gNQ0zmVmQu3T03qP6UyjhtmdtwtbuI+DASn/7iQB3HGTXkdBwGddzxPlmiql5vlA==}
|
||||
|
||||
'@vitest/mocker@4.1.9':
|
||||
resolution: {integrity: sha512-EVkXzBjrPGM+cK8/ANWgBrkUCfJfb38/EfTSO8h7pWvKkyPkpWxvR7BkD2MyItMF62C97zAEoqdpUixwR/e+Rw==}
|
||||
'@vitest/mocker@4.1.10':
|
||||
resolution: {integrity: sha512-v0xaezt+DKEmKfaxg133ldzADrwLGd7Ze1MfQQTYfvs8OqZIwbxyxaYURivwV7sWy5fqn3rH5uOrSp07bp44Ow==}
|
||||
peerDependencies:
|
||||
msw: ^2.4.9
|
||||
vite: ^6.0.0 || ^7.0.0 || ^8.0.0
|
||||
@@ -4990,20 +4990,20 @@ packages:
|
||||
vite:
|
||||
optional: true
|
||||
|
||||
'@vitest/pretty-format@4.1.9':
|
||||
resolution: {integrity: sha512-s0iufns3iIFitdgm+YR7g1whCAaGtXz459VS9/PqyKDEEFgYIhsHOQmXgIgDuYCt7DeQmiZT0Qe2OA2p4ZPu5A==}
|
||||
'@vitest/pretty-format@4.1.10':
|
||||
resolution: {integrity: sha512-W1HsjSH4MXQ9YfmmhLAoIYf1HRfekQCGngeIgcei6MP5QQGWUe0gkopdZQaVCFO+JDJMrAJGwa5pRpNpvy4P8Q==}
|
||||
|
||||
'@vitest/runner@4.1.9':
|
||||
resolution: {integrity: sha512-KXLMDtc7oe70+3mJfGrPUWPesswH+3sTxAMAMl8DG7I8IUQT4XW718dY5ID3vPUcmlu27CcKfY4P3h3I29SLJg==}
|
||||
'@vitest/runner@4.1.10':
|
||||
resolution: {integrity: sha512-IKI6kpIH+LmpROplyLwBBaCfMgOZOMsygVa6BARD6ahA04VRuJSa6OaVG7kRvSEMD870Vd91rSSw0eegtWyLGg==}
|
||||
|
||||
'@vitest/snapshot@4.1.9':
|
||||
resolution: {integrity: sha512-Jc7RKGNBo8Z28WYIm0Niej4xdSPByRf6mU58VpHQkd6Zh05rlnA+twjbK5HyeIGHxrzsc3mJgS43uM0CZKzaIA==}
|
||||
'@vitest/snapshot@4.1.10':
|
||||
resolution: {integrity: sha512-xRkfOT1qpTAi/Ti4Y1LtfRc3kEuqxGw59eN2jN9pRWMtS/XDevekhcFSqvQqjUNGksfjMJu3Y+oJ+4Ypn2OaJw==}
|
||||
|
||||
'@vitest/spy@4.1.9':
|
||||
resolution: {integrity: sha512-fHpsS6mIi+PiEW+vcRVOMkX1oSaPKne3VOclSFICPcGOmfKgXPU5iAah+wcNcj2xPrCCmfq99IDGf+EojhhvhA==}
|
||||
'@vitest/spy@4.1.10':
|
||||
resolution: {integrity: sha512-PLf/Ugvoq5wO/b4rwYCR1h2PSIdXz7wnkQFMiUpLdtM7l6pqVFcQIBEHyT1+l+cj7mNwAfZHzqXqDyjvOuwbDw==}
|
||||
|
||||
'@vitest/utils@4.1.9':
|
||||
resolution: {integrity: sha512-A51o8ymO5PpqlWNnBP9ZHPXDIpuMtTLlGSjN7la4US+LJzoUMyhwjA5QXlm39JexgwHKW4Xjs8Z2d3dLCXOeuA==}
|
||||
'@vitest/utils@4.1.10':
|
||||
resolution: {integrity: sha512-fy9am/HWxbaGt/Sawrp90vt6Y6jQwf1RX77cz3uwoJwJVMli/e1IEwRPnMNJ7vKfPTwo0diXifkpPvwH9v7nGA==}
|
||||
|
||||
'@wasm-audio-decoders/common@9.0.7':
|
||||
resolution: {integrity: sha512-WRaUuWSKV7pkttBygml/a6dIEpatq2nnZGFIoPTc5yPLkxL6Wk4YaslPM98OPQvWacvNZ+Py9xROGDtrFBDzag==}
|
||||
@@ -8133,20 +8133,20 @@ packages:
|
||||
yaml:
|
||||
optional: true
|
||||
|
||||
vitest@4.1.9:
|
||||
resolution: {integrity: sha512-nE3/LEyc0z87uHYLZebqCUOaJr2hdtuPp7BQ4BosVFnfltxgAvMG08NyrSGlPpOUWvR27c5flSmYFTNr78L9GQ==}
|
||||
vitest@4.1.10:
|
||||
resolution: {integrity: sha512-R9jUTe5S4Qb0HCd4TNqpC7oGcrMssMRGXLW80ubjWsW9VH5GF8y1Y0SFLY9AbqSk6nt0PnOx4H4WNJYZ13GUPw==}
|
||||
engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
'@edge-runtime/vm': '*'
|
||||
'@opentelemetry/api': ^1.9.0
|
||||
'@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0
|
||||
'@vitest/browser-playwright': 4.1.9
|
||||
'@vitest/browser-preview': 4.1.9
|
||||
'@vitest/browser-webdriverio': 4.1.9
|
||||
'@vitest/coverage-istanbul': 4.1.9
|
||||
'@vitest/coverage-v8': 4.1.9
|
||||
'@vitest/ui': 4.1.9
|
||||
'@vitest/browser-playwright': 4.1.10
|
||||
'@vitest/browser-preview': 4.1.10
|
||||
'@vitest/browser-webdriverio': 4.1.10
|
||||
'@vitest/coverage-istanbul': 4.1.10
|
||||
'@vitest/coverage-v8': 4.1.10
|
||||
'@vitest/ui': 4.1.10
|
||||
happy-dom: '*'
|
||||
jsdom: '*'
|
||||
vite: ^6.0.0 || ^7.0.0 || ^8.0.0
|
||||
@@ -9199,9 +9199,9 @@ snapshots:
|
||||
'@colors/colors@1.5.0':
|
||||
optional: true
|
||||
|
||||
'@copilotkit/aimock@1.35.0(vitest@4.1.9)':
|
||||
'@copilotkit/aimock@1.35.0(vitest@4.1.10)':
|
||||
optionalDependencies:
|
||||
vitest: 4.1.9(@opentelemetry/api@1.9.1)(@types/node@26.1.0)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(jsdom@29.1.1(@noble/hashes@2.2.0))(vite@8.1.3(@types/node@26.1.0)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.22.4)(yaml@2.9.0))
|
||||
vitest: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.0)(@vitest/browser-playwright@4.1.10)(@vitest/coverage-v8@4.1.10)(jsdom@29.1.1(@noble/hashes@2.2.0))(vite@8.1.3(@types/node@26.1.0)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.22.4)(yaml@2.9.0))
|
||||
|
||||
'@create-markdown/preview@2.0.3(shiki@4.3.0)':
|
||||
optionalDependencies:
|
||||
@@ -11244,26 +11244,26 @@ snapshots:
|
||||
|
||||
'@urbit/aura@3.0.0': {}
|
||||
|
||||
'@vitest/browser-playwright@4.1.9(playwright@1.61.1)(vite@8.1.3(@types/node@24.13.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.22.4)(yaml@2.9.0))(vitest@4.1.9)':
|
||||
'@vitest/browser-playwright@4.1.10(playwright@1.61.1)(vite@8.1.3(@types/node@24.13.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.22.4)(yaml@2.9.0))(vitest@4.1.10)':
|
||||
dependencies:
|
||||
'@vitest/browser': 4.1.9(vite@8.1.3(@types/node@24.13.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.22.4)(yaml@2.9.0))(vitest@4.1.9)
|
||||
'@vitest/mocker': 4.1.9(vite@8.1.3(@types/node@24.13.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.22.4)(yaml@2.9.0))
|
||||
'@vitest/browser': 4.1.10(vite@8.1.3(@types/node@24.13.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.22.4)(yaml@2.9.0))(vitest@4.1.10)
|
||||
'@vitest/mocker': 4.1.10(vite@8.1.3(@types/node@24.13.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.22.4)(yaml@2.9.0))
|
||||
playwright: 1.61.1
|
||||
tinyrainbow: 3.1.0
|
||||
vitest: 4.1.9(@opentelemetry/api@1.9.1)(@types/node@24.13.2)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(jsdom@29.1.1(@noble/hashes@2.2.0))(vite@8.1.3(@types/node@24.13.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.22.4)(yaml@2.9.0))
|
||||
vitest: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@24.13.2)(@vitest/browser-playwright@4.1.10)(@vitest/coverage-v8@4.1.10)(jsdom@29.1.1(@noble/hashes@2.2.0))(vite@8.1.3(@types/node@24.13.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.22.4)(yaml@2.9.0))
|
||||
transitivePeerDependencies:
|
||||
- bufferutil
|
||||
- msw
|
||||
- utf-8-validate
|
||||
- vite
|
||||
|
||||
'@vitest/browser-playwright@4.1.9(playwright@1.61.1)(vite@8.1.3(@types/node@26.1.0)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.22.4)(yaml@2.9.0))(vitest@4.1.9)':
|
||||
'@vitest/browser-playwright@4.1.10(playwright@1.61.1)(vite@8.1.3(@types/node@26.1.0)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.22.4)(yaml@2.9.0))(vitest@4.1.10)':
|
||||
dependencies:
|
||||
'@vitest/browser': 4.1.9(vite@8.1.3(@types/node@26.1.0)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.22.4)(yaml@2.9.0))(vitest@4.1.9)
|
||||
'@vitest/mocker': 4.1.9(vite@8.1.3(@types/node@26.1.0)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.22.4)(yaml@2.9.0))
|
||||
'@vitest/browser': 4.1.10(vite@8.1.3(@types/node@26.1.0)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.22.4)(yaml@2.9.0))(vitest@4.1.10)
|
||||
'@vitest/mocker': 4.1.10(vite@8.1.3(@types/node@26.1.0)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.22.4)(yaml@2.9.0))
|
||||
playwright: 1.61.1
|
||||
tinyrainbow: 3.1.0
|
||||
vitest: 4.1.9(@opentelemetry/api@1.9.1)(@types/node@26.1.0)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(jsdom@29.1.1(@noble/hashes@2.2.0))(vite@8.1.3(@types/node@26.1.0)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.22.4)(yaml@2.9.0))
|
||||
vitest: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.0)(@vitest/browser-playwright@4.1.10)(@vitest/coverage-v8@4.1.10)(jsdom@29.1.1(@noble/hashes@2.2.0))(vite@8.1.3(@types/node@26.1.0)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.22.4)(yaml@2.9.0))
|
||||
transitivePeerDependencies:
|
||||
- bufferutil
|
||||
- msw
|
||||
@@ -11271,16 +11271,16 @@ snapshots:
|
||||
- vite
|
||||
optional: true
|
||||
|
||||
'@vitest/browser@4.1.9(vite@8.1.3(@types/node@24.13.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.22.4)(yaml@2.9.0))(vitest@4.1.9)':
|
||||
'@vitest/browser@4.1.10(vite@8.1.3(@types/node@24.13.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.22.4)(yaml@2.9.0))(vitest@4.1.10)':
|
||||
dependencies:
|
||||
'@blazediff/core': 1.9.1
|
||||
'@vitest/mocker': 4.1.9(vite@8.1.3(@types/node@24.13.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.22.4)(yaml@2.9.0))
|
||||
'@vitest/utils': 4.1.9
|
||||
'@vitest/mocker': 4.1.10(vite@8.1.3(@types/node@24.13.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.22.4)(yaml@2.9.0))
|
||||
'@vitest/utils': 4.1.10
|
||||
magic-string: 0.30.21
|
||||
pngjs: 7.0.0
|
||||
sirv: 3.0.2
|
||||
tinyrainbow: 3.1.0
|
||||
vitest: 4.1.9(@opentelemetry/api@1.9.1)(@types/node@24.13.2)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(jsdom@29.1.1(@noble/hashes@2.2.0))(vite@8.1.3(@types/node@24.13.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.22.4)(yaml@2.9.0))
|
||||
vitest: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@24.13.2)(@vitest/browser-playwright@4.1.10)(@vitest/coverage-v8@4.1.10)(jsdom@29.1.1(@noble/hashes@2.2.0))(vite@8.1.3(@types/node@24.13.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.22.4)(yaml@2.9.0))
|
||||
ws: 8.21.0
|
||||
transitivePeerDependencies:
|
||||
- bufferutil
|
||||
@@ -11288,16 +11288,16 @@ snapshots:
|
||||
- utf-8-validate
|
||||
- vite
|
||||
|
||||
'@vitest/browser@4.1.9(vite@8.1.3(@types/node@26.1.0)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.22.4)(yaml@2.9.0))(vitest@4.1.9)':
|
||||
'@vitest/browser@4.1.10(vite@8.1.3(@types/node@26.1.0)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.22.4)(yaml@2.9.0))(vitest@4.1.10)':
|
||||
dependencies:
|
||||
'@blazediff/core': 1.9.1
|
||||
'@vitest/mocker': 4.1.9(vite@8.1.3(@types/node@26.1.0)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.22.4)(yaml@2.9.0))
|
||||
'@vitest/utils': 4.1.9
|
||||
'@vitest/mocker': 4.1.10(vite@8.1.3(@types/node@26.1.0)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.22.4)(yaml@2.9.0))
|
||||
'@vitest/utils': 4.1.10
|
||||
magic-string: 0.30.21
|
||||
pngjs: 7.0.0
|
||||
sirv: 3.0.2
|
||||
tinyrainbow: 3.1.0
|
||||
vitest: 4.1.9(@opentelemetry/api@1.9.1)(@types/node@26.1.0)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(jsdom@29.1.1(@noble/hashes@2.2.0))(vite@8.1.3(@types/node@26.1.0)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.22.4)(yaml@2.9.0))
|
||||
vitest: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.0)(@vitest/browser-playwright@4.1.10)(@vitest/coverage-v8@4.1.10)(jsdom@29.1.1(@noble/hashes@2.2.0))(vite@8.1.3(@types/node@26.1.0)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.22.4)(yaml@2.9.0))
|
||||
ws: 8.21.0
|
||||
transitivePeerDependencies:
|
||||
- bufferutil
|
||||
@@ -11306,10 +11306,10 @@ snapshots:
|
||||
- vite
|
||||
optional: true
|
||||
|
||||
'@vitest/coverage-v8@4.1.9(@vitest/browser@4.1.9)(vitest@4.1.9)':
|
||||
'@vitest/coverage-v8@4.1.10(@vitest/browser@4.1.10)(vitest@4.1.10)':
|
||||
dependencies:
|
||||
'@bcoe/v8-coverage': 1.0.2
|
||||
'@vitest/utils': 4.1.9
|
||||
'@vitest/utils': 4.1.10
|
||||
ast-v8-to-istanbul: 1.0.4
|
||||
istanbul-lib-coverage: 3.2.2
|
||||
istanbul-lib-report: 3.0.1
|
||||
@@ -11318,56 +11318,56 @@ snapshots:
|
||||
obug: 2.1.3
|
||||
std-env: 4.1.0
|
||||
tinyrainbow: 3.1.0
|
||||
vitest: 4.1.9(@opentelemetry/api@1.9.1)(@types/node@26.1.0)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(jsdom@29.1.1(@noble/hashes@2.2.0))(vite@8.1.3(@types/node@26.1.0)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.22.4)(yaml@2.9.0))
|
||||
vitest: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.0)(@vitest/browser-playwright@4.1.10)(@vitest/coverage-v8@4.1.10)(jsdom@29.1.1(@noble/hashes@2.2.0))(vite@8.1.3(@types/node@26.1.0)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.22.4)(yaml@2.9.0))
|
||||
optionalDependencies:
|
||||
'@vitest/browser': 4.1.9(vite@8.1.3(@types/node@26.1.0)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.22.4)(yaml@2.9.0))(vitest@4.1.9)
|
||||
'@vitest/browser': 4.1.10(vite@8.1.3(@types/node@26.1.0)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.22.4)(yaml@2.9.0))(vitest@4.1.10)
|
||||
|
||||
'@vitest/expect@4.1.9':
|
||||
'@vitest/expect@4.1.10':
|
||||
dependencies:
|
||||
'@standard-schema/spec': 1.1.0
|
||||
'@types/chai': 5.2.3
|
||||
'@vitest/spy': 4.1.9
|
||||
'@vitest/utils': 4.1.9
|
||||
'@vitest/spy': 4.1.10
|
||||
'@vitest/utils': 4.1.10
|
||||
chai: 6.2.2
|
||||
tinyrainbow: 3.1.0
|
||||
|
||||
'@vitest/mocker@4.1.9(vite@8.1.3(@types/node@24.13.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.22.4)(yaml@2.9.0))':
|
||||
'@vitest/mocker@4.1.10(vite@8.1.3(@types/node@24.13.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.22.4)(yaml@2.9.0))':
|
||||
dependencies:
|
||||
'@vitest/spy': 4.1.9
|
||||
'@vitest/spy': 4.1.10
|
||||
estree-walker: 3.0.3
|
||||
magic-string: 0.30.21
|
||||
optionalDependencies:
|
||||
vite: 8.1.3(@types/node@24.13.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.22.4)(yaml@2.9.0)
|
||||
|
||||
'@vitest/mocker@4.1.9(vite@8.1.3(@types/node@26.1.0)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.22.4)(yaml@2.9.0))':
|
||||
'@vitest/mocker@4.1.10(vite@8.1.3(@types/node@26.1.0)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.22.4)(yaml@2.9.0))':
|
||||
dependencies:
|
||||
'@vitest/spy': 4.1.9
|
||||
'@vitest/spy': 4.1.10
|
||||
estree-walker: 3.0.3
|
||||
magic-string: 0.30.21
|
||||
optionalDependencies:
|
||||
vite: 8.1.3(@types/node@26.1.0)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.22.4)(yaml@2.9.0)
|
||||
|
||||
'@vitest/pretty-format@4.1.9':
|
||||
'@vitest/pretty-format@4.1.10':
|
||||
dependencies:
|
||||
tinyrainbow: 3.1.0
|
||||
|
||||
'@vitest/runner@4.1.9':
|
||||
'@vitest/runner@4.1.10':
|
||||
dependencies:
|
||||
'@vitest/utils': 4.1.9
|
||||
'@vitest/utils': 4.1.10
|
||||
pathe: 2.0.3
|
||||
|
||||
'@vitest/snapshot@4.1.9':
|
||||
'@vitest/snapshot@4.1.10':
|
||||
dependencies:
|
||||
'@vitest/pretty-format': 4.1.9
|
||||
'@vitest/utils': 4.1.9
|
||||
'@vitest/pretty-format': 4.1.10
|
||||
'@vitest/utils': 4.1.10
|
||||
magic-string: 0.30.21
|
||||
pathe: 2.0.3
|
||||
|
||||
'@vitest/spy@4.1.9': {}
|
||||
'@vitest/spy@4.1.10': {}
|
||||
|
||||
'@vitest/utils@4.1.9':
|
||||
'@vitest/utils@4.1.10':
|
||||
dependencies:
|
||||
'@vitest/pretty-format': 4.1.9
|
||||
'@vitest/pretty-format': 4.1.10
|
||||
convert-source-map: 2.0.0
|
||||
tinyrainbow: 3.1.0
|
||||
|
||||
@@ -14927,15 +14927,15 @@ snapshots:
|
||||
tsx: 4.22.4
|
||||
yaml: 2.9.0
|
||||
|
||||
vitest@4.1.9(@opentelemetry/api@1.9.1)(@types/node@24.13.2)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(jsdom@29.1.1(@noble/hashes@2.2.0))(vite@8.1.3(@types/node@24.13.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.22.4)(yaml@2.9.0)):
|
||||
vitest@4.1.10(@opentelemetry/api@1.9.1)(@types/node@24.13.2)(@vitest/browser-playwright@4.1.10)(@vitest/coverage-v8@4.1.10)(jsdom@29.1.1(@noble/hashes@2.2.0))(vite@8.1.3(@types/node@24.13.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.22.4)(yaml@2.9.0)):
|
||||
dependencies:
|
||||
'@vitest/expect': 4.1.9
|
||||
'@vitest/mocker': 4.1.9(vite@8.1.3(@types/node@24.13.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.22.4)(yaml@2.9.0))
|
||||
'@vitest/pretty-format': 4.1.9
|
||||
'@vitest/runner': 4.1.9
|
||||
'@vitest/snapshot': 4.1.9
|
||||
'@vitest/spy': 4.1.9
|
||||
'@vitest/utils': 4.1.9
|
||||
'@vitest/expect': 4.1.10
|
||||
'@vitest/mocker': 4.1.10(vite@8.1.3(@types/node@24.13.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.22.4)(yaml@2.9.0))
|
||||
'@vitest/pretty-format': 4.1.10
|
||||
'@vitest/runner': 4.1.10
|
||||
'@vitest/snapshot': 4.1.10
|
||||
'@vitest/spy': 4.1.10
|
||||
'@vitest/utils': 4.1.10
|
||||
es-module-lexer: 2.3.0
|
||||
expect-type: 1.4.0
|
||||
magic-string: 0.30.21
|
||||
@@ -14952,21 +14952,21 @@ snapshots:
|
||||
optionalDependencies:
|
||||
'@opentelemetry/api': 1.9.1
|
||||
'@types/node': 24.13.2
|
||||
'@vitest/browser-playwright': 4.1.9(playwright@1.61.1)(vite@8.1.3(@types/node@24.13.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.22.4)(yaml@2.9.0))(vitest@4.1.9)
|
||||
'@vitest/coverage-v8': 4.1.9(@vitest/browser@4.1.9)(vitest@4.1.9)
|
||||
'@vitest/browser-playwright': 4.1.10(playwright@1.61.1)(vite@8.1.3(@types/node@24.13.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.22.4)(yaml@2.9.0))(vitest@4.1.10)
|
||||
'@vitest/coverage-v8': 4.1.10(@vitest/browser@4.1.10)(vitest@4.1.10)
|
||||
jsdom: 29.1.1(@noble/hashes@2.2.0)
|
||||
transitivePeerDependencies:
|
||||
- msw
|
||||
|
||||
vitest@4.1.9(@opentelemetry/api@1.9.1)(@types/node@26.1.0)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(jsdom@29.1.1(@noble/hashes@2.2.0))(vite@8.1.3(@types/node@26.1.0)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.22.4)(yaml@2.9.0)):
|
||||
vitest@4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.0)(@vitest/browser-playwright@4.1.10)(@vitest/coverage-v8@4.1.10)(jsdom@29.1.1(@noble/hashes@2.2.0))(vite@8.1.3(@types/node@26.1.0)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.22.4)(yaml@2.9.0)):
|
||||
dependencies:
|
||||
'@vitest/expect': 4.1.9
|
||||
'@vitest/mocker': 4.1.9(vite@8.1.3(@types/node@26.1.0)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.22.4)(yaml@2.9.0))
|
||||
'@vitest/pretty-format': 4.1.9
|
||||
'@vitest/runner': 4.1.9
|
||||
'@vitest/snapshot': 4.1.9
|
||||
'@vitest/spy': 4.1.9
|
||||
'@vitest/utils': 4.1.9
|
||||
'@vitest/expect': 4.1.10
|
||||
'@vitest/mocker': 4.1.10(vite@8.1.3(@types/node@26.1.0)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.22.4)(yaml@2.9.0))
|
||||
'@vitest/pretty-format': 4.1.10
|
||||
'@vitest/runner': 4.1.10
|
||||
'@vitest/snapshot': 4.1.10
|
||||
'@vitest/spy': 4.1.10
|
||||
'@vitest/utils': 4.1.10
|
||||
es-module-lexer: 2.3.0
|
||||
expect-type: 1.4.0
|
||||
magic-string: 0.30.21
|
||||
@@ -14983,8 +14983,8 @@ snapshots:
|
||||
optionalDependencies:
|
||||
'@opentelemetry/api': 1.9.1
|
||||
'@types/node': 26.1.0
|
||||
'@vitest/browser-playwright': 4.1.9(playwright@1.61.1)(vite@8.1.3(@types/node@26.1.0)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.22.4)(yaml@2.9.0))(vitest@4.1.9)
|
||||
'@vitest/coverage-v8': 4.1.9(@vitest/browser@4.1.9)(vitest@4.1.9)
|
||||
'@vitest/browser-playwright': 4.1.10(playwright@1.61.1)(vite@8.1.3(@types/node@26.1.0)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.22.4)(yaml@2.9.0))(vitest@4.1.10)
|
||||
'@vitest/coverage-v8': 4.1.10(@vitest/browser@4.1.10)(vitest@4.1.10)
|
||||
jsdom: 29.1.1(@noble/hashes@2.2.0)
|
||||
transitivePeerDependencies:
|
||||
- msw
|
||||
|
||||
@@ -45,6 +45,8 @@ export function runShardPlans(
|
||||
options?: {
|
||||
concurrency?: number;
|
||||
env?: Record<string, string | undefined>;
|
||||
fsModuleCacheMaxBytes?: number;
|
||||
nodeCompileCacheMaxBytes?: number;
|
||||
runChild?: (
|
||||
args: string[],
|
||||
childEnv: Record<string, string | undefined>,
|
||||
|
||||
@@ -14,9 +14,14 @@ import { acquireLocalHeavyCheckLockSync } from "./lib/local-heavy-check-runtime.
|
||||
// stacking outer and inner parallelism oversubscribes the 4 vCPU runner class.
|
||||
const PLAN_CONCURRENCY = 2;
|
||||
const FS_MODULE_CACHE_PATH_ENV_KEY = "OPENCLAW_VITEST_FS_MODULE_CACHE_PATH";
|
||||
const FS_MODULE_CACHE_WRITER_ENV_KEY = "OPENCLAW_VITEST_FS_MODULE_CACHE_WRITER";
|
||||
const NODE_COMPILE_CACHE_PATH_ENV_KEY = "NODE_COMPILE_CACHE";
|
||||
const NODE_COMPILE_CACHE_WRITER_ENV_KEY = "OPENCLAW_NODE_COMPILE_CACHE_WRITER";
|
||||
const FS_MODULE_CACHE_MAX_BYTES = 2 * 1024 * 1024 * 1024;
|
||||
const NODE_COMPILE_CACHE_MAX_BYTES = 1024 * 1024 * 1024;
|
||||
const FS_MODULE_CACHE_PRUNE_TARGET_RATIO = 0.75;
|
||||
const FS_MODULE_CACHE_METADATA_FILE = "_metadata.json";
|
||||
const FS_MODULE_CACHE_GENERATION_FILE = ".openclaw-transform-generation";
|
||||
|
||||
function parseJsonEnv(env, name, fallback = null) {
|
||||
try {
|
||||
@@ -115,7 +120,10 @@ export function pruneFsModuleCache(root, maxBytes = FS_MODULE_CACHE_MAX_BYTES) {
|
||||
}
|
||||
const fileStat = statSync(filePath);
|
||||
totalBytes += fileStat.size;
|
||||
if (entry.name !== FS_MODULE_CACHE_METADATA_FILE) {
|
||||
if (
|
||||
entry.name !== FS_MODULE_CACHE_METADATA_FILE &&
|
||||
entry.name !== FS_MODULE_CACHE_GENERATION_FILE
|
||||
) {
|
||||
files.push({ filePath, mtimeMs: fileStat.mtimeMs, size: fileStat.size });
|
||||
}
|
||||
}
|
||||
@@ -216,6 +224,7 @@ export async function runShardPlans(plans, options = {}) {
|
||||
const runner = options.runChild ?? runChild;
|
||||
const scratchDir = options.scratchDir ?? mkdtempSync(join(tmpdir(), "openclaw-node-shard-"));
|
||||
const persistentCacheRoot = baseEnv[FS_MODULE_CACHE_PATH_ENV_KEY]?.trim();
|
||||
const nodeCompileCacheRoot = baseEnv[NODE_COMPILE_CACHE_PATH_ENV_KEY]?.trim();
|
||||
|
||||
let nextIndex = 0;
|
||||
let exitCode = 0;
|
||||
@@ -243,16 +252,30 @@ export async function runShardPlans(plans, options = {}) {
|
||||
}
|
||||
});
|
||||
await Promise.all(workers);
|
||||
if (persistentCacheRoot) {
|
||||
if (persistentCacheRoot && baseEnv[FS_MODULE_CACHE_WRITER_ENV_KEY] === "1") {
|
||||
try {
|
||||
const pruned = pruneFsModuleCache(persistentCacheRoot);
|
||||
if (pruned.removedFiles > 0) {
|
||||
process.stdout.write(
|
||||
`[shard:cache] pruned ${pruned.removedFiles} files (${pruned.beforeBytes} -> ${pruned.afterBytes} bytes)\n`,
|
||||
);
|
||||
}
|
||||
const pruned = pruneFsModuleCache(
|
||||
persistentCacheRoot,
|
||||
options.fsModuleCacheMaxBytes ?? FS_MODULE_CACHE_MAX_BYTES,
|
||||
);
|
||||
process.stdout.write(
|
||||
`[shard:cache] vitest ${pruned.beforeBytes} -> ${pruned.afterBytes} bytes; removed ${pruned.removedFiles} files\n`,
|
||||
);
|
||||
} catch (error) {
|
||||
console.warn(`[shard:cache] failed to prune persistent cache: ${String(error)}`);
|
||||
console.warn(`[shard:cache] failed to prune Vitest cache: ${String(error)}`);
|
||||
}
|
||||
}
|
||||
if (nodeCompileCacheRoot && baseEnv[NODE_COMPILE_CACHE_WRITER_ENV_KEY] === "1") {
|
||||
try {
|
||||
const pruned = pruneFsModuleCache(
|
||||
nodeCompileCacheRoot,
|
||||
options.nodeCompileCacheMaxBytes ?? NODE_COMPILE_CACHE_MAX_BYTES,
|
||||
);
|
||||
process.stdout.write(
|
||||
`[shard:cache] node-compile ${pruned.beforeBytes} -> ${pruned.afterBytes} bytes; removed ${pruned.removedFiles} files\n`,
|
||||
);
|
||||
} catch (error) {
|
||||
console.warn(`[shard:cache] failed to prune Node compile cache: ${String(error)}`);
|
||||
}
|
||||
}
|
||||
return exitCode;
|
||||
|
||||
@@ -18,6 +18,7 @@ export type NodeTestShard = {
|
||||
groups?: NodeTestShardGroup[];
|
||||
timeoutMinutes?: number;
|
||||
planConcurrency?: number;
|
||||
saveVitestFsCache?: boolean;
|
||||
};
|
||||
|
||||
export type NodeTestPlanOptions = {
|
||||
@@ -36,3 +37,6 @@ export function createNodeTestShardBundles(
|
||||
options: NodeTestPlanOptions & { compact: true },
|
||||
): CompactNodeTestShard[];
|
||||
export function createNodeTestShardBundles(options?: NodeTestPlanOptions): NodeTestShard[];
|
||||
export function assignVitestFsCacheWriter<T extends Pick<NodeTestShard, "shardName" | "groups">>(
|
||||
shards: T[],
|
||||
): Array<T & { saveVitestFsCache: boolean }>;
|
||||
|
||||
@@ -1419,6 +1419,24 @@ export function createNodeTestShardBundles(options = {}) {
|
||||
return [...unbundled, ...bundled].toSorted(compareFullNodeTestAdmissionOrder);
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark one semantic cache producer without coupling persistence to matrix order.
|
||||
* The broad core unit graph is shared by most shards; precise changed plans
|
||||
* fall back to their first (normally only) job.
|
||||
*/
|
||||
export function assignVitestFsCacheWriter(shards) {
|
||||
const preferredIndex = shards.findIndex(
|
||||
(shard) =>
|
||||
shard.shardName === "core-unit-fast" ||
|
||||
shard.groups?.some((group) => group.shard_name === "core-unit-fast"),
|
||||
);
|
||||
const writerIndex = preferredIndex >= 0 ? preferredIndex : shards.length > 0 ? 0 : -1;
|
||||
return shards.map((shard, index) => ({
|
||||
...shard,
|
||||
saveVitestFsCache: index === writerIndex,
|
||||
}));
|
||||
}
|
||||
|
||||
function createCompactNodeTestShardBundles(options = {}) {
|
||||
const shards = createNodeTestShards(options);
|
||||
const groupsByRunner = new Map();
|
||||
|
||||
+21
-7
@@ -350,25 +350,39 @@ function resolveExplicitVitestMode(argv) {
|
||||
return mode;
|
||||
}
|
||||
|
||||
function resolveVitestCompileCacheSafeEnv(env) {
|
||||
if (!env.NODE_COMPILE_CACHE && !env.NODE_COMPILE_CACHE_PORTABLE) {
|
||||
return env;
|
||||
}
|
||||
// Coverage can be enabled inside a dynamic Vitest config, which this wrapper
|
||||
// cannot know before spawning. Keep the cache for orchestration/build tools,
|
||||
// but never let a Vitest child deserialize bytecode into V8 coverage.
|
||||
const spawnEnv = { ...env, NODE_DISABLE_COMPILE_CACHE: "1" };
|
||||
delete spawnEnv.NODE_COMPILE_CACHE;
|
||||
delete spawnEnv.NODE_COMPILE_CACHE_PORTABLE;
|
||||
return spawnEnv;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds default watchdog env for non-watch Vitest runs.
|
||||
*/
|
||||
export function resolveRunVitestSpawnEnv(env = process.env, argv = []) {
|
||||
const baseEnv = resolveVitestCompileCacheSafeEnv(env);
|
||||
const explicitMode = resolveExplicitVitestMode(argv);
|
||||
if (explicitMode === "watch") {
|
||||
return env;
|
||||
return baseEnv;
|
||||
}
|
||||
if (explicitMode !== "run" && !isTruthyEnvValue(env.CI)) {
|
||||
return env;
|
||||
if (explicitMode !== "run" && !isTruthyEnvValue(baseEnv.CI)) {
|
||||
return baseEnv;
|
||||
}
|
||||
const defaultTimeoutMs = resolveDefaultVitestNoOutputTimeoutMs(argv);
|
||||
const hasTimeout = Object.hasOwn(env, VITEST_NO_OUTPUT_TIMEOUT_ENV_KEY);
|
||||
const hasTimeout = Object.hasOwn(baseEnv, VITEST_NO_OUTPUT_TIMEOUT_ENV_KEY);
|
||||
const timeoutMs = hasTimeout
|
||||
? parsePositiveInt(env[VITEST_NO_OUTPUT_TIMEOUT_ENV_KEY])
|
||||
? parsePositiveInt(baseEnv[VITEST_NO_OUTPUT_TIMEOUT_ENV_KEY])
|
||||
: defaultTimeoutMs;
|
||||
const hasHeartbeat = Object.hasOwn(env, VITEST_NO_OUTPUT_HEARTBEAT_ENV_KEY);
|
||||
const hasHeartbeat = Object.hasOwn(baseEnv, VITEST_NO_OUTPUT_HEARTBEAT_ENV_KEY);
|
||||
return {
|
||||
...env,
|
||||
...baseEnv,
|
||||
...(!hasTimeout ? { [VITEST_NO_OUTPUT_TIMEOUT_ENV_KEY]: String(defaultTimeoutMs) } : {}),
|
||||
...(!hasHeartbeat && timeoutMs !== null && DEFAULT_VITEST_NO_OUTPUT_HEARTBEAT_MS < timeoutMs
|
||||
? { [VITEST_NO_OUTPUT_HEARTBEAT_ENV_KEY]: String(DEFAULT_VITEST_NO_OUTPUT_HEARTBEAT_MS) }
|
||||
|
||||
@@ -4,8 +4,10 @@ import { join, relative, resolve } from "node:path";
|
||||
import fg from "fast-glob";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import {
|
||||
assignVitestFsCacheWriter,
|
||||
createNodeTestShardBundles,
|
||||
createNodeTestShards,
|
||||
type NodeTestShard,
|
||||
} from "../../scripts/lib/ci-node-test-plan.mjs";
|
||||
import { expectNoNodeFsScans } from "../../src/test-utils/fs-scan-assertions.js";
|
||||
import { listGitTrackedFiles, sortRepoPaths, toRepoPath } from "../../src/test-utils/repo-files.js";
|
||||
@@ -107,6 +109,40 @@ function isGatewayServerTestFile(file: string): boolean {
|
||||
}
|
||||
|
||||
describe("scripts/lib/ci-node-test-plan.mjs", () => {
|
||||
it("assigns one semantic Vitest cache writer without changing shard order", () => {
|
||||
const full = createNodeTestShardBundles({ includeReleaseOnlyPluginShards: false });
|
||||
const compact = createNodeTestShardBundles({
|
||||
includeReleaseOnlyPluginShards: false,
|
||||
compact: true,
|
||||
});
|
||||
|
||||
const expectWriter = (plan: Array<Pick<NodeTestShard, "groups" | "shardName">>) => {
|
||||
const marked = assignVitestFsCacheWriter(plan);
|
||||
expect(marked.map((shard) => shard.shardName)).toEqual(plan.map((shard) => shard.shardName));
|
||||
expect(marked.filter((shard) => shard.saveVitestFsCache)).toHaveLength(1);
|
||||
expect(
|
||||
marked.find((shard) => shard.saveVitestFsCache)?.shardName === "core-unit-fast" ||
|
||||
marked
|
||||
.find((shard) => shard.saveVitestFsCache)
|
||||
?.groups?.some((group) => group.shard_name === "core-unit-fast"),
|
||||
).toBe(true);
|
||||
};
|
||||
expectWriter(full);
|
||||
expectWriter(compact);
|
||||
|
||||
expect(assignVitestFsCacheWriter([])).toEqual([]);
|
||||
const changedOnly = {
|
||||
checkName: "checks-node-changed-only",
|
||||
configs: ["test/vitest/vitest.unit.config.ts"],
|
||||
requiresDist: false,
|
||||
runner: DEFAULT_NODE_TEST_RUNNER,
|
||||
shardName: "changed-only",
|
||||
};
|
||||
expect(assignVitestFsCacheWriter([changedOnly])).toEqual([
|
||||
{ ...changedOnly, saveVitestFsCache: true },
|
||||
]);
|
||||
});
|
||||
|
||||
it("creates split shards without walking test roots", () => {
|
||||
const payload = expectNoNodeFsScans<{
|
||||
includePatterns: number;
|
||||
|
||||
@@ -199,24 +199,53 @@ describe("scripts/ci-run-node-test-shard.mjs", () => {
|
||||
const slot = path.join(persistentRoot, "vitest-cache-0");
|
||||
mkdirSync(slot, { recursive: true });
|
||||
const metadata = path.join(slot, "_metadata.json");
|
||||
const generation = path.join(persistentRoot, ".openclaw-transform-generation");
|
||||
const oldest = path.join(slot, "oldest");
|
||||
const newest = path.join(slot, "newest");
|
||||
writeFileSync(metadata, "{}", "utf8");
|
||||
writeFileSync(generation, "g", "utf8");
|
||||
writeFileSync(oldest, "aaaaaaaa", "utf8");
|
||||
writeFileSync(newest, "bbbbbbbb", "utf8");
|
||||
utimesSync(oldest, new Date(1_000), new Date(1_000));
|
||||
utimesSync(newest, new Date(2_000), new Date(2_000));
|
||||
|
||||
expect(pruneFsModuleCache(persistentRoot, 16)).toEqual({
|
||||
beforeBytes: 18,
|
||||
afterBytes: 10,
|
||||
beforeBytes: 19,
|
||||
afterBytes: 11,
|
||||
removedFiles: 1,
|
||||
});
|
||||
expect(existsSync(metadata)).toBe(true);
|
||||
expect(existsSync(generation)).toBe(true);
|
||||
expect(existsSync(oldest)).toBe(false);
|
||||
expect(existsSync(newest)).toBe(true);
|
||||
});
|
||||
|
||||
it("prunes persistent caches only in the designated writer job", async () => {
|
||||
const persistentRoot = makeScratchDir();
|
||||
const transform = path.join(persistentRoot, "vitest-cache-0", "entry");
|
||||
mkdirSync(path.dirname(transform), { recursive: true });
|
||||
writeFileSync(transform, "cached", "utf8");
|
||||
const plans = resolveShardPlans({
|
||||
OPENCLAW_NODE_TEST_CONFIGS_JSON: JSON.stringify(["test/vitest/vitest.unit.config.ts"]),
|
||||
});
|
||||
const run = (writer: string) =>
|
||||
runShardPlans(plans, {
|
||||
concurrency: 1,
|
||||
env: {
|
||||
OPENCLAW_VITEST_FS_MODULE_CACHE_PATH: persistentRoot,
|
||||
OPENCLAW_VITEST_FS_MODULE_CACHE_WRITER: writer,
|
||||
},
|
||||
fsModuleCacheMaxBytes: 0,
|
||||
runChild: async () => 0,
|
||||
scratchDir: makeScratchDir(),
|
||||
});
|
||||
|
||||
await run("0");
|
||||
expect(existsSync(transform)).toBe(true);
|
||||
await run("1");
|
||||
expect(existsSync(transform)).toBe(false);
|
||||
});
|
||||
|
||||
it("stops scheduling new plans after a failure and reports the first failing code", async () => {
|
||||
const scratchDir = makeScratchDir();
|
||||
const started: string[] = [];
|
||||
|
||||
@@ -1777,7 +1777,7 @@ describe("ci workflow guards", () => {
|
||||
}
|
||||
});
|
||||
|
||||
it("persists isolated Vitest transform caches for Linux Node shards", () => {
|
||||
it("persists isolated transform and compile caches with one semantic writer", () => {
|
||||
const workflow = readCiWorkflow();
|
||||
const nodeTestJob = workflow.jobs["checks-node-core-test-nondist-shard"];
|
||||
const setupNodeStep = nodeTestJob.steps.find(
|
||||
@@ -1787,6 +1787,9 @@ describe("ci workflow guards", () => {
|
||||
const stickyStep = action.runs.steps.find(
|
||||
(step: WorkflowStep) => step.name === "Mount Vitest transform cache sticky disk",
|
||||
);
|
||||
const protectedSeedStep = action.runs.steps.find(
|
||||
(step: WorkflowStep) => step.name === "Mount protected Vitest transform seed",
|
||||
);
|
||||
const writerStep = action.runs.steps.find(
|
||||
(step: WorkflowStep) => step.name === "Restore and save Vitest transform cache",
|
||||
);
|
||||
@@ -1796,32 +1799,118 @@ describe("ci workflow guards", () => {
|
||||
const configureStep = action.runs.steps.find(
|
||||
(step: WorkflowStep) => step.name === "Configure Vitest transform cache",
|
||||
);
|
||||
const compileStickyStep = action.runs.steps.find(
|
||||
(step: WorkflowStep) => step.name === "Mount Node compile cache sticky disk",
|
||||
);
|
||||
const compileWriterStep = action.runs.steps.find(
|
||||
(step: WorkflowStep) => step.name === "Restore and save Node compile cache",
|
||||
);
|
||||
const compileReaderStep = action.runs.steps.find(
|
||||
(step: WorkflowStep) => step.name === "Restore Node compile cache",
|
||||
);
|
||||
const compileConfigureStep = action.runs.steps.find(
|
||||
(step: WorkflowStep) => step.name === "Configure Node compile cache",
|
||||
);
|
||||
const buildSetupNodeStep = workflow.jobs["build-artifacts"].steps.find(
|
||||
(step: WorkflowStep) => step.name === "Setup Node environment",
|
||||
);
|
||||
|
||||
expect(setupNodeStep.with).toMatchObject({
|
||||
"node-compile-cache": "true",
|
||||
"node-compile-cache-scope": "test",
|
||||
"vitest-fs-cache": "true",
|
||||
"save-vitest-fs-cache": "${{ strategy.job-index == 0 && 'true' || 'false' }}",
|
||||
"save-node-compile-cache": "${{ matrix.save_vitest_fs_cache && 'true' || 'false' }}",
|
||||
"save-vitest-fs-cache": "${{ matrix.save_vitest_fs_cache && 'true' || 'false' }}",
|
||||
});
|
||||
expect(action.inputs["vitest-fs-cache"].default).toBe("false");
|
||||
expect(action.inputs["save-vitest-fs-cache"].default).toBe("false");
|
||||
expect(action.inputs["node-compile-cache"].default).toBe("false");
|
||||
expect(action.inputs["node-compile-cache-scope"].default).toBe("test");
|
||||
expect(action.inputs["save-node-compile-cache"].default).toBe("false");
|
||||
expect(protectedSeedStep).toMatchObject({
|
||||
uses: "useblacksmith/stickydisk@5b350170ae4ef55b536b548ef5f5896e76a6b54f",
|
||||
with: {
|
||||
path: "/var/tmp/openclaw-vitest-fs-protected-seed",
|
||||
commit: false,
|
||||
},
|
||||
});
|
||||
expect(protectedSeedStep.if).toContain("github.event_name == 'pull_request'");
|
||||
expect(stickyStep).toMatchObject({
|
||||
uses: "useblacksmith/stickydisk@5b350170ae4ef55b536b548ef5f5896e76a6b54f",
|
||||
with: {
|
||||
path: "/var/tmp/openclaw-vitest-fs-cache",
|
||||
commit: "${{ inputs.save-vitest-fs-cache == 'true' && 'on-change' || 'false' }}",
|
||||
commit: "${{ inputs.save-vitest-fs-cache == 'true' && 'true' || 'false' }}",
|
||||
},
|
||||
});
|
||||
expect(stickyStep.if).toContain("inputs.sticky-disk == 'true'");
|
||||
expect(stickyStep.with.key).toContain("vitest-fs-v1-");
|
||||
expect(stickyStep.with.key).toContain("vitest-fs-v2-");
|
||||
expect(stickyStep.with.key).toContain("format('pr-{0}', github.event.pull_request.number)");
|
||||
expect(stickyStep.with.key).toContain("hashFiles('pnpm-lock.yaml')");
|
||||
expect(stickyStep.with.key).not.toContain("hashFiles");
|
||||
expect(writerStep.uses).toBe("actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae");
|
||||
expect(writerStep.if).toContain("inputs.save-vitest-fs-cache == 'true'");
|
||||
expect(writerStep.with.key).toContain("github.run_id");
|
||||
expect(writerStep.with["restore-keys"]).toContain("hashFiles('pnpm-lock.yaml')");
|
||||
expect(writerStep.with.key).toContain("github.run_attempt");
|
||||
expect(writerStep.with["restore-keys"]).toContain("**/tsconfig*.json");
|
||||
expect(readerStep.uses).toBe(CACHE_V5);
|
||||
expect(readerStep.if).toContain("inputs.save-vitest-fs-cache != 'true'");
|
||||
expect(readerStep.with["restore-keys"]).toBe(writerStep.with["restore-keys"]);
|
||||
expect(configureStep.run).toContain("OPENCLAW_VITEST_FS_MODULE_CACHE_PATH=$cache_root");
|
||||
expect(configureStep.run).toContain(".openclaw-transform-generation");
|
||||
expect(configureStep.run).toContain('"$(<"$seed_generation_file")" == "$CACHE_GENERATION"');
|
||||
expect(configureStep.run).toContain("Ignoring protected Vitest transform seed");
|
||||
expect(configureStep.run).toContain("OPENCLAW_VITEST_FS_MODULE_CACHE_WRITER=");
|
||||
expect(compileStickyStep.with).toMatchObject({
|
||||
path: "/var/tmp/openclaw-node-compile-cache",
|
||||
commit:
|
||||
"${{ inputs.save-node-compile-cache == 'true' && github.event_name != 'pull_request' && 'true' || 'false' }}",
|
||||
});
|
||||
expect(compileStickyStep.with.key).toContain(
|
||||
"node-compile-v2-${{ inputs.node-compile-cache-scope }}-protected-",
|
||||
);
|
||||
expect(compileWriterStep.with.key).toContain(
|
||||
"node-compile-v2-${{ inputs.node-compile-cache-scope }}-",
|
||||
);
|
||||
expect(compileWriterStep.with.key).toContain("github.run_attempt");
|
||||
expect(compileReaderStep.with["restore-keys"]).toBe(compileWriterStep.with["restore-keys"]);
|
||||
expect(compileConfigureStep.run).toContain("NODE_COMPILE_CACHE=$cache_root");
|
||||
expect(compileConfigureStep.run).toContain("NODE_COMPILE_CACHE_PORTABLE=1");
|
||||
expect(buildSetupNodeStep.with).toMatchObject({
|
||||
"node-compile-cache": "true",
|
||||
"node-compile-cache-scope": "build",
|
||||
"save-node-compile-cache":
|
||||
"${{ github.event_name == 'push' && github.ref == 'refs/heads/main' && 'true' || 'false' }}",
|
||||
});
|
||||
expect(buildSetupNodeStep.with["node-compile-cache-scope"]).not.toBe(
|
||||
setupNodeStep.with["node-compile-cache-scope"],
|
||||
);
|
||||
});
|
||||
|
||||
it("warms protected caches without main-run cancellation and cleans closed PR archives", () => {
|
||||
const warmerSource = readFileSync(".github/workflows/vitest-cache-warm.yml", "utf8");
|
||||
const warmer = parse(warmerSource);
|
||||
const cleanup = parse(readFileSync(".github/workflows/pr-cache-cleanup.yml", "utf8"));
|
||||
const warmerSetup = warmer.jobs.warm.steps.find(
|
||||
(step: WorkflowStep) => step.name === "Setup Node environment",
|
||||
);
|
||||
|
||||
expect(warmer.concurrency["cancel-in-progress"]).toBe(false);
|
||||
expect(warmer.on.workflow_dispatch).toBeUndefined();
|
||||
expect(warmer.on.repository_dispatch.types).toEqual(["vitest-cache-warm"]);
|
||||
expect(warmer.jobs.warm.if).toBe("github.repository == 'openclaw/openclaw'");
|
||||
expect(warmerSource).toContain('cron: "17 8 * * *"');
|
||||
expect(warmerSource).toContain('candidate.shardName === "core-unit-fast"');
|
||||
expect(warmerSetup.with).toMatchObject({
|
||||
"node-compile-cache-scope": "test",
|
||||
"save-node-compile-cache": "true",
|
||||
"save-vitest-fs-cache": "true",
|
||||
"sticky-disk": "true",
|
||||
"vitest-fs-cache": "true",
|
||||
});
|
||||
expect(cleanup.permissions.actions).toBe("write");
|
||||
expect(cleanup.on.pull_request_target.types).toEqual(["closed"]);
|
||||
expect(cleanup.on.pull_request).toBeUndefined();
|
||||
expect(cleanup.jobs.cleanup.steps[0].run).toContain("gh cache delete");
|
||||
expect(cleanup.jobs.cleanup.steps[0].run).toContain("--ref");
|
||||
});
|
||||
|
||||
it("uses bundled Node shards and telemetry-backed runner sizes", () => {
|
||||
|
||||
@@ -492,6 +492,30 @@ describe("scripts/run-vitest", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("disables an inherited Node compile cache for every Vitest child", () => {
|
||||
expect(
|
||||
resolveRunVitestSpawnEnv(
|
||||
{
|
||||
NODE_COMPILE_CACHE: "/tmp/node-compile",
|
||||
NODE_COMPILE_CACHE_PORTABLE: "1",
|
||||
PATH: "/usr/bin",
|
||||
},
|
||||
["run"],
|
||||
),
|
||||
).toEqual({
|
||||
NODE_DISABLE_COMPILE_CACHE: "1",
|
||||
OPENCLAW_VITEST_NO_OUTPUT_HEARTBEAT_MS: "30000",
|
||||
OPENCLAW_VITEST_NO_OUTPUT_TIMEOUT_MS: "120000",
|
||||
PATH: "/usr/bin",
|
||||
});
|
||||
expect(
|
||||
resolveRunVitestSpawnEnv({ NODE_COMPILE_CACHE: "/tmp/node-compile", PATH: "/usr/bin" }, [
|
||||
"run",
|
||||
"--coverage=false",
|
||||
]),
|
||||
).toMatchObject({ NODE_DISABLE_COMPILE_CACHE: "1" });
|
||||
});
|
||||
|
||||
it("uses a longer default stall watchdog for broad e2e and project shard configs", () => {
|
||||
const timeout = String(DEFAULT_LONG_RUNNING_VITEST_NO_OUTPUT_TIMEOUT_MS);
|
||||
const extraLongTimeout = String(DEFAULT_EXTRA_LONG_RUNNING_VITEST_NO_OUTPUT_TIMEOUT_MS);
|
||||
|
||||
+3
-3
@@ -41,12 +41,12 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/markdown-it": "14.1.2",
|
||||
"@vitest/browser": "4.1.9",
|
||||
"@vitest/browser-playwright": "4.1.9",
|
||||
"@vitest/browser": "4.1.10",
|
||||
"@vitest/browser-playwright": "4.1.10",
|
||||
"jsdom": "29.1.1",
|
||||
"openclaw": "workspace:*",
|
||||
"playwright": "1.61.1",
|
||||
"vite": "8.1.3",
|
||||
"vitest": "4.1.9"
|
||||
"vitest": "4.1.10"
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user