Skip to content

Commit 2d31b13

Browse files
committed
refactor: migrate CLI from Node.js to Bun
- Replace Node.js child_process with Bun.spawn/Bun.spawnSync - Replace http/https modules with native fetch() - Replace http.createServer with Bun.serve for OAuth callback - Update shebangs to use #!/usr/bin/env bun - Convert test files from Node test runner to Bun test runner - Update package.json: type: module, scripts use bun - Update tsconfig.json for Bun bundler resolution - Add @types/bun to dev dependencies - Remove compiled dist/ artifacts (Bun runs TS directly) - Simplify pgai wrapper to use TypeScript directly
1 parent 08f1e7d commit 2d31b13

File tree

14 files changed

+1160
-1475
lines changed

14 files changed

+1160
-1475
lines changed

cli/bin/postgres-ai.ts

Lines changed: 321 additions & 300 deletions
Large diffs are not rendered by default.

cli/bun.lock

Lines changed: 256 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

cli/lib/auth-server.ts

Lines changed: 49 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,3 @@
1-
import * as http from "http";
2-
import { URL } from "url";
3-
41
/**
52
* OAuth callback result
63
*/
@@ -13,7 +10,7 @@ export interface CallbackResult {
1310
* Callback server structure
1411
*/
1512
export interface CallbackServer {
16-
server: http.Server;
13+
server: { stop: () => void };
1714
promise: Promise<CallbackResult>;
1815
getPort: () => number;
1916
}
@@ -34,7 +31,7 @@ function escapeHtml(str: string | null): string {
3431
}
3532

3633
/**
37-
* Create and start callback server, returning server object and promise
34+
* Create and start callback server using Bun.serve
3835
* @param port - Port to listen on (0 for random available port)
3936
* @param expectedState - Expected state parameter for CSRF protection
4037
* @param timeoutMs - Timeout in milliseconds
@@ -46,42 +43,42 @@ export function createCallbackServer(
4643
timeoutMs: number = 300000
4744
): CallbackServer {
4845
let resolved = false;
49-
let server: http.Server | null = null;
5046
let actualPort = port;
5147
let resolveCallback: (value: CallbackResult) => void;
5248
let rejectCallback: (reason: Error) => void;
53-
49+
let serverInstance: ReturnType<typeof Bun.serve> | null = null;
50+
5451
const promise = new Promise<CallbackResult>((resolve, reject) => {
5552
resolveCallback = resolve;
5653
rejectCallback = reject;
5754
});
58-
55+
5956
// Timeout handler
6057
const timeout = setTimeout(() => {
6158
if (!resolved) {
6259
resolved = true;
63-
if (server) {
64-
server.close();
60+
if (serverInstance) {
61+
serverInstance.stop();
6562
}
6663
rejectCallback(new Error("Authentication timeout. Please try again."));
6764
}
6865
}, timeoutMs);
69-
70-
// Request handler
71-
const requestHandler = (req: http.IncomingMessage, res: http.ServerResponse): void => {
72-
if (resolved) {
73-
return;
74-
}
7566

76-
// Only handle /callback path
77-
if (!req.url || !req.url.startsWith("/callback")) {
78-
res.writeHead(404, { "Content-Type": "text/plain" });
79-
res.end("Not Found");
80-
return;
81-
}
67+
serverInstance = Bun.serve({
68+
port: port,
69+
hostname: "127.0.0.1",
70+
fetch(req) {
71+
if (resolved) {
72+
return new Response("Already handled", { status: 200 });
73+
}
74+
75+
const url = new URL(req.url);
76+
77+
// Only handle /callback path
78+
if (!url.pathname.startsWith("/callback")) {
79+
return new Response("Not Found", { status: 404 });
80+
}
8281

83-
try {
84-
const url = new URL(req.url, `http://localhost:${actualPort}`);
8582
const code = url.searchParams.get("code");
8683
const state = url.searchParams.get("state");
8784
const error = url.searchParams.get("error");
@@ -91,9 +88,11 @@ export function createCallbackServer(
9188
if (error) {
9289
resolved = true;
9390
clearTimeout(timeout);
94-
95-
res.writeHead(400, { "Content-Type": "text/html" });
96-
res.end(`
91+
92+
setTimeout(() => serverInstance?.stop(), 100);
93+
rejectCallback(new Error(`OAuth error: ${error}${errorDescription ? ` - ${errorDescription}` : ""}`));
94+
95+
return new Response(`
9796
<!DOCTYPE html>
9897
<html>
9998
<head>
@@ -114,19 +113,12 @@ export function createCallbackServer(
114113
</div>
115114
</body>
116115
</html>
117-
`);
118-
119-
if (server) {
120-
server.close();
121-
}
122-
rejectCallback(new Error(`OAuth error: ${error}${errorDescription ? ` - ${errorDescription}` : ""}`));
123-
return;
116+
`, { status: 400, headers: { "Content-Type": "text/html" } });
124117
}
125118

126119
// Validate required parameters
127120
if (!code || !state) {
128-
res.writeHead(400, { "Content-Type": "text/html" });
129-
res.end(`
121+
return new Response(`
130122
<!DOCTYPE html>
131123
<html>
132124
<head>
@@ -145,17 +137,18 @@ export function createCallbackServer(
145137
</div>
146138
</body>
147139
</html>
148-
`);
149-
return;
140+
`, { status: 400, headers: { "Content-Type": "text/html" } });
150141
}
151142

152143
// Validate state (CSRF protection)
153144
if (expectedState && state !== expectedState) {
154145
resolved = true;
155146
clearTimeout(timeout);
156-
157-
res.writeHead(400, { "Content-Type": "text/html" });
158-
res.end(`
147+
148+
setTimeout(() => serverInstance?.stop(), 100);
149+
rejectCallback(new Error("State mismatch (possible CSRF attack)"));
150+
151+
return new Response(`
159152
<!DOCTYPE html>
160153
<html>
161154
<head>
@@ -174,21 +167,17 @@ export function createCallbackServer(
174167
</div>
175168
</body>
176169
</html>
177-
`);
178-
179-
if (server) {
180-
server.close();
181-
}
182-
rejectCallback(new Error("State mismatch (possible CSRF attack)"));
183-
return;
170+
`, { status: 400, headers: { "Content-Type": "text/html" } });
184171
}
185172

186173
// Success!
187174
resolved = true;
188175
clearTimeout(timeout);
189-
190-
res.writeHead(200, { "Content-Type": "text/html" });
191-
res.end(`
176+
177+
setTimeout(() => serverInstance?.stop(), 100);
178+
resolveCallback({ code, state });
179+
180+
return new Response(`
192181
<!DOCTYPE html>
193182
<html>
194183
<head>
@@ -207,61 +196,24 @@ export function createCallbackServer(
207196
</div>
208197
</body>
209198
</html>
210-
`);
211-
212-
if (server) {
213-
server.close();
214-
}
215-
resolveCallback({ code, state });
216-
} catch (err) {
217-
if (!resolved) {
218-
resolved = true;
219-
clearTimeout(timeout);
220-
res.writeHead(500, { "Content-Type": "text/plain" });
221-
res.end("Internal Server Error");
222-
if (server) {
223-
server.close();
224-
}
225-
rejectCallback(err instanceof Error ? err : new Error(String(err)));
226-
}
227-
}
228-
};
229-
230-
// Create server
231-
server = http.createServer(requestHandler);
232-
233-
server.on("error", (err: Error) => {
234-
if (!resolved) {
235-
resolved = true;
236-
clearTimeout(timeout);
237-
rejectCallback(err);
238-
}
199+
`, { status: 200, headers: { "Content-Type": "text/html" } });
200+
},
239201
});
240202

241-
server.listen(port, "127.0.0.1", () => {
242-
const address = server?.address();
243-
if (address && typeof address === "object") {
244-
actualPort = address.port;
245-
}
246-
});
247-
203+
actualPort = serverInstance.port;
204+
248205
return {
249-
server,
206+
server: { stop: () => serverInstance?.stop() },
250207
promise,
251-
getPort: () => {
252-
const address = server?.address();
253-
return address && typeof address === "object" ? address.port : 0;
254-
},
208+
getPort: () => actualPort,
255209
};
256210
}
257211

258212
/**
259213
* Get the actual port the server is listening on
260-
* @param server - HTTP server instance
214+
* @param server - Bun server instance
261215
* @returns Port number
262216
*/
263-
export function getServerPort(server: http.Server): number {
264-
const address = server.address();
265-
return address && typeof address === "object" ? address.port : 0;
217+
export function getServerPort(server: ReturnType<typeof Bun.serve>): number {
218+
return server.port;
266219
}
267-

cli/lib/init.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -159,13 +159,13 @@ export type InitPlan = {
159159
steps: InitStep[];
160160
};
161161

162-
function packageRootDirFromCompiled(): string {
163-
// dist/lib/init.js -> <pkg>/dist/lib ; package root is ../..
164-
return path.resolve(__dirname, "..", "..");
162+
function packageRootDir(): string {
163+
// lib/init.ts -> <pkg>/lib ; package root is ..
164+
return path.resolve(__dirname, "..");
165165
}
166166

167167
function sqlDir(): string {
168-
return path.join(packageRootDirFromCompiled(), "sql");
168+
return path.join(packageRootDir(), "sql");
169169
}
170170

171171
function loadSqlTemplate(filename: string): string {

0 commit comments

Comments
 (0)