File size: 4,914 Bytes
45afc75
0ecb196
45afc75
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
af956d0
 
 
 
 
 
45afc75
 
 
 
 
 
af956d0
45afc75
af956d0
 
45afc75
 
 
 
 
 
 
 
 
 
 
af956d0
 
 
 
 
45afc75
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
bd99930
 
 
 
45afc75
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
#!/usr/bin/env node
// DriveCore Branch Debug CLI
//
// Usage (from your repo root, in the VS Code terminal):
//   node ./cli/eventdash-debug.mjs "API throws 500 on /checkout for Amex cards"
//   node ./cli/eventdash-debug.mjs --base origin/main "describe the failure here"
//   node ./cli/eventdash-debug.mjs --editor cursor "..."
//
// Flags:
//   --base <ref>     Diff base. Default: auto-detects merge-base with origin/main, falls back to HEAD~1.
//   --editor <id>    vscode | cursor. Default: vscode.
//   --endpoint <url> Override API endpoint.
//
// Reads LOVABLE_ENDPOINT from env if --endpoint is not passed.

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);
}

// Determine base ref
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 { /* try next */ }
  }
  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`);
});