























@@ -2,6 +2,8 @@ import fs from "node:fs";
22import path from "node:path";
33import { collectFilesSync, relativeToCwd } from "./check-file-utils.js";
445+type Offender = { file: string; hint: string; line?: number; specifier?: string };
6+57const FORBIDDEN_PATTERNS: Array<{ pattern: RegExp; hint: string }> = [
68{
79pattern: /["']openclaw\/plugin-sdk["']/,
@@ -33,13 +35,10 @@ const FORBIDDEN_PATTERNS: Array<{ pattern: RegExp; hint: string }> = [
3335},
3436];
353736-const FORBIDDEN_TEST_SUPPORT_PATTERNS: Array<{ pattern: RegExp; hint: string }> = [
37-{
38-pattern:
39-/\b(?:import|export)\b[\s\S]*?\bfrom\s*["'](?:\.\.\/){2,}src\/(?:agents|channels|config|infra|plugins|routing|security|test-helpers|test-utils)\/[^"']+["']/,
40-hint: "Use openclaw/plugin-sdk/testing or a focused plugin-sdk test/runtime subpath instead of core internals.",
41-},
42-];
38+const STATIC_RELATIVE_MODULE_PATTERN = /\b(?:import|export)\b[\s\S]*?\bfrom\s*["']([^"']+)["']/g;
39+40+const RELATIVE_CORE_HINT =
41+"Use openclaw/plugin-sdk/testing or a focused plugin-sdk test/runtime subpath instead of core internals.";
43424443function isExtensionTestFile(filePath: string): boolean {
4544return /\.test\.[cm]?[jt]sx?$/u.test(filePath) || /\.e2e\.test\.[cm]?[jt]sx?$/u.test(filePath);
@@ -56,31 +55,69 @@ function collectExtensionTestFiles(rootDir: string): string[] {
5655});
5756}
585758+function lineNumberForOffset(content: string, offset: number): number {
59+let line = 1;
60+for (let index = 0; index < offset; index += 1) {
61+if (content.charCodeAt(index) === 10) {
62+line += 1;
63+}
64+}
65+return line;
66+}
67+68+function resolvesToRepoSrc(filePath: string, specifier: string): boolean {
69+if (!specifier.startsWith(".")) {
70+return false;
71+}
72+const resolved = path.resolve(path.dirname(filePath), specifier);
73+const repoRelative = path.relative(process.cwd(), resolved).replaceAll(path.sep, "/");
74+return repoRelative === "src" || repoRelative.startsWith("src/");
75+}
76+77+function collectRelativeCoreImportOffenders(filePath: string, content: string): Offender[] {
78+const offenders: Offender[] = [];
79+for (const match of content.matchAll(STATIC_RELATIVE_MODULE_PATTERN)) {
80+const specifier = match[1];
81+if (!specifier || !resolvesToRepoSrc(filePath, specifier)) {
82+continue;
83+}
84+offenders.push({
85+file: filePath,
86+hint: RELATIVE_CORE_HINT,
87+line: lineNumberForOffset(content, match.index ?? 0),
88+ specifier,
89+});
90+}
91+return offenders;
92+}
93+5994function main() {
6095const extensionsDir = path.join(process.cwd(), "extensions");
6196const files = collectExtensionTestFiles(extensionsDir);
62-const offenders: Array<{ file: string; hint: string }> = [];
97+const offenders: Offender[] = [];
63986499for (const file of files) {
65100const content = fs.readFileSync(file, "utf8");
66-const rules = isExtensionTestSupportFile(file)
67- ? [...FORBIDDEN_PATTERNS, ...FORBIDDEN_TEST_SUPPORT_PATTERNS]
68- : FORBIDDEN_PATTERNS;
69-for (const rule of rules) {
101+for (const rule of FORBIDDEN_PATTERNS) {
70102if (!rule.pattern.test(content)) {
71103continue;
72104}
73105offenders.push({ file, hint: rule.hint });
74106break;
75107}
108+offenders.push(...collectRelativeCoreImportOffenders(file, content));
76109}
7711078111if (offenders.length > 0) {
79112console.error(
80113"Extension test files must stay on extension test bridges or public plugin-sdk surfaces.",
81114);
82115for (const offender of offenders.toSorted((a, b) => a.file.localeCompare(b.file))) {
83-console.error(`- ${relativeToCwd(offender.file)}: ${offender.hint}`);
116+const location = offender.line
117+ ? `${relativeToCwd(offender.file)}:${offender.line}`
118+ : relativeToCwd(offender.file);
119+const specifier = offender.specifier ? ` (${offender.specifier})` : "";
120+console.error(`- ${location}${specifier}: ${offender.hint}`);
84121}
85122process.exit(1);
86123}
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。