staticsitegenerators
Version:
A comprehensive, partially automatically generated comparison of static site generators with some minimal meta data about them
129 lines (128 loc) • 5.02 kB
JavaScript
/* eslint camelcase:0 */
import naturalCompare from 'string-natural-compare';
import { validateCredentials, getGitHubRepositories } from '@bevry/github-api';
import arrangeKeys from 'arrangekeys';
import crypto from 'node:crypto';
/** The preferred order of keys when arranging entry objects */
const keyorder = 'id name github gitlab bitbucket website license language description created_at updated_at abandoned is extensible stars forks watchers';
/**
* Sort an array of entries by name and then by GitHub repository name.
* @param data The array of entries with name and optional github properties to sort
* @returns Sorted array of entries
*/
function sort(data) {
return data.sort((a, b) => naturalCompare(a.name, b.name, {
caseInsensitive: true,
}) ||
naturalCompare(a.github, b.github, {
caseInsensitive: true,
}));
}
/**
* Compare two values for case-insensitive equality, handling type coercion.
* @param a The first value to compare
* @param b The second value to compare
* @returns True if the values are considered equal, false otherwise
*/
function same(a, b) {
const ta = typeof a, tb = typeof b;
if (ta === tb) {
if (ta === 'string') {
return a.toLowerCase() === b.toLowerCase();
}
return a === b;
}
/* eslint eqeqeq:0 */
return a == b;
}
/**
* Trim redundant data from the listing and enhance with GitHub API data.
* @param data An array of raw entries to hydrate with GitHub metadata
* @param opts Configuration options for the hydration process including corrective mode, cache duration, and logging function
* @returns A promise resolving to both raw and hydrated entry arrays
*/
export async function hydrate(data, opts = {}) {
validateCredentials();
if (opts.corrective == null)
opts.corrective = false;
if (opts.cache == null)
opts.cache = 1000 * 60 * 60 * 24; // one day
const rawMap = {};
const hydratedMap = {};
const githubRepos = [];
data.forEach(function (entry, index) {
delete entry.id;
const key = (entry.github && entry.github.toLowerCase()) || index;
rawMap[key] = Object.assign({}, arrangeKeys(entry, keyorder));
hydratedMap[key] = Object.assign({
id: crypto
.createHash('md5')
.update(JSON.stringify({
name: entry.name,
website: entry.website,
github: entry.github,
}))
.digest('hex'),
}, arrangeKeys(entry, keyorder));
if (entry.github) {
githubRepos.push(entry.github);
}
});
// Log
if (opts.log) {
opts.log('info', `Fetching the github information, all ${githubRepos.length} of them`);
}
// Enhance with github data
const repos = await getGitHubRepositories(githubRepos);
for (const github of repos) {
// Prepare
const key = github.full_name.toLowerCase();
const raw = rawMap[key];
const hydrated = hydratedMap[key];
// Confirm existence as name may have changed from the listing, for example a repo rename
if (raw == null) {
if (opts.log) {
opts.log('warn', `${github.full_name} is missing, likely due to rename`);
}
continue; // skip
}
// Apply github fields
const fields = {
description: github.description,
language: github.language,
license: github.license && github.license.key,
website: github.homepage &&
github.homepage.toLowerCase().includes(`github.com/${key}`) &&
github.homepage,
stars: github.stargazers_count,
watchers: github.watchers_count,
forks: github.forks_count,
// @ts-expect-error typescript is wrong
created_at: github.created_at,
// @ts-expect-error typescript is wrong
updated_at: github.updated_at,
};
for (const [key, value] of Object.entries(fields)) {
const rawValue = raw[key];
if (value) {
if (opts.corrective && rawValue && value && same(rawValue, value)) {
if (opts.log) {
opts.log('note', `trimming ${key} on ${github.full_name} as it is the same as the github data: ${value}`);
}
delete raw[key];
}
if (hydrated[key] == null) {
if (opts.log) {
opts.log('info', `added ${key} on ${github.full_name} from the github data`);
}
hydrated[key] = value;
}
}
}
hydratedMap[key] = arrangeKeys(hydrated, keyorder);
}
return {
hydrated: sort(Object.keys(hydratedMap).map((k) => hydratedMap[k])),
raw: sort(Object.keys(rawMap).map((k) => rawMap[k])),
};
}