blob: b1c682a278b69c992d3dc4621094c393e2e2ef9c [file] [log] [blame]
/* This file is a part of @mdn/browser-compat-data
* See LICENSE file for more information. */
/**
* @typedef {object} Stats
* @property {number} commits
* @property {number} changed
* @property {number} insertions
* @property {number} deletions
* @property {number} releaseContributors
* @property {number} totalContributors
* @property {number} features
* @property {number} stars
* @property {string} start
* @property {string} end
*/
/**
* @typedef {Pick<Stats, 'commits' | 'changed' | 'insertions' | 'deletions'>} ChangeStats
*/
import { styleText } from 'node:util';
import { spawn, walk } from '../../utils/index.js';
import pluralize from '../lib/pluralize.js';
import { queryPRs, githubAPI } from './utils.js';
/**
* Get stargazers for the repository
* @returns {Promise<number>} The number of stargazer
*/
const stargazers = async () => {
const json = githubAPI('');
return json.stargazers_count;
};
/**
* Get the number of contributors that have committed to the repository
* @returns {number} The number of contributors that have contributed to the repository
*/
const contributors = () => {
const data = spawn('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 {string} start The last version number
* @returns {ChangeStats} The statistics
*/
const stats = (start) => {
// Get just the diff stats summary
const diff = spawn('git', ['diff', '--shortstat', `${start}...origin/main`]);
if (diff === '') {
console.error(
styleText('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(
styleText('red', 'No changes for which to generate statistics.'),
);
process.exit(1);
}
const { changed, insertions, deletions } = /** @type {*} */ (match.groups);
// Get the number of commits
const commits = spawn('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 {string} fromDate The date of the last release
* @returns {Set<string>} The authors of the commits
*/
const getReleaseContributors = (fromDate) => {
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 {number} The number of features
*/
const countFeatures = () => [...walk()].length;
/**
* Format the stats as Markdown
* @param {Stats} details The stats to format
* @returns {string} The formatted stats
*/
export const formatStats = (details) =>
[
'### 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 {string} start The last release number
* @param {string} end This release number
* @param {string} startDate The date of the last release
* @returns {Promise<Stats>} The release statistics
*/
export const getStats = async (start, end, startDate) => ({
start,
end,
...stats(start),
releaseContributors: getReleaseContributors(startDate).size,
totalContributors: contributors(),
stars: await stargazers(),
features: countFeatures(),
});