| #!/usr/bin/env node |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| import { execSync } from "node:child_process"; |
| import { resolve } from "node:path"; |
|
|
| const DEFAULT_ENDPOINT = |
| process.env.LOVABLE_ENDPOINT || |
| "https://project--bff39f15-1e2d-4d34-8f4b-7070bac6dbae.lovable.app/api/public/branch-debug"; |
|
|
| const args = process.argv.slice(2); |
| const opts = { |
| base: null, |
| editor: "vscode", |
| endpoint: DEFAULT_ENDPOINT, |
| token: process.env.BRANCH_DEBUG_TOKEN || process.env.LOVABLE_BRANCH_DEBUG_TOKEN || "", |
| }; |
| const positional = []; |
| for (let i = 0; i < args.length; i++) { |
| const a = args[i]; |
| if (a === "--base") opts.base = args[++i]; |
| else if (a === "--editor") opts.editor = args[++i]; |
| else if (a === "--endpoint") opts.endpoint = args[++i]; |
| else if (a === "--token") opts.token = args[++i]; |
| else if (a === "-h" || a === "--help") { |
| console.log("Usage: node cli/eventdash-debug.mjs [--base <ref>] [--editor vscode|cursor] [--endpoint <url>] [--token <token>] \"failure description\""); |
| console.log("\nAuth: pass --token, or set BRANCH_DEBUG_TOKEN env var. Required by the server."); |
| process.exit(0); |
| } else positional.push(a); |
| } |
|
|
| const failureDescription = positional.join(" ").trim(); |
| if (!failureDescription) { |
| console.error("✗ Provide a failure description as an argument."); |
| console.error(' e.g. node cli/eventdash-debug.mjs "Checkout 500s on Amex cards after deploy"'); |
| process.exit(1); |
| } |
|
|
| if (!opts.token) { |
| console.error("✗ Missing auth token. Set BRANCH_DEBUG_TOKEN env var or pass --token <token>."); |
| process.exit(1); |
| } |
|
|
| function sh(cmd) { |
| return execSync(cmd, { encoding: "utf8", maxBuffer: 50 * 1024 * 1024 }).trim(); |
| } |
|
|
| let repoRoot; |
| let branch; |
| try { |
| repoRoot = sh("git rev-parse --show-toplevel"); |
| branch = sh("git rev-parse --abbrev-ref HEAD"); |
| } catch { |
| console.error("✗ Not inside a git repo. Run this from your project root."); |
| process.exit(1); |
| } |
|
|
| |
| let base = opts.base; |
| if (!base) { |
| for (const candidate of ["origin/main", "origin/master", "main", "master"]) { |
| try { |
| base = sh(`git merge-base HEAD ${candidate}`); |
| break; |
| } catch { } |
| } |
| if (!base) base = "HEAD~1"; |
| } |
|
|
| console.log(`▸ Repo: ${repoRoot}`); |
| console.log(`▸ Branch: ${branch}`); |
| console.log(`▸ Base: ${base}`); |
| console.log(`▸ Failure: ${failureDescription}\n`); |
|
|
| let diff; |
| try { |
| diff = sh(`git diff ${base}...HEAD`); |
| } catch (e) { |
| console.error("✗ git diff failed:", e.message); |
| process.exit(1); |
| } |
|
|
| if (!diff) { |
| console.error("✗ Empty diff. Nothing changed between base and HEAD."); |
| process.exit(1); |
| } |
|
|
| console.log(`▸ Sending ${(diff.length / 1024).toFixed(1)} KB diff to ${opts.endpoint} ...\n`); |
|
|
| const res = await fetch(opts.endpoint, { |
| method: "POST", |
| headers: { |
| "Content-Type": "application/json", |
| "Authorization": `Bearer ${opts.token}`, |
| }, |
| body: JSON.stringify({ diff, failureDescription, repoRoot, editor: opts.editor }), |
| }); |
|
|
| if (!res.ok) { |
| const text = await res.text(); |
| console.error(`✗ API error ${res.status}: ${text}`); |
| process.exit(1); |
| } |
|
|
| const data = await res.json(); |
| const { summary, suspects, sanitizationStats } = data; |
|
|
| const C = { dim: "\x1b[2m", reset: "\x1b[0m", bold: "\x1b[1m", high: "\x1b[31m", med: "\x1b[33m", low: "\x1b[36m", link: "\x1b[34m" }; |
| const tag = (c) => ({ high: C.high, medium: C.med, low: C.low }[c] || ""); |
|
|
| console.log(`${C.bold}Summary${C.reset}\n ${summary}\n`); |
| console.log(`${C.dim}IP Shield: ${sanitizationStats.identifiersTokenized} identifiers tokenized · ${sanitizationStats.commentsStripped} comments stripped · ${sanitizationStats.secretsBlocked} secrets blocked${C.reset}\n`); |
|
|
| if (!suspects?.length) { |
| console.log("No suspects returned."); |
| process.exit(0); |
| } |
|
|
| suspects.forEach((s, i) => { |
| const jump = s.jumpUrl || `${opts.editor}://file/${resolve(repoRoot, s.filePath)}:${s.lineStart}`; |
| console.log(`${tag(s.confidence)}${C.bold}[${i + 1}] ${s.confidence.toUpperCase()}${C.reset} ${s.filePath}:${s.lineStart}-${s.lineEnd}${s.functionName ? ` ${C.dim}(${s.functionName})${C.reset}` : ""}`); |
| console.log(` ${C.bold}${s.changeSummary}${C.reset}`); |
| console.log(` ${s.mechanism}`); |
| console.log(` ${C.link}${jump}${C.reset} ${C.dim}← ⌘-click to open in ${opts.editor === "cursor" ? "Cursor" : "VS Code"}${C.reset}\n`); |
| }); |
|
|