midql-cli 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -0
- package/dist/{App-W6AAY42M.js → App-WRKCMUDZ.js} +34 -13
- package/dist/{chunk-3CIK5I4M.js → chunk-4C62NPJ6.js} +1 -1
- package/dist/chunk-5GQDAM2V.js +176 -0
- package/dist/{chunk-WN6T44OZ.js → chunk-XTMRD34N.js} +28 -23
- package/dist/cli.js +8 -8
- package/dist/{controller-HDNDKU6K.js → controller-WRZHP73H.js} +6 -4
- package/dist/{execute-E45HSHNE.js → execute-FFUQSEFN.js} +23 -20
- package/dist/{maintenance-QHIUTVV3.js → maintenance-IAOCUGWQ.js} +7 -7
- package/dist/{meta-NZ5Z6NYN.js → meta-V6NMP2YL.js} +88 -19
- package/dist/profileWizard-7GJFTDLY.js +206 -0
- package/dist/{profiles-4QGMCJAO.js → profiles-GGTSKNLB.js} +2 -2
- package/dist/profiles-SW242OOI.js +16 -0
- package/dist/{query-RCUXCOAF.js → query-4C3XZBC2.js} +10 -10
- package/package.json +1 -1
- package/dist/chunk-7WNCISNV.js +0 -106
- /package/dist/{chunk-3QAR6XBK.js → chunk-5YB2C5KL.js} +0 -0
package/README.md
CHANGED
|
@@ -76,6 +76,7 @@ All values are sent as bound parameters — never interpolated into SQL — so i
|
|
|
76
76
|
|
|
77
77
|
## REPL reference
|
|
78
78
|
|
|
79
|
+
- Results adapt to your terminal: columns shrink to fit the window, and when a row is too wide to fit at all, MidQL switches to a vertical record view (one `column value` pair per line) automatically.
|
|
79
80
|
- Input starting with a SQL keyword runs as raw SQL on a **pinned session connection**, so `BEGIN` / `COMMIT` / `ROLLBACK` work naturally; an open transaction shows a `[tx]` badge in the prompt.
|
|
80
81
|
- **Tab** accepts the top completion (verbs → tables → columns → operators, context-aware in both NL and SQL modes). Columns reachable through a foreign key are annotated `(accounts, joined)`.
|
|
81
82
|
- **↑/↓** history, **Ctrl+R** history search, **Ctrl+C** clear line (twice to exit), **Ctrl+A/E** home/end, **Esc** dismiss.
|
|
@@ -83,6 +84,9 @@ All values are sent as bound parameters — never interpolated into SQL — so i
|
|
|
83
84
|
| Command | Description |
|
|
84
85
|
|---|---|
|
|
85
86
|
| `\c <profile\|url> [alias]` | connect (multiple connections at once) |
|
|
87
|
+
| `\profiles` | list saved profiles |
|
|
88
|
+
| `\profile add [name]` | create a profile with a guided step-by-step wizard (masked password input) |
|
|
89
|
+
| `\profile remove <name>` | delete a saved profile |
|
|
86
90
|
| `\use <alias>` | switch the active connection |
|
|
87
91
|
| `\connections` | list open connections |
|
|
88
92
|
| `\disconnect <alias>` | close a connection |
|
|
@@ -12,10 +12,10 @@ import {
|
|
|
12
12
|
startsWithSqlKeyword,
|
|
13
13
|
tokenize,
|
|
14
14
|
translate
|
|
15
|
-
} from "./chunk-
|
|
16
|
-
import "./chunk-
|
|
17
|
-
import "./chunk-
|
|
18
|
-
import "./chunk-
|
|
15
|
+
} from "./chunk-XTMRD34N.js";
|
|
16
|
+
import "./chunk-4C62NPJ6.js";
|
|
17
|
+
import "./chunk-5GQDAM2V.js";
|
|
18
|
+
import "./chunk-5YB2C5KL.js";
|
|
19
19
|
import "./chunk-PXDMSYWH.js";
|
|
20
20
|
import {
|
|
21
21
|
AmbiguityError,
|
|
@@ -36,6 +36,7 @@ function LineEditor({
|
|
|
36
36
|
promptColor,
|
|
37
37
|
disabled,
|
|
38
38
|
history,
|
|
39
|
+
mask,
|
|
39
40
|
onSubmit,
|
|
40
41
|
onExitRequest,
|
|
41
42
|
onChange,
|
|
@@ -153,9 +154,10 @@ function LineEditor({
|
|
|
153
154
|
},
|
|
154
155
|
{ isActive: !disabled }
|
|
155
156
|
);
|
|
156
|
-
const
|
|
157
|
-
const
|
|
158
|
-
const
|
|
157
|
+
const display = mask ? "\u2022".repeat(buffer.length) : buffer;
|
|
158
|
+
const before = display.slice(0, cursor);
|
|
159
|
+
const at = display[cursor] ?? " ";
|
|
160
|
+
const after = display.slice(cursor + 1);
|
|
159
161
|
return /* @__PURE__ */ jsxs(Text, { children: [
|
|
160
162
|
/* @__PURE__ */ jsx(Text, { color: promptColor, bold: true, children: promptText }),
|
|
161
163
|
/* @__PURE__ */ jsx(Text, { children: before }),
|
|
@@ -408,8 +410,9 @@ function App({ controller, version, initialTarget }) {
|
|
|
408
410
|
async (value, echoPrefix) => {
|
|
409
411
|
setBuffer("");
|
|
410
412
|
setSearchMode(false);
|
|
411
|
-
|
|
412
|
-
|
|
413
|
+
const masked = controller.pendingConfirmation()?.mask === true;
|
|
414
|
+
if (value.trim().length > 0 || masked) {
|
|
415
|
+
append([{ id: 0, kind: "echo", text: `${echoPrefix}${masked ? "\u2022\u2022\u2022\u2022\u2022\u2022" : value}` }]);
|
|
413
416
|
}
|
|
414
417
|
setBusy(true);
|
|
415
418
|
const result = await controller.handleInput(value);
|
|
@@ -423,17 +426,34 @@ function App({ controller, version, initialTarget }) {
|
|
|
423
426
|
[append, controller, exit]
|
|
424
427
|
);
|
|
425
428
|
useEffect(() => {
|
|
426
|
-
|
|
429
|
+
const banner = [
|
|
427
430
|
{
|
|
428
431
|
id: 0,
|
|
429
432
|
kind: "plain",
|
|
430
433
|
text: `midql v${version} \u2014 plain English or raw SQL \xB7 \\help for commands \xB7 \\q to quit`
|
|
431
434
|
}
|
|
432
|
-
]
|
|
435
|
+
];
|
|
436
|
+
if (!initialTarget) {
|
|
437
|
+
const profiles = controller.config.profiles;
|
|
438
|
+
if (profiles.length > 0) {
|
|
439
|
+
banner.push({
|
|
440
|
+
id: 0,
|
|
441
|
+
kind: "info",
|
|
442
|
+
text: `Saved profiles: ${profiles.map((profile) => profile.name).join(", ")} \u2014 connect with \\c <name>`
|
|
443
|
+
});
|
|
444
|
+
} else {
|
|
445
|
+
banner.push({
|
|
446
|
+
id: 0,
|
|
447
|
+
kind: "info",
|
|
448
|
+
text: "Get started: \\profile add (guided setup) or \\c postgres://user:pass@host:5432/db"
|
|
449
|
+
});
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
append(banner);
|
|
433
453
|
if (initialTarget) {
|
|
434
454
|
void runInput(`\\c ${initialTarget}`, "");
|
|
435
455
|
}
|
|
436
|
-
}, [append, initialTarget, runInput, version]);
|
|
456
|
+
}, [append, controller, initialTarget, runInput, version]);
|
|
437
457
|
const info = controller.promptInfo();
|
|
438
458
|
const pending = controller.pendingConfirmation();
|
|
439
459
|
const connection = controller.manager.active();
|
|
@@ -492,7 +512,7 @@ function App({ controller, version, initialTarget }) {
|
|
|
492
512
|
let promptColor;
|
|
493
513
|
if (pending) {
|
|
494
514
|
promptText = `${pending.prompt}> `;
|
|
495
|
-
promptColor = "red";
|
|
515
|
+
promptColor = pending.prompt === "y/N" || pending.prompt.includes("confirm") ? "red" : "cyan";
|
|
496
516
|
} else if (searchMode) {
|
|
497
517
|
promptText = "(history search)> ";
|
|
498
518
|
promptColor = "magenta";
|
|
@@ -520,6 +540,7 @@ function App({ controller, version, initialTarget }) {
|
|
|
520
540
|
promptColor,
|
|
521
541
|
disabled: busy,
|
|
522
542
|
history: controller.inputHistory,
|
|
543
|
+
mask: pending?.mask === true,
|
|
523
544
|
onSubmit: (value) => void runInput(value, promptText),
|
|
524
545
|
onExitRequest: handleExitRequest,
|
|
525
546
|
onChange: setBuffer,
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/repl/format.ts
|
|
4
|
+
import Table from "cli-table3";
|
|
5
|
+
import pc from "picocolors";
|
|
6
|
+
var MAX_ROWS_SHOWN = 50;
|
|
7
|
+
var ROW_LIMIT_BEFORE_TRUNCATION = 200;
|
|
8
|
+
var MAX_CELL_WIDTH = 40;
|
|
9
|
+
var MIN_CELL_WIDTH = 5;
|
|
10
|
+
var CHROME_PER_COLUMN = 3;
|
|
11
|
+
function cellText(value) {
|
|
12
|
+
if (value === null || value === void 0) {
|
|
13
|
+
return "\u2205";
|
|
14
|
+
}
|
|
15
|
+
if (value instanceof Date) {
|
|
16
|
+
return value.toISOString();
|
|
17
|
+
}
|
|
18
|
+
if (typeof value === "object") {
|
|
19
|
+
return JSON.stringify(value);
|
|
20
|
+
}
|
|
21
|
+
return String(value).replace(/\r?\n/g, " ");
|
|
22
|
+
}
|
|
23
|
+
function clip(text, width) {
|
|
24
|
+
if (text.length <= width) {
|
|
25
|
+
return text;
|
|
26
|
+
}
|
|
27
|
+
return `${text.slice(0, Math.max(1, width - 1))}\u2026`;
|
|
28
|
+
}
|
|
29
|
+
function terminalWidth() {
|
|
30
|
+
const columns = process.stdout.columns;
|
|
31
|
+
return typeof columns === "number" && columns > 20 ? columns : 100;
|
|
32
|
+
}
|
|
33
|
+
function fitColumnWidths(naturals, available) {
|
|
34
|
+
const widths = naturals.map((natural) => Math.min(natural, MAX_CELL_WIDTH));
|
|
35
|
+
const chrome = widths.length * CHROME_PER_COLUMN + 1;
|
|
36
|
+
let total = widths.reduce((sum, width) => sum + width, 0) + chrome;
|
|
37
|
+
if (total <= available) {
|
|
38
|
+
return widths;
|
|
39
|
+
}
|
|
40
|
+
while (total > available) {
|
|
41
|
+
let widestIndex = -1;
|
|
42
|
+
for (let index = 0; index < widths.length; index++) {
|
|
43
|
+
if (widths[index] > MIN_CELL_WIDTH && (widestIndex < 0 || widths[index] > widths[widestIndex])) {
|
|
44
|
+
widestIndex = index;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
if (widestIndex < 0) {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
widths[widestIndex] = widths[widestIndex] - 1;
|
|
51
|
+
total -= 1;
|
|
52
|
+
}
|
|
53
|
+
return widths;
|
|
54
|
+
}
|
|
55
|
+
function verticalRecords(result, shown, width) {
|
|
56
|
+
const labelWidth = Math.min(
|
|
57
|
+
Math.max(...result.columns.map((column) => column.length)),
|
|
58
|
+
Math.floor(width / 3)
|
|
59
|
+
);
|
|
60
|
+
const valueWidth = Math.max(10, width - labelWidth - 3);
|
|
61
|
+
const lines = [];
|
|
62
|
+
shown.forEach((row, index) => {
|
|
63
|
+
const header = `\u2500[ row ${index + 1} ]`;
|
|
64
|
+
lines.push(pc.cyan(header.padEnd(Math.min(width, labelWidth + valueWidth + 3), "\u2500")));
|
|
65
|
+
for (const column of result.columns) {
|
|
66
|
+
const label = clip(column, labelWidth).padEnd(labelWidth);
|
|
67
|
+
const raw = cellText(row[column]);
|
|
68
|
+
const value = raw === "\u2205" ? pc.dim("\u2205") : clip(raw, valueWidth);
|
|
69
|
+
lines.push(`${pc.cyan(label)} ${value}`);
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
return lines.join("\n");
|
|
73
|
+
}
|
|
74
|
+
function plainTable(head, rows) {
|
|
75
|
+
const table = new Table({
|
|
76
|
+
head: head.map((column) => pc.cyan(column)),
|
|
77
|
+
style: { head: [], border: [] }
|
|
78
|
+
});
|
|
79
|
+
for (const row of rows) {
|
|
80
|
+
table.push(row);
|
|
81
|
+
}
|
|
82
|
+
return table.toString();
|
|
83
|
+
}
|
|
84
|
+
function describeTable(table) {
|
|
85
|
+
const rows = table.columns.map((column) => {
|
|
86
|
+
const flags = [];
|
|
87
|
+
if (column.isPrimaryKey) {
|
|
88
|
+
flags.push("PK");
|
|
89
|
+
}
|
|
90
|
+
for (const fk of table.foreignKeys) {
|
|
91
|
+
const index = fk.columns.indexOf(column.name);
|
|
92
|
+
if (index >= 0) {
|
|
93
|
+
flags.push(`FK \u2192 ${fk.referencedTable}.${fk.referencedColumns[index]}`);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return [
|
|
97
|
+
column.name,
|
|
98
|
+
column.dataType,
|
|
99
|
+
column.nullable ? "yes" : "no",
|
|
100
|
+
column.hasDefault ? "yes" : "no",
|
|
101
|
+
flags.join(", ")
|
|
102
|
+
];
|
|
103
|
+
});
|
|
104
|
+
return plainTable(["column", "type", "nullable", "default", "keys"], rows);
|
|
105
|
+
}
|
|
106
|
+
function formatTableList(schema) {
|
|
107
|
+
const rows = schema.tables.map((table) => [
|
|
108
|
+
table.name,
|
|
109
|
+
String(table.columns.length),
|
|
110
|
+
`~${table.approxRowCount}`
|
|
111
|
+
]);
|
|
112
|
+
return plainTable(["table", "columns", "rows"], rows);
|
|
113
|
+
}
|
|
114
|
+
function formatResultSet(result, width = terminalWidth()) {
|
|
115
|
+
if (result.columns.length === 0) {
|
|
116
|
+
const verb = result.command || "OK";
|
|
117
|
+
return pc.green(
|
|
118
|
+
`${verb} \xB7 ${result.rowCount} row${result.rowCount === 1 ? "" : "s"} affected \xB7 ${result.durationMs}ms`
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
if (result.rows.length === 0) {
|
|
122
|
+
return pc.dim(`(no rows) \xB7 ${result.durationMs}ms`);
|
|
123
|
+
}
|
|
124
|
+
const truncate = result.rows.length > ROW_LIMIT_BEFORE_TRUNCATION;
|
|
125
|
+
const shown = truncate ? result.rows.slice(0, MAX_ROWS_SHOWN) : result.rows;
|
|
126
|
+
const naturals = result.columns.map((column) => {
|
|
127
|
+
let widest = column.length;
|
|
128
|
+
for (const row of shown) {
|
|
129
|
+
const length = cellText(row[column]).length;
|
|
130
|
+
if (length > widest) {
|
|
131
|
+
widest = length;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return Math.max(widest, MIN_CELL_WIDTH);
|
|
135
|
+
});
|
|
136
|
+
const widths = fitColumnWidths(naturals, width);
|
|
137
|
+
const lines = [];
|
|
138
|
+
if (widths === null) {
|
|
139
|
+
lines.push(verticalRecords(result, shown, width));
|
|
140
|
+
} else {
|
|
141
|
+
const table = new Table({
|
|
142
|
+
head: result.columns.map((column, index) => pc.cyan(clip(column, widths[index]))),
|
|
143
|
+
colWidths: widths.map((cellWidth) => cellWidth + 2),
|
|
144
|
+
style: { head: [], border: [] }
|
|
145
|
+
});
|
|
146
|
+
for (const row of shown) {
|
|
147
|
+
table.push(
|
|
148
|
+
result.columns.map((column, index) => {
|
|
149
|
+
const raw = cellText(row[column]);
|
|
150
|
+
return raw === "\u2205" ? pc.dim("\u2205") : clip(raw, widths[index]);
|
|
151
|
+
})
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
lines.push(table.toString());
|
|
155
|
+
}
|
|
156
|
+
if (truncate) {
|
|
157
|
+
lines.push(
|
|
158
|
+
pc.yellow(
|
|
159
|
+
`\u2026 showing first ${MAX_ROWS_SHOWN} of ${result.rows.length} rows (add 'first N' or use \\export)`
|
|
160
|
+
)
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
lines.push(
|
|
164
|
+
pc.dim(
|
|
165
|
+
`${result.rows.length} row${result.rows.length === 1 ? "" : "s"} \xB7 ${result.durationMs}ms`
|
|
166
|
+
)
|
|
167
|
+
);
|
|
168
|
+
return lines.join("\n");
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
export {
|
|
172
|
+
plainTable,
|
|
173
|
+
describeTable,
|
|
174
|
+
formatTableList,
|
|
175
|
+
formatResultSet
|
|
176
|
+
};
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
ConnectionManager
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-4C62NPJ6.js";
|
|
5
5
|
import {
|
|
6
6
|
formatResultSet
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-5GQDAM2V.js";
|
|
8
8
|
import {
|
|
9
9
|
historyFilePath,
|
|
10
10
|
loadConfig
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-5YB2C5KL.js";
|
|
12
12
|
import {
|
|
13
13
|
AmbiguityError,
|
|
14
14
|
MidqlError,
|
|
@@ -1376,6 +1376,19 @@ function searchHistory(entries, term) {
|
|
|
1376
1376
|
}
|
|
1377
1377
|
|
|
1378
1378
|
// src/repl/controller.ts
|
|
1379
|
+
function confirmationPrompt(promptText, expectedText, execute) {
|
|
1380
|
+
return {
|
|
1381
|
+
prompt: promptText,
|
|
1382
|
+
async handle(input) {
|
|
1383
|
+
const normalized = input.trim();
|
|
1384
|
+
const confirmed = expectedText ? normalized === expectedText : /^y(es)?$/i.test(normalized);
|
|
1385
|
+
if (!confirmed) {
|
|
1386
|
+
return { blocks: [block("info", "Cancelled.")] };
|
|
1387
|
+
}
|
|
1388
|
+
return { blocks: await execute() };
|
|
1389
|
+
}
|
|
1390
|
+
};
|
|
1391
|
+
}
|
|
1379
1392
|
function block(kind, text) {
|
|
1380
1393
|
return { kind, text };
|
|
1381
1394
|
}
|
|
@@ -1448,8 +1461,8 @@ var ReplController = class {
|
|
|
1448
1461
|
pendingConfirmation() {
|
|
1449
1462
|
return this.pending;
|
|
1450
1463
|
}
|
|
1451
|
-
setPending(
|
|
1452
|
-
this.pending =
|
|
1464
|
+
setPending(prompt) {
|
|
1465
|
+
this.pending = prompt;
|
|
1453
1466
|
}
|
|
1454
1467
|
async handleInput(rawInput) {
|
|
1455
1468
|
const input = rawInput.trim();
|
|
@@ -1461,7 +1474,7 @@ var ReplController = class {
|
|
|
1461
1474
|
}
|
|
1462
1475
|
try {
|
|
1463
1476
|
if (input.startsWith("\\")) {
|
|
1464
|
-
const { dispatchMeta } = await import("./meta-
|
|
1477
|
+
const { dispatchMeta } = await import("./meta-V6NMP2YL.js");
|
|
1465
1478
|
return await dispatchMeta(this, input);
|
|
1466
1479
|
}
|
|
1467
1480
|
return await this.handleQuery(input);
|
|
@@ -1492,13 +1505,8 @@ var ReplController = class {
|
|
|
1492
1505
|
async respondToPending(input) {
|
|
1493
1506
|
const pending = this.pending;
|
|
1494
1507
|
this.pending = null;
|
|
1495
|
-
const normalized = input.trim();
|
|
1496
|
-
const confirmed = pending.expectedText ? normalized === pending.expectedText : /^y(es)?$/i.test(normalized);
|
|
1497
|
-
if (!confirmed) {
|
|
1498
|
-
return { blocks: [block("info", "Cancelled.")] };
|
|
1499
|
-
}
|
|
1500
1508
|
try {
|
|
1501
|
-
return
|
|
1509
|
+
return await pending.handle(input);
|
|
1502
1510
|
} catch (error) {
|
|
1503
1511
|
return { blocks: [this.errorBlock(error)] };
|
|
1504
1512
|
}
|
|
@@ -1535,7 +1543,7 @@ var ReplController = class {
|
|
|
1535
1543
|
}
|
|
1536
1544
|
}
|
|
1537
1545
|
async runAst(connection, input, ast) {
|
|
1538
|
-
const { executeAst } = await import("./execute-
|
|
1546
|
+
const { executeAst } = await import("./execute-FFUQSEFN.js");
|
|
1539
1547
|
return executeAst(this, connection, input, ast);
|
|
1540
1548
|
}
|
|
1541
1549
|
async runRawSql(connection, sql) {
|
|
@@ -1567,19 +1575,15 @@ var ReplController = class {
|
|
|
1567
1575
|
)
|
|
1568
1576
|
);
|
|
1569
1577
|
blocks.push(block("warn", `Type '${expected}' to confirm:`));
|
|
1570
|
-
this.pending =
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1578
|
+
this.pending = confirmationPrompt(
|
|
1579
|
+
`confirm ${expected}`,
|
|
1580
|
+
expected,
|
|
1581
|
+
() => this.executeRaw(connection, sql)
|
|
1582
|
+
);
|
|
1575
1583
|
} else {
|
|
1576
1584
|
blocks.push(block("warn", `\u26A0${prodBanner} ${verb} will modify ${table ?? "data"}.`));
|
|
1577
1585
|
blocks.push(block("warn", "Proceed? [y/N]"));
|
|
1578
|
-
this.pending =
|
|
1579
|
-
prompt: "y/N",
|
|
1580
|
-
expectedText: null,
|
|
1581
|
-
execute: () => this.executeRaw(connection, sql)
|
|
1582
|
-
};
|
|
1586
|
+
this.pending = confirmationPrompt("y/N", null, () => this.executeRaw(connection, sql));
|
|
1583
1587
|
}
|
|
1584
1588
|
return { blocks };
|
|
1585
1589
|
}
|
|
@@ -1636,6 +1640,7 @@ export {
|
|
|
1636
1640
|
classifyRawSql,
|
|
1637
1641
|
loadHistory,
|
|
1638
1642
|
searchHistory,
|
|
1643
|
+
confirmationPrompt,
|
|
1639
1644
|
block,
|
|
1640
1645
|
startsWithSqlKeyword,
|
|
1641
1646
|
hasSqlShape,
|
package/dist/cli.js
CHANGED
|
@@ -9,8 +9,8 @@ async function startRepl(target) {
|
|
|
9
9
|
const [{ render }, { createElement }, { App }, { ReplController }] = await Promise.all([
|
|
10
10
|
import("ink"),
|
|
11
11
|
import("react"),
|
|
12
|
-
import("./App-
|
|
13
|
-
import("./controller-
|
|
12
|
+
import("./App-WRKCMUDZ.js"),
|
|
13
|
+
import("./controller-WRZHP73H.js")
|
|
14
14
|
]);
|
|
15
15
|
const controller = new ReplController();
|
|
16
16
|
const instance = render(
|
|
@@ -37,7 +37,7 @@ program.argument("[connection]", "profile name or connection URL to connect on s
|
|
|
37
37
|
});
|
|
38
38
|
program.command("query <input>").description("run a single query (plain English or raw SQL) and exit").requiredOption("--db <target>", "profile name or connection URL").option("--yes", "allow destructive statements", false).option("--format <format>", "output format: table, json, csv", "table").action(async (input, options) => {
|
|
39
39
|
try {
|
|
40
|
-
const { runQueryCommand } = await import("./query-
|
|
40
|
+
const { runQueryCommand } = await import("./query-4C3XZBC2.js");
|
|
41
41
|
const format = ["table", "json", "csv"].includes(options.format) ? options.format : "table";
|
|
42
42
|
process.exit(await runQueryCommand(input, { db: options.db, yes: options.yes, format }));
|
|
43
43
|
} catch (error) {
|
|
@@ -46,7 +46,7 @@ program.command("query <input>").description("run a single query (plain English
|
|
|
46
46
|
});
|
|
47
47
|
program.command("backup [file]").description("back up a database via pg_dump / mysqldump").requiredOption("--db <target>", "profile name or connection URL").option("--sql", "write a plain SQL dump instead of the custom format", false).action(async (file, options) => {
|
|
48
48
|
try {
|
|
49
|
-
const { runBackupCommand } = await import("./maintenance-
|
|
49
|
+
const { runBackupCommand } = await import("./maintenance-IAOCUGWQ.js");
|
|
50
50
|
process.exit(await runBackupCommand(file, options));
|
|
51
51
|
} catch (error) {
|
|
52
52
|
fail(error);
|
|
@@ -54,7 +54,7 @@ program.command("backup [file]").description("back up a database via pg_dump / m
|
|
|
54
54
|
});
|
|
55
55
|
program.command("restore <file>").description("restore a database from a backup file").requiredOption("--db <target>", "profile name or connection URL").option("--yes", "confirm overwriting the target database", false).action(async (file, options) => {
|
|
56
56
|
try {
|
|
57
|
-
const { runRestoreCommand } = await import("./maintenance-
|
|
57
|
+
const { runRestoreCommand } = await import("./maintenance-IAOCUGWQ.js");
|
|
58
58
|
process.exit(await runRestoreCommand(file, options));
|
|
59
59
|
} catch (error) {
|
|
60
60
|
fail(error);
|
|
@@ -63,7 +63,7 @@ program.command("restore <file>").description("restore a database from a backup
|
|
|
63
63
|
var profiles = program.command("profiles").description("manage saved connection profiles");
|
|
64
64
|
profiles.command("list").description("list saved profiles").action(async () => {
|
|
65
65
|
try {
|
|
66
|
-
const { runProfilesList } = await import("./profiles-
|
|
66
|
+
const { runProfilesList } = await import("./profiles-GGTSKNLB.js");
|
|
67
67
|
runProfilesList();
|
|
68
68
|
} catch (error) {
|
|
69
69
|
fail(error);
|
|
@@ -71,7 +71,7 @@ profiles.command("list").description("list saved profiles").action(async () => {
|
|
|
71
71
|
});
|
|
72
72
|
profiles.command("add <name>").description("save a connection profile").requiredOption("--dialect <dialect>", "postgres or mysql").requiredOption("--host <host>", "database host").requiredOption("--database <database>", "database name").requiredOption("--user <user>", "database user").option("--port <port>", "database port").option("--password <password>", "password to store encrypted").option("--password-env <name>", "environment variable to read the password from").option("--env <env>", "environment tag: dev, staging, prod", "dev").option("--readonly", "open this connection read-only by default", false).option("--ssl", "connect over SSL", false).action(async (name, options) => {
|
|
73
73
|
try {
|
|
74
|
-
const { runProfilesAdd } = await import("./profiles-
|
|
74
|
+
const { runProfilesAdd } = await import("./profiles-GGTSKNLB.js");
|
|
75
75
|
process.exit(
|
|
76
76
|
runProfilesAdd(name, {
|
|
77
77
|
dialect: String(options.dialect),
|
|
@@ -92,7 +92,7 @@ profiles.command("add <name>").description("save a connection profile").required
|
|
|
92
92
|
});
|
|
93
93
|
profiles.command("remove <name>").description("delete a saved profile").action(async (name) => {
|
|
94
94
|
try {
|
|
95
|
-
const { runProfilesRemove } = await import("./profiles-
|
|
95
|
+
const { runProfilesRemove } = await import("./profiles-GGTSKNLB.js");
|
|
96
96
|
process.exit(runProfilesRemove(name));
|
|
97
97
|
} catch (error) {
|
|
98
98
|
fail(error);
|
|
@@ -2,17 +2,19 @@
|
|
|
2
2
|
import {
|
|
3
3
|
ReplController,
|
|
4
4
|
block,
|
|
5
|
+
confirmationPrompt,
|
|
5
6
|
hasSqlShape,
|
|
6
7
|
startsWithSqlKeyword
|
|
7
|
-
} from "./chunk-
|
|
8
|
-
import "./chunk-
|
|
9
|
-
import "./chunk-
|
|
10
|
-
import "./chunk-
|
|
8
|
+
} from "./chunk-XTMRD34N.js";
|
|
9
|
+
import "./chunk-4C62NPJ6.js";
|
|
10
|
+
import "./chunk-5GQDAM2V.js";
|
|
11
|
+
import "./chunk-5YB2C5KL.js";
|
|
11
12
|
import "./chunk-PXDMSYWH.js";
|
|
12
13
|
import "./chunk-VFC3HWTF.js";
|
|
13
14
|
export {
|
|
14
15
|
ReplController,
|
|
15
16
|
block,
|
|
17
|
+
confirmationPrompt,
|
|
16
18
|
hasSqlShape,
|
|
17
19
|
startsWithSqlKeyword
|
|
18
20
|
};
|
|
@@ -10,15 +10,16 @@ import {
|
|
|
10
10
|
import {
|
|
11
11
|
astIsWrite,
|
|
12
12
|
block,
|
|
13
|
-
classifyAst
|
|
14
|
-
|
|
15
|
-
|
|
13
|
+
classifyAst,
|
|
14
|
+
confirmationPrompt
|
|
15
|
+
} from "./chunk-XTMRD34N.js";
|
|
16
|
+
import "./chunk-4C62NPJ6.js";
|
|
16
17
|
import {
|
|
17
18
|
describeTable,
|
|
18
19
|
formatResultSet,
|
|
19
20
|
formatTableList
|
|
20
|
-
} from "./chunk-
|
|
21
|
-
import "./chunk-
|
|
21
|
+
} from "./chunk-5GQDAM2V.js";
|
|
22
|
+
import "./chunk-5YB2C5KL.js";
|
|
22
23
|
import "./chunk-PXDMSYWH.js";
|
|
23
24
|
import {
|
|
24
25
|
ParseError
|
|
@@ -87,11 +88,13 @@ function confirmDropTable(controller, connection, input, table, built) {
|
|
|
87
88
|
const meta = connection.schema.tables.find((entry) => entry.name === table);
|
|
88
89
|
const rowNote = meta ? ` (~${meta.approxRowCount} rows)` : "";
|
|
89
90
|
const prodBanner = connection.env === "prod" ? " (PRODUCTION)" : "";
|
|
90
|
-
controller.setPending(
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
91
|
+
controller.setPending(
|
|
92
|
+
confirmationPrompt(
|
|
93
|
+
`type '${table}' to confirm`,
|
|
94
|
+
table,
|
|
95
|
+
() => runBuilt(controller, connection, input, built)
|
|
96
|
+
)
|
|
97
|
+
);
|
|
95
98
|
return {
|
|
96
99
|
blocks: [
|
|
97
100
|
block("sql", `\u2192 ${built.sql}`),
|
|
@@ -135,18 +138,18 @@ async function confirmDestructive(controller, connection, input, ast, built, dan
|
|
|
135
138
|
}
|
|
136
139
|
if (danger === "catastrophic") {
|
|
137
140
|
blocks.push(block("warn", `Type '${ast.table}' to confirm:`));
|
|
138
|
-
controller.setPending(
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
141
|
+
controller.setPending(
|
|
142
|
+
confirmationPrompt(
|
|
143
|
+
`type '${ast.table}' to confirm`,
|
|
144
|
+
ast.table,
|
|
145
|
+
() => runBuilt(controller, connection, input, built)
|
|
146
|
+
)
|
|
147
|
+
);
|
|
143
148
|
} else {
|
|
144
149
|
blocks.push(block("warn", "Proceed? [y/N]"));
|
|
145
|
-
controller.setPending(
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
execute: () => runBuilt(controller, connection, input, built)
|
|
149
|
-
});
|
|
150
|
+
controller.setPending(
|
|
151
|
+
confirmationPrompt("y/N", null, () => runBuilt(controller, connection, input, built))
|
|
152
|
+
);
|
|
150
153
|
}
|
|
151
154
|
return { blocks };
|
|
152
155
|
}
|
|
@@ -1,4 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
resolveTarget
|
|
4
|
+
} from "./chunk-4C62NPJ6.js";
|
|
5
|
+
import {
|
|
6
|
+
loadConfig
|
|
7
|
+
} from "./chunk-5YB2C5KL.js";
|
|
8
|
+
import "./chunk-PXDMSYWH.js";
|
|
2
9
|
import {
|
|
3
10
|
backupDatabase,
|
|
4
11
|
defaultBackupFile
|
|
@@ -7,13 +14,6 @@ import {
|
|
|
7
14
|
restoreDatabase
|
|
8
15
|
} from "./chunk-3FPZQHHV.js";
|
|
9
16
|
import "./chunk-NCN3ZBOJ.js";
|
|
10
|
-
import {
|
|
11
|
-
resolveTarget
|
|
12
|
-
} from "./chunk-3CIK5I4M.js";
|
|
13
|
-
import {
|
|
14
|
-
loadConfig
|
|
15
|
-
} from "./chunk-3QAR6XBK.js";
|
|
16
|
-
import "./chunk-PXDMSYWH.js";
|
|
17
17
|
import "./chunk-VFC3HWTF.js";
|
|
18
18
|
|
|
19
19
|
// src/commands/maintenance.ts
|
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
block,
|
|
4
|
+
confirmationPrompt,
|
|
4
5
|
loadHistory,
|
|
5
6
|
searchHistory
|
|
6
|
-
} from "./chunk-
|
|
7
|
-
import "./chunk-
|
|
7
|
+
} from "./chunk-XTMRD34N.js";
|
|
8
|
+
import "./chunk-4C62NPJ6.js";
|
|
8
9
|
import {
|
|
9
10
|
describeTable,
|
|
10
11
|
formatTableList,
|
|
11
12
|
plainTable
|
|
12
|
-
} from "./chunk-
|
|
13
|
-
import "./chunk-
|
|
13
|
+
} from "./chunk-5GQDAM2V.js";
|
|
14
|
+
import "./chunk-5YB2C5KL.js";
|
|
14
15
|
import "./chunk-PXDMSYWH.js";
|
|
15
16
|
import "./chunk-VFC3HWTF.js";
|
|
16
17
|
|
|
@@ -213,6 +214,72 @@ var commands = {
|
|
|
213
214
|
};
|
|
214
215
|
}
|
|
215
216
|
},
|
|
217
|
+
profiles: {
|
|
218
|
+
usage: "\\profiles",
|
|
219
|
+
description: "List saved connection profiles",
|
|
220
|
+
async run() {
|
|
221
|
+
const { listProfiles } = await import("./profiles-SW242OOI.js");
|
|
222
|
+
const profiles = listProfiles();
|
|
223
|
+
if (profiles.length === 0) {
|
|
224
|
+
return {
|
|
225
|
+
blocks: [
|
|
226
|
+
block("info", "No saved profiles yet."),
|
|
227
|
+
block("plain", "Create one with \\profile add \u2014 it walks you through every field.")
|
|
228
|
+
]
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
const rows = profiles.map((profile) => [
|
|
232
|
+
profile.name,
|
|
233
|
+
profile.dialect,
|
|
234
|
+
`${profile.user}@${profile.host}:${profile.port}/${profile.database}`,
|
|
235
|
+
profile.env,
|
|
236
|
+
profile.readonly ? "read-only" : "read-write",
|
|
237
|
+
profile.encryptedPassword ? "stored" : profile.passwordEnv ? `env:${profile.passwordEnv}` : "none"
|
|
238
|
+
]);
|
|
239
|
+
return {
|
|
240
|
+
blocks: [
|
|
241
|
+
block(
|
|
242
|
+
"table",
|
|
243
|
+
plainTable(["name", "dialect", "target", "env", "mode", "password"], rows)
|
|
244
|
+
),
|
|
245
|
+
block(
|
|
246
|
+
"plain",
|
|
247
|
+
"Connect: \\c <name> \xB7 Add: \\profile add \xB7 Remove: \\profile remove <name>"
|
|
248
|
+
)
|
|
249
|
+
]
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
},
|
|
253
|
+
profile: {
|
|
254
|
+
usage: "\\profile add [name] | remove <name>",
|
|
255
|
+
description: "Create a profile step by step, or delete one",
|
|
256
|
+
async run(controller, args) {
|
|
257
|
+
const action = args[0]?.toLowerCase();
|
|
258
|
+
if (action === "add" || action === "new" || action === "create") {
|
|
259
|
+
const { startProfileWizard } = await import("./profileWizard-7GJFTDLY.js");
|
|
260
|
+
return startProfileWizard(controller, args[1]);
|
|
261
|
+
}
|
|
262
|
+
if (action === "remove" || action === "delete" || action === "rm") {
|
|
263
|
+
const name = args[1];
|
|
264
|
+
if (!name) {
|
|
265
|
+
return { blocks: [block("error", "Usage: \\profile remove <name>")] };
|
|
266
|
+
}
|
|
267
|
+
const { getProfile } = await import("./profiles-SW242OOI.js");
|
|
268
|
+
if (!getProfile(name)) {
|
|
269
|
+
return { blocks: [block("error", `Profile "${name}" not found. See \\profiles.`)] };
|
|
270
|
+
}
|
|
271
|
+
controller.setPending(
|
|
272
|
+
confirmationPrompt("y/N", null, async () => {
|
|
273
|
+
const { deleteProfile } = await import("./profiles-SW242OOI.js");
|
|
274
|
+
deleteProfile(name);
|
|
275
|
+
return [block("success", `Profile "${name}" removed.`)];
|
|
276
|
+
})
|
|
277
|
+
);
|
|
278
|
+
return { blocks: [block("warn", `Remove profile "${name}"? [y/N]`)] };
|
|
279
|
+
}
|
|
280
|
+
return { blocks: [block("error", "Usage: \\profile add [name] | remove <name>")] };
|
|
281
|
+
}
|
|
282
|
+
},
|
|
216
283
|
export: {
|
|
217
284
|
usage: "\\export csv|json <file>",
|
|
218
285
|
description: "Export the last result set to a file",
|
|
@@ -277,21 +344,23 @@ var commands = {
|
|
|
277
344
|
return { blocks: [block("error", "Usage: \\restore <file>")] };
|
|
278
345
|
}
|
|
279
346
|
const prodBanner = connection.env === "prod" ? " (PRODUCTION)" : "";
|
|
280
|
-
controller.setPending(
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
347
|
+
controller.setPending(
|
|
348
|
+
confirmationPrompt(
|
|
349
|
+
`type '${connection.database}' to confirm`,
|
|
350
|
+
connection.database,
|
|
351
|
+
async () => {
|
|
352
|
+
const { restoreDatabase } = await import("./restore-4QXG2WRR.js");
|
|
353
|
+
await restoreDatabase(
|
|
354
|
+
connection.dialect,
|
|
355
|
+
connection.options,
|
|
356
|
+
file,
|
|
357
|
+
controller.config.tools
|
|
358
|
+
);
|
|
359
|
+
await controller.manager.refreshSchema(connection.alias).catch(() => connection);
|
|
360
|
+
return [block("success", `Restored ${connection.database} from ${file}.`)];
|
|
361
|
+
}
|
|
362
|
+
)
|
|
363
|
+
);
|
|
295
364
|
return {
|
|
296
365
|
blocks: [
|
|
297
366
|
block(
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
block
|
|
4
|
+
} from "./chunk-XTMRD34N.js";
|
|
5
|
+
import "./chunk-4C62NPJ6.js";
|
|
6
|
+
import "./chunk-5GQDAM2V.js";
|
|
7
|
+
import {
|
|
8
|
+
getProfile,
|
|
9
|
+
saveProfile
|
|
10
|
+
} from "./chunk-5YB2C5KL.js";
|
|
11
|
+
import "./chunk-PXDMSYWH.js";
|
|
12
|
+
import "./chunk-VFC3HWTF.js";
|
|
13
|
+
|
|
14
|
+
// src/repl/profileWizard.ts
|
|
15
|
+
var steps = [
|
|
16
|
+
{
|
|
17
|
+
key: "name",
|
|
18
|
+
prompt: "profile name",
|
|
19
|
+
question: () => "Profile name (e.g. main, staging-db):",
|
|
20
|
+
apply(value, state) {
|
|
21
|
+
const name = value.trim();
|
|
22
|
+
if (!/^[\w-]+$/.test(name)) {
|
|
23
|
+
return "Use letters, numbers, - or _ only.";
|
|
24
|
+
}
|
|
25
|
+
if (getProfile(name)) {
|
|
26
|
+
return `Profile "${name}" already exists \u2014 pick another name, or remove it first with \\profile remove ${name}.`;
|
|
27
|
+
}
|
|
28
|
+
state.name = name;
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
key: "dialect",
|
|
34
|
+
prompt: "dialect",
|
|
35
|
+
question: () => "Database type \u2014 postgres or mysql [postgres]:",
|
|
36
|
+
apply(value, state) {
|
|
37
|
+
const dialect = value.trim().toLowerCase() || "postgres";
|
|
38
|
+
if (dialect !== "postgres" && dialect !== "mysql") {
|
|
39
|
+
return `"${value.trim()}" is not supported \u2014 type postgres or mysql.`;
|
|
40
|
+
}
|
|
41
|
+
state.dialect = dialect;
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
key: "host",
|
|
47
|
+
prompt: "host",
|
|
48
|
+
question: () => "Host [localhost]:",
|
|
49
|
+
apply(value, state) {
|
|
50
|
+
state.host = value.trim() || "localhost";
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
key: "port",
|
|
56
|
+
prompt: "port",
|
|
57
|
+
question: (state) => `Port [${state.dialect === "mysql" ? 3306 : 5432}]:`,
|
|
58
|
+
apply(value, state) {
|
|
59
|
+
const fallback = state.dialect === "mysql" ? 3306 : 5432;
|
|
60
|
+
const trimmed = value.trim();
|
|
61
|
+
if (!trimmed) {
|
|
62
|
+
state.port = fallback;
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
const port = Number(trimmed);
|
|
66
|
+
if (!Number.isInteger(port) || port < 1 || port > 65535) {
|
|
67
|
+
return `"${trimmed}" is not a valid port.`;
|
|
68
|
+
}
|
|
69
|
+
state.port = port;
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
key: "database",
|
|
75
|
+
prompt: "database",
|
|
76
|
+
question: () => "Database name:",
|
|
77
|
+
apply(value, state) {
|
|
78
|
+
const database = value.trim();
|
|
79
|
+
if (!database) {
|
|
80
|
+
return "Database name is required.";
|
|
81
|
+
}
|
|
82
|
+
state.database = database;
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
key: "user",
|
|
88
|
+
prompt: "user",
|
|
89
|
+
question: () => "User:",
|
|
90
|
+
apply(value, state) {
|
|
91
|
+
const user = value.trim();
|
|
92
|
+
if (!user) {
|
|
93
|
+
return "User is required.";
|
|
94
|
+
}
|
|
95
|
+
state.user = user;
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
key: "password",
|
|
101
|
+
prompt: "password",
|
|
102
|
+
question: () => "Password \u2014 stored encrypted. Empty = none, or env:VAR_NAME to read from an environment variable:",
|
|
103
|
+
mask: true,
|
|
104
|
+
apply(value, state) {
|
|
105
|
+
const trimmed = value.trim();
|
|
106
|
+
if (!trimmed) {
|
|
107
|
+
state.password = null;
|
|
108
|
+
state.passwordEnv = null;
|
|
109
|
+
} else if (trimmed.toLowerCase().startsWith("env:")) {
|
|
110
|
+
state.password = null;
|
|
111
|
+
state.passwordEnv = trimmed.slice(4);
|
|
112
|
+
} else {
|
|
113
|
+
state.password = value;
|
|
114
|
+
state.passwordEnv = null;
|
|
115
|
+
}
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
key: "env",
|
|
121
|
+
prompt: "environment",
|
|
122
|
+
question: () => "Environment \u2014 dev, staging or prod [dev]:",
|
|
123
|
+
apply(value, state) {
|
|
124
|
+
const env = value.trim().toLowerCase() || "dev";
|
|
125
|
+
if (env !== "dev" && env !== "staging" && env !== "prod") {
|
|
126
|
+
return `"${value.trim()}" is not valid \u2014 type dev, staging or prod.`;
|
|
127
|
+
}
|
|
128
|
+
state.env = env;
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
key: "readonly",
|
|
134
|
+
prompt: "read-only",
|
|
135
|
+
question: () => "Open read-only by default? y/N:",
|
|
136
|
+
apply(value, state) {
|
|
137
|
+
state.readonly = /^y(es)?$/i.test(value.trim());
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
];
|
|
142
|
+
function finishWizard(state) {
|
|
143
|
+
const profile = {
|
|
144
|
+
name: state.name,
|
|
145
|
+
dialect: state.dialect,
|
|
146
|
+
host: state.host,
|
|
147
|
+
port: state.port,
|
|
148
|
+
database: state.database,
|
|
149
|
+
user: state.user,
|
|
150
|
+
encryptedPassword: null,
|
|
151
|
+
passwordEnv: state.passwordEnv ?? null,
|
|
152
|
+
env: state.env,
|
|
153
|
+
readonly: state.readonly ?? false,
|
|
154
|
+
ssl: false
|
|
155
|
+
};
|
|
156
|
+
saveProfile(profile, state.password ?? void 0);
|
|
157
|
+
const blocks = [
|
|
158
|
+
block(
|
|
159
|
+
"success",
|
|
160
|
+
`Profile "${profile.name}" saved \u2014 ${profile.dialect}://${profile.user}@${profile.host}:${profile.port}/${profile.database} (${profile.env}${profile.readonly ? ", read-only" : ""})`
|
|
161
|
+
),
|
|
162
|
+
block("info", `Connect with: \\c ${profile.name}`)
|
|
163
|
+
];
|
|
164
|
+
if (state.password) {
|
|
165
|
+
blocks.push(block("plain", "Password stored encrypted in ~/.midql (key file: ~/.midql/.key)."));
|
|
166
|
+
}
|
|
167
|
+
return blocks;
|
|
168
|
+
}
|
|
169
|
+
function askStep(controller, index, state, prefix) {
|
|
170
|
+
const step = steps[index];
|
|
171
|
+
if (!step) {
|
|
172
|
+
return { blocks: [...prefix, ...finishWizard(state)] };
|
|
173
|
+
}
|
|
174
|
+
controller.setPending({
|
|
175
|
+
prompt: step.prompt,
|
|
176
|
+
mask: step.mask,
|
|
177
|
+
async handle(input) {
|
|
178
|
+
if (input.trim().toLowerCase() === "cancel") {
|
|
179
|
+
return { blocks: [block("info", "Profile setup cancelled.")] };
|
|
180
|
+
}
|
|
181
|
+
const error = step.apply(input, state);
|
|
182
|
+
if (error) {
|
|
183
|
+
return askStep(controller, index, state, [block("warn", error)]);
|
|
184
|
+
}
|
|
185
|
+
return askStep(controller, index + 1, state, []);
|
|
186
|
+
}
|
|
187
|
+
});
|
|
188
|
+
return { blocks: [...prefix, block("info", step.question(state))] };
|
|
189
|
+
}
|
|
190
|
+
function startProfileWizard(controller, name) {
|
|
191
|
+
const state = {};
|
|
192
|
+
const intro = [
|
|
193
|
+
block("plain", "New connection profile \u2014 answer each question, type 'cancel' to abort.")
|
|
194
|
+
];
|
|
195
|
+
if (name) {
|
|
196
|
+
const error = steps[0].apply(name, state);
|
|
197
|
+
if (error) {
|
|
198
|
+
return askStep(controller, 0, state, [...intro, block("warn", error)]);
|
|
199
|
+
}
|
|
200
|
+
return askStep(controller, 1, state, intro);
|
|
201
|
+
}
|
|
202
|
+
return askStep(controller, 0, state, intro);
|
|
203
|
+
}
|
|
204
|
+
export {
|
|
205
|
+
startProfileWizard
|
|
206
|
+
};
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
plainTable
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-5GQDAM2V.js";
|
|
5
5
|
import {
|
|
6
6
|
deleteProfile,
|
|
7
7
|
listProfiles,
|
|
8
8
|
saveProfile
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-5YB2C5KL.js";
|
|
10
10
|
import "./chunk-VFC3HWTF.js";
|
|
11
11
|
|
|
12
12
|
// src/commands/profiles.ts
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
deleteProfile,
|
|
4
|
+
getProfile,
|
|
5
|
+
listProfiles,
|
|
6
|
+
resolvePassword,
|
|
7
|
+
saveProfile
|
|
8
|
+
} from "./chunk-5YB2C5KL.js";
|
|
9
|
+
import "./chunk-VFC3HWTF.js";
|
|
10
|
+
export {
|
|
11
|
+
deleteProfile,
|
|
12
|
+
getProfile,
|
|
13
|
+
listProfiles,
|
|
14
|
+
resolvePassword,
|
|
15
|
+
saveProfile
|
|
16
|
+
};
|
|
@@ -6,12 +6,6 @@ import {
|
|
|
6
6
|
buildQuery,
|
|
7
7
|
dialectFor
|
|
8
8
|
} from "./chunk-TLTYYFT5.js";
|
|
9
|
-
import {
|
|
10
|
-
toCsv
|
|
11
|
-
} from "./chunk-XJAN6OQ7.js";
|
|
12
|
-
import {
|
|
13
|
-
toJson
|
|
14
|
-
} from "./chunk-GWY47EDH.js";
|
|
15
9
|
import {
|
|
16
10
|
astIsWrite,
|
|
17
11
|
classifyAst,
|
|
@@ -20,20 +14,26 @@ import {
|
|
|
20
14
|
rawSqlIsWrite,
|
|
21
15
|
startsWithSqlKeyword,
|
|
22
16
|
translate
|
|
23
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-XTMRD34N.js";
|
|
24
18
|
import {
|
|
25
19
|
PostgresAdapter,
|
|
26
20
|
resolveTarget
|
|
27
|
-
} from "./chunk-
|
|
21
|
+
} from "./chunk-4C62NPJ6.js";
|
|
28
22
|
import {
|
|
29
23
|
describeTable,
|
|
30
24
|
formatResultSet,
|
|
31
25
|
formatTableList
|
|
32
|
-
} from "./chunk-
|
|
26
|
+
} from "./chunk-5GQDAM2V.js";
|
|
33
27
|
import {
|
|
34
28
|
loadConfig
|
|
35
|
-
} from "./chunk-
|
|
29
|
+
} from "./chunk-5YB2C5KL.js";
|
|
36
30
|
import "./chunk-PXDMSYWH.js";
|
|
31
|
+
import {
|
|
32
|
+
toCsv
|
|
33
|
+
} from "./chunk-XJAN6OQ7.js";
|
|
34
|
+
import {
|
|
35
|
+
toJson
|
|
36
|
+
} from "./chunk-GWY47EDH.js";
|
|
37
37
|
import "./chunk-VFC3HWTF.js";
|
|
38
38
|
|
|
39
39
|
// src/commands/query.ts
|
package/package.json
CHANGED
package/dist/chunk-7WNCISNV.js
DELETED
|
@@ -1,106 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
// src/repl/format.ts
|
|
4
|
-
import Table from "cli-table3";
|
|
5
|
-
import pc from "picocolors";
|
|
6
|
-
var MAX_ROWS_SHOWN = 50;
|
|
7
|
-
var ROW_LIMIT_BEFORE_TRUNCATION = 200;
|
|
8
|
-
var MAX_CELL_WIDTH = 40;
|
|
9
|
-
function formatCell(value) {
|
|
10
|
-
if (value === null || value === void 0) {
|
|
11
|
-
return pc.dim("\u2205");
|
|
12
|
-
}
|
|
13
|
-
if (value instanceof Date) {
|
|
14
|
-
return value.toISOString();
|
|
15
|
-
}
|
|
16
|
-
if (typeof value === "object") {
|
|
17
|
-
return JSON.stringify(value);
|
|
18
|
-
}
|
|
19
|
-
const text = String(value);
|
|
20
|
-
if (text.length > MAX_CELL_WIDTH) {
|
|
21
|
-
return `${text.slice(0, MAX_CELL_WIDTH - 1)}\u2026`;
|
|
22
|
-
}
|
|
23
|
-
return text;
|
|
24
|
-
}
|
|
25
|
-
function plainTable(head, rows) {
|
|
26
|
-
const table = new Table({
|
|
27
|
-
head: head.map((column) => pc.cyan(column)),
|
|
28
|
-
style: { head: [], border: [] }
|
|
29
|
-
});
|
|
30
|
-
for (const row of rows) {
|
|
31
|
-
table.push(row);
|
|
32
|
-
}
|
|
33
|
-
return table.toString();
|
|
34
|
-
}
|
|
35
|
-
function describeTable(table) {
|
|
36
|
-
const rows = table.columns.map((column) => {
|
|
37
|
-
const flags = [];
|
|
38
|
-
if (column.isPrimaryKey) {
|
|
39
|
-
flags.push("PK");
|
|
40
|
-
}
|
|
41
|
-
for (const fk of table.foreignKeys) {
|
|
42
|
-
const index = fk.columns.indexOf(column.name);
|
|
43
|
-
if (index >= 0) {
|
|
44
|
-
flags.push(`FK \u2192 ${fk.referencedTable}.${fk.referencedColumns[index]}`);
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
return [
|
|
48
|
-
column.name,
|
|
49
|
-
column.dataType,
|
|
50
|
-
column.nullable ? "yes" : "no",
|
|
51
|
-
column.hasDefault ? "yes" : "no",
|
|
52
|
-
flags.join(", ")
|
|
53
|
-
];
|
|
54
|
-
});
|
|
55
|
-
return plainTable(["column", "type", "nullable", "default", "keys"], rows);
|
|
56
|
-
}
|
|
57
|
-
function formatTableList(schema) {
|
|
58
|
-
const rows = schema.tables.map((table) => [
|
|
59
|
-
table.name,
|
|
60
|
-
String(table.columns.length),
|
|
61
|
-
`~${table.approxRowCount}`
|
|
62
|
-
]);
|
|
63
|
-
return plainTable(["table", "columns", "rows"], rows);
|
|
64
|
-
}
|
|
65
|
-
function formatResultSet(result) {
|
|
66
|
-
if (result.columns.length === 0) {
|
|
67
|
-
const verb = result.command || "OK";
|
|
68
|
-
return pc.green(
|
|
69
|
-
`${verb} \xB7 ${result.rowCount} row${result.rowCount === 1 ? "" : "s"} affected \xB7 ${result.durationMs}ms`
|
|
70
|
-
);
|
|
71
|
-
}
|
|
72
|
-
if (result.rows.length === 0) {
|
|
73
|
-
return pc.dim(`(no rows) \xB7 ${result.durationMs}ms`);
|
|
74
|
-
}
|
|
75
|
-
const truncate = result.rows.length > ROW_LIMIT_BEFORE_TRUNCATION;
|
|
76
|
-
const shown = truncate ? result.rows.slice(0, MAX_ROWS_SHOWN) : result.rows;
|
|
77
|
-
const table = new Table({
|
|
78
|
-
head: result.columns.map((column) => pc.cyan(column)),
|
|
79
|
-
style: { head: [], border: [] },
|
|
80
|
-
wordWrap: true
|
|
81
|
-
});
|
|
82
|
-
for (const row of shown) {
|
|
83
|
-
table.push(result.columns.map((column) => formatCell(row[column])));
|
|
84
|
-
}
|
|
85
|
-
const lines = [table.toString()];
|
|
86
|
-
if (truncate) {
|
|
87
|
-
lines.push(
|
|
88
|
-
pc.yellow(
|
|
89
|
-
`\u2026 showing first ${MAX_ROWS_SHOWN} of ${result.rows.length} rows (add 'first N' or use \\export)`
|
|
90
|
-
)
|
|
91
|
-
);
|
|
92
|
-
}
|
|
93
|
-
lines.push(
|
|
94
|
-
pc.dim(
|
|
95
|
-
`${result.rows.length} row${result.rows.length === 1 ? "" : "s"} \xB7 ${result.durationMs}ms`
|
|
96
|
-
)
|
|
97
|
-
);
|
|
98
|
-
return lines.join("\n");
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
export {
|
|
102
|
-
plainTable,
|
|
103
|
-
describeTable,
|
|
104
|
-
formatTableList,
|
|
105
|
-
formatResultSet
|
|
106
|
-
};
|
|
File without changes
|