Files
openclaw/scripts/check-pairing-account-scope.mts
Peter Steinberger c70aee247e refactor(scripts): migrate JavaScript tools to TypeScript (#121005)
* refactor(scripts): migrate JavaScript tools to TypeScript

* fix(ci): keep changed-scope preflight zero-install

* fix(ci): preserve zero-install script owners

* fix(ci): complete script migration follow-through

* fix(release): keep stable closeout zero-install

* fix(scripts): preserve standalone execution boundaries

* fix(scripts): repair standalone loader boundaries

* fix(scripts): normalize gateway observation ids

* fix(scripts): keep Docker packager standalone

* test(scripts): preserve rebase cleanup helpers

* test(sessions): use tracked temp directory
2026-08-09 07:21:35 -07:00

95 lines
2.7 KiB
TypeScript

#!/usr/bin/env node
// Checks pairing config logic for account-scoped allowlist handling.
import ts from "typescript";
import { createPairingGuardContext } from "./lib/pairing-guard-context.mts";
import {
collectFileViolations,
getPropertyNameText,
runAsScript,
toLine,
} from "./lib/ts-guard-utils.mts";
const { repoRoot, sourceRoots } = createPairingGuardContext(import.meta.url);
function isUndefinedLikeExpression(node: ts.Node) {
if (ts.isIdentifier(node) && node.text === "undefined") {
return true;
}
return node.kind === ts.SyntaxKind.NullKeyword;
}
function hasRequiredAccountIdProperty(node: ts.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: string, filePath: string) {
const sourceFile = ts.createSourceFile(filePath, content, ts.ScriptTarget.Latest, true);
const violations: { line: number; reason: string }[] = [];
const visit = (node: ts.Node): void => {
if (ts.isCallExpression(node) && ts.isIdentifier(node.expression)) {
const callName = node.expression.text;
if (callName === "readChannelAllowFromStore") {
const accountIdArgument = node.arguments[2];
if (!accountIdArgument || isUndefinedLikeExpression(accountIdArgument)) {
violations.push({
line: toLine(sourceFile, node),
reason: "readChannelAllowFromStore call must pass explicit accountId as 3rd arg",
});
}
} 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);