Files
moltbot/scripts/check-pairing-account-scope.mjs
2026-03-02 14:36:41 +00:00

101 lines
2.8 KiB
JavaScript

#!/usr/bin/env node
import ts from "typescript";
import { createPairingGuardContext } from "./lib/pairing-guard-context.mjs";
import {
collectFileViolations,
getPropertyNameText,
runAsScript,
toLine,
} from "./lib/ts-guard-utils.mjs";
const { repoRoot, sourceRoots } = createPairingGuardContext(import.meta.url);
function isUndefinedLikeExpression(node) {
if (ts.isIdentifier(node) && node.text === "undefined") {
return true;
}
return node.kind === ts.SyntaxKind.NullKeyword;
}
function hasRequiredAccountIdProperty(node) {
if (!ts.isObjectLiteralExpression(node)) {
return false;
}
for (const property of node.properties) {
if (ts.isShorthandPropertyAssignment(property) && property.name.text === "accountId") {
return true;
}
if (!ts.isPropertyAssignment(property)) {
continue;
}
if (getPropertyNameText(property.name) !== "accountId") {
continue;
}
if (isUndefinedLikeExpression(property.initializer)) {
return false;
}
return true;
}
return false;
}
function findViolations(content, filePath) {
const sourceFile = ts.createSourceFile(filePath, content, ts.ScriptTarget.Latest, true);
const violations = [];
const visit = (node) => {
if (ts.isCallExpression(node) && ts.isIdentifier(node.expression)) {
const callName = node.expression.text;
if (callName === "readChannelAllowFromStore") {
if (node.arguments.length < 3 || isUndefinedLikeExpression(node.arguments[2])) {
violations.push({
line: toLine(sourceFile, node),
reason: "readChannelAllowFromStore call must pass explicit accountId as 3rd arg",
});
}
} else if (
callName === "readLegacyChannelAllowFromStore" ||
callName === "readLegacyChannelAllowFromStoreSync"
) {
violations.push({
line: toLine(sourceFile, node),
reason: `${callName} is legacy-only; use account-scoped readChannelAllowFromStore* APIs`,
});
} else if (callName === "upsertChannelPairingRequest") {
const firstArg = node.arguments[0];
if (!firstArg || !hasRequiredAccountIdProperty(firstArg)) {
violations.push({
line: toLine(sourceFile, node),
reason: "upsertChannelPairingRequest call must include accountId in params",
});
}
}
}
ts.forEachChild(node, visit);
};
visit(sourceFile);
return violations;
}
async function main() {
const violations = await collectFileViolations({
sourceRoots,
repoRoot,
findViolations,
});
if (violations.length === 0) {
return;
}
console.error("Found unscoped pairing-store calls:");
for (const violation of violations) {
console.error(`- ${violation.path}:${violation.line} (${violation.reason})`);
}
process.exit(1);
}
runAsScript(import.meta.url, main);