use anyhow::{bail, Context, Result};
use std::process::Command;
const EMIT_SCRIPT: &str = r#"
const sourcePath = process.env.PUSHKIN_SOURCE;
const exportName = process.env.PUSHKIN_EXPORT;
const epoch = process.env.PUSHKIN_EPOCH;
const module = await import(sourcePath);
const schema = module[exportName];
if (schema === undefined) {
console.error(`pushkin: export '${exportName}' not found in ${sourcePath}`);
process.exit(3);
}
const { z } = await import("zod");
const jsonSchema = z.toJSONSchema(schema, { target: "draft-2020-12", io: "input" });
const withHeader = {
$schema: "https://json-schema.org/draft/2020-12/schema",
$comment: `GENERATED by pushkin compile from ${sourcePath.split("/").pop()} — do not hand-edit. pushkin-epoch: ${epoch}`,
...jsonSchema,
};
console.log(JSON.stringify(withHeader, null, 2));
"#;
pub fn emit_schema_from_zod(source: &str, export_name: &str, epoch: u32) -> Result<String> {
let absolute = std::fs::canonicalize(source)
.with_context(|| format!("contract source '{source}' not found"))?;
let output = Command::new("bun")
.arg("-e")
.arg(EMIT_SCRIPT)
.env("PUSHKIN_SOURCE", &absolute)
.env("PUSHKIN_EXPORT", export_name)
.env("PUSHKIN_EPOCH", epoch.to_string())
.output()
.context("cannot run 'bun' — the JS runtime is required for Zod authoring (spec §4.2)")?;
if !output.status.success() {
bail!(
"Zod authoring emission failed for '{source}' (export {export_name}): {}",
String::from_utf8_lossy(&output.stderr).trim()
);
}
let mut schema = String::from_utf8(output.stdout).context("emitted schema is not UTF-8")?;
if !schema.ends_with('\n') {
schema.push('\n');
}
Ok(schema)
}
pub fn export_name_for(contract: &str) -> String {
let mut pascal = String::new();
for part in contract.split(['-', '_']) {
let mut chars = part.chars();
if let Some(first) = chars.next() {
pascal.extend(first.to_uppercase());
pascal.push_str(chars.as_str());
}
}
format!("{pascal}CreateSchema")
}