diff --git a/.eslintignore b/.eslintignore index a10e4a756..2315fe2b6 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1740,6 +1740,9 @@ packages/tools/generate-database-types.js.map packages/tools/generate-images.d.ts packages/tools/generate-images.js packages/tools/generate-images.js.map +packages/tools/git-changelog.d.ts +packages/tools/git-changelog.js +packages/tools/git-changelog.js.map packages/tools/lerna-add.d.ts packages/tools/lerna-add.js packages/tools/lerna-add.js.map diff --git a/.gitignore b/.gitignore index f1a8f0442..246a0ebe6 100644 --- a/.gitignore +++ b/.gitignore @@ -1725,6 +1725,9 @@ packages/tools/generate-database-types.js.map packages/tools/generate-images.d.ts packages/tools/generate-images.js packages/tools/generate-images.js.map +packages/tools/git-changelog.d.ts +packages/tools/git-changelog.js +packages/tools/git-changelog.js.map packages/tools/lerna-add.d.ts packages/tools/lerna-add.js packages/tools/lerna-add.js.map diff --git a/packages/tools/git-changelog.js b/packages/tools/git-changelog.ts similarity index 79% rename from packages/tools/git-changelog.js rename to packages/tools/git-changelog.ts index 0a074c7a2..209a909c0 100644 --- a/packages/tools/git-changelog.js +++ b/packages/tools/git-changelog.ts @@ -1,25 +1,51 @@ -'use strict'; - // Supported commit formats: // (Desktop|Mobile|Android|iOS[CLI): (New|Improved|Fixed): Some message..... (#ISSUE) -const { execCommand, githubUsername } = require('./tool-utils.js'); +import { execCommand, githubUsername } from './tool-utils'; -// From https://stackoverflow.com/a/6234804/561309 -function escapeHtml(unsafe) { - // We only escape <> as this is enough for Markdown - return unsafe - // .replace(/&/g, '&') - .replace(//g, '>'); - // .replace(/"/g, '"') - // .replace(/'/g, '''); +interface LogEntry { + message: string; + commit: string; + author: Author; } -async function gitLog(sinceTag) { - let lines = await execCommand(`git log --pretty=format:"%H::::DIV::::%ae::::DIV::::%an::::DIV::::%s" ${sinceTag}..HEAD`); - lines = lines.split('\n'); +enum Platform { + Android = 'android', + Ios = 'ios', + Desktop = 'desktop', + Clipper = 'clipper', + Server = 'server', + Cli = 'cli', + PluginGenerator = 'plugin-generator', +} + +enum PublishFormat { + Full = 'full', + Simple = 'simple', +} + +interface Options { + publishFormat: PublishFormat; +} + +interface Author { + email: string; + name: string; + login: string; +} + +// From https://stackoverflow.com/a/6234804/561309 +function escapeHtml(unsafe: string) { + // We only escape <> as this is enough for Markdown + return unsafe + .replace(//g, '>'); +} + +async function gitLog(sinceTag: string) { + const commandResult = await execCommand(`git log --pretty=format:"%H::::DIV::::%ae::::DIV::::%an::::DIV::::%s" ${sinceTag}..HEAD`); + const lines = commandResult.split('\n'); const output = []; for (const line of lines) { @@ -46,31 +72,22 @@ async function gitLog(sinceTag) { } async function gitTags() { - const lines = await execCommand('git tag --sort=committerdate'); + const lines: string = await execCommand('git tag --sort=committerdate'); return lines.split('\n').map(l => l.trim()).filter(l => !!l); } -function platformFromTag(tagName) { - if (tagName.indexOf('v') === 0) return 'desktop'; - if (tagName.indexOf('android') >= 0) return 'android'; - if (tagName.indexOf('ios') >= 0) return 'ios'; - if (tagName.indexOf('clipper') === 0) return 'clipper'; - if (tagName.indexOf('cli') === 0) return 'cli'; - if (tagName.indexOf('server') === 0) return 'server'; - if (tagName.indexOf('plugin-generator') === 0) return 'plugin-generator'; +function platformFromTag(tagName: string): Platform { + if (tagName.indexOf('v') === 0) return Platform.Desktop; + if (tagName.indexOf('android') >= 0) return Platform.Android; + if (tagName.indexOf('ios') >= 0) return Platform.Ios; + if (tagName.indexOf('clipper') === 0) return Platform.Clipper; + if (tagName.indexOf('cli') === 0) return Platform.Cli; + if (tagName.indexOf('server') === 0) return Platform.Server; + if (tagName.indexOf('plugin-generator') === 0) return Platform.PluginGenerator; throw new Error(`Could not determine platform from tag: "${tagName}"`); } -// function tagPrefixFromPlatform(platform) { -// if (platform === 'desktop') return ''; -// if (platform === 'android') return 'android-'; -// if (platform === 'ios') return 'ios-'; -// if (platform === 'clipper') return 'clipper-'; -// if (platform === 'cli') return 'cli-'; -// throw new Error(`Could not determine tag prefix from platform: ${platform}`); -// } - -function filterLogs(logs, platform) { +function filterLogs(logs: LogEntry[], platform: Platform) { const output = []; const revertedLogs = []; @@ -124,7 +141,7 @@ function filterLogs(logs, platform) { return output; } -function formatCommitMessage(commit, msg, author, options) { +function formatCommitMessage(commit: string, msg: string, author: Author, options: Options): string { options = Object.assign({}, { publishFormat: 'full' }, options); let output = ''; @@ -133,8 +150,8 @@ function formatCommitMessage(commit, msg, author, options) { let subModule = ''; - const isPlatformPrefix = prefix => { - prefix = prefix.split(',').map(p => p.trim().toLowerCase()); + const isPlatformPrefix = (prefixString: string) => { + const prefix = prefixString.split(',').map(p => p.trim().toLowerCase()); for (const p of prefix) { if (['android', 'mobile', 'ios', 'desktop', 'cli', 'clipper', 'all', 'api', 'plugins', 'server'].indexOf(p) >= 0) return true; } @@ -154,7 +171,7 @@ function formatCommitMessage(commit, msg, author, options) { output = output.split('\n')[0].trim(); - const detectType = msg => { + const detectType = (msg: string) => { msg = msg.trim().toLowerCase(); if (msg.indexOf('fix') === 0) return 'fixed'; @@ -166,7 +183,7 @@ function formatCommitMessage(commit, msg, author, options) { return 'improved'; }; - const parseCommitMessage = (msg, subModule) => { + const parseCommitMessage = (msg: string, subModule: string) => { const parts = msg.split(':'); if (parts.length === 1) { @@ -202,20 +219,14 @@ function formatCommitMessage(commit, msg, author, options) { if (t.indexOf('improved') === 0) type = 'improved'; if (t.indexOf('security') === 0) type = 'security'; - // if (t.indexOf('security') === 0) { - // type = 'security'; - // parts.splice(0, 1); - // message = parts.join(':').trim(); - // } - if (!type) { type = detectType(message); if (originalType.toLowerCase() === 'tinymce') originalType = 'WYSIWYG'; message = `${originalType}: ${message}`; } - let issueNumber = output.match(/#(\d+)/); - issueNumber = issueNumber && issueNumber.length >= 2 ? issueNumber[1] : null; + const issueNumberMatch = output.match(/#(\d+)/); + const issueNumber = issueNumberMatch && issueNumberMatch.length >= 2 ? issueNumberMatch[1] : null; return { type: type, @@ -271,7 +282,7 @@ function formatCommitMessage(commit, msg, author, options) { return escapeHtml(output); } -function createChangeLog(logs, options) { +function createChangeLog(logs: LogEntry[], options: Options) { const output = []; for (const log of logs) { @@ -281,35 +292,13 @@ function createChangeLog(logs, options) { return output; } -function capitalizeFirstLetter(string) { +function capitalizeFirstLetter(string: string) { return string.charAt(0).toUpperCase() + string.slice(1); } -// function decreaseTagVersion(tag) { -// const s = tag.split('.'); - -// let updated = false; - -// for (let tokenIndex = s.length - 1; tokenIndex >= 0; tokenIndex--) { -// const token = s[tokenIndex]; -// const s2 = token.split('-'); -// let num = Number(s2[0]); -// num--; -// if (num >= 0) { -// updated = true; -// s[tokenIndex] = num; -// break; -// } -// } - -// if (!updated) throw new Error(`Cannot decrease tag version: ${tag}`); - -// return s.join('.'); -// } - // This function finds the first relevant tag starting from the given tag. // The first "relevant tag" is the one that exists, and from which there are changes. -async function findFirstRelevantTag(baseTag, platform, allTags) { +async function findFirstRelevantTag(baseTag: string, platform: Platform, allTags: string[]) { let baseTagIndex = allTags.indexOf(baseTag); if (baseTagIndex < 0) baseTagIndex = allTags.length; @@ -349,8 +338,8 @@ async function main() { const filteredLogs = filterLogs(logsSinceTags, platform); - let publishFormat = 'full'; - if (['android', 'ios'].indexOf(platform) >= 0) publishFormat = 'simple'; + let publishFormat: PublishFormat = PublishFormat.Full; + if (['android', 'ios'].indexOf(platform) >= 0) publishFormat = PublishFormat.Simple; if (argv.publishFormat) publishFormat = argv.publishFormat; let changelog = createChangeLog(filteredLogs, { publishFormat: publishFormat }); diff --git a/packages/tools/tool-utils.ts b/packages/tools/tool-utils.ts index 8849de2d6..9ef09fd85 100644 --- a/packages/tools/tool-utils.ts +++ b/packages/tools/tool-utils.ts @@ -109,7 +109,7 @@ async function saveGitHubUsernameCache(cache: any) { // Returns the project root dir export const rootDir = require('path').dirname(require('path').dirname(__dirname)); -export function execCommand(command: string, options: any = null) { +export function execCommand(command: string, options: any = null): Promise { options = options || {}; const exec = require('child_process').exec; @@ -322,6 +322,11 @@ export async function githubUsername(email: string, name: string) { const urlsToTry = [ `https://api.github.com/search/users?q=${encodeURI(email)}+in:email`, + + // Note that this can fail if the email could not be found and the user + // shares a name with someone else. It's rare enough that we can leave + // it for now. + // https://github.com/laurent22/joplin/pull/5390 `https://api.github.com/search/users?q=user:${encodeURI(name)}`, ];