@vyriy/ssg 0.9.1 → 0.9.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/html.js DELETED
@@ -1,228 +0,0 @@
1
- import { escapeHtml, renderMarkdown } from './markdown.js';
2
- import { getAbsoluteUrl } from './sitemap.js';
3
- const escapedStyleCloseTag = ['<', String.fromCodePoint(92), '/style'].join('');
4
- const googleAnalyticsMeasurementIdPattern = /^G-[A-Z0-9]+$/;
5
- export const defaultStylesheet = String.raw `
6
- :root{color-scheme:light;--vyriy-bg:#f8faf7;--vyriy-surface:#ffffff;--vyriy-text:#17211b;--vyriy-muted:#5b675f;--vyriy-line:#dbe3dd;--vyriy-accent:#176f5b;--vyriy-accent-soft:#e2f3ee;--vyriy-code:#10201a}*{box-sizing:border-box}body{margin:0;background:var(--vyriy-bg);color:var(--vyriy-text);font-family:Inter,ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;line-height:1.65}a{color:var(--vyriy-accent);text-decoration-thickness:.08em;text-underline-offset:.18em}img{max-width:100%;height:auto}.vyriy-layout{min-height:100vh;display:flex;flex-direction:column}.vyriy-header{border-bottom:1px solid var(--vyriy-line);background:rgba(248,250,247,.92);backdrop-filter:blur(12px)}.vyriy-header__inner{max-width:1120px;margin:0 auto;padding:1rem 1.5rem;display:flex;align-items:center;justify-content:space-between;gap:1rem}.vyriy-header__brand{display:inline-flex;align-items:center;gap:.75rem;color:var(--vyriy-text);font-weight:800;font-size:1.2rem;text-decoration:none}.vyriy-header__logo{width:44px;height:44px;object-fit:contain}.vyriy-nav{display:flex;gap:1rem;flex-wrap:wrap}.vyriy-nav a{color:var(--vyriy-muted);font-weight:650;text-decoration:none}.vyriy-nav a:hover{color:var(--vyriy-accent)}.vyriy-main{width:100%;max-width:1120px;margin:0 auto;padding:3rem 1.5rem 4rem;flex:1}.vyriy-page{max-width:820px}.vyriy-page__content h1{font-size:clamp(2.2rem,5vw,4.2rem);line-height:1.05;margin:0 0 1.25rem}.vyriy-page__content h2{font-size:1.8rem;line-height:1.2;margin:2.5rem 0 .75rem}.vyriy-page__content h3{font-size:1.25rem;margin:2rem 0 .5rem}.vyriy-page__content p,.vyriy-page__content li{font-size:1.05rem}.vyriy-page__content pre{overflow:auto;padding:1rem;border-radius:8px;background:var(--vyriy-code);color:#f4fff9}.vyriy-page__content code{font-size:.92em}.vyriy-page__content :not(pre)>code{padding:.12rem .35rem;border-radius:5px;background:var(--vyriy-accent-soft);color:var(--vyriy-code)}.vyriy-page__content table{width:100%;border-collapse:collapse;margin:1.5rem 0;display:block;overflow:auto}.vyriy-page__content th,.vyriy-page__content td{border:1px solid var(--vyriy-line);padding:.65rem .75rem;text-align:left}.vyriy-page__content blockquote{margin:1.5rem 0;padding:.25rem 1rem;border-left:4px solid var(--vyriy-accent);color:var(--vyriy-muted);background:var(--vyriy-surface)}.vyriy-card{border:1px solid var(--vyriy-line);border-radius:8px;background:var(--vyriy-surface)}.vyriy-card__link{display:block;height:100%;padding:1rem;color:inherit;text-decoration:none}.vyriy-card__title{font-size:1.1rem;line-height:1.25;margin:0 0 .5rem}.vyriy-card__description{margin:0;color:var(--vyriy-muted)}.vyriy-card__meta{margin-top:.85rem;display:flex;gap:.75rem;align-items:center;flex-wrap:wrap;color:var(--vyriy-muted);font-size:.9rem}.vyriy-card__tags,.vyriy-tags{display:flex;gap:.4rem;flex-wrap:wrap;list-style:none;padding:0;margin:0}.vyriy-card__tag,.vyriy-tag{display:inline-flex;border-radius:999px;background:var(--vyriy-accent-soft);color:var(--vyriy-accent);padding:.15rem .5rem;font-size:.78rem;text-decoration:none}.vyriy-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(240px,1fr));gap:1rem;margin-top:1.5rem}.vyriy-catalog__content{display:grid;grid-template-columns:repeat(auto-fit,minmax(260px,1fr));gap:1rem}.vyriy-pagination{margin-top:2rem;display:flex;gap:.5rem;flex-wrap:wrap}.vyriy-pagination a,.vyriy-pagination span{min-width:2.25rem;padding:.4rem .7rem;border:1px solid var(--vyriy-line);border-radius:6px;text-align:center;text-decoration:none}.vyriy-pagination [aria-current=page]{background:var(--vyriy-accent);color:#fff;border-color:var(--vyriy-accent)}.vyriy-footer{border-top:1px solid var(--vyriy-line);color:var(--vyriy-muted);padding:1.5rem;text-align:center}.vyriy-search{max-width:720px}.vyriy-search__form{display:flex;gap:.5rem;margin:1.5rem 0}.vyriy-search__form input{flex:1;min-width:0;padding:.7rem .85rem;border:1px solid var(--vyriy-line);border-radius:6px}.vyriy-search__form button{padding:.7rem 1rem;border:0;border-radius:6px;background:var(--vyriy-accent);color:#fff;font-weight:700}.vyriy-search__result{padding:1rem 0;border-top:1px solid var(--vyriy-line)}@media (max-width:720px){.vyriy-header__inner{align-items:flex-start;flex-direction:column}.vyriy-main{padding-top:2rem}.vyriy-search__form{flex-direction:column}}
7
- `;
8
- const renderStylesheet = (stylesheetHref, stylesheetContent) => {
9
- if (stylesheetContent) {
10
- return `<style>${stylesheetContent.trim().replaceAll('</style', escapedStyleCloseTag)}</style>`;
11
- }
12
- return `<link href="${escapeHtml(stylesheetHref ?? '/styles.css')}" rel="stylesheet" />`;
13
- };
14
- const renderGoogleAnalyticsTag = (measurementId) => {
15
- if (!measurementId) {
16
- return '';
17
- }
18
- if (!googleAnalyticsMeasurementIdPattern.test(measurementId)) {
19
- throw new Error(`Invalid Google Analytics measurement ID: ${measurementId}`);
20
- }
21
- return [
22
- `<script async src="https://www.googletagmanager.com/gtag/js?id=${escapeHtml(measurementId)}"></script>`,
23
- '<script>',
24
- 'window.dataLayer = window.dataLayer || [];',
25
- 'function gtag(){dataLayer.push(arguments);}',
26
- "gtag('js', new Date());",
27
- `gtag('config', '${escapeHtml(measurementId)}');`,
28
- '</script>',
29
- ].join('');
30
- };
31
- const minifyHtml = (document) => document.trim().replaceAll(/>\s+</gu, '><');
32
- const renderHeader = (siteName) => {
33
- return [
34
- '<header class="vyriy-header"><div class="vyriy-header__inner">',
35
- `<a class="vyriy-header__brand" href="/"><img class="vyriy-header__logo" src="/assets/vyriy-v-wings.png" alt="" width="72" height="46" />${escapeHtml(siteName)}</a>`,
36
- '<nav class="vyriy-nav" aria-label="Primary"><a href="/blog/">Blog</a><a href="/docs/">Docs</a><a href="/examples/">Examples</a><a href="/search/">Search</a></nav>',
37
- '</div></header>',
38
- ].join('');
39
- };
40
- const renderLayout = (body, siteName, footerText) => {
41
- return `<div class="vyriy-layout">${renderHeader(siteName)}<main class="vyriy-main">${body}</main><footer class="vyriy-footer">${escapeHtml(footerText)}</footer></div>`;
42
- };
43
- const renderDocument = (title, description, body, { canonicalPath, footerText, googleAnalyticsMeasurementId, robotsDirective, siteName, siteUrl, stylesheetContent, stylesheetHref, }) => {
44
- return minifyHtml([
45
- '<!DOCTYPE html><html lang="en"><head>',
46
- '<meta charset="utf-8" />',
47
- '<meta name="viewport" content="width=device-width, initial-scale=1" />',
48
- `<title>${escapeHtml(title)}</title>`,
49
- `<meta name="description" content="${escapeHtml(description)}" />`,
50
- robotsDirective ? `<meta name="robots" content="${escapeHtml(robotsDirective)}" />` : '',
51
- renderStylesheet(stylesheetHref, stylesheetContent),
52
- canonicalPath ? `<link href="${escapeHtml(getAbsoluteUrl(canonicalPath, siteUrl))}" rel="canonical" />` : '',
53
- '<link href="/favicon.ico" rel="icon" sizes="any" />',
54
- '<link href="/favicon-16x16.png" rel="icon" sizes="16x16" />',
55
- '<link href="/favicon-32x32.png" rel="icon" sizes="32x32" />',
56
- '<link href="/apple-touch-icon.png" rel="apple-touch-icon" />',
57
- renderGoogleAnalyticsTag(googleAnalyticsMeasurementId),
58
- '</head><body>',
59
- renderLayout(body, siteName, footerText),
60
- '</body></html>',
61
- ].join(''));
62
- };
63
- const renderCard = (entry) => {
64
- return [
65
- '<article class="vyriy-card">',
66
- `<a aria-label="${escapeHtml(entry.title)}" class="vyriy-card__link" href="${escapeHtml(entry.href)}">`,
67
- `<h2 class="vyriy-card__title">${escapeHtml(entry.title)}</h2>`,
68
- `<p class="vyriy-card__description">${escapeHtml(entry.description)}</p>`,
69
- entry.date || entry.tags.length > 0
70
- ? [
71
- '<div class="vyriy-card__meta">',
72
- entry.date
73
- ? `<time class="vyriy-card__date" datetime="${escapeHtml(entry.date)}">${escapeHtml(entry.date)}</time>`
74
- : '',
75
- entry.tags.length > 0
76
- ? `<ul class="vyriy-card__tags" aria-label="Tags">${entry.tags
77
- .map((tag) => `<li class="vyriy-card__tag">${escapeHtml(tag)}</li>`)
78
- .join('')}</ul>`
79
- : '',
80
- '</div>',
81
- ].join('')
82
- : '',
83
- '</a></article>',
84
- ].join('');
85
- };
86
- export const renderPage = (page, options) => {
87
- const featured = options.featured ?? [];
88
- const related = options.related ?? [];
89
- const tags = options.showTags ? page.tags : [];
90
- const body = [
91
- '<section class="vyriy-page"><div class="vyriy-page__content">',
92
- renderMarkdown(page.content),
93
- '</div>',
94
- featured.length > 0
95
- ? `<section aria-label="Featured posts" class="vyriy-grid">${featured
96
- .map((item) => renderCard({
97
- date: item.date ?? '',
98
- description: item.description,
99
- href: item.url,
100
- tags: item.tags,
101
- title: item.title,
102
- }))
103
- .join('')}</section>`
104
- : '',
105
- tags.length > 0
106
- ? `<nav aria-label="Post tags" class="vyriy-tags">${tags
107
- .map((tag) => `<a class="vyriy-tag" href="/search/?tag=${encodeURIComponent(tag)}">${escapeHtml(tag)}</a>`)
108
- .join('')}</nav>`
109
- : '',
110
- related.length > 0
111
- ? `<aside class="vyriy-page__related"><h2>Related posts</h2><div class="vyriy-grid">${related
112
- .map((item) => renderCard({
113
- date: '',
114
- description: item.description,
115
- href: item.url,
116
- tags: item.tags,
117
- title: item.title,
118
- }))
119
- .join('')}</div></aside>`
120
- : '',
121
- '</section>',
122
- ].join('');
123
- return renderDocument(page.title, page.description, body, options);
124
- };
125
- export const renderContentIndex = (entries, { page = 1, pages = 1, sectionPath, sectionTitle, ...documentOptions }) => {
126
- const pageTitle = pages > 1 && page > 1 ? `${sectionTitle} ${page}` : sectionTitle;
127
- const cards = entries.map(renderCard).join('');
128
- const pagination = pages > 1
129
- ? `<nav class="vyriy-pagination" aria-label="${escapeHtml(sectionTitle)} pagination">${Array.from({ length: pages }, (_value, index) => {
130
- const targetPage = index + 1;
131
- const href = targetPage <= 1 ? `/${sectionPath}/` : `/${sectionPath}/${targetPage}/`;
132
- return targetPage === page
133
- ? `<span aria-current="page">${targetPage}</span>`
134
- : `<a href="${escapeHtml(href)}">${targetPage}</a>`;
135
- }).join('')}</nav>`
136
- : '';
137
- return renderDocument(pageTitle, documentOptions.siteName === sectionTitle ? sectionTitle : `${sectionTitle} from ${documentOptions.siteName}.`, `<section class="vyriy-catalog"><h1>${escapeHtml(sectionTitle)}</h1><div class="vyriy-catalog__content">${cards}</div>${pagination}</section>`, {
138
- ...documentOptions,
139
- canonicalPath: page <= 1 ? `/${sectionPath}/` : `/${sectionPath}/${page}/`,
140
- });
141
- };
142
- export const renderNotFoundPage = (options) => {
143
- return renderDocument('Page not found', 'The requested page could not be found.', '<section class="vyriy-page"><div class="vyriy-page__content"><h1>Page not found</h1><p>The requested page could not be found.</p></div></section>', {
144
- ...options,
145
- canonicalPath: '',
146
- });
147
- };
148
- export const searchScript = String.raw `
149
- (() => {
150
- const root = document.getElementById('search-root');
151
- if (!root) return;
152
- const documentsUrl = root.dataset.documentsUrl || '/search/documents.json';
153
- const indexUrl = root.dataset.indexUrl || '/search/minisearch-index.json';
154
- const parameters = new URLSearchParams(window.location.search);
155
- const tag = parameters.get('tag');
156
- const query = (parameters.get('q') || '').trim();
157
- const input = document.querySelector('.vyriy-search__form input[name="q"]');
158
- if (input) input.value = parameters.get('q') || '';
159
- const searchOptions = {
160
- boost: {
161
- content: 1,
162
- description: 2,
163
- tags: 3,
164
- title: 4
165
- },
166
- fuzzy: 0.2,
167
- prefix: true
168
- };
169
- const miniSearchOptions = {
170
- fields: ['title', 'description', 'tags', 'content'],
171
- storeFields: ['title', 'description', 'section', 'slug', 'url', 'tags', 'date'],
172
- searchOptions
173
- };
174
- const escapeHtml = (value) => String(value).replaceAll('&', '&amp;').replaceAll('<', '&lt;').replaceAll('>', '&gt;').replaceAll('"', '&quot;').replaceAll("'", '&#39;');
175
- const render = (documents) => {
176
- if (!documents.length) {
177
- root.innerHTML = '<p>No matching pages yet.</p>';
178
- return;
179
- }
180
- root.innerHTML = documents.map((document) => '<article class="vyriy-search__result"><a href="' + escapeHtml(document.url) + '"><h2>' + escapeHtml(document.title) + '</h2><p>' + escapeHtml(document.description || '') + '</p></a></article>').join('');
181
- };
182
- const getDocumentMap = (documents) => new Map(documents.map((document) => [document.id, document]));
183
- const getTagMatches = (documents, value) => {
184
- const normalizedTag = value.trim().toLowerCase();
185
- return documents.filter((document) => Array.isArray(document.tags) && document.tags.some((candidate) => candidate.trim().toLowerCase() === normalizedTag));
186
- };
187
- const getQueryMatches = (documents, indexJson, value) => {
188
- const MiniSearch = window.MiniSearch;
189
- if (!MiniSearch) throw new Error('MiniSearch is unavailable.');
190
- const documentsById = getDocumentMap(documents);
191
- const search = MiniSearch.loadJSON(JSON.stringify(indexJson), miniSearchOptions);
192
- return search.search(value).map((result) => documentsById.get(result.id) || result).filter(Boolean);
193
- };
194
- if (!tag && !query) {
195
- root.innerHTML = '<p>Choose a post tag or enter a search query to see matching pages.</p>';
196
- return;
197
- }
198
- const dataRequests = [fetch(documentsUrl)];
199
- if (query) dataRequests.push(fetch(indexUrl));
200
- Promise.all(dataRequests)
201
- .then((responses) => {
202
- for (const response of responses) {
203
- if (!response.ok) throw new Error('Search data unavailable.');
204
- }
205
- return Promise.all(responses.map((response, index) => index === 1 ? response.text() : response.json()));
206
- })
207
- .then(([documents, indexText]) => {
208
- const indexJson = indexText ? JSON.parse(indexText) : undefined;
209
- const queryMatches = query ? getQueryMatches(documents, indexJson, query) : documents;
210
- render(tag ? getTagMatches(queryMatches, tag) : queryMatches);
211
- })
212
- .catch(() => { root.innerHTML = '<p>Search data could not be loaded.</p>'; });
213
- })();
214
- `;
215
- export const renderSearchPage = (options) => {
216
- return renderDocument(`${options.siteName} Search`, `Search pages from ${options.siteName}.`, [
217
- '<section class="vyriy-search"><h1>Search</h1><p>Search by text query or tag.</p>',
218
- '<form class="vyriy-search__form" action="/search/"><input name="q" placeholder="Search" type="search" /><button type="submit">Search</button></form>',
219
- '<div data-documents-url="/search/documents.json" data-index-url="/search/minisearch-index.json" id="search-root"></div>',
220
- '<script src="/assets/minisearch.js"></script>',
221
- '<script src="/assets/search.js"></script>',
222
- '</section>',
223
- ].join(''), {
224
- ...options,
225
- canonicalPath: '',
226
- robotsDirective: 'noindex, follow',
227
- });
228
- };
package/markdown.d.ts DELETED
@@ -1,3 +0,0 @@
1
- export declare const escapeHtml: (value: string) => string;
2
- export declare const renderMarkdown: (markdown: string) => string;
3
- export declare const getPlainTextFromMarkdown: (markdown: string) => string;
package/markdown.js DELETED
@@ -1,31 +0,0 @@
1
- import { createElement } from 'react';
2
- import { html as renderReactHtml } from '@vyriy/render/html';
3
- import ReactMarkdown from 'react-markdown';
4
- import rehypeHighlight from 'rehype-highlight';
5
- import remarkGfm from 'remark-gfm';
6
- import { replaceInlineCode, replaceMarkdownLinks, stripFencedCode, stripHtmlTags } from './plain.js';
7
- const markdownSyntaxPattern = /[#>*_~|[\](){}\\-]+/gu;
8
- const whitespacePattern = /\s+/gu;
9
- const htmlCharacterPattern = /[&<>"']/gu;
10
- const htmlCharacterEntities = {
11
- '&': '&amp;',
12
- '"': '&quot;',
13
- "'": '&#39;',
14
- '<': '&lt;',
15
- '>': '&gt;',
16
- };
17
- export const escapeHtml = (value) => {
18
- return value.replaceAll(htmlCharacterPattern, (character) => htmlCharacterEntities[character]);
19
- };
20
- export const renderMarkdown = (markdown) => {
21
- return renderReactHtml(createElement(ReactMarkdown, {
22
- rehypePlugins: [rehypeHighlight],
23
- remarkPlugins: [remarkGfm],
24
- }, markdown));
25
- };
26
- export const getPlainTextFromMarkdown = (markdown) => {
27
- return stripHtmlTags(replaceInlineCode(replaceMarkdownLinks(stripFencedCode(markdown))))
28
- .replaceAll(markdownSyntaxPattern, ' ')
29
- .replaceAll(whitespacePattern, ' ')
30
- .trim();
31
- };
package/ssg.js DELETED
@@ -1,199 +0,0 @@
1
- import { copyFile, mkdir, readdir, readFile, writeFile } from 'node:fs/promises';
2
- import { createRequire } from 'node:module';
3
- import { dirname, join, resolve } from 'node:path';
4
- import { buildContentEntries, buildContentSection, getHomePageFeaturedContent, getRelatedDocumentsMap, getSiteSearchDocuments, writeContentData, writeContentEntryDocuments, } from './content.js';
5
- import { defaultStylesheet, renderNotFoundPage, renderPage, renderSearchPage, searchScript } from './html.js';
6
- import { parsePage } from './parse-page.js';
7
- import { writeRobotsTxt } from './robots.js';
8
- import { writeSitemap } from './sitemap.js';
9
- const defaultSiteName = 'Vyriy';
10
- const defaultContentPath = 'site';
11
- const defaultOutputPath = 'dist';
12
- const require = createRequire(import.meta.url);
13
- const miniSearchBrowserPath = join(dirname(dirname(require.resolve('minisearch'))), 'umd/index.js');
14
- const defaultSections = [
15
- {
16
- path: 'blog',
17
- title: 'Blog',
18
- },
19
- {
20
- index: false,
21
- path: 'docs',
22
- title: 'Documentation',
23
- },
24
- {
25
- path: 'examples',
26
- title: 'Examples',
27
- },
28
- ];
29
- const isNodeError = (error) => {
30
- return typeof error === 'object' && error !== null && 'code' in error;
31
- };
32
- const pathExists = async (path) => {
33
- try {
34
- await readdir(path);
35
- return true;
36
- }
37
- catch (error) {
38
- if (isNodeError(error) && error.code === 'ENOENT') {
39
- return false;
40
- }
41
- throw error;
42
- }
43
- };
44
- const copyDirectory = async (sourcePath, outputPath) => {
45
- let entries;
46
- try {
47
- entries = await readdir(sourcePath, {
48
- withFileTypes: true,
49
- });
50
- }
51
- catch (error) {
52
- if (isNodeError(error) && error.code === 'ENOENT') {
53
- return;
54
- }
55
- throw error;
56
- }
57
- await mkdir(outputPath, {
58
- recursive: true,
59
- });
60
- await Promise.all(entries.map(async (entry) => {
61
- const sourceEntryPath = join(sourcePath, entry.name);
62
- const outputEntryPath = join(outputPath, entry.name);
63
- if (entry.isDirectory()) {
64
- await copyDirectory(sourceEntryPath, outputEntryPath);
65
- return;
66
- }
67
- if (entry.isFile()) {
68
- await mkdir(dirname(outputEntryPath), {
69
- recursive: true,
70
- });
71
- await copyFile(sourceEntryPath, outputEntryPath);
72
- }
73
- }));
74
- };
75
- const getDefaultPages = (contentPath) => [
76
- {
77
- canonicalPath: '/consulting/',
78
- inputPath: join(contentPath, 'consulting/README.md'),
79
- outputPath: 'consulting/index.html',
80
- },
81
- {
82
- canonicalPath: '/docs/',
83
- inputPath: join(contentPath, 'docs/README.md'),
84
- outputPath: 'docs/index.html',
85
- },
86
- ];
87
- const renderOptionsFrom = (options) => {
88
- const siteName = options.siteName ?? defaultSiteName;
89
- return {
90
- defaultTitle: options.defaultTitle ?? siteName,
91
- footerText: options.footerText ?? `Copyright © 2026 ${siteName}`,
92
- googleAnalyticsMeasurementId: options.googleAnalyticsMeasurementId,
93
- siteName,
94
- siteUrl: options.siteUrl,
95
- stylesheetContent: options.stylesheetContent,
96
- stylesheetHref: options.stylesheetHref ?? (options.stylesheetContent ? undefined : '/styles.css'),
97
- };
98
- };
99
- const writeStandalonePage = async (page, outputDirectory, renderOptions) => {
100
- try {
101
- const pageData = parsePage(await readFile(page.inputPath, 'utf8'), renderOptions.defaultTitle);
102
- if (!pageData.published) {
103
- return undefined;
104
- }
105
- await mkdir(dirname(join(outputDirectory, page.outputPath)), {
106
- recursive: true,
107
- });
108
- await writeFile(join(outputDirectory, page.outputPath), renderPage(pageData, {
109
- ...renderOptions,
110
- canonicalPath: page.canonicalPath,
111
- }));
112
- return page.canonicalPath;
113
- }
114
- catch (error) {
115
- if (isNodeError(error) && error.code === 'ENOENT') {
116
- return undefined;
117
- }
118
- throw error;
119
- }
120
- };
121
- export const buildStaticSite = async (options = {}) => {
122
- const cwd = options.cwd ?? process.cwd();
123
- const contentPath = resolve(cwd, options.contentPath ?? defaultContentPath);
124
- const outputDirectory = resolve(cwd, options.outputPath ?? defaultOutputPath);
125
- const publicPath = resolve(cwd, options.publicPath ?? join(options.contentPath ?? defaultContentPath, 'public'));
126
- const homePage = options.homePage ?? {
127
- canonicalPath: '/',
128
- inputPath: join(contentPath, 'home/README.md'),
129
- outputPath: 'index.html',
130
- };
131
- const pages = options.pages ?? getDefaultPages(contentPath);
132
- const sections = options.sections ?? defaultSections;
133
- const renderOptions = renderOptionsFrom(options);
134
- await mkdir(outputDirectory, {
135
- recursive: true,
136
- });
137
- if (!options.stylesheetContent && !options.stylesheetHref) {
138
- await writeFile(join(outputDirectory, 'styles.css'), `${defaultStylesheet.trim()}\n`);
139
- }
140
- await mkdir(join(outputDirectory, 'assets'), {
141
- recursive: true,
142
- });
143
- await Promise.all([
144
- copyFile(miniSearchBrowserPath, join(outputDirectory, 'assets/minisearch.js')),
145
- writeFile(join(outputDirectory, 'assets/search.js'), `${searchScript.trim()}\n`),
146
- ]);
147
- const sectionResults = await Promise.all(sections.map(async (section) => {
148
- if (section.index === false) {
149
- return {
150
- result: {
151
- entries: await buildContentEntries(section, contentPath, renderOptions.defaultTitle),
152
- indexPaths: [],
153
- },
154
- section,
155
- };
156
- }
157
- return {
158
- result: await buildContentSection(section, contentPath, outputDirectory, renderOptions),
159
- section,
160
- };
161
- }));
162
- const contentSections = sectionResults.map(({ result, section }) => ({
163
- entries: result.entries,
164
- section: section.path,
165
- }));
166
- const relatedDocuments = getRelatedDocumentsMap(getSiteSearchDocuments(contentSections));
167
- const featured = getHomePageFeaturedContent(contentSections);
168
- const standalonePaths = (await Promise.all(pages.map((page) => writeStandalonePage(page, outputDirectory, renderOptions)))).filter((path) => Boolean(path));
169
- const homePageData = parsePage(await readFile(homePage.inputPath, 'utf8'), renderOptions.defaultTitle);
170
- await writeFile(join(outputDirectory, homePage.outputPath), renderPage(homePageData, {
171
- ...renderOptions,
172
- canonicalPath: homePage.canonicalPath,
173
- featured,
174
- }));
175
- await Promise.all(sectionResults.map(({ result, section }) => writeContentEntryDocuments(section, result.entries, outputDirectory, {
176
- ...renderOptions,
177
- relatedDocuments,
178
- })));
179
- await writeFile(join(outputDirectory, '404.html'), renderNotFoundPage(renderOptions));
180
- await mkdir(join(outputDirectory, 'search'), {
181
- recursive: true,
182
- });
183
- await writeFile(join(outputDirectory, 'search/index.html'), renderSearchPage(renderOptions));
184
- await writeContentData(contentSections, outputDirectory);
185
- if (options.copyPublic ?? (await pathExists(publicPath))) {
186
- await copyDirectory(publicPath, outputDirectory);
187
- }
188
- await writeSitemap(outputDirectory, [
189
- {
190
- path: homePage.canonicalPath ?? '/',
191
- },
192
- ...standalonePaths.map((path) => ({
193
- path,
194
- })),
195
- ...sectionResults.flatMap(({ result }) => result.indexPaths.map((path) => ({ path }))),
196
- ...sectionResults.flatMap(({ result }) => result.entries.map((entry) => ({ path: entry.href }))),
197
- ], options.siteUrl);
198
- await writeRobotsTxt(outputDirectory, options.siteUrl);
199
- };
File without changes
File without changes
File without changes