aboutsummaryrefslogtreecommitdiff
path: root/website
diff options
context:
space:
mode:
Diffstat (limited to 'website')
-rw-r--r--website/.gitignore1
-rw-r--r--website/astro.config.mjs2
-rw-r--r--website/scripts/prepare-docs.mjs80
-rw-r--r--website/src/components/Header.astro149
-rw-r--r--website/src/content.config.ts3
5 files changed, 230 insertions, 5 deletions
diff --git a/website/.gitignore b/website/.gitignore
index 4466e0a1..2d6ddb63 100644
--- a/website/.gitignore
+++ b/website/.gitignore
@@ -2,3 +2,4 @@ node_modules/
public/
dist/
.astro/
+src/content/docs/preview/
diff --git a/website/astro.config.mjs b/website/astro.config.mjs
index badd2873..2da2dd44 100644
--- a/website/astro.config.mjs
+++ b/website/astro.config.mjs
@@ -62,6 +62,7 @@ export default defineConfig({
},
],
components: {
+ Header: './src/components/Header.astro',
SiteTitle: './src/components/SiteTitle.astro',
},
customCss: ['./src/styles/starlight.css'],
@@ -129,6 +130,7 @@ export default defineConfig({
label: 'Updates',
items: [
{ label: 'Releases', link: '/releases/' },
+ { label: 'Preview docs', slug: 'docs/preview' },
{ label: 'Blog', link: '/blog/' },
],
},
diff --git a/website/scripts/prepare-docs.mjs b/website/scripts/prepare-docs.mjs
index c6fefaf7..19201a18 100644
--- a/website/scripts/prepare-docs.mjs
+++ b/website/scripts/prepare-docs.mjs
@@ -1,12 +1,23 @@
-import { cp, mkdir, rm } from 'node:fs/promises';
-import { dirname, resolve } from 'node:path';
+import { cp, mkdir, readdir, readFile, rm, writeFile } from 'node:fs/promises';
+import { dirname, join, relative, resolve } from 'node:path';
import { fileURLToPath } from 'node:url';
+import process from 'node:process';
const websiteDir = dirname(fileURLToPath(import.meta.url));
const repoRoot = resolve(websiteDir, '../..');
const publicDir = resolve(repoRoot, 'website/public');
+const stableDocsDir = resolve(repoRoot, 'website/src/content/docs');
+const previewDocsSourceDir = resolve(repoRoot, 'docs/next/website/src/content/docs');
+const previewDocsDir = resolve(stableDocsDir, 'preview');
-await preparePublicAssets();
+if (process.argv[2] === '--rewrite-preview-doc-fixture') {
+ const chunks = [];
+ for await (const chunk of process.stdin) chunks.push(chunk);
+ process.stdout.write(rewritePreviewDocContent(Buffer.concat(chunks).toString('utf8')));
+} else {
+ await preparePublicAssets();
+ await preparePreviewDocs();
+}
async function preparePublicAssets() {
await rm(publicDir, { recursive: true, force: true });
@@ -15,11 +26,17 @@ async function preparePublicAssets() {
for (const file of [
'install.sh',
'latest.json',
+ 'preview.json',
'robots.txt',
'_headers',
'_redirects',
]) {
- await cp(resolve(repoRoot, 'website', file), resolve(publicDir, file));
+ const source = resolve(repoRoot, 'website', file);
+ try {
+ await cp(source, resolve(publicDir, file));
+ } catch (error) {
+ if (file !== 'preview.json' || error.code !== 'ENOENT') throw error;
+ }
}
for (const directory of ['assets', 'css']) {
@@ -28,3 +45,58 @@ async function preparePublicAssets() {
});
}
}
+
+async function preparePreviewDocs() {
+ await rm(previewDocsDir, { recursive: true, force: true });
+ await copyPreviewDocs(previewDocsSourceDir, previewDocsDir);
+}
+
+async function copyPreviewDocs(sourceDir, destinationDir) {
+ await mkdir(destinationDir, { recursive: true });
+ for (const entry of await readdir(sourceDir, { withFileTypes: true })) {
+ const source = join(sourceDir, entry.name);
+ const destination = join(destinationDir, entry.name);
+ if (entry.isDirectory()) {
+ await copyPreviewDocs(source, destination);
+ continue;
+ }
+ if (!entry.isFile()) continue;
+
+ const content = await readFile(source, 'utf8');
+ const relativePath = relative(previewDocsSourceDir, source);
+ await writeFile(destination, rewritePreviewDocContent(content, relativePath), 'utf8');
+ }
+}
+
+export function rewritePreviewDocContent(content, relativePath = '') {
+ const rewritten = content
+ .replaceAll('/docs/', '/docs/preview/')
+ .replaceAll('../../../public/', '../../../../public/');
+ return insertPreviewNotice(rewritten, relativePath);
+}
+
+function insertPreviewNotice(content, relativePath) {
+ const notice = [
+ '> Preview docs describe unreleased preview builds. Stable docs remain at [/docs/](/docs/).',
+ '',
+ '',
+ ].join('\n');
+ const indexPrefix =
+ relativePath === 'index.mdx'
+ ? content.replace('title: Herdr documentation', 'title: Herdr preview documentation')
+ : content;
+ const frontmatter = indexPrefix.match(/^---\n[\s\S]*?\n---\n/);
+ if (!frontmatter) {
+ return insertNoticeAfterImports(indexPrefix, notice);
+ }
+ const body = indexPrefix.slice(frontmatter[0].length);
+ return `${frontmatter[0]}\n${insertNoticeAfterImports(body, notice)}`;
+}
+
+function insertNoticeAfterImports(content, notice) {
+ const imports = content.match(/^(\s*import .+?;\n)+\s*/);
+ if (!imports) {
+ return `${notice}${content}`;
+ }
+ return `${imports[0]}${notice}${content.slice(imports[0].length)}`;
+}
diff --git a/website/src/components/Header.astro b/website/src/components/Header.astro
new file mode 100644
index 00000000..982efd2e
--- /dev/null
+++ b/website/src/components/Header.astro
@@ -0,0 +1,149 @@
+---
+import config from 'virtual:starlight/user-config';
+
+import LanguageSelect from 'virtual:starlight/components/LanguageSelect';
+import Search from 'virtual:starlight/components/Search';
+import SiteTitle from 'virtual:starlight/components/SiteTitle';
+import SocialIcons from 'virtual:starlight/components/SocialIcons';
+import ThemeSelect from 'virtual:starlight/components/ThemeSelect';
+
+const shouldRenderSearch =
+ config.pagefind || config.components.Search !== '@astrojs/starlight/components/Search.astro';
+
+const pathname = Astro.url.pathname;
+const isDocs = pathname === '/docs/' || pathname.startsWith('/docs/');
+const isPreview = pathname === '/docs/preview/' || pathname.startsWith('/docs/preview/');
+const stableHref = isPreview ? pathname.replace(/^\/docs\/preview\/?/, '/docs/') : pathname;
+const previewHref = isPreview
+ ? pathname
+ : pathname === '/docs/'
+ ? '/docs/preview/'
+ : pathname.replace(/^\/docs\/?/, '/docs/preview/');
+---
+
+<div class="header">
+ <div class="title-wrapper sl-flex">
+ <SiteTitle />
+ </div>
+ <div class="sl-flex print:hidden search-wrapper">
+ {isDocs && (
+ <nav class="docs-channel-switcher" aria-label="Documentation channel">
+ <a href={stableHref} aria-current={!isPreview ? 'page' : undefined}>Stable</a>
+ <a href={previewHref} aria-current={isPreview ? 'page' : undefined}>Preview</a>
+ </nav>
+ )}
+ {shouldRenderSearch && <Search />}
+ </div>
+ <div class="sl-hidden md:sl-flex print:hidden right-group">
+ <div class="sl-flex social-icons">
+ <SocialIcons />
+ </div>
+ <ThemeSelect />
+ <LanguageSelect />
+ </div>
+</div>
+
+<style>
+ @layer starlight.core {
+ .header {
+ display: flex;
+ gap: var(--sl-nav-gap);
+ justify-content: space-between;
+ align-items: center;
+ height: 100%;
+ }
+
+ .title-wrapper {
+ overflow: clip;
+ padding: 0.25rem;
+ margin: -0.25rem;
+ min-width: 0;
+ }
+
+ .search-wrapper {
+ align-items: center;
+ gap: 0.75rem;
+ min-width: 0;
+ }
+
+ .docs-channel-switcher {
+ display: flex;
+ align-items: center;
+ border: 1px solid var(--sl-color-hairline);
+ border-radius: 4px;
+ overflow: hidden;
+ background: var(--sl-color-bg-nav);
+ font-size: var(--sl-text-xs);
+ font-weight: 700;
+ line-height: 1;
+ white-space: nowrap;
+ }
+
+ .docs-channel-switcher a {
+ color: var(--sl-color-gray-2);
+ text-decoration: none;
+ padding: 0.45rem 0.6rem;
+ }
+
+ .docs-channel-switcher a + a {
+ border-inline-start: 1px solid var(--sl-color-hairline);
+ }
+
+ .docs-channel-switcher a[aria-current="page"] {
+ color: var(--sl-color-accent-high);
+ background: color-mix(in srgb, var(--sl-color-accent) 14%, transparent);
+ }
+
+ .docs-channel-switcher a:hover {
+ color: var(--sl-color-accent-high);
+ }
+
+ .right-group,
+ .social-icons {
+ gap: 1rem;
+ align-items: center;
+ }
+
+ .social-icons::after {
+ content: "";
+ height: 2rem;
+ border-inline-end: 1px solid var(--sl-color-gray-5);
+ }
+
+ @media (max-width: 38rem) {
+ .docs-channel-switcher {
+ display: none;
+ }
+ }
+
+ @media (min-width: 50rem) {
+ :global(:root[data-has-sidebar]) {
+ --__sidebar-pad: calc(2 * var(--sl-nav-pad-x));
+ }
+
+ :global(:root:not([data-has-toc])) {
+ --__toc-width: 0rem;
+ }
+
+ .header {
+ --__sidebar-width: max(0rem, var(--sl-content-inline-start, 0rem) - var(--sl-nav-pad-x));
+ --__main-column-fr: calc(
+ (
+ 100% + var(--__sidebar-pad, 0rem) - var(--__toc-width, var(--sl-sidebar-width)) -
+ (2 * var(--__toc-width, var(--sl-nav-pad-x))) - var(--sl-content-inline-start, 0rem) -
+ var(--sl-content-width)
+ ) / 2
+ );
+ display: grid;
+ grid-template-columns:
+ minmax(
+ calc(var(--__sidebar-width) + max(0rem, var(--__main-column-fr) - var(--sl-nav-gap))),
+ auto
+ )
+ 1fr
+ auto;
+ align-content: center;
+ }
+ }
+ }
+</style>
diff --git a/website/src/content.config.ts b/website/src/content.config.ts
index c9a9b8fd..834d37cf 100644
--- a/website/src/content.config.ts
+++ b/website/src/content.config.ts
@@ -5,7 +5,8 @@ import { docsSchema } from '@astrojs/starlight/schema';
function docsPath({ entry }: { entry: string }) {
const slug = entry.replace(/\.(md|mdx|markdown|mdown|mkdn|mkd|mdwn)$/i, '');
- return slug === 'index' ? 'docs' : `docs/${slug}`;
+ const normalized = slug.replace(/\/index$/, '');
+ return normalized === 'index' ? 'docs' : `docs/${normalized}`;
}
export const collections = {