function changeCurRelease(cb) { console.log('Linking new release as current...'); var curUpdateDir = path.join(config.path.releases, taskUid().get()); seq( cmdFactory('mv ' + startUpdateDir + ' ' + curUpdateDir), cmdFactory('rm -R ' + config.path.app.replace(/\/$/, '') + ' && ln -s ' + curUpdateDir + ' ' + config.path.app.replace(/\/$/, '')), function(err, result) { if (err) return cb(err); needFallback = true; return cb(null); }); }
return function(next) { var runMigrations = false; var runNpmInstall = false; var runEditConfig = false; var runReindex = false; var runFXMigrations = false; seq( checkBranch, resetDir, gitFetch, checkMigrations, checkFXMigrations, checkPackageJson, checkConfigSample, checkIndexes, confirmUpdate, changeCurBranch, gitSrvUpdate, npmInstall, runMigrateTask, //resetDir, showResults, next ); function checkBranch(cb) { if (argv.f) { shipit.log('> skipping branch test'); return cb(null); } shipit.log('> Checking git repo:'); shipit.remote('cd ' + target.path + ' && git rev-parse --abbrev-ref HEAD', function(err, res) { if (err) return cb(err); for (var i = 0; i < res.length; i++) { if (res[i].stdout.trim() !== target.branch) { shipit.log('Current branch is "' + res[i].stdout.trim() + '", but configured "' + target.branch + '"'); return cb(new Error('branchCheckFailed')); } } return cb(null); }); } function resetDir(cb) { shipit.log('> Reseting directory rights: '); var resetCmd; if (shipit.config.remote.resetDirScript) { resetCmd = shipit.config.remote.resetDirScript; } else { resetCmd = 'sudo /usr/bin/chown -R ' + target.owner + ':' + target.owner + ' ' + target.path + ' && sudo /usr/bin/chmod -R g+w ' + target.path; } shipit.remote(resetCmd, function(err, res) { if (err) return cb(err); shipit.log('%s: dir %s reset to "%s:%s"', shipit.config.servers, target.path, target.owner, target.owner); return cb(null); }); } function gitFetch(cb) { shipit.log('> Fetching updates: '); shipit.remote('cd ' + target.path + ' && ' + ' git remote prune origin && git fetch', cb ); } function checkMigrations(cb) { shipit.log('> Checking db migrations: '); var configName = 'config.json'; var re = /(migrations\/.*\.(sql|js))/ig; shipit.mute(); shipit.remote('cd ' + target.path + ' && git diff --name-only origin/' + target.branch, function(err, res) { shipit.unmute(); if (err) return cb(err); if (!re.test(res[0].stdout)) { shipit.log(chalk.dim('No migrations are found')); return cb(null); } shipit.log(chalk.bgYellow('Migrations are found: ' + res[0].stdout.match(re).length + ' new')); runMigrations = true; var arr; var migrations = []; while ((arr = re.exec(res[0].stdout)) !== null) { migrations.push(arr[0]); } for (var i = 0; i < migrations.length; i++) { shipit.log('%s. %s', i + 1, migrations[i]); } shipit.remote('cd ' + target.path + ' && cat ./' + configName, function(err, res) { if (err) { shipit.log('config not found'); return cb(null); } shipit.log('Parsing config: '); var config; try { config = JSON.parse(res[0].stdout); } catch (e) { shipit.log(e); return cb('Can\'t parse the ' + configName); } var cmd = 'mysql -h' + config.mysql.host + ' -u' + config.mysql.user + ' -p\'' + config.mysql.password + '\' ' + config.mysql.database; shipit.log('You should run: '); for (var i = 0; i < migrations.length; i++) { shipit.log(cmd + ' < ' + migrations[i]); } return cb(null); }); }); } function checkFXMigrations(cb) { shipit.log('> Checking fx migrations: '); var re = /(migrations\/flexible\/.*\.xml)/ig; shipit.mute(); shipit.remote('cd ' + target.path + ' && git diff --name-only origin/' + target.branch, function(err, res) { shipit.unmute(); if (err) return cb(err); if (!re.test(res[0].stdout)) { shipit.log(chalk.dim('No fx migrations are found')); return cb(null); } shipit.log(chalk.bgYellow('FX migrations are found: ' + res[0].stdout.match(re).length + ' new')); runFXMigrations = true; var arr; var migrations = []; while ((arr = re.exec(res[0].stdout)) !== null) { migrations.push(arr[0]); } for (var i = 0; i < migrations.length; i++) { shipit.log('%s. %s', i + 1, migrations[i]); } cb(null); }); } function checkPackageJson(cb) { shipit.log('> Checking package.json changes: '); shipit.remote('cd ' + target.path + ' && git diff ..origin/' + target.branch + ' package.json | grep \'^+\'' + ' | awk \'!/package.json/\' | awk \'!/version/\'', function(err, res) { if (err) return cb(err); if (!res[0].stdout) { shipit.log(chalk.dim('No new packages are found')); return cb(null); } shipit.log(chalk.bgYellow('New packages are found!')); shipit.log('You should run "npm install" to install them!'); runNpmInstall = true; return cb(null); }); } function checkConfigSample(cb) { shipit.log('> Checking config file changes...'); var configRe = /(config.(js|json)\.sample)/ig; shipit.mute(); shipit.remote('cd ' + target.path + ' && ls', function(err, res) { shipit.unmute(); if (err) return cb(err); if (!configRe.test(res[0].stdout)) { shipit.log('no config sample is found..'); return cb(null); } var configName = res[0].stdout.match(configRe)[0]; shipit.remote('cd ' + target.path + ' && git diff ..origin/' + target.branch + ' ' + configName, function(err, res) { if (err) return cb(err); if (!res[0].stdout) { shipit.log(chalk.dim('No config changes are found')); return cb(null); } // for easy copy/paste console.log(res[0].stdout); shipit.log(chalk.bgYellow('Config changes are found!')); shipit.log('You should edit the config file!'); runEditConfig = true; return cb(null); }); }); } function checkIndexes(cb) { shipit.log('> Checking changes in indexes.js...'); var fnameRe = /(indexes.js)/ig; shipit.mute(); shipit.remote('cd ' + target.path + ' && ls', function(err, res) { shipit.unmute(); if (err) return cb(err); if (!fnameRe.test(res[0].stdout)) { shipit.log('indexes.js not found'); return cb(null); } var fileName = res[0].stdout.match(fnameRe)[0]; shipit.remote('cd ' + target.path + ' && git diff ..origin/' + target.branch + ' ' + fileName, function(err, res) { if (err) return cb(err); if (!res[0].stdout) { shipit.log(chalk.dim('indexes.js changes not found')); return cb(null); } shipit.log(chalk.bgYellow('indexes.js changes are found!')); shipit.log('You should rebuild sphinx indexes'); runReindex = true; return cb(null); }); }); } function confirmUpdate(cb) { shipit.log('> Files to be updated:'); shipit.remote('cd ' + target.path + ' && git diff --color --name-status ..origin/' + target.branch, function(err, res) { if (err) return cb(err); if (runMigrations) { if (target.migrateTask) { shipit.log(chalk.bgRed('You should run task `migrate`!')); } else { shipit.log(chalk.bgRed('You should run the migrations!')); } } if (runEditConfig) shipit.log(chalk.bgRed('You should edit the config file!')); if (runReindex) shipit.log(chalk.bgRed('You should rebuild Sphinx indexes')); if (runFXMigrations) shipit.log(chalk.bgRed('You should run task `migrate_flexible`!')); inquirer.prompt([{ type: 'confirm', message: 'Do you want to update the ' + chalk.yellow(targetName) + ' ?', name: 'confirm' }], function(ans) { if (!ans.confirm) { shipit.log('task was stopped by user'); return taskCallback(); } return cb(null); }); }); } function changeCurBranch(cb) { if (!argv.f) return cb(null); shipit.log('> Changing current git branch: '); shipit.remote('cd ' + target.path + ' && git checkout ' + target.branch, cb); } function gitSrvUpdate(cb) { shipit.log('> Updating servers: '); shipit.remote('cd ' + target.path + ' && git reset --hard origin/' + target.branch, function(err, res) { if (err) return cb(err); shipit.log(chalk.green('The ') + chalk.yellow(targetName) + chalk.green(' is reset to "origin/' + target.branch + '"')); return cb(null); }); } function npmInstall(cb) { if (runNpmInstall) shipit.log(chalk.red('New modules were found, you should run "npm intall"!')); inquirer.prompt([{ type: 'confirm', message: 'Do you want to run npm install?', name: 'confirm', default: runNpmInstall }], function(ans) { if (!ans.confirm) return cb(null); shipit.remote('cd ' + target.path + ' && npm install', cb); }); } function runMigrateTask(cb) { if (!(runMigrations && target.migrateTask)) return cb(null); var env = target.branch === 'master' ? 'production' : 'development'; var ndPath = targets.hasOwnProperty('noodoo') ? targets.noodoo.path : path.join(target.path, '..', '..'); var cmd = 'cd ' + ndPath + ' && NODE_ENV=' + env + ' node tasks ' + path.basename(target.path) + '/migrate'; shipit.log('You should run: '); shipit.log(chalk.yellow(cmd)); inquirer.prompt([{ type: 'confirm', message: 'Do you want to run migrate task?', name: 'confirm', default: false }], function(ans) { if (!ans.confirm) return cb(null); shipit.remoteSingle(cmd, cb); }); } function showResults(cb) { shipit.log('> Results: '); shipit.remote('cd ' + target.path + ' && echo "Git log:" && git log --color --oneline -n5' + ' && echo "\nLast changed files:" && git diff --color --name-status @{1}.. || true', cb); } };
function promptSelTasks(cb) { var prompts = []; var choices; var config = shipit.config.remote; // get remote dirs for the recursive tasks first var tasks = {}; for (var id in config.targets) { if (config.targets[id].recursive) { tasks[id] = remoteDirsFac(config.targets[id].path); } } function remoteDirsFac(remotePath) { return function(cb) { lib.getRemoteDirs(remotePath, function(err, childDirs) { if (err) return cb(err); return cb(null, childDirs); }); }; } seq(tasks, function(err, childDirs) { if (err) return cb(err); selTasks(childDirs); }); function selTasks(childDirs) { choices = []; for (var id in config.targets) { if (config.targets[id].recursive) { for (var i = 0; i < childDirs[id].length; i++) { choices.push({ name: id + '/' + childDirs[id][i], checked: false }); } } else { choices.push({ name: id, checked: false }); } choices.push(new inquirer.Separator()); } choices.pop(); // last separator prompts.push({ type: 'checkbox', message: 'Select target to update: ', name: 'tasks', choices: choices, }); inquirer.prompt(prompts, function(res) { var selTasks = {}; var count = 0; var rtaskId, dirname; for (var taskId in res) { if (!res.hasOwnProperty(taskId)) continue; for (var i = 0; i < res[taskId].length; i++) { count++; if (config.targets[res[taskId][i]]) { selTasks[res[taskId][i]] = config.targets[res[taskId][i]]; } else { rtaskId = res[taskId][i].split('/')[0]; dirname = res[taskId][i].split('/')[1]; selTasks[res[taskId][i]] = utils.copy(config.targets[rtaskId]); selTasks[res[taskId][i]].path = path.join(selTasks[res[taskId][i]].path, dirname); } } } if (count === 0) return cb('You must choose at least one task to continue'); return cb(null, selTasks); }); } }
function doTasks(targets) { seq( // target's unique targetLoop, // server's general confirmRestart, runRestartScript, showStatus, dovReport, taskCallback ); function slog(text) { var args = Array.prototype.slice.call(arguments, 1); args.unshift(chalk.yellow(text)); return function(next) { console.log.apply(console, args); next(); }; } function targetLoop(cb) { var tasks = []; for (var key in targets) { tasks.push(slog('Synchronizing ' + key + '...')); tasks.push(targetFactory(targets[key], key)); tasks.push(slog('The ' + key + ' is synchronized with origin!')); } tasks.push(cb); seq.apply(seq, tasks); } function targetFactory(target, targetName) { return function(next) { var runMigrations = false; var runNpmInstall = false; var runEditConfig = false; var runReindex = false; var runFXMigrations = false; seq( checkBranch, resetDir, gitFetch, checkMigrations, checkFXMigrations, checkPackageJson, checkConfigSample, checkIndexes, confirmUpdate, changeCurBranch, gitSrvUpdate, npmInstall, runMigrateTask, //resetDir, showResults, next ); function checkBranch(cb) { if (argv.f) { shipit.log('> skipping branch test'); return cb(null); } shipit.log('> Checking git repo:'); shipit.remote('cd ' + target.path + ' && git rev-parse --abbrev-ref HEAD', function(err, res) { if (err) return cb(err); for (var i = 0; i < res.length; i++) { if (res[i].stdout.trim() !== target.branch) { shipit.log('Current branch is "' + res[i].stdout.trim() + '", but configured "' + target.branch + '"'); return cb(new Error('branchCheckFailed')); } } return cb(null); }); } function resetDir(cb) { shipit.log('> Reseting directory rights: '); var resetCmd; if (shipit.config.remote.resetDirScript) { resetCmd = shipit.config.remote.resetDirScript; } else { resetCmd = 'sudo /usr/bin/chown -R ' + target.owner + ':' + target.owner + ' ' + target.path + ' && sudo /usr/bin/chmod -R g+w ' + target.path; } shipit.remote(resetCmd, function(err, res) { if (err) return cb(err); shipit.log('%s: dir %s reset to "%s:%s"', shipit.config.servers, target.path, target.owner, target.owner); return cb(null); }); } function gitFetch(cb) { shipit.log('> Fetching updates: '); shipit.remote('cd ' + target.path + ' && ' + ' git remote prune origin && git fetch', cb ); } function checkMigrations(cb) { shipit.log('> Checking db migrations: '); var configName = 'config.json'; var re = /(migrations\/.*\.(sql|js))/ig; shipit.mute(); shipit.remote('cd ' + target.path + ' && git diff --name-only origin/' + target.branch, function(err, res) { shipit.unmute(); if (err) return cb(err); if (!re.test(res[0].stdout)) { shipit.log(chalk.dim('No migrations are found')); return cb(null); } shipit.log(chalk.bgYellow('Migrations are found: ' + res[0].stdout.match(re).length + ' new')); runMigrations = true; var arr; var migrations = []; while ((arr = re.exec(res[0].stdout)) !== null) { migrations.push(arr[0]); } for (var i = 0; i < migrations.length; i++) { shipit.log('%s. %s', i + 1, migrations[i]); } shipit.remote('cd ' + target.path + ' && cat ./' + configName, function(err, res) { if (err) { shipit.log('config not found'); return cb(null); } shipit.log('Parsing config: '); var config; try { config = JSON.parse(res[0].stdout); } catch (e) { shipit.log(e); return cb('Can\'t parse the ' + configName); } var cmd = 'mysql -h' + config.mysql.host + ' -u' + config.mysql.user + ' -p\'' + config.mysql.password + '\' ' + config.mysql.database; shipit.log('You should run: '); for (var i = 0; i < migrations.length; i++) { shipit.log(cmd + ' < ' + migrations[i]); } return cb(null); }); }); } function checkFXMigrations(cb) { shipit.log('> Checking fx migrations: '); var re = /(migrations\/flexible\/.*\.xml)/ig; shipit.mute(); shipit.remote('cd ' + target.path + ' && git diff --name-only origin/' + target.branch, function(err, res) { shipit.unmute(); if (err) return cb(err); if (!re.test(res[0].stdout)) { shipit.log(chalk.dim('No fx migrations are found')); return cb(null); } shipit.log(chalk.bgYellow('FX migrations are found: ' + res[0].stdout.match(re).length + ' new')); runFXMigrations = true; var arr; var migrations = []; while ((arr = re.exec(res[0].stdout)) !== null) { migrations.push(arr[0]); } for (var i = 0; i < migrations.length; i++) { shipit.log('%s. %s', i + 1, migrations[i]); } cb(null); }); } function checkPackageJson(cb) { shipit.log('> Checking package.json changes: '); shipit.remote('cd ' + target.path + ' && git diff ..origin/' + target.branch + ' package.json | grep \'^+\'' + ' | awk \'!/package.json/\' | awk \'!/version/\'', function(err, res) { if (err) return cb(err); if (!res[0].stdout) { shipit.log(chalk.dim('No new packages are found')); return cb(null); } shipit.log(chalk.bgYellow('New packages are found!')); shipit.log('You should run "npm install" to install them!'); runNpmInstall = true; return cb(null); }); } function checkConfigSample(cb) { shipit.log('> Checking config file changes...'); var configRe = /(config.(js|json)\.sample)/ig; shipit.mute(); shipit.remote('cd ' + target.path + ' && ls', function(err, res) { shipit.unmute(); if (err) return cb(err); if (!configRe.test(res[0].stdout)) { shipit.log('no config sample is found..'); return cb(null); } var configName = res[0].stdout.match(configRe)[0]; shipit.remote('cd ' + target.path + ' && git diff ..origin/' + target.branch + ' ' + configName, function(err, res) { if (err) return cb(err); if (!res[0].stdout) { shipit.log(chalk.dim('No config changes are found')); return cb(null); } // for easy copy/paste console.log(res[0].stdout); shipit.log(chalk.bgYellow('Config changes are found!')); shipit.log('You should edit the config file!'); runEditConfig = true; return cb(null); }); }); } function checkIndexes(cb) { shipit.log('> Checking changes in indexes.js...'); var fnameRe = /(indexes.js)/ig; shipit.mute(); shipit.remote('cd ' + target.path + ' && ls', function(err, res) { shipit.unmute(); if (err) return cb(err); if (!fnameRe.test(res[0].stdout)) { shipit.log('indexes.js not found'); return cb(null); } var fileName = res[0].stdout.match(fnameRe)[0]; shipit.remote('cd ' + target.path + ' && git diff ..origin/' + target.branch + ' ' + fileName, function(err, res) { if (err) return cb(err); if (!res[0].stdout) { shipit.log(chalk.dim('indexes.js changes not found')); return cb(null); } shipit.log(chalk.bgYellow('indexes.js changes are found!')); shipit.log('You should rebuild sphinx indexes'); runReindex = true; return cb(null); }); }); } function confirmUpdate(cb) { shipit.log('> Files to be updated:'); shipit.remote('cd ' + target.path + ' && git diff --color --name-status ..origin/' + target.branch, function(err, res) { if (err) return cb(err); if (runMigrations) { if (target.migrateTask) { shipit.log(chalk.bgRed('You should run task `migrate`!')); } else { shipit.log(chalk.bgRed('You should run the migrations!')); } } if (runEditConfig) shipit.log(chalk.bgRed('You should edit the config file!')); if (runReindex) shipit.log(chalk.bgRed('You should rebuild Sphinx indexes')); if (runFXMigrations) shipit.log(chalk.bgRed('You should run task `migrate_flexible`!')); inquirer.prompt([{ type: 'confirm', message: 'Do you want to update the ' + chalk.yellow(targetName) + ' ?', name: 'confirm' }], function(ans) { if (!ans.confirm) { shipit.log('task was stopped by user'); return taskCallback(); } return cb(null); }); }); } function changeCurBranch(cb) { if (!argv.f) return cb(null); shipit.log('> Changing current git branch: '); shipit.remote('cd ' + target.path + ' && git checkout ' + target.branch, cb); } function gitSrvUpdate(cb) { shipit.log('> Updating servers: '); shipit.remote('cd ' + target.path + ' && git reset --hard origin/' + target.branch, function(err, res) { if (err) return cb(err); shipit.log(chalk.green('The ') + chalk.yellow(targetName) + chalk.green(' is reset to "origin/' + target.branch + '"')); return cb(null); }); } function npmInstall(cb) { if (runNpmInstall) shipit.log(chalk.red('New modules were found, you should run "npm intall"!')); inquirer.prompt([{ type: 'confirm', message: 'Do you want to run npm install?', name: 'confirm', default: runNpmInstall }], function(ans) { if (!ans.confirm) return cb(null); shipit.remote('cd ' + target.path + ' && npm install', cb); }); } function runMigrateTask(cb) { if (!(runMigrations && target.migrateTask)) return cb(null); var env = target.branch === 'master' ? 'production' : 'development'; var ndPath = targets.hasOwnProperty('noodoo') ? targets.noodoo.path : path.join(target.path, '..', '..'); var cmd = 'cd ' + ndPath + ' && NODE_ENV=' + env + ' node tasks ' + path.basename(target.path) + '/migrate'; shipit.log('You should run: '); shipit.log(chalk.yellow(cmd)); inquirer.prompt([{ type: 'confirm', message: 'Do you want to run migrate task?', name: 'confirm', default: false }], function(ans) { if (!ans.confirm) return cb(null); shipit.remoteSingle(cmd, cb); }); } function showResults(cb) { shipit.log('> Results: '); shipit.remote('cd ' + target.path + ' && echo "Git log:" && git log --color --oneline -n5' + ' && echo "\nLast changed files:" && git diff --color --name-status @{1}.. || true', cb); } }; } function confirmRestart(cb) { inquirer.prompt([{ type: 'confirm', message: 'Do you want to restart the servers?', name: 'confirm' }], function(ans) { if (!ans.confirm) { shipit.log('task was stopped by user'); return dovReport(taskCallback); } return cb(null); }); } function runRestartScript(cb) { shipit.log('> Restarting servers: '); var openUrls = []; for (var key in targets) { if (targets[key].openUrl) openUrls.push(targets[key].openUrl); } shipit.remoteOneByOne(shipit.config.remote.restartScript, { openUrls: openUrls }, function(err, res) { if (err) return cb(err); shipit.log(chalk.green('All nodes from "%s" are restarted'), shipit.config.servers); // for (var key in targets) { // console.log(targets[key]); // if (targets[key].openUrl) open(targets[key].openUrl); // } return cb(null); }); } function showStatus(cb) { shipit.log('> Server status: '); setTimeout(function() { shipit.remote(shipit.config.remote.showStatusScript, cb); }, 2000); } function dovReport(cb) { var tasks = []; for (var key in targets) { tasks.push(reportFactory(targets[key], key)); } tasks.push(cb); seq.apply(seq, tasks); function reportFactory(target, targetName) { return function(next) { shipit.log('> Checking release'); var tagRe = /v(\d+.\d+.\d+)$/i; shipit.remote('cd ' + target.path + ' && git describe --tags', function(err, res) { if (err) return next(null); if (!tagRe.test(res[0].stdout.trim())) return next(null); shipit.log('release is found!'); var tag = res[0].stdout.trim().match(tagRe)[1]; shipit.mute(); shipit.remote('cd ' + target.path + ' && cat ./CHANGELOG.md', function(err, res) { shipit.unmute(); if (err) return next(err); var data = res[0].stdout.trim(); var lines = data.split('\n'); var block = []; var count = 0; for (var i = 0; i < lines.length; i++) { if (lines[i].indexOf('##') === 0) count++; if (count === 2) block.push(lines[i]); if (count === 3) break; } block.splice(0, 1); console.log('<b>Версия ' + tag + ' для ' + targetName + ' собрана и установлена на PROD.</b>'); console.log('Список изменений:'); block.forEach(function(line) { if (line && line.trim() !== '') console.log(line); }); return next(null); }); }); }; } } }
shipit.task('build', function(taskCallback) { require('./init')(shipit); argv = require('yargs') .usage('Usage: $0 build <release-version>') .example('$0 build -v 1.6.2') .describe('v', 'Specify build version in semver format: X.Y.Z') .alias('v', 'version') .alias('p', ['patch']) .describe('p', 'Build patch version: X.Y.Z+1') .alias('n', 'minor') .describe('n', 'Build minor version: X.Y+1.0') .demand(2, 'No release version specified') .help('h') .alias('h', 'help') .argv; seq( gitReset, gitUpdate, parseVersion, updatePackageJson, updateChangelog, commit, addTag, confirm, gitPush, gitPushTags, printDovReport, copy2clipboard, taskCallback ); function gitReset(branch, cb) { shipit.log('> Reseting branch "%s"', shipit.config.branch); shipit.local('git reset --hard origin/' + shipit.config.branch, { cwd: shipit.config.local.path }, cb); } function gitUpdate(cb) { shipit.log('> Fetching updates:'); shipit.local('git checkout ' + shipit.config.branch + ' && git fetch && git diff --name-status origin/' + shipit.config.branch + ' && git reset --hard origin/' + shipit.config.branch, { cwd: shipit.config.local.path }, function(err, res) { if (err) return cb(err); shipit.log(chalk.green('"%s" is reset to "origin/%s"'), shipit.config.branch, shipit.config.branch); return cb(null); }); } function parseVersion(cb) { if (argv.v) return parse(argv.v); shipit.local('git describe --abbrev=0 --tags', { cwd: shipit.config.local.path }, function(err, res) { if (err) return cb(err); parse(res.stdout); }); function parse(versionString) { var s = versionString.trim(); ver.x = parseInt(s.match(reVer)[1], 10); ver.y = parseInt(s.match(reVer)[2], 10); ver.z = parseInt(s.match(reVer)[3], 10); if (argv.p) ver.z++; if (argv.n) { ver.y++; ver.z = 0; } shipit.log('> Release version: %s.%s.%s', ver.x, ver.y, ver.z); return cb(null); } } function updatePackageJson(cb) { shipit.log('> Edit package.json'); var file = path.join(shipit.config.local.path, 'package.json'); var content = fs.readFileSync(file, 'utf8'); content = content.replace( /"version":.*"(\d+.\d+.\d+)"/i, '"version": "' + ver.toString() + '"' ); fs.writeFileSync(file, content, 'utf8'); return cb(null); } function updateChangelog(cb) { shipit.log('> Edit CHANGELOG.md'); var file = path.join(shipit.config.local.path, 'CHANGELOG.md'); var data = fs.readFileSync(file).toString(); var lines = data.split('\n'); var insertlineNumber = 1; var urlRe = /(^.*https:\/\/github.com\/.*\/compare\/)/im; var url = data.match(urlRe); if (url) { console.log('> inserting compare url...'); url = url[0]; var prev = 'v' + data.match(/##\s(\d+\.\d+\.\d+)/i)[1]; url = url + prev + '...' + 'v' + ver.toString(); } var ts = moment().format('YYYY-MM-DD HH:mm'); var insertion = '\n## ' + ver.toString() + ' ' + ts; if (url) insertion += '\n\n' + url; lines.splice(insertlineNumber, 0, insertion); var text = lines.join('\n'); fs.writeFile(file, text, function(err) { if (err) return cb(err); return cb(null); }); } function commit(cb) { var m = 'Version ' + ver.toString(); shipit.log('> Creating commit "%s"', m); shipit.local('git commit -am "' + m + '"', { cwd: shipit.config.local.path }, function(err, res) { if (err) return cb(err); shipit.log(chalk.green('Commit added')); return cb(null); }); } function addTag(cb) { var tag = 'v' + ver.toString(); shipit.log('> Adding new tag "%s"', tag); shipit.local('git tag ' + tag, { cwd: shipit.config.local.path }, function(err, res) { if (err) return cb(err); shipit.log(chalk.green('Tag added')); return cb(null); }); } function removeTag(cb) { var tag = 'v' + ver.toString(); shipit.log('> Removing new tag locally "%s"', tag); shipit.local('git tag -d ' + tag, { cwd: shipit.config.local.path }, function(err, res) { if (err) return cb(err); shipit.log(chalk.green('Tag removed')); return cb(null); }); } function confirm(cb) { shipit.local('head -n20 CHANGELOG.md && echo "++++" && head package.json && git hist -n10', { cwd: shipit.config.local.path }, function(err, res) { if (err) return cb(err); inquirer.prompt([{ type: 'confirm', message: 'Push "' + shipit.config.branch + '" to "origin/' + shipit.config.branch + '"?', name: 'confirm' }], function(ans) { if (!ans.confirm) { shipit.log('task was stopped by user'); return removeTag(taskCallback); } return cb(null); }); }); } function gitPush(cb) { shipit.log('> Pushing new version to origin: '); shipit.local('git push origin ' + shipit.config.branch + ':' + shipit.config.branch, { cwd: shipit.config.local.path }, function(err, res) { if (err) return cb(err); shipit.log(chalk.green('"%s" is pushed to "origin/%s"'), shipit.config.branch, shipit.config.branch); return cb(null); }); } function gitPushTags(cb) { shipit.log('> Pushing tags to origin: '); shipit.local('git push origin --tags', { cwd: shipit.config.local.path }, function(err, res) { if (err) return cb(err); shipit.log(chalk.green('all tags are pushed to origin')); return cb(null); }); } function printDovReport(cb) { dovReport = '<b>Собран релиз ' + ver.toString() + ' для ' + path.basename(shipit.config.local.path) + ' и установлен на PROD.</b>\n'; var file = path.join(shipit.config.local.path, 'CHANGELOG.md'); var block = []; var count = 0; //var reTag = /^##/; var readline = require('readline'); var fs = require('fs'); var fstream = fs.createReadStream(file); var rl = readline.createInterface({ input: fstream }); rl.on('line', function(line) { // console.log(line); // there is no documented way to close/shutdown/abort/destroy // a generic Readable stream as of Node 5.3.0. if (count > 2) return; if (line.indexOf('##') !== -1) count++; // save all between second and third "##"" if (count === 2) block.push(line); }).on('close', done); function done() { block.splice(0, 1); dovReport += 'Список изменений:\n'; //console.log('<ul>'); block.forEach(function(line) { if (line && line.trim().length > 0) { console.log(line); dovReport += line + '\n'; } }); // console.log(dovReport); cb(null); } } function copy2clipboard(cb) { shipit.local('echo "' + dovReport + '" | pbcopy', 'copying to clipboard..', cb); } });
function checkChangelog(branch, cb) { // all changes should be in one single section between ##master and ## x.y.z seq( checkSingleSection, checkPlacement, checkDuplicates, cb ); function checkSingleSection(branch, next) { shipit.mute(); shipit.local('git diff -U0 origin/master CHANGELOG.md | grep "@@" | wc -l', { cwd: shipit.config.local.path }, function(err, res) { shipit.unmute(); if (err) return next(err); if (res.stdout.trim() !== '1') isOK = false; return next(null); }); } function checkPlacement(next) { shipit.log('Running CHANGELOG checks...'); var reMaster = /##\smaster/i; var reVer = /##\s(\d+)\.(\d+)\.(\d+)/; var reDiff = /^[+-]\s/; shipit.mute(); shipit.local('git diff -U10 origin/master CHANGELOG.md', { cwd: shipit.config.local.path }, function(err, res) { shipit.unmute(); if (err) return next(err); if (res.stdout.trim() === '') { isOK = true; return next(null); } var lines = res.stdout.split('\n'); var masterFound, afterMasterSection; for (var i = 0; i < lines.length; i++) { if (reMaster.test(lines[i])) masterFound = true; if (masterFound && reVer.test(lines[i])) afterMasterSection = true; if (afterMasterSection) { //console.log(lines[i]); if (reDiff.test(lines[i].trim())) { isOK = false; break; } } } return next(null); }); } function checkDuplicates(next) { //var cmd = "/bin/bash -c \"git diff -U0 origin/master CHANGELOG.md | grep -v '^[\s+]$' | sort | uniq -c | grep -v '^ *1 ' | grep -v '\[Post\]'\""; var rePost = /\[Post\]/; shipit.mute(); shipit.local('git diff origin/master CHANGELOG.md', { cwd: shipit.config.local.path }, function(err, res) { shipit.unmute(); if (err) return next(err); var lines = res.stdout.split('\n'); var sorted_arr = lines.slice().sort(); for (var i = 0; i < sorted_arr.length - 1; i++) { if (sorted_arr[i].length > 10 && !rePost.test(sorted_arr[i]) && sorted_arr[i + 1] == sorted_arr[i]) { dups.push(sorted_arr[i]); } } if (dups.length > 0) isOK = false; return next(null); }); } }
shipit.task('bupd', function(taskCallback) { require('./init')(shipit); argv = require('yargs') .usage('Usage: $0 bupd -b <branch-name>') .example('$0 build -b test') .alias('b', ['branch']) .describe('b', 'Update branch') .help('h') .alias('h', 'help') .argv; var branches = ['test', 'staging', 'development']; if (argv.branch) branches = [argv.branch]; var isOK, isPush, dups = []; seq( branchLoop, taskCallback ); function branchLoop(cb) { var tasks = []; branches.forEach(function(branch) { tasks = tasks.concat([ seqFactory(gitReset, branch), seqFactory(gitMergeMaster, branch), seqFactory(checkChangelog, branch), seqFactory(confirm, branch), seqFactory(gitPush, branch) ]); }); tasks.push(cb); seq.apply(seq, tasks); function seqFactory(func, branch) { return function(next) { return func(branch, next); }; } } function gitReset(branch, cb) { isOK = true; shipit.log('> Reseting branch "%s"', branch); shipit.local('git checkout ' + branch + ' && git fetch && git reset --hard origin/' + branch, { cwd: shipit.config.local.path }, cb); } function gitMergeMaster(branch, cb) { shipit.log('> Merging "master" into "%s"', branch); shipit.local('git pull origin master', { cwd: shipit.config.local.path }, cb); } function checkChangelog(branch, cb) { // all changes should be in one single section between ##master and ## x.y.z seq( checkSingleSection, checkPlacement, checkDuplicates, cb ); function checkSingleSection(branch, next) { shipit.mute(); shipit.local('git diff -U0 origin/master CHANGELOG.md | grep "@@" | wc -l', { cwd: shipit.config.local.path }, function(err, res) { shipit.unmute(); if (err) return next(err); if (res.stdout.trim() !== '1') isOK = false; return next(null); }); } function checkPlacement(next) { shipit.log('Running CHANGELOG checks...'); var reMaster = /##\smaster/i; var reVer = /##\s(\d+)\.(\d+)\.(\d+)/; var reDiff = /^[+-]\s/; shipit.mute(); shipit.local('git diff -U10 origin/master CHANGELOG.md', { cwd: shipit.config.local.path }, function(err, res) { shipit.unmute(); if (err) return next(err); if (res.stdout.trim() === '') { isOK = true; return next(null); } var lines = res.stdout.split('\n'); var masterFound, afterMasterSection; for (var i = 0; i < lines.length; i++) { if (reMaster.test(lines[i])) masterFound = true; if (masterFound && reVer.test(lines[i])) afterMasterSection = true; if (afterMasterSection) { //console.log(lines[i]); if (reDiff.test(lines[i].trim())) { isOK = false; break; } } } return next(null); }); } function checkDuplicates(next) { //var cmd = "/bin/bash -c \"git diff -U0 origin/master CHANGELOG.md | grep -v '^[\s+]$' | sort | uniq -c | grep -v '^ *1 ' | grep -v '\[Post\]'\""; var rePost = /\[Post\]/; shipit.mute(); shipit.local('git diff origin/master CHANGELOG.md', { cwd: shipit.config.local.path }, function(err, res) { shipit.unmute(); if (err) return next(err); var lines = res.stdout.split('\n'); var sorted_arr = lines.slice().sort(); for (var i = 0; i < sorted_arr.length - 1; i++) { if (sorted_arr[i].length > 10 && !rePost.test(sorted_arr[i]) && sorted_arr[i + 1] == sorted_arr[i]) { dups.push(sorted_arr[i]); } } if (dups.length > 0) isOK = false; return next(null); }); } } function confirm(branch, cb) { shipit.mute(); shipit.local('git diff origin/master CHANGELOG.md', { cwd: shipit.config.local.path }, function(err, res) { shipit.unmute(); if (err) return cb(err); shipit.log('CHANGELOG.md:'); var lines = res.stdout.split('\n'); lines.forEach(function(line){ if (dups.indexOf(line) !== -1) console.log(chalk.magenta(line)); else console.log(line); }); if (dups.length > 0) { shipit.log(chalk.bgYellow('Duplicates are found!')); dups.forEach(function(str){ shipit.log('dup:', str); }); } inquirer.prompt([{ type: 'confirm', message: 'Push branch "' + branch + '" to "origin/' + branch + '"?', name: 'confirm', default: isOK }], function(ans) { if (!ans.confirm) { shipit.log('task was canceled'); isPush = false; return gitReset(branch, cb); } isPush = true; return cb(null); }); }); } function gitPush(branch, cb) { if (!isPush) return cb(null); shipit.log('> Pushing new version of "%s" to origin:', branch); shipit.local('git push origin ' + branch + ':' + branch, { cwd: shipit.config.local.path }, cb); } });
function doTasks(selTasks) { enableLog(config.path.log); colorPrint('Selected tasks: "' + Object.keys(selTasks).join(' ') + '"', 'dim'); var needFallback = false; seq( copy, update, changeLastWorked, changeCurRelease, removeOldCopies, restartServer, function(err) { if (err) { if (needFallback) { colorPrint('You should perform rollback to last working release. Don\'t run update task ' + 'before you rollback to pervious configuration!', 'fgRed'); colorPrint('To rollback make: ln -sf `readlink ' + lastWorkedDir + '` ' + config.path.app.replace(/\/$/, ''), 'fgRed'); } return execError(err); } return gulpCallback(); }); function copy(cb) { console.log('Copying current app...'); var tasks = []; if (fs.existsSync(startUpdateDir)) { console.log('Previous failed update found. Removing it...'); tasks.push(cmdFactory('rm -Rf ' + startUpdateDir)); } tasks.push(cmdFactory('cp -R ' + config.path.app + '/ ' + startUpdateDir)); tasks.push(cb); seq.apply(this, tasks); } function update(cb) { console.log('Starting updates...'); var tasks = []; for (var id in selTasks) { if (!selTasks.hasOwnProperty(id)) continue; tasks.push(cmdFactory('git pull && npm install', path.join(startUpdateDir, selTasks[id].path), 'updating ' + id)); } tasks.push(cb); seq.apply(this, tasks); } function changeLastWorked(cb) { console.log('Saving link to current working app...'); cmdFactory('rm -Rf ' + lastWorkedDir + ' && ln -s `readlink ' + config.path.app.replace(/\/$/, '') + '` ' + lastWorkedDir)(cb); } function changeCurRelease(cb) { console.log('Linking new release as current...'); var curUpdateDir = path.join(config.path.releases, taskUid().get()); seq( cmdFactory('mv ' + startUpdateDir + ' ' + curUpdateDir), cmdFactory('rm -R ' + config.path.app.replace(/\/$/, '') + ' && ln -s ' + curUpdateDir + ' ' + config.path.app.replace(/\/$/, '')), function(err, result) { if (err) return cb(err); needFallback = true; return cb(null); }); } function removeOldCopies(cb) { console.log('Removing old copies...'); var allDirs = getDirsSync(config.path.releases); var dirRegexp = /\w+-[\d\d\.]+-(\d+)/; var filtered = allDirs.filter(function(val) { return dirRegexp.test(val); }); if (filtered.length <= config.maxBackups) return cb(null); var sorted = filtered.sort(function(a, b) { // reverse order return parseInt(b.match(dirRegexp)[1]) - parseInt(a.match(dirRegexp)[1]); }); var tasks = []; sorted.slice(config.maxBackups, sorted.length).forEach(function(dirname) { tasks.push(cmdFactory('rm -Rf ' + path.join(config.path.releases, dirname))); }); tasks.push(cb); seq.apply(this, tasks); } function restartServer(cb) { if (config.path.hasOwnProperty('restartScript')) { console.log('Running server restart script...'); cmdFactory(config.path.restartScript, path.dirname(config.path.restartScript))(cb); } else { console.log('No server restart script found...'); return cb(null); } } }