UNPKG

yaba-release-cli

Version:

Yaba is a simple CLI tool that helps you manage releases of your Github projects.

190 lines (162 loc) 5.94 kB
import fs from 'fs'; import prompts from 'prompts'; import { isBlank, format as _format } from './string-utils.js'; import path from 'path'; import { format } from 'date-fns'; import { appConstants } from './constants.js'; import {fileURLToPath} from 'url'; import { retrieveCurrentRepoName } from './git.js'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); /** * returns the current date in 'yyyy-MM-dd' format * * @returns {string} the current formatted date */ export function releaseDate() { return format(new Date(), appConstants.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_yyyyMMdd.HHmm' format. * * @param tagName the given tag name * @returns {string|*} the prepared tag name */ export function releaseTagName(tagName) { if (isBlank(tagName)) { const currentDate = format(new Date(), appConstants.TAG_DATE_FORMAT); const currentMinute = format(new Date(), "HHmm"); return `prod_global_${currentDate}.${currentMinute}`; } return tagName; } /** * promotes minute-precision prod_global tags to second precision. * * @param tagName tag candidate that may have collided * @param now optional date for deterministic testing * @returns {string|null} second-precision fallback tag, or null when template does not match */ export function promoteTagPrecisionToSeconds(tagName, now = new Date()) { const normalized = `${tagName || ""}`.trim(); const matched = normalized.match(/^((?:hotfix_)?prod_global_)(\d{8})\.(\d{4})$/); if (!matched) { return null; } const prefix = matched[1]; const datePart = matched[2]; const secondPrecision = format(now, "HHmmss"); return `${prefix}${datePart}.${secondPrecision}`; } /** * 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|*} */ export function releaseName(name) { return isBlank(name) ? `Global release ${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 */ export function prepareChangeLog(givenBody, changeLog) { if (!isBlank(givenBody)) { return givenBody; } let releaseMessage = ""; changeLog.forEach(commit => { let message = commit.split('\n')[0]; releaseMessage += `- ${message}\n`; }); return isBlank(releaseMessage) ? "* No changes" : releaseMessage; } /** * retrieves the owner of the repository. * * @param owner the owner of the repository * @param username the username of the authenticated user * @returns {string} */ export function retrieveOwner(owner, username) { return (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 repository name resolved from git remote settings if it is a git repo. * * @param repo the repository to retrieve the repository name for the release * @returns {string} */ export function retrieveReleaseRepo(repo) { return (repo || retrieveCurrentRepoName()); } /** * checks if all the required environment variables are set. * * @returns {boolean} */ export function requiredEnvVariablesExist() { return !!process.env.YABA_GITHUB_ACCESS_TOKEN; } /** * 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. */ export async function releaseCreatePermit(interactive) { if (interactive === false) { return true; } const response = await prompts({ type: 'confirm', name: 'create', message: 'Are you sure to create the release?', initial: false }); return response.create; } /** * @param repo the repository to prepare Slack message for * @param message the newsletter body to send to Slack channel(s) * @param releaseUrl the release tag url on Github * @param releaseName the title of the release * @param compareUrl the compare URL between previous and current release refs * @returns {object} the formatted JSON payload to send to Slack */ export function prepareSlackMessage(repo, message, releaseUrl, releaseName, compareUrl) { const slackMessageTemplatePath = path.join(__dirname, appConstants.SLACK_POST_TEMPLATE); const templateFile = fs.readFileSync(slackMessageTemplatePath, 'utf8'); const templateJson = JSON.parse(templateFile); return applyTemplateValues(templateJson, { repo: repo, newsletterBody: message.trim(), releaseUrl: releaseUrl, releaseName: releaseName, compareUrl: compareUrl || releaseUrl }); } function applyTemplateValues(value, replacements) { if (Array.isArray(value)) { return value.map(item => applyTemplateValues(item, replacements)); } if (value && typeof value === 'object') { const mappedEntries = Object.entries(value).map(([key, nestedValue]) => { return [key, applyTemplateValues(nestedValue, replacements)]; }); return Object.fromEntries(mappedEntries); } if (typeof value === 'string') { return _format(value, replacements); } return value; }