

























@@ -0,0 +1,104 @@
1+#!/usr/bin/env node
2+import fs from "node:fs";
3+import path from "node:path";
4+5+const repoRoot = path.resolve(new URL("..", import.meta.url).pathname);
6+7+const CHANNEL_EXTENSION_IDS = new Set([
8+"discord",
9+"imessage",
10+"irc",
11+"line",
12+"matrix",
13+"mattermost",
14+"nextcloud-talk",
15+"signal",
16+"slack",
17+"telegram",
18+"whatsapp",
19+]);
20+21+const HELPER_BASENAME_PATTERNS = [
22+/^action-runtime\.ts$/,
23+/^actions(?:\..*)?\.ts$/,
24+/^active-listener\.ts$/,
25+/^access-control\.ts$/,
26+/^channel\.ts$/,
27+/^client(?:[-.].*)?\.ts$/,
28+/^recipient-resolution\.ts$/,
29+/^rich-menu\.ts$/,
30+/^send(?:[-.].*)?\.ts$/,
31+/^sent-message-cache\.ts$/,
32+/^thread-bindings\.ts$/,
33+];
34+35+const FORBIDDEN_PATTERNS = [/\bloadConfig\s*\(/, /\.config\.loadConfig\s*\(/];
36+37+function* walk(dir) {
38+for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
39+if (entry.name === "node_modules" || entry.name === "dist") {
40+continue;
41+}
42+const absolute = path.join(dir, entry.name);
43+if (entry.isDirectory()) {
44+yield* walk(absolute);
45+continue;
46+}
47+yield absolute;
48+}
49+}
50+51+function isCandidate(relativePath) {
52+const parts = relativePath.split(path.sep);
53+if (parts[0] !== "extensions" || parts[2] !== "src") {
54+return false;
55+}
56+if (!CHANNEL_EXTENSION_IDS.has(parts[1])) {
57+return false;
58+}
59+if (
60+relativePath.endsWith(".test.ts") ||
61+relativePath.endsWith(".test-harness.ts") ||
62+relativePath.endsWith(".d.ts")
63+) {
64+return false;
65+}
66+if (parts.includes("monitor") || parts.includes("cli")) {
67+return false;
68+}
69+if (parts.includes("actions")) {
70+return true;
71+}
72+const basename = path.basename(relativePath);
73+return HELPER_BASENAME_PATTERNS.some((pattern) => pattern.test(basename));
74+}
75+76+function main() {
77+const violations = [];
78+for (const absolute of walk(path.join(repoRoot, "extensions"))) {
79+const relativePath = path.relative(repoRoot, absolute);
80+if (!isCandidate(relativePath)) {
81+continue;
82+}
83+const lines = fs.readFileSync(absolute, "utf8").split(/\r?\n/);
84+lines.forEach((line, index) => {
85+if (FORBIDDEN_PATTERNS.some((pattern) => pattern.test(line))) {
86+violations.push(`${relativePath}:${index + 1}: ${line.trim()}`);
87+}
88+});
89+}
90+if (violations.length === 0) {
91+return;
92+}
93+console.error(
94+[
95+"Runtime channel send/action/client/pairing helpers must not call loadConfig().",
96+"Load and resolve config at the command/gateway/monitor boundary, then pass cfg through.",
97+"",
98+ ...violations,
99+].join("\n"),
100+);
101+process.exitCode = 1;
102+}
103+104+main();
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。