Version command, operand to option, hashsum fixes

This commit is contained in:
Juan Ferrer 2021-10-23 15:20:35 +02:00
parent 99461688cd
commit 24b35e2e90
10 changed files with 265 additions and 64 deletions

View File

@ -46,6 +46,7 @@ Database versioning commands:
* **init**: Initialize an empty workspace.
* **pull**: Incorporates database routines changes into workspace.
* **push**: Apply changes into database.
* **version**: Creates a new version.
Local server management commands:

View File

@ -8,8 +8,9 @@ const docker = require('./docker');
* Dumps structure and fixtures from remote.
*/
class Dump {
get myOpts() {
get localOpts() {
return {
operand: 'remote',
alias: {
remote: 'r'
},

View File

@ -6,8 +6,9 @@ const shajs = require('sha.js');
const nodegit = require('nodegit');
class Pull {
get myOpts() {
get localOpts() {
return {
operand: 'remote',
alias: {
force: 'f',
checkout: 'c'
@ -68,6 +69,14 @@ class Pull {
console.log(`Incorporating routine changes.`);
const exporters = [
new Exporter('function'),
new Exporter('procedure'),
new Exporter('view'),
new Exporter('trigger'),
new Exporter('event')
];
for (const exporter of exporters)
await exporter.init();
@ -75,40 +84,47 @@ class Pull {
if (!await fs.pathExists(exportDir))
await fs.mkdir(exportDir);
// Initialize SHA data
let newShaSums = {};
let oldShaSums;
const shaFile = `${opts.workspace}/.shasums.json`;
if (await fs.pathExists(shaFile))
oldShaSums = JSON.parse(await fs.readFile(shaFile, 'utf8'));
// Delete old schemas
const schemas = await fs.readdir(exportDir);
for (const schema of schemas) {
if (opts.schemas.indexOf(schema) == -1)
await fs.remove(`${exportDir}/${schema}`, {recursive: true});
}
let shaSums;
const shaFile = `${opts.workspace}/.shasums.json`;
if (await fs.pathExists(shaFile))
shaSums = JSON.parse(await fs.readFile(shaFile, 'utf8'));
else
shaSums = {};
// Export objects to SQL files
for (const schema of opts.schemas) {
let schemaDir = `${exportDir}/${schema}`;
newShaSums[schema] = {};
let schemaDir = `${exportDir}/${schema}`;
if (!await fs.pathExists(schemaDir))
await fs.mkdir(schemaDir);
let schemaSums = shaSums[schema];
if (!schemaSums) schemaSums = shaSums[schema] = {};
for (const exporter of exporters) {
const objectType = exporter.objectType;
const newSums = newShaSums[schema][objectType] = {};
let oldSums = {};
try {
oldSums = oldShaSums[schema][objectType];
} catch (e) {}
let objectSums = schemaSums[objectType];
if (!objectSums) objectSums = schemaSums[objectType] = {};
await exporter.export(conn, exportDir, schema, objectSums);
await exporter.export(conn, exportDir, schema, newSums, oldSums);
}
}
await fs.writeFile(shaFile, JSON.stringify(shaSums, null, ' '));
// Save SHA data
await fs.writeFile(shaFile, JSON.stringify(newShaSums, null, ' '));
}
}
@ -129,7 +145,7 @@ class Exporter {
this.formatter = require(`${templateDir}.js`);
}
async export(conn, exportDir, schema, shaSums) {
async export(conn, exportDir, schema, newSums, oldSums) {
const [res] = await conn.query(this.query, [schema]);
if (!res.length) return;
@ -167,30 +183,14 @@ class Exporter {
const shaSum = shajs('sha256')
.update(JSON.stringify(sql))
.digest('hex');
shaSums[routineName] = shaSum;
newSums[routineName] = shaSum;
let changed = true;
if (await fs.pathExists(routineFile)) {
const currentSql = await fs.readFile(routineFile, 'utf8');
changed = shaSums[routineName] !== shaSum;;
}
if (changed) {
if (oldSums[routineName] !== shaSum)
await fs.writeFile(routineFile, sql);
shaSums[routineName] = shaSum;
}
}
}
}
const exporters = [
new Exporter('function'),
new Exporter('procedure'),
new Exporter('view'),
new Exporter('trigger'),
new Exporter('event')
];
module.exports = Pull;
if (require.main === module)

View File

@ -10,8 +10,9 @@ const nodegit = require('nodegit');
* @property {Boolean} user Whether to change current user version
*/
class Push {
get myOpts() {
get localOpts() {
return {
operand: 'remote',
alias: {
force: 'f',
user: 'u'
@ -252,9 +253,11 @@ class Push {
`INSERT INTO versionUser
SET code = ?,
user = ?,
${column} = ?
${column} = ?,
updated = NOW()
ON DUPLICATE KEY UPDATE
${column} = VALUES(${column})`,
${column} = VALUES(${column}),
updated = VALUES(updated)`,
[
opts.code,
user,
@ -265,9 +268,11 @@ class Push {
await this.conn.query(
`INSERT INTO version
SET code = ?,
${column} = ?
${column} = ?,
updated = NOW()
ON DUPLICATE KEY UPDATE
${column} = VALUES(${column})`,
${column} = VALUES(${column}),
updated = VALUES(updated)`,
[
opts.code,
value

View File

@ -16,7 +16,7 @@ const Server = require('./server/server');
* @property {Boolean} random Whether to use a random container name
*/
class Run {
get myOpts() {
get localOpts() {
return {
alias: {
ci: 'c',

189
myvc-version.js Normal file
View File

@ -0,0 +1,189 @@
const MyVC = require('./myvc');
const fs = require('fs-extra');
/**
* Creates a new version.
*/
class Version {
mainOpt = 'name';
get localOpts() {
return {
operand: 'name',
name: {
name: 'n'
},
default: {
remote: 'production'
}
};
}
async run(myvc, opts) {
const verionsDir =`${opts.workspace}/versions`;
let versionDir;
let versionName = opts.name;
// Fetch last version number
const conn = await myvc.dbConnect();
const version = await myvc.fetchDbVersion() || {};
try {
await conn.query('START TRANSACTION');
const [[row]] = await conn.query(
`SELECT lastNumber FROM version WHERE code = ? FOR UPDATE`,
[opts.code]
);
const lastVersion = row && row.lastNumber;
console.log(
`Database information:`
+ `\n -> Version: ${version.number}`
+ `\n -> Last version: ${lastVersion}`
);
let newVersion = lastVersion ? parseInt(lastVersion) + 1 : 1;
newVersion = String(newVersion).padStart(opts.versionDigits, '0');
// Get version name
const versionNames = new Set();
const versionDirs = await fs.readdir(verionsDir);
for (const versionNameDir of versionDirs) {
const split = versionNameDir.split('-');
const versionName = split[1];
if (versionName) versionNames.add(versionName);
}
if (!versionName) {
let attempts;
const maxAttempts = 1000;
for (attempts = 0; attempts < maxAttempts; attempts++) {
versionName = randomName();
if (!versionNames.has(versionName)) break;
}
if (attempts === maxAttempts)
throw new Error(`Cannot create a unique version name after ${attempts} attempts`);
} else {
const isNameValid = typeof versionName === 'string'
&& /^[a-zA-Z0-9]+$/.test(versionName);
if (!isNameValid)
throw new Error('Version name can only contain letters or numbers');
if (versionNames.has(versionName))
throw new Error('Version with same name already exists');
}
// Create version
const versionFolder = `${newVersion}-${versionName}`;
versionDir = `${verionsDir}/${versionFolder}`;
await conn.query(
`UPDATE version SET lastNumber = ? WHERE code = ?`,
[newVersion, opts.code]
);
await fs.mkdir(versionDir);
console.log(`New version folder created: ${versionFolder}`);
await conn.query('COMMIT');
} catch (err) {
await conn.query('ROLLBACK');
if (versionDir && await fs.pathExists(versionDir))
await fs.remove(versionDir, {recursive: true});
throw err;
}
}
}
function randomName() {
const color = random(colors);
let plant = random(plants);
plant = plant.charAt(0).toUpperCase() + plant.slice(1);
return color + plant;
}
function random(array) {
return array[Math.floor(Math.random() * array.length)];
}
const colors = [
'aqua',
'azure',
'black',
'blue',
'bronze',
'brown',
'chocolate',
'crimson',
'golden',
'gray',
'green',
'lime',
'maroon',
'navy',
'orange',
'pink',
'purple',
'red',
'salmon',
'silver',
'teal',
'turquoise',
'yellow',
'wheat',
'white'
];
const plants = [
'anthurium',
'aralia',
'arborvitae',
'asparagus',
'aspidistra',
'bamboo',
'birch',
'carnation',
'camellia',
'cataractarum',
'chico',
'chrysanthemum',
'cordyline',
'cyca',
'cymbidium',
'dendro',
'dracena',
'erica',
'eucalyptus',
'fern',
'galax',
'gerbera',
'hydrangea',
'ivy',
'laurel',
'lilium',
'mastic',
'medeola',
'monstera',
'moss',
'oak',
'orchid',
'palmetto',
'paniculata',
'phormium',
'raphis',
'roebelini',
'rose',
'ruscus',
'salal',
'tulip'
];
module.exports = Version;
if (require.main === module)
new MyVC().run(Version);

View File

@ -1,4 +1,5 @@
versionSchema: myvc
versionDigits: 5
schemas:
- myvc
fixtures:

25
myvc.js
View File

@ -57,6 +57,7 @@ class MyVC {
'init',
'pull',
'push',
'version',
'dump',
'start',
'run'
@ -69,13 +70,16 @@ class MyVC {
command = new Klass();
}
const commandOpts = getopts(argv, command.myOpts);
const commandOpts = getopts(argv, command.localOpts);
Object.assign(cliOpts, commandOpts);
for (const opt in cliOpts) {
for (const opt in cliOpts)
if (opt.length > 1 || opt == '_')
opts[opt] = cliOpts[opt];
}
const operandToOpt = command.localOpts.operand;
if (opts._.length >= 2 && operandToOpt)
opts[operandToOpt] = opts._[1];
parameter('Workspace:', opts.workspace);
parameter('Remote:', opts.remote || 'local');
@ -155,6 +159,11 @@ class MyVC {
this.opts = opts;
}
async unload() {
if (this.conn)
await this.conn.end();
}
async dbConnect() {
if (!this.conn)
this.conn = await this.createConnection();
@ -165,11 +174,6 @@ class MyVC {
return await mysql.createConnection(this.opts.dbConfig);
}
async unload() {
if (this.conn)
await this.conn.end();
}
async fetchDbVersion() {
const {opts} = this;
@ -201,6 +205,7 @@ class MyVC {
const changesMap = new Map();
async function pushChanges(diff) {
if (!diff) return;
const patches = await diff.patches();
for (const patch of patches) {
@ -230,9 +235,7 @@ class MyVC {
}
await pushChanges(await this.getUnstaged(repo));
const stagedDiff = await this.getStaged(repo);
if (stagedDiff) await pushChanges(stagedDiff);
await pushChanges(await this.getStaged(repo));
return changes.sort((a, b) => {
if (b.mark != a.mark)

View File

@ -1,6 +1,6 @@
{
"name": "myvc",
"version": "1.1.11",
"version": "1.1.12",
"author": "Verdnatura Levante SL",
"description": "MySQL Version Control",
"license": "GPL-3.0",

View File

@ -1,20 +1,21 @@
CREATE TABLE `version` (
`code` varchar(255) NOT NULL,
`number` char(11) NULL DEFAULT NULL,
`gitCommit` varchar(255) NULL DEFAULT NULL,
`updated` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
`code` VARCHAR(255) NOT NULL,
`number` CHAR(11) NULL DEFAULT NULL,
`gitCommit` VARCHAR(255) NULL DEFAULT NULL,
`updated` DATETIME NOT NULL DEFAULT NULL
) ENGINE=InnoDB;
ALTER TABLE `version`
ADD PRIMARY KEY (`code`);
CREATE TABLE `versionUser` (
`code` varchar(255) NOT NULL,
`user` varchar(255) NOT NULL,
`number` char(11) NULL DEFAULT NULL,
`gitCommit` varchar(255) NULL DEFAULT NULL,
`updated` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
`code` VARCHAR(255) NOT NULL,
`user` VARCHAR(255) NOT NULL,
`number` CHAR(11) NULL DEFAULT NULL,
`gitCommit` VARCHAR(255) NULL DEFAULT NULL,
`updated` DATETIME NOT NULL DEFAULT NULL,
`lastNumber` CHAR(11) NULL DEFAULT NULL,
) ENGINE=InnoDB;
ALTER TABLE `versionUser`