UNPKG

markdown-rambler

Version:

Yet another opinionated & powerful static site generator.

885 lines (861 loc) 34 kB
var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod )); var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/index.ts var src_exports = {}; __export(src_exports, { MarkdownRambler: () => MarkdownRambler, addScript: () => addScript, append: () => append, findElement: () => findElement, html: () => html, insertBefore: () => insertBefore, insertBeforeStylesheets: () => insertBeforeStylesheets }); module.exports = __toCommonJS(src_exports); // src/util/at.ts if (!("at" in Array.prototype)) { Array.prototype.at = function(index) { if (index >= 0) return this[index]; return this[this.length + index]; }; } // src/MarkdownRambler.ts var import_os2 = require("os"); var import_util2 = require("util"); var import_node_path3 = require("path"); var import_globby = require("globby"); var import_lodash2 = __toESM(require("lodash"), 1); var import_unified = require("unified"); var import_to_vfile = require("to-vfile"); var import_vfile_matter = require("vfile-matter"); var import_vfile_rename = require("vfile-rename"); var import_vfile_mkdirp = require("vfile-mkdirp"); var import_remark_rehype = __toESM(require("remark-rehype"), 1); var import_rehype_stringify2 = __toESM(require("rehype-stringify"), 1); var import_xast_util_feed = require("xast-util-feed"); var import_xast_util_to_xml = require("xast-util-to-xml"); var import_minisearch = __toESM(require("minisearch"), 1); // src/mdast/parsers.ts var import_remark_parse = __toESM(require("remark-parse"), 1); var import_remark_frontmatter = __toESM(require("remark-frontmatter"), 1); var import_remark_directive = __toESM(require("remark-directive"), 1); var import_hastscript = require("hastscript"); var import_unist_util_visit = require("unist-util-visit"); // src/mdast/table.ts var import_micromark_extension_gfm_table = require("micromark-extension-gfm-table"); var import_mdast_util_gfm_table = require("mdast-util-gfm-table"); function table() { const data = this.data(); add("micromarkExtensions", import_micromark_extension_gfm_table.gfmTable); add("fromMarkdownExtensions", import_mdast_util_gfm_table.gfmTableFromMarkdown); add("toMarkdownExtensions", import_mdast_util_gfm_table.gfmTableToMarkdown); function add(field, value) { data[field] = data[field] || []; data[field].push(value); } } // src/mdast/parsers.ts var parsers_default = [import_remark_parse.default, import_remark_frontmatter.default, table, import_remark_directive.default]; var transformDirectives = (directives2) => (tree, vFile) => { const visitor = (node, index, parent) => { if (node.type === "textDirective" || node.type === "leafDirective" || node.type === "containerDirective") { const previousSibling = node.type === "textDirective" && parent.children[index - 1]; if (node.type === "textDirective" && previousSibling && !previousSibling.value.endsWith(" ")) { node.type = "text"; node.value = `:${node.name}`; return; } if (node.name in directives2) { const hast = directives2[node.name](node, index, parent, vFile); const data = node.data || (node.data = {}); data.hName = hast.tagName; data.hProperties = hast.properties; data.hChildren = hast.children; } else { const hast = (0, import_hastscript.h)(node.name, node.attributes); const data = node.data || (node.data = {}); data.hName = hast.tagName; data.hProperties = hast.properties; } } }; (0, import_unist_util_visit.visit)(tree, visitor); }; // src/mdast/formatters.ts var import_remark_prettier = __toESM(require("remark-prettier"), 1); var import_remark_reference_links = __toESM(require("remark-reference-links"), 1); // src/unist/order-links.ts var import_unist_util_visit2 = require("unist-util-visit"); var import_unist_util_is = require("unist-util-is"); var import_unist_util_remove = require("unist-util-remove"); function orderDefinitions() { return transformer; } function transformer(tree) { let id = 0; const refs = []; const defs = []; const store = []; (0, import_unist_util_visit2.visit)(tree, ["linkReference", "imageReference", "definition"], (node) => { if ((0, import_unist_util_is.is)(node, ["linkReference", "imageReference"])) refs.push(node); if ((0, import_unist_util_is.is)(node, "definition")) defs.push(node); }); refs.forEach((ref) => { const def = defs.find((d) => d.identifier === ref.identifier); const reusableDef = store.find((d) => d.url === def.url); if (reusableDef) { ref.identifier = reusableDef.identifier; ref.label = reusableDef.identifier; } else { const identifier = isNaN(ref.identifier) ? ref.identifier : String(++id); ref.identifier = identifier; ref.label = identifier; store.push({ type: "definition", title: def.title, url: def.url, identifier, label: identifier }); } }); (0, import_unist_util_remove.remove)(tree, "definition"); store.sort(definitionSorter).forEach((def) => { tree.children.push(def); }); } function definitionSorter(a, b) { if (isNaN(a.identifier) && !isNaN(b.identifier)) return 1; if (!isNaN(a.identifier) && isNaN(b.identifier)) return -1; if (!isNaN(b.identifier) && !isNaN(a.identifier)) return Number(a.identifier) - Number(b.identifier); return a.identifier - b.identifier; } var order_links_default = orderDefinitions; // src/mdast/formatters.ts var formatters_default = [import_remark_prettier.default, import_remark_reference_links.default, order_links_default]; // src/mdast/helpers.ts var import_unist_util_visit3 = require("unist-util-visit"); var getDocumentTitle = (tree) => { let title = ""; (0, import_unist_util_visit3.visit)(tree, "heading", (node) => { if (node.depth === 1) { title = node.children[0].value; return false; } }); return title; }; var removeDocumentTitle = () => (tree) => { (0, import_unist_util_visit3.visit)(tree, "heading", (node, index, parent) => { if (node.depth === 1) { parent == null ? void 0 : parent.children.splice(index, 1); return false; } }); }; // src/hast/transformers.ts var import_node_path2 = require("path"); var import_rehype_urls = __toESM(require("rehype-urls"), 1); var import_rehype_slug = __toESM(require("rehype-slug"), 1); var import_rehype_autolink_headings = __toESM(require("rehype-autolink-headings"), 1); var import_rehype_document = __toESM(require("rehype-document"), 1); // src/hast/plugins.ts var import_hastscript2 = require("hastscript"); // src/hast/traversal.ts var import_unist_util_visit4 = require("unist-util-visit"); var findElement = (tree, test, callback) => { const matcher = typeof test === "function" ? test : (node) => node.tagName === test; (0, import_unist_util_visit4.visit)(tree, "element", (node, index, parent) => { if (matcher(node) && parent && index !== null) { return callback(node, index, parent); } }); }; var append = (tree, test, ...elements) => { findElement(tree, test, (node) => { node.children.push(...elements); return false; }); return tree; }; var insertBefore = (tree, test, element) => { findElement(tree, test, (node, index, parent) => { parent == null ? void 0 : parent.children.splice(index, 0, element); return false; }); return tree; }; var insertBeforeStylesheets = (tree, element) => { const test = (node) => { var _a; const rel = [(_a = node == null ? void 0 : node.properties) == null ? void 0 : _a.rel].flat(); return node.tagName === "link" && rel.includes("stylesheet"); }; return insertBefore(tree, test, element); }; // src/hast/plugins.ts var addInlineScript = ({ type, content }) => (tree) => { const script = (0, import_hastscript2.h)("script", { type }, content); return append(tree, "body", script); }; var addScript = ({ src }) => (tree) => { const script = (0, import_hastscript2.h)("script", { src }); return append(tree, "body", script); }; // src/util/index.ts var import_path = require("path"); var groupByType = (vFiles) => vFiles.reduce((group, vFile) => { const { type } = vFile.data.meta; group[type] = group[type] ?? []; group[type].push(vFile); return group; }, {}); var getDefaults = (type, defaults) => { if (!type || type === "page" || !(type in defaults)) { return (defaults == null ? void 0 : defaults.page) ?? {}; } return { ...defaults.page, ...defaults[type] }; }; var resolveFrontMatter = (matter2, vFile) => { const obj = {}; for (const key in matter2) { switch (key) { case "tags": const tags = typeof matter2[key] === "string" ? matter2[key].split(/[ ,]+/) : matter2[key]; obj[key] = tags; break; case "image": const image = typeof matter2[key] === "string" ? { src: matter2[key] } : matter2[key]; image.src = (0, import_path.isAbsolute)(image.src) ? image.src : (0, import_path.join)(vFile.data.pathname, image.src); obj[key] = image; break; case "published": case "modified": obj[key] = new Date(matter2[key]); break; default: obj[key] = matter2[key]; break; } } return obj; }; var buildMetaData = (vFile, type, options) => { const defaults = getDefaults(type, options.defaults); const { pathname } = vFile.data; const base = { type, host: options.host, pathname, href: options.host + pathname, name: options.name, language: options.language, manifest: options.manifest, feed: options.feed }; const matter2 = resolveFrontMatter(vFile.data.matter, vFile); const pageStylesheets = typeof matter2.stylesheets === "string" ? [matter2.stylesheets] : matter2.stylesheets ?? []; const pageScripts = typeof matter2.scripts === "string" ? [matter2.scripts] : matter2.scripts ?? []; delete matter2.stylesheets; delete matter2.scripts; return Object.assign(base, defaults, matter2, { pageStylesheets: pageStylesheets.map((sheet) => (0, import_path.join)(pathname, sheet)), pageScripts: pageScripts.map((script) => (0, import_path.join)(pathname, script)) }); }; var iso = (value) => !value ? void 0 : (typeof value === "string" ? new Date(value) : value).toISOString(); var unique = (value, index, self) => self.indexOf(value) === index; var ucFirst = (value) => value[0].toUpperCase() + value.slice(1); // src/hast/metaTags.ts var getMetaTags = (meta) => { var _a; const tags = []; const type = meta.type === "article" ? "article" : "website"; tags.push({ property: "og:type", content: type }); if (meta.draft) tags.push({ name: "robots", content: "noindex" }); if (meta.description) tags.push({ name: "description", property: "og:description", content: meta.description }); if (meta.name) tags.push({ property: "og:site_name", content: meta.name }); if (meta.href) tags.push({ property: "og:url", content: meta.href }); if (meta.author) tags.push({ name: "author", content: meta.author.name }); if (meta.published) tags.push({ property: "article:published_time", content: iso(meta.published) }); if (meta.modified) tags.push({ property: "article:modified_time", content: iso(meta.modified) }); if ((_a = meta.author) == null ? void 0 : _a.twitter) { tags.push( { name: "twitter:title", property: "og:title", content: meta.title }, { name: "twitter:description", content: meta.description }, { name: "twitter:card", content: "summary_large_image" }, { name: "twitter:image", property: "og:image", content: meta.host + meta.image.src }, { name: "twitter:site", content: meta.author.twitter }, { name: "twitter:creator", content: meta.author.twitter }, { name: "twitter:image:alt", content: meta.title } ); } return tags; }; // src/hast/linkTags.ts var import_path2 = require("path"); var mimeTypes = { ".ico": "image/vnd.microsoft.icon", ".jpeg": "image/jpeg", ".jpg": "image/jpeg", ".png": "image/png", ".svg": "image/svg+xml", ".webp": "image/webp" }; var getLinkTags = (meta) => { const tags = [ { rel: "icon", href: "/favicon.ico", sizes: "any" }, { rel: "apple-touch-icon", href: "/apple-touch-icon.png" } ]; if (meta.manifest) tags.push({ rel: "manifest", href: meta.manifest }); if (meta.host && meta.href) tags.push({ rel: "canonical", href: meta.href }); const stylesheets = meta.bundledStylesheets ?? meta.stylesheets; if (stylesheets) tags.push(...stylesheets.map((href) => ({ rel: "stylesheet", href }))); if (meta.pageStylesheets) tags.push(...meta.pageStylesheets.map((href) => ({ rel: "stylesheet", href }))); if (meta.icon) { const ext = (0, import_path2.extname)(meta.icon.src); const type = mimeTypes[ext]; tags.push({ rel: "icon", href: meta.icon.src, type }); } if (meta.prefetch) { tags.push({ rel: "prefetch", href: meta.prefetch }); } if (meta.feed) { tags.push({ rel: "alternate", type: "application/rss+xml", href: meta.host + meta.feed.pathname, title: meta.feed.title }); } return tags; }; // src/util/fs.ts var import_os = require("os"); var import_fs = require("fs"); var import_promises = require("fs/promises"); var import_node_path = require("path"); var import_svgo = require("svgo"); var isFloating = (pathname) => !/^[\.\/]/.test(pathname); var isIndex = (filename) => /(README|index)\.md$/.test(filename); var resolveTargetPathname = (from, to) => { const fromPathname = resolvePathname(from); const [, filename, suffix] = to.match(/(?<filename>[^\?#]*)(?<suffix>.*)/); const toPathname = (0, import_node_path.resolve)(fromPathname, ...[isIndex(from) ? "." : ".."], resolvePathname(filename)); return toPathname + suffix; }; var resolvePathname = (filename) => { return (isFloating(filename) ? "/" : "") + filename.replace(/(README|index)?\.md$/, "").replace(/(.+)\/$/, "$1"); }; var ensureDir = async (target) => { const dir = (0, import_node_path.dirname)(target); try { await (0, import_promises.access)(dir, import_fs.constants.W_OK); } catch (error) { await (0, import_promises.mkdir)(dir, { recursive: true }); } }; var isDir = async (dir) => { try { await (0, import_promises.access)(dir, import_fs.constants.W_OK); return true; } catch (error) { return false; } }; var write = async (target, output) => { await ensureDir(target); (0, import_promises.writeFile)(target, output); }; var copy = async (source, target) => { await ensureDir(target); (0, import_promises.copyFile)(source, target); }; var append2 = async (source, target) => { await (0, import_promises.appendFile)(target, await (0, import_promises.readFile)(source) + import_os.EOL); }; var optimizeSVG = async (source, target) => { const optimized = (0, import_svgo.optimize)(await (0, import_promises.readFile)(source), { path: source, plugins: ["removeDimensions", { name: "removeAttrs", params: { attrs: "content" } }] }); await ensureDir(target); await (0, import_promises.writeFile)(target, optimized.data, "utf8"); }; var isInIgnoreDir = (file, ignorePatterns) => { return ignorePatterns.some((dir) => { if (dir instanceof RegExp) return dir.test(file); if (typeof dir === "string") return new RegExp(dir).test(file); if (typeof dir === "function") return dir(file); throw new Error(`Invalid ignoreDir value (${dir})`); }); }; var watchDir = async ({ dir, cb, ignorePatterns }) => { const callback = (eventType, filename) => { const file = (0, import_node_path.join)(dir, filename); if (isInIgnoreDir(file, ignorePatterns)) return; if (eventType === "change") { if (filename) { cb([dir, filename]); } else { console.warn("Filename not provided for", eventType, filename); } } }; if (await isDir(dir)) { (0, import_fs.watch)(dir, { recursive: true }, callback); return dir; } }; // src/hast/transformers.ts var fixRelativeLinks = (vFile) => (url, node) => { const { href, pathname } = url; if (href.startsWith(".") && ((0, import_node_path2.extname)(pathname) === ".md" || node.tagName === "img")) { return resolveTargetPathname(vFile.history.at(0), href); } return url; }; var defaultTransformers = (vFile) => { const { data } = vFile; const { meta, structuredContent } = data; const { title, language, scripts, bundledScripts, pageScripts } = meta; const js = [...bundledScripts ?? scripts ?? [], ...pageScripts ?? []]; return [ [() => (0, import_rehype_urls.default)(fixRelativeLinks(vFile))], // Hack to allow rehype-urls plugin again downstream import_rehype_slug.default, [import_rehype_autolink_headings.default, { behavior: "wrap", test: ["h2", "h3", "h4", "h5", "h6"] }], [import_rehype_document.default, { title, language, meta: getMetaTags(meta), link: getLinkTags(meta), js }], [addInlineScript, { type: "application/ld+json", content: JSON.stringify(structuredContent) }] ]; }; var transformers_default = defaultTransformers; // src/hast/render.ts var import_rehype_stringify = __toESM(require("rehype-stringify"), 1); var import_rehype_format = __toESM(require("rehype-format"), 1); var render_default = [import_rehype_format.default, import_rehype_stringify.default]; // src/util/structured-content.ts var import_lodash = __toESM(require("lodash"), 1); var getPage = (meta, base) => import_lodash.default.merge( {}, base, { "@type": "WebSite", sameAs: meta.sameAs }, meta.structuredContent ); var getArticle = (meta, base) => { var _a, _b, _c; const image = ((_a = meta.image) == null ? void 0 : _a.src) || ((_c = (_b = meta.publisher) == null ? void 0 : _b.logo) == null ? void 0 : _c.src); return import_lodash.default.merge( {}, base, { "@type": "Article", headline: meta.title, description: meta.description }, image ? { image } : {}, meta.structuredContent ); }; var getStructuredContent = (meta) => { var _a, _b, _c; const base = { "@context": "https://schema.org", "@type": "WebSite", mainEntityOfPage: { "@type": "WebPage", "@id": meta.href }, datePublished: iso(meta.published), dateModified: iso(meta.modified), inLanguage: meta.language }; if (((_a = meta.tags) == null ? void 0 : _a.length) > 0) { base.keywords = meta.tags.join(","); } if (meta.author) { base.author = { "@type": "Person", name: meta.author.name, url: meta.author.href }; } if (meta.publisher) { base.publisher = { "@type": "Organization", "@id": `${meta.publisher.href}/#organization`, name: meta.publisher.name, logo: { "@type": "ImageObject", url: meta.publisher.logo.src } }; } const type = ((_c = (_b = meta.structuredContent) == null ? void 0 : _b["@type"]) == null ? void 0 : _c.toLowerCase()) ?? meta.type; switch (type) { case "article": return getArticle(meta, base); default: return getPage(meta, base); } }; // src/hast/layout.ts var import_htm = __toESM(require("htm"), 1); var import_hastscript3 = require("hastscript"); var html = import_htm.default.bind(import_hastscript3.h); var layout = (options) => (node, vFile) => { const children = options.layout(node.children, vFile.data.meta); return { ...node, children: Array.isArray(children) ? children : [children] }; }; // src/MarkdownRambler.ts var debug = (0, import_util2.debuglog)("markdown-rambler"); var dbg = (vFile, text) => { const filename = typeof vFile === "string" ? vFile : vFile.history.at(0); const padded = filename.length > 28 ? `...${filename.slice(-25)}` : filename.padStart(28); debug(`[${padded}] ${text}`); }; var MarkdownRambler = class { constructor(options = {}) { this.options = Object.freeze(this.setDefaultOptions(options)); } setDefaultOptions(options) { return import_lodash2.default.defaultsDeep(options, { contentDir: !options.contentFiles ? "content" : ".", contentFiles: "**/*", publicDir: "public", outputDir: "dist", ignorePattern: /^(\.|node_modules)/, host: "", name: "", language: "en", manifest: false, sitemap: true, feed: false, search: false }); } getTransformers(vFile) { const { rehypePlugins = [] } = this.options; const plugins = [ typeof transformers_default === "function" ? transformers_default(vFile) : transformers_default, typeof rehypePlugins === "function" ? rehypePlugins(vFile) : rehypePlugins ]; return plugins.flat(); } /** @public */ async run() { const files = await this.getContentFiles(); if (!this.options.watch && this.options.publicDir) { await this.bundleAssets("stylesheets"); await this.bundleAssets("scripts"); } const markdownFiles = files.filter(([dir, filename]) => filename.endsWith(".md")); const parsedVFiles = await this.parseMarkdownFiles(markdownFiles); if (this.options.linkFiles) { const files2 = groupByType(parsedVFiles); parsedVFiles.forEach((vFile) => { vFile.data.vFiles = files2; }); } const vFiles = await this.renderMarkdownFiles(parsedVFiles); vFiles.map(async (vFile) => { dbg(vFile, `Writing ${vFile.history.at(-1)}`); this.verbose(`Writing ${vFile.history.at(-1)}`); await (0, import_vfile_mkdirp.mkdirp)(vFile); await import_to_vfile.toVFile.write(vFile); }); const assetFiles = files.filter(([dir, filename]) => !filename.endsWith(".md")); await Promise.all(assetFiles.map((file) => this.copyAsset(file))); console.log(`\u2714 ${vFiles.length} pages and ${assetFiles.length} asset files (in ${this.options.outputDir})`); const publishedVFiles = vFiles.filter((vFile) => !vFile.data.meta.draft); if (this.options.feed) { const feed = await this.renderFeed(publishedVFiles); if (feed) console.log(`\u2714 Feed (at ${feed})`); } if (this.options.sitemap) { const sitemap = await this.renderSitemap(publishedVFiles); if (sitemap) console.log(`\u2714 Sitemap (at ${sitemap})`); } if (this.options.search) { const search = await this.renderSearchIndex(publishedVFiles); if (search) console.log(`\u2714 Search index (at ${search})`); } if (this.options.watch) { const watchDirs = await this.watch([this.options.contentDir, this.options.publicDir].flat().filter(unique)); watchDirs.forEach((dir) => console.log(`\u25B6\uFE0E Watching for changes in ${dir}`)); } } async getContentFiles() { const dirs = [this.options.publicDir, this.options.contentDir].flat().filter(unique); const glob = [this.options.contentFiles].flat().filter(unique); const files = []; for (const cwd of dirs) { const result = await (0, import_globby.globby)(glob, { cwd, ignore: ["**/node_modules"] }); files.push(result.map((filename) => [cwd ?? "", filename])); } return files.flat(); } async bundleAssets(assetType) { const bundled = `bundled${ucFirst(assetType)}`; const pageTypes = Object.keys(this.options.defaults); const orderedPageTypes = ["page", ...pageTypes].filter(unique); const assets = {}; for (const pageType of orderedPageTypes) { assets[pageType] = assets[pageType] ?? []; if (this.options.defaults[pageType][assetType]) { for (const asset of this.options.defaults[pageType][assetType]) { if (!assets[pageType].includes(asset)) { const source = (0, import_node_path3.join)(this.options.publicDir, asset); const ext = (0, import_node_path3.extname)(source); const bundle = (0, import_node_path3.join)(asset, `../${pageType}.bundle${ext}`); const target = (0, import_node_path3.join)(this.options.outputDir, bundle); if (pageType !== "page" && assets.page.includes(asset)) { } else if (assets[pageType].length === 0) { dbg(source, `Copying ${target} (${asset})`); this.verbose(`Copying ${target} (${asset})`); await copy(source, target); assets[pageType].push(asset); if (pageType === "page") { this.options.defaults[pageType][bundled] = [bundle]; } else { this.options.defaults[pageType][bundled] = [this.options.defaults.page[bundled][0], bundle]; } } else { dbg(source, `Appending ${source} to ${target}`); this.verbose(`Appending ${source} to ${target}`); await append2(source, target); assets[pageType].push(asset); } } } } } } async copyAsset(file) { const [dir, filename] = file; const source = (0, import_node_path3.join)(dir, filename); const target = (0, import_node_path3.join)(this.options.outputDir, filename); if (filename.endsWith(".svg")) { dbg(source, `Optimizing to ${target}`); this.verbose(`Optimizing ${target}`); await optimizeSVG(source, target); } else { dbg(source, `Copying to ${target}`); this.verbose(`Copying ${target}`); await copy(source, target); } } async handleFile(file) { const [dir, filename] = file; if (filename.endsWith(".md")) { const parsedVFile = await this.parseMarkdownFile(file); const vFile = await this.renderMarkdownFile(parsedVFile); dbg(vFile, `Writing ${vFile.history.at(-1)}`); this.verbose(`Writing ${vFile.history.at(-1)}`); await (0, import_vfile_mkdirp.mkdirp)(vFile); await import_to_vfile.toVFile.write(vFile); } else { this.copyAsset(file); } } async parseMarkdownFile([dir, filename]) { const source = (0, import_node_path3.join)(dir, filename); const vFile = (0, import_to_vfile.readSync)(source); vFile.history.unshift(filename); return this.parseMarkdownVFile(vFile); } async parseMarkdownVFile(vFile) { dbg(vFile, `Parsing source file`); const options = this.options; const outputDir = options.outputDir; const parsers = options.parsers ?? parsers_default; const remarkPlugins = options.remarkPlugins ?? []; const parser = (0, import_unified.unified)().use([...parsers, ...remarkPlugins]); const tree = parser.parse(vFile); if (options.formatMarkdown) { dbg(vFile, `Formatting`); const plugins = [parsers_default[0], parsers_default[1], ...formatters_default]; const formattedVFile = await (0, import_unified.unified)().use(plugins).process(String(vFile)); if (formattedVFile.value.toString() !== vFile.value.toString()) { this.verbose(`Formatting ${vFile.history.at(-1)}`); await write(vFile.history.at(-1), formattedVFile.value); } } const filename = vFile.history.at(0); const pathname = resolvePathname(filename); vFile = (0, import_vfile_rename.rename)(vFile, { dirname: (0, import_node_path3.join)(outputDir, pathname), stem: "index", extname: ".html" }); dbg(vFile, `Rendered file will be written to ${vFile.history.at(-1)}`); const type = typeof options.type === "function" ? options.type(filename, vFile.data.matter) || "page" : "page"; (0, import_vfile_matter.matter)(vFile); vFile.data.pathname = pathname; const meta = buildMetaData(vFile, type, options); vFile.data.markdown = String(vFile.value); vFile.data.tree = tree; vFile.data.meta = meta; vFile.data.meta.title = meta.title ?? getDocumentTitle(tree); vFile.data.structuredContent = getStructuredContent(meta); dbg(vFile, `The ${type} "${meta.title}" will be served from ${pathname}`); return vFile; } parseMarkdownFiles(files) { return Promise.all(files.map((file) => this.parseMarkdownFile(file))); } renderMarkdownFiles(vFiles) { return Promise.all(vFiles.map((vFile) => this.renderMarkdownFile(vFile))); } async renderMarkdownFile(vFile) { dbg(vFile, `Rendering ${vFile.history.at(-1)}`); const { options } = this; const transformers = this.getTransformers(vFile); const renderers = options.renderers ?? render_default; const processor = (0, import_unified.unified)().use(transformDirectives, options.directives ? options.directives : false).use(import_remark_rehype.default, options.remarkRehypeOptions ?? {}).use(layout, vFile.data.meta.layout ? { layout: vFile.data.meta.layout } : false).use(transformers).use(renderers); const tree = await processor.run(vFile.data.tree, vFile); vFile.value = processor.stringify(tree); return vFile; } async renderFeed(vFiles) { if (!this.options.feed) return; if (!this.options.host) { this.verbose("Unable to render RSS feed without `host`"); return; } const { filter } = this.options.feed; const filtered = typeof filter === "function" ? vFiles.filter((vFile) => filter(vFile.data.meta.type, vFile)) : [...vFiles]; const items = await Promise.all( filtered.sort((a, b) => +new Date(b.data.meta.published) - +new Date(a.data.meta.published)).slice(0, 10).map(async (vFile) => { const plugins = [removeDocumentTitle, import_remark_rehype.default, import_rehype_stringify2.default]; const processor = (0, import_unified.unified)().use(plugins); const tree = await processor.run(vFile.data.tree); const html2 = processor.stringify(tree); const { meta } = vFile.data; return { title: meta.title, description: meta.description, descriptionHtml: html2, author: meta.author, url: (0, import_node_path3.join)(this.options.host, meta.pathname), modified: meta.modified, published: meta.published, tags: meta.tags }; }) ); const filename = (0, import_node_path3.join)(this.options.outputDir, this.options.feed.pathname); const title = this.options.feed.title ?? this.options.name; const description = this.options.feed.description ?? this.options.defaults.page.description; const author = this.options.feed.author ?? this.options.defaults.page.author.name; const contents = (0, import_xast_util_feed.rss)( { title, description, author, tags: this.options.feed.tags, url: this.options.host, lang: this.options.language, feedUrl: (0, import_node_path3.join)(this.options.host, this.options.feed.pathname) }, items ); await write(filename, (0, import_xast_util_to_xml.toXml)(contents)); return filename; } async renderSitemap(vFiles) { if (!this.options.host) { this.verbose("Unable to render sitemap without `host`"); return; } const { host } = this.options; const filename = (0, import_node_path3.join)(this.options.outputDir, "sitemap.txt"); const items = vFiles.map((vFile) => vFile.data.meta.href); await write(filename, items.sort().join(import_os2.EOL) + import_os2.EOL); return filename; } async renderSearchIndex(vFiles) { const defaults = { outputDir: "_search", filter: () => true }; const options = this.options.search === true ? defaults : import_lodash2.default.defaults(this.options.search, defaults); const documents = vFiles.filter((vFile) => options.filter(vFile.data.meta.type, vFile)).map((vFile, index) => ({ id: index, title: vFile.data.meta.title, description: vFile.data.meta.description, pathname: vFile.data.meta.pathname, content: vFile.data.markdown, tags: vFile.data.meta.tags })); const miniSearch = new import_minisearch.default({ fields: ["title", "tags", "description", "content"], storeFields: ["title", "description", "pathname"] }); await miniSearch.addAllAsync(documents); const filename = (0, import_node_path3.join)(this.options.outputDir, options.outputDir, "index.json"); await write(filename, JSON.stringify(miniSearch.toJSON())); return filename; } async watch(dirs) { const ignorePatterns = [this.options.outputDir, this.options.ignorePattern].flat(); if (dirs) { const watchDirs = await Promise.all( dirs.map((dir) => watchDir({ dir, cb: (file) => this.handleFile(file), ignorePatterns })) ); return watchDirs.filter(Boolean); } return []; } verbose(text) { if (this.options.verbose) { console.log(text); } } }; // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { MarkdownRambler, addScript, append, findElement, html, insertBefore, insertBeforeStylesheets });