Files
JiboViteDocs/node_modules/@lando/vitepress-theme-default-plus/utils/get-contributors.js
Your Name 38652eb9b5 Initalize
2026-05-03 12:12:57 -04:00

139 lines
4.7 KiB
JavaScript

import {default as execSync} from './parse-stdout.js';
import findIndex from 'lodash-es/findIndex.js';
import gravatarUrl from 'gravatar-url';
import groupBy from 'lodash-es/groupBy.js';
import Debug from 'debug';
const parseStringInclude = data => {
const parts = data.trim().split(' ');
// add a single commit if we dont have any commits
if (!Number.isInteger(parseInt(parts[0]))) parts[0] = 1;
// mod part 0 so it is parsed correclty downstream
parts[0] = parts[0] = ` ${parts[0]}\t`;
return parts.join(' ');
};
export default function async(
cwd,
{
merge = 'name',
debotify = true,
include = [],
exclude = [],
} = {},
{
debug = Debug('@lando/get-contributors'), // eslint-disable-line
paths = [],
} = {},
) {
// start with a command that will get ALL THE AUTHORS
const command = ['git', '--no-pager', 'shortlog', '-nes', 'HEAD'];
const opts = {cwd, stdin: 'inherit'};
// then scope to paths if appropriate
if (paths.length > 0) command.push('--', ...paths);
// run
debug('running command %o with exec options %o', command, opts);
const stdout = execSync(command.join(' '), opts);
// parse git data into a string
let data = stdout.split('\n');
// separate out include strings and objects
const includeStrings = include.filter(contributor => typeof contributor === 'string') ?? [];
const includeObjects = include.filter(contributor => typeof contributor === 'object') ?? [];
// add in any include strings
if (includeStrings.length > 0) for (const contributor of includeStrings) data.push(parseStringInclude(contributor));
// map strings to <VPTeamMembersItem.vue> compatible objects
data = data.map(item => item.trim().match(/^(\d+)\t(.*) <(.*)>$/))
.filter(item => item !== null)
.map(([, commits, name, email]) => ({
commits: Number.parseInt(commits, 10),
email,
name: name.trim(),
avatar: gravatarUrl(email),
title: undefined,
org: undefined,
maintainer: false,
links: [],
}));
// add in any include objects
if (includeObjects.length > 0) {
for (const contributor of includeObjects) {
// try to see if we already have this contrib
const existing = data.find(member => member.email === contributor.email || member.email === contributor.mergeWith);
// if we do then update it
if (existing) Object.assign(existing, contributor);
// otherwise treat it as a new contrib only merge only is true
else if (!existing && contributor.mergeOnly !== true) data.push({name: '', email: '', ...contributor});
}
};
// remove any bots
if (debotify) {
data = data
.filter(contributor => !contributor.email.includes('[bot]') && !contributor.name.includes('[bot]'))
.filter(contributor => contributor.email !== 'rtfm47@lando.dev');
}
// remove any excluded contributors
if (exclude.length > 0) {
for (let excluded of exclude) {
// if excluded is a string then map into an object
if (typeof excluded === 'string' && excluded.match(/^(.*) <(.*)>$/) !== null) {
const parts = excluded.match(/^(.*) <(.*)>$/);
excluded = {name: parts[1], email: parts[2]};
}
// attampte to exclude
if (findIndex(data, excluded) > -1) data.splice(findIndex(data, excluded), 1);
}
}
// attempt to merge same named entries together
// this will prefer the member metadata eg email, avatar etc with the most commits
// it will also add all the commits together
if (merge !== false && ['email', 'name'].includes(merge)) {
const grouped = groupBy(data, merge);
// attempt merge strategy for any merge with more than one match
for (const [id, matches] of Object.entries(grouped)) {
if (matches.length > 1) {
const best = matches[0];
best.commits = matches.map(match => match.commits).reduce((sum, amount) => sum + amount, 0);
// special handling for org/title
for (const special of ['links', 'org', 'title']) {
if (!best[special] || (Array.isArray(best[special]) && best[special].length === 0)) {
best[special] = matches
.map(match => match[special])
.filter(data => data !== undefined && data !== null && data !== '' && data.length !== 0)[0];
}
}
// reset matches
grouped[id] = [best];
}
}
// reset data with merged things
data = Object.entries(grouped).map(([name, matches]) => matches[0]);
}
// sort by commits
data = data.sort((a, b) => b.commits - a.commits);
// separate maintainers from contribs
const maintainers = data.filter(contrib => contrib.maintainer);
const contributors = data.filter(contrib => !contrib.maintainer);
// return contribs with maintainers in the front
return maintainers.concat(contributors);
}