diff --git a/README.md b/README.md index 1ad0035..d70155b 100644 --- a/README.md +++ b/README.md @@ -275,8 +275,8 @@ $ myt fixtures [] ### run -Builds and starts local database server container. It only rebuilds the image -dump has been modified. +Builds and starts local database server container. It only rebuilds the image +when dump have been modified. ```text $ myt run [-c|--ci] [-r|--random] diff --git a/cli.js b/cli.js index e64eff7..7ee97fb 100755 --- a/cli.js +++ b/cli.js @@ -1,4 +1,4 @@ #!/usr/bin/env node const Myt = require('./myt'); -new Myt().run(); +new Myt().cli(); diff --git a/lib/command.js b/lib/command.js index 51673df..65b96e6 100644 --- a/lib/command.js +++ b/lib/command.js @@ -1,19 +1,37 @@ /** * Base class for Myt commands. */ -module.exports = class MytCommand { +module.exports = class MytCommand{ constructor(myt, opts) { this.myt = myt; this.opts = opts; + this.handlers = {}; + } + + async cli(myt, opts) { + const reporter = this.constructor.reporter; + if (reporter) + for (const event in reporter) { + const handler = reporter[event]; + if (typeof handler == 'string') { + this.on(event, () => console.log(handler)); + } else if (handler instanceof Function) + this.on(event, handler); + } + + await this.run(myt, opts); } async run(myt, opts) { throw new Error('run command not defined'); } - emit(event) { - const messages = this.constructor.messages; - if (messages && messages[event]) - console.log(messages[event]); + on(event, handler) { + this.handlers[event] = handler; + } + + emit(event, ...args) { + const handler = this.handlers[event]; + if (handler) handler (...args); } } diff --git a/lib/server.js b/lib/server.js index 6ffbd77..a0264f2 100644 --- a/lib/server.js +++ b/lib/server.js @@ -25,8 +25,6 @@ module.exports = class Server { port: dbConfig.port }; - console.log('Waiting for MySQL init process...'); - async function checker() { elapsedTime += interval; let status; @@ -46,10 +44,7 @@ module.exports = class Server { conn.on('error', () => {}); conn.connect(err => { conn.destroy(); - if (!err) { - console.log('MySQL process ready.'); - return resolve(); - } + if (!err) return resolve(); if (elapsedTime >= maxInterval) reject(new Error(`MySQL not initialized whithin ${elapsedTime / 1000} secs`)); diff --git a/myt-clean.js b/myt-clean.js index fed9bc1..93801d3 100644 --- a/myt-clean.js +++ b/myt-clean.js @@ -26,6 +26,21 @@ class Clean extends Command { } }; + static reporter = { + versionsArchived: function(nVersions) { + if (nVersions) + console.log(` -> ${oldVersions.length} versions archived.`); + else + console.log(` -> No versions archived.`); + }, + versionLogPurged: function(nPurged) { + if (nPurged) + console.log(` -> ${nPurged} changes purged from log.`); + else + console.log(` -> No logs purged.`); + } + }; + async run(myt, opts) { const conn = await myt.dbConnect(); const archiveDir = path.join(opts.versionsDir, '.archive'); @@ -71,9 +86,9 @@ class Clean extends Command { await fs.rmdir(srcDir); } - console.log(` -> ${oldVersions.length} versions archived.`); + this.emit('versionsArchived', oldVersions.length); } else - console.log(` -> No versions archived.`); + this.emit('versionsArchived'); if (opts.purge) { const versionDb = new VersionDb(myt, opts.versionsDir); @@ -103,11 +118,8 @@ class Clean extends Command { nPurged++; } } - - if (nPurged) - console.log(` -> ${nPurged} versions purged from log.`); - else - console.log(` -> No versions purged from log.`); + + this.emit('versionLogPurged', nPurged); } } } @@ -146,4 +158,4 @@ class VersionDb { module.exports = Clean; if (require.main === module) - new Myt().run(Clean); + new Myt().cli(Clean); diff --git a/myt-create.js b/myt-create.js index 9083b63..1754d9a 100755 --- a/myt-create.js +++ b/myt-create.js @@ -27,6 +27,11 @@ class Create extends Command { } }; + async cli(myt, opts) { + await super.cli(myt, opts); + console.log('Routine created.'); + } + async run(myt, opts) { const match = opts.name.match(/^(\w+)\.(\w+)$/); if (!match) @@ -63,12 +68,10 @@ class Create extends Command { const routineFile = `${routineDir}/${name}.sql`; await fs.writeFile(routineFile, sql); - - console.log('Routine created.'); } } module.exports = Create; if (require.main === module) - new Myt().run(Create); + new Myt().cli(Create); diff --git a/myt-dump.js b/myt-dump.js index f10ab17..19b13f0 100644 --- a/myt-dump.js +++ b/myt-dump.js @@ -28,7 +28,7 @@ class Dump extends Command { ] }; - static messages = { + static reporter = { dumpStructure: 'Dumping structure.', dumpData: 'Dumping data.', dumpPrivileges: 'Dumping privileges.', @@ -128,5 +128,4 @@ class Dump extends Command { module.exports = Dump; if (require.main === module) - new Myt().run(Dump); - + new Myt().cli(Dump); diff --git a/myt-fixtures.js b/myt-fixtures.js index 5d8c3a6..5b09570 100644 --- a/myt-fixtures.js +++ b/myt-fixtures.js @@ -25,4 +25,4 @@ class Fixtures extends Command { module.exports = Fixtures; if (require.main === module) - new Myt().run(Fixtures); + new Myt().cli(Fixtures); diff --git a/myt-init.js b/myt-init.js index 0487330..d5e302d 100755 --- a/myt-init.js +++ b/myt-init.js @@ -33,4 +33,4 @@ class Init extends Command { module.exports = Init; if (require.main === module) - new Myt().run(Init); + new Myt().cli(Init); diff --git a/myt-pull.js b/myt-pull.js index d472893..ce5510c 100755 --- a/myt-pull.js +++ b/myt-pull.js @@ -32,6 +32,13 @@ class Pull extends Command { ] }; + static reporter = { + creatingBranch: function(branchName) { + console.log(`Creating branch '${branchName}' from database commit.`); + }, + routineChanges: 'Incorporating routine changes.' + }; + async run(myt, opts) { const conn = await myt.dbConnect(); const repo = await myt.openRepo(); @@ -73,7 +80,7 @@ class Pull extends Command { if (version && version.gitCommit) { const now = parseInt(new Date().toJSON()); const branchName = `myt-pull_${now}`; - console.log(`Creating branch '${branchName}' from database commit.`); + this.emit('creatingBranch', branchName); const commit = await repo.getCommit(version.gitCommit); const branch = await nodegit.Branch.create(repo, `myt-pull_${now}`, commit, () => {}); @@ -83,7 +90,7 @@ class Pull extends Command { // Export routines to SQL files - console.log(`Incorporating routine changes.`); + this.emit('routineChanges', branchName); const engine = new ExporterEngine(conn, opts); await engine.init(); @@ -180,4 +187,4 @@ class Pull extends Command { module.exports = Pull; if (require.main === module) - new Myt().run(Pull); + new Myt().cli(Pull); diff --git a/myt-push.js b/myt-push.js index c0fac46..3862312 100644 --- a/myt-push.js +++ b/myt-push.js @@ -37,6 +37,95 @@ class Push extends Command { ] }; + static reporter = { + applyingVersions: 'Applying versions.', + applyingRoutines: 'Applying changed routines.', + dbInfo: function(version) { + console.log( + `Database information:` + + `\n -> Version: ${version.number}` + + `\n -> Commit: ${version.gitCommit}` + ); + }, + version(version, error) { + let actionMsg; + let number, color; + + if (!error) { + actionMsg = version.apply + ? '[A]'.green + : '[I]'.blue; + number = version.number; + color = 'cyan'; + } else { + actionMsg = '[W]'.yellow; + switch(action) { + case 'badVersion': + number = '?????'; + color = 'yellow'; + break; + case 'wrongDirectory': + number = '*****'; + color = 'gray'; + break; + } + } + + const numberMsg = `[${number}]`[color]; + console.log('', `${actionMsg}${numberMsg}`.bold, version.name); + }, + logScript(script) { + let actionMsg; + if (script.apply) + actionMsg = '[+]'.green; + else if (!script.matchRegex) + actionMsg = '[W]'.yellow; + else + actionMsg = '[I]'.blue; + + console.log(' ', actionMsg.bold, script.file); + }, + change(status, ignore, change) { + let actionMsg; + if (ignore) + actionMsg = '[I]'.blue; + else + actionMsg = '[A]'.green; + + let statusMsg; + switch(status) { + case 'added': + statusMsg = '[+]'.green; + break; + case 'deleted': + statusMsg = '[-]'.red; + break; + case 'modified': + statusMsg = '[·]'.yellow; + break; + } + + const typeMsg = `[${change.type.abbr}]`[change.type.color]; + console.log('', + (actionMsg + statusMsg).bold, + typeMsg.bold, + change.fullName + ); + }, + versionsApplied: function(nVersions, nChanges) { + if (nVersions) { + console.log(` -> ${nVersions} versions with ${nChanges} changes applied.`); + } else + console.log(` -> No versions applied.`); + }, + routinesApplied: function(nRoutines) { + if (nRoutines) { + console.log(` -> ${nRoutines} routines changed.`); + } else + console.log(` -> No routines changed.`); + } + }; + async run(myt, opts) { const conn = await myt.dbConnect(); this.conn = conn; @@ -85,24 +174,7 @@ class Push extends Command { await releaseLock(); } - async push(myt, opts, conn) { - const pushConn = await myt.createConnection(); - - // Get database version - - const dbVersion = await myt.fetchDbVersion() || {}; - - console.log( - `Database information:` - + `\n -> Version: ${dbVersion.number}` - + `\n -> Commit: ${dbVersion.gitCommit}` - ); - - if (!dbVersion.number) - dbVersion.number = String('0').padStart(opts.versionDigits, '0'); - if (!/^[0-9]*$/.test(dbVersion.number)) - throw new Error('Wrong database version'); - + async cli(myt, opts) { // Prevent push to production by mistake if (opts.remote == 'production') { @@ -134,34 +206,31 @@ class Push extends Command { } } + await super.cli(myt, opts); + } + + async push(myt, opts, conn) { + const pushConn = await myt.createConnection(); + + // Get database version + + const dbVersion = await myt.fetchDbVersion() || {}; + this.emit('dbInfo', dbVersion); + + if (!dbVersion.number) + dbVersion.number = String('0').padStart(opts.versionDigits, '0'); + if (!/^[0-9]*$/.test(dbVersion.number)) + throw new Error('Wrong database version'); + // Apply versions - console.log('Applying versions.'); + this.emit('applyingVersions'); let nVersions = 0; let nChanges = 0; - let silent = true; + let showLog = false; const versionsDir = opts.versionsDir; - function logVersion(version, name, action, error) { - let actionMsg; - switch(action) { - case 'apply': - actionMsg = '[A]'.green; - break; - case 'ignore': - actionMsg = '[I]'.blue; - break; - default: - actionMsg = '[W]'.yellow; - } - - console.log('', (actionMsg + version).bold, name); - } - function logScript(type, message, error) { - console.log(' ', type.bold, message); - } - const skipFiles = new Set([ 'README.md', '.archive' @@ -173,44 +242,27 @@ class Push extends Command { if (skipFiles.has(versionDir)) continue; const version = await myt.loadVersion(versionDir); - if (!version) { - logVersion('[?????]'.yellow, versionDir, 'warn', - `Wrong directory name.` - ); - continue; - } - if (version.number.length != dbVersion.number.length) { - logVersion('[*****]'.gray, versionDir, 'warn' - `Bad version length, should have ${dbVersion.number.length} characters.` - ); - continue; - } + let apply = false; - const {apply} = version; - if (apply) silent = false; - if (silent) continue; + if (!version) + this.emit('version', version, 'wrongDirectory'); + else if (version.number.length != dbVersion.number.length) + this.emit('version', version, 'badVersion'); + else + apply = version.apply; - const action = apply ? 'apply' : 'ignore'; - logVersion(`[${version.number}]`.cyan, version.name, action); + if (apply) showLog = true; + if (showLog) this.emit('version', version); if (!apply) continue; for (const script of version.scripts) { - const scriptFile = script.file; - - if (!script.matchRegex) { - logScript('[W]'.yellow, scriptFile, `Wrong file name.`); - continue; - } - - const actionMsg = script.apply ? '[+]'.green : '[I]'.blue; - logScript(actionMsg, scriptFile); - + this.emit('logScript', script); if (!script.apply) continue; let err; try { await connExt.queryFromFile(pushConn, - `${versionsDir}/${versionDir}/${scriptFile}`); + `${versionsDir}/${versionDir}/${script.file}`); } catch (e) { err = e; } @@ -232,7 +284,7 @@ class Push extends Command { [ opts.code, version.number, - scriptFile, + script.file, err && err.errno, err && err.message ] @@ -247,16 +299,11 @@ class Push extends Command { } } - if (nVersions) { - console.log(` -> ${nVersions} versions with ${nChanges} changes applied.`); - } else - console.log(` -> No versions applied.`); + this.emit('versionsApplied', nVersions, nChanges); // Apply routines - console.log('Applying changed routines.'); - - const gitExists = await fs.pathExists(`${opts.workspace}/.git`); + this.emit('applyingRoutines'); let nRoutines = 0; const changes = await this.changedRoutines(dbVersion.gitCommit); @@ -320,26 +367,15 @@ class Push extends Command { && opts.mockFunctions.indexOf(name) !== -1; const ignore = newSql == oldSql || isMockFn; - let statusMsg; + let status; if (exists && !oldSql) - statusMsg = '[+]'.green; + status = 'added'; else if (!exists) - statusMsg = '[-]'.red; + status = 'deleted'; else - statusMsg = '[·]'.yellow; + status = 'modified'; - let actionMsg; - if (ignore) - actionMsg = '[I]'.blue; - else - actionMsg = '[A]'.green; - - const typeMsg = `[${change.type.abbr}]`[change.type.color]; - console.log('', - (actionMsg + statusMsg).bold, - typeMsg.bold, - change.fullName - ); + this.emit('change', status, ignore, change); if (!ignore) { const scapedSchema = SqlString.escapeId(schema, true); @@ -387,11 +423,9 @@ class Push extends Command { await finalize(); - if (nRoutines) { - console.log(` -> ${nRoutines} routines changed.`); - } else - console.log(` -> No routines changed.`); + this.emit('routinesApplied', nRoutines); + const gitExists = await fs.pathExists(`${opts.workspace}/.git`); if (gitExists && opts.commit) { const repo = await nodegit.Repository.open(this.opts.workspace); const head = await repo.getHeadCommit(); @@ -558,4 +592,4 @@ class Routine { module.exports = Push; if (require.main === module) - new Myt().run(Push); + new Myt().cli(Push); diff --git a/myt-run.js b/myt-run.js index 2915949..6d9ccb4 100644 --- a/myt-run.js +++ b/myt-run.js @@ -10,7 +10,7 @@ const SqlString = require('sqlstring'); /** * Builds the database image and runs a container. It only rebuilds the image - * when dump has been modified. Some workarounds have been used to avoid a bug + * when dump have been modified. Some workarounds have been used to avoid a bug * with OverlayFS driver on MacOS. */ class Run extends Command { @@ -33,6 +33,15 @@ class Run extends Command { ] }; + static reporter = { + buildingImage: 'Building container image.', + runningContainer: 'Running container.', + waitingDb: 'Waiting for MySQL init process.', + mockingDate: 'Mocking date functions.', + applyingFixtures: 'Applying fixtures.', + creatingTriggers: 'Creating triggers.' + }; + async run(myt, opts) { const dumpDir = opts.dumpDir; const dumpDataDir = path.join(dumpDir, '.dump'); @@ -43,6 +52,8 @@ class Run extends Command { // Build base image + this.emit('buildingImage'); + let basePath = dumpDir; let baseDockerfile = path.join(dumpDir, 'Dockerfile'); @@ -73,8 +84,10 @@ class Run extends Command { // Run container + this.emit('runningContainer'); + const isRandom = opts.random; - const dbConfig = Object.assign({}, opts.dbConfig); + const dbConfig = opts.dbConfig; let runOptions; @@ -117,12 +130,13 @@ class Run extends Command { } } + this.emit('waitingDb'); await server.wait(); const conn = await myt.createConnection(); // Mock date functions - console.log('Mocking date functions.'); + this.emit('mockingDate'); const mockDateScript = path.join(dumpDir, 'mockDate.sql'); if (opts.mockDate) { @@ -143,11 +157,11 @@ class Run extends Command { commit: true, dbConfig }); - await myt.runCommand(Push, opts); + await myt.run(Push, opts); // Apply fixtures - console.log('Applying fixtures.'); + this.emit('applyingFixtures'); const fixturesFiles = [ 'fixtures.before', '.fixtures', @@ -166,7 +180,7 @@ class Run extends Command { // Create triggers if (!hasTriggers) { - console.log('Creating triggers.'); + this.emit('creatingTriggers'); for (const schema of opts.schemas) { const triggersPath = `${opts.routinesDir}/${schema}/triggers`; @@ -179,6 +193,7 @@ class Run extends Command { } } + await conn.end(); return server; } } @@ -186,4 +201,4 @@ class Run extends Command { module.exports = Run; if (require.main === module) - new Myt().run(Run); + new Myt().cli(Run); diff --git a/myt-start.js b/myt-start.js index 25d28f7..314f20c 100644 --- a/myt-start.js +++ b/myt-start.js @@ -15,6 +15,10 @@ class Start extends Command { description: 'Start local database server container' }; + static reporter = { + startingContainer: 'Starting container.' + }; + async run(myt, opts) { const ct = new Container(opts.code); let status; @@ -27,7 +31,7 @@ class Start extends Command { }); exists = true; } catch (err) { - server = await myt.runCommand(Run, opts); + server = await myt.run(Run, opts); } if (exists) { @@ -35,6 +39,7 @@ class Start extends Command { case 'running': break; case 'exited': + this.emit('startingContainer'); await ct.start(); server = new Server(ct, opts.dbConfig); await server.wait(); @@ -51,4 +56,4 @@ class Start extends Command { module.exports = Start; if (require.main === module) - new Myt().run(Start); + new Myt().cli(Start); diff --git a/myt-version.js b/myt-version.js index 909525e..219bcaf 100644 --- a/myt-version.js +++ b/myt-version.js @@ -26,6 +26,19 @@ class Version extends Command { } }; + static reporter = { + dbInfo: function(number, lastNumber) { + console.log( + `Database information:` + + `\n -> Version: ${number}` + + `\n -> Last version: ${lastNumber}` + ); + }, + versionCreated: function(versionName) { + console.log(`New version created: ${versionName}`); + } + }; + async run(myt, opts) { let newVersionDir; @@ -45,12 +58,7 @@ class Version extends Command { ); const number = row && row.number; const lastNumber = row && row.lastNumber; - - console.log( - `Database information:` - + `\n -> Version: ${number}` - + `\n -> Last version: ${lastNumber}` - ); + this.emit('dbInfo', number, lastNumber); let newVersion; if (lastNumber) @@ -117,7 +125,7 @@ class Version extends Command { `${newVersionDir}/00-firstScript.sql`, '-- Place your SQL code here\n' ); - console.log(`New version created: ${versionFolder}`); + this.emit('versionCreated', versionFolder); await conn.query('COMMIT'); } catch (err) { @@ -215,4 +223,4 @@ const plants = [ module.exports = Version; if (require.main === module) - new Myt().run(Version); + new Myt().cli(Version); diff --git a/myt.js b/myt.js index e4c5f52..e84c941 100755 --- a/myt.js +++ b/myt.js @@ -40,7 +40,9 @@ class Myt { ] }; - async run(Command) { + async cli(Command) { + this.cliMode = true; + console.log( 'Myt'.green, `v${packageJson.version}`.magenta @@ -144,9 +146,9 @@ class Myt { parameter('Workspace:', opts.workspace); parameter('Remote:', opts.remote || 'local'); - await this.load(opts); - await this.runCommand(Command, opts); - await this.unload(); + await this.init(opts); + await this.run(Command, opts); + await this.deinit(); } catch (err) { if (err.name == 'Error' && !opts.debug) { console.error('Error:'.gray, err.message.red); @@ -164,12 +166,16 @@ class Myt { process.exit(); } - async runCommand(Command, opts) { + async run(Command, opts) { + if (!opts) opts = this.opts; const command = new Command(this, opts); - return await command.run(this, opts); + if (this.cliMode) + return await command.cli(this, opts); + else + return await command.run(this, opts); } - async load(opts) { + async init(opts) { // Configuration file const defaultConfig = require(`${__dirname}/assets/myt.default.yml`); @@ -265,7 +271,7 @@ class Myt { this.opts = opts; } - async unload() { + async deinit() { if (this.conn) await this.conn.end(); } @@ -434,4 +440,4 @@ class Myt { module.exports = Myt; if (require.main === module) - new Myt().run(); + new Myt().cli(); diff --git a/package-lock.json b/package-lock.json index 638072a..422ae1b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@verdnatura/myt", - "version": "1.5.27", + "version": "1.5.28", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@verdnatura/myt", - "version": "1.5.27", + "version": "1.5.28", "license": "GPL-3.0", "dependencies": { "@sqltools/formatter": "^1.2.5", diff --git a/package.json b/package.json index 069f070..bd1d0b1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@verdnatura/myt", - "version": "1.5.27", + "version": "1.5.28", "author": "Verdnatura Levante SL", "description": "MySQL version control", "license": "GPL-3.0",