@bungres/kit 0.1.1 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +19 -12
- package/dist/cli.js +1511 -636
- package/dist/commands/drop.d.ts.map +1 -1
- package/dist/commands/init.d.ts +2 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/studio.d.ts.map +1 -1
- package/dist/index.js +616 -198
- package/package.json +19 -3
- package/src/cli.ts +0 -162
- package/src/commands/drop.ts +0 -92
- package/src/commands/fresh.ts +0 -17
- package/src/commands/generate.ts +0 -151
- package/src/commands/migrate.ts +0 -84
- package/src/commands/pull.ts +0 -339
- package/src/commands/push.ts +0 -105
- package/src/commands/refresh.ts +0 -37
- package/src/commands/seed.ts +0 -41
- package/src/commands/status.ts +0 -64
- package/src/commands/studio.ts +0 -471
- package/src/commands/tusky.ts +0 -83
- package/src/config.ts +0 -102
- package/src/differ.ts +0 -236
- package/src/ensure-db.ts +0 -32
- package/src/index.ts +0 -21
- package/src/schema-loader.ts +0 -50
- package/src/utils/colors.ts +0 -4
package/src/commands/studio.ts
DELETED
|
@@ -1,471 +0,0 @@
|
|
|
1
|
-
import { createDB } from "@bungres/orm";
|
|
2
|
-
import type { ResolvedConfig } from "../config.js";
|
|
3
|
-
import { loadSchemas, type SchemaEntry } from "../schema-loader.js";
|
|
4
|
-
import * as path from "node:path";
|
|
5
|
-
import { colorize } from "../utils/colors.js";
|
|
6
|
-
|
|
7
|
-
// ---------------------------------------------------------------------------
|
|
8
|
-
// studio — Start a local web interface to browse database data
|
|
9
|
-
// ---------------------------------------------------------------------------
|
|
10
|
-
|
|
11
|
-
export async function runStudio(config: ResolvedConfig): Promise<void> {
|
|
12
|
-
const schemas = await loadSchemas(config.schema);
|
|
13
|
-
|
|
14
|
-
if (schemas.length === 0) {
|
|
15
|
-
console.warn("No table definitions found in schema files.");
|
|
16
|
-
return;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
const schemaObj: Record<string, any> = {};
|
|
20
|
-
for (const s of schemas) {
|
|
21
|
-
schemaObj[s.exportName] = s.table;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
const db = createDB({ url: config.dbUrl, schema: schemaObj });
|
|
25
|
-
|
|
26
|
-
const port = process.env.PORT ? parseInt(process.env.PORT, 10) : 5555;
|
|
27
|
-
|
|
28
|
-
const server = Bun.serve({
|
|
29
|
-
port,
|
|
30
|
-
async fetch(req) {
|
|
31
|
-
const url = new URL(req.url);
|
|
32
|
-
|
|
33
|
-
// API: List tables
|
|
34
|
-
if (req.method === "GET" && url.pathname === "/api/tables") {
|
|
35
|
-
const tables = schemas.map(s => ({
|
|
36
|
-
name: s.config.name,
|
|
37
|
-
exportName: s.exportName,
|
|
38
|
-
columns: Object.entries(s.config.columns).map(([key, col]) => ({
|
|
39
|
-
name: col.name,
|
|
40
|
-
type: col.dataType,
|
|
41
|
-
primaryKey: col.primaryKey
|
|
42
|
-
}))
|
|
43
|
-
}));
|
|
44
|
-
return new Response(JSON.stringify(tables), {
|
|
45
|
-
headers: { "Content-Type": "application/json" }
|
|
46
|
-
});
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
// API: Get table data
|
|
50
|
-
if (req.method === "GET" && url.pathname.startsWith("/api/tables/") && url.pathname.endsWith("/data")) {
|
|
51
|
-
const tableName = url.pathname.split("/")[3];
|
|
52
|
-
const schema = schemas.find(s => s.config.name === tableName);
|
|
53
|
-
|
|
54
|
-
if (!schema) {
|
|
55
|
-
return new Response("Table not found", { status: 404 });
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
try {
|
|
59
|
-
// Fetch max 100 rows for the studio view
|
|
60
|
-
const data = await db.select().from(schema.table).limit(100);
|
|
61
|
-
return new Response(JSON.stringify(data), {
|
|
62
|
-
headers: { "Content-Type": "application/json" }
|
|
63
|
-
});
|
|
64
|
-
} catch (e: any) {
|
|
65
|
-
return new Response(JSON.stringify({ error: e.message }), {
|
|
66
|
-
status: 500,
|
|
67
|
-
headers: { "Content-Type": "application/json" }
|
|
68
|
-
});
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
// API: Open in editor
|
|
73
|
-
if (req.method === "POST" && url.pathname === "/api/editor/open") {
|
|
74
|
-
try {
|
|
75
|
-
const body = (await req.json()) as { tableName: string };
|
|
76
|
-
const tableName = body.tableName;
|
|
77
|
-
const schema = schemas.find(s => s.config.name === tableName);
|
|
78
|
-
|
|
79
|
-
if (schema && schema.filePath) {
|
|
80
|
-
console.log(`Attempting to open ${schema.filePath} in editor...`);
|
|
81
|
-
// Attempt 1: Bun native (relies on $EDITOR or `code` in PATH)
|
|
82
|
-
Bun.openInEditor(schema.filePath, { editor: "vscode" });
|
|
83
|
-
|
|
84
|
-
return new Response(JSON.stringify({
|
|
85
|
-
success: true,
|
|
86
|
-
filePath: schema.filePath
|
|
87
|
-
}), {
|
|
88
|
-
headers: { "Content-Type": "application/json" }
|
|
89
|
-
});
|
|
90
|
-
}
|
|
91
|
-
return new Response("Schema not found", { status: 404 });
|
|
92
|
-
} catch (e) {
|
|
93
|
-
return new Response("Invalid request", { status: 400 });
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
// Frontend: Serve the HTML
|
|
98
|
-
if (req.method === "GET" && url.pathname === "/") {
|
|
99
|
-
return new Response(htmlTemplate, {
|
|
100
|
-
headers: { "Content-Type": "text/html" }
|
|
101
|
-
});
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
return new Response("Not found", { status: 404 });
|
|
105
|
-
}
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
console.log(colorize("=========================================", "cyan"));
|
|
109
|
-
console.log(colorize(`🐘 Bungres Studio is running!`, "cyan"));
|
|
110
|
-
console.log(colorize(` Local: http://localhost:${server.port}`, "green"));
|
|
111
|
-
console.log(colorize("=========================================", "cyan"));
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
const htmlTemplate = `
|
|
115
|
-
<!DOCTYPE html>
|
|
116
|
-
<html lang="en">
|
|
117
|
-
<head>
|
|
118
|
-
<meta charset="UTF-8">
|
|
119
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
120
|
-
<title>Bungres Studio</title>
|
|
121
|
-
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
|
|
122
|
-
<style>
|
|
123
|
-
:root {
|
|
124
|
-
--bg-color: #0d1117;
|
|
125
|
-
--panel-bg: #161b22;
|
|
126
|
-
--border-color: #30363d;
|
|
127
|
-
--text-main: #c9d1d9;
|
|
128
|
-
--text-muted: #8b949e;
|
|
129
|
-
--accent-color: #58a6ff;
|
|
130
|
-
--accent-hover: #1f6feb;
|
|
131
|
-
--header-bg: #161b22;
|
|
132
|
-
--row-hover: #1f2428;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
* { box-sizing: border-box; }
|
|
136
|
-
|
|
137
|
-
body {
|
|
138
|
-
margin: 0;
|
|
139
|
-
padding: 0;
|
|
140
|
-
font-family: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;
|
|
141
|
-
background-color: var(--bg-color);
|
|
142
|
-
color: var(--text-main);
|
|
143
|
-
display: flex;
|
|
144
|
-
height: 100vh;
|
|
145
|
-
overflow: hidden;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
/* Sidebar */
|
|
149
|
-
.sidebar {
|
|
150
|
-
width: 250px;
|
|
151
|
-
background-color: var(--panel-bg);
|
|
152
|
-
border-right: 1px solid var(--border-color);
|
|
153
|
-
display: flex;
|
|
154
|
-
flex-direction: column;
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
.sidebar-header {
|
|
158
|
-
padding: 20px;
|
|
159
|
-
border-bottom: 1px solid var(--border-color);
|
|
160
|
-
display: flex;
|
|
161
|
-
align-items: center;
|
|
162
|
-
gap: 10px;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
.sidebar-header h1 {
|
|
166
|
-
margin: 0;
|
|
167
|
-
font-size: 16px;
|
|
168
|
-
font-weight: 600;
|
|
169
|
-
color: white;
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
.table-list {
|
|
173
|
-
flex: 1;
|
|
174
|
-
overflow-y: auto;
|
|
175
|
-
padding: 10px 0;
|
|
176
|
-
list-style: none;
|
|
177
|
-
margin: 0;
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
.table-item {
|
|
181
|
-
padding: 8px 20px;
|
|
182
|
-
cursor: pointer;
|
|
183
|
-
font-size: 14px;
|
|
184
|
-
color: var(--text-muted);
|
|
185
|
-
transition: all 0.2s ease;
|
|
186
|
-
display: flex;
|
|
187
|
-
align-items: center;
|
|
188
|
-
gap: 8px;
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
.table-item:hover {
|
|
192
|
-
background-color: var(--row-hover);
|
|
193
|
-
color: var(--text-main);
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
.table-item.active {
|
|
197
|
-
background-color: rgba(88, 166, 255, 0.1);
|
|
198
|
-
color: var(--accent-color);
|
|
199
|
-
border-right: 3px solid var(--accent-color);
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
/* Main Content */
|
|
203
|
-
.main {
|
|
204
|
-
flex: 1;
|
|
205
|
-
display: flex;
|
|
206
|
-
flex-direction: column;
|
|
207
|
-
background-color: var(--bg-color);
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
.main-header {
|
|
211
|
-
height: 60px;
|
|
212
|
-
padding: 0 20px;
|
|
213
|
-
border-bottom: 1px solid var(--border-color);
|
|
214
|
-
display: flex;
|
|
215
|
-
align-items: center;
|
|
216
|
-
justify-content: space-between;
|
|
217
|
-
background-color: var(--header-bg);
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
.main-header h2 {
|
|
221
|
-
margin: 0;
|
|
222
|
-
font-size: 16px;
|
|
223
|
-
font-weight: 500;
|
|
224
|
-
color: white;
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
.btn {
|
|
228
|
-
background-color: var(--accent-color);
|
|
229
|
-
color: white;
|
|
230
|
-
border: none;
|
|
231
|
-
padding: 6px 12px;
|
|
232
|
-
border-radius: 6px;
|
|
233
|
-
font-size: 12px;
|
|
234
|
-
font-weight: 500;
|
|
235
|
-
cursor: pointer;
|
|
236
|
-
transition: background-color 0.2s ease;
|
|
237
|
-
display: flex;
|
|
238
|
-
align-items: center;
|
|
239
|
-
gap: 6px;
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
.btn:hover {
|
|
243
|
-
background-color: var(--accent-hover);
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
.btn:disabled {
|
|
247
|
-
opacity: 0.5;
|
|
248
|
-
cursor: not-allowed;
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
.content-area {
|
|
252
|
-
flex: 1;
|
|
253
|
-
overflow: auto;
|
|
254
|
-
padding: 20px;
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
/* Data Table */
|
|
258
|
-
.data-grid-container {
|
|
259
|
-
background-color: var(--panel-bg);
|
|
260
|
-
border: 1px solid var(--border-color);
|
|
261
|
-
border-radius: 8px;
|
|
262
|
-
overflow: hidden;
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
table {
|
|
266
|
-
width: 100%;
|
|
267
|
-
border-collapse: collapse;
|
|
268
|
-
text-align: left;
|
|
269
|
-
font-size: 13px;
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
th {
|
|
273
|
-
background-color: rgba(255,255,255,0.02);
|
|
274
|
-
color: var(--text-muted);
|
|
275
|
-
font-weight: 500;
|
|
276
|
-
padding: 10px 16px;
|
|
277
|
-
border-bottom: 1px solid var(--border-color);
|
|
278
|
-
white-space: nowrap;
|
|
279
|
-
position: sticky;
|
|
280
|
-
top: 0;
|
|
281
|
-
z-index: 10;
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
td {
|
|
285
|
-
padding: 10px 16px;
|
|
286
|
-
border-bottom: 1px solid var(--border-color);
|
|
287
|
-
color: var(--text-main);
|
|
288
|
-
max-width: 300px;
|
|
289
|
-
overflow: hidden;
|
|
290
|
-
text-overflow: ellipsis;
|
|
291
|
-
white-space: nowrap;
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
tr:last-child td {
|
|
295
|
-
border-bottom: none;
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
tr:hover td {
|
|
299
|
-
background-color: var(--row-hover);
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
.type-number { color: #79c0ff; }
|
|
303
|
-
.type-string { color: #a5d6ff; }
|
|
304
|
-
.type-boolean { color: #ff7b72; }
|
|
305
|
-
.type-date { color: #d2a8ff; }
|
|
306
|
-
.type-null { color: var(--text-muted); font-style: italic; }
|
|
307
|
-
|
|
308
|
-
.empty-state {
|
|
309
|
-
display: flex;
|
|
310
|
-
flex-direction: column;
|
|
311
|
-
align-items: center;
|
|
312
|
-
justify-content: center;
|
|
313
|
-
height: 100%;
|
|
314
|
-
color: var(--text-muted);
|
|
315
|
-
}
|
|
316
|
-
</style>
|
|
317
|
-
</head>
|
|
318
|
-
<body>
|
|
319
|
-
|
|
320
|
-
<div class="sidebar">
|
|
321
|
-
<div class="sidebar-header">
|
|
322
|
-
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
323
|
-
<ellipse cx="12" cy="5" rx="9" ry="3"></ellipse>
|
|
324
|
-
<path d="M21 12c0 1.66-4 3-9 3s-9-1.34-9-3"></path>
|
|
325
|
-
<path d="M3 5v14c0 1.66 4 3 9 3s9-1.34 9-3V5"></path>
|
|
326
|
-
</svg>
|
|
327
|
-
<h1>Bungres Studio</h1>
|
|
328
|
-
</div>
|
|
329
|
-
<ul class="table-list" id="table-list">
|
|
330
|
-
<!-- Populated by JS -->
|
|
331
|
-
</ul>
|
|
332
|
-
</div>
|
|
333
|
-
|
|
334
|
-
<div class="main">
|
|
335
|
-
<div class="main-header">
|
|
336
|
-
<h2 id="current-table-name">Select a table</h2>
|
|
337
|
-
<button id="refresh-btn" class="btn" disabled>
|
|
338
|
-
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
339
|
-
<polyline points="23 4 23 10 17 10"></polyline>
|
|
340
|
-
<polyline points="1 20 1 14 7 14"></polyline>
|
|
341
|
-
<path d="M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15"></path>
|
|
342
|
-
</svg>
|
|
343
|
-
Refresh Data
|
|
344
|
-
</button>
|
|
345
|
-
</div>
|
|
346
|
-
<div class="content-area">
|
|
347
|
-
<div id="data-container" class="empty-state">
|
|
348
|
-
<p>Select a table from the sidebar to view data</p>
|
|
349
|
-
</div>
|
|
350
|
-
</div>
|
|
351
|
-
</div>
|
|
352
|
-
|
|
353
|
-
<script>
|
|
354
|
-
let currentTable = null;
|
|
355
|
-
let tablesData = [];
|
|
356
|
-
|
|
357
|
-
// Format values for the data grid
|
|
358
|
-
function formatValue(val) {
|
|
359
|
-
if (val === null || val === undefined) return '<span class="type-null">null</span>';
|
|
360
|
-
if (typeof val === 'number') return \`<span class="type-number">\${val}</span>\`;
|
|
361
|
-
if (typeof val === 'boolean') return \`<span class="type-boolean">\${val}</span>\`;
|
|
362
|
-
if (val instanceof Date || (typeof val === 'string' && val.match(/^\\d{4}-\\d{2}-\\d{2}T/))) {
|
|
363
|
-
return \`<span class="type-date">\${new Date(val).toLocaleString()}</span>\`;
|
|
364
|
-
}
|
|
365
|
-
// Escape HTML to prevent XSS
|
|
366
|
-
const safeStr = String(val)
|
|
367
|
-
.replace(/&/g, "&")
|
|
368
|
-
.replace(/</g, "<")
|
|
369
|
-
.replace(/>/g, ">");
|
|
370
|
-
return \`<span class="type-string">\${safeStr}</span>\`;
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
async function loadTables() {
|
|
374
|
-
try {
|
|
375
|
-
const res = await fetch('/api/tables');
|
|
376
|
-
tablesData = await res.json();
|
|
377
|
-
|
|
378
|
-
const list = document.getElementById('table-list');
|
|
379
|
-
list.innerHTML = '';
|
|
380
|
-
|
|
381
|
-
tablesData.forEach(t => {
|
|
382
|
-
const li = document.createElement('li');
|
|
383
|
-
li.className = 'table-item';
|
|
384
|
-
li.innerHTML = \`
|
|
385
|
-
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
386
|
-
<rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect>
|
|
387
|
-
<line x1="3" y1="9" x2="21" y2="9"></line>
|
|
388
|
-
<line x1="9" y1="21" x2="9" y2="9"></line>
|
|
389
|
-
</svg>
|
|
390
|
-
\${t.name}
|
|
391
|
-
\`;
|
|
392
|
-
li.onclick = () => selectTable(t.name);
|
|
393
|
-
list.appendChild(li);
|
|
394
|
-
});
|
|
395
|
-
} catch (e) {
|
|
396
|
-
console.error("Failed to load tables", e);
|
|
397
|
-
}
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
async function selectTable(name) {
|
|
401
|
-
currentTable = name;
|
|
402
|
-
|
|
403
|
-
// Update UI active state
|
|
404
|
-
document.querySelectorAll('.table-item').forEach(el => {
|
|
405
|
-
if (el.textContent.trim() === name) el.classList.add('active');
|
|
406
|
-
else el.classList.remove('active');
|
|
407
|
-
});
|
|
408
|
-
|
|
409
|
-
document.getElementById('current-table-name').textContent = name;
|
|
410
|
-
document.getElementById('refresh-btn').disabled = false;
|
|
411
|
-
|
|
412
|
-
const container = document.getElementById('data-container');
|
|
413
|
-
container.innerHTML = '<div class="empty-state">Loading data...</div>';
|
|
414
|
-
container.className = '';
|
|
415
|
-
|
|
416
|
-
try {
|
|
417
|
-
const res = await fetch(\`/api/tables/\${name}/data\`);
|
|
418
|
-
if (!res.ok) throw new Error(await res.text());
|
|
419
|
-
const rows = await res.json();
|
|
420
|
-
|
|
421
|
-
if (rows.length === 0) {
|
|
422
|
-
container.innerHTML = '<div class="empty-state">No data in this table</div>';
|
|
423
|
-
return;
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
const columns = Object.keys(rows[0]);
|
|
427
|
-
|
|
428
|
-
let html = '<div class="data-grid-container"><table><thead><tr>';
|
|
429
|
-
columns.forEach(col => {
|
|
430
|
-
html += \`<th>\${col}</th>\`;
|
|
431
|
-
});
|
|
432
|
-
html += '</tr></thead><tbody>';
|
|
433
|
-
|
|
434
|
-
rows.forEach(row => {
|
|
435
|
-
html += '<tr>';
|
|
436
|
-
columns.forEach(col => {
|
|
437
|
-
html += \`<td>\${formatValue(row[col])}</td>\`;
|
|
438
|
-
});
|
|
439
|
-
html += '</tr>';
|
|
440
|
-
});
|
|
441
|
-
|
|
442
|
-
html += '</tbody></table></div>';
|
|
443
|
-
container.innerHTML = html;
|
|
444
|
-
|
|
445
|
-
} catch (e) {
|
|
446
|
-
container.innerHTML = \`<div class="empty-state" style="color: #ff7b72;">Error: \${e.message}</div>\`;
|
|
447
|
-
}
|
|
448
|
-
}
|
|
449
|
-
|
|
450
|
-
document.getElementById('refresh-btn').addEventListener('click', async () => {
|
|
451
|
-
if (!currentTable) return;
|
|
452
|
-
|
|
453
|
-
const btn = document.getElementById('refresh-btn');
|
|
454
|
-
const originalText = btn.innerHTML;
|
|
455
|
-
btn.innerHTML = 'Refreshing...';
|
|
456
|
-
|
|
457
|
-
try {
|
|
458
|
-
await selectTable(currentTable);
|
|
459
|
-
} catch (e) {
|
|
460
|
-
console.error("Failed to refresh data", e);
|
|
461
|
-
} finally {
|
|
462
|
-
setTimeout(() => { btn.innerHTML = originalText; }, 300);
|
|
463
|
-
}
|
|
464
|
-
});
|
|
465
|
-
|
|
466
|
-
// Init
|
|
467
|
-
loadTables();
|
|
468
|
-
</script>
|
|
469
|
-
</body>
|
|
470
|
-
</html>
|
|
471
|
-
`;
|
package/src/commands/tusky.ts
DELETED
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
import * as readline from "node:readline";
|
|
2
|
-
import { createDB } from "@bungres/orm";
|
|
3
|
-
import type { ResolvedConfig } from "../config.js";
|
|
4
|
-
import { loadSchemas } from "../schema-loader.js";
|
|
5
|
-
|
|
6
|
-
// ---------------------------------------------------------------------------
|
|
7
|
-
// tusky — boot up a basic REPL connected to the database
|
|
8
|
-
// ---------------------------------------------------------------------------
|
|
9
|
-
|
|
10
|
-
export async function runTusky(config: ResolvedConfig): Promise<void> {
|
|
11
|
-
const schemas = await loadSchemas(config.schema);
|
|
12
|
-
|
|
13
|
-
const schemaObj: Record<string, any> = {};
|
|
14
|
-
for (const s of schemas) {
|
|
15
|
-
schemaObj[s.exportName] = s.table;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
const db = createDB({ url: config.dbUrl, schema: schemaObj });
|
|
19
|
-
|
|
20
|
-
console.log("=========================================");
|
|
21
|
-
console.log("🐘 Welcome to Bungres REPL (Tusky)");
|
|
22
|
-
console.log("=========================================");
|
|
23
|
-
console.log("\nDatabase connection established.");
|
|
24
|
-
console.log("\nPre-loaded Context:");
|
|
25
|
-
console.log(" - db (Bungres Database Client)");
|
|
26
|
-
for (const s of schemas) {
|
|
27
|
-
console.log(` - ${s.exportName} (Table)`);
|
|
28
|
-
}
|
|
29
|
-
console.log("\nExample query: await db.select().from(users)");
|
|
30
|
-
console.log("Type .exit to quit.\n");
|
|
31
|
-
|
|
32
|
-
// Create a custom REPL using readline since Bun doesn't support node:repl.start()
|
|
33
|
-
const rl = readline.createInterface({
|
|
34
|
-
input: process.stdin,
|
|
35
|
-
output: process.stdout,
|
|
36
|
-
prompt: "bungres> "
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
// Inject variables into the global scope so eval() can see them
|
|
40
|
-
(globalThis as any).db = db;
|
|
41
|
-
for (const s of schemas) {
|
|
42
|
-
(globalThis as any)[s.exportName] = s.table;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
rl.prompt();
|
|
46
|
-
|
|
47
|
-
rl.on("line", async (line) => {
|
|
48
|
-
const input = line.trim();
|
|
49
|
-
if (input === ".exit") {
|
|
50
|
-
rl.close();
|
|
51
|
-
return;
|
|
52
|
-
}
|
|
53
|
-
if (!input) {
|
|
54
|
-
rl.prompt();
|
|
55
|
-
return;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
try {
|
|
59
|
-
let code = input;
|
|
60
|
-
|
|
61
|
-
// DX Improvement: If the input starts with a SQL keyword, automatically wrap it in db.raw()
|
|
62
|
-
const isRawSql = /^(select|insert|update|delete|create|drop|alter|truncate|with)\b/i.test(input);
|
|
63
|
-
|
|
64
|
-
if (isRawSql) {
|
|
65
|
-
console.log(`(Running as raw SQL: await db.raw(\`${input}\`))`);
|
|
66
|
-
code = `(async () => { return await db.raw(\`${input}\`); })()`;
|
|
67
|
-
} else if (input.includes("await ")) {
|
|
68
|
-
// Top-level await needs an async IIFE.
|
|
69
|
-
code = `(async () => { return ${input}; })()`;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
const result = await eval(code);
|
|
73
|
-
console.log(result);
|
|
74
|
-
} catch (err: any) {
|
|
75
|
-
console.error(err.message || err);
|
|
76
|
-
}
|
|
77
|
-
rl.prompt();
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
rl.on("close", () => {
|
|
81
|
-
process.exit(0);
|
|
82
|
-
});
|
|
83
|
-
}
|
package/src/config.ts
DELETED
|
@@ -1,102 +0,0 @@
|
|
|
1
|
-
import { resolve, join } from "node:path";
|
|
2
|
-
|
|
3
|
-
// ---------------------------------------------------------------------------
|
|
4
|
-
// Config — mirrors drizzle-kit's config shape, adapted for bungres
|
|
5
|
-
// ---------------------------------------------------------------------------
|
|
6
|
-
|
|
7
|
-
export interface BungresKitConfig {
|
|
8
|
-
/**
|
|
9
|
-
* Glob pattern(s) pointing to your schema file(s).
|
|
10
|
-
* e.g. "./src/db/schema.ts" or "./src/db/schema/index.ts"
|
|
11
|
-
*/
|
|
12
|
-
schema: string | string[];
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Path to the seed file to execute with `bungres seed`.
|
|
16
|
-
* e.g. "./src/db/seed.ts"
|
|
17
|
-
*/
|
|
18
|
-
seed?: string;
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Output directory for generated migration .sql files.
|
|
22
|
-
* Equivalent to drizzle's `out`. Default: "./migrations"
|
|
23
|
-
*/
|
|
24
|
-
out?: string;
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* Postgres connection credentials.
|
|
28
|
-
* URL can also come from the DATABASE_URL environment variable.
|
|
29
|
-
*/
|
|
30
|
-
dbCredentials?: {
|
|
31
|
-
url: string;
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* Postgres schema name to introspect with `bungres pull`.
|
|
36
|
-
* Default: "public"
|
|
37
|
-
*/
|
|
38
|
-
dbSchema?: string;
|
|
39
|
-
|
|
40
|
-
/** Print every SQL statement executed. Default: false */
|
|
41
|
-
verbose?: boolean;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
export interface ResolvedConfig {
|
|
45
|
-
schema: string | string[];
|
|
46
|
-
seed: string;
|
|
47
|
-
out: string;
|
|
48
|
-
dbUrl: string;
|
|
49
|
-
dbSchema: string;
|
|
50
|
-
/** @deprecated use out — kept internally for compat */
|
|
51
|
-
migrationsDir: string;
|
|
52
|
-
outDir: string;
|
|
53
|
-
verbose: boolean;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
const CONFIG_FILES = [
|
|
57
|
-
"bungres.config.ts",
|
|
58
|
-
"bungres.config.js",
|
|
59
|
-
"bungres.config.mts",
|
|
60
|
-
"bungres.config.mjs",
|
|
61
|
-
];
|
|
62
|
-
|
|
63
|
-
export async function loadConfig(cwd = process.cwd()): Promise<ResolvedConfig> {
|
|
64
|
-
let userConfig: Partial<BungresKitConfig> = {};
|
|
65
|
-
|
|
66
|
-
for (const file of CONFIG_FILES) {
|
|
67
|
-
const configPath = join(cwd, file);
|
|
68
|
-
if (await Bun.file(configPath).exists()) {
|
|
69
|
-
const mod = await import(resolve(configPath));
|
|
70
|
-
userConfig = mod.default ?? mod;
|
|
71
|
-
break;
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
const dbUrl =
|
|
76
|
-
userConfig.dbCredentials?.url ??
|
|
77
|
-
process.env["DATABASE_URL"] ??
|
|
78
|
-
process.env["POSTGRES_URL"] ??
|
|
79
|
-
"";
|
|
80
|
-
|
|
81
|
-
if (!dbUrl) {
|
|
82
|
-
console.error(
|
|
83
|
-
"bungres: No database URL found.\n" +
|
|
84
|
-
" Add dbCredentials.url to bungres.config.ts or set DATABASE_URL."
|
|
85
|
-
);
|
|
86
|
-
process.exit(1);
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
const out = userConfig.out ?? "./migrations";
|
|
90
|
-
|
|
91
|
-
return {
|
|
92
|
-
schema: userConfig.schema ?? "src/db/schema/**/*.ts",
|
|
93
|
-
seed: userConfig.seed ?? "src/db/seed.ts",
|
|
94
|
-
out,
|
|
95
|
-
dbUrl,
|
|
96
|
-
dbSchema: userConfig.dbSchema ?? "public",
|
|
97
|
-
// internal aliases kept so commands don't need changing
|
|
98
|
-
migrationsDir: out,
|
|
99
|
-
outDir: "./src/db/generated",
|
|
100
|
-
verbose: userConfig.verbose ?? false,
|
|
101
|
-
};
|
|
102
|
-
}
|