Compare commits
13 Commits
1.0.0-alph
...
1.0.0-alph
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0ec6b214cd | ||
|
|
dcc379607b | ||
|
|
ea39e4bad6 | ||
|
|
86ac444781 | ||
|
|
8e7703652e | ||
|
|
a53aab7ccf | ||
|
|
e8ce789943 | ||
|
|
0fc09fb3c7 | ||
|
|
473c3a4846 | ||
|
|
b572044439 | ||
|
|
cd101d6ba0 | ||
|
|
48bb30dd84 | ||
|
|
508b3e0006 |
2
.github/workflows/lint.yml
vendored
2
.github/workflows/lint.yml
vendored
@@ -1,5 +1,5 @@
|
|||||||
name: Lint
|
name: Lint
|
||||||
on: [push, pull_request]
|
on: [pull_request]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
lint:
|
lint:
|
||||||
|
|||||||
7
.gitignore
vendored
7
.gitignore
vendored
@@ -1,16 +1,9 @@
|
|||||||
bin
|
|
||||||
build
|
build
|
||||||
ldd
|
|
||||||
|
|
||||||
# misc
|
# misc
|
||||||
*.log
|
*.log
|
||||||
.DS_Store
|
.DS_Store
|
||||||
node_modules
|
node_modules
|
||||||
.cache
|
|
||||||
dist
|
|
||||||
|
|
||||||
# dependencies
|
|
||||||
/node_modules
|
|
||||||
|
|
||||||
# debug
|
# debug
|
||||||
npm-debug.log*
|
npm-debug.log*
|
||||||
|
|||||||
50
README.md
50
README.md
@@ -7,16 +7,16 @@
|
|||||||
|
|
||||||
A zero-config local MariaDB instance for local development (using Docker) so you can finally stop doing things like:
|
A zero-config local MariaDB instance for local development (using Docker) so you can finally stop doing things like:
|
||||||
|
|
||||||
- Using SQLite for dev and MariaDB/MySQL for production
|
- Using different databases for dev and prod environments (e.g. SQLite vs MariaDB/MySQL)
|
||||||
- Installing a local database server directly
|
- Installing a local database server directly on your machine
|
||||||
- Spending a lot of time to get up and running on a new dev environment
|
- Spending time getting up and running in a new development environment
|
||||||
|
|
||||||
## How does it fit your workflow?
|
## How does it fit into your workflow?
|
||||||
|
|
||||||
While this tool is meant to be installed as a dependency to your projects, it actually runs as a single database server.
|
While this tool is designed to be installed as a dependency in your projects, it actually runs as a single database server.
|
||||||
This makes it possible to optimize the resources when working on multiple projects at the same time.
|
This makes it possible to optimize resources when working on multiple projects at the same time.
|
||||||
|
|
||||||
Feel free to install this tool as a dependency to all of your projects, CLI commands will act on the same instance and all of your databases will share the same storage volume.
|
Feel free to install this tool as a dependency in any project where you need a MariaDB/MySQL database, CLI commands will act on the same instance and all your databases will share the same storage volume.
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
@@ -30,6 +30,10 @@ Feel free to install this tool as a dependency to all of your projects, CLI comm
|
|||||||
|
|
||||||
- **Docker:** this tool uses docker (compose) to spwan some containers for you. A basic default installation is usually more than enough (e.g. `brew install docker` or similar).
|
- **Docker:** this tool uses docker (compose) to spwan some containers for you. A basic default installation is usually more than enough (e.g. `brew install docker` or similar).
|
||||||
|
|
||||||
|
## Project lifecycle, contribution and support policy
|
||||||
|
|
||||||
|
Our policies are available on our [main oranization page](https://github.com/mep-agency#projects-lifecycle-contribution-and-support-policy).
|
||||||
|
|
||||||
## Original author
|
## Original author
|
||||||
|
|
||||||
- Marco Lipparini ([liarco](https://github.com/liarco))
|
- Marco Lipparini ([liarco](https://github.com/liarco))
|
||||||
@@ -70,7 +74,7 @@ Usage: ldd [options] [command]
|
|||||||
# ...
|
# ...
|
||||||
```
|
```
|
||||||
|
|
||||||
## Starting a new project
|
### Starting a new project
|
||||||
|
|
||||||
Creating a brand new database for your project is pretty easy:
|
Creating a brand new database for your project is pretty easy:
|
||||||
|
|
||||||
@@ -105,7 +109,35 @@ Stopping local database containers...
|
|||||||
|
|
||||||
## Advanced configuration
|
## Advanced configuration
|
||||||
|
|
||||||
We hope you never have to use it, but just in case, here are some ENV vars you can set on your machine to customize the behavior of the application:
|
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.
|
- `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.
|
||||||
- `LDD_DB_PORT` (default: `3306`): The database server will be attached to this port on your local machine. You can customize this to avoid any conflicts with other services.
|
- `LDD_DB_PORT` (default: `3306`): The database server will be attached to this port on your local machine. You can customize this to avoid any conflicts with other services.
|
||||||
|
|||||||
3
bin/ldd.js
Executable file
3
bin/ldd.js
Executable file
@@ -0,0 +1,3 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
require('../build/index.js');
|
||||||
69
lifecycle.js
69
lifecycle.js
@@ -1,69 +0,0 @@
|
|||||||
#!/usr/bin/env node
|
|
||||||
|
|
||||||
// Inspired by: https://blog.xendit.engineer/how-we-repurposed-npm-to-publish-and-distribute-our-go-binaries-for-internal-cli-23981b80911b
|
|
||||||
|
|
||||||
const fs = require('fs');
|
|
||||||
const path = require('path');
|
|
||||||
|
|
||||||
const BIN_BASE_DIR = `${__dirname}/bin/@mep-agency`;
|
|
||||||
const BIN_BASE_NAME = `${BIN_BASE_DIR}/local-dev-db`;
|
|
||||||
|
|
||||||
if (!fs.existsSync(path.dirname(BIN_BASE_DIR)) || !fs.statSync(path.dirname(BIN_BASE_DIR)).isDirectory()) {
|
|
||||||
console.info('Binaries are not available, this probably means we are in a development environment... skipping!');
|
|
||||||
|
|
||||||
process.exit(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
const ARCH_MAPPING = {
|
|
||||||
x64: '',
|
|
||||||
};
|
|
||||||
|
|
||||||
const PLATFORM_MAPPING = {
|
|
||||||
darwin: 'macos',
|
|
||||||
linux: 'linux',
|
|
||||||
win32: 'win.exe',
|
|
||||||
};
|
|
||||||
|
|
||||||
async function install(callback) {
|
|
||||||
if (PLATFORM_MAPPING[process.platform] === undefined) {
|
|
||||||
callback(`Unsupported platform: "${process.platform}"`);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ARCH_MAPPING[process.arch] === undefined) {
|
|
||||||
callback(`Unsupported architecture: "${process.arch}"`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const binaryNameTokens = [BIN_BASE_NAME, PLATFORM_MAPPING[process.platform], ARCH_MAPPING[process.arch]].filter(
|
|
||||||
(token) => token.length > 0,
|
|
||||||
);
|
|
||||||
|
|
||||||
console.info(`Copying the relevant binary for your platform ${process.platform} (${process.arch})`);
|
|
||||||
|
|
||||||
fs.copyFileSync(binaryNameTokens.join('-'), './ldd');
|
|
||||||
|
|
||||||
callback(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse command line arguments and call the right method
|
|
||||||
var actions = {
|
|
||||||
install: install,
|
|
||||||
};
|
|
||||||
|
|
||||||
const argv = process.argv;
|
|
||||||
|
|
||||||
if (argv && argv.length > 2) {
|
|
||||||
var cmd = process.argv[2];
|
|
||||||
if (!actions[cmd]) {
|
|
||||||
console.log('Invalid command.');
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
actions[cmd](function (err) {
|
|
||||||
if (err) {
|
|
||||||
console.error(err);
|
|
||||||
process.exit(1);
|
|
||||||
} else {
|
|
||||||
process.exit(0);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
35
package.json
35
package.json
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@mep-agency/local-dev-db",
|
"name": "@mep-agency/local-dev-db",
|
||||||
"version": "1.0.0-alpha4",
|
"version": "1.0.0-alpha13",
|
||||||
"private": false,
|
"private": false,
|
||||||
"description": "A zero-config local MariaDB instance for local development (using Docker)",
|
"description": "A zero-config local MariaDB instance for local development (using Docker)",
|
||||||
"author": "Marco Lipparini <developer@liarco.net>",
|
"author": "Marco Lipparini <developer@liarco.net>",
|
||||||
@@ -21,39 +21,28 @@
|
|||||||
"url": "https://github.com/mep-agency/local-dev-db/issues"
|
"url": "https://github.com/mep-agency/local-dev-db/issues"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "rm -rf ./bin && rm -rf ./build && tsc && pkg -c package.json ./build/src/index.js",
|
"build": "rm -rf ./build && tsc",
|
||||||
"watch": "tsc --watch",
|
"watch": "tsc --watch",
|
||||||
"format": "prettier --write \"**/*.{ts,md,scss,css,js}\"",
|
"format": "prettier --write \"**/*.{ts,md,scss,css,js}\"",
|
||||||
"lint": "prettier --check \"**/*.{ts,md,scss,css,js}\"",
|
"lint": "prettier --check \"**/*.{ts,md,scss,css,js}\""
|
||||||
"install": "node lifecycle.js install"
|
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
"ldd": "./ldd"
|
"ldd": "./bin/ldd.js"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"bin",
|
"bin",
|
||||||
"docker",
|
"build/**/*.js",
|
||||||
"lifecycle.js",
|
"docker"
|
||||||
"LICENCE",
|
|
||||||
"README.md"
|
|
||||||
],
|
],
|
||||||
"pkg": {
|
|
||||||
"targets": [
|
|
||||||
"linux-x64",
|
|
||||||
"macos-x64",
|
|
||||||
"win-x64"
|
|
||||||
],
|
|
||||||
"outputPath": "bin"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@inquirer/prompts": "^3.0.0",
|
|
||||||
"@types/mysql": "^2.15.21",
|
"@types/mysql": "^2.15.21",
|
||||||
"commander": "^11.0.0",
|
|
||||||
"docker-cli-js": "^2.10.0",
|
|
||||||
"mysql": "^2.18.1",
|
|
||||||
"pkg": "^5.8.1",
|
|
||||||
"prettier": "^2.8.8",
|
"prettier": "^2.8.8",
|
||||||
"typescript": "^5.1.3"
|
"typescript": "^5.1.3"
|
||||||
},
|
},
|
||||||
"dependencies": {}
|
"dependencies": {
|
||||||
|
"@inquirer/prompts": "^4.0.0",
|
||||||
|
"commander": "^12.0.0",
|
||||||
|
"docker-cli-js": "^2.10.0",
|
||||||
|
"mysql": "^2.18.1"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
61
src/config.ts
Normal file
61
src/config.ts
Normal 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();
|
||||||
76
src/index.ts
76
src/index.ts
@@ -1,20 +1,27 @@
|
|||||||
import path from 'path';
|
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import { program } from 'commander';
|
import { program } from 'commander';
|
||||||
import { confirm } from '@inquirer/prompts';
|
import { confirm } from '@inquirer/prompts';
|
||||||
import { dockerCommand } from 'docker-cli-js';
|
import { dockerCommand } from 'docker-cli-js';
|
||||||
import mysql from 'mysql';
|
import mysql from 'mysql';
|
||||||
|
|
||||||
import packageInfo from '../package.json';
|
import config from './config';
|
||||||
|
|
||||||
let PACKAGE_INSTALLATION_PATH = path.normalize(
|
const LDD_ROOT_PATH = `${__dirname}/..`;
|
||||||
__dirname.startsWith('/snapshot') ? path.dirname(process.execPath) : `${__dirname}/../..`,
|
|
||||||
);
|
interface DockerImagesCommandResult {
|
||||||
|
images: {
|
||||||
|
repository: string;
|
||||||
|
tag: string;
|
||||||
|
'image id': string;
|
||||||
|
created: string;
|
||||||
|
size: string;
|
||||||
|
}[];
|
||||||
|
}
|
||||||
|
|
||||||
const dockerCompose: typeof dockerCommand = async (command, options) => {
|
const dockerCompose: typeof dockerCommand = async (command, options) => {
|
||||||
try {
|
try {
|
||||||
return await dockerCommand(
|
return await dockerCommand(
|
||||||
`compose --file "${PACKAGE_INSTALLATION_PATH}/docker/docker-compose.yml" --project-name "ldd" ${command}`,
|
`compose --file "${LDD_ROOT_PATH}/docker/docker-compose.yml" --project-name "ldd" ${command}`,
|
||||||
{ echo: false, ...(options ?? {}) },
|
{ echo: false, ...(options ?? {}) },
|
||||||
);
|
);
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
@@ -63,17 +70,46 @@ 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
|
program
|
||||||
.command('start')
|
.command('start')
|
||||||
.description('Starts your local DB server')
|
.description('Starts your local DB server')
|
||||||
.action(async (str, options) => {
|
.action(async () => {
|
||||||
console.info('Starting local database containers...');
|
console.info('Starting local database containers...');
|
||||||
|
|
||||||
|
const requiredImages = [
|
||||||
|
`mariadb:${process.env.LDD_DB_IMAGE_TAG ?? 'latest'}`,
|
||||||
|
`phpmyadmin:${process.env.LDD_PMA_IMAGE_TAG ?? 'latest'}`,
|
||||||
|
];
|
||||||
|
|
||||||
|
try {
|
||||||
|
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...');
|
||||||
|
}
|
||||||
|
} catch (e: any) {
|
||||||
|
if (e.stderr === undefined) {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.error(`ERROR: ${e.stderr}`);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
await dockerCompose('up -d');
|
await dockerCompose('up -d');
|
||||||
|
|
||||||
console.info('');
|
console.info('');
|
||||||
|
console.info('Done!');
|
||||||
console.info(`A PhpMyAdmin instance is running on: http://127.0.0.1:${process.env.LDD_PMA_PORT ?? 8010}`);
|
console.info(`A PhpMyAdmin instance is running on: http://127.0.0.1:${process.env.LDD_PMA_PORT ?? 8010}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -109,7 +145,7 @@ program
|
|||||||
program
|
program
|
||||||
.command('create')
|
.command('create')
|
||||||
.description('Creates a new database')
|
.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) => {
|
.action(async (databaseName) => {
|
||||||
const username = databaseName;
|
const username = databaseName;
|
||||||
const userPwd = `${databaseName}-pwd`;
|
const userPwd = `${databaseName}-pwd`;
|
||||||
@@ -129,15 +165,18 @@ program
|
|||||||
program
|
program
|
||||||
.command('drop')
|
.command('drop')
|
||||||
.description('Drops the given database and its default user (if they exist)')
|
.description('Drops the given database and its default user (if they exist)')
|
||||||
.argument('<db_name>', 'The database name')
|
.argument(config.dbName !== undefined ? '[db_name]' : '<db_name>', 'The database name', config.dbName)
|
||||||
.action(async (databaseName) => {
|
.option('-f,--force', 'Skip safety confirmation', false)
|
||||||
|
.action(async (databaseName, options) => {
|
||||||
const username = databaseName;
|
const username = databaseName;
|
||||||
const userPwd = `${databaseName}-pwd`;
|
const userPwd = `${databaseName}-pwd`;
|
||||||
|
|
||||||
const confirmation = await confirm({
|
const confirmation =
|
||||||
|
options.force === true ||
|
||||||
|
(await confirm({
|
||||||
message: `This action will delete your database "${databaseName}" and cannot be reverted. Are you sure?`,
|
message: `This action will delete your database "${databaseName}" and cannot be reverted. Are you sure?`,
|
||||||
default: false,
|
default: false,
|
||||||
});
|
}));
|
||||||
|
|
||||||
if (confirmation !== true) {
|
if (confirmation !== true) {
|
||||||
console.info('Aborting...');
|
console.info('Aborting...');
|
||||||
@@ -177,7 +216,7 @@ program
|
|||||||
program
|
program
|
||||||
.command('dump')
|
.command('dump')
|
||||||
.description('Creates a SQL dump file of the given database')
|
.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) => {
|
.action(async (databaseName) => {
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
const month = now.getMonth().toString().padStart(2, '0');
|
const month = now.getMonth().toString().padStart(2, '0');
|
||||||
@@ -203,11 +242,14 @@ program
|
|||||||
.command('import')
|
.command('import')
|
||||||
.description('Runs all queries from the given SQL file')
|
.description('Runs all queries from the given SQL file')
|
||||||
.argument('<sql_file_path>', 'The SQL file to import')
|
.argument('<sql_file_path>', 'The SQL file to import')
|
||||||
.action(async (sqlFilePath) => {
|
.option('-f,--force', 'Skip safety confirmation', false)
|
||||||
const confirmation = await confirm({
|
.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?',
|
message: 'This action will execute any SQL statement found in the given file and cannot be reverted. Are you sure?',
|
||||||
default: false,
|
default: false,
|
||||||
});
|
}));
|
||||||
|
|
||||||
if (confirmation !== true) {
|
if (confirmation !== true) {
|
||||||
console.info('Aborting...');
|
console.info('Aborting...');
|
||||||
|
|||||||
Reference in New Issue
Block a user