@verdnatura/myt 1.5.23 → 1.5.25
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/README.md +2 -3
- package/myt-clean.js +77 -11
- package/myt-push.js +49 -78
- package/myt-run.js +4 -5
- package/myt-version.js +3 -3
- package/myt.js +59 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -250,7 +250,7 @@ $ myt create [-t <type>] <schema>.<name>
|
|
|
250
250
|
Cleans all already applied versions older than *maxOldVersions*.
|
|
251
251
|
|
|
252
252
|
```text
|
|
253
|
-
$ myt clean
|
|
253
|
+
$ myt clean [-p|--purge]
|
|
254
254
|
```
|
|
255
255
|
|
|
256
256
|
## Local server commands
|
|
@@ -276,8 +276,7 @@ $ myt fixtures [<remote>]
|
|
|
276
276
|
### run
|
|
277
277
|
|
|
278
278
|
Builds and starts local database server container. It only rebuilds the image
|
|
279
|
-
|
|
280
|
-
is different to today.
|
|
279
|
+
dump has been modified.
|
|
281
280
|
|
|
282
281
|
```text
|
|
283
282
|
$ myt run [-c|--ci] [-r|--random]
|
package/myt-clean.js
CHANGED
|
@@ -8,27 +8,40 @@ const path = require('path');
|
|
|
8
8
|
*/
|
|
9
9
|
class Clean extends Command {
|
|
10
10
|
static usage = {
|
|
11
|
-
description: 'Cleans old applied versions'
|
|
11
|
+
description: 'Cleans old applied versions',
|
|
12
|
+
params: {
|
|
13
|
+
purge: 'Wether to remove non-existent scripts from DB log'
|
|
14
|
+
}
|
|
12
15
|
};
|
|
13
16
|
|
|
14
17
|
static opts = {
|
|
18
|
+
alias: {
|
|
19
|
+
purge: 'p'
|
|
20
|
+
},
|
|
21
|
+
boolean: [
|
|
22
|
+
'purge'
|
|
23
|
+
],
|
|
15
24
|
default: {
|
|
16
25
|
remote: 'production'
|
|
17
26
|
}
|
|
18
27
|
};
|
|
19
28
|
|
|
20
29
|
async run(myt, opts) {
|
|
21
|
-
await myt.dbConnect();
|
|
22
|
-
const
|
|
23
|
-
const
|
|
30
|
+
const conn = await myt.dbConnect();
|
|
31
|
+
const versionDirs = await fs.readdir(opts.versionsDir);
|
|
32
|
+
const archiveDir = path.join(opts.versionsDir, '.archive');
|
|
33
|
+
|
|
34
|
+
const dbVersion = await myt.fetchDbVersion() || {};
|
|
35
|
+
const number = parseInt(dbVersion.number);
|
|
24
36
|
|
|
25
37
|
const oldVersions = [];
|
|
26
|
-
const versionDirs = await fs.readdir(opts.versionsDir);
|
|
27
38
|
for (const versionDir of versionDirs) {
|
|
28
|
-
const
|
|
29
|
-
|
|
39
|
+
const version = await myt.loadVersion(versionDir);
|
|
40
|
+
const shouldArchive = version
|
|
41
|
+
&& !version.apply
|
|
42
|
+
&& parseInt(version.number) < number;
|
|
30
43
|
|
|
31
|
-
if (
|
|
44
|
+
if (shouldArchive)
|
|
32
45
|
oldVersions.push(versionDir);
|
|
33
46
|
}
|
|
34
47
|
|
|
@@ -36,7 +49,6 @@ class Clean extends Command {
|
|
|
36
49
|
&& oldVersions.length > opts.maxOldVersions) {
|
|
37
50
|
oldVersions.splice(-opts.maxOldVersions);
|
|
38
51
|
|
|
39
|
-
const archiveDir = path.join(opts.versionsDir, '.archive');
|
|
40
52
|
if (!await fs.pathExists(archiveDir))
|
|
41
53
|
await fs.mkdir(archiveDir);
|
|
42
54
|
|
|
@@ -46,9 +58,63 @@ class Clean extends Command {
|
|
|
46
58
|
path.join(archiveDir, oldVersion)
|
|
47
59
|
);
|
|
48
60
|
|
|
49
|
-
console.log(`Old versions
|
|
61
|
+
console.log(`Old versions archived: ${oldVersions.length}`);
|
|
50
62
|
} else
|
|
51
|
-
console.log(`No versions to
|
|
63
|
+
console.log(`No versions to archive.`);
|
|
64
|
+
|
|
65
|
+
if (opts.purge) {
|
|
66
|
+
const versionDb = new VersionDb(myt, opts.versionsDir);
|
|
67
|
+
versionDb.load();
|
|
68
|
+
|
|
69
|
+
const archiveDb = new VersionDb(myt, archiveDir);
|
|
70
|
+
archiveDb.load();
|
|
71
|
+
|
|
72
|
+
const [res] = await conn.query(
|
|
73
|
+
`SELECT number, file FROM versionLog
|
|
74
|
+
WHERE code = ?
|
|
75
|
+
ORDER BY number, file`,
|
|
76
|
+
[opts.code]
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
for (const script of res) {
|
|
80
|
+
const hasVersion = await versionDb.hasScript(script);
|
|
81
|
+
const hasArchive = await archiveDb.hasScript(script);
|
|
82
|
+
|
|
83
|
+
if (!hasVersion && !hasArchive) {
|
|
84
|
+
await conn.query(
|
|
85
|
+
`DELETE FROM versionLog
|
|
86
|
+
WHERE code = ? AND number = ? AND file = ?`,
|
|
87
|
+
[opts.code, script.number, script.file]
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
class VersionDb {
|
|
96
|
+
constructor(myt, baseDir) {
|
|
97
|
+
Object.assign(this, {myt, baseDir});
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
async load() {
|
|
101
|
+
const versionMap = this.versionMap = new Map();
|
|
102
|
+
if (await fs.pathExists(this.baseDir)) {
|
|
103
|
+
const dirs = await fs.readdir(this.baseDir);
|
|
104
|
+
for (const dir of dirs) {
|
|
105
|
+
const version = this.myt.parseVersionDir(dir);
|
|
106
|
+
if (!version) continue;
|
|
107
|
+
versionMap.set(version.number, dir);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return versionMap;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
async hasScript(script) {
|
|
114
|
+
const dir = this.versionMap.get(script.number);
|
|
115
|
+
if (!dir) return false;
|
|
116
|
+
const scriptPath = path.join(this.baseDir, dir, script.file);
|
|
117
|
+
return await fs.pathExists(scriptPath);
|
|
52
118
|
}
|
|
53
119
|
}
|
|
54
120
|
|
package/myt-push.js
CHANGED
|
@@ -90,17 +90,17 @@ class Push extends Command {
|
|
|
90
90
|
|
|
91
91
|
// Get database version
|
|
92
92
|
|
|
93
|
-
const
|
|
93
|
+
const dbVersion = await myt.fetchDbVersion() || {};
|
|
94
94
|
|
|
95
95
|
console.log(
|
|
96
96
|
`Database information:`
|
|
97
|
-
+ `\n -> Version: ${
|
|
98
|
-
+ `\n -> Commit: ${
|
|
97
|
+
+ `\n -> Version: ${dbVersion.number}`
|
|
98
|
+
+ `\n -> Commit: ${dbVersion.gitCommit}`
|
|
99
99
|
);
|
|
100
100
|
|
|
101
|
-
if (!
|
|
102
|
-
|
|
103
|
-
if (!/^[0-9]*$/.test(
|
|
101
|
+
if (!dbVersion.number)
|
|
102
|
+
dbVersion.number = String('0').padStart(opts.versionDigits, '0');
|
|
103
|
+
if (!/^[0-9]*$/.test(dbVersion.number))
|
|
104
104
|
throw new Error('Wrong database version');
|
|
105
105
|
|
|
106
106
|
// Prevent push to production by mistake
|
|
@@ -142,16 +142,25 @@ class Push extends Command {
|
|
|
142
142
|
let silent = true;
|
|
143
143
|
const versionsDir = opts.versionsDir;
|
|
144
144
|
|
|
145
|
-
function logVersion(version, name, error) {
|
|
146
|
-
|
|
145
|
+
function logVersion(version, name, action, error) {
|
|
146
|
+
let actionMsg;
|
|
147
|
+
switch(action) {
|
|
148
|
+
case 'apply':
|
|
149
|
+
actionMsg = '[A]'.green;
|
|
150
|
+
break;
|
|
151
|
+
case 'ignore':
|
|
152
|
+
actionMsg = '[I]'.blue;
|
|
153
|
+
break;
|
|
154
|
+
default:
|
|
155
|
+
actionMsg = '[W]'.yellow;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
console.log('', (actionMsg + version).bold, name);
|
|
147
159
|
}
|
|
148
160
|
function logScript(type, message, error) {
|
|
149
161
|
console.log(' ', type.bold, message);
|
|
150
162
|
}
|
|
151
|
-
|
|
152
|
-
return /\.undo\.sql$/.test(script);
|
|
153
|
-
}
|
|
154
|
-
|
|
163
|
+
|
|
155
164
|
const skipFiles = new Set([
|
|
156
165
|
'README.md',
|
|
157
166
|
'.archive'
|
|
@@ -159,87 +168,49 @@ class Push extends Command {
|
|
|
159
168
|
|
|
160
169
|
if (await fs.pathExists(versionsDir)) {
|
|
161
170
|
const versionDirs = await fs.readdir(versionsDir);
|
|
162
|
-
const [[realm]] = await this.conn.query(
|
|
163
|
-
`SELECT realm
|
|
164
|
-
FROM versionConfig`
|
|
165
|
-
);
|
|
166
|
-
|
|
167
171
|
for (const versionDir of versionDirs) {
|
|
168
172
|
if (skipFiles.has(versionDir)) continue;
|
|
173
|
+
const version = await myt.loadVersion(versionDir);
|
|
169
174
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
logVersion('[?????]'.yellow, versionDir,
|
|
175
|
+
if (!version) {
|
|
176
|
+
logVersion('[?????]'.yellow, versionDir, 'warn',
|
|
173
177
|
`Wrong directory name.`
|
|
174
178
|
);
|
|
175
179
|
continue;
|
|
176
180
|
}
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
if (versionNumber.length != version.number.length) {
|
|
182
|
-
logVersion('[*****]'.gray, versionDir,
|
|
183
|
-
`Bad version length, should have ${version.number.length} characters.`
|
|
181
|
+
if (version.number.length != dbVersion.number.length) {
|
|
182
|
+
logVersion('[*****]'.gray, versionDir, 'warn'
|
|
183
|
+
`Bad version length, should have ${dbVersion.number.length} characters.`
|
|
184
184
|
);
|
|
185
185
|
continue;
|
|
186
186
|
}
|
|
187
187
|
|
|
188
|
-
const
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
const [versionLog] = await conn.query(
|
|
192
|
-
`SELECT file FROM versionLog
|
|
193
|
-
WHERE code = ?
|
|
194
|
-
AND number = ?
|
|
195
|
-
AND errorNumber IS NULL`,
|
|
196
|
-
[opts.code, versionNumber]
|
|
197
|
-
);
|
|
198
|
-
|
|
199
|
-
for (const script of scripts)
|
|
200
|
-
if (!isUndoScript(script)
|
|
201
|
-
&& versionLog.findIndex(x => x.file == script) === -1) {
|
|
202
|
-
silent = false;
|
|
203
|
-
break;
|
|
204
|
-
}
|
|
205
|
-
|
|
188
|
+
const {apply} = version;
|
|
189
|
+
if (apply) silent = false;
|
|
206
190
|
if (silent) continue;
|
|
207
|
-
logVersion(`[${versionNumber}]`.cyan, versionName);
|
|
208
191
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
if (!match) {
|
|
213
|
-
logScript('[W]'.yellow, script, `Wrong file name.`);
|
|
214
|
-
continue;
|
|
215
|
-
}
|
|
192
|
+
const action = apply ? 'apply' : 'ignore';
|
|
193
|
+
logVersion(`[${version.number}]`.cyan, version.name, action);
|
|
216
194
|
|
|
217
|
-
|
|
195
|
+
if (!apply) continue;
|
|
218
196
|
|
|
219
|
-
|
|
197
|
+
for (const script of version.scripts) {
|
|
198
|
+
const scriptFile = script.file;
|
|
199
|
+
|
|
200
|
+
if (!script.matchRegex) {
|
|
201
|
+
logScript('[W]'.yellow, scriptFile, `Wrong file name.`);
|
|
220
202
|
continue;
|
|
203
|
+
}
|
|
221
204
|
|
|
222
|
-
const
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
AND file = ?`,
|
|
227
|
-
[
|
|
228
|
-
opts.code,
|
|
229
|
-
versionNumber,
|
|
230
|
-
script
|
|
231
|
-
]
|
|
232
|
-
);
|
|
233
|
-
const apply = !row || row.errorNumber;
|
|
234
|
-
const actionMsg = apply ? '[+]'.green : '[I]'.blue;
|
|
235
|
-
|
|
236
|
-
logScript(actionMsg, script);
|
|
237
|
-
if (!apply) continue;
|
|
205
|
+
const actionMsg = script.apply ? '[+]'.green : '[I]'.blue;
|
|
206
|
+
logScript(actionMsg, scriptFile);
|
|
207
|
+
|
|
208
|
+
if (!script.apply) continue;
|
|
238
209
|
|
|
239
210
|
let err;
|
|
240
211
|
try {
|
|
241
212
|
await connExt.queryFromFile(pushConn,
|
|
242
|
-
`${
|
|
213
|
+
`${versionsDir}/${versionDir}/${scriptFile}`);
|
|
243
214
|
} catch (e) {
|
|
244
215
|
err = e;
|
|
245
216
|
}
|
|
@@ -260,8 +231,8 @@ class Push extends Command {
|
|
|
260
231
|
errorMessage = VALUES(errorMessage)`,
|
|
261
232
|
[
|
|
262
233
|
opts.code,
|
|
263
|
-
|
|
264
|
-
|
|
234
|
+
version.number,
|
|
235
|
+
scriptFile,
|
|
265
236
|
err && err.errno,
|
|
266
237
|
err && err.message
|
|
267
238
|
]
|
|
@@ -271,7 +242,7 @@ class Push extends Command {
|
|
|
271
242
|
nChanges++;
|
|
272
243
|
}
|
|
273
244
|
|
|
274
|
-
await this.updateVersion('number',
|
|
245
|
+
await this.updateVersion('number', version.number);
|
|
275
246
|
}
|
|
276
247
|
}
|
|
277
248
|
|
|
@@ -282,7 +253,7 @@ class Push extends Command {
|
|
|
282
253
|
const gitExists = await fs.pathExists(`${opts.workspace}/.git`);
|
|
283
254
|
|
|
284
255
|
let nRoutines = 0;
|
|
285
|
-
const changes = await this.changedRoutines(
|
|
256
|
+
const changes = await this.changedRoutines(dbVersion.gitCommit);
|
|
286
257
|
|
|
287
258
|
const routines = [];
|
|
288
259
|
for (const change of changes)
|
|
@@ -359,7 +330,7 @@ class Push extends Command {
|
|
|
359
330
|
|
|
360
331
|
const typeMsg = `[${change.type.abbr}]`[change.type.color];
|
|
361
332
|
console.log('',
|
|
362
|
-
(
|
|
333
|
+
(actionMsg + statusMsg).bold,
|
|
363
334
|
typeMsg.bold,
|
|
364
335
|
change.fullName
|
|
365
336
|
);
|
|
@@ -419,7 +390,7 @@ class Push extends Command {
|
|
|
419
390
|
const repo = await nodegit.Repository.open(this.opts.workspace);
|
|
420
391
|
const head = await repo.getHeadCommit();
|
|
421
392
|
|
|
422
|
-
if (head &&
|
|
393
|
+
if (head && dbVersion.gitCommit !== head.sha())
|
|
423
394
|
await this.updateVersion('gitCommit', head.sha());
|
|
424
395
|
}
|
|
425
396
|
|
package/myt-run.js
CHANGED
|
@@ -9,10 +9,9 @@ const connExt = require('./lib/conn');
|
|
|
9
9
|
const SqlString = require('sqlstring');
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
|
-
* Builds the database image and runs a container. It only rebuilds the
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* to avoid a bug with OverlayFS driver on MacOS.
|
|
12
|
+
* Builds the database image and runs a container. It only rebuilds the image
|
|
13
|
+
* when dump has been modified. Some workarounds have been used to avoid a bug
|
|
14
|
+
* with OverlayFS driver on MacOS.
|
|
16
15
|
*/
|
|
17
16
|
class Run extends Command {
|
|
18
17
|
static usage = {
|
|
@@ -140,7 +139,7 @@ class Run extends Command {
|
|
|
140
139
|
const hasTriggers = await fs.exists(`${dumpDataDir}/triggers.sql`);
|
|
141
140
|
|
|
142
141
|
Object.assign(opts, {
|
|
143
|
-
triggers: hasTriggers,
|
|
142
|
+
triggers: !hasTriggers,
|
|
144
143
|
commit: true,
|
|
145
144
|
dbConfig
|
|
146
145
|
});
|
package/myt-version.js
CHANGED
|
@@ -74,9 +74,9 @@ class Version extends Command {
|
|
|
74
74
|
const versionNames = new Set();
|
|
75
75
|
const versionDirs = await fs.readdir(opts.versionsDir);
|
|
76
76
|
for (const versionDir of versionDirs) {
|
|
77
|
-
const
|
|
78
|
-
if (!
|
|
79
|
-
versionNames.add(
|
|
77
|
+
const version = myt.parseVersionDir(versionDir);
|
|
78
|
+
if (!version) continue;
|
|
79
|
+
versionNames.add(version.name);
|
|
80
80
|
}
|
|
81
81
|
|
|
82
82
|
if (!versionName) {
|
package/myt.js
CHANGED
|
@@ -11,6 +11,8 @@ const mysql = require('mysql2/promise');
|
|
|
11
11
|
const nodegit = require('nodegit');
|
|
12
12
|
const camelToSnake = require('./lib/util').camelToSnake;
|
|
13
13
|
|
|
14
|
+
const scriptRegex = /^[0-9]{2}-[a-zA-Z0-9_]+(?:\.(?!undo)([a-zA-Z0-9_]+))?(\.undo)?\.sql$/;
|
|
15
|
+
|
|
14
16
|
class Myt {
|
|
15
17
|
static usage = {
|
|
16
18
|
description: 'Utility for database versioning',
|
|
@@ -305,6 +307,11 @@ class Myt {
|
|
|
305
307
|
`${__dirname}/assets/structure.sql`, 'utf8');
|
|
306
308
|
await conn.query(structure);
|
|
307
309
|
}
|
|
310
|
+
|
|
311
|
+
const [[realm]] = await conn.query(
|
|
312
|
+
`SELECT realm FROM versionConfig`
|
|
313
|
+
);
|
|
314
|
+
this.realm = realm;
|
|
308
315
|
}
|
|
309
316
|
|
|
310
317
|
return this.conn;
|
|
@@ -332,6 +339,58 @@ class Myt {
|
|
|
332
339
|
};
|
|
333
340
|
}
|
|
334
341
|
|
|
342
|
+
async loadVersion(versionDir) {
|
|
343
|
+
const {opts} = this;
|
|
344
|
+
|
|
345
|
+
const info = this.parseVersionDir(versionDir);
|
|
346
|
+
if (!info) return null;
|
|
347
|
+
|
|
348
|
+
const versionsDir = opts.versionsDir;
|
|
349
|
+
const scriptsDir = `${versionsDir}/${versionDir}`;
|
|
350
|
+
const scriptList = await fs.readdir(scriptsDir);
|
|
351
|
+
|
|
352
|
+
const [res] = await this.conn.query(
|
|
353
|
+
`SELECT file, errorNumber IS NOT NULL hasError
|
|
354
|
+
FROM versionLog
|
|
355
|
+
WHERE code = ?
|
|
356
|
+
AND number = ?`,
|
|
357
|
+
[opts.code, info.number]
|
|
358
|
+
);
|
|
359
|
+
const versionLog = new Map();
|
|
360
|
+
res.map(x => versionLog.set(x.file, x));
|
|
361
|
+
|
|
362
|
+
let applyVersion = false;
|
|
363
|
+
const scripts = [];
|
|
364
|
+
|
|
365
|
+
for (const file of scriptList) {
|
|
366
|
+
const match = file.match(scriptRegex);
|
|
367
|
+
if (match) {
|
|
368
|
+
const scriptRealm = match[1];
|
|
369
|
+
const isUndo = !!match[2];
|
|
370
|
+
|
|
371
|
+
if ((scriptRealm && scriptRealm !== this.realm) || isUndo)
|
|
372
|
+
continue;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
const logInfo = versionLog.get(file);
|
|
376
|
+
const apply = !logInfo || logInfo.hasError;
|
|
377
|
+
if (apply) applyVersion = true;
|
|
378
|
+
|
|
379
|
+
scripts.push({
|
|
380
|
+
file,
|
|
381
|
+
matchRegex: !!match,
|
|
382
|
+
apply
|
|
383
|
+
});
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
return {
|
|
387
|
+
number: info.number,
|
|
388
|
+
name: info.name,
|
|
389
|
+
scripts,
|
|
390
|
+
apply: applyVersion
|
|
391
|
+
};
|
|
392
|
+
}
|
|
393
|
+
|
|
335
394
|
async openRepo() {
|
|
336
395
|
const {opts} = this;
|
|
337
396
|
|