docs: document ci dependency docs scripts

This commit is contained in:
Peter Steinberger
2026-06-04 23:42:32 -04:00
parent 6b0ffa2106
commit 980c91d293
20 changed files with 215 additions and 1 deletions

View File

@@ -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");

View File

@@ -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) {

View File

@@ -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,

View File

@@ -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;

View File

@@ -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,

View File

@@ -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;

View File

@@ -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));

View File

@@ -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;

View File

@@ -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");

View File

@@ -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,

View File

@@ -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);

View File

@@ -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",

View File

@@ -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",

View File

@@ -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;

View File

@@ -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";

View File

@@ -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,

View File

@@ -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 })) {

View File

@@ -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 (

View File

@@ -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 =

View File

@@ -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";