mirror of
https://github.com/openclaw/openclaw.git
synced 2026-07-21 02:06:43 +00:00
refactor(scripts): share repository file discovery
This commit is contained in:
@@ -1,8 +1,19 @@
|
||||
// Check File Utils helper supports OpenClaw script workflows.
|
||||
import { execFileSync } from "node:child_process";
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
|
||||
const DEFAULT_SKIPPED_DIR_NAMES = new Set(["node_modules", "dist", "coverage", ".generated"]);
|
||||
export const REPO_SCAN_ROOTS = ["src", "test", "extensions", "packages", "ui", "scripts"] as const;
|
||||
export const REPO_SCAN_SKIPPED_DIR_NAMES: ReadonlySet<string> = new Set([
|
||||
".artifacts",
|
||||
".generated",
|
||||
"coverage",
|
||||
"dist",
|
||||
"fixtures",
|
||||
"node_modules",
|
||||
"vendor",
|
||||
]);
|
||||
|
||||
export function isCodeFile(filePath: string): boolean {
|
||||
if (filePath.endsWith(".d.ts")) {
|
||||
@@ -11,6 +22,20 @@ export function isCodeFile(filePath: string): boolean {
|
||||
return /\.(?:[cm]?ts|[cm]?js|tsx|jsx)$/u.test(filePath);
|
||||
}
|
||||
|
||||
export function isTestRelatedFile(relativePath: string): boolean {
|
||||
return (
|
||||
/(?:^|[/.])(?:test|spec)\.[cm]?[jt]sx?$/u.test(relativePath) ||
|
||||
/\.(?:e2e|live)\.test\.[cm]?[jt]sx?$/u.test(relativePath) ||
|
||||
/\.(?:test-helpers|test-utils|test-harness|test-support)\.[cm]?[jt]sx?$/u.test(relativePath) ||
|
||||
/-(?:test-helpers|test-utils|test-harness|test-support)\.[cm]?[jt]sx?$/u.test(relativePath) ||
|
||||
/(?:^|\/)(?:test|tests|test-helpers|test-utils|test-harness|test-support)\//u.test(
|
||||
relativePath,
|
||||
) ||
|
||||
relativePath.startsWith("scripts/e2e/") ||
|
||||
/^scripts\/.*-(?:client|e2e|harness|probe|smoke)\.[cm]?[jt]s$/u.test(relativePath)
|
||||
);
|
||||
}
|
||||
|
||||
export function collectFilesSync(
|
||||
rootDir: string,
|
||||
options: {
|
||||
@@ -51,6 +76,42 @@ export function collectFilesSync(
|
||||
return files;
|
||||
}
|
||||
|
||||
export function listRepoFilesSync(
|
||||
repoRoot: string,
|
||||
options: {
|
||||
includeFile: (relativePath: string) => boolean;
|
||||
roots?: readonly string[];
|
||||
skipDirNames?: ReadonlySet<string>;
|
||||
},
|
||||
): string[] {
|
||||
const roots = options.roots ?? REPO_SCAN_ROOTS;
|
||||
try {
|
||||
return execFileSync("git", ["-C", repoRoot, "ls-files", "--", ...roots], {
|
||||
encoding: "utf8",
|
||||
stdio: ["ignore", "pipe", "ignore"],
|
||||
})
|
||||
.split(/\r?\n/u)
|
||||
.filter(Boolean)
|
||||
.map(toPosixPath)
|
||||
.filter(options.includeFile)
|
||||
.toSorted((left, right) => left.localeCompare(right));
|
||||
} catch {
|
||||
return roots
|
||||
.flatMap((root) => {
|
||||
const absoluteRoot = path.join(repoRoot, root);
|
||||
if (!fs.existsSync(absoluteRoot)) {
|
||||
return [];
|
||||
}
|
||||
return collectFilesSync(absoluteRoot, {
|
||||
includeFile: (filePath) =>
|
||||
options.includeFile(toPosixPath(path.relative(repoRoot, filePath))),
|
||||
skipDirNames: options.skipDirNames ?? REPO_SCAN_SKIPPED_DIR_NAMES,
|
||||
}).map((filePath) => toPosixPath(path.relative(repoRoot, filePath)));
|
||||
})
|
||||
.toSorted((left, right) => left.localeCompare(right));
|
||||
}
|
||||
}
|
||||
|
||||
export function toPosixPath(filePath: string): string {
|
||||
if (path.sep === "/") {
|
||||
return filePath;
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
#!/usr/bin/env node
|
||||
// Test Env Mutation Report script supports OpenClaw repository automation.
|
||||
|
||||
import { execFileSync } from "node:child_process";
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import ts from "typescript";
|
||||
import { collectFilesSync, isCodeFile, toPosixPath } from "./check-file-utils.js";
|
||||
import { isCodeFile, isTestRelatedFile, listRepoFilesSync } from "./check-file-utils.js";
|
||||
|
||||
type EnvMutationOperation = "assign" | "delete" | "replace" | "stubEnv";
|
||||
|
||||
@@ -37,16 +36,6 @@ export type TestEnvMutationReport = {
|
||||
};
|
||||
|
||||
const DYNAMIC_ENV_KEY = "<dynamic>";
|
||||
const DEFAULT_SCAN_ROOTS = ["src", "test", "extensions", "packages", "ui", "scripts"];
|
||||
const DEFAULT_SKIPPED_DIR_NAMES = new Set([
|
||||
".artifacts",
|
||||
".generated",
|
||||
"coverage",
|
||||
"dist",
|
||||
"fixtures",
|
||||
"node_modules",
|
||||
"vendor",
|
||||
]);
|
||||
const TRACKED_ENV_KEYS = new Set([
|
||||
"HOME",
|
||||
"HOMEDRIVE",
|
||||
@@ -75,49 +64,10 @@ const DEFAULT_ALLOWED_FILES = new Map([
|
||||
],
|
||||
]);
|
||||
|
||||
function isTestRelatedFile(relativePath: string): boolean {
|
||||
return (
|
||||
/(?:^|[/.])(?:test|spec)\.[cm]?[jt]sx?$/u.test(relativePath) ||
|
||||
/\.(?:e2e|live)\.test\.[cm]?[jt]sx?$/u.test(relativePath) ||
|
||||
/\.(?:test-helpers|test-utils|test-harness|test-support)\.[cm]?[jt]sx?$/u.test(relativePath) ||
|
||||
/-(?:test-helpers|test-utils|test-harness|test-support)\.[cm]?[jt]sx?$/u.test(relativePath) ||
|
||||
/(?:^|\/)(?:test|tests|test-helpers|test-utils|test-harness|test-support)\//u.test(
|
||||
relativePath,
|
||||
) ||
|
||||
relativePath.startsWith("scripts/e2e/") ||
|
||||
/^scripts\/.*-(?:client|e2e|harness|probe|smoke)\.[cm]?[jt]s$/u.test(relativePath)
|
||||
);
|
||||
}
|
||||
|
||||
function listGitFiles(repoRoot: string): string[] | null {
|
||||
try {
|
||||
const stdout = execFileSync("git", ["-C", repoRoot, "ls-files", "--", ...DEFAULT_SCAN_ROOTS], {
|
||||
encoding: "utf8",
|
||||
stdio: ["ignore", "pipe", "ignore"],
|
||||
});
|
||||
return stdout.split(/\r?\n/u).filter(Boolean);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function listCandidateFiles(repoRoot: string): string[] {
|
||||
const gitFiles = listGitFiles(repoRoot);
|
||||
const relativeFiles =
|
||||
gitFiles ??
|
||||
DEFAULT_SCAN_ROOTS.flatMap((root) => {
|
||||
const absoluteRoot = path.join(repoRoot, root);
|
||||
if (!fs.existsSync(absoluteRoot)) {
|
||||
return [];
|
||||
}
|
||||
return collectFilesSync(absoluteRoot, {
|
||||
includeFile: isCodeFile,
|
||||
skipDirNames: DEFAULT_SKIPPED_DIR_NAMES,
|
||||
}).map((filePath) => toPosixPath(path.relative(repoRoot, filePath)));
|
||||
});
|
||||
return relativeFiles
|
||||
.filter((file) => isCodeFile(file) && isTestRelatedFile(file))
|
||||
.toSorted((left, right) => left.localeCompare(right));
|
||||
return listRepoFilesSync(repoRoot, {
|
||||
includeFile: (file) => isCodeFile(file) && isTestRelatedFile(file),
|
||||
});
|
||||
}
|
||||
|
||||
function isIdentifier(node: ts.Node, text: string): boolean {
|
||||
@@ -193,7 +143,9 @@ function createFinding(params: {
|
||||
operation: EnvMutationOperation;
|
||||
sourceFile: ts.SourceFile;
|
||||
}): TestEnvMutationFinding {
|
||||
const { line } = params.sourceFile.getLineAndCharacterOfPosition(params.node.getStart());
|
||||
const { line } = params.sourceFile.getLineAndCharacterOfPosition(
|
||||
params.node.getStart(params.sourceFile),
|
||||
);
|
||||
const allowReason = params.allowedFiles.get(params.file);
|
||||
return {
|
||||
allowed: allowReason !== undefined,
|
||||
@@ -213,7 +165,7 @@ function scanFile(params: {
|
||||
}): TestEnvMutationFinding[] {
|
||||
const absolutePath = path.join(params.repoRoot, params.file);
|
||||
const source = fs.readFileSync(absolutePath, "utf8");
|
||||
const sourceFile = ts.createSourceFile(params.file, source, ts.ScriptTarget.Latest, true);
|
||||
const sourceFile = ts.createSourceFile(params.file, source, ts.ScriptTarget.Latest);
|
||||
const lines = source.split(/\r?\n/u);
|
||||
const findings: TestEnvMutationFinding[] = [];
|
||||
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
#!/usr/bin/env node
|
||||
// Test Skip Inventory reports skipped, conditional, todo, and focused tests.
|
||||
|
||||
import { execFileSync } from "node:child_process";
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import ts from "typescript";
|
||||
import { collectFilesSync, isCodeFile, toPosixPath } from "./check-file-utils.js";
|
||||
import { isCodeFile, isTestRelatedFile, listRepoFilesSync } from "./check-file-utils.js";
|
||||
|
||||
type SkipInventoryKind = "alias" | "call";
|
||||
type SkipInventoryReason =
|
||||
@@ -42,16 +41,6 @@ export type TestSkipInventoryReport = {
|
||||
};
|
||||
};
|
||||
|
||||
const DEFAULT_SCAN_ROOTS = ["src", "test", "extensions", "packages", "ui", "scripts"];
|
||||
const DEFAULT_SKIPPED_DIR_NAMES = new Set([
|
||||
".artifacts",
|
||||
".generated",
|
||||
"coverage",
|
||||
"dist",
|
||||
"fixtures",
|
||||
"node_modules",
|
||||
"vendor",
|
||||
]);
|
||||
const EMPTY_REASON_COUNTS: Record<SkipInventoryReason, number> = {
|
||||
"conditional-skip": 0,
|
||||
"explicit-skip": 0,
|
||||
@@ -65,49 +54,10 @@ const SKIP_METHODS = new Set(["only", "runIf", "skip", "skipIf", "todo"]);
|
||||
const TEST_TARGETS = new Set(["describe", "it", "test"]);
|
||||
const TRANSPARENT_CHAIN_METHODS = new Set(["concurrent", "each", "sequential"]);
|
||||
|
||||
function isTestRelatedFile(relativePath: string): boolean {
|
||||
return (
|
||||
/(?:^|[/.])(?:test|spec)\.[cm]?[jt]sx?$/u.test(relativePath) ||
|
||||
/\.(?:e2e|live)\.test\.[cm]?[jt]sx?$/u.test(relativePath) ||
|
||||
/\.(?:test-helpers|test-utils|test-harness|test-support)\.[cm]?[jt]sx?$/u.test(relativePath) ||
|
||||
/-(?:test-helpers|test-utils|test-harness|test-support)\.[cm]?[jt]sx?$/u.test(relativePath) ||
|
||||
/(?:^|\/)(?:test|tests|test-helpers|test-utils|test-harness|test-support)\//u.test(
|
||||
relativePath,
|
||||
) ||
|
||||
relativePath.startsWith("scripts/e2e/") ||
|
||||
/^scripts\/.*-(?:client|e2e|harness|probe|smoke)\.[cm]?[jt]s$/u.test(relativePath)
|
||||
);
|
||||
}
|
||||
|
||||
function listGitFiles(repoRoot: string): string[] | null {
|
||||
try {
|
||||
const stdout = execFileSync("git", ["-C", repoRoot, "ls-files", "--", ...DEFAULT_SCAN_ROOTS], {
|
||||
encoding: "utf8",
|
||||
stdio: ["ignore", "pipe", "ignore"],
|
||||
});
|
||||
return stdout.split(/\r?\n/u).filter(Boolean);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function listCandidateFiles(repoRoot: string): string[] {
|
||||
const gitFiles = listGitFiles(repoRoot);
|
||||
const relativeFiles =
|
||||
gitFiles ??
|
||||
DEFAULT_SCAN_ROOTS.flatMap((root) => {
|
||||
const absoluteRoot = path.join(repoRoot, root);
|
||||
if (!fs.existsSync(absoluteRoot)) {
|
||||
return [];
|
||||
}
|
||||
return collectFilesSync(absoluteRoot, {
|
||||
includeFile: isCodeFile,
|
||||
skipDirNames: DEFAULT_SKIPPED_DIR_NAMES,
|
||||
}).map((filePath) => toPosixPath(path.relative(repoRoot, filePath)));
|
||||
});
|
||||
return relativeFiles
|
||||
.filter((file) => isCodeFile(file) && isTestRelatedFile(file))
|
||||
.toSorted((left, right) => left.localeCompare(right));
|
||||
return listRepoFilesSync(repoRoot, {
|
||||
includeFile: (file) => isCodeFile(file) && isTestRelatedFile(file),
|
||||
});
|
||||
}
|
||||
|
||||
function expressionText(sourceFile: ts.SourceFile, node: ts.Node): string {
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
#!/usr/bin/env node
|
||||
// Type Suppression Inventory reports unchecked any casts and expected TypeScript errors.
|
||||
|
||||
import { execFileSync } from "node:child_process";
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import ts from "typescript";
|
||||
import { collectFilesSync, isCodeFile, toPosixPath } from "./check-file-utils.js";
|
||||
import {
|
||||
REPO_SCAN_ROOTS,
|
||||
REPO_SCAN_SKIPPED_DIR_NAMES,
|
||||
listRepoFilesSync,
|
||||
toPosixPath,
|
||||
} from "./check-file-utils.js";
|
||||
|
||||
type TypeSuppressionKind = "as-any" | "expect-error" | "type-assertion-any";
|
||||
|
||||
@@ -29,58 +33,20 @@ export type TypeSuppressionReport = {
|
||||
};
|
||||
};
|
||||
|
||||
const DEFAULT_SCAN_ROOTS = ["src", "test", "extensions", "packages", "ui", "scripts"];
|
||||
const TYPE_SUPPRESSION_CANDIDATE_PATTERN = /\bany\b|@ts-expect-error/u;
|
||||
const DEFAULT_SKIPPED_DIR_NAMES = new Set([
|
||||
".artifacts",
|
||||
".generated",
|
||||
"coverage",
|
||||
"dist",
|
||||
"fixtures",
|
||||
"node_modules",
|
||||
"vendor",
|
||||
]);
|
||||
|
||||
function listGitFiles(repoRoot: string, roots: readonly string[]): string[] | null {
|
||||
try {
|
||||
const stdout = execFileSync("git", ["-C", repoRoot, "ls-files", "--", ...roots], {
|
||||
encoding: "utf8",
|
||||
stdio: ["ignore", "pipe", "ignore"],
|
||||
});
|
||||
return stdout.split(/\r?\n/u).filter(Boolean);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function listCandidateFiles(repoRoot: string, roots: readonly string[]): string[] {
|
||||
const gitFiles = listGitFiles(repoRoot, roots);
|
||||
const relativeFiles =
|
||||
gitFiles ??
|
||||
roots.flatMap((root) => {
|
||||
const absoluteRoot = path.join(repoRoot, root);
|
||||
if (!fs.existsSync(absoluteRoot)) {
|
||||
return [];
|
||||
}
|
||||
return collectFilesSync(absoluteRoot, {
|
||||
includeFile: isCodeFile,
|
||||
skipDirNames: DEFAULT_SKIPPED_DIR_NAMES,
|
||||
}).map((filePath) => toPosixPath(path.relative(repoRoot, filePath)));
|
||||
});
|
||||
return relativeFiles
|
||||
.filter((file) => {
|
||||
return listRepoFilesSync(repoRoot, {
|
||||
includeFile: (file) => {
|
||||
const pathSegments = toPosixPath(file).split("/");
|
||||
return (
|
||||
/\.[cm]?tsx?$/u.test(file) &&
|
||||
!file.endsWith(".d.ts") &&
|
||||
!pathSegments.some((segment) => DEFAULT_SKIPPED_DIR_NAMES.has(segment))
|
||||
!pathSegments.some((segment) => REPO_SCAN_SKIPPED_DIR_NAMES.has(segment))
|
||||
);
|
||||
})
|
||||
.toSorted((left, right) => left.localeCompare(right));
|
||||
}
|
||||
|
||||
function sourceKindForFile(file: string): ts.ScriptKind {
|
||||
return file.endsWith(".tsx") ? ts.ScriptKind.TSX : ts.ScriptKind.TS;
|
||||
},
|
||||
roots,
|
||||
});
|
||||
}
|
||||
|
||||
function addAnyCastFindings(
|
||||
@@ -149,7 +115,7 @@ export function collectTypeSuppressionReport(params: {
|
||||
roots?: readonly string[];
|
||||
}): TypeSuppressionReport {
|
||||
const files = [
|
||||
...(params.files ?? listCandidateFiles(params.repoRoot, params.roots ?? DEFAULT_SCAN_ROOTS)),
|
||||
...(params.files ?? listCandidateFiles(params.repoRoot, params.roots ?? REPO_SCAN_ROOTS)),
|
||||
]
|
||||
.map(toPosixPath)
|
||||
.toSorted((left, right) => left.localeCompare(right));
|
||||
@@ -166,13 +132,7 @@ export function collectTypeSuppressionReport(params: {
|
||||
if (!TYPE_SUPPRESSION_CANDIDATE_PATTERN.test(source)) {
|
||||
continue;
|
||||
}
|
||||
const sourceFile = ts.createSourceFile(
|
||||
file,
|
||||
source,
|
||||
ts.ScriptTarget.Latest,
|
||||
false,
|
||||
sourceKindForFile(file),
|
||||
);
|
||||
const sourceFile = ts.createSourceFile(file, source, ts.ScriptTarget.Latest);
|
||||
addAnyCastFindings(sourceFile, file, findings);
|
||||
addExpectErrorFindings(sourceFile, file, findings);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user