@luckymingxuan/dbcli 0.1.0 → 0.1.2
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/package.json +5 -1
- package/src/commands/connect.ts +0 -259
- package/src/commands/query.ts +0 -51
- package/src/commands/tables.ts +0 -77
- package/src/drivers/interface.ts +0 -42
- package/src/drivers/postgres.ts +0 -97
- package/src/index.ts +0 -85
- package/tsconfig.json +0 -17
package/package.json
CHANGED
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@luckymingxuan/dbcli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Database CLI tool",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"bin": {
|
|
8
8
|
"dbcli": "dist/index.js"
|
|
9
9
|
},
|
|
10
|
+
"files": [
|
|
11
|
+
"dist",
|
|
12
|
+
"README.md"
|
|
13
|
+
],
|
|
10
14
|
"scripts": {
|
|
11
15
|
"build": "tsc",
|
|
12
16
|
"start": "node dist/index.js",
|
package/src/commands/connect.ts
DELETED
|
@@ -1,259 +0,0 @@
|
|
|
1
|
-
import chalk from 'chalk';
|
|
2
|
-
import inquirer from 'inquirer';
|
|
3
|
-
import { PostgresDriver } from '../drivers/postgres.js';
|
|
4
|
-
import { promises as fs } from 'fs';
|
|
5
|
-
import path from 'path';
|
|
6
|
-
import os from 'os';
|
|
7
|
-
|
|
8
|
-
interface ConnectionInfo {
|
|
9
|
-
url: string;
|
|
10
|
-
database: string;
|
|
11
|
-
host: string;
|
|
12
|
-
port: number;
|
|
13
|
-
username: string;
|
|
14
|
-
password: string;
|
|
15
|
-
lastConnected: string;
|
|
16
|
-
enabled: boolean;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
const CONFIG_DIR = path.join(os.homedir(), '.dbcli');
|
|
20
|
-
const CONFIG_FILE = path.join(CONFIG_DIR, 'connections.json');
|
|
21
|
-
|
|
22
|
-
let connections: Map<string, ConnectionInfo> = new Map();
|
|
23
|
-
|
|
24
|
-
async function loadConnections(): Promise<void> {
|
|
25
|
-
try {
|
|
26
|
-
await fs.mkdir(CONFIG_DIR, { recursive: true });
|
|
27
|
-
const data = await fs.readFile(CONFIG_FILE, 'utf-8');
|
|
28
|
-
const parsed = JSON.parse(data) as Record<string, ConnectionInfo>;
|
|
29
|
-
connections = new Map(Object.entries(parsed));
|
|
30
|
-
} catch {
|
|
31
|
-
connections = new Map();
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
async function saveConnections(): Promise<void> {
|
|
36
|
-
await fs.mkdir(CONFIG_DIR, { recursive: true });
|
|
37
|
-
const obj = Object.fromEntries(connections);
|
|
38
|
-
await fs.writeFile(CONFIG_FILE, JSON.stringify(obj, null, 2), 'utf-8');
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
export function getConnections(): Map<string, ConnectionInfo> {
|
|
42
|
-
return connections;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
export async function getActiveConnection(): Promise<ConnectionInfo | null> {
|
|
46
|
-
await loadConnections();
|
|
47
|
-
for (const conn of connections.values()) {
|
|
48
|
-
if (conn.enabled) {
|
|
49
|
-
return conn;
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
return null;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
export async function getConnectionByName(name: string): Promise<ConnectionInfo | null> {
|
|
56
|
-
await loadConnections();
|
|
57
|
-
return connections.get(name) || null;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
async function promptCredentials(): Promise<{ username: string; password: string }> {
|
|
61
|
-
const answers = await inquirer.prompt([
|
|
62
|
-
{
|
|
63
|
-
type: 'input',
|
|
64
|
-
name: 'username',
|
|
65
|
-
message: 'Username:',
|
|
66
|
-
default: 'postgres',
|
|
67
|
-
},
|
|
68
|
-
{
|
|
69
|
-
type: 'password',
|
|
70
|
-
name: 'password',
|
|
71
|
-
message: 'Password:',
|
|
72
|
-
mask: '*',
|
|
73
|
-
},
|
|
74
|
-
]);
|
|
75
|
-
return answers;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
function isUrl(str: string): boolean {
|
|
79
|
-
return str.includes('://');
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
function buildUrl(info: ConnectionInfo, username: string, password: string): string {
|
|
83
|
-
return `postgresql://${username}:${password}@${info.host}:${info.port}/${info.database}`;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
export async function showConnections(): Promise<void> {
|
|
87
|
-
await loadConnections();
|
|
88
|
-
|
|
89
|
-
if (connections.size === 0) {
|
|
90
|
-
console.log(chalk.yellow('No saved connections.'));
|
|
91
|
-
console.log(chalk.cyan('Use "dbcli connect <url>" to add a new connection.'));
|
|
92
|
-
return;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
const activeConnection = Array.from(connections.values()).find((conn) => conn.enabled);
|
|
96
|
-
if (activeConnection) {
|
|
97
|
-
console.log(chalk.green(`Current connected database: "${activeConnection.database}" (user: ${activeConnection.username})`));
|
|
98
|
-
} else {
|
|
99
|
-
console.log(chalk.yellow('No database is currently connected.'));
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
console.log(chalk.cyan('Saved connections:'));
|
|
103
|
-
const rows = Array.from(connections.entries()).map(([name, info]) => ({
|
|
104
|
-
username: info.username || '-',
|
|
105
|
-
database: info.database,
|
|
106
|
-
host: info.host,
|
|
107
|
-
port: info.port,
|
|
108
|
-
status: info.enabled ? 'enabled' : 'disabled',
|
|
109
|
-
lastConnected: info.lastConnected,
|
|
110
|
-
}));
|
|
111
|
-
console.table(rows);
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
export async function deleteConnection(name: string): Promise<void> {
|
|
115
|
-
await loadConnections();
|
|
116
|
-
|
|
117
|
-
if (!connections.has(name)) {
|
|
118
|
-
console.error(chalk.red(`Connection "${name}" not found.`));
|
|
119
|
-
process.exit(1);
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
connections.delete(name);
|
|
123
|
-
await saveConnections();
|
|
124
|
-
console.log(chalk.yellow(`Connection "${name}" deleted.`));
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
export async function connect(nameUrl: string, options?: { username?: string; password?: string; current?: boolean }): Promise<void> {
|
|
128
|
-
await loadConnections();
|
|
129
|
-
|
|
130
|
-
let database: string;
|
|
131
|
-
let host: string;
|
|
132
|
-
let port: number;
|
|
133
|
-
let existing: ConnectionInfo | null = null;
|
|
134
|
-
let urlUsername = '';
|
|
135
|
-
let urlPassword = '';
|
|
136
|
-
|
|
137
|
-
if (isUrl(nameUrl)) {
|
|
138
|
-
const url = new URL(nameUrl);
|
|
139
|
-
database = url.pathname.slice(1) || 'postgres';
|
|
140
|
-
host = url.hostname;
|
|
141
|
-
port = parseInt(url.port) || 5432;
|
|
142
|
-
urlUsername = url.username;
|
|
143
|
-
urlPassword = url.password;
|
|
144
|
-
existing = connections.get(database) || null;
|
|
145
|
-
} else {
|
|
146
|
-
database = nameUrl;
|
|
147
|
-
existing = connections.get(database) || null;
|
|
148
|
-
if (!existing) {
|
|
149
|
-
console.error(chalk.red(`Connection "${database}" not found.`));
|
|
150
|
-
console.log(chalk.cyan('Use "dbcli connect <url>" to add a new connection.'));
|
|
151
|
-
process.exit(1);
|
|
152
|
-
}
|
|
153
|
-
host = existing.host;
|
|
154
|
-
port = existing.port;
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
let credentials: { username: string; password: string };
|
|
158
|
-
|
|
159
|
-
if (options?.current) {
|
|
160
|
-
const activeConn = Array.from(connections.values()).find((conn) => conn.enabled);
|
|
161
|
-
if (!activeConn) {
|
|
162
|
-
console.error(chalk.red('No currently connected database found.'));
|
|
163
|
-
process.exit(1);
|
|
164
|
-
}
|
|
165
|
-
credentials = { username: activeConn.username, password: activeConn.password };
|
|
166
|
-
} else if (options?.username && options?.password) {
|
|
167
|
-
credentials = { username: options.username, password: options.password };
|
|
168
|
-
} else if (urlUsername || urlPassword) {
|
|
169
|
-
credentials = { username: urlUsername, password: urlPassword };
|
|
170
|
-
} else if (existing && existing.username && existing.password) {
|
|
171
|
-
const useExisting = await inquirer.prompt([
|
|
172
|
-
{
|
|
173
|
-
type: 'input',
|
|
174
|
-
name: 'value',
|
|
175
|
-
message: `You have an existing account "${existing.username}". Enter "y" to use it or anything else to re-login:`,
|
|
176
|
-
default: 'y',
|
|
177
|
-
},
|
|
178
|
-
]);
|
|
179
|
-
|
|
180
|
-
if (useExisting.value.toLowerCase() === 'y') {
|
|
181
|
-
credentials = { username: existing.username, password: existing.password };
|
|
182
|
-
} else {
|
|
183
|
-
credentials = await promptCredentials();
|
|
184
|
-
}
|
|
185
|
-
} else {
|
|
186
|
-
credentials = await promptCredentials();
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
let finalUrl: string;
|
|
190
|
-
if (existing) {
|
|
191
|
-
finalUrl = buildUrl(existing, credentials.username, credentials.password);
|
|
192
|
-
} else {
|
|
193
|
-
finalUrl = `postgresql://${credentials.username}:${credentials.password}@${host}:${port}/${database}`;
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
const driver = new PostgresDriver();
|
|
197
|
-
|
|
198
|
-
try {
|
|
199
|
-
console.log(chalk.cyan(`Connecting to db("${database}")...`));
|
|
200
|
-
await driver.connect(finalUrl);
|
|
201
|
-
|
|
202
|
-
for (const conn of connections.values()) {
|
|
203
|
-
conn.enabled = false;
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
const connectionInfo: ConnectionInfo = {
|
|
207
|
-
url: `postgresql://${host}:${port}/${database}`,
|
|
208
|
-
database,
|
|
209
|
-
host,
|
|
210
|
-
port,
|
|
211
|
-
username: credentials.username,
|
|
212
|
-
password: credentials.password,
|
|
213
|
-
lastConnected: new Date().toISOString(),
|
|
214
|
-
enabled: true,
|
|
215
|
-
};
|
|
216
|
-
|
|
217
|
-
connections.set(database, connectionInfo);
|
|
218
|
-
await saveConnections();
|
|
219
|
-
|
|
220
|
-
console.log(chalk.green(`Connected to db("${database}") as user("${credentials.username}") successfully!`));
|
|
221
|
-
process.exit(0);
|
|
222
|
-
} catch (error) {
|
|
223
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
224
|
-
console.error(chalk.red(`Connection failed: ${message}`));
|
|
225
|
-
process.exit(1);
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
export async function disconnect(name: string): Promise<void> {
|
|
230
|
-
await loadConnections();
|
|
231
|
-
|
|
232
|
-
if (!connections.has(name)) {
|
|
233
|
-
console.error(chalk.red(`Connection "${name}" not found.`));
|
|
234
|
-
process.exit(1);
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
const conn = connections.get(name)!;
|
|
238
|
-
conn.enabled = false;
|
|
239
|
-
await saveConnections();
|
|
240
|
-
|
|
241
|
-
console.log(chalk.yellow(`Disconnected from "${name}".`));
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
export async function logout(name: string): Promise<void> {
|
|
245
|
-
await loadConnections();
|
|
246
|
-
|
|
247
|
-
if (!connections.has(name)) {
|
|
248
|
-
console.error(chalk.red(`Connection "${name}" not found.`));
|
|
249
|
-
process.exit(1);
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
const conn = connections.get(name)!;
|
|
253
|
-
conn.enabled = false;
|
|
254
|
-
conn.username = '';
|
|
255
|
-
conn.password = '';
|
|
256
|
-
await saveConnections();
|
|
257
|
-
|
|
258
|
-
console.log(chalk.yellow(`Logged out from "${name}". Username and password cleared.`));
|
|
259
|
-
}
|
package/src/commands/query.ts
DELETED
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
import chalk from 'chalk';
|
|
2
|
-
import { PostgresDriver } from '../drivers/postgres.js';
|
|
3
|
-
import { getActiveConnection } from './connect.js';
|
|
4
|
-
|
|
5
|
-
async function getActiveDriver(): Promise<{ driver: PostgresDriver; connName: string } | null> {
|
|
6
|
-
const activeConn = await getActiveConnection();
|
|
7
|
-
if (!activeConn) {
|
|
8
|
-
return null;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
const url = `postgresql://${activeConn.username}:${activeConn.password}@${activeConn.host}:${activeConn.port}/${activeConn.database}`;
|
|
12
|
-
const driver = new PostgresDriver();
|
|
13
|
-
await driver.connect(url);
|
|
14
|
-
|
|
15
|
-
const connName = activeConn.database;
|
|
16
|
-
return { driver, connName };
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export async function executeQuery(sql: string): Promise<void> {
|
|
20
|
-
const result = await getActiveDriver();
|
|
21
|
-
|
|
22
|
-
if (!result) {
|
|
23
|
-
console.log(chalk.red('No active database connection.'));
|
|
24
|
-
console.log(chalk.cyan('Please use "dbcli connect <url>" to connect to a database first.'));
|
|
25
|
-
process.exit(1);
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
const { driver, connName } = result;
|
|
29
|
-
|
|
30
|
-
try {
|
|
31
|
-
console.log(chalk.gray(`Executing on database "${connName}":`));
|
|
32
|
-
console.log(chalk.cyan(sql));
|
|
33
|
-
console.log();
|
|
34
|
-
|
|
35
|
-
const queryResult = await driver.query(sql);
|
|
36
|
-
|
|
37
|
-
if (queryResult.rows.length === 0) {
|
|
38
|
-
console.log(chalk.yellow(`Query executed successfully. No rows returned.`));
|
|
39
|
-
console.log(chalk.gray(`Row count: ${queryResult.rowCount}`));
|
|
40
|
-
} else {
|
|
41
|
-
console.table(queryResult.rows);
|
|
42
|
-
console.log(chalk.gray(`Total: ${queryResult.rows.length} row(s)`));
|
|
43
|
-
}
|
|
44
|
-
} catch (error) {
|
|
45
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
46
|
-
console.error(chalk.red(`Query failed: ${message}`));
|
|
47
|
-
process.exit(1);
|
|
48
|
-
} finally {
|
|
49
|
-
await driver.disconnect();
|
|
50
|
-
}
|
|
51
|
-
}
|
package/src/commands/tables.ts
DELETED
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
import chalk from 'chalk';
|
|
2
|
-
import { PostgresDriver } from '../drivers/postgres.js';
|
|
3
|
-
import { getActiveConnection } from './connect.js';
|
|
4
|
-
|
|
5
|
-
async function getActiveDriver(): Promise<{ driver: PostgresDriver; connName: string } | null> {
|
|
6
|
-
const activeConn = await getActiveConnection();
|
|
7
|
-
if (!activeConn) {
|
|
8
|
-
return null;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
const url = `postgresql://${activeConn.username}:${activeConn.password}@${activeConn.host}:${activeConn.port}/${activeConn.database}`;
|
|
12
|
-
const driver = new PostgresDriver();
|
|
13
|
-
await driver.connect(url);
|
|
14
|
-
|
|
15
|
-
const connName = activeConn.database;
|
|
16
|
-
return { driver, connName };
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export async function listTables(): Promise<void> {
|
|
20
|
-
const result = await getActiveDriver();
|
|
21
|
-
|
|
22
|
-
if (!result) {
|
|
23
|
-
console.log(chalk.red('No active database connection.'));
|
|
24
|
-
console.log(chalk.cyan('Please use "dbcli connect <url>" to connect to a database first.'));
|
|
25
|
-
process.exit(1);
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
const { driver, connName } = result;
|
|
29
|
-
|
|
30
|
-
try {
|
|
31
|
-
const tables = await driver.listTables();
|
|
32
|
-
|
|
33
|
-
if (tables.length === 0) {
|
|
34
|
-
console.log(chalk.yellow(`No tables found in database "${connName}".`));
|
|
35
|
-
} else {
|
|
36
|
-
console.log(chalk.cyan(`Tables in database "${connName}":`));
|
|
37
|
-
console.table(tables);
|
|
38
|
-
}
|
|
39
|
-
} finally {
|
|
40
|
-
await driver.disconnect();
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
export async function describeTable(tableName: string): Promise<void> {
|
|
45
|
-
const result = await getActiveDriver();
|
|
46
|
-
|
|
47
|
-
if (!result) {
|
|
48
|
-
console.log(chalk.red('No active database connection.'));
|
|
49
|
-
console.log(chalk.cyan('Please use "dbcli connect <url>" to connect to a database first.'));
|
|
50
|
-
process.exit(1);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
const { driver, connName } = result;
|
|
54
|
-
|
|
55
|
-
try {
|
|
56
|
-
const columns = await driver.describeTable('public', tableName);
|
|
57
|
-
|
|
58
|
-
if (columns.length === 0) {
|
|
59
|
-
console.log(chalk.yellow(`Table "${tableName}" not found in database "${connName}".`));
|
|
60
|
-
} else {
|
|
61
|
-
console.log(chalk.cyan(`Structure of table "${tableName}" in database "${connName}":`));
|
|
62
|
-
console.table(columns);
|
|
63
|
-
|
|
64
|
-
const hasDescriptions = columns.some(col => col.description);
|
|
65
|
-
if (hasDescriptions) {
|
|
66
|
-
console.log(chalk.gray('\nColumn descriptions:'));
|
|
67
|
-
columns.forEach(col => {
|
|
68
|
-
if (col.description) {
|
|
69
|
-
console.log(` ${chalk.white(col.name)}: ${chalk.gray(col.description)}`);
|
|
70
|
-
}
|
|
71
|
-
});
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
} finally {
|
|
75
|
-
await driver.disconnect();
|
|
76
|
-
}
|
|
77
|
-
}
|
package/src/drivers/interface.ts
DELETED
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* @Author: Mingxuan songmingxuan936@gmail.com
|
|
3
|
-
* @Date: 2026-04-04 13:28:20
|
|
4
|
-
* @LastEditors: Mingxuan songmingxuan936@gmail.com
|
|
5
|
-
* @LastEditTime: 2026-04-04 13:28:41
|
|
6
|
-
* @FilePath: /dbcli/src/drivers/interface.ts
|
|
7
|
-
* @Description:
|
|
8
|
-
*
|
|
9
|
-
* Copyright (c) 2026 by ${git_name_email}, All Rights Reserved.
|
|
10
|
-
*/
|
|
11
|
-
export interface QueryResult {
|
|
12
|
-
rows: Record<string, unknown>[];
|
|
13
|
-
rowCount: number;
|
|
14
|
-
fields: FieldInfo[];
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export interface FieldInfo {
|
|
18
|
-
name: string;
|
|
19
|
-
dataTypeID: number;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export interface TableInfo {
|
|
23
|
-
schema: string;
|
|
24
|
-
name: string;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export interface ColumnInfo {
|
|
28
|
-
name: string;
|
|
29
|
-
dataType: string;
|
|
30
|
-
isNullable: boolean;
|
|
31
|
-
defaultValue: string | null;
|
|
32
|
-
description: string | null;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
export interface DatabaseDriver {
|
|
36
|
-
connect(url: string): Promise<void>;
|
|
37
|
-
disconnect(): Promise<void>;
|
|
38
|
-
isConnected(): boolean;
|
|
39
|
-
query(sql: string): Promise<QueryResult>;
|
|
40
|
-
listTables(): Promise<TableInfo[]>;
|
|
41
|
-
describeTable(schema: string, table: string): Promise<ColumnInfo[]>;
|
|
42
|
-
}
|
package/src/drivers/postgres.ts
DELETED
|
@@ -1,97 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* @Author: Mingxuan songmingxuan936@gmail.com
|
|
3
|
-
* @Date: 2026-04-05 17:01:59
|
|
4
|
-
* @LastEditors: Mingxuan songmingxuan936@gmail.com
|
|
5
|
-
* @LastEditTime: 2026-04-05 17:42:59
|
|
6
|
-
* @FilePath: /dbcli/src/drivers/postgres.ts
|
|
7
|
-
* @Description:
|
|
8
|
-
*
|
|
9
|
-
* Copyright (c) 2026 by ${git_name_email}, All Rights Reserved.
|
|
10
|
-
*/
|
|
11
|
-
import pg from 'pg';
|
|
12
|
-
import type { DatabaseDriver, QueryResult, TableInfo, ColumnInfo } from './interface.js';
|
|
13
|
-
|
|
14
|
-
export class PostgresDriver implements DatabaseDriver {
|
|
15
|
-
private client: pg.PoolClient | null = null;
|
|
16
|
-
private pool: pg.Pool | null = null;
|
|
17
|
-
private connected = false;
|
|
18
|
-
|
|
19
|
-
async connect(url: string): Promise<void> {
|
|
20
|
-
const connectionUrl = new URL(url);
|
|
21
|
-
|
|
22
|
-
this.pool = new pg.Pool({
|
|
23
|
-
host: connectionUrl.hostname,
|
|
24
|
-
port: parseInt(connectionUrl.port) || 5432,
|
|
25
|
-
user: connectionUrl.username,
|
|
26
|
-
password: connectionUrl.password,
|
|
27
|
-
database: connectionUrl.pathname.slice(1) || 'postgres',
|
|
28
|
-
ssl: connectionUrl.searchParams.get('sslmode') === 'require' ? { rejectUnauthorized: false } : undefined,
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
this.client = await this.pool.connect();
|
|
32
|
-
this.connected = true;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
async disconnect(): Promise<void> {
|
|
36
|
-
if (this.client) {
|
|
37
|
-
this.client.release();
|
|
38
|
-
this.client = null;
|
|
39
|
-
}
|
|
40
|
-
if (this.pool) {
|
|
41
|
-
await this.pool.end();
|
|
42
|
-
this.pool = null;
|
|
43
|
-
}
|
|
44
|
-
this.connected = false;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
isConnected(): boolean {
|
|
48
|
-
return this.connected && this.client !== null;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
async query(sql: string, params?: unknown[]): Promise<QueryResult> {
|
|
52
|
-
if (!this.client) {
|
|
53
|
-
throw new Error('Not connected to database');
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
const result = await this.client.query(sql, params);
|
|
57
|
-
return {
|
|
58
|
-
rows: result.rows,
|
|
59
|
-
rowCount: result.rowCount ?? 0,
|
|
60
|
-
fields: result.fields.map((f: pg.FieldDef) => ({
|
|
61
|
-
name: f.name,
|
|
62
|
-
dataTypeID: f.dataTypeID,
|
|
63
|
-
})),
|
|
64
|
-
};
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
async listTables(): Promise<TableInfo[]> {
|
|
68
|
-
const result = await this.query(`
|
|
69
|
-
SELECT schemaname as schema, tablename as name
|
|
70
|
-
FROM pg_tables
|
|
71
|
-
WHERE schemaname NOT IN ('pg_catalog', 'information_schema')
|
|
72
|
-
ORDER BY schemaname, tablename
|
|
73
|
-
`);
|
|
74
|
-
return result.rows as unknown as TableInfo[];
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
async describeTable(schema: string, table: string): Promise<ColumnInfo[]> {
|
|
78
|
-
const result = await this.query(`
|
|
79
|
-
SELECT
|
|
80
|
-
a.attname as name,
|
|
81
|
-
format_type(a.atttypid, a.atttypmod) as data_type,
|
|
82
|
-
CASE WHEN a.attnotnull THEN 'NO' ELSE 'YES' END as is_nullable,
|
|
83
|
-
pg_get_expr(ad.adbin, a.attrelid) as default_value,
|
|
84
|
-
col_description(a.attrelid, a.attnum) as description
|
|
85
|
-
FROM pg_attribute a
|
|
86
|
-
JOIN pg_class c ON c.oid = a.attrelid
|
|
87
|
-
JOIN pg_namespace n ON n.oid = c.relnamespace
|
|
88
|
-
LEFT JOIN pg_attrdef ad ON ad.adrelid = a.attrelid AND ad.adnum = a.attnum
|
|
89
|
-
WHERE n.nspname = $1
|
|
90
|
-
AND c.relname = $2
|
|
91
|
-
AND a.attnum > 0
|
|
92
|
-
AND NOT a.attisdropped
|
|
93
|
-
ORDER BY a.attnum
|
|
94
|
-
`, [schema, table]);
|
|
95
|
-
return result.rows as unknown as ColumnInfo[];
|
|
96
|
-
}
|
|
97
|
-
}
|
package/src/index.ts
DELETED
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
import { Command } from 'commander';
|
|
2
|
-
import { connect, disconnect, logout, showConnections as statusConnections, deleteConnection } from './commands/connect.js';
|
|
3
|
-
import { listTables, describeTable } from './commands/tables.js';
|
|
4
|
-
import { executeQuery } from './commands/query.js';
|
|
5
|
-
|
|
6
|
-
const program = new Command();
|
|
7
|
-
|
|
8
|
-
program
|
|
9
|
-
.name('dbcli')
|
|
10
|
-
.description('Database CLI tool')
|
|
11
|
-
.version('0.1.0')
|
|
12
|
-
.addHelpCommand(false);
|
|
13
|
-
|
|
14
|
-
program
|
|
15
|
-
.command('connect')
|
|
16
|
-
.description('Connect to a database (-u <user> -p <pass> for credentials, -c to use current)')
|
|
17
|
-
.argument('<db-name|url>', 'Connection name (for existing) or full URL (for new connection)')
|
|
18
|
-
.option('-u, --username <username>', 'Username for authentication')
|
|
19
|
-
.option('-p, --password <password>', 'Password for authentication')
|
|
20
|
-
.option('-c, --current', 'Use credentials from currently connected database')
|
|
21
|
-
.action(async (nameUrl: string, options) => {
|
|
22
|
-
await connect(nameUrl, options);
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
program
|
|
26
|
-
.option('-s, --status', 'Show all saved connections status')
|
|
27
|
-
.action(async (options, cmd) => {
|
|
28
|
-
if (cmd.args && cmd.args.length > 0) {
|
|
29
|
-
console.error(`error: unknown command '${cmd.args[0]}'`);
|
|
30
|
-
process.exit(1);
|
|
31
|
-
}
|
|
32
|
-
if (options.status) {
|
|
33
|
-
await statusConnections();
|
|
34
|
-
process.exit(0);
|
|
35
|
-
}
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
program
|
|
39
|
-
.command('disconnect')
|
|
40
|
-
.description('Disconnect from a database (keeps credentials)')
|
|
41
|
-
.argument('<db-name>', 'Connection name to disconnect')
|
|
42
|
-
.action(async (name: string) => {
|
|
43
|
-
await disconnect(name);
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
program
|
|
47
|
-
.command('logout')
|
|
48
|
-
.description('Logout from a connection (clears credentials)')
|
|
49
|
-
.argument('<db-name>', 'Connection name to logout')
|
|
50
|
-
.action(async (name: string) => {
|
|
51
|
-
await logout(name);
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
program
|
|
55
|
-
.command('delete')
|
|
56
|
-
.description('Delete a saved connection completely')
|
|
57
|
-
.argument('<db-name>', 'Connection name to delete')
|
|
58
|
-
.action(async (name: string) => {
|
|
59
|
-
await deleteConnection(name);
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
program
|
|
63
|
-
.command('tables')
|
|
64
|
-
.description('List all tables in the connected database')
|
|
65
|
-
.action(async () => {
|
|
66
|
-
await listTables();
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
program
|
|
70
|
-
.command('describe')
|
|
71
|
-
.description('Show the structure of a table')
|
|
72
|
-
.argument('<table>', 'Table name to describe')
|
|
73
|
-
.action(async (table: string) => {
|
|
74
|
-
await describeTable(table);
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
program
|
|
78
|
-
.command('query')
|
|
79
|
-
.description('Execute a SQL query')
|
|
80
|
-
.argument('<sql>', 'SQL query to execute (use quotes for complex queries)')
|
|
81
|
-
.action(async (sql: string) => {
|
|
82
|
-
await executeQuery(sql);
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
program.parse();
|
package/tsconfig.json
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"compilerOptions": {
|
|
3
|
-
"target": "ES2022",
|
|
4
|
-
"module": "ESNext",
|
|
5
|
-
"moduleResolution": "bundler",
|
|
6
|
-
"outDir": "./dist",
|
|
7
|
-
"rootDir": "./src",
|
|
8
|
-
"strict": true,
|
|
9
|
-
"esModuleInterop": true,
|
|
10
|
-
"skipLibCheck": true,
|
|
11
|
-
"forceConsistentCasingInFileNames": true,
|
|
12
|
-
"resolveJsonModule": true,
|
|
13
|
-
"declaration": true
|
|
14
|
-
},
|
|
15
|
-
"include": ["src/**/*"],
|
|
16
|
-
"exclude": ["node_modules", "dist"]
|
|
17
|
-
}
|