Files
local-dev-db/src/index.ts
2023-07-16 05:38:10 +02:00

231 lines
6.9 KiB
TypeScript

import path from 'path';
import fs from 'fs';
import { program } from 'commander';
import { confirm } from '@inquirer/prompts';
import { dockerCommand } from 'docker-cli-js';
import mysql from 'mysql';
import packageInfo from '../package.json';
let PACKAGE_INSTALLATION_PATH = path.normalize(
__dirname.startsWith('/snapshot') ? `${path.dirname(process.execPath)}/..` : `${__dirname}/../..`,
);
const dockerCompose: typeof dockerCommand = async (command, options) => {
try {
return await dockerCommand(
`compose --file "${PACKAGE_INSTALLATION_PATH}/docker/docker-compose.yml" --project-name "ldd" ${command}`,
{ echo: false, ...(options ?? {}) },
);
} catch (e: any) {
if (e.stderr === undefined) {
throw e;
}
console.error(`ERROR: ${e.stderr}`);
process.exit(1);
}
};
const execQuery = (query: string, database: string = 'defaultdb') => {
return new Promise((resolve) => {
const connection = mysql.createConnection({
host: '127.0.0.1',
port: Number(process.env.LDD_DB_PORT ?? '3306'),
user: 'root',
password: process.env.LDD_DB_ROOT_PASSWORD ?? 'not-secure-pwd',
database,
multipleStatements: true,
});
connection.connect((error) => {
if (error) {
if (error.code === 'ECONNREFUSED') {
console.error(`ERROR: Could't connect to the DB server. Did you forget to start it?`);
process.exit(1);
}
throw error;
}
});
connection.query(query, (error, results) => {
if (error) {
console.error(`ERROR: ${error.sqlMessage}`);
process.exit(1);
}
resolve(results);
});
connection.end();
});
};
program.name('ldd').description(packageInfo.description).version(packageInfo.version);
program
.command('start')
.description('Starts your local DB server')
.action(async (str, options) => {
console.info('Starting local database containers...');
await dockerCompose('up -d');
console.info('');
console.info(`A PhpMyAdmin instance is running on: http://127.0.0.1:${process.env.LDD_PMA_PORT ?? 8010}`);
});
program
.command('stop')
.description('Stops your local DB server')
.action(async () => {
console.info('Stopping local database containers...');
await dockerCompose('down');
});
program
.command('destroy')
.description('Stops all containers (if running) and deletes any related volumes')
.action(async () => {
const confirmation = await confirm({
message: 'This action will delete all of your data and cannot be reverted. Are you sure?',
default: false,
});
if (confirmation !== true) {
console.info('Aborting...');
process.exit(0);
}
console.info('Destroying local database containers...');
await dockerCompose('down -v');
});
program
.command('create')
.description('Creates a new database')
.argument('<db_name>', 'The database name')
.action(async (databaseName) => {
const username = databaseName;
const userPwd = `${databaseName}-pwd`;
console.info(`Creating a new DB named "${databaseName}"...`);
await execQuery(
`START TRANSACTION; CREATE DATABASE \`${databaseName}\`; CREATE USER '${username}'@'%' IDENTIFIED BY '${userPwd}'; GRANT ALL ON \`${databaseName}\`.* TO '${username}'@'%'; FLUSH PRIVILEGES; COMMIT;`,
);
console.info(`A new user has been created with full permissions on "${databaseName}".`);
console.info('');
console.info(`Username: ${username}`);
console.info(`Password: ${userPwd}`);
});
program
.command('drop')
.description('Drops the given database and its default user (if they exist)')
.argument('<db_name>', 'The database name')
.action(async (databaseName) => {
const username = databaseName;
const userPwd = `${databaseName}-pwd`;
const confirmation = await confirm({
message: `This action will delete your database "${databaseName}" and cannot be reverted. Are you sure?`,
default: false,
});
if (confirmation !== true) {
console.info('Aborting...');
process.exit(0);
}
console.info(`Dropping DB "${databaseName}" and its default user...`);
await execQuery(`DROP DATABASE IF EXISTS \`${databaseName}\`; DROP USER IF EXISTS \`${databaseName}\`;`);
});
program
.command('dump-all')
.description('Creates a SQL dump file of all databases')
.action(async () => {
const now = new Date();
const month = now.getMonth().toString().padStart(2, '0');
const date = now.getDate().toString().padStart(2, '0');
const hours = now.getHours().toString().padStart(2, '0');
const minutes = now.getMinutes().toString().padStart(2, '0');
const seconds = now.getSeconds().toString().padStart(2, '0');
const dumpFileName = `db-full-dump_${now.getFullYear()}-${month}-${date}_${hours}-${minutes}-${seconds}.sql`;
console.info(`Exporting all databases to "${dumpFileName}"...`);
fs.writeFileSync(
`./${dumpFileName}`,
(
await dockerCompose(
'exec db sh -c \'exec mariadb-dump --all-databases --lock-tables -uroot -p"$MARIADB_ROOT_PASSWORD"\'',
)
).raw,
);
});
program
.command('dump')
.description('Creates a SQL dump file of the given database')
.argument('<db_name>', 'The database name')
.action(async (databaseName) => {
const now = new Date();
const month = now.getMonth().toString().padStart(2, '0');
const date = now.getDate().toString().padStart(2, '0');
const hours = now.getHours().toString().padStart(2, '0');
const minutes = now.getMinutes().toString().padStart(2, '0');
const seconds = now.getSeconds().toString().padStart(2, '0');
const dumpFileName = `db-${databaseName}-dump_${now.getFullYear()}-${month}-${date}_${hours}-${minutes}-${seconds}.sql`;
console.info(`Exporting database to "${dumpFileName}"...`);
fs.writeFileSync(
`./${dumpFileName}`,
(
await dockerCompose(
`exec db sh -c \'exec mariadb-dump --databases "${databaseName}" --lock-tables -uroot -p"$MARIADB_ROOT_PASSWORD"\'`,
)
).raw,
);
});
program
.command('import')
.description('Runs all queries from the given SQL file')
.argument('<sql_file_path>', 'The SQL file to import')
.action(async (sqlFilePath) => {
const confirmation = await confirm({
message: 'This action will execute any SQL statement found in the given file and cannot be reverted. Are you sure?',
default: false,
});
if (confirmation !== true) {
console.info('Aborting...');
process.exit(0);
}
console.info(`Importing data from "${sqlFilePath}"...`);
if (!sqlFilePath.endsWith('.sql') || !fs.existsSync(sqlFilePath) || !fs.statSync(sqlFilePath).isFile()) {
console.error(`ERROR: Invalid SQL file`);
process.exit(1);
}
execQuery(fs.readFileSync(sqlFilePath).toString());
console.info('Done. Remember you might have to create dedicated users in order to access new databases.');
});
program.parse();