1 | 'use strict';
|
2 |
|
3 | const path = require('path');
|
4 | const glob = require('glob');
|
5 | const fs = require('fs-extra');
|
6 |
|
7 | const JSONSchemaSequelizer = require('../lib');
|
8 |
|
9 | function fixedName(value) {
|
10 | return value
|
11 | .replace(/([A-Z])/g, (_, $1) => `_${$1}`)
|
12 | .replace(/\W+/g, '_')
|
13 | .toLowerCase();
|
14 | }
|
15 |
|
16 | module.exports = (conn, config) => {
|
17 | return Promise.resolve()
|
18 | .then(() => {
|
19 | const _cwd = process.cwd();
|
20 | const _migrations = conn.sequelize.options.migrations || {};
|
21 | const _baseDir = _migrations.directory || conn.sequelize.options.directory;
|
22 |
|
23 |
|
24 | if (!fs.existsSync(_baseDir)) {
|
25 | throw new Error(`Missing ${_baseDir} directory`);
|
26 | }
|
27 |
|
28 | const _allowed = typeof config.options.only === 'string'
|
29 | ? String(config.options.only).split(',')
|
30 | : [];
|
31 |
|
32 |
|
33 | if (Array.isArray(config.options.only)) {
|
34 | Array.prototype.push.apply(_allowed, config.options.only);
|
35 | }
|
36 |
|
37 | const _models = Object.keys(conn.models)
|
38 | .filter(x => (_allowed.length ? _allowed.indexOf(x) !== -1 : true))
|
39 | .map(x => conn.models[x]);
|
40 |
|
41 | const _logger = config.logger || {};
|
42 |
|
43 | _logger.error = _logger.error || console.error.bind(console);
|
44 | _logger.message = _logger.message || console.log.bind(console);
|
45 |
|
46 | const schemaFile = path.join(_baseDir, 'schema.js');
|
47 | const schemaJson = path.join(_baseDir, 'schema.json');
|
48 | const migrationsDir = path.join(_baseDir, 'migrations');
|
49 | const migrationsFile = path.join(migrationsDir, 'index.json');
|
50 |
|
51 | function upgrade() {
|
52 | const fixedRefs = {};
|
53 |
|
54 | Object.keys(conn.$refs).forEach(ref => {
|
55 |
|
56 | if (!_models[ref]) {
|
57 | fixedRefs[ref] = conn.$refs[ref].$schema;
|
58 | }
|
59 | });
|
60 |
|
61 | _logger.message(`write ${path.relative(_cwd, schemaJson)}`);
|
62 |
|
63 | fs.outputJsonSync(schemaJson,
|
64 | JSONSchemaSequelizer.bundle(_models, fixedRefs,
|
65 | typeof config.options.apply === 'string' && config.options.apply), { spaces: 2 });
|
66 |
|
67 | _logger.message(`${_models.length} model${_models.length === 1 ? '' : 's'} exported`);
|
68 | }
|
69 |
|
70 | function reset() {
|
71 |
|
72 | if (!fs.existsSync(schemaFile)) {
|
73 | throw new Error(`Missing ${schemaFile} file`);
|
74 | }
|
75 |
|
76 | const migrations = glob.sync('*.js', { cwd: migrationsDir });
|
77 |
|
78 | if (!_migrations.database) {
|
79 | if (config.options.create) {
|
80 | fs.outputJsonSync(migrationsFile, migrations, { spaces: 2 });
|
81 | } else {
|
82 | fs.outputFileSync(migrationsFile, '[]');
|
83 | }
|
84 | }
|
85 |
|
86 | return Promise.resolve()
|
87 | .then(() => {
|
88 | _logger.message(`read ${path.relative(_cwd, schemaFile)}`);
|
89 | })
|
90 | .then(() => {
|
91 | const instance = JSONSchemaSequelizer.migrate(conn.sequelize, require(schemaFile), true);
|
92 | const method = config.options.create ? 'up' : 'down';
|
93 |
|
94 | if (!instance[method]) {
|
95 | throw new Error(`Missing ${method}() method!`);
|
96 | }
|
97 |
|
98 | return instance[method]();
|
99 | })
|
100 | .then(() => {
|
101 | _logger.message(`${config.options.create ? 'applied' : 'reverted'} ${path.relative(_cwd, schemaFile)}`);
|
102 | });
|
103 | }
|
104 |
|
105 | function write() {
|
106 | const fulldate = [
|
107 | new Date().getFullYear(),
|
108 | `0${new Date().getMonth() + 1}`.substr(-2),
|
109 | `0${new Date().getDate() + 1}`.substr(-2),
|
110 | ].join('');
|
111 |
|
112 | const dump = fs.existsSync(schemaJson)
|
113 | ? fs.readJsonSync(schemaJson)
|
114 | : {};
|
115 |
|
116 | return JSONSchemaSequelizer.generate(dump || {}, _models, false, conn.sequelize.options.define)
|
117 | .then(results => {
|
118 |
|
119 | if (!results.length) {
|
120 | _logger.message('Without changes');
|
121 | return;
|
122 | }
|
123 |
|
124 | results.forEach((result, key) => {
|
125 |
|
126 | if (!result.code) {
|
127 | return;
|
128 | }
|
129 |
|
130 | const hourtime = [
|
131 | `0${new Date().getHours()}`.substr(-2),
|
132 | `0${new Date().getMinutes()}`.substr(-2),
|
133 | `0${new Date().getSeconds()}`.substr(-2),
|
134 | '.',
|
135 | `000${new Date().getMilliseconds()}`.substr(-3),
|
136 | ].join('');
|
137 |
|
138 | const name = typeof config.options.make === 'string'
|
139 | ? `_${fixedName(config.options.make)}`
|
140 | : `_${result.code.indexOf('createTable') > -1 ? 'create' : 'update'}_${fixedName(result.model.tableName).replace(/^_/, '')}`;
|
141 |
|
142 | const file = path.join(migrationsDir, `${fulldate}${hourtime}.${key}${name}.js`);
|
143 | const src = path.relative(_cwd, file);
|
144 |
|
145 | _logger.message(`write ${src}`);
|
146 | fs.outputFileSync(file, result.code);
|
147 | });
|
148 | });
|
149 | }
|
150 |
|
151 | function check() {
|
152 | let method = 'status';
|
153 |
|
154 | const params = {};
|
155 |
|
156 | ['up', 'down', 'prev', 'next'].forEach(key => {
|
157 |
|
158 | if (config.options[key]) {
|
159 | method = key;
|
160 |
|
161 |
|
162 | if (typeof config.options[key] === 'string') {
|
163 | params.migrations = params.migrations || [];
|
164 | params.migrations.push(config.options[key]);
|
165 | }
|
166 | }
|
167 | });
|
168 |
|
169 | ['from', 'to'].forEach(key => {
|
170 |
|
171 | if (typeof config.options[key] === 'string') {
|
172 | params[key] = config.options[key];
|
173 | }
|
174 | });
|
175 |
|
176 |
|
177 | if (Array.isArray(config.migrations) && config.migrations.length) {
|
178 | params.migrations = params.migrations || [];
|
179 | config.migrations.forEach(migration => {
|
180 | params.migrations.push(migration);
|
181 | });
|
182 | }
|
183 |
|
184 | return Promise.all([
|
185 | config.options.apply
|
186 | ? JSONSchemaSequelizer.generate({}, _models, true, conn.sequelize.options.define)
|
187 | : null,
|
188 | JSONSchemaSequelizer.migrate(conn.sequelize, {
|
189 | database: _migrations.database,
|
190 | configFile: migrationsFile,
|
191 | baseDir: migrationsDir,
|
192 | logging(message) {
|
193 | _logger.message(message);
|
194 | },
|
195 | })[method](params),
|
196 | ])
|
197 | .then(results => {
|
198 | const result = results[1];
|
199 |
|
200 |
|
201 | if (results[0]) {
|
202 | _logger.message(`write ${path.relative(_cwd, schemaFile)}`);
|
203 | fs.outputFileSync(schemaFile, results[0].code);
|
204 | }
|
205 |
|
206 | if (!Array.isArray(result)) {
|
207 |
|
208 | if (result.executed && result.executed.length === 0) {
|
209 | _logger.message('No executed migrations');
|
210 | }
|
211 |
|
212 |
|
213 | if (result.pending && result.pending.length) {
|
214 | _logger.message('Pending migrations:');
|
215 |
|
216 | result.pending.forEach(x => {
|
217 | _logger.message(`- ${x}`);
|
218 | });
|
219 | }
|
220 |
|
221 |
|
222 | if (result.pending && result.pending.length === 0) {
|
223 | _logger.message('No pending migrations');
|
224 | }
|
225 | } else if (!result.length) {
|
226 | _logger.message('No changes were made');
|
227 | } else {
|
228 | _logger.message(`${result.length} migration${
|
229 | result.length === 1 ? '' : 's'
|
230 | } ${
|
231 | result.length === 1 ? 'was' : 'were'
|
232 | } ${
|
233 | config.options.up || config.options.next ? 'applied' : 'reverted'
|
234 | }`);
|
235 | }
|
236 | });
|
237 | }
|
238 |
|
239 |
|
240 | if (config.options.create || config.options.destroy) {
|
241 | return reset();
|
242 | }
|
243 |
|
244 |
|
245 | if (config.options.apply) {
|
246 | return (upgrade(), check());
|
247 | }
|
248 |
|
249 |
|
250 | if (config.options.make) {
|
251 | return write();
|
252 | }
|
253 |
|
254 | return check();
|
255 | })
|
256 | .then(() => conn.close())
|
257 | .then(() => process.exit());
|
258 | };
|