index.js 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593
  1. 'use strict';
  2. exports.__esModule = true;
  3. var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck');
  4. var _classCallCheck3 = _interopRequireDefault(_classCallCheck2);
  5. var _freeze = require('babel-runtime/core-js/object/freeze');
  6. var _freeze2 = _interopRequireDefault(_freeze);
  7. var _template2 = require('lodash/template');
  8. var _template3 = _interopRequireDefault(_template2);
  9. var _max2 = require('lodash/max');
  10. var _max3 = _interopRequireDefault(_max2);
  11. var _map2 = require('lodash/map');
  12. var _map3 = _interopRequireDefault(_map2);
  13. var _isUndefined2 = require('lodash/isUndefined');
  14. var _isUndefined3 = _interopRequireDefault(_isUndefined2);
  15. var _isEmpty2 = require('lodash/isEmpty');
  16. var _isEmpty3 = _interopRequireDefault(_isEmpty2);
  17. var _isBoolean2 = require('lodash/isBoolean');
  18. var _isBoolean3 = _interopRequireDefault(_isBoolean2);
  19. var _includes2 = require('lodash/includes');
  20. var _includes3 = _interopRequireDefault(_includes2);
  21. var _get2 = require('lodash/get');
  22. var _get3 = _interopRequireDefault(_get2);
  23. var _filter2 = require('lodash/filter');
  24. var _filter3 = _interopRequireDefault(_filter2);
  25. var _each2 = require('lodash/each');
  26. var _each3 = _interopRequireDefault(_each2);
  27. var _difference2 = require('lodash/difference');
  28. var _difference3 = _interopRequireDefault(_difference2);
  29. var _bind2 = require('lodash/bind');
  30. var _bind3 = _interopRequireDefault(_bind2);
  31. var _assign2 = require('lodash/assign');
  32. var _assign3 = _interopRequireDefault(_assign2);
  33. var _fs = require('fs');
  34. var _fs2 = _interopRequireDefault(_fs);
  35. var _path = require('path');
  36. var _path2 = _interopRequireDefault(_path);
  37. var _mkdirp = require('mkdirp');
  38. var _mkdirp2 = _interopRequireDefault(_mkdirp);
  39. var _bluebird = require('bluebird');
  40. var _bluebird2 = _interopRequireDefault(_bluebird);
  41. var _helpers = require('../helpers');
  42. var helpers = _interopRequireWildcard(_helpers);
  43. var _inherits = require('inherits');
  44. var _inherits2 = _interopRequireDefault(_inherits);
  45. function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } }
  46. function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
  47. // Migrator
  48. // -------
  49. function LockError(msg) {
  50. this.name = 'MigrationLocked';
  51. this.message = msg;
  52. }
  53. (0, _inherits2.default)(LockError, Error);
  54. var CONFIG_DEFAULT = (0, _freeze2.default)({
  55. extension: 'js',
  56. loadExtensions: ['.co', '.coffee', '.eg', '.iced', '.js', '.litcoffee', '.ls', '.ts'],
  57. tableName: 'knex_migrations',
  58. schemaName: null,
  59. directory: './migrations',
  60. disableTransactions: false
  61. });
  62. // The new migration we're performing, typically called from the `knex.migrate`
  63. // interface on the main `knex` object. Passes the `knex` instance performing
  64. // the migration.
  65. var Migrator = function () {
  66. function Migrator(knex) {
  67. (0, _classCallCheck3.default)(this, Migrator);
  68. this.knex = knex;
  69. this.config = this.setConfig(knex.client.config.migrations);
  70. this._activeMigration = {
  71. fileName: null
  72. };
  73. }
  74. // Migrators to the latest configuration.
  75. Migrator.prototype.latest = function latest(config) {
  76. var _this = this;
  77. this.config = this.setConfig(config);
  78. return this._migrationData().tap(validateMigrationList).spread(function (all, completed) {
  79. var migrations = (0, _difference3.default)(all, completed);
  80. var transactionForAll = !_this.config.disableTransactions && (0, _isEmpty3.default)((0, _filter3.default)(migrations, function (name) {
  81. var migration = require(_path2.default.join(_this._absoluteConfigDir(), name));
  82. return !_this._useTransaction(migration);
  83. }));
  84. if (transactionForAll) {
  85. return _this.knex.transaction(function (trx) {
  86. return _this._runBatch(migrations, 'up', trx);
  87. });
  88. } else {
  89. return _this._runBatch(migrations, 'up');
  90. }
  91. });
  92. };
  93. // Rollback the last "batch" of migrations that were run.
  94. Migrator.prototype.rollback = function rollback(config) {
  95. var _this2 = this;
  96. return _bluebird2.default.try(function () {
  97. _this2.config = _this2.setConfig(config);
  98. return _this2._migrationData().tap(validateMigrationList).then(function (val) {
  99. return _this2._getLastBatch(val);
  100. }).then(function (migrations) {
  101. return _this2._runBatch((0, _map3.default)(migrations, 'name'), 'down');
  102. });
  103. });
  104. };
  105. Migrator.prototype.status = function status(config) {
  106. this.config = this.setConfig(config);
  107. return _bluebird2.default.all([getTable(this.knex, this.config.tableName, this.config.schemaName).select('*'), this._listAll()]).spread(function (db, code) {
  108. return db.length - code.length;
  109. });
  110. };
  111. // Retrieves and returns the current migration version we're on, as a promise.
  112. // If no migrations have been run yet, return "none".
  113. Migrator.prototype.currentVersion = function currentVersion(config) {
  114. this.config = this.setConfig(config);
  115. return this._listCompleted().then(function (completed) {
  116. var val = (0, _max3.default)((0, _map3.default)(completed, function (value) {
  117. return value.split('_')[0];
  118. }));
  119. return (0, _isUndefined3.default)(val) ? 'none' : val;
  120. });
  121. };
  122. Migrator.prototype.forceFreeMigrationsLock = function forceFreeMigrationsLock(config) {
  123. var _this3 = this;
  124. this.config = this.setConfig(config);
  125. var lockTable = this._getLockTableName();
  126. return getSchemaBuilder(this.knex, this.config.schemaName).hasTable(lockTable).then(function (exist) {
  127. return exist && _this3._freeLock();
  128. });
  129. };
  130. // Creates a new migration, with a given name.
  131. Migrator.prototype.make = function make(name, config) {
  132. var _this4 = this;
  133. this.config = this.setConfig(config);
  134. if (!name) {
  135. return _bluebird2.default.reject(new Error('A name must be specified for the generated migration'));
  136. }
  137. return this._ensureFolder(config).then(function (val) {
  138. return _this4._generateStubTemplate(val);
  139. }).then(function (val) {
  140. return _this4._writeNewMigration(name, val);
  141. });
  142. };
  143. // Lists all available migration versions, as a sorted array.
  144. Migrator.prototype._listAll = function _listAll(config) {
  145. this.config = this.setConfig(config);
  146. var loadExtensions = this.config.loadExtensions;
  147. return _bluebird2.default.promisify(_fs2.default.readdir, { context: _fs2.default })(this._absoluteConfigDir()).then(function (migrations) {
  148. return (0, _filter3.default)(migrations, function (value) {
  149. var extension = _path2.default.extname(value);
  150. return (0, _includes3.default)(loadExtensions, extension);
  151. }).sort();
  152. });
  153. };
  154. // Ensures a folder for the migrations exist, dependent on the migration
  155. // config settings.
  156. Migrator.prototype._ensureFolder = function _ensureFolder() {
  157. var dir = this._absoluteConfigDir();
  158. return _bluebird2.default.promisify(_fs2.default.stat, { context: _fs2.default })(dir).catch(function () {
  159. return _bluebird2.default.promisify(_mkdirp2.default)(dir);
  160. });
  161. };
  162. // Ensures that a proper table has been created, dependent on the migration
  163. // config settings.
  164. Migrator.prototype._ensureTable = function _ensureTable() {
  165. var _this5 = this;
  166. var trx = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : this.knex;
  167. var _config = this.config,
  168. tableName = _config.tableName,
  169. schemaName = _config.schemaName;
  170. var lockTable = this._getLockTableName();
  171. var lockTableWithSchema = this._getLockTableNameWithSchema();
  172. return getSchemaBuilder(trx, schemaName).hasTable(tableName).then(function (exists) {
  173. return !exists && _this5._createMigrationTable(tableName, schemaName, trx);
  174. }).then(function () {
  175. return getSchemaBuilder(trx, schemaName).hasTable(lockTable);
  176. }).then(function (exists) {
  177. return !exists && _this5._createMigrationLockTable(lockTable, trx);
  178. }).then(function () {
  179. return getTable(trx, lockTable, _this5.config.schemaName).select('*');
  180. }).then(function (data) {
  181. return !data.length && trx.into(lockTableWithSchema).insert({ is_locked: 0 });
  182. });
  183. };
  184. Migrator.prototype._createMigrationTable = function _createMigrationTable(tableName, schemaName) {
  185. var trx = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : this.knex;
  186. return getSchemaBuilder(trx, schemaName).createTable(getTableName(tableName), function (t) {
  187. t.increments();
  188. t.string('name');
  189. t.integer('batch');
  190. t.timestamp('migration_time');
  191. });
  192. };
  193. Migrator.prototype._createMigrationLockTable = function _createMigrationLockTable(tableName) {
  194. var trx = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : this.knex;
  195. return getSchemaBuilder(trx, this.config.schemaName).createTable(tableName, function (t) {
  196. t.integer('is_locked');
  197. });
  198. };
  199. Migrator.prototype._getLockTableName = function _getLockTableName() {
  200. return this.config.tableName + '_lock';
  201. };
  202. Migrator.prototype._getLockTableNameWithSchema = function _getLockTableNameWithSchema() {
  203. return this.config.schemaName ? this.config.schemaName + '.' + this._getLockTableName() : this._getLockTableName();
  204. };
  205. Migrator.prototype._isLocked = function _isLocked(trx) {
  206. var tableName = this._getLockTableName();
  207. return getTable(this.knex, tableName, this.config.schemaName).transacting(trx).forUpdate().select('*').then(function (data) {
  208. return data[0].is_locked;
  209. });
  210. };
  211. Migrator.prototype._lockMigrations = function _lockMigrations(trx) {
  212. var tableName = this._getLockTableName();
  213. return getTable(this.knex, tableName, this.config.schemaName).transacting(trx).update({ is_locked: 1 });
  214. };
  215. Migrator.prototype._getLock = function _getLock(trx) {
  216. var _this6 = this;
  217. var transact = trx ? function (fn) {
  218. return fn(trx);
  219. } : function (fn) {
  220. return _this6.knex.transaction(fn);
  221. };
  222. return transact(function (trx) {
  223. return _this6._isLocked(trx).then(function (isLocked) {
  224. if (isLocked) {
  225. throw new Error("Migration table is already locked");
  226. }
  227. }).then(function () {
  228. return _this6._lockMigrations(trx);
  229. });
  230. }).catch(function (err) {
  231. throw new LockError(err.message);
  232. });
  233. };
  234. Migrator.prototype._freeLock = function _freeLock() {
  235. var trx = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : this.knex;
  236. var tableName = this._getLockTableName();
  237. return getTable(trx, tableName, this.config.schemaName).update({ is_locked: 0 });
  238. };
  239. // Run a batch of current migrations, in sequence.
  240. Migrator.prototype._runBatch = function _runBatch(migrations, direction, trx) {
  241. var _this7 = this;
  242. return this._getLock(trx)
  243. // When there is a wrapping transaction, some migrations
  244. // could have been done while waiting for the lock:
  245. .then(function () {
  246. return trx ? _this7._listCompleted(trx) : [];
  247. }).then(function (completed) {
  248. return migrations = (0, _difference3.default)(migrations, completed);
  249. }).then(function () {
  250. return _bluebird2.default.all((0, _map3.default)(migrations, (0, _bind3.default)(_this7._validateMigrationStructure, _this7)));
  251. }).then(function () {
  252. return _this7._latestBatchNumber(trx);
  253. }).then(function (batchNo) {
  254. if (direction === 'up') batchNo++;
  255. return batchNo;
  256. }).then(function (batchNo) {
  257. return _this7._waterfallBatch(batchNo, migrations, direction, trx);
  258. }).tap(function () {
  259. return _this7._freeLock(trx);
  260. }).catch(function (error) {
  261. var cleanupReady = _bluebird2.default.resolve();
  262. if (error instanceof LockError) {
  263. // If locking error do not free the lock.
  264. helpers.warn('Can\'t take lock to run migrations: ' + error.message);
  265. helpers.warn('If you are sure migrations are not running you can release the ' + 'lock manually by deleting all the rows from migrations lock ' + 'table: ' + _this7._getLockTableNameWithSchema());
  266. } else {
  267. if (_this7._activeMigration.fileName) {
  268. helpers.warn('migration file "' + _this7._activeMigration.fileName + '" failed');
  269. }
  270. helpers.warn('migration failed with error: ' + error.message);
  271. // If the error was not due to a locking issue, then remove the lock.
  272. cleanupReady = _this7._freeLock(trx);
  273. }
  274. return cleanupReady.finally(function () {
  275. throw error;
  276. });
  277. });
  278. };
  279. // Validates some migrations by requiring and checking for an `up` and `down`
  280. // function.
  281. Migrator.prototype._validateMigrationStructure = function _validateMigrationStructure(name) {
  282. var migration = require(_path2.default.join(this._absoluteConfigDir(), name));
  283. if (typeof migration.up !== 'function' || typeof migration.down !== 'function') {
  284. throw new Error('Invalid migration: ' + name + ' must have both an up and down function');
  285. }
  286. return name;
  287. };
  288. // Lists all migrations that have been completed for the current db, as an
  289. // array.
  290. Migrator.prototype._listCompleted = function _listCompleted() {
  291. var trx = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : this.knex;
  292. var _config2 = this.config,
  293. tableName = _config2.tableName,
  294. schemaName = _config2.schemaName;
  295. return this._ensureTable(trx).then(function () {
  296. return trx.from(getTableName(tableName, schemaName)).orderBy('id').select('name');
  297. }).then(function (migrations) {
  298. return (0, _map3.default)(migrations, 'name');
  299. });
  300. };
  301. // Gets the migration list from the specified migration directory, as well as
  302. // the list of completed migrations to check what should be run.
  303. Migrator.prototype._migrationData = function _migrationData() {
  304. return _bluebird2.default.all([this._listAll(), this._listCompleted()]);
  305. };
  306. // Generates the stub template for the current migration, returning a compiled
  307. // template.
  308. Migrator.prototype._generateStubTemplate = function _generateStubTemplate() {
  309. var stubPath = this.config.stub || _path2.default.join(__dirname, 'stub', this.config.extension + '.stub');
  310. return _bluebird2.default.promisify(_fs2.default.readFile, { context: _fs2.default })(stubPath).then(function (stub) {
  311. return (0, _template3.default)(stub.toString(), { variable: 'd' });
  312. });
  313. };
  314. // Write a new migration to disk, using the config and generated filename,
  315. // passing any `variables` given in the config to the template.
  316. Migrator.prototype._writeNewMigration = function _writeNewMigration(name, tmpl) {
  317. var config = this.config;
  318. var dir = this._absoluteConfigDir();
  319. if (name[0] === '-') name = name.slice(1);
  320. var filename = yyyymmddhhmmss() + '_' + name + '.' + config.extension;
  321. return _bluebird2.default.promisify(_fs2.default.writeFile, { context: _fs2.default })(_path2.default.join(dir, filename), tmpl(config.variables || {})).return(_path2.default.join(dir, filename));
  322. };
  323. // Get the last batch of migrations, by name, ordered by insert id in reverse
  324. // order.
  325. Migrator.prototype._getLastBatch = function _getLastBatch() {
  326. var _config3 = this.config,
  327. tableName = _config3.tableName,
  328. schemaName = _config3.schemaName;
  329. return getTable(this.knex, tableName, schemaName).where('batch', function (qb) {
  330. qb.max('batch').from(getTableName(tableName, schemaName));
  331. }).orderBy('id', 'desc');
  332. };
  333. // Returns the latest batch number.
  334. Migrator.prototype._latestBatchNumber = function _latestBatchNumber() {
  335. var trx = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : this.knex;
  336. return trx.from(getTableName(this.config.tableName, this.config.schemaName)).max('batch as max_batch').then(function (obj) {
  337. return obj[0].max_batch || 0;
  338. });
  339. };
  340. // If transaction config for a single migration is defined, use that.
  341. // Otherwise, rely on the common config. This allows enabling/disabling
  342. // transaction for a single migration at will, regardless of the common
  343. // config.
  344. Migrator.prototype._useTransaction = function _useTransaction(migration, allTransactionsDisabled) {
  345. var singleTransactionValue = (0, _get3.default)(migration, 'config.transaction');
  346. return (0, _isBoolean3.default)(singleTransactionValue) ? singleTransactionValue : !allTransactionsDisabled;
  347. };
  348. // Runs a batch of `migrations` in a specified `direction`, saving the
  349. // appropriate database information as the migrations are run.
  350. Migrator.prototype._waterfallBatch = function _waterfallBatch(batchNo, migrations, direction, trx) {
  351. var _this8 = this;
  352. var trxOrKnex = trx || this.knex;
  353. var _config4 = this.config,
  354. tableName = _config4.tableName,
  355. schemaName = _config4.schemaName,
  356. disableTransactions = _config4.disableTransactions;
  357. var directory = this._absoluteConfigDir();
  358. var current = _bluebird2.default.bind({ failed: false, failedOn: 0 });
  359. var log = [];
  360. (0, _each3.default)(migrations, function (migration) {
  361. var name = migration;
  362. _this8._activeMigration.fileName = name;
  363. migration = require(directory + '/' + name);
  364. // We're going to run each of the migrations in the current "up".
  365. current = current.then(function () {
  366. if (!trx && _this8._useTransaction(migration, disableTransactions)) {
  367. return _this8._transaction(migration, direction, name);
  368. }
  369. return warnPromise(migration[direction](trxOrKnex, _bluebird2.default), name);
  370. }).then(function () {
  371. log.push(_path2.default.join(directory, name));
  372. if (direction === 'up') {
  373. return trxOrKnex.into(getTableName(tableName, schemaName)).insert({
  374. name: name,
  375. batch: batchNo,
  376. migration_time: new Date()
  377. });
  378. }
  379. if (direction === 'down') {
  380. return trxOrKnex.from(getTableName(tableName, schemaName)).where({ name: name }).del();
  381. }
  382. });
  383. });
  384. return current.thenReturn([batchNo, log]);
  385. };
  386. Migrator.prototype._transaction = function _transaction(migration, direction, name) {
  387. return this.knex.transaction(function (trx) {
  388. return warnPromise(migration[direction](trx, _bluebird2.default), name, function () {
  389. trx.commit();
  390. });
  391. });
  392. };
  393. Migrator.prototype._absoluteConfigDir = function _absoluteConfigDir() {
  394. return _path2.default.resolve(process.cwd(), this.config.directory);
  395. };
  396. Migrator.prototype.setConfig = function setConfig(config) {
  397. return (0, _assign3.default)({}, CONFIG_DEFAULT, this.config || {}, config);
  398. };
  399. return Migrator;
  400. }();
  401. // Validates that migrations are present in the appropriate directories.
  402. exports.default = Migrator;
  403. function validateMigrationList(migrations) {
  404. var all = migrations[0];
  405. var completed = migrations[1];
  406. var diff = (0, _difference3.default)(completed, all);
  407. if (!(0, _isEmpty3.default)(diff)) {
  408. throw new Error('The migration directory is corrupt, the following files are missing: ' + diff.join(', '));
  409. }
  410. }
  411. function warnPromise(value, name, fn) {
  412. if (!value || typeof value.then !== 'function') {
  413. helpers.warn('migration ' + name + ' did not return a promise');
  414. if (fn && typeof fn === 'function') fn();
  415. }
  416. return value;
  417. }
  418. // Ensure that we have 2 places for each of the date segments.
  419. function padDate(segment) {
  420. segment = segment.toString();
  421. return segment[1] ? segment : '0' + segment;
  422. }
  423. // Get a date object in the correct format, without requiring a full out library
  424. // like "moment.js".
  425. function yyyymmddhhmmss() {
  426. var d = new Date();
  427. return d.getFullYear().toString() + padDate(d.getMonth() + 1) + padDate(d.getDate()) + padDate(d.getHours()) + padDate(d.getMinutes()) + padDate(d.getSeconds());
  428. }
  429. //Get schema-aware table name
  430. function getTableName(tableName, schemaName) {
  431. return schemaName ? schemaName + '.' + tableName : tableName;
  432. }
  433. //Get schema-aware query builder for a given table and schema name
  434. function getTable(trxOrKnex, tableName, schemaName) {
  435. return schemaName ? trxOrKnex(tableName).withSchema(schemaName) : trxOrKnex(tableName);
  436. }
  437. //Get schema-aware schema builder for a given schema nam
  438. function getSchemaBuilder(trxOrKnex, schemaName) {
  439. return schemaName ? trxOrKnex.schema.withSchema(schemaName) : trxOrKnex.schema;
  440. }
  441. module.exports = exports['default'];