ci: broaden extension boundary guards

This commit is contained in:
Peter Steinberger
2026-04-27 21:02:24 +01:00
parent e9b1fbb8c4
commit 08e7561972
4 changed files with 28 additions and 35 deletions

View File

@@ -6,13 +6,6 @@ import { fileURLToPath } from "node:url";
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
const GUARDED_PUBLIC_API_BARRELS = [
"extensions/discord/api.ts",
"extensions/slack/api.ts",
"extensions/telegram/api.ts",
"extensions/whatsapp/api.ts",
];
const LOCAL_WILDCARD_REEXPORT_PATTERN = /^\s*export\s+(?:type\s+)?\*\s+from\s+["'](?:\.{1,2}\/)/u;
async function walkFiles(rootDir, predicate) {
@@ -38,24 +31,11 @@ async function walkFiles(rootDir, predicate) {
}
async function listGuardedFiles(rootDir = repoRoot) {
const runtimeApiFiles = await walkFiles(path.join(rootDir, "extensions"), (filePath) =>
filePath.endsWith(`${path.sep}runtime-api.ts`),
return walkFiles(
path.join(rootDir, "extensions"),
(filePath) =>
filePath.endsWith(`${path.sep}runtime-api.ts`) || filePath.endsWith(`${path.sep}api.ts`),
);
const files = new Set(runtimeApiFiles);
for (const relativePath of GUARDED_PUBLIC_API_BARRELS) {
const filePath = path.join(rootDir, relativePath);
try {
const stat = await fs.stat(filePath);
if (stat.isFile()) {
files.add(filePath);
}
} catch (error) {
if (!error || typeof error !== "object" || !("code" in error) || error.code !== "ENOENT") {
throw error;
}
}
}
return [...files].toSorted((left, right) => left.localeCompare(right));
}
export function findLocalWildcardReexports(source) {

View File

@@ -33,13 +33,26 @@ const FORBIDDEN_PATTERNS: Array<{ pattern: RegExp; hint: string }> = [
},
];
const FORBIDDEN_TEST_SUPPORT_PATTERNS: Array<{ pattern: RegExp; hint: string }> = [
{
pattern:
/\b(?:import|export)\b[\s\S]*?\bfrom\s*["'](?:\.\.\/){2,}src\/(?:agents|channels|config|infra|plugins|routing|security|test-helpers|test-utils)\/[^"']+["']/,
hint: "Use openclaw/plugin-sdk/testing or a focused plugin-sdk test/runtime subpath instead of core internals.",
},
];
function isExtensionTestFile(filePath: string): boolean {
return /\.test\.[cm]?[jt]sx?$/u.test(filePath) || /\.e2e\.test\.[cm]?[jt]sx?$/u.test(filePath);
}
function isExtensionTestSupportFile(filePath: string): boolean {
return /(?:^|[/\\])test-support(?:[/\\]|$)/u.test(filePath) && /\.[cm]?[jt]sx?$/u.test(filePath);
}
function collectExtensionTestFiles(rootDir: string): string[] {
return collectFilesSync(rootDir, {
includeFile: (filePath) => isExtensionTestFile(filePath),
includeFile: (filePath) =>
isExtensionTestFile(filePath) || isExtensionTestSupportFile(filePath),
});
}
@@ -50,7 +63,10 @@ function main() {
for (const file of files) {
const content = fs.readFileSync(file, "utf8");
for (const rule of FORBIDDEN_PATTERNS) {
const rules = isExtensionTestSupportFile(file)
? [...FORBIDDEN_PATTERNS, ...FORBIDDEN_TEST_SUPPORT_PATTERNS]
: FORBIDDEN_PATTERNS;
for (const rule of rules) {
if (!rule.pattern.test(content)) {
continue;
}
@@ -70,7 +86,7 @@ function main() {
}
console.log(
`OK: extension test files avoid direct core test/internal imports (${files.length} checked).`,
`OK: extension test files and support helpers avoid direct core test/internal imports (${files.length} checked).`,
);
}