knex-migrator 4.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/CHANGELOG.md +171 -0
- package/LICENSE +19 -0
- package/README.md +347 -0
- package/bin/knex-migrator +13 -0
- package/bin/knex-migrator-health +35 -0
- package/bin/knex-migrator-init +34 -0
- package/bin/knex-migrator-migrate +38 -0
- package/bin/knex-migrator-reset +31 -0
- package/bin/knex-migrator-rollback +37 -0
- package/lib/database.js +204 -0
- package/lib/errors.js +84 -0
- package/lib/index.js +1243 -0
- package/lib/locking.js +113 -0
- package/lib/utils.js +213 -0
- package/logging.js +8 -0
- package/migrations/add-primary-key-to-lock-table.js +58 -0
- package/migrations/field-length.js +22 -0
- package/migrations/index.js +17 -0
- package/migrations/lock-table.js +41 -0
- package/migrations/use-index.js +24 -0
- package/package.json +80 -0
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const program = require('commander');
|
|
4
|
+
const utils = require('../lib/utils');
|
|
5
|
+
|
|
6
|
+
const logging = require('../logging');
|
|
7
|
+
let knexMigrator;
|
|
8
|
+
|
|
9
|
+
utils.getKnexMigrator({path: process.cwd()})
|
|
10
|
+
.then(function (KnexMigrator) {
|
|
11
|
+
program
|
|
12
|
+
.option('--mgpath <path>')
|
|
13
|
+
.option('--force')
|
|
14
|
+
.option('--v <version>', 'The version to rollback to.')
|
|
15
|
+
.parse(process.argv);
|
|
16
|
+
|
|
17
|
+
try {
|
|
18
|
+
knexMigrator = new KnexMigrator({knexMigratorFilePath: program.mgpath, executedFromShell: true});
|
|
19
|
+
} catch (err) {
|
|
20
|
+
logging.error(err);
|
|
21
|
+
process.exit(1);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return knexMigrator.rollback({force: program.force, version: program.v})
|
|
25
|
+
.then(function () {
|
|
26
|
+
logging.info('Rollback was successful.');
|
|
27
|
+
});
|
|
28
|
+
})
|
|
29
|
+
.catch(function (err) {
|
|
30
|
+
logging.error(err.message);
|
|
31
|
+
|
|
32
|
+
if (err.help) {
|
|
33
|
+
logging.info(err.help);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
process.exit(1);
|
|
37
|
+
});
|
package/lib/database.js
ADDED
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
const knex = require('knex'),
|
|
2
|
+
Promise = require('bluebird'),
|
|
3
|
+
omit = require('lodash/omit'),
|
|
4
|
+
debug = require('debug')('knex-migrator:database'),
|
|
5
|
+
errors = require('./errors');
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @NOTE: Knex-migrator only supports knex query builder.
|
|
9
|
+
*
|
|
10
|
+
* @param options
|
|
11
|
+
* @returns {Knex.QueryBuilder | Knex}
|
|
12
|
+
*/
|
|
13
|
+
exports.connect = function connect(options) {
|
|
14
|
+
options = options || {};
|
|
15
|
+
const client = options.client;
|
|
16
|
+
|
|
17
|
+
if (client === 'sqlite3') {
|
|
18
|
+
options.useNullAsDefault = options.useNullAsDefault || false;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if (client === 'mysql') {
|
|
22
|
+
options.connection.timezone = options.connection.timezone || 'UTC';
|
|
23
|
+
options.connection.charset = options.connection.charset || 'utf8mb4';
|
|
24
|
+
options.connection.collation = options.connection.collation || 'utf8mb4_general_ci';
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return knex(options);
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* If you instantiate knex, you won't know if the connection works.
|
|
32
|
+
* This helper functions is used to test the connection. It's basically a "test query".
|
|
33
|
+
*
|
|
34
|
+
* @param connection
|
|
35
|
+
* @returns {Bluebird<R> | Bluebird<any> | Promise<T>}
|
|
36
|
+
*/
|
|
37
|
+
exports.ensureConnectionWorks = (connection) => {
|
|
38
|
+
return connection.raw('SELECT 1+1 as RESULT;')
|
|
39
|
+
.catch((err) => {
|
|
40
|
+
if (err.code === 'ENOTFOUND' || err.code === 'ETIMEDOUT' || err.code === 'EAI_AGAIN') {
|
|
41
|
+
throw new errors.DatabaseError({
|
|
42
|
+
message: 'Invalid database host.',
|
|
43
|
+
help: 'Please double check your database config.',
|
|
44
|
+
err: err
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
throw new errors.DatabaseError({
|
|
49
|
+
message: err.message,
|
|
50
|
+
help: 'Unknown database error',
|
|
51
|
+
err: err
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* @description Helper to create a transaction.
|
|
58
|
+
* @param callback
|
|
59
|
+
* @returns {*}
|
|
60
|
+
*/
|
|
61
|
+
module.exports.createTransaction = function (connection, callback) {
|
|
62
|
+
return connection.transaction(callback);
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* @description Helper to create the migration table.
|
|
67
|
+
*
|
|
68
|
+
* @TODO: https://github.com/TryGhost/knex-migrator/issues/118
|
|
69
|
+
* @TODO: https://github.com/TryGhost/knex-migrator/issues/91
|
|
70
|
+
* @returns {Bluebird<R> | Bluebird<any> | * | Promise<T>}
|
|
71
|
+
*/
|
|
72
|
+
exports.createMigrationsTable = function createMigrationsTable(connection) {
|
|
73
|
+
return connection('migrations')
|
|
74
|
+
.catch(function (err) {
|
|
75
|
+
// CASE: table does not exist
|
|
76
|
+
if (err.errno === 1 || err.errno === 1146) {
|
|
77
|
+
debug('Creating table: migrations');
|
|
78
|
+
|
|
79
|
+
return connection.schema.createTable('migrations', function (table) {
|
|
80
|
+
table.increments().primary();
|
|
81
|
+
table.string('name');
|
|
82
|
+
table.string('version');
|
|
83
|
+
table.string('currentVersion');
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
throw err;
|
|
88
|
+
});
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Knex-migrator has an inbuilt feature to create a database if it does not exist yet.
|
|
93
|
+
*
|
|
94
|
+
* @param dbConfig
|
|
95
|
+
* @returns {*}
|
|
96
|
+
*/
|
|
97
|
+
exports.createDatabaseIfNotExist = function createDatabaseIfNotExist(dbConfig) {
|
|
98
|
+
const name = dbConfig.connection.database,
|
|
99
|
+
charset = dbConfig.connection.charset || 'utf8mb4',
|
|
100
|
+
collation = dbConfig.connection.collation || 'utf8mb4_general_ci';
|
|
101
|
+
|
|
102
|
+
// @NOTE: Skip, because sqlite3 is a file based database.
|
|
103
|
+
if (dbConfig.client === 'sqlite3') {
|
|
104
|
+
return Promise.resolve();
|
|
105
|
+
} else if (dbConfig.client !== 'mysql') {
|
|
106
|
+
return Promise.reject(new errors.KnexMigrateError({
|
|
107
|
+
message: 'Database is not supported.'
|
|
108
|
+
}));
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const connection = exports.connect({
|
|
112
|
+
client: dbConfig.client,
|
|
113
|
+
connection: omit(dbConfig.connection, ['database'])
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
debug('Create database', name);
|
|
117
|
+
|
|
118
|
+
return exports.ensureConnectionWorks(connection)
|
|
119
|
+
.then(function () {
|
|
120
|
+
return connection.raw('CREATE DATABASE `' + name + '` CHARACTER SET ' + charset + ' COLLATE ' + collation + ';');
|
|
121
|
+
})
|
|
122
|
+
.catch(function (err) {
|
|
123
|
+
// CASE: DB exists
|
|
124
|
+
if (err.errno === 1007) {
|
|
125
|
+
return Promise.resolve();
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
throw new errors.DatabaseError({
|
|
129
|
+
message: err.message,
|
|
130
|
+
err: err,
|
|
131
|
+
code: 'DATABASE_CREATION_FAILED'
|
|
132
|
+
});
|
|
133
|
+
})
|
|
134
|
+
.finally(function () {
|
|
135
|
+
return new Promise(function (resolve, reject) {
|
|
136
|
+
connection.destroy(function (err) {
|
|
137
|
+
if (err) {
|
|
138
|
+
return reject(err);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
debug('Destroy connection');
|
|
142
|
+
resolve();
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Drops a database. Is called when you call `knex-migrator reset`.
|
|
150
|
+
*
|
|
151
|
+
* @param options
|
|
152
|
+
* @returns {*}
|
|
153
|
+
*/
|
|
154
|
+
exports.drop = function drop(options) {
|
|
155
|
+
options = options || {};
|
|
156
|
+
|
|
157
|
+
const connection = options.connection,
|
|
158
|
+
dbConfig = options.dbConfig;
|
|
159
|
+
|
|
160
|
+
if (dbConfig.client === 'mysql') {
|
|
161
|
+
debug('Drop database: ' + dbConfig.connection.database);
|
|
162
|
+
|
|
163
|
+
return connection.raw('DROP DATABASE `' + dbConfig.connection.database + '`;')
|
|
164
|
+
.catch(function (err) {
|
|
165
|
+
// CASE: database does not exist, skip
|
|
166
|
+
if (err.errno === 1049) {
|
|
167
|
+
return Promise.resolve();
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return Promise.reject(new errors.KnexMigrateError({
|
|
171
|
+
err: err
|
|
172
|
+
}));
|
|
173
|
+
});
|
|
174
|
+
} else if (dbConfig.client === 'sqlite3') {
|
|
175
|
+
// @NOTE: sqlite3 does not support "DROP DATABASE". We have to drop each table instead.
|
|
176
|
+
// @NOTE: We cannot just remove the sqlite3 file, because any database connection will get invalid.
|
|
177
|
+
return connection.raw('SELECT name FROM sqlite_master WHERE type="table";')
|
|
178
|
+
.then(function (tables) {
|
|
179
|
+
return Promise.each(tables, function (table) {
|
|
180
|
+
if (table.name === 'sqlite_sequence') {
|
|
181
|
+
debug('Skip drop table: ' + table.name);
|
|
182
|
+
return Promise.resolve();
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
debug('Drop table: ' + table.name);
|
|
186
|
+
return connection.schema.dropTableIfExists(table.name);
|
|
187
|
+
});
|
|
188
|
+
})
|
|
189
|
+
.catch(function (err) {
|
|
190
|
+
// CASE: database file was never initialised
|
|
191
|
+
if (err.errno === 10) {
|
|
192
|
+
return Promise.resolve();
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
return Promise.reject(new errors.KnexMigrateError({
|
|
196
|
+
err: err
|
|
197
|
+
}));
|
|
198
|
+
});
|
|
199
|
+
} else {
|
|
200
|
+
return Promise.reject(new errors.KnexMigrateError({
|
|
201
|
+
message: 'Database client not supported: ' + dbConfig.client
|
|
202
|
+
}));
|
|
203
|
+
}
|
|
204
|
+
};
|
package/lib/errors.js
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
const util = require('util');
|
|
2
|
+
const each = require('lodash/each');
|
|
3
|
+
const errors = require('ghost-ignition').errors;
|
|
4
|
+
|
|
5
|
+
function KnexMigrateError(options) {
|
|
6
|
+
options = options || {};
|
|
7
|
+
errors.IgnitionError.call(this, options);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const knexMigratorErrors = {
|
|
11
|
+
MigrationExistsError: function MigrationExistsError(options) {
|
|
12
|
+
KnexMigrateError.call(this, Object.assign({
|
|
13
|
+
id: 100,
|
|
14
|
+
errorType: 'MigrationExistsError'
|
|
15
|
+
}, options));
|
|
16
|
+
},
|
|
17
|
+
DatabaseIsNotOkError: function DatabaseIsNotOkError(options) {
|
|
18
|
+
KnexMigrateError.call(this, Object.assign({
|
|
19
|
+
id: 200,
|
|
20
|
+
errorType: 'DatabaseIsNotOkError',
|
|
21
|
+
help: 'If knex-migrator is not installed, please run "npm install -g knex-migrator" \nRead more here: https://github.com/TryGhost/knex-migrator'
|
|
22
|
+
}, options));
|
|
23
|
+
},
|
|
24
|
+
MigrationScriptError: function MigrationScriptError(options) {
|
|
25
|
+
KnexMigrateError.call(this, Object.assign({
|
|
26
|
+
id: 300,
|
|
27
|
+
errorType: 'MigrationScriptError'
|
|
28
|
+
}, options));
|
|
29
|
+
},
|
|
30
|
+
RollbackError: function RollbackError(options) {
|
|
31
|
+
KnexMigrateError.call(this, Object.assign({
|
|
32
|
+
id: 400,
|
|
33
|
+
errorType: 'RollbackError'
|
|
34
|
+
}, options));
|
|
35
|
+
},
|
|
36
|
+
MigrationsAreLockedError: function MigrationsAreLockedError(options) {
|
|
37
|
+
KnexMigrateError.call(this, Object.assign({
|
|
38
|
+
id: 500,
|
|
39
|
+
errorType: 'MigrationsAreLockedError'
|
|
40
|
+
}, options));
|
|
41
|
+
},
|
|
42
|
+
LockError: function LockError(options) {
|
|
43
|
+
KnexMigrateError.call(this, Object.assign({
|
|
44
|
+
id: 500,
|
|
45
|
+
errorType: 'LockError'
|
|
46
|
+
}, options));
|
|
47
|
+
},
|
|
48
|
+
UnlockError: function UnlockError(options) {
|
|
49
|
+
KnexMigrateError.call(this, Object.assign({
|
|
50
|
+
id: 500,
|
|
51
|
+
errorType: 'UnlockError'
|
|
52
|
+
}, options));
|
|
53
|
+
},
|
|
54
|
+
DatabaseError: function DatabaseError(options) {
|
|
55
|
+
KnexMigrateError.call(this, Object.assign({
|
|
56
|
+
id: 500,
|
|
57
|
+
errorType: 'DatabaseError'
|
|
58
|
+
}, options));
|
|
59
|
+
},
|
|
60
|
+
IrreversibleMigrationError: function IrreversibleMigrationError(options) {
|
|
61
|
+
KnexMigrateError.call(this, Object.assign({
|
|
62
|
+
id: 500,
|
|
63
|
+
errorType: 'IrreversibleMigrationError'
|
|
64
|
+
}, options));
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
util.inherits(KnexMigrateError, errors.IgnitionError);
|
|
69
|
+
|
|
70
|
+
each(knexMigratorErrors, function (error) {
|
|
71
|
+
util.inherits(error, KnexMigrateError);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
// we need to inherit all general errors from KnexMigrateError, otherwise we have to check instanceof IgnitionError
|
|
75
|
+
each(errors, function (error) {
|
|
76
|
+
if (error.name === 'IgnitionError' || typeof error === 'object') {
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
util.inherits(error, KnexMigrateError);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
module.exports = Object.assign(knexMigratorErrors, errors);
|
|
84
|
+
module.exports.KnexMigrateError = KnexMigrateError;
|