diff --git a/README.md b/README.md index 6a29338..e85f56c 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,7 @@ enable `yaba` CLI to access your repos. Now, your personal access token is generated. Copy that token and define that one as an environment variable: ```shell -export GITHUB_ACCESS_TOKEN=generated_personal_access_token +export YABA_GITHUB_ACCESS_TOKEN=generated_personal_access_token ``` You can define that env variable into `~/.bashrc`, `~/.bash_profile` or `~/.zshrc` file, choose which one is suitable @@ -52,7 +52,7 @@ If the repository owner is another GitHub account or organisation, you can defin owner to the command in every run. ```shell -export GITHUB_REPO_OWNER=repository_owner +export YABA_GITHUB_REPO_OWNER=repository_owner ``` Always `-o` or `--owner` has precedence over authenticated user. Presendence is @@ -64,13 +64,13 @@ If you want to announce your release/changelog to the specific Slack channel, yo variable with the appropriate value. ```shell -export SLACK_HOOK_URL=your_slack_hook_url +export YABA_SLACK_HOOK_URL=your_slack_hook_url ``` Also, multiple hook URLs allowed to be defined like below: ```shell -export SLACK_HOOK_URL=your_slack_hook_url_1,your_slack_hook_url_2,... +export YABA_SLACK_HOOK_URL=your_slack_hook_url_1,your_slack_hook_url_2,... ``` If the above variable is set and the `-p` command given while running the command, an announcement will be post to the diff --git a/bin/assets/slack-post-template.json b/bin/assets/slack-post-template.json new file mode 100644 index 0000000..4dd2887 --- /dev/null +++ b/bin/assets/slack-post-template.json @@ -0,0 +1,33 @@ +{ + "text": ":warning: Release has been created for {repo}!", + "blocks": [ + { + "type": "section", + "text": { + "type": "mrkdwn", + "text": " will release `{repo}` soon!" + } + }, + { + "type": "section", + "text": { + "type": "mrkdwn", + "text": ":rocket: *{repo} Changelog*" + } + }, + { + "type": "section", + "text": { + "type": "mrkdwn", + "text": "```{changelog}```" + } + }, + { + "type": "section", + "text": { + "type": "mrkdwn", + "text": "Visit release page: <{releaseUrl}|{releaseName}>" + } + } + ] +} diff --git a/bin/assets/yaba-update-message-template.txt b/bin/assets/yaba-update-message-template.txt new file mode 100644 index 0000000..bedb27c --- /dev/null +++ b/bin/assets/yaba-update-message-template.txt @@ -0,0 +1,2 @@ +New version of Yaba available! {localVersion} -> {lastVersion} +Please run {updateCommand} to update! diff --git a/bin/index.js b/bin/index.js index 14cf7e8..b045d4a 100755 --- a/bin/index.js +++ b/bin/index.js @@ -1,78 +1,105 @@ #!/usr/bin/env node -// third party lib definitions const kleur = require("kleur"); - -// local variables const helper = require('./utils/helper.js'); const tool = require('./utils/tool.js'); const flow = require('./utils/flow.js'); const options = require('./utils/command.js').options; -async function run() { +runYaba(); + +async function runYaba() { try { // https://www.npmjs.com/package/tiny-updater OR https://www.npmjs.com/package/update-notifier // can be used instead below method. - await tool.checkUpdate(); // check if the yaba cli has newer version + await tool.checkUpdate(); // check if the yaba cli has newer version // check required ENV variables - flow.checkRequiredEnvVariables(); + flow.checkRequiredEnvVariables(); // check if the current directory is git repo - if (options.repo == undefined && !helper.isGitRepo()) { - console.log(`The directory '${helper.retrieveCurrentDirectory()}' is not a Git repo.`); - return; - } + checkDirectory() // check internet connection + await flow.checkInternetConnection(); + // prepare username, repoOwner and releaseRepo const username = await flow.retrieveUsername(); const repoOwner = helper.retrieveOwner(options.owner, username); const releaseRepo = helper.retrieveReleaseRepo(options.repo); - // fetch latest release - const latestRelease = await flow.fetchLatestRelease(repoOwner, releaseRepo); - // fetch head branch - const headBranch = await flow.fetchHeadBranch(repoOwner, releaseRepo); - - if (headBranch == null) { - console.log(kleur.red("Head branch can not be found! The release has been interrupted!")); - return; - } + const headBranch = await checkHeadBranch(repoOwner, releaseRepo); // preparing the changeLog from the main/master branch if there is no previous release - let changeLog = latestRelease == null - ? await flow.listCommits(repoOwner, releaseRepo, headBranch) - : await flow.prepareChangelog(repoOwner, releaseRepo, latestRelease.tag_name, headBranch); + let changeLog = await flow.prepareChangeLog(repoOwner, releaseRepo, headBranch); // show only changelog - if (changeLog.length != 0 && options.changelog) { - console.log('\n\n' + kleur.green().underline(`${releaseRepo} changelog for upcoming release:`) + `\n\n${helper.prepareChangeLog(options.body, changeLog)}\n`); + if (canShowChangelog(changeLog)) { + printChangelog(releaseRepo, changeLog); } // create the release - if (changeLog.length != 0 && !options.changelog) { - const isPermitted = await helper.releaseCreatePermit(options.interactive); - if (isPermitted) { - let preparedChangeLog = helper.prepareChangeLog(options.body, changeLog); - await flow.createRelease(repoOwner, releaseRepo, options.draft, options.releaseName, preparedChangeLog, options.tag); - helper.playSound(options.sound); - - // publishes the changelog on slack - flow.publishToSlack(options.publish, releaseRepo, preparedChangeLog); - - } else { - console.log('Release was not prepared!'); - } + if (canCreateRelease(changeLog)) { + await prepareRelease(changeLog, repoOwner, releaseRepo); } + // release completed, to prevent hanging forcing to exit + process.exit(1); + } catch (error) { console.log(error); } } -run(); +async function prepareRelease(changeLog, repoOwner, releaseRepo) { + + const hasReleaseCreatePermission = await helper.releaseCreatePermit(options.interactive); + if (hasReleaseCreatePermission) { + let preparedChangeLog = helper.prepareChangeLog(options.body, changeLog); + const releaseUrl = await flow.createRelease(repoOwner, releaseRepo, options.draft, options.releaseName, + preparedChangeLog, options.tag); + + // play yaba sound if the release successfully created + helper.playSound(options.sound); + + // publishes the changelog on slack + await flow.publishToSlack(options.publish, releaseRepo, preparedChangeLog, releaseUrl, options.releaseName); + + } else { + console.log('Release was not prepared!'); + } +} + +async function checkHeadBranch(repoOwner, releaseRepo) { + const headBranch = await flow.fetchHeadBranch(repoOwner, releaseRepo); + if (headBranch == null) { + console.log(kleur.red("Head branch can not be found! The release has been interrupted!")); + process.exit(); + } + return headBranch; +} + +function checkDirectory() { + // check if the current directory is git repo + if (options.repo == undefined && !helper.isGitRepo()) { + console.log(`The directory '${helper.retrieveCurrentDirectory()}' is not a Git repo.`); + process.exit(); + } +} + +function printChangelog(repoName, changeLog) { + console.log('\n\n' + kleur.green().underline(`${repoName} changelog for upcoming release:`) + `\n\n${helper.prepareChangeLog(options.body, changeLog)}\n`); +} + +function canCreateRelease(changeLog) { + return changeLog.length != 0 && !options.changelog; +} + +function canShowChangelog(changeLog) { + return changeLog.length != 0 && options.changelog; +} + diff --git a/bin/utils/command.js b/bin/utils/command.js index 00274dc..7a32092 100644 --- a/bin/utils/command.js +++ b/bin/utils/command.js @@ -4,10 +4,10 @@ const package = require('../../package.json'); const commands = yargs .usage("Usage: yaba -o -r -t -n -b -d " + "-c -i -s -p ") - .option("o", { alias: "owner", describe: "The repository owner.", type: "string" }) - .option("r", { alias: "repo", describe: "The repository name.", type: "string" }) - .option("t", { alias: "tag", describe: "The name of the tag.", type: "string" }) - .option("n", { alias: "release-name", describe: "The name of the release.", type: "string" }) + .option("o", {alias: "owner", describe: "The repository owner.", type: "string"}) + .option("r", {alias: "repo", describe: "The repository name.", type: "string"}) + .option("t", {alias: "tag", describe: "The name of the tag.", type: "string"}) + .option("n", {alias: "release-name", describe: "The name of the release.", type: "string"}) .option("b", { alias: "body", describe: "Text describing the contents of the tag. If not provided, the default changelog " + diff --git a/bin/utils/constants.js b/bin/utils/constants.js new file mode 100644 index 0000000..af6b0f6 --- /dev/null +++ b/bin/utils/constants.js @@ -0,0 +1,8 @@ +module.exports = Object.freeze({ + SOUND_PATH: '../assets/yaba.mp3', + SLACK_POST_TEMPLATE: '../assets/slack-post-template.json', + RELEASE_DATE_FORMAT: 'yyyy-MM-dd', + TAG_DATE_FORMAT: 'yyyyMMdd', + UPDATE_COMMAND: 'npm update -g yaba-release-cli', + UPDATE_MESSAGE_TEMPLATE: '../assets/yaba-update-message-template.txt' +}); diff --git a/bin/utils/flow.js b/bin/utils/flow.js index 9099fc9..44a08f3 100755 --- a/bin/utils/flow.js +++ b/bin/utils/flow.js @@ -1,4 +1,4 @@ -const { Octokit } = require("octokit"); +const {Octokit} = require("octokit"); const supportsHyperlinks = require('supports-hyperlinks'); const hyperlinker = require('hyperlinker'); const isOnline = require('is-online'); @@ -8,35 +8,51 @@ const helper = require('./helper.js'); const kleur = require('kleur'); const axios = require('axios'); const octokit = new Octokit({ - auth: process.env.GITHUB_ACCESS_TOKEN + auth: process.env.GITHUB_ACCESS_TOKEN || process.env.YABA_GITHUB_ACCESS_TOKEN }); module.exports = { + + /** + * checks if the all required environment variables in place. + */ checkRequiredEnvVariables: function () { spinner.start('Checking required ENV variables...'); if (helper.requiredEnvVariablesExist() == false) { spinner.fail('The required env variables are not set in order to run the command.'); - return; + process.exit(); } spinner.succeed('Required ENV variables in place.'); }, + /** + * checks if internet connection is available. + * + * @returns {Promise} + */ checkInternetConnection: async function () { spinner.start('Checking internet connection...'); const isInternetUp = await isOnline(); if (!isInternetUp) { spinner.fail('There is no internet connection!'); - return; + process.exit(); } spinner.succeed('Internet connection established.'); }, + /** + * fetches the latest release + * + * @param owner the owner of the repository + * @param repo the repository to fetch latest release for + * @returns {Promise} + */ fetchLatestRelease: async function (owner, repo) { spinner.start('Fetching latest release...'); try { - const { data: release } = await octokit.request('GET /repos/{owner}/{repo}/releases/latest', { + const {data: release} = await octokit.request('GET /repos/{owner}/{repo}/releases/latest', { owner: owner, repo: repo }); @@ -48,9 +64,16 @@ module.exports = { } }, + /** + * fetches head branch name + * + * @param owner the owner of the repository + * @param repo the repository to fetch the head branch name + * @returns {Promise} + */ fetchHeadBranch: async function (owner, repo) { spinner.start('Fetching head branch...'); - const { data: branches } = await octokit.request('GET /repos/{owner}/{repo}/branches', { + const {data: branches} = await octokit.request('GET /repos/{owner}/{repo}/branches', { owner: owner, repo: repo }); @@ -59,9 +82,37 @@ module.exports = { return headBranch; }, + /** + * if no release found, this will list all the commits in the head branch, otherwise will prepare the changelog + * between the latest release and the given head branch. + * + * @param owner the owner of the repository + * @param repo the repository to prepare changelog for + * @param head the head branch + * @returns {Promise<*|*>} + */ + prepareChangeLog: async function (owner, repo, head) { + // fetch latest release + const latestRelease = await this.fetchLatestRelease(owner, repo); + + let changeLog = latestRelease == null + ? await this.listCommits(owner, repo, head) + : await this.prepareChangelog(owner, repo, latestRelease.tag_name, head); + return changeLog; + }, + + /** + * prepares changelog with the given parameters + * + * @param owner the owner of the repository + * @param repo the repository to prepare changelog for + * @param base the branch to compare with the {@code head} + * @param head the head branch of the {@code repo} + * @returns {Promise} + */ prepareChangelog: async function (owner, repo, base, head) { spinner.start('Preparing the changelog....'); - const { data: changeLog } = await octokit.request('GET /repos/{owner}/{repo}/compare/{base}...{head}', { + const {data: changeLog} = await octokit.request('GET /repos/{owner}/{repo}/compare/{base}...{head}', { owner: owner, repo: repo, base: base, @@ -78,9 +129,16 @@ module.exports = { }); }, + /** + * fetches the commits from the {@code head} branch + * @param owner the owner of the repository + * @param repo the repository to fetch commits from + * @param head the head branch of the {@code repo} + * @returns {Promise<*>} + */ listCommits: async function (owner, repo, head) { spinner.start(`Fetching commits from ${head} branch...`); - const { data: commits } = await octokit.request('GET /repos/{owner}/{repo}/commits', { + const {data: commits} = await octokit.request('GET /repos/{owner}/{repo}/commits', { owner: owner, repo: repo }); @@ -92,12 +150,23 @@ module.exports = { }); }, + /** + * creates the release with the given parameters + * + * @param owner the owner of the repository + * @param repo the repository to create release for + * @param draft defines if release is a draft or not + * @param name the name/title of the release + * @param body the changelog to be defined in the release + * @param tag_name the tag name + * @returns {Promise} + */ createRelease: async function (owner, repo, draft, name, body, tag_name) { try { spinner.start('Preparing the release...'); - const { data: newRelease } = await octokit.request('POST /repos/{owner}/{repo}/releases', { + const {data: newRelease} = await octokit.request('POST /repos/{owner}/{repo}/releases', { owner: owner, repo: repo, draft: draft, @@ -108,67 +177,80 @@ module.exports = { const releaseUrl = prepareReleaseUrl(newRelease.html_url); spinner.succeed(`Release has been prepared on Github. ${releaseUrl}`); + return releaseUrl; } catch (error) { let errorMessage = "\n"; - error.errors.forEach(element => { + let errors = error.response.data.errors; + let message = error.response.data.message; + errors.forEach(element => { errorMessage += `\t* field: '${element.field}' - code: '${element.code}'`; }); - spinner.fail(`Something went wrong while preparing the release! ${errorMessage}`); + spinner.fail(`${message} while preparing the release! ${errorMessage}`); + process.exit(); } }, + /** + * retrieves the username with the help of the authentication token + * @returns {Promise<*>} + */ retrieveUsername: async function () { - const { data: user } = await octokit.request('GET /user'); + const {data: user} = await octokit.request('GET /user'); return user.login; }, - publishToSlack: function (publish, repo, message) { + /** + * publishes the given message to the slack channels which are defined in the environment variables + * @param publish decides if the given message to be published to the slack channels + * @param repo the repository to prepare the slack message header + * @param changelog the changelog/message to send to the slack channels + * @param releaseUrl the release tag url on Github + * @param releaseName the title of the release + */ + publishToSlack: async function (publish, repo, changelog, releaseUrl, releaseName) { if (publish == true) { spinner.start('Sending release information to Slack channel...'); - const slackHookUrls = process.env.SLACK_HOOK_URL; + const slackHookUrls = process.env.SLACK_HOOK_URL || process.env.YABA_SLACK_HOOK_URL; if (!slackHookUrls) { spinner.fail("Release not announced on Slack: configuration not found!"); return; } const slackHookUrlList = slackHookUrls.split(","); - slackHookUrlList.forEach(channelUrl => postToSlack(channelUrl, repo, message)); + const message = helper.prepareSlackMessage(repo, changelog, releaseUrl, releaseName); + for (const channelUrl of slackHookUrlList) { + await postToSlack(channelUrl, message); + } spinner.succeed('Changelog published to Slack.'); } } } -function postToSlack(channelUrl, repo, message) { - axios - .post(channelUrl, { - "text": `${repo} Changelog`, - "blocks": [ - { - "type": "section", - "text": { - "type": "mrkdwn", - "text": `:rocket: *${repo} Changelog*` - } - }, - { - "type": "section", - "text": { - "type": "mrkdwn", - "text": "```" + message + "```" - } - } - ] - }) +/** + * Sends the given message to the given Slack channel + * + * @param channelUrl the Slack channel webhook url to post {@code message} to + * @param repo the name of the release repository + * @param message the message to send to the given Slack {@code channelUrl} + */ +async function postToSlack(channelUrl, message) { + await axios + .post(channelUrl, message) .catch(error => { spinner.fail(`Something went wrong while sending to Slack channel: ${error}`) }); } +/** + * + * @param releaseUrl + * @returns {string|*} + */ function prepareReleaseUrl(releaseUrl) { if (supportsHyperlinks.stdout) { diff --git a/bin/utils/helper.js b/bin/utils/helper.js index 95b58cc..903289f 100755 --- a/bin/utils/helper.js +++ b/bin/utils/helper.js @@ -1,35 +1,60 @@ const fs = require("fs"); -const prompts = require('prompts'); -const player = require('play-sound')(opts = {}) +const prompts = require("prompts"); +const player = require("play-sound")(opts = {}); +const stringUtils = require('./string-utils.js'); const path = require("path"); -const { format } = require('date-fns') +const {format} = require('date-fns'); +const constants = require('./constants'); module.exports = { + + /** + * returns the current date in 'yyyy-MM-dd' format + * + * @returns {string} the current formatted date + */ releaseDate: function () { - return format(new Date(), 'yyyy-MM-dd'); + return format(new Date(), constants.RELEASE_DATE_FORMAT); }, + /** + * prepares the tag name of the release. if the given {@code tagName} is blank, + * this prepares the tag name in 'prod_global_yyyy-MM-dd.1' format. + * + * @param tagName the given tag name + * @returns {string|*} the prepared tag name + */ releaseTagName: function (tagName) { - if (this.isBlank(tagName)) { - const currentDate = format(new Date(), 'yyyyMMdd'); + if (stringUtils.isBlank(tagName)) { + const currentDate = format(new Date(), constants.TAG_DATE_FORMAT); return `prod_global_${currentDate}.1`; } return tagName; }, - isBlank: function (str) { - return (str == "undefined" || !str || /^\s*$/.test(str)); - }, - + /** + * prepares the title of the release. if the given {@code name} is blank, this prepares + * the title in 'Global release yyyy-MM-dd' format. + * @param name the given release name + * @returns {string|*} + */ releaseName: function (name) { - return this.isBlank(name) ? `Global release ${this.releaseDate()}` : name; + return stringUtils.isBlank(name) ? `Global release ${this.releaseDate()}` : name; }, + /** + * prepares the release changelog. if {@code givenBody} is blank, this prepares the all the commits inside + * the given {@code changelog} in a list as release changelog. + * + * @param givenBody the changelog which is defined by the user + * @param changeLog the changelog which is generated automatically by checking head branch against the latest release + * @returns {string|*} the release changelog + */ prepareChangeLog: function (givenBody, changeLog) { - if (!this.isBlank(givenBody)) { + if (!stringUtils.isBlank(givenBody)) { return givenBody; } @@ -40,13 +65,23 @@ module.exports = { releaseMessage += `* ${message}\n`; }); - return this.isBlank(releaseMessage) ? "* No changes" : releaseMessage; + return stringUtils.isBlank(releaseMessage) ? "* No changes" : releaseMessage; }, + /** + * checks if the current directory is a git repo or not. + * + * @returns {boolean} {@code true} if the current directory is a git repo, otherwise returns {@code false} + */ isGitRepo: function () { return fs.existsSync(".git/"); }, + /** + * check if the current directory is a git repo, if yes this will return the name of the directory + * + * @returns {string} + */ retrieveCurrentRepoName: function () { if (!this.isGitRepo()) { return "not a git repo"; @@ -54,31 +89,70 @@ module.exports = { return this.retrieveCurrentDirectory(); }, + /** + * retrieves the current directory name + * + * @returns {string} + */ retrieveCurrentDirectory: function () { const currentFolderPath = process.cwd(); return currentFolderPath.substring(currentFolderPath.lastIndexOf('/') + 1, currentFolderPath.length); }, + /** + * retrieves the owner of the repository. + * + * @param owner the owner of the repository + * @param username the username of the authenticated user + * @returns {string} + */ retrieveOwner: function (owner, username) { - return (owner || process.env.GITHUB_REPO_OWNER || username); + return (owner || (process.env.GITHUB_REPO_OWNER || process.env.YABA_GITHUB_REPO_OWNER) || username); }, + /** + * + * prepares the release repository name. if the given {@code repo} is not defined, this will try to return + * the current directory as release repo if it is a git repo. + * + * @param repo the repository to retrieve the repository name for the release + * @returns {string} + */ retrieveReleaseRepo: function (repo) { return (repo || this.retrieveCurrentRepoName()); }, + /** + * + * retrieves the head branch from the given {@code branch} list. + * + * @param branches the all the branches which are belongs to the related repository. + * @returns {null|*} + */ retrieveHeadBranch: function (branches) { let headBranch = branches.find(branch => branch.name === 'master' || branch.name === 'main'); return headBranch == undefined ? null : headBranch.name; }, + /** + * checks if all the required environment variables are set. + * + * @returns {boolean} + */ requiredEnvVariablesExist: function () { - if (process.env.GITHUB_ACCESS_TOKEN) { + if (process.env.GITHUB_ACCESS_TOKEN || process.env.YABA_GITHUB_ACCESS_TOKEN) { return true; } return false; }, + /** + * shows prompt regarding the given parameter + * + * @param interactive the parameter to decide if yaba need to show prompt for the release creation + * @returns {@code true} if the given {@code interactive} parameter is set to {@code true}, + * otherwise it returns the result depending on the prompt question. + */ releaseCreatePermit: async function (interactive) { if (interactive == false) { @@ -95,10 +169,32 @@ module.exports = { return response.create; }, + /** + * plays if the given {@code sound} parameter is {@code true} + * + * @param sound the parameter to decide to play sound + */ playSound: function (sound) { if (sound == true) { - const filePath = path.join(__dirname, "../assets/yaba.mp3"); + const filePath = path.join(__dirname, constants.SOUND_PATH); player.play(filePath); } + }, + + /** + * @param repo the repository to prepare Slack message for + * @param message the changelog to send to Slack channel(s) + * @param releaseUrl the release tag url on Github + * @param releaseName the title of the release + * @returns {string} the formatted JSON to send to Slack + */ + prepareSlackMessage: function (repo, message, releaseUrl, releaseName) { + + const slackMessageTemplatePath = path.join(__dirname, constants.SLACK_POST_TEMPLATE); + let templateFile = fs.readFileSync(slackMessageTemplatePath); + let templateJson = JSON.parse(templateFile); + let slackMessageTemplate = JSON.stringify(templateJson); + + return stringUtils.format(slackMessageTemplate, {repo: repo, changelog: message, releaseUrl: releaseUrl, releaseName: releaseName}); } } diff --git a/bin/utils/string-utils.js b/bin/utils/string-utils.js new file mode 100644 index 0000000..debd97e --- /dev/null +++ b/bin/utils/string-utils.js @@ -0,0 +1,24 @@ +module.exports = { + /** + * checks if the given string is blank or not. + * + * @param str the given string + * @returns {boolean|boolean} true if the given string is blank, otherwise returns true + */ + isBlank: function (str) { + return (str == "undefined" || !str || /^\s*$/.test(str)); + }, + + /** + * replaces the placeholders which are in the {@code given} string with the given {@code dict}. + * + * @param given the given string that will be formatted with the given {@code dict} + * @param dict the dictionary that will replace placeholders in {@code given} sting + * @returns {*} + */ + format: function (given, dict) { + return given.replace(/{(\w+)}/g, function (match, key) { + return typeof dict[key] !== 'undefined' ? dict[key] : match; + }); + } +} diff --git a/bin/utils/tool.js b/bin/utils/tool.js index 5a41bc1..5cfab05 100755 --- a/bin/utils/tool.js +++ b/bin/utils/tool.js @@ -1,10 +1,19 @@ const kleur = require('kleur'); const semver = require('semver'); const boxen = require('boxen'); +const stringUtils = require('./string-utils.js'); const latestVersion = require('latest-version'); const package = require('../../package.json'); +const path = require("path"); +const fs = require("fs"); +const constants = require('./constants'); module.exports = { + + /** + * checks if yaba has newer version. + * @returns {Promise} + */ checkUpdate: async function () { const localVersion = package.version; @@ -17,13 +26,23 @@ module.exports = { } } +/** + * prepares version update message to show to the user + * + * @param lastVersion the latest version which is available + * @param localVersion the version which is installed on user's local machine + * @returns {string} the version update message + */ function prepareUpdateMessage(lastVersion, localVersion) { - const localVersionMessage = kleur.red().bold(localVersion); - const lastVersionMessage = kleur.green().bold(lastVersion) - const updateCommand = kleur.green('npm update -g yaba-release-cli'); - const message = `New version of Yaba available! ${localVersionMessage} -> ${lastVersionMessage}` + - `\nPlease run ${updateCommand} to update!`; + const templatePath = path.join(__dirname, constants.UPDATE_MESSAGE_TEMPLATE); + const templateFile = fs.readFileSync(templatePath, 'utf8'); + + const message = stringUtils.format(templateFile, { + localVersion: kleur.red().bold(localVersion), + lastVersion: kleur.green().bold(lastVersion), + updateCommand: kleur.green(constants.UPDATE_COMMAND) + }); return message; } diff --git a/package.json b/package.json index db38380..f195806 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "yaba-release-cli", - "version": "1.5.2", + "version": "1.5.3", "description": "Yaba is a simple CLI tool that helps you manage releases of your Github projects.", "main": "bin/index.js", "scripts": {