@hesed/mysql 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +202 -0
- package/README.md +289 -0
- package/bin/dev.cmd +3 -0
- package/bin/dev.js +5 -0
- package/bin/run.cmd +3 -0
- package/bin/run.js +5 -0
- package/dist/commands/mysql/auth/add.d.ts +18 -0
- package/dist/commands/mysql/auth/add.js +57 -0
- package/dist/commands/mysql/auth/test.d.ts +12 -0
- package/dist/commands/mysql/auth/test.js +41 -0
- package/dist/commands/mysql/auth/update.d.ts +18 -0
- package/dist/commands/mysql/auth/update.js +74 -0
- package/dist/commands/mysql/describe-table.d.ts +11 -0
- package/dist/commands/mysql/describe-table.js +37 -0
- package/dist/commands/mysql/explain-query.d.ts +13 -0
- package/dist/commands/mysql/explain-query.js +39 -0
- package/dist/commands/mysql/list-databases.d.ts +9 -0
- package/dist/commands/mysql/list-databases.js +31 -0
- package/dist/commands/mysql/list-tables.d.ts +9 -0
- package/dist/commands/mysql/list-tables.js +31 -0
- package/dist/commands/mysql/query.d.ts +14 -0
- package/dist/commands/mysql/query.js +47 -0
- package/dist/commands/mysql/show-indexes.d.ts +11 -0
- package/dist/commands/mysql/show-indexes.js +37 -0
- package/dist/config.d.ts +13 -0
- package/dist/config.js +18 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/mysql/config-loader.d.ts +32 -0
- package/dist/mysql/config-loader.js +27 -0
- package/dist/mysql/database.d.ts +85 -0
- package/dist/mysql/database.js +5 -0
- package/dist/mysql/index.d.ts +3 -0
- package/dist/mysql/index.js +1 -0
- package/dist/mysql/mysql-client.d.ts +60 -0
- package/dist/mysql/mysql-client.js +131 -0
- package/dist/mysql/mysql-utils.d.ts +68 -0
- package/dist/mysql/mysql-utils.js +370 -0
- package/dist/mysql/query-validator.d.ts +38 -0
- package/dist/mysql/query-validator.js +103 -0
- package/oclif.manifest.json +527 -0
- package/package.json +102 -0
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { confirm, input } from '@inquirer/prompts';
|
|
2
|
+
import { Command, Flags } from '@oclif/core';
|
|
3
|
+
import { action } from '@oclif/core/ux';
|
|
4
|
+
import { default as fs } from 'fs-extra';
|
|
5
|
+
import { default as path } from 'node:path';
|
|
6
|
+
import { testDirectConnection } from '../../../mysql/index.js';
|
|
7
|
+
export default class AuthUpdate extends Command {
|
|
8
|
+
static args = {};
|
|
9
|
+
static description = 'Update an existing MySQL connection profile';
|
|
10
|
+
static enableJsonFlag = true;
|
|
11
|
+
static examples = [
|
|
12
|
+
'<%= config.bin %> <%= command.id %> --ssl',
|
|
13
|
+
'<%= config.bin %> <%= command.id %> --profile staging',
|
|
14
|
+
];
|
|
15
|
+
static flags = {
|
|
16
|
+
database: Flags.string({ char: 'd', description: 'Database name', required: !process.stdout.isTTY }),
|
|
17
|
+
host: Flags.string({ description: 'MySQL host', required: !process.stdout.isTTY }),
|
|
18
|
+
password: Flags.string({ char: 'p', description: 'Password', required: !process.stdout.isTTY }),
|
|
19
|
+
port: Flags.integer({ char: 'P', description: 'MySQL port', required: !process.stdout.isTTY }),
|
|
20
|
+
profile: Flags.string({ description: 'Profile name to update', required: false }),
|
|
21
|
+
ssl: Flags.boolean({ allowNo: true, default: false, description: 'Use SSL', required: false }),
|
|
22
|
+
user: Flags.string({ char: 'u', description: 'Username', required: !process.stdout.isTTY }),
|
|
23
|
+
};
|
|
24
|
+
async run() {
|
|
25
|
+
const { flags } = await this.parse(AuthUpdate);
|
|
26
|
+
const configPath = path.join(this.config.configDir, 'mysql-config.json');
|
|
27
|
+
let config;
|
|
28
|
+
try {
|
|
29
|
+
config = await fs.readJSON(configPath);
|
|
30
|
+
}
|
|
31
|
+
catch (error) {
|
|
32
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
33
|
+
if (msg.toLowerCase().includes('no such file or directory')) {
|
|
34
|
+
this.log('Run auth:add instead');
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
this.log(msg);
|
|
38
|
+
}
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
const profileName = flags.profile ?? config.defaultProfile;
|
|
42
|
+
const existingProfile = config.profiles[profileName];
|
|
43
|
+
if (!existingProfile) {
|
|
44
|
+
this.error(`Profile "${profileName}" not found. Available: ${Object.keys(config.profiles).join(', ')}`);
|
|
45
|
+
}
|
|
46
|
+
const host = flags.host ??
|
|
47
|
+
(await input({ default: existingProfile.host, message: 'MySQL host:', prefill: 'tab', required: true }));
|
|
48
|
+
const port = flags.port ??
|
|
49
|
+
Number(await input({ default: String(existingProfile.port), message: 'Port:', prefill: 'tab', required: true }));
|
|
50
|
+
const user = flags.user ?? (await input({ default: existingProfile.user, message: 'Username:', prefill: 'tab', required: true }));
|
|
51
|
+
const password = flags.password ??
|
|
52
|
+
(await input({ default: existingProfile.password, message: 'Password:', prefill: 'tab', required: true }));
|
|
53
|
+
const database = flags.database ??
|
|
54
|
+
(await input({ default: existingProfile.database, message: 'Database:', prefill: 'tab', required: true }));
|
|
55
|
+
const answer = await confirm({ message: `Override profile "${profileName}"?` });
|
|
56
|
+
if (!answer) {
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
config.profiles[profileName] = { database, host, password, port, ssl: flags.ssl, user };
|
|
60
|
+
await fs.writeJSON(configPath, config, { mode: 0o600 });
|
|
61
|
+
action.start('Testing connection');
|
|
62
|
+
const updatedConfig = await fs.readJSON(configPath);
|
|
63
|
+
const result = await testDirectConnection(updatedConfig.profiles[profileName]);
|
|
64
|
+
if (result.success) {
|
|
65
|
+
action.stop('✓ successful');
|
|
66
|
+
this.log(`Profile "${profileName}" updated successfully`);
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
action.stop('✗ failed');
|
|
70
|
+
this.error('Connection failed. Please check your configuration.');
|
|
71
|
+
}
|
|
72
|
+
return result;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
export default class MySQLDescribeTable extends Command {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
5
|
+
static flags: {
|
|
6
|
+
format: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
7
|
+
profile: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
8
|
+
table: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
9
|
+
};
|
|
10
|
+
run(): Promise<void>;
|
|
11
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { Command, Flags } from '@oclif/core';
|
|
2
|
+
import { closeConnections, describeTable, getMySQLConfig, setConfigDir } from '../../mysql/index.js';
|
|
3
|
+
export default class MySQLDescribeTable extends Command {
|
|
4
|
+
static description = 'Describe the structure of a MySQL table';
|
|
5
|
+
static examples = [
|
|
6
|
+
'<%= config.bin %> <%= command.id %> --table users',
|
|
7
|
+
'<%= config.bin %> <%= command.id %> --table orders --format json --profile prod',
|
|
8
|
+
];
|
|
9
|
+
static flags = {
|
|
10
|
+
format: Flags.string({
|
|
11
|
+
default: 'table',
|
|
12
|
+
description: 'Output format',
|
|
13
|
+
options: ['table', 'json', 'toon'],
|
|
14
|
+
}),
|
|
15
|
+
profile: Flags.string({ description: 'Database profile name from config', required: false }),
|
|
16
|
+
table: Flags.string({ char: 't', description: 'Table name to describe', required: true }),
|
|
17
|
+
};
|
|
18
|
+
async run() {
|
|
19
|
+
const { flags } = await this.parse(MySQLDescribeTable);
|
|
20
|
+
setConfigDir(this.config.configDir);
|
|
21
|
+
let profile;
|
|
22
|
+
try {
|
|
23
|
+
profile = flags.profile ?? (await getMySQLConfig()).defaultProfile;
|
|
24
|
+
}
|
|
25
|
+
catch (error) {
|
|
26
|
+
this.error(error instanceof Error ? error.message : String(error));
|
|
27
|
+
}
|
|
28
|
+
const result = await describeTable(profile, flags.table, flags.format);
|
|
29
|
+
await closeConnections();
|
|
30
|
+
if (result.success) {
|
|
31
|
+
this.log(result.result ?? '');
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
this.error(result.error ?? 'Failed to describe table');
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
export default class MySQLExplain extends Command {
|
|
3
|
+
static args: {
|
|
4
|
+
query: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
|
|
5
|
+
};
|
|
6
|
+
static description: string;
|
|
7
|
+
static examples: string[];
|
|
8
|
+
static flags: {
|
|
9
|
+
format: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
10
|
+
profile: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
11
|
+
};
|
|
12
|
+
run(): Promise<void>;
|
|
13
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { Args, Command, Flags } from '@oclif/core';
|
|
2
|
+
import { closeConnections, explainQuery, getMySQLConfig, setConfigDir } from '../../mysql/index.js';
|
|
3
|
+
export default class MySQLExplain extends Command {
|
|
4
|
+
static args = {
|
|
5
|
+
query: Args.string({ description: 'SQL query to explain', required: true }),
|
|
6
|
+
};
|
|
7
|
+
static description = 'Show the execution plan for a MySQL query';
|
|
8
|
+
static examples = [
|
|
9
|
+
'<%= config.bin %> <%= command.id %> "SELECT * FROM users WHERE id = 1"',
|
|
10
|
+
'<%= config.bin %> <%= command.id %> "SELECT * FROM orders JOIN users ON orders.user_id = users.id" --format json',
|
|
11
|
+
];
|
|
12
|
+
static flags = {
|
|
13
|
+
format: Flags.string({
|
|
14
|
+
default: 'table',
|
|
15
|
+
description: 'Output format',
|
|
16
|
+
options: ['table', 'json', 'toon'],
|
|
17
|
+
}),
|
|
18
|
+
profile: Flags.string({ description: 'Database profile name from config', required: false }),
|
|
19
|
+
};
|
|
20
|
+
async run() {
|
|
21
|
+
const { args, flags } = await this.parse(MySQLExplain);
|
|
22
|
+
setConfigDir(this.config.configDir);
|
|
23
|
+
let profile;
|
|
24
|
+
try {
|
|
25
|
+
profile = flags.profile ?? (await getMySQLConfig()).defaultProfile;
|
|
26
|
+
}
|
|
27
|
+
catch (error) {
|
|
28
|
+
this.error(error instanceof Error ? error.message : String(error));
|
|
29
|
+
}
|
|
30
|
+
const result = await explainQuery(profile, args.query, flags.format);
|
|
31
|
+
await closeConnections();
|
|
32
|
+
if (result.success) {
|
|
33
|
+
this.log(result.result ?? '');
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
this.error(result.error ?? 'Failed to explain query');
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
export default class MySQLDatabases extends Command {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
5
|
+
static flags: {
|
|
6
|
+
profile: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
7
|
+
};
|
|
8
|
+
run(): Promise<void>;
|
|
9
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { Command, Flags } from '@oclif/core';
|
|
2
|
+
import { closeConnections, getMySQLConfig, listDatabases, setConfigDir } from '../../mysql/index.js';
|
|
3
|
+
export default class MySQLDatabases extends Command {
|
|
4
|
+
static description = 'List all databases accessible on the MySQL server';
|
|
5
|
+
static examples = [
|
|
6
|
+
'<%= config.bin %> <%= command.id %>',
|
|
7
|
+
'<%= config.bin %> <%= command.id %> --profile staging',
|
|
8
|
+
];
|
|
9
|
+
static flags = {
|
|
10
|
+
profile: Flags.string({ description: 'Database profile name from config', required: false }),
|
|
11
|
+
};
|
|
12
|
+
async run() {
|
|
13
|
+
const { flags } = await this.parse(MySQLDatabases);
|
|
14
|
+
setConfigDir(this.config.configDir);
|
|
15
|
+
let profile;
|
|
16
|
+
try {
|
|
17
|
+
profile = flags.profile ?? (await getMySQLConfig()).defaultProfile;
|
|
18
|
+
}
|
|
19
|
+
catch (error) {
|
|
20
|
+
this.error(error instanceof Error ? error.message : String(error));
|
|
21
|
+
}
|
|
22
|
+
const result = await listDatabases(profile);
|
|
23
|
+
await closeConnections();
|
|
24
|
+
if (result.success) {
|
|
25
|
+
this.logJson(result.databases);
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
this.error(result.error ?? 'Failed to list databases');
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
export default class MySQLTables extends Command {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
5
|
+
static flags: {
|
|
6
|
+
profile: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
7
|
+
};
|
|
8
|
+
run(): Promise<void>;
|
|
9
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { Command, Flags } from '@oclif/core';
|
|
2
|
+
import { closeConnections, getMySQLConfig, listTables, setConfigDir } from '../../mysql/index.js';
|
|
3
|
+
export default class MySQLTables extends Command {
|
|
4
|
+
static description = 'List all tables in the current MySQL database';
|
|
5
|
+
static examples = [
|
|
6
|
+
'<%= config.bin %> <%= command.id %>',
|
|
7
|
+
'<%= config.bin %> <%= command.id %> --profile local',
|
|
8
|
+
];
|
|
9
|
+
static flags = {
|
|
10
|
+
profile: Flags.string({ description: 'Database profile name from config', required: false }),
|
|
11
|
+
};
|
|
12
|
+
async run() {
|
|
13
|
+
const { flags } = await this.parse(MySQLTables);
|
|
14
|
+
setConfigDir(this.config.configDir);
|
|
15
|
+
let profile;
|
|
16
|
+
try {
|
|
17
|
+
profile = flags.profile ?? (await getMySQLConfig()).defaultProfile;
|
|
18
|
+
}
|
|
19
|
+
catch (error) {
|
|
20
|
+
this.error(error instanceof Error ? error.message : String(error));
|
|
21
|
+
}
|
|
22
|
+
const result = await listTables(profile);
|
|
23
|
+
await closeConnections();
|
|
24
|
+
if (result.success) {
|
|
25
|
+
this.logJson(result.tables);
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
this.error(result.error ?? 'Failed to list tables');
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
export default class MySQLQuery extends Command {
|
|
3
|
+
static args: {
|
|
4
|
+
query: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
|
|
5
|
+
};
|
|
6
|
+
static description: string;
|
|
7
|
+
static examples: string[];
|
|
8
|
+
static flags: {
|
|
9
|
+
format: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
10
|
+
profile: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
11
|
+
'skip-confirmation': import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
12
|
+
};
|
|
13
|
+
run(): Promise<void>;
|
|
14
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { Args, Command, Flags } from '@oclif/core';
|
|
2
|
+
import { closeConnections, executeQuery, getMySQLConfig, setConfigDir } from '../../mysql/index.js';
|
|
3
|
+
export default class MySQLQuery extends Command {
|
|
4
|
+
static args = {
|
|
5
|
+
query: Args.string({ description: 'SQL query to execute', required: true }),
|
|
6
|
+
};
|
|
7
|
+
static description = 'Execute a SQL query against a MySQL database';
|
|
8
|
+
static examples = [
|
|
9
|
+
'<%= config.bin %> <%= command.id %> "SELECT * FROM users LIMIT 10"',
|
|
10
|
+
'<%= config.bin %> <%= command.id %> "UPDATE users SET email = \'user@email.com\' WHERE id = 999" --format json',
|
|
11
|
+
'<%= config.bin %> <%= command.id %> "DELETE FROM sessions" --profile prod --skip-confirmation',
|
|
12
|
+
];
|
|
13
|
+
static flags = {
|
|
14
|
+
format: Flags.string({
|
|
15
|
+
default: 'table',
|
|
16
|
+
description: 'Output format',
|
|
17
|
+
options: ['table', 'json', 'csv', 'toon'],
|
|
18
|
+
}),
|
|
19
|
+
profile: Flags.string({ description: 'Database profile name from config', required: false }),
|
|
20
|
+
'skip-confirmation': Flags.boolean({
|
|
21
|
+
default: false,
|
|
22
|
+
description: 'Skip confirmation prompt for destructive operations',
|
|
23
|
+
}),
|
|
24
|
+
};
|
|
25
|
+
async run() {
|
|
26
|
+
const { args, flags } = await this.parse(MySQLQuery);
|
|
27
|
+
setConfigDir(this.config.configDir);
|
|
28
|
+
let profile;
|
|
29
|
+
try {
|
|
30
|
+
profile = flags.profile ?? (await getMySQLConfig()).defaultProfile;
|
|
31
|
+
}
|
|
32
|
+
catch (error) {
|
|
33
|
+
this.error(error instanceof Error ? error.message : String(error));
|
|
34
|
+
}
|
|
35
|
+
const result = await executeQuery(args.query, profile, flags.format, flags['skip-confirmation']);
|
|
36
|
+
await closeConnections();
|
|
37
|
+
if (result.success) {
|
|
38
|
+
this.log(result.result ?? '');
|
|
39
|
+
}
|
|
40
|
+
else if (result.requiresConfirmation) {
|
|
41
|
+
this.log(`${result.message ?? 'Destructive operation requires confirmation.'}\nRe-run with --skip-confirmation to proceed.`);
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
this.error(result.error ?? 'Query failed');
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
export default class MySQLIndexes extends Command {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
5
|
+
static flags: {
|
|
6
|
+
format: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
7
|
+
profile: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
8
|
+
table: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
9
|
+
};
|
|
10
|
+
run(): Promise<void>;
|
|
11
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { Command, Flags } from '@oclif/core';
|
|
2
|
+
import { closeConnections, getMySQLConfig, setConfigDir, showIndexes } from '../../mysql/index.js';
|
|
3
|
+
export default class MySQLIndexes extends Command {
|
|
4
|
+
static description = 'Show indexes for a MySQL table';
|
|
5
|
+
static examples = [
|
|
6
|
+
'<%= config.bin %> <%= command.id %> --table users',
|
|
7
|
+
'<%= config.bin %> <%= command.id %> --table orders --format json --profile prod',
|
|
8
|
+
];
|
|
9
|
+
static flags = {
|
|
10
|
+
format: Flags.string({
|
|
11
|
+
default: 'table',
|
|
12
|
+
description: 'Output format',
|
|
13
|
+
options: ['table', 'json', 'toon'],
|
|
14
|
+
}),
|
|
15
|
+
profile: Flags.string({ description: 'Database profile name from config', required: false }),
|
|
16
|
+
table: Flags.string({ char: 't', description: 'Table name to show indexes for', required: true }),
|
|
17
|
+
};
|
|
18
|
+
async run() {
|
|
19
|
+
const { flags } = await this.parse(MySQLIndexes);
|
|
20
|
+
setConfigDir(this.config.configDir);
|
|
21
|
+
let profile;
|
|
22
|
+
try {
|
|
23
|
+
profile = flags.profile ?? (await getMySQLConfig()).defaultProfile;
|
|
24
|
+
}
|
|
25
|
+
catch (error) {
|
|
26
|
+
this.error(error instanceof Error ? error.message : String(error));
|
|
27
|
+
}
|
|
28
|
+
const result = await showIndexes(profile, flags.table, flags.format);
|
|
29
|
+
await closeConnections();
|
|
30
|
+
if (result.success) {
|
|
31
|
+
this.log(result.result ?? '');
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
this.error(result.error ?? 'Failed to show indexes');
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export interface DatabaseProfile {
|
|
2
|
+
database: string;
|
|
3
|
+
host: string;
|
|
4
|
+
password: string;
|
|
5
|
+
port: number;
|
|
6
|
+
ssl?: boolean;
|
|
7
|
+
user: string;
|
|
8
|
+
}
|
|
9
|
+
export interface MySQLJsonConfig {
|
|
10
|
+
defaultProfile: string;
|
|
11
|
+
profiles: Record<string, DatabaseProfile>;
|
|
12
|
+
}
|
|
13
|
+
export declare function readConfig(configDir: string, log: (message: string) => void): Promise<MySQLJsonConfig | undefined>;
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { default as fs } from 'fs-extra';
|
|
2
|
+
import { default as path } from 'node:path';
|
|
3
|
+
export async function readConfig(configDir, log) {
|
|
4
|
+
const configPath = path.join(configDir, 'mysql-config.json');
|
|
5
|
+
try {
|
|
6
|
+
return await fs.readJSON(configPath);
|
|
7
|
+
}
|
|
8
|
+
catch (error) {
|
|
9
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
10
|
+
if (msg.toLowerCase().includes('no such file or directory')) {
|
|
11
|
+
log('Missing connection config');
|
|
12
|
+
}
|
|
13
|
+
else {
|
|
14
|
+
log(msg);
|
|
15
|
+
}
|
|
16
|
+
return undefined;
|
|
17
|
+
}
|
|
18
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { run } from '@oclif/core';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { run } from '@oclif/core';
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { ConnectionOptions as MySQL2ConnectionOptions } from 'mysql2/promise';
|
|
2
|
+
import type { DatabaseProfile } from '../config.js';
|
|
3
|
+
/**
|
|
4
|
+
* Safety configuration for query execution
|
|
5
|
+
*/
|
|
6
|
+
interface SafetyConfig {
|
|
7
|
+
blacklistedOperations: string[];
|
|
8
|
+
defaultLimit: number;
|
|
9
|
+
requireConfirmationFor: string[];
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Main configuration structure
|
|
13
|
+
*/
|
|
14
|
+
export interface MySQLConfig {
|
|
15
|
+
defaultFormat: 'csv' | 'json' | 'table' | 'toon';
|
|
16
|
+
defaultProfile: string;
|
|
17
|
+
profiles: Record<string, DatabaseProfile>;
|
|
18
|
+
safety: SafetyConfig;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* MySQL connection options for mysql2 driver
|
|
22
|
+
*/
|
|
23
|
+
type MySQLConnectionOptions = Pick<MySQL2ConnectionOptions, 'connectTimeout' | 'database' | 'host' | 'multipleStatements' | 'password' | 'port' | 'ssl' | 'user'>;
|
|
24
|
+
/**
|
|
25
|
+
* Get MySQL connection options for a specific profile
|
|
26
|
+
*
|
|
27
|
+
* @param config - Configuration object
|
|
28
|
+
* @param profileName - Profile name
|
|
29
|
+
* @returns MySQL connection options
|
|
30
|
+
*/
|
|
31
|
+
export declare function getMySQLConnectionOptions(config: MySQLConfig, profileName: string): MySQLConnectionOptions;
|
|
32
|
+
export {};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Get MySQL connection options for a specific profile
|
|
3
|
+
*
|
|
4
|
+
* @param config - Configuration object
|
|
5
|
+
* @param profileName - Profile name
|
|
6
|
+
* @returns MySQL connection options
|
|
7
|
+
*/
|
|
8
|
+
export function getMySQLConnectionOptions(config, profileName) {
|
|
9
|
+
const profile = config.profiles[profileName];
|
|
10
|
+
if (!profile) {
|
|
11
|
+
const availableProfiles = Object.keys(config.profiles).join(', ');
|
|
12
|
+
throw new Error(`Profile "${profileName}" not found. Available profiles: ${availableProfiles}`);
|
|
13
|
+
}
|
|
14
|
+
const options = {
|
|
15
|
+
connectTimeout: 10_000,
|
|
16
|
+
database: profile.database,
|
|
17
|
+
host: profile.host,
|
|
18
|
+
multipleStatements: false,
|
|
19
|
+
password: profile.password,
|
|
20
|
+
port: profile.port,
|
|
21
|
+
user: profile.user,
|
|
22
|
+
};
|
|
23
|
+
if (profile.ssl) {
|
|
24
|
+
options.ssl = {};
|
|
25
|
+
}
|
|
26
|
+
return options;
|
|
27
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Database abstraction interface
|
|
3
|
+
* Defines the contract for the MySQL utility implementation
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Query execution result for SELECT/SHOW/DESCRIBE/EXPLAIN queries
|
|
7
|
+
*/
|
|
8
|
+
export interface QueryResult {
|
|
9
|
+
error?: string;
|
|
10
|
+
message?: string;
|
|
11
|
+
requiresConfirmation?: boolean;
|
|
12
|
+
result?: string;
|
|
13
|
+
success: boolean;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Database list result
|
|
17
|
+
*/
|
|
18
|
+
export interface DatabaseListResult {
|
|
19
|
+
databases?: string[];
|
|
20
|
+
error?: string;
|
|
21
|
+
result?: string;
|
|
22
|
+
success: boolean;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Table list result
|
|
26
|
+
*/
|
|
27
|
+
export interface TableListResult {
|
|
28
|
+
error?: string;
|
|
29
|
+
result?: string;
|
|
30
|
+
success: boolean;
|
|
31
|
+
tables?: string[];
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Table structure result
|
|
35
|
+
*/
|
|
36
|
+
export interface TableStructureResult {
|
|
37
|
+
error?: string;
|
|
38
|
+
result?: string;
|
|
39
|
+
structure?: Record<string, unknown>[];
|
|
40
|
+
success: boolean;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Index information result
|
|
44
|
+
*/
|
|
45
|
+
export interface IndexResult {
|
|
46
|
+
error?: string;
|
|
47
|
+
indexes?: Record<string, unknown>[];
|
|
48
|
+
result?: string;
|
|
49
|
+
success: boolean;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Query plan result
|
|
53
|
+
*/
|
|
54
|
+
export interface ExplainResult {
|
|
55
|
+
error?: string;
|
|
56
|
+
plan?: Record<string, unknown>[];
|
|
57
|
+
result?: string;
|
|
58
|
+
success: boolean;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Connection test result
|
|
62
|
+
*/
|
|
63
|
+
export interface ConnectionTestResult {
|
|
64
|
+
database?: string;
|
|
65
|
+
error?: string;
|
|
66
|
+
result?: string;
|
|
67
|
+
success: boolean;
|
|
68
|
+
version?: string;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Output format type
|
|
72
|
+
*/
|
|
73
|
+
export type OutputFormat = 'csv' | 'json' | 'table' | 'toon';
|
|
74
|
+
/**
|
|
75
|
+
* Database utility interface
|
|
76
|
+
*/
|
|
77
|
+
export interface DatabaseUtil {
|
|
78
|
+
closeAll(): Promise<void>;
|
|
79
|
+
describeTable(profileName: string, table: string, format?: OutputFormat): Promise<TableStructureResult>;
|
|
80
|
+
executeQuery(profileName: string, query: string, format?: OutputFormat, skipConfirmation?: boolean): Promise<QueryResult>;
|
|
81
|
+
explainQuery(profileName: string, query: string, format?: OutputFormat): Promise<ExplainResult>;
|
|
82
|
+
listDatabases(profileName: string): Promise<DatabaseListResult>;
|
|
83
|
+
listTables(profileName: string): Promise<TableListResult>;
|
|
84
|
+
showIndexes(profileName: string, table: string, format?: OutputFormat): Promise<IndexResult>;
|
|
85
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export type { MySQLJsonConfig } from '../config.js';
|
|
2
|
+
export type { ConnectionTestResult } from './database.js';
|
|
3
|
+
export { closeConnections, describeTable, executeQuery, explainQuery, getMySQLConfig, listDatabases, listTables, setConfigDir, showIndexes, testDirectConnection, } from './mysql-client.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { closeConnections, describeTable, executeQuery, explainQuery, getMySQLConfig, listDatabases, listTables, setConfigDir, showIndexes, testDirectConnection, } from './mysql-client.js';
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type { DatabaseProfile } from '../config.js';
|
|
2
|
+
import type { MySQLConfig } from './config-loader.js';
|
|
3
|
+
import type { ConnectionTestResult, DatabaseListResult, ExplainResult, IndexResult, OutputFormat, QueryResult, TableListResult, TableStructureResult } from './database.js';
|
|
4
|
+
/**
|
|
5
|
+
* Set the config directory for the singleton client
|
|
6
|
+
* @param dir - Oclif config directory path
|
|
7
|
+
*/
|
|
8
|
+
export declare function setConfigDir(dir: string): void;
|
|
9
|
+
/**
|
|
10
|
+
* Get the loaded MySQL config, initializing if needed
|
|
11
|
+
*/
|
|
12
|
+
export declare function getMySQLConfig(): Promise<MySQLConfig>;
|
|
13
|
+
/**
|
|
14
|
+
* Execute SQL query
|
|
15
|
+
* @param query - SQL query to execute
|
|
16
|
+
* @param profile - Database profile name
|
|
17
|
+
* @param format - Output format
|
|
18
|
+
* @param skipConfirmation - Skip confirmation for destructive operations
|
|
19
|
+
*/
|
|
20
|
+
export declare function executeQuery(query: string, profile: string, format?: OutputFormat, skipConfirmation?: boolean): Promise<QueryResult>;
|
|
21
|
+
/**
|
|
22
|
+
* List all databases
|
|
23
|
+
* @param profile - Database profile name
|
|
24
|
+
*/
|
|
25
|
+
export declare function listDatabases(profile: string): Promise<DatabaseListResult>;
|
|
26
|
+
/**
|
|
27
|
+
* List all tables in current database
|
|
28
|
+
* @param profile - Database profile name
|
|
29
|
+
*/
|
|
30
|
+
export declare function listTables(profile: string): Promise<TableListResult>;
|
|
31
|
+
/**
|
|
32
|
+
* Describe table structure
|
|
33
|
+
* @param profile - Database profile name
|
|
34
|
+
* @param table - Table name
|
|
35
|
+
* @param format - Output format
|
|
36
|
+
*/
|
|
37
|
+
export declare function describeTable(profile: string, table: string, format?: 'json' | 'table' | 'toon'): Promise<TableStructureResult>;
|
|
38
|
+
/**
|
|
39
|
+
* Show table indexes
|
|
40
|
+
* @param profile - Database profile name
|
|
41
|
+
* @param table - Table name
|
|
42
|
+
* @param format - Output format
|
|
43
|
+
*/
|
|
44
|
+
export declare function showIndexes(profile: string, table: string, format?: 'json' | 'table' | 'toon'): Promise<IndexResult>;
|
|
45
|
+
/**
|
|
46
|
+
* Explain query execution plan
|
|
47
|
+
* @param profile - Database profile name
|
|
48
|
+
* @param query - SQL query to explain
|
|
49
|
+
* @param format - Output format
|
|
50
|
+
*/
|
|
51
|
+
export declare function explainQuery(profile: string, query: string, format?: 'json' | 'table' | 'toon'): Promise<ExplainResult>;
|
|
52
|
+
/**
|
|
53
|
+
* Test a connection directly with profile options (without loading JSON config)
|
|
54
|
+
* @param profile - Database connection profile options
|
|
55
|
+
*/
|
|
56
|
+
export declare function testDirectConnection(profile: DatabaseProfile): Promise<ConnectionTestResult>;
|
|
57
|
+
/**
|
|
58
|
+
* Close all connections
|
|
59
|
+
*/
|
|
60
|
+
export declare function closeConnections(): Promise<void>;
|