mirror of
https://github.com/openclaw/clawhub.git
synced 2026-08-14 00:47:57 +00:00
103 lines
3.4 KiB
TypeScript
103 lines
3.4 KiB
TypeScript
import { spawnSync } from "node:child_process";
|
|
import { mkdtempSync, readFileSync, readdirSync } from "node:fs";
|
|
import { tmpdir } from "node:os";
|
|
import { join, relative } from "node:path";
|
|
import ts from "typescript";
|
|
|
|
const root = process.cwd();
|
|
const convexDir = join(root, "convex");
|
|
const FUNCTION_FACTORIES = new Set([
|
|
"query",
|
|
"mutation",
|
|
"action",
|
|
"internalQuery",
|
|
"internalMutation",
|
|
"internalAction",
|
|
]);
|
|
|
|
function walkTsFiles(dir: string): string[] {
|
|
const files: string[] = [];
|
|
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
if (entry.name.startsWith("_generated")) continue;
|
|
const fullPath = join(dir, entry.name);
|
|
if (entry.isDirectory()) {
|
|
files.push(...walkTsFiles(fullPath));
|
|
continue;
|
|
}
|
|
if (!entry.isFile() || !entry.name.endsWith(".ts")) continue;
|
|
files.push(fullPath);
|
|
}
|
|
return files;
|
|
}
|
|
|
|
function getExpectedIdentifiers() {
|
|
const identifiers = new Set<string>();
|
|
for (const filePath of walkTsFiles(convexDir)) {
|
|
const source = readFileSync(filePath, "utf8");
|
|
const modulePath = relative(convexDir, filePath).replace(/\\/g, "/").replace(/\.ts$/, ".js");
|
|
const sourceFile = ts.createSourceFile(filePath, source, ts.ScriptTarget.Latest, true);
|
|
ts.forEachChild(sourceFile, (node) => {
|
|
if (!ts.isVariableStatement(node)) return;
|
|
const isExported = node.modifiers?.some(
|
|
(modifier) => modifier.kind === ts.SyntaxKind.ExportKeyword,
|
|
);
|
|
if (!isExported) return;
|
|
|
|
for (const declaration of node.declarationList.declarations) {
|
|
if (!ts.isIdentifier(declaration.name) || !declaration.initializer) continue;
|
|
if (!ts.isCallExpression(declaration.initializer)) continue;
|
|
const callee = declaration.initializer.expression;
|
|
if (!ts.isIdentifier(callee) || !FUNCTION_FACTORIES.has(callee.text)) continue;
|
|
identifiers.add(`${modulePath}:${declaration.name.text}`);
|
|
}
|
|
});
|
|
}
|
|
return identifiers;
|
|
}
|
|
|
|
function getRemoteIdentifiers(cliArgs: string[]) {
|
|
const tempDir = mkdtempSync(join(tmpdir(), "clawhub-function-spec-"));
|
|
const result = spawnSync("bunx", ["convex", "function-spec", "--file", ...cliArgs], {
|
|
cwd: tempDir,
|
|
encoding: "utf8",
|
|
});
|
|
if (result.status !== 0) {
|
|
process.stderr.write(result.stderr || result.stdout);
|
|
process.exit(result.status ?? 1);
|
|
}
|
|
|
|
const fileName = readdirSync(tempDir)
|
|
.filter((entry) => entry.startsWith("function_spec_") && entry.endsWith(".json"))
|
|
.sort()
|
|
.at(-1);
|
|
if (!fileName) {
|
|
process.stderr.write(result.stderr || result.stdout || "Missing function spec output\n");
|
|
process.exit(1);
|
|
}
|
|
|
|
const parsed = JSON.parse(readFileSync(join(tempDir, fileName), "utf8")) as {
|
|
functions?: Array<{ identifier?: string }>;
|
|
};
|
|
return new Set(
|
|
(parsed.functions ?? []).flatMap((entry) => (entry.identifier ? [entry.identifier] : [])),
|
|
);
|
|
}
|
|
|
|
const cliArgs = process.argv.slice(2);
|
|
const expected = getExpectedIdentifiers();
|
|
const remote = getRemoteIdentifiers(cliArgs);
|
|
|
|
const missing = Array.from(expected)
|
|
.filter((identifier) => !remote.has(identifier))
|
|
.sort();
|
|
|
|
if (missing.length > 0) {
|
|
console.error("Convex contract mismatch. Missing remote identifiers:");
|
|
for (const identifier of missing) {
|
|
console.error(`- ${identifier}`);
|
|
}
|
|
process.exit(1);
|
|
}
|
|
|
|
console.log(`Convex contract ok: ${expected.size} identifiers matched remote deployment.`);
|