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
JavaScript
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;
}