Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions docs/.vitepress/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,17 @@ export default defineConfig({
sitemap: {
hostname: SITE_URL,
},
// llms.txt v2 link relations: each page declares its Markdown mirror via
// rel="alternate" type="text/markdown", and the covering llms.txt via
// rel="describedby". Mirrors are copied into dist by scripts/copy-md-mirrors.mjs.
transformHead: ({ pageData }) => {
const rel = pageData.relativePath || '';
const mirror = rel ? `${SITE_BASE}${rel.replace(/\.md$/, '')}.md` : `${SITE_BASE}index.md`;
return [
['link', { rel: 'describedby', type: 'text/plain', href: `${SITE_BASE}llms.txt` }],
['link', { rel: 'alternate', type: 'text/markdown', href: mirror }],
];
},
head: [
['meta', { name: 'google-site-verification', content: '0H_0qOZVNDMTnOAHH8oitfbbeDyUGgkzzI2rLOM1YHM' }],
['meta', { property: 'og:type', content: 'website' }],
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
"prepare": "yarn build",
"prepublishOnly": "yarn build",
"docs:dev": "vitepress dev docs",
"docs:build": "typedoc && vitepress build docs",
"docs:build": "typedoc && vitepress build docs && node scripts/copy-md-mirrors.mjs",
"docs:preview": "vitepress preview docs"
},
"devDependencies": {
Expand Down
36 changes: 36 additions & 0 deletions scripts/copy-md-mirrors.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// Copies VitePress markdown sources into dist as .md mirrors, so every page
// has a clean Markdown twin served at the same URL path (llms.txt v2 pattern).
// Mirrors are excluded from sitemap.xml by construction: the sitemap is
// generated during `vitepress build`, which runs before this script.
//
// Excluded from mirroring: .vitepress config, public/ assets (already copied
// verbatim by VitePress itself), node_modules.
import { copyFileSync, mkdirSync, readdirSync, statSync } from "node:fs";
import { dirname, join } from "node:path";

const DOCS_DIR = new URL("../docs", import.meta.url).pathname;
const DIST_DIR = join(DOCS_DIR, ".vitepress", "dist");
const SKIP_DIRS = new Set([".vitepress", "public", "node_modules", "dist"]);

const walk = (dir, base = "") => {
const out = [];
for (const entry of readdirSync(dir)) {
const full = join(dir, entry);
const rel = base ? `${base}/${entry}` : entry;
if (statSync(full).isDirectory()) {
if (SKIP_DIRS.has(entry)) continue;
out.push(...walk(full, rel));
} else if (entry.endsWith(".md")) {
out.push(rel);
}
}
return out;
};

const mirrors = walk(DOCS_DIR);
for (const rel of mirrors) {
const target = join(DIST_DIR, rel);
mkdirSync(dirname(target), { recursive: true });
copyFileSync(join(DOCS_DIR, rel), target);
}
console.log(`md mirrors: copied ${mirrors.length} pages into dist`);
30 changes: 30 additions & 0 deletions tests/contracts/md-mirrors-v2.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// NJS-SEO-6 follow-up: contract test for llms.txt v2 markdown mirrors and
// link relations (rel="alternate" type="text/markdown" + rel="describedby").
import { readFileSync } from "node:fs";
import { describe, expect, it } from "vitest";

const read = (p: string) => readFileSync(p, "utf8");

describe("llms.txt v2: markdown mirrors and link relations", () => {
it("build pipeline copies markdown mirrors into dist", () => {
const pkg = JSON.parse(read("package.json"));
expect(pkg.scripts["docs:build"]).toContain("copy-md-mirrors.mjs");
});

it("transformHead injects per-page alternate and global describedby links", () => {
const config = read("docs/.vitepress/config.ts");
expect(config).toContain('rel: \'describedby\'');
expect(config).toContain('rel: \'alternate\'');
expect(config).toContain("type: 'text/markdown'");
// mirror path derives from pageData.relativePath so each page points at its own .md twin
expect(config).toContain("pageData.relativePath");
});

it("mirror script skips sitemap, vitepress internals and public assets", () => {
const script = read("scripts/copy-md-mirrors.mjs");
expect(script).toContain("SKIP_DIRS");
for (const skipped of [".vitepress", "public", "node_modules", "dist"]) {
expect(script).toContain(skipped);
}
});
});
Loading