mysql-migration 1.2.2 → 1.2.4
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/.prettierrc.json +10 -0
- package/README.md +118 -28
- package/index.js +29 -29
- package/package.json +10 -6
- package/src/commands/back.js +90 -65
- package/src/commands/batch.js +24 -23
- package/src/commands/create.js +13 -11
- package/src/commands/init.js +24 -13
- package/src/commands/run.js +134 -103
- package/src/utils/functions.js +66 -79
package/.prettierrc.json
ADDED
package/README.md
CHANGED
|
@@ -1,25 +1,73 @@
|
|
|
1
|
-
|
|
1
|
+
<div align="center">
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
# 🗄️ MySQL Migration Database
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
**A command-line companion that helps you manage versioned MySQL schema changes from your Node.js projects.**
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
[](https://www.npmjs.com/package/mysql-migration)
|
|
8
|
+
[](LICENSE.md)
|
|
9
|
+
[](https://nodejs.org/)
|
|
8
10
|
|
|
9
|
-
|
|
11
|
+
</div>
|
|
10
12
|
|
|
11
|
-
|
|
13
|
+
---
|
|
12
14
|
|
|
13
|
-
|
|
15
|
+
## ✨ Features
|
|
14
16
|
|
|
15
|
-
|
|
17
|
+
- 🎯 **Multi-database support** — Orchestrate migrations across different environments
|
|
18
|
+
- ⏱️ **Timestamped migration files** — Generated with a single command
|
|
19
|
+
- ⚡ **Forward and backward execution** — Run or roll back batches confidently
|
|
20
|
+
- 🔧 **Scriptable CLI** — Plugs into CI/CD pipelines via standard Node.js tooling
|
|
21
|
+
- 📦 **Zero dependencies** — Lightweight and fast
|
|
16
22
|
|
|
17
|
-
|
|
23
|
+
## 📋 Prerequisites
|
|
18
24
|
|
|
19
|
-
-
|
|
20
|
-
-
|
|
21
|
-
-
|
|
22
|
-
|
|
25
|
+
- **Node.js** v16 or later.
|
|
26
|
+
- **MySQL** instance reachable from where you run the CLI.
|
|
27
|
+
- A project workspace where you can store migration files and configuration.
|
|
28
|
+
|
|
29
|
+
## 📦 Installation
|
|
30
|
+
|
|
31
|
+
Install locally within your project:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
npm install --save-dev mysql-migration
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Or run ad hoc without installing by prefixing commands with `npx`.
|
|
38
|
+
|
|
39
|
+
## 🚀 Quick Start
|
|
40
|
+
|
|
41
|
+
**1️⃣ Create a configuration file**
|
|
42
|
+
|
|
43
|
+
Create `mysql-migration.config.json` in your project root (see [Configuration](#%EF%B8%8F-configuration)).
|
|
44
|
+
|
|
45
|
+
**2️⃣ Initialize the migrations table**
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
npx mysql-migration init
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
**3️⃣ Generate your first migration**
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
npx mysql-migration create add_users_table main-db
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
**4️⃣ Apply pending migrations**
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
npx mysql-migration run main-db
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## ⚙️ Configuration
|
|
64
|
+
|
|
65
|
+
The CLI reads settings from `mysql-migration.config.json`. Define each database you manage inside the `databases` object. Every entry requires the connection credentials documented below:
|
|
66
|
+
|
|
67
|
+
- `host`: MySQL server host name or IP.
|
|
68
|
+
- `database`: Database schema name.
|
|
69
|
+
- `user`: User with migration privileges.
|
|
70
|
+
- `password`: Corresponding password.
|
|
23
71
|
|
|
24
72
|
```json
|
|
25
73
|
{
|
|
@@ -34,29 +82,71 @@ The configuration file `mysql-migration.config.json` should export an object wit
|
|
|
34
82
|
}
|
|
35
83
|
```
|
|
36
84
|
|
|
37
|
-
|
|
85
|
+
Add as many database entries as you need. You can then target each one via the CLI commands below.
|
|
86
|
+
|
|
87
|
+
## 📖 Usage
|
|
88
|
+
|
|
89
|
+
### 📝 Available Commands
|
|
90
|
+
|
|
91
|
+
| Command | Description |
|
|
92
|
+
|---------|-------------|
|
|
93
|
+
| `npx mysql-migration help` | 📚 Show all available commands |
|
|
94
|
+
| `npx mysql-migration init` | 🎬 Initialize the migrations table |
|
|
95
|
+
| `npx mysql-migration create <name> <dbName>` | ✏️ Scaffold a timestamped migration file |
|
|
96
|
+
| `npx mysql-migration run [dbName]` | ▶️ Execute all pending migrations |
|
|
97
|
+
| `npx mysql-migration rollback <dbName> <batch>` | ⏪ Roll back migrations to specified batch |
|
|
98
|
+
| `npx mysql-migration batch <dbName>` | 📊 Display recorded batches |
|
|
99
|
+
|
|
100
|
+
### ✏️ Create a New Migration
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
npx mysql-migration create migration-name database-name
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
A new file appears in the `migrations/` directory, timestamped and ready for your SQL `up` and `down` statements.
|
|
107
|
+
|
|
108
|
+
### ▶️ Run Pending Migrations
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
npx mysql-migration run database-name
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
All migrations that have not yet been applied will run sequentially for the selected database.
|
|
115
|
+
|
|
116
|
+
### ⏪ Roll Back Migrations
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
npx mysql-migration rollback database-name batch
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Use this when you need to revert the database state to the specified batch number.
|
|
123
|
+
|
|
124
|
+
### 📊 Inspect Batches
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
npx mysql-migration batch database-name
|
|
128
|
+
```
|
|
38
129
|
|
|
39
|
-
|
|
130
|
+
View the recorded batches to understand which migrations were executed together.
|
|
40
131
|
|
|
41
|
-
|
|
132
|
+
## 🔧 Troubleshooting
|
|
42
133
|
|
|
43
|
-
|
|
44
|
-
|
|
134
|
+
- **Authentication errors**: Verify credentials in `mysql-migration.config.json` match your MySQL user.
|
|
135
|
+
- **Connection refused**: Ensure the MySQL server accepts remote connections from your host and the port is open.
|
|
136
|
+
- **Missing migrations folder**: Run `npx mysql-migration init` to scaffold the `migrations/` directory and configuration file.
|
|
45
137
|
|
|
46
|
-
|
|
138
|
+
## 💬 Support
|
|
47
139
|
|
|
48
|
-
|
|
140
|
+
Use `npx mysql-migration help` to review commands, or open an issue on the repository if you encounter bugs or have enhancement ideas.
|
|
49
141
|
|
|
50
|
-
|
|
51
|
-
 `npx mysql-migration run database-name(optional)`
|
|
142
|
+
---
|
|
52
143
|
|
|
53
|
-
|
|
144
|
+
<div align="center">
|
|
54
145
|
|
|
55
|
-
###
|
|
146
|
+
### 📄 License
|
|
56
147
|
|
|
57
|
-
|
|
58
|
-
 `npx mysql-migration rollback database-name batch`
|
|
148
|
+
This project is distributed under a **custom non-commercial license**. Please review [`LICENSE.md`](LICENSE.md) for the full terms before using the software.
|
|
59
149
|
|
|
60
|
-
|
|
150
|
+
Made with ❤️ by [SherKan](https://github.com/SherKan-n)
|
|
61
151
|
|
|
62
|
-
>
|
|
152
|
+
</div>
|
package/index.js
CHANGED
|
@@ -1,44 +1,44 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
const { Command } = require(
|
|
2
|
+
const { Command } = require("commander");
|
|
3
3
|
const program = new Command();
|
|
4
4
|
//---------------------------------------
|
|
5
5
|
program
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
6
|
+
.command("help")
|
|
7
|
+
.description("Show all available commands")
|
|
8
|
+
.action(() => {
|
|
9
|
+
console.log(`\nUsage: cli-tool <command> [options]\n`);
|
|
10
|
+
console.log(`Available commands:\n`);
|
|
11
|
+
console.log(` init Initialize migration`);
|
|
12
|
+
console.log(` run [dbName] Run migration`);
|
|
13
|
+
console.log(` rollback <dbName> <batch> Rollback migration`);
|
|
14
|
+
console.log(` create <name> <dbName> Create a new migration`);
|
|
15
|
+
console.log(` batch <dbName> Get the batched migrations`);
|
|
16
|
+
console.log(` help Show this help message\n`);
|
|
17
|
+
});
|
|
18
18
|
//---------------------------------------
|
|
19
19
|
program
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
20
|
+
.command("init")
|
|
21
|
+
.description("Initialize migration")
|
|
22
|
+
.action(() => require("./src/commands/init"));
|
|
23
23
|
//---------------------------------------
|
|
24
24
|
program
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
25
|
+
.command("run [dbName]")
|
|
26
|
+
.description("Run migration")
|
|
27
|
+
.action((dbName) => require("./src/commands/run")(dbName));
|
|
28
28
|
//---------------------------------------
|
|
29
29
|
program
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
30
|
+
.command("rollback <dbName> <batch>")
|
|
31
|
+
.description("Rollback migration")
|
|
32
|
+
.action((dbName, batch) => require("./src/commands/back")(dbName, batch));
|
|
33
33
|
//---------------------------------------
|
|
34
34
|
program
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
35
|
+
.command("create <migrationName> <dbName>")
|
|
36
|
+
.description("Create a new migration")
|
|
37
|
+
.action((migrationName, dbName) => require("./src/commands/create")(migrationName, dbName));
|
|
38
38
|
//---------------------------------------
|
|
39
39
|
program
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
40
|
+
.command("batch <dbName>")
|
|
41
|
+
.description("Get the batched migrations")
|
|
42
|
+
.action((dbName) => require("./src/commands/batch")(dbName));
|
|
43
43
|
//---------------------------------------
|
|
44
|
-
program.parse(process.argv);
|
|
44
|
+
program.parse(process.argv);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mysql-migration",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.4",
|
|
4
4
|
"description": "Migration for mysql database",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
|
@@ -16,13 +16,17 @@
|
|
|
16
16
|
"keywords": [
|
|
17
17
|
"mysql migration",
|
|
18
18
|
"migration",
|
|
19
|
-
"mysql"
|
|
19
|
+
"mysql",
|
|
20
|
+
"database",
|
|
21
|
+
"mysql2",
|
|
22
|
+
"database migration",
|
|
23
|
+
"mysql migration database",
|
|
24
|
+
"mysql migration tool"
|
|
20
25
|
],
|
|
21
26
|
"dependencies": {
|
|
22
|
-
"commander": "^
|
|
23
|
-
"
|
|
24
|
-
"
|
|
25
|
-
"mysql2": "^3.13.0"
|
|
27
|
+
"commander": "^14.0.1",
|
|
28
|
+
"dayjs": "^1.11.18",
|
|
29
|
+
"mysql2": "^3.15.3"
|
|
26
30
|
},
|
|
27
31
|
"author": "SherKan",
|
|
28
32
|
"license": "SEE LICENSE IN LICENSE.md"
|
package/src/commands/back.js
CHANGED
|
@@ -1,65 +1,90 @@
|
|
|
1
|
-
|
|
2
|
-
//==============================================================================
|
|
3
|
-
const fs = require(
|
|
4
|
-
const mysql = require(
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
const
|
|
8
|
-
const
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
console.
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
1
|
+
"use strict";
|
|
2
|
+
//==============================================================================
|
|
3
|
+
const fs = require("fs");
|
|
4
|
+
const mysql = require("mysql2");
|
|
5
|
+
const path = require("path");
|
|
6
|
+
//---------------------------------------
|
|
7
|
+
const currentPath = process.cwd();
|
|
8
|
+
const configPath = path.join(currentPath, "migrations", "mysql-migration.config.json");
|
|
9
|
+
if (!fs.existsSync(configPath)) {
|
|
10
|
+
console.error(
|
|
11
|
+
"\x1b[31m%s\x1b[0m",
|
|
12
|
+
'Error: Config file "mysql-migration.config.json" not found. Run "mysql-migration init" first.'
|
|
13
|
+
);
|
|
14
|
+
process.exit(1);
|
|
15
|
+
}
|
|
16
|
+
const config = require(configPath);
|
|
17
|
+
const {
|
|
18
|
+
checkTableMigrations,
|
|
19
|
+
createTableMigrations,
|
|
20
|
+
getAllMigrations,
|
|
21
|
+
getCurrentBatch,
|
|
22
|
+
deleteMigration,
|
|
23
|
+
} = require("../utils/functions");
|
|
24
|
+
//==============================================================================
|
|
25
|
+
async function back_migration(dbName, batch) {
|
|
26
|
+
const connection = {};
|
|
27
|
+
const databases = config.databases;
|
|
28
|
+
|
|
29
|
+
//---------------------------------------
|
|
30
|
+
if (dbName) {
|
|
31
|
+
const batchNumber = parseInt(batch);
|
|
32
|
+
if (isNaN(batchNumber)) {
|
|
33
|
+
console.error("\x1b[31m%s\x1b[0m", `Error: Invalid batch number.`);
|
|
34
|
+
process.exit(1);
|
|
35
|
+
}
|
|
36
|
+
//---------------------------------------
|
|
37
|
+
if (!databases[dbName]) {
|
|
38
|
+
console.error("\x1b[31m%s\x1b[0m", `Error: Invalid database name "${dbName}".`);
|
|
39
|
+
process.exit(1);
|
|
40
|
+
}
|
|
41
|
+
//---------------------------------------
|
|
42
|
+
connection[dbName] = mysql.createConnection(databases[dbName]);
|
|
43
|
+
try {
|
|
44
|
+
await connection[dbName].promise().connect();
|
|
45
|
+
} catch (err) {
|
|
46
|
+
console.error("\x1b[31m%s\x1b[0m", `Error: Unable to connect to database "${dbName}".\n${err}`);
|
|
47
|
+
process.exit(1);
|
|
48
|
+
}
|
|
49
|
+
//---------------------------------------
|
|
50
|
+
const tableMigrations = await checkTableMigrations(connection[dbName]);
|
|
51
|
+
if (!tableMigrations) await createTableMigrations(connection[dbName]);
|
|
52
|
+
//---------------------------------------
|
|
53
|
+
const currentBatch = await getCurrentBatch(connection[dbName]);
|
|
54
|
+
if (batchNumber >= currentBatch) {
|
|
55
|
+
console.error("\x1b[31m%s\x1b[0m", `Error: Invalid batch number, the current batch is "${currentBatch}".`);
|
|
56
|
+
await connection[dbName].promise().end();
|
|
57
|
+
process.exit(1);
|
|
58
|
+
}
|
|
59
|
+
const migrations = await getAllMigrations(connection[dbName], batchNumber);
|
|
60
|
+
if (migrations.length === 0) {
|
|
61
|
+
console.log("\x1b[32m%s\x1b[0m", `Nothing to rollback for batch greater than ${batchNumber}.`);
|
|
62
|
+
await connection[dbName].promise().end();
|
|
63
|
+
process.exit(0);
|
|
64
|
+
}
|
|
65
|
+
//---------------------------------------
|
|
66
|
+
for (let file of migrations) {
|
|
67
|
+
if (!fs.existsSync(`${currentPath}/migrations/${dbName}_db/${file.migration}.js`)) {
|
|
68
|
+
console.warn("\x1b[33m%s\x1b[0m", `Warning: Migration "${file.migration}" not found.`);
|
|
69
|
+
} else {
|
|
70
|
+
const migrationPath = `${currentPath}/migrations/${dbName}_db/${file.migration}`;
|
|
71
|
+
const migration = require(migrationPath);
|
|
72
|
+
try {
|
|
73
|
+
await migration.down(connection[dbName]);
|
|
74
|
+
await deleteMigration(connection[dbName], file.migration, batchNumber);
|
|
75
|
+
console.log(
|
|
76
|
+
"\x1b[32m%s\x1b[0m",
|
|
77
|
+
`Migration "${file.migration}" has been successfully rolled back.`
|
|
78
|
+
);
|
|
79
|
+
} catch (err) {
|
|
80
|
+
console.warn("\x1b[33m%s\x1b[0m", `Warning: "${err}" in migration "${file.migration}".`);
|
|
81
|
+
} finally {
|
|
82
|
+
delete require.cache[require.resolve(migrationPath)];
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
await connection[dbName].promise().end();
|
|
87
|
+
} else console.error("\x1b[31m%s\x1b[0m", `Error: Database name is empty !`);
|
|
88
|
+
}
|
|
89
|
+
//==============================================================================
|
|
90
|
+
module.exports = back_migration;
|
package/src/commands/batch.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
|
|
1
|
+
"use strict";
|
|
2
2
|
//==============================================================================
|
|
3
|
-
const mysql = require(
|
|
3
|
+
const mysql = require("mysql2");
|
|
4
4
|
//---------------------------------------
|
|
5
5
|
const currentPath = process.cwd();
|
|
6
6
|
const config = require(`${currentPath}/migrations/mysql-migration.config.json`);
|
|
@@ -12,14 +12,14 @@ async function show_batched_migrations(dbName) {
|
|
|
12
12
|
//---------------------------------------
|
|
13
13
|
if (dbName) {
|
|
14
14
|
if (!databases[dbName]) {
|
|
15
|
-
console.error(
|
|
15
|
+
console.error("\x1b[31m%s\x1b[0m", `Error: Invalid database name "${dbName}".`);
|
|
16
16
|
process.exit(1);
|
|
17
17
|
}
|
|
18
18
|
//---------------------------------------
|
|
19
19
|
connection[dbName] = mysql.createConnection(databases[dbName]);
|
|
20
20
|
connection[dbName].connect((err) => {
|
|
21
21
|
if (err) {
|
|
22
|
-
console.error(
|
|
22
|
+
console.error("\x1b[31m%s\x1b[0m", `Error: Unable to connect to database "${dbName}".\n${err}`);
|
|
23
23
|
process.exit(1);
|
|
24
24
|
}
|
|
25
25
|
});
|
|
@@ -29,24 +29,25 @@ async function show_batched_migrations(dbName) {
|
|
|
29
29
|
//---------------------------------------
|
|
30
30
|
const currentBatch = await getCurrentBatch(connection[dbName]);
|
|
31
31
|
//---------------------------------------
|
|
32
|
-
getAllBatches(connection[dbName])
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
32
|
+
getAllBatches(connection[dbName])
|
|
33
|
+
.then((migrations) => {
|
|
34
|
+
if (migrations.length > 0) {
|
|
35
|
+
console.log("\x1b[32m%s\x1b[0m", `Batched migrations for database "${dbName}":`);
|
|
36
|
+
migrations.forEach((migration) => {
|
|
37
|
+
const migrationName = migration.migration.split("_").slice(4).join("_");
|
|
38
|
+
console.log(`[Batch ${migration.batch}] - ${migrationName}`);
|
|
39
|
+
});
|
|
40
|
+
} else console.log("\x1b[32m%s\x1b[0m", `No batched migrations for database "${dbName}".`);
|
|
41
|
+
connection[dbName].end();
|
|
42
|
+
})
|
|
43
|
+
.catch((err) => {
|
|
44
|
+
console.error("\x1b[31m%s\x1b[0m", `Error: ${err}`);
|
|
45
|
+
connection[dbName].end();
|
|
46
|
+
})
|
|
47
|
+
.finally(() => {
|
|
48
|
+
console.log("\x1b[36m%s\x1b[0m", `Current batch for database "${dbName}": ${currentBatch}`);
|
|
49
|
+
});
|
|
50
|
+
} else console.error("\x1b[31m%s\x1b[0m", `Error: Database name is empty !`);
|
|
50
51
|
}
|
|
51
52
|
//==============================================================================
|
|
52
|
-
module.exports = show_batched_migrations;
|
|
53
|
+
module.exports = show_batched_migrations;
|
package/src/commands/create.js
CHANGED
|
@@ -1,31 +1,33 @@
|
|
|
1
|
-
|
|
1
|
+
"use strict";
|
|
2
2
|
//---------------------------------------
|
|
3
|
-
const fs = require(
|
|
4
|
-
const
|
|
3
|
+
const fs = require("fs");
|
|
4
|
+
const dayjs = require("dayjs");
|
|
5
5
|
//---------------------------------------
|
|
6
6
|
const currentPath = process.cwd();
|
|
7
7
|
const config = require(`${currentPath}/migrations/mysql-migration.config.json`);
|
|
8
8
|
//---------------------------------------
|
|
9
9
|
function create_migration(migrationName, dbName) {
|
|
10
10
|
if (!migrationName) {
|
|
11
|
-
console.error(
|
|
11
|
+
console.error("\x1b[31m%s\x1b[0m", `Error: Migration name is empty !`);
|
|
12
12
|
process.exit(1);
|
|
13
13
|
}
|
|
14
14
|
//---------------------------------------
|
|
15
15
|
const databases = Object.keys(config.databases);
|
|
16
16
|
//---------------------------------------
|
|
17
17
|
if (!databases.includes(dbName)) {
|
|
18
|
-
console.error(
|
|
18
|
+
console.error("\x1b[31m%s\x1b[0m", `Error: Invalid database name "${dbName}" can be: ${databases.join(", ")}.`);
|
|
19
19
|
process.exit(1);
|
|
20
20
|
}
|
|
21
21
|
//---------------------------------------
|
|
22
|
-
if (!fs.existsSync(`${currentPath}/migrations/${dbName}_db`))
|
|
22
|
+
if (!fs.existsSync(`${currentPath}/migrations/${dbName}_db`))
|
|
23
|
+
fs.mkdirSync(`${currentPath}/migrations/${dbName}_db`);
|
|
23
24
|
//---------------------------------------
|
|
24
|
-
const currentDate =
|
|
25
|
+
const currentDate = dayjs().format("YYYY_MM_DD_HHmmss");
|
|
25
26
|
const fileName = `${currentDate}_${migrationName}.js`;
|
|
26
27
|
const filePath = `${currentPath}/migrations/${dbName}_db/${fileName}`;
|
|
27
28
|
//---------------------------------------
|
|
28
|
-
if (fs.existsSync(filePath))
|
|
29
|
+
if (fs.existsSync(filePath))
|
|
30
|
+
console.warn("\x1b[33m%s\x1b[0m", `Warning: File "${fileName}" already exists in the "migrations" directory.`);
|
|
29
31
|
else {
|
|
30
32
|
const dataText = `module.exports = {
|
|
31
33
|
up: (connection) => {
|
|
@@ -51,10 +53,10 @@ function create_migration(migrationName, dbName) {
|
|
|
51
53
|
});
|
|
52
54
|
});
|
|
53
55
|
}
|
|
54
|
-
}
|
|
56
|
+
};`;
|
|
55
57
|
fs.writeFileSync(filePath, dataText);
|
|
56
|
-
console.log(
|
|
58
|
+
console.log("\x1b[32m%s\x1b[0m", `Created migration file: "${fileName}".`);
|
|
57
59
|
}
|
|
58
60
|
}
|
|
59
61
|
//---------------------------------------
|
|
60
|
-
module.exports = create_migration;
|
|
62
|
+
module.exports = create_migration;
|
package/src/commands/init.js
CHANGED
|
@@ -1,18 +1,29 @@
|
|
|
1
|
-
|
|
1
|
+
"use strict";
|
|
2
2
|
//---------------------------------------
|
|
3
|
-
const fs = require(
|
|
3
|
+
const fs = require("fs");
|
|
4
4
|
//---------------------------------------
|
|
5
5
|
const currentPath = process.cwd();
|
|
6
6
|
//---------------------------------------
|
|
7
7
|
if (!fs.existsSync(`${currentPath}/migrations`)) fs.mkdirSync(`${currentPath}/migrations`);
|
|
8
|
-
if (!fs.existsSync(`${currentPath}/migrations/mysql-migration.config.json`))
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
8
|
+
if (!fs.existsSync(`${currentPath}/migrations/mysql-migration.config.json`))
|
|
9
|
+
fs.writeFileSync(
|
|
10
|
+
`${currentPath}/migrations/mysql-migration.config.json`,
|
|
11
|
+
JSON.stringify(
|
|
12
|
+
{
|
|
13
|
+
databases: {
|
|
14
|
+
db_name: {
|
|
15
|
+
host: "db_host",
|
|
16
|
+
user: "db_user",
|
|
17
|
+
password: "db_password",
|
|
18
|
+
database: "db_name",
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
null,
|
|
23
|
+
3
|
|
24
|
+
)
|
|
25
|
+
);
|
|
26
|
+
console.log(
|
|
27
|
+
"\x1b[32m%s\x1b[0m",
|
|
28
|
+
`Created migration config file: "mysql-migration.config.json" in the "migrations" directory.`
|
|
29
|
+
);
|
package/src/commands/run.js
CHANGED
|
@@ -1,103 +1,134 @@
|
|
|
1
|
-
|
|
2
|
-
//==============================================================================
|
|
3
|
-
const fs = require(
|
|
4
|
-
const mysql = require(
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
const
|
|
8
|
-
const
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
console.
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
1
|
+
"use strict";
|
|
2
|
+
//==============================================================================
|
|
3
|
+
const fs = require("fs");
|
|
4
|
+
const mysql = require("mysql2");
|
|
5
|
+
const path = require("path");
|
|
6
|
+
//---------------------------------------
|
|
7
|
+
const currentPath = process.cwd();
|
|
8
|
+
const configPath = path.join(currentPath, "migrations", "mysql-migration.config.json");
|
|
9
|
+
if (!fs.existsSync(configPath)) {
|
|
10
|
+
console.error(
|
|
11
|
+
"\x1b[31m%s\x1b[0m",
|
|
12
|
+
'Error: Config file "mysql-migration.config.json" not found. Run "mysql-migration init" first.'
|
|
13
|
+
);
|
|
14
|
+
process.exit(1);
|
|
15
|
+
}
|
|
16
|
+
const config = require(configPath);
|
|
17
|
+
const {
|
|
18
|
+
checkTableMigrations,
|
|
19
|
+
createTableMigrations,
|
|
20
|
+
getAllMigrations,
|
|
21
|
+
getCurrentBatch,
|
|
22
|
+
insertMigration,
|
|
23
|
+
} = require("../utils/functions");
|
|
24
|
+
//==============================================================================
|
|
25
|
+
async function run_migration(dbName) {
|
|
26
|
+
const connection = {},
|
|
27
|
+
migrations = [];
|
|
28
|
+
const databases = config.databases;
|
|
29
|
+
//---------------------------------------
|
|
30
|
+
if (dbName) {
|
|
31
|
+
if (!databases[dbName]) {
|
|
32
|
+
console.error("\x1b[31m%s\x1b[0m", `Error: Invalid database name "${dbName}".`);
|
|
33
|
+
process.exit(1);
|
|
34
|
+
}
|
|
35
|
+
//---------------------------------------
|
|
36
|
+
connection[dbName] = mysql.createConnection(databases[dbName]);
|
|
37
|
+
try {
|
|
38
|
+
await connection[dbName].promise().connect();
|
|
39
|
+
} catch (err) {
|
|
40
|
+
console.error("\x1b[31m%s\x1b[0m", `Error: Unable to connect to database "${dbName}".\n${err}`);
|
|
41
|
+
process.exit(1);
|
|
42
|
+
}
|
|
43
|
+
//---------------------------------------
|
|
44
|
+
const tableMigrations = await checkTableMigrations(connection[dbName]);
|
|
45
|
+
if (!tableMigrations) await createTableMigrations(connection[dbName]);
|
|
46
|
+
//---------------------------------------
|
|
47
|
+
const batch = (await getCurrentBatch(connection[dbName])) + 1;
|
|
48
|
+
const allMigrations = await getAllMigrations(connection[dbName]);
|
|
49
|
+
const migrationsDir = `${currentPath}/migrations/${dbName}_db`;
|
|
50
|
+
if (!fs.existsSync(migrationsDir)) {
|
|
51
|
+
console.log("\x1b[32m%s\x1b[0m", "Nothing to migrate.\n");
|
|
52
|
+
await connection[dbName].promise().end();
|
|
53
|
+
process.exit(0);
|
|
54
|
+
}
|
|
55
|
+
const migrationFiles = fs.readdirSync(migrationsDir);
|
|
56
|
+
//---------------------------------------
|
|
57
|
+
const pendingMigrations = migrationFiles.filter(
|
|
58
|
+
(val) => !allMigrations.some((val2) => val.includes(val2.migration))
|
|
59
|
+
);
|
|
60
|
+
//---------------------------------------
|
|
61
|
+
if (pendingMigrations.length === 0) {
|
|
62
|
+
console.log("\x1b[32m%s\x1b[0m", "Nothing to migrate.\n");
|
|
63
|
+
await connection[dbName].promise().end();
|
|
64
|
+
process.exit(0);
|
|
65
|
+
}
|
|
66
|
+
//---------------------------------------
|
|
67
|
+
for (let file of pendingMigrations) {
|
|
68
|
+
const migrationPath = `${currentPath}/migrations/${dbName}_db/${file}`;
|
|
69
|
+
const migration = require(migrationPath);
|
|
70
|
+
try {
|
|
71
|
+
await migration.up(connection[dbName]);
|
|
72
|
+
await insertMigration(connection[dbName], file.replace(".js", ""), batch);
|
|
73
|
+
console.log("\x1b[36m%s\x1b[0m", `Migrated: "${file}" successfully.`);
|
|
74
|
+
} catch (err) {
|
|
75
|
+
console.warn("\x1b[33m%s\x1b[0m", `Warning: "${err}" in migration "${file}".`);
|
|
76
|
+
} finally {
|
|
77
|
+
delete require.cache[require.resolve(migrationPath)];
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
console.log("\x1b[32m%s\x1b[0m", "All migrations have been completed successfully.\n");
|
|
81
|
+
await connection[dbName].promise().end();
|
|
82
|
+
} else {
|
|
83
|
+
for (let key in databases) {
|
|
84
|
+
connection[key] = mysql.createConnection(databases[key]);
|
|
85
|
+
try {
|
|
86
|
+
await connection[key].promise().connect();
|
|
87
|
+
} catch (err) {
|
|
88
|
+
console.error("\x1b[31m%s\x1b[0m", `Error: Unable to connect to database "${key}".`);
|
|
89
|
+
process.exit(1);
|
|
90
|
+
}
|
|
91
|
+
//---------------------------------------
|
|
92
|
+
const tableMigrations = await checkTableMigrations(connection[key]);
|
|
93
|
+
if (!tableMigrations) await createTableMigrations(connection[key]);
|
|
94
|
+
//---------------------------------------
|
|
95
|
+
const batch = (await getCurrentBatch(connection[key])) + 1;
|
|
96
|
+
|
|
97
|
+
const allMigrations = await getAllMigrations(connection[key]);
|
|
98
|
+
//---------------------------------------
|
|
99
|
+
if (!fs.existsSync(`${currentPath}/migrations/${key}_db`)) continue;
|
|
100
|
+
const migration = fs.readdirSync(`${currentPath}/migrations/${key}_db`);
|
|
101
|
+
//---------------------------------------
|
|
102
|
+
const diffMigrations = migration.filter(
|
|
103
|
+
(val) => !allMigrations.some((val2) => val.includes(val2.migration))
|
|
104
|
+
);
|
|
105
|
+
//---------------------------------------
|
|
106
|
+
for (let m of diffMigrations) migrations.push([m, key, batch]);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
//---------------------------------------
|
|
110
|
+
if (migrations.length === 0) {
|
|
111
|
+
console.log("\x1b[32m%s\x1b[0m", "Nothing to migrate.\n");
|
|
112
|
+
for (let key in connection) await connection[key].promise().end();
|
|
113
|
+
process.exit(0);
|
|
114
|
+
}
|
|
115
|
+
//---------------------------------------
|
|
116
|
+
for (let [file, key, batch] of migrations) {
|
|
117
|
+
const migrationPath = `${currentPath}/migrations/${key}_db/${file}`;
|
|
118
|
+
const migration = require(migrationPath);
|
|
119
|
+
try {
|
|
120
|
+
await migration.up(connection[key]);
|
|
121
|
+
await insertMigration(connection[key], file.replace(".js", ""), batch);
|
|
122
|
+
console.log("\x1b[36m%s\x1b[0m", `Migrated: "${file}" in database "${key}" successfully.`);
|
|
123
|
+
} catch (err) {
|
|
124
|
+
console.warn("\x1b[33m%s\x1b[0m", `Warning: "${err}" in migration "${file}" in database "${key}".`);
|
|
125
|
+
} finally {
|
|
126
|
+
delete require.cache[require.resolve(migrationPath)];
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
console.log("\x1b[32m%s\x1b[0m", "All migrations have been completed successfully.\n");
|
|
130
|
+
for (let key in connection) await connection[key].promise().end();
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
//==============================================================================
|
|
134
|
+
module.exports = run_migration;
|
package/src/utils/functions.js
CHANGED
|
@@ -1,79 +1,66 @@
|
|
|
1
|
-
function checkTableMigrations(connection) {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
}
|
|
46
|
-
//---------------------------------------
|
|
47
|
-
function
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
function getAllBatches(connection) {
|
|
68
|
-
return new Promise((resolve) => {
|
|
69
|
-
connection.query("SELECT `batch`, `migration` FROM `migrations`;", (error, results) => {
|
|
70
|
-
if (error) throw error;
|
|
71
|
-
resolve(results?.length > 0 ? results : []);
|
|
72
|
-
});
|
|
73
|
-
});
|
|
74
|
-
}
|
|
75
|
-
//---------------------------------------
|
|
76
|
-
module.exports = {
|
|
77
|
-
checkTableMigrations, createTableMigrations, getAllMigrations, getCurrentBatch, insertMigration, deleteMigration,
|
|
78
|
-
getAllBatches
|
|
79
|
-
}
|
|
1
|
+
async function checkTableMigrations(connection) {
|
|
2
|
+
try {
|
|
3
|
+
await connection.promise().query("SELECT `id` FROM `migrations` LIMIT 1;");
|
|
4
|
+
return true;
|
|
5
|
+
} catch (error) {
|
|
6
|
+
if (error && error.code === "ER_NO_SUCH_TABLE") return false;
|
|
7
|
+
throw error;
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
//---------------------------------------
|
|
11
|
+
async function createTableMigrations(connection) {
|
|
12
|
+
const query =
|
|
13
|
+
"CREATE TABLE `migrations` (\
|
|
14
|
+
`id` INT NOT NULL AUTO_INCREMENT,\
|
|
15
|
+
`migration` VARCHAR(255) NOT NULL,\
|
|
16
|
+
`batch` INT NOT NULL DEFAULT 1,\
|
|
17
|
+
`created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,\
|
|
18
|
+
PRIMARY KEY (`id`));";
|
|
19
|
+
await connection.promise().query(query);
|
|
20
|
+
return true;
|
|
21
|
+
}
|
|
22
|
+
//---------------------------------------
|
|
23
|
+
async function getAllMigrations(connection, batch = null) {
|
|
24
|
+
let query = "SELECT `migration` FROM `migrations`";
|
|
25
|
+
const params = [];
|
|
26
|
+
if (batch !== null && batch !== undefined) {
|
|
27
|
+
query += " WHERE `batch` > ?";
|
|
28
|
+
params.push(batch);
|
|
29
|
+
}
|
|
30
|
+
const [results] = await connection.promise().query(`${query};`, params);
|
|
31
|
+
return Array.isArray(results) ? results : [];
|
|
32
|
+
}
|
|
33
|
+
//---------------------------------------
|
|
34
|
+
async function getCurrentBatch(connection) {
|
|
35
|
+
const [results] = await connection
|
|
36
|
+
.promise()
|
|
37
|
+
.query("SELECT `batch` FROM `migrations` ORDER BY `batch` DESC LIMIT 1;");
|
|
38
|
+
return Array.isArray(results) && results.length > 0 ? results[0].batch : 0;
|
|
39
|
+
}
|
|
40
|
+
//---------------------------------------
|
|
41
|
+
async function insertMigration(connection, migration, batch) {
|
|
42
|
+
const query = "INSERT INTO `migrations` (`migration`, `batch`) VALUES (?, ?);";
|
|
43
|
+
await connection.promise().query(query, [migration, batch]);
|
|
44
|
+
return true;
|
|
45
|
+
}
|
|
46
|
+
//---------------------------------------
|
|
47
|
+
async function deleteMigration(connection, migration, batch) {
|
|
48
|
+
const query = "DELETE FROM `migrations` WHERE `migration` = ? AND `batch` > ?;";
|
|
49
|
+
await connection.promise().query(query, [migration, batch]);
|
|
50
|
+
return true;
|
|
51
|
+
}
|
|
52
|
+
//---------------------------------------
|
|
53
|
+
async function getAllBatches(connection) {
|
|
54
|
+
const [results] = await connection.promise().query("SELECT `batch`, `migration` FROM `migrations`;");
|
|
55
|
+
return Array.isArray(results) ? results : [];
|
|
56
|
+
}
|
|
57
|
+
//---------------------------------------
|
|
58
|
+
module.exports = {
|
|
59
|
+
checkTableMigrations,
|
|
60
|
+
createTableMigrations,
|
|
61
|
+
getAllMigrations,
|
|
62
|
+
getCurrentBatch,
|
|
63
|
+
insertMigration,
|
|
64
|
+
deleteMigration,
|
|
65
|
+
getAllBatches,
|
|
66
|
+
};
|