import { execFile } from "node:child_process"; import { promisify } from "node:util"; import { mkdirSync } from "node:fs"; import path from "node:path"; import crypto from "node:crypto"; import type { NextRequest } from "next/server"; import { NextResponse } from "next/server"; const execFileAsync = promisify(execFile); const SESSION_COOKIE = "cardcli_demo_session"; const DEMO_HOME_ROOT = process.env.CARDCLI_DEMO_HOME_ROOT ?? "/tmp/cardcli-demo"; const SHARED_DEMO_SESSION = (process.env.CARDCLI_DEMO_SHARED_SESSION ?? "true") === "true"; function cardCliBinPath() { return ( process.env.CARDCLI_BIN ?? path.resolve(process.cwd(), "..", "target", "release", process.platform === "win32" ? "card-cli.exe" : "card-cli") ); } function sessionIdFromRequest(request: NextRequest) { if (SHARED_DEMO_SESSION) { return "shared-demo"; } return request.cookies.get(SESSION_COOKIE)?.value ?? crypto.randomUUID(); } function cardCliHome(sessionId: string) { const dir = path.join(DEMO_HOME_ROOT, "sessions", sessionId); mkdirSync(dir, { recursive: true }); return dir; } function cardCliEnv(sessionId: string): NodeJS.ProcessEnv { return { ...process.env, CARDCLI_HOME: cardCliHome(sessionId), CARDCLI_SOLANA_PRIVATE_KEY_BASE58: process.env.CARDCLI_DEMO_SOLANA_PRIVATE_KEY ?? process.env.CARDCLI_SOLANA_PRIVATE_KEY_BASE58 ?? "", CARDCLI_FEE_PROGRAM_ID: process.env.CARDCLI_DEMO_FEE_PROGRAM_ID ?? process.env.CARDCLI_FEE_PROGRAM_ID ?? "EpB6hUZUf1vvvTVAYvEN57pjUWfYswaAuKGGQDHP5iH", CARDCLI_ONCHAIN_FEE_COLLECTION_ENABLED: process.env.CARDCLI_ONCHAIN_FEE_COLLECTION_ENABLED ?? "true", CARDCLI_FEE_FIXED_CENTS: process.env.CARDCLI_FEE_FIXED_CENTS ?? "10", CARDCLI_FEE_VARIABLE_BPS: process.env.CARDCLI_FEE_VARIABLE_BPS ?? "20", CARDCLI_RPC_URL: process.env.CARDCLI_RPC_URL ?? "https://api.devnet.solana.com", CARDCLI_COINBASE_BASE_URL: process.env.CARDCLI_COINBASE_BASE_URL ?? "https://api.coinbase.com", }; } export async function runCardCli( request: NextRequest, args: string[], ): Promise<{ sessionId: string; payload: unknown; stderr?: string }> { const sessionId = sessionIdFromRequest(request); const env = cardCliEnv(sessionId); const bin = cardCliBinPath(); try { const { stdout, stderr } = await execFileAsync(bin, [...args, "--format", "json"], { env, maxBuffer: 10 * 1024 * 1024, }); return { sessionId, payload: JSON.parse(stdout), stderr, }; } catch (error) { const err = error as { stdout?: string; stderr?: string; message?: string }; const raw = err.stderr || err.stdout || JSON.stringify({ ok: false, error: { message: err.message } }); try { return { sessionId, payload: JSON.parse(raw), stderr: err.stderr, }; } catch { return { sessionId, payload: { ok: false, error: { code: "WEB_EXEC_ERROR", message: err.message ?? "Failed to run card-cli", raw, }, }, stderr: err.stderr, }; } } } export function jsonWithSession(sessionId: string, payload: unknown, init?: ResponseInit) { const response = NextResponse.json(payload, init); if (!SHARED_DEMO_SESSION) { response.cookies.set(SESSION_COOKIE, sessionId, { httpOnly: true, sameSite: "lax", path: "/", maxAge: 60 * 60 * 24, }); } return response; }