diff --git a/README.md b/README.md index 1f10be3..e4573ce 100644 --- a/README.md +++ b/README.md @@ -275,9 +275,8 @@ $ myt fixtures [] ### run -Builds and starts local database server 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. +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..46fbbc3 100644 --- a/lib/command.js +++ b/lib/command.js @@ -1,19 +1,30 @@ +const EventEmitter = require('node:events'); + /** * Base class for Myt commands. */ -module.exports = class MytCommand { +module.exports = class MytCommand extends EventEmitter { constructor(myt, opts) { + super(); this.myt = myt; this.opts = opts; } + 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]); - } } 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 43d0b31..b1cb8d3 100644 --- a/myt-clean.js +++ b/myt-clean.js @@ -17,6 +17,13 @@ class Clean extends Command { } }; + static reporter = { + versionsDeleted: function(nVersions) { + console.log(`Old versions deleted: ${nVersions}`); + }, + noVersionsDeleted: 'No versions to delete.' + }; + async run(myt, opts) { await myt.dbConnect(); const version = await myt.fetchDbVersion() || {}; @@ -46,13 +53,13 @@ class Clean extends Command { path.join(archiveDir, oldVersion) ); - console.log(`Old versions deleted: ${oldVersions.length}`); + this.emit('versionsDeleted', oldVersions.length); } else - console.log(`No versions to delete.`); + this.emit('noVersionsDeleted'); } } 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 ec96400..c91482d 100644 --- a/myt-push.js +++ b/myt-push.js @@ -37,6 +37,88 @@ 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(data, action) { + let {version} = data; + let name = data.dir; + let num, color; + switch(action) { + case 'apply': + num = version.number; + name = version.name; + color = 'cyan'; + break; + case 'badVersion': + num = '?????'; + color = 'yellow'; + break; + case 'wrongDirectory': + num = '*****'; + color = 'gray'; + break; + } + console.log('', `[${num[color].bold}]`, name); + }, + logScript(script, action, error) { + let actionMsg; + switch(action) { + case 'apply': + actionMsg = '[+]'.green; + break; + case 'ignore': + actionMsg = '[I]'.blue; + break; + default: + actionMsg = '[W]'.yellow; + break; + } + console.log(' ', actionMsg.bold, script); + }, + change(status, ignore, change) { + let statusMsg; + switch(status) { + case 'added': + statusMsg = '[+]'.green; + break; + case 'deleted': + statusMsg = '[-]'.red; + break; + case 'modified': + statusMsg = '[·]'.yellow; + break; + } + + let actionMsg; + if (ignore) + actionMsg = '[I]'.blue; + else + actionMsg = '[A]'.green; + + const typeMsg = `[${change.type.abbr}]`[change.type.color]; + console.log('', + (statusMsg + actionMsg).bold, + typeMsg.bold, + change.fullName + ); + }, + routinesApplied: function(nRoutines) { + if (nRoutines > 0) { + console.log(` -> ${nRoutines} routines have changed.`); + } else { + console.log(` -> No routines changed.`); + } + } + }; + async run(myt, opts) { const conn = await myt.dbConnect(); this.conn = conn; @@ -85,24 +167,7 @@ class Push extends Command { await releaseLock(); } - async push(myt, opts, conn) { - const pushConn = await myt.createConnection(); - - // Get database version - - const version = await myt.fetchDbVersion() || {}; - - console.log( - `Database information:` - + `\n -> Version: ${version.number}` - + `\n -> Commit: ${version.gitCommit}` - ); - - if (!version.number) - version.number = String('0').padStart(opts.versionDigits, '0'); - if (!/^[0-9]*$/.test(version.number)) - throw new Error('Wrong database version'); - + async cli(myt, opts) { // Prevent push to production by mistake if (opts.remote == 'production') { @@ -134,20 +199,30 @@ class Push extends Command { } } + await super.cli(myt, opts); + } + + async push(myt, opts, conn) { + const pushConn = await myt.createConnection(); + + // Get database version + + const version = await myt.fetchDbVersion() || {}; + this.emit('dbInfo', version); + + if (!version.number) + version.number = String('0').padStart(opts.versionDigits, '0'); + if (!/^[0-9]*$/.test(version.number)) + throw new Error('Wrong database version'); + // Apply versions - console.log('Applying versions.'); + this.emit('applyingVersions'); let nChanges = 0; let silent = true; const versionsDir = opts.versionsDir; - function logVersion(version, name, error) { - console.log('', version.bold, name); - } - function logScript(type, message, error) { - console.log(' ', type.bold, message); - } function isUndoScript(script) { return /\.undo\.sql$/.test(script); } @@ -159,29 +234,28 @@ class Push extends Command { if (await fs.pathExists(versionsDir)) { const versionDirs = await fs.readdir(versionsDir); - const [[realm]] = await this.conn.query( - `SELECT realm - FROM versionConfig` + const [[row]] = await this.conn.query( + `SELECT realm FROM versionConfig` ); + const realm = row?.realm; for (const versionDir of versionDirs) { if (skipFiles.has(versionDir)) continue; const dirVersion = myt.parseVersionDir(versionDir); + const versionData = { + version: dirVersion, + current: version + }; + if (!dirVersion) { - logVersion('[?????]'.yellow, versionDir, - `Wrong directory name.` - ); + this.emit('version', versionData, 'wrongDirectory'); continue; } const versionNumber = dirVersion.number; - const versionName = dirVersion.name; - if (versionNumber.length != version.number.length) { - logVersion('[*****]'.gray, versionDir, - `Bad version length, should have ${version.number.length} characters.` - ); + this.emit('version', versionData, 'badVersion'); continue; } @@ -204,18 +278,17 @@ class Push extends Command { } if (silent) continue; - logVersion(`[${versionNumber}]`.cyan, versionName); + this.emit('version', versionData, 'apply'); for (const script of scripts) { const match = script.match(/^[0-9]{2}-[a-zA-Z0-9_]+(?:\.(?!undo)([a-zA-Z0-9_]+))?(?:\.undo)?\.sql$/); if (!match) { - logScript('[W]'.yellow, script, `Wrong file name.`); + this.emit('logScript', script, 'warn', 'wrongFile'); continue; } const skipRealm = match[1] && match[1] !== realm; - if (isUndoScript(script) || skipRealm) continue; @@ -231,9 +304,7 @@ class Push extends Command { ] ); const apply = !row || row.errorNumber; - const actionMsg = apply ? '[+]'.green : '[I]'.blue; - - logScript(actionMsg, script); + this.emit('logScript', script, apply ? 'apply' : 'ignore'); if (!apply) continue; let err; @@ -277,9 +348,7 @@ class Push extends Command { // 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(version.gitCommit); @@ -343,26 +412,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('', - (statusMsg + actionMsg).bold, - typeMsg.bold, - change.fullName - ); + this.emit('change', status, ignore, change); if (!ignore) { const scapedSchema = SqlString.escapeId(schema, true); @@ -410,10 +468,9 @@ class Push extends Command { await finalize(); - if (nRoutines > 0) { - console.log(` -> ${nRoutines} routines have 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); @@ -581,4 +638,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 b78932c..4a4bf82 100644 --- a/myt-run.js +++ b/myt-run.js @@ -10,9 +10,8 @@ const SqlString = require('sqlstring'); /** * 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. + * image when dump have been modified. Some workarounds have been used to avoid + * a bug with OverlayFS driver on MacOS. */ class Run extends Command { static usage = { @@ -34,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'); @@ -44,6 +52,8 @@ class Run extends Command { // Build base image + this.emit('buildingImage'); + let basePath = dumpDir; let baseDockerfile = path.join(dumpDir, 'Dockerfile'); @@ -74,6 +84,8 @@ class Run extends Command { // Run container + this.emit('runningContainer'); + const isRandom = opts.random; const dbConfig = Object.assign({}, opts.dbConfig); @@ -118,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) { @@ -144,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', @@ -167,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`; @@ -187,4 +200,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 cd4472d..c64e331 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 15b21d7..0f425b8 100755 --- a/myt.js +++ b/myt.js @@ -38,7 +38,9 @@ class Myt { ] }; - async run(Command) { + async cli(Command) { + this.cliMode = true; + console.log( 'Myt'.green, `v${packageJson.version}`.magenta @@ -142,9 +144,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); @@ -162,12 +164,15 @@ class Myt { process.exit(); } - async runCommand(Command, opts) { + async run(Command, 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`); @@ -263,7 +268,7 @@ class Myt { this.opts = opts; } - async unload() { + async deinit() { if (this.conn) await this.conn.end(); } @@ -375,4 +380,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 40bcf92..22c8791 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@verdnatura/myt", - "version": "1.5.22", + "version": "1.5.23", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@verdnatura/myt", - "version": "1.5.22", + "version": "1.5.23", "license": "GPL-3.0", "dependencies": { "@sqltools/formatter": "^1.2.5", diff --git a/package.json b/package.json index a096914..48d2b72 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@verdnatura/myt", - "version": "1.5.23", + "version": "1.5.24", "author": "Verdnatura Levante SL", "description": "MySQL version control", "license": "GPL-3.0",