| #!/usr/bin/env node |
|
|
| import ts from "typescript"; |
| import { runCallsiteGuard } from "./lib/callsite-guard.mjs"; |
| import { runAsScript, toLine, unwrapExpression } from "./lib/ts-guard-utils.mjs"; |
|
|
| const sourceRoots = [ |
| "src/channels", |
| "src/infra/outbound", |
| "src/line", |
| "src/media-understanding", |
| "extensions", |
| ]; |
| const allowedRelativePaths = new Set(["extensions/feishu/src/dedup.ts"]); |
|
|
| function collectOsTmpdirImports(sourceFile) { |
| const osModuleSpecifiers = new Set(["node:os", "os"]); |
| const osNamespaceOrDefault = new Set(); |
| const namedTmpdir = new Set(); |
| for (const statement of sourceFile.statements) { |
| if (!ts.isImportDeclaration(statement)) { |
| continue; |
| } |
| if (!statement.importClause || !ts.isStringLiteral(statement.moduleSpecifier)) { |
| continue; |
| } |
| if (!osModuleSpecifiers.has(statement.moduleSpecifier.text)) { |
| continue; |
| } |
| const clause = statement.importClause; |
| if (clause.name) { |
| osNamespaceOrDefault.add(clause.name.text); |
| } |
| if (!clause.namedBindings) { |
| continue; |
| } |
| if (ts.isNamespaceImport(clause.namedBindings)) { |
| osNamespaceOrDefault.add(clause.namedBindings.name.text); |
| continue; |
| } |
| for (const element of clause.namedBindings.elements) { |
| if ((element.propertyName?.text ?? element.name.text) === "tmpdir") { |
| namedTmpdir.add(element.name.text); |
| } |
| } |
| } |
| return { osNamespaceOrDefault, namedTmpdir }; |
| } |
|
|
| export function findMessagingTmpdirCallLines(content, fileName = "source.ts") { |
| const sourceFile = ts.createSourceFile(fileName, content, ts.ScriptTarget.Latest, true); |
| const { osNamespaceOrDefault, namedTmpdir } = collectOsTmpdirImports(sourceFile); |
| const lines = []; |
|
|
| const visit = (node) => { |
| if (ts.isCallExpression(node)) { |
| const callee = unwrapExpression(node.expression); |
| if ( |
| ts.isPropertyAccessExpression(callee) && |
| callee.name.text === "tmpdir" && |
| ts.isIdentifier(callee.expression) && |
| osNamespaceOrDefault.has(callee.expression.text) |
| ) { |
| lines.push(toLine(sourceFile, callee)); |
| } else if (ts.isIdentifier(callee) && namedTmpdir.has(callee.text)) { |
| lines.push(toLine(sourceFile, callee)); |
| } |
| } |
| ts.forEachChild(node, visit); |
| }; |
|
|
| visit(sourceFile); |
| return lines; |
| } |
|
|
| export async function main() { |
| await runCallsiteGuard({ |
| importMetaUrl: import.meta.url, |
| sourceRoots, |
| findCallLines: findMessagingTmpdirCallLines, |
| skipRelativePath: (relativePath) => allowedRelativePaths.has(relativePath), |
| header: "Found os.tmpdir()/tmpdir() usage in messaging/channel runtime sources:", |
| footer: |
| "Use resolvePreferredOpenClawTmpDir() or plugin-sdk temp helpers instead of host tmp defaults.", |
| sortViolations: false, |
| }); |
| } |
|
|
| runAsScript(import.meta.url, main); |
|
|