index.js 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584
  1. "use strict";
  2. var _bluebird = _interopRequireDefault(require("bluebird"));
  3. var _fs = _interopRequireDefault(require("fs"));
  4. var _migration2 = _interopRequireDefault(require("./migration"));
  5. var _path = _interopRequireDefault(require("path"));
  6. var _events = require("events");
  7. var _migrationsList = _interopRequireDefault(require("./migrationsList"));
  8. var _Storage = _interopRequireDefault(require("./storages/Storage"));
  9. var _JSONStorage = _interopRequireDefault(require("./storages/JSONStorage"));
  10. var _MongoDBStorage = _interopRequireDefault(require("./storages/MongoDBStorage"));
  11. var _SequelizeStorage = _interopRequireDefault(require("./storages/SequelizeStorage"));
  12. function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
  13. function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; }
  14. function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(Object(source), true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; }
  15. function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
  16. /**
  17. * @class Umzug
  18. * @extends EventEmitter
  19. */
  20. module.exports = class Umzug extends _events.EventEmitter {
  21. /**
  22. * Constructs Umzug instance.
  23. *
  24. * @param {Object} [options]
  25. * @param {String} [options.storage='json'] - The storage. Possible values:
  26. * 'json', 'sequelize', 'mongodb', an argument for `require()`, including absolute paths.
  27. * @param {function|false} [options.logging=false] - The logging function.
  28. * A function that gets executed every time migrations start and have ended.
  29. * @param {String} [options.upName='up'] - The name of the positive method
  30. * in migrations.
  31. * @param {String} [options.downName='down'] - The name of the negative method
  32. * in migrations.
  33. * @param {Object} [options.storageOptions] - The options for the storage.
  34. * Check the available storages for further details.
  35. * @param {Object|Array} [options.migrations] - options for loading migration
  36. * files, or (advanced) an array of Migration instances
  37. * @param {Array} [options.migrations.params] - The params that gets passed to
  38. * the migrations. Might be an array or a synchronous function which returns
  39. * an array.
  40. * @param {String} [options.migrations.path] - The path to the migrations
  41. * directory.
  42. * @param {RegExp} [options.migrations.pattern] - The pattern that determines
  43. * whether or not a file is a migration.
  44. * @param {Migration~wrap} [options.migrations.wrap] - A function that
  45. * receives and returns the to be executed function. This can be used to
  46. * modify the function.
  47. * @param {Migration~customResolver} [options.migrations.customResolver] - A
  48. * function that specifies how to get a migration object from a path. This
  49. * should return an object of the form { up: Function, down: Function }.
  50. * Without this defined, a regular javascript import will be performed.
  51. * @constructs Umzug
  52. */
  53. constructor(options = {}) {
  54. super();
  55. this.options = _objectSpread({
  56. storage: 'json',
  57. storageOptions: {},
  58. logging: false,
  59. upName: 'up',
  60. downName: 'down'
  61. }, options);
  62. if (this.options.logging && typeof this.options.logging !== 'function') {
  63. throw new Error('The logging-option should be either a function or false');
  64. }
  65. if (!Array.isArray(this.options.migrations)) {
  66. this.options.migrations = _objectSpread({
  67. params: [],
  68. path: _path.default.resolve(process.cwd(), 'migrations'),
  69. pattern: /^\d+[\w-]+\.js$/,
  70. traverseDirectories: false,
  71. wrap: fun => fun
  72. }, this.options.migrations);
  73. }
  74. this.storage = this._initStorage();
  75. }
  76. /**
  77. * Executes given migrations with a given method.
  78. *
  79. * @param {Object} [options]
  80. * @param {String[]} [options.migrations=[]]
  81. * @param {String} [options.method='up']
  82. * @returns {Promise}
  83. */
  84. execute(options = {}) {
  85. const self = this;
  86. options = _objectSpread({
  87. migrations: [],
  88. method: 'up'
  89. }, options);
  90. return _bluebird.default.map(options.migrations, migration => self._findMigration(migration)).then(migrations => _objectSpread({}, options, {
  91. migrations
  92. })).then(options => _bluebird.default.each(options.migrations, migration => {
  93. const name = _path.default.basename(migration.file, _path.default.extname(migration.file));
  94. let startTime;
  95. return self._wasExecuted(migration).catch(() => false).then(executed => typeof executed === 'undefined' ? true : executed).tap(executed => {
  96. if (!executed || options.method === 'down') {
  97. const fun = migration[options.method] || _bluebird.default.resolve;
  98. let params = self.options.migrations.params;
  99. if (typeof params === 'function') {
  100. params = params();
  101. }
  102. if (options.method === 'up') {
  103. self.log('== ' + name + ': migrating =======');
  104. self.emit('migrating', name, migration);
  105. } else {
  106. self.log('== ' + name + ': reverting =======');
  107. self.emit('reverting', name, migration);
  108. }
  109. startTime = new Date();
  110. return fun.apply(migration, params);
  111. }
  112. }).then(executed => {
  113. if (!executed && options.method === 'up') {
  114. return _bluebird.default.resolve(self.storage.logMigration(migration.file));
  115. } else if (options.method === 'down') {
  116. return _bluebird.default.resolve(self.storage.unlogMigration(migration.file));
  117. }
  118. }).tap(() => {
  119. const duration = ((new Date() - startTime) / 1000).toFixed(3);
  120. if (options.method === 'up') {
  121. self.log('== ' + name + ': migrated (' + duration + 's)\n');
  122. self.emit('migrated', name, migration);
  123. } else {
  124. self.log('== ' + name + ': reverted (' + duration + 's)\n');
  125. self.emit('reverted', name, migration);
  126. }
  127. });
  128. }));
  129. }
  130. /**
  131. * Lists executed migrations.
  132. *
  133. * @returns {Promise.<Migration>}
  134. */
  135. executed() {
  136. return _bluebird.default.resolve(this.storage.executed()).bind(this).map(file => new _migration2.default(file));
  137. }
  138. /**
  139. * Lists pending migrations.
  140. *
  141. * @returns {Promise.<Migration[]>}
  142. */
  143. pending() {
  144. return this._findMigrations().bind(this).then(function (all) {
  145. return _bluebird.default.join(all, this.executed());
  146. }).spread((all, executed) => {
  147. const executedFiles = executed.map(migration => migration.file);
  148. return all.filter(migration => executedFiles.indexOf(migration.file) === -1);
  149. });
  150. }
  151. /**
  152. * Execute migrations up.
  153. *
  154. * If options is a migration name (String), it will be executed.
  155. * If options is a list of migration names (String[]), them will be executed.
  156. * If options is Object:
  157. * - { from: 'migration-1', to: 'migration-n' } - execute migrations in range.
  158. * - { migrations: [] } - execute migrations in array.
  159. *
  160. * @param {String|String[]|Object} options
  161. * @param {String} [options.from] - The first migration to execute (exc).
  162. * @param {String} [options.to] - The last migration to execute (inc).
  163. * @param {String[]} [options.migrations] - List of migrations to execute.
  164. * @returns {Promise}
  165. */
  166. up(options) {
  167. return this._run('up', options, this.pending.bind(this));
  168. }
  169. /**
  170. * Execute migrations down.
  171. *
  172. * If options is a migration name (String), it will be executed.
  173. * If options is a list of migration names (String[]), them will be executed.
  174. * If options is Object:
  175. * - { from: 'migration-n', to: 'migration-1' } - execute migrations in range.
  176. * - { migrations: [] } - execute migrations in array.
  177. *
  178. * @param {String|String[]|Object} options
  179. * @param {String} [options.from] - The first migration to execute (exc).
  180. * @param {String} [options.to] - The last migration to execute (inc).
  181. * @param {String[]} [options.migrations] - List of migrations to execute.
  182. * @returns {Promise}
  183. */
  184. down(options) {
  185. const getExecuted = function () {
  186. return this.executed().bind(this).then(migrations => migrations.reverse());
  187. }.bind(this);
  188. if (typeof options === 'undefined' || !Object.keys(options).length) {
  189. return getExecuted().bind(this).then(function (migrations) {
  190. return migrations[0] ? this.down(migrations[0].file) : _bluebird.default.resolve([]);
  191. });
  192. } else {
  193. return this._run('down', options, getExecuted.bind(this));
  194. }
  195. }
  196. /**
  197. * Callback function to get migrations in right order.
  198. *
  199. * @callback Umzug~rest
  200. * @return {Promise.<Migration[]>}
  201. */
  202. /**
  203. * Execute migrations either down or up.
  204. *
  205. * If options is a migration name (String), it will be executed.
  206. * If options is a list of migration names (String[]), them will be executed.
  207. * If options is Object:
  208. * - { from: 'migration-1', to: 'migration-n' } - execute migrations in range.
  209. * - { migrations: [] } - execute migrations in array.
  210. *
  211. * @param {String} method - Method to run. Either 'up' or 'down'.
  212. * @param {String|String[]|Object} options
  213. * @param {String} [options.from] - The first migration to execute (exc).
  214. * @param {String} [options.to] - The last migration to execute (inc).
  215. * @param {String[]} [options.migrations] - List of migrations to execute.
  216. * @param {Umzug~rest} [rest] - Function to get migrations in right order.
  217. * @returns {Promise}
  218. * @private
  219. */
  220. _run(method, options, rest) {
  221. if (typeof options === 'string') {
  222. return this._run(method, [options]);
  223. } else if (Array.isArray(options)) {
  224. return _bluebird.default.resolve(options).bind(this).map(function (migration) {
  225. return this._findMigration(migration);
  226. }).then(function (migrations) {
  227. return method === 'up' ? this._arePending(migrations) : this._wereExecuted(migrations);
  228. }).then(function () {
  229. return this._run(method, {
  230. migrations: options
  231. });
  232. });
  233. }
  234. options = _objectSpread({
  235. to: null,
  236. from: null,
  237. migrations: null
  238. }, options || {});
  239. if (options.migrations) {
  240. return this.execute({
  241. migrations: options.migrations,
  242. method: method
  243. });
  244. } else {
  245. return rest().bind(this).then(function (migrations) {
  246. let result = _bluebird.default.resolve().bind(this);
  247. if (options.to) {
  248. result = result.then(function () {
  249. // There must be a migration matching to options.to...
  250. return this._findMigration(options.to);
  251. }).then(function (migration) {
  252. // ... and it must be pending/executed.
  253. return method === 'up' ? this._isPending(migration) : this._wasExecuted(migration);
  254. });
  255. }
  256. return result.then(() => _bluebird.default.resolve(migrations));
  257. }).then(function (migrations) {
  258. if (options.from) {
  259. return this._findMigrationsFromMatch(options.from, method);
  260. } else {
  261. return migrations;
  262. }
  263. }).then(function (migrations) {
  264. return this._findMigrationsUntilMatch(options.to, migrations);
  265. }).then(function (migrationFiles) {
  266. return this._run(method, {
  267. migrations: migrationFiles
  268. });
  269. });
  270. }
  271. }
  272. /**
  273. * Lists pending/executed migrations depending on method from a given
  274. * migration excluding it.
  275. *
  276. * @param {String} from - Migration name to be searched.
  277. * @param {String} method - Either 'up' or 'down'. If method is 'up', only
  278. * pending migrations will be accepted. Otherwise only executed migrations
  279. * will be accepted.
  280. * @returns {Promise.<Migration[]>}
  281. * @private
  282. */
  283. _findMigrationsFromMatch(from, method) {
  284. // We'll fetch all migrations and work our way from start to finish
  285. return this._findMigrations().bind(this).then(migrations => {
  286. let found = false;
  287. return migrations.filter(migration => {
  288. if (migration.testFileName(from)) {
  289. found = true;
  290. return false;
  291. }
  292. return found;
  293. });
  294. }).filter(function (fromMigration) {
  295. // now check if they need to be run based on status and method
  296. return this._wasExecuted(fromMigration).then(() => {
  297. if (method === 'up') {
  298. return false;
  299. } else {
  300. return true;
  301. }
  302. }).catch(() => {
  303. if (method === 'up') {
  304. return true;
  305. } else {
  306. return false;
  307. }
  308. });
  309. });
  310. }
  311. /**
  312. * Pass message to logger if logging is enabled.
  313. *
  314. * @param {*} message - Message to be logged.
  315. */
  316. log(message) {
  317. if (this.options.logging) {
  318. this.options.logging(message);
  319. }
  320. }
  321. /**
  322. * Try to require and initialize storage.
  323. *
  324. * @returns {*|SequelizeStorage|JSONStorage|MongoDBStorage|Storage}
  325. * @private
  326. */
  327. _initStorage() {
  328. if (typeof this.options.storage !== 'string') {
  329. return this.options.storage;
  330. }
  331. let StorageClass;
  332. try {
  333. StorageClass = this._getStorageClass();
  334. } catch (e) {
  335. throw new Error('Unable to resolve the storage: ' + this.options.storage + ', ' + e);
  336. }
  337. let storage = new StorageClass(this.options.storageOptions);
  338. if (storage && storage.options && storage.options.storageOptions) {
  339. console.warn('Deprecated: Umzug Storage constructor has changed!', 'old syntax: new Storage({ storageOptions: { ... } })', 'new syntax: new Storage({ ... })', 'where ... represents the same storageOptions passed to Umzug constructor.', 'For more information: https://github.com/sequelize/umzug/pull/137');
  340. storage = new StorageClass(this.options);
  341. }
  342. return storage;
  343. }
  344. _getStorageClass() {
  345. switch (this.options.storage) {
  346. case 'none':
  347. return _Storage.default;
  348. case 'json':
  349. return _JSONStorage.default;
  350. case 'mongodb':
  351. return _MongoDBStorage.default;
  352. case 'sequelize':
  353. return _SequelizeStorage.default;
  354. default:
  355. return require(this.options.storage);
  356. }
  357. }
  358. /**
  359. * Loads all migrations in ascending order.
  360. *
  361. * @returns {Promise.<Migration[]>}
  362. * @private
  363. */
  364. _findMigrations(migrationPath) {
  365. if (Array.isArray(this.options.migrations)) {
  366. return _bluebird.default.resolve(this.options.migrations);
  367. }
  368. const isRoot = !migrationPath;
  369. if (isRoot) {
  370. migrationPath = this.options.migrations.path;
  371. }
  372. return _bluebird.default.promisify(_fs.default.readdir)(migrationPath).bind(this).map(function (file) {
  373. const filePath = _path.default.resolve(migrationPath, file);
  374. if (this.options.migrations.traverseDirectories) {
  375. if (_fs.default.lstatSync(filePath).isDirectory()) {
  376. return this._findMigrations(filePath).then(migrations => migrations);
  377. }
  378. }
  379. if (this.options.migrations.pattern.test(file)) {
  380. return new _migration2.default(filePath, this.options);
  381. }
  382. return file;
  383. }).reduce((a, b) => a.concat(b), []) // flatten the result to an array
  384. .filter(file => file instanceof _migration2.default // only care about Migration
  385. ).then(migrations => {
  386. if (isRoot) {
  387. // only sort if its root
  388. return migrations.sort((a, b) => {
  389. if (a.file > b.file) {
  390. return 1;
  391. } else if (a.file < b.file) {
  392. return -1;
  393. } else {
  394. return 0;
  395. }
  396. });
  397. }
  398. return migrations;
  399. });
  400. }
  401. /**
  402. * Gets a migration with a given name.
  403. *
  404. * @param {String} needle - Name of the migration.
  405. * @returns {Promise.<Migration>}
  406. * @private
  407. */
  408. _findMigration(needle) {
  409. return this._findMigrations().then(migrations => migrations.filter(migration => migration.testFileName(needle))[0]).then(migration => {
  410. if (migration) {
  411. return migration;
  412. } else {
  413. return _bluebird.default.reject(new Error('Unable to find migration: ' + needle));
  414. }
  415. });
  416. }
  417. /**
  418. * Checks if migration is executed. It will success if and only if there is
  419. * an executed migration with a given name.
  420. *
  421. * @param {String} _migration - Name of migration to be checked.
  422. * @returns {Promise}
  423. * @private
  424. */
  425. _wasExecuted(_migration) {
  426. return this.executed().filter(migration => migration.testFileName(_migration.file)).then(migrations => {
  427. if (migrations[0]) {
  428. return _bluebird.default.resolve();
  429. } else {
  430. return _bluebird.default.reject(new Error('Migration was not executed: ' + _migration.file));
  431. }
  432. });
  433. }
  434. /**
  435. * Checks if a list of migrations are all executed. It will success if and
  436. * only if there is an executed migration for each given name.
  437. *
  438. * @param {String[]} migrationNames - List of migration names to be checked.
  439. * @returns {Promise}
  440. * @private
  441. */
  442. _wereExecuted(migrationNames) {
  443. return _bluebird.default.resolve(migrationNames).bind(this).map(function (migration) {
  444. return this._wasExecuted(migration);
  445. });
  446. }
  447. /**
  448. * Checks if migration is pending. It will success if and only if there is
  449. * a pending migration with a given name.
  450. *
  451. * @param {String} _migration - Name of migration to be checked.
  452. * @returns {Promise}
  453. * @private
  454. */
  455. _isPending(_migration) {
  456. return this.pending().filter(migration => migration.testFileName(_migration.file)).then(migrations => {
  457. if (migrations[0]) {
  458. return _bluebird.default.resolve();
  459. } else {
  460. return _bluebird.default.reject(new Error('Migration is not pending: ' + _migration.file));
  461. }
  462. });
  463. }
  464. /**
  465. * Checks if a list of migrations are all pending. It will success if and only
  466. * if there is a pending migration for each given name.
  467. *
  468. * @param {String[]} migrationNames - List of migration names to be checked.
  469. * @returns {Promise}
  470. * @private
  471. */
  472. _arePending(migrationNames) {
  473. return _bluebird.default.resolve(migrationNames).bind(this).map(function (migration) {
  474. return this._isPending(migration);
  475. });
  476. }
  477. /**
  478. * Skip migrations in a given migration list after `to` migration.
  479. *
  480. * @param {String} to - The last one migration to be accepted.
  481. * @param {Migration[]} migrations - Migration list to be filtered.
  482. * @returns {Promise.<String>} - List of migrations before `to`.
  483. * @private
  484. */
  485. _findMigrationsUntilMatch(to, migrations) {
  486. return _bluebird.default.resolve(migrations).map(migration => migration.file).reduce((acc, migration) => {
  487. if (acc.add) {
  488. acc.migrations.push(migration);
  489. if (to && migration.indexOf(to) === 0) {
  490. // Stop adding the migrations once the final migration
  491. // has been added.
  492. acc.add = false;
  493. }
  494. }
  495. return acc;
  496. }, {
  497. migrations: [],
  498. add: true
  499. }).get('migrations');
  500. }
  501. };
  502. module.exports.migrationsList = _migrationsList.default;