mirror of
https://github.com/openclaw/openclaw.git
synced 2026-06-06 05:51:15 +08:00
docs: document test project scripts
This commit is contained in:
@@ -1,3 +1,5 @@
|
||||
// Dispatches Vitest project shards for explicit targets, changed files, or the
|
||||
// full local suite.
|
||||
import fs from "node:fs";
|
||||
import { performance } from "node:perf_hooks";
|
||||
import { formatMs } from "./lib/check-timing-summary.mjs";
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// Test-project planning helpers used by scripts/run-vitest.mjs,
|
||||
// scripts/test-projects.mjs, and focused tests. Exports are intentionally
|
||||
// granular so project selection stays testable without spawning Vitest.
|
||||
import { spawnSync } from "node:child_process";
|
||||
import fs from "node:fs";
|
||||
import os from "node:os";
|
||||
@@ -250,6 +253,9 @@ function uniqueOrdered(values) {
|
||||
return [...new Set(values)];
|
||||
}
|
||||
|
||||
/**
|
||||
* Orders full-suite specs so expensive shards start first in parallel runs.
|
||||
*/
|
||||
export function orderFullSuiteSpecsForParallelRun(specs, shardTimings = new Map()) {
|
||||
const sortedSpecs = specs.toSorted((a, b) => {
|
||||
const weightDelta =
|
||||
@@ -831,7 +837,9 @@ const BROAD_CHANGED_ENV_KEY = "OPENCLAW_TEST_CHANGED_BROAD";
|
||||
const VITEST_NO_OUTPUT_TIMEOUT_ENV_KEY = "OPENCLAW_VITEST_NO_OUTPUT_TIMEOUT_MS";
|
||||
const VITEST_NO_OUTPUT_HEARTBEAT_ENV_KEY = "OPENCLAW_VITEST_NO_OUTPUT_HEARTBEAT_MS";
|
||||
const VITEST_NO_OUTPUT_RETRY_ENV_KEY = "OPENCLAW_VITEST_NO_OUTPUT_RETRY";
|
||||
/** Default no-output timeout applied to test-projects Vitest children. */
|
||||
export const DEFAULT_TEST_PROJECTS_VITEST_NO_OUTPUT_TIMEOUT_MS = String(900_000);
|
||||
/** Default heartbeat interval applied to test-projects Vitest children. */
|
||||
export const DEFAULT_TEST_PROJECTS_VITEST_NO_OUTPUT_HEARTBEAT_MS = String(
|
||||
DEFAULT_VITEST_NO_OUTPUT_HEARTBEAT_MS,
|
||||
);
|
||||
@@ -1138,6 +1146,9 @@ function expandExplicitSourceTestTargets(targetArgs, cwd) {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds explicit test path targets that do not match any known project plan.
|
||||
*/
|
||||
export function findUnmatchedExplicitTestTargets(args, cwd = process.cwd()) {
|
||||
const { targetArgs } = parseTestProjectsArgs(args, cwd);
|
||||
if (targetArgs.length === 0) {
|
||||
@@ -1775,6 +1786,9 @@ function resolvePreciseChangedTestTargets(changedPath, options) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps changed repo paths to the smallest useful Vitest target plan.
|
||||
*/
|
||||
export function resolveChangedTestTargetPlan(changedPaths, options = {}) {
|
||||
if (changedPaths.length === 0) {
|
||||
return { mode: "none", targets: [] };
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// Shared helpers for running Vitest JSON reports and reading duration data.
|
||||
import { spawnSync } from "node:child_process";
|
||||
import fs from "node:fs";
|
||||
import os from "node:os";
|
||||
@@ -6,6 +7,9 @@ import path from "node:path";
|
||||
const normalizeRepoPath = (value) => value.split(path.sep).join("/");
|
||||
const repoRoot = path.resolve(process.cwd());
|
||||
|
||||
/**
|
||||
* Normalizes absolute or relative file names to repo-relative POSIX paths.
|
||||
*/
|
||||
export function normalizeTrackedRepoPath(value) {
|
||||
const normalizedValue = typeof value === "string" ? value : String(value ?? "");
|
||||
const repoRelative = path.isAbsolute(normalizedValue)
|
||||
@@ -17,10 +21,16 @@ export function normalizeTrackedRepoPath(value) {
|
||||
return normalizeRepoPath(repoRelative);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads and parses a JSON file.
|
||||
*/
|
||||
export function readJsonFile(filePath) {
|
||||
return JSON.parse(fs.readFileSync(filePath, "utf8"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a JSON file or returns the provided fallback on failure.
|
||||
*/
|
||||
export function tryReadJsonFile(filePath, fallback) {
|
||||
try {
|
||||
return readJsonFile(filePath);
|
||||
@@ -29,6 +39,9 @@ export function tryReadJsonFile(filePath, fallback) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs Vitest with the JSON reporter unless an existing report was supplied.
|
||||
*/
|
||||
export function runVitestJsonReport({
|
||||
config,
|
||||
reportPath = "",
|
||||
@@ -63,6 +76,9 @@ export function runVitestJsonReport({
|
||||
return resolvedReportPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts per-file durations from a Vitest JSON report.
|
||||
*/
|
||||
export function collectVitestFileDurations(report, normalizeFile = (value) => value) {
|
||||
return (report.testResults ?? [])
|
||||
.map((result) => {
|
||||
@@ -79,6 +95,9 @@ export function collectVitestFileDurations(report, normalizeFile = (value) => va
|
||||
.filter((entry) => entry.file.length > 0 && entry.durationMs > 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts per-assertion durations from a Vitest JSON report.
|
||||
*/
|
||||
export function collectVitestAssertionDurations(report, normalizeFile = (value) => value) {
|
||||
return (report.testResults ?? []).flatMap((result) => {
|
||||
const file = typeof result.name === "string" ? normalizeFile(result.name) : "";
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// Reports which unit tests qualify for the unit-fast routing lane.
|
||||
import {
|
||||
collectBroadUnitFastTestCandidates,
|
||||
collectUnitFastTestFileAnalysis,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// Refreshes the checked-in CLI startup benchmark fixture.
|
||||
import { spawnSync } from "node:child_process";
|
||||
import { parseFlagArgs, stringFlag, intFlag } from "./lib/arg-utils.mjs";
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
// Runs the closed-loop voice-call test slice through the repo Vitest wrapper.
|
||||
import { execFileSync } from "node:child_process";
|
||||
import { bundledPluginFile } from "./lib/bundled-plugin-paths.mjs";
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
// Reports transitive npm package manifest risks such as lifecycle scripts,
|
||||
// exotic specs, and recently published versions.
|
||||
import { readFile } from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import process from "node:process";
|
||||
@@ -20,6 +22,7 @@ const PINNED_GITHUB_TARBALL_PATTERN =
|
||||
const EXOTIC_SPEC_PATTERN = /^(?:git\+|github:|gitlab:|bitbucket:|https?:)/iu;
|
||||
const RECENTLY_PUBLISHED_VERSION_TYPE = "recently-published-version";
|
||||
const NPM_PACKUMENT_ACCEPT_HEADER = "application/json";
|
||||
/** Maximum npm packument response size accepted by the risk scanner. */
|
||||
export const NPM_PACKUMENT_RESPONSE_MAX_BYTES = 16 * 1024 * 1024;
|
||||
|
||||
function isAllowedPinnedSpec(spec) {
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
// Runs the tsdown build with output cleanup, stale chunk pruning, and bounded
|
||||
// child-process diagnostics.
|
||||
import { spawn, spawnSync } from "node:child_process";
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
@@ -68,6 +70,9 @@ function pruneStaleRuntimeSymlinks() {
|
||||
removeDistPluginNodeModulesSymlinks(path.join(cwd, "dist-runtime"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes build output roots while preserving explicitly protected artifacts.
|
||||
*/
|
||||
export function cleanTsdownOutputRoots(params = {}) {
|
||||
const cwd = params.cwd ?? process.cwd();
|
||||
const fsImpl = params.fs ?? fs;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#!/usr/bin/env node
|
||||
// Routes UI package commands through the repo's Node/pnpm wrappers.
|
||||
import { spawn, spawnSync } from "node:child_process";
|
||||
import fs from "node:fs";
|
||||
import { createRequire } from "node:module";
|
||||
@@ -18,6 +19,9 @@ function usage() {
|
||||
process.stderr.write("Usage: node scripts/ui.js <install|dev|build|test> [...args]\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether Windows needs cmd.exe for a command shim.
|
||||
*/
|
||||
export function shouldUseCmdExeForCommand(cmd, platform = process.platform) {
|
||||
if (platform !== "win32") {
|
||||
return false;
|
||||
@@ -26,6 +30,9 @@ export function shouldUseCmdExeForCommand(cmd, platform = process.platform) {
|
||||
return WINDOWS_CMD_EXE_EXTENSIONS.has(extension);
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the spawn call for a UI command, including Windows cmd.exe wrapping.
|
||||
*/
|
||||
export function resolveSpawnCall(cmd, args, envOverride, params = {}) {
|
||||
const platform = params.platform ?? process.platform;
|
||||
const comSpec = params.comSpec ?? process.env.ComSpec ?? "cmd.exe";
|
||||
@@ -54,6 +61,9 @@ export function resolveSpawnCall(cmd, args, envOverride, params = {}) {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the pnpm-backed spawn call for UI package scripts.
|
||||
*/
|
||||
export function resolvePnpmSpawnCall(pnpmArgs, envOverride, params = {}) {
|
||||
const env = envOverride ?? process.env;
|
||||
const platform = params.platform ?? process.platform;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#!/usr/bin/env node
|
||||
// Validates that a referenced release-publish workflow run is usable for approval.
|
||||
import fs from "node:fs";
|
||||
|
||||
const run = JSON.parse(fs.readFileSync(0, "utf8"));
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
// Verifies Docker image attestations cover required platforms and predicates.
|
||||
import { execFileSync } from "node:child_process";
|
||||
import process from "node:process";
|
||||
|
||||
@@ -7,6 +8,9 @@ const ATTESTATION_REFERENCE_TYPE = "attestation-manifest";
|
||||
const EXPECTED_ATTESTATION_ARTIFACT_TYPE = "application/vnd.docker.attestation.manifest.v1+json";
|
||||
const REQUIRED_PREDICATES = ["https://spdx.dev/Document", "https://slsa.dev/provenance/v1"];
|
||||
|
||||
/**
|
||||
* Rewrites an image reference to use the provided digest.
|
||||
*/
|
||||
export function imageRefForDigest(imageRef, digest) {
|
||||
const atIndex = imageRef.indexOf("@");
|
||||
if (atIndex >= 0) {
|
||||
@@ -18,6 +22,9 @@ export function imageRefForDigest(imageRef, digest) {
|
||||
return `${base}@${digest}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses os/architecture[/variant] platform strings.
|
||||
*/
|
||||
export function parsePlatform(value) {
|
||||
const [os, architecture, variant] = value.split("/");
|
||||
if (!os || !architecture || value.split("/").length > 3) {
|
||||
@@ -49,6 +56,9 @@ function parseJson(raw, label) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Collects missing/mismatched attestation errors for required image platforms.
|
||||
*/
|
||||
export function collectDockerAttestationErrors(params) {
|
||||
const {
|
||||
imageRef,
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
// Verifies published plugin npm packages include built runtime entries and
|
||||
// metadata expected by OpenClaw.
|
||||
import { execFileSync } from "node:child_process";
|
||||
import fs from "node:fs";
|
||||
import os from "node:os";
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// Runs the broad verification graph used by Crabbox/Testbox: check then test.
|
||||
import { performance } from "node:perf_hooks";
|
||||
import { formatMs, printTimingSummary } from "./lib/check-timing-summary.mjs";
|
||||
import { runManagedCommand } from "./lib/managed-child-process.mjs";
|
||||
@@ -7,6 +8,9 @@ const stages = [
|
||||
{ name: "test", args: ["test"] },
|
||||
];
|
||||
|
||||
/**
|
||||
* Renders CLI usage for the verification wrapper.
|
||||
*/
|
||||
export function usage() {
|
||||
return [
|
||||
"Usage: node scripts/verify.mjs",
|
||||
@@ -18,6 +22,9 @@ export function usage() {
|
||||
].join("\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses verify wrapper CLI args.
|
||||
*/
|
||||
export function parseVerifyArgs(argv) {
|
||||
const args = { help: false };
|
||||
for (const arg of argv) {
|
||||
@@ -45,6 +52,9 @@ async function runStage(stage) {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs verification stages in order and stops at the first failure.
|
||||
*/
|
||||
export async function main(argv = process.argv.slice(2)) {
|
||||
let args;
|
||||
try {
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
// Shared Vitest child process-group signal forwarding helpers.
|
||||
export function shouldUseDetachedVitestProcessGroup(platform = process.platform) {
|
||||
return platform !== "win32";
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves the PID or process-group target for Vitest signal forwarding.
|
||||
*/
|
||||
export function resolveVitestProcessGroupSignalTarget(params) {
|
||||
const pid = params.childPid;
|
||||
if (typeof pid !== "number" || !Number.isInteger(pid) || pid <= 0) {
|
||||
@@ -10,6 +14,9 @@ export function resolveVitestProcessGroupSignalTarget(params) {
|
||||
return shouldUseDetachedVitestProcessGroup(params.platform) ? -pid : pid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Forwards a signal to the Vitest child or process group.
|
||||
*/
|
||||
export function forwardSignalToVitestProcessGroup(params) {
|
||||
const target = resolveVitestProcessGroupSignalTarget({
|
||||
childPid: params.child.pid,
|
||||
@@ -54,6 +61,9 @@ function ensureProcessListenerCapacity(processObject, eventName, additionalListe
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Installs signal/exit cleanup handlers for a Vitest child process group.
|
||||
*/
|
||||
export function installVitestProcessGroupCleanup(params) {
|
||||
const processObject = params.processObject ?? process;
|
||||
const platform = params.platform ?? process.platform;
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
#!/usr/bin/env node
|
||||
// Watches dev source paths and restarts scripts/run-node.mjs when relevant
|
||||
// files change.
|
||||
import { spawn } from "node:child_process";
|
||||
import { createHash } from "node:crypto";
|
||||
import fs from "node:fs";
|
||||
@@ -101,6 +103,7 @@ const sleep = (ms) =>
|
||||
const createWatchLockKey = (cwd, args) =>
|
||||
createHash("sha256").update(cwd).update("\0").update(args.join("\0")).digest("hex").slice(0, 12);
|
||||
|
||||
/** Resolves the lock path that prevents duplicate watch-node loops. */
|
||||
export const resolveWatchLockPath = (cwd, args = []) =>
|
||||
path.join(cwd, WATCH_LOCK_DIR, `${createWatchLockKey(cwd, args)}.json`);
|
||||
|
||||
@@ -258,6 +261,9 @@ const releaseWatchLock = (lockHandle) => {
|
||||
* watchPaths?: string[];
|
||||
* }} [params]
|
||||
*/
|
||||
/**
|
||||
* Runs the watch loop and restarts the child process on relevant changes.
|
||||
*/
|
||||
export async function runWatchMain(params = {}) {
|
||||
const deps = {
|
||||
spawn: params.spawn ?? spawn,
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
// Windows cmd.exe quoting helpers for npm/pnpm command shims.
|
||||
const WINDOWS_UNSAFE_CMD_CHARS_RE = /[&|<>%\r\n]/;
|
||||
|
||||
/**
|
||||
* Resolves the correctly cased PATH key in a Windows-style env object.
|
||||
*/
|
||||
export function resolvePathEnvKey(env) {
|
||||
return Object.keys(env).find((key) => key.toLowerCase() === "path") ?? "PATH";
|
||||
}
|
||||
@@ -15,6 +19,9 @@ function escapeForCmdExe(arg) {
|
||||
return `"${escaped.replace(/"/g, '""')}"`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a cmd.exe-safe command line or rejects unsafe shell metacharacters.
|
||||
*/
|
||||
export function buildCmdExeCommandLine(command, args) {
|
||||
const escapedCommand = escapeForCmdExe(command);
|
||||
const commandLine = [escapedCommand, ...args.map(escapeForCmdExe)].join(" ");
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// Builds the generated official channel catalog from publishable channel plugins.
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import { pathToFileURL } from "node:url";
|
||||
@@ -5,6 +6,7 @@ import officialExternalChannelCatalog from "./lib/official-external-channel-cata
|
||||
import { isRecord, trimString } from "./lib/record-shared.mjs";
|
||||
import { writeTextFileIfChanged } from "./runtime-postbuild-shared.mjs";
|
||||
|
||||
/** Generated official channel catalog path in dist. */
|
||||
export const OFFICIAL_CHANNEL_CATALOG_RELATIVE_PATH = "dist/channel-catalog.json";
|
||||
|
||||
function toCatalogInstall(value, packageName) {
|
||||
@@ -61,6 +63,9 @@ function getCatalogChannelId(entry) {
|
||||
return trimString(entry?.openclaw?.channel?.id) || trimString(entry?.name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Collects publishable channel catalog entries from bundled and external channels.
|
||||
*/
|
||||
export function buildOfficialChannelCatalog(params = {}) {
|
||||
const repoRoot = params.cwd ?? params.repoRoot ?? process.cwd();
|
||||
const extensionsRoot = path.join(repoRoot, "extensions");
|
||||
|
||||
Reference in New Issue
Block a user