mirror of
https://github.com/openclaw/openclaw.git
synced 2026-06-06 05:51:15 +08:00
docs: document root guard scripts
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
#!/usr/bin/env node
|
||||
// Runs knip unused-file detection and compares results to the allowlist.
|
||||
import { spawn } from "node:child_process";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import {
|
||||
@@ -8,9 +9,21 @@ import {
|
||||
import { createPnpmRunnerSpawnSpec } from "./pnpm-runner.mjs";
|
||||
|
||||
const KNIP_VERSION = "6.8.0";
|
||||
/**
|
||||
* Timeout for the unused-file knip child process.
|
||||
*/
|
||||
export const KNIP_TIMEOUT_MS = 10 * 60 * 1000;
|
||||
/**
|
||||
* Grace period before force-killing a timed-out knip child process.
|
||||
*/
|
||||
export const KNIP_KILL_GRACE_MS = 5_000;
|
||||
/**
|
||||
* Heartbeat interval used while knip runs without output.
|
||||
*/
|
||||
export const KNIP_HEARTBEAT_MS = 60_000;
|
||||
/**
|
||||
* Maximum buffered knip output retained for diagnostics.
|
||||
*/
|
||||
export const KNIP_MAX_BUFFER_BYTES = 16 * 1024 * 1024;
|
||||
const KNIP_ARGS = [
|
||||
"--config",
|
||||
@@ -37,6 +50,9 @@ function isLikelyRepoFilePath(value) {
|
||||
return /^(apps|docs|extensions|packages|scripts|src|test|ui)\//u.test(normalizeRepoPath(value));
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses compact knip output into unused file paths.
|
||||
*/
|
||||
export function parseKnipCompactUnusedFiles(output) {
|
||||
const files = [];
|
||||
let inUnusedFilesSection = false;
|
||||
@@ -68,6 +84,9 @@ export function parseKnipCompactUnusedFiles(output) {
|
||||
return uniqueSorted(files);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares detected unused files against the checked-in allowlist.
|
||||
*/
|
||||
export function compareUnusedFilesToAllowlist(
|
||||
actualFiles,
|
||||
allowlistFiles,
|
||||
@@ -90,6 +109,9 @@ export function compareUnusedFilesToAllowlist(
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats unused-file allowlist drift for CLI output.
|
||||
*/
|
||||
export function formatUnusedFileComparison(comparison) {
|
||||
const lines = [];
|
||||
if (!comparison.allowlistIsSorted) {
|
||||
@@ -132,6 +154,9 @@ function signalProcessTree(child, signal) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs knip and returns parsed unused-file results.
|
||||
*/
|
||||
export async function runKnipUnusedFiles(params = {}) {
|
||||
const run = params.spawnCommand ?? spawn;
|
||||
const timeoutMs = params.timeoutMs ?? KNIP_TIMEOUT_MS;
|
||||
@@ -284,6 +309,9 @@ export async function runKnipUnusedFiles(params = {}) {
|
||||
});
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Checks detected unused files against the current allowlist.
|
||||
*/
|
||||
export function checkUnusedFiles(
|
||||
output,
|
||||
allowlistFiles = KNIP_UNUSED_FILE_ALLOWLIST,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
// Audits patched dependency pins for exact versions and drift.
|
||||
import { execFileSync } from "node:child_process";
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
@@ -106,10 +107,16 @@ function collectWorkspaceViolations(cwd) {
|
||||
return violations;
|
||||
}
|
||||
|
||||
/**
|
||||
* Collects dependency pin violations for the current workspace.
|
||||
*/
|
||||
export function collectDependencyPinViolations(cwd = process.cwd()) {
|
||||
return [...collectPackageJsonViolations(cwd), ...collectWorkspaceViolations(cwd)];
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the full dependency pin audit payload.
|
||||
*/
|
||||
export function collectDependencyPinAudit(cwd = process.cwd()) {
|
||||
const packageJsonFiles = listTrackedPackageJsonFiles(cwd);
|
||||
let packageSpecCount = 0;
|
||||
@@ -128,6 +135,9 @@ export function collectDependencyPinAudit(cwd = process.cwd()) {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the dependency pin check.
|
||||
*/
|
||||
export async function main() {
|
||||
const audit = collectDependencyPinAudit();
|
||||
const { violations } = audit;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#!/usr/bin/env node
|
||||
// Scans source files for usage of deprecated API markers.
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import { collectDeprecatedInternalConfigApiViolations } from "./lib/deprecated-config-api-guard.mjs";
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#!/usr/bin/env node
|
||||
// Checks deprecated JSDoc blocks for required migration details.
|
||||
import fs from "node:fs";
|
||||
import { createRequire } from "node:module";
|
||||
import path from "node:path";
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
// Validates docs i18n glossary terms against configured usage rules.
|
||||
import { execFileSync } from "node:child_process";
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
// Validates docs MDX files for syntax and repository-specific conventions.
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
@@ -91,6 +92,9 @@ function parsePositiveIntegerArg(raw, label) {
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses docs MDX check arguments.
|
||||
*/
|
||||
export function parseArgs(argv) {
|
||||
const roots = [];
|
||||
let jsonOut = "";
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#!/usr/bin/env node
|
||||
// Runs duplicate-code detection with repo-specific excludes.
|
||||
import { spawnSync } from "node:child_process";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
// Advises on ineffective or suspicious dynamic import patterns.
|
||||
import { promises as fs } from "node:fs";
|
||||
import path from "node:path";
|
||||
import ts from "typescript";
|
||||
@@ -90,6 +91,9 @@ function isIgnoredTestHelperPath(filePath) {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds dynamic import advisories in a single source file.
|
||||
*/
|
||||
export function findDynamicImportAdvisories(content, fileName = "source.ts") {
|
||||
const sourceFile = ts.createSourceFile(fileName, content, ts.ScriptTarget.Latest, true);
|
||||
const staticRuntimeImports = new Map();
|
||||
@@ -171,6 +175,9 @@ export function findDynamicImportAdvisories(content, fileName = "source.ts") {
|
||||
return advisories;
|
||||
}
|
||||
|
||||
/**
|
||||
* Collects dynamic import advisories across configured source roots.
|
||||
*/
|
||||
export async function collectDynamicImportAdvisories(options = {}) {
|
||||
const roots = options.roots ?? defaultRoots;
|
||||
const files = await collectTypeScriptFilesFromRoots(roots, {
|
||||
@@ -195,6 +202,9 @@ export async function collectDynamicImportAdvisories(options = {}) {
|
||||
return advisories;
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the dynamic import advisory check.
|
||||
*/
|
||||
export async function main(argv = process.argv.slice(2)) {
|
||||
const fail = argv.includes("--fail");
|
||||
const json = argv.includes("--json");
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
// Verifies extension packages compile through their package-local TypeScript boundary.
|
||||
import { spawn, spawnSync } from "node:child_process";
|
||||
import {
|
||||
existsSync,
|
||||
@@ -49,6 +50,9 @@ function parseMode(argv) {
|
||||
return mode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves the compile worker count from CLI/env/default settings.
|
||||
*/
|
||||
export function resolveCompileConcurrency(
|
||||
env = process.env,
|
||||
availableParallelism = os.availableParallelism(),
|
||||
@@ -98,6 +102,9 @@ function createStepOutputCapture() {
|
||||
return { text: "", truncatedChars: 0 };
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends child-process output while preserving only the diagnostic tail.
|
||||
*/
|
||||
export function appendBoundedStepOutput(buffer, chunk, maxChars = STEP_OUTPUT_MAX_CHARS) {
|
||||
const nextText = buffer.text + String(chunk);
|
||||
if (nextText.length <= maxChars) {
|
||||
@@ -114,6 +121,9 @@ function formatCapturedStepOutput(buffer) {
|
||||
return `[output truncated ${buffer.truncatedChars} chars; showing tail]\n${buffer.text}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats the successful boundary compile summary.
|
||||
*/
|
||||
export function formatBoundaryCheckSuccessSummary(params = {}) {
|
||||
const lines = ["extension package boundary check passed"];
|
||||
if (params.mode) {
|
||||
@@ -143,6 +153,9 @@ export function formatBoundaryCheckSuccessSummary(params = {}) {
|
||||
return `${lines.join("\n")}\n`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats skipped compile progress for fresh extension canaries.
|
||||
*/
|
||||
export function formatSkippedCompileProgress(params = {}) {
|
||||
const skippedCount = params.skippedCount ?? 0;
|
||||
const totalCount = params.totalCount ?? 0;
|
||||
@@ -157,6 +170,9 @@ export function formatSkippedCompileProgress(params = {}) {
|
||||
return `skipped ${skippedCount} fresh plugin compiles\n`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats slow extension compile diagnostics.
|
||||
*/
|
||||
export function formatSlowCompileSummary(params = {}) {
|
||||
const compileTimings = Array.isArray(params.compileTimings) ? params.compileTimings : [];
|
||||
if (compileTimings.length === 0) {
|
||||
@@ -174,6 +190,9 @@ export function formatSlowCompileSummary(params = {}) {
|
||||
return `${lines.join("\n")}\n`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats a failed boundary-check child process step.
|
||||
*/
|
||||
export function formatStepFailure(label, params = {}) {
|
||||
const stdoutSection = summarizeOutputSection("stdout", params.stdout ?? "");
|
||||
const stderrSection = summarizeOutputSection("stderr", params.stderr ?? "");
|
||||
@@ -285,6 +304,9 @@ function collectOldestMtime(paths) {
|
||||
return Number.isFinite(oldestMtimeMs) ? oldestMtimeMs : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether an extension boundary compile canary is still fresh.
|
||||
*/
|
||||
export function isBoundaryCompileFresh(extensionId, params = {}) {
|
||||
const rootDir = params.rootDir ?? repoRoot;
|
||||
const extensionRoot = resolve(rootDir, "extensions", extensionId);
|
||||
@@ -363,6 +385,9 @@ function abortSiblingSteps(abortController) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs one node-based boundary check step with timeout and output capture.
|
||||
*/
|
||||
export function runNodeStepAsync(label, args, timeoutMs, params = {}) {
|
||||
const abortController = params.abortController;
|
||||
const killProcess = params.killProcess ?? process.kill.bind(process);
|
||||
@@ -540,6 +565,9 @@ export function runNodeStepAsync(label, args, timeoutMs, params = {}) {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs boundary check steps with bounded concurrency.
|
||||
*/
|
||||
export async function runNodeStepsWithConcurrency(steps, concurrency) {
|
||||
const abortController = new AbortController();
|
||||
let firstFailure = null;
|
||||
@@ -571,6 +599,9 @@ export async function runNodeStepsWithConcurrency(steps, concurrency) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves canary artifact paths for an extension boundary compile.
|
||||
*/
|
||||
export function resolveCanaryArtifactPaths(extensionId, rootDir = repoRoot) {
|
||||
const extensionRoot = resolve(rootDir, "extensions", extensionId);
|
||||
return {
|
||||
@@ -580,18 +611,27 @@ export function resolveCanaryArtifactPaths(extensionId, rootDir = repoRoot) {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes canary artifacts for one extension.
|
||||
*/
|
||||
export function cleanupCanaryArtifacts(extensionId, rootDir = repoRoot) {
|
||||
const { canaryPath, tsconfigPath } = resolveCanaryArtifactPaths(extensionId, rootDir);
|
||||
rmSync(canaryPath, { force: true });
|
||||
rmSync(tsconfigPath, { force: true });
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes canary artifacts for multiple extensions.
|
||||
*/
|
||||
export function cleanupCanaryArtifactsForExtensions(extensionIds, rootDir = repoRoot) {
|
||||
for (const extensionId of extensionIds) {
|
||||
cleanupCanaryArtifacts(extensionId, rootDir);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Installs signal/exit cleanup for extension canary artifacts.
|
||||
*/
|
||||
export function installCanaryArtifactCleanup(extensionIds, params = {}) {
|
||||
const rootDir = params.rootDir ?? repoRoot;
|
||||
const processObject = params.processObject ?? process;
|
||||
@@ -612,6 +652,9 @@ function resolveBoundaryTsStampPath(extensionId, rootDir = repoRoot) {
|
||||
return resolve(rootDir, "extensions", extensionId, "dist", ".boundary-tsc.stamp");
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves the local lock path for extension boundary checks.
|
||||
*/
|
||||
export function resolveBoundaryCheckLockPath(rootDir = repoRoot) {
|
||||
return resolve(rootDir, "dist", ".extension-package-boundary.lock");
|
||||
}
|
||||
@@ -649,6 +692,9 @@ function removeStaleBoundaryCheckLock(lockPath) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Acquires the single-process lock for extension boundary checks.
|
||||
*/
|
||||
export function acquireBoundaryCheckLock(params = {}) {
|
||||
const rootDir = params.rootDir ?? repoRoot;
|
||||
const processObject = params.processObject ?? process;
|
||||
@@ -848,6 +894,9 @@ async function runCanaryCheck(extensionIds) {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the extension package TypeScript boundary check.
|
||||
*/
|
||||
export async function main(argv = process.argv.slice(2)) {
|
||||
const startedAt = Date.now();
|
||||
const mode = parseMode(argv);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
// Inventories extension imports to enforce plugin SDK boundary rules.
|
||||
import { promises as fs } from "node:fs";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
@@ -225,6 +226,9 @@ function collectEntriesByModeFromModuleReferences(filePath, references) {
|
||||
return entriesByMode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Collects the current extension plugin SDK boundary inventory.
|
||||
*/
|
||||
export async function collectExtensionPluginSdkBoundaryInventory(mode) {
|
||||
if (!MODES.has(mode)) {
|
||||
throw new Error(`Unknown mode: ${mode}`);
|
||||
@@ -253,6 +257,9 @@ export async function collectExtensionPluginSdkBoundaryInventory(mode) {
|
||||
return inventoryByMode[mode];
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the checked-in expected boundary inventory.
|
||||
*/
|
||||
export async function readExpectedInventory(mode) {
|
||||
try {
|
||||
return JSON.parse(await fs.readFile(baselinePathByMode[mode], "utf8"));
|
||||
@@ -270,6 +277,9 @@ export async function readExpectedInventory(mode) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Diffs expected and actual boundary inventory entries.
|
||||
*/
|
||||
export function diffInventory(expected, actual) {
|
||||
return diffInventoryEntries(expected, actual, compareEntries);
|
||||
}
|
||||
@@ -294,6 +304,9 @@ function formatInventoryHuman(mode, inventory) {
|
||||
return lines.join("\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the boundary inventory check with CLI-style inputs and outputs.
|
||||
*/
|
||||
export async function runExtensionPluginSdkBoundaryCheck(argv, io) {
|
||||
const args = argv ?? process.argv.slice(2);
|
||||
const streams = io ?? { stdout: process.stdout, stderr: process.stderr };
|
||||
@@ -343,6 +356,9 @@ export async function runExtensionPluginSdkBoundaryCheck(argv, io) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Entrypoint wrapper for the extension plugin SDK boundary check.
|
||||
*/
|
||||
export async function main(argv, io) {
|
||||
const exitCode = await runExtensionPluginSdkBoundaryCheck(argv, io);
|
||||
if (!io) {
|
||||
|
||||
Reference in New Issue
Block a user