db-migrate 1.0.0-beta.9 → 1.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.
- package/LICENSE +26 -0
- package/README.md +13 -5
- package/api.js +27 -2
- package/connect.js +29 -11
- package/lib/chain.js +57 -2
- package/lib/commands/check.js +3 -3
- package/lib/commands/create-migration.js +23 -3
- package/lib/commands/down.js +1 -1
- package/lib/commands/fix.js +53 -0
- package/lib/commands/generated.js +1 -0
- package/lib/commands/helper/register-events.js +3 -2
- package/lib/commands/run.js +23 -8
- package/lib/commands/seed.js +11 -45
- package/lib/commands/set-default-argv.js +17 -1
- package/lib/commands/sync.js +3 -3
- package/lib/commands/undo-seed.js +11 -51
- package/lib/commands/up.js +3 -3
- package/lib/driver/index.js +110 -41
- package/lib/executors/versioned/v2.js +190 -12
- package/lib/file.js +36 -1
- package/lib/format-error.js +119 -0
- package/lib/interface/seederInterface.js +1 -1
- package/lib/learn.js +54 -17
- package/lib/methods/v2/statedone.js +18 -0
- package/lib/methods/v2/statetravel.js +41 -35
- package/lib/methods/v2/translatestate.js +41 -9
- package/lib/state.js +459 -76
- package/lib/walker.js +195 -89
- package/package.json +18 -22
- package/CHANGELOG.md +0 -487
- package/get.js +0 -67
- package/pnpm-lock.yaml +0 -2574
- package/te.js +0 -70
package/lib/commands/sync.js
CHANGED
|
@@ -21,15 +21,15 @@ async function prepare (internals, config) {
|
|
|
21
21
|
);
|
|
22
22
|
|
|
23
23
|
if (internals.locTitle) {
|
|
24
|
-
migrator.
|
|
24
|
+
migrator.directory = path.resolve(
|
|
25
25
|
internals.argv['migrations-dir'],
|
|
26
26
|
internals.locTitle
|
|
27
27
|
);
|
|
28
28
|
} else {
|
|
29
|
-
migrator.
|
|
29
|
+
migrator.directory = path.resolve(internals.argv['migrations-dir']);
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
internals.migrationsDir = migrator.
|
|
32
|
+
internals.migrationsDir = migrator.directory;
|
|
33
33
|
|
|
34
34
|
await migrator.createMigrationsTable();
|
|
35
35
|
log.verbose('migration table created');
|
|
@@ -1,54 +1,14 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
log.info('Defaulting to running 1 down seed.');
|
|
14
|
-
internals.argv.count = 1;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
if (internals.argv._.length > 0) {
|
|
18
|
-
internals.argv.destination = internals.argv._.shift().toString();
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
index.connect(
|
|
22
|
-
{
|
|
23
|
-
config: config.getCurrent().settings,
|
|
24
|
-
internals: internals,
|
|
25
|
-
prefix: 'seed'
|
|
26
|
-
},
|
|
27
|
-
Seeder,
|
|
28
|
-
function (err, seeder) {
|
|
29
|
-
assert.ifError(err);
|
|
30
|
-
|
|
31
|
-
var seedDir =
|
|
32
|
-
internals.mode !== 'static' ? 'vcseeder-dir' : 'staticseeder-dir';
|
|
33
|
-
|
|
34
|
-
seeder.seedDir = path.resolve(internals.argv[seedDir]);
|
|
35
|
-
|
|
36
|
-
if (internals.mode === 'static') {
|
|
37
|
-
internals.onComplete(seeder, callback, {
|
|
38
|
-
stack: "Static seeders can't be undone. Use VC Seeders instead!"
|
|
39
|
-
});
|
|
40
|
-
} else {
|
|
41
|
-
seeder.createSeedsTable(function (err) {
|
|
42
|
-
if (_assert(err, callback)) {
|
|
43
|
-
seeder.down(
|
|
44
|
-
internals.argv,
|
|
45
|
-
internals.onComplete.bind(this, seeder, internals, callback)
|
|
46
|
-
);
|
|
47
|
-
}
|
|
48
|
-
});
|
|
49
|
-
}
|
|
50
|
-
}
|
|
3
|
+
/**
|
|
4
|
+
* Seeders are not part of db-migrate 1.0, their implementation was never
|
|
5
|
+
* finished. A new concept follows separately, see #687.
|
|
6
|
+
*/
|
|
7
|
+
module.exports = function () {
|
|
8
|
+
return Promise.reject(
|
|
9
|
+
new Error(
|
|
10
|
+
'Seeders are not supported by db-migrate 1.0. The unfinished seeders ' +
|
|
11
|
+
'of db-migrate 0.11 are still available there.'
|
|
12
|
+
)
|
|
51
13
|
);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
module.exports = executeUndoSeed;
|
|
14
|
+
};
|
package/lib/commands/up.js
CHANGED
|
@@ -21,15 +21,15 @@ async function prepare (internals, config) {
|
|
|
21
21
|
);
|
|
22
22
|
|
|
23
23
|
if (internals.locTitle) {
|
|
24
|
-
migrator.
|
|
24
|
+
migrator.directory = path.resolve(
|
|
25
25
|
internals.argv['migrations-dir'],
|
|
26
26
|
internals.locTitle
|
|
27
27
|
);
|
|
28
28
|
} else {
|
|
29
|
-
migrator.
|
|
29
|
+
migrator.directory = path.resolve(internals.argv['migrations-dir']);
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
internals.migrationsDir = migrator.
|
|
32
|
+
internals.migrationsDir = migrator.directory;
|
|
33
33
|
|
|
34
34
|
await migrator.createMigrationsTable();
|
|
35
35
|
log.verbose('migration table created');
|
package/lib/driver/index.js
CHANGED
|
@@ -16,6 +16,64 @@ var ShadowProto = {
|
|
|
16
16
|
}
|
|
17
17
|
};
|
|
18
18
|
|
|
19
|
+
/**
|
|
20
|
+
* Attach the failed statement to errors of the driver, so the error output
|
|
21
|
+
* can show what failed. Drivers already setting sql (mysql2) keep theirs.
|
|
22
|
+
*/
|
|
23
|
+
const annotateSql = (err, sql, hasParams) => {
|
|
24
|
+
if (err && typeof err === 'object' && err.sql === undefined) {
|
|
25
|
+
try {
|
|
26
|
+
err.sql = sql;
|
|
27
|
+
// the values are not kept, they might be sensitive
|
|
28
|
+
Object.defineProperty(err, 'sqlParams', { value: hasParams });
|
|
29
|
+
} catch (e) {}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return err;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
function trackSql (db) {
|
|
36
|
+
['runSql', 'all'].forEach(method => {
|
|
37
|
+
const original = db[method];
|
|
38
|
+
|
|
39
|
+
if (typeof original !== 'function') {
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
db[method] = function (sql, ...args) {
|
|
44
|
+
const callback =
|
|
45
|
+
typeof args[args.length - 1] === 'function' ? args.pop() : null;
|
|
46
|
+
const hasParams = args.length > 0;
|
|
47
|
+
|
|
48
|
+
if (callback) {
|
|
49
|
+
// the returned promise is left alone, the callback handles errors
|
|
50
|
+
return original.call(this, sql, ...args, function (err, ...res) {
|
|
51
|
+
return callback.call(
|
|
52
|
+
this,
|
|
53
|
+
err ? annotateSql(err, sql, hasParams) : err,
|
|
54
|
+
...res
|
|
55
|
+
);
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const ret = original.call(this, sql, ...args);
|
|
60
|
+
if (ret && typeof ret.catch === 'function') {
|
|
61
|
+
return ret.catch(err => {
|
|
62
|
+
throw annotateSql(err, sql, hasParams);
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return ret;
|
|
67
|
+
};
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Established tunnels by their config. db-migrate opens more than one
|
|
73
|
+
* connection, which all share the tunnel listening on the local port.
|
|
74
|
+
*/
|
|
75
|
+
const tunnels = {};
|
|
76
|
+
|
|
19
77
|
exports.connect = function (config, intern, callback) {
|
|
20
78
|
var driver, req;
|
|
21
79
|
let plugin = false;
|
|
@@ -37,7 +95,7 @@ exports.connect = function (config, intern, callback) {
|
|
|
37
95
|
|
|
38
96
|
if (config.driver === undefined) {
|
|
39
97
|
throw new Error(
|
|
40
|
-
'config must include a driver key
|
|
98
|
+
'config must include a driver key specifying which driver to use'
|
|
41
99
|
);
|
|
42
100
|
}
|
|
43
101
|
|
|
@@ -92,64 +150,75 @@ exports.connect = function (config, intern, callback) {
|
|
|
92
150
|
log.verbose('connecting');
|
|
93
151
|
|
|
94
152
|
var connect = function (config) {
|
|
95
|
-
driver.connect(
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
153
|
+
driver.connect(
|
|
154
|
+
// safe disconnect of our config object into userspace
|
|
155
|
+
JSON.parse(JSON.stringify(config)),
|
|
156
|
+
intern,
|
|
157
|
+
function (err, db) {
|
|
158
|
+
if (err) {
|
|
159
|
+
callback(err);
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
log.verbose('connected');
|
|
101
163
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
164
|
+
if (!global.immunity) {
|
|
165
|
+
db = Shadow.infect(db, internals, ShadowProto);
|
|
166
|
+
}
|
|
105
167
|
|
|
106
|
-
|
|
107
|
-
|
|
168
|
+
trackSql(db);
|
|
169
|
+
callback(null, db);
|
|
170
|
+
}
|
|
171
|
+
);
|
|
108
172
|
};
|
|
109
173
|
|
|
110
174
|
if (config.tunnel) {
|
|
111
|
-
|
|
175
|
+
// tunnels are provided by plugins, e.g. db-migrate-plugin-tunnel-ssh
|
|
112
176
|
var tunnelConfig = JSON.parse(JSON.stringify(config.tunnel));
|
|
113
177
|
const { tunnelType } = tunnelConfig;
|
|
178
|
+
const type = tunnelType && tunnelType !== 'ssh' ? tunnelType : 'ssh';
|
|
179
|
+
const hook = `connection:tunnel:${type}`;
|
|
114
180
|
|
|
115
181
|
if (plugins) {
|
|
116
|
-
plugin = plugins.overwrite(
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
182
|
+
plugin = plugins.overwrite(hook);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (!plugin) {
|
|
186
|
+
return callback(
|
|
187
|
+
new Error(
|
|
188
|
+
`A ${type} tunnel is configured, but no plugin provides it. ` +
|
|
189
|
+
`Install db-migrate-plugin-tunnel-${type}, e.g. ` +
|
|
190
|
+
`npm install db-migrate-plugin-tunnel-${type}`
|
|
191
|
+
)
|
|
120
192
|
);
|
|
121
193
|
}
|
|
122
194
|
|
|
123
195
|
tunnelConfig.dstHost = config.host;
|
|
124
196
|
tunnelConfig.dstPort = config.port;
|
|
125
197
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
}
|
|
198
|
+
// Point the db host/port to our local tunnel, on a copy as the config
|
|
199
|
+
// is used again for further connections
|
|
200
|
+
config = Object.assign({}, config, {
|
|
201
|
+
host: '127.0.0.1',
|
|
202
|
+
port: tunnelConfig.localPort
|
|
203
|
+
});
|
|
133
204
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
205
|
+
const key = JSON.stringify([type, tunnelConfig]);
|
|
206
|
+
if (!tunnels[key]) {
|
|
207
|
+
tunnels[key] = Promise.resolve().then(() => plugin[hook](tunnelConfig));
|
|
208
|
+
// a failed tunnel may be retried
|
|
209
|
+
tunnels[key].catch(() => delete tunnels[key]);
|
|
138
210
|
}
|
|
139
211
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
connect(config);
|
|
152
|
-
});
|
|
212
|
+
tunnels[key]
|
|
213
|
+
.then(
|
|
214
|
+
function () {
|
|
215
|
+
log.verbose(`${type} tunnel connected on port`, tunnelConfig.localPort);
|
|
216
|
+
connect(config);
|
|
217
|
+
},
|
|
218
|
+
function (err) {
|
|
219
|
+
callback(err);
|
|
220
|
+
}
|
|
221
|
+
);
|
|
153
222
|
} else {
|
|
154
223
|
connect(config);
|
|
155
224
|
}
|
|
@@ -5,10 +5,108 @@ const State = require('../../state');
|
|
|
5
5
|
const log = require('db-migrate-shared').log;
|
|
6
6
|
const Learn = require('../../learn');
|
|
7
7
|
const Chain = require('../../chain');
|
|
8
|
-
const StateTravel = require('../../methods/v2/statetravel');
|
|
8
|
+
const StateTravel = require('../../methods/v2/statetravel')();
|
|
9
|
+
const StateNoStepTravel = require('../../methods/v2/statetravel')(false);
|
|
9
10
|
const Migrate = require('../../methods/v2/migrate');
|
|
11
|
+
const StateDone = require('../../methods/v2/statedone');
|
|
10
12
|
const TranslateState = require('../../methods/v2/translatestate');
|
|
11
13
|
const AddConventions = require('../../methods/v2/conventions');
|
|
14
|
+
const util = require('util');
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* How to recover from an interrupted run of a migration, set per migration
|
|
18
|
+
* by _meta.recovery:
|
|
19
|
+
*
|
|
20
|
+
* skip: skip the steps already executed and continue with the rest
|
|
21
|
+
* rollback: revert the steps already executed and run the migration again
|
|
22
|
+
*/
|
|
23
|
+
const RECOVERY_MODES = ['skip', 'rollback'];
|
|
24
|
+
const RECOVERY_DEFAULT = 'skip';
|
|
25
|
+
|
|
26
|
+
const recoveryMode = (file, interrupted) => {
|
|
27
|
+
const name = file.name;
|
|
28
|
+
const mode = file.get()._meta.recovery || RECOVERY_DEFAULT;
|
|
29
|
+
|
|
30
|
+
if (RECOVERY_MODES.indexOf(mode) === -1) {
|
|
31
|
+
throw new Error(
|
|
32
|
+
`Invalid recovery mode "${mode}" in migration "${name}", use one of ` +
|
|
33
|
+
RECOVERY_MODES.join(', ')
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (interrupted.rollback) {
|
|
38
|
+
if (mode !== 'rollback') {
|
|
39
|
+
log.warn(
|
|
40
|
+
`[recovery] ${name}: the previous run was interrupted while rolling ` +
|
|
41
|
+
'back, continuing the rollback'
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return 'rollback';
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (mode === 'skip' && interrupted.changed) {
|
|
49
|
+
throw new Error(
|
|
50
|
+
`Migration "${name}" was interrupted at step ${interrupted.step} and ` +
|
|
51
|
+
'changed since, so the executed steps can not be skipped safely. ' +
|
|
52
|
+
"Set _meta.recovery to 'rollback' to revert them, or repair the " +
|
|
53
|
+
'state manually.'
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return mode;
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Revert the steps executed by an interrupted run. A step started but not
|
|
62
|
+
* done did not change the database, so its reverse operation is dropped.
|
|
63
|
+
*/
|
|
64
|
+
/**
|
|
65
|
+
* Keep the reverse operations of the steps executed on the database. The
|
|
66
|
+
* failed step only changed the database, if its main statement went through
|
|
67
|
+
* before failing, signaled by the driver (e.g. a foreign key of createTable
|
|
68
|
+
* failed after the table was created).
|
|
69
|
+
*/
|
|
70
|
+
const executedSteps = (internals, failed, signaled) => {
|
|
71
|
+
const done = internals.migrationDone;
|
|
72
|
+
|
|
73
|
+
internals.modSchema.s = internals.modSchema.s.filter(
|
|
74
|
+
entry =>
|
|
75
|
+
entry.n <= done || (signaled && failed > done && entry.n === failed)
|
|
76
|
+
);
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Name the instruction a migration failed at, or after if the instruction
|
|
81
|
+
* itself finished and the migration code failed afterwards.
|
|
82
|
+
*/
|
|
83
|
+
const describeFailure = (err, chain, internals) => {
|
|
84
|
+
if (!err || typeof err !== 'object' || err.instruction || chain.op === 0) {
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const after = internals.migrationDone >= chain.op;
|
|
89
|
+
err.instruction = `${after ? 'after' : 'at'} step ${chain.op} ${
|
|
90
|
+
chain.current
|
|
91
|
+
}`;
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
const recoverByRollback = async (context, driver, file, interrupted) => {
|
|
95
|
+
const internals = context.internals;
|
|
96
|
+
const pdriver = context._pdriver;
|
|
97
|
+
|
|
98
|
+
await State.rollingBack(pdriver, internals);
|
|
99
|
+
internals.modSchema.s = internals.modSchema.s.filter(
|
|
100
|
+
entry => !(entry.n > interrupted.done)
|
|
101
|
+
);
|
|
102
|
+
await TranslateState(context._driver, file, driver, internals, pdriver);
|
|
103
|
+
|
|
104
|
+
internals.unlearn = false;
|
|
105
|
+
internals.rollback = false;
|
|
106
|
+
internals.rollbackContinue = false;
|
|
107
|
+
internals.modSchema = { i: {}, c: {}, f: {}, s: [] };
|
|
108
|
+
await State.endMigration(pdriver, file, internals);
|
|
109
|
+
};
|
|
12
110
|
|
|
13
111
|
const execUnit = {
|
|
14
112
|
_extend: (context, type) => {
|
|
@@ -28,15 +126,87 @@ const execUnit = {
|
|
|
28
126
|
|
|
29
127
|
up: async function (context, driver, file) {
|
|
30
128
|
const _file = file.get();
|
|
31
|
-
const chain = new Chain(context._driver, file, driver, context.internals);
|
|
129
|
+
const chain = new Chain(context._driver, file, driver, context.internals, context._pdriver);
|
|
32
130
|
if (!_file._meta.noDefaultColumn) {
|
|
33
131
|
chain.addChain(AddConventions);
|
|
34
132
|
}
|
|
35
|
-
chain.addChain(Learn);
|
|
36
133
|
chain.addChain(StateTravel);
|
|
134
|
+
chain.addChain(Learn);
|
|
135
|
+
chain.addChain(StateNoStepTravel);
|
|
37
136
|
chain.addChain(Migrate);
|
|
137
|
+
chain.addChain(StateDone);
|
|
138
|
+
|
|
139
|
+
const interrupted = await State.startMigration(
|
|
140
|
+
context._pdriver,
|
|
141
|
+
file,
|
|
142
|
+
context.internals,
|
|
143
|
+
{ recover: true }
|
|
144
|
+
);
|
|
145
|
+
|
|
146
|
+
if (interrupted) {
|
|
147
|
+
const mode = recoveryMode(file, interrupted);
|
|
148
|
+
log.warn(
|
|
149
|
+
`[recovery] ${file.name}: the previous run was interrupted at step ` +
|
|
150
|
+
`${interrupted.step}, ${interrupted.done} steps were executed, ` +
|
|
151
|
+
`recovering by ${mode}`
|
|
152
|
+
);
|
|
153
|
+
|
|
154
|
+
if (mode === 'rollback') {
|
|
155
|
+
await recoverByRollback(context, driver, file, interrupted);
|
|
156
|
+
await State.startMigration(context._pdriver, file, context.internals);
|
|
157
|
+
} else {
|
|
158
|
+
chain.recovery = interrupted;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
context.internals.migrationDone = chain.recovery ? chain.recovery.done : 0;
|
|
38
163
|
|
|
39
|
-
|
|
164
|
+
// startMigration - needs secondary instance since we can not afford to
|
|
165
|
+
// loose state and the transaction start will include these for roll back
|
|
166
|
+
// we will disable them probably at all from DDL when the driver does not
|
|
167
|
+
// explicitly signal DDL transaction support (like crdb)
|
|
168
|
+
try {
|
|
169
|
+
await _file.migrate(chain, {
|
|
170
|
+
options: context.internals.safeOptions,
|
|
171
|
+
seedLink: context.seedLink,
|
|
172
|
+
dbm: context.internals.safeOptions.dbmigrate
|
|
173
|
+
});
|
|
174
|
+
} catch (err) {
|
|
175
|
+
// transfer last state
|
|
176
|
+
chain.transferInt();
|
|
177
|
+
executedSteps(
|
|
178
|
+
context.internals,
|
|
179
|
+
chain.op,
|
|
180
|
+
chain.udriver._counter.previousSignal() === true
|
|
181
|
+
);
|
|
182
|
+
describeFailure(err, chain, context.internals);
|
|
183
|
+
|
|
184
|
+
log.error(
|
|
185
|
+
`Migration "${file.name}" failed, rolling back: ${
|
|
186
|
+
(err && err.message) || err
|
|
187
|
+
}`
|
|
188
|
+
);
|
|
189
|
+
await State.rollingBack(context._pdriver, context.internals);
|
|
190
|
+
await execUnit.down(context, driver, file, { abort: true });
|
|
191
|
+
throw err;
|
|
192
|
+
}
|
|
193
|
+
await Promise.promisify(context.writeMigrationRecord.bind(context))(file);
|
|
194
|
+
return State.endMigration(context._pdriver, file, context.internals);
|
|
195
|
+
// end migration, same as start migration
|
|
196
|
+
},
|
|
197
|
+
|
|
198
|
+
fix: async function (context, driver, file) {
|
|
199
|
+
const _file = file.get();
|
|
200
|
+
const chain = new Chain(context._driver, file, driver, context.internals, context._pdriver);
|
|
201
|
+
if (!_file._meta.noDefaultColumn) {
|
|
202
|
+
chain.addChain(AddConventions);
|
|
203
|
+
}
|
|
204
|
+
chain.addChain(Learn);
|
|
205
|
+
chain.addChain(StateTravel);
|
|
206
|
+
|
|
207
|
+
await State.startMigration(context._pdriver, file, context.internals, {
|
|
208
|
+
op: 'fix'
|
|
209
|
+
});
|
|
40
210
|
// startMigration - needs secondary instance since we can not afford to
|
|
41
211
|
// loose state and the transaction start will include these for roll back
|
|
42
212
|
// we will disable them probably at all from DDL when the driver does not
|
|
@@ -53,23 +223,31 @@ const execUnit = {
|
|
|
53
223
|
// transfer last state
|
|
54
224
|
chain.transferInt();
|
|
55
225
|
|
|
226
|
+
describeFailure(err, chain, context.internals);
|
|
56
227
|
log.error(
|
|
57
|
-
|
|
58
|
-
|
|
228
|
+
`Migration "${file.name}" failed, rolling back: ${
|
|
229
|
+
(err && err.message) || err
|
|
230
|
+
}`
|
|
59
231
|
);
|
|
60
232
|
await execUnit.down(context, driver, file);
|
|
61
233
|
throw err;
|
|
62
234
|
}
|
|
63
|
-
await
|
|
64
|
-
|
|
235
|
+
await State.endMigration(context._pdriver, file, context.internals);
|
|
236
|
+
log.verbose(`[fix] current schema`, util.inspect(context.internals.schema, false, null, true));
|
|
65
237
|
// end migration, same as start migration
|
|
66
238
|
},
|
|
67
239
|
|
|
68
|
-
down: async function (context, driver, file) {
|
|
240
|
+
down: async function (context, driver, file, { abort } = {}) {
|
|
241
|
+
// if we get the abort signal this means we are in a rollback routine
|
|
242
|
+
// which means in turn we want to avoid reloading the state
|
|
243
|
+
if (abort !== true) {
|
|
244
|
+
await State.startMigration(context._pdriver, file, context.internals, {
|
|
245
|
+
op: 'down'
|
|
246
|
+
});
|
|
247
|
+
}
|
|
69
248
|
// start migration, see up comments
|
|
70
|
-
await
|
|
71
|
-
await
|
|
72
|
-
await State.endMigration(context._driver, file, context.internals);
|
|
249
|
+
await TranslateState(context._driver, file, driver, context.internals, context._pdriver);
|
|
250
|
+
await State.endMigration(context._pdriver, file, context.internals);
|
|
73
251
|
return Promise.promisify(context.deleteMigrationRecord.bind(context))(file);
|
|
74
252
|
// end migration, see up comments
|
|
75
253
|
}
|
package/lib/file.js
CHANGED
|
@@ -56,6 +56,12 @@ function parseTitle (name) {
|
|
|
56
56
|
|
|
57
57
|
const filesRegEx = /\.js$/;
|
|
58
58
|
|
|
59
|
+
/**
|
|
60
|
+
* Loaders of plugins for their file extensions, files without one are
|
|
61
|
+
* required.
|
|
62
|
+
*/
|
|
63
|
+
let loaders = {};
|
|
64
|
+
|
|
59
65
|
const File = function () {
|
|
60
66
|
if (arguments.length >= 3) {
|
|
61
67
|
this.title = arguments[0];
|
|
@@ -81,7 +87,27 @@ File.prototype = {
|
|
|
81
87
|
},
|
|
82
88
|
|
|
83
89
|
get: function () {
|
|
84
|
-
|
|
90
|
+
if (!this._required) {
|
|
91
|
+
let file = this.path;
|
|
92
|
+
let loader = loaders[path.extname(file).slice(1)];
|
|
93
|
+
|
|
94
|
+
// migrations recorded in the database carry no extension, require
|
|
95
|
+
// resolves its own ones, files of plugins have to be looked up
|
|
96
|
+
if (!loader) {
|
|
97
|
+
const extension = Object.keys(loaders).find(ext =>
|
|
98
|
+
fs.existsSync(`${file}.${ext}`)
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
if (extension) {
|
|
102
|
+
file = `${file}.${extension}`;
|
|
103
|
+
loader = loaders[extension];
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
this._required = loader ? loader(file) : require(file);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return this._required;
|
|
85
111
|
},
|
|
86
112
|
|
|
87
113
|
write: function (data) {
|
|
@@ -104,6 +130,7 @@ File.registerHook = function (Plugin, internals) {
|
|
|
104
130
|
filesRegEx: filesRegEx,
|
|
105
131
|
extensions: 'js'
|
|
106
132
|
};
|
|
133
|
+
loaders = {};
|
|
107
134
|
|
|
108
135
|
if (!plugin) {
|
|
109
136
|
return Promise.resolve(null);
|
|
@@ -137,6 +164,14 @@ File.registerHook = function (Plugin, internals) {
|
|
|
137
164
|
if (parser && parser.extensions) {
|
|
138
165
|
internals.parser.extensions =
|
|
139
166
|
internals.parser.extensions + '|' + parser.extensions;
|
|
167
|
+
|
|
168
|
+
// a parser may load its files itself, returning the migration
|
|
169
|
+
// module, instead of registering them for require
|
|
170
|
+
if (typeof parser.load === 'function') {
|
|
171
|
+
parser.extensions.split('|').forEach(extension => {
|
|
172
|
+
loaders[extension] = parser.load;
|
|
173
|
+
});
|
|
174
|
+
}
|
|
140
175
|
}
|
|
141
176
|
})
|
|
142
177
|
.then(function () {
|