blob: fec529528bfd3313ea80082074c9efba9c7d0969 [file] [log] [blame] [edit]
/* This file is a part of @mdn/browser-compat-data
* See LICENSE file for more information. */
export interface Stats {
commits: number;
changed: number;
insertions: number;
deletions: number;
releaseContributors: number;
totalContributors: number;
features: number;
stars: number;
start: string;
end: string;
}
type ChangeStats = Pick<
Stats,
'commits' | 'changed' | 'insertions' | 'deletions'
>;
import chalk from 'chalk-template';
import { walk } from '../../utils/index.js';
import pluralize from '../lib/pluralize.js';
import { exec, queryPRs, githubAPI } from './utils.js';
/**
* Get stargazers for the repository
* @returns The number of stargazer
*/
const stargazers = async (): Promise<number> => {
const json = githubAPI('');
return json.stargazers_count;
};
/**
* Get the number of contributors that have committed to the repository
* @returns The number of contributors that have contributed to the repository
*/
const contributors = (): number => {
const data = exec(
'gh api /repos/mdn/browser-compat-data/contributors?anon=1 --paginate',
);
return JSON.parse('[' + data.replace(/\]\[/g, '],[') + ']').flat(1).length;
};
/**
* Get all of the stats for the release
* @param start The last version number
* @returns The statistics
*/
const stats = (start: string): ChangeStats => {
// Get just the diff stats summary
const diff = exec(`git diff --shortstat ${start}...origin/main`);
if (diff === '') {
console.error(chalk`{red No changes for which to generate statistics.}`);
process.exit(1);
}
// Extract the numbers from a line like this:
// 50 files changed, 1988 insertions(+), 2056 deletions(-)
const match = diff.match(
/(?<changed>\d+) files? changed(?:, (?<insertions>\d+) insertions?(\(\+\)))?(?:, (?<deletions>\d+) deletions?\(-\))?/,
);
if (!match) {
console.error(chalk`{red No changes for which to generate statistics.}`);
process.exit(1);
}
const { changed, insertions, deletions } = match.groups as any;
// Get the number of commits
const commits = exec(`git rev-list --count ${start}...origin/main`);
return {
commits: Number(commits),
changed: Number(changed) || 0,
insertions: Number(insertions) || 0,
deletions: Number(deletions) || 0,
};
};
/**
* Get the number of contributors that have committed to this release
* @param fromDate The date of the last release
* @returns The authors of the commits
*/
const getReleaseContributors = (fromDate: string): Set<string> => {
const prs = queryPRs({
json: 'author',
search: `merged:>=${fromDate}`,
});
return new Set(prs.map((pr) => pr.author.login));
};
/**
* Count the number of features in BCD
* @returns The number of features
*/
const countFeatures = (): number => [...walk()].length;
/**
* Format the stats as Markdown
* @param details The stats to format
* @returns The formatted stats
*/
export const formatStats = (details: Stats): string =>
[
'### Statistics',
'',
`- ${pluralize('contributor', details.releaseContributors)} ${
details.releaseContributors > 1 ? 'have' : 'has'
} changed ${pluralize('file', details.changed)} with ${pluralize(
'addition',
details.insertions,
)} and ${pluralize('deletion', details.deletions)} in ${pluralize(
'commit',
details.commits,
)} ([\`${details.start}...${
details.end
}\`](https://github.com/mdn/browser-compat-data/compare/${
details.start
}...${details.end}))`,
`- ${pluralize('total feature', details.features)}`,
`- ${pluralize('total contributor', details.totalContributors)}`,
`- ${pluralize('total stargazer', details.stars)}`,
'',
].join('\n');
/**
* Get the statistics for the release
* @param start The last release number
* @param end This release number
* @param startDate The date of the last release
* @returns The release statistics
*/
export const getStats = async (
start: string,
end: string,
startDate: string,
): Promise<Stats> => ({
start,
end,
...stats(start),
releaseContributors: getReleaseContributors(startDate).size,
totalContributors: contributors(),
stars: await stargazers(),
features: countFeatures(),
});