@verdnatura/myt 1.5.6 → 1.5.8
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 +10 -0
- package/exporters/event.ejs +4 -4
- package/exporters/event.js +6 -0
- package/exporters/function.ejs +5 -5
- package/exporters/function.js +4 -0
- package/exporters/procedure.ejs +4 -4
- package/exporters/trigger.js +6 -1
- package/exporters/view.ejs +1 -1
- package/exporters/view.js +4 -0
- package/lib/command.js +3 -6
- package/lib/conn.js +141 -0
- package/lib/dumper.js +83 -0
- package/lib/exporter-engine.js +2 -3
- package/lib/exporter.js +11 -65
- package/lib/repo.js +29 -0
- package/lib/util.js +3 -2
- package/myt-clean.js +1 -2
- package/myt-create.js +74 -0
- package/myt-dump.js +10 -9
- package/myt-fixtures.js +6 -5
- package/myt-init.js +0 -1
- package/myt-pull.js +57 -6
- package/myt-push.js +72 -8
- package/myt-run.js +3 -3
- package/myt-start.js +0 -1
- package/myt-version.js +1 -2
- package/myt.js +45 -337
- package/package.json +2 -2
- package/server/docker-dump.sh +1 -1
- package/template/package.json +1 -1
- package/template/remotes/docker.ini +5 -0
package/myt.js
CHANGED
|
@@ -10,52 +10,46 @@ const path = require('path');
|
|
|
10
10
|
const mysql = require('mysql2/promise');
|
|
11
11
|
const nodegit = require('nodegit');
|
|
12
12
|
const camelToSnake = require('./lib/util').camelToSnake;
|
|
13
|
-
const docker = require('./lib/docker');
|
|
14
|
-
const Command = require('./lib/command');
|
|
15
13
|
|
|
16
14
|
class Myt {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
default: {
|
|
47
|
-
workspace: process.cwd()
|
|
48
|
-
}
|
|
49
|
-
};
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
async run(CommandClass) {
|
|
15
|
+
static usage = {
|
|
16
|
+
description: 'Utility for database versioning',
|
|
17
|
+
params: {
|
|
18
|
+
remote: 'Name of remote to use',
|
|
19
|
+
workspace: 'The base directory of the project',
|
|
20
|
+
socket: 'Wether to connect to database via socket',
|
|
21
|
+
debug: 'Wether to enable debug mode',
|
|
22
|
+
version: 'Display the version number and exit',
|
|
23
|
+
help: 'Display this help message'
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
static opts = {
|
|
28
|
+
alias: {
|
|
29
|
+
remote: 'r',
|
|
30
|
+
workspace: 'w',
|
|
31
|
+
socket: 'k',
|
|
32
|
+
debug: 'd',
|
|
33
|
+
version: 'v',
|
|
34
|
+
help: 'h'
|
|
35
|
+
},
|
|
36
|
+
boolean: [
|
|
37
|
+
'debug',
|
|
38
|
+
'version',
|
|
39
|
+
'help'
|
|
40
|
+
]
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
async run(Command) {
|
|
53
44
|
console.log(
|
|
54
45
|
'Myt'.green,
|
|
55
46
|
`v${packageJson.version}`.magenta
|
|
56
47
|
);
|
|
57
48
|
|
|
58
|
-
|
|
49
|
+
let baseOpts = this.constructor.opts;
|
|
50
|
+
baseOpts.default = Object.assign(baseOpts.default || {}, {
|
|
51
|
+
workspace: process.cwd()
|
|
52
|
+
});
|
|
59
53
|
const opts = this.getopts(baseOpts);
|
|
60
54
|
|
|
61
55
|
if (opts.debug) {
|
|
@@ -68,7 +62,7 @@ class Myt {
|
|
|
68
62
|
|
|
69
63
|
try {
|
|
70
64
|
const commandName = opts._[0];
|
|
71
|
-
if (!
|
|
65
|
+
if (!Command && commandName) {
|
|
72
66
|
if (!/^[a-z]+$/.test(commandName))
|
|
73
67
|
throw new Error (`Invalid command name '${commandName}'`);
|
|
74
68
|
|
|
@@ -76,20 +70,20 @@ class Myt {
|
|
|
76
70
|
|
|
77
71
|
if (!await fs.pathExists(commandFile))
|
|
78
72
|
throw new Error (`Unknown command '${commandName}'`);
|
|
79
|
-
|
|
73
|
+
Command = require(commandFile);
|
|
80
74
|
}
|
|
81
75
|
|
|
82
|
-
if (!
|
|
83
|
-
this.showHelp(baseOpts, this.usage);
|
|
76
|
+
if (!Command) {
|
|
77
|
+
this.showHelp(baseOpts, this.constructor.usage);
|
|
84
78
|
process.exit(0);
|
|
85
79
|
}
|
|
86
80
|
|
|
87
81
|
const allOpts = Object.assign({}, baseOpts);
|
|
88
82
|
|
|
89
|
-
if (
|
|
90
|
-
for (const key in
|
|
83
|
+
if (Command.opts)
|
|
84
|
+
for (const key in Command.opts) {
|
|
91
85
|
const baseValue = baseOpts[key];
|
|
92
|
-
const cmdValue =
|
|
86
|
+
const cmdValue = Command.opts[key];
|
|
93
87
|
if (Array.isArray(baseValue))
|
|
94
88
|
allOpts[key] = baseValue.concat(cmdValue);
|
|
95
89
|
else if (typeof baseValue == 'object')
|
|
@@ -103,7 +97,7 @@ class Myt {
|
|
|
103
97
|
console.log('Command options:'.magenta, commandOpts);
|
|
104
98
|
Object.assign(opts, commandOpts);
|
|
105
99
|
|
|
106
|
-
const operandToOpt =
|
|
100
|
+
const operandToOpt = Command.usage.operand;
|
|
107
101
|
if (opts._.length >= 2 && operandToOpt)
|
|
108
102
|
opts[operandToOpt] = opts._[1];
|
|
109
103
|
|
|
@@ -111,7 +105,7 @@ class Myt {
|
|
|
111
105
|
console.log('Final options:'.magenta, opts);
|
|
112
106
|
|
|
113
107
|
if (opts.help) {
|
|
114
|
-
this.showHelp(
|
|
108
|
+
this.showHelp(Command.opts, Command.usage, commandName);
|
|
115
109
|
process.exit(0);
|
|
116
110
|
}
|
|
117
111
|
|
|
@@ -150,7 +144,7 @@ class Myt {
|
|
|
150
144
|
parameter('Remote:', opts.remote || 'local');
|
|
151
145
|
|
|
152
146
|
await this.load(opts);
|
|
153
|
-
await this.runCommand(
|
|
147
|
+
await this.runCommand(Command, opts);
|
|
154
148
|
await this.unload();
|
|
155
149
|
} catch (err) {
|
|
156
150
|
if (err.name == 'Error' && !opts.debug) {
|
|
@@ -169,9 +163,8 @@ class Myt {
|
|
|
169
163
|
process.exit();
|
|
170
164
|
}
|
|
171
165
|
|
|
172
|
-
async runCommand(
|
|
173
|
-
const command = new
|
|
174
|
-
command.opts = opts;
|
|
166
|
+
async runCommand(Command, opts) {
|
|
167
|
+
const command = new Command(this, opts);
|
|
175
168
|
return await command.run(this, opts);
|
|
176
169
|
}
|
|
177
170
|
|
|
@@ -315,70 +308,6 @@ class Myt {
|
|
|
315
308
|
};
|
|
316
309
|
}
|
|
317
310
|
|
|
318
|
-
async changedRoutines(commitSha) {
|
|
319
|
-
const repo = await this.openRepo();
|
|
320
|
-
const changes = [];
|
|
321
|
-
const changesMap = new Map();
|
|
322
|
-
|
|
323
|
-
async function pushChanges(diff) {
|
|
324
|
-
if (!diff) return;
|
|
325
|
-
const patches = await diff.patches();
|
|
326
|
-
|
|
327
|
-
for (const patch of patches) {
|
|
328
|
-
const path = patch.newFile().path();
|
|
329
|
-
const match = path.match(/^routines\/(.+)\.sql$/);
|
|
330
|
-
if (!match) continue;
|
|
331
|
-
|
|
332
|
-
let change = changesMap.get(match[1]);
|
|
333
|
-
if (!change) {
|
|
334
|
-
change = {path: match[1]};
|
|
335
|
-
changes.push(change);
|
|
336
|
-
changesMap.set(match[1], change);
|
|
337
|
-
}
|
|
338
|
-
change.mark = patch.isDeleted() ? '-' : '+';
|
|
339
|
-
}
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
const head = await repo.getHeadCommit();
|
|
343
|
-
|
|
344
|
-
if (head && commitSha) {
|
|
345
|
-
let commit;
|
|
346
|
-
let notFound;
|
|
347
|
-
|
|
348
|
-
try {
|
|
349
|
-
commit = await repo.getCommit(commitSha);
|
|
350
|
-
notFound = false;
|
|
351
|
-
} catch (err) {
|
|
352
|
-
if (err.errorFunction == 'Commit.lookup')
|
|
353
|
-
notFound = true;
|
|
354
|
-
else
|
|
355
|
-
throw err;
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
if (notFound) {
|
|
359
|
-
console.warn(`Database commit not found, trying git fetch`.yellow);
|
|
360
|
-
await repo.fetchAll();
|
|
361
|
-
commit = await repo.getCommit(commitSha);
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
const commitTree = await commit.getTree();
|
|
365
|
-
|
|
366
|
-
const headTree = await head.getTree();
|
|
367
|
-
const diff = await headTree.diff(commitTree);
|
|
368
|
-
await pushChanges(diff);
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
await pushChanges(await this.getUnstaged(repo));
|
|
372
|
-
await pushChanges(await this.getStaged(repo));
|
|
373
|
-
|
|
374
|
-
return changes.sort((a, b) => {
|
|
375
|
-
if (b.mark != a.mark)
|
|
376
|
-
return b.mark == '-' ? 1 : -1;
|
|
377
|
-
return a.path.localeCompare(b.path);
|
|
378
|
-
|
|
379
|
-
});
|
|
380
|
-
}
|
|
381
|
-
|
|
382
311
|
async openRepo() {
|
|
383
312
|
const {opts} = this;
|
|
384
313
|
|
|
@@ -388,94 +317,6 @@ class Myt {
|
|
|
388
317
|
return await nodegit.Repository.open(opts.workspace);
|
|
389
318
|
}
|
|
390
319
|
|
|
391
|
-
async getStaged(repo) {
|
|
392
|
-
const head = await repo.getHeadCommit();
|
|
393
|
-
|
|
394
|
-
try {
|
|
395
|
-
const emptyTree = '4b825dc642cb6eb9a060e54bf8d69288fbee4904';
|
|
396
|
-
const headTree = await (head
|
|
397
|
-
? head.getTree()
|
|
398
|
-
: nodegit.Tree.lookup(repo, emptyTree)
|
|
399
|
-
);
|
|
400
|
-
return await nodegit.Diff.treeToIndex(repo, headTree, null);
|
|
401
|
-
} catch (err) {
|
|
402
|
-
console.warn('Cannot fetch staged changes:', err.message);
|
|
403
|
-
}
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
async getUnstaged(repo) {
|
|
407
|
-
return await nodegit.Diff.indexToWorkdir(repo, null, {
|
|
408
|
-
flags: nodegit.Diff.OPTION.SHOW_UNTRACKED_CONTENT
|
|
409
|
-
| nodegit.Diff.OPTION.RECURSE_UNTRACKED_DIRS
|
|
410
|
-
});
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
async initDump(dumpFile) {
|
|
414
|
-
const dumpDir = this.opts.dumpDir;
|
|
415
|
-
if (!await fs.pathExists(dumpDir))
|
|
416
|
-
await fs.mkdir(dumpDir);
|
|
417
|
-
|
|
418
|
-
const dumpPath = path.join(dumpDir, dumpFile);
|
|
419
|
-
|
|
420
|
-
// FIXME: If it's called after docker.build() statement it creates an
|
|
421
|
-
// "invalid" WriteStream
|
|
422
|
-
const dumpStream = await fs.createWriteStream(dumpPath);
|
|
423
|
-
|
|
424
|
-
await docker.build(__dirname, {
|
|
425
|
-
tag: 'myt/client',
|
|
426
|
-
file: path.join(__dirname, 'server', 'Dockerfile.client')
|
|
427
|
-
}, this.opts.debug);
|
|
428
|
-
|
|
429
|
-
return dumpStream;
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
async dumpFixtures(dumpStream, tables, replace) {
|
|
433
|
-
const fixturesArgs = [
|
|
434
|
-
'--no-create-info',
|
|
435
|
-
'--skip-triggers',
|
|
436
|
-
'--skip-extended-insert',
|
|
437
|
-
'--skip-disable-keys',
|
|
438
|
-
'--skip-add-locks',
|
|
439
|
-
'--skip-set-charset',
|
|
440
|
-
'--skip-comments',
|
|
441
|
-
'--skip-tz-utc'
|
|
442
|
-
];
|
|
443
|
-
|
|
444
|
-
if (replace)
|
|
445
|
-
fixturesArgs.push('--replace');
|
|
446
|
-
|
|
447
|
-
for (const schema in tables) {
|
|
448
|
-
const escapedSchema = '`'+ schema.replace('`', '``') +'`';
|
|
449
|
-
await dumpStream.write(
|
|
450
|
-
`USE ${escapedSchema};\n`,
|
|
451
|
-
'utf8'
|
|
452
|
-
);
|
|
453
|
-
|
|
454
|
-
const args = fixturesArgs.concat([schema], tables[schema]);
|
|
455
|
-
await this.runDump('mysqldump', args, dumpStream);
|
|
456
|
-
}
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
async runDump(command, args, dumpStream) {
|
|
460
|
-
const iniPath = path.join(this.opts.subdir || '', 'remotes', this.opts.iniFile);
|
|
461
|
-
const myArgs = [
|
|
462
|
-
`--defaults-file=${iniPath}`
|
|
463
|
-
];
|
|
464
|
-
const execOptions = {
|
|
465
|
-
stdio: [
|
|
466
|
-
process.stdin,
|
|
467
|
-
dumpStream,
|
|
468
|
-
process.stderr
|
|
469
|
-
]
|
|
470
|
-
};
|
|
471
|
-
const commandArgs = [command].concat(myArgs, args);
|
|
472
|
-
await docker.run('myt/client', commandArgs, {
|
|
473
|
-
addHost: 'host.docker.internal:host-gateway',
|
|
474
|
-
volume: `${this.opts.mytDir}:/workspace`,
|
|
475
|
-
rm: true
|
|
476
|
-
}, execOptions);
|
|
477
|
-
}
|
|
478
|
-
|
|
479
320
|
showHelp(opts, usage, command) {
|
|
480
321
|
const prefix = `${'Usage:'.gray} [npx] myt`;
|
|
481
322
|
|
|
@@ -505,139 +346,6 @@ class Myt {
|
|
|
505
346
|
}
|
|
506
347
|
}
|
|
507
348
|
}
|
|
508
|
-
|
|
509
|
-
/**
|
|
510
|
-
* Executes an SQL script.
|
|
511
|
-
*
|
|
512
|
-
* @param {Connection} conn MySQL connection object
|
|
513
|
-
* @returns {Array<Result>} The resultset
|
|
514
|
-
*/
|
|
515
|
-
async queryFromFile(conn, file) {
|
|
516
|
-
const sql = await fs.readFile(file, 'utf8');
|
|
517
|
-
return await this.multiQuery(conn, sql);
|
|
518
|
-
}
|
|
519
|
-
|
|
520
|
-
/**
|
|
521
|
-
* Executes a multi-query string.
|
|
522
|
-
*
|
|
523
|
-
* @param {Connection} conn MySQL connection object
|
|
524
|
-
* @param {String} sql SQL multi-query string
|
|
525
|
-
* @returns {Array<Result>} The resultset
|
|
526
|
-
*/
|
|
527
|
-
async multiQuery(conn, sql) {
|
|
528
|
-
let results = [];
|
|
529
|
-
const stmts = this.querySplit(sql);
|
|
530
|
-
|
|
531
|
-
for (const stmt of stmts)
|
|
532
|
-
results = results.concat(await conn.query(stmt));
|
|
533
|
-
|
|
534
|
-
return results;
|
|
535
|
-
}
|
|
536
|
-
|
|
537
|
-
/**
|
|
538
|
-
* Splits an SQL muti-query into a single-query array, it does an small
|
|
539
|
-
* parse to correctly handle the DELIMITER statement.
|
|
540
|
-
*
|
|
541
|
-
* @param {Array<String>} stmts The splitted SQL statements
|
|
542
|
-
*/
|
|
543
|
-
querySplit(sql) {
|
|
544
|
-
const stmts = [];
|
|
545
|
-
let i,
|
|
546
|
-
char,
|
|
547
|
-
token,
|
|
548
|
-
escaped,
|
|
549
|
-
stmtStart;
|
|
550
|
-
|
|
551
|
-
let delimiter = ';';
|
|
552
|
-
const delimiterRe = /\s*delimiter\s+(\S+)[^\S\r\n]*(?:\r?\n|\r|$)/yi;
|
|
553
|
-
|
|
554
|
-
function begins(str) {
|
|
555
|
-
let j;
|
|
556
|
-
for (j = 0; j < str.length; j++)
|
|
557
|
-
if (sql[i + j] != str[j])
|
|
558
|
-
return false;
|
|
559
|
-
i += j;
|
|
560
|
-
return true;
|
|
561
|
-
}
|
|
562
|
-
|
|
563
|
-
for (i = 0; i < sql.length;) {
|
|
564
|
-
stmtStart = i;
|
|
565
|
-
|
|
566
|
-
delimiterRe.lastIndex = i;
|
|
567
|
-
const match = sql.match(delimiterRe);
|
|
568
|
-
if (match) {
|
|
569
|
-
delimiter = match[1];
|
|
570
|
-
i += match[0].length;
|
|
571
|
-
continue;
|
|
572
|
-
}
|
|
573
|
-
|
|
574
|
-
let delimiterFound = false;
|
|
575
|
-
while (i < sql.length) {
|
|
576
|
-
char = sql[i];
|
|
577
|
-
|
|
578
|
-
if (token) {
|
|
579
|
-
if (!escaped && begins(token.end))
|
|
580
|
-
token = null;
|
|
581
|
-
else {
|
|
582
|
-
escaped = !escaped && token.escape(char);
|
|
583
|
-
i++;
|
|
584
|
-
}
|
|
585
|
-
} else {
|
|
586
|
-
delimiterFound = begins(delimiter);
|
|
587
|
-
if (delimiterFound) break;
|
|
588
|
-
|
|
589
|
-
const tok = tokenIndex.get(char);
|
|
590
|
-
if (tok && begins(tok.start))
|
|
591
|
-
token = tok;
|
|
592
|
-
else
|
|
593
|
-
i++;
|
|
594
|
-
}
|
|
595
|
-
}
|
|
596
|
-
|
|
597
|
-
let len = i - stmtStart;
|
|
598
|
-
if (delimiterFound) len -= delimiter.length;
|
|
599
|
-
const stmt = sql.substr(stmtStart, len);
|
|
600
|
-
|
|
601
|
-
if (!/^\s*$/.test(stmt))
|
|
602
|
-
stmts.push(stmt);
|
|
603
|
-
}
|
|
604
|
-
|
|
605
|
-
return stmts;
|
|
606
|
-
}
|
|
607
|
-
}
|
|
608
|
-
|
|
609
|
-
const tokens = {
|
|
610
|
-
string: {
|
|
611
|
-
start: '\'',
|
|
612
|
-
end: '\'',
|
|
613
|
-
escape: char => char == '\'' || char == '\\'
|
|
614
|
-
},
|
|
615
|
-
quotedString: {
|
|
616
|
-
start: '"',
|
|
617
|
-
end: '"',
|
|
618
|
-
escape: char => char == '"' || char == '\\'
|
|
619
|
-
},
|
|
620
|
-
id: {
|
|
621
|
-
start: '`',
|
|
622
|
-
end: '`',
|
|
623
|
-
escape: char => char == '`'
|
|
624
|
-
},
|
|
625
|
-
multiComment: {
|
|
626
|
-
start: '/*',
|
|
627
|
-
end: '*/',
|
|
628
|
-
escape: () => false
|
|
629
|
-
},
|
|
630
|
-
singleComment: {
|
|
631
|
-
start: '-- ',
|
|
632
|
-
end: '\n',
|
|
633
|
-
escape: () => false
|
|
634
|
-
}
|
|
635
|
-
};
|
|
636
|
-
|
|
637
|
-
const tokenIndex = new Map();
|
|
638
|
-
for (const tokenId in tokens) {
|
|
639
|
-
const token = tokens[tokenId];
|
|
640
|
-
tokenIndex.set(token.start[0], token);
|
|
641
349
|
}
|
|
642
350
|
|
|
643
351
|
module.exports = Myt;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@verdnatura/myt",
|
|
3
|
-
"version": "1.5.
|
|
3
|
+
"version": "1.5.8",
|
|
4
4
|
"author": "Verdnatura Levante SL",
|
|
5
5
|
"description": "MySQL version control",
|
|
6
6
|
"license": "GPL-3.0",
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
"url": "https://github.com/verdnatura/myt.git"
|
|
13
13
|
},
|
|
14
14
|
"dependencies": {
|
|
15
|
-
"@sqltools/formatter": "^1.2.
|
|
15
|
+
"@sqltools/formatter": "^1.2.5",
|
|
16
16
|
"colors": "^1.4.0",
|
|
17
17
|
"ejs": "^3.1.6",
|
|
18
18
|
"fs-extra": "^8.1.0",
|
package/server/docker-dump.sh
CHANGED
package/template/package.json
CHANGED