1import chalk from 'chalk'; 2import path from 'path'; 3import terminalLink from 'terminal-link'; 4 5import { GitLog, GitFileLog } from './Git'; 6 7const { white, cyan, red, green, yellow, blue, gray, dim, reset } = chalk; 8 9/** 10 * Uses `terminal-link` to create clickable link in the terminal. 11 * If the terminal doesn't support this feature, fallback just to provided text, it would look ugly on the CI. 12 */ 13export function link(text: string, url: string) { 14 return terminalLink(text, url, { fallback: (text) => text }); 15} 16 17/** 18 * Formats an entry from git log. 19 */ 20export function formatCommitLog(log: GitLog): string { 21 const authorName = green(log.authorName); 22 const commitHash = formatCommitHash(log.hash); 23 const title = formatCommitTitle(log.title); 24 const date = log.committerRelativeDate; 25 26 return `${commitHash} ${title} ${gray(`by ${authorName} ${date}`)}`; 27} 28 29/** 30 * Formats commit title. So far it only makes closing PR number a link to the PR on GitHub. 31 */ 32export function formatCommitTitle(title: string): string { 33 return title.replace( 34 /\(#(\d+)\)$/g, 35 `(${blue.bold(link('#$1', 'https://github.com/expo/expo/pull/$1'))})` 36 ); 37} 38 39/** 40 * Formats commit hash to display it as a link pointing to the commit on GitHub. 41 */ 42export function formatCommitHash(hash?: string): string { 43 if (!hash) { 44 return gray('undefined'); 45 } 46 const url = `https://github.com/expo/expo/commit/${hash}`; 47 const abbreviatedHash = hash.substring(0, 6); 48 49 return yellow.bold(`(${link(abbreviatedHash, url)})`); 50} 51 52/** 53 * Formats markdown changelog entry to be displayed nicely in the terminal. 54 * Replaces links to terminal chars sequence that prints clickable text pointing to given URL. 55 */ 56export function formatChangelogEntry(entry: string): string { 57 return entry 58 .replace(/\[(#\d+|@\w+)\]\(([^)]+?)\)/g, blue.bold(link('$1', '$2'))) 59 .replace(/(\W)([_*]{2})([^\2]*?)\2(\W)/g, '$1' + reset.bold('$3') + '$4') 60 .replace(/(\W)([_*])([^\2]*?)\2(\W)/g, '$1' + reset.italic('$3') + '$4') 61 .replace(/`([^`]+?)`/g, dim('$1')); 62} 63 64/** 65 * Formats file log - that is a relative file path and the status (modified, added, etc.). 66 */ 67export function formatFileLog(fileLog: GitFileLog): string { 68 const uri = `vscode://file/${fileLog.path}`; 69 return `${link(fileLog.relativePath, uri)} ${gray(`(${fileLog.status})`)}`; 70} 71 72/** 73 * Removes all non-ascii characters (characters with unicode number between `0` and `127` are left untouched) from the string. 74 * https://www.utf8-chartable.de/unicode-utf8-table.pl?number=128 75 */ 76export function stripNonAsciiChars(str: string): string { 77 return str.replace(/[^\x00-\x7F]/gu, ''); 78} 79 80/** 81 * Makes Xcode logs pretty as xcpretty :) 82 */ 83export function formatXcodeBuildOutput(output: string): string { 84 return output 85 .replace(/^(\/.*)(:\d+:\d+): (error|warning|note)(:.*)$/gm, (_, p1, p2, p3, p4) => { 86 if (p3 === 'note') { 87 return gray.bold(p3) + white.bold(p4); 88 } 89 const relativePath = path.relative(process.cwd(), p1); 90 const logColor = p3 === 'error' ? red.bold : yellow.bold; 91 92 return cyan.bold(relativePath + p2) + ' ' + logColor(p3 + p4); 93 }) 94 .replace(/^(In file included from )(\/[^\n]+)(:\d+:[^\n]*)$/gm, (_, p1, p2, p3) => { 95 const relativePath = path.relative(process.cwd(), p2); 96 return gray.italic(p1 + relativePath + p3); 97 }) 98 .replace(/\s\^\n(\s[^\n]+)?/g, green.bold('$&\n')) 99 .replace(/\*\* BUILD FAILED \*\*/, red.bold('$&')); 100} 101