mirror of
https://github.com/openclaw/openclaw.git
synced 2026-07-09 15:59:30 +00:00
docs: document test project scripts
This commit is contained in:
parent
9b1a01e4f9
commit
a59eba3ee1
17 changed files with 107 additions and 0 deletions
|
|
@ -1,3 +1,5 @@
|
||||||
|
// Dispatches Vitest project shards for explicit targets, changed files, or the
|
||||||
|
// full local suite.
|
||||||
import fs from "node:fs";
|
import fs from "node:fs";
|
||||||
import { performance } from "node:perf_hooks";
|
import { performance } from "node:perf_hooks";
|
||||||
import { formatMs } from "./lib/check-timing-summary.mjs";
|
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 { spawnSync } from "node:child_process";
|
||||||
import fs from "node:fs";
|
import fs from "node:fs";
|
||||||
import os from "node:os";
|
import os from "node:os";
|
||||||
|
|
@ -250,6 +253,9 @@ function uniqueOrdered(values) {
|
||||||
return [...new Set(values)];
|
return [...new Set(values)];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Orders full-suite specs so expensive shards start first in parallel runs.
|
||||||
|
*/
|
||||||
export function orderFullSuiteSpecsForParallelRun(specs, shardTimings = new Map()) {
|
export function orderFullSuiteSpecsForParallelRun(specs, shardTimings = new Map()) {
|
||||||
const sortedSpecs = specs.toSorted((a, b) => {
|
const sortedSpecs = specs.toSorted((a, b) => {
|
||||||
const weightDelta =
|
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_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_HEARTBEAT_ENV_KEY = "OPENCLAW_VITEST_NO_OUTPUT_HEARTBEAT_MS";
|
||||||
const VITEST_NO_OUTPUT_RETRY_ENV_KEY = "OPENCLAW_VITEST_NO_OUTPUT_RETRY";
|
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);
|
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(
|
export const DEFAULT_TEST_PROJECTS_VITEST_NO_OUTPUT_HEARTBEAT_MS = String(
|
||||||
DEFAULT_VITEST_NO_OUTPUT_HEARTBEAT_MS,
|
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()) {
|
export function findUnmatchedExplicitTestTargets(args, cwd = process.cwd()) {
|
||||||
const { targetArgs } = parseTestProjectsArgs(args, cwd);
|
const { targetArgs } = parseTestProjectsArgs(args, cwd);
|
||||||
if (targetArgs.length === 0) {
|
if (targetArgs.length === 0) {
|
||||||
|
|
@ -1775,6 +1786,9 @@ function resolvePreciseChangedTestTargets(changedPath, options) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maps changed repo paths to the smallest useful Vitest target plan.
|
||||||
|
*/
|
||||||
export function resolveChangedTestTargetPlan(changedPaths, options = {}) {
|
export function resolveChangedTestTargetPlan(changedPaths, options = {}) {
|
||||||
if (changedPaths.length === 0) {
|
if (changedPaths.length === 0) {
|
||||||
return { mode: "none", targets: [] };
|
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 { spawnSync } from "node:child_process";
|
||||||
import fs from "node:fs";
|
import fs from "node:fs";
|
||||||
import os from "node:os";
|
import os from "node:os";
|
||||||
|
|
@ -6,6 +7,9 @@ import path from "node:path";
|
||||||
const normalizeRepoPath = (value) => value.split(path.sep).join("/");
|
const normalizeRepoPath = (value) => value.split(path.sep).join("/");
|
||||||
const repoRoot = path.resolve(process.cwd());
|
const repoRoot = path.resolve(process.cwd());
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Normalizes absolute or relative file names to repo-relative POSIX paths.
|
||||||
|
*/
|
||||||
export function normalizeTrackedRepoPath(value) {
|
export function normalizeTrackedRepoPath(value) {
|
||||||
const normalizedValue = typeof value === "string" ? value : String(value ?? "");
|
const normalizedValue = typeof value === "string" ? value : String(value ?? "");
|
||||||
const repoRelative = path.isAbsolute(normalizedValue)
|
const repoRelative = path.isAbsolute(normalizedValue)
|
||||||
|
|
@ -17,10 +21,16 @@ export function normalizeTrackedRepoPath(value) {
|
||||||
return normalizeRepoPath(repoRelative);
|
return normalizeRepoPath(repoRelative);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads and parses a JSON file.
|
||||||
|
*/
|
||||||
export function readJsonFile(filePath) {
|
export function readJsonFile(filePath) {
|
||||||
return JSON.parse(fs.readFileSync(filePath, "utf8"));
|
return JSON.parse(fs.readFileSync(filePath, "utf8"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads a JSON file or returns the provided fallback on failure.
|
||||||
|
*/
|
||||||
export function tryReadJsonFile(filePath, fallback) {
|
export function tryReadJsonFile(filePath, fallback) {
|
||||||
try {
|
try {
|
||||||
return readJsonFile(filePath);
|
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({
|
export function runVitestJsonReport({
|
||||||
config,
|
config,
|
||||||
reportPath = "",
|
reportPath = "",
|
||||||
|
|
@ -63,6 +76,9 @@ export function runVitestJsonReport({
|
||||||
return resolvedReportPath;
|
return resolvedReportPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extracts per-file durations from a Vitest JSON report.
|
||||||
|
*/
|
||||||
export function collectVitestFileDurations(report, normalizeFile = (value) => value) {
|
export function collectVitestFileDurations(report, normalizeFile = (value) => value) {
|
||||||
return (report.testResults ?? [])
|
return (report.testResults ?? [])
|
||||||
.map((result) => {
|
.map((result) => {
|
||||||
|
|
@ -79,6 +95,9 @@ export function collectVitestFileDurations(report, normalizeFile = (value) => va
|
||||||
.filter((entry) => entry.file.length > 0 && entry.durationMs > 0);
|
.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) {
|
export function collectVitestAssertionDurations(report, normalizeFile = (value) => value) {
|
||||||
return (report.testResults ?? []).flatMap((result) => {
|
return (report.testResults ?? []).flatMap((result) => {
|
||||||
const file = typeof result.name === "string" ? normalizeFile(result.name) : "";
|
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 {
|
import {
|
||||||
collectBroadUnitFastTestCandidates,
|
collectBroadUnitFastTestCandidates,
|
||||||
collectUnitFastTestFileAnalysis,
|
collectUnitFastTestFileAnalysis,
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
// Refreshes the checked-in CLI startup benchmark fixture.
|
||||||
import { spawnSync } from "node:child_process";
|
import { spawnSync } from "node:child_process";
|
||||||
import { parseFlagArgs, stringFlag, intFlag } from "./lib/arg-utils.mjs";
|
import { parseFlagArgs, stringFlag, intFlag } from "./lib/arg-utils.mjs";
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
#!/usr/bin/env node
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
// Runs the closed-loop voice-call test slice through the repo Vitest wrapper.
|
||||||
import { execFileSync } from "node:child_process";
|
import { execFileSync } from "node:child_process";
|
||||||
import { bundledPluginFile } from "./lib/bundled-plugin-paths.mjs";
|
import { bundledPluginFile } from "./lib/bundled-plugin-paths.mjs";
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
#!/usr/bin/env node
|
#!/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 { readFile } from "node:fs/promises";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import process from "node:process";
|
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 EXOTIC_SPEC_PATTERN = /^(?:git\+|github:|gitlab:|bitbucket:|https?:)/iu;
|
||||||
const RECENTLY_PUBLISHED_VERSION_TYPE = "recently-published-version";
|
const RECENTLY_PUBLISHED_VERSION_TYPE = "recently-published-version";
|
||||||
const NPM_PACKUMENT_ACCEPT_HEADER = "application/json";
|
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;
|
export const NPM_PACKUMENT_RESPONSE_MAX_BYTES = 16 * 1024 * 1024;
|
||||||
|
|
||||||
function isAllowedPinnedSpec(spec) {
|
function isAllowedPinnedSpec(spec) {
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
#!/usr/bin/env node
|
#!/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 { spawn, spawnSync } from "node:child_process";
|
||||||
import fs from "node:fs";
|
import fs from "node:fs";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
|
|
@ -68,6 +70,9 @@ function pruneStaleRuntimeSymlinks() {
|
||||||
removeDistPluginNodeModulesSymlinks(path.join(cwd, "dist-runtime"));
|
removeDistPluginNodeModulesSymlinks(path.join(cwd, "dist-runtime"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes build output roots while preserving explicitly protected artifacts.
|
||||||
|
*/
|
||||||
export function cleanTsdownOutputRoots(params = {}) {
|
export function cleanTsdownOutputRoots(params = {}) {
|
||||||
const cwd = params.cwd ?? process.cwd();
|
const cwd = params.cwd ?? process.cwd();
|
||||||
const fsImpl = params.fs ?? fs;
|
const fsImpl = params.fs ?? fs;
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
#!/usr/bin/env node
|
#!/usr/bin/env node
|
||||||
|
// Routes UI package commands through the repo's Node/pnpm wrappers.
|
||||||
import { spawn, spawnSync } from "node:child_process";
|
import { spawn, spawnSync } from "node:child_process";
|
||||||
import fs from "node:fs";
|
import fs from "node:fs";
|
||||||
import { createRequire } from "node:module";
|
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");
|
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) {
|
export function shouldUseCmdExeForCommand(cmd, platform = process.platform) {
|
||||||
if (platform !== "win32") {
|
if (platform !== "win32") {
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -26,6 +30,9 @@ export function shouldUseCmdExeForCommand(cmd, platform = process.platform) {
|
||||||
return WINDOWS_CMD_EXE_EXTENSIONS.has(extension);
|
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 = {}) {
|
export function resolveSpawnCall(cmd, args, envOverride, params = {}) {
|
||||||
const platform = params.platform ?? process.platform;
|
const platform = params.platform ?? process.platform;
|
||||||
const comSpec = params.comSpec ?? process.env.ComSpec ?? "cmd.exe";
|
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 = {}) {
|
export function resolvePnpmSpawnCall(pnpmArgs, envOverride, params = {}) {
|
||||||
const env = envOverride ?? process.env;
|
const env = envOverride ?? process.env;
|
||||||
const platform = params.platform ?? process.platform;
|
const platform = params.platform ?? process.platform;
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
#!/usr/bin/env node
|
#!/usr/bin/env node
|
||||||
|
// Validates that a referenced release-publish workflow run is usable for approval.
|
||||||
import fs from "node:fs";
|
import fs from "node:fs";
|
||||||
|
|
||||||
const run = JSON.parse(fs.readFileSync(0, "utf8"));
|
const run = JSON.parse(fs.readFileSync(0, "utf8"));
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
#!/usr/bin/env node
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
// Verifies Docker image attestations cover required platforms and predicates.
|
||||||
import { execFileSync } from "node:child_process";
|
import { execFileSync } from "node:child_process";
|
||||||
import process from "node: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 EXPECTED_ATTESTATION_ARTIFACT_TYPE = "application/vnd.docker.attestation.manifest.v1+json";
|
||||||
const REQUIRED_PREDICATES = ["https://spdx.dev/Document", "https://slsa.dev/provenance/v1"];
|
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) {
|
export function imageRefForDigest(imageRef, digest) {
|
||||||
const atIndex = imageRef.indexOf("@");
|
const atIndex = imageRef.indexOf("@");
|
||||||
if (atIndex >= 0) {
|
if (atIndex >= 0) {
|
||||||
|
|
@ -18,6 +22,9 @@ export function imageRefForDigest(imageRef, digest) {
|
||||||
return `${base}@${digest}`;
|
return `${base}@${digest}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses os/architecture[/variant] platform strings.
|
||||||
|
*/
|
||||||
export function parsePlatform(value) {
|
export function parsePlatform(value) {
|
||||||
const [os, architecture, variant] = value.split("/");
|
const [os, architecture, variant] = value.split("/");
|
||||||
if (!os || !architecture || value.split("/").length > 3) {
|
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) {
|
export function collectDockerAttestationErrors(params) {
|
||||||
const {
|
const {
|
||||||
imageRef,
|
imageRef,
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
#!/usr/bin/env node
|
#!/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 { execFileSync } from "node:child_process";
|
||||||
import fs from "node:fs";
|
import fs from "node:fs";
|
||||||
import os from "node:os";
|
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 { performance } from "node:perf_hooks";
|
||||||
import { formatMs, printTimingSummary } from "./lib/check-timing-summary.mjs";
|
import { formatMs, printTimingSummary } from "./lib/check-timing-summary.mjs";
|
||||||
import { runManagedCommand } from "./lib/managed-child-process.mjs";
|
import { runManagedCommand } from "./lib/managed-child-process.mjs";
|
||||||
|
|
@ -7,6 +8,9 @@ const stages = [
|
||||||
{ name: "test", args: ["test"] },
|
{ name: "test", args: ["test"] },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renders CLI usage for the verification wrapper.
|
||||||
|
*/
|
||||||
export function usage() {
|
export function usage() {
|
||||||
return [
|
return [
|
||||||
"Usage: node scripts/verify.mjs",
|
"Usage: node scripts/verify.mjs",
|
||||||
|
|
@ -18,6 +22,9 @@ export function usage() {
|
||||||
].join("\n");
|
].join("\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses verify wrapper CLI args.
|
||||||
|
*/
|
||||||
export function parseVerifyArgs(argv) {
|
export function parseVerifyArgs(argv) {
|
||||||
const args = { help: false };
|
const args = { help: false };
|
||||||
for (const arg of argv) {
|
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)) {
|
export async function main(argv = process.argv.slice(2)) {
|
||||||
let args;
|
let args;
|
||||||
try {
|
try {
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,11 @@
|
||||||
|
// Shared Vitest child process-group signal forwarding helpers.
|
||||||
export function shouldUseDetachedVitestProcessGroup(platform = process.platform) {
|
export function shouldUseDetachedVitestProcessGroup(platform = process.platform) {
|
||||||
return platform !== "win32";
|
return platform !== "win32";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolves the PID or process-group target for Vitest signal forwarding.
|
||||||
|
*/
|
||||||
export function resolveVitestProcessGroupSignalTarget(params) {
|
export function resolveVitestProcessGroupSignalTarget(params) {
|
||||||
const pid = params.childPid;
|
const pid = params.childPid;
|
||||||
if (typeof pid !== "number" || !Number.isInteger(pid) || pid <= 0) {
|
if (typeof pid !== "number" || !Number.isInteger(pid) || pid <= 0) {
|
||||||
|
|
@ -10,6 +14,9 @@ export function resolveVitestProcessGroupSignalTarget(params) {
|
||||||
return shouldUseDetachedVitestProcessGroup(params.platform) ? -pid : pid;
|
return shouldUseDetachedVitestProcessGroup(params.platform) ? -pid : pid;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Forwards a signal to the Vitest child or process group.
|
||||||
|
*/
|
||||||
export function forwardSignalToVitestProcessGroup(params) {
|
export function forwardSignalToVitestProcessGroup(params) {
|
||||||
const target = resolveVitestProcessGroupSignalTarget({
|
const target = resolveVitestProcessGroupSignalTarget({
|
||||||
childPid: params.child.pid,
|
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) {
|
export function installVitestProcessGroupCleanup(params) {
|
||||||
const processObject = params.processObject ?? process;
|
const processObject = params.processObject ?? process;
|
||||||
const platform = params.platform ?? process.platform;
|
const platform = params.platform ?? process.platform;
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
#!/usr/bin/env node
|
#!/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 { spawn } from "node:child_process";
|
||||||
import { createHash } from "node:crypto";
|
import { createHash } from "node:crypto";
|
||||||
import fs from "node:fs";
|
import fs from "node:fs";
|
||||||
|
|
@ -101,6 +103,7 @@ const sleep = (ms) =>
|
||||||
const createWatchLockKey = (cwd, args) =>
|
const createWatchLockKey = (cwd, args) =>
|
||||||
createHash("sha256").update(cwd).update("\0").update(args.join("\0")).digest("hex").slice(0, 12);
|
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 = []) =>
|
export const resolveWatchLockPath = (cwd, args = []) =>
|
||||||
path.join(cwd, WATCH_LOCK_DIR, `${createWatchLockKey(cwd, args)}.json`);
|
path.join(cwd, WATCH_LOCK_DIR, `${createWatchLockKey(cwd, args)}.json`);
|
||||||
|
|
||||||
|
|
@ -258,6 +261,9 @@ const releaseWatchLock = (lockHandle) => {
|
||||||
* watchPaths?: string[];
|
* watchPaths?: string[];
|
||||||
* }} [params]
|
* }} [params]
|
||||||
*/
|
*/
|
||||||
|
/**
|
||||||
|
* Runs the watch loop and restarts the child process on relevant changes.
|
||||||
|
*/
|
||||||
export async function runWatchMain(params = {}) {
|
export async function runWatchMain(params = {}) {
|
||||||
const deps = {
|
const deps = {
|
||||||
spawn: params.spawn ?? spawn,
|
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]/;
|
const WINDOWS_UNSAFE_CMD_CHARS_RE = /[&|<>%\r\n]/;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolves the correctly cased PATH key in a Windows-style env object.
|
||||||
|
*/
|
||||||
export function resolvePathEnvKey(env) {
|
export function resolvePathEnvKey(env) {
|
||||||
return Object.keys(env).find((key) => key.toLowerCase() === "path") ?? "PATH";
|
return Object.keys(env).find((key) => key.toLowerCase() === "path") ?? "PATH";
|
||||||
}
|
}
|
||||||
|
|
@ -15,6 +19,9 @@ function escapeForCmdExe(arg) {
|
||||||
return `"${escaped.replace(/"/g, '""')}"`;
|
return `"${escaped.replace(/"/g, '""')}"`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds a cmd.exe-safe command line or rejects unsafe shell metacharacters.
|
||||||
|
*/
|
||||||
export function buildCmdExeCommandLine(command, args) {
|
export function buildCmdExeCommandLine(command, args) {
|
||||||
const escapedCommand = escapeForCmdExe(command);
|
const escapedCommand = escapeForCmdExe(command);
|
||||||
const commandLine = [escapedCommand, ...args.map(escapeForCmdExe)].join(" ");
|
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 fs from "node:fs";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import { pathToFileURL } from "node:url";
|
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 { isRecord, trimString } from "./lib/record-shared.mjs";
|
||||||
import { writeTextFileIfChanged } from "./runtime-postbuild-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";
|
export const OFFICIAL_CHANNEL_CATALOG_RELATIVE_PATH = "dist/channel-catalog.json";
|
||||||
|
|
||||||
function toCatalogInstall(value, packageName) {
|
function toCatalogInstall(value, packageName) {
|
||||||
|
|
@ -61,6 +63,9 @@ function getCatalogChannelId(entry) {
|
||||||
return trimString(entry?.openclaw?.channel?.id) || trimString(entry?.name);
|
return trimString(entry?.openclaw?.channel?.id) || trimString(entry?.name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Collects publishable channel catalog entries from bundled and external channels.
|
||||||
|
*/
|
||||||
export function buildOfficialChannelCatalog(params = {}) {
|
export function buildOfficialChannelCatalog(params = {}) {
|
||||||
const repoRoot = params.cwd ?? params.repoRoot ?? process.cwd();
|
const repoRoot = params.cwd ?? params.repoRoot ?? process.cwd();
|
||||||
const extensionsRoot = path.join(repoRoot, "extensions");
|
const extensionsRoot = path.join(repoRoot, "extensions");
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue