diff --git a/.dockerignore b/.dockerignore index 07ff583..651665b 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,4 +1,2 @@ node_modules -package.json -package-lock.json -README.md +.git diff --git a/Dockerfile.client b/Dockerfile.client index 21f413c..cf2d158 100644 --- a/Dockerfile.client +++ b/Dockerfile.client @@ -1,26 +1,11 @@ FROM debian:bullseye-slim -ENV TZ Europe/Madrid - ARG DEBIAN_FRONTEND=noninteractive RUN apt-get update \ && apt-get install -y --no-install-recommends \ mariadb-client \ libmariadb3 \ - git \ - jq \ - iputils-ping \ - dnsutils \ && rm -rf /var/lib/apt/lists/* -COPY \ - myvc-dump.sh \ - myvc-push.sh \ - structure.sql \ - /usr/local/bin/ - -COPY \ - workspace/remotes/local.ini \ - /usr/local/bin/db.ini - +COPY myvc-dump.sh /usr/local/bin/ WORKDIR /workspace diff --git a/README.md b/README.md index ec08a5b..8005b4b 100644 --- a/README.md +++ b/README.md @@ -8,13 +8,11 @@ development, so it may not be fully functional. Any help is welcomed! Feel free to contribute. -## Prerequisites +## Requirements -Required applications. - -* Node.js = 12.17.0 LTS +* Node.js <= 12.0 * Git -* Docker +* Docker (Only to setup a local server) ## Installation @@ -41,19 +39,19 @@ $ myvc [-w|--workspace] [-e|--env] [-h|--help] command ``` The default workspace directory is the current working directory and unless -otherwise indicated, the default environment is *production*. +otherwise indicated, the default environment is *local*. Commands for database versioning: * **init**: Initialize an empty workspace. * **pull**: Export database routines into workspace. - * **push**: Apply changes into database, uses *test* environment by default. + * **push**: Apply changes into database. Commands for local server management: - * **dump**: Export database structure and fixtures. - * **run**: Builds and starts local database server container. - * **start**: Starts local database server container. + * **dump**: Export database structure and fixtures from *production*. + * **run**: Build and starts local database server container. + * **start**: Start local database server container. Each command can have its own specific commandline options. @@ -65,7 +63,7 @@ First of all you have to initalize your workspace. $ myvc init ``` -Now yoy can configure MyVC using *myvc.config.json* file, located at the root of +Now you can configure MyVC using *myvc.config.yml* file, located at the root of your workspace. This file should include the project codename and schemas/tables wich are exported when you use *pull* or *dump* commands. diff --git a/docker-run.js b/docker-run.js deleted file mode 100644 index 8580144..0000000 --- a/docker-run.js +++ /dev/null @@ -1,39 +0,0 @@ - -const path = require('path'); -const execFile = require('child_process').execFile; -const spawn = require('child_process').spawn; - -module.exports = async function(command, workspace, ...args) { - const buildArgs = [ - 'build', - '-t', 'myvc/client', - '-f', path.join(__dirname, 'Dockerfile.client'), - __dirname - ]; - await new Promise((resolve, reject) => { - execFile('docker', buildArgs, (err, stdout, stderr) => { - if (err) - return reject(err); - resolve({stdout, stderr}); - }); - }) - - let runArgs = [ - 'run', - '-v', `${workspace}:/workspace`, - 'myvc/client', - command - ]; - runArgs = runArgs.concat(args); - - await new Promise((resolve, reject) => { - const child = spawn('docker', runArgs, { - stdio: [ - process.stdin, - process.stdout, - process.stderr - ] - }); - child.on('exit', code => resolve(code)); - }) -}; diff --git a/docker.js b/docker.js index e6efd64..77d57ff 100644 --- a/docker.js +++ b/docker.js @@ -1,253 +1,125 @@ - +const spawn = require('child_process').spawn; const execFile = require('child_process').execFile; -const log = require('fancy-log'); -const path = require('path'); -module.exports = class Docker { - constructor(name, context) { - Object.assign(this, { - id: name, - name, - isRandom: name == null, - dbConf: { - host: 'localhost', - port: '3306', - username: 'root', - password: 'root' - }, - imageTag: name || 'myvc/dump', - context - }); - } +const docker = { + async run(image, commandArgs, options, execOptions) { + const args = commandArgs + ? [image].concat(commandArgs) + : image; + const execMode = options.detach ? 'exec' : 'spawn'; + + const child = await this.exec('run', + args, + options, + execMode, + execOptions + ); + return options.detach + ? new Container(child.stdout.trim()) + : child; + }, - /** - * Builds the database image and runs a container. It only rebuilds the - * image when fixtures have been modified or when the day on which the - * image was built is different to today. Some workarounds have been used - * to avoid a bug with OverlayFS driver on MacOS. - * - * @param {Boolean} ci continuous integration environment argument - */ - async run(ci) { - let dockerfilePath = path.join(__dirname, 'Dockerfile'); - - await this.execFile('docker', [ - 'build', - '-t', 'myvc/server', - '-f', `${dockerfilePath}.server`, - __dirname - ]); + async build(url, options, execOptions) { + return await this.exec('build', + url, + options, + 'spawn', + execOptions + ); + }, - let d = new Date(); - let pad = v => v < 10 ? '0' + v : v; - let stamp = `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`; + async start(id, options) { + const ct = new Container(id); + await ct.start(options); + return ct; + }, - await this.execFile('docker', [ - 'build', - '-t', this.imageTag, - '-f', `${dockerfilePath}.dump`, - '--build-arg', `STAMP=${stamp}`, - this.context - ]); + async stop(id, options) { + const ct = new Container(id); + return await ct.stop(options); + }, - let dockerArgs; + async rm(id, options) { + const ct = new Container(id); + return await ct.rm(options); + }, - if (this.isRandom) - dockerArgs = ['-p', '3306']; - else { - try { - await this.rm(); - } catch (e) {} - dockerArgs = ['--name', this.name, '-p', `3306:${this.dbConf.port}`]; - } + async inspect(id, options) { + const ct = new Container(id); + return await ct.inspect(options); + }, - let runChown = process.platform != 'linux'; - const container = await this.execFile('docker', [ - 'run', - '--env', `RUN_CHOWN=${runChown}`, - '-d', - ...dockerArgs, - this.imageTag - ]); - this.id = container.stdout.trim(); + async exec(command, args, options, execMode, execOptions) { + const execArgs = [command]; - try { - if (this.isRandom) { - let netSettings = await this.execJson('docker', [ - 'inspect', '-f', '{{json .NetworkSettings}}', this.id - ]); - - if (ci) - this.dbConf.host = netSettings.Gateway; - - this.dbConf.port = netSettings.Ports['3306/tcp'][0]['HostPort']; + if (options) + for (const option in options) { + execArgs.push(`--${camelToSnake(option)}`); + if (typeof options[option] !== 'boolean') + execArgs.push(options[option]); } - await this.wait(); - } catch (err) { - if (this.isRandom) - await this.rm(); - throw err; - } - } + if (Array.isArray(args)) + Array.prototype.push.apply(execArgs, args); + else if (args) + execArgs.push(args); - /** - * Does the minium effort to start the database container, if it doesn't - * exists calls the 'docker' task, if it is started does nothing. Keep in - * mind that when you do not rebuild the docker you may be using an outdated - * version of it. - */ - async start() { - let state; - try { - state = await this.execJson('docker', [ - 'inspect', '-f', '{{json .State}}', this.id - ]); - } catch (err) { - return await this.run(); - } + return await new Promise((resolve, reject) => { + if (execMode == 'spawn') { + if (execOptions === true) + execOptions = { + stdio: [ + process.stdin, + process.stdout, + process.stderr + ] + }; - switch (state.Status) { - case 'running': - return; - case 'exited': - await this.execFile('docker', ['start', this.id]); - await this.wait(); - return; - default: - throw new Error(`Unknown docker status: ${state.Status}`); - } - } - - waitForHealthy() { - return new Promise((resolve, reject) => { - let interval = 100; - let elapsedTime = 0; - let maxInterval = 4 * 60 * 1000; - - log('Waiting for container to be ready...'); - - async function checker() { - elapsedTime += interval; - let status; - - try { - let status = await this.execJson('docker', [ - 'inspect', '-f', '{{.State.Health.Status}}', this.id - ]); - status = status.trimEnd(); - } catch (err) { - return reject(new Error(err.message)); - } - - if (status == 'unhealthy') - return reject(new Error('Docker exited, please see the docker logs for more info')); - - if (status == 'healthy') { - log('Container ready.'); - return resolve(); - } - - if (elapsedTime >= maxInterval) - reject(new Error(`Container not initialized whithin ${elapsedTime / 1000} secs`)); - else - setTimeout(bindedChecker, interval); - } - let bindedChecker = checker.bind(this); - bindedChecker(); - }); - } - - wait() { - return new Promise((resolve, reject) => { - const mysql = require('mysql2'); - - let interval = 100; - let elapsedTime = 0; - let maxInterval = 4 * 60 * 1000; - - let myConf = { - user: this.dbConf.username, - password: this.dbConf.password, - host: this.dbConf.host, - port: this.dbConf.port - }; - - log('Waiting for MySQL init process...'); - - async function checker() { - elapsedTime += interval; - let state; - - try { - state = await this.execJson('docker', [ - 'inspect', '-f', '{{json .State}}', this.id - ]); - } catch (err) { - return reject(new Error(err.message)); - } - - if (state.Status === 'exited') - return reject(new Error('Docker exited, please see the docker logs for more info')); - - let conn = mysql.createConnection(myConf); - conn.on('error', () => {}); - conn.connect(err => { - conn.destroy(); - if (!err) { - log('MySQL process ready.'); - return resolve(); - } - - if (elapsedTime >= maxInterval) - reject(new Error(`MySQL not initialized whithin ${elapsedTime / 1000} secs`)); + const child = spawn('docker', execArgs, execOptions || undefined); + child.on('exit', code => { + if (code !== 0) { + const args = JSON.stringify(execArgs); + reject(new Error(`'docker' ${args}: Exit code: ${code}`)); + } else + resolve(code); + }); + } else { + execFile('docker', execArgs, (err, stdout, stderr) => { + if (err) + reject(err); else - setTimeout(bindedChecker, interval); + resolve({stdout, stderr}); }); } - let bindedChecker = checker.bind(this); - bindedChecker(); }); } - - async rm() { - try { - await this.execFile('docker', ['stop', this.id]); - await this.execFile('docker', ['rm', '-v', this.id]); - } catch (e) {} - } - - /** - * Promisified version of execFile(). - * - * @param {String} command The exec command - * @param {Array} args The command arguments - * @return {Promise} The promise - */ - execFile(command, args) { - return new Promise((resolve, reject) => { - execFile(command, args, (err, stdout, stderr) => { - if (err) - reject(err); - else { - resolve({ - stdout: stdout, - stderr: stderr - }); - } - }); - }); - } - - /** - * Executes a command whose return is json. - * - * @param {String} command The exec command - * @param {Array} args The command arguments - * @return {Object} The parsed JSON - */ - async execJson(command, args) { - const result = await this.execFile(command, args); - return JSON.parse(result.stdout); - } }; + +class Container { + construct(id) { + this.id = id; + } + + async start(options) { + await docker.exec('start', this.id, options); + } + + async stop(options) { + await docker.exec('stop', this.id, options); + } + + async rm(options) { + await docker.exec('rm', this.id, options); + } + + async inspect(options) { + const child = await docker.exec('inspect', this.id, options); + return JSON.parse(child.stdout); + } +} + +function camelToSnake(str) { + return str.replace(/[A-Z]/g, match => `-${match.toLowerCase()}`); +} + +module.exports = docker; \ No newline at end of file diff --git a/index.js b/index.js index 19b25f3..93611aa 100644 --- a/index.js +++ b/index.js @@ -1,76 +1,109 @@ - +require('require-yaml'); require('colors'); const getopts = require('getopts'); -const package = require('./package.json'); +const packageJson = require('./package.json'); const fs = require('fs-extra'); const ini = require('ini'); const path = require('path'); -const dockerRun = require('./docker-run'); - -console.log('MyVC (MySQL Version Control)'.green, `v${package.version}`.magenta); - -const argv = process.argv.slice(2); -const cliOpts = getopts(argv, { - alias: { - env: 'e', - workspace: 'w', - help: 'h', - version: 'v' - }, - default: { - workspace: process.cwd(), - env: 'production' - } -}) - -if (cliOpts.version) - process.exit(0); - -const command = cliOpts._[0]; -if (!command) { - console.log('Usage:'.gray, '[npx] myvc [-w|--workspace] [-e|--env] [-h|--help] command'.blue); - process.exit(0); -} - -const commandArgs = { - push: { - alias: { - env: 'e', - force: 'f', - user: 'u', - applyUncommited: 'a' - }, - default: { - env: 'test' - } - } -}; -const commandOpts = getopts(argv, commandArgs[command]); -Object.assign(cliOpts, commandOpts); - -const opts = {}; -for (let opt in cliOpts) { - if (opt.length > 1 || opt == '_') - opts[opt] = cliOpts[opt]; -} - -function parameter(parameter, value) { - console.log(parameter.gray, value.blue); -} - -parameter('Environment:', opts.env); -parameter('Workspace:', opts.workspace); -parameter('Command:', command); +const mysql = require('mysql2/promise'); +const nodegit = require('nodegit'); class MyVC { + async run(command) { + console.log( + 'MyVC (MySQL Version Control)'.green, + `v${packageJson.version}`.magenta + ); + + const opts = {}; + const argv = process.argv.slice(2); + const cliOpts = getopts(argv, { + alias: { + env: 'e', + workspace: 'w', + debug: 'd', + version: 'v', + help: 'h' + }, + default: { + workspace: process.cwd() + } + }) + + if (cliOpts.version) + process.exit(0); + + try { + if (!command) { + const commandName = cliOpts._[0]; + if (!commandName) { + console.log( + 'Usage:'.gray, + '[npx] myvc' + + '[-w|--workspace]' + + '[-e|--env]' + + '[-d|--debug]' + + '[-h|--help]' + + '[-v|--version]' + + 'command'.blue + ); + process.exit(0); + } + + const commands = [ + 'init', + 'pull', + 'push', + 'dump', + 'start', + 'run' + ]; + + if (commands.indexOf(commandName) == -1) + throw new Error (`Unknown command '${commandName}'`); + + const Klass = require(`./myvc-${commandName}`); + command = new Klass(); + } + + const commandOpts = getopts(argv, command.myOpts); + Object.assign(cliOpts, commandOpts); + + for (const opt in cliOpts) { + if (opt.length > 1 || opt == '_') + opts[opt] = cliOpts[opt]; + } + + parameter('Workspace:', opts.workspace); + parameter('Environment:', opts.env); + + await this.load(opts); + command.opts = opts; + await command.run(this, opts); + await this.unload(); + } catch (err) { + if (err.name == 'Error' && !opts.debug) + console.error('Error:'.gray, err.message.red); + else + throw err; + } + + function parameter(parameter, value) { + console.log(parameter.gray, (value || 'null').blue); + } + + process.exit(); + } + async load(opts) { // Configuration file + + const config = require(`${__dirname}/myvc.default.yml`); - const configFile = 'myvc.config.json'; + const configFile = 'myvc.config.yml'; const configPath = path.join(opts.workspace, configFile); - if (!await fs.pathExists(configPath)) - throw new Error(`Config file not found: ${configFile}`); - const config = require(configPath); + if (await fs.pathExists(configPath)) + Object.assign(config, require(configPath)); Object.assign(opts, config); opts.configFile = configFile; @@ -90,98 +123,130 @@ class MyVC { const iniConfig = ini.parse(await fs.readFile(iniPath, 'utf8')).client; const dbConfig = { - host: !opts.env ? 'localhost' : iniConfig.host, + host: iniConfig.host, port: iniConfig.port, user: iniConfig.user, password: iniConfig.password, + database: opts.versionSchema, authPlugins: { mysql_clear_password() { return () => iniConfig.password + '\0'; } } }; - + if (iniConfig.ssl_ca) { dbConfig.ssl = { ca: await fs.readFile(`${opts.workspace}/${iniConfig.ssl_ca}`), rejectUnauthorized: iniConfig.ssl_verify_server_cert != undefined } } + if (!opts.env) + dbConfig.socketPath = '/var/run/mysqld/mysqld.sock'; Object.assign(opts, { iniFile, dbConfig }); + this.opts = opts; } - async init(opts) { - const templateDir = `${__dirname}/workspace`; - const templates = await fs.readdir(templateDir); - for (let template of templates){ - const dst = `${opts.workspace}/${template}`; - if (!await fs.pathExists(dst)) - await fs.copy(`${templateDir}/${template}`, dst); + async dbConnect() { + if (!this.conn) + this.conn = await this.createConnection(); + return this.conn; + } + + async createConnection() { + return await mysql.createConnection(this.opts.dbConfig); + } + + async unload() { + if (this.conn) + await this.conn.end(); + } + + async fetchDbVersion() { + const {opts} = this; + + const [[res]] = await this.conn.query( + `SELECT COUNT(*) > 0 tableExists + FROM information_schema.tables + WHERE TABLE_SCHEMA = ? + AND TABLE_NAME = 'version'`, + [opts.versionSchema] + ); + + if (!res.tableExists) { + const structure = await fs.readFile(`${__dirname}/structure.sql`, 'utf8'); + await this.conn.query(structure); + return null; } + + const [[version]] = await this.conn.query( + `SELECT number, gitCommit + FROM version WHERE code = ?`, + [opts.code] + ); + return version; } - async pull(opts) { - const pull = require('./myvc-pull'); - await pull( - opts.workspace, - opts.schemas, - opts.dbConfig + async changedRoutines(commit) { + const repo = await nodegit.Repository.open(this.opts.workspace); + + const from = await repo.getCommit(commit); + const fromTree = await from.getTree(); + + const to = await repo.getHeadCommit(); + const toTree = await to.getTree(); + + const diff = await toTree.diff(fromTree); + const patches = await diff.patches(); + + const changes = []; + for (const patch of patches) { + const path = patch.newFile().path(); + const match = path.match(/^routines\/(.+)\.sql$/); + if (!match) continue; + + changes.push({ + mark: patch.isDeleted() ? '-' : '+', + path: match[1] + }); + } + + return changes.sort( + (a, b) => b.mark == '-' && b.mark != a.mark ? 1 : -1 ); } - async push(opts) { - let args = []; - if (opts.force) args.push('-f'); - if (opts.user) args.push('-u'); - if (opts.applyUncommited) args.push('-a'); - if (opts.env) args = args.concat(['-e', opts.env]); + async cachedChanges() { + const changes = []; + const dumpDir = `${this.opts.workspace}/dump`; + const dumpChanges = `${dumpDir}/.changes`; - await dockerRun('myvc-push.sh', - opts.workspace, - ...args - ); - } + if (!await fs.pathExists(dumpChanges)) + return null; - async dump (opts) { - await dockerRun('myvc-dump.sh', - opts.workspace, - opts.configFile, - opts.iniFile - ); - } + const readline = require('readline'); + const rl = readline.createInterface({ + input: fs.createReadStream(dumpChanges), + //output: process.stdout, + console: false + }); - async run(opts) { - const Docker = require('./docker'); - const container = new Docker(opts.code, opts.workspace); - await container.run(); - } + for await (const line of rl) { + changes.push({ + mark: line.charAt(0), + path: line.substr(1) + }); + } - async start(opts) { - const Docker = require('./docker'); - const container = new Docker(opts.code, opts.workspace); - await container.start(); + return changes; } } -(async function() { - try { - const myvc = new MyVC(); - - if (command != 'load' && myvc[command]) { - await myvc.load(opts); - await myvc[command](opts); - } else - throw new Error (`Unknown command '${command}'`); - } catch (err) { - if (err.name == 'Error') - console.error('Error:'.gray, err.message.red); - else - throw err; - } -})(); - module.exports = MyVC; + +if (require.main === module) + new MyVC().run(); diff --git a/myvc-dump.js b/myvc-dump.js new file mode 100644 index 0000000..0a9bbef --- /dev/null +++ b/myvc-dump.js @@ -0,0 +1,93 @@ + +const MyVC = require('./index'); +const fs = require('fs-extra'); +const path = require('path'); +const docker = require('./docker'); + +class Dump { + get myOpts() { + return { + alias: { + env: 'e' + }, + default: { + env: 'production' + } + }; + } + + async run(myvc, opts) { + const conn = await myvc.dbConnect(); + + const dumpDir = `${opts.workspace}/dump`; + if (!await fs.pathExists(dumpDir)) + await fs.mkdir(dumpDir); + + const dumpFile = `${dumpDir}/.dump.sql`; + const dumpStream = await fs.createWriteStream(dumpFile); + const execOptions = { + stdio: [ + process.stdin, + dumpStream, + process.stderr + ] + }; + + await docker.build(__dirname, { + tag: 'myvc/client', + file: path.join(__dirname, 'Dockerfile.client') + }, !!this.opts.debug); + + let dumpArgs = [ + `--defaults-file=${opts.iniFile}`, + '--default-character-set=utf8', + '--no-data', + '--comments', + '--triggers', + '--routines', + '--events', + '--databases' + ]; + dumpArgs = dumpArgs.concat(opts.schemas); + await this.dockerRun('myvc-dump.sh', dumpArgs, execOptions); + + const fixturesArgs = [ + `--defaults-file=${opts.iniFile}`, + '--no-create-info', + '--skip-triggers', + '--insert-ignore' + ]; + for (const schema in opts.fixtures) { + await dumpStream.write( + `USE ${conn.escapeId(schema, true)};\n`, + 'utf8' + ); + + const args = fixturesArgs.concat([schema], opts.fixtures[schema]); + await this.dockerRun('mysqldump', args, execOptions); + } + + await dumpStream.end(); + + const version = await myvc.fetchDbVersion(); + if (version){ + await fs.writeFile( + `${dumpDir}/.dump.json`, + JSON.stringify(version) + ); + } + } + + async dockerRun(command, args, execOptions) { + const commandArgs = [command].concat(args); + await docker.run('myvc/client', commandArgs, { + volume: `${this.opts.workspace}:/workspace` + }, execOptions); + } +} + +module.exports = Dump; + +if (require.main === module) + new MyVC().run(Dump); + diff --git a/myvc-dump.sh b/myvc-dump.sh index a887ffb..7972a19 100755 --- a/myvc-dump.sh +++ b/myvc-dump.sh @@ -1,34 +1,3 @@ #!/bin/bash -set -e -CONFIG_FILE=$1 -INI_FILE=$2 -DUMP_DIR="dump" -DUMP_FILE="$DUMP_DIR/.dump.sql" - -echo "SELECT 1;" | mysql --defaults-file="$INI_FILE" >> /dev/null -SCHEMAS=( $(jq -r ".schemas[]" "$CONFIG_FILE") ) - -mkdir -p "$DUMP_DIR" - -mysqldump \ - --defaults-file="$INI_FILE" \ - --default-character-set=utf8 \ - --no-data \ - --comments \ - --triggers --routines --events \ - --databases \ - ${SCHEMAS[@]} \ - | sed 's/ AUTO_INCREMENT=[0-9]* //g' \ - > "$DUMP_FILE" - -for SCHEMA in $(jq -r ".fixtures | keys[]" "$CONFIG_FILE"); do - TABLES=( $(jq -r ".fixtures.$SCHEMA[]" "$CONFIG_FILE") ) - - echo "USE \`$SCHEMA\`;" >> "$DUMP_FILE" - mysqldump \ - --defaults-file="$INI_FILE" \ - --no-create-info \ - --skip-triggers \ - $SCHEMA ${TABLES[@]} >> "$DUMP_FILE" -done +mysqldump $@ | sed 's/ AUTO_INCREMENT=[0-9]* //g' diff --git a/myvc-init.js b/myvc-init.js new file mode 100755 index 0000000..556b38a --- /dev/null +++ b/myvc-init.js @@ -0,0 +1,20 @@ + +const MyVC = require('./index'); +const fs = require('fs-extra'); + +class Init { + async run(myvc, opts) { + const templateDir = `${__dirname}/template`; + const templates = await fs.readdir(templateDir); + for (let template of templates) { + const dst = `${opts.workspace}/${template}`; + if (!await fs.pathExists(dst)) + await fs.copy(`${templateDir}/${template}`, dst); + } + } +} + +module.exports = Init; + +if (require.main === module) + new MyVC().run(Init); diff --git a/myvc-pull.js b/myvc-pull.js index 58ee7c8..71cbb4e 100755 --- a/myvc-pull.js +++ b/myvc-pull.js @@ -1,39 +1,64 @@ +const MyVC = require('./index'); const fs = require('fs-extra'); -const mysql = require('mysql2/promise'); const ejs = require('ejs'); +class Pull { + async run(myvc, opts) { + const conn = await myvc.dbConnect(); + + for (const exporter of exporters) + await exporter.init(); + + const exportDir = `${opts.workspace}/routines`; + if (await fs.pathExists(exportDir)) + await fs.remove(exportDir, {recursive: true}); + await fs.mkdir(exportDir); + + for (const schema of opts.schemas) { + let schemaDir = `${exportDir}/${schema}`; + + if (!await fs.pathExists(schemaDir)) + await fs.mkdir(schemaDir); + + for (const exporter of exporters) + await exporter.export(conn, exportDir, schema); + } + } +} + class Exporter { - constructor(objectName, callback) { + constructor(objectName) { this.objectName = objectName; - this.callback = callback; this.dstDir = `${objectName}s`; + } - const templateDir = `${__dirname}/exporters/${objectName}`; - this.query = fs.readFileSync(`${templateDir}.sql`, 'utf8'); + async init() { + const templateDir = `${__dirname}/exporters/${this.objectName}`; + this.query = await fs.readFile(`${templateDir}.sql`, 'utf8'); - const templateFile = fs.readFileSync(`${templateDir}.ejs`, 'utf8'); + const templateFile = await fs.readFile(`${templateDir}.ejs`, 'utf8'); this.template = ejs.compile(templateFile); - if (fs.existsSync(`${templateDir}.js`)) + if (await fs.pathExists(`${templateDir}.js`)) this.formatter = require(`${templateDir}.js`); } async export(conn, exportDir, schema) { - const res = await conn.execute(this.query, [schema]); - if (!res[0].length) return; + const [res] = await conn.query(this.query, [schema]); + if (!res.length) return; const routineDir = `${exportDir}/${schema}/${this.dstDir}`; - if (!fs.existsSync(routineDir)) - fs.mkdirSync(routineDir); + if (!await fs.pathExists(routineDir)) + await fs.mkdir(routineDir); - for (let params of res[0]) { + for (const params of res) { if (this.formatter) this.formatter(params, schema) params.schema = schema; let sql = this.template(params); - fs.writeFileSync(`${routineDir}/${params.name}.sql`, sql); + await fs.writeFile(`${routineDir}/${params.name}.sql`, sql); } } } @@ -46,35 +71,7 @@ const exporters = [ new Exporter('event') ]; -// Exports objects for all schemas +module.exports = Pull; -module.exports = async function main(workspace, schemas, dbConf) { - const conn = await mysql.createConnection(dbConf); - conn.queryFromFile = function(file, params) { - return this.execute( - fs.readFileSync(`${file}.sql`, 'utf8'), - params - ); - } - - try { - const exportDir = `${workspace}/routines`; - if (fs.existsSync(exportDir)) - fs.removeSync(exportDir, {recursive: true}); - fs.mkdirSync(exportDir); - - for (let schema of schemas) { - let schemaDir = `${exportDir}/${schema}`; - - if (!fs.existsSync(schemaDir)) - fs.mkdirSync(schemaDir); - - for (let exporter of exporters) - await exporter.export(conn, exportDir, schema); - } - } catch(err) { - console.error(err); - } finally { - await conn.end(); - } -}; +if (require.main === module) + new MyVC().run(Pull); diff --git a/myvc-push.js b/myvc-push.js new file mode 100644 index 0000000..43feed2 --- /dev/null +++ b/myvc-push.js @@ -0,0 +1,419 @@ + +const MyVC = require('./index'); +const fs = require('fs-extra'); + +const typeMap = { + events: { + name: 'EVENT', + abbr: 'EVNT', + color: 'cyan' + }, + functions: { + name: 'FUNCTION', + abbr: 'FUNC', + color: 'cyan' + }, + procedures: { + name: 'PROCEDURE', + abbr: 'PROC', + color: 'yellow' + }, + triggers: { + name: 'TRIGGER', + abbr: 'TRIG', + color: 'blue' + }, + views: { + name: 'VIEW', + abbr: 'VIEW', + color: 'magenta' + }, +}; + +class Routine { + construct(path, mark) { + const path = path + const split = path.split('/'); + + const fullPath = `${this.opts.workspace}/routines/${path}.sql`; + const schema = split[0]; + const type = typeMap[split[1]]; + const name = split[2]; + + Object.assign(this, { + path, + mark: mark, + exists: await fs.pathExists(fullPath), + type, + schema, + name, + fullName: `${schema}.${name}`, + isRoutine: ['FUNC', 'PROC'].indexOf(type.abbr) !== -1 + }); + } +} + +const tokens = { + string: { + start: '\'', + end: '\'', + escape: char => char == '\'' || char == '\\' + }, + id: { + start: '`', + end: '`', + escape: char => char == '`' + }, + multiComment: { + start: '/*', + end: '*/', + escape: () => false + }, + singleComment: { + start: '-- ', + end: '\n', + escape: () => false + } +}; + +const tokenIndex = new Map(); +for (const tokenId in tokens) { + const token = tokens[tokenId]; + tokenIndex.set(token.start[0], token); +} + +class Push { + get myOpts() { + return { + alias: { + force: 'f', + user: 'u', + applyUncommited: 'a' + } + }; + } + + async run(myvc, opts) { + const conn = await myvc.dbConnect(); + this.conn = conn; + + const version = await myvc.fetchDbVersion() || {}; + + console.log( + `Database information:` + + `\n -> Version: ${version.number}` + + `\n -> Commit: ${version.gitCommit}` + ); + + if (!/^[0-9]*$/.test(version.number)) + throw new Error('Wrong database version'); + if (!version.number) + version.number = '00000'; + + if (opts.user) { + const [[user]] = conn.query( + `SELECT LEFT(USER(), INSTR(USER(), '@') - 1)` + ); + let [[userVersion]] = await conn.query( + `SELECT number, gitCommit + FROM versionUser + WHERE code = ? AND user = ?`, + [opts.code, user] + ); + userVersion = userVersion || {}; + console.log( + `User information:` + + `\n -> User: ${user}` + + `\n -> Version: ${userVersion.number}` + + `\n -> Commit: ${userVersion.gitCommit}` + ); + + if (userVersion.number > version.number) + version = userVersion; + } + + if (opts.env == 'production') { + console.log( + '\n ( ( ) ( ( ) ) ' + + '\n )\\ ))\\ ) ( /( )\\ ) ( ))\\ ) ( /( ( /( ' + + '\n(()/(()/( )\\()|()/( ( )\\ ) /(()/( )\\()) )\\())' + + '\n /(_))(_)|(_)\\ /(_)) )\\ (((_) ( )(_))(_)|(_)\\ ((_)\\ ' + + '\n(_))(_)) ((_|_))_ _ ((_))\\___(_(_()|__)) ((_) _((_)' + + '\n| _ \\ _ \\ / _ \\| \\| | | ((/ __|_ _|_ _| / _ \\| \\| |' + + '\n| _/ /| (_) | |) | |_| || (__ | | | | | (_) | . |' + + '\n|_| |_|_\\ \\___/|___/ \\___/ \\___| |_| |___| \\___/|_|\\_|' + + '\n' + ); + + if (!opts.force) { + const readline = require('readline'); + const rl = readline.createInterface({ + input: process.stdin, + output: process.stdout + }); + const answer = await new Promise(resolve => { + rl.question('Are you sure? (Default: no) [yes|no] ', resolve); + }); + rl.close(); + + if (answer !== 'yes') + throw new Error('Changes aborted'); + } + } + + console.log('Applying versions.'); + + let nChanges = 0; + const versionsDir = `${opts.workspace}/versions`; + + function logVersion(type, version, name) { + console.log('', type.bold, `[${version.bold}]`, name); + } + + if (await fs.pathExists(versionsDir)) { + const versionDirs = await fs.readdir(versionsDir); + + for (const versionDir of versionDirs) { + if (versionDir == 'README.md') + continue; + + const match = versionDir.match(/^([0-9]{5})-([a-zA-Z0-9]+)?$/); + if (!match) { + logVersion('[W]'.yellow, '?????', versionDir); + continue; + } + + const dirVersion = match[1]; + const versionName = match[2]; + + if (version.number >= dirVersion) { + logVersion('[I]'.blue, dirVersion, versionName); + continue; + } + + logVersion('[+]'.green, dirVersion, versionName); + const scriptsDir = `${versionsDir}/${versionDir}`; + const scripts = await fs.readdir(scriptsDir); + + for (const script of scripts) { + if (!/^[0-9]{2}-[a-zA-Z0-9_]+\.sql$/.test(script)) { + console.log(` - Ignoring wrong file name: ${script}`); + continue; + } + + console.log(` - ${script}`); + await this.queryFromFile(pushConn, `${scriptsDir}/${script}`); + nChanges++; + } + + //await this.updateVersion(nChanges, 'number', dirVersion); + } + } + + const pushConn = await myvc.createConnection(); + + console.log('Applying changed routines.'); + + let nRoutines = 0; + let changes = await fs.pathExists(`${opts.workspace}/.git`) + ? await myvc.changedRoutines(version.gitCommit) + : await myvc.cachedChanges(); + changes = await this.parseChanges(changes); + + await conn.query( + `CREATE TEMPORARY TABLE tProcsPriv + ENGINE = MEMORY + SELECT * FROM mysql.procs_priv LIMIT 0` + ); + + const routines = []; + for (const change of changes) + if (change.isRoutine) + routines.push([change.schema, change.name]); + + if (routines.length) { + await conn.query( + `DROP TEMPORARY TABLE IF EXISTS tProcsPriv` + ); + await conn.query( + `CREATE TEMPORARY TABLE tProcsPriv + ENGINE = MEMORY + SELECT * FROM mysql.procs_priv + WHERE (Db, Routine_name) IN (?)`, + [routines] + ); + } + + for (const change of changes) { + const actionMsg = change.exists ? '[+]'.green : '[-]'.red; + const typeMsg = `[${change.type.abbr}]`[change.type.color]; + + console.log('', actionMsg.bold, typeMsg.bold, change.fullName); + + if (change.exists) + await this.queryFromFile(pushConn, `routines/${change.path}.sql`); + else { + const escapedName = + conn.escapeId(change.schema, true) + '.' + + conn.escapeId(change.name, true); + + const query = `DROP ${change.type.name} IF EXISTS ${escapedName}`; + await conn.query(query); + } + + nRoutines++; + } + + if (routines.length) { + await conn.query( + `INSERT IGNORE INTO mysql.procs_priv + SELECT * FROM tProcsPriv` + ); + await conn.query( + `DROP TEMPORARY TABLE tProcsPriv` + ); + } + + await pushConn.end(); + + if (nRoutines > 0) { + await conn.query('FLUSH PRIVILEGES'); + //await this.updateVersion(nRoutines, 'gitCommit', version.gitCommit); + + console.log(` -> ${nRoutines} routines have changed.`); + } else + console.log(` -> No routines changed.`); + } + + async parseChanges(changes) { + const routines = []; + for (const change of changes) + routines.push(new Routine(change)); + return routines; + } + + async updateVersion(nChanges, column, value) { + if (nChanges == 0) return; + const {opts} = this; + + column = this.conn.escapeId(column, true); + + if (opts.user) { + await this.conn.query( + `INSERT INTO versionUser + SET code = ?, + user = ?, + ${column} = ? + ON DUPLICATE KEY UPDATE + ${column} = VALUES(${column})`, + [ + opts.code, + user, + value + ] + ); + } else { + await this.conn.query( + `INSERT INTO version + SET code = ?, + ${column} = ? + ON DUPLICATE KEY UPDATE + ${column} = VALUES(${column})`, + [ + opts.code, + value + ] + ); + } + } + + /** + * Executes an SQL script. + * + * @param {String} file Path to the SQL script + * @returns {Array} The resultset + */ + async queryFromFile(conn, file) { + let results = []; + const stmts = this.querySplit(await fs.readFile(file, 'utf8')); + + for (const stmt of stmts) + results = results.concat(await conn.query(stmt)); + + return results; + } + + /** + * Splits an SQL muti-query into a single-query array, it does an small + * parse to correctly handle the DELIMITER statement. + * + * @param {Array} stmts The splitted SQL statements + */ + querySplit(sql) { + const stmts = []; + let i, + char, + token, + escaped, + stmtStart; + + let delimiter = ';'; + const delimiterRe = /\s*delimiter\s+(\S+)[^\S\r\n]*(?:\r?\n|\r)/yi; + + function begins(str) { + let j; + for (j = 0; j < str.length; j++) + if (sql[i + j] != str[j]) + return false; + i += j; + return true; + } + + for (i = 0; i < sql.length;) { + stmtStart = i; + + delimiterRe.lastIndex = i; + const match = sql.match(delimiterRe); + if (match) { + delimiter = match[1]; + i += match[0].length; + continue; + } + + while (i < sql.length) { + char = sql[i]; + + if (token) { + if (!escaped && begins(token.end)) + token = null; + else { + escaped = !escaped && token.escape(char); + i++; + } + } else { + if (begins(delimiter)) break; + + const tok = tokenIndex.get(char); + if (tok && begins(tok.start)) + token = tok; + else + i++; + } + } + + const len = i - stmtStart - delimiter.length; + stmts.push(sql.substr(stmtStart, len)); + } + + const len = stmts.length; + if (len > 1 && /^\s*$/.test(stmts[len - 1])) + stmts.pop(); + + return stmts; + } +} + +module.exports = Push; + +if (require.main === module) + new MyVC().run(Push); diff --git a/myvc-push.sh b/myvc-push.sh deleted file mode 100755 index a6f7d30..0000000 --- a/myvc-push.sh +++ /dev/null @@ -1,394 +0,0 @@ -#!/bin/bash - -FORCE=FALSE -IS_USER=FALSE -APPLY_UNCOMMITED=FALSE -WORKSPACE="$PWD" - -error() { - local MESSAGE=$1 - >&2 echo "[ERR] $MESSAGE" - exit 1 -} -warn() { - local MESSAGE=$1 - >&2 echo "[WAR] $MESSAGE" -} -log() { - local MESSAGE=$1 - echo "[LOG] $MESSAGE" -} - -while getopts ":fuae:" option -do - case $option in - f) - FORCE=TRUE - ;; - u) - IS_USER=TRUE - ;; - e) - ENV="$OPTARG" - ;; - a) - APPLY_UNCOMMITED=TRUE - ;; - \?|:) - error "Usage: $0 [-f] [-u] [-a] [-e environment]" - ;; - esac -done - -shift $(($OPTIND - 1)) - -# Load configuration - -CONFIG_FILE="myvc.config.json" - -if [ ! -f "$CONFIG_FILE" ]; then - error "Config file not found: $CONFIG_FILE" -fi - -DIR="$(dirname "${BASH_SOURCE[0]}")" -CODE=$(jq -r ".code" "$CONFIG_FILE") - -# Load database configuration - -if [ -z "$ENV" ]; then - INI_FILE="$DIR/db.ini" -else - INI_FILE="$WORKSPACE/remotes/$ENV.ini" -fi - -if [ ! -f "$INI_FILE" ]; then - error "Database config file not found: $INI_FILE" -fi - -log "Using config file: $INI_FILE" -echo "SELECT 1;" | mysql --defaults-file="$INI_FILE" >> /dev/null - -if [ "$?" -ne "0" ]; then - error "Cannot connect to database." -fi - -# Fetch git information - -if [ ! -d "$WORKSPACE/.git" ]; then - error "Git directory not initialized." -fi - -COMMIT_SHA=$(git rev-parse HEAD) - -if [ "$?" -ne "0" ]; then - error "Cannot fetch Git HEAD." -fi - -log "HEAD: $COMMIT_SHA" - -git diff-index --quiet --cached HEAD -- -STAGED=$? - -git diff-files --quiet -CHANGED=$? - -UNTRACKED=`git ls-files --others --exclude-standard` - -if [ "$STAGED" == "1" ] || [ "$CHANGED" == "1" ] || [ -n "$UNTRACKED" ]; then - if [ "$APPLY_UNCOMMITED" == "TRUE" ]; then - warn "You are applying uncommited changes." - else - error "You have uncommited changes, commit them before pushing or use -a option." - fi -fi - -# Query functions - -dbQuery() { - local SQL=$1 - local SCHEMA=$2 - RETVAL=`echo "$SQL" | mysql --defaults-file="$INI_FILE" --silent --raw "$SCHEMA"` -} -dbExec() { - local SQL=$1 - local SCHEMA=$2 - echo "$SQL" | mysql --defaults-file="$INI_FILE" "$SCHEMA" -} -dbExecFromFile() { - local FILE_PATH=$1 - local SCHEMA=$2 - mysql --defaults-file="$INI_FILE" --default-character-set=utf8 --comments "$SCHEMA" < $FILE_PATH -} - -# Fetch database version - -VERSION_SCHEMA=$(jq -r ".versionSchema" "$CONFIG_FILE") - -if [ "$VERSION_SCHEMA" == "null" ]; then - VERSION_SCHEMA="myvc" -fi - -read -r -d '' SQL << EOM - SELECT COUNT(*) - FROM information_schema.tables - WHERE TABLE_SCHEMA = '$VERSION_SCHEMA' - AND TABLE_NAME = 'version' -EOM - -dbQuery "$SQL" -TABLE_EXISTS=$RETVAL - -SCHEMA="\`$VERSION_SCHEMA\`" - -if [ "$TABLE_EXISTS" -eq "0" ]; then - dbExec "CREATE DATABASE IF NOT EXISTS $SCHEMA" - dbExecFromFile "$DIR/structure.sql" "$VERSION_SCHEMA" - log "Version tables created into $SCHEMA schema." -fi - -dbQuery "SELECT number, gitCommit FROM $SCHEMA.version WHERE code = '$CODE'" -RETVAL=($RETVAL) -DB_VERSION=${RETVAL[0]} -DB_COMMIT=${RETVAL[1]} - -log "Database information:" -log " -> Version: $DB_VERSION" -log " -> Commit: $DB_COMMIT" - -if [[ ! "$DB_VERSION" =~ ^[0-9]*$ ]]; then - error "Wrong database version." -fi -if [ -z "$DB_VERSION" ]; then - DB_VERSION=00000 -fi - -if [ "$IS_USER" == "TRUE" ]; then - log "User information:" - - dbQuery "SELECT LEFT(USER(), INSTR(USER(), '@') - 1)" - DB_USER=$RETVAL - log " -> Name: $DB_USER" - - dbQuery "SELECT number, gitCommit FROM $SCHEMA.versionUser WHERE code = '$CODE' AND user = '$DB_USER'" - RETVAL=($RETVAL) - USER_VERSION=${RETVAL[0]} - USER_COMMIT=${RETVAL[1]} - - log " -> Version: $USER_VERSION" - log " -> Commit: $USER_COMMIT" - - if [ ! -z "$USER_VERSION" ]; then - if [ "$USER_VERSION" -gt "$DB_VERSION" ]; then - DB_VERSION=$USER_VERSION - DB_COMMIT=$USER_COMMIT - fi - fi -fi - -# Production protection - -if [ "$ENV" == "production" ]; then - echo "" - echo " ( ( ) ( ( ) ) " - echo " )\ ))\ ) ( /( )\ ) ( ))\ ) ( /( ( /( " - echo "(()/(()/( )\()|()/( ( )\ ) /(()/( )\()) )\())" - echo " /(_))(_)|(_)\ /(_)) )\ (((_) ( )(_))(_)|(_)\ ((_)\ " - echo "(_))(_)) ((_|_))_ _ ((_))\___(_(_()|__)) ((_) _((_)" - echo "| _ \ _ \ / _ \| \| | | ((/ __|_ _|_ _| / _ \| \| |" - echo "| _/ /| (_) | |) | |_| || (__ | | | | | (_) | . |" - echo "|_| |_|_\ \___/|___/ \___/ \___| |_| |___| \___/|_|\_|" - echo "" - - if [ "$FORCE" != "TRUE" ]; then - read -p "[INT] Are you sure? (Default: no) [yes|no]: " ANSWER - - if [ "$ANSWER" != "yes" ]; then - log "Aborting changes." - exit - fi - fi -fi - -# Apply versions - -N_CHANGES=0 -VERSIONS_DIR="$WORKSPACE/versions" - -if [ -d "$VERSIONS_DIR" ]; then - LAST_APPLIED_VERSION=$DB_VERSION - - for DIR_PATH in "$VERSIONS_DIR/"*; do - DIR_NAME=$(basename $DIR_PATH) - DIR_VERSION=${DIR_NAME:0:5} - - if [ "$DIR_NAME" == "README.md" ]; then - continue - fi - if [[ ! "$DIR_NAME" =~ ^[0-9]{5}(-[a-zA-Z0-9]+)?$ ]]; then - warn "Ignoring wrong directory name: $DIR_NAME" - continue - fi - if [ "$DB_VERSION" -ge "$DIR_VERSION" ]; then - log "Ignoring already applied version: $DIR_NAME" - continue - fi - - log "Applying version: $DIR_NAME" - - for FILE in "$DIR_PATH/"*; do - FILE_NAME=$(basename "$FILE") - - if [ "$FILE_NAME" == "*" ]; then - continue - fi - if [[ ! "$FILE_NAME" =~ ^[0-9]{2}-[a-zA-Z0-9_]+\.sql$ ]]; then - warn "Ignoring wrong file name: $FILE_NAME" - continue - fi - - log " -> $FILE_NAME" - dbExecFromFile "$FILE" - N_CHANGES=$((N_CHANGES + 1)) - done - - LAST_APPLIED_VERSION=$DIR_VERSION - done -fi - -# Apply routines - -applyRoutines() { - FILES_CMD=$1 - - for FILE_PATH in `$FILES_CMD`; do - FILE_NAME=$(basename $FILE_PATH) - - if [[ ! "$FILE_PATH" =~ ^routines/ ]]; then - continue - fi - if [[ ! "$FILE_NAME" =~ ^[a-zA-Z0-9_]+\.sql$ ]]; then - warn "Ignoring wrong file name: $FILE_NAME" - continue - fi - - FILE_REL_PATH=${FILE_PATH//routines\/} - - IFS='/' read -ra SPLIT <<< "$FILE_REL_PATH" - SCHEMA=${SPLIT[0]} - NAME=${SPLIT[2]} - NAME=${NAME//\.sql/} - - ROUTINE_TYPE=${SPLIT[1]} - case "$ROUTINE_TYPE" in - events) - ROUTINE_TYPE=EVENT - ;; - functions) - ROUTINE_TYPE=FUNCTION - ;; - procedures) - ROUTINE_TYPE=PROCEDURE - ;; - triggers) - ROUTINE_TYPE=TRIGGER - ;; - views) - ROUTINE_TYPE=VIEW - ;; - *) - warn "Ignoring unknown routine type: $ROUTINE_TYPE" - continue - ;; - esac - - ROUTINE_NAME="\`$SCHEMA\`.\`$NAME\`" - - if [[ -f "$FILE_PATH" ]]; then - ACTION="REPLACE" - else - ACTION="DROP" - fi - - log " -> $ACTION: $ROUTINE_TYPE $ROUTINE_NAME" - - if [ "$ACTION" == "REPLACE" ]; then - dbExecFromFile "$FILE_PATH" "$SCHEMA" - else - dbExec "DROP $ROUTINE_TYPE IF EXISTS $ROUTINE_NAME" - fi - - ROUTINES_CHANGED=$((ROUTINES_CHANGED + 1)) - done -} - -ROUTINES_CHANGED=0 -ROUTINES_DIR="$WORKSPACE/routines" - -if [ -d "$ROUTINES_DIR" ]; then - log "Applying changed routines." - - PROCS_FILE=.procs-priv.sql - mysqldump \ - --defaults-file="$INI_FILE" \ - --no-create-info \ - --skip-triggers \ - --insert-ignore \ - mysql procs_priv > "$PROCS_FILE" - - if [ -z "$DB_COMMIT" ]; then - applyRoutines "find routines -type f" - else - applyRoutines "git diff --name-only --diff-filter=D $DB_COMMIT -- routines" - applyRoutines "git diff --name-only --diff-filter=d $DB_COMMIT -- routines" - fi - - if [ "$ROUTINES_CHANGED" -gt "0" ]; then - dbExecFromFile "$PROCS_FILE" "mysql" - - if [ "$?" -eq "0" ]; then - dbExec "FLUSH PRIVILEGES" - rm "$PROCS_FILE" - else - warn "An error ocurred when restoring routine privileges, backup saved at $PROCS_FILE" - fi - - log " -> $ROUTINES_CHANGED routines have changed." - else - log " -> No routines changed." - rm "$PROCS_FILE" - fi -fi - -N_CHANGES=$((N_CHANGES + ROUTINES_CHANGED)) - -# Display summary - -if [ "$N_CHANGES" -gt "0" ]; then - if [ "$IS_USER" == "TRUE" ]; then - SQL=( - "INSERT INTO $SCHEMA.versionUser SET " - "code = '$CODE', " - "user = '$DB_USER', " - "number = '$LAST_APPLIED_VERSION', " - "gitCommit = '$COMMIT_SHA' " - "ON DUPLICATE KEY UPDATE " - "number = VALUES(number), " - "gitCommit = VALUES(gitCommit)" - ) - else - SQL=( - "INSERT INTO $SCHEMA.version SET " - "code = '$CODE', " - "number = '$LAST_APPLIED_VERSION', " - "gitCommit = '$COMMIT_SHA' " - "ON DUPLICATE KEY UPDATE " - "number = VALUES(number), " - "gitCommit = VALUES(gitCommit)" - ) - fi - - dbExec "${SQL[*]}" - log "Changes applied succesfully." -else - log "No changes applied." -fi diff --git a/myvc-run.js b/myvc-run.js new file mode 100644 index 0000000..96d0d10 --- /dev/null +++ b/myvc-run.js @@ -0,0 +1,112 @@ + +const MyVC = require('./index'); +const docker = require('./docker'); +const fs = require('fs-extra'); +const Server = require('./server/server'); + +/** + * Builds the database image and runs a container. It only rebuilds the + * image when fixtures have been modified or when the day on which the + * image was built is different to today. Some workarounds have been used + * to avoid a bug with OverlayFS driver on MacOS. + * + * @param {Boolean} ci continuous integration environment argument + */ +class Run { + get myOpts() { + return { + alias: { + ci: 'c', + random: 'r' + } + }; + } + + async run(myvc, opts) { + const server = new Server(opts.code, opts.workspace); + await server.run(); + + const dumpDir = `${opts.workspace}/dump`; + const dumpInfo = `${dumpDir}/.dump.json`; + + if (await fs.pathExists(dumpInfo)) { + const version = JSON.parse( + await fs.readFileSync(dumpInfo, 'utf8') + ); + + const fd = await fs.open(`${dumpDir}/.changes`, 'w+'); + const changes = await myvc.changedRoutines(version.gitCommit); + + for (const change of changes) + fs.write(fd, change.mark + change.path + '\n'); + + await fs.close(fd); + } + + const dockerfilePath = path.join(__dirname, 'server', 'Dockerfile'); + + await docker.build(__dirname, { + tag: 'myvc/server', + file: `${dockerfilePath}.server` + }); + + const today = new Date(); + const pad = v => v < 10 ? '0' + v : v; + const year = today.getFullYear(); + const month = pad(today.getMonth() + 1); + const day = pad(today.getDate()); + const stamp = `${year}-${month}-${day}`; + + await docker.build(__dirname, { + tag: this.imageTag, + file: `${dockerfilePath}.dump`, + buildArg: `STAMP=${stamp}` + }); + + let runOptions; + + if (this.isRandom) + runOptions = {publish: '3306'}; + else { + runOptions = { + name: this.name, + publish: `3306:${this.dbConf.port}` + }; + try { + await this.rm(); + } catch (e) {} + } + + const runChown = process.platform != 'linux'; + + Object.assign(runOptions, null, { + env: `RUN_CHOWN=${runChown}`, + detach: true + }); + const ct = await docker.run(this.imageTag, null, runOptions); + + try { + if (this.isRandom) { + const netSettings = await ct.inspect({ + filter: '{{json .NetworkSettings}}' + }); + + if (opts.ci) + this.dbConf.host = netSettings.Gateway; + + this.dbConf.port = netSettings.Ports['3306/tcp'][0]['HostPort']; + } + + await this.wait(); + } catch (err) { + if (this.isRandom) + await this.rm(); + throw err; + } + } +} + +module.exports = Run; + +if (require.main === module) + new MyVC().run(Run); diff --git a/myvc-start.js b/myvc-start.js new file mode 100644 index 0000000..287ad7b --- /dev/null +++ b/myvc-start.js @@ -0,0 +1,42 @@ + +const MyVC = require('./index'); +const docker = require('./docker'); +const Server = require('./server/server'); + +/** + * Does the minium effort to start the database container, if it doesn't + * exists calls the run command, if it is started does nothing. Keep in + * mind that when you do not rebuild the docker you may be using an outdated + * version of it. + */ +class Start { + async run(myvc, opts) { + const server = new Server(opts.code, opts.workspace); + await server.start(); + + let status; + try { + status = await docker.inspect(opts.code, { + filter: '{{json .State.Status}}' + }); + } catch (err) { + return await this.run(); + } + + switch (status) { + case 'running': + return; + case 'exited': + await docker.start(opts.code); + await this.wait(); + return; + default: + throw new Error(`Unknown docker status: ${status}`); + } + } +} + +module.exports = Start; + +if (require.main === module) + new MyVC().run(Start); diff --git a/myvc.default.yml b/myvc.default.yml new file mode 100755 index 0000000..6191bea --- /dev/null +++ b/myvc.default.yml @@ -0,0 +1,7 @@ +versionSchema: myvc +schemas: + - myvc +fixtures: + myvc: + - version + - versionUser diff --git a/myvc.js b/myvc.js index bedbf2f..755a52e 100755 --- a/myvc.js +++ b/myvc.js @@ -1,3 +1,4 @@ #!/usr/bin/env node -require('./'); +const MyVC = require('./'); +new MyVC().run(); diff --git a/package-lock.json b/package-lock.json index b6f9c68..22a14cb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,14 +1,80 @@ { "name": "myvc", - "version": "1.0.0", + "version": "1.0.18", "lockfileVersion": 1, "requires": true, "dependencies": { + "@sindresorhus/is": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-2.1.1.tgz", + "integrity": "sha512-/aPsuoj/1Dw/kzhkgz+ES6TxG0zfTMGLwuK2ZG00k/iJzYHTLCE8mVU8EPqEOp/lmxPoq1C1C9RYToRKb2KEfg==" + }, "@sqltools/formatter": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/@sqltools/formatter/-/formatter-1.2.2.tgz", "integrity": "sha512-/5O7Fq6Vnv8L6ucmPjaWbVG1XkP4FO+w5glqfkIsq3Xw4oyNAdJddbnYodNDAfjVUvo/rrSCTom4kAND7T1o5Q==" }, + "@szmarczak/http-timer": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.5.tgz", + "integrity": "sha512-PyRA9sm1Yayuj5OIoJ1hGt2YISX45w9WcFbh6ddT0Z/0yaFxOtGLInr4jUfU1EAFVs0Yfyfev4RNwBlUaHdlDQ==", + "requires": { + "defer-to-connect": "^2.0.0" + } + }, + "@types/cacheable-request": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.1.tgz", + "integrity": "sha512-ykFq2zmBGOCbpIXtoVbz4SKY5QriWPh3AjyU4G74RYbtt5yOc5OfaY75ftjg7mikMOla1CTGpX3lLbuJh8DTrQ==", + "requires": { + "@types/http-cache-semantics": "*", + "@types/keyv": "*", + "@types/node": "*", + "@types/responselike": "*" + } + }, + "@types/http-cache-semantics": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.0.tgz", + "integrity": "sha512-c3Xy026kOF7QOTn00hbIllV1dLR9hG9NkSrLQgCVs8NF6sBU+VGWjD3wLPhmh1TYAc7ugCFsvHYMN4VcBN1U1A==" + }, + "@types/keyv": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.1.tgz", + "integrity": "sha512-MPtoySlAZQ37VoLaPcTHCu1RWJ4llDkULYZIzOYxlhxBqYPB0RsRlmMU0R6tahtFe27mIdkHV+551ZWV4PLmVw==", + "requires": { + "@types/node": "*" + } + }, + "@types/node": { + "version": "14.14.8", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.8.tgz", + "integrity": "sha512-z/5Yd59dCKI5kbxauAJgw6dLPzW+TNOItNE00PkpzNwUIEwdj/Lsqwq94H5DdYBX7C13aRA0CY32BK76+neEUA==" + }, + "@types/responselike": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.0.tgz", + "integrity": "sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA==", + "requires": { + "@types/node": "*" + } + }, + "abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" + }, + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, "ansi-gray": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/ansi-gray/-/ansi-gray-0.1.1.tgz", @@ -17,6 +83,11 @@ "ansi-wrap": "0.1.0" } }, + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" + }, "ansi-styles": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", @@ -30,6 +101,20 @@ "resolved": "https://registry.npmjs.org/ansi-wrap/-/ansi-wrap-0.1.0.tgz", "integrity": "sha1-qCJQ3bABXponyoLoLqYDu/pF768=" }, + "aproba": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", + "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==" + }, + "are-we-there-yet": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz", + "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==", + "requires": { + "delegates": "^1.0.0", + "readable-stream": "^2.0.6" + } + }, "argparse": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", @@ -38,16 +123,61 @@ "sprintf-js": "~1.0.2" } }, + "asn1": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", + "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", + "requires": { + "safer-buffer": "~2.1.0" + } + }, + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" + }, "async": { "version": "0.9.2", "resolved": "https://registry.npmjs.org/async/-/async-0.9.2.tgz", "integrity": "sha1-rqdNXmHB+JlhO/ZL2mbUx48v0X0=" }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" + }, + "aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" + }, + "aws4": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.11.0.tgz", + "integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==" + }, "balanced-match": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" }, + "bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", + "requires": { + "tweetnacl": "^0.14.3" + } + }, + "bl": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/bl/-/bl-1.2.3.tgz", + "integrity": "sha512-pvcNpa0UU69UT341rO6AYy4FVAIkUHuZXRIWbq+zHnsVcRzDDjIAhGuuYoi0d//cwIwtt4pkpKycWEfjdV+vww==", + "requires": { + "readable-stream": "^2.3.5", + "safe-buffer": "^5.1.1" + } + }, "brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -57,6 +187,53 @@ "concat-map": "0.0.1" } }, + "buffer-alloc": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/buffer-alloc/-/buffer-alloc-1.2.0.tgz", + "integrity": "sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==", + "requires": { + "buffer-alloc-unsafe": "^1.1.0", + "buffer-fill": "^1.0.0" + } + }, + "buffer-alloc-unsafe": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz", + "integrity": "sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg==" + }, + "buffer-fill": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/buffer-fill/-/buffer-fill-1.0.0.tgz", + "integrity": "sha1-+PeLdniYiO858gXNY39o5wISKyw=" + }, + "cacheable-lookup": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-2.0.1.tgz", + "integrity": "sha512-EMMbsiOTcdngM/K6gV/OxF2x0t07+vMOWxZNSCRQMjO2MY2nhZQ6OYhOOpyQrbhqsgtvKGI7hcq6xjnA92USjg==", + "requires": { + "@types/keyv": "^3.1.1", + "keyv": "^4.0.0" + } + }, + "cacheable-request": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.1.tgz", + "integrity": "sha512-lt0mJ6YAnsrBErpTMWeu5kl/tg9xMAWjavYTN6VQXM1A/teBITuNcccXsCxF0tDQQJf9DfAaX5O4e0zp0KlfZw==", + "requires": { + "clone-response": "^1.0.2", + "get-stream": "^5.1.0", + "http-cache-semantics": "^4.0.0", + "keyv": "^4.0.0", + "lowercase-keys": "^2.0.0", + "normalize-url": "^4.1.0", + "responselike": "^2.0.0" + } + }, + "caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" + }, "chalk": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", @@ -67,6 +244,31 @@ "supports-color": "^5.3.0" } }, + "chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==" + }, + "clone-response": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz", + "integrity": "sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws=", + "requires": { + "mimic-response": "^1.0.0" + }, + "dependencies": { + "mimic-response": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", + "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==" + } + } + }, + "code-point-at": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" + }, "color-convert": { "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", @@ -90,16 +292,97 @@ "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==" }, + "combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "requires": { + "delayed-stream": "~1.0.0" + } + }, "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" }, + "console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=" + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + }, + "dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "requires": { + "assert-plus": "^1.0.0" + } + }, + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "requires": { + "ms": "^2.1.1" + } + }, + "decompress-response": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-5.0.0.tgz", + "integrity": "sha512-TLZWWybuxWgoW7Lykv+gq9xvzOsUjQ9tF09Tj6NSTYGMTCHNXzrPnD6Hi+TgZq19PyTAGH4Ll/NIM/eTGglnMw==", + "requires": { + "mimic-response": "^2.0.0" + } + }, + "deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==" + }, + "defer-to-connect": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.0.tgz", + "integrity": "sha512-bYL2d05vOSf1JEZNx5vSAtPuBMkX8K9EUutg7zlKvTqKXHt7RhWJFbmd7qakVuf13i+IkGmp6FwSsONOf6VYIg==" + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" + }, + "delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=" + }, "denque": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/denque/-/denque-1.4.1.tgz", "integrity": "sha512-OfzPuSZKGcgr96rf1oODnfjqBFmr1DVoc/TrItj3Ohe0Ah1C5WX5Baquw/9U9KovnQ88EqmJbD66rKYUQYN1tQ==" }, + "detect-libc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", + "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=" + }, + "duplexer3": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz", + "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=" + }, + "ecc-jsbn": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", + "requires": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" + } + }, "ejs": { "version": "3.1.5", "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.5.tgz", @@ -108,6 +391,14 @@ "jake": "^10.6.1" } }, + "end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "requires": { + "once": "^1.4.0" + } + }, "escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", @@ -118,6 +409,16 @@ "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==" }, + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" + }, + "extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" + }, "fancy-log": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/fancy-log/-/fancy-log-1.3.3.tgz", @@ -129,6 +430,16 @@ "time-stamp": "^1.0.0" } }, + "fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" + }, + "fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" + }, "filelist": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.1.tgz", @@ -137,6 +448,26 @@ "minimatch": "^3.0.4" } }, + "forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" + }, + "form-data": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + } + }, + "fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==" + }, "fs-extra": { "version": "8.1.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", @@ -147,6 +478,34 @@ "universalify": "^0.1.0" } }, + "fs-minipass": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.7.tgz", + "integrity": "sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA==", + "requires": { + "minipass": "^2.6.0" + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" + }, + "gauge": { + "version": "2.7.4", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", + "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", + "requires": { + "aproba": "^1.0.3", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.0", + "object-assign": "^4.1.0", + "signal-exit": "^3.0.0", + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wide-align": "^1.1.0" + } + }, "generate-function": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz", @@ -155,21 +514,106 @@ "is-property": "^1.0.2" } }, + "get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "requires": { + "pump": "^3.0.0" + } + }, "getopts": { "version": "2.2.5", "resolved": "https://registry.npmjs.org/getopts/-/getopts-2.2.5.tgz", "integrity": "sha512-9jb7AW5p3in+IiJWhQiZmmwkpLaR/ccTWdWQCtZM66HJcHHLegowh4q4tSD7gouUyeNvFWRavfK9GXosQHDpFA==" }, + "getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "requires": { + "assert-plus": "^1.0.0" + } + }, + "glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "got": { + "version": "10.7.0", + "resolved": "https://registry.npmjs.org/got/-/got-10.7.0.tgz", + "integrity": "sha512-aWTDeNw9g+XqEZNcTjMMZSy7B7yE9toWOFYip7ofFTLleJhvZwUxxTxkTpKvF+p1SAA4VHmuEy7PiHTHyq8tJg==", + "requires": { + "@sindresorhus/is": "^2.0.0", + "@szmarczak/http-timer": "^4.0.0", + "@types/cacheable-request": "^6.0.1", + "cacheable-lookup": "^2.0.0", + "cacheable-request": "^7.0.1", + "decompress-response": "^5.0.0", + "duplexer3": "^0.1.4", + "get-stream": "^5.0.0", + "lowercase-keys": "^2.0.0", + "mimic-response": "^2.1.0", + "p-cancelable": "^2.0.0", + "p-event": "^4.0.0", + "responselike": "^2.0.0", + "to-readable-stream": "^2.0.0", + "type-fest": "^0.10.0" + } + }, "graceful-fs": { "version": "4.2.4", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==" }, + "har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" + }, + "har-validator": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz", + "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==", + "requires": { + "ajv": "^6.12.3", + "har-schema": "^2.0.0" + } + }, "has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" }, + "has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=" + }, + "http-cache-semantics": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz", + "integrity": "sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==" + }, + "http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", + "requires": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + } + }, "iconv-lite": { "version": "0.6.2", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.2.tgz", @@ -178,16 +622,66 @@ "safer-buffer": ">= 2.1.2 < 3.0.0" } }, + "ignore-walk": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.3.tgz", + "integrity": "sha512-m7o6xuOaT1aqheYHKf8W6J5pYH85ZI9w077erOzLje3JsB1gkafkAhHHY19dqjulgIZHFm32Cp5uNZgcQqdJKw==", + "requires": { + "minimatch": "^3.0.4" + } + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, "ini": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==" }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "requires": { + "number-is-nan": "^1.0.0" + } + }, "is-property": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz", "integrity": "sha1-V/4cTkhHTt1lsJkR8msc1Ald2oQ=" }, + "is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" + }, + "isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" + }, "jake": { "version": "10.8.2", "resolved": "https://registry.npmjs.org/jake/-/jake-10.8.2.tgz", @@ -208,6 +702,39 @@ "esprima": "^4.0.0" } }, + "jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" + }, + "json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==" + }, + "json-schema": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", + "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + }, + "json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" + }, + "json5": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.3.tgz", + "integrity": "sha512-KXPvOm8K9IJKFM0bmdn8QXh7udDh1g/giieX0NLCaMnb4hEiVFqnop2ImTXCc5e0/oHz3LTqmHGtExn5hfMkOA==", + "requires": { + "minimist": "^1.2.5" + } + }, "jsonfile": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", @@ -216,11 +743,40 @@ "graceful-fs": "^4.1.6" } }, + "jsprim": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", + "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", + "requires": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.2.3", + "verror": "1.10.0" + } + }, + "keyv": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.0.3.tgz", + "integrity": "sha512-zdGa2TOpSZPq5mU6iowDARnMBZgtCqJ11dJROFi6tg6kTn4nuUdU09lFyLFSaHrWqpIJ+EBq4E8/Dc0Vx5vLdA==", + "requires": { + "json-buffer": "3.0.1" + } + }, + "lodash": { + "version": "4.17.20", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", + "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==" + }, "long": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" }, + "lowercase-keys": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", + "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==" + }, "lru-cache": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", @@ -229,6 +785,24 @@ "yallist": "^4.0.0" } }, + "mime-db": { + "version": "1.44.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz", + "integrity": "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==" + }, + "mime-types": { + "version": "2.1.27", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.27.tgz", + "integrity": "sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==", + "requires": { + "mime-db": "1.44.0" + } + }, + "mimic-response": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-2.1.0.tgz", + "integrity": "sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA==" + }, "minimatch": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", @@ -237,6 +811,48 @@ "brace-expansion": "^1.1.7" } }, + "minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" + }, + "minipass": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.9.0.tgz", + "integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==", + "requires": { + "safe-buffer": "^5.1.2", + "yallist": "^3.0.0" + }, + "dependencies": { + "yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" + } + } + }, + "minizlib": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.3.3.tgz", + "integrity": "sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q==", + "requires": { + "minipass": "^2.9.0" + } + }, + "mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "requires": { + "minimist": "^1.2.5" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, "mysql2": { "version": "2.2.5", "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-2.2.5.tgz", @@ -276,16 +892,326 @@ } } }, + "nan": { + "version": "2.14.2", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.2.tgz", + "integrity": "sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ==" + }, + "needle": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/needle/-/needle-2.5.2.tgz", + "integrity": "sha512-LbRIwS9BfkPvNwNHlsA41Q29kL2L/6VaOJ0qisM5lLWsTV3nP15abO5ITL6L81zqFhzjRKDAYjpcBcwM0AVvLQ==", + "requires": { + "debug": "^3.2.6", + "iconv-lite": "^0.4.4", + "sax": "^1.2.4" + }, + "dependencies": { + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + } + } + }, + "node-gyp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-4.0.0.tgz", + "integrity": "sha512-2XiryJ8sICNo6ej8d0idXDEMKfVfFK7kekGCtJAuelGsYHQxhj13KTf95swTCN2dZ/4lTfZ84Fu31jqJEEgjWA==", + "requires": { + "glob": "^7.0.3", + "graceful-fs": "^4.1.2", + "mkdirp": "^0.5.0", + "nopt": "2 || 3", + "npmlog": "0 || 1 || 2 || 3 || 4", + "osenv": "0", + "request": "^2.87.0", + "rimraf": "2", + "semver": "~5.3.0", + "tar": "^4.4.8", + "which": "1" + } + }, + "node-pre-gyp": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.13.0.tgz", + "integrity": "sha512-Md1D3xnEne8b/HGVQkZZwV27WUi1ZRuZBij24TNaZwUPU3ZAFtvT6xxJGaUVillfmMKnn5oD1HoGsp2Ftik7SQ==", + "requires": { + "detect-libc": "^1.0.2", + "mkdirp": "^0.5.1", + "needle": "^2.2.1", + "nopt": "^4.0.1", + "npm-packlist": "^1.1.6", + "npmlog": "^4.0.2", + "rc": "^1.2.7", + "rimraf": "^2.6.1", + "semver": "^5.3.0", + "tar": "^4" + }, + "dependencies": { + "nopt": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.3.tgz", + "integrity": "sha512-CvaGwVMztSMJLOeXPrez7fyfObdZqNUK1cPAEzLHrTybIua9pMdmmPR5YwtfNftIOMv3DPUhFaxsZMNTQO20Kg==", + "requires": { + "abbrev": "1", + "osenv": "^0.1.4" + } + } + } + }, + "nodegit": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/nodegit/-/nodegit-0.27.0.tgz", + "integrity": "sha512-E9K4gPjWiA0b3Tx5lfWCzG7Cvodi2idl3V5UD2fZrOrHikIfrN7Fc2kWLtMUqqomyoToYJLeIC8IV7xb1CYRLA==", + "requires": { + "fs-extra": "^7.0.0", + "got": "^10.7.0", + "json5": "^2.1.0", + "lodash": "^4.17.14", + "nan": "^2.14.0", + "node-gyp": "^4.0.0", + "node-pre-gyp": "^0.13.0", + "ramda": "^0.25.0", + "tar-fs": "^1.16.3" + }, + "dependencies": { + "fs-extra": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", + "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", + "requires": { + "graceful-fs": "^4.1.2", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + } + } + } + }, + "nopt": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz", + "integrity": "sha1-xkZdvwirzU2zWTF/eaxopkayj/k=", + "requires": { + "abbrev": "1" + } + }, + "normalize-url": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-4.5.0.tgz", + "integrity": "sha512-2s47yzUxdexf1OhyRi4Em83iQk0aPvwTddtFz4hnSSw9dCEsLEGf6SwIO8ss/19S9iBb5sJaOuTvTGDeZI00BQ==" + }, + "npm-bundled": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.1.1.tgz", + "integrity": "sha512-gqkfgGePhTpAEgUsGEgcq1rqPXA+tv/aVBlgEzfXwA1yiUJF7xtEt3CtVwOjNYQOVknDk0F20w58Fnm3EtG0fA==", + "requires": { + "npm-normalize-package-bin": "^1.0.1" + } + }, + "npm-normalize-package-bin": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-1.0.1.tgz", + "integrity": "sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA==" + }, + "npm-packlist": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-1.4.8.tgz", + "integrity": "sha512-5+AZgwru5IevF5ZdnFglB5wNlHG1AOOuw28WhUq8/8emhBmLv6jX5by4WJCh7lW0uSYZYS6DXqIsyZVIXRZU9A==", + "requires": { + "ignore-walk": "^3.0.1", + "npm-bundled": "^1.0.1", + "npm-normalize-package-bin": "^1.0.1" + } + }, + "npmlog": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", + "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", + "requires": { + "are-we-there-yet": "~1.1.2", + "console-control-strings": "~1.1.0", + "gauge": "~2.7.3", + "set-blocking": "~2.0.0" + } + }, + "number-is-nan": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" + }, + "oauth-sign": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==" + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "requires": { + "wrappy": "1" + } + }, + "os-homedir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", + "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=" + }, + "os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=" + }, + "osenv": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", + "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", + "requires": { + "os-homedir": "^1.0.0", + "os-tmpdir": "^1.0.0" + } + }, + "p-cancelable": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.0.0.tgz", + "integrity": "sha512-wvPXDmbMmu2ksjkB4Z3nZWTSkJEb9lqVdMaCKpZUGJG9TMiNp9XcbG3fn9fPKjem04fJMJnXoyFPk2FmgiaiNg==" + }, + "p-event": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/p-event/-/p-event-4.2.0.tgz", + "integrity": "sha512-KXatOjCRXXkSePPb1Nbi0p0m+gQAwdlbhi4wQKJPI1HsMQS9g+Sqp2o+QHziPr7eYJyOZet836KoHEVM1mwOrQ==", + "requires": { + "p-timeout": "^3.1.0" + } + }, + "p-finally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=" + }, + "p-timeout": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-3.2.0.tgz", + "integrity": "sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==", + "requires": { + "p-finally": "^1.0.0" + } + }, "parse-node-version": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parse-node-version/-/parse-node-version-1.0.1.tgz", "integrity": "sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA==" }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" + }, + "performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" + }, + "process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" + }, "pseudomap": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=" }, + "psl": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", + "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==" + }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" + }, + "qs": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" + }, + "ramda": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.25.0.tgz", + "integrity": "sha512-GXpfrYVPwx3K7RQ6aYT8KPS8XViSXUVJT1ONhoKPE9VAleW42YE+U+8VEyGWt41EnEQW7gwecYJriTI0pKoecQ==" + }, + "rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "requires": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + } + }, + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "request": { + "version": "2.88.2", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", + "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", + "requires": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.3", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.5.0", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + } + }, "require-yaml": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/require-yaml/-/require-yaml-0.0.1.tgz", @@ -294,16 +1220,57 @@ "js-yaml": "^3.14.0" } }, + "responselike": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.0.tgz", + "integrity": "sha512-xH48u3FTB9VsZw7R+vvgaKeLKzT6jOogbQhEe/jewwnZgzPcnyWui2Av6JpoYZF/91uueC+lqhWqeURw5/qhCw==", + "requires": { + "lowercase-keys": "^2.0.0" + } + }, + "rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "requires": { + "glob": "^7.1.3" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, "safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, + "sax": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" + }, + "semver": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz", + "integrity": "sha1-myzl094C0XxgEq0yaqa00M9U+U8=" + }, "seq-queue": { "version": "0.0.5", "resolved": "https://registry.npmjs.org/seq-queue/-/seq-queue-0.0.5.tgz", "integrity": "sha1-1WgS4cAXpuTnw+Ojeh2m143TyT4=" }, + "set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" + }, + "signal-exit": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", + "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==" + }, "sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", @@ -314,6 +1281,53 @@ "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.2.tgz", "integrity": "sha512-vF4ZbYdKS8OnoJAWBmMxCQDkiEBkGQYU7UZPtL8flbDRSNkhaXvRJ279ZtI6M+zDaQovVU4tuRgzK5fVhvFAhg==" }, + "sshpk": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", + "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", + "requires": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" + } + }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=" + }, "supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", @@ -322,16 +1336,164 @@ "has-flag": "^3.0.0" } }, + "tar": { + "version": "4.4.13", + "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.13.tgz", + "integrity": "sha512-w2VwSrBoHa5BsSyH+KxEqeQBAllHhccyMFVHtGtdMpF4W7IRWfZjFiQceJPChOeTsSDVUpER2T8FA93pr0L+QA==", + "requires": { + "chownr": "^1.1.1", + "fs-minipass": "^1.2.5", + "minipass": "^2.8.6", + "minizlib": "^1.2.1", + "mkdirp": "^0.5.0", + "safe-buffer": "^5.1.2", + "yallist": "^3.0.3" + }, + "dependencies": { + "yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" + } + } + }, + "tar-fs": { + "version": "1.16.3", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-1.16.3.tgz", + "integrity": "sha512-NvCeXpYx7OsmOh8zIOP/ebG55zZmxLE0etfWRbWok+q2Qo8x/vOR/IJT1taADXPe+jsiu9axDb3X4B+iIgNlKw==", + "requires": { + "chownr": "^1.0.1", + "mkdirp": "^0.5.1", + "pump": "^1.0.0", + "tar-stream": "^1.1.2" + }, + "dependencies": { + "pump": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/pump/-/pump-1.0.3.tgz", + "integrity": "sha512-8k0JupWme55+9tCVE+FS5ULT3K6AbgqrGa58lTT49RpyfwwcGedHqaC5LlQNdEAumn/wFsu6aPwkuPMioy8kqw==", + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + } + } + }, + "tar-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-1.6.2.tgz", + "integrity": "sha512-rzS0heiNf8Xn7/mpdSVVSMAWAoy9bfb1WOTYC78Z0UQKeKa/CWS8FOq0lKGNa8DWKAn9gxjCvMLYc5PGXYlK2A==", + "requires": { + "bl": "^1.0.0", + "buffer-alloc": "^1.2.0", + "end-of-stream": "^1.0.0", + "fs-constants": "^1.0.0", + "readable-stream": "^2.3.0", + "to-buffer": "^1.1.1", + "xtend": "^4.0.0" + } + }, "time-stamp": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/time-stamp/-/time-stamp-1.1.0.tgz", "integrity": "sha1-dkpaEa9QVhkhsTPztE5hhofg9cM=" }, + "to-buffer": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.1.1.tgz", + "integrity": "sha512-lx9B5iv7msuFYE3dytT+KE5tap+rNYw+K4jVkb9R/asAb+pbBSM17jtunHplhBe6RRJdZx3Pn2Jph24O32mOVg==" + }, + "to-readable-stream": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/to-readable-stream/-/to-readable-stream-2.1.0.tgz", + "integrity": "sha512-o3Qa6DGg1CEXshSdvWNX2sN4QHqg03SPq7U6jPXRahlQdl5dK8oXjkU/2/sGrnOZKeGV1zLSO8qPwyKklPPE7w==" + }, + "tough-cookie": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", + "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", + "requires": { + "psl": "^1.1.28", + "punycode": "^2.1.1" + } + }, + "tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "requires": { + "safe-buffer": "^5.0.1" + } + }, + "tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" + }, + "type-fest": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.10.0.tgz", + "integrity": "sha512-EUV9jo4sffrwlg8s0zDhP0T2WD3pru5Xi0+HTE3zTUmBaZNhfkite9PdSJwdXLwPVW0jnAHT56pZHIOYckPEiw==" + }, "universalify": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==" }, + "uri-js": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.0.tgz", + "integrity": "sha512-B0yRTzYdUCCn9n+F4+Gh4yIDtMQcaJsmYBDsTSG8g/OejKBodLQ2IHfN3bM7jUsRXndopT7OIXWdYqc1fjmV6g==", + "requires": { + "punycode": "^2.1.0" + } + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + }, + "uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==" + }, + "verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "requires": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "requires": { + "isexe": "^2.0.0" + } + }, + "wide-align": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", + "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", + "requires": { + "string-width": "^1.0.2 || 2" + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + }, + "xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==" + }, "yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", diff --git a/package.json b/package.json index 66ee4b4..f5f89ae 100644 --- a/package.json +++ b/package.json @@ -1,13 +1,10 @@ { "name": "myvc", - "version": "1.0.18", + "version": "1.1.0", "author": "Verdnatura Levante SL", "description": "MySQL Version Control", "license": "GPL-3.0", - "bin": { - "myvc": "myvc.js", - "myvc-push": "myvc-push.sh" - }, + "bin": "myvc.js", "repository": { "type": "git", "url": "https://github.com/verdnatura/myvc.git" @@ -21,6 +18,7 @@ "getopts": "^2.2.5", "ini": "^1.3.5", "mysql2": "^2.2.5", + "nodegit": "^0.27.0", "require-yaml": "0.0.1" }, "main": "index.js", diff --git a/Dockerfile.server b/server/Dockerfile similarity index 53% rename from Dockerfile.server rename to server/Dockerfile index d9c36fc..9b8eb6c 100644 --- a/Dockerfile.server +++ b/server/Dockerfile @@ -5,34 +5,51 @@ ENV TZ Europe/Madrid ARG DEBIAN_FRONTEND=noninteractive RUN apt-get update \ - && apt-get install -y --no-install-recommends curl ca-certificates \ + && apt-get install -y --no-install-recommends \ + curl \ + ca-certificates \ + tzdata \ && curl -sL https://apt.verdnatura.es/conf/verdnatura.gpg | apt-key add - \ && echo "deb http://apt.verdnatura.es/ jessie main" > /etc/apt/sources.list.d/vn.list \ && apt-get update \ && apt-get install -y \ - git \ - jq \ vn-mariadb \ - && apt-get purge -y --auto-remove curl ca-certificates \ + && curl -sL https://deb.nodesource.com/setup_14.x | bash - \ + && apt-get install -y --no-install-recommends \ + nodejs \ && rm -rf /var/lib/apt/lists/* -COPY docker/docker.cnf /etc/mysql/conf.d/ -COPY \ - docker/docker-init.sh \ - docker/docker-temp-start.sh \ - docker/docker-temp-stop.sh \ - docker/docker-dump.sh \ - docker/docker-start.sh \ - myvc-push.sh \ - structure.sql \ - db.ini \ - /usr/local/bin/ - RUN mkdir /mysql-data \ && chown -R mysql:mysql /mysql-data +WORKDIR /myvc + +COPY \ + package.json \ + package-lock.json \ + ./ +RUN npm install --only=prod + +COPY \ + structure.sql \ + index.js \ + myvc.js \ + myvc.default.yml \ + db.ini \ + ./ +RUN ln -s /myvc/myvc.js /usr/local/bin/myvc + WORKDIR /workspace +COPY server/docker.cnf /etc/mysql/conf.d/ +COPY \ + server/docker-init.sh \ + server/docker-temp-start.sh \ + server/docker-temp-stop.sh \ + server/docker-dump.sh \ + server/docker-start.sh \ + /usr/local/bin/ + USER mysql ENTRYPOINT ["docker-start.sh"] diff --git a/Dockerfile.dump b/server/Dockerfile.dump similarity index 69% rename from Dockerfile.dump rename to server/Dockerfile.dump index aad14d5..a93cc55 100644 --- a/Dockerfile.dump +++ b/server/Dockerfile.dump @@ -4,20 +4,26 @@ USER root COPY \ dump/.dump.sql \ - dump/structure.sql \ - myvc.config.json \ + dump/structure.sql \ + dump/ +COPY myvc.config.yml \ ./ + RUN gosu mysql docker-init.sh \ && docker-dump.sh dump/structure \ && docker-dump.sh dump/.dump \ && gosu mysql docker-temp-stop.sh -COPY routines ./routines -COPY versions ./versions -COPY dump/fixtures.sql ./ +COPY routines routines +COPY versions versions +COPY \ + dump/fixtures.sql \ + dump/.changes \ + dump/ + ARG STAMP=unknown RUN gosu mysql docker-temp-start.sh \ - && myvc-push.sh -a \ + && myvc push \ && docker-dump.sh dump/fixtures \ && gosu mysql docker-temp-stop.sh diff --git a/docker/docker-dump.sh b/server/docker-dump.sh similarity index 84% rename from docker/docker-dump.sh rename to server/docker-dump.sh index 6071b05..c5480ab 100755 --- a/docker/docker-dump.sh +++ b/server/docker-dump.sh @@ -2,8 +2,8 @@ FILE="$1.sql" -if [ -f "$FILE" ]; then +#if [ -f "$FILE" ]; then echo "[LOG] -> Importing $FILE" export MYSQL_PWD=root mysql -u root --default-character-set=utf8 --comments -f < "$FILE" -fi +#fi diff --git a/docker/docker-init.sh b/server/docker-init.sh similarity index 100% rename from docker/docker-init.sh rename to server/docker-init.sh diff --git a/docker/docker-start.sh b/server/docker-start.sh similarity index 100% rename from docker/docker-start.sh rename to server/docker-start.sh diff --git a/docker/docker-temp-start.sh b/server/docker-temp-start.sh similarity index 100% rename from docker/docker-temp-start.sh rename to server/docker-temp-start.sh diff --git a/docker/docker-temp-stop.sh b/server/docker-temp-stop.sh similarity index 100% rename from docker/docker-temp-stop.sh rename to server/docker-temp-stop.sh diff --git a/docker/docker.cnf b/server/docker.cnf similarity index 100% rename from docker/docker.cnf rename to server/docker.cnf diff --git a/server/server.js b/server/server.js new file mode 100644 index 0000000..72868be --- /dev/null +++ b/server/server.js @@ -0,0 +1,81 @@ + +const log = require('fancy-log'); +const path = require('path'); +const docker = require('../docker'); + +module.exports = class Server { + constructor(name, context) { + Object.assign(this, { + id: name, + name, + isRandom: name == null, + dbConf: { + host: 'localhost', + port: '3306', + username: 'root', + password: 'root' + }, + imageTag: name || 'myvc/dump', + context + }); + } + + wait() { + return new Promise((resolve, reject) => { + const mysql = require('mysql2'); + + let interval = 100; + let elapsedTime = 0; + let maxInterval = 4 * 60 * 1000; + + let myConf = { + user: this.dbConf.username, + password: this.dbConf.password, + host: this.dbConf.host, + port: this.dbConf.port + }; + + log('Waiting for MySQL init process...'); + + async function checker() { + elapsedTime += interval; + let status; + + try { + status = await docker.inspect(this.id, { + filter: '{{json .State.Status}}' + }); + } catch (err) { + return reject(new Error(err.message)); + } + + if (status === 'exited') + return reject(new Error('Docker exited, please see the docker logs for more info')); + + let conn = mysql.createConnection(myConf); + conn.on('error', () => {}); + conn.connect(err => { + conn.destroy(); + if (!err) { + log('MySQL process ready.'); + return resolve(); + } + + if (elapsedTime >= maxInterval) + reject(new Error(`MySQL not initialized whithin ${elapsedTime / 1000} secs`)); + else + setTimeout(bindedChecker, interval); + }); + } + let bindedChecker = checker.bind(this); + bindedChecker(); + }); + } + + async rm() { + try { + await docker.stop(this.id); + await docker.rm(this.id, {volumes: true}); + } catch (e) {} + } +}; diff --git a/workspace/.gitignore b/template/.gitignore similarity index 100% rename from workspace/.gitignore rename to template/.gitignore diff --git a/workspace/dump/.dump.sql b/template/dump/.dump.sql similarity index 100% rename from workspace/dump/.dump.sql rename to template/dump/.dump.sql diff --git a/workspace/dump/fixtures.sql b/template/dump/fixtures.sql similarity index 100% rename from workspace/dump/fixtures.sql rename to template/dump/fixtures.sql diff --git a/workspace/dump/structure.sql b/template/dump/structure.sql similarity index 100% rename from workspace/dump/structure.sql rename to template/dump/structure.sql diff --git a/template/myvc.config.yml b/template/myvc.config.yml new file mode 100755 index 0000000..666e079 --- /dev/null +++ b/template/myvc.config.yml @@ -0,0 +1,11 @@ +code: my-db +schemas: + - myvc + - my_db +fixtures: + myvc: + - version + - versionUser + my_db: + - table1 + - table2 diff --git a/workspace/package.json b/template/package.json similarity index 100% rename from workspace/package.json rename to template/package.json diff --git a/workspace/routines/README.md b/template/routines/README.md similarity index 100% rename from workspace/routines/README.md rename to template/routines/README.md diff --git a/workspace/versions/00001-firstVersion/00-test.sql b/template/versions/00001-firstVersion/00-test.sql similarity index 100% rename from workspace/versions/00001-firstVersion/00-test.sql rename to template/versions/00001-firstVersion/00-test.sql diff --git a/workspace/versions/README.md b/template/versions/README.md similarity index 100% rename from workspace/versions/README.md rename to template/versions/README.md diff --git a/workspace/myvc.config.json b/workspace/myvc.config.json deleted file mode 100755 index 2c66f0b..0000000 --- a/workspace/myvc.config.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "code": "my-db", - "schemas": [ - "myvc", - "my_db" - ], - "fixtures": { - "myvc": [ - "version", - "versionUser" - ], - "my_db": [ - "table1", - "table2" - ] - } -}