knex-migrator 5.4.1 → 6.0.0

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.
@@ -1,38 +1,44 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- var program = require('commander');
4
- var utils = require('../lib/utils');
5
-
6
- var logging = require('@tryghost/logging');
7
- var knexMigrator;
8
-
9
- utils.getKnexMigrator({path: process.cwd()})
10
- .then(function (KnexMigrator) {
11
- program
12
- .option('--v <item>')
13
- .option('--only <item>')
14
- .option('--mgpath <path>')
15
- .option('--force')
16
- .option('--init')
17
- .parse(process.argv);
18
-
19
- try {
20
- knexMigrator = new KnexMigrator({knexMigratorFilePath: program.mgpath, executedFromShell: true});
21
- } catch (err) {
22
- logging.error(err);
23
- process.exit(1);
24
- }
25
-
26
- return knexMigrator.migrate({
27
- version: program.v,
28
- only: program.only,
29
- force: program.force,
30
- init: program.init
31
- }).then(function () {
32
- logging.info('Finished database migration!');
3
+ const program = require('commander').program;
4
+ const utils = require('../lib/utils');
5
+
6
+ const logging = require('@tryghost/logging');
7
+
8
+ async function main() {
9
+ const KnexMigrator = await utils.getKnexMigrator({path: process.cwd()});
10
+
11
+ program
12
+ .option('--v <item>')
13
+ .option('--only <item>')
14
+ .option('--mgpath <path>')
15
+ .option('--force')
16
+ .option('--init')
17
+ .parse(process.argv);
18
+
19
+ const options = program.opts();
20
+ let knexMigrator;
21
+
22
+ try {
23
+ knexMigrator = new KnexMigrator({
24
+ knexMigratorFilePath: options.mgpath,
25
+ executedFromShell: true,
33
26
  });
34
- })
35
- .catch(function (err) {
27
+ } catch (err) {
28
+ logging.error(err);
29
+ process.exit(1);
30
+ }
31
+
32
+ await knexMigrator.migrate({
33
+ version: options.v,
34
+ only: options.only,
35
+ force: options.force,
36
+ init: options.init,
37
+ });
38
+ logging.info('Finished database migration!');
39
+ }
40
+
41
+ main().catch(function (err) {
36
42
  logging.error(err);
37
43
  process.exit(1);
38
44
  });
@@ -1,31 +1,36 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- var program = require('commander');
4
- var utils = require('../lib/utils');
5
-
6
- var logging = require('@tryghost/logging');
7
- var knexMigrator;
8
-
9
- utils.getKnexMigrator({path: process.cwd()})
10
- .then(function (KnexMigrator) {
11
- program
12
- .option('--mgpath <path>')
13
- .option('--force')
14
- .parse(process.argv);
15
-
16
- try {
17
- knexMigrator = new KnexMigrator({knexMigratorFilePath: program.mgpath, executedFromShell: true});
18
- } catch (err) {
19
- logging.error(err);
20
- process.exit(1);
21
- }
22
-
23
- return knexMigrator.reset({force: program.force})
24
- .then(function () {
25
- logging.info('Finished database reset!');
26
- });
27
- })
28
- .catch(function (err) {
3
+ const program = require('commander').program;
4
+ const utils = require('../lib/utils');
5
+
6
+ const logging = require('@tryghost/logging');
7
+
8
+ async function main() {
9
+ const KnexMigrator = await utils.getKnexMigrator({path: process.cwd()});
10
+
11
+ program
12
+ .option('--mgpath <path>')
13
+ .option('--force')
14
+ .parse(process.argv);
15
+
16
+ const options = program.opts();
17
+ let knexMigrator;
18
+
19
+ try {
20
+ knexMigrator = new KnexMigrator({
21
+ knexMigratorFilePath: options.mgpath,
22
+ executedFromShell: true,
23
+ });
24
+ } catch (err) {
25
+ logging.error(err);
26
+ process.exit(1);
27
+ }
28
+
29
+ await knexMigrator.reset({force: options.force});
30
+ logging.info('Finished database reset!');
31
+ }
32
+
33
+ main().catch(function (err) {
29
34
  logging.error(err);
30
35
  process.exit(1);
31
36
  });
@@ -1,32 +1,37 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- const program = require('commander');
3
+ const program = require('commander').program;
4
4
  const utils = require('../lib/utils');
5
5
 
6
6
  const logging = require('@tryghost/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
7
 
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) {
8
+ async function main() {
9
+ const KnexMigrator = await utils.getKnexMigrator({path: process.cwd()});
10
+
11
+ program
12
+ .option('--mgpath <path>')
13
+ .option('--force')
14
+ .option('--v <version>', 'The version to rollback to.')
15
+ .parse(process.argv);
16
+
17
+ const options = program.opts();
18
+ let knexMigrator;
19
+
20
+ try {
21
+ knexMigrator = new KnexMigrator({
22
+ knexMigratorFilePath: options.mgpath,
23
+ executedFromShell: true,
24
+ });
25
+ } catch (err) {
26
+ logging.error(err);
27
+ process.exit(1);
28
+ }
29
+
30
+ await knexMigrator.rollback({force: options.force, version: options.v});
31
+ logging.info('Rollback was successful.');
32
+ }
33
+
34
+ main().catch(function (err) {
30
35
  logging.error(err.message);
31
36
 
32
37
  if (err.help) {
package/lib/database.js CHANGED
@@ -1,9 +1,51 @@
1
- const knex = require('knex'),
1
+ const path = require('path'),
2
+ bundledKnex = require('knex'),
2
3
  omit = require('lodash/omit'),
3
4
  debug = require('debug')('knex-migrator:database'),
4
5
  errors = require('./errors');
5
6
  const DatabaseInfo = require('@tryghost/database-info');
6
- const {sequence} = require('@tryghost/promise');
7
+ const { sequence } = require('@tryghost/promise');
8
+
9
+ function destroyConnection(connection) {
10
+ return new Promise(function (resolve, reject) {
11
+ connection.destroy(function (err) {
12
+ if (err) {
13
+ reject(err);
14
+ return;
15
+ }
16
+
17
+ resolve();
18
+ });
19
+ });
20
+ }
21
+
22
+ function loadKnex(options) {
23
+ options = options || {};
24
+
25
+ if (options.knexModulePath) {
26
+ let resolvedKnexPath;
27
+
28
+ try {
29
+ resolvedKnexPath = require.resolve('knex', {
30
+ paths: [path.resolve(options.knexModulePath)],
31
+ });
32
+ } catch (err) {
33
+ if (err.code !== 'MODULE_NOT_FOUND') {
34
+ throw err;
35
+ }
36
+
37
+ debug(
38
+ 'Could not load project knex from ' + options.knexModulePath + ': ' + err.message,
39
+ );
40
+ }
41
+
42
+ if (resolvedKnexPath) {
43
+ return require(resolvedKnexPath);
44
+ }
45
+ }
46
+
47
+ return bundledKnex;
48
+ }
7
49
 
8
50
  /**
9
51
  * @NOTE: Knex-migrator only supports knex query builder.
@@ -11,7 +53,7 @@ const {sequence} = require('@tryghost/promise');
11
53
  * @param options
12
54
  * @returns {Knex.QueryBuilder | Knex}
13
55
  */
14
- exports.connect = function connect(options) {
56
+ exports.connect = function connect(options, connectionOptions) {
15
57
  options = options || {};
16
58
 
17
59
  // Alias `mysql` to `mysql2` so we can maintain backwards compatibility
@@ -19,9 +61,14 @@ exports.connect = function connect(options) {
19
61
  options.client = 'mysql2';
20
62
  }
21
63
 
64
+ // Alias `sqlite3` to `better-sqlite3` so we can maintain backwards compatibility
65
+ if (options.client === 'sqlite3') {
66
+ options.client = 'better-sqlite3';
67
+ }
68
+
22
69
  const client = options.client;
23
70
 
24
- if (client === 'sqlite3') {
71
+ if (client === 'better-sqlite3') {
25
72
  options.useNullAsDefault = options.useNullAsDefault || false;
26
73
  }
27
74
 
@@ -33,7 +80,7 @@ exports.connect = function connect(options) {
33
80
  delete options.connection.filename;
34
81
  }
35
82
 
36
- return knex(options);
83
+ return loadKnex(connectionOptions)(options);
37
84
  };
38
85
 
39
86
  /**
@@ -43,23 +90,24 @@ exports.connect = function connect(options) {
43
90
  * @param connection
44
91
  * @returns {Promise<R> | Promise<any> | Promise<T>}
45
92
  */
46
- exports.ensureConnectionWorks = (connection) => {
47
- return connection.raw('SELECT 1+1 as RESULT;')
48
- .catch((err) => {
49
- if (err.code === 'ENOTFOUND' || err.code === 'ETIMEDOUT' || err.code === 'EAI_AGAIN') {
50
- throw new errors.DatabaseError({
51
- message: 'Invalid database host.',
52
- help: 'Please double check your database config.',
53
- err: err
54
- });
55
- }
56
-
93
+ exports.ensureConnectionWorks = async function ensureConnectionWorks(connection) {
94
+ try {
95
+ await connection.raw('SELECT 1+1 as RESULT;');
96
+ } catch (err) {
97
+ if (err.code === 'ENOTFOUND' || err.code === 'ETIMEDOUT' || err.code === 'EAI_AGAIN') {
57
98
  throw new errors.DatabaseError({
58
- message: err.message,
59
- help: 'Unknown database error',
60
- err: err
99
+ message: 'Invalid database host.',
100
+ help: 'Please double check your database config.',
101
+ err: err,
61
102
  });
103
+ }
104
+
105
+ throw new errors.DatabaseError({
106
+ message: err.message,
107
+ help: 'Unknown database error',
108
+ err: err,
62
109
  });
110
+ }
63
111
  };
64
112
 
65
113
  /**
@@ -101,54 +149,52 @@ exports.createMigrationsTable = async function createMigrationsTable(connection)
101
149
  * @param dbConfig
102
150
  * @returns {*}
103
151
  */
104
- exports.createDatabaseIfNotExist = function createDatabaseIfNotExist(dbConfig) {
105
- const name = dbConfig.connection.database,
106
- charset = dbConfig.connection.charset || 'utf8mb4';
152
+ exports.createDatabaseIfNotExist = async function createDatabaseIfNotExist(
153
+ dbConfig,
154
+ connectionOptions,
155
+ ) {
156
+ const name = dbConfig.connection.database;
157
+ const charset = dbConfig.connection.charset || 'utf8mb4';
107
158
 
108
159
  // @NOTE: Skip, because sqlite3 is a file based database.
109
160
  if (DatabaseInfo.isSQLiteConfig(dbConfig)) {
110
- return Promise.resolve();
111
- } else if (!DatabaseInfo.isMySQLConfig(dbConfig)) {
112
- return Promise.reject(new errors.KnexMigrateError({
113
- message: 'Database is not supported.'
114
- }));
161
+ return;
115
162
  }
116
163
 
117
- const connection = exports.connect({
118
- client: dbConfig.client,
119
- connection: omit(dbConfig.connection, ['database'])
120
- });
164
+ if (!DatabaseInfo.isMySQLConfig(dbConfig)) {
165
+ throw new errors.KnexMigrateError({
166
+ message: 'Database is not supported.',
167
+ });
168
+ }
169
+
170
+ const connection = exports.connect(
171
+ {
172
+ client: dbConfig.client,
173
+ connection: omit(dbConfig.connection, ['database']),
174
+ },
175
+ connectionOptions,
176
+ );
121
177
 
122
178
  debug('Create database', name);
123
179
 
124
- return exports.ensureConnectionWorks(connection)
125
- .then(function () {
126
- return connection.raw('CREATE DATABASE `' + name + '` CHARACTER SET ' + charset + ';');
127
- })
128
- .catch(function (err) {
129
- // CASE: DB exists
130
- if (err.errno === 1007) {
131
- return Promise.resolve();
132
- }
180
+ try {
181
+ await exports.ensureConnectionWorks(connection);
182
+ await connection.raw('CREATE DATABASE `' + name + '` CHARACTER SET ' + charset + ';');
183
+ } catch (err) {
184
+ // CASE: DB exists
185
+ if (err.errno === 1007) {
186
+ return;
187
+ }
133
188
 
134
- throw new errors.DatabaseError({
135
- message: err.message,
136
- err: err,
137
- code: 'DATABASE_CREATION_FAILED'
138
- });
139
- })
140
- .finally(function () {
141
- return new Promise(function (resolve, reject) {
142
- connection.destroy(function (err) {
143
- if (err) {
144
- return reject(err);
145
- }
146
-
147
- debug('Destroy connection');
148
- resolve();
149
- });
150
- });
189
+ throw new errors.DatabaseError({
190
+ message: err.message,
191
+ err: err,
192
+ code: 'DATABASE_CREATION_FAILED',
151
193
  });
194
+ } finally {
195
+ await destroyConnection(connection);
196
+ debug('Destroy connection');
197
+ }
152
198
  };
153
199
 
154
200
  /**
@@ -157,68 +203,74 @@ exports.createDatabaseIfNotExist = function createDatabaseIfNotExist(dbConfig) {
157
203
  * @param options
158
204
  * @returns {*}
159
205
  */
160
- exports.drop = function drop(options) {
206
+ exports.drop = async function drop(options) {
161
207
  options = options || {};
162
208
 
163
- const connection = options.connection,
164
- dbConfig = options.dbConfig;
209
+ const connection = options.connection;
210
+ const dbConfig = options.dbConfig;
165
211
 
166
212
  if (DatabaseInfo.isMySQL(connection)) {
167
213
  debug('Drop database: ' + dbConfig.connection.database);
168
214
 
169
- return connection.raw('DROP DATABASE `' + dbConfig.connection.database + '`;')
170
- .catch(function (err) {
171
- // CASE: database does not exist, skip
172
- if (err.errno === 1049) {
173
- return Promise.resolve();
174
- }
215
+ try {
216
+ await connection.raw('DROP DATABASE `' + dbConfig.connection.database + '`;');
217
+ } catch (err) {
218
+ // CASE: database does not exist, skip
219
+ if (err.errno === 1049) {
220
+ return;
221
+ }
175
222
 
176
- return Promise.reject(new errors.KnexMigrateError({
177
- err: err
178
- }));
223
+ throw new errors.KnexMigrateError({
224
+ err: err,
179
225
  });
180
- } else if (DatabaseInfo.isSQLite(connection)) {
181
- // @NOTE: sqlite3 does not support "DROP DATABASE". We have to drop each table instead.
182
- // @NOTE: We cannot just remove the sqlite3 file, because any database connection will get invalid.
183
- // @NOTE: better-sqlite3 enforces foreign key constraints, so dropping a parent table before
184
- // its referencing children trips an FK error. Disable foreign keys for the duration of the
185
- // drop and restore afterwards. SQLite uses a single connection here, so the PRAGMA persists
186
- // across the drops. The legacy sqlite3 driver does not enforce these, so we leave it untouched.
187
- const isBetterSqlite = connection.client.config.client === 'better-sqlite3';
188
-
189
- return (isBetterSqlite ? connection.raw('PRAGMA foreign_keys = OFF;') : Promise.resolve())
190
- .then(function () {
191
- return connection.raw(`SELECT name FROM sqlite_master WHERE type='table';`);
192
- })
193
- .then(function (tables) {
194
- return sequence(tables.map(table => () => {
226
+ }
227
+
228
+ return;
229
+ }
230
+
231
+ if (DatabaseInfo.isSQLite(connection)) {
232
+ // @NOTE: SQLite does not support "DROP DATABASE". We have to drop each table instead.
233
+ // @NOTE: We cannot just remove the database file, because any database connection will get invalid.
234
+ // @NOTE: better-sqlite3 (which `sqlite3` is aliased to) enforces foreign key constraints, so
235
+ // dropping a parent table before its referencing children trips an FK error. Disable foreign
236
+ // keys for the duration of the drop and restore afterwards. SQLite uses a single connection
237
+ // here, so the PRAGMA persists across the drops.
238
+ try {
239
+ await connection.raw('PRAGMA foreign_keys = OFF;');
240
+
241
+ const tables = await connection.raw(
242
+ `SELECT name FROM sqlite_master WHERE type='table';`,
243
+ );
244
+
245
+ await sequence(
246
+ tables.map((table) => async () => {
195
247
  if (table.name === 'sqlite_sequence') {
196
248
  debug('Skip drop table: ' + table.name);
197
- return Promise.resolve();
249
+ return;
198
250
  }
199
251
 
200
252
  debug('Drop table: ' + table.name);
201
- return connection.schema.dropTableIfExists(table.name);
202
- }));
203
- })
204
- .catch(function (err) {
205
- // CASE: database file was never initialised
206
- if (err.errno === 10) {
207
- return Promise.resolve();
208
- }
209
-
210
- return Promise.reject(new errors.KnexMigrateError({
211
- err: err
212
- }));
213
- })
214
- .finally(function () {
215
- if (isBetterSqlite) {
216
- return connection.raw('PRAGMA foreign_keys = ON;');
217
- }
253
+ await connection.schema.dropTableIfExists(table.name);
254
+ }),
255
+ );
256
+ } catch (err) {
257
+ // CASE: database file was never initialised
258
+ if (err.errno === 10) {
259
+ return;
260
+ }
261
+
262
+ throw new errors.KnexMigrateError({
263
+ err: err,
218
264
  });
219
- } else {
220
- return Promise.reject(new errors.KnexMigrateError({
221
- message: 'Database client not supported: ' + dbConfig.client
222
- }));
265
+ } finally {
266
+ // Best-effort restore; never let a failed PRAGMA mask the drop result.
267
+ await connection.raw('PRAGMA foreign_keys = ON;').catch(function () {});
268
+ }
269
+
270
+ return;
223
271
  }
272
+
273
+ throw new errors.KnexMigrateError({
274
+ message: 'Database client not supported: ' + dbConfig.client,
275
+ });
224
276
  };