Skip to content

Commit 125e6ed

Browse files
CyberClarenceclaude
andcommitted
Release v2.1.4 - React web panel + proper build pipeline
- Rebuild web UI using React (compiled with Bun) - Add build:web script to compile React app and embed in binary - Update build pipeline (local + GitHub Actions) to build web panel first - Clean up web enable output to only show web panel URL - Add build:linux and build:all npm scripts - Remove binary from git (now in .gitignore) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <[email protected]>
1 parent e53a262 commit 125e6ed

File tree

11 files changed

+1261
-762
lines changed

11 files changed

+1261
-762
lines changed

.github/workflows/release.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,9 @@ jobs:
3636
- name: Install dependencies
3737
run: bun install
3838

39+
- name: Build web panel
40+
run: bun run scripts/build-web.ts
41+
3942
- name: Build binary
4043
run: |
4144
mkdir -p dist

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ bun.lock
44

55
# Build output
66
dist/
7+
pgforge
8+
pgforge-*
79
easy-db
810
easy-db-*
911

package.json

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,10 @@
88
},
99
"scripts": {
1010
"dev": "bun run src/cli.ts",
11-
"build": "bun build --compile src/cli.ts --outfile pgforge",
11+
"build:web": "bun run scripts/build-web.ts",
12+
"build": "bun run build:web && bun build --compile src/cli.ts --outfile pgforge",
13+
"build:linux": "bun run build:web && bun build --compile --target=bun-linux-x64 src/cli.ts --outfile pgforge-linux",
14+
"build:all": "bun run build:web && bun build --compile src/cli.ts --outfile pgforge && bun build --compile --target=bun-linux-x64 src/cli.ts --outfile pgforge-linux",
1215
"build:release": "bun run scripts/build.ts",
1316
"test": "bun test test/*.test.ts",
1417
"test:e2e": "bun test test/e2e",
@@ -26,6 +29,10 @@
2629
"postgres": "^3.4.7"
2730
},
2831
"devDependencies": {
29-
"@types/bun": "latest"
32+
"@types/bun": "latest",
33+
"@types/react": "^19.2.7",
34+
"@types/react-dom": "^19.2.3",
35+
"react": "^19.2.3",
36+
"react-dom": "^19.2.3"
3037
}
3138
}

pgforge

-60.5 MB
Binary file not shown.

scripts/build-web.ts

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
#!/usr/bin/env bun
2+
/**
3+
* Build script for the web panel
4+
* Compiles React app and embeds it into panel.ts
5+
*/
6+
7+
import { join } from "path";
8+
9+
const ROOT = join(import.meta.dir, "..");
10+
const WEB_APP_DIR = join(ROOT, "src/web/app");
11+
const PANEL_TS = join(ROOT, "src/web/panel.ts");
12+
13+
async function buildWebPanel() {
14+
console.log("Building web panel...");
15+
16+
// Build the React app with Bun
17+
const result = await Bun.build({
18+
entrypoints: [join(WEB_APP_DIR, "App.tsx")],
19+
minify: true,
20+
target: "browser",
21+
});
22+
23+
if (!result.success) {
24+
console.error("Build failed:");
25+
for (const log of result.logs) {
26+
console.error(log);
27+
}
28+
process.exit(1);
29+
}
30+
31+
// Get the bundled JavaScript
32+
const jsBundle = await result.outputs[0].text();
33+
34+
// Read the HTML template
35+
const htmlTemplate = await Bun.file(join(WEB_APP_DIR, "index.html")).text();
36+
37+
// Replace the script tag with the inline bundle
38+
const finalHtml = htmlTemplate.replace(
39+
/<script type="module" src="\.\/App\.tsx"><\/script>/,
40+
`<script>${jsBundle}</script>`
41+
);
42+
43+
// Escape backticks and dollar signs for template literal
44+
const escapedHtml = finalHtml
45+
.replace(/\\/g, "\\\\")
46+
.replace(/`/g, "\\`")
47+
.replace(/\$\{/g, "\\${");
48+
49+
// Write the panel.ts file
50+
const panelTs = `// AUTO-GENERATED - DO NOT EDIT
51+
// Run 'bun run build:web' to regenerate
52+
export const PANEL_HTML = \`${escapedHtml}\`;
53+
`;
54+
55+
await Bun.write(PANEL_TS, panelTs);
56+
console.log("Web panel built successfully!");
57+
}
58+
59+
buildWebPanel();

scripts/build.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,16 @@ const TARGETS = [
1515
async function build() {
1616
console.log(`\n🔨 Building PgForge v${VERSION}\n`);
1717

18+
// Build web panel first
19+
console.log(`🌐 Building web panel...`);
20+
const webResult = await Bun.$`bun run scripts/build-web.ts`.quiet().nothrow();
21+
if (webResult.exitCode !== 0) {
22+
console.error(`❌ Web panel build failed`);
23+
console.error(webResult.stderr.toString());
24+
process.exit(1);
25+
}
26+
console.log(`✅ Web panel built`);
27+
1828
// Create dist directory
1929
await Bun.$`mkdir -p dist`.quiet();
2030

src/commands/web.ts

Lines changed: 9 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -141,8 +141,6 @@ export async function webStatus(): Promise<void> {
141141

142142
async function startWebServer(port: number, hostname: string, displayHost: string, generatedPassword: string | null): Promise<void> {
143143
const config = await getWebConfig();
144-
const state = await getState();
145-
const databases = Object.values(state.databases);
146144

147145
// Write PID file so we can stop the server later
148146
await Bun.write(getWebPidFile(), String(process.pid));
@@ -690,40 +688,19 @@ async function startWebServer(port: number, hostname: string, displayHost: strin
690688
},
691689
});
692690

693-
// Display the styled output with sections using the new design
694-
ui.printSectionBox("Development Tools", [
695-
{ label: "Studio", value: `http://${displayHost}:${port}`, color: "highlight" },
696-
{ label: "Mailpit", value: `http://${displayHost}:54324`, color: "muted" },
697-
{ label: "MCP", value: `http://${displayHost}:54321/mcp`, color: "muted" },
691+
// Display the web panel URL
692+
console.log();
693+
ui.printSectionBox("Web Panel", [
694+
{ label: "URL", value: `http://${displayHost}:${port}`, color: "highlight" },
698695
]);
699696

700-
ui.printSectionBox("APIs", [
701-
{ label: "Project URL", value: `http://${displayHost}:54321`, color: "highlight" },
702-
{ label: "REST", value: `http://${displayHost}:54321/rest/v1`, color: "highlight" },
703-
{ label: "GraphQL", value: `http://${displayHost}:54321/graphql/v1`, color: "highlight" },
704-
{ label: "Edge Functions", value: `http://${displayHost}:54321/functions/v1`, color: "highlight" },
705-
], "🌐");
706-
707-
// Database section
708-
if (databases.length > 0) {
709-
const firstDb = databases[0];
710-
const connectionUrl = `postgresql://${firstDb.username}:${firstDb.password}@${displayHost}:${firstDb.port}/${firstDb.database}`;
711-
ui.printSectionBox("Database", [
712-
{ label: "URL", value: connectionUrl, color: "highlight" },
713-
], "💾");
714-
} else {
715-
ui.printSectionBox("Database", [
716-
{ label: "Status", value: "No databases created yet", color: "muted" },
717-
], "💾");
697+
if (generatedPassword) {
698+
ui.printSectionBox("Access", [
699+
{ label: "Password", value: generatedPassword, color: "warning" },
700+
], "🔑");
718701
}
719702

720-
// Authentication keys
721-
ui.printSectionBox("Authentication Keys", [
722-
{ label: "Publishable", value: "sb_publishable", color: "success" },
723-
{ label: "Secret", value: generatedPassword || "sb_secret", color: "warning" },
724-
], "🔑");
725-
726703
console.log();
727-
console.log(ui.brand.muted("Press Ctrl+C to stop • Ctrl+D to detach"));
704+
console.log(ui.brand.muted("Press Ctrl+C to stop"));
728705
console.log();
729706
}

src/lib/constants.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { join } from "path";
55
// VERSION
66
// =============================================================================
77

8-
export const VERSION = "2.1.3";
8+
export const VERSION = "2.1.4";
99
export const STATE_VERSION = 1; // Increment when state schema changes
1010

1111
// =============================================================================

0 commit comments

Comments
 (0)