mysql-migration 1.0.5
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/README.md +47 -0
- package/index.js +23 -0
- package/package.json +25 -0
- package/src/commands/create.js +60 -0
- package/src/commands/init.js +18 -0
- package/src/commands/run.js +103 -0
- package/src/utils/functions.js +57 -0
package/README.md
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# MySQL Migration Database
|
|
2
|
+
|
|
3
|
+
A simple command-line tool for generating and running database migrations for MySQL.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
You can install `mysql-migration` using npm:
|
|
8
|
+
npm install -g mysql-migration
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
### Create a new migration
|
|
14
|
+
|
|
15
|
+
To create a new migration, run the following command:
|
|
16
|
+
npx mysql-migration migration-name database-name
|
|
17
|
+
|
|
18
|
+
This will create a new migration file in the `migrations` directory with a timestamp and the name `migration-name`.
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
### Running migrations
|
|
22
|
+
|
|
23
|
+
To run pending migrations, run the following command:
|
|
24
|
+
npx mysql-migration run database-name (optional)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
This will run all migrations that have not yet been run.
|
|
28
|
+
|
|
29
|
+
### Initialize the migrations table
|
|
30
|
+
|
|
31
|
+
If you have not yet run any migrations, you need to initialize the migrations table by running the following command:
|
|
32
|
+
npx mysql-migration init
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
### Configuration
|
|
36
|
+
|
|
37
|
+
The configuration file should export an object with the following properties:
|
|
38
|
+
|
|
39
|
+
- `host`: the MySQL server host
|
|
40
|
+
- `database`: the name of the database to migrate
|
|
41
|
+
- `user`: the MySQL user name
|
|
42
|
+
- `password`: the MySQL user password
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
### License
|
|
46
|
+
|
|
47
|
+
This package is licensed under the MIT License.
|
package/index.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
const program = require('commander');
|
|
3
|
+
//---------------------------------------
|
|
4
|
+
program
|
|
5
|
+
.version('1.0.0')
|
|
6
|
+
.description('Migration for mysql database');
|
|
7
|
+
//---------------------------------------
|
|
8
|
+
program
|
|
9
|
+
.command('init')
|
|
10
|
+
.description('Initialize migration')
|
|
11
|
+
.action(() => require('./src/commands/init'));
|
|
12
|
+
//---------------------------------------
|
|
13
|
+
program
|
|
14
|
+
.command('run [dbName]')
|
|
15
|
+
.description('Run migration')
|
|
16
|
+
.action((dbName) => require('./src/commands/run')(dbName));
|
|
17
|
+
//---------------------------------------
|
|
18
|
+
program
|
|
19
|
+
.command('create <migrationName> <dbName>')
|
|
20
|
+
.description('Create a new migration')
|
|
21
|
+
.action((migrationName, dbName) => require('./src/commands/create')(migrationName, dbName));
|
|
22
|
+
//---------------------------------------
|
|
23
|
+
program.parse(process.argv);
|
package/package.json
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "mysql-migration",
|
|
3
|
+
"version": "1.0.5",
|
|
4
|
+
"description": "Migration for mysql database",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"mysql-migration": "./index.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
11
|
+
},
|
|
12
|
+
"keywords": [
|
|
13
|
+
"mysql migration",
|
|
14
|
+
"migration",
|
|
15
|
+
"mysql"
|
|
16
|
+
],
|
|
17
|
+
"dependencies": {
|
|
18
|
+
"mysql": "^2.18.1",
|
|
19
|
+
"moment": "^2.24.0",
|
|
20
|
+
"fs": "0.0.1-security",
|
|
21
|
+
"commander": "^2.20.0"
|
|
22
|
+
},
|
|
23
|
+
"author": "SherKan",
|
|
24
|
+
"license": "MIT"
|
|
25
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
//---------------------------------------
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const moment = require('moment');
|
|
5
|
+
//---------------------------------------
|
|
6
|
+
const currentPath = process.cwd();
|
|
7
|
+
const config = require(`${currentPath}/migrations/config.json`);
|
|
8
|
+
//---------------------------------------
|
|
9
|
+
function create_migration(migrationName, dbName) {
|
|
10
|
+
if (!migrationName) {
|
|
11
|
+
console.error('\x1b[31m%s\x1b[0m', `Error: Migration name is empty !`);
|
|
12
|
+
process.exit(1);
|
|
13
|
+
}
|
|
14
|
+
//---------------------------------------
|
|
15
|
+
const databases = Object.keys(config.databases);
|
|
16
|
+
//---------------------------------------
|
|
17
|
+
if (!databases.includes(dbName)) {
|
|
18
|
+
console.error('\x1b[31m%s\x1b[0m', `Error: Invalid database name "${dbName}" can be: ${databases.join(', ')}.`);
|
|
19
|
+
process.exit(1);
|
|
20
|
+
}
|
|
21
|
+
//---------------------------------------
|
|
22
|
+
if (!fs.existsSync(`${currentPath}/migrations/${dbName}_db`)) fs.mkdirSync(`${currentPath}/migrations/${dbName}_db`);
|
|
23
|
+
//---------------------------------------
|
|
24
|
+
const currentDate = moment().format('YYYY_MM_DD_HHmmss');
|
|
25
|
+
const fileName = `${currentDate}_${migrationName}.js`;
|
|
26
|
+
const filePath = `${currentPath}/migrations/${dbName}_db/${fileName}`;
|
|
27
|
+
//---------------------------------------
|
|
28
|
+
if (fs.existsSync(filePath)) console.warn('\x1b[33m%s\x1b[0m', `Warning: File "${fileName}" already exists in the "migrations" directory.`);
|
|
29
|
+
else {
|
|
30
|
+
const dataText = `module.exports = {
|
|
31
|
+
up: (connection) => {
|
|
32
|
+
const query = "";
|
|
33
|
+
|
|
34
|
+
return new Promise((resolve, reject) => {
|
|
35
|
+
if (!query) reject('Migration query is empty !');
|
|
36
|
+
connection.query(query, (err) => {
|
|
37
|
+
if (err) reject(err);
|
|
38
|
+
resolve();
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
},
|
|
42
|
+
|
|
43
|
+
down: (connection) => {
|
|
44
|
+
const query = "";
|
|
45
|
+
|
|
46
|
+
return new Promise((resolve, reject) => {
|
|
47
|
+
if (!query) reject('Migration query is empty !');
|
|
48
|
+
connection.query(query, (err) => {
|
|
49
|
+
if (err) reject(err);
|
|
50
|
+
resolve();
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
};`
|
|
55
|
+
fs.writeFileSync(filePath, dataText);
|
|
56
|
+
console.log('\x1b[32m%s\x1b[0m', `Created migration file: "${fileName}".`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
//---------------------------------------
|
|
60
|
+
module.exports = create_migration;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
//---------------------------------------
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
//---------------------------------------
|
|
5
|
+
const currentPath = process.cwd();
|
|
6
|
+
//---------------------------------------
|
|
7
|
+
if (!fs.existsSync(`${currentPath}/migrations`)) fs.mkdirSync(`${currentPath}/migrations`);
|
|
8
|
+
if (!fs.existsSync(`${currentPath}/migrations/config.json`)) fs.writeFileSync(`${currentPath}/migrations/config.json`, JSON.stringify({
|
|
9
|
+
"databases": {
|
|
10
|
+
"db_name": {
|
|
11
|
+
"host": "db_host",
|
|
12
|
+
"user": "db_user",
|
|
13
|
+
"password": "db_password",
|
|
14
|
+
"database": "db_name"
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}, null, 3));
|
|
18
|
+
console.log('\x1b[32m%s\x1b[0m', `Created migration config file: "config.json" in the "migrations" directory.`);
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
//==============================================================================
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const mysql = require('mysql');
|
|
5
|
+
//---------------------------------------
|
|
6
|
+
const currentPath = process.cwd();
|
|
7
|
+
const config = require(`${currentPath}/migrations/config.json`);
|
|
8
|
+
const { checkTableMigrations, createTableMigrations, getAllMigrations, getCurrentBatch, insertMigration } = require("../utils/functions");
|
|
9
|
+
//==============================================================================
|
|
10
|
+
async function run_migration(dbName) {
|
|
11
|
+
const connection = {}, migrations = [];
|
|
12
|
+
const databases = config.databases;
|
|
13
|
+
//---------------------------------------
|
|
14
|
+
if (dbName) {
|
|
15
|
+
if (!databases[dbName]) {
|
|
16
|
+
console.error('\x1b[31m%s\x1b[0m', `Error: Invalid database name "${dbName}".`);
|
|
17
|
+
process.exit(1);
|
|
18
|
+
}
|
|
19
|
+
//---------------------------------------
|
|
20
|
+
connection[dbName] = mysql.createConnection(databases[dbName]);
|
|
21
|
+
connection[dbName].connect((err) => {
|
|
22
|
+
if (err) {
|
|
23
|
+
console.error('\x1b[31m%s\x1b[0m', `Error: Unable to connect to database "${dbName}".\n${err}`);
|
|
24
|
+
process.exit(1);
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
//---------------------------------------
|
|
28
|
+
const tableMigrations = await checkTableMigrations(connection[dbName]);
|
|
29
|
+
if (!tableMigrations) await createTableMigrations(connection[dbName]);
|
|
30
|
+
//---------------------------------------
|
|
31
|
+
const batch = await getCurrentBatch(connection[dbName]) + 1;
|
|
32
|
+
const allMigrations = await getAllMigrations(connection[dbName]);
|
|
33
|
+
const migration = fs.readdirSync(`${currentPath}/migrations/${dbName}_db`);
|
|
34
|
+
//---------------------------------------
|
|
35
|
+
const migrations = migration.filter(val => !allMigrations.some(val2 => val.includes(val2.migration)));
|
|
36
|
+
//---------------------------------------
|
|
37
|
+
if (migrations.length === 0) {
|
|
38
|
+
console.log('\x1b[32m%s\x1b[0m', 'Nothing to migrate.\n');
|
|
39
|
+
connection[dbName].end();
|
|
40
|
+
process.exit(1);
|
|
41
|
+
}
|
|
42
|
+
//---------------------------------------
|
|
43
|
+
for (let file of migrations) {
|
|
44
|
+
const migration = require(`${currentPath}/migrations/${dbName}_db/${file}`);
|
|
45
|
+
try {
|
|
46
|
+
await migration.up(connection[dbName]);
|
|
47
|
+
await insertMigration(connection[dbName], file.replace('.js', ''), batch);
|
|
48
|
+
console.log('\x1b[36m%s\x1b[0m', `Migrated: "${file}" successfully.`);
|
|
49
|
+
}
|
|
50
|
+
catch (err) {
|
|
51
|
+
console.warn('\x1b[33m%s\x1b[0m', `Warning: "${err}" in migration "${file}".`);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
console.log('\x1b[32m%s\x1b[0m', 'All migrations have been completed successfully.\n');
|
|
55
|
+
connection[dbName].end();
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
for (let key in databases) {
|
|
59
|
+
connection[key] = mysql.createConnection(databases[key]);
|
|
60
|
+
connection[key].connect((err) => {
|
|
61
|
+
if (err) {
|
|
62
|
+
console.error('\x1b[31m%s\x1b[0m', `Error: Unable to connect to database "${key}".`);
|
|
63
|
+
process.exit(1);
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
//---------------------------------------
|
|
67
|
+
const tableMigrations = await checkTableMigrations(connection[key]);
|
|
68
|
+
if (!tableMigrations) await createTableMigrations(connection[key]);
|
|
69
|
+
//---------------------------------------
|
|
70
|
+
const batch = await getCurrentBatch(connection[key]) + 1;
|
|
71
|
+
const allMigrations = await getAllMigrations(connection[key]);
|
|
72
|
+
//---------------------------------------
|
|
73
|
+
if (!fs.existsSync(`${currentPath}/migrations/${key}_db`)) continue;
|
|
74
|
+
const migration = fs.readdirSync(`${currentPath}/migrations/${key}_db`);
|
|
75
|
+
//---------------------------------------
|
|
76
|
+
const diffMigrations = migration.filter(val => !allMigrations.some(val2 => val.includes(val2.migration)));
|
|
77
|
+
//---------------------------------------
|
|
78
|
+
for (let m of diffMigrations) migrations.push([m, key, batch]);
|
|
79
|
+
}
|
|
80
|
+
//---------------------------------------
|
|
81
|
+
if (migrations.length === 0) {
|
|
82
|
+
console.log('\x1b[32m%s\x1b[0m', 'Nothing to migrate.\n');
|
|
83
|
+
for (let key in connection) connection[key].end();
|
|
84
|
+
process.exit(1);
|
|
85
|
+
}
|
|
86
|
+
//---------------------------------------
|
|
87
|
+
for (let [file, key, batch] of migrations) {
|
|
88
|
+
const migration = require(`${currentPath}/migrations/${key}_db/${file}`);
|
|
89
|
+
try {
|
|
90
|
+
await migration.up(connection[key]);
|
|
91
|
+
await insertMigration(connection[key], file.replace('.js', ''), batch);
|
|
92
|
+
console.log('\x1b[36m%s\x1b[0m', `Migrated: "${file}" in database "${key}" successfully.`);
|
|
93
|
+
}
|
|
94
|
+
catch (err) {
|
|
95
|
+
console.warn('\x1b[33m%s\x1b[0m', `Warning: "${err}" in migration "${file}" in database "${key}".`);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
console.log('\x1b[32m%s\x1b[0m', 'All migrations have been completed successfully.\n');
|
|
99
|
+
for (let key in connection) connection[key].end();
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
//==============================================================================
|
|
103
|
+
module.exports = run_migration;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
function checkTableMigrations(connection) {
|
|
2
|
+
return new Promise((resolve) => {
|
|
3
|
+
connection.query("SELECT `id` FROM `migrations` LIMIT 1;", (error) => {
|
|
4
|
+
if (error) resolve(false);
|
|
5
|
+
else resolve(true);
|
|
6
|
+
});
|
|
7
|
+
});
|
|
8
|
+
}
|
|
9
|
+
//---------------------------------------
|
|
10
|
+
function createTableMigrations(connection) {
|
|
11
|
+
return new Promise((resolve) => {
|
|
12
|
+
const query = "CREATE TABLE `migrations` (\
|
|
13
|
+
`id` INT NOT NULL AUTO_INCREMENT,\
|
|
14
|
+
`migration` VARCHAR(255) NOT NULL,\
|
|
15
|
+
`batch` INT NOT NULL DEFAULT 1,\
|
|
16
|
+
`created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,\
|
|
17
|
+
PRIMARY KEY (`id`));"
|
|
18
|
+
connection.query(query, (error) => {
|
|
19
|
+
if (error) throw error;
|
|
20
|
+
resolve(true);
|
|
21
|
+
});
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
//---------------------------------------
|
|
25
|
+
function getAllMigrations(connection) {
|
|
26
|
+
return new Promise((resolve) => {
|
|
27
|
+
connection.query("SELECT `migration` FROM `migrations`;", (error, results) => {
|
|
28
|
+
if (error) throw error;
|
|
29
|
+
if (results?.length > 0) resolve(results);
|
|
30
|
+
else resolve([]);
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
//---------------------------------------
|
|
35
|
+
function getCurrentBatch(connection) {
|
|
36
|
+
return new Promise((resolve) => {
|
|
37
|
+
connection.query("SELECT `batch` FROM `migrations` ORDER BY `batch` DESC LIMIT 1;", (error, results) => {
|
|
38
|
+
if (error) throw error;
|
|
39
|
+
if (results?.length > 0) resolve(results[0].batch);
|
|
40
|
+
else resolve(0);
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
//---------------------------------------
|
|
45
|
+
function insertMigration(connection, migration, batch) {
|
|
46
|
+
return new Promise((resolve) => {
|
|
47
|
+
const query = `INSERT INTO \`migrations\` (\`migration\`, \`batch\`) VALUES ('${migration}', '${batch}');`
|
|
48
|
+
connection.query(query, (error) => {
|
|
49
|
+
if (error) throw error;
|
|
50
|
+
resolve(true);
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
//---------------------------------------
|
|
55
|
+
module.exports = {
|
|
56
|
+
checkTableMigrations, createTableMigrations, getAllMigrations, getCurrentBatch, insertMigration
|
|
57
|
+
}
|