@emdaer/plugin-contributors-details-github
Version:
An emdaer plugin that gathers and renders contributor details from GitHub
135 lines (125 loc) • 3.78 kB
JavaScript
/* @flow */
const fs = require('fs-extra');
const fetch = require('node-fetch').default;
type Contributor = {
login: string,
avatar_url: string,
html_url: string,
login: string,
name: string,
bio: string,
};
const DEFAULT_PATH = './AUTHORS';
const DEFAULT_SUMMARY_TITLE = 'Contributors';
const CONTRIBUTORS_DATA_FILE =
'./.emdaer/.offline/plugin-contributors-details-github/contributors-data.json';
const EXAMPLE_FORMAT = '"Name <username>"';
function getSummary(summary: string): string {
return `<summary><strong>${summary}</strong></summary><br />`;
}
async function fetchUser(login: string, name: string): Promise<Contributor> {
const response = await fetch(`https://api.github.com/users/${login}`);
const res = await response.json();
if (response.status !== 404 && response.status >= 400) {
throw new Error(`Unable to fetch ${login} from GitHub:`, res.message);
}
return Object.assign(res, {
login,
name,
avatar_url: `https://avatars0.githubusercontent.com/u/${res.id}?s=24`,
});
}
async function cacheContributorData(
contributors: Array<Contributor>
): Promise<Array<Contributor>> {
return fs
.outputJson(CONTRIBUTORS_DATA_FILE, await contributors, { spaces: 2 })
.then(() => contributors);
}
async function getContributors(
contributors: Array<string>
): Promise<Array<Contributor>> {
return Promise.all(
contributors.map(contributor => {
const [name, loginPart] = contributor.split('<').map(part => part.trim());
if (!loginPart) {
throw new Error(
`Contributor login for ${name} is not valid: Expected ${EXAMPLE_FORMAT}`
);
}
const login = loginPart
.split('>')
.shift()
.trim();
return fetchUser(login, name);
})
)
.catch(() => fs.readJson(CONTRIBUTORS_DATA_FILE))
.then(data => {
const invalidContributor = data.find(contributor => !contributor.id);
if (invalidContributor) {
throw new Error(
`User ${invalidContributor.login} is not found in GitHub.`
);
}
return cacheContributorData(data);
});
}
/**
* A plugin to generate details
* @example
* <!--emdaer-p
* - '@emdaer/plugin-contributors-details-github'
* -->
* @param options
* @param {string} [options.path] The path to the contributors list
* @param {string} [options.title] The title of the details summary
* @returns {Promise<string>} The details element
*/
async function contributorsDetailsPlugin(
{
path = DEFAULT_PATH,
title = DEFAULT_SUMMARY_TITLE,
}: {
path?: string,
title?: string,
} = {
path: DEFAULT_PATH,
title: DEFAULT_SUMMARY_TITLE,
}
): Promise<?string> {
let authorFileContents;
try {
authorFileContents = (await fs.readFile(path)).toString();
} catch (e) {
throw new Error(`${path} does not exist`);
}
const contributors = authorFileContents.split('\n').filter(person => person);
if (contributors.length < 1) {
throw new Error(
`Your contributors file (${path}) must contain contributors in the format: ${EXAMPLE_FORMAT}`
);
}
let contributorsData;
try {
contributorsData = await getContributors(Array.from(new Set(contributors)));
} catch (e) {
throw new Error(`Missing contributor info: ${e.message}`);
}
return `<details>
${getSummary(title)}
${contributorsData
.map(
contributor =>
`<a${contributor.bio ? ` title="${contributor.bio}"` : ''} href="${
contributor.html_url
}">
<img align="left" src="${contributor.avatar_url}" />
</a>
<strong>${contributor.name}</strong>
<br /><br />`
)
.join('\n')}
</details>`;
}
module.exports = contributorsDetailsPlugin;