@verdnatura/myt 1.5.6 → 1.5.7

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/lib/command.js CHANGED
@@ -2,12 +2,9 @@
2
2
  * Base class for Myt commands.
3
3
  */
4
4
  module.exports = class MytCommand {
5
- get usage() {
6
- return {};
7
- }
8
-
9
- get localOpts() {
10
- return {};
5
+ constructor(myt, opts) {
6
+ this.myt = myt;
7
+ this.opts = opts;
11
8
  }
12
9
 
13
10
  async run(myt, opts) {
package/lib/conn.js ADDED
@@ -0,0 +1,141 @@
1
+ const fs = require('fs-extra');
2
+
3
+ /**
4
+ * Executes an SQL script.
5
+ *
6
+ * @param {Connection} conn MySQL connection object
7
+ * @returns {Array<Result>} The resultset
8
+ */
9
+ async function queryFromFile(conn, file) {
10
+ const sql = await fs.readFile(file, 'utf8');
11
+ return await this.multiQuery(conn, sql);
12
+ }
13
+
14
+ /**
15
+ * Executes a multi-query string.
16
+ *
17
+ * @param {Connection} conn MySQL connection object
18
+ * @param {String} sql SQL multi-query string
19
+ * @returns {Array<Result>} The resultset
20
+ */
21
+ async function multiQuery(conn, sql) {
22
+ let results = [];
23
+ const stmts = this.querySplit(sql);
24
+
25
+ for (const stmt of stmts)
26
+ results = results.concat(await conn.query(stmt));
27
+
28
+ return results;
29
+ }
30
+
31
+ /**
32
+ * Splits an SQL muti-query into a single-query array, it does an small
33
+ * parse to correctly handle the DELIMITER statement.
34
+ *
35
+ * @param {Array<String>} stmts The splitted SQL statements
36
+ */
37
+ function querySplit(sql) {
38
+ const stmts = [];
39
+ let i,
40
+ char,
41
+ token,
42
+ escaped,
43
+ stmtStart;
44
+
45
+ let delimiter = ';';
46
+ const delimiterRe = /\s*delimiter\s+(\S+)[^\S\r\n]*(?:\r?\n|\r|$)/yi;
47
+
48
+ function begins(str) {
49
+ let j;
50
+ for (j = 0; j < str.length; j++)
51
+ if (sql[i + j] != str[j])
52
+ return false;
53
+ i += j;
54
+ return true;
55
+ }
56
+
57
+ for (i = 0; i < sql.length;) {
58
+ stmtStart = i;
59
+
60
+ delimiterRe.lastIndex = i;
61
+ const match = sql.match(delimiterRe);
62
+ if (match) {
63
+ delimiter = match[1];
64
+ i += match[0].length;
65
+ continue;
66
+ }
67
+
68
+ let delimiterFound = false;
69
+ while (i < sql.length) {
70
+ char = sql[i];
71
+
72
+ if (token) {
73
+ if (!escaped && begins(token.end))
74
+ token = null;
75
+ else {
76
+ escaped = !escaped && token.escape(char);
77
+ i++;
78
+ }
79
+ } else {
80
+ delimiterFound = begins(delimiter);
81
+ if (delimiterFound) break;
82
+
83
+ const tok = tokenIndex.get(char);
84
+ if (tok && begins(tok.start))
85
+ token = tok;
86
+ else
87
+ i++;
88
+ }
89
+ }
90
+
91
+ let len = i - stmtStart;
92
+ if (delimiterFound) len -= delimiter.length;
93
+ const stmt = sql.substr(stmtStart, len);
94
+
95
+ if (!/^\s*$/.test(stmt))
96
+ stmts.push(stmt);
97
+ }
98
+
99
+ return stmts;
100
+ }
101
+
102
+ const tokens = {
103
+ string: {
104
+ start: '\'',
105
+ end: '\'',
106
+ escape: char => char == '\'' || char == '\\'
107
+ },
108
+ quotedString: {
109
+ start: '"',
110
+ end: '"',
111
+ escape: char => char == '"' || char == '\\'
112
+ },
113
+ id: {
114
+ start: '`',
115
+ end: '`',
116
+ escape: char => char == '`'
117
+ },
118
+ multiComment: {
119
+ start: '/*',
120
+ end: '*/',
121
+ escape: () => false
122
+ },
123
+ singleComment: {
124
+ start: '-- ',
125
+ end: '\n',
126
+ escape: () => false
127
+ }
128
+ };
129
+
130
+ const tokenIndex = new Map();
131
+ for (const tokenId in tokens) {
132
+ const token = tokens[tokenId];
133
+ tokenIndex.set(token.start[0], token);
134
+ }
135
+
136
+ module.exports = {
137
+ queryFromFile,
138
+ multiQuery,
139
+ querySplit,
140
+ tokens
141
+ };
package/lib/dumper.js ADDED
@@ -0,0 +1,83 @@
1
+ const docker = require('./docker');
2
+ const fs = require('fs-extra');
3
+ const path = require('path');
4
+
5
+ module.exports = class Dumper {
6
+ constructor(opts) {
7
+ this.opts = opts;
8
+ }
9
+
10
+ async init(dumpFile) {
11
+ const dumpDir = this.opts.dumpDir;
12
+ if (!await fs.pathExists(dumpDir))
13
+ await fs.mkdir(dumpDir);
14
+
15
+ const dumpPath = path.join(dumpDir, dumpFile);
16
+
17
+ // FIXME: If it's called after docker.build() statement it creates an
18
+ // "invalid" WriteStream
19
+ const dumpStream = await fs.createWriteStream(dumpPath);
20
+
21
+ const buidDir = path.join(__dirname, '..',)
22
+ await docker.build(buidDir, {
23
+ tag: 'myt/client',
24
+ file: path.join(buidDir, 'server', 'Dockerfile.client')
25
+ }, this.opts.debug);
26
+
27
+ this.dumpStream = dumpStream;
28
+ }
29
+
30
+ async use(schema) {
31
+ const escapedSchema = '`'+ schema.replace('`', '``') +'`';
32
+ await this.dumpStream.write(
33
+ `USE ${escapedSchema};\n`,
34
+ 'utf8'
35
+ );
36
+ }
37
+
38
+ async dumpFixtures(tables, replace) {
39
+ const fixturesArgs = [
40
+ '--no-create-info',
41
+ '--skip-triggers',
42
+ '--skip-extended-insert',
43
+ '--skip-disable-keys',
44
+ '--skip-add-locks',
45
+ '--skip-set-charset',
46
+ '--skip-comments',
47
+ '--skip-tz-utc'
48
+ ];
49
+
50
+ if (replace)
51
+ fixturesArgs.push('--replace');
52
+
53
+ for (const schema in tables) {
54
+ await this.use(schema);
55
+ const args = fixturesArgs.concat([schema], tables[schema]);
56
+ await this.runDump('mysqldump', args, this.dumpStream);
57
+ }
58
+ }
59
+
60
+ async runDump(command, args) {
61
+ const iniPath = path.join(this.opts.subdir || '', 'remotes', this.opts.iniFile);
62
+ const myArgs = [
63
+ `--defaults-file=${iniPath}`
64
+ ];
65
+ const execOptions = {
66
+ stdio: [
67
+ process.stdin,
68
+ this.dumpStream,
69
+ process.stderr
70
+ ]
71
+ };
72
+ const commandArgs = [command].concat(myArgs, args);
73
+ await docker.run('myt/client', commandArgs, {
74
+ addHost: 'host.docker.internal:host-gateway',
75
+ volume: `${this.opts.mytDir}:/workspace`,
76
+ rm: true
77
+ }, execOptions);
78
+ }
79
+
80
+ async end() {
81
+ await this.dumpStream.end();
82
+ }
83
+ }
package/lib/repo.js ADDED
@@ -0,0 +1,29 @@
1
+ const nodegit = require('nodegit');
2
+
3
+ async function getStaged(repo) {
4
+ const head = await repo.getHeadCommit();
5
+
6
+ try {
7
+ const emptyTree = '4b825dc642cb6eb9a060e54bf8d69288fbee4904';
8
+ const headTree = await (head
9
+ ? head.getTree()
10
+ : nodegit.Tree.lookup(repo, emptyTree)
11
+ );
12
+ return await nodegit.Diff.treeToIndex(repo, headTree, null);
13
+ } catch (err) {
14
+ console.warn('Cannot fetch staged changes:', err.message);
15
+ }
16
+ }
17
+
18
+ async function getUnstaged(repo) {
19
+ const Diff = nodegit.Diff;
20
+ return await Diff.indexToWorkdir(repo, null, {
21
+ flags: Diff.OPTION.SHOW_UNTRACKED_CONTENT
22
+ | Diff.OPTION.RECURSE_UNTRACKED_DIRS
23
+ });
24
+ }
25
+
26
+ module.exports = {
27
+ getStaged,
28
+ getUnstaged
29
+ };
package/lib/util.js CHANGED
@@ -1,6 +1,7 @@
1
-
2
1
  function camelToSnake(str) {
3
2
  return str.replace(/[A-Z]/g, match => `-${match.toLowerCase()}`);
4
3
  }
5
4
 
6
- module.exports.camelToSnake = camelToSnake;
5
+ module.exports = {
6
+ camelToSnake
7
+ };
package/myt-clean.js CHANGED
@@ -1,4 +1,3 @@
1
-
2
1
  const Myt = require('./myt');
3
2
  const Command = require('./lib/command');
4
3
  const fs = require('fs-extra');
@@ -11,7 +10,7 @@ class Clean extends Command {
11
10
  description: 'Cleans old applied versions'
12
11
  };
13
12
 
14
- static localOpts = {
13
+ static opts = {
15
14
  default: {
16
15
  remote: 'production'
17
16
  }
package/myt-dump.js CHANGED
@@ -1,7 +1,7 @@
1
-
2
1
  const Myt = require('./myt');
3
2
  const Command = require('./lib/command');
4
3
  const fs = require('fs-extra');
4
+ const Dumper = require('./lib/dumper');
5
5
 
6
6
  class Dump extends Command {
7
7
  static usage = {
@@ -9,14 +9,15 @@ class Dump extends Command {
9
9
  operand: 'remote'
10
10
  };
11
11
 
12
- static localOpts = {
12
+ static opts = {
13
13
  default: {
14
14
  remote: 'production'
15
15
  }
16
16
  };
17
17
 
18
18
  async run(myt, opts) {
19
- const dumpStream = await myt.initDump('.dump.sql');
19
+ const dumper = new Dumper(opts);
20
+ await dumper.init('.dump.sql');
20
21
 
21
22
  console.log('Dumping structure.');
22
23
  let dumpArgs = [
@@ -29,10 +30,10 @@ class Dump extends Command {
29
30
  '--databases'
30
31
  ];
31
32
  dumpArgs = dumpArgs.concat(opts.schemas);
32
- await myt.runDump('docker-dump.sh', dumpArgs, dumpStream);
33
+ await dumper.runDump('docker-dump.sh', dumpArgs);
33
34
 
34
35
  console.log('Dumping fixtures.');
35
- await myt.dumpFixtures(dumpStream, opts.fixtures);
36
+ await dumper.dumpFixtures(opts.fixtures);
36
37
 
37
38
  console.log('Dumping privileges.');
38
39
  const privs = opts.privileges;
@@ -45,12 +46,12 @@ class Dump extends Command {
45
46
  ];
46
47
  if (privs.where) args.push('--where', privs.where);
47
48
  args = args.concat(['mysql'], privs.tables);
48
-
49
- await dumpStream.write('USE `mysql`;\n', 'utf8');
50
- await myt.runDump('mysqldump', args, dumpStream);
49
+
50
+ await dumper.use('mysql');
51
+ await dumper.runDump('mysqldump', args);
51
52
  }
52
53
 
53
- await dumpStream.end();
54
+ await dumper.end();
54
55
 
55
56
  console.log('Saving version.');
56
57
  await myt.dbConnect();
package/myt-fixtures.js CHANGED
@@ -1,6 +1,6 @@
1
-
2
1
  const Myt = require('./myt');
3
2
  const Command = require('./lib/command');
3
+ const Dumper = require('./lib/dumper');
4
4
 
5
5
  class Fixtures extends Command {
6
6
  static usage = {
@@ -8,16 +8,17 @@ class Fixtures extends Command {
8
8
  operand: 'remote'
9
9
  };
10
10
 
11
- static localOpts = {
11
+ static opts = {
12
12
  default: {
13
13
  remote: 'docker'
14
14
  }
15
15
  };
16
16
 
17
17
  async run(myt, opts) {
18
- const dumpStream = await myt.initDump('fixtures.sql');
19
- await myt.dumpFixtures(dumpStream, opts.localFixtures, true);
20
- await dumpStream.end();
18
+ const dumper = new Dumper(opts);
19
+ await dumper.init('fixtures.sql');
20
+ await dumper.dumpFixtures(opts.localFixtures, true);
21
+ await dumper.end();
21
22
  }
22
23
  }
23
24
 
package/myt-init.js CHANGED
@@ -1,4 +1,3 @@
1
-
2
1
  const Myt = require('./myt');
3
2
  const Command = require('./lib/command');
4
3
  const fs = require('fs-extra');
package/myt-pull.js CHANGED
@@ -1,9 +1,9 @@
1
-
2
1
  const Myt = require('./myt');
3
2
  const Command = require('./lib/command');
4
3
  const fs = require('fs-extra');
5
4
  const nodegit = require('nodegit');
6
5
  const ExporterEngine = require('./lib/exporter-engine');
6
+ const repoExt = require('./lib/repo');
7
7
 
8
8
  class Pull extends Command {
9
9
  static usage = {
@@ -17,7 +17,7 @@ class Pull extends Command {
17
17
  operand: 'remote'
18
18
  };
19
19
 
20
- static localOpts = {
20
+ static opts = {
21
21
  alias: {
22
22
  force: 'f',
23
23
  checkout: 'c',
@@ -52,14 +52,14 @@ class Pull extends Command {
52
52
 
53
53
  // Check for unstaged changes
54
54
 
55
- const unstagedDiff = await myt.getUnstaged(repo);
55
+ const unstagedDiff = await repoExt.getUnstaged(repo);
56
56
 
57
57
  if (await hasChanges(unstagedDiff))
58
58
  throw new Error('You have unstaged changes, save them before pull');
59
59
 
60
60
  // Check for staged changes
61
61
 
62
- const stagedDiff = await myt.getStaged(repo);
62
+ const stagedDiff = await repoExt.getStaged(repo);
63
63
 
64
64
  if (await hasChanges(stagedDiff))
65
65
  throw new Error('You have staged changes, save them before pull');
package/myt-push.js CHANGED
@@ -1,9 +1,10 @@
1
-
2
1
  const Myt = require('./myt');
3
2
  const Command = require('./lib/command');
4
3
  const fs = require('fs-extra');
5
4
  const nodegit = require('nodegit');
6
5
  const ExporterEngine = require('./lib/exporter-engine');
6
+ const connExt = require('./lib/conn');
7
+ const repoExt = require('./lib/repo');
7
8
 
8
9
  /**
9
10
  * Pushes changes to remote.
@@ -20,7 +21,7 @@ class Push extends Command {
20
21
  operand: 'remote'
21
22
  };
22
23
 
23
- static localOpts = {
24
+ static opts = {
24
25
  alias: {
25
26
  force: 'f',
26
27
  commit: 'c',
@@ -217,7 +218,7 @@ class Push extends Command {
217
218
 
218
219
  let err;
219
220
  try {
220
- await myt.queryFromFile(pushConn,
221
+ await connExt.queryFromFile(pushConn,
221
222
  `${scriptsDir}/${script}`);
222
223
  } catch (e) {
223
224
  err = e;
@@ -261,7 +262,7 @@ class Push extends Command {
261
262
  const gitExists = await fs.pathExists(`${opts.workspace}/.git`);
262
263
 
263
264
  let nRoutines = 0;
264
- let changes = await myt.changedRoutines(version.gitCommit);
265
+ let changes = await this.changedRoutines(version.gitCommit);
265
266
  changes = this.parseChanges(changes);
266
267
 
267
268
  const routines = [];
@@ -334,7 +335,7 @@ class Push extends Command {
334
335
  if (change.type.name === 'VIEW')
335
336
  await pushConn.query(`USE ${scapedSchema}`);
336
337
 
337
- await myt.multiQuery(pushConn, newSql);
338
+ await connExt.multiQuery(pushConn, newSql);
338
339
 
339
340
  if (change.isRoutine) {
340
341
  await conn.query(
@@ -416,6 +417,68 @@ class Push extends Command {
416
417
  );
417
418
  }
418
419
 
420
+ async changedRoutines(commitSha) {
421
+ const repo = await this.myt.openRepo();
422
+ const changes = [];
423
+ const changesMap = new Map();
424
+
425
+ async function pushChanges(diff) {
426
+ if (!diff) return;
427
+ const patches = await diff.patches();
428
+
429
+ for (const patch of patches) {
430
+ const path = patch.newFile().path();
431
+ const match = path.match(/^routines\/(.+)\.sql$/);
432
+ if (!match) continue;
433
+
434
+ let change = changesMap.get(match[1]);
435
+ if (!change) {
436
+ change = {path: match[1]};
437
+ changes.push(change);
438
+ changesMap.set(match[1], change);
439
+ }
440
+ change.mark = patch.isDeleted() ? '-' : '+';
441
+ }
442
+ }
443
+
444
+ const head = await repo.getHeadCommit();
445
+
446
+ if (head && commitSha) {
447
+ let commit;
448
+ let notFound;
449
+
450
+ try {
451
+ commit = await repo.getCommit(commitSha);
452
+ notFound = false;
453
+ } catch (err) {
454
+ if (err.errorFunction == 'Commit.lookup')
455
+ notFound = true;
456
+ else
457
+ throw err;
458
+ }
459
+
460
+ if (notFound) {
461
+ console.warn(`Database commit not found, trying git fetch`.yellow);
462
+ await repo.fetchAll();
463
+ commit = await repo.getCommit(commitSha);
464
+ }
465
+
466
+ const commitTree = await commit.getTree();
467
+
468
+ const headTree = await head.getTree();
469
+ const diff = await headTree.diff(commitTree);
470
+ await pushChanges(diff);
471
+ }
472
+
473
+ await pushChanges(await repoExt.getUnstaged(repo));
474
+ await pushChanges(await repoExt.getStaged(repo));
475
+
476
+ return changes.sort((a, b) => {
477
+ if (b.mark != a.mark)
478
+ return b.mark == '-' ? 1 : -1;
479
+ return a.path.localeCompare(b.path);
480
+ });
481
+ }
419
482
  }
420
483
 
421
484
  const typeMap = {
package/myt-run.js CHANGED
@@ -1,4 +1,3 @@
1
-
2
1
  const Myt = require('./myt');
3
2
  const Command = require('./lib/command');
4
3
  const Push = require('./myt-push');
@@ -6,6 +5,7 @@ const docker = require('./lib/docker');
6
5
  const fs = require('fs-extra');
7
6
  const path = require('path');
8
7
  const Server = require('./lib/server');
8
+ const connExt = require('./lib/conn');
9
9
 
10
10
  /**
11
11
  * Builds the database image and runs a container. It only rebuilds the
@@ -22,7 +22,7 @@ class Run extends Command {
22
22
  }
23
23
  };
24
24
 
25
- static localOpts = {
25
+ static opts = {
26
26
  alias: {
27
27
  ci: 'c',
28
28
  random: 'r'
@@ -144,7 +144,7 @@ class Run extends Command {
144
144
 
145
145
  const triggersDir = await fs.readdir(triggersPath);
146
146
  for (const triggerFile of triggersDir)
147
- await myt.queryFromFile(conn, `${triggersPath}/${triggerFile}`);
147
+ await connExt.queryFromFile(conn, `${triggersPath}/${triggerFile}`);
148
148
  }
149
149
 
150
150
  return server;
package/myt-start.js CHANGED
@@ -1,4 +1,3 @@
1
-
2
1
  const Myt = require('./myt');
3
2
  const Command = require('./lib/command');
4
3
  const Container = require('./lib/docker').Container;
package/myt-version.js CHANGED
@@ -1,4 +1,3 @@
1
-
2
1
  const Myt = require('./myt');
3
2
  const Command = require('./lib/command');
4
3
  const fs = require('fs-extra');
@@ -15,7 +14,7 @@ class Version extends Command {
15
14
  operand: 'name'
16
15
  };
17
16
 
18
- static localOpts = {
17
+ static opts = {
19
18
  alias: {
20
19
  name: 'n'
21
20
  },
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
- get usage() {
18
- return {
19
- description: 'Utility for database versioning',
20
- params: {
21
- remote: 'Name of remote to use',
22
- workspace: 'The base directory of the project',
23
- socket: 'Wether to connect to database via socket',
24
- debug: 'Wether to enable debug mode',
25
- version: 'Display the version number and exit',
26
- help: 'Display this help message'
27
- }
28
- };
29
- }
30
-
31
- get localOpts() {
32
- return {
33
- alias: {
34
- remote: 'r',
35
- workspace: 'w',
36
- socket: 'k',
37
- debug: 'd',
38
- version: 'v',
39
- help: 'h'
40
- },
41
- boolean: [
42
- 'debug',
43
- 'version',
44
- 'help'
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
- const baseOpts = this.localOpts;
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 (!CommandClass && commandName) {
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
- CommandClass = require(commandFile);
73
+ Command = require(commandFile);
80
74
  }
81
75
 
82
- if (!CommandClass) {
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 (CommandClass.localOpts)
90
- for (const key in CommandClass.localOpts) {
83
+ if (Command.opts)
84
+ for (const key in Command.opts) {
91
85
  const baseValue = baseOpts[key];
92
- const cmdValue = CommandClass.localOpts[key];
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 = CommandClass.usage.operand;
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(CommandClass.localOpts, CommandClass.usage, commandName);
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(CommandClass, opts);
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(CommandClass, opts) {
173
- const command = new CommandClass();
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.6",
3
+ "version": "1.5.7",
4
4
  "author": "Verdnatura Levante SL",
5
5
  "description": "MySQL version control",
6
6
  "license": "GPL-3.0",
@@ -1,4 +1,4 @@
1
1
  #!/bin/bash
2
2
 
3
3
  # FIXME: It can corrupt data
4
- mysqldump $@ | sed 's/ AUTO_INCREMENT=[0-9]* //g'
4
+ mysqldump $@ | sed 's/ AUTO_INCREMENT=[0-9]*//g'
@@ -8,6 +8,6 @@
8
8
  "type": "git"
9
9
  },
10
10
  "dependencies": {
11
- "@verdnatura/myt": "^1.5.6"
11
+ "@verdnatura/myt": "^1.5.7"
12
12
  }
13
13
  }
@@ -0,0 +1,5 @@
1
+ [client]
2
+ host = host.docker.internal
3
+ port = 3306
4
+ user = root
5
+ password = root