diff --git a/.github/workflows/website_deploy.yml b/.github/workflows/website_deploy.yml index 9cd109b..4fcd435 100644 --- a/.github/workflows/website_deploy.yml +++ b/.github/workflows/website_deploy.yml @@ -32,6 +32,19 @@ jobs: - run: npm ci - run: npm run build + - name: Check docs llms.txt + run: | + file=.vitepress/dist/llms.txt + if [ ! -s "$file" ]; then + echo "::error::$file is missing or empty" + exit 1 + fi + count=$(grep -cE '^- \[[^]]+\]\(https://docs\.swmansion\.com/cairo-debugger/[^)]+\)' "$file" || true) + if [ "$count" -eq 0 ]; then + echo "::error::$file lists no pages" + exit 1 + fi + echo "llms.txt lists $count pages" - name: Upload GitHub Pages artifact uses: actions/upload-pages-artifact@v5 with: diff --git a/website/.vitepress/config.mjs b/website/.vitepress/config.mjs index d7cdd88..a863a17 100644 --- a/website/.vitepress/config.mjs +++ b/website/.vitepress/config.mjs @@ -40,6 +40,8 @@ export default defineConfig({ lang, base, + transformHead: (context) => [swmStructuredData(context)], + buildEnd: writeLlmsTxt, head: [ ["meta", {httpEquiv: "Content-Language", content: lang}], ["link", {rel: "icon", href: `${base}favicon.svg`, type: "image/x-icon"}], diff --git a/website/.vitepress/swm-geo.mjs b/website/.vitepress/swm-geo.mjs new file mode 100644 index 0000000..bc11182 --- /dev/null +++ b/website/.vitepress/swm-geo.mjs @@ -0,0 +1,121 @@ +import fs from "node:fs"; +import path from "node:path"; + +const ORGANIZATION_ID = "https://swmansion.com/#organization"; +const SITE = "https://docs.swmansion.com"; + +const escapeRegExp = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + +const decode = (value) => + value + .replace(/&/g, "&") + .replace(/</g, "<") + .replace(/>/g, ">") + .replace(/"/g, '"') + .replace(/(?:39|x27);/g, "'") + .trim(); + +const projectFromBase = (base) => base.replace(/\//g, ""); + +// Same @id as swmansion.com, so engines read one company across both domains. +export function swmStructuredData({ siteConfig }) { + const { base, description, title } = siteConfig.site; + const project = projectFromBase(base); + + return [ + "script", + { type: "application/ld+json" }, + JSON.stringify({ + "@context": "https://schema.org", + "@graph": [ + { + "@type": "Organization", + "@id": ORGANIZATION_ID, + name: "Software Mansion", + url: "https://swmansion.com", + sameAs: [ + "https://github.com/software-mansion", + "https://www.linkedin.com/company/software-mansion/", + "https://twitter.com/swmansion", + "https://www.youtube.com/c/SoftwareMansion", + ], + }, + { + "@type": "SoftwareSourceCode", + name: title, + ...(description ? { description } : {}), + ...(project + ? { + codeRepository: `https://github.com/software-mansion/${project}`, + } + : {}), + author: { "@id": ORGANIZATION_ID }, + maintainer: { "@id": ORGANIZATION_ID }, + }, + ], + }), + ]; +} + +function collectHtml(dir, root = dir, found = []) { + for (const entry of fs.readdirSync(dir, { withFileTypes: true })) { + const full = path.join(dir, entry.name); + if (entry.isDirectory()) collectHtml(full, root, found); + else if (entry.name.endsWith(".html") && entry.name !== "404.html") + found.push(path.relative(root, full)); + } + return found; +} + +export function buildLlmsTxt({ base, description, files, readFile, title }) { + const entries = []; + + for (const file of files) { + const html = readFile(file); + const raw = /