Initalize

This commit is contained in:
Your Name
2026-05-03 12:12:57 -04:00
commit 38652eb9b5
10603 changed files with 1762136 additions and 0 deletions

View File

@@ -0,0 +1,30 @@
import Debug from 'debug';
import {default as resolveGitPaths} from '../utils/resolve-git-paths.js';
import {default as getContributors} from '../utils/get-contributors.js';
export default async function(pageData, {
debug = Debug('@lando/add-contributors'), // eslint-disable-line
siteConfig,
} = {}) {
debug = debug.extend(`${pageData.relativePath}`);
// get path
const {frontmatter, relativePath} = pageData;
// compute git things
const gitDir = siteConfig?.userConfig?.gitRoot;
const gitPaths = resolveGitPaths(relativePath, siteConfig.srcDir.replace(`${gitDir}/`, ''), frontmatter['git-include']);
// get contributors
const contributors = frontmatter.contributors || siteConfig?.site?.themeConfig?.contributors || false;
// add contributors unless turned off
if (contributors !== false) {
try {
pageData.contributors = await getContributors(gitDir, contributors, {debug, paths: gitPaths});
debug('set contributors %o', pageData.contributors);
} catch (error) {
debug('could not get contributor information, considering this non-fatal but you should investigate and resolve');
console.error(error);
}
}
};

View File

@@ -0,0 +1,65 @@
import {resolve} from 'node:path';
import Debug from 'debug';
export default async function(pageData, {
debug = Debug('@lando/add-metadata'), // eslint-disable-line
siteConfig,
} = {}) {
debug = debug.extend(`${pageData.relativePath}`);
// get stuff
const {frontmatter, lastUpdated, relativePath} = pageData;
const {site} = siteConfig;
// make sure header info is at least an empty array
if (!Array.isArray(frontmatter.head)) frontmatter.head = [];
// retrieve metadata
const autometa = siteConfig?.site?.themeConfig?.autometa ?? false;
if (autometa !== false) {
const {canonicalUrl, image, twitter, x} = autometa;
const title = frontmatter.title ?? pageData.title ?? site.title;
const description = frontmatter.description ?? frontmatter.summary ?? site.description;
const i = frontmatter.image ?? image ?? site?.logo?.src;
const xandle = x ?? twitter;
const published = new Date(Number.isNaN(lastUpdated) || !lastUpdated ? Date.now() : lastUpdated);
// generics
frontmatter.head.push(
['meta', {name: 'twitter:card', content: 'summary'}],
['meta', {name: 'twitter:title', content: title}],
['meta', {name: 'twitter:description', content: description}],
['meta', {name: 'twitter:image', content: i}],
['meta', {name: 'twitter:image:alt', content: title}],
['meta', {property: 'og:type', content: 'article'}],
['meta', {property: 'og:title', content: title}],
['meta', {property: 'og:description', content: description}],
['meta', {property: 'og:site_name', content: site.title}],
['meta', {name: 'og:image', content: i}],
['meta', {name: 'og:image:alt', content: title}],
['meta', {property: 'article:published_time', content: published}],
['meta', {itemprop: 'name', content: title}],
['meta', {itemprop: 'description', content: description}],
);
debug('set metadata %o', {title, description, i, published});
// twitter/x
if (xandle) {
frontmatter.head.push(['meta', {name: 'twitter:site', content: xandle}]);
debug('set xandle to %o', xandle);
}
// canonical stuff
if (canonicalUrl) {
const pathname = relativePath.replace(/(^|\/)index\.md$/, '$1').replace(/\.md$/, site.cleanUrls ? '' : '.html');
const url = new URL(resolve(site.base, pathname), canonicalUrl);
const {href} = url;
frontmatter.head.unshift(
['meta', {name: 'twitter:url', content: href}],
['meta', {property: 'og:url', content: href}],
['link', {rel: 'canonical', href: href}],
);
debug('set canonical url to %o', href);
}
}
};

View File

@@ -0,0 +1,29 @@
import Debug from 'debug';
const getContributor = (id, contributors = []) => contributors.find(contributor => contributor.email === id)
?? contributors.find(contributor => contributor.name === id);
const getLink = author => {
if (author.link) return author.link;
else if (Array.isArray(author?.links) && author.links[0]) return author.links[0].link;
else if (author.email) return `mailto:${author.email}`;
};
export default async function(pageData, {
team,
debug = Debug('@lando/augment-authors'), // eslint-disable-line
} = {}) {
debug = debug.extend(`${pageData.relativePath}`);
const {frontmatter} = pageData;
// normalize and augment author info
if (Array.isArray(frontmatter.authors)) {
frontmatter.authors = frontmatter.authors
.map(author => typeof author === 'string' ? getContributor(author, team) : author)
.filter(author => author && author !== false && author !== null)
.map(author => ({...author, link: getLink(author)}));
}
// log
debug('augmented author information to %o', {authors: frontmatter.authors});
};

View File

@@ -0,0 +1,20 @@
import fg from 'fast-glob';
import Debug from 'debug';
export default async function(siteConfig, {debug = Debug('@lando/build-collections')} = {}) { // eslint-disable-line
// ensure siteConfig.collections is at least an empty object
if (!siteConfig.collections || typeof siteConfig.collections !== 'object') siteConfig.collections = {};
// before we start lets make sure we have a list of paths for each collection
// we do it like this to minimize running fastglob a bunch of times
for (const [collection, config] of Object.entries(siteConfig?.site?.themeConfig?.collections ?? {})) {
if (!Array.isArray(siteConfig.collections[collection])) {
siteConfig.collections[collection] = fg.globSync(config.patterns ?? [], {
dot: true,
cwd: siteConfig.srcDir,
onlyFiles: true,
});
debug('built collection %o with page listing %o', collection, siteConfig.collections[collection]);
}
}
};

View File

@@ -0,0 +1,94 @@
import {writeFileSync} from 'node:fs';
import {join} from 'node:path';
import {default as createContentLoader} from '../utils/create-content-loader.js';
import Debug from 'debug';
import {Feed} from 'feed';
const normalizeUrl = url => url.replace(/([^:]\/)\/+/g, '$1');
// helper to normalize things
const normalizeConfig = (config = {}, feed = 'feed') => {
// pluralize
if (!config.patterns && config.pattern) {
config.patterns = config.pattern;
delete config.pattern;
}
// arrayize
if (typeof config.patterns === 'string') config.patterns = [config.patterns];
// set filename if it isnt set
if (!config.file) config.file = `/${feed}.rss`;
// add the feed name just for the hell of it?
config.name = feed;
return config;
};
const sort = items => items.sort((a, b) => a.timestamp < b.timestamp ? 1 : -1);
export default async function(siteConfig, {debug = Debug('@lando/generate-feeds')} = {}) { // eslint-disable-line
const {userConfig, site, outDir} = siteConfig;
// get config from priority sources
const config = userConfig.feeds
|| userConfig?.themeConfig?.feeds
|| userConfig.feed
|| userConfig?.themeConfig?.feed;
// if feeds is false or undefined then just end it all right here
if (!config) return;
// if feeds has only one top level feed then bump it down
const feeds = (config.patterns || config.pattern) ? {feed: config} : config;
// loop through and generate feedzzz
await Promise.all(Object.entries(feeds).map(async ([name, config]) => {
config = normalizeConfig(config, name);
const {href} = new URL(site.base ?? '/', config.baseUrl ?? userConfig.baseUrl);
const feed = new Feed({
title: config.title ?? site.title ?? '',
description: config.description ?? config.description ?? '',
id: normalizeUrl(config.id ?? href),
link: normalizeUrl(config.link ?? href),
language: config.language ?? 'en',
image: config.image ?? '',
favicon: normalizeUrl(config.favicon ?? `${href}/favicon.ico`),
copyright: config.copyright ?? '',
});
// get items
const items = await createContentLoader(config.patterns, {excerpt: true, render: true, siteConfig}, {debug}).load();
// and loop theough them to add
for (const item of sort(items)) {
const {authors, timestamp, excerpt, html, summary, title, url} = item;
// initial payload
const data = {
title,
id: normalizeUrl(`${href}${url}`),
link: normalizeUrl(`${href}${url}`),
description: excerpt !== '' ? excerpt : summary,
content: html,
date: new Date(timestamp),
};
// add authors if we have them
if (authors && Array.isArray(authors)) data.authors = authors.map(({name, link}) => ({name, link}));
// SHE ALWAYS NEEDS TO FEED
feed.addItem(data);
}
// write feed
const dest = join(outDir, config.file);
writeFileSync(dest, feed.rss2());
debug('generated rss feed %o', dest);
}));
}

View File

@@ -0,0 +1,63 @@
import {writeFileSync} from 'node:fs';
import {resolve} from 'node:path';
import merge from 'lodash-es/merge.js';
import robotstxt from 'generate-robotstxt';
import Debug from 'debug';
const defaults = {
allowAll: false,
disallowAll: false,
policy: [],
policies: [],
file: 'robots.txt',
};
const disallowAllPolicy = {
userAgent: '*',
disallow: '/',
};
const allowAllPolicy = {
userAgent: '*',
disallow: '',
};
export default async function({userConfig, outDir}, {debug = Debug('@lando/generate-robots')} = {}) { // eslint-disable-line
// get robots config or defaults
const robots = merge({}, defaults, userConfig.robots || userConfig?.themeConfig?.robots);
// munge policies together
robots.policy = [...robots.policy, ...robots.policies].filter(policy => policy);
// if no polices and disallowAll=false then assume allowAll
if (Array.isArray(robots.policy) && robots.policy.length === 0 && !robots.disallowAll) robots.allowAll = true;
// robot vibes
const {allowAll, disallowAll, host, sitemap} = robots;
// if disallow all then thats all we need really
if (disallowAll) robots.policy = [disallowAllPolicy];
// ditto for allow all
else if (allowAll) robots.policy = [allowAllPolicy];
// build generate options
const options = {policy: robots.policy};
// add host
if (host) options.host = host;
// add sitemap
if (sitemap) options.sitemap = sitemap;
debug('resolved robots.txt config from %o to %o', robots, options);
// write file
try {
const dest = resolve(outDir, robots.file);
const content = await robotstxt(options);
writeFileSync(dest, content);
debug('generated %o with content \n%O', dest, content);
} catch (error) {
throw error;
}
};

View File

@@ -0,0 +1,63 @@
import {existsSync, lstatSync} from 'node:fs';
import {resolve} from 'node:path';
import sortBy from 'lodash-es/sortBy.js';
import uniq from 'lodash-es/uniq.js';
import Debug from 'debug';
import {default as getTimestamp} from '../utils/get-timestamp.js';
export default async function(pageData, {
siteConfig,
debug = Debug('@lando/normalize-frontmatter'), // eslint-disable-line
} = {}) {
debug = debug.extend(`${pageData.relativePath}`);
const {frontmatter, relativePath} = pageData;
// use lastUpdated for date if its there
if (!frontmatter.date && Number.isInteger(pageData.lastUpdated)) frontmatter.date = pageData.lastUpdated;
// if we still dont have a date then we need to discover with git
if (!frontmatter.date
&& existsSync(resolve(siteConfig.srcDir, relativePath))
&& lstatSync(resolve(siteConfig.srcDir, relativePath)).isFile()
) {
frontmatter.date = await getTimestamp(resolve(siteConfig.srcDir, relativePath), {debug});
}
// standardize some date info
const date = new Date(frontmatter.date);
pageData.timestamp = date.getTime();
pageData.datetime = date.toJSON();
// prefer authors over author
if (frontmatter.authors === undefined && frontmatter.author !== undefined) {
pageData.frontmatter.authors = frontmatter.author;
delete pageData.frontmatter.author;
}
// prefer authors be an array
if (pageData.frontmatter.authors && !Array.isArray(pageData.frontmatter.authors)) {
pageData.frontmatter.authors = [pageData.frontmatter.authors];
}
// do a final check to make sure authors is at least an empty array
if (!pageData.frontmatter.authors) pageData.frontmatter.authors = [];
// consolidate it all into an array at frontmatter.tags
if (!frontmatter.tags) pageData.frontmatter.tags = [];
if (frontmatter.tags && typeof frontmatter.tags === 'string') pageData.frontmatter.tags = [pageData.frontmatter.tags];
if (frontmatter.tag && typeof frontmatter.tag === 'string') pageData.frontmatter.tags.push(pageData.frontmatter.tag);
if (Array.isArray(pageData.frontmatter.tag)) {
pageData.frontmatter.tags = pageData.frontmatter.tags.concat(pageData.frontmatter.tag);
}
delete pageData.frontmatter.tag;
// make sure tags are unique
if (Array.isArray(pageData.frontmatter.tags)) pageData.frontmatter.tags = sortBy(uniq((pageData.frontmatter.tags)));
// log
debug('normalized date information to %o', {date: frontmatter.date, timestamp: pageData.timestamp, datetime: pageData.datetime});
debug('normalized author information to %o', {authors: pageData.frontmatter.authors});
debug('normalized tags information to %o', {tags: pageData.frontmatter.tags});
};

View File

@@ -0,0 +1,29 @@
import Debug from 'debug';
export default async function(pageData, {
siteConfig,
debug = Debug('@lando/normalize-legacy-frontmatter'), // eslint-disable-line
} = {}) {
debug = debug.extend(`${pageData.relativePath}`);
const {frontmatter} = pageData;
// map and remove legacy vuepress2 theme blog setting
if (!frontmatter.collection && frontmatter.blog === true) {
pageData.frontmatter.collection = 'post';
delete pageData.frontmatter.blog;
debug('mapped frontmatter.blog to frontmatter.collection === post');
// ditto for guide setting
} else if (!frontmatter.collection && frontmatter.guide === true) {
pageData.frontmatter.collection = 'guide';
delete pageData.frontmatter.guide;
debug('mapped frontmatter.guide to frontmatter.collection === guide');
}
// ditto for updated
if (!frontmatter.date && frontmatter?.updated?.timestamp) {
pageData.frontmatter.date = frontmatter.updated.timestamp;
delete pageData.frontmatter.updated.timestamp;
debug('mapped frontmatter.updated.timestamp to frontmatter.date');
}
};

View File

@@ -0,0 +1,38 @@
import merge from 'lodash-es/merge.js';
import Debug from 'debug';
export default async function(pageData, {
siteConfig,
debug = Debug('@lando/parse-collections'), // eslint-disable-line
} = {}) {
debug = debug.extend(`${pageData.relativePath}`);
// get stuff
const {site} = siteConfig;
const {themeConfig} = site;
const {contributors, frontmatter, relativePath} = pageData;
// loop through collections so we can assign default values
await Promise.all(Object.entries(themeConfig?.collections ?? {}).map(async ([collection, config]) => {
// get collection pages so we can do the match
const pages = siteConfig?.collections[collection] ?? [];
// if this is a match then do the collection stuff we need to do
if (pages.includes(relativePath) || frontmatter.collection === collection) {
// if no author is set then we should be able to set it with contrib info
if ((frontmatter.authors === undefined
|| (Array.isArray(frontmatter.authors) && frontmatter.authors.length === 0))
&& Array.isArray(contributors)) {
frontmatter.authors = contributors;
debug('set authors %o using contributors information', frontmatter.authors.map(author => author.name));
}
// merge over defaults and save config and call it a day
pageData.frontmatter = merge({}, config.frontmatter, frontmatter);
pageData.collection = config;
// log
debug('rebased on collection %o defaults %O', collection, config.frontmatter);
}
}));
};