Skip to content

Commit 506bd26

Browse files
committed
Initial commit
1 parent 0f64d70 commit 506bd26

32 files changed

+4184
-901
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,3 +39,5 @@ yarn-error.log*
3939
# typescript
4040
*.tsbuildinfo
4141
next-env.d.ts
42+
43+
dist/

build.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { $ } from "bun";
2+
3+
// Build the CLI binary
4+
const result = await Bun.build({
5+
entrypoints: ["./bin/prdash.ts"],
6+
outdir: "./dist",
7+
target: "bun",
8+
minify: true,
9+
sourcemap: "none",
10+
});
11+
12+
if (!result.success) {
13+
console.error("Build failed:");
14+
for (const log of result.logs) {
15+
console.error(log);
16+
}
17+
process.exit(1);
18+
}
19+
20+
// Make the binary executable
21+
await $`chmod +x ./dist/prdash.js`;
22+
23+
// Rename to just 'prdash' for cleaner bin usage
24+
await $`mv ./dist/prdash.js ./dist/prdash`;
25+
26+
console.log("✅ Build complete: ./dist/prdash");
27+

bun.lock

Lines changed: 95 additions & 763 deletions
Large diffs are not rendered by default.

bunfig.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
[serve.static]
2+
plugins = ["bun-plugin-tailwind"]
3+

components.json

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
{
2+
"$schema": "https://ui.shadcn.com/schema.json",
3+
"style": "new-york",
4+
"rsc": false,
5+
"tsx": true,
6+
"tailwind": {
7+
"config": "",
8+
"css": "src/browser/index.css",
9+
"baseColor": "neutral",
10+
"cssVariables": true,
11+
"prefix": ""
12+
},
13+
"iconLibrary": "lucide",
14+
"aliases": {
15+
"components": "@/browser/components",
16+
"utils": "@/browser/cn",
17+
"ui": "@/browser/ui",
18+
"lib": "@/browser",
19+
"hooks": "@/browser/hooks"
20+
},
21+
"registries": {}
22+
}

package.json

Lines changed: 36 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,49 @@
11
{
22
"name": "prdash",
33
"version": "0.1.0",
4-
"private": true,
4+
"description": "A fast, local PR review dashboard",
5+
"type": "module",
6+
"bin": {
7+
"prdash": "./dist/prdash"
8+
},
9+
"files": [
10+
"dist"
11+
],
512
"scripts": {
6-
"dev": "next dev",
7-
"build": "next build",
8-
"start": "next start",
9-
"lint": "eslint"
13+
"dev": "concurrently \"bun run build:browser --watch\" \"bun run dev:node\"",
14+
"dev:node": "bun run src/node/main.ts",
15+
"build:browser": "bun run ./scripts/build-browser.ts"
1016
},
17+
"keywords": [
18+
"github",
19+
"pull-request",
20+
"code-review",
21+
"diff",
22+
"cli"
23+
],
24+
"license": "MIT",
1125
"dependencies": {
12-
"next": "16.0.6",
13-
"react": "19.2.0",
14-
"react-dom": "19.2.0"
26+
"@radix-ui/react-collapsible": "^1.1.12",
27+
"@radix-ui/react-slot": "^1.2.4",
28+
"clsx": "^2.1.1",
29+
"diff": "^8.0.2",
30+
"gitdiff-parser": "^0.3.1",
31+
"lucide-react": "^0.555.0",
32+
"react-router-dom": "^7.9.6",
33+
"refractor": "^5.0.0",
34+
"tailwind-merge": "^3.4.0",
35+
"tw-animate-css": "^1.4.0"
1536
},
1637
"devDependencies": {
17-
"@tailwindcss/postcss": "^4",
18-
"@types/node": "^20",
38+
"@hono/node-server": "^1.19.6",
39+
"@types/bun": "latest",
1940
"@types/react": "^19",
2041
"@types/react-dom": "^19",
21-
"eslint": "^9",
22-
"eslint-config-next": "16.0.6",
42+
"bun-plugin-tailwind": "latest",
43+
"concurrently": "^9.2.1",
44+
"hono": "^4.10.7",
45+
"react": "19.2.0",
46+
"react-dom": "19.2.0",
2347
"tailwindcss": "^4",
2448
"typescript": "^5"
2549
}

scripts/build-browser.ts

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import tailwind from "bun-plugin-tailwind";
2+
import { watch } from "fs";
3+
import { resolve } from "path";
4+
5+
const isWatch = process.argv.includes("--watch");
6+
7+
async function build() {
8+
const result = await Bun.build({
9+
entrypoints: ["./src/browser/index.html"],
10+
outdir: "./dist/browser",
11+
plugins: [tailwind],
12+
});
13+
14+
if (!result.success) {
15+
console.error("Build failed:");
16+
for (const log of result.logs) {
17+
console.error(log);
18+
}
19+
return false;
20+
}
21+
22+
console.log(`Bundled ${result.outputs.length} files`);
23+
for (const output of result.outputs) {
24+
console.log(` ${output.path}`);
25+
}
26+
return true;
27+
}
28+
29+
await build();
30+
31+
if (isWatch) {
32+
console.log("\nWatching for changes...");
33+
const srcDir = resolve(import.meta.dir, "..", "src", "browser");
34+
35+
let debounce: Timer | null = null;
36+
watch(srcDir, { recursive: true }, (_event, filename) => {
37+
if (debounce) clearTimeout(debounce);
38+
debounce = setTimeout(async () => {
39+
console.log(`\nFile changed: ${filename}`);
40+
await build();
41+
}, 100);
42+
});
43+
}
44+

src/api/api.ts

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
import { Hono } from "hono";
2+
import {
3+
getPullRequest,
4+
getPullRequestFiles,
5+
getPullRequestComments,
6+
createReviewComment,
7+
replyToComment,
8+
} from "./github";
9+
import { parseDiffWithHighlighting } from "./diff";
10+
11+
const api = new Hono().basePath("/api");
12+
13+
// Get PR details
14+
api.get("/pr/:owner/:repo/:number", async (c) => {
15+
const { owner, repo, number } = c.req.param();
16+
try {
17+
const pr = await getPullRequest(owner, repo, parseInt(number, 10));
18+
return c.json(pr);
19+
} catch (error) {
20+
return c.json(
21+
{ error: error instanceof Error ? error.message : "Failed to fetch PR" },
22+
500
23+
);
24+
}
25+
});
26+
27+
// Get PR files
28+
api.get("/pr/:owner/:repo/:number/files", async (c) => {
29+
const { owner, repo, number } = c.req.param();
30+
try {
31+
const files = await getPullRequestFiles(owner, repo, parseInt(number, 10));
32+
return c.json(files);
33+
} catch (error) {
34+
return c.json(
35+
{ error: error instanceof Error ? error.message : "Failed to fetch files" },
36+
500
37+
);
38+
}
39+
});
40+
41+
// Get PR comments
42+
api.get("/pr/:owner/:repo/:number/comments", async (c) => {
43+
const { owner, repo, number } = c.req.param();
44+
try {
45+
const comments = await getPullRequestComments(
46+
owner,
47+
repo,
48+
parseInt(number, 10)
49+
);
50+
return c.json(comments);
51+
} catch (error) {
52+
return c.json(
53+
{ error: error instanceof Error ? error.message : "Failed to fetch comments" },
54+
500
55+
);
56+
}
57+
});
58+
59+
// Create PR comment
60+
api.post("/pr/:owner/:repo/:number/comments", async (c) => {
61+
const { owner, repo, number } = c.req.param();
62+
const body = await c.req.json();
63+
64+
try {
65+
let comment;
66+
67+
if (body.reply_to_id) {
68+
comment = await replyToComment(
69+
owner,
70+
repo,
71+
parseInt(number, 10),
72+
body.reply_to_id,
73+
body.body
74+
);
75+
} else {
76+
comment = await createReviewComment(
77+
owner,
78+
repo,
79+
parseInt(number, 10),
80+
body.body,
81+
body.commit_id,
82+
body.path,
83+
body.line,
84+
body.side || "RIGHT"
85+
);
86+
}
87+
88+
return c.json(comment);
89+
} catch (error) {
90+
return c.json(
91+
{ error: error instanceof Error ? error.message : "Failed to create comment" },
92+
500
93+
);
94+
}
95+
});
96+
97+
// Parse and highlight diff (server-side with caching)
98+
api.post("/parse-diff", async (c) => {
99+
const { patch, filename, previousFilename, sha } = await c.req.json();
100+
101+
if (!patch || !filename) {
102+
return c.json({ error: "Missing patch or filename" }, 400);
103+
}
104+
105+
try {
106+
// Use SHA as cache key for immutable caching
107+
const parsed = parseDiffWithHighlighting(patch, filename, previousFilename, sha);
108+
return c.json(parsed);
109+
} catch (error) {
110+
return c.json(
111+
{ error: error instanceof Error ? error.message : "Failed to parse diff" },
112+
500
113+
);
114+
}
115+
});
116+
117+
export default api;

src/api/client.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import { hc } from "hono/client";
2+
import app from "./api";
3+
4+
export const client = hc<typeof app>(window.location.origin);

0 commit comments

Comments
 (0)