5 Commits

Author SHA1 Message Date
Marco Lipparini
ea39e4bad6 Optimizing package size (#5) 2023-07-19 11:59:48 +02:00
Marco Lipparini
86ac444781 Preparing release 1.0.0-alpha10 (#4) 2023-07-18 22:16:27 +02:00
Marco Lipparini
8e7703652e Adding support for project configuration files (#3) 2023-07-18 21:55:48 +02:00
Marco Lipparini
a53aab7ccf Adding feedback to the "start" command when required images are not available (#2) 2023-07-18 19:42:22 +02:00
Marco Lipparini
e8ce789943 Adding "--force" flag to some commands (#1)
* Adding "--force" flag to "drop" command

* Adding "--force" flag to "import" command
2023-07-18 19:41:39 +02:00
6 changed files with 147 additions and 24 deletions

View File

@@ -1,5 +1,5 @@
name: Lint
on: [push, pull_request]
on: [pull_request]
jobs:
lint:

View File

@@ -109,6 +109,34 @@ Stopping local database containers...
## Advanced configuration
The goal of LDD is to speed up the process of setting up new projects and synchronizing a common system configuration across multiple environments. That's why we don't plan to support deep customization options.
However, there are some common use cases that require a bit more flexibility, so the following features may help.
### Project config files
Each project usually requires its own database, and you will probably need to run most commands against it, depending on the project you are working on.
The closest available `ldd.json` file is used to load the configuration for the current project:
```json
{
"dbName": "my-awesome-app"
}
```
With the configuration above, any command will default to `my-awesome-app` as the `<db_name>` argument value if nothing is passed manually:
```bash
$ yarn ldd create
Loading configuration from: /MyProjects/my-awesome-app/ldd.json
Creating a new DB named "my-awesome-app"...
# ...
```
### ENV variables
We hope you never have to use them, but just in case, here are some ENV vars you can set on your machine to customize the behavior of the application:
- `LDD_DB_IMAGE_TAG` (default: `latest`): we use the official [MariaDB](https://hub.docker.com/_/mariadb) Docker image. You can pick a different tag if you wish.

View File

@@ -1,3 +1,3 @@
#!/usr/bin/env node
require('../build/src/index.js');
require('../build/index.js');

View File

@@ -1,6 +1,6 @@
{
"name": "@mep-agency/local-dev-db",
"version": "1.0.0-alpha9",
"version": "1.0.0-alpha11",
"private": false,
"description": "A zero-config local MariaDB instance for local development (using Docker)",
"author": "Marco Lipparini <developer@liarco.net>",
@@ -31,10 +31,8 @@
},
"files": [
"bin",
"build",
"docker",
"LICENCE",
"README.md"
"build/**/*.js",
"docker"
],
"devDependencies": {
"@types/mysql": "^2.15.21",

61
src/config.ts Normal file
View File

@@ -0,0 +1,61 @@
import fs from 'fs';
import path from 'path';
const JSON_CONFIG_FILE_NAME = 'ldd.json';
const PACKAGE_JSON_PATH = `${__dirname}/../package.json`;
const DEFAULT_CONFIG: Partial<JsonConfiguration> = {
dbName: undefined,
};
interface JsonConfiguration {
dbName?: string;
packageInfo: {
name: string;
description: string;
version: string;
};
}
const findAndReadConfig = () => {
let userConfig = {};
try {
let startdir = process.cwd();
while (true) {
var list = fs.readdirSync(startdir);
if (list.indexOf(JSON_CONFIG_FILE_NAME) != -1) {
// Found
console.info(`Loading configuration from: ${path.join(startdir, JSON_CONFIG_FILE_NAME)}`);
userConfig = JSON.parse(fs.readFileSync(path.join(startdir, JSON_CONFIG_FILE_NAME)).toString());
break;
} else if (startdir == '/') {
// Root dir, file not found
break;
} else {
startdir = path.normalize(path.join(startdir, '..'));
}
}
} catch (e) {
console.error('ERROR: Failed loading LDD configuration file...');
process.exit(1);
}
try {
return {
...DEFAULT_CONFIG,
...userConfig,
packageInfo: JSON.parse(fs.readFileSync(PACKAGE_JSON_PATH).toString()),
} as JsonConfiguration;
} catch (e) {
console.error('ERROR: Failed loading LDD package.json...');
process.exit(1);
}
};
export default findAndReadConfig();

View File

@@ -4,9 +4,19 @@ import { confirm } from '@inquirer/prompts';
import { dockerCommand } from 'docker-cli-js';
import mysql from 'mysql';
import packageInfo from '../package.json';
import config from './config';
let PACKAGE_INSTALLATION_PATH = `${__dirname}/../..`;
const PACKAGE_INSTALLATION_PATH = `${__dirname}/../..`;
interface DockerImagesCommandResult {
images: {
repository: string;
tag: string;
'image id': string;
created: string;
size: string;
}[];
}
const dockerCompose: typeof dockerCommand = async (command, options) => {
try {
@@ -60,17 +70,37 @@ const execQuery = (query: string, database: string = 'defaultdb') => {
});
};
program.name('ldd').description(packageInfo.description).version(packageInfo.version);
program.name('ldd').description(config.packageInfo.description).version(config.packageInfo.version);
program
.command('start')
.description('Starts your local DB server')
.action(async (str, options) => {
.action(async () => {
console.info('Starting local database containers...');
const requiredImages = [
`mariadb:${process.env.LDD_DB_IMAGE_TAG ?? 'latest'}`,
`phpmyadmin:${process.env.LDD_PMA_IMAGE_TAG ?? 'latest'}`,
];
const availableImagesImages = ((await dockerCommand('images', { echo: false })) as DockerImagesCommandResult).images
.map((imageData) => `${imageData.repository}:${imageData.tag}`)
.filter((imageName) => requiredImages.includes(imageName));
const missingImages = requiredImages.filter((requiredImage) => !availableImagesImages.includes(requiredImage));
if (missingImages.length > 0) {
console.info('');
console.info('The following images will be downloaded as they are required but not available:');
missingImages.map((image) => console.info(` - ${image}`));
console.info('');
console.info('This may take some time, please wait...');
}
await dockerCompose('up -d');
console.info('');
console.info('Done!');
console.info(`A PhpMyAdmin instance is running on: http://127.0.0.1:${process.env.LDD_PMA_PORT ?? 8010}`);
});
@@ -106,7 +136,7 @@ program
program
.command('create')
.description('Creates a new database')
.argument('<db_name>', 'The database name')
.argument(config.dbName !== undefined ? '[db_name]' : '<db_name>', 'The database name', config.dbName)
.action(async (databaseName) => {
const username = databaseName;
const userPwd = `${databaseName}-pwd`;
@@ -126,15 +156,18 @@ program
program
.command('drop')
.description('Drops the given database and its default user (if they exist)')
.argument('<db_name>', 'The database name')
.action(async (databaseName) => {
.argument(config.dbName !== undefined ? '[db_name]' : '<db_name>', 'The database name', config.dbName)
.option('-f,--force', 'Skip safety confirmation', false)
.action(async (databaseName, options) => {
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,
});
const confirmation =
options.force === true ||
(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...');
@@ -174,7 +207,7 @@ program
program
.command('dump')
.description('Creates a SQL dump file of the given database')
.argument('<db_name>', 'The database name')
.argument(config.dbName !== undefined ? '[db_name]' : '<db_name>', 'The database name', config.dbName)
.action(async (databaseName) => {
const now = new Date();
const month = now.getMonth().toString().padStart(2, '0');
@@ -200,11 +233,14 @@ 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,
});
.option('-f,--force', 'Skip safety confirmation', false)
.action(async (sqlFilePath, options) => {
const confirmation =
options.force === true ||
(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...');