drixio 1.1.5 → 1.1.7
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/dist/cli.js +3960 -142
- package/dist/studio/assets/index-CM4X0SRj.css +1 -0
- package/dist/studio/assets/index-dfKs6wCl.js +497 -0
- package/dist/studio/index.html +3 -3
- package/package.json +4 -1
- package/dist/studio/assets/index-CpEbodwz.css +0 -1
- package/dist/studio/assets/index-DfJyH7Vs.js +0 -365
package/dist/cli.js
CHANGED
|
@@ -1,20 +1,332 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
var
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
|
+
var __esm = (fn, res) => function __init() {
|
|
5
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
6
|
+
};
|
|
7
|
+
var __export = (target, all) => {
|
|
8
|
+
for (var name in all)
|
|
9
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
|
+
};
|
|
3
11
|
|
|
12
|
+
// src/cli/ui/logo.ts
|
|
13
|
+
import pc from "picocolors";
|
|
14
|
+
function printLogo() {
|
|
15
|
+
console.log(
|
|
16
|
+
"\n\n\n" + pc.bold(
|
|
17
|
+
l1(" \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \n") + l2(" \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557 \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557 \u2588\u2588\u2551 \u255A\u2588\u2588\u2557\u2588\u2588\u2554\u255D \u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2550\u2550\u2588\u2588\u2557\n") + l3(" \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D \u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2554\u255D \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551\n") + l4(" \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557 \u2588\u2588\u2551 \u2588\u2588\u2554\u2588\u2588\u2557 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551\n") + l5(" \u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2554\u255D \u2588\u2588\u2557 \u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\n") + l6(" \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D")
|
|
18
|
+
)
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
function printCustomDashboard(title, rows) {
|
|
22
|
+
const terminalWidth = 74;
|
|
23
|
+
const dashes = "\u2550".repeat(terminalWidth - 2);
|
|
24
|
+
console.log(pc.cyan(`
|
|
25
|
+
\u2554${dashes}\u2557`));
|
|
26
|
+
const spacesNeeded = terminalWidth - 2 - title.length;
|
|
27
|
+
const leftSpace = Math.max(0, Math.floor(spacesNeeded / 2));
|
|
28
|
+
const rightSpace = Math.max(0, spacesNeeded - leftSpace);
|
|
29
|
+
const titleContent = " ".repeat(leftSpace) + pc.bold(pc.white(title)) + " ".repeat(rightSpace);
|
|
30
|
+
console.log(pc.cyan("\u2551") + titleContent + pc.cyan("\u2551"));
|
|
31
|
+
console.log(pc.cyan(`\u2560${"\u2550".repeat(terminalWidth - 2)}\u2563`));
|
|
32
|
+
const printLine = (label, value) => {
|
|
33
|
+
const paddedLabel = label.padEnd(12);
|
|
34
|
+
const lineContent = ` ${pc.bold(paddedLabel)}: ${value}`;
|
|
35
|
+
const rawText = ` ${paddedLabel}: ` + value.replace(/\x1b\[[0-9;]*m/g, "");
|
|
36
|
+
const padding = " ".repeat(Math.max(0, terminalWidth - 2 - rawText.length));
|
|
37
|
+
console.log(pc.cyan("\u2551") + lineContent + padding + pc.cyan("\u2551"));
|
|
38
|
+
};
|
|
39
|
+
for (const row of rows) {
|
|
40
|
+
printLine(row.label, row.value);
|
|
41
|
+
}
|
|
42
|
+
console.log(pc.cyan(`\u255A${"\u2550".repeat(terminalWidth - 2)}\u255D`));
|
|
43
|
+
console.log(
|
|
44
|
+
pc.dim(
|
|
45
|
+
" Use arrow keys to navigate \u2022 Enter to select \u2022 Ctrl+C to exit\n"
|
|
46
|
+
)
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
function printDashboard(dbConfig) {
|
|
50
|
+
const headerTitle = ` Lightweight Interactive TUI Database Client \u2022 v${process.env.npm_package_version} `;
|
|
51
|
+
let dbTypeVal = "None";
|
|
52
|
+
let targetVal = "-";
|
|
53
|
+
let sourceVal = "None";
|
|
54
|
+
if (dbConfig.type === "unknown") {
|
|
55
|
+
sourceVal = pc.dim(
|
|
56
|
+
"No configuration found. Please run check to configure."
|
|
57
|
+
);
|
|
58
|
+
} else {
|
|
59
|
+
dbTypeVal = dbConfig.type.toUpperCase();
|
|
60
|
+
targetVal = dbConfig.targetUrl;
|
|
61
|
+
sourceVal = dbConfig.source === ".env" ? "Loaded from project .env file" : dbConfig.source === "auto-detected" ? "Auto-detected local SQLite file" : "Manual connection config";
|
|
62
|
+
}
|
|
63
|
+
printCustomDashboard(headerTitle, [
|
|
64
|
+
{ label: "Database", value: dbTypeVal },
|
|
65
|
+
{
|
|
66
|
+
label: "Target",
|
|
67
|
+
value: targetVal.length > 45 ? "..." + targetVal.slice(-42) : targetVal
|
|
68
|
+
},
|
|
69
|
+
{ label: "Source", value: sourceVal },
|
|
70
|
+
{
|
|
71
|
+
label: "Working Dir",
|
|
72
|
+
value: process.cwd().length > 45 ? "..." + process.cwd().slice(-42) : process.cwd()
|
|
73
|
+
}
|
|
74
|
+
]);
|
|
75
|
+
}
|
|
76
|
+
var rgb, l1, l2, l3, l4, l5, l6;
|
|
77
|
+
var init_logo = __esm({
|
|
78
|
+
"src/cli/ui/logo.ts"() {
|
|
79
|
+
"use strict";
|
|
80
|
+
rgb = (r, g, b) => (text) => `\x1B[38;2;${r};${g};${b}m${text}\x1B[39m`;
|
|
81
|
+
l1 = rgb(0, 255, 255);
|
|
82
|
+
l2 = rgb(0, 230, 245);
|
|
83
|
+
l3 = rgb(0, 210, 235);
|
|
84
|
+
l4 = rgb(0, 188, 220);
|
|
85
|
+
l5 = rgb(0, 168, 205);
|
|
86
|
+
l6 = rgb(0, 148, 188);
|
|
87
|
+
}
|
|
88
|
+
});
|
|
4
89
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
90
|
+
// src/adapters/sqlite.ts
|
|
91
|
+
var SqliteAdapter;
|
|
92
|
+
var init_sqlite = __esm({
|
|
93
|
+
"src/adapters/sqlite.ts"() {
|
|
94
|
+
"use strict";
|
|
95
|
+
SqliteAdapter = class {
|
|
96
|
+
dbPath;
|
|
97
|
+
db = null;
|
|
98
|
+
constructor(connection) {
|
|
99
|
+
this.dbPath = connection;
|
|
100
|
+
}
|
|
101
|
+
async getDb() {
|
|
102
|
+
if (!this.db) {
|
|
103
|
+
let modFs = "node:fs";
|
|
104
|
+
const fs12 = await import(modFs);
|
|
105
|
+
if (!fs12.existsSync(this.dbPath)) {
|
|
106
|
+
throw new Error(`Failed to found database file at: ${this.dbPath}`);
|
|
107
|
+
}
|
|
108
|
+
let mod = "node:sqlite";
|
|
109
|
+
const sqlite = await import(mod);
|
|
110
|
+
this.db = new sqlite.DatabaseSync(this.dbPath);
|
|
111
|
+
}
|
|
112
|
+
return this.db;
|
|
113
|
+
}
|
|
114
|
+
quoteIdentifier(name) {
|
|
115
|
+
return `"${name.replace(/"/g, '""')}"`;
|
|
116
|
+
}
|
|
117
|
+
async getStatus() {
|
|
118
|
+
try {
|
|
119
|
+
const db = await this.getDb();
|
|
120
|
+
const vQuery = db.prepare("SELECT sqlite_version() as v");
|
|
121
|
+
const vRow = vQuery.get();
|
|
122
|
+
let modFs = "node:fs";
|
|
123
|
+
const fs12 = await import(modFs);
|
|
124
|
+
const stats = fs12.statSync(this.dbPath);
|
|
125
|
+
let modPath = "node:path";
|
|
126
|
+
const path13 = await import(modPath);
|
|
127
|
+
const dbName = path13.basename(this.dbPath);
|
|
128
|
+
return {
|
|
129
|
+
status: "connected",
|
|
130
|
+
dbType: "sqlite",
|
|
131
|
+
dbName,
|
|
132
|
+
version: vRow?.v,
|
|
133
|
+
activeConnections: 1,
|
|
134
|
+
// SQLite is single file, essentially 1 active connection for the app
|
|
135
|
+
sizeBytes: stats.size,
|
|
136
|
+
uptime: process.uptime()
|
|
137
|
+
};
|
|
138
|
+
} catch (e) {
|
|
139
|
+
return {
|
|
140
|
+
status: "error",
|
|
141
|
+
dbType: "sqlite"
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
async getTables() {
|
|
146
|
+
const db = await this.getDb();
|
|
147
|
+
const results = db.prepare(
|
|
148
|
+
"SELECT name FROM sqlite_master WHERE type = 'table' AND name NOT LIKE 'sqlite_%' ORDER BY name;"
|
|
149
|
+
).all();
|
|
150
|
+
return results.map((row) => row.name);
|
|
151
|
+
}
|
|
152
|
+
async getSchema(tableName) {
|
|
153
|
+
const db = await this.getDb();
|
|
154
|
+
const quoted = this.quoteIdentifier(tableName);
|
|
155
|
+
const pragmaQuery = db.prepare(`PRAGMA table_info(${quoted})`);
|
|
156
|
+
const info = pragmaQuery.all();
|
|
157
|
+
const fkQuery = db.prepare(`PRAGMA foreign_key_list(${quoted})`);
|
|
158
|
+
const fks = fkQuery.all();
|
|
159
|
+
return info.map((col) => {
|
|
160
|
+
const fk = fks.find((f) => f.from === col.name);
|
|
161
|
+
return {
|
|
162
|
+
name: col.name,
|
|
163
|
+
type: col.type,
|
|
164
|
+
isPk: col.pk > 0,
|
|
165
|
+
nullable: col.notnull === 0,
|
|
166
|
+
defaultValue: col.dflt_value != null ? String(col.dflt_value) : void 0,
|
|
167
|
+
fkTarget: fk ? { table: fk.table, column: fk.to } : void 0
|
|
168
|
+
};
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
async getIndexes(tableName) {
|
|
172
|
+
const db = await this.getDb();
|
|
173
|
+
const quoted = this.quoteIdentifier(tableName);
|
|
174
|
+
const indexListQuery = db.prepare(`PRAGMA index_list(${quoted})`);
|
|
175
|
+
const indexList = indexListQuery.all();
|
|
176
|
+
const indexes = [];
|
|
177
|
+
for (const idx of indexList) {
|
|
178
|
+
if (idx.origin === "pk") continue;
|
|
179
|
+
const indexInfoQuery = db.prepare(`PRAGMA index_info(${this.quoteIdentifier(idx.name)})`);
|
|
180
|
+
const columns = indexInfoQuery.all();
|
|
181
|
+
indexes.push({
|
|
182
|
+
name: idx.name,
|
|
183
|
+
columns: columns.map((c) => c.name),
|
|
184
|
+
isUnique: idx.unique > 0
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
return indexes;
|
|
188
|
+
}
|
|
189
|
+
async getData(tableName, limit = 50, offset = 0, whereClause, orderBy) {
|
|
190
|
+
const db = await this.getDb();
|
|
191
|
+
const schema = await this.getSchema(tableName);
|
|
192
|
+
const columns = schema.map((col) => col.name);
|
|
193
|
+
let sql = `SELECT * FROM ${this.quoteIdentifier(tableName)}`;
|
|
194
|
+
if (whereClause) {
|
|
195
|
+
sql += ` WHERE ${whereClause}`;
|
|
196
|
+
}
|
|
197
|
+
if (orderBy) {
|
|
198
|
+
sql += ` ORDER BY ${this.quoteIdentifier(orderBy.col)} ${orderBy.asc ? "ASC" : "DESC"}`;
|
|
199
|
+
}
|
|
200
|
+
sql += ` LIMIT ${limit} OFFSET ${offset}`;
|
|
201
|
+
const dataQuery = db.prepare(sql);
|
|
202
|
+
const rows = dataQuery.all();
|
|
203
|
+
return { columns, rows };
|
|
204
|
+
}
|
|
205
|
+
async query(sql) {
|
|
206
|
+
const db = await this.getDb();
|
|
207
|
+
const trimmed = sql.trim().toUpperCase();
|
|
208
|
+
const isRead = trimmed.startsWith("SELECT") || trimmed.startsWith("PRAGMA") || trimmed.startsWith("EXPLAIN") || trimmed.startsWith("WITH");
|
|
209
|
+
if (isRead) {
|
|
210
|
+
const query = db.prepare(sql);
|
|
211
|
+
const rows = query.all();
|
|
212
|
+
let columns = [];
|
|
213
|
+
if (rows.length > 0) {
|
|
214
|
+
columns = Object.keys(rows[0]);
|
|
215
|
+
}
|
|
216
|
+
return { columns, rows };
|
|
217
|
+
} else {
|
|
218
|
+
const query = db.prepare(sql);
|
|
219
|
+
query.run();
|
|
220
|
+
return { columns: ["Result"], rows: [{ Result: "Success" }] };
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
async executeSql(sql) {
|
|
224
|
+
const db = await this.getDb();
|
|
225
|
+
db.exec(sql);
|
|
226
|
+
}
|
|
227
|
+
async close() {
|
|
228
|
+
if (this.db) {
|
|
229
|
+
this.db.close();
|
|
230
|
+
this.db = null;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
async insert(tableName, rows) {
|
|
234
|
+
if (rows.length === 0) return;
|
|
235
|
+
const db = await this.getDb();
|
|
236
|
+
const cols = Object.keys(rows[0]);
|
|
237
|
+
const colsQuoted = cols.map((c) => this.quoteIdentifier(c)).join(", ");
|
|
238
|
+
const placeholders = cols.map(() => "?").join(", ");
|
|
239
|
+
const sql = `INSERT INTO ${this.quoteIdentifier(tableName)} (${colsQuoted}) VALUES (${placeholders})`;
|
|
240
|
+
const stmt = db.prepare(sql);
|
|
241
|
+
for (const row of rows) {
|
|
242
|
+
const values = cols.map((c) => row[c]);
|
|
243
|
+
stmt.run(...values);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
// src/adapters/postgres.ts
|
|
251
|
+
import pg from "pg";
|
|
252
|
+
var PostgresAdapter;
|
|
253
|
+
var init_postgres = __esm({
|
|
254
|
+
"src/adapters/postgres.ts"() {
|
|
255
|
+
"use strict";
|
|
256
|
+
PostgresAdapter = class {
|
|
257
|
+
client;
|
|
258
|
+
connected = false;
|
|
259
|
+
constructor(connection) {
|
|
260
|
+
this.client = new pg.Client({
|
|
261
|
+
connectionString: connection
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
async connectIfNecessary() {
|
|
265
|
+
if (!this.connected) {
|
|
266
|
+
await this.client.connect();
|
|
267
|
+
this.connected = true;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
quoteIdentifier(name) {
|
|
271
|
+
return `"${name.replace(/"/g, '""')}"`;
|
|
272
|
+
}
|
|
273
|
+
async getStatus() {
|
|
274
|
+
try {
|
|
275
|
+
await this.connectIfNecessary();
|
|
276
|
+
const dbRes = await this.client.query("SELECT current_database() as db, version() as version");
|
|
277
|
+
const dbName = dbRes.rows[0]?.db;
|
|
278
|
+
const version = dbRes.rows[0]?.version?.split(" ")[1] || dbRes.rows[0]?.version;
|
|
279
|
+
let activeConnections = 0;
|
|
280
|
+
let transactions = 0;
|
|
281
|
+
let uptime = 0;
|
|
282
|
+
try {
|
|
283
|
+
const uptimeRes = await this.client.query("SELECT EXTRACT(EPOCH FROM (now() - pg_postmaster_start_time())) as uptime");
|
|
284
|
+
uptime = parseInt(uptimeRes.rows[0]?.uptime || "0", 10);
|
|
285
|
+
} catch (e) {
|
|
286
|
+
}
|
|
287
|
+
try {
|
|
288
|
+
const statRes = await this.client.query("SELECT sum(numbackends) as conns, sum(xact_commit + xact_rollback) as txs FROM pg_stat_database");
|
|
289
|
+
activeConnections = parseInt(statRes.rows[0]?.conns || "0", 10);
|
|
290
|
+
transactions = parseInt(statRes.rows[0]?.txs || "0", 10);
|
|
291
|
+
} catch (e) {
|
|
292
|
+
}
|
|
293
|
+
let sizeBytes = 0;
|
|
294
|
+
try {
|
|
295
|
+
const sizeRes = await this.client.query("SELECT pg_database_size(current_database()) as size");
|
|
296
|
+
sizeBytes = parseInt(sizeRes.rows[0]?.size || "0", 10);
|
|
297
|
+
} catch (e) {
|
|
298
|
+
}
|
|
299
|
+
return {
|
|
300
|
+
status: "connected",
|
|
301
|
+
dbType: "postgres",
|
|
302
|
+
dbName,
|
|
303
|
+
version,
|
|
304
|
+
activeConnections,
|
|
305
|
+
sizeBytes,
|
|
306
|
+
transactions,
|
|
307
|
+
uptime
|
|
308
|
+
};
|
|
309
|
+
} catch (e) {
|
|
310
|
+
return {
|
|
311
|
+
status: "error",
|
|
312
|
+
dbType: "postgres"
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
async getTables() {
|
|
317
|
+
await this.connectIfNecessary();
|
|
318
|
+
const query = `
|
|
13
319
|
SELECT tablename
|
|
14
320
|
FROM pg_catalog.pg_tables
|
|
15
321
|
WHERE schemaname != 'pg_catalog' AND schemaname != 'information_schema'
|
|
16
322
|
ORDER BY tablename;
|
|
17
|
-
|
|
323
|
+
`;
|
|
324
|
+
const res = await this.client.query(query);
|
|
325
|
+
return res.rows.map((row) => row.tablename);
|
|
326
|
+
}
|
|
327
|
+
async getSchema(tableName) {
|
|
328
|
+
await this.connectIfNecessary();
|
|
329
|
+
const query = `
|
|
18
330
|
SELECT c.column_name, c.data_type, c.is_nullable, c.column_default,
|
|
19
331
|
(SELECT count(*)
|
|
20
332
|
FROM information_schema.key_column_usage kcu
|
|
@@ -22,7 +334,8 @@ var Jt=Object.defineProperty;var E=(s,a)=>()=>(s&&(a=s(s=0)),a);var L=(s,a)=>{fo
|
|
|
22
334
|
ON kcu.constraint_name = tc.constraint_name
|
|
23
335
|
WHERE tc.constraint_type = 'PRIMARY KEY'
|
|
24
336
|
AND kcu.table_name = c.table_name
|
|
25
|
-
AND kcu.column_name = c.column_name
|
|
337
|
+
AND kcu.column_name = c.column_name
|
|
338
|
+
AND kcu.table_schema = c.table_schema) as is_pk,
|
|
26
339
|
(SELECT ccu.table_name || '.' || ccu.column_name
|
|
27
340
|
FROM information_schema.table_constraints tc
|
|
28
341
|
JOIN information_schema.key_column_usage kcu
|
|
@@ -31,11 +344,33 @@ var Jt=Object.defineProperty;var E=(s,a)=>()=>(s&&(a=s(s=0)),a);var L=(s,a)=>{fo
|
|
|
31
344
|
ON ccu.constraint_name = tc.constraint_name
|
|
32
345
|
WHERE tc.constraint_type = 'FOREIGN KEY'
|
|
33
346
|
AND tc.table_name = c.table_name
|
|
347
|
+
AND tc.table_schema = c.table_schema
|
|
34
348
|
AND kcu.column_name = c.column_name
|
|
35
349
|
LIMIT 1) as fk_target
|
|
36
350
|
FROM information_schema.columns c
|
|
37
|
-
WHERE c.table_name = $1
|
|
38
|
-
|
|
351
|
+
WHERE c.table_name = $1 AND c.table_schema = 'public'
|
|
352
|
+
ORDER BY c.ordinal_position;
|
|
353
|
+
`;
|
|
354
|
+
const res = await this.client.query(query, [tableName]);
|
|
355
|
+
return res.rows.map((col) => {
|
|
356
|
+
let fkTarget;
|
|
357
|
+
if (col.fk_target) {
|
|
358
|
+
const parts = col.fk_target.split(".");
|
|
359
|
+
fkTarget = { table: parts[0], column: parts[1] };
|
|
360
|
+
}
|
|
361
|
+
return {
|
|
362
|
+
name: col.column_name,
|
|
363
|
+
type: col.data_type,
|
|
364
|
+
isPk: parseInt(col.is_pk) > 0,
|
|
365
|
+
nullable: col.is_nullable === "YES",
|
|
366
|
+
defaultValue: col.column_default != null ? String(col.column_default) : void 0,
|
|
367
|
+
fkTarget
|
|
368
|
+
};
|
|
369
|
+
});
|
|
370
|
+
}
|
|
371
|
+
async getIndexes(tableName) {
|
|
372
|
+
await this.connectIfNecessary();
|
|
373
|
+
const query = `
|
|
39
374
|
SELECT
|
|
40
375
|
i.relname as index_name,
|
|
41
376
|
a.attname as column_name,
|
|
@@ -55,144 +390,3627 @@ var Jt=Object.defineProperty;var E=(s,a)=>()=>(s&&(a=s(s=0)),a);var L=(s,a)=>{fo
|
|
|
55
390
|
AND t.relname = $1
|
|
56
391
|
ORDER BY
|
|
57
392
|
i.relname, a.attnum;
|
|
58
|
-
|
|
393
|
+
`;
|
|
394
|
+
const res = await this.client.query(query, [tableName]);
|
|
395
|
+
const rows = res.rows;
|
|
396
|
+
const indexMap = /* @__PURE__ */ new Map();
|
|
397
|
+
for (const row of rows) {
|
|
398
|
+
if (row.is_primary) continue;
|
|
399
|
+
const idxName = row.index_name;
|
|
400
|
+
if (!indexMap.has(idxName)) {
|
|
401
|
+
indexMap.set(idxName, {
|
|
402
|
+
name: idxName,
|
|
403
|
+
columns: [],
|
|
404
|
+
isUnique: row.is_unique
|
|
405
|
+
});
|
|
406
|
+
}
|
|
407
|
+
indexMap.get(idxName).columns.push(row.column_name);
|
|
408
|
+
}
|
|
409
|
+
return Array.from(indexMap.values());
|
|
410
|
+
}
|
|
411
|
+
async getData(tableName, limit = 50, offset = 0, whereClause, orderBy) {
|
|
412
|
+
await this.connectIfNecessary();
|
|
413
|
+
const schema = await this.getSchema(tableName);
|
|
414
|
+
const columns = schema.map((col) => col.name);
|
|
415
|
+
let sql = `SELECT * FROM ${this.quoteIdentifier(tableName)}`;
|
|
416
|
+
if (whereClause) {
|
|
417
|
+
sql += ` WHERE ${whereClause}`;
|
|
418
|
+
}
|
|
419
|
+
if (orderBy) {
|
|
420
|
+
sql += ` ORDER BY ${this.quoteIdentifier(orderBy.col)} ${orderBy.asc ? "ASC" : "DESC"}`;
|
|
421
|
+
}
|
|
422
|
+
sql += ` LIMIT $1 OFFSET $2`;
|
|
423
|
+
const res = await this.client.query(sql, [limit, offset]);
|
|
424
|
+
return { columns, rows: res.rows };
|
|
425
|
+
}
|
|
426
|
+
async query(sql) {
|
|
427
|
+
await this.connectIfNecessary();
|
|
428
|
+
const res = await this.client.query(sql);
|
|
429
|
+
let columns = [];
|
|
430
|
+
if (res.fields) {
|
|
431
|
+
columns = res.fields.map((f) => f.name);
|
|
432
|
+
}
|
|
433
|
+
return { columns, rows: res.rows || [] };
|
|
434
|
+
}
|
|
435
|
+
async executeSql(sql) {
|
|
436
|
+
await this.connectIfNecessary();
|
|
437
|
+
await this.client.query(sql);
|
|
438
|
+
}
|
|
439
|
+
async close() {
|
|
440
|
+
if (this.connected) {
|
|
441
|
+
await this.client.end();
|
|
442
|
+
this.connected = false;
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
async insert(tableName, rows) {
|
|
446
|
+
if (rows.length === 0) return;
|
|
447
|
+
await this.connectIfNecessary();
|
|
448
|
+
const cols = Object.keys(rows[0]);
|
|
449
|
+
const colsQuoted = cols.map((c) => this.quoteIdentifier(c)).join(", ");
|
|
450
|
+
for (const row of rows) {
|
|
451
|
+
const placeholders = cols.map((_, i) => `$${i + 1}`).join(", ");
|
|
452
|
+
const sql = `INSERT INTO ${this.quoteIdentifier(tableName)} (${colsQuoted}) VALUES (${placeholders})`;
|
|
453
|
+
const values = cols.map((c) => row[c]);
|
|
454
|
+
await this.client.query(sql, values);
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
};
|
|
458
|
+
}
|
|
459
|
+
});
|
|
460
|
+
|
|
461
|
+
// src/adapters/mysql.ts
|
|
462
|
+
import mysql from "mysql2/promise";
|
|
463
|
+
var MysqlAdapter;
|
|
464
|
+
var init_mysql = __esm({
|
|
465
|
+
"src/adapters/mysql.ts"() {
|
|
466
|
+
"use strict";
|
|
467
|
+
MysqlAdapter = class {
|
|
468
|
+
connection;
|
|
469
|
+
pool = null;
|
|
470
|
+
constructor(connection) {
|
|
471
|
+
this.connection = connection;
|
|
472
|
+
}
|
|
473
|
+
async getPool() {
|
|
474
|
+
if (!this.pool) {
|
|
475
|
+
this.pool = mysql.createPool(this.connection);
|
|
476
|
+
this.pool.on("connection", (connection) => {
|
|
477
|
+
connection.query("SET SESSION sql_mode = 'ANSI_QUOTES'");
|
|
478
|
+
});
|
|
479
|
+
}
|
|
480
|
+
return this.pool;
|
|
481
|
+
}
|
|
482
|
+
quoteIdentifier(name) {
|
|
483
|
+
return `\`${name.replace(/`/g, "``")}\``;
|
|
484
|
+
}
|
|
485
|
+
async getStatus() {
|
|
486
|
+
try {
|
|
487
|
+
const pool = await this.getPool();
|
|
488
|
+
const [dbRow] = await pool.query("SELECT DATABASE() as db, VERSION() as version");
|
|
489
|
+
const dbName = dbRow[0]?.db;
|
|
490
|
+
const version = dbRow[0]?.version;
|
|
491
|
+
const [statusRows] = await pool.query("SHOW GLOBAL STATUS WHERE Variable_name IN ('Threads_connected', 'Queries', 'Uptime')");
|
|
492
|
+
let activeConnections = 0;
|
|
493
|
+
let queries = 0;
|
|
494
|
+
let uptime = 0;
|
|
495
|
+
for (const row of statusRows) {
|
|
496
|
+
if (row.Variable_name === "Threads_connected") activeConnections = parseInt(row.Value, 10);
|
|
497
|
+
if (row.Variable_name === "Queries") queries = parseInt(row.Value, 10);
|
|
498
|
+
if (row.Variable_name === "Uptime") uptime = parseInt(row.Value, 10);
|
|
499
|
+
}
|
|
500
|
+
let sizeBytes = 0;
|
|
501
|
+
if (dbName) {
|
|
502
|
+
const [sizeRow] = await pool.query("SELECT SUM(data_length + index_length) as size FROM information_schema.TABLES WHERE table_schema = ?", [dbName]);
|
|
503
|
+
sizeBytes = parseInt(sizeRow[0]?.size || "0", 10);
|
|
504
|
+
}
|
|
505
|
+
return {
|
|
506
|
+
status: "connected",
|
|
507
|
+
dbType: "mysql",
|
|
508
|
+
dbName,
|
|
509
|
+
version,
|
|
510
|
+
activeConnections,
|
|
511
|
+
sizeBytes,
|
|
512
|
+
queries,
|
|
513
|
+
uptime
|
|
514
|
+
};
|
|
515
|
+
} catch (e) {
|
|
516
|
+
return {
|
|
517
|
+
status: "error",
|
|
518
|
+
dbType: "mysql"
|
|
519
|
+
};
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
async getTables() {
|
|
523
|
+
const pool = await this.getPool();
|
|
524
|
+
const [rows] = await pool.query("SHOW TABLES;");
|
|
525
|
+
return rows.map((row) => Object.values(row)[0]);
|
|
526
|
+
}
|
|
527
|
+
async getSchema(tableName) {
|
|
528
|
+
const pool = await this.getPool();
|
|
529
|
+
const [rows] = await pool.query(
|
|
530
|
+
`SHOW COLUMNS FROM ${this.quoteIdentifier(tableName)}`
|
|
531
|
+
);
|
|
532
|
+
const columns = rows;
|
|
533
|
+
const [fkRows] = await pool.query(
|
|
534
|
+
`
|
|
59
535
|
SELECT COLUMN_NAME, REFERENCED_TABLE_NAME, REFERENCED_COLUMN_NAME
|
|
60
536
|
FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE
|
|
61
537
|
WHERE TABLE_SCHEMA = DATABASE()
|
|
62
538
|
AND TABLE_NAME = ?
|
|
63
539
|
AND REFERENCED_TABLE_NAME IS NOT NULL
|
|
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
|
-
|
|
540
|
+
`,
|
|
541
|
+
[tableName]
|
|
542
|
+
);
|
|
543
|
+
const fks = fkRows;
|
|
544
|
+
return columns.map((col) => {
|
|
545
|
+
const fk = fks.find((f) => f.COLUMN_NAME === col.Field);
|
|
546
|
+
let enumValues;
|
|
547
|
+
const typeUpper = col.Type.toUpperCase();
|
|
548
|
+
if (typeUpper.startsWith("ENUM")) {
|
|
549
|
+
const enumMatch = col.Type.match(/enum\((.*?)\)/i);
|
|
550
|
+
if (enumMatch) {
|
|
551
|
+
enumValues = enumMatch[1].split(",").map((s) => s.trim().replace(/^'|'$/g, ""));
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
return {
|
|
555
|
+
name: col.Field,
|
|
556
|
+
type: col.Type,
|
|
557
|
+
isPk: col.Key === "PRI",
|
|
558
|
+
nullable: col.Null === "YES",
|
|
559
|
+
defaultValue: col.Default != null ? String(col.Default) : void 0,
|
|
560
|
+
enumValues,
|
|
561
|
+
fkTarget: fk ? {
|
|
562
|
+
table: fk.REFERENCED_TABLE_NAME,
|
|
563
|
+
column: fk.REFERENCED_COLUMN_NAME
|
|
564
|
+
} : void 0
|
|
565
|
+
};
|
|
566
|
+
});
|
|
567
|
+
}
|
|
568
|
+
async getIndexes(tableName) {
|
|
569
|
+
const pool = await this.getPool();
|
|
570
|
+
const [rows] = await pool.query(`SHOW INDEX FROM ${this.quoteIdentifier(tableName)}`);
|
|
571
|
+
const indexMap = /* @__PURE__ */ new Map();
|
|
572
|
+
for (const row of rows) {
|
|
573
|
+
if (row.Key_name === "PRIMARY") continue;
|
|
574
|
+
const idxName = row.Key_name;
|
|
575
|
+
if (!indexMap.has(idxName)) {
|
|
576
|
+
indexMap.set(idxName, {
|
|
577
|
+
name: idxName,
|
|
578
|
+
columns: [],
|
|
579
|
+
isUnique: row.Non_unique === 0
|
|
580
|
+
});
|
|
581
|
+
}
|
|
582
|
+
indexMap.get(idxName).columns.push(row.Column_name);
|
|
583
|
+
}
|
|
584
|
+
return Array.from(indexMap.values());
|
|
585
|
+
}
|
|
586
|
+
async getData(tableName, limit = 50, offset = 0, whereClause, orderBy) {
|
|
587
|
+
const pool = await this.getPool();
|
|
588
|
+
const schema = await this.getSchema(tableName);
|
|
589
|
+
const columns = schema.map((col) => col.name);
|
|
590
|
+
let sql = `SELECT * FROM ${this.quoteIdentifier(tableName)}`;
|
|
591
|
+
if (whereClause) {
|
|
592
|
+
sql += ` WHERE ${whereClause}`;
|
|
593
|
+
}
|
|
594
|
+
if (orderBy) {
|
|
595
|
+
sql += ` ORDER BY ${this.quoteIdentifier(orderBy.col)} ${orderBy.asc ? "ASC" : "DESC"}`;
|
|
596
|
+
}
|
|
597
|
+
const [rows] = await pool.query(sql + " LIMIT ? OFFSET ?", [limit, offset]);
|
|
598
|
+
return { columns, rows };
|
|
599
|
+
}
|
|
600
|
+
async query(sql) {
|
|
601
|
+
const pool = await this.getPool();
|
|
602
|
+
const [rows, fields] = await pool.query(sql);
|
|
603
|
+
let columns = [];
|
|
604
|
+
let data = [];
|
|
605
|
+
if (fields && Array.isArray(fields)) {
|
|
606
|
+
columns = fields.map((f) => f.name);
|
|
607
|
+
data = rows;
|
|
608
|
+
} else {
|
|
609
|
+
columns = ["Result"];
|
|
610
|
+
data = [
|
|
611
|
+
{ Result: "Success", AffectedRows: rows.affectedRows }
|
|
612
|
+
];
|
|
613
|
+
}
|
|
614
|
+
return { columns, rows: data };
|
|
615
|
+
}
|
|
616
|
+
async executeSql(sql) {
|
|
617
|
+
const pool = await this.getPool();
|
|
618
|
+
await pool.query(sql);
|
|
619
|
+
}
|
|
620
|
+
async close() {
|
|
621
|
+
if (this.pool) {
|
|
622
|
+
await this.pool.end();
|
|
623
|
+
this.pool = null;
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
async insert(tableName, rows) {
|
|
627
|
+
if (rows.length === 0) return;
|
|
628
|
+
const pool = await this.getPool();
|
|
629
|
+
const cols = Object.keys(rows[0]);
|
|
630
|
+
const colsQuoted = cols.map((c) => this.quoteIdentifier(c)).join(", ");
|
|
631
|
+
const placeholders = cols.map(() => "?").join(", ");
|
|
632
|
+
const sql = `INSERT INTO ${this.quoteIdentifier(tableName)} (${colsQuoted}) VALUES (${placeholders})`;
|
|
633
|
+
for (const row of rows) {
|
|
634
|
+
const values = cols.map((c) => row[c]);
|
|
635
|
+
await pool.query(sql, values);
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
};
|
|
639
|
+
}
|
|
640
|
+
});
|
|
641
|
+
|
|
642
|
+
// src/core/factory.ts
|
|
643
|
+
var factory_exports = {};
|
|
644
|
+
__export(factory_exports, {
|
|
645
|
+
createDBAdapter: () => createDBAdapter
|
|
646
|
+
});
|
|
647
|
+
function createDBAdapter(config) {
|
|
648
|
+
switch (config.type) {
|
|
649
|
+
case "sqlite":
|
|
650
|
+
return new SqliteAdapter(config.targetUrl);
|
|
651
|
+
case "postgres":
|
|
652
|
+
return new PostgresAdapter(config.targetUrl);
|
|
653
|
+
case "mysql":
|
|
654
|
+
return new MysqlAdapter(config.targetUrl);
|
|
655
|
+
default:
|
|
656
|
+
throw new Error(`Unsupported database type: ${config.type}`);
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
var init_factory = __esm({
|
|
660
|
+
"src/core/factory.ts"() {
|
|
661
|
+
"use strict";
|
|
662
|
+
init_sqlite();
|
|
663
|
+
init_postgres();
|
|
664
|
+
init_mysql();
|
|
665
|
+
}
|
|
666
|
+
});
|
|
667
|
+
|
|
668
|
+
// src/core/dialect.ts
|
|
669
|
+
function getDialect(type) {
|
|
670
|
+
switch (type) {
|
|
671
|
+
case "sqlite":
|
|
672
|
+
return new SqliteDialect();
|
|
673
|
+
case "postgres":
|
|
674
|
+
return new PostgresDialect();
|
|
675
|
+
case "mysql":
|
|
676
|
+
return new MysqlDialect();
|
|
677
|
+
default:
|
|
678
|
+
return new SqliteDialect();
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
var SqliteDialect, PostgresDialect, MysqlDialect;
|
|
682
|
+
var init_dialect = __esm({
|
|
683
|
+
"src/core/dialect.ts"() {
|
|
684
|
+
"use strict";
|
|
685
|
+
SqliteDialect = class {
|
|
686
|
+
quoteIdentifier(name) {
|
|
687
|
+
return `"${name}"`;
|
|
688
|
+
}
|
|
689
|
+
escapeString(val) {
|
|
690
|
+
return val.replace(/'/g, "''");
|
|
691
|
+
}
|
|
692
|
+
buildCreateTable(tableName, columns) {
|
|
693
|
+
const lines = columns.map((col) => {
|
|
694
|
+
let typeStr = "";
|
|
695
|
+
if (col.type === "Integer") typeStr = "INTEGER";
|
|
696
|
+
if (col.type === "Text") typeStr = "TEXT";
|
|
697
|
+
if (col.type === "Boolean") typeStr = "BOOLEAN";
|
|
698
|
+
if (col.type === "Decimal") typeStr = "REAL";
|
|
699
|
+
if (col.type === "DateTime") typeStr = "DATETIME";
|
|
700
|
+
if (col.type === "Enum" && col.enumValues) {
|
|
701
|
+
typeStr = `TEXT CHECK(${this.quoteIdentifier(col.name)} IN (${col.enumValues.map((v) => `'${this.escapeString(v)}'`).join(", ")}))`;
|
|
702
|
+
}
|
|
703
|
+
if (col.isPk && col.type === "Integer") typeStr = "INTEGER PRIMARY KEY AUTOINCREMENT";
|
|
704
|
+
else if (col.isPk) typeStr += " PRIMARY KEY";
|
|
705
|
+
else {
|
|
706
|
+
if (!col.nullable) typeStr += " NOT NULL";
|
|
707
|
+
}
|
|
708
|
+
if (col.defaultValue && col.defaultValue !== "AutoInc" && !col.defaultValue.startsWith("FK ->")) {
|
|
709
|
+
if (col.defaultValue === "Timestamp") {
|
|
710
|
+
typeStr += " DEFAULT CURRENT_TIMESTAMP";
|
|
711
|
+
} else {
|
|
712
|
+
typeStr += ` DEFAULT ${col.defaultValue}`;
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
return ` ${this.quoteIdentifier(col.name)} ${typeStr}`;
|
|
716
|
+
});
|
|
717
|
+
const fks = columns.filter((col) => col.fkTarget).map((col) => ` FOREIGN KEY (${this.quoteIdentifier(col.name)}) REFERENCES ${this.quoteIdentifier(col.fkTarget.table)}(${this.quoteIdentifier(col.fkTarget.column)})`);
|
|
718
|
+
if (fks.length > 0) {
|
|
719
|
+
lines.push(...fks);
|
|
720
|
+
}
|
|
721
|
+
return `CREATE TABLE ${this.quoteIdentifier(tableName)} (
|
|
722
|
+
${lines.join(",\n")}
|
|
723
|
+
);`;
|
|
724
|
+
}
|
|
725
|
+
};
|
|
726
|
+
PostgresDialect = class {
|
|
727
|
+
quoteIdentifier(name) {
|
|
728
|
+
return `"${name}"`;
|
|
729
|
+
}
|
|
730
|
+
escapeString(val) {
|
|
731
|
+
return val.replace(/'/g, "''");
|
|
732
|
+
}
|
|
733
|
+
buildCreateTable(tableName, columns) {
|
|
734
|
+
const lines = columns.map((col) => {
|
|
735
|
+
let typeStr = "";
|
|
736
|
+
if (col.isPk && col.type === "Integer") typeStr = "SERIAL PRIMARY KEY";
|
|
737
|
+
else {
|
|
738
|
+
if (col.type === "Integer") typeStr = "INTEGER";
|
|
739
|
+
if (col.type === "Text") typeStr = "TEXT";
|
|
740
|
+
if (col.type === "Boolean") typeStr = "BOOLEAN";
|
|
741
|
+
if (col.type === "Decimal") typeStr = "NUMERIC";
|
|
742
|
+
if (col.type === "DateTime") typeStr = "TIMESTAMP";
|
|
743
|
+
if (col.type === "Enum" && col.enumValues) {
|
|
744
|
+
typeStr = `VARCHAR(255) CHECK(${this.quoteIdentifier(col.name)} IN (${col.enumValues.map((v) => `'${this.escapeString(v)}'`).join(", ")}))`;
|
|
745
|
+
}
|
|
746
|
+
if (col.isPk) typeStr += " PRIMARY KEY";
|
|
747
|
+
if (!col.nullable && !col.isPk) typeStr += " NOT NULL";
|
|
748
|
+
}
|
|
749
|
+
if (col.defaultValue && col.defaultValue !== "AutoInc" && !col.defaultValue.startsWith("FK ->")) {
|
|
750
|
+
if (col.defaultValue === "Timestamp") {
|
|
751
|
+
typeStr += " DEFAULT CURRENT_TIMESTAMP";
|
|
752
|
+
} else {
|
|
753
|
+
typeStr += ` DEFAULT ${col.defaultValue}`;
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
return ` ${this.quoteIdentifier(col.name)} ${typeStr}`;
|
|
757
|
+
});
|
|
758
|
+
const fks = columns.filter((col) => col.fkTarget).map((col) => ` FOREIGN KEY (${this.quoteIdentifier(col.name)}) REFERENCES ${this.quoteIdentifier(col.fkTarget.table)}(${this.quoteIdentifier(col.fkTarget.column)})`);
|
|
759
|
+
if (fks.length > 0) {
|
|
760
|
+
lines.push(...fks);
|
|
761
|
+
}
|
|
762
|
+
return `CREATE TABLE ${this.quoteIdentifier(tableName)} (
|
|
763
|
+
${lines.join(",\n")}
|
|
764
|
+
);`;
|
|
765
|
+
}
|
|
766
|
+
};
|
|
767
|
+
MysqlDialect = class {
|
|
768
|
+
quoteIdentifier(name) {
|
|
769
|
+
return `\`${name}\``;
|
|
770
|
+
}
|
|
771
|
+
escapeString(val) {
|
|
772
|
+
return val.replace(/'/g, "''");
|
|
773
|
+
}
|
|
774
|
+
buildCreateTable(tableName, columns) {
|
|
775
|
+
const lines = columns.map((col) => {
|
|
776
|
+
let typeStr = "";
|
|
777
|
+
if (col.type === "Integer") typeStr = "INT";
|
|
778
|
+
if (col.type === "Text") typeStr = "VARCHAR(255)";
|
|
779
|
+
if (col.type === "Boolean") typeStr = "BOOLEAN";
|
|
780
|
+
if (col.type === "Decimal") typeStr = "DOUBLE";
|
|
781
|
+
if (col.type === "DateTime") typeStr = "DATETIME";
|
|
782
|
+
if (col.type === "Enum" && col.enumValues) {
|
|
783
|
+
typeStr = `VARCHAR(255) CHECK(${this.quoteIdentifier(col.name)} IN (${col.enumValues.map((v) => `'${this.escapeString(v)}'`).join(", ")}))`;
|
|
784
|
+
}
|
|
785
|
+
if (col.isPk && col.type === "Integer") typeStr += " AUTO_INCREMENT PRIMARY KEY";
|
|
786
|
+
else if (col.isPk) typeStr += " PRIMARY KEY";
|
|
787
|
+
if (!col.nullable && !col.isPk) typeStr += " NOT NULL";
|
|
788
|
+
if (col.defaultValue && col.defaultValue !== "AutoInc" && !col.defaultValue.startsWith("FK ->")) {
|
|
789
|
+
if (col.defaultValue === "Timestamp") {
|
|
790
|
+
typeStr += " DEFAULT CURRENT_TIMESTAMP";
|
|
791
|
+
} else {
|
|
792
|
+
typeStr += ` DEFAULT ${col.defaultValue}`;
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
return ` ${this.quoteIdentifier(col.name)} ${typeStr}`;
|
|
796
|
+
});
|
|
797
|
+
const fks = columns.filter((col) => col.fkTarget).map((col) => ` FOREIGN KEY (${this.quoteIdentifier(col.name)}) REFERENCES ${this.quoteIdentifier(col.fkTarget.table)}(${this.quoteIdentifier(col.fkTarget.column)})`);
|
|
798
|
+
if (fks.length > 0) {
|
|
799
|
+
lines.push(...fks);
|
|
800
|
+
}
|
|
801
|
+
return `CREATE TABLE ${this.quoteIdentifier(tableName)} (
|
|
802
|
+
${lines.join(",\n")}
|
|
803
|
+
);`;
|
|
804
|
+
}
|
|
805
|
+
};
|
|
806
|
+
}
|
|
807
|
+
});
|
|
808
|
+
|
|
809
|
+
// src/cli/ui/table.ts
|
|
810
|
+
import pc4 from "picocolors";
|
|
811
|
+
function drawTable(columns, rows, options = {}) {
|
|
812
|
+
const { title, emptyMessage = "No records found", maxColWidth = 30 } = options;
|
|
813
|
+
const colWidths = {};
|
|
814
|
+
let totalMinWidth = 0;
|
|
815
|
+
for (const col of columns) {
|
|
816
|
+
let maxValLength = col.length;
|
|
817
|
+
for (const row of rows) {
|
|
818
|
+
const valStr = String(row[col] ?? "");
|
|
819
|
+
if (valStr.length > maxValLength) {
|
|
820
|
+
maxValLength = valStr.length;
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
const finalW = Math.max(col.length, Math.min(maxColWidth, maxValLength));
|
|
824
|
+
colWidths[col] = finalW;
|
|
825
|
+
totalMinWidth += finalW;
|
|
826
|
+
}
|
|
827
|
+
const dividerWidths = 3 * (columns.length - 1);
|
|
828
|
+
const dynamicWidth = totalMinWidth + dividerWidths + 4;
|
|
829
|
+
const currentTerminalWidth = Math.max(74, dynamicWidth);
|
|
830
|
+
const availableTextWidth = currentTerminalWidth - 4 - dividerWidths;
|
|
831
|
+
if (totalMinWidth < availableTextWidth && columns.length > 0) {
|
|
832
|
+
const lastCol = columns[columns.length - 1];
|
|
833
|
+
colWidths[lastCol] += availableTextWidth - totalMinWidth;
|
|
834
|
+
}
|
|
835
|
+
const truncate = (str, maxLen) => {
|
|
836
|
+
if (str.length > maxLen) {
|
|
837
|
+
return str.slice(0, maxLen - 3) + "...";
|
|
838
|
+
}
|
|
839
|
+
return str;
|
|
840
|
+
};
|
|
841
|
+
const dashes = "\u2550".repeat(currentTerminalWidth - 2);
|
|
842
|
+
console.log(pc4.cyan(`
|
|
843
|
+
\u2554${dashes}\u2557`));
|
|
844
|
+
if (title) {
|
|
845
|
+
const spacesNeeded = currentTerminalWidth - 2 - title.length;
|
|
846
|
+
const leftSpace = Math.max(0, Math.floor(spacesNeeded / 2));
|
|
847
|
+
const rightSpace = Math.max(0, spacesNeeded - leftSpace);
|
|
848
|
+
const titleContent = " ".repeat(leftSpace) + pc4.bold(pc4.white(title)) + " ".repeat(rightSpace);
|
|
849
|
+
console.log(pc4.cyan("\u2551") + titleContent + pc4.cyan("\u2551"));
|
|
850
|
+
console.log(pc4.cyan(`\u2560${dashes}\u2563`));
|
|
851
|
+
}
|
|
852
|
+
if (columns.length > 0) {
|
|
853
|
+
const headerRow = columns.map((col) => pc4.bold(pc4.white(truncate(col, colWidths[col]).padEnd(colWidths[col])))).join(pc4.cyan(" \u2551 "));
|
|
854
|
+
console.log(pc4.cyan("\u2551 ") + headerRow + pc4.cyan(" \u2551"));
|
|
855
|
+
console.log(pc4.cyan(`\u2560${dashes}\u2563`));
|
|
856
|
+
}
|
|
857
|
+
if (rows.length === 0) {
|
|
858
|
+
const emptySpaces = currentTerminalWidth - 4 - emptyMessage.length;
|
|
859
|
+
const leftPad = Math.max(0, Math.floor(emptySpaces / 2));
|
|
860
|
+
const rightPad = Math.max(0, emptySpaces - leftPad);
|
|
861
|
+
console.log(
|
|
862
|
+
pc4.cyan("\u2551 ") + " ".repeat(leftPad) + pc4.yellow(emptyMessage) + " ".repeat(rightPad) + pc4.cyan(" \u2551")
|
|
863
|
+
);
|
|
864
|
+
} else {
|
|
865
|
+
for (const row of rows) {
|
|
866
|
+
const rowContent = columns.map(
|
|
867
|
+
(col) => pc4.white(truncate(String(row[col] ?? ""), colWidths[col]).padEnd(colWidths[col]))
|
|
868
|
+
).join(pc4.cyan(" \u2551 "));
|
|
869
|
+
console.log(pc4.cyan("\u2551 ") + rowContent + pc4.cyan(" \u2551"));
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
console.log(pc4.cyan(`\u255A${dashes}\u255D`));
|
|
873
|
+
}
|
|
874
|
+
var init_table = __esm({
|
|
875
|
+
"src/cli/ui/table.ts"() {
|
|
876
|
+
"use strict";
|
|
877
|
+
}
|
|
878
|
+
});
|
|
879
|
+
|
|
880
|
+
// subcommands/query.ts
|
|
881
|
+
var query_exports = {};
|
|
882
|
+
__export(query_exports, {
|
|
883
|
+
runQueryCommand: () => runQueryCommand
|
|
884
|
+
});
|
|
885
|
+
import pc9 from "picocolors";
|
|
886
|
+
async function runQueryCommand(dbConfig, args) {
|
|
887
|
+
if (dbConfig.type === "unknown") {
|
|
888
|
+
console.log(pc9.red("Error: No database connection found. Cannot run query."));
|
|
889
|
+
process.exit(1);
|
|
890
|
+
}
|
|
891
|
+
const adapter = createDBAdapter(dbConfig);
|
|
892
|
+
let sql = args[0];
|
|
893
|
+
if (!sql) {
|
|
894
|
+
const { input: input7 } = await import("@inquirer/prompts");
|
|
895
|
+
sql = await input7({
|
|
896
|
+
message: "Enter your SQL query:"
|
|
897
|
+
});
|
|
898
|
+
}
|
|
899
|
+
if (!sql || sql.trim() === "") {
|
|
900
|
+
console.log(pc9.yellow("No query provided. Exiting."));
|
|
901
|
+
process.exit(0);
|
|
902
|
+
}
|
|
903
|
+
try {
|
|
904
|
+
const result = await adapter.query(sql);
|
|
905
|
+
if (result.columns.length === 0) {
|
|
906
|
+
console.log(pc9.yellow("Query executed successfully. (No output)"));
|
|
907
|
+
process.exit(0);
|
|
908
|
+
}
|
|
909
|
+
drawTable(result.columns, result.rows, { title: "Query Result", maxColWidth: 50 });
|
|
910
|
+
console.log(pc9.dim(`
|
|
911
|
+
(${result.rows.length} rows)`));
|
|
912
|
+
await adapter.close();
|
|
913
|
+
process.exit(0);
|
|
914
|
+
} catch (e) {
|
|
915
|
+
console.log(pc9.red(`
|
|
916
|
+
Query Error: ${e.message}
|
|
917
|
+
`));
|
|
918
|
+
process.exit(1);
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
var init_query = __esm({
|
|
922
|
+
"subcommands/query.ts"() {
|
|
923
|
+
"use strict";
|
|
924
|
+
init_factory();
|
|
925
|
+
init_table();
|
|
926
|
+
}
|
|
927
|
+
});
|
|
928
|
+
|
|
929
|
+
// subcommands/export.ts
|
|
930
|
+
var export_exports = {};
|
|
931
|
+
__export(export_exports, {
|
|
932
|
+
runExportCommand: () => runExportCommand
|
|
933
|
+
});
|
|
934
|
+
import pc10 from "picocolors";
|
|
935
|
+
import fs3 from "fs/promises";
|
|
936
|
+
import path3 from "path";
|
|
937
|
+
async function runExportCommand(dbConfig, args, options) {
|
|
938
|
+
if (dbConfig.type === "unknown") {
|
|
939
|
+
console.log(pc10.red("Error: No database connection found. Cannot run export."));
|
|
940
|
+
process.exit(1);
|
|
941
|
+
}
|
|
942
|
+
const adapter = createDBAdapter(dbConfig);
|
|
943
|
+
let tableName = args[0];
|
|
944
|
+
let format = options.format;
|
|
945
|
+
const schemaOnly = options["schema-only"];
|
|
946
|
+
const { select: select11 } = await import("@inquirer/prompts");
|
|
947
|
+
if (!tableName) {
|
|
948
|
+
const allTables = await adapter.getTables();
|
|
949
|
+
if (allTables.length === 0) {
|
|
950
|
+
console.log(pc10.yellow("No tables found in the database."));
|
|
951
|
+
process.exit(0);
|
|
952
|
+
}
|
|
953
|
+
const tableChoices = [
|
|
954
|
+
{ name: "All Tables (*)", value: "*" },
|
|
955
|
+
...allTables.map((t) => ({ name: t, value: t }))
|
|
956
|
+
];
|
|
957
|
+
tableName = await select11({
|
|
958
|
+
message: "Which table do you want to export?",
|
|
959
|
+
choices: tableChoices
|
|
960
|
+
});
|
|
961
|
+
}
|
|
962
|
+
if (!format) {
|
|
963
|
+
format = await select11({
|
|
964
|
+
message: "Which format do you want to export?",
|
|
965
|
+
choices: [
|
|
966
|
+
{ name: "CSV", value: "csv" },
|
|
967
|
+
{ name: "JSON", value: "json" }
|
|
968
|
+
]
|
|
969
|
+
});
|
|
970
|
+
}
|
|
971
|
+
const exportDir = path3.join(process.cwd(), "drixio_exports");
|
|
972
|
+
await fs3.mkdir(exportDir, { recursive: true });
|
|
973
|
+
const tablesToExport = tableName === "*" ? await adapter.getTables() : [tableName];
|
|
974
|
+
console.log(pc10.cyan(`
|
|
975
|
+
Starting export to ${exportDir}...`));
|
|
976
|
+
for (const table of tablesToExport) {
|
|
977
|
+
try {
|
|
978
|
+
if (schemaOnly) {
|
|
979
|
+
const schema = await adapter.getSchema(table);
|
|
980
|
+
if (format === "json") {
|
|
981
|
+
const fp = path3.join(exportDir, `${table}_schema.json`);
|
|
982
|
+
await fs3.writeFile(fp, JSON.stringify(schema, null, 2), "utf-8");
|
|
983
|
+
console.log(pc10.green(`\u2714 Exported Schema (JSON): ${table}`));
|
|
984
|
+
} else {
|
|
985
|
+
const fp = path3.join(exportDir, `${table}_schema.csv`);
|
|
986
|
+
const headers = "Name,Type,IsPrimaryKey,Nullable\n";
|
|
987
|
+
const rows = schema.map((c) => `"${c.name}","${c.type}","${c.isPk}","${c.nullable}"`).join("\n");
|
|
988
|
+
await fs3.writeFile(fp, headers + rows, "utf-8");
|
|
989
|
+
console.log(pc10.green(`\u2714 Exported Schema (CSV): ${table}`));
|
|
990
|
+
}
|
|
991
|
+
} else {
|
|
992
|
+
const batchSize = 1e3;
|
|
993
|
+
let batchOffset = 0;
|
|
994
|
+
const allExportRows = [];
|
|
995
|
+
let exportColumns = [];
|
|
996
|
+
let lastBatch;
|
|
997
|
+
do {
|
|
998
|
+
lastBatch = await adapter.getData(table, batchSize, batchOffset);
|
|
999
|
+
if (exportColumns.length === 0) exportColumns = lastBatch.columns;
|
|
1000
|
+
allExportRows.push(...lastBatch.rows);
|
|
1001
|
+
batchOffset += batchSize;
|
|
1002
|
+
} while (lastBatch.rows.length === batchSize);
|
|
1003
|
+
const data = { columns: exportColumns, rows: allExportRows };
|
|
1004
|
+
if (format === "json") {
|
|
1005
|
+
const fp = path3.join(exportDir, `${table}_data.json`);
|
|
1006
|
+
await fs3.writeFile(fp, JSON.stringify(data.rows, null, 2), "utf-8");
|
|
1007
|
+
console.log(pc10.green(`\u2714 Exported Data (JSON): ${table} (${data.rows.length} rows)`));
|
|
1008
|
+
} else {
|
|
1009
|
+
const fp = path3.join(exportDir, `${table}_data.csv`);
|
|
1010
|
+
let csvStr = "";
|
|
1011
|
+
if (data.columns.length > 0) {
|
|
1012
|
+
const headers = data.columns.join(",") + "\n";
|
|
1013
|
+
const rowsStr = data.rows.map((r) => {
|
|
1014
|
+
return data.columns.map((c) => `"${String(r[c] ?? "").replace(/"/g, '""')}"`).join(",");
|
|
1015
|
+
}).join("\n");
|
|
1016
|
+
csvStr = headers + rowsStr;
|
|
1017
|
+
}
|
|
1018
|
+
await fs3.writeFile(fp, csvStr, "utf-8");
|
|
1019
|
+
console.log(pc10.green(`\u2714 Exported Data (CSV): ${table} (${data.rows.length} rows)`));
|
|
1020
|
+
}
|
|
1021
|
+
}
|
|
1022
|
+
} catch (e) {
|
|
1023
|
+
console.log(pc10.red(`\u2718 Failed to export table ${table}: ${e.message}`));
|
|
1024
|
+
}
|
|
1025
|
+
}
|
|
1026
|
+
await adapter.close();
|
|
1027
|
+
console.log(pc10.cyan("Export complete."));
|
|
1028
|
+
process.exit(0);
|
|
1029
|
+
}
|
|
1030
|
+
var init_export = __esm({
|
|
1031
|
+
"subcommands/export.ts"() {
|
|
1032
|
+
"use strict";
|
|
1033
|
+
init_factory();
|
|
1034
|
+
}
|
|
1035
|
+
});
|
|
1036
|
+
|
|
1037
|
+
// src/server/api.ts
|
|
1038
|
+
import { Hono } from "hono";
|
|
1039
|
+
import { spawn } from "child_process";
|
|
1040
|
+
function nodeToWebStream(nodeStream) {
|
|
1041
|
+
return new ReadableStream({
|
|
1042
|
+
start(controller) {
|
|
1043
|
+
nodeStream.on("data", (chunk) => controller.enqueue(chunk));
|
|
1044
|
+
nodeStream.on("end", () => controller.close());
|
|
1045
|
+
nodeStream.on("error", (err) => controller.error(err));
|
|
1046
|
+
},
|
|
1047
|
+
cancel() {
|
|
1048
|
+
nodeStream.destroy();
|
|
1049
|
+
}
|
|
1050
|
+
});
|
|
1051
|
+
}
|
|
1052
|
+
function registerApiRoutes(app, dbConfig) {
|
|
1053
|
+
const api = new Hono();
|
|
1054
|
+
const adapter = createDBAdapter(dbConfig);
|
|
1055
|
+
const getDbName = () => {
|
|
1056
|
+
let dbName = "database";
|
|
1057
|
+
if (dbConfig.targetUrl) {
|
|
1058
|
+
if (dbConfig.type === "sqlite") {
|
|
1059
|
+
dbName = dbConfig.targetUrl.replace("file:", "").split(/[/\\]/).pop() || dbName;
|
|
1060
|
+
} else {
|
|
1061
|
+
dbName = dbConfig.targetUrl.split("/").pop()?.split("?")[0] || dbName;
|
|
1062
|
+
}
|
|
1063
|
+
}
|
|
1064
|
+
dbName = dbName.replace(/\.sqlite$|\.db$/, "");
|
|
1065
|
+
return dbName.replace(/[^a-zA-Z0-9_-]/g, "");
|
|
1066
|
+
};
|
|
1067
|
+
const getDatetimeStr = () => {
|
|
1068
|
+
const d = /* @__PURE__ */ new Date();
|
|
1069
|
+
const pad = (n) => n.toString().padStart(2, "0");
|
|
1070
|
+
return `${d.getFullYear()}${pad(d.getMonth() + 1)}${pad(d.getDate())}_${pad(d.getHours())}${pad(d.getMinutes())}${pad(d.getSeconds())}`;
|
|
1071
|
+
};
|
|
1072
|
+
api.get("/tables", async (c) => {
|
|
1073
|
+
try {
|
|
1074
|
+
const tables = await adapter.getTables();
|
|
1075
|
+
return c.json({ success: true, data: tables });
|
|
1076
|
+
} catch (e) {
|
|
1077
|
+
return c.json({ success: false, error: e.message }, 500);
|
|
1078
|
+
}
|
|
1079
|
+
});
|
|
1080
|
+
api.get("/config", (c) => {
|
|
1081
|
+
let dbName = "Database";
|
|
1082
|
+
if (dbConfig.targetUrl) {
|
|
1083
|
+
if (dbConfig.type === "sqlite") {
|
|
1084
|
+
dbName = dbConfig.targetUrl.replace("file:", "").split(/[/\\]/).pop() || dbName;
|
|
1085
|
+
} else {
|
|
1086
|
+
dbName = dbConfig.targetUrl.split("/").pop()?.split("?")[0] || "Database";
|
|
1087
|
+
}
|
|
1088
|
+
}
|
|
1089
|
+
return c.json({ success: true, data: { dbType: dbConfig.type, dbName } });
|
|
1090
|
+
});
|
|
1091
|
+
api.get("/status", async (c) => {
|
|
1092
|
+
try {
|
|
1093
|
+
const status = await adapter.getStatus();
|
|
1094
|
+
const tables = await adapter.getTables();
|
|
1095
|
+
status.totalTables = tables.length;
|
|
1096
|
+
if (status.dbType === "sqlite") {
|
|
1097
|
+
let modOs = "node:os";
|
|
1098
|
+
const os = await import(modOs);
|
|
1099
|
+
status.osMemTotal = os.totalmem();
|
|
1100
|
+
status.osMemUsed = os.totalmem() - os.freemem();
|
|
1101
|
+
status.osCpuUsage = os.loadavg()[0];
|
|
1102
|
+
}
|
|
1103
|
+
return c.json({ success: true, data: status });
|
|
1104
|
+
} catch (e) {
|
|
1105
|
+
return c.json({ success: false, error: e.message }, 500);
|
|
1106
|
+
}
|
|
1107
|
+
});
|
|
1108
|
+
api.get("/tables/stats", async (c) => {
|
|
1109
|
+
try {
|
|
1110
|
+
const tables = await adapter.getTables();
|
|
1111
|
+
const stats = {};
|
|
1112
|
+
for (const t of tables) {
|
|
1113
|
+
try {
|
|
1114
|
+
const res = await adapter.query(`SELECT COUNT(*) as c FROM ${adapter.quoteIdentifier(t)}`);
|
|
1115
|
+
if (res && res.rows && res.rows.length > 0) {
|
|
1116
|
+
const row = res.rows[0];
|
|
1117
|
+
const countVal = Object.values(row)[0];
|
|
1118
|
+
stats[t] = parseInt(String(countVal), 10) || 0;
|
|
1119
|
+
} else {
|
|
1120
|
+
stats[t] = 0;
|
|
1121
|
+
}
|
|
1122
|
+
} catch (e) {
|
|
1123
|
+
stats[t] = 0;
|
|
1124
|
+
}
|
|
1125
|
+
}
|
|
1126
|
+
return c.json({ success: true, data: stats });
|
|
1127
|
+
} catch (e) {
|
|
1128
|
+
return c.json({ success: false, error: e.message }, 500);
|
|
1129
|
+
}
|
|
1130
|
+
});
|
|
1131
|
+
api.get("/tables/:name/schema", async (c) => {
|
|
1132
|
+
const tableName = c.req.param("name");
|
|
1133
|
+
try {
|
|
1134
|
+
const schema = await adapter.getSchema(tableName);
|
|
1135
|
+
return c.json({ success: true, data: schema });
|
|
1136
|
+
} catch (e) {
|
|
1137
|
+
return c.json({ success: false, error: e.message }, 500);
|
|
1138
|
+
}
|
|
1139
|
+
});
|
|
1140
|
+
api.get("/tables/:name/indexes", async (c) => {
|
|
1141
|
+
const tableName = c.req.param("name");
|
|
1142
|
+
try {
|
|
1143
|
+
const indexes = await adapter.getIndexes(tableName);
|
|
1144
|
+
return c.json({ success: true, data: indexes });
|
|
1145
|
+
} catch (e) {
|
|
1146
|
+
return c.json({ success: false, error: e.message }, 500);
|
|
1147
|
+
}
|
|
1148
|
+
});
|
|
1149
|
+
api.get("/tables/:name/data", async (c) => {
|
|
1150
|
+
const tableName = c.req.param("name");
|
|
1151
|
+
const limit = parseInt(c.req.query("limit") || "50", 10);
|
|
1152
|
+
const offset = parseInt(c.req.query("offset") || "0", 10);
|
|
1153
|
+
const whereClause = c.req.query("where") || "";
|
|
1154
|
+
const orderCol = c.req.query("orderCol");
|
|
1155
|
+
const orderAscStr = c.req.query("orderAsc");
|
|
1156
|
+
let orderBy = void 0;
|
|
1157
|
+
if (orderCol) {
|
|
1158
|
+
orderBy = { col: orderCol, asc: orderAscStr !== "false" };
|
|
1159
|
+
}
|
|
1160
|
+
try {
|
|
1161
|
+
const data = await adapter.getData(tableName, limit, offset, whereClause, orderBy);
|
|
1162
|
+
return c.json({ success: true, data });
|
|
1163
|
+
} catch (e) {
|
|
1164
|
+
return c.json({ success: false, error: e.message }, 500);
|
|
1165
|
+
}
|
|
1166
|
+
});
|
|
1167
|
+
api.post("/query", async (c) => {
|
|
1168
|
+
try {
|
|
1169
|
+
const { sql } = await c.req.json();
|
|
1170
|
+
const result = await adapter.query(sql);
|
|
1171
|
+
return c.json({ success: true, data: result });
|
|
1172
|
+
} catch (e) {
|
|
1173
|
+
return c.json({ success: false, error: e.message }, 500);
|
|
1174
|
+
}
|
|
1175
|
+
});
|
|
1176
|
+
api.get("/tables/:name/export", async (c) => {
|
|
1177
|
+
const tableName = c.req.param("name");
|
|
1178
|
+
const format = c.req.query("format") || "csv";
|
|
1179
|
+
const whereClause = c.req.query("where") || "";
|
|
1180
|
+
const orderCol = c.req.query("orderCol");
|
|
1181
|
+
const orderAscStr = c.req.query("orderAsc");
|
|
1182
|
+
let orderBy = void 0;
|
|
1183
|
+
if (orderCol) {
|
|
1184
|
+
orderBy = { col: orderCol, asc: orderAscStr !== "false" };
|
|
1185
|
+
}
|
|
1186
|
+
try {
|
|
1187
|
+
const batchSize = 1e3;
|
|
1188
|
+
let batchOffset = 0;
|
|
1189
|
+
const allRows = [];
|
|
1190
|
+
let exportColumns = [];
|
|
1191
|
+
let lastBatch;
|
|
1192
|
+
do {
|
|
1193
|
+
lastBatch = await adapter.getData(tableName, batchSize, batchOffset, whereClause, orderBy);
|
|
1194
|
+
if (exportColumns.length === 0) exportColumns = lastBatch.columns;
|
|
1195
|
+
allRows.push(...lastBatch.rows);
|
|
1196
|
+
batchOffset += batchSize;
|
|
1197
|
+
} while (lastBatch.rows.length === batchSize);
|
|
1198
|
+
const rows = allRows;
|
|
1199
|
+
const timestamp = getDatetimeStr();
|
|
1200
|
+
const exportFilename = `${tableName}_export_${timestamp}`;
|
|
1201
|
+
if (format === "json") {
|
|
1202
|
+
const jsonStr = JSON.stringify(rows, null, 2);
|
|
1203
|
+
c.header("Content-Disposition", `attachment; filename="${exportFilename}.json"`);
|
|
1204
|
+
c.header("Content-Type", "application/json");
|
|
1205
|
+
return c.body(jsonStr);
|
|
1206
|
+
} else {
|
|
1207
|
+
let csvStr = "";
|
|
1208
|
+
if (rows.length > 0) {
|
|
1209
|
+
const headers = Object.keys(rows[0]);
|
|
1210
|
+
const headerStr = headers.join(",");
|
|
1211
|
+
const rowStrs = rows.map((r) => {
|
|
1212
|
+
return headers.map((h) => {
|
|
1213
|
+
let val = r[h];
|
|
1214
|
+
if (val === null || val === void 0) val = "";
|
|
1215
|
+
val = String(val);
|
|
1216
|
+
if (val.includes(",") || val.includes('"') || val.includes("\n")) {
|
|
1217
|
+
val = `"${val.replace(/"/g, '""')}"`;
|
|
1218
|
+
}
|
|
1219
|
+
return val;
|
|
1220
|
+
}).join(",");
|
|
1221
|
+
});
|
|
1222
|
+
csvStr = [headerStr, ...rowStrs].join("\n");
|
|
1223
|
+
}
|
|
1224
|
+
c.header("Content-Disposition", `attachment; filename="${exportFilename}.csv"`);
|
|
1225
|
+
c.header("Content-Type", "text/csv");
|
|
1226
|
+
return c.body(csvStr);
|
|
1227
|
+
}
|
|
1228
|
+
} catch (e) {
|
|
1229
|
+
return c.text(`Export Failed: ${e.message}`, 500);
|
|
1230
|
+
}
|
|
1231
|
+
});
|
|
1232
|
+
api.get("/database/export", async (c) => {
|
|
1233
|
+
try {
|
|
1234
|
+
const type = dbConfig.type;
|
|
1235
|
+
const url = dbConfig.targetUrl;
|
|
1236
|
+
let child;
|
|
1237
|
+
let filename = "backup.sql";
|
|
1238
|
+
const executeNativeDump = (cmd, args, envName) => {
|
|
1239
|
+
return new Promise((resolve, reject) => {
|
|
1240
|
+
const cp = spawn(cmd, args);
|
|
1241
|
+
let started = false;
|
|
1242
|
+
cp.on("error", (err) => {
|
|
1243
|
+
if (!started) reject(new Error(`Native tool '${cmd}' not found. Please install ${envName}.`));
|
|
1244
|
+
});
|
|
1245
|
+
setTimeout(() => {
|
|
1246
|
+
if (!cp.killed) {
|
|
1247
|
+
started = true;
|
|
1248
|
+
resolve(nodeToWebStream(cp.stdout));
|
|
1249
|
+
}
|
|
1250
|
+
}, 100);
|
|
1251
|
+
});
|
|
1252
|
+
};
|
|
1253
|
+
try {
|
|
1254
|
+
let stream;
|
|
1255
|
+
const baseName = getDbName();
|
|
1256
|
+
const timeStr = getDatetimeStr();
|
|
1257
|
+
filename = `${baseName}_backup_${timeStr}.sql`;
|
|
1258
|
+
if (type === "sqlite") {
|
|
1259
|
+
const dbPath = url.replace("file:", "");
|
|
1260
|
+
stream = await executeNativeDump("sqlite3", [dbPath, ".dump"], "SQLite CLI");
|
|
1261
|
+
} else if (type === "mysql") {
|
|
1262
|
+
const parsed = new URL(url);
|
|
1263
|
+
const user = parsed.username;
|
|
1264
|
+
const pass = parsed.password;
|
|
1265
|
+
const host = parsed.hostname;
|
|
1266
|
+
const port = parsed.port || "3306";
|
|
1267
|
+
const dbname = parsed.pathname.substring(1);
|
|
1268
|
+
stream = await executeNativeDump("mysqldump", ["-u", user, `-p${pass}`, "-h", host, "-P", port, dbname], "MySQL Client");
|
|
1269
|
+
} else if (type === "postgres") {
|
|
1270
|
+
stream = await executeNativeDump("pg_dump", [url], "PostgreSQL CLI");
|
|
1271
|
+
} else {
|
|
1272
|
+
throw new Error("Unsupported database type for native export");
|
|
1273
|
+
}
|
|
1274
|
+
c.header("Content-Disposition", `attachment; filename="${filename}"`);
|
|
1275
|
+
c.header("Content-Type", "application/sql");
|
|
1276
|
+
return c.body(stream);
|
|
1277
|
+
} catch (err) {
|
|
1278
|
+
if (err.message.includes("Native tool")) {
|
|
1279
|
+
let sql = "";
|
|
1280
|
+
const baseName = getDbName();
|
|
1281
|
+
const timeStr = getDatetimeStr();
|
|
1282
|
+
if (type === "sqlite") {
|
|
1283
|
+
sql = await generateSqliteDump(adapter);
|
|
1284
|
+
} else if (type === "mysql") {
|
|
1285
|
+
sql = await generateMysqlDump(adapter);
|
|
1286
|
+
} else {
|
|
1287
|
+
throw err;
|
|
1288
|
+
}
|
|
1289
|
+
c.header("Content-Disposition", `attachment; filename="${baseName}_backup_${timeStr}.sql"`);
|
|
1290
|
+
c.header("Content-Type", "application/sql");
|
|
1291
|
+
return c.body(sql);
|
|
1292
|
+
}
|
|
1293
|
+
throw err;
|
|
1294
|
+
}
|
|
1295
|
+
} catch (e) {
|
|
1296
|
+
const errorHtml = `<html><body><h3>Database Export Failed</h3><p>${e.message}</p></body></html>`;
|
|
1297
|
+
c.header("Content-Type", "text/html");
|
|
1298
|
+
return c.body(errorHtml);
|
|
1299
|
+
}
|
|
1300
|
+
});
|
|
1301
|
+
api.get("/database/dictionary", async (c) => {
|
|
1302
|
+
try {
|
|
1303
|
+
const dbName = getDbName();
|
|
1304
|
+
let md = `# Data Dictionary: ${dbName}
|
|
1305
|
+
|
|
1306
|
+
`;
|
|
1307
|
+
const tables = await adapter.getTables();
|
|
1308
|
+
for (const t of tables) {
|
|
1309
|
+
md += `## Table: \`${t}\`
|
|
1310
|
+
|
|
1311
|
+
`;
|
|
1312
|
+
md += `| Column | Type | PK | Nullable |
|
|
1313
|
+
|---|---|---|---|
|
|
1314
|
+
`;
|
|
1315
|
+
const schema = await adapter.getSchema(t);
|
|
1316
|
+
for (const col of schema) {
|
|
1317
|
+
md += `| ${col.name} | ${col.type} | ${col.isPk ? "Yes" : "No"} | ${col.nullable ? "Yes" : "No"} |
|
|
1318
|
+
`;
|
|
1319
|
+
}
|
|
1320
|
+
md += `
|
|
1321
|
+
`;
|
|
1322
|
+
}
|
|
1323
|
+
c.header("Content-Disposition", `attachment; filename="${dbName}_dictionary_${getDatetimeStr()}.md"`);
|
|
1324
|
+
c.header("Content-Type", "text/markdown");
|
|
1325
|
+
return c.body(md);
|
|
1326
|
+
} catch (e) {
|
|
1327
|
+
return c.text(`Data Dictionary Export Failed: ${e.message}`, 500);
|
|
1328
|
+
}
|
|
1329
|
+
});
|
|
1330
|
+
api.get("/database/schema-only", async (c) => {
|
|
1331
|
+
try {
|
|
1332
|
+
const type = dbConfig.type;
|
|
1333
|
+
const url = dbConfig.targetUrl;
|
|
1334
|
+
const filename = `${getDbName()}_schema_${getDatetimeStr()}.sql`;
|
|
1335
|
+
if (type === "sqlite") {
|
|
1336
|
+
let sqlDump = "-- Drixio SQLite Schema Dump\n\n";
|
|
1337
|
+
const tablesResult = await adapter.query("SELECT sql FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%';");
|
|
1338
|
+
for (const row of tablesResult.rows) {
|
|
1339
|
+
if (row.sql) sqlDump += `${row.sql};
|
|
1340
|
+
|
|
1341
|
+
`;
|
|
1342
|
+
}
|
|
1343
|
+
c.header("Content-Disposition", `attachment; filename="${filename}"`);
|
|
1344
|
+
c.header("Content-Type", "application/sql");
|
|
1345
|
+
return c.body(sqlDump);
|
|
1346
|
+
} else {
|
|
1347
|
+
return c.text("Schema-only export for this DB type currently requires manual DDL extraction. Coming soon!", 501);
|
|
1348
|
+
}
|
|
1349
|
+
} catch (e) {
|
|
1350
|
+
return c.text(`Schema Export Failed: ${e.message}`, 500);
|
|
1351
|
+
}
|
|
1352
|
+
});
|
|
1353
|
+
app.route("/api", api);
|
|
1354
|
+
}
|
|
1355
|
+
async function generateSqliteDump(adapter) {
|
|
1356
|
+
let sqlDump = "-- Drixio SQLite Fallback Backup\n\n";
|
|
1357
|
+
try {
|
|
1358
|
+
const tablesResult = await adapter.query("SELECT name, sql FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%';");
|
|
1359
|
+
for (const row of tablesResult.rows) {
|
|
1360
|
+
if (!row.sql) continue;
|
|
1361
|
+
sqlDump += `${row.sql};
|
|
1362
|
+
|
|
1363
|
+
`;
|
|
1364
|
+
const data = await adapter.query(`SELECT * FROM ${adapter.quoteIdentifier(row.name)}`);
|
|
1365
|
+
for (const d of data.rows) {
|
|
1366
|
+
const keys = Object.keys(d).map((k) => adapter.quoteIdentifier(k)).join(", ");
|
|
1367
|
+
const vals = Object.values(d).map((v) => {
|
|
1368
|
+
if (v === null) return "NULL";
|
|
1369
|
+
if (typeof v === "number") return v;
|
|
1370
|
+
return `'${String(v).replace(/'/g, "''")}'`;
|
|
1371
|
+
}).join(", ");
|
|
1372
|
+
sqlDump += `INSERT INTO ${adapter.quoteIdentifier(row.name)} (${keys}) VALUES (${vals});
|
|
1373
|
+
`;
|
|
1374
|
+
}
|
|
1375
|
+
sqlDump += "\n";
|
|
1376
|
+
}
|
|
1377
|
+
} catch (e) {
|
|
1378
|
+
sqlDump += `-- Error generating backup: ${e}
|
|
1379
|
+
`;
|
|
1380
|
+
}
|
|
1381
|
+
return sqlDump;
|
|
1382
|
+
}
|
|
1383
|
+
async function generateMysqlDump(adapter) {
|
|
1384
|
+
let sqlDump = "-- Drixio MySQL Fallback Backup\n\n";
|
|
1385
|
+
try {
|
|
1386
|
+
const tables = await adapter.getTables();
|
|
1387
|
+
for (const table of tables) {
|
|
1388
|
+
try {
|
|
1389
|
+
const createTableResult = await adapter.query(`SHOW CREATE TABLE ${adapter.quoteIdentifier(table)}`);
|
|
1390
|
+
if (createTableResult.rows && createTableResult.rows.length > 0) {
|
|
1391
|
+
const row = createTableResult.rows[0];
|
|
1392
|
+
const vals = Object.values(row);
|
|
1393
|
+
const createSql = row["Create Table"] || row["Create View"] || (vals.length > 1 ? vals[1] : null);
|
|
1394
|
+
if (createSql) {
|
|
1395
|
+
sqlDump += `${createSql};
|
|
1396
|
+
|
|
1397
|
+
`;
|
|
1398
|
+
}
|
|
1399
|
+
}
|
|
1400
|
+
const data = await adapter.query(`SELECT * FROM ${adapter.quoteIdentifier(table)}`);
|
|
1401
|
+
for (const d of data.rows) {
|
|
1402
|
+
const keys = Object.keys(d).map((k) => adapter.quoteIdentifier(k)).join(", ");
|
|
1403
|
+
const vals = Object.values(d).map((v) => {
|
|
1404
|
+
if (v === null) return "NULL";
|
|
1405
|
+
if (typeof v === "number") return v;
|
|
1406
|
+
let str = String(v);
|
|
1407
|
+
str = str.replace(/\\/g, "\\\\");
|
|
1408
|
+
str = str.replace(/'/g, "''");
|
|
1409
|
+
str = str.replace(/\n/g, "\\n");
|
|
1410
|
+
str = str.replace(/\r/g, "\\r");
|
|
1411
|
+
return `'${str}'`;
|
|
1412
|
+
}).join(", ");
|
|
1413
|
+
sqlDump += `INSERT INTO ${adapter.quoteIdentifier(table)} (${keys}) VALUES (${vals});
|
|
1414
|
+
`;
|
|
1415
|
+
}
|
|
1416
|
+
sqlDump += "\n";
|
|
1417
|
+
} catch (tableErr) {
|
|
1418
|
+
sqlDump += `-- Error backing up table ${table}: ${tableErr}
|
|
1419
|
+
|
|
1420
|
+
`;
|
|
1421
|
+
}
|
|
1422
|
+
}
|
|
1423
|
+
} catch (e) {
|
|
1424
|
+
sqlDump += `-- Error generating backup: ${e}
|
|
1425
|
+
`;
|
|
1426
|
+
}
|
|
1427
|
+
return sqlDump;
|
|
1428
|
+
}
|
|
1429
|
+
var init_api = __esm({
|
|
1430
|
+
"src/server/api.ts"() {
|
|
1431
|
+
"use strict";
|
|
1432
|
+
init_factory();
|
|
1433
|
+
}
|
|
1434
|
+
});
|
|
1435
|
+
|
|
1436
|
+
// src/server/index.ts
|
|
1437
|
+
var server_exports = {};
|
|
1438
|
+
__export(server_exports, {
|
|
1439
|
+
runStudio: () => runStudio
|
|
1440
|
+
});
|
|
1441
|
+
import { serve } from "@hono/node-server";
|
|
1442
|
+
import { Hono as Hono2 } from "hono";
|
|
1443
|
+
import { serveStatic } from "@hono/node-server/serve-static";
|
|
1444
|
+
import open from "open";
|
|
1445
|
+
import path4 from "path";
|
|
1446
|
+
import pc11 from "picocolors";
|
|
1447
|
+
import { fileURLToPath } from "url";
|
|
1448
|
+
async function runStudio(dbConfig) {
|
|
1449
|
+
const app = new Hono2();
|
|
1450
|
+
registerApiRoutes(app, dbConfig);
|
|
1451
|
+
const isDev = __dirname.includes("src") || __dirname.includes("server");
|
|
1452
|
+
const studioDistPath = isDev ? path4.resolve(__dirname, "../../dist/studio") : path4.resolve(__dirname, "./studio");
|
|
1453
|
+
const fs12 = await import("fs/promises");
|
|
1454
|
+
app.use("/*", serveStatic({ root: path4.relative(process.cwd(), studioDistPath) }));
|
|
1455
|
+
app.get("*", async (c) => {
|
|
1456
|
+
const indexPath = path4.join(studioDistPath, "index.html");
|
|
1457
|
+
try {
|
|
1458
|
+
const html = await fs12.readFile(indexPath, "utf-8");
|
|
1459
|
+
return c.html(html);
|
|
1460
|
+
} catch (e) {
|
|
1461
|
+
return c.text("Drixio Studio static files not found. Did you run build?", 404);
|
|
1462
|
+
}
|
|
1463
|
+
});
|
|
1464
|
+
const defaultPort = process.env.PORT ? parseInt(process.env.PORT, 10) : 51213;
|
|
1465
|
+
const port = await getAvailablePort(defaultPort);
|
|
1466
|
+
console.log(pc11.cyan(`
|
|
1467
|
+
Starting Drixio Studio on http://localhost:${port}...`));
|
|
1468
|
+
console.log(pc11.dim(`Press Ctrl+C to stop the server.
|
|
1469
|
+
`));
|
|
1470
|
+
serve({
|
|
1471
|
+
fetch: app.fetch,
|
|
1472
|
+
port
|
|
1473
|
+
});
|
|
1474
|
+
await open(`http://localhost:${port}`);
|
|
1475
|
+
}
|
|
1476
|
+
async function getAvailablePort(startPort) {
|
|
1477
|
+
const net = await import("net");
|
|
1478
|
+
let currentPort = startPort;
|
|
1479
|
+
while (true) {
|
|
1480
|
+
const isAvailable = await new Promise((resolve) => {
|
|
1481
|
+
const server = net.createServer();
|
|
1482
|
+
server.unref();
|
|
1483
|
+
server.on("error", () => resolve(false));
|
|
1484
|
+
server.listen(currentPort, () => {
|
|
1485
|
+
server.close(() => resolve(true));
|
|
1486
|
+
});
|
|
1487
|
+
});
|
|
1488
|
+
if (isAvailable) return currentPort;
|
|
1489
|
+
currentPort++;
|
|
1490
|
+
}
|
|
1491
|
+
}
|
|
1492
|
+
var __filename, __dirname;
|
|
1493
|
+
var init_server = __esm({
|
|
1494
|
+
"src/server/index.ts"() {
|
|
1495
|
+
"use strict";
|
|
1496
|
+
init_api();
|
|
1497
|
+
__filename = fileURLToPath(import.meta.url);
|
|
1498
|
+
__dirname = path4.dirname(__filename);
|
|
1499
|
+
}
|
|
1500
|
+
});
|
|
1501
|
+
|
|
1502
|
+
// subcommands/backup.ts
|
|
1503
|
+
var backup_exports = {};
|
|
1504
|
+
__export(backup_exports, {
|
|
1505
|
+
runBackupCommand: () => runBackupCommand
|
|
1506
|
+
});
|
|
1507
|
+
import pc12 from "picocolors";
|
|
1508
|
+
import fs4 from "fs/promises";
|
|
1509
|
+
import path5 from "path";
|
|
1510
|
+
async function runBackupCommand(dbConfig) {
|
|
1511
|
+
if (dbConfig.type === "unknown") {
|
|
1512
|
+
console.log(pc12.red("Error: No database connection found. Cannot run backup."));
|
|
1513
|
+
process.exit(1);
|
|
1514
|
+
}
|
|
1515
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
1516
|
+
const backupDir = path5.join(process.cwd(), `drixio_backup_${timestamp}`);
|
|
1517
|
+
console.log(pc12.cyan(`
|
|
1518
|
+
Starting database backup...`));
|
|
1519
|
+
console.log(pc12.dim(`Database: ${dbConfig.type}`));
|
|
1520
|
+
console.log(pc12.dim(`Target: ${dbConfig.targetUrl}`));
|
|
1521
|
+
console.log(pc12.dim(`Backup Directory: ${backupDir}
|
|
1522
|
+
`));
|
|
1523
|
+
await fs4.mkdir(backupDir, { recursive: true });
|
|
1524
|
+
if (dbConfig.type === "sqlite") {
|
|
1525
|
+
try {
|
|
1526
|
+
const targetFileName = path5.basename(dbConfig.targetUrl) || "database.sqlite";
|
|
1527
|
+
const destFile = path5.join(backupDir, targetFileName);
|
|
1528
|
+
await fs4.copyFile(dbConfig.targetUrl, destFile);
|
|
1529
|
+
console.log(pc12.green(`\u2714 Copied raw SQLite file to ${destFile}`));
|
|
1530
|
+
} catch (e) {
|
|
1531
|
+
console.log(pc12.red(`\u2718 Failed to copy SQLite file: ${e.message}`));
|
|
1532
|
+
}
|
|
1533
|
+
}
|
|
1534
|
+
const adapter = createDBAdapter(dbConfig);
|
|
1535
|
+
try {
|
|
1536
|
+
const tables = await adapter.getTables();
|
|
1537
|
+
if (tables.length === 0) {
|
|
1538
|
+
console.log(pc12.yellow("No tables found to backup."));
|
|
1539
|
+
}
|
|
1540
|
+
for (const table of tables) {
|
|
1541
|
+
try {
|
|
1542
|
+
const schema = await adapter.getSchema(table);
|
|
1543
|
+
const batchSize = 1e3;
|
|
1544
|
+
let batchOffset = 0;
|
|
1545
|
+
const allRows = [];
|
|
1546
|
+
let lastBatch;
|
|
1547
|
+
do {
|
|
1548
|
+
lastBatch = await adapter.getData(table, batchSize, batchOffset);
|
|
1549
|
+
allRows.push(...lastBatch.rows);
|
|
1550
|
+
batchOffset += batchSize;
|
|
1551
|
+
} while (lastBatch.rows.length === batchSize);
|
|
1552
|
+
const dumpObj = {
|
|
1553
|
+
table,
|
|
1554
|
+
schema,
|
|
1555
|
+
totalRows: allRows.length,
|
|
1556
|
+
data: allRows
|
|
1557
|
+
};
|
|
1558
|
+
const fp = path5.join(backupDir, `${table}.json`);
|
|
1559
|
+
await fs4.writeFile(fp, JSON.stringify(dumpObj, null, 2), "utf-8");
|
|
1560
|
+
console.log(pc12.green(`\u2714 Dumped table: ${table} (${allRows.length} rows)`));
|
|
1561
|
+
} catch (e) {
|
|
1562
|
+
console.log(pc12.red(`\u2718 Failed to dump table ${table}: ${e.message}`));
|
|
1563
|
+
}
|
|
1564
|
+
}
|
|
1565
|
+
} catch (e) {
|
|
1566
|
+
console.log(pc12.red(`
|
|
1567
|
+
Backup Error: ${e.message}`));
|
|
1568
|
+
} finally {
|
|
1569
|
+
await adapter.close();
|
|
1570
|
+
}
|
|
1571
|
+
console.log(pc12.cyan(`
|
|
1572
|
+
Backup successfully completed at ${backupDir}`));
|
|
1573
|
+
process.exit(0);
|
|
1574
|
+
}
|
|
1575
|
+
var init_backup = __esm({
|
|
1576
|
+
"subcommands/backup.ts"() {
|
|
1577
|
+
"use strict";
|
|
1578
|
+
init_factory();
|
|
1579
|
+
}
|
|
1580
|
+
});
|
|
1581
|
+
|
|
1582
|
+
// subcommands/import.ts
|
|
1583
|
+
var import_exports = {};
|
|
1584
|
+
__export(import_exports, {
|
|
1585
|
+
runImportCommand: () => runImportCommand
|
|
1586
|
+
});
|
|
1587
|
+
import pc13 from "picocolors";
|
|
1588
|
+
import fs5 from "fs/promises";
|
|
1589
|
+
import path6 from "path";
|
|
1590
|
+
function parseCSV(text) {
|
|
1591
|
+
const lines = text.split(/\r?\n/).filter((line) => line.trim() !== "");
|
|
1592
|
+
if (lines.length === 0) return [];
|
|
1593
|
+
const parseLine = (line) => {
|
|
1594
|
+
const result = [];
|
|
1595
|
+
let current = "";
|
|
1596
|
+
let inQuotes = false;
|
|
1597
|
+
for (let i = 0; i < line.length; i++) {
|
|
1598
|
+
const char = line[i];
|
|
1599
|
+
if (char === '"' && line[i + 1] === '"') {
|
|
1600
|
+
current += '"';
|
|
1601
|
+
i++;
|
|
1602
|
+
} else if (char === '"') {
|
|
1603
|
+
inQuotes = !inQuotes;
|
|
1604
|
+
} else if (char === "," && !inQuotes) {
|
|
1605
|
+
result.push(current);
|
|
1606
|
+
current = "";
|
|
1607
|
+
} else {
|
|
1608
|
+
current += char;
|
|
1609
|
+
}
|
|
1610
|
+
}
|
|
1611
|
+
result.push(current);
|
|
1612
|
+
return result;
|
|
1613
|
+
};
|
|
1614
|
+
const headers = parseLine(lines[0]);
|
|
1615
|
+
const rows = [];
|
|
1616
|
+
for (let i = 1; i < lines.length; i++) {
|
|
1617
|
+
const values = parseLine(lines[i]);
|
|
1618
|
+
const row = {};
|
|
1619
|
+
headers.forEach((h, index) => {
|
|
1620
|
+
row[h] = values[index] ?? "";
|
|
1621
|
+
});
|
|
1622
|
+
rows.push(row);
|
|
1623
|
+
}
|
|
1624
|
+
return rows;
|
|
1625
|
+
}
|
|
1626
|
+
async function runImportCommand(dbConfig, args, options) {
|
|
1627
|
+
if (dbConfig.type === "unknown") {
|
|
1628
|
+
console.log(pc13.red("Error: No database connection found. Cannot run import."));
|
|
1629
|
+
process.exit(1);
|
|
1630
|
+
}
|
|
1631
|
+
const adapter = createDBAdapter(dbConfig);
|
|
1632
|
+
let filePath = args[0];
|
|
1633
|
+
let tableName = options.table;
|
|
1634
|
+
const { input: input7, select: select11 } = await import("@inquirer/prompts");
|
|
1635
|
+
if (!filePath) {
|
|
1636
|
+
filePath = await input7({ message: "Enter the path to your CSV or JSON file:" });
|
|
1637
|
+
}
|
|
1638
|
+
const resolvedPath = path6.resolve(process.cwd(), filePath);
|
|
1639
|
+
try {
|
|
1640
|
+
await fs5.access(resolvedPath);
|
|
1641
|
+
} catch {
|
|
1642
|
+
console.log(pc13.red(`Error: File not found at ${resolvedPath}`));
|
|
1643
|
+
process.exit(1);
|
|
1644
|
+
}
|
|
1645
|
+
if (!tableName) {
|
|
1646
|
+
const allTables = await adapter.getTables();
|
|
1647
|
+
if (allTables.length === 0) {
|
|
1648
|
+
console.log(pc13.yellow("No tables found. Please create a table first."));
|
|
1649
|
+
process.exit(1);
|
|
1650
|
+
}
|
|
1651
|
+
tableName = await select11({
|
|
1652
|
+
message: "Which table do you want to import data into?",
|
|
1653
|
+
choices: allTables.map((t) => ({ name: t, value: t }))
|
|
1654
|
+
});
|
|
1655
|
+
}
|
|
1656
|
+
console.log(pc13.cyan(`
|
|
1657
|
+
Reading file...`));
|
|
1658
|
+
const fileContent = await fs5.readFile(resolvedPath, "utf-8");
|
|
1659
|
+
let rowsToInsert = [];
|
|
1660
|
+
if (filePath.toLowerCase().endsWith(".json")) {
|
|
1661
|
+
try {
|
|
1662
|
+
rowsToInsert = JSON.parse(fileContent);
|
|
1663
|
+
if (!Array.isArray(rowsToInsert)) {
|
|
1664
|
+
throw new Error("JSON root must be an array of objects.");
|
|
1665
|
+
}
|
|
1666
|
+
} catch (e) {
|
|
1667
|
+
console.log(pc13.red(`Invalid JSON format: ${e.message}`));
|
|
1668
|
+
process.exit(1);
|
|
1669
|
+
}
|
|
1670
|
+
} else if (filePath.toLowerCase().endsWith(".csv")) {
|
|
1671
|
+
rowsToInsert = parseCSV(fileContent);
|
|
1672
|
+
} else {
|
|
1673
|
+
console.log(pc13.red("Error: Unsupported file extension. Please provide a .csv or .json file."));
|
|
1674
|
+
process.exit(1);
|
|
1675
|
+
}
|
|
1676
|
+
if (rowsToInsert.length === 0) {
|
|
1677
|
+
console.log(pc13.yellow("No data found to import."));
|
|
1678
|
+
process.exit(0);
|
|
1679
|
+
}
|
|
1680
|
+
console.log(pc13.cyan(`Importing ${rowsToInsert.length} rows into '${tableName}'...`));
|
|
1681
|
+
const CHUNK_SIZE = 500;
|
|
1682
|
+
let inserted = 0;
|
|
1683
|
+
try {
|
|
1684
|
+
for (let i = 0; i < rowsToInsert.length; i += CHUNK_SIZE) {
|
|
1685
|
+
const chunk = rowsToInsert.slice(i, i + CHUNK_SIZE);
|
|
1686
|
+
await adapter.insert(tableName, chunk);
|
|
1687
|
+
inserted += chunk.length;
|
|
1688
|
+
process.stdout.write(`\r${pc13.dim(`Progress: ${inserted} / ${rowsToInsert.length}`)}`);
|
|
1689
|
+
}
|
|
1690
|
+
console.log(pc13.green(`
|
|
1691
|
+
|
|
1692
|
+
\u2714 Successfully imported ${inserted} rows into ${tableName}!`));
|
|
1693
|
+
} catch (e) {
|
|
1694
|
+
console.log(pc13.red(`
|
|
1695
|
+
\u2718 Import failed at row ${inserted}: ${e.message}`));
|
|
1696
|
+
} finally {
|
|
1697
|
+
await adapter.close();
|
|
1698
|
+
}
|
|
1699
|
+
process.exit(0);
|
|
1700
|
+
}
|
|
1701
|
+
var init_import = __esm({
|
|
1702
|
+
"subcommands/import.ts"() {
|
|
1703
|
+
"use strict";
|
|
1704
|
+
init_factory();
|
|
1705
|
+
}
|
|
1706
|
+
});
|
|
1707
|
+
|
|
1708
|
+
// subcommands/seed.ts
|
|
1709
|
+
var seed_exports = {};
|
|
1710
|
+
__export(seed_exports, {
|
|
1711
|
+
runSeedCommand: () => runSeedCommand
|
|
1712
|
+
});
|
|
1713
|
+
import pc14 from "picocolors";
|
|
1714
|
+
function generateRandomString(length) {
|
|
1715
|
+
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
|
1716
|
+
let result = "";
|
|
1717
|
+
for (let i = 0; i < length; i++) result += chars.charAt(Math.floor(Math.random() * chars.length));
|
|
1718
|
+
return result;
|
|
1719
|
+
}
|
|
1720
|
+
function generateRandomEmail() {
|
|
1721
|
+
const domains = ["gmail.com", "yahoo.com", "outlook.com", "example.com"];
|
|
1722
|
+
const name = generateRandomString(8).toLowerCase();
|
|
1723
|
+
const domain = domains[Math.floor(Math.random() * domains.length)];
|
|
1724
|
+
return `${name}@${domain}`;
|
|
1725
|
+
}
|
|
1726
|
+
function generateFakeData(col) {
|
|
1727
|
+
const name = col.name.toLowerCase();
|
|
1728
|
+
if (name.includes("email")) return generateRandomEmail();
|
|
1729
|
+
if (name.includes("phone")) return `+1${Math.floor(Math.random() * 9e9 + 1e9)}`;
|
|
1730
|
+
if (name.includes("name")) return `User_${generateRandomString(5)}`;
|
|
1731
|
+
if (name.includes("url") || name.includes("link")) return `https://example.com/${generateRandomString(6)}`;
|
|
1732
|
+
if (name.includes("date") || name.includes("time") || name.includes("created") || name.includes("updated")) {
|
|
1733
|
+
const d = new Date(Date.now() - Math.floor(Math.random() * 1e10));
|
|
1734
|
+
return d.toISOString().replace("T", " ").slice(0, 19);
|
|
1735
|
+
}
|
|
1736
|
+
const type = col.type.toLowerCase();
|
|
1737
|
+
if (type.includes("int") || type.includes("num") || type.includes("float") || type.includes("double")) {
|
|
1738
|
+
return Math.floor(Math.random() * 1e3);
|
|
1739
|
+
}
|
|
1740
|
+
if (type.includes("bool") || type === "tinyint(1)") {
|
|
1741
|
+
return Math.random() > 0.5;
|
|
1742
|
+
}
|
|
1743
|
+
if (type.includes("char") || type.includes("text") || type.includes("string")) {
|
|
1744
|
+
return generateRandomString(10);
|
|
1745
|
+
}
|
|
1746
|
+
return generateRandomString(5);
|
|
1747
|
+
}
|
|
1748
|
+
async function runSeedCommand(dbConfig, args) {
|
|
1749
|
+
if (dbConfig.type === "unknown") {
|
|
1750
|
+
console.log(pc14.red("Error: No database connection found. Cannot run seed."));
|
|
1751
|
+
process.exit(1);
|
|
1752
|
+
}
|
|
1753
|
+
const adapter = createDBAdapter(dbConfig);
|
|
1754
|
+
let tableName = args[0];
|
|
1755
|
+
let countStr = args[1];
|
|
1756
|
+
const { input: input7, select: select11 } = await import("@inquirer/prompts");
|
|
1757
|
+
if (!tableName) {
|
|
1758
|
+
const allTables = await adapter.getTables();
|
|
1759
|
+
if (allTables.length === 0) {
|
|
1760
|
+
console.log(pc14.yellow("No tables found. Please create a table first."));
|
|
1761
|
+
process.exit(1);
|
|
1762
|
+
}
|
|
1763
|
+
tableName = await select11({
|
|
1764
|
+
message: "Which table do you want to seed with fake data?",
|
|
1765
|
+
choices: allTables.map((t) => ({ name: t, value: t }))
|
|
1766
|
+
});
|
|
1767
|
+
}
|
|
1768
|
+
let count = parseInt(countStr);
|
|
1769
|
+
if (isNaN(count) || count <= 0) {
|
|
1770
|
+
const res = await input7({ message: "How many rows to generate?", default: "50" });
|
|
1771
|
+
count = parseInt(res);
|
|
1772
|
+
if (isNaN(count) || count <= 0) {
|
|
1773
|
+
console.log(pc14.red("Error: Invalid count number."));
|
|
1774
|
+
process.exit(1);
|
|
1775
|
+
}
|
|
1776
|
+
}
|
|
1777
|
+
console.log(pc14.cyan(`
|
|
1778
|
+
Analyzing schema for table '${tableName}'...`));
|
|
1779
|
+
let schema;
|
|
1780
|
+
try {
|
|
1781
|
+
schema = await adapter.getSchema(tableName);
|
|
1782
|
+
} catch (e) {
|
|
1783
|
+
console.log(pc14.red(`Error: ${e.message}`));
|
|
1784
|
+
process.exit(1);
|
|
1785
|
+
}
|
|
1786
|
+
const targetCols = schema.filter((c) => !c.isPk);
|
|
1787
|
+
if (targetCols.length === 0) {
|
|
1788
|
+
console.log(pc14.yellow("Table only has Primary Key columns. Seeding might fail if they don't auto-increment."));
|
|
1789
|
+
}
|
|
1790
|
+
console.log(pc14.cyan(`Generating ${count} records...`));
|
|
1791
|
+
const rowsToInsert = [];
|
|
1792
|
+
for (let i = 0; i < count; i++) {
|
|
1793
|
+
const row = {};
|
|
1794
|
+
for (const col of targetCols.length > 0 ? targetCols : schema) {
|
|
1795
|
+
row[col.name] = generateFakeData(col);
|
|
1796
|
+
}
|
|
1797
|
+
rowsToInsert.push(row);
|
|
1798
|
+
}
|
|
1799
|
+
const CHUNK_SIZE = 500;
|
|
1800
|
+
let inserted = 0;
|
|
1801
|
+
try {
|
|
1802
|
+
if (dbConfig.type === "sqlite") await adapter.executeSql("PRAGMA foreign_keys = OFF;");
|
|
1803
|
+
else if (dbConfig.type === "mysql") await adapter.executeSql("SET FOREIGN_KEY_CHECKS = 0;");
|
|
1804
|
+
else if (dbConfig.type === "postgres") await adapter.executeSql("SET session_replication_role = replica;");
|
|
1805
|
+
for (let i = 0; i < rowsToInsert.length; i += CHUNK_SIZE) {
|
|
1806
|
+
const chunk = rowsToInsert.slice(i, i + CHUNK_SIZE);
|
|
1807
|
+
await adapter.insert(tableName, chunk);
|
|
1808
|
+
inserted += chunk.length;
|
|
1809
|
+
process.stdout.write(`\r${pc14.dim(`Progress: ${inserted} / ${count}`)}`);
|
|
1810
|
+
}
|
|
1811
|
+
console.log(pc14.green(`
|
|
1812
|
+
|
|
1813
|
+
\u2714 Successfully seeded ${inserted} fake records into ${tableName}!`));
|
|
1814
|
+
} catch (e) {
|
|
1815
|
+
console.log(pc14.red(`
|
|
1816
|
+
\u2718 Seed failed: ${e.message}`));
|
|
1817
|
+
} finally {
|
|
1818
|
+
try {
|
|
1819
|
+
if (dbConfig.type === "sqlite") await adapter.executeSql("PRAGMA foreign_keys = ON;");
|
|
1820
|
+
else if (dbConfig.type === "mysql") await adapter.executeSql("SET FOREIGN_KEY_CHECKS = 1;");
|
|
1821
|
+
else if (dbConfig.type === "postgres") await adapter.executeSql("SET session_replication_role = DEFAULT;");
|
|
1822
|
+
} catch (e) {
|
|
1823
|
+
}
|
|
1824
|
+
await adapter.close();
|
|
1825
|
+
}
|
|
1826
|
+
process.exit(0);
|
|
1827
|
+
}
|
|
1828
|
+
var init_seed = __esm({
|
|
1829
|
+
"subcommands/seed.ts"() {
|
|
1830
|
+
"use strict";
|
|
1831
|
+
init_factory();
|
|
1832
|
+
}
|
|
1833
|
+
});
|
|
1834
|
+
|
|
1835
|
+
// subcommands/diagram.ts
|
|
1836
|
+
var diagram_exports = {};
|
|
1837
|
+
__export(diagram_exports, {
|
|
1838
|
+
runDiagramCommand: () => runDiagramCommand
|
|
1839
|
+
});
|
|
1840
|
+
import pc15 from "picocolors";
|
|
1841
|
+
import fs6 from "fs/promises";
|
|
1842
|
+
import path7 from "path";
|
|
1843
|
+
async function runDiagramCommand(dbConfig) {
|
|
1844
|
+
if (dbConfig.type === "unknown") {
|
|
1845
|
+
console.log(pc15.red("Error: No database connection found. Cannot generate diagram."));
|
|
1846
|
+
process.exit(1);
|
|
1847
|
+
}
|
|
1848
|
+
const adapter = createDBAdapter(dbConfig);
|
|
1849
|
+
console.log(pc15.cyan(`
|
|
1850
|
+
Scanning database to generate ER diagram...`));
|
|
1851
|
+
try {
|
|
1852
|
+
const allTables = await adapter.getTables();
|
|
1853
|
+
if (allTables.length === 0) {
|
|
1854
|
+
console.log(pc15.yellow("No tables found in the database."));
|
|
1855
|
+
process.exit(0);
|
|
1856
|
+
}
|
|
1857
|
+
let mermaidCode = "erDiagram\n";
|
|
1858
|
+
for (const table of allTables) {
|
|
1859
|
+
mermaidCode += ` ${table} {
|
|
1860
|
+
`;
|
|
1861
|
+
const schema = await adapter.getSchema(table);
|
|
1862
|
+
for (const col of schema) {
|
|
1863
|
+
const safeType = col.type.replace(/\s+/g, "_").replace(/[^a-zA-Z0-9_]/g, "");
|
|
1864
|
+
const pk = col.isPk ? " PK" : "";
|
|
1865
|
+
mermaidCode += ` ${safeType} ${col.name}${pk}
|
|
1866
|
+
`;
|
|
1867
|
+
}
|
|
1868
|
+
mermaidCode += ` }
|
|
1869
|
+
`;
|
|
1870
|
+
}
|
|
1871
|
+
const markdownOutput = `
|
|
102
1872
|
# Database Schema Diagram
|
|
103
1873
|
|
|
104
1874
|
You can paste this Mermaid code directly into [Draw.io](https://app.diagrams.net/) (Arrange > Insert > Advanced > Mermaid) or view it on GitHub/GitLab!
|
|
105
1875
|
|
|
106
1876
|
\`\`\`mermaid
|
|
107
|
-
${
|
|
1877
|
+
${mermaidCode}
|
|
108
1878
|
\`\`\`
|
|
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
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
1879
|
+
`;
|
|
1880
|
+
const outPath = path7.resolve(process.cwd(), "drixio_schema.md");
|
|
1881
|
+
await fs6.writeFile(outPath, markdownOutput.trim(), "utf-8");
|
|
1882
|
+
console.log(pc15.green(`
|
|
1883
|
+
\u2714 Diagram generated successfully!`));
|
|
1884
|
+
console.log(pc15.white(`Output saved to: ${pc15.bold(outPath)}`));
|
|
1885
|
+
console.log(pc15.dim("Tip: Open this file in VSCode with a Markdown viewer or paste it into Draw.io."));
|
|
1886
|
+
} catch (e) {
|
|
1887
|
+
console.log(pc15.red(`
|
|
1888
|
+
\u2718 Failed to generate diagram: ${e.message}`));
|
|
1889
|
+
} finally {
|
|
1890
|
+
await adapter.close();
|
|
1891
|
+
}
|
|
1892
|
+
process.exit(0);
|
|
1893
|
+
}
|
|
1894
|
+
var init_diagram = __esm({
|
|
1895
|
+
"subcommands/diagram.ts"() {
|
|
1896
|
+
"use strict";
|
|
1897
|
+
init_factory();
|
|
1898
|
+
}
|
|
1899
|
+
});
|
|
1900
|
+
|
|
1901
|
+
// subcommands/exec.ts
|
|
1902
|
+
var exec_exports = {};
|
|
1903
|
+
__export(exec_exports, {
|
|
1904
|
+
runExecCommand: () => runExecCommand
|
|
1905
|
+
});
|
|
1906
|
+
import pc16 from "picocolors";
|
|
1907
|
+
import fs7 from "fs/promises";
|
|
1908
|
+
import path8 from "path";
|
|
1909
|
+
async function runExecCommand(dbConfig, args) {
|
|
1910
|
+
if (dbConfig.type === "unknown") {
|
|
1911
|
+
console.log(pc16.red("Error: No database connection found. Cannot run exec."));
|
|
1912
|
+
process.exit(1);
|
|
1913
|
+
}
|
|
1914
|
+
let filePath = args[0];
|
|
1915
|
+
const { input: input7 } = await import("@inquirer/prompts");
|
|
1916
|
+
if (!filePath) {
|
|
1917
|
+
filePath = await input7({ message: "Enter the path to your .sql file:" });
|
|
1918
|
+
}
|
|
1919
|
+
const resolvedPath = path8.resolve(process.cwd(), filePath);
|
|
1920
|
+
try {
|
|
1921
|
+
await fs7.access(resolvedPath);
|
|
1922
|
+
} catch {
|
|
1923
|
+
console.log(pc16.red(`Error: File not found at ${resolvedPath}`));
|
|
1924
|
+
process.exit(1);
|
|
1925
|
+
}
|
|
1926
|
+
const sqlContent = await fs7.readFile(resolvedPath, "utf-8");
|
|
1927
|
+
if (!sqlContent.trim()) {
|
|
1928
|
+
console.log(pc16.yellow("File is empty."));
|
|
1929
|
+
process.exit(0);
|
|
1930
|
+
}
|
|
1931
|
+
const adapter = createDBAdapter(dbConfig);
|
|
1932
|
+
console.log(pc16.cyan(`
|
|
1933
|
+
Executing SQL script from ${filePath}...`));
|
|
1934
|
+
try {
|
|
1935
|
+
await adapter.executeSql(sqlContent);
|
|
1936
|
+
console.log(pc16.green(`\u2714 Script executed successfully!`));
|
|
1937
|
+
} catch (e) {
|
|
1938
|
+
console.log(pc16.red(`\u2718 Execution failed: ${e.message}`));
|
|
1939
|
+
} finally {
|
|
1940
|
+
await adapter.close();
|
|
1941
|
+
}
|
|
1942
|
+
process.exit(0);
|
|
1943
|
+
}
|
|
1944
|
+
var init_exec = __esm({
|
|
1945
|
+
"subcommands/exec.ts"() {
|
|
1946
|
+
"use strict";
|
|
1947
|
+
init_factory();
|
|
1948
|
+
}
|
|
1949
|
+
});
|
|
1950
|
+
|
|
1951
|
+
// subcommands/generateTypes.ts
|
|
1952
|
+
var generateTypes_exports = {};
|
|
1953
|
+
__export(generateTypes_exports, {
|
|
1954
|
+
runGenerateTypesCommand: () => runGenerateTypesCommand
|
|
1955
|
+
});
|
|
1956
|
+
import pc17 from "picocolors";
|
|
1957
|
+
import fs8 from "fs/promises";
|
|
1958
|
+
import path9 from "path";
|
|
1959
|
+
function mapSqlTypeToTs(sqlType) {
|
|
1960
|
+
const type = sqlType.toLowerCase();
|
|
1961
|
+
if (type.includes("int") || type.includes("num") || type.includes("float") || type.includes("double") || type.includes("real")) {
|
|
1962
|
+
return "number";
|
|
1963
|
+
}
|
|
1964
|
+
if (type.includes("bool") || type === "tinyint(1)") {
|
|
1965
|
+
return "boolean";
|
|
1966
|
+
}
|
|
1967
|
+
if (type.includes("char") || type.includes("text") || type.includes("uuid") || type.includes("string")) {
|
|
1968
|
+
return "string";
|
|
1969
|
+
}
|
|
1970
|
+
if (type.includes("date") || type.includes("time")) {
|
|
1971
|
+
return "Date";
|
|
1972
|
+
}
|
|
1973
|
+
if (type.includes("json")) {
|
|
1974
|
+
return "any";
|
|
1975
|
+
}
|
|
1976
|
+
return "string";
|
|
1977
|
+
}
|
|
1978
|
+
async function runGenerateTypesCommand(dbConfig) {
|
|
1979
|
+
if (dbConfig.type === "unknown") {
|
|
1980
|
+
console.log(pc17.red("Error: No database connection found. Cannot generate types."));
|
|
1981
|
+
process.exit(1);
|
|
1982
|
+
}
|
|
1983
|
+
const adapter = createDBAdapter(dbConfig);
|
|
1984
|
+
console.log(pc17.cyan(`
|
|
1985
|
+
Scanning database to generate TypeScript interfaces...`));
|
|
1986
|
+
try {
|
|
1987
|
+
const allTables = await adapter.getTables();
|
|
1988
|
+
if (allTables.length === 0) {
|
|
1989
|
+
console.log(pc17.yellow("No tables found in the database."));
|
|
1990
|
+
process.exit(0);
|
|
1991
|
+
}
|
|
1992
|
+
let tsCode = `// Generated by Drixio CLI
|
|
1993
|
+
// Database: ${dbConfig.type.toUpperCase()}
|
|
1994
|
+
|
|
1995
|
+
`;
|
|
1996
|
+
for (const table of allTables) {
|
|
1997
|
+
const interfaceName = table.charAt(0).toUpperCase() + table.slice(1).replace(/_([a-z])/g, (g) => g[1].toUpperCase());
|
|
1998
|
+
tsCode += `export interface ${interfaceName} {
|
|
1999
|
+
`;
|
|
2000
|
+
const schema = await adapter.getSchema(table);
|
|
2001
|
+
for (const col of schema) {
|
|
2002
|
+
const tsType = mapSqlTypeToTs(col.type);
|
|
2003
|
+
const optionalFlag = col.nullable ? "?" : "";
|
|
2004
|
+
tsCode += ` ${col.name}${optionalFlag}: ${tsType};
|
|
2005
|
+
`;
|
|
2006
|
+
}
|
|
2007
|
+
tsCode += `}
|
|
2008
|
+
|
|
2009
|
+
`;
|
|
2010
|
+
}
|
|
2011
|
+
const outPath = path9.resolve(process.cwd(), "drixio-types.d.ts");
|
|
2012
|
+
await fs8.writeFile(outPath, tsCode.trim() + "\n", "utf-8");
|
|
2013
|
+
console.log(pc17.green(`\u2714 TypeScript interfaces generated successfully!`));
|
|
2014
|
+
console.log(pc17.white(`Output saved to: ${pc17.bold(outPath)}`));
|
|
2015
|
+
} catch (e) {
|
|
2016
|
+
console.log(pc17.red(`
|
|
2017
|
+
\u2718 Failed to generate types: ${e.message}`));
|
|
2018
|
+
} finally {
|
|
2019
|
+
await adapter.close();
|
|
2020
|
+
}
|
|
2021
|
+
process.exit(0);
|
|
2022
|
+
}
|
|
2023
|
+
var init_generateTypes = __esm({
|
|
2024
|
+
"subcommands/generateTypes.ts"() {
|
|
2025
|
+
"use strict";
|
|
2026
|
+
init_factory();
|
|
2027
|
+
}
|
|
2028
|
+
});
|
|
2029
|
+
|
|
2030
|
+
// subcommands/init.ts
|
|
2031
|
+
var init_exports = {};
|
|
2032
|
+
__export(init_exports, {
|
|
2033
|
+
runInitCommand: () => runInitCommand
|
|
2034
|
+
});
|
|
2035
|
+
import pc18 from "picocolors";
|
|
2036
|
+
import fs9 from "fs/promises";
|
|
2037
|
+
import path10 from "path";
|
|
2038
|
+
async function runInitCommand(args) {
|
|
2039
|
+
const { select: select11, input: input7, password } = await import("@inquirer/prompts");
|
|
2040
|
+
let dialect = args[0];
|
|
2041
|
+
if (!dialect || !["sqlite", "mysql", "postgres"].includes(dialect.toLowerCase())) {
|
|
2042
|
+
dialect = await select11({
|
|
2043
|
+
message: "Which database do you want to initialize locally?",
|
|
2044
|
+
choices: [
|
|
2045
|
+
{ name: "SQLite (Local File)", value: "sqlite" },
|
|
2046
|
+
{ name: "MySQL (Local Server)", value: "mysql" },
|
|
2047
|
+
{ name: "PostgreSQL (Local Server)", value: "postgres" }
|
|
2048
|
+
]
|
|
2049
|
+
});
|
|
2050
|
+
}
|
|
2051
|
+
dialect = dialect.toLowerCase();
|
|
2052
|
+
console.log(pc18.cyan(`
|
|
2053
|
+
Initializing a local ${dialect} database...`));
|
|
2054
|
+
let dbUrl = "";
|
|
2055
|
+
if (dialect === "sqlite") {
|
|
2056
|
+
let dbName = args[1] || "database.sqlite";
|
|
2057
|
+
if (!dbName.endsWith(".sqlite") && !dbName.endsWith(".db")) {
|
|
2058
|
+
dbName += ".sqlite";
|
|
2059
|
+
}
|
|
2060
|
+
const dbPath = path10.resolve(process.cwd(), dbName);
|
|
2061
|
+
try {
|
|
2062
|
+
await fs9.access(dbPath);
|
|
2063
|
+
console.log(pc18.yellow(`File ${dbName} already exists.`));
|
|
2064
|
+
} catch {
|
|
2065
|
+
await fs9.writeFile(dbPath, "");
|
|
2066
|
+
console.log(pc18.green(`\u2714 Created local database file: ${dbName}`));
|
|
2067
|
+
}
|
|
2068
|
+
dbUrl = `file:${dbName}`;
|
|
2069
|
+
} else {
|
|
2070
|
+
console.log(
|
|
2071
|
+
pc18.dim(
|
|
2072
|
+
"Please provide credentials for your local server (e.g. running via XAMPP, Homebrew, etc.)"
|
|
2073
|
+
)
|
|
2074
|
+
);
|
|
2075
|
+
const host = await input7({ message: "Server Host:", default: "localhost" });
|
|
2076
|
+
const port = await input7({
|
|
2077
|
+
message: "Server Port:",
|
|
2078
|
+
default: dialect === "mysql" ? "3306" : "5432"
|
|
2079
|
+
});
|
|
2080
|
+
const user = await input7({
|
|
2081
|
+
message: "Username:",
|
|
2082
|
+
default: dialect === "mysql" ? "root" : "postgres"
|
|
2083
|
+
});
|
|
2084
|
+
const pass = await password({ message: "Password (leave empty if none):" });
|
|
2085
|
+
let dbName = args[1];
|
|
2086
|
+
if (!dbName) {
|
|
2087
|
+
dbName = await input7({ message: "New Database Name (e.g. my_project):" });
|
|
2088
|
+
}
|
|
2089
|
+
if (!dbName || !/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(dbName)) {
|
|
2090
|
+
console.log(
|
|
2091
|
+
pc18.red(
|
|
2092
|
+
"Invalid database name. Please use only letters, numbers, and underscores."
|
|
2093
|
+
)
|
|
2094
|
+
);
|
|
2095
|
+
process.exit(1);
|
|
2096
|
+
}
|
|
2097
|
+
console.log(
|
|
2098
|
+
pc18.cyan(`
|
|
2099
|
+
Connecting to local server to create database '${dbName}'...`)
|
|
2100
|
+
);
|
|
2101
|
+
try {
|
|
2102
|
+
if (dialect === "mysql") {
|
|
2103
|
+
const mysql2 = await import("mysql2/promise");
|
|
2104
|
+
const conn = await mysql2.createConnection({
|
|
2105
|
+
host,
|
|
2106
|
+
port: parseInt(port),
|
|
2107
|
+
user,
|
|
2108
|
+
password: pass
|
|
2109
|
+
});
|
|
2110
|
+
await conn.query(`CREATE DATABASE IF NOT EXISTS \`${dbName}\``);
|
|
2111
|
+
await conn.end();
|
|
2112
|
+
} else if (dialect === "postgres") {
|
|
2113
|
+
const pg2 = await import("pg");
|
|
2114
|
+
const conn = new pg2.Client({
|
|
2115
|
+
host,
|
|
2116
|
+
port: parseInt(port),
|
|
2117
|
+
user,
|
|
2118
|
+
password: pass,
|
|
2119
|
+
database: "postgres"
|
|
2120
|
+
// Explicitly connect to default maintenance db
|
|
2121
|
+
});
|
|
2122
|
+
await conn.connect();
|
|
2123
|
+
const res = await conn.query(
|
|
2124
|
+
"SELECT 1 FROM pg_database WHERE datname = $1",
|
|
2125
|
+
[dbName]
|
|
2126
|
+
);
|
|
2127
|
+
if (res.rowCount === 0) {
|
|
2128
|
+
await conn.query(`CREATE DATABASE "${dbName}"`);
|
|
2129
|
+
}
|
|
2130
|
+
await conn.end();
|
|
2131
|
+
}
|
|
2132
|
+
console.log(pc18.green(`\u2714 Created local database: ${dbName}`));
|
|
2133
|
+
} catch (e) {
|
|
2134
|
+
console.log(
|
|
2135
|
+
pc18.red(`\u2718 Failed to create database on server: ${e.message}`)
|
|
2136
|
+
);
|
|
2137
|
+
console.log(
|
|
2138
|
+
pc18.dim(
|
|
2139
|
+
`
|
|
2140
|
+
Could not connect to the local ${dialect} server on ${host}:${port}.`
|
|
2141
|
+
)
|
|
2142
|
+
);
|
|
2143
|
+
if (e.code === "ECONNREFUSED" || e.message.includes("ECONNREFUSED") || e.message.includes("connect")) {
|
|
2144
|
+
console.log(
|
|
2145
|
+
pc18.yellow(
|
|
2146
|
+
`
|
|
2147
|
+
\u{1F4A1} It seems you don't have ${dialect} installed or running locally.`
|
|
2148
|
+
)
|
|
2149
|
+
);
|
|
2150
|
+
if (dialect === "mysql") {
|
|
2151
|
+
console.log(
|
|
2152
|
+
pc18.white(
|
|
2153
|
+
`\u{1F449} Download MySQL here: ${pc18.cyan("https://dev.mysql.com/downloads/installer/")}`
|
|
2154
|
+
)
|
|
2155
|
+
);
|
|
2156
|
+
} else if (dialect === "postgres") {
|
|
2157
|
+
console.log(
|
|
2158
|
+
pc18.white(
|
|
2159
|
+
`\u{1F449} Download PostgreSQL here: ${pc18.cyan("https://www.postgresql.org/download/")}`
|
|
2160
|
+
)
|
|
2161
|
+
);
|
|
2162
|
+
console.log(
|
|
2163
|
+
pc18.white(
|
|
2164
|
+
`\u{1F449} Or use Postgres.app for Mac: ${pc18.cyan("https://postgresapp.com/")}`
|
|
2165
|
+
)
|
|
2166
|
+
);
|
|
2167
|
+
}
|
|
2168
|
+
console.log(
|
|
2169
|
+
pc18.dim(
|
|
2170
|
+
`(Alternatively, you can run 'drixio init sqlite' for a zero-install local database!)`
|
|
2171
|
+
)
|
|
2172
|
+
);
|
|
2173
|
+
}
|
|
2174
|
+
process.exit(1);
|
|
2175
|
+
}
|
|
2176
|
+
const scheme = dialect === "postgres" ? "postgres://" : "mysql://";
|
|
2177
|
+
const auth = pass ? `${user}:${pass}` : `${user}`;
|
|
2178
|
+
dbUrl = `${scheme}${auth}@${host}:${port}/${dbName}`;
|
|
2179
|
+
}
|
|
2180
|
+
const envPath = path10.resolve(process.cwd(), ".env");
|
|
2181
|
+
let envContent = "";
|
|
2182
|
+
try {
|
|
2183
|
+
envContent = await fs9.readFile(envPath, "utf-8");
|
|
2184
|
+
} catch {
|
|
2185
|
+
}
|
|
2186
|
+
if (envContent.includes("DATABASE_URL=")) {
|
|
2187
|
+
envContent = envContent.replace(
|
|
2188
|
+
/DATABASE_URL=.*/g,
|
|
2189
|
+
`DATABASE_URL=${dbUrl}`
|
|
2190
|
+
);
|
|
2191
|
+
console.log(
|
|
2192
|
+
pc18.yellow(`\u2714 Updated existing .env file with new DATABASE_URL`)
|
|
2193
|
+
);
|
|
2194
|
+
} else {
|
|
2195
|
+
envContent += (envContent.endsWith("\n") || envContent.length === 0 ? "" : "\n") + `DATABASE_URL=${dbUrl}
|
|
2196
|
+
`;
|
|
2197
|
+
console.log(pc18.green(`\u2714 Saved connection string to .env file`));
|
|
2198
|
+
}
|
|
2199
|
+
await fs9.writeFile(envPath, envContent, "utf-8");
|
|
2200
|
+
console.log(pc18.green(`
|
|
2201
|
+
\u{1F389} Initialization Complete!`));
|
|
2202
|
+
console.log(
|
|
2203
|
+
pc18.white(
|
|
2204
|
+
`You can now run ${pc18.bold("npx drixio")} to manage it!`
|
|
2205
|
+
)
|
|
2206
|
+
);
|
|
2207
|
+
process.exit(0);
|
|
2208
|
+
}
|
|
2209
|
+
var init_init = __esm({
|
|
2210
|
+
"subcommands/init.ts"() {
|
|
2211
|
+
"use strict";
|
|
2212
|
+
}
|
|
2213
|
+
});
|
|
2214
|
+
|
|
2215
|
+
// subcommands/drop.ts
|
|
2216
|
+
var drop_exports = {};
|
|
2217
|
+
__export(drop_exports, {
|
|
2218
|
+
runDropDbCommand: () => runDropDbCommand
|
|
2219
|
+
});
|
|
2220
|
+
import pc19 from "picocolors";
|
|
2221
|
+
import fs10 from "fs/promises";
|
|
2222
|
+
import path11 from "path";
|
|
2223
|
+
async function runDropDbCommand(args) {
|
|
2224
|
+
const { select: select11, input: input7, password, confirm: confirm4 } = await import("@inquirer/prompts");
|
|
2225
|
+
let dialect = args[0];
|
|
2226
|
+
if (!dialect || !["sqlite", "mysql", "postgres"].includes(dialect.toLowerCase())) {
|
|
2227
|
+
dialect = await select11({
|
|
2228
|
+
message: "Which database type do you want to drop?",
|
|
2229
|
+
choices: [
|
|
2230
|
+
{ name: "SQLite (Local File)", value: "sqlite" },
|
|
2231
|
+
{ name: "MySQL (Local Server)", value: "mysql" },
|
|
2232
|
+
{ name: "PostgreSQL (Local Server)", value: "postgres" }
|
|
2233
|
+
]
|
|
2234
|
+
});
|
|
2235
|
+
}
|
|
2236
|
+
dialect = dialect.toLowerCase();
|
|
2237
|
+
let dbName = args[1];
|
|
2238
|
+
if (dialect === "sqlite") {
|
|
2239
|
+
if (!dbName) {
|
|
2240
|
+
dbName = await input7({ message: "Enter the SQLite filename to delete (e.g. database.sqlite):", default: "database.sqlite" });
|
|
2241
|
+
}
|
|
2242
|
+
if (!dbName.endsWith(".sqlite") && !dbName.endsWith(".db")) {
|
|
2243
|
+
dbName += ".sqlite";
|
|
2244
|
+
}
|
|
2245
|
+
const sure = await confirm4({ message: `Are you absolutely sure you want to delete ${dbName}? This cannot be undone!`, default: false });
|
|
2246
|
+
if (!sure) {
|
|
2247
|
+
console.log(pc19.yellow("Aborted."));
|
|
2248
|
+
process.exit(0);
|
|
2249
|
+
}
|
|
2250
|
+
const dbPath = path11.resolve(process.cwd(), dbName);
|
|
2251
|
+
try {
|
|
2252
|
+
await fs10.unlink(dbPath);
|
|
2253
|
+
console.log(pc19.green(`\u2714 Deleted local database file: ${dbName}`));
|
|
2254
|
+
} catch (e) {
|
|
2255
|
+
console.log(pc19.red(`\u2718 Failed to delete file: ${e.message}`));
|
|
2256
|
+
}
|
|
2257
|
+
} else {
|
|
2258
|
+
console.log(pc19.dim(`Please provide credentials for your local ${dialect} server to drop a database.`));
|
|
2259
|
+
const host = await input7({ message: "Server Host:", default: "localhost" });
|
|
2260
|
+
const port = await input7({ message: "Server Port:", default: dialect === "mysql" ? "3306" : "5432" });
|
|
2261
|
+
const user = await input7({ message: "Username:", default: dialect === "mysql" ? "root" : "postgres" });
|
|
2262
|
+
const pass = await password({ message: "Password (leave empty if none):" });
|
|
2263
|
+
if (!dbName) {
|
|
2264
|
+
dbName = await input7({ message: "Which Database Name do you want to DROP?" });
|
|
2265
|
+
}
|
|
2266
|
+
const sure = await confirm4({ message: `Are you absolutely sure you want to DROP DATABASE '${dbName}' from ${host}? All data will be lost!`, default: false });
|
|
2267
|
+
if (!sure) {
|
|
2268
|
+
console.log(pc19.yellow("Aborted."));
|
|
2269
|
+
process.exit(0);
|
|
2270
|
+
}
|
|
2271
|
+
console.log(pc19.cyan(`
|
|
2272
|
+
Connecting to local server to drop database '${dbName}'...`));
|
|
2273
|
+
try {
|
|
2274
|
+
if (dialect === "mysql") {
|
|
2275
|
+
const mysql2 = await import("mysql2/promise");
|
|
2276
|
+
const conn = await mysql2.createConnection({ host, port: parseInt(port), user, password: pass });
|
|
2277
|
+
await conn.query(`DROP DATABASE IF EXISTS \`${dbName}\``);
|
|
2278
|
+
await conn.end();
|
|
2279
|
+
} else if (dialect === "postgres") {
|
|
2280
|
+
const pg2 = await import("pg");
|
|
2281
|
+
const conn = new pg2.Client({ host, port: parseInt(port), user, password: pass, database: "postgres" });
|
|
2282
|
+
await conn.connect();
|
|
2283
|
+
await conn.query(`DROP DATABASE IF EXISTS "${dbName}"`);
|
|
2284
|
+
await conn.end();
|
|
2285
|
+
}
|
|
2286
|
+
console.log(pc19.green(`\u2714 Dropped database: ${dbName}`));
|
|
2287
|
+
} catch (e) {
|
|
2288
|
+
console.log(pc19.red(`\u2718 Failed to drop database on server: ${e.message}`));
|
|
2289
|
+
process.exit(1);
|
|
2290
|
+
}
|
|
2291
|
+
}
|
|
2292
|
+
process.exit(0);
|
|
2293
|
+
}
|
|
2294
|
+
var init_drop = __esm({
|
|
2295
|
+
"subcommands/drop.ts"() {
|
|
2296
|
+
"use strict";
|
|
2297
|
+
}
|
|
2298
|
+
});
|
|
2299
|
+
|
|
2300
|
+
// src/cli/menus/tableManager.ts
|
|
2301
|
+
import { select as select6, Separator as Separator3 } from "@inquirer/prompts";
|
|
2302
|
+
import pc20 from "picocolors";
|
|
2303
|
+
var selectTableManager;
|
|
2304
|
+
var init_tableManager = __esm({
|
|
2305
|
+
"src/cli/menus/tableManager.ts"() {
|
|
2306
|
+
"use strict";
|
|
2307
|
+
selectTableManager = async () => await select6({
|
|
2308
|
+
message: "Table Manager - Choose an action:",
|
|
2309
|
+
theme: {
|
|
2310
|
+
prefix: pc20.cyan("\u2713 "),
|
|
2311
|
+
icon: {
|
|
2312
|
+
cursor: pc20.cyan("\u203A ")
|
|
2313
|
+
},
|
|
2314
|
+
style: {
|
|
2315
|
+
message: (text) => pc20.bold(pc20.white(text)),
|
|
2316
|
+
highlight: (text) => {
|
|
2317
|
+
const clean = text.replace(/\x1b\[[0-9;]*m/g, "");
|
|
2318
|
+
return clean.includes(" Back") ? pc20.red(clean) : pc20.cyan(clean);
|
|
2319
|
+
}
|
|
2320
|
+
}
|
|
2321
|
+
},
|
|
2322
|
+
choices: [
|
|
2323
|
+
{
|
|
2324
|
+
name: " Create New Table",
|
|
2325
|
+
value: "create",
|
|
2326
|
+
description: "Build a new database table."
|
|
2327
|
+
},
|
|
2328
|
+
{
|
|
2329
|
+
name: " Modify Existing Table",
|
|
2330
|
+
value: "modify",
|
|
2331
|
+
description: "Modify an existing table's schema structure."
|
|
2332
|
+
},
|
|
2333
|
+
{
|
|
2334
|
+
name: " Drop Table",
|
|
2335
|
+
value: "drop",
|
|
2336
|
+
description: "Delete an entire table and its data."
|
|
2337
|
+
},
|
|
2338
|
+
new Separator3(),
|
|
2339
|
+
{
|
|
2340
|
+
name: pc20.dim(" Back"),
|
|
2341
|
+
value: "back",
|
|
2342
|
+
description: "Back to the main menu."
|
|
2343
|
+
}
|
|
2344
|
+
]
|
|
2345
|
+
});
|
|
2346
|
+
}
|
|
2347
|
+
});
|
|
2348
|
+
|
|
2349
|
+
// src/cli/wizards/columnWizard.ts
|
|
2350
|
+
import { input as input3, select as select7, confirm } from "@inquirer/prompts";
|
|
2351
|
+
import pc21 from "picocolors";
|
|
2352
|
+
async function promptColumnSchema(adapter, hasPkSoFar, existingColumns = [], defaultName = "") {
|
|
2353
|
+
let colName = "";
|
|
2354
|
+
while (!colName) {
|
|
2355
|
+
colName = await input3({
|
|
2356
|
+
message: "Column name (leave empty to cancel/finish):",
|
|
2357
|
+
default: defaultName
|
|
2358
|
+
});
|
|
2359
|
+
if (!colName.trim()) {
|
|
2360
|
+
return { col: null, isPk: false };
|
|
2361
|
+
}
|
|
2362
|
+
if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(colName)) {
|
|
2363
|
+
console.log(pc21.red("Invalid column name. Use only letters, numbers, and underscores."));
|
|
2364
|
+
colName = "";
|
|
2365
|
+
} else if (existingColumns.includes(colName)) {
|
|
2366
|
+
console.log(pc21.red("Column name already exists!"));
|
|
2367
|
+
colName = "";
|
|
2368
|
+
}
|
|
2369
|
+
}
|
|
2370
|
+
const colType = await select7({
|
|
2371
|
+
message: `Select type for column '${colName}':`,
|
|
2372
|
+
choices: [
|
|
2373
|
+
{ name: "Integer", value: "Integer" },
|
|
2374
|
+
{ name: "Text (String)", value: "Text" },
|
|
2375
|
+
{ name: "Boolean", value: "Boolean" },
|
|
2376
|
+
{ name: "Decimal (Float)", value: "Decimal" },
|
|
2377
|
+
{ name: "DateTime", value: "DateTime" },
|
|
2378
|
+
{ name: "Enum", value: "Enum" }
|
|
2379
|
+
]
|
|
2380
|
+
});
|
|
2381
|
+
let enumValues = void 0;
|
|
2382
|
+
if (colType === "Enum") {
|
|
2383
|
+
const enumStr = await input3({
|
|
2384
|
+
message: `Enter comma-separated Enum values (e.g. active, pending, deleted):`
|
|
2385
|
+
});
|
|
2386
|
+
enumValues = enumStr.split(",").map((s) => s.trim()).filter((s) => s);
|
|
2387
|
+
if (enumValues.length === 0) {
|
|
2388
|
+
console.log(pc21.yellow("Warning: No enum values provided."));
|
|
2389
|
+
}
|
|
2390
|
+
}
|
|
2391
|
+
let defaultValue = "";
|
|
2392
|
+
if (colType === "DateTime") {
|
|
2393
|
+
const isTimestamp = await confirm({
|
|
2394
|
+
message: `Set default to CURRENT_TIMESTAMP?`,
|
|
2395
|
+
default: false
|
|
2396
|
+
});
|
|
2397
|
+
if (isTimestamp) {
|
|
2398
|
+
defaultValue = "Timestamp";
|
|
2399
|
+
}
|
|
2400
|
+
}
|
|
2401
|
+
let isPk = false;
|
|
2402
|
+
let fkTarget = void 0;
|
|
2403
|
+
if (colType !== "DateTime") {
|
|
2404
|
+
const keyChoices = [{ name: "None", value: "none" }];
|
|
2405
|
+
if (!hasPkSoFar) {
|
|
2406
|
+
keyChoices.push({ name: "Primary Key", value: "pk" });
|
|
2407
|
+
}
|
|
2408
|
+
keyChoices.push({ name: "Foreign Key", value: "fk" });
|
|
2409
|
+
const keyType = await select7({
|
|
2410
|
+
message: `Key type for '${colName}':`,
|
|
2411
|
+
choices: keyChoices
|
|
2412
|
+
});
|
|
2413
|
+
if (keyType === "pk") {
|
|
2414
|
+
isPk = true;
|
|
2415
|
+
if (colType === "Integer") defaultValue = "AutoInc";
|
|
2416
|
+
} else if (keyType === "fk") {
|
|
2417
|
+
const allTables = await adapter.getTables();
|
|
2418
|
+
if (allTables.length > 0) {
|
|
2419
|
+
const targetTable = await select7({
|
|
2420
|
+
message: "Select target table:",
|
|
2421
|
+
choices: allTables.map((t) => ({ name: t, value: t }))
|
|
2422
|
+
});
|
|
2423
|
+
const targetSchema = await adapter.getSchema(targetTable);
|
|
2424
|
+
if (targetSchema.length > 0) {
|
|
2425
|
+
const targetCol = await select7({
|
|
2426
|
+
message: "Select target column:",
|
|
2427
|
+
choices: targetSchema.map((c) => ({
|
|
2428
|
+
name: `${c.name} (${c.type})`,
|
|
2429
|
+
value: c.name
|
|
2430
|
+
}))
|
|
2431
|
+
});
|
|
2432
|
+
fkTarget = { table: targetTable, column: targetCol };
|
|
2433
|
+
defaultValue = `FK -> ${targetTable}.${targetCol}`;
|
|
2434
|
+
} else {
|
|
2435
|
+
console.log(pc21.yellow("Target table has no columns."));
|
|
2436
|
+
}
|
|
2437
|
+
} else {
|
|
2438
|
+
console.log(pc21.yellow("No other tables found to link to."));
|
|
2439
|
+
}
|
|
2440
|
+
}
|
|
2441
|
+
}
|
|
2442
|
+
let nullable = false;
|
|
2443
|
+
if (!isPk) {
|
|
2444
|
+
nullable = await confirm({
|
|
2445
|
+
message: `Can '${colName}' be NULL?`,
|
|
2446
|
+
default: true
|
|
2447
|
+
});
|
|
2448
|
+
}
|
|
2449
|
+
if (!isPk && !fkTarget && colType !== "DateTime") {
|
|
2450
|
+
if (colType === "Boolean") {
|
|
2451
|
+
const defaultBool = await select7({
|
|
2452
|
+
message: `Default value for '${colName}':`,
|
|
2453
|
+
choices: [
|
|
2454
|
+
{ name: "None", value: "" },
|
|
2455
|
+
{ name: "TRUE", value: "TRUE" },
|
|
2456
|
+
{ name: "FALSE", value: "FALSE" }
|
|
2457
|
+
]
|
|
2458
|
+
});
|
|
2459
|
+
defaultValue = defaultBool;
|
|
2460
|
+
} else if (colType === "Enum" && enumValues) {
|
|
2461
|
+
const defaultEnum = await select7({
|
|
2462
|
+
message: `Default value for '${colName}':`,
|
|
2463
|
+
choices: [
|
|
2464
|
+
{ name: "None", value: "" },
|
|
2465
|
+
...enumValues.map((v) => ({ name: v, value: `'${v}'` }))
|
|
2466
|
+
]
|
|
2467
|
+
});
|
|
2468
|
+
defaultValue = defaultEnum;
|
|
2469
|
+
} else {
|
|
2470
|
+
const defaultStr = await input3({
|
|
2471
|
+
message: `Default value for '${colName}' (leave empty for none):`
|
|
2472
|
+
});
|
|
2473
|
+
if (defaultStr) {
|
|
2474
|
+
if (colType === "Text") {
|
|
2475
|
+
defaultValue = `'${defaultStr.replace(/'/g, "''")}'`;
|
|
2476
|
+
} else {
|
|
2477
|
+
defaultValue = defaultStr;
|
|
2478
|
+
}
|
|
2479
|
+
}
|
|
2480
|
+
}
|
|
2481
|
+
}
|
|
2482
|
+
const col = {
|
|
2483
|
+
name: colName,
|
|
2484
|
+
type: colType,
|
|
2485
|
+
isPk,
|
|
2486
|
+
nullable,
|
|
2487
|
+
defaultValue,
|
|
2488
|
+
fkTarget,
|
|
2489
|
+
enumValues
|
|
2490
|
+
};
|
|
2491
|
+
return { col, isPk };
|
|
2492
|
+
}
|
|
2493
|
+
var init_columnWizard = __esm({
|
|
2494
|
+
"src/cli/wizards/columnWizard.ts"() {
|
|
2495
|
+
"use strict";
|
|
2496
|
+
}
|
|
2497
|
+
});
|
|
2498
|
+
|
|
2499
|
+
// src/cli/wizards/buildTable.ts
|
|
2500
|
+
import { input as input4, confirm as confirm2 } from "@inquirer/prompts";
|
|
2501
|
+
import pc22 from "picocolors";
|
|
2502
|
+
async function runWizard(dbConfig) {
|
|
2503
|
+
const adapter = createDBAdapter(dbConfig);
|
|
2504
|
+
let tableName = "";
|
|
2505
|
+
while (!tableName) {
|
|
2506
|
+
tableName = await input4({
|
|
2507
|
+
message: "Enter the new table name:"
|
|
2508
|
+
});
|
|
2509
|
+
if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(tableName)) {
|
|
2510
|
+
console.log(
|
|
2511
|
+
pc22.red(
|
|
2512
|
+
"Invalid table name. Use only letters, numbers, and underscores."
|
|
2513
|
+
)
|
|
2514
|
+
);
|
|
2515
|
+
tableName = "";
|
|
2516
|
+
}
|
|
2517
|
+
}
|
|
2518
|
+
const columns = [];
|
|
2519
|
+
let hasPk = false;
|
|
2520
|
+
let addMore = true;
|
|
2521
|
+
while (addMore) {
|
|
2522
|
+
printCurrentColumns(tableName, columns);
|
|
2523
|
+
const existingColumns = columns.map((c) => c.name);
|
|
2524
|
+
const { col, isPk } = await promptColumnSchema(adapter, hasPk, existingColumns);
|
|
2525
|
+
if (!col) {
|
|
2526
|
+
break;
|
|
2527
|
+
}
|
|
2528
|
+
if (isPk) hasPk = true;
|
|
2529
|
+
columns.push(col);
|
|
2530
|
+
addMore = await confirm2({
|
|
2531
|
+
theme: {
|
|
2532
|
+
prefix: pc22.cyan("\n\u2713 "),
|
|
2533
|
+
style: {
|
|
2534
|
+
message: (text) => pc22.bold(pc22.white(text)),
|
|
2535
|
+
highlight: (text) => {
|
|
2536
|
+
const clean = text.replace(/\x1b\[[0-9;]*m/g, "");
|
|
2537
|
+
return pc22.cyan(clean);
|
|
2538
|
+
}
|
|
2539
|
+
}
|
|
2540
|
+
},
|
|
2541
|
+
message: "Add another column?",
|
|
2542
|
+
default: true
|
|
2543
|
+
});
|
|
2544
|
+
}
|
|
2545
|
+
printCurrentColumns(tableName, columns);
|
|
2546
|
+
console.log(pc22.dim("Generating SQL..."));
|
|
2547
|
+
const dialect = getDialect(dbConfig.type);
|
|
2548
|
+
const sql = dialect.buildCreateTable(tableName, columns);
|
|
2549
|
+
console.log(pc22.cyan("\n--- SQL Preview ---"));
|
|
2550
|
+
console.log(pc22.yellow(sql));
|
|
2551
|
+
console.log(pc22.cyan("-------------------\n"));
|
|
2552
|
+
const proceed = await confirm2({
|
|
2553
|
+
message: "Do you want to execute this SQL to build the table?",
|
|
2554
|
+
default: true
|
|
2555
|
+
});
|
|
2556
|
+
if (proceed) {
|
|
2557
|
+
try {
|
|
2558
|
+
console.log(pc22.dim("Executing SQL..."));
|
|
2559
|
+
await adapter.executeSql(sql);
|
|
2560
|
+
console.log(pc22.green(`
|
|
2561
|
+
\u2713 Table '${tableName}' created successfully!`));
|
|
2562
|
+
} catch (e) {
|
|
2563
|
+
console.log(pc22.red(`
|
|
2564
|
+
x Error creating table: ${e.message}`));
|
|
2565
|
+
} finally {
|
|
2566
|
+
await adapter.close();
|
|
2567
|
+
}
|
|
2568
|
+
} else {
|
|
2569
|
+
console.log(pc22.yellow("Aborted table creation."));
|
|
2570
|
+
await adapter.close();
|
|
2571
|
+
}
|
|
2572
|
+
await waitForEnter2();
|
|
2573
|
+
}
|
|
2574
|
+
async function waitForEnter2() {
|
|
2575
|
+
const { input: input7 } = await import("@inquirer/prompts");
|
|
2576
|
+
await input7({
|
|
2577
|
+
message: "Press Enter to continue..."
|
|
2578
|
+
});
|
|
2579
|
+
}
|
|
2580
|
+
function printCurrentColumns(tableName, columns) {
|
|
2581
|
+
console.clear();
|
|
2582
|
+
console.log("\n");
|
|
2583
|
+
const colWidths = {
|
|
2584
|
+
name: 20,
|
|
2585
|
+
type: 18,
|
|
2586
|
+
key: 8,
|
|
2587
|
+
nullable: 10,
|
|
2588
|
+
defaultCol: 16
|
|
2589
|
+
};
|
|
2590
|
+
const totalWidth = colWidths.name + colWidths.type + colWidths.key + colWidths.nullable + colWidths.defaultCol + 14;
|
|
2591
|
+
console.log(pc22.cyan(`\u2554${"\u2550".repeat(totalWidth)}\u2557`));
|
|
2592
|
+
const spacesNeeded = totalWidth - tableName.length;
|
|
2593
|
+
const leftSpace = Math.max(0, Math.floor(spacesNeeded / 2));
|
|
2594
|
+
const rightSpace = Math.max(0, spacesNeeded - leftSpace);
|
|
2595
|
+
const titleContent = " ".repeat(leftSpace) + pc22.bold(pc22.white(tableName)) + " ".repeat(rightSpace);
|
|
2596
|
+
console.log(pc22.cyan("\u2551") + titleContent + pc22.cyan("\u2551"));
|
|
2597
|
+
const drawDivider = (left, mid, right) => {
|
|
2598
|
+
return pc22.cyan(
|
|
2599
|
+
left + "\u2550".repeat(colWidths.name + 2) + mid + "\u2550".repeat(colWidths.type + 2) + mid + "\u2550".repeat(colWidths.key + 2) + mid + "\u2550".repeat(colWidths.nullable + 2) + mid + "\u2550".repeat(colWidths.defaultCol + 2) + right
|
|
2600
|
+
);
|
|
2601
|
+
};
|
|
2602
|
+
console.log(drawDivider("\u2560", "\u2566", "\u2563"));
|
|
2603
|
+
console.log(
|
|
2604
|
+
pc22.cyan("\u2551 ") + pc22.bold(pc22.white("Column Name".padEnd(colWidths.name))) + pc22.cyan(" \u2551 ") + pc22.bold(pc22.white("Type".padEnd(colWidths.type))) + pc22.cyan(" \u2551 ") + pc22.bold(pc22.white("Key".padEnd(colWidths.key))) + pc22.cyan(" \u2551 ") + pc22.bold(pc22.white("Nullable".padEnd(colWidths.nullable))) + pc22.cyan(" \u2551 ") + pc22.bold(pc22.white("Default".padEnd(colWidths.defaultCol))) + pc22.cyan(" \u2551")
|
|
2605
|
+
);
|
|
2606
|
+
if (columns.length > 0) {
|
|
2607
|
+
console.log(drawDivider("\u2560", "\u256C", "\u2563"));
|
|
2608
|
+
}
|
|
2609
|
+
for (let i = 0; i < columns.length; i++) {
|
|
2610
|
+
const col = columns[i];
|
|
2611
|
+
const nameStr = pc22.white(col.name.padEnd(colWidths.name));
|
|
2612
|
+
const typeStr = pc22.white(col.type.padEnd(colWidths.type));
|
|
2613
|
+
const pkRaw = col.isPk ? "PK" : "-";
|
|
2614
|
+
const pkStr = col.isPk ? pc22.green(pkRaw.padEnd(colWidths.key)) : pc22.dim(pkRaw.padEnd(colWidths.key));
|
|
2615
|
+
const nullRaw = col.nullable ? "Yes" : "No";
|
|
2616
|
+
const nullStr = col.nullable ? pc22.green(nullRaw.padEnd(colWidths.nullable)) : pc22.dim(nullRaw.padEnd(colWidths.nullable));
|
|
2617
|
+
const extraRaw = col.defaultValue || "-";
|
|
2618
|
+
const extraStr = col.defaultValue ? pc22.yellow(extraRaw.padEnd(colWidths.defaultCol)) : pc22.dim(extraRaw.padEnd(colWidths.defaultCol));
|
|
2619
|
+
console.log(
|
|
2620
|
+
pc22.cyan("\u2551 ") + nameStr + pc22.cyan(" \u2551 ") + typeStr + pc22.cyan(" \u2551 ") + pkStr + pc22.cyan(" \u2551 ") + nullStr + pc22.cyan(" \u2551 ") + extraStr + pc22.cyan(" \u2551")
|
|
2621
|
+
);
|
|
2622
|
+
}
|
|
2623
|
+
if (columns.length === 0) {
|
|
2624
|
+
console.log(drawDivider("\u2560", "\u2569", "\u2563"));
|
|
2625
|
+
const emptyMsg = "No columns added yet";
|
|
2626
|
+
const emptyLeft = Math.max(
|
|
2627
|
+
0,
|
|
2628
|
+
Math.floor((totalWidth - emptyMsg.length) / 2)
|
|
2629
|
+
);
|
|
2630
|
+
const emptyRight = Math.max(0, totalWidth - emptyMsg.length - emptyLeft);
|
|
2631
|
+
console.log(
|
|
2632
|
+
pc22.cyan("\u2551") + " ".repeat(emptyLeft) + pc22.dim(emptyMsg) + " ".repeat(emptyRight) + pc22.cyan("\u2551")
|
|
2633
|
+
);
|
|
2634
|
+
console.log(pc22.cyan(`\u255A${"\u2550".repeat(totalWidth)}\u255D`));
|
|
2635
|
+
} else {
|
|
2636
|
+
console.log(drawDivider("\u255A", "\u2569", "\u255D"));
|
|
2637
|
+
}
|
|
2638
|
+
console.log(pc22.dim(" Leave empty the Column Name to cancel.\n"));
|
|
2639
|
+
}
|
|
2640
|
+
var init_buildTable = __esm({
|
|
2641
|
+
"src/cli/wizards/buildTable.ts"() {
|
|
2642
|
+
"use strict";
|
|
2643
|
+
init_dialect();
|
|
2644
|
+
init_factory();
|
|
2645
|
+
init_columnWizard();
|
|
2646
|
+
}
|
|
2647
|
+
});
|
|
2648
|
+
|
|
2649
|
+
// src/cli/views/sqlRunner.ts
|
|
2650
|
+
import fs11 from "fs/promises";
|
|
2651
|
+
import path12 from "path";
|
|
2652
|
+
import { input as input5 } from "@inquirer/prompts";
|
|
2653
|
+
import pc23 from "picocolors";
|
|
2654
|
+
async function runSqlRunner(dbConfig) {
|
|
2655
|
+
console.log(
|
|
2656
|
+
pc23.green(
|
|
2657
|
+
"\nInitiating SQL runner for experts. Please ensure database connection is properly configured."
|
|
2658
|
+
)
|
|
2659
|
+
);
|
|
2660
|
+
if (dbConfig.type === "unknown") {
|
|
2661
|
+
console.log(
|
|
2662
|
+
pc23.yellow(`
|
|
2663
|
+
No database connection found. Please setup first.`)
|
|
2664
|
+
);
|
|
2665
|
+
return;
|
|
2666
|
+
}
|
|
2667
|
+
try {
|
|
2668
|
+
const filePath = await input5({
|
|
2669
|
+
message: "Enter the path to your .md or .sql file:"
|
|
2670
|
+
});
|
|
2671
|
+
if (filePath && filePath.trim()) {
|
|
2672
|
+
const absolutePath = path12.resolve(process.cwd(), filePath.trim());
|
|
2673
|
+
let fileContent = "";
|
|
2674
|
+
try {
|
|
2675
|
+
fileContent = await fs11.readFile(absolutePath, "utf-8");
|
|
2676
|
+
} catch (err) {
|
|
2677
|
+
console.log(pc23.red(`
|
|
2678
|
+
x Failed to read file: ${err.message}`));
|
|
2679
|
+
return;
|
|
2680
|
+
}
|
|
2681
|
+
let sqlToExecute = fileContent;
|
|
2682
|
+
if (absolutePath.toLowerCase().endsWith(".md")) {
|
|
2683
|
+
const matches = [
|
|
2684
|
+
...fileContent.matchAll(/```(?:sql)?\n([\s\S]*?)```/gi)
|
|
2685
|
+
];
|
|
2686
|
+
if (matches.length > 0) {
|
|
2687
|
+
sqlToExecute = matches.map((m) => m[1].trim()).join("\n\n");
|
|
2688
|
+
}
|
|
2689
|
+
}
|
|
2690
|
+
if (sqlToExecute.trim()) {
|
|
2691
|
+
console.log(pc23.dim("Executing SQL..."));
|
|
2692
|
+
const adapter = createDBAdapter(dbConfig);
|
|
2693
|
+
try {
|
|
2694
|
+
await adapter.executeSql(sqlToExecute);
|
|
2695
|
+
console.log(pc23.green("\n\u2713 SQL executed successfully!"));
|
|
2696
|
+
} finally {
|
|
2697
|
+
await adapter.close();
|
|
2698
|
+
}
|
|
2699
|
+
} else {
|
|
2700
|
+
console.log(pc23.yellow("\n\u26A0\uFE0F No SQL found in the file. Aborted."));
|
|
2701
|
+
}
|
|
2702
|
+
} else {
|
|
2703
|
+
console.log(pc23.yellow("\nNo file path entered. Aborted."));
|
|
2704
|
+
}
|
|
2705
|
+
} catch (e) {
|
|
2706
|
+
console.log(pc23.red(`
|
|
2707
|
+
Error executing SQL: ${e.message}`));
|
|
2708
|
+
}
|
|
2709
|
+
}
|
|
2710
|
+
var init_sqlRunner = __esm({
|
|
2711
|
+
"src/cli/views/sqlRunner.ts"() {
|
|
2712
|
+
"use strict";
|
|
2713
|
+
init_factory();
|
|
2714
|
+
}
|
|
2715
|
+
});
|
|
2716
|
+
|
|
2717
|
+
// src/cli/wizards/modifyTable.ts
|
|
2718
|
+
import { select as select9, confirm as confirm3, input as input6 } from "@inquirer/prompts";
|
|
2719
|
+
import pc24 from "picocolors";
|
|
2720
|
+
async function runDropTable(dbConfig) {
|
|
2721
|
+
const adapter = createDBAdapter(dbConfig);
|
|
2722
|
+
try {
|
|
2723
|
+
const tables = await adapter.getTables();
|
|
2724
|
+
if (tables.length === 0) {
|
|
2725
|
+
console.log(pc24.yellow("No tables found in the database."));
|
|
2726
|
+
return;
|
|
2727
|
+
}
|
|
2728
|
+
const targetTable = await select9({
|
|
2729
|
+
message: "Select a table to drop:",
|
|
2730
|
+
choices: tables.map((t) => ({ name: t, value: t }))
|
|
2731
|
+
});
|
|
2732
|
+
const sure = await confirm3({
|
|
2733
|
+
message: `Are you sure you want to DROP TABLE '${targetTable}'? This will delete all its data!`,
|
|
2734
|
+
default: false
|
|
2735
|
+
});
|
|
2736
|
+
if (sure) {
|
|
2737
|
+
let sql = `DROP TABLE ${targetTable}`;
|
|
2738
|
+
if (dbConfig.type === "sqlite" || dbConfig.type === "postgres") {
|
|
2739
|
+
sql = `DROP TABLE "${targetTable}"`;
|
|
2740
|
+
} else if (dbConfig.type === "mysql") {
|
|
2741
|
+
sql = `DROP TABLE \`${targetTable}\``;
|
|
2742
|
+
}
|
|
2743
|
+
await adapter.executeSql(sql);
|
|
2744
|
+
console.log(pc24.green(`\u2713 Table '${targetTable}' dropped successfully.`));
|
|
2745
|
+
}
|
|
2746
|
+
} catch (e) {
|
|
2747
|
+
console.log(pc24.red(`Error: ${e.message}`));
|
|
2748
|
+
} finally {
|
|
2749
|
+
await adapter.close();
|
|
2750
|
+
}
|
|
2751
|
+
}
|
|
2752
|
+
async function runModifyTable(dbConfig, action) {
|
|
2753
|
+
const adapter = createDBAdapter(dbConfig);
|
|
2754
|
+
try {
|
|
2755
|
+
const tables = await adapter.getTables();
|
|
2756
|
+
if (tables.length === 0) {
|
|
2757
|
+
console.log(pc24.yellow("No tables found in the database."));
|
|
2758
|
+
return;
|
|
2759
|
+
}
|
|
2760
|
+
const targetTable = await select9({
|
|
2761
|
+
message: "Select a table to modify:",
|
|
2762
|
+
choices: tables.map((t) => ({ name: t, value: t }))
|
|
2763
|
+
});
|
|
2764
|
+
const schema = await adapter.getSchema(targetTable);
|
|
2765
|
+
if (action === "add") {
|
|
2766
|
+
const hasPk = schema.some((c) => c.isPk);
|
|
2767
|
+
const existingColumns = schema.map((c) => c.name);
|
|
2768
|
+
const { col } = await promptColumnSchema(adapter, hasPk, existingColumns);
|
|
2769
|
+
if (col) {
|
|
2770
|
+
const dialect = getDialect(dbConfig.type);
|
|
2771
|
+
const colSql = dialect.buildCreateTable("tmp", [col]).split("\n")[1].trim().replace(/,$/, "");
|
|
2772
|
+
let sql = `ALTER TABLE ${targetTable} ADD COLUMN ${colSql}`;
|
|
2773
|
+
if (dbConfig.type === "sqlite" || dbConfig.type === "postgres") {
|
|
2774
|
+
sql = `ALTER TABLE "${targetTable}" ADD COLUMN ${colSql}`;
|
|
2775
|
+
} else if (dbConfig.type === "mysql") {
|
|
2776
|
+
sql = `ALTER TABLE \`${targetTable}\` ADD COLUMN ${colSql}`;
|
|
2777
|
+
}
|
|
2778
|
+
await adapter.executeSql(sql);
|
|
2779
|
+
console.log(pc24.green(`\u2713 Column '${col.name}' added successfully.`));
|
|
2780
|
+
}
|
|
2781
|
+
} else if (action === "rename") {
|
|
2782
|
+
if (schema.length === 0) return console.log(pc24.yellow("Table has no columns."));
|
|
2783
|
+
const oldCol = await select9({
|
|
2784
|
+
message: "Select a column to rename:",
|
|
2785
|
+
choices: schema.map((c) => ({ name: `${c.name} (${c.type})`, value: c.name }))
|
|
2786
|
+
});
|
|
2787
|
+
const newCol = await input6({ message: `New name for '${oldCol}':` });
|
|
2788
|
+
if (newCol && newCol !== oldCol) {
|
|
2789
|
+
let sql = `ALTER TABLE ${targetTable} RENAME COLUMN ${oldCol} TO ${newCol}`;
|
|
2790
|
+
if (dbConfig.type === "sqlite" || dbConfig.type === "postgres") {
|
|
2791
|
+
sql = `ALTER TABLE "${targetTable}" RENAME COLUMN "${oldCol}" TO "${newCol}"`;
|
|
2792
|
+
} else if (dbConfig.type === "mysql") {
|
|
2793
|
+
sql = `ALTER TABLE \`${targetTable}\` RENAME COLUMN \`${oldCol}\` TO \`${newCol}\``;
|
|
2794
|
+
}
|
|
2795
|
+
await adapter.executeSql(sql);
|
|
2796
|
+
console.log(pc24.green(`\u2713 Column renamed to '${newCol}' successfully.`));
|
|
2797
|
+
}
|
|
2798
|
+
} else if (action === "modify") {
|
|
2799
|
+
if (schema.length === 0) return console.log(pc24.yellow("Table has no columns."));
|
|
2800
|
+
if (dbConfig.type === "sqlite") {
|
|
2801
|
+
console.log(pc24.yellow("SQLite does not support altering column types directly. Please recreate the table."));
|
|
2802
|
+
return;
|
|
2803
|
+
}
|
|
2804
|
+
const targetCol = await select9({
|
|
2805
|
+
message: "Select a column to modify:",
|
|
2806
|
+
choices: schema.map((c) => ({ name: `${c.name} (${c.type})`, value: c.name }))
|
|
2807
|
+
});
|
|
2808
|
+
const hasPk = schema.some((c) => c.isPk && c.name !== targetCol);
|
|
2809
|
+
const existingColumns = schema.map((c) => c.name).filter((n) => n !== targetCol);
|
|
2810
|
+
const { col } = await promptColumnSchema(adapter, hasPk, existingColumns, targetCol);
|
|
2811
|
+
if (col) {
|
|
2812
|
+
let sql = "";
|
|
2813
|
+
if (dbConfig.type === "postgres") {
|
|
2814
|
+
const pgTypeMap = {
|
|
2815
|
+
Integer: "INTEGER",
|
|
2816
|
+
Text: "TEXT",
|
|
2817
|
+
Boolean: "BOOLEAN",
|
|
2818
|
+
Decimal: "NUMERIC",
|
|
2819
|
+
DateTime: "TIMESTAMP",
|
|
2820
|
+
Enum: "TEXT"
|
|
2821
|
+
};
|
|
2822
|
+
const pgType = pgTypeMap[col.type] ?? "TEXT";
|
|
2823
|
+
const statements = [
|
|
2824
|
+
`ALTER TABLE "${targetTable}" ALTER COLUMN "${col.name}" TYPE ${pgType} USING "${col.name}"::${pgType}`
|
|
2825
|
+
];
|
|
2826
|
+
if (!col.nullable && !col.isPk) {
|
|
2827
|
+
statements.push(`ALTER TABLE "${targetTable}" ALTER COLUMN "${col.name}" SET NOT NULL`);
|
|
2828
|
+
} else {
|
|
2829
|
+
statements.push(`ALTER TABLE "${targetTable}" ALTER COLUMN "${col.name}" DROP NOT NULL`);
|
|
2830
|
+
}
|
|
2831
|
+
if (col.defaultValue && col.defaultValue !== "AutoInc" && !col.defaultValue.startsWith("FK ->")) {
|
|
2832
|
+
const defVal = col.defaultValue === "Timestamp" ? "CURRENT_TIMESTAMP" : col.defaultValue;
|
|
2833
|
+
statements.push(`ALTER TABLE "${targetTable}" ALTER COLUMN "${col.name}" SET DEFAULT ${defVal}`);
|
|
2834
|
+
} else {
|
|
2835
|
+
statements.push(`ALTER TABLE "${targetTable}" ALTER COLUMN "${col.name}" DROP DEFAULT`);
|
|
2836
|
+
}
|
|
2837
|
+
sql = statements.join(";\n");
|
|
2838
|
+
console.log(pc24.yellow("Note: Complex constraint modifications might require raw SQL in Postgres."));
|
|
2839
|
+
} else if (dbConfig.type === "mysql") {
|
|
2840
|
+
const dialect = getDialect(dbConfig.type);
|
|
2841
|
+
const colDef = dialect.buildCreateTable("tmp", [col]).split("\n")[1].trim().replace(/,$/, "");
|
|
2842
|
+
sql = `ALTER TABLE \`${targetTable}\` MODIFY COLUMN ${colDef}`;
|
|
2843
|
+
}
|
|
2844
|
+
try {
|
|
2845
|
+
await adapter.executeSql(sql);
|
|
2846
|
+
console.log(pc24.green(`\u2713 Column '${targetCol}' modified successfully.`));
|
|
2847
|
+
} catch (e) {
|
|
2848
|
+
console.log(pc24.red(`x Could not modify column automatically. Try using Raw SQL.`));
|
|
2849
|
+
console.log(pc24.dim(e.message));
|
|
2850
|
+
}
|
|
2851
|
+
}
|
|
2852
|
+
} else if (action === "delete") {
|
|
2853
|
+
if (schema.length === 0) return console.log(pc24.yellow("Table has no columns."));
|
|
2854
|
+
const targetCol = await select9({
|
|
2855
|
+
message: "Select a column to delete:",
|
|
2856
|
+
choices: schema.map((c) => ({ name: `${c.name} (${c.type})`, value: c.name }))
|
|
2857
|
+
});
|
|
2858
|
+
const sure = await confirm3({
|
|
2859
|
+
message: `Are you sure you want to delete column '${targetCol}'? Data will be lost!`,
|
|
2860
|
+
default: false
|
|
2861
|
+
});
|
|
2862
|
+
if (sure) {
|
|
2863
|
+
let sql = `ALTER TABLE ${targetTable} DROP COLUMN ${targetCol}`;
|
|
2864
|
+
if (dbConfig.type === "sqlite" || dbConfig.type === "postgres") {
|
|
2865
|
+
sql = `ALTER TABLE "${targetTable}" DROP COLUMN "${targetCol}"`;
|
|
2866
|
+
} else if (dbConfig.type === "mysql") {
|
|
2867
|
+
sql = `ALTER TABLE \`${targetTable}\` DROP COLUMN \`${targetCol}\``;
|
|
2868
|
+
}
|
|
2869
|
+
try {
|
|
2870
|
+
await adapter.executeSql(sql);
|
|
2871
|
+
console.log(pc24.green(`\u2713 Column '${targetCol}' deleted successfully.`));
|
|
2872
|
+
} catch (e) {
|
|
2873
|
+
console.log(pc24.red(`x Error deleting column: ${e.message}`));
|
|
2874
|
+
}
|
|
2875
|
+
}
|
|
2876
|
+
}
|
|
2877
|
+
} catch (e) {
|
|
2878
|
+
console.log(pc24.red(`Error: ${e.message}`));
|
|
2879
|
+
} finally {
|
|
2880
|
+
await adapter.close();
|
|
2881
|
+
}
|
|
2882
|
+
}
|
|
2883
|
+
var init_modifyTable = __esm({
|
|
2884
|
+
"src/cli/wizards/modifyTable.ts"() {
|
|
2885
|
+
"use strict";
|
|
2886
|
+
init_factory();
|
|
2887
|
+
init_dialect();
|
|
2888
|
+
init_columnWizard();
|
|
2889
|
+
}
|
|
2890
|
+
});
|
|
2891
|
+
|
|
2892
|
+
// src/cli/wizards/tableManagerFlow.ts
|
|
2893
|
+
var tableManagerFlow_exports = {};
|
|
2894
|
+
__export(tableManagerFlow_exports, {
|
|
2895
|
+
runTableManagerFlow: () => runTableManagerFlow
|
|
2896
|
+
});
|
|
2897
|
+
import pc25 from "picocolors";
|
|
2898
|
+
import { select as select10, Separator as Separator4 } from "@inquirer/prompts";
|
|
2899
|
+
async function runTableManagerFlow(dbConfig) {
|
|
2900
|
+
if (dbConfig.type === "unknown") {
|
|
2901
|
+
console.log(
|
|
2902
|
+
pc25.yellow(`
|
|
2903
|
+
No database connection found. Please setup first.`)
|
|
2904
|
+
);
|
|
2905
|
+
await waitForEnter3();
|
|
2906
|
+
return;
|
|
2907
|
+
}
|
|
2908
|
+
const adapter = await Promise.resolve().then(() => (init_factory(), factory_exports)).then(
|
|
2909
|
+
(m) => m.createDBAdapter(dbConfig)
|
|
2910
|
+
);
|
|
2911
|
+
let running = true;
|
|
2912
|
+
while (running) {
|
|
2913
|
+
console.clear();
|
|
2914
|
+
let tablesCount = "Scanning...";
|
|
2915
|
+
try {
|
|
2916
|
+
const tables = await adapter.getTables();
|
|
2917
|
+
tablesCount = `${tables.length} tables detected`;
|
|
2918
|
+
} catch (e) {
|
|
2919
|
+
tablesCount = pc25.red(e.message);
|
|
2920
|
+
}
|
|
2921
|
+
const targetVal = dbConfig.targetUrl;
|
|
2922
|
+
printCustomDashboard(` Table Builder & Manager \u2022 [${dbConfig.type.toUpperCase()}]`, [
|
|
2923
|
+
{ label: "Database", value: dbConfig.type.toUpperCase() },
|
|
2924
|
+
{ label: "Target", value: targetVal.length > 45 ? "..." + targetVal.slice(-42) : targetVal },
|
|
2925
|
+
{ label: "Tables", value: tablesCount }
|
|
2926
|
+
]);
|
|
2927
|
+
const action = await selectTableManager();
|
|
2928
|
+
switch (action) {
|
|
2929
|
+
case "create":
|
|
2930
|
+
const createMethod = await select10({
|
|
2931
|
+
message: "How do you want to create the table?",
|
|
2932
|
+
theme: {
|
|
2933
|
+
prefix: pc25.cyan("\u2713 "),
|
|
2934
|
+
icon: {
|
|
2935
|
+
cursor: pc25.cyan("\u203A ")
|
|
2936
|
+
},
|
|
2937
|
+
style: {
|
|
2938
|
+
message: (text) => pc25.bold(pc25.white(text)),
|
|
2939
|
+
highlight: (text) => {
|
|
2940
|
+
const clean = text.replace(/\x1b\[[0-9;]*m/g, "");
|
|
2941
|
+
return clean.includes("Cancel") ? pc25.red(clean) : pc25.cyan(clean);
|
|
2942
|
+
}
|
|
2943
|
+
}
|
|
2944
|
+
},
|
|
2945
|
+
choices: [
|
|
2946
|
+
{
|
|
2947
|
+
name: " Interactive Wizard (Beginner Friendly)",
|
|
2948
|
+
value: "wizard",
|
|
2949
|
+
description: "Follow the step-by-step guide to build your table."
|
|
2950
|
+
},
|
|
2951
|
+
{
|
|
2952
|
+
name: " Raw SQL (Experts)",
|
|
2953
|
+
value: "sql",
|
|
2954
|
+
description: "Write / Import your own CREATE TABLE statement."
|
|
2955
|
+
},
|
|
2956
|
+
new Separator4(),
|
|
2957
|
+
{
|
|
2958
|
+
name: pc25.dim(" Cancel"),
|
|
2959
|
+
value: "cancel",
|
|
2960
|
+
description: "Back to Table Manager"
|
|
2961
|
+
}
|
|
2962
|
+
]
|
|
2963
|
+
});
|
|
2964
|
+
if (createMethod === "wizard") await runWizard(dbConfig);
|
|
2965
|
+
else if (createMethod === "sql") await runSqlRunner(dbConfig);
|
|
2966
|
+
break;
|
|
2967
|
+
case "modify":
|
|
2968
|
+
await handleModifyMenu(dbConfig);
|
|
2969
|
+
break;
|
|
2970
|
+
case "drop":
|
|
2971
|
+
await runDropTable(dbConfig);
|
|
2972
|
+
await waitForEnter3();
|
|
2973
|
+
break;
|
|
2974
|
+
case "back":
|
|
2975
|
+
running = false;
|
|
2976
|
+
break;
|
|
2977
|
+
}
|
|
2978
|
+
}
|
|
2979
|
+
await adapter.close();
|
|
2980
|
+
}
|
|
2981
|
+
async function handleModifyMenu(dbConfig) {
|
|
2982
|
+
const action = await select10({
|
|
2983
|
+
message: "Modify Table Actions:",
|
|
2984
|
+
choices: [
|
|
2985
|
+
{ name: "Add Column", value: "add_col" },
|
|
2986
|
+
{ name: "Rename Column", value: "rename_col" },
|
|
2987
|
+
{
|
|
2988
|
+
name: "Modify Column Settings (MySQL/Postgres)",
|
|
2989
|
+
value: "mod_col",
|
|
2990
|
+
disabled: dbConfig.type === "sqlite"
|
|
2991
|
+
},
|
|
2992
|
+
{ name: "Delete Column", value: "del_col" },
|
|
2993
|
+
{ name: " Back", value: "back" }
|
|
2994
|
+
]
|
|
2995
|
+
});
|
|
2996
|
+
if (action === "back") return;
|
|
2997
|
+
switch (action) {
|
|
2998
|
+
case "add_col":
|
|
2999
|
+
await runModifyTable(dbConfig, "add");
|
|
3000
|
+
break;
|
|
3001
|
+
case "rename_col":
|
|
3002
|
+
await runModifyTable(dbConfig, "rename");
|
|
3003
|
+
break;
|
|
3004
|
+
case "mod_col":
|
|
3005
|
+
await runModifyTable(dbConfig, "modify");
|
|
3006
|
+
break;
|
|
3007
|
+
case "del_col":
|
|
3008
|
+
await runModifyTable(dbConfig, "delete");
|
|
3009
|
+
break;
|
|
3010
|
+
}
|
|
3011
|
+
await waitForEnter3();
|
|
3012
|
+
}
|
|
3013
|
+
async function waitForEnter3() {
|
|
3014
|
+
const { input: input7 } = await import("@inquirer/prompts");
|
|
3015
|
+
await input7({
|
|
3016
|
+
message: "Press Enter to continue..."
|
|
3017
|
+
});
|
|
3018
|
+
}
|
|
3019
|
+
var init_tableManagerFlow = __esm({
|
|
3020
|
+
"src/cli/wizards/tableManagerFlow.ts"() {
|
|
3021
|
+
"use strict";
|
|
3022
|
+
init_tableManager();
|
|
3023
|
+
init_buildTable();
|
|
3024
|
+
init_sqlRunner();
|
|
3025
|
+
init_modifyTable();
|
|
3026
|
+
init_logo();
|
|
3027
|
+
}
|
|
3028
|
+
});
|
|
3029
|
+
|
|
3030
|
+
// src/cli/main.ts
|
|
3031
|
+
init_logo();
|
|
3032
|
+
import pc26 from "picocolors";
|
|
3033
|
+
|
|
3034
|
+
// src/core/loader.ts
|
|
3035
|
+
import fs from "fs/promises";
|
|
3036
|
+
import path from "path";
|
|
3037
|
+
async function detectDatabase(databaseUrl) {
|
|
3038
|
+
const cwd = process.cwd();
|
|
3039
|
+
if (databaseUrl) {
|
|
3040
|
+
let type = "sqlite";
|
|
3041
|
+
if (databaseUrl.startsWith("postgres://") || databaseUrl.startsWith("postgresql://")) {
|
|
3042
|
+
type = "postgres";
|
|
3043
|
+
} else if (databaseUrl.startsWith("mysql://")) {
|
|
3044
|
+
type = "mysql";
|
|
3045
|
+
} else {
|
|
3046
|
+
type = "sqlite";
|
|
3047
|
+
}
|
|
3048
|
+
let targetUrl = databaseUrl.replace("file:", "");
|
|
3049
|
+
if (type === "sqlite" && !path.isAbsolute(targetUrl)) {
|
|
3050
|
+
targetUrl = path.resolve(cwd, targetUrl);
|
|
3051
|
+
}
|
|
3052
|
+
return {
|
|
3053
|
+
type,
|
|
3054
|
+
targetUrl,
|
|
3055
|
+
source: "manual"
|
|
3056
|
+
};
|
|
3057
|
+
}
|
|
3058
|
+
const searchDirs = [
|
|
3059
|
+
".",
|
|
3060
|
+
"prisma",
|
|
3061
|
+
"db",
|
|
3062
|
+
"database",
|
|
3063
|
+
"src/db",
|
|
3064
|
+
"src/database"
|
|
3065
|
+
];
|
|
3066
|
+
for (const dir of searchDirs) {
|
|
3067
|
+
try {
|
|
3068
|
+
const targetDir = path.join(cwd, dir);
|
|
3069
|
+
const files = await fs.readdir(targetDir);
|
|
3070
|
+
const sqliteFile = files.find(
|
|
3071
|
+
(file) => file.endsWith(".db") || file.endsWith(".sqlite") || file.endsWith(".sqlite3")
|
|
3072
|
+
);
|
|
3073
|
+
if (sqliteFile) {
|
|
3074
|
+
return {
|
|
3075
|
+
type: "sqlite",
|
|
3076
|
+
targetUrl: path.join(targetDir, sqliteFile),
|
|
3077
|
+
source: "auto-detected"
|
|
3078
|
+
};
|
|
3079
|
+
}
|
|
3080
|
+
} catch {
|
|
3081
|
+
}
|
|
3082
|
+
}
|
|
3083
|
+
const prismaSchemaPath = path.join(cwd, "prisma", "schema.prisma");
|
|
3084
|
+
try {
|
|
3085
|
+
const schemaContent = await fs.readFile(prismaSchemaPath, "utf-8");
|
|
3086
|
+
const providerMatch = schemaContent.match(
|
|
3087
|
+
/provider\s*=\s*["']([^"']+)["']/
|
|
3088
|
+
);
|
|
3089
|
+
const urlMatch = schemaContent.match(
|
|
3090
|
+
/url\s*=\s*(?:env\(["']([^"']+)["']\)|["']([^"']+)["'])/
|
|
3091
|
+
);
|
|
3092
|
+
if (providerMatch) {
|
|
3093
|
+
let provider = providerMatch[1];
|
|
3094
|
+
if (provider === "postgresql") provider = "postgres";
|
|
3095
|
+
let targetUrl = "";
|
|
3096
|
+
if (urlMatch && urlMatch[2]) {
|
|
3097
|
+
targetUrl = urlMatch[2];
|
|
3098
|
+
if (targetUrl.startsWith("file:")) {
|
|
3099
|
+
targetUrl = path.join(cwd, "prisma", targetUrl.replace("file:", ""));
|
|
3100
|
+
}
|
|
3101
|
+
}
|
|
3102
|
+
if (targetUrl && (provider === "sqlite" || provider === "postgres" || provider === "mysql")) {
|
|
3103
|
+
return {
|
|
3104
|
+
type: provider,
|
|
3105
|
+
targetUrl,
|
|
3106
|
+
source: ".env"
|
|
3107
|
+
// Treats static config as .env source or configuration
|
|
3108
|
+
};
|
|
3109
|
+
}
|
|
3110
|
+
}
|
|
3111
|
+
} catch {
|
|
3112
|
+
}
|
|
3113
|
+
const envPath = path.join(cwd, ".env");
|
|
3114
|
+
try {
|
|
3115
|
+
const envContent = await fs.readFile(envPath, "utf-8");
|
|
3116
|
+
const dbKeys = [
|
|
3117
|
+
"DATABASE_URL",
|
|
3118
|
+
"DB_URL",
|
|
3119
|
+
"POSTGRES_URL",
|
|
3120
|
+
"POSTGRES_PRISMA_URL",
|
|
3121
|
+
"MYSQL_URL"
|
|
3122
|
+
];
|
|
3123
|
+
for (const key of dbKeys) {
|
|
3124
|
+
const regex = new RegExp(`${key}\\s*=\\s*["']?([^"'\\r\\n]+)["']?`);
|
|
3125
|
+
const match = envContent.match(regex);
|
|
3126
|
+
if (match) {
|
|
3127
|
+
const url = match[1];
|
|
3128
|
+
if (url.startsWith("file:") || url.endsWith(".db") || url.includes(".sqlite")) {
|
|
3129
|
+
let targetUrl = url.replace("file:", "");
|
|
3130
|
+
if (!path.isAbsolute(targetUrl)) {
|
|
3131
|
+
targetUrl = path.resolve(cwd, targetUrl);
|
|
3132
|
+
}
|
|
3133
|
+
return {
|
|
3134
|
+
type: "sqlite",
|
|
3135
|
+
targetUrl,
|
|
3136
|
+
source: ".env"
|
|
3137
|
+
};
|
|
3138
|
+
} else if (url.startsWith("postgres://") || url.startsWith("postgresql://")) {
|
|
3139
|
+
return {
|
|
3140
|
+
type: "postgres",
|
|
3141
|
+
targetUrl: url,
|
|
3142
|
+
source: ".env"
|
|
3143
|
+
};
|
|
3144
|
+
} else if (url.startsWith("mysql://")) {
|
|
3145
|
+
return {
|
|
3146
|
+
type: "mysql",
|
|
3147
|
+
targetUrl: url,
|
|
3148
|
+
source: ".env"
|
|
3149
|
+
};
|
|
3150
|
+
}
|
|
3151
|
+
}
|
|
3152
|
+
}
|
|
3153
|
+
} catch (error) {
|
|
3154
|
+
}
|
|
3155
|
+
return {
|
|
3156
|
+
type: "unknown",
|
|
3157
|
+
targetUrl: "",
|
|
3158
|
+
source: "manual"
|
|
3159
|
+
};
|
|
3160
|
+
}
|
|
3161
|
+
async function saveDatabaseUrl(url) {
|
|
3162
|
+
const envPath = path.join(process.cwd(), ".env");
|
|
3163
|
+
let envContent = "";
|
|
3164
|
+
try {
|
|
3165
|
+
envContent = await fs.readFile(envPath, "utf-8");
|
|
3166
|
+
} catch (e) {
|
|
3167
|
+
}
|
|
3168
|
+
const regex = /DATABASE_URL\s*=\s*["']?([^"'\r\n]+)["']?/;
|
|
3169
|
+
if (regex.test(envContent)) {
|
|
3170
|
+
envContent = envContent.replace(regex, `DATABASE_URL="${url}"`);
|
|
3171
|
+
} else {
|
|
3172
|
+
if (envContent && !envContent.endsWith("\n")) {
|
|
3173
|
+
envContent += "\n";
|
|
3174
|
+
}
|
|
3175
|
+
envContent += `DATABASE_URL="${url}"
|
|
3176
|
+
`;
|
|
3177
|
+
}
|
|
3178
|
+
await fs.writeFile(envPath, envContent, "utf-8");
|
|
3179
|
+
}
|
|
3180
|
+
|
|
3181
|
+
// src/cli/views/editor.ts
|
|
3182
|
+
init_factory();
|
|
3183
|
+
import { select as select3, Separator } from "@inquirer/prompts";
|
|
3184
|
+
import pc5 from "picocolors";
|
|
3185
|
+
|
|
3186
|
+
// src/cli/menus/table.ts
|
|
3187
|
+
import { select } from "@inquirer/prompts";
|
|
3188
|
+
import pc2 from "picocolors";
|
|
3189
|
+
var selectTable = async (tableChoices) => await select({
|
|
3190
|
+
message: "Select a table to edit data:",
|
|
3191
|
+
theme: {
|
|
3192
|
+
prefix: pc2.cyan("\u2713 "),
|
|
3193
|
+
icon: {
|
|
3194
|
+
cursor: pc2.cyan("\u203A ")
|
|
3195
|
+
},
|
|
3196
|
+
style: {
|
|
3197
|
+
message: (text) => pc2.bold(pc2.white(text)),
|
|
3198
|
+
highlight: (text) => {
|
|
3199
|
+
const clean = text.replace(/\x1b\[[0-9;]*m/g, "");
|
|
3200
|
+
return clean.includes(" Back") ? pc2.red(clean) : pc2.cyan(clean);
|
|
3201
|
+
}
|
|
3202
|
+
}
|
|
3203
|
+
},
|
|
3204
|
+
choices: tableChoices
|
|
3205
|
+
});
|
|
3206
|
+
|
|
3207
|
+
// src/cli/wizards/dataEditor.ts
|
|
3208
|
+
init_dialect();
|
|
3209
|
+
import { input, select as select2 } from "@inquirer/prompts";
|
|
3210
|
+
import pc3 from "picocolors";
|
|
3211
|
+
import fs2 from "fs/promises";
|
|
3212
|
+
import path2 from "path";
|
|
3213
|
+
async function runBeginnerAdd(adapter, dbType, tableName, columns) {
|
|
3214
|
+
const dialect = getDialect(dbType);
|
|
3215
|
+
console.log(pc3.cyan(`
|
|
3216
|
+
--- Add Data to [${tableName}] ---`));
|
|
3217
|
+
console.log(pc3.dim("Leave a field completely empty (press Enter) to skip it (e.g. for AutoInc or NULL)"));
|
|
3218
|
+
const insertCols = [];
|
|
3219
|
+
const insertVals = [];
|
|
3220
|
+
for (const col of columns) {
|
|
3221
|
+
if (col.isPk && (col.type.toLowerCase().includes("int") || col.type.toLowerCase() === "integer")) {
|
|
3222
|
+
console.log(pc3.dim(`Skipping '${col.name}' (Auto Increment Primary Key)`));
|
|
3223
|
+
continue;
|
|
3224
|
+
}
|
|
3225
|
+
const val = await input({ message: `Value for '${col.name}' (${col.type}):` });
|
|
3226
|
+
if (val.trim() !== "") {
|
|
3227
|
+
insertCols.push(dialect.quoteIdentifier(col.name));
|
|
3228
|
+
insertVals.push(`'${dialect.escapeString(val)}'`);
|
|
3229
|
+
}
|
|
3230
|
+
}
|
|
3231
|
+
if (insertCols.length === 0) {
|
|
3232
|
+
console.log(pc3.yellow("No data entered. Aborted."));
|
|
3233
|
+
return;
|
|
3234
|
+
}
|
|
3235
|
+
const sql = `INSERT INTO ${dialect.quoteIdentifier(tableName)} (${insertCols.join(", ")}) VALUES (${insertVals.join(", ")});`;
|
|
3236
|
+
console.log(pc3.dim("\nExecuting: ") + pc3.yellow(sql));
|
|
3237
|
+
try {
|
|
3238
|
+
await adapter.executeSql(sql);
|
|
3239
|
+
console.log(pc3.green("\u2713 Data added successfully!"));
|
|
3240
|
+
} catch (e) {
|
|
3241
|
+
console.log(pc3.red(`x Failed to add data: ${e.message}`));
|
|
3242
|
+
}
|
|
3243
|
+
}
|
|
3244
|
+
async function runBeginnerEdit(adapter, dbType, tableName, columns) {
|
|
3245
|
+
const dialect = getDialect(dbType);
|
|
3246
|
+
console.log(pc3.cyan(`
|
|
3247
|
+
--- Edit Data in [${tableName}] ---`));
|
|
3248
|
+
if (columns.length === 0) return;
|
|
3249
|
+
const pkColSchema = columns.find((c) => c.isPk) || columns[0];
|
|
3250
|
+
const pkCol = pkColSchema.name;
|
|
3251
|
+
const pkVal = await input({
|
|
3252
|
+
message: `Enter the '${pkCol}' of the record you want to edit:`
|
|
3253
|
+
});
|
|
3254
|
+
if (!pkVal.trim()) {
|
|
3255
|
+
console.log(pc3.yellow("Aborted."));
|
|
3256
|
+
return;
|
|
3257
|
+
}
|
|
3258
|
+
const targetCol = await select2({
|
|
3259
|
+
message: "Which column do you want to update?",
|
|
3260
|
+
choices: columns.map((c) => ({ name: c.name, value: c.name }))
|
|
3261
|
+
});
|
|
3262
|
+
const newVal = await input({
|
|
3263
|
+
message: `New value for '${targetCol}' (leave empty for NULL):`
|
|
3264
|
+
});
|
|
3265
|
+
let valStr = "NULL";
|
|
3266
|
+
if (newVal.trim() !== "") {
|
|
3267
|
+
valStr = `'${dialect.escapeString(newVal)}'`;
|
|
3268
|
+
}
|
|
3269
|
+
const sql = `UPDATE ${dialect.quoteIdentifier(tableName)} SET ${dialect.quoteIdentifier(targetCol)} = ${valStr} WHERE ${dialect.quoteIdentifier(pkCol)} = '${dialect.escapeString(pkVal)}';`;
|
|
3270
|
+
console.log(pc3.dim("\nExecuting: ") + pc3.yellow(sql));
|
|
3271
|
+
try {
|
|
3272
|
+
await adapter.executeSql(sql);
|
|
3273
|
+
console.log(pc3.green("\u2713 Data updated successfully!"));
|
|
3274
|
+
} catch (e) {
|
|
3275
|
+
console.log(pc3.red(`x Failed to update data: ${e.message}`));
|
|
3276
|
+
}
|
|
3277
|
+
}
|
|
3278
|
+
async function runBeginnerDelete(adapter, dbType, tableName, columns) {
|
|
3279
|
+
const dialect = getDialect(dbType);
|
|
3280
|
+
console.log(pc3.cyan(`
|
|
3281
|
+
--- Delete Data from [${tableName}] ---`));
|
|
3282
|
+
if (columns.length === 0) return;
|
|
3283
|
+
const pkColSchema = columns.find((c) => c.isPk) || columns[0];
|
|
3284
|
+
const pkCol = pkColSchema.name;
|
|
3285
|
+
const pkVal = await input({
|
|
3286
|
+
message: `Enter the '${pkCol}' of the record you want to delete:`
|
|
3287
|
+
});
|
|
3288
|
+
if (!pkVal.trim()) {
|
|
3289
|
+
console.log(pc3.yellow("Aborted."));
|
|
3290
|
+
return;
|
|
3291
|
+
}
|
|
3292
|
+
const sql = `DELETE FROM ${dialect.quoteIdentifier(tableName)} WHERE ${dialect.quoteIdentifier(pkCol)} = '${dialect.escapeString(pkVal)}';`;
|
|
3293
|
+
console.log(pc3.dim("\nExecuting: ") + pc3.yellow(sql));
|
|
3294
|
+
try {
|
|
3295
|
+
await adapter.executeSql(sql);
|
|
3296
|
+
console.log(pc3.green("\u2713 Data deleted successfully!"));
|
|
3297
|
+
} catch (e) {
|
|
3298
|
+
console.log(pc3.red(`x Failed to delete data: ${e.message}`));
|
|
3299
|
+
}
|
|
3300
|
+
}
|
|
3301
|
+
async function runExpertMode(adapter) {
|
|
3302
|
+
console.log(pc3.cyan(`
|
|
3303
|
+
--- Expert Mode: Execute Raw SQL ---`));
|
|
3304
|
+
try {
|
|
3305
|
+
const filePath = await input({
|
|
3306
|
+
message: "Enter the path to your .md or .sql file:"
|
|
3307
|
+
});
|
|
3308
|
+
if (filePath && filePath.trim()) {
|
|
3309
|
+
const absolutePath = path2.resolve(process.cwd(), filePath.trim());
|
|
3310
|
+
let fileContent = "";
|
|
3311
|
+
try {
|
|
3312
|
+
fileContent = await fs2.readFile(absolutePath, "utf-8");
|
|
3313
|
+
} catch (err) {
|
|
3314
|
+
console.log(pc3.red(`
|
|
3315
|
+
x Failed to read file: ${err.message}`));
|
|
3316
|
+
return;
|
|
3317
|
+
}
|
|
3318
|
+
let sqlToExecute = fileContent;
|
|
3319
|
+
if (absolutePath.toLowerCase().endsWith(".md")) {
|
|
3320
|
+
const matches = [
|
|
3321
|
+
...fileContent.matchAll(/```(?:sql)?\n([\s\S]*?)```/gi)
|
|
3322
|
+
];
|
|
3323
|
+
if (matches.length > 0) {
|
|
3324
|
+
sqlToExecute = matches.map((m) => m[1].trim()).join("\n\n");
|
|
3325
|
+
}
|
|
3326
|
+
}
|
|
3327
|
+
if (sqlToExecute.trim()) {
|
|
3328
|
+
console.log(pc3.dim("\nExecuting SQL..."));
|
|
3329
|
+
console.log(pc3.yellow(sqlToExecute));
|
|
3330
|
+
try {
|
|
3331
|
+
await adapter.executeSql(sqlToExecute);
|
|
3332
|
+
console.log(pc3.green("\n\u2713 SQL executed successfully!"));
|
|
3333
|
+
} catch (e) {
|
|
3334
|
+
console.log(pc3.red(`
|
|
3335
|
+
x Error executing SQL: ${e.message}`));
|
|
3336
|
+
}
|
|
3337
|
+
} else {
|
|
3338
|
+
console.log(pc3.yellow("\n\u26A0\uFE0F No SQL found in the file. Aborted."));
|
|
3339
|
+
}
|
|
3340
|
+
} else {
|
|
3341
|
+
console.log(pc3.yellow("\nNo file path entered. Aborted."));
|
|
3342
|
+
}
|
|
3343
|
+
} catch (e) {
|
|
3344
|
+
console.log(pc3.red(`
|
|
3345
|
+
Error: ${e.message}`));
|
|
3346
|
+
}
|
|
3347
|
+
}
|
|
3348
|
+
|
|
3349
|
+
// src/cli/views/editor.ts
|
|
3350
|
+
init_table();
|
|
3351
|
+
init_logo();
|
|
3352
|
+
async function viewTables(dbConfig) {
|
|
3353
|
+
const adapter = createDBAdapter(dbConfig);
|
|
3354
|
+
let viewing = true;
|
|
3355
|
+
while (viewing) {
|
|
3356
|
+
console.clear();
|
|
3357
|
+
let tables = [];
|
|
3358
|
+
let totalRows = 0;
|
|
3359
|
+
let totalDataCount = "Scanning...";
|
|
3360
|
+
try {
|
|
3361
|
+
tables = await adapter.getTables();
|
|
3362
|
+
for (const table of tables) {
|
|
3363
|
+
try {
|
|
3364
|
+
const quote = dbConfig.type === "mysql" ? "`" : '"';
|
|
3365
|
+
const res = await adapter.query(`SELECT COUNT(*) as count FROM ${quote}${table}${quote}`);
|
|
3366
|
+
if (res.rows.length > 0 && res.rows[0].count != null) {
|
|
3367
|
+
totalRows += Number(res.rows[0].count);
|
|
3368
|
+
}
|
|
3369
|
+
} catch (e) {
|
|
3370
|
+
}
|
|
3371
|
+
}
|
|
3372
|
+
totalDataCount = `${totalRows.toLocaleString()} rows across ${tables.length} tables`;
|
|
3373
|
+
} catch (e) {
|
|
3374
|
+
totalDataCount = pc5.red(e.message);
|
|
3375
|
+
}
|
|
3376
|
+
const targetVal = dbConfig.targetUrl;
|
|
3377
|
+
const sourceVal = dbConfig.source === ".env" ? "Loaded from project .env file" : dbConfig.source === "auto-detected" ? "Auto-detected local SQLite file" : "Manual connection config";
|
|
3378
|
+
printCustomDashboard(` Data Browser & Editor \u2022 [${dbConfig.type.toUpperCase()}]`, [
|
|
3379
|
+
{ label: "Database", value: dbConfig.type.toUpperCase() },
|
|
3380
|
+
{ label: "Target", value: targetVal.length > 45 ? "..." + targetVal.slice(-42) : targetVal },
|
|
3381
|
+
{ label: "Source", value: sourceVal },
|
|
3382
|
+
{ label: "Total Data", value: totalDataCount }
|
|
3383
|
+
]);
|
|
3384
|
+
try {
|
|
3385
|
+
if (tables.length === 0) {
|
|
3386
|
+
console.log(pc5.yellow("No tables found in this database."));
|
|
3387
|
+
await waitForEnter();
|
|
3388
|
+
await adapter.close();
|
|
3389
|
+
return;
|
|
3390
|
+
}
|
|
3391
|
+
const tableChoices = [
|
|
3392
|
+
...tables.map((table) => ({
|
|
3393
|
+
name: table,
|
|
3394
|
+
value: table
|
|
3395
|
+
})),
|
|
3396
|
+
new Separator(),
|
|
3397
|
+
{
|
|
3398
|
+
name: pc5.dim(" Back"),
|
|
3399
|
+
value: "BACK"
|
|
3400
|
+
}
|
|
3401
|
+
];
|
|
3402
|
+
const selectedTable = await selectTable(tableChoices);
|
|
3403
|
+
if (selectedTable === "BACK") {
|
|
3404
|
+
viewing = false;
|
|
3405
|
+
continue;
|
|
3406
|
+
}
|
|
3407
|
+
let tableLoop = true;
|
|
3408
|
+
let currentPage = 1;
|
|
3409
|
+
let currentWhere = "";
|
|
3410
|
+
const limit = 50;
|
|
3411
|
+
while (tableLoop) {
|
|
3412
|
+
console.clear();
|
|
3413
|
+
const offset = (currentPage - 1) * limit;
|
|
3414
|
+
const schema = await adapter.getSchema(selectedTable);
|
|
3415
|
+
const colNames = schema.map((c) => c.name);
|
|
3416
|
+
let data;
|
|
3417
|
+
try {
|
|
3418
|
+
data = await adapter.getData(
|
|
3419
|
+
selectedTable,
|
|
3420
|
+
limit,
|
|
3421
|
+
offset,
|
|
3422
|
+
currentWhere
|
|
3423
|
+
);
|
|
3424
|
+
} catch (e) {
|
|
3425
|
+
console.log(pc5.red(`
|
|
3426
|
+
Error fetching data: ${e.message}
|
|
3427
|
+
`));
|
|
3428
|
+
currentWhere = "";
|
|
3429
|
+
const { input: input7 } = await import("@inquirer/prompts");
|
|
3430
|
+
await input7({ message: "Click Enter to continue..." });
|
|
3431
|
+
continue;
|
|
3432
|
+
}
|
|
3433
|
+
const rows = data.rows;
|
|
3434
|
+
const detailHeaderTitle = ` ${selectedTable} (Page ${currentPage}) `;
|
|
3435
|
+
drawTable(colNames, rows, {
|
|
3436
|
+
title: detailHeaderTitle,
|
|
3437
|
+
maxColWidth: 30
|
|
3438
|
+
});
|
|
3439
|
+
const hasNextPage = rows.length === limit;
|
|
3440
|
+
const hasPrevPage = currentPage > 1;
|
|
3441
|
+
const actionChoices = [
|
|
3442
|
+
{ name: "Add Data", value: "add" },
|
|
3443
|
+
{ name: "Edit Data", value: "edit" },
|
|
3444
|
+
{ name: "Delete Data", value: "delete" },
|
|
3445
|
+
new Separator(),
|
|
3446
|
+
{ name: "Search Data", value: "search" },
|
|
3447
|
+
{
|
|
3448
|
+
name: currentWhere ? "Clear Search" : pc5.dim("Clear Search (disabled)"),
|
|
3449
|
+
value: currentWhere ? "clear_search" : "noop"
|
|
3450
|
+
},
|
|
3451
|
+
new Separator(),
|
|
3452
|
+
{ name: "Export to CSV", value: "exportCsv" },
|
|
3453
|
+
{ name: "Export to JSON", value: "exportJson" },
|
|
3454
|
+
new Separator()
|
|
3455
|
+
];
|
|
3456
|
+
if (hasPrevPage)
|
|
3457
|
+
actionChoices.push({ name: "Previous Page", value: "prev" });
|
|
3458
|
+
if (hasNextPage)
|
|
3459
|
+
actionChoices.push({ name: "Next Page", value: "next" });
|
|
3460
|
+
actionChoices.push({ name: pc5.dim(" Back"), value: "BACK" });
|
|
3461
|
+
const action = await select3({
|
|
3462
|
+
message: "Select an action for this table:",
|
|
3463
|
+
theme: {
|
|
3464
|
+
prefix: pc5.cyan("\u2713 "),
|
|
3465
|
+
icon: {
|
|
3466
|
+
cursor: pc5.cyan("\u203A ")
|
|
3467
|
+
},
|
|
3468
|
+
style: {
|
|
3469
|
+
message: (text) => pc5.bold(pc5.white(text)),
|
|
3470
|
+
highlight: (text) => {
|
|
3471
|
+
const clean = text.replace(/\x1b\[[0-9;]*m/g, "");
|
|
3472
|
+
return clean.includes(" Back") ? pc5.red(clean) : pc5.cyan(clean);
|
|
3473
|
+
}
|
|
3474
|
+
}
|
|
3475
|
+
},
|
|
3476
|
+
choices: actionChoices
|
|
3477
|
+
});
|
|
3478
|
+
if (action === "prev") {
|
|
3479
|
+
currentPage--;
|
|
3480
|
+
continue;
|
|
3481
|
+
}
|
|
3482
|
+
if (action === "next") {
|
|
3483
|
+
currentPage++;
|
|
3484
|
+
continue;
|
|
3485
|
+
}
|
|
3486
|
+
if (action === "noop") {
|
|
3487
|
+
continue;
|
|
3488
|
+
}
|
|
3489
|
+
if (action === "BACK") {
|
|
3490
|
+
tableLoop = false;
|
|
3491
|
+
continue;
|
|
3492
|
+
}
|
|
3493
|
+
if (action === "clear_search") {
|
|
3494
|
+
currentWhere = "";
|
|
3495
|
+
currentPage = 1;
|
|
3496
|
+
continue;
|
|
3497
|
+
}
|
|
3498
|
+
if (action === "search") {
|
|
3499
|
+
const { input: input7 } = await import("@inquirer/prompts");
|
|
3500
|
+
const searchInput = await input7({
|
|
3501
|
+
message: "Enter Search (e.g. `age > 18` or `John` for fuzzy search):"
|
|
3502
|
+
});
|
|
3503
|
+
const searchVal = searchInput.trim();
|
|
3504
|
+
if (searchVal) {
|
|
3505
|
+
const isSqlCondition = /[=<>]|LIKE|IN|AND|OR/i.test(searchVal);
|
|
3506
|
+
if (isSqlCondition) {
|
|
3507
|
+
currentWhere = searchVal;
|
|
3508
|
+
} else {
|
|
3509
|
+
const strCols = schema.filter(
|
|
3510
|
+
(c) => c.type.toLowerCase().includes("char") || c.type.toLowerCase().includes("text")
|
|
3511
|
+
);
|
|
3512
|
+
if (strCols.length > 0) {
|
|
3513
|
+
const likeOp = dbConfig.type === "postgres" ? "ILIKE" : "LIKE";
|
|
3514
|
+
const conditions = strCols.map(
|
|
3515
|
+
(c) => `${adapter.quoteIdentifier(c.name)} ${likeOp} '%${searchVal.replace(/'/g, "''")}%'`
|
|
3516
|
+
);
|
|
3517
|
+
currentWhere = conditions.join(" OR ");
|
|
3518
|
+
} else {
|
|
3519
|
+
currentWhere = `${adapter.quoteIdentifier(schema[0].name)} = '${searchVal}'`;
|
|
3520
|
+
}
|
|
3521
|
+
}
|
|
3522
|
+
currentPage = 1;
|
|
3523
|
+
}
|
|
3524
|
+
continue;
|
|
3525
|
+
}
|
|
3526
|
+
if (action === "exportCsv" || action === "exportJson") {
|
|
3527
|
+
try {
|
|
3528
|
+
console.log(pc5.yellow("\nExporting data..."));
|
|
3529
|
+
const batchSize = 1e3;
|
|
3530
|
+
let batchOffset = 0;
|
|
3531
|
+
const allExportRows = [];
|
|
3532
|
+
let exportColumns = [];
|
|
3533
|
+
let lastBatch;
|
|
3534
|
+
do {
|
|
3535
|
+
lastBatch = await adapter.getData(
|
|
3536
|
+
selectedTable,
|
|
3537
|
+
batchSize,
|
|
3538
|
+
batchOffset,
|
|
3539
|
+
currentWhere
|
|
3540
|
+
);
|
|
3541
|
+
if (exportColumns.length === 0) exportColumns = lastBatch.columns;
|
|
3542
|
+
allExportRows.push(...lastBatch.rows);
|
|
3543
|
+
batchOffset += batchSize;
|
|
3544
|
+
} while (lastBatch.rows.length === batchSize);
|
|
3545
|
+
const allData = { columns: exportColumns, rows: allExportRows };
|
|
3546
|
+
const fs12 = await import("fs/promises");
|
|
3547
|
+
const path13 = await import("path");
|
|
3548
|
+
const exportDir = path13.join(process.cwd(), "drixio_exports");
|
|
3549
|
+
await fs12.mkdir(exportDir, { recursive: true });
|
|
3550
|
+
if (action === "exportCsv") {
|
|
3551
|
+
const headers = allData.columns.join(",") + "\n";
|
|
3552
|
+
const rowsStr = allData.rows.map((r) => {
|
|
3553
|
+
return allData.columns.map((c) => {
|
|
3554
|
+
const val = String(r[c] ?? "").replace(/"/g, '""');
|
|
3555
|
+
return `"${val}"`;
|
|
3556
|
+
}).join(",");
|
|
3557
|
+
}).join("\n");
|
|
3558
|
+
const fp = path13.join(exportDir, `${selectedTable}.csv`);
|
|
3559
|
+
await fs12.writeFile(fp, headers + rowsStr, "utf-8");
|
|
3560
|
+
console.log(pc5.green(`
|
|
3561
|
+
Exported to ${fp}`));
|
|
3562
|
+
} else {
|
|
3563
|
+
const fp = path13.join(exportDir, `${selectedTable}.json`);
|
|
3564
|
+
await fs12.writeFile(
|
|
3565
|
+
fp,
|
|
3566
|
+
JSON.stringify(allData.rows, null, 2),
|
|
3567
|
+
"utf-8"
|
|
3568
|
+
);
|
|
3569
|
+
console.log(pc5.green(`
|
|
3570
|
+
Exported to ${fp}`));
|
|
3571
|
+
}
|
|
3572
|
+
} catch (e) {
|
|
3573
|
+
console.log(pc5.red(`
|
|
3574
|
+
Export Error: ${e.message}`));
|
|
3575
|
+
}
|
|
3576
|
+
const { input: input7 } = await import("@inquirer/prompts");
|
|
3577
|
+
await input7({ message: "Click Enter to continue..." });
|
|
3578
|
+
continue;
|
|
3579
|
+
}
|
|
3580
|
+
const mode = await select3({
|
|
3581
|
+
message: `Select mode for ${action} data:`,
|
|
3582
|
+
choices: [
|
|
3583
|
+
{ name: "Beginner (Interactive Step-by-Step)", value: "beginner" },
|
|
3584
|
+
{
|
|
3585
|
+
name: "Expert (Load SQL from .sql or .md file)",
|
|
3586
|
+
value: "expert"
|
|
3587
|
+
},
|
|
3588
|
+
new Separator(),
|
|
3589
|
+
{ name: pc5.dim("Cancel"), value: "cancel" }
|
|
3590
|
+
]
|
|
3591
|
+
});
|
|
3592
|
+
if (mode === "cancel") {
|
|
3593
|
+
continue;
|
|
3594
|
+
}
|
|
3595
|
+
if (mode === "expert") {
|
|
3596
|
+
await runExpertMode(adapter);
|
|
3597
|
+
await waitForEnter();
|
|
3598
|
+
continue;
|
|
3599
|
+
}
|
|
3600
|
+
if (action === "add") {
|
|
3601
|
+
await runBeginnerAdd(adapter, dbConfig.type, selectedTable, schema);
|
|
3602
|
+
await waitForEnter();
|
|
3603
|
+
} else if (action === "edit") {
|
|
3604
|
+
await runBeginnerEdit(adapter, dbConfig.type, selectedTable, schema);
|
|
3605
|
+
await waitForEnter();
|
|
3606
|
+
} else if (action === "delete") {
|
|
3607
|
+
await runBeginnerDelete(
|
|
3608
|
+
adapter,
|
|
3609
|
+
dbConfig.type,
|
|
3610
|
+
selectedTable,
|
|
3611
|
+
schema
|
|
3612
|
+
);
|
|
3613
|
+
await waitForEnter();
|
|
3614
|
+
}
|
|
3615
|
+
}
|
|
3616
|
+
} catch (error) {
|
|
3617
|
+
console.log(pc5.red(`
|
|
3618
|
+
x Error: ${error.message}`));
|
|
3619
|
+
await waitForEnter();
|
|
3620
|
+
viewing = false;
|
|
3621
|
+
}
|
|
3622
|
+
}
|
|
3623
|
+
await adapter.close();
|
|
3624
|
+
}
|
|
3625
|
+
async function waitForEnter() {
|
|
3626
|
+
const { input: input7 } = await import("@inquirer/prompts");
|
|
3627
|
+
await input7({
|
|
3628
|
+
message: "Press Enter to continue..."
|
|
3629
|
+
});
|
|
3630
|
+
}
|
|
3631
|
+
|
|
3632
|
+
// src/cli/wizards/setupConn.ts
|
|
3633
|
+
import { select as select4, input as input2 } from "@inquirer/prompts";
|
|
3634
|
+
import pc6 from "picocolors";
|
|
3635
|
+
init_factory();
|
|
3636
|
+
async function runSetup(currentConfig) {
|
|
3637
|
+
const setupMethod = await select4({
|
|
3638
|
+
message: "What method you want to use to setup database connection?",
|
|
3639
|
+
choices: [
|
|
3640
|
+
{
|
|
3641
|
+
name: " Auto-Config",
|
|
3642
|
+
value: "auto",
|
|
3643
|
+
description: "Automatically detect and setup database connection (May fail for some cases)."
|
|
3644
|
+
},
|
|
3645
|
+
{
|
|
3646
|
+
name: " Manual Config",
|
|
3647
|
+
value: "manual",
|
|
3648
|
+
description: "Manually setup database connection by entering parameters step by step."
|
|
3649
|
+
}
|
|
3650
|
+
]
|
|
3651
|
+
});
|
|
3652
|
+
let newConfig = currentConfig;
|
|
3653
|
+
switch (setupMethod) {
|
|
3654
|
+
case "auto":
|
|
3655
|
+
const retryConfig = await detectDatabase();
|
|
3656
|
+
if (retryConfig.type !== "unknown") {
|
|
3657
|
+
newConfig = retryConfig;
|
|
3658
|
+
console.log(pc6.green("\nDatabase connection setup successfully."));
|
|
3659
|
+
} else {
|
|
3660
|
+
console.log(
|
|
3661
|
+
pc6.red(
|
|
3662
|
+
"\nFailed to auto-detect database connection. Please try manual configuration."
|
|
3663
|
+
)
|
|
3664
|
+
);
|
|
3665
|
+
}
|
|
3666
|
+
break;
|
|
3667
|
+
case "manual":
|
|
3668
|
+
const databaseUrl = await input2({
|
|
3669
|
+
message: "Enter the database connection URL: "
|
|
3670
|
+
});
|
|
3671
|
+
if (databaseUrl) {
|
|
3672
|
+
const tempConfig = await detectDatabase(databaseUrl);
|
|
3673
|
+
console.log(pc6.dim("\nTesting connection..."));
|
|
3674
|
+
try {
|
|
3675
|
+
const adapter = createDBAdapter(tempConfig);
|
|
3676
|
+
await adapter.getTables();
|
|
3677
|
+
await adapter.close();
|
|
3678
|
+
newConfig = tempConfig;
|
|
3679
|
+
await saveDatabaseUrl(databaseUrl);
|
|
3680
|
+
console.log(
|
|
3681
|
+
pc6.green(
|
|
3682
|
+
"\u2713 Database connection setup successfully and saved to .env!"
|
|
3683
|
+
)
|
|
3684
|
+
);
|
|
3685
|
+
} catch (e) {
|
|
3686
|
+
console.log(
|
|
3687
|
+
pc6.red(
|
|
3688
|
+
`
|
|
3689
|
+
x Connection failed: ${e.message}
|
|
3690
|
+
Please enter a valid database URL or check your database status.`
|
|
3691
|
+
)
|
|
3692
|
+
);
|
|
3693
|
+
}
|
|
3694
|
+
} else {
|
|
3695
|
+
console.log(
|
|
3696
|
+
pc6.red(
|
|
3697
|
+
"\nFailed to detect database connection. Please enter valid database URL."
|
|
3698
|
+
)
|
|
3699
|
+
);
|
|
3700
|
+
}
|
|
3701
|
+
break;
|
|
3702
|
+
}
|
|
3703
|
+
return newConfig;
|
|
3704
|
+
}
|
|
3705
|
+
|
|
3706
|
+
// src/cli/menus/action.ts
|
|
3707
|
+
import { select as select5, Separator as Separator2 } from "@inquirer/prompts";
|
|
3708
|
+
import pc7 from "picocolors";
|
|
3709
|
+
var selectAction = async (dbConfig) => await select5({
|
|
3710
|
+
message: "Select an action:",
|
|
3711
|
+
theme: {
|
|
3712
|
+
prefix: pc7.cyan("\u2713 "),
|
|
3713
|
+
icon: {
|
|
3714
|
+
cursor: pc7.cyan("\u203A ")
|
|
3715
|
+
},
|
|
3716
|
+
style: {
|
|
3717
|
+
message: (text) => pc7.bold(pc7.white(text)),
|
|
3718
|
+
highlight: (text) => {
|
|
3719
|
+
const clean = text.replace(/\x1b\[[0-9;]*m/g, "");
|
|
3720
|
+
return clean.includes("Exit") ? pc7.red(clean) : pc7.cyan(clean);
|
|
3721
|
+
}
|
|
3722
|
+
}
|
|
3723
|
+
},
|
|
3724
|
+
choices: [
|
|
3725
|
+
{
|
|
3726
|
+
name: " Data Browser & Editor",
|
|
3727
|
+
value: "editor",
|
|
3728
|
+
description: "View, insert, update, and delete row data in your tables.",
|
|
3729
|
+
disabled: dbConfig.type === "unknown"
|
|
3730
|
+
},
|
|
3731
|
+
{
|
|
3732
|
+
name: " Run Raw SQL (REPL)",
|
|
3733
|
+
value: "repl",
|
|
3734
|
+
description: "Execute arbitrary SQL queries interactively.",
|
|
3735
|
+
disabled: dbConfig.type === "unknown"
|
|
3736
|
+
},
|
|
3737
|
+
{
|
|
3738
|
+
name: " Table Builder & Manager",
|
|
3739
|
+
value: "table",
|
|
3740
|
+
description: "Create new tables, or modify/drop existing tables.",
|
|
3741
|
+
disabled: dbConfig.type === "unknown"
|
|
3742
|
+
},
|
|
3743
|
+
new Separator2(),
|
|
3744
|
+
{
|
|
3745
|
+
name: dbConfig.type === "unknown" ? " Setup Connection" : " Connection Settings",
|
|
3746
|
+
value: dbConfig.type === "unknown" ? "setup" : "re-configure",
|
|
3747
|
+
description: dbConfig.type === "unknown" ? "Setup the database connection manually." : "Re-configure the database connection."
|
|
3748
|
+
},
|
|
3749
|
+
{
|
|
3750
|
+
name: pc7.dim(" Exit"),
|
|
3751
|
+
value: "exit",
|
|
3752
|
+
description: "Exit the Drixio CLI application."
|
|
3753
|
+
}
|
|
3754
|
+
]
|
|
3755
|
+
});
|
|
3756
|
+
|
|
3757
|
+
// src/cli/views/repl.ts
|
|
3758
|
+
init_factory();
|
|
3759
|
+
init_table();
|
|
3760
|
+
import pc8 from "picocolors";
|
|
3761
|
+
async function runRepl(dbConfig) {
|
|
3762
|
+
if (dbConfig.type === "unknown") return;
|
|
3763
|
+
const dbAdapter = createDBAdapter(dbConfig);
|
|
3764
|
+
let running = true;
|
|
3765
|
+
const { input: input7 } = await import("@inquirer/prompts");
|
|
3766
|
+
console.clear();
|
|
3767
|
+
console.log(
|
|
3768
|
+
pc8.cyan(
|
|
3769
|
+
`
|
|
3770
|
+
\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557`
|
|
3771
|
+
)
|
|
3772
|
+
);
|
|
3773
|
+
console.log(
|
|
3774
|
+
pc8.cyan(
|
|
3775
|
+
`\u2551 Interactive SQL REPL \u2551`
|
|
3776
|
+
)
|
|
3777
|
+
);
|
|
3778
|
+
console.log(
|
|
3779
|
+
pc8.cyan(
|
|
3780
|
+
`\u2560\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2563`
|
|
3781
|
+
)
|
|
3782
|
+
);
|
|
3783
|
+
console.log(
|
|
3784
|
+
pc8.cyan(`\u2551 `) + pc8.dim(
|
|
3785
|
+
`Type your SQL queries directly. Type 'exit' or 'quit' to Back.`
|
|
3786
|
+
) + ` ` + pc8.cyan(`\u2551`)
|
|
3787
|
+
);
|
|
3788
|
+
console.log(
|
|
3789
|
+
pc8.cyan(
|
|
3790
|
+
`\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D
|
|
3791
|
+
`
|
|
3792
|
+
)
|
|
3793
|
+
);
|
|
3794
|
+
while (running) {
|
|
3795
|
+
try {
|
|
3796
|
+
const queryStr = await input7({
|
|
3797
|
+
message: pc8.green(`${dbConfig.type}>`)
|
|
3798
|
+
});
|
|
3799
|
+
const sql = queryStr.trim();
|
|
3800
|
+
if (sql.toLowerCase() === "exit" || sql.toLowerCase() === "quit") {
|
|
3801
|
+
running = false;
|
|
3802
|
+
continue;
|
|
3803
|
+
}
|
|
3804
|
+
if (sql === "") continue;
|
|
3805
|
+
const result = await dbAdapter.query(sql);
|
|
3806
|
+
if (result.columns.length === 0) {
|
|
3807
|
+
console.log(pc8.yellow("Query executed successfully. (No output)"));
|
|
3808
|
+
continue;
|
|
3809
|
+
}
|
|
3810
|
+
drawTable(result.columns, result.rows, {
|
|
3811
|
+
title: "Query Result",
|
|
3812
|
+
maxColWidth: 50
|
|
3813
|
+
});
|
|
3814
|
+
console.log(pc8.dim(`
|
|
3815
|
+
(${result.rows.length} rows)`));
|
|
3816
|
+
console.log();
|
|
3817
|
+
} catch (e) {
|
|
3818
|
+
if (e.name === "ExitPromptError") {
|
|
3819
|
+
running = false;
|
|
3820
|
+
} else {
|
|
3821
|
+
console.log(pc8.red(`
|
|
3822
|
+
Error: ${e.message}
|
|
3823
|
+
`));
|
|
3824
|
+
}
|
|
3825
|
+
}
|
|
3826
|
+
}
|
|
3827
|
+
}
|
|
3828
|
+
|
|
3829
|
+
// src/cli/main.ts
|
|
3830
|
+
import { parseArgs } from "util";
|
|
3831
|
+
async function main() {
|
|
3832
|
+
const args = process.argv.slice(2);
|
|
3833
|
+
let customUrl;
|
|
3834
|
+
if (args.length > 0 && (args[0].startsWith("postgres://") || args[0].startsWith("postgresql://") || args[0].startsWith("mysql://") || args[0].startsWith("file:"))) {
|
|
3835
|
+
customUrl = args[0];
|
|
3836
|
+
}
|
|
3837
|
+
const { positionals, values } = parseArgs({
|
|
3838
|
+
args,
|
|
3839
|
+
options: {
|
|
3840
|
+
format: { type: "string" },
|
|
3841
|
+
"schema-only": { type: "boolean" },
|
|
3842
|
+
table: { type: "string" },
|
|
3843
|
+
help: { type: "boolean" }
|
|
3844
|
+
},
|
|
3845
|
+
strict: false,
|
|
3846
|
+
allowPositionals: true
|
|
3847
|
+
});
|
|
3848
|
+
if (values.help) {
|
|
3849
|
+
console.log(pc26.cyan(`
|
|
190
3850
|
Drixio CLI - Modern Database Manager
|
|
191
|
-
`))
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
3851
|
+
`));
|
|
3852
|
+
console.log(`${pc26.bold("Usage:")} npx drixio [command] [options]
|
|
3853
|
+
`);
|
|
3854
|
+
console.log(`${pc26.bold("Commands:")}`);
|
|
3855
|
+
console.log(` ${pc26.green("query")} "<sql>" Run a quick SQL query`);
|
|
3856
|
+
console.log(
|
|
3857
|
+
` ${pc26.green("exec")} <file.sql> Execute a SQL script file`
|
|
3858
|
+
);
|
|
3859
|
+
console.log(
|
|
3860
|
+
` ${pc26.green("export")} [table] Export table(s) to CSV/JSON`
|
|
3861
|
+
);
|
|
3862
|
+
console.log(
|
|
3863
|
+
` ${pc26.green("import")} [file] Import JSON/CSV into a table`
|
|
3864
|
+
);
|
|
3865
|
+
console.log(
|
|
3866
|
+
` ${pc26.green("seed")} [table] [count] Generate fake data for a table`
|
|
3867
|
+
);
|
|
3868
|
+
console.log(
|
|
3869
|
+
` ${pc26.green("diagram")} Generate a Mermaid ER diagram`
|
|
3870
|
+
);
|
|
3871
|
+
console.log(
|
|
3872
|
+
` ${pc26.green("generate-types")} Generate TypeScript interfaces`
|
|
3873
|
+
);
|
|
3874
|
+
console.log(
|
|
3875
|
+
` ${pc26.green("backup")} Backup the entire database`
|
|
3876
|
+
);
|
|
3877
|
+
console.log(
|
|
3878
|
+
` ${pc26.green("studio")} Launch the Web UI Studio`
|
|
3879
|
+
);
|
|
3880
|
+
console.log(
|
|
3881
|
+
` ${pc26.green("init")} [db_type] Initialize a local database & .env`
|
|
3882
|
+
);
|
|
3883
|
+
console.log(` ${pc26.green("drop-db")} [db_type] Drop a local database`);
|
|
3884
|
+
console.log(`
|
|
3885
|
+
${pc26.bold("Options:")}`);
|
|
3886
|
+
console.log(` --help Show this help message`);
|
|
3887
|
+
console.log(` --format <type> Specify export format (csv|json)`);
|
|
3888
|
+
console.log(` --schema-only Export schema without data`);
|
|
3889
|
+
console.log(` --table <name> Specify table for import`);
|
|
3890
|
+
console.log(
|
|
3891
|
+
`
|
|
3892
|
+
If you don't provide a command, Drixio will launch the Interactive UI!`
|
|
3893
|
+
);
|
|
3894
|
+
process.exit(0);
|
|
3895
|
+
}
|
|
3896
|
+
const command = customUrl ? void 0 : positionals[0];
|
|
3897
|
+
if (command === "query") {
|
|
3898
|
+
const { runQueryCommand: runQueryCommand2 } = await Promise.resolve().then(() => (init_query(), query_exports));
|
|
3899
|
+
const dbConfig2 = await detectDatabase();
|
|
3900
|
+
await runQueryCommand2(dbConfig2, positionals.slice(1));
|
|
3901
|
+
return;
|
|
3902
|
+
}
|
|
3903
|
+
if (command === "export") {
|
|
3904
|
+
const { runExportCommand: runExportCommand2 } = await Promise.resolve().then(() => (init_export(), export_exports));
|
|
3905
|
+
const dbConfig2 = await detectDatabase();
|
|
3906
|
+
await runExportCommand2(dbConfig2, positionals.slice(1), values);
|
|
3907
|
+
return;
|
|
3908
|
+
}
|
|
3909
|
+
if (command === "studio") {
|
|
3910
|
+
const { runStudio: runStudio2 } = await Promise.resolve().then(() => (init_server(), server_exports));
|
|
3911
|
+
const dbConfig2 = await detectDatabase();
|
|
3912
|
+
await runStudio2(dbConfig2);
|
|
3913
|
+
return;
|
|
3914
|
+
}
|
|
3915
|
+
if (command === "backup") {
|
|
3916
|
+
const { runBackupCommand: runBackupCommand2 } = await Promise.resolve().then(() => (init_backup(), backup_exports));
|
|
3917
|
+
const dbConfig2 = await detectDatabase();
|
|
3918
|
+
await runBackupCommand2(dbConfig2);
|
|
3919
|
+
return;
|
|
3920
|
+
}
|
|
3921
|
+
if (command === "import") {
|
|
3922
|
+
const { runImportCommand: runImportCommand2 } = await Promise.resolve().then(() => (init_import(), import_exports));
|
|
3923
|
+
const dbConfig2 = await detectDatabase();
|
|
3924
|
+
await runImportCommand2(dbConfig2, positionals.slice(1), values);
|
|
3925
|
+
return;
|
|
3926
|
+
}
|
|
3927
|
+
if (command === "seed") {
|
|
3928
|
+
const { runSeedCommand: runSeedCommand2 } = await Promise.resolve().then(() => (init_seed(), seed_exports));
|
|
3929
|
+
const dbConfig2 = await detectDatabase();
|
|
3930
|
+
await runSeedCommand2(dbConfig2, positionals.slice(1));
|
|
3931
|
+
return;
|
|
3932
|
+
}
|
|
3933
|
+
if (command === "diagram") {
|
|
3934
|
+
const { runDiagramCommand: runDiagramCommand2 } = await Promise.resolve().then(() => (init_diagram(), diagram_exports));
|
|
3935
|
+
const dbConfig2 = await detectDatabase();
|
|
3936
|
+
await runDiagramCommand2(dbConfig2);
|
|
3937
|
+
return;
|
|
3938
|
+
}
|
|
3939
|
+
if (command === "exec") {
|
|
3940
|
+
const { runExecCommand: runExecCommand2 } = await Promise.resolve().then(() => (init_exec(), exec_exports));
|
|
3941
|
+
const dbConfig2 = await detectDatabase();
|
|
3942
|
+
await runExecCommand2(dbConfig2, positionals.slice(1));
|
|
3943
|
+
return;
|
|
3944
|
+
}
|
|
3945
|
+
if (command === "generate-types") {
|
|
3946
|
+
const { runGenerateTypesCommand: runGenerateTypesCommand2 } = await Promise.resolve().then(() => (init_generateTypes(), generateTypes_exports));
|
|
3947
|
+
const dbConfig2 = await detectDatabase();
|
|
3948
|
+
await runGenerateTypesCommand2(dbConfig2);
|
|
3949
|
+
return;
|
|
3950
|
+
}
|
|
3951
|
+
if (command === "init") {
|
|
3952
|
+
const { runInitCommand: runInitCommand2 } = await Promise.resolve().then(() => (init_init(), init_exports));
|
|
3953
|
+
await runInitCommand2(positionals.slice(1));
|
|
3954
|
+
return;
|
|
3955
|
+
}
|
|
3956
|
+
if (command === "drop-db") {
|
|
3957
|
+
const { runDropDbCommand: runDropDbCommand2 } = await Promise.resolve().then(() => (init_drop(), drop_exports));
|
|
3958
|
+
await runDropDbCommand2(positionals.slice(1));
|
|
3959
|
+
return;
|
|
3960
|
+
}
|
|
3961
|
+
let running = true;
|
|
3962
|
+
let dbConfig = await detectDatabase(customUrl);
|
|
3963
|
+
while (running) {
|
|
3964
|
+
console.clear();
|
|
3965
|
+
printLogo();
|
|
3966
|
+
printDashboard(dbConfig);
|
|
3967
|
+
const action = await selectAction(dbConfig);
|
|
3968
|
+
switch (action) {
|
|
3969
|
+
case "editor":
|
|
3970
|
+
if (dbConfig.type === "unknown") {
|
|
3971
|
+
console.log(
|
|
3972
|
+
pc26.yellow(
|
|
3973
|
+
`
|
|
3974
|
+
No database connection found. Please run ${pc26.bold("drixio check")} first.`
|
|
3975
|
+
)
|
|
3976
|
+
);
|
|
3977
|
+
await waitForEnter4();
|
|
3978
|
+
} else {
|
|
3979
|
+
await viewTables(dbConfig);
|
|
3980
|
+
}
|
|
3981
|
+
break;
|
|
3982
|
+
case "table":
|
|
3983
|
+
const { runTableManagerFlow: runTableManagerFlow2 } = await Promise.resolve().then(() => (init_tableManagerFlow(), tableManagerFlow_exports));
|
|
3984
|
+
await runTableManagerFlow2(dbConfig);
|
|
3985
|
+
break;
|
|
3986
|
+
case "repl":
|
|
3987
|
+
await runRepl(dbConfig);
|
|
3988
|
+
break;
|
|
3989
|
+
case "setup":
|
|
3990
|
+
case "re-configure":
|
|
3991
|
+
dbConfig = await runSetup(dbConfig);
|
|
3992
|
+
await waitForEnter4();
|
|
3993
|
+
break;
|
|
3994
|
+
case "exit":
|
|
3995
|
+
running = false;
|
|
3996
|
+
console.log(pc26.dim("\nThanks for using Drixio. Goodbye!"));
|
|
3997
|
+
break;
|
|
3998
|
+
}
|
|
3999
|
+
}
|
|
4000
|
+
async function waitForEnter4() {
|
|
4001
|
+
const { input: input7 } = await import("@inquirer/prompts");
|
|
4002
|
+
await input7({
|
|
4003
|
+
message: "Click Enter to continue..."
|
|
4004
|
+
});
|
|
4005
|
+
}
|
|
4006
|
+
}
|
|
4007
|
+
|
|
4008
|
+
// bin/cli.ts
|
|
4009
|
+
main().catch((error) => {
|
|
4010
|
+
if (error.name === "ExitPromptError") {
|
|
4011
|
+
console.log("\n Exit Drixio.");
|
|
4012
|
+
process.exit(0);
|
|
4013
|
+
}
|
|
4014
|
+
console.error("\n Error: ", error);
|
|
4015
|
+
process.exit(1);
|
|
4016
|
+
});
|