1
0
mirror of https://github.com/laurent22/joplin.git synced 2024-12-21 09:38:01 +02:00

Tools: Include packages that have been updated by Renovate in changelog

This commit is contained in:
Laurent Cozic 2023-03-09 16:25:21 +00:00
parent 99c6c9b411
commit d871b3c7d6
5 changed files with 228 additions and 9 deletions

View File

@ -846,6 +846,7 @@ packages/tools/convertThemesToCss.js
packages/tools/generate-database-types.js
packages/tools/generate-images.js
packages/tools/git-changelog.js
packages/tools/git-changelog.test.js
packages/tools/licenseChecker.js
packages/tools/release-android.js
packages/tools/release-cli.js

1
.gitignore vendored
View File

@ -834,6 +834,7 @@ packages/tools/convertThemesToCss.js
packages/tools/generate-database-types.js
packages/tools/generate-images.js
packages/tools/git-changelog.js
packages/tools/git-changelog.test.js
packages/tools/licenseChecker.js
packages/tools/release-android.js
packages/tools/release-cli.js

View File

@ -71,7 +71,7 @@ async function main() {
}
if (require.main === module) {
// eslint-disable-next-line promise/prefer-await-to-then -- Old code before rule was applied
// eslint-disable-next-line promise/prefer-await-to-then
main().catch((error) => {
console.error('Fatal error');
console.error(error);

View File

@ -0,0 +1,84 @@
import { expectThrow } from '@joplin/lib/testing/test-utils';
import { filesApplyToPlatform, parseRenovateMessage, RenovateMessage, summarizeRenovateMessages } from './git-changelog';
describe('git-changelog', () => {
test('should find out if a file path is relevant to a platform', async () => {
type TestCase = [string[], string, boolean];
const testCases: TestCase[] = [
[['packages/app-mobile/package.json'], 'ios', true],
[['packages/app-mobile/package.json'], 'android', true],
[['packages/app-mobile/package.json'], 'destop', false],
[[], 'destop', false],
[['packages/server/package.json'], 'server', true],
[['packages/app-mobile/package.json', 'packages/server/package.json'], 'server', true],
[['packages/app-mobile/package.json', 'packages/server/package.json'], 'android', true],
[['packages/app-mobile/package.json', 'packages/server/package.json'], 'desktop', false],
[['packages/server/package.json'], 'desktop', false],
[['packages/lib/package.json'], 'server', true],
[['packages/lib/package.json'], 'desktop', true],
[['packages/lib/package.json'], 'android', true],
[['packages/lib/package.json'], 'clipper', false],
[['packages/app-clipper/package.json'], 'clipper', true],
];
for (const testCase of testCases) {
const [files, platform, expected] = testCase;
const actual = filesApplyToPlatform(files, platform);
expect(actual).toBe(expected);
}
});
test('should parse Renovate messages', async () => {
type TestCase = [string, string, string];
const testCases: TestCase[] = [
['Update typescript-eslint monorepo to v5 (#7291)', 'typescript-eslint', 'v5'],
['Update aws-sdk-js-v3 monorepo to v3.215.0', 'aws-sdk-js-v3', 'v3.215.0'],
['Update dependency moment to v2.29.4 (#7087)', 'moment', 'v2.29.4'],
];
for (const testCase of testCases) {
const [message, pkg, version] = testCase;
const actual = parseRenovateMessage(message);
expect(actual.package).toBe(pkg);
expect(actual.version).toBe(version);
}
await expectThrow(async () => parseRenovateMessage('not a renovate message'));
});
test('should summarize Renovate messages', async () => {
type TestCase = [RenovateMessage[], string];
const testCases: TestCase[] = [
[
[
{ package: 'sas', version: 'v1.0' },
{ package: 'sas', version: 'v1.2' },
{ package: 'moment', version: 'v3.4' },
{ package: 'eslint', version: 'v1.2' },
],
'Updated packages moment (v3.4), sas (v1.2)',
],
[
[
{ package: 'eslint', version: 'v1.2' },
],
'',
],
[
[],
'',
],
];
for (const testCase of testCases) {
const [messages, expected] = testCase;
const actual = summarizeRenovateMessages(messages);
expect(actual).toBe(expected);
}
});
});

View File

@ -8,6 +8,7 @@ interface LogEntry {
message: string;
commit: string;
author: Author;
files: string[];
}
enum Platform {
@ -58,6 +59,10 @@ async function gitLog(sinceTag: string) {
const authorEmail = splitted[1];
const authorName = splitted[2];
const message = splitted[3].trim();
let files: string[] = [];
const filesResult = await execCommand(`git diff-tree --no-commit-id --name-only ${commit} -r`);
files = filesResult.split('\n').map(s => s.trim()).filter(s => !!s);
output.push({
commit: commit,
@ -67,6 +72,7 @@ async function gitLog(sinceTag: string) {
name: authorName,
login: await githubUsername(authorEmail, authorName),
},
files,
});
}
@ -91,6 +97,102 @@ function platformFromTag(tagName: string): Platform {
throw new Error(`Could not determine platform from tag: "${tagName}"`);
}
export const filesApplyToPlatform = (files: string[], platform: string): boolean => {
const isMainApp = ['android', 'ios', 'desktop', 'cli', 'server'].includes(platform);
const isMobile = ['android', 'ios'].includes(platform);
for (const file of files) {
if (file.startsWith('packages/app-cli') && platform === 'cli') return true;
if (file.startsWith('packages/app-clipper') && platform === 'clipper') return true;
if (file.startsWith('packages/app-mobile') && isMobile) return true;
if (file.startsWith('packages/app-desktop') && platform === 'desktop') return true;
if (file.startsWith('packages/fork-htmlparser2') && isMainApp) return true;
if (file.startsWith('packages/fork-uslug') && isMainApp) return true;
if (file.startsWith('packages/htmlpack') && isMainApp) return true;
if (file.startsWith('packages/lib') && isMainApp) return true;
if (file.startsWith('packages/pdf-viewer') && platform === 'desktop') return true;
if (file.startsWith('packages/react-native-') && isMobile) return true;
if (file.startsWith('packages/renderer') && isMainApp) return true;
if (file.startsWith('packages/server') && platform === 'server') return true;
if (file.startsWith('packages/tools') && isMainApp) return true;
if (file.startsWith('packages/turndown') && isMainApp) return true;
}
return false;
};
export interface RenovateMessage {
package: string;
version: string;
}
export const parseRenovateMessage = (message: string): RenovateMessage => {
const regexes = [
/^Update dependency ([^\s]+) to ([^\s]+)/,
/^Update ([^\s]+) monorepo to ([^\s]+)/,
];
for (const regex of regexes) {
const m = message.match(regex);
if (m) {
return {
package: m[1],
version: m[2],
};
}
}
throw new Error(`Not a Renovate message: ${message}`);
};
export const summarizeRenovateMessages = (messages: RenovateMessage[]): string => {
// Exclude some dev dependencies
messages = messages.filter(m => {
if ([
'yeoman-generator',
'madge',
'lint-staged',
'gettext-extractor',
'gettext-extractor',
'ts-jest',
'ts-node',
'typescript',
'eslint',
'jest',
].includes(m.package)) {
return false;
}
if (m.package.startsWith('@types/')) return false;
if (m.package.startsWith('typescript-')) return false;
return true;
});
const temp: Record<string, string> = {};
for (const message of messages) {
if (!temp[message.package]) {
temp[message.package] = message.version;
} else {
if (message.version > temp[message.package]) {
temp[message.package] = message.version;
}
}
}
const temp2: string[] = [];
for (const [pkg, version] of Object.entries(temp)) {
temp2.push(`${pkg} (${version})`);
}
temp2.sort();
if (temp2.length) return `Updated packages ${temp2.join(', ')}`;
return '';
};
function filterLogs(logs: LogEntry[], platform: Platform) {
const output: LogEntry[] = [];
const revertedLogs = [];
@ -98,6 +200,8 @@ function filterLogs(logs: LogEntry[], platform: Platform) {
// eslint-disable-next-line no-unused-vars, @typescript-eslint/no-unused-vars
// let updatedTranslations = false;
const renovateMessages: RenovateMessage[] = [];
for (const log of logs) {
// Save to an array any commit that has been reverted, and exclude
@ -110,9 +214,18 @@ function filterLogs(logs: LogEntry[], platform: Platform) {
if (revertedLogs.indexOf(log.message) >= 0) continue;
let isRenovate = false;
let prefix = log.message.trim().toLowerCase().split(':');
if (prefix.length <= 1) continue;
prefix = prefix[0].split(',').map(s => s.trim());
if (prefix.length <= 1) {
if (log.author && log.author.name === 'renovate[bot]') {
prefix = ['renovate'];
isRenovate = true;
} else {
continue;
}
} else {
prefix = prefix[0].split(',').map(s => s.trim());
}
let addIt = false;
@ -128,6 +241,11 @@ function filterLogs(logs: LogEntry[], platform: Platform) {
if (platform === 'server' && prefix.indexOf('server') >= 0) addIt = true;
if (platform === 'cloud' && (prefix.indexOf('cloud') >= 0 || prefix.indexOf('server') >= 0)) addIt = true;
if (isRenovate && filesApplyToPlatform(log.files, platform)) {
renovateMessages.push(parseRenovateMessage(log.message));
addIt = false;
}
// Translation updates often comes in format "Translation: Update pt_PT.po"
// but that's not useful in a changelog especially since most people
// don't know country and language codes. So we catch all these and
@ -148,6 +266,16 @@ function filterLogs(logs: LogEntry[], platform: Platform) {
// Actually we don't really need this info - translations are being updated all the time
// if (updatedTranslations) output.push({ message: 'Updated translations' });
const renovateSummary = summarizeRenovateMessages(renovateMessages);
if (renovateSummary) {
output.push({
author: { name: '', email: '', login: '' },
commit: '',
files: [],
message: renovateSummary,
});
}
return output;
}
@ -281,7 +409,9 @@ function formatCommitMessage(commit: string, msg: string, author: Author, option
} else {
const commitStrings = [commit.substr(0, 7)];
if (authorMd) commitStrings.push(`by ${authorMd}`);
output += ` (${commitStrings.join(' ')})`;
if (commitStrings.join('').length) {
output += ` (${commitStrings.join(' ')})`;
}
}
}
@ -379,8 +509,11 @@ async function main() {
console.info(changelogString.join('\n'));
}
main().catch((error) => {
console.error('Fatal error');
console.error(error);
process.exit(1);
});
if (require.main === module) {
// eslint-disable-next-line promise/prefer-await-to-then
main().catch((error) => {
console.error('Fatal error');
console.error(error);
process.exit(1);
});
}