@verdnatura/myt 1.5.17 → 1.5.19
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/.dockerignore +2 -2
- package/LICENSE +17 -17
- package/README.md +278 -278
- package/assets/db.ini +5 -5
- package/assets/myt.default.yml +11 -11
- package/assets/structure.sql +24 -24
- package/cli.js +4 -4
- package/exporters/event.ejs +18 -18
- package/exporters/event.js +31 -31
- package/exporters/event.sql +19 -19
- package/exporters/function.ejs +17 -17
- package/exporters/function.js +27 -27
- package/exporters/function.sql +16 -16
- package/exporters/procedure.ejs +11 -11
- package/exporters/procedure.js +23 -23
- package/exporters/procedure.sql +14 -14
- package/exporters/trigger.ejs +4 -4
- package/exporters/trigger.js +10 -10
- package/exporters/trigger.sql +13 -13
- package/exporters/view.ejs +7 -7
- package/exporters/view.js +29 -29
- package/exporters/view.sql +12 -12
- package/lib/command.js +13 -13
- package/lib/conn.js +141 -141
- package/lib/docker.js +133 -133
- package/lib/dumper.js +83 -83
- package/lib/exporter-engine.js +112 -112
- package/lib/exporter.js +86 -86
- package/lib/repo.js +29 -29
- package/lib/server.js +71 -71
- package/lib/util.js +7 -7
- package/myt-clean.js +58 -58
- package/myt-create.js +74 -74
- package/myt-dump.js +75 -75
- package/myt-fixtures.js +29 -29
- package/myt-init.js +36 -36
- package/myt-pull.js +183 -183
- package/myt-push.js +568 -562
- package/myt-run.js +177 -177
- package/myt-start.js +54 -54
- package/myt-version.js +218 -218
- package/myt.js +361 -361
- package/package.json +41 -41
- package/server/Dockerfile.base +1 -1
- package/server/Dockerfile.client +14 -14
- package/server/Dockerfile.dump +16 -16
- package/server/Dockerfile.server +25 -25
- package/server/docker-dump.sh +4 -4
- package/server/docker-fixtures.sh +4 -4
- package/server/docker-import.sh +6 -6
- package/server/docker-init.sh +20 -20
- package/server/docker-start.sh +10 -10
- package/server/docker.cnf +9 -9
- package/template/dump/Dockerfile.base +1 -1
- package/template/dump/afterDump.sql +1 -1
- package/template/dump/beforeDump.sql +1 -1
- package/template/dump/fixtures.sql +1 -1
- package/template/myt.config.yml +11 -11
- package/template/package.json +13 -13
- package/template/remotes/docker.ini +4 -4
- package/template/remotes/local.ini +5 -5
- package/template/routines/README.md +1 -1
- package/template/versions/00001-firstVersion/00-firstScript.sql +1 -1
- package/template/versions/README.md +1 -1
package/myt.js
CHANGED
|
@@ -1,361 +1,361 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
require('require-yaml');
|
|
4
|
-
require('colors');
|
|
5
|
-
const getopts = require('getopts');
|
|
6
|
-
const packageJson = require('./package.json');
|
|
7
|
-
const fs = require('fs-extra');
|
|
8
|
-
const ini = require('ini');
|
|
9
|
-
const path = require('path');
|
|
10
|
-
const mysql = require('mysql2/promise');
|
|
11
|
-
const nodegit = require('nodegit');
|
|
12
|
-
const camelToSnake = require('./lib/util').camelToSnake;
|
|
13
|
-
|
|
14
|
-
class Myt {
|
|
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
|
-
debug: 'Wether to enable debug mode',
|
|
21
|
-
version: 'Display the version number and exit',
|
|
22
|
-
help: 'Display this help message'
|
|
23
|
-
}
|
|
24
|
-
};
|
|
25
|
-
|
|
26
|
-
static opts = {
|
|
27
|
-
alias: {
|
|
28
|
-
remote: 'r',
|
|
29
|
-
workspace: 'w',
|
|
30
|
-
debug: 'd',
|
|
31
|
-
version: 'v',
|
|
32
|
-
help: 'h'
|
|
33
|
-
},
|
|
34
|
-
boolean: [
|
|
35
|
-
'debug',
|
|
36
|
-
'version',
|
|
37
|
-
'help'
|
|
38
|
-
]
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
async run(Command) {
|
|
42
|
-
console.log(
|
|
43
|
-
'Myt'.green,
|
|
44
|
-
`v${packageJson.version}`.magenta
|
|
45
|
-
);
|
|
46
|
-
this.packageJson = packageJson;
|
|
47
|
-
|
|
48
|
-
let baseOpts = this.constructor.opts;
|
|
49
|
-
baseOpts.default = Object.assign(baseOpts.default || {}, {
|
|
50
|
-
workspace: process.cwd()
|
|
51
|
-
});
|
|
52
|
-
const opts = this.getopts(baseOpts);
|
|
53
|
-
|
|
54
|
-
if (opts.debug) {
|
|
55
|
-
console.warn('Debug mode enabled.'.yellow);
|
|
56
|
-
console.log('Global options:'.magenta, opts);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
if (opts.version)
|
|
60
|
-
process.exit(0);
|
|
61
|
-
|
|
62
|
-
try {
|
|
63
|
-
const commandName = opts._[0];
|
|
64
|
-
if (!Command && commandName) {
|
|
65
|
-
if (!/^[a-z]+$/.test(commandName))
|
|
66
|
-
throw new Error (`Invalid command name '${commandName}'`);
|
|
67
|
-
|
|
68
|
-
const commandFile = path.join(__dirname, `myt-${commandName}.js`);
|
|
69
|
-
|
|
70
|
-
if (!await fs.pathExists(commandFile))
|
|
71
|
-
throw new Error (`Unknown command '${commandName}'`);
|
|
72
|
-
Command = require(commandFile);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
if (!Command) {
|
|
76
|
-
this.showHelp(baseOpts, this.constructor.usage);
|
|
77
|
-
process.exit(0);
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
const allOpts = Object.assign({}, baseOpts);
|
|
81
|
-
|
|
82
|
-
if (Command.opts)
|
|
83
|
-
for (const key in Command.opts) {
|
|
84
|
-
const baseValue = baseOpts[key];
|
|
85
|
-
const cmdValue = Command.opts[key];
|
|
86
|
-
if (Array.isArray(baseValue))
|
|
87
|
-
allOpts[key] = baseValue.concat(cmdValue);
|
|
88
|
-
else if (typeof baseValue == 'object')
|
|
89
|
-
allOpts[key] = Object.assign({}, baseValue, cmdValue);
|
|
90
|
-
else
|
|
91
|
-
allOpts[key] = cmdValue;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
const commandOpts = this.getopts(allOpts);
|
|
95
|
-
if (opts.debug)
|
|
96
|
-
console.log('Command options:'.magenta, commandOpts);
|
|
97
|
-
Object.assign(opts, commandOpts);
|
|
98
|
-
|
|
99
|
-
const operandToOpt = Command.usage.operand;
|
|
100
|
-
if (opts._.length >= 2 && operandToOpt)
|
|
101
|
-
opts[operandToOpt] = opts._[1];
|
|
102
|
-
|
|
103
|
-
if (opts.debug)
|
|
104
|
-
console.log('Final options:'.magenta, opts);
|
|
105
|
-
|
|
106
|
-
if (opts.help) {
|
|
107
|
-
this.showHelp(Command.opts, Command.usage, commandName);
|
|
108
|
-
process.exit(0);
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
// Check version
|
|
112
|
-
|
|
113
|
-
let depVersion;
|
|
114
|
-
const versionRegex = /^[^~]?([0-9]+)\.([0-9]+).([0-9]+)$/;
|
|
115
|
-
const wsPackageFile = path.join(opts.workspace, 'package.json');
|
|
116
|
-
|
|
117
|
-
if (await fs.pathExists(wsPackageFile)) {
|
|
118
|
-
const wsPackageJson = require(wsPackageFile);
|
|
119
|
-
try {
|
|
120
|
-
depVersion = wsPackageJson
|
|
121
|
-
.dependencies[packageJson.name]
|
|
122
|
-
.match(versionRegex);
|
|
123
|
-
} catch (e) {}
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
if (depVersion) {
|
|
127
|
-
const myVersion = packageJson.version.match(versionRegex);
|
|
128
|
-
|
|
129
|
-
const isSameVersion =
|
|
130
|
-
depVersion[1] === myVersion[1] &&
|
|
131
|
-
depVersion[2] === myVersion[2];
|
|
132
|
-
if (!isSameVersion)
|
|
133
|
-
throw new Error(`Myt version differs a lot from package.json, please run 'npm i' first to install the proper version.`);
|
|
134
|
-
|
|
135
|
-
const isSameMinor = depVersion[3] === myVersion[3];
|
|
136
|
-
if (!isSameMinor)
|
|
137
|
-
console.warn(`Warning! Myt minor version differs from package.json, maybe you shoud run 'npm i' to install the proper version.`.yellow);
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
// Load method
|
|
141
|
-
|
|
142
|
-
parameter('Workspace:', opts.workspace);
|
|
143
|
-
parameter('Remote:', opts.remote || 'local');
|
|
144
|
-
|
|
145
|
-
await this.load(opts);
|
|
146
|
-
await this.runCommand(Command, opts);
|
|
147
|
-
await this.unload();
|
|
148
|
-
} catch (err) {
|
|
149
|
-
if (err.name == 'Error' && !opts.debug) {
|
|
150
|
-
console.error('Error:'.gray, err.message.red);
|
|
151
|
-
console.log(`You can get more details about the error by passing the 'debug' option.`.yellow);
|
|
152
|
-
} else
|
|
153
|
-
console.log(err.stack.magenta);
|
|
154
|
-
|
|
155
|
-
process.exit(1);
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
function parameter(parameter, value) {
|
|
159
|
-
console.log(parameter.gray, (value || 'null').blue);
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
process.exit();
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
async runCommand(Command, opts) {
|
|
166
|
-
const command = new Command(this, opts);
|
|
167
|
-
return await command.run(this, opts);
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
async load(opts) {
|
|
171
|
-
// Configuration file
|
|
172
|
-
|
|
173
|
-
const config = require(`${__dirname}/assets/myt.default.yml`);
|
|
174
|
-
|
|
175
|
-
const configFile = 'myt.config.yml';
|
|
176
|
-
const configPath = path.join(opts.workspace, configFile);
|
|
177
|
-
if (await fs.pathExists(configPath))
|
|
178
|
-
Object.assign(config, require(configPath));
|
|
179
|
-
|
|
180
|
-
Object.assign(opts, config);
|
|
181
|
-
opts.configFile = configFile;
|
|
182
|
-
|
|
183
|
-
if (!opts.mytDir)
|
|
184
|
-
opts.mytDir = path.join(opts.workspace, opts.subdir || '');
|
|
185
|
-
|
|
186
|
-
opts.routinesDir = path.join(opts.mytDir, 'routines');
|
|
187
|
-
opts.versionsDir = path.join(opts.mytDir, 'versions');
|
|
188
|
-
opts.dumpDir = path.join(opts.mytDir, 'dump');
|
|
189
|
-
|
|
190
|
-
// Database configuration
|
|
191
|
-
|
|
192
|
-
let iniDir = path.join(__dirname, 'assets');
|
|
193
|
-
let iniFile = 'db.ini';
|
|
194
|
-
|
|
195
|
-
if (opts.remote) {
|
|
196
|
-
iniDir = `${opts.mytDir}/remotes`;
|
|
197
|
-
iniFile = `${opts.remote}.ini`;
|
|
198
|
-
}
|
|
199
|
-
const iniPath = path.join(iniDir, iniFile);
|
|
200
|
-
|
|
201
|
-
if (!await fs.pathExists(iniPath))
|
|
202
|
-
throw new Error(`Database config file not found: ${iniPath}`);
|
|
203
|
-
|
|
204
|
-
let dbConfig;
|
|
205
|
-
try {
|
|
206
|
-
const iniData = ini.parse(await fs.readFile(iniPath, 'utf8')).client;
|
|
207
|
-
const iniConfig = {};
|
|
208
|
-
for (const key in iniData) {
|
|
209
|
-
const value = iniData[key];
|
|
210
|
-
const newKey = key.replace(/-/g, '_');
|
|
211
|
-
iniConfig[newKey] = value !== undefined ? value : true;
|
|
212
|
-
}
|
|
213
|
-
dbConfig = {
|
|
214
|
-
host: iniConfig.host,
|
|
215
|
-
port: iniConfig.port,
|
|
216
|
-
user: iniConfig.user,
|
|
217
|
-
password: iniConfig.password,
|
|
218
|
-
multipleStatements: true
|
|
219
|
-
};
|
|
220
|
-
if (iniConfig.enable_cleartext_plugin) {
|
|
221
|
-
dbConfig.authPlugins = {
|
|
222
|
-
mysql_clear_password() {
|
|
223
|
-
return () => iniConfig.password + '\0';
|
|
224
|
-
}
|
|
225
|
-
};
|
|
226
|
-
}
|
|
227
|
-
if (iniConfig.ssl_ca) {
|
|
228
|
-
dbConfig.ssl = {
|
|
229
|
-
ca: await fs.readFile(`${opts.mytDir}/${iniConfig.ssl_ca}`),
|
|
230
|
-
rejectUnauthorized: iniConfig.ssl_verify_server_cert != undefined
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
if (iniConfig.socket) {
|
|
234
|
-
dbConfig.socketPath = iniConfig.socket;
|
|
235
|
-
}
|
|
236
|
-
} catch(err) {
|
|
237
|
-
const newErr = Error(`Cannot process the ini file, check that the syntax is correct: ${iniPath}`);
|
|
238
|
-
newErr.stack += `\nCaused by: ${err.stack}`;
|
|
239
|
-
throw newErr;
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
Object.assign(opts, {
|
|
243
|
-
iniFile,
|
|
244
|
-
dbConfig
|
|
245
|
-
});
|
|
246
|
-
this.opts = opts;
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
async unload() {
|
|
250
|
-
if (this.conn)
|
|
251
|
-
await this.conn.end();
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
getopts(opts) {
|
|
255
|
-
const argv = process.argv.slice(2);
|
|
256
|
-
const values = getopts(argv, opts);
|
|
257
|
-
const cleanValues = {};
|
|
258
|
-
for (const opt in values)
|
|
259
|
-
if (opt.length > 1 || opt == '_')
|
|
260
|
-
cleanValues[opt] = values[opt];
|
|
261
|
-
return cleanValues;
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
async dbConnect() {
|
|
265
|
-
const {opts} = this;
|
|
266
|
-
|
|
267
|
-
if (!this.conn) {
|
|
268
|
-
const conn = this.conn = await this.createConnection();
|
|
269
|
-
|
|
270
|
-
const [[schema]] = await conn.query(
|
|
271
|
-
`SHOW DATABASES LIKE ?`, [opts.versionSchema]
|
|
272
|
-
);
|
|
273
|
-
|
|
274
|
-
if (!schema)
|
|
275
|
-
await conn.query(`CREATE DATABASE ??`, [opts.versionSchema]);
|
|
276
|
-
await conn.query(`USE ??`, [opts.versionSchema]);
|
|
277
|
-
|
|
278
|
-
const [[res]] = await conn.query(
|
|
279
|
-
`SELECT COUNT(*) > 0 tableExists
|
|
280
|
-
FROM information_schema.tables
|
|
281
|
-
WHERE TABLE_SCHEMA = ?
|
|
282
|
-
AND TABLE_NAME = 'version'`,
|
|
283
|
-
[opts.versionSchema]
|
|
284
|
-
);
|
|
285
|
-
|
|
286
|
-
if (!res.tableExists) {
|
|
287
|
-
const structure = await fs.readFile(
|
|
288
|
-
`${__dirname}/assets/structure.sql`, 'utf8');
|
|
289
|
-
await conn.query(structure);
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
return this.conn;
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
async createConnection() {
|
|
297
|
-
return await mysql.createConnection(this.opts.dbConfig);
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
async fetchDbVersion() {
|
|
301
|
-
const [[version]] = await this.conn.query(
|
|
302
|
-
`SELECT number, gitCommit
|
|
303
|
-
FROM version WHERE code = ?`,
|
|
304
|
-
[this.opts.code]
|
|
305
|
-
);
|
|
306
|
-
return version;
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
parseVersionDir(versionDir) {
|
|
310
|
-
const match = versionDir.match(/^([0-9]+)-([a-zA-Z0-9]+)?$/);
|
|
311
|
-
if (!match) return null;
|
|
312
|
-
return {
|
|
313
|
-
number: match[1],
|
|
314
|
-
name: match[2]
|
|
315
|
-
};
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
async openRepo() {
|
|
319
|
-
const {opts} = this;
|
|
320
|
-
|
|
321
|
-
if (!await fs.pathExists(`${opts.workspace}/.git`))
|
|
322
|
-
throw new Error ('Git not initialized');
|
|
323
|
-
|
|
324
|
-
return await nodegit.Repository.open(opts.workspace);
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
showHelp(opts, usage, command) {
|
|
328
|
-
const prefix = `${'Usage:'.gray} [npx] myt`;
|
|
329
|
-
|
|
330
|
-
if (command) {
|
|
331
|
-
let log = [prefix, command.blue];
|
|
332
|
-
if (usage.operand) log.push(`[<${usage.operand}>]`);
|
|
333
|
-
if (opts) log.push('[<options>]');
|
|
334
|
-
console.log(log.join(' '))
|
|
335
|
-
} else
|
|
336
|
-
console.log(`${prefix} [<options>] ${'<command>'.blue} [<args>]`);
|
|
337
|
-
|
|
338
|
-
if (usage.description)
|
|
339
|
-
console.log(`${'Description:'.gray} ${usage.description}`);
|
|
340
|
-
|
|
341
|
-
if (opts && opts.alias) {
|
|
342
|
-
const alias = opts.alias;
|
|
343
|
-
const boolean = opts.boolean || [];
|
|
344
|
-
|
|
345
|
-
console.log('Options:'.gray);
|
|
346
|
-
for (const opt in alias) {
|
|
347
|
-
const paramDescription = usage.params[opt] || '';
|
|
348
|
-
let longOpt = opt;
|
|
349
|
-
if (boolean.indexOf(longOpt) === -1)
|
|
350
|
-
longOpt += ` <string>`;
|
|
351
|
-
longOpt = camelToSnake(longOpt).padEnd(22, ' ')
|
|
352
|
-
console.log(` -${alias[opt]}, --${longOpt} ${paramDescription}`);
|
|
353
|
-
}
|
|
354
|
-
}
|
|
355
|
-
}
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
module.exports = Myt;
|
|
359
|
-
|
|
360
|
-
if (require.main === module)
|
|
361
|
-
new Myt().run();
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
require('require-yaml');
|
|
4
|
+
require('colors');
|
|
5
|
+
const getopts = require('getopts');
|
|
6
|
+
const packageJson = require('./package.json');
|
|
7
|
+
const fs = require('fs-extra');
|
|
8
|
+
const ini = require('ini');
|
|
9
|
+
const path = require('path');
|
|
10
|
+
const mysql = require('mysql2/promise');
|
|
11
|
+
const nodegit = require('nodegit');
|
|
12
|
+
const camelToSnake = require('./lib/util').camelToSnake;
|
|
13
|
+
|
|
14
|
+
class Myt {
|
|
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
|
+
debug: 'Wether to enable debug mode',
|
|
21
|
+
version: 'Display the version number and exit',
|
|
22
|
+
help: 'Display this help message'
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
static opts = {
|
|
27
|
+
alias: {
|
|
28
|
+
remote: 'r',
|
|
29
|
+
workspace: 'w',
|
|
30
|
+
debug: 'd',
|
|
31
|
+
version: 'v',
|
|
32
|
+
help: 'h'
|
|
33
|
+
},
|
|
34
|
+
boolean: [
|
|
35
|
+
'debug',
|
|
36
|
+
'version',
|
|
37
|
+
'help'
|
|
38
|
+
]
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
async run(Command) {
|
|
42
|
+
console.log(
|
|
43
|
+
'Myt'.green,
|
|
44
|
+
`v${packageJson.version}`.magenta
|
|
45
|
+
);
|
|
46
|
+
this.packageJson = packageJson;
|
|
47
|
+
|
|
48
|
+
let baseOpts = this.constructor.opts;
|
|
49
|
+
baseOpts.default = Object.assign(baseOpts.default || {}, {
|
|
50
|
+
workspace: process.cwd()
|
|
51
|
+
});
|
|
52
|
+
const opts = this.getopts(baseOpts);
|
|
53
|
+
|
|
54
|
+
if (opts.debug) {
|
|
55
|
+
console.warn('Debug mode enabled.'.yellow);
|
|
56
|
+
console.log('Global options:'.magenta, opts);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (opts.version)
|
|
60
|
+
process.exit(0);
|
|
61
|
+
|
|
62
|
+
try {
|
|
63
|
+
const commandName = opts._[0];
|
|
64
|
+
if (!Command && commandName) {
|
|
65
|
+
if (!/^[a-z]+$/.test(commandName))
|
|
66
|
+
throw new Error (`Invalid command name '${commandName}'`);
|
|
67
|
+
|
|
68
|
+
const commandFile = path.join(__dirname, `myt-${commandName}.js`);
|
|
69
|
+
|
|
70
|
+
if (!await fs.pathExists(commandFile))
|
|
71
|
+
throw new Error (`Unknown command '${commandName}'`);
|
|
72
|
+
Command = require(commandFile);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (!Command) {
|
|
76
|
+
this.showHelp(baseOpts, this.constructor.usage);
|
|
77
|
+
process.exit(0);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const allOpts = Object.assign({}, baseOpts);
|
|
81
|
+
|
|
82
|
+
if (Command.opts)
|
|
83
|
+
for (const key in Command.opts) {
|
|
84
|
+
const baseValue = baseOpts[key];
|
|
85
|
+
const cmdValue = Command.opts[key];
|
|
86
|
+
if (Array.isArray(baseValue))
|
|
87
|
+
allOpts[key] = baseValue.concat(cmdValue);
|
|
88
|
+
else if (typeof baseValue == 'object')
|
|
89
|
+
allOpts[key] = Object.assign({}, baseValue, cmdValue);
|
|
90
|
+
else
|
|
91
|
+
allOpts[key] = cmdValue;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const commandOpts = this.getopts(allOpts);
|
|
95
|
+
if (opts.debug)
|
|
96
|
+
console.log('Command options:'.magenta, commandOpts);
|
|
97
|
+
Object.assign(opts, commandOpts);
|
|
98
|
+
|
|
99
|
+
const operandToOpt = Command.usage.operand;
|
|
100
|
+
if (opts._.length >= 2 && operandToOpt)
|
|
101
|
+
opts[operandToOpt] = opts._[1];
|
|
102
|
+
|
|
103
|
+
if (opts.debug)
|
|
104
|
+
console.log('Final options:'.magenta, opts);
|
|
105
|
+
|
|
106
|
+
if (opts.help) {
|
|
107
|
+
this.showHelp(Command.opts, Command.usage, commandName);
|
|
108
|
+
process.exit(0);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Check version
|
|
112
|
+
|
|
113
|
+
let depVersion;
|
|
114
|
+
const versionRegex = /^[^~]?([0-9]+)\.([0-9]+).([0-9]+)$/;
|
|
115
|
+
const wsPackageFile = path.join(opts.workspace, 'package.json');
|
|
116
|
+
|
|
117
|
+
if (await fs.pathExists(wsPackageFile)) {
|
|
118
|
+
const wsPackageJson = require(wsPackageFile);
|
|
119
|
+
try {
|
|
120
|
+
depVersion = wsPackageJson
|
|
121
|
+
.dependencies[packageJson.name]
|
|
122
|
+
.match(versionRegex);
|
|
123
|
+
} catch (e) {}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (depVersion) {
|
|
127
|
+
const myVersion = packageJson.version.match(versionRegex);
|
|
128
|
+
|
|
129
|
+
const isSameVersion =
|
|
130
|
+
depVersion[1] === myVersion[1] &&
|
|
131
|
+
depVersion[2] === myVersion[2];
|
|
132
|
+
if (!isSameVersion)
|
|
133
|
+
throw new Error(`Myt version differs a lot from package.json, please run 'npm i' first to install the proper version.`);
|
|
134
|
+
|
|
135
|
+
const isSameMinor = depVersion[3] === myVersion[3];
|
|
136
|
+
if (!isSameMinor)
|
|
137
|
+
console.warn(`Warning! Myt minor version differs from package.json, maybe you shoud run 'npm i' to install the proper version.`.yellow);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Load method
|
|
141
|
+
|
|
142
|
+
parameter('Workspace:', opts.workspace);
|
|
143
|
+
parameter('Remote:', opts.remote || 'local');
|
|
144
|
+
|
|
145
|
+
await this.load(opts);
|
|
146
|
+
await this.runCommand(Command, opts);
|
|
147
|
+
await this.unload();
|
|
148
|
+
} catch (err) {
|
|
149
|
+
if (err.name == 'Error' && !opts.debug) {
|
|
150
|
+
console.error('Error:'.gray, err.message.red);
|
|
151
|
+
console.log(`You can get more details about the error by passing the 'debug' option.`.yellow);
|
|
152
|
+
} else
|
|
153
|
+
console.log(err.stack.magenta);
|
|
154
|
+
|
|
155
|
+
process.exit(1);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function parameter(parameter, value) {
|
|
159
|
+
console.log(parameter.gray, (value || 'null').blue);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
process.exit();
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
async runCommand(Command, opts) {
|
|
166
|
+
const command = new Command(this, opts);
|
|
167
|
+
return await command.run(this, opts);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
async load(opts) {
|
|
171
|
+
// Configuration file
|
|
172
|
+
|
|
173
|
+
const config = require(`${__dirname}/assets/myt.default.yml`);
|
|
174
|
+
|
|
175
|
+
const configFile = 'myt.config.yml';
|
|
176
|
+
const configPath = path.join(opts.workspace, configFile);
|
|
177
|
+
if (await fs.pathExists(configPath))
|
|
178
|
+
Object.assign(config, require(configPath));
|
|
179
|
+
|
|
180
|
+
Object.assign(opts, config);
|
|
181
|
+
opts.configFile = configFile;
|
|
182
|
+
|
|
183
|
+
if (!opts.mytDir)
|
|
184
|
+
opts.mytDir = path.join(opts.workspace, opts.subdir || '');
|
|
185
|
+
|
|
186
|
+
opts.routinesDir = path.join(opts.mytDir, 'routines');
|
|
187
|
+
opts.versionsDir = path.join(opts.mytDir, 'versions');
|
|
188
|
+
opts.dumpDir = path.join(opts.mytDir, 'dump');
|
|
189
|
+
|
|
190
|
+
// Database configuration
|
|
191
|
+
|
|
192
|
+
let iniDir = path.join(__dirname, 'assets');
|
|
193
|
+
let iniFile = 'db.ini';
|
|
194
|
+
|
|
195
|
+
if (opts.remote) {
|
|
196
|
+
iniDir = `${opts.mytDir}/remotes`;
|
|
197
|
+
iniFile = `${opts.remote}.ini`;
|
|
198
|
+
}
|
|
199
|
+
const iniPath = path.join(iniDir, iniFile);
|
|
200
|
+
|
|
201
|
+
if (!await fs.pathExists(iniPath))
|
|
202
|
+
throw new Error(`Database config file not found: ${iniPath}`);
|
|
203
|
+
|
|
204
|
+
let dbConfig;
|
|
205
|
+
try {
|
|
206
|
+
const iniData = ini.parse(await fs.readFile(iniPath, 'utf8')).client;
|
|
207
|
+
const iniConfig = {};
|
|
208
|
+
for (const key in iniData) {
|
|
209
|
+
const value = iniData[key];
|
|
210
|
+
const newKey = key.replace(/-/g, '_');
|
|
211
|
+
iniConfig[newKey] = value !== undefined ? value : true;
|
|
212
|
+
}
|
|
213
|
+
dbConfig = {
|
|
214
|
+
host: iniConfig.host,
|
|
215
|
+
port: iniConfig.port,
|
|
216
|
+
user: iniConfig.user,
|
|
217
|
+
password: iniConfig.password,
|
|
218
|
+
multipleStatements: true
|
|
219
|
+
};
|
|
220
|
+
if (iniConfig.enable_cleartext_plugin) {
|
|
221
|
+
dbConfig.authPlugins = {
|
|
222
|
+
mysql_clear_password() {
|
|
223
|
+
return () => iniConfig.password + '\0';
|
|
224
|
+
}
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
if (iniConfig.ssl_ca) {
|
|
228
|
+
dbConfig.ssl = {
|
|
229
|
+
ca: await fs.readFile(`${opts.mytDir}/${iniConfig.ssl_ca}`),
|
|
230
|
+
rejectUnauthorized: iniConfig.ssl_verify_server_cert != undefined
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
if (iniConfig.socket) {
|
|
234
|
+
dbConfig.socketPath = iniConfig.socket;
|
|
235
|
+
}
|
|
236
|
+
} catch(err) {
|
|
237
|
+
const newErr = Error(`Cannot process the ini file, check that the syntax is correct: ${iniPath}`);
|
|
238
|
+
newErr.stack += `\nCaused by: ${err.stack}`;
|
|
239
|
+
throw newErr;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
Object.assign(opts, {
|
|
243
|
+
iniFile,
|
|
244
|
+
dbConfig
|
|
245
|
+
});
|
|
246
|
+
this.opts = opts;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
async unload() {
|
|
250
|
+
if (this.conn)
|
|
251
|
+
await this.conn.end();
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
getopts(opts) {
|
|
255
|
+
const argv = process.argv.slice(2);
|
|
256
|
+
const values = getopts(argv, opts);
|
|
257
|
+
const cleanValues = {};
|
|
258
|
+
for (const opt in values)
|
|
259
|
+
if (opt.length > 1 || opt == '_')
|
|
260
|
+
cleanValues[opt] = values[opt];
|
|
261
|
+
return cleanValues;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
async dbConnect() {
|
|
265
|
+
const {opts} = this;
|
|
266
|
+
|
|
267
|
+
if (!this.conn) {
|
|
268
|
+
const conn = this.conn = await this.createConnection();
|
|
269
|
+
|
|
270
|
+
const [[schema]] = await conn.query(
|
|
271
|
+
`SHOW DATABASES LIKE ?`, [opts.versionSchema]
|
|
272
|
+
);
|
|
273
|
+
|
|
274
|
+
if (!schema)
|
|
275
|
+
await conn.query(`CREATE DATABASE ??`, [opts.versionSchema]);
|
|
276
|
+
await conn.query(`USE ??`, [opts.versionSchema]);
|
|
277
|
+
|
|
278
|
+
const [[res]] = await conn.query(
|
|
279
|
+
`SELECT COUNT(*) > 0 tableExists
|
|
280
|
+
FROM information_schema.tables
|
|
281
|
+
WHERE TABLE_SCHEMA = ?
|
|
282
|
+
AND TABLE_NAME = 'version'`,
|
|
283
|
+
[opts.versionSchema]
|
|
284
|
+
);
|
|
285
|
+
|
|
286
|
+
if (!res.tableExists) {
|
|
287
|
+
const structure = await fs.readFile(
|
|
288
|
+
`${__dirname}/assets/structure.sql`, 'utf8');
|
|
289
|
+
await conn.query(structure);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
return this.conn;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
async createConnection() {
|
|
297
|
+
return await mysql.createConnection(this.opts.dbConfig);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
async fetchDbVersion() {
|
|
301
|
+
const [[version]] = await this.conn.query(
|
|
302
|
+
`SELECT number, gitCommit
|
|
303
|
+
FROM version WHERE code = ?`,
|
|
304
|
+
[this.opts.code]
|
|
305
|
+
);
|
|
306
|
+
return version;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
parseVersionDir(versionDir) {
|
|
310
|
+
const match = versionDir.match(/^([0-9]+)-([a-zA-Z0-9]+)?$/);
|
|
311
|
+
if (!match) return null;
|
|
312
|
+
return {
|
|
313
|
+
number: match[1],
|
|
314
|
+
name: match[2]
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
async openRepo() {
|
|
319
|
+
const {opts} = this;
|
|
320
|
+
|
|
321
|
+
if (!await fs.pathExists(`${opts.workspace}/.git`))
|
|
322
|
+
throw new Error ('Git not initialized');
|
|
323
|
+
|
|
324
|
+
return await nodegit.Repository.open(opts.workspace);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
showHelp(opts, usage, command) {
|
|
328
|
+
const prefix = `${'Usage:'.gray} [npx] myt`;
|
|
329
|
+
|
|
330
|
+
if (command) {
|
|
331
|
+
let log = [prefix, command.blue];
|
|
332
|
+
if (usage.operand) log.push(`[<${usage.operand}>]`);
|
|
333
|
+
if (opts) log.push('[<options>]');
|
|
334
|
+
console.log(log.join(' '))
|
|
335
|
+
} else
|
|
336
|
+
console.log(`${prefix} [<options>] ${'<command>'.blue} [<args>]`);
|
|
337
|
+
|
|
338
|
+
if (usage.description)
|
|
339
|
+
console.log(`${'Description:'.gray} ${usage.description}`);
|
|
340
|
+
|
|
341
|
+
if (opts && opts.alias) {
|
|
342
|
+
const alias = opts.alias;
|
|
343
|
+
const boolean = opts.boolean || [];
|
|
344
|
+
|
|
345
|
+
console.log('Options:'.gray);
|
|
346
|
+
for (const opt in alias) {
|
|
347
|
+
const paramDescription = usage.params[opt] || '';
|
|
348
|
+
let longOpt = opt;
|
|
349
|
+
if (boolean.indexOf(longOpt) === -1)
|
|
350
|
+
longOpt += ` <string>`;
|
|
351
|
+
longOpt = camelToSnake(longOpt).padEnd(22, ' ')
|
|
352
|
+
console.log(` -${alias[opt]}, --${longOpt} ${paramDescription}`);
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
module.exports = Myt;
|
|
359
|
+
|
|
360
|
+
if (require.main === module)
|
|
361
|
+
new Myt().run();
|