mirror of
https://github.com/openclaw/openclaw.git
synced 2026-06-06 05:51:15 +08:00
docs: document ci dependency docs scripts
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
// Checks core web-fetch surfaces for provider-owned Firecrawl coupling.
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { collectSourceFileContents } from "./lib/source-file-scan-cache.mjs";
|
||||
@@ -34,6 +35,9 @@ const suspiciousPatterns = [
|
||||
|
||||
let webFetchProviderViolationsPromise;
|
||||
|
||||
/**
|
||||
* Collects web-fetch provider boundary violations in core source files.
|
||||
*/
|
||||
export async function collectWebFetchProviderBoundaryViolations() {
|
||||
if (!webFetchProviderViolationsPromise) {
|
||||
webFetchProviderViolationsPromise = (async () => {
|
||||
@@ -81,6 +85,9 @@ export async function collectWebFetchProviderBoundaryViolations() {
|
||||
return await webFetchProviderViolationsPromise;
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the web-fetch provider boundary check.
|
||||
*/
|
||||
export async function main(argv, io) {
|
||||
const args = argv ?? process.argv.slice(2);
|
||||
const json = args.includes("--json");
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
// Inventories core web-search surfaces that still mention bundled providers.
|
||||
import { promises as fs } from "node:fs";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
@@ -153,6 +154,9 @@ function scanGenericCoreImports(lines, relativeFile, inventory) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Collects web-search provider boundary inventory from core source files.
|
||||
*/
|
||||
export async function collectWebSearchProviderBoundaryInventory() {
|
||||
if (!webSearchProviderInventoryPromise) {
|
||||
webSearchProviderInventoryPromise = (async () => {
|
||||
@@ -184,6 +188,9 @@ export async function collectWebSearchProviderBoundaryInventory() {
|
||||
return await webSearchProviderInventoryPromise;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the expected web-search provider boundary inventory baseline.
|
||||
*/
|
||||
export async function readExpectedInventory() {
|
||||
try {
|
||||
return JSON.parse(await fs.readFile(baselinePath, "utf8"));
|
||||
@@ -195,6 +202,9 @@ export async function readExpectedInventory() {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Diffs expected and actual web-search provider boundary inventory entries.
|
||||
*/
|
||||
export function diffInventory(expected, actual) {
|
||||
return diffInventoryEntries(expected, actual, compareInventoryEntries);
|
||||
}
|
||||
@@ -219,6 +229,9 @@ function formatEntry(entry) {
|
||||
return `${entry.provider} ${entry.file}:${entry.line} ${entry.reason}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the web-search provider boundary baseline check.
|
||||
*/
|
||||
export async function runWebSearchProviderBoundaryCheck(argv, io) {
|
||||
return await runBaselineInventoryCheck({
|
||||
argv: argv ?? process.argv.slice(2),
|
||||
@@ -231,6 +244,9 @@ export async function runWebSearchProviderBoundaryCheck(argv, io) {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Entrypoint wrapper for the web-search provider boundary check.
|
||||
*/
|
||||
export async function main(argv, io) {
|
||||
const exitCode = await runWebSearchProviderBoundaryCheck(argv, io);
|
||||
if (!io && exitCode !== 0) {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
// Ensures webhook handlers authenticate before reading request bodies.
|
||||
import path from "node:path";
|
||||
import ts from "typescript";
|
||||
import { bundledPluginCallsite, bundledPluginFile } from "./lib/bundled-plugin-paths.mjs";
|
||||
@@ -29,6 +30,9 @@ function getCalleeName(expression) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds request body reads that occur before webhook auth validation.
|
||||
*/
|
||||
export function findBlockedWebhookBodyReadLines(content, fileName = "source.ts") {
|
||||
const sourceFile = ts.createSourceFile(fileName, content, ts.ScriptTarget.Latest, true);
|
||||
const lines = [];
|
||||
@@ -45,6 +49,9 @@ export function findBlockedWebhookBodyReadLines(content, fileName = "source.ts")
|
||||
return lines;
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the webhook auth/body-order guard.
|
||||
*/
|
||||
export async function main() {
|
||||
await runCallsiteGuard({
|
||||
importMetaUrl: import.meta.url,
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
// Runs the repository check lanes selected by CLI arguments.
|
||||
import { performance } from "node:perf_hooks";
|
||||
import { printTimingSummary } from "./lib/check-timing-summary.mjs";
|
||||
import { runManagedCommand } from "./lib/managed-child-process.mjs";
|
||||
|
||||
/**
|
||||
* Returns command usage text for the aggregate check runner.
|
||||
*/
|
||||
export function usage() {
|
||||
return [
|
||||
"Usage: node scripts/check.mjs [--timed] [--include-architecture] [--include-test-types]",
|
||||
@@ -16,6 +20,9 @@ export function usage() {
|
||||
].join("\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses aggregate check runner arguments.
|
||||
*/
|
||||
export function parseCheckArgs(argv) {
|
||||
const args = {
|
||||
help: false,
|
||||
@@ -39,6 +46,9 @@ export function parseCheckArgs(argv) {
|
||||
return args;
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs selected repository check lanes.
|
||||
*/
|
||||
export async function main(argv = process.argv.slice(2)) {
|
||||
let args;
|
||||
try {
|
||||
@@ -158,6 +168,9 @@ async function runSerial(commands) {
|
||||
return results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs one managed check command and returns timing/status details.
|
||||
*/
|
||||
export async function runCommand(command, runManagedCommandImpl = runManagedCommand) {
|
||||
const startedAt = performance.now();
|
||||
let status = 1;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// Determines CI scope from changed paths.
|
||||
import { execFileSync } from "node:child_process";
|
||||
import { appendFileSync } from "node:fs";
|
||||
import { isDirectRunUrl } from "./lib/direct-run.mjs";
|
||||
@@ -64,6 +65,9 @@ const NODE_FAST_SCOPE_RE = new RegExp(
|
||||
* @param {string[]} changedPaths
|
||||
* @returns {ChangedScope}
|
||||
*/
|
||||
/**
|
||||
* Detects high-level CI scope from changed file paths.
|
||||
*/
|
||||
export function detectChangedScope(changedPaths) {
|
||||
if (!Array.isArray(changedPaths) || changedPaths.length === 0) {
|
||||
return {
|
||||
@@ -158,6 +162,9 @@ export function detectChangedScope(changedPaths) {
|
||||
* @param {string[]} changedPaths
|
||||
* @returns {NodeFastScope}
|
||||
*/
|
||||
/**
|
||||
* Detects whether node-fast CI can cover the changed paths.
|
||||
*/
|
||||
export function detectNodeFastScope(changedPaths) {
|
||||
if (!Array.isArray(changedPaths) || changedPaths.length === 0) {
|
||||
return { runFastOnly: false, runPluginContracts: false, runCiRouting: false };
|
||||
@@ -207,6 +214,9 @@ function detectInstallSmokeScopeForPath(path) {
|
||||
* @param {string[]} changedPaths
|
||||
* @returns {InstallSmokeScope}
|
||||
*/
|
||||
/**
|
||||
* Detects whether install-smoke CI should run for changed paths.
|
||||
*/
|
||||
export function detectInstallSmokeScope(changedPaths) {
|
||||
if (!Array.isArray(changedPaths) || changedPaths.length === 0) {
|
||||
return { runFastInstallSmoke: true, runFullInstallSmoke: true };
|
||||
@@ -232,6 +242,9 @@ export function detectInstallSmokeScope(changedPaths) {
|
||||
* @param {string} [cwd]
|
||||
* @returns {string[]}
|
||||
*/
|
||||
/**
|
||||
* Lists changed paths for CI base/head inputs.
|
||||
*/
|
||||
export function listChangedPaths(
|
||||
base,
|
||||
head = "HEAD",
|
||||
@@ -263,6 +276,9 @@ export function listChangedPaths(
|
||||
* @param {string} [outputPath]
|
||||
* @param {InstallSmokeScope} [installSmokeScope]
|
||||
*/
|
||||
/**
|
||||
* Writes CI scope decisions to GitHub Actions output.
|
||||
*/
|
||||
export function writeGitHubOutput(
|
||||
scope,
|
||||
outputPath = process.env.GITHUB_OUTPUT,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
// Summarizes GitHub Actions run/job timings for CI analysis.
|
||||
import { execFileSync } from "node:child_process";
|
||||
import { parsePositiveInt } from "./lib/numeric-options.mjs";
|
||||
|
||||
@@ -46,6 +47,9 @@ function normalizeRunJob(job) {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Flattens paginated GitHub run job responses.
|
||||
*/
|
||||
export function collectRunJobsFromPages(pages) {
|
||||
return pages.flatMap((page) => (Array.isArray(page.jobs) ? page.jobs.map(normalizeRunJob) : []));
|
||||
}
|
||||
@@ -118,6 +122,9 @@ function collectRunTimingContext(run) {
|
||||
return { created, jobs, updated };
|
||||
}
|
||||
|
||||
/**
|
||||
* Summarizes longest jobs and total timing for a workflow run.
|
||||
*/
|
||||
export function summarizeRunTimings(run, limit = 15) {
|
||||
const { created, jobs, updated } = collectRunTimingContext(run);
|
||||
const byDuration = [...jobs]
|
||||
@@ -142,6 +149,9 @@ export function summarizeRunTimings(run, limit = 15) {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Summarizes pnpm store warmup overlap near run start.
|
||||
*/
|
||||
export function summarizePnpmStoreWarmupBarrier(run, windowSeconds = 5) {
|
||||
const { jobs } = collectRunTimingContext(run);
|
||||
const preflight = jobs.find((job) => job.name === "preflight");
|
||||
@@ -182,6 +192,9 @@ export function summarizePnpmStoreWarmupBarrier(run, windowSeconds = 5) {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Selects the latest main push CI run, optionally matching a head SHA.
|
||||
*/
|
||||
export function selectLatestMainPushCiRun(runs, headSha = null) {
|
||||
const pushRuns = runs.filter((run) => run.event === "push");
|
||||
if (headSha) {
|
||||
@@ -337,6 +350,9 @@ function printSection(title, jobs, metric) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses CI run timing CLI arguments.
|
||||
*/
|
||||
export function parseRunTimingArgs(args) {
|
||||
let explicitRunId;
|
||||
let limit = 15;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// Finds duplicate PRs after merge and closes overlapping candidates.
|
||||
import { execFileSync } from "node:child_process";
|
||||
import { pathToFileURL } from "node:url";
|
||||
|
||||
@@ -10,6 +11,9 @@ Closes explicit duplicate PRs after a landed PR, after verifying the landed PR i
|
||||
each duplicate has either a shared referenced issue or overlapping changed hunks. Defaults to dry-run.`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses comma-separated PR numbers from CLI/env input.
|
||||
*/
|
||||
export function parsePrNumberList(value) {
|
||||
return [
|
||||
...new Set(
|
||||
@@ -27,6 +31,9 @@ export function parsePrNumberList(value) {
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses duplicate PR close workflow arguments.
|
||||
*/
|
||||
export function parseArgs(argv, env = process.env) {
|
||||
const args = {
|
||||
apply: false,
|
||||
@@ -109,6 +116,9 @@ function intersectSets(left, right) {
|
||||
return [...left].filter((value) => right.has(value));
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses changed hunk ranges from unified diff text.
|
||||
*/
|
||||
export function parseUnifiedDiffRanges(diffText) {
|
||||
const ranges = new Map();
|
||||
let currentPath = null;
|
||||
@@ -136,6 +146,9 @@ export function parseUnifiedDiffRanges(diffText) {
|
||||
return ranges;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reports whether two PR diffs touch overlapping hunks.
|
||||
*/
|
||||
export function hasOverlappingHunks(leftRanges, rightRanges) {
|
||||
for (const [path, left] of leftRanges) {
|
||||
const right = rightRanges.get(path) ?? [];
|
||||
@@ -182,6 +195,9 @@ Evidence: ${formatEvidence(evidence)}.
|
||||
Closing #${candidate.number} as a duplicate.`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the close/skip plan for duplicate PR candidates.
|
||||
*/
|
||||
export function buildDuplicateClosePlan({ candidates, diffs, landed, repo }) {
|
||||
if (landed.state !== "MERGED" || !landed.mergedAt) {
|
||||
throw new Error(`#${landed.number} is not merged`);
|
||||
@@ -246,6 +262,9 @@ function loadDiff(repo, number, runGh) {
|
||||
return runGh(["pr", "diff", String(number), "--repo", repo, "--color=never"]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies labels/comments/closes for planned duplicate PR actions.
|
||||
*/
|
||||
export function applyClosePlan({ labels = DEFAULT_LABELS, plan, repo, runGh }) {
|
||||
for (const item of plan) {
|
||||
if (item.action !== "close") {
|
||||
@@ -261,6 +280,9 @@ export function applyClosePlan({ labels = DEFAULT_LABELS, plan, repo, runGh }) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the duplicate PR close workflow.
|
||||
*/
|
||||
export function runDuplicateCloseWorkflow(args, runGh = defaultRunGh) {
|
||||
const landed = loadPr(args.repo, args.landedPr, runGh);
|
||||
const candidates = args.duplicates.map((number) => loadPr(args.repo, number, runGh));
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// Copies bundled plugin metadata into generated runtime locations.
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import { pathToFileURL } from "node:url";
|
||||
@@ -30,6 +31,9 @@ function shouldCopyBundledPluginMetadata(id, env, buildablePluginDirs) {
|
||||
return env.OPENCLAW_BUILD_PRIVATE_QA === "1";
|
||||
}
|
||||
|
||||
/**
|
||||
* Rewrites package extension entries for bundled metadata output.
|
||||
*/
|
||||
export function rewritePackageExtensions(entries) {
|
||||
if (!Array.isArray(entries)) {
|
||||
return undefined;
|
||||
@@ -234,6 +238,9 @@ function copyDeclaredPluginSkillPaths(params) {
|
||||
* env?: NodeJS.ProcessEnv;
|
||||
* }} [params]
|
||||
*/
|
||||
/**
|
||||
* Copies bundled plugin metadata and package extension files.
|
||||
*/
|
||||
export function copyBundledPluginMetadata(params = {}) {
|
||||
const repoRoot = params.cwd ?? params.repoRoot ?? process.cwd();
|
||||
const env = params.env ?? process.env;
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
// Copies the CommonJS plugin SDK root alias into dist output.
|
||||
import { readFileSync } from "node:fs";
|
||||
import { resolve } from "node:path";
|
||||
import { pathToFileURL } from "node:url";
|
||||
import { writeTextFileIfChanged } from "./runtime-postbuild-shared.mjs";
|
||||
|
||||
/**
|
||||
* Copies the plugin SDK root alias source into the configured output path.
|
||||
*/
|
||||
export function copyPluginSdkRootAlias(params = {}) {
|
||||
const cwd = params.cwd ?? process.cwd();
|
||||
const source = resolve(cwd, "src/plugin-sdk/root-alias.cjs");
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#!/usr/bin/env node
|
||||
// Resolves and delegates to the repo-local or PATH crabbox binary.
|
||||
import { spawn, spawnSync } from "node:child_process";
|
||||
import {
|
||||
accessSync,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
// Builds dependency change reports from lockfile and manifest diffs.
|
||||
import { execFileSync } from "node:child_process";
|
||||
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
@@ -41,6 +42,9 @@ function versionsFor(payload, packageName) {
|
||||
return new Set(payload[packageName] ?? []);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a structured dependency diff report from base/head payloads.
|
||||
*/
|
||||
export function createDependencyChangesReport({
|
||||
basePayload,
|
||||
headPayload,
|
||||
@@ -184,10 +188,16 @@ function readGitFile(ref, filePath, cwd) {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reports whether a path is a dependency-related file.
|
||||
*/
|
||||
export function isDependencyFile(filePath) {
|
||||
return DEPENDENCY_FILE_PATTERNS.some((pattern) => pattern.test(filePath));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns git pathspecs used for dependency diff collection.
|
||||
*/
|
||||
export function dependencyDiffPathspecs() {
|
||||
return [...DEPENDENCY_DIFF_PATHS];
|
||||
}
|
||||
@@ -276,6 +286,9 @@ async function writeArtifact(filePath, content) {
|
||||
await writeFile(filePath, content, "utf8");
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates and writes dependency change report artifacts.
|
||||
*/
|
||||
export async function runDependencyChangesReport(options) {
|
||||
const headLockfileText = await readFile(path.join(options.rootDir, options.headLockfile), "utf8");
|
||||
const baseLockfileText = options.baseRef
|
||||
@@ -293,6 +306,9 @@ export async function runDependencyChangesReport(options) {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the dependency changes report CLI.
|
||||
*/
|
||||
export async function main(argv = process.argv.slice(2)) {
|
||||
const options = parseArgs(argv);
|
||||
const report = await runDependencyChangesReport(options);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
// Reports dependency ownership, closure, and risk surface from lockfile data.
|
||||
import { execFileSync } from "node:child_process";
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
@@ -36,6 +37,9 @@ function normalizeDependencies(record = {}) {
|
||||
return entries.toSorted((left, right) => left.name.localeCompare(right.name));
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the package name from a pnpm lockfile package key.
|
||||
*/
|
||||
export function packageNameFromLockKey(lockKey) {
|
||||
const peerSuffixIndex = lockKey.indexOf("(");
|
||||
const baseKey = peerSuffixIndex >= 0 ? lockKey.slice(0, peerSuffixIndex) : lockKey;
|
||||
@@ -143,6 +147,9 @@ function collectReportTarget({ repoRoot, packageJson, ownershipPath }) {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Collects dependency ownership and transitive surface metadata.
|
||||
*/
|
||||
export function collectDependencyOwnershipSurfaceReport(params = {}) {
|
||||
const repoRoot = path.resolve(params.repoRoot ?? process.cwd());
|
||||
const packageJson = readJson(path.join(repoRoot, "package.json"));
|
||||
@@ -262,6 +269,9 @@ export function collectDependencyOwnershipSurfaceReport(params = {}) {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Collects policy errors from a dependency ownership surface report.
|
||||
*/
|
||||
export function collectDependencyOwnershipSurfaceCheckErrors(report) {
|
||||
return report.ownershipGaps.map(
|
||||
(name) => `root dependency '${name}' is missing from ${DEFAULT_OWNERSHIP_PATH}`,
|
||||
@@ -289,6 +299,9 @@ function pluralize(count, singular, plural = `${singular}s`) {
|
||||
return `${count} ${count === 1 ? singular : plural}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders a dependency ownership surface report as Markdown.
|
||||
*/
|
||||
export function renderDependencyOwnershipSurfaceMarkdownReport(report) {
|
||||
const lines = [
|
||||
"# Dependency Ownership and Install Surface Report",
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
// Checks resolved dependencies against npm advisory data and gate policy.
|
||||
import { readFile } from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import process from "node:process";
|
||||
@@ -124,6 +125,9 @@ function sortFindings(findings) {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Classifies npm advisory findings into report-only findings and hard blockers.
|
||||
*/
|
||||
export function classifyVulnerabilityFindings({ allAdvisories, productionAdvisories }) {
|
||||
const allFindings = flattenAdvisories(allAdvisories, "all");
|
||||
const productionFindings = flattenAdvisories(productionAdvisories, "production");
|
||||
@@ -150,6 +154,9 @@ function countPayloadVersions(payload) {
|
||||
return Object.values(payload).reduce((sum, versions) => sum + versions.length, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the dependency vulnerability gate against pnpm-lock.yaml.
|
||||
*/
|
||||
export async function runDependencyVulnerabilityGate({
|
||||
rootDir = process.cwd(),
|
||||
fetchImpl = fetch,
|
||||
@@ -195,6 +202,9 @@ export async function runDependencyVulnerabilityGate({
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the dependency vulnerability gate report as Markdown.
|
||||
*/
|
||||
export function renderDependencyVulnerabilityGateMarkdownReport(report) {
|
||||
const lines = [
|
||||
"# npm Advisory Vulnerability Gate: Resolved Dependency Graph",
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
// Audits docs links, routes, redirects, and Mintlify anchors.
|
||||
import { spawnSync } from "node:child_process";
|
||||
import fs from "node:fs";
|
||||
import os from "node:os";
|
||||
@@ -48,7 +49,11 @@ function normalizeSlashes(p) {
|
||||
return p.replace(/\\/g, "/");
|
||||
}
|
||||
|
||||
/** @param {string} p */
|
||||
/**
|
||||
* Normalizes a docs route by stripping query, hash, and edge slashes.
|
||||
*
|
||||
* @param {string} p
|
||||
*/
|
||||
export function normalizeRoute(p) {
|
||||
const [withoutFragment] = p.split("#");
|
||||
const [withoutQuery] = withoutFragment.split("?");
|
||||
@@ -276,6 +281,8 @@ export function sanitizeDocsConfigForEnglishOnly(value) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares a docs directory, mirroring ClawHub docs when available.
|
||||
*
|
||||
* @param {string} [sourceDir]
|
||||
* @param {{
|
||||
* resolveClawHubRepoPathImpl?: typeof resolveClawHubRepoPath;
|
||||
@@ -310,6 +317,9 @@ export function prepareMirroredDocsDir(sourceDir = DOCS_DIR, options = {}) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an English-only temporary docs tree for Mintlify anchor checks.
|
||||
*/
|
||||
export function prepareAnchorAuditDocsDir(sourceDir = DOCS_DIR) {
|
||||
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-docs-anchor-audit-"));
|
||||
try {
|
||||
@@ -406,6 +416,9 @@ export function resolveMintlifyAnchorAuditInvocation(params) {
|
||||
return createMintlifyPnpmRunnerSpawnSpec(params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Audits local docs links against route, file, and redirect indexes.
|
||||
*/
|
||||
export function auditDocsLinks(options = {}) {
|
||||
const docsDir = options.docsDir ?? DOCS_DIR;
|
||||
const index = buildAuditIndex(docsDir, {
|
||||
@@ -537,6 +550,8 @@ export function auditDocsLinks(options = {}) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the docs link audit CLI.
|
||||
*
|
||||
* @param {{
|
||||
* args?: string[];
|
||||
* comSpec?: string;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
// Lists source docs pages and routes for docs-aware tooling.
|
||||
import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
|
||||
import { join, relative } from "node:path";
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
// Syncs source docs into the generated publish tree.
|
||||
import { execFileSync } from "node:child_process";
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
@@ -298,6 +299,9 @@ function getGitHeadSha(repoPath) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves the local ClawHub repository path used for docs mirroring.
|
||||
*/
|
||||
export function resolveClawHubRepoPath(value = "", options = {}) {
|
||||
const required = options.required !== false;
|
||||
const candidates = [
|
||||
@@ -579,6 +583,9 @@ function rewriteClawHubMarkdownLinks(raw, relativeSourcePath, source) {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Mirrors ClawHub docs into the target docs tree.
|
||||
*/
|
||||
export function syncClawHubDocsTree(targetDocsDir, options = {}) {
|
||||
const repoPath = resolveClawHubRepoPath(options.repoPath || "", {
|
||||
required: options.required !== false,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
// Ensures CLI startup benchmark assets are built before checks.
|
||||
import { spawnSync } from "node:child_process";
|
||||
import { existsSync } from "node:fs";
|
||||
import path from "node:path";
|
||||
@@ -25,10 +26,16 @@ function positiveEnvInt(name, env, fallback) {
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves the CLI startup build timeout from environment.
|
||||
*/
|
||||
export function resolveCliStartupBuildTimeoutMs(env = process.env) {
|
||||
return positiveEnvInt("OPENCLAW_CLI_STARTUP_BUILD_TIMEOUT_MS", env, DEFAULT_BUILD_TIMEOUT_MS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reports whether required CLI startup build outputs exist.
|
||||
*/
|
||||
export function hasCliStartupBuild(params = {}) {
|
||||
const rootDir = params.rootDir ?? repoRoot;
|
||||
const exists = params.existsSync ?? existsSync;
|
||||
@@ -36,6 +43,9 @@ export function hasCliStartupBuild(params = {}) {
|
||||
return hasEntry && exists(path.join(rootDir, startupMetadataPath));
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds CLI startup assets when required outputs are missing.
|
||||
*/
|
||||
export function ensureCliStartupBuild(params = {}) {
|
||||
const rootDir = params.rootDir ?? repoRoot;
|
||||
if (hasCliStartupBuild({ rootDir, existsSync: params.existsSync })) {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
// Ensures memory extension runtime entries are built before checks.
|
||||
import { spawnSync } from "node:child_process";
|
||||
import { existsSync, readdirSync } from "node:fs";
|
||||
import path from "node:path";
|
||||
@@ -27,6 +28,9 @@ function positiveEnvInt(name, env, fallback) {
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves the extension memory build timeout from environment.
|
||||
*/
|
||||
export function resolveExtensionMemoryBuildTimeoutMs(env = process.env) {
|
||||
return positiveEnvInt(
|
||||
"OPENCLAW_EXTENSION_MEMORY_BUILD_TIMEOUT_MS",
|
||||
@@ -52,6 +56,9 @@ function collectExpectedExtensionMemoryEntryIds(rootDir, env) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reports whether built memory extension entries exist.
|
||||
*/
|
||||
export function hasBuiltExtensionMemoryEntries(params = {}) {
|
||||
const rootDir = params.rootDir ?? repoRoot;
|
||||
const exists = params.existsSync ?? existsSync;
|
||||
@@ -80,6 +87,9 @@ export function hasBuiltExtensionMemoryEntries(params = {}) {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds memory extension entries when required outputs are missing.
|
||||
*/
|
||||
export function ensureExtensionMemoryBuild(params = {}) {
|
||||
const rootDir = params.rootDir ?? repoRoot;
|
||||
if (
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#!/usr/bin/env node
|
||||
// Ensures Playwright Chromium is installed or a usable system browser is available.
|
||||
import { spawnSync as spawnSyncImpl } from "node:child_process";
|
||||
import { existsSync as existsSyncImpl, realpathSync } from "node:fs";
|
||||
import { dirname, resolve } from "node:path";
|
||||
@@ -18,6 +19,9 @@ const playwrightInstallWithDepsArgs = [
|
||||
"chromium",
|
||||
];
|
||||
const executableOverrideEnvKey = "PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH";
|
||||
/**
|
||||
* System Chromium executable paths used before downloading Playwright browsers.
|
||||
*/
|
||||
export const systemChromiumExecutableCandidates = [
|
||||
"/snap/bin/chromium",
|
||||
"/usr/bin/chromium-browser",
|
||||
@@ -26,6 +30,9 @@ export const systemChromiumExecutableCandidates = [
|
||||
"/usr/bin/google-chrome-stable",
|
||||
];
|
||||
|
||||
/**
|
||||
* Checks whether a Chromium executable can start enough to print its version.
|
||||
*/
|
||||
export function canRunChromiumExecutable(executablePath, spawnSync = spawnSyncImpl) {
|
||||
const result = spawnSync(executablePath, ["--version"], {
|
||||
stdio: "ignore",
|
||||
@@ -33,6 +40,9 @@ export function canRunChromiumExecutable(executablePath, spawnSync = spawnSyncIm
|
||||
return result.status === 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves the first runnable system Chromium executable path.
|
||||
*/
|
||||
export function resolveSystemChromiumExecutablePath(
|
||||
existsSync = existsSyncImpl,
|
||||
spawnSync = spawnSyncImpl,
|
||||
@@ -44,6 +54,9 @@ export function resolveSystemChromiumExecutablePath(
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the pnpm runner invocation for Playwright browser install.
|
||||
*/
|
||||
export function resolvePlaywrightInstallRunner(options = {}) {
|
||||
const env = options.env ?? process.env;
|
||||
return resolvePnpmRunner({
|
||||
@@ -59,6 +72,9 @@ function isTruthyEnvFlag(value) {
|
||||
return normalized === "1" || normalized === "true" || normalized === "yes" || normalized === "on";
|
||||
}
|
||||
|
||||
/**
|
||||
* Reports whether Linux system dependencies should be installed with Chromium.
|
||||
*/
|
||||
export function shouldInstallPlaywrightSystemDependencies(options = {}) {
|
||||
const env = options.env ?? process.env;
|
||||
const platform = options.platform ?? process.platform;
|
||||
@@ -76,6 +92,9 @@ export function shouldInstallPlaywrightSystemDependencies(options = {}) {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether this module is the direct script entrypoint.
|
||||
*/
|
||||
export function isDirectScriptExecution(
|
||||
argvEntry = process.argv[1],
|
||||
modulePath = fileURLToPath(import.meta.url),
|
||||
@@ -91,6 +110,9 @@ export function isDirectScriptExecution(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures a runnable Chromium exists for Playwright-based UI tests.
|
||||
*/
|
||||
export function ensurePlaywrightChromium(options = {}) {
|
||||
const env = options.env ?? process.env;
|
||||
const executableOverride =
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
// Formats docs Markdown/MDX and repairs Mintlify accordion indentation.
|
||||
import { execFileSync, spawnSync } from "node:child_process";
|
||||
import fs from "node:fs";
|
||||
import os from "node:os";
|
||||
|
||||
Reference in New Issue
Block a user