mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 10:10:45 +00:00
ci: broaden extension boundary guards
This commit is contained in:
@@ -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) {
|
||||
|
||||
@@ -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).`,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,8 @@ export const pluginSdkEntrypoints = [...pluginSdkEntryList];
|
||||
|
||||
export const pluginSdkSubpaths = pluginSdkEntrypoints.filter((entry) => entry !== "index");
|
||||
|
||||
// Transitional compatibility/helper surfaces owned by their matching bundled plugin.
|
||||
// Cross-owner extension imports are blocked by the package contract guardrails.
|
||||
export const reservedBundledPluginSdkEntrypoints = [
|
||||
"bluebubbles",
|
||||
"bluebubbles-policy",
|
||||
@@ -58,6 +60,8 @@ export const reservedBundledPluginSdkEntrypoints = [
|
||||
"zalouser",
|
||||
] as const;
|
||||
|
||||
// Supported SDK facades backed by bundled plugins. These are intentionally public
|
||||
// until they move to generic, plugin-neutral contracts.
|
||||
export const supportedBundledFacadeSdkEntrypoints = [
|
||||
"lmstudio",
|
||||
"lmstudio-runtime",
|
||||
@@ -66,6 +70,7 @@ export const supportedBundledFacadeSdkEntrypoints = [
|
||||
"tts-runtime",
|
||||
] as const;
|
||||
|
||||
// Plugin-owned surfaces that are intentionally public and documented for third-party plugins.
|
||||
export const publicPluginOwnedSdkEntrypoints = [
|
||||
"browser-config",
|
||||
"image-generation-core",
|
||||
|
||||
@@ -242,14 +242,6 @@ function collectCrossOwnerReservedSdkImports(): Array<{
|
||||
|
||||
for (const file of collectExtensionFiles(resolve(REPO_ROOT, "extensions"))) {
|
||||
const repoRelativePath = relative(REPO_ROOT, file).replaceAll("\\", "/");
|
||||
if (
|
||||
/(?:^|\/)(?:__tests__|tests|test-support)(?:\/|$)/.test(repoRelativePath) ||
|
||||
/(?:^|\/)test-support\.[cm]?tsx?$/.test(repoRelativePath) ||
|
||||
/\.test-support\.[cm]?tsx?$/.test(repoRelativePath) ||
|
||||
/\.test\.[cm]?tsx?$/.test(repoRelativePath)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
const pluginId = repoRelativePath.split("/")[1];
|
||||
const source = readFileSync(file, "utf8");
|
||||
for (const match of source.matchAll(importPattern)) {
|
||||
|
||||
Reference in New Issue
Block a user