knex-migrator 4.0.3 → 4.1.1

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.
Files changed (4) hide show
  1. package/README.md +12 -12
  2. package/lib/index.js +170 -194
  3. package/lib/utils.js +24 -11
  4. package/package.json +5 -5
package/README.md CHANGED
@@ -6,14 +6,14 @@ A database migration tool for [knex.js](https://github.com/tgriesser/knex), whic
6
6
 
7
7
  - [x] JS API
8
8
  - [x] CLI Tool
9
- - [x] Differentiation between database initialisation and migration (Support for a database schema, [like we use in Ghost](https://github.com/TryGhost/Ghost/blob/1.16.2/core/server/data/schema/schema.js))
9
+ - [x] Differentiation between database initialization and migration (Support for a database schema, [like we use in Ghost](https://github.com/TryGhost/Ghost/blob/1.16.2/core/server/data/schema/schema.js))
10
10
  - [x] Support for database creation
11
11
  - [x] Hooks
12
12
  - [x] Rollback to latest version
13
13
  - [x] Auto-Rollback on error
14
14
  - [x] Database health check
15
15
  - [x] Supports transactions
16
- - [x] Full atomic, support for separate DML/DDL scripts (no autocommit)
16
+ - [x] Full atomic, support for separate DML/DDL scripts (no autocommit)
17
17
  - [x] Migration lock
18
18
  - [x] Full debug & pretty log support
19
19
  - [x] Custom migration folder structure
@@ -36,8 +36,8 @@ Add me to your globals:
36
36
 
37
37
  - Replicas are unsupported, because Knex.js [doesn't support them](https://github.com/tgriesser/knex/issues/2253).
38
38
  - Sqlite does **not** support read locks by default. Read [here](https://github.com/TryGhost/knex-migrator/issues/87) why.
39
- - [Comparision](https://github.com/TryGhost/knex-migrator/issues/119) with other available migration tools.
40
- - Don't mix DDL/DML statements in a migration script. In MySQL DDL statements use implicit commits.
39
+ - [Comparison](https://github.com/TryGhost/knex-migrator/issues/119) with other available migration tools.
40
+ - Don't mix DDL/DML statements in a migration script. In MySQL DDL statements use implicit commits.
41
41
  - It's highly recommended to write both the `up` and the `down` function to ensure a full rollback.
42
42
  - If your process dies while migrations are running, knex-migrator won't be able to release the migration lock.
43
43
  To release to lock you can run `knex-migrator rollback`. **But** it's recommended to check your database first to see in which state it is.
@@ -73,7 +73,7 @@ Please take a look at [this real example](https://github.com/TryGhost/Ghost/blob
73
73
 
74
74
  ```
75
75
  project/
76
- migrations/
76
+ migrations/
77
77
  hooks/
78
78
  init/
79
79
  index.js
@@ -88,9 +88,9 @@ project/
88
88
  versions/
89
89
  1.0/
90
90
  1-add-events-table.js
91
- 2-normalise-settings.js
91
+ 2-normalise-settings.js
92
92
  2.0/
93
- 1-add-timestamps-columns.js
93
+ 1-add-timestamps-columns.js
94
94
  2.1/
95
95
  1-remove-empty-strings.js
96
96
  2-add-webhooks-table.js
@@ -199,7 +199,7 @@ Commands:
199
199
 
200
200
  #### knex-migrator init
201
201
 
202
- - Initialises your database based on your init scripts
202
+ - Initializes your database based on your init scripts
203
203
  - Creates the database if it was not created yet
204
204
 
205
205
  ##### Options
@@ -223,10 +223,10 @@ Commands:
223
223
  ##### Options
224
224
 
225
225
  ```bash
226
- # The bersion you would like to migrate to
226
+ # The version you would like to migrate to
227
227
  --v
228
228
 
229
- # Combo Feature to check whether the database was already initialised
229
+ # Combo Feature to check whether the database was already initialized
230
230
  --init
231
231
 
232
232
  # Force the execution no matter which current version you are on
@@ -312,13 +312,13 @@ knexMigrator.reset
312
312
  knexMigrator.isDatabaseOK()
313
313
  .then(function() {
314
314
  // database is OK
315
- // initialisation & migrations are not missing
315
+ // initialization & migrations are not missing
316
316
  })
317
317
  .catch(function(err) {
318
318
  if (err.code === 'DB_NOT_INITIALISED') {
319
319
  return knexMigrator.init();
320
320
  }
321
-
321
+
322
322
  if (err.code === 'DB_NEEDS_MIGRATION') {
323
323
  return knexMigrator.migrate();
324
324
  }
package/lib/index.js CHANGED
@@ -119,7 +119,7 @@ KnexMigrator.prototype.init = function init(options) {
119
119
  }
120
120
  })
121
121
  .then(function () {
122
- const initTasks = utils.readTasks(path.join(self.migrationPath, 'init'));
122
+ const initTasks = utils.listFiles(path.join(self.migrationPath, 'init'));
123
123
 
124
124
  /**
125
125
  * CASE 1: You can disable init completion manually
@@ -159,12 +159,12 @@ KnexMigrator.prototype.init = function init(options) {
159
159
  // CASE: Run over all migration scripts and add the file name to the database.
160
160
  return Promise.each(versionsToMigrateTo, function (versionToMigrateTo) {
161
161
  let versionPath = path.join(self.migrationPath, self.subfolder, versionToMigrateTo);
162
- let filesToMigrateTo = utils.readTasks(versionPath) || [];
162
+ let filesToMigrateTo = utils.listFiles(versionPath) || [];
163
163
 
164
164
  return Promise.each(filesToMigrateTo, function (fileToMigrateTo) {
165
165
  // CASE: check if migration exists, do not insert twice
166
166
  return transacting('migrations')
167
- .where('name', fileToMigrateTo.name)
167
+ .where('name', fileToMigrateTo)
168
168
  .then(function (migrationExists) {
169
169
  if (migrationExists.length) {
170
170
  return Promise.resolve();
@@ -172,7 +172,7 @@ KnexMigrator.prototype.init = function init(options) {
172
172
 
173
173
  return transacting('migrations')
174
174
  .insert({
175
- name: fileToMigrateTo.name,
175
+ name: fileToMigrateTo,
176
176
  version: versionToMigrateTo,
177
177
  currentVersion: self.currentVersion
178
178
  });
@@ -286,11 +286,10 @@ KnexMigrator.prototype.init = function init(options) {
286
286
  * @param {Object} options - Custom options you can pass in (version, force, init, only, skip)
287
287
  * @returns {Bluebird<any>}
288
288
  */
289
- KnexMigrator.prototype.migrate = function migrate(options) {
289
+ KnexMigrator.prototype.migrate = async function migrate(options) {
290
290
  options = options || {};
291
291
 
292
- let self = this,
293
- onlyVersion = options.version,
292
+ let onlyVersion = options.version,
294
293
  force = options.force,
295
294
  init = options.init,
296
295
  onlyFile = options.only,
@@ -308,14 +307,11 @@ KnexMigrator.prototype.migrate = function migrate(options) {
308
307
 
309
308
  // CASE: `--init` flag is passed. Combo feature.
310
309
  if (init) {
311
- return this.init()
312
- .then(function () {
313
- return self.migrate(_.omit(options, 'init'));
314
- });
310
+ await this.init();
315
311
  }
316
312
 
317
313
  try {
318
- hooks = require(path.join(self.migrationPath, '/hooks/migrate'));
314
+ hooks = require(path.join(this.migrationPath, '/hooks/migrate'));
319
315
  } catch (err) {
320
316
  debug('Hook Error: ' + err.message);
321
317
  debug('No hooks found, no problem.');
@@ -323,152 +319,130 @@ KnexMigrator.prototype.migrate = function migrate(options) {
323
319
 
324
320
  this.connection = database.connect(this.dbConfig);
325
321
 
326
- return database.ensureConnectionWorks(this.connection)
327
- .then(function () {
328
- return migrations.run(self.connection);
329
- })
330
- .then(function () {
331
- return locking.lock(self.connection);
332
- })
333
- .then(function () {
334
- return self._integrityCheck({
335
- force: force
336
- });
337
- })
338
- .then(function (result) {
339
- _.each(result, function (value, version) {
340
- // CASE: Log which versions won't be executed based on the "only" flag
341
- if (onlyVersion && version !== onlyVersion) {
342
- debug('Do not execute: ' + version);
343
- return;
344
- }
345
- });
322
+ try {
323
+ await database.ensureConnectionWorks(this.connection);
346
324
 
347
- if (onlyVersion) {
348
- // CASE: filter out versions which should not run
349
- let containsVersion = _.find(result, function (obj, key) {
350
- return key === onlyVersion;
351
- });
325
+ await migrations.run(this.connection);
352
326
 
353
- if (!containsVersion) {
354
- logging.warn('Cannot find requested version: ' + onlyVersion);
355
- }
356
- }
327
+ await locking.lock(this.connection);
357
328
 
358
- _.each(result, function (value, version) {
359
- // CASE: compare files on disk with files in database
360
- if (value.expected !== value.actual) {
361
- debug('Need to execute migrations for: ' + version);
362
- versionsToMigrate.push(version);
363
- }
364
- });
365
- })
366
- .then(function executeBeforeHook() {
367
- if (!versionsToMigrate.length) {
368
- return;
369
- }
329
+ const result = await this._integrityCheck({force});
370
330
 
371
- if (hooks.before) {
372
- debug('Before hook');
373
- return hooks.before({
374
- connection: self.connection
375
- });
376
- }
377
- })
378
- .then(function executeMigrations() {
379
- if (!versionsToMigrate.length) {
331
+ _.each(result, function (_value, version) {
332
+ // CASE: Log which versions won't be executed based on the "only" flag
333
+ if (onlyVersion && version !== onlyVersion) {
334
+ debug('Do not execute: ' + version);
380
335
  return;
381
336
  }
337
+ });
382
338
 
383
- return Promise.each(versionsToMigrate, function (versionToMigrate) {
384
- return self._migrateTo({
385
- version: versionToMigrate,
386
- only: onlyFile,
387
- hooks: hooks
388
- });
339
+ if (onlyVersion) {
340
+ // CASE: filter out versions which should not run
341
+ let containsVersion = _.find(result, function (_obj, key) {
342
+ return key === onlyVersion;
389
343
  });
390
- })
391
- .then(function executeAfterHook() {
392
- if (!versionsToMigrate.length) {
393
- return;
394
- }
395
344
 
396
- if (hooks.after) {
397
- debug('After hook');
398
- return hooks.after({
399
- connection: self.connection
400
- });
401
- }
402
- })
403
- .then(function () {
404
- return locking.unlock(self.connection);
405
- })
406
- .catch(function (err) {
407
- // CASE: Do not rollback if migrations are locked
408
- if (err instanceof errors.MigrationsAreLockedError) {
409
- throw err;
345
+ if (!containsVersion) {
346
+ logging.warn('Cannot find requested version: ' + onlyVersion);
410
347
  }
348
+ }
411
349
 
412
- // CASE: Do not rollback migration scripts, if lock error
413
- if (err instanceof errors.LockError) {
414
- throw err;
350
+ _.each(result, function (value, version) {
351
+ // CASE: compare files on disk with files in database
352
+ if (value.expected !== value.actual) {
353
+ debug('Need to execute migrations for: ' + version);
354
+ versionsToMigrate.push(version);
415
355
  }
356
+ });
416
357
 
417
- // CASE: ETIMEDOUT, ENOTFOUND
418
- if (err instanceof errors.DatabaseError) {
419
- throw err;
358
+ if (versionsToMigrate.length) {
359
+ if (hooks.before) {
360
+ debug('Before hook');
361
+ await hooks.before({
362
+ connection: this.connection
363
+ });
420
364
  }
421
365
 
422
- if (err.context && err.context.name) {
423
- debug(`Task failed: ${err.context.name}`);
424
- }
366
+ logging.info('Running migrations.');
425
367
 
426
- debug(`Rolling back: ${err.message}`);
368
+ for (const versionToMigrate of versionsToMigrate) {
369
+ try {
370
+ await this._migrateTo({
371
+ version: versionToMigrate,
372
+ only: onlyFile,
373
+ hooks: hooks
374
+ });
375
+ } catch (err) {
376
+ // CASE: Do not rollback if migrations are locked
377
+ if (err instanceof errors.MigrationsAreLockedError) {
378
+ throw err;
379
+ }
427
380
 
428
- versionsToMigrate.reverse();
381
+ // CASE: Do not rollback migration scripts, if lock error
382
+ if (err instanceof errors.LockError) {
383
+ throw err;
384
+ }
429
385
 
430
- // CASE: rollback in reversed order
431
- return Promise.each(versionsToMigrate, function (version) {
432
- return self._rollback({version: version, task: err.context});
433
- }).then(function () {
434
- throw err;
435
- }).catch(function (innerErr) {
436
- if (errors.utils.isIgnitionError(innerErr)) {
437
- throw err;
438
- }
386
+ // CASE: ETIMEDOUT, ENOTFOUND
387
+ if (err instanceof errors.DatabaseError) {
388
+ throw err;
389
+ }
439
390
 
440
- throw new errors.RollbackError({
441
- message: innerErr.message,
442
- err: innerErr,
443
- context: `OuterError: ${err.message}`
444
- });
445
- }).finally(function () {
446
- return locking.unlock(self.connection);
447
- });
448
- }).finally(function () {
449
- let ops = [];
391
+ if (err.context && err.context.name) {
392
+ debug(`Task failed: ${err.context.name}`);
393
+ }
450
394
 
451
- if (hooks.shutdown) {
452
- ops.push(function shutdownHook() {
453
- debug('Shutdown hook');
454
- return hooks.shutdown({
455
- executedFromShell: self.executedFromShell
456
- });
457
- });
395
+ logging.info(`Rolling back: ${err.message}.`);
396
+
397
+ const versionsMigrated = versionsToMigrate.slice(
398
+ 0,
399
+ versionsToMigrate.indexOf(versionToMigrate) + 1
400
+ );
401
+
402
+ versionsMigrated.reverse();
403
+
404
+ try {
405
+ for (const versionMigrated of versionsMigrated) {
406
+ await this._rollback({version: versionMigrated, task: err.context});
407
+ }
408
+ logging.info(`Rollback was successful.`);
409
+ throw err;
410
+ } catch (innerErr) {
411
+ if (errors.utils.isIgnitionError(innerErr)) {
412
+ throw err;
413
+ }
414
+
415
+ throw new errors.RollbackError({
416
+ message: innerErr.message,
417
+ err: innerErr,
418
+ context: `OuterError: ${err.message}`
419
+ });
420
+ } finally {
421
+ await locking.unlock(this.connection);
422
+ }
423
+ }
458
424
  }
459
425
 
460
- ops.push(function destroyConnection() {
461
- debug('Destroy connection');
462
- return self.connection.destroy()
463
- .then(function () {
464
- debug('Destroyed connection');
465
- });
426
+ if (hooks.after) {
427
+ debug('After hook');
428
+ await hooks.after({
429
+ connection: this.connection
430
+ });
431
+ }
432
+ }
433
+ await locking.unlock(this.connection);
434
+ } finally {
435
+ if (hooks.shutdown) {
436
+ debug('Shutdown hook');
437
+ await hooks.shutdown({
438
+ executedFromShell: this.executedFromShell
466
439
  });
440
+ }
467
441
 
468
- return Promise.each(ops, function (op) {
469
- return op.bind(self)();
470
- });
471
- });
442
+ debug('Destroy connection');
443
+ await this.connection.destroy();
444
+ debug('Destroyed connection');
445
+ }
472
446
  };
473
447
 
474
448
  /**
@@ -1045,8 +1019,8 @@ KnexMigrator.prototype._migrateTo = function _migrateTo(options) {
1045
1019
  throw new errors.MigrationScriptError({
1046
1020
  message: 'Field length of %field% in %table% is too long!'.replace('%field%', field).replace('%table%', table),
1047
1021
  context: 'This usually happens if your database encoding is utf8mb4.\n' +
1048
- 'All unique fields and indexes must be lower than 191 characters.\n' +
1049
- 'Please correct your field length and reset your database with knex-migrator reset.\n',
1022
+ 'All unique fields and indexes must be lower than 191 characters.\n' +
1023
+ 'Please correct your field length and reset your database with knex-migrator reset.\n',
1050
1024
  help: 'Read more here: https://github.com/TryGhost/knex-migrator/issues/51\n',
1051
1025
  err: err
1052
1026
  });
@@ -1132,7 +1106,6 @@ KnexMigrator.prototype._integrityCheck = function _integrityCheck(options) {
1132
1106
  subfolder = this.subfolder,
1133
1107
  force = options.force,
1134
1108
  folders = [],
1135
- operations = {},
1136
1109
  toReturn = {},
1137
1110
  futureVersions = [];
1138
1111
 
@@ -1148,29 +1121,69 @@ KnexMigrator.prototype._integrityCheck = function _integrityCheck(options) {
1148
1121
  // ignore
1149
1122
  }
1150
1123
 
1151
- _.each(folders, function (folder) {
1152
- // CASE: versions/1.1-members or versions/2.0-payments
1153
- if (folder !== 'init') {
1154
- try {
1155
- folder = folder.match(/([\d._]+)/)[0];
1156
- } catch (err) {
1157
- logging.warn('Cannot parse folder name.');
1158
- logging.warn('Ignore Folder: ' + folder);
1159
- return;
1160
- }
1161
- }
1124
+ return this
1125
+ .connection('migrations')
1126
+ .select('version')
1127
+ .count('version', {as: 'c'})
1128
+ .groupBy('version')
1129
+ .then((dbMigrations) => {
1130
+ _.each(folders, function (folder) {
1131
+ // CASE: versions/1.1-members or versions/2.0-payments
1132
+ if (folder !== 'init') {
1133
+ try {
1134
+ folder = folder.match(/([\d._]+)/)[0];
1135
+ } catch (err) {
1136
+ logging.warn('Cannot parse folder name.');
1137
+ logging.warn('Ignore Folder: ' + folder);
1138
+ return;
1139
+ }
1140
+ }
1141
+
1142
+ // CASE:
1143
+ // if your current version is 1.0 and you add migration scripts for the next version 1.1
1144
+ // we won't execute them until your current version changes to 1.1 or until you force KM to migrate to it
1145
+ if (self.currentVersion && !force) {
1146
+ if (utils.isGreaterThanVersion({smallerVersion: self.currentVersion, greaterVersion: folder})) {
1147
+ futureVersions.push(folder);
1148
+ }
1149
+ }
1162
1150
 
1163
- // CASE:
1164
- // if your current version is 1.0 and you add migration scripts for the next version 1.1
1165
- // we won't execute them until your current version changes to 1.1 or until you force KM to migrate to it
1166
- if (self.currentVersion && !force) {
1167
- if (utils.isGreaterThanVersion({smallerVersion: self.currentVersion, greaterVersion: folder})) {
1168
- futureVersions.push(folder);
1151
+ let actual = 0;
1152
+ let expected;
1153
+
1154
+ const migrationCount = dbMigrations.find(m => m.version === folder);
1155
+ if (migrationCount) {
1156
+ actual = migrationCount.c;
1157
+ }
1158
+
1159
+ if (folder !== 'init') {
1160
+ expected = utils.listFiles(path.join(self.migrationPath, subfolder, folder)).length;
1161
+ } else {
1162
+ expected = utils.listFiles(path.join(self.migrationPath, folder)).length;
1163
+ }
1164
+
1165
+ debug('Version ' + folder + ' expected: ' + expected);
1166
+ debug('Version ' + folder + ' actual: ' + actual);
1167
+
1168
+ toReturn[folder] = {
1169
+ expected: expected,
1170
+ actual: actual
1171
+ };
1172
+ });
1173
+
1174
+ // CASE: ensure that either you have to run `migrate --force` or they ran already
1175
+ if (futureVersions.length) {
1176
+ _.each(futureVersions, function (futureVersion) {
1177
+ if (toReturn[futureVersion].actual !== toReturn[futureVersion].expected) {
1178
+ logging.warn('knex-migrator is skipping ' + futureVersion);
1179
+ logging.warn('Current version in MigratorConfig.js is smaller then requested version, use --force to proceed!');
1180
+ logging.warn('Please run `knex-migrator migrate --v ' + futureVersion + ' --force` to proceed!');
1181
+ delete toReturn[futureVersion];
1182
+ }
1183
+ });
1169
1184
  }
1170
- }
1171
1185
 
1172
- operations[folder] = self.connection('migrations').where({
1173
- version: folder
1186
+ return toReturn;
1174
1187
  }).catch(function onMigrationsLookupError(err) {
1175
1188
  // CASE: no database selected (database.connection.database="")
1176
1189
  if (err.errno === 1046) {
@@ -1199,43 +1212,6 @@ KnexMigrator.prototype._integrityCheck = function _integrityCheck(options) {
1199
1212
 
1200
1213
  throw err;
1201
1214
  });
1202
- });
1203
-
1204
- return Promise.props(operations)
1205
- .then(function (result) {
1206
- _.each(result, function (value, version) {
1207
- let actual = value.length,
1208
- expected = actual;
1209
-
1210
- if (version !== 'init') {
1211
- expected = utils.readTasks(path.join(self.migrationPath, subfolder, version)).length;
1212
- } else {
1213
- expected = utils.readTasks(path.join(self.migrationPath, version)).length;
1214
- }
1215
-
1216
- debug('Version ' + version + ' expected: ' + expected);
1217
- debug('Version ' + version + ' actual: ' + actual);
1218
-
1219
- toReturn[version] = {
1220
- expected: expected,
1221
- actual: actual
1222
- };
1223
- });
1224
-
1225
- // CASE: ensure that either you have to run `migrate --force` or they ran already
1226
- if (futureVersions.length) {
1227
- _.each(futureVersions, function (futureVersion) {
1228
- if (toReturn[futureVersion].actual !== toReturn[futureVersion].expected) {
1229
- logging.warn('knex-migrator is skipping ' + futureVersion);
1230
- logging.warn('Current version in MigratorConfig.js is smaller then requested version, use --force to proceed!');
1231
- logging.warn('Please run `knex-migrator migrate --v ' + futureVersion + ' --force` to proceed!');
1232
- delete toReturn[futureVersion];
1233
- }
1234
- });
1235
- }
1236
-
1237
- return toReturn;
1238
- });
1239
1215
  };
1240
1216
 
1241
1217
  module.exports = KnexMigrator;
package/lib/utils.js CHANGED
@@ -46,15 +46,13 @@ module.exports.loadConfig = function loadConfig(options) {
46
46
  };
47
47
 
48
48
  /**
49
- * @description Reads all migration files from disk based on a path.
50
- * It returns an Array of migration files including it's up/down hooks, config and the name.
49
+ * @description List all migration files from disk based on a path.
51
50
  *
52
51
  * @param absolutePath
53
52
  * @returns {Array}
54
53
  */
55
- exports.readTasks = function readTasks(absolutePath) {
56
- let files = [],
57
- tasks = [];
54
+ exports.listFiles = function listFiles(absolutePath) {
55
+ let files = [];
58
56
 
59
57
  try {
60
58
  files = fs.readdirSync(absolutePath);
@@ -65,13 +63,28 @@ exports.readTasks = function readTasks(absolutePath) {
65
63
  });
66
64
  }
67
65
 
68
- _.each(files, function (file) {
66
+ files = files.filter(function (file) {
69
67
  // CASE: ignore dot files
70
- if (file.match(/^\./)) {
71
- debug('Ignore Dotfile: ' + file);
72
- return;
73
- }
68
+ return !file.match(/^\./);
69
+ });
70
+
71
+ debug(files);
72
+ return files;
73
+ };
74
+
75
+ /**
76
+ * @description Reads all migration files from disk based on a path.
77
+ * It returns an Array of migration files including it's up/down hooks, config and the name.
78
+ *
79
+ * @param absolutePath
80
+ * @returns {Array}
81
+ */
82
+ exports.readTasks = function readTasks(absolutePath) {
83
+ let tasks = [];
84
+
85
+ const files = exports.listFiles(absolutePath);
74
86
 
87
+ _.each(files, function (file) {
75
88
  let executeFn = require(path.join(absolutePath, file));
76
89
 
77
90
  try {
@@ -92,7 +105,7 @@ exports.readTasks = function readTasks(absolutePath) {
92
105
  }
93
106
  });
94
107
 
95
- debug(files);
108
+ debug(tasks);
96
109
  return tasks;
97
110
  };
98
111
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "knex-migrator",
3
- "version": "4.0.3",
3
+ "version": "4.1.1",
4
4
  "description": "Database migrations with knex.",
5
5
  "keywords": [
6
6
  "ghost",
@@ -44,18 +44,18 @@
44
44
  "knex-migrator-rollback": "./bin/knex-migrator-rollback"
45
45
  },
46
46
  "engines": {
47
- "node": "^10.13.0 || ^12.10.0 || ^14.14.0"
47
+ "node": "^12.22.1 || ^14.17.0 || ^16.13.0"
48
48
  },
49
49
  "dependencies": {
50
50
  "bluebird": "3.7.2",
51
51
  "commander": "5.1.0",
52
52
  "compare-ver": "2.0.2",
53
- "debug": "4.3.1",
54
- "ghost-ignition": "4.6.1",
53
+ "debug": "4.3.2",
54
+ "ghost-ignition": "4.6.3",
55
55
  "knex": "0.21.19",
56
56
  "lodash": "4.17.21",
57
57
  "moment": "2.24.0",
58
- "nconf": "0.11.2",
58
+ "nconf": "0.11.3",
59
59
  "resolve": "1.20.0"
60
60
  },
61
61
  "files": [