From be963864d0e55028d30991ac677409f63a35426d Mon Sep 17 00:00:00 2001 From: Jason Kummerl Date: Tue, 2 Jun 2026 17:28:37 -0400 Subject: [PATCH 01/22] docs: generate sitemap manifest in Vite --- .github/workflows/cloudflare-deploy.yml | 6 +- docs/package.json | 6 +- docs/scripts/generate-sitemap-manifest.mjs | 153 --------------------- docs/vite.config.ts | 8 +- pnpm-lock.yaml | 10 +- 5 files changed, 17 insertions(+), 166 deletions(-) delete mode 100644 docs/scripts/generate-sitemap-manifest.mjs diff --git a/.github/workflows/cloudflare-deploy.yml b/.github/workflows/cloudflare-deploy.yml index 21a5360..cd2a327 100644 --- a/.github/workflows/cloudflare-deploy.yml +++ b/.github/workflows/cloudflare-deploy.yml @@ -47,9 +47,9 @@ jobs: - name: Build Package run: pnpm run build - - name: Generate sitemap manifest - working-directory: docs - run: pnpm run sitemap:manifest + # The sitemap manifest now emits from docs-kit's + # `sitemapManifestPlugin` in `docs/vite.config.ts`, which runs + # automatically during the Vite build inside `pnpm run deploy`. - name: Deploy Docs working-directory: docs diff --git a/docs/package.json b/docs/package.json index c23103d..c87c4e7 100644 --- a/docs/package.json +++ b/docs/package.json @@ -4,15 +4,13 @@ "private": true, "type": "module", "scripts": { - "build": "tsx ./scripts/fetch-github-stats.ts && node ./scripts/generate-sitemap-manifest.mjs && tsx ./scripts/generate-social-cards.ts && vite build", + "build": "tsx ./scripts/fetch-github-stats.ts && tsx ./scripts/generate-social-cards.ts && vite build", "cf-typegen": "wrangler types && mv worker-configuration.d.ts src/", "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", "deploy": "npm run build && wrangler deploy", "dev": "vite dev", "generate-social": "tsx ./scripts/generate-social-cards.ts", - "sitemap:manifest": "node ./scripts/generate-sitemap-manifest.mjs", - "sitemap:watch": "chokidar 'src/routes/**/*.svelte' 'src/routes/**/*.svx' 'src/routes/**/*.md' --ignore 'src/routes/examples/+page.ts' -c 'node ./scripts/generate-sitemap-manifest.mjs' --initial --silent", "social:watch": "chokidar 'src/lib/components/OG.svelte' 'scripts/generate-social-cards.ts' -c 'tsx ./scripts/generate-social-cards.ts' --initial --silent", "format": "prettier --write .", "lint": "prettier --check . && eslint .", @@ -20,7 +18,7 @@ "preview": "vite preview" }, "dependencies": { - "@humanspeak/docs-kit": "github:humanspeak/docs-kit#2026.6.4", + "@humanspeak/docs-kit": "github:humanspeak/docs-kit#2026.6.5", "@humanspeak/memory-cache": "workspace:*", "@humanspeak/svelte-markdown": "^1.5.4", "github-slugger": "^2.0.0", diff --git a/docs/scripts/generate-sitemap-manifest.mjs b/docs/scripts/generate-sitemap-manifest.mjs deleted file mode 100644 index d9823a0..0000000 --- a/docs/scripts/generate-sitemap-manifest.mjs +++ /dev/null @@ -1,153 +0,0 @@ -import { readdir, readFile, stat, writeFile } from 'node:fs/promises' -import { join, resolve as resolvePath } from 'node:path' - -const ROOT = resolvePath(process.cwd(), 'src', 'routes') - -/** Convert a +page file path to a route path */ -function toRoutePath(file) { - let p = file.replace(ROOT, '') - p = p.replace(/\/\+page\.(svelte|svx|md)$/i, '') - return p === '' ? '/' : p -} - -/** Recursively find +page files */ -async function findPageFiles(dir, out = []) { - const entries = await readdir(dir, { withFileTypes: true }) - for (const e of entries) { - const full = join(dir, e.name) - if (e.isDirectory()) { - await findPageFiles(full, out) - } else if (/\+page\.(svelte|svx|md)$/i.test(e.name)) { - out.push(full) - } - } - return out -} - -/** - * Load example metadata from individual +page.ts files - * @param {string} examplePath - Path to the example directory - * @returns {Promise} Example metadata or null if not found - */ -async function loadExampleMetadata(examplePath) { - try { - const pageTs = join(examplePath, '+page.ts') - const content = await readFile(pageTs, 'utf8') - - // Extract title from the load function return - const titleMatch = content.match(/title:\s*['"`]([^'"`]+)['"`]/) - const sourceUrlMatch = content.match(/sourceUrl:\s*['"`]([^'"`]+)['"`]/) - - if (titleMatch) { - return { - title: titleMatch[1], - sourceUrl: sourceUrlMatch?.[1] || null - } - } - } catch { - // File doesn't exist or can't be read, that's okay - } - return null -} - -/** - * Generate example metadata for the examples landing page - * @param {Object} manifest - The sitemap manifest - * @returns {Promise} Examples metadata object - */ -async function generateExamplesMetadata(manifest) { - const exampleRoutes = Object.keys(manifest) - .filter((route) => route.startsWith('/examples/') && route !== '/examples') - .sort() - - const examples = {} - - for (const route of exampleRoutes) { - const slug = route.replace('/examples/', '') - const examplePath = join(ROOT, 'examples', slug) - - // Try to load metadata from the example's +page.ts file - const metadata = await loadExampleMetadata(examplePath) - - if (metadata) { - const title = metadata.title - examples[slug] = { - title, - description: `Interactive ${title.toLowerCase()} animation example using Svelte Motion.`, - sourceUrl: metadata.sourceUrl - } - } else { - // Fallback: generate from slug - const title = slug - .split('-') - .map((word) => word.charAt(0).toUpperCase() + word.slice(1)) - .join(' ') - - examples[slug] = { - title, - description: `Interactive ${title.toLowerCase()} animation example using Svelte Motion.`, - sourceUrl: null - } - } - } - - return examples -} - -/** - * Update the examples +page.ts file with the latest example metadata - * @param {Object} examples - Examples metadata object - */ -async function updateExamplesPageTs(examples) { - const examplesPageTs = resolvePath(process.cwd(), 'src', 'routes', 'examples', '+page.ts') - - try { - let content = await readFile(examplesPageTs, 'utf8') - - // Find the examples object in the file and replace it - // More robust regex that matches the entire examples object declaration - const examplesObjectRegex = /const examples\s*=\s*\{[\s\S]*?\n\s*\}(?=\s*\n\s*return)/ - // Format as JS object literal with single quotes and unquoted keys (matching prettier config) - const jsObject = JSON.stringify(examples, null, 4) - .replace(/"([^"]+)":/g, (_, key) => - /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(key) ? `${key}:` : `'${key}':` - ) - .replace(/: "([^"]*)"/g, (_, value) => `: '${value}'`) - .replace(/: null/g, ': null') - const newExamplesObject = `const examples = ${jsObject.replace(/^/gm, ' ').trim()}` - - if (examplesObjectRegex.test(content)) { - content = content.replace(examplesObjectRegex, newExamplesObject) - await writeFile(examplesPageTs, content, 'utf8') - console.log(`Updated examples metadata in ${examplesPageTs}`) - } else { - console.warn(`Could not find examples object pattern in ${examplesPageTs}`) - } - } catch (error) { - console.warn(`Could not update examples page: ${error.message}`) - } -} - -async function main() { - const files = await findPageFiles(ROOT) - const manifest = {} - for (const file of files) { - const s = await stat(file) - const route = toRoutePath(file) - // Non-recursive lastmod: use the +page file's mtime only - manifest[route] = new Date(s.mtimeMs).toISOString().slice(0, 10) - } - - const dest = resolvePath(process.cwd(), 'src', 'lib', 'sitemap-manifest.json') - await writeFile(dest, JSON.stringify(manifest, null, 2) + '\n', 'utf8') - console.log(`Sitemap manifest written to ${dest}`) - - // Generate and update examples metadata - const examples = await generateExamplesMetadata(manifest) - await updateExamplesPageTs(examples) -} - -main().catch((err) => { - console.error(err) - process.exit(1) -}) diff --git a/docs/vite.config.ts b/docs/vite.config.ts index 1600bba..da613f0 100644 --- a/docs/vite.config.ts +++ b/docs/vite.config.ts @@ -1,4 +1,9 @@ -import { docMirrorsPlugin, llmsFullPlugin, llmsPlugin } from '@humanspeak/docs-kit/vite' +import { + docMirrorsPlugin, + llmsFullPlugin, + llmsPlugin, + sitemapManifestPlugin +} from '@humanspeak/docs-kit/vite' import { sveltekit } from '@sveltejs/kit/vite' import tailwindcss from '@tailwindcss/vite' import { defineConfig } from 'vite' @@ -7,6 +12,7 @@ import { docsConfig } from './src/lib/docs-config' export default defineConfig({ plugins: [ + sitemapManifestPlugin({ blogDir: false }), docMirrorsPlugin({ siteUrl: docsConfig.url }), llmsFullPlugin({ siteUrl: docsConfig.url, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c3706d5..d9e2089 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -81,8 +81,8 @@ importers: docs: dependencies: '@humanspeak/docs-kit': - specifier: github:humanspeak/docs-kit#2026.6.4 - version: https://codeload.github.com/humanspeak/docs-kit/tar.gz/20610949ada1a430ee9300b3a88367e4d9caffbb(@humanspeak/svelte-markdown@1.5.4(svelte@5.56.0(@typescript-eslint/types@8.60.0)))(@humanspeak/svelte-motion@0.6.2(svelte@5.56.0(@typescript-eslint/types@8.60.0)))(@internationalized/date@3.12.0)(@lucide/svelte@1.17.0(svelte@5.56.0(@typescript-eslint/types@8.60.0)))(@sveltejs/kit@2.61.1(@sveltejs/vite-plugin-svelte@7.1.2(svelte@5.56.0(@typescript-eslint/types@8.60.0))(vite@8.0.14(@types/node@25.9.1)(esbuild@0.28.0)(jiti@2.7.0)(tsx@4.22.4)))(svelte@5.56.0(@typescript-eslint/types@8.60.0))(typescript@6.0.3)(vite@8.0.14(@types/node@25.9.1)(esbuild@0.28.0)(jiti@2.7.0)(tsx@4.22.4)))(mode-watcher@1.1.0(svelte@5.56.0(@typescript-eslint/types@8.60.0)))(shiki@4.1.0)(svelte@5.56.0(@typescript-eslint/types@8.60.0)) + specifier: github:humanspeak/docs-kit#2026.6.5 + version: https://codeload.github.com/humanspeak/docs-kit/tar.gz/e1198d98213319920f43eceaa83eef3a5b7854b1(@humanspeak/svelte-markdown@1.5.4(svelte@5.56.0(@typescript-eslint/types@8.60.0)))(@humanspeak/svelte-motion@0.6.2(svelte@5.56.0(@typescript-eslint/types@8.60.0)))(@internationalized/date@3.12.0)(@lucide/svelte@1.17.0(svelte@5.56.0(@typescript-eslint/types@8.60.0)))(@sveltejs/kit@2.61.1(@sveltejs/vite-plugin-svelte@7.1.2(svelte@5.56.0(@typescript-eslint/types@8.60.0))(vite@8.0.14(@types/node@25.9.1)(esbuild@0.28.0)(jiti@2.7.0)(tsx@4.22.4)))(svelte@5.56.0(@typescript-eslint/types@8.60.0))(typescript@6.0.3)(vite@8.0.14(@types/node@25.9.1)(esbuild@0.28.0)(jiti@2.7.0)(tsx@4.22.4)))(mode-watcher@1.1.0(svelte@5.56.0(@typescript-eslint/types@8.60.0)))(shiki@4.1.0)(svelte@5.56.0(@typescript-eslint/types@8.60.0)) '@humanspeak/memory-cache': specifier: workspace:* version: link:.. @@ -672,8 +672,8 @@ packages: resolution: {integrity: sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==} engines: {node: '>=18.18.0'} - '@humanspeak/docs-kit@https://codeload.github.com/humanspeak/docs-kit/tar.gz/20610949ada1a430ee9300b3a88367e4d9caffbb': - resolution: {gitHosted: true, tarball: https://codeload.github.com/humanspeak/docs-kit/tar.gz/20610949ada1a430ee9300b3a88367e4d9caffbb} + '@humanspeak/docs-kit@https://codeload.github.com/humanspeak/docs-kit/tar.gz/e1198d98213319920f43eceaa83eef3a5b7854b1': + resolution: {gitHosted: true, integrity: sha512-6DRISAXr36xDU6zHqvAmCPZdY3tQJyjljfrj/dv8D6JAu3xrb2IdKbfZjGmRL+ACNpyBeEMyED8ZNJ1Q34RGrQ==, tarball: https://codeload.github.com/humanspeak/docs-kit/tar.gz/e1198d98213319920f43eceaa83eef3a5b7854b1} version: 0.0.0 peerDependencies: '@humanspeak/svelte-markdown': '>=1.0.0' @@ -3325,7 +3325,7 @@ snapshots: '@humanfs/types@0.15.0': {} - '@humanspeak/docs-kit@https://codeload.github.com/humanspeak/docs-kit/tar.gz/20610949ada1a430ee9300b3a88367e4d9caffbb(@humanspeak/svelte-markdown@1.5.4(svelte@5.56.0(@typescript-eslint/types@8.60.0)))(@humanspeak/svelte-motion@0.6.2(svelte@5.56.0(@typescript-eslint/types@8.60.0)))(@internationalized/date@3.12.0)(@lucide/svelte@1.17.0(svelte@5.56.0(@typescript-eslint/types@8.60.0)))(@sveltejs/kit@2.61.1(@sveltejs/vite-plugin-svelte@7.1.2(svelte@5.56.0(@typescript-eslint/types@8.60.0))(vite@8.0.14(@types/node@25.9.1)(esbuild@0.28.0)(jiti@2.7.0)(tsx@4.22.4)))(svelte@5.56.0(@typescript-eslint/types@8.60.0))(typescript@6.0.3)(vite@8.0.14(@types/node@25.9.1)(esbuild@0.28.0)(jiti@2.7.0)(tsx@4.22.4)))(mode-watcher@1.1.0(svelte@5.56.0(@typescript-eslint/types@8.60.0)))(shiki@4.1.0)(svelte@5.56.0(@typescript-eslint/types@8.60.0))': + '@humanspeak/docs-kit@https://codeload.github.com/humanspeak/docs-kit/tar.gz/e1198d98213319920f43eceaa83eef3a5b7854b1(@humanspeak/svelte-markdown@1.5.4(svelte@5.56.0(@typescript-eslint/types@8.60.0)))(@humanspeak/svelte-motion@0.6.2(svelte@5.56.0(@typescript-eslint/types@8.60.0)))(@internationalized/date@3.12.0)(@lucide/svelte@1.17.0(svelte@5.56.0(@typescript-eslint/types@8.60.0)))(@sveltejs/kit@2.61.1(@sveltejs/vite-plugin-svelte@7.1.2(svelte@5.56.0(@typescript-eslint/types@8.60.0))(vite@8.0.14(@types/node@25.9.1)(esbuild@0.28.0)(jiti@2.7.0)(tsx@4.22.4)))(svelte@5.56.0(@typescript-eslint/types@8.60.0))(typescript@6.0.3)(vite@8.0.14(@types/node@25.9.1)(esbuild@0.28.0)(jiti@2.7.0)(tsx@4.22.4)))(mode-watcher@1.1.0(svelte@5.56.0(@typescript-eslint/types@8.60.0)))(shiki@4.1.0)(svelte@5.56.0(@typescript-eslint/types@8.60.0))': dependencies: '@humanspeak/svelte-motion': 0.6.2(svelte@5.56.0(@typescript-eslint/types@8.60.0)) '@humanspeak/svelte-satori-fix': 0.0.6(svelte@5.56.0(@typescript-eslint/types@8.60.0)) From d865b11f194029af3eb3ebb76bc19846db090915 Mon Sep 17 00:00:00 2001 From: Jason Kummerl Date: Tue, 2 Jun 2026 17:33:44 -0400 Subject: [PATCH 02/22] docs: use updated docs header and footer --- docs/src/lib/components/general/Footer.svelte | 7 +++++-- docs/src/lib/components/general/Header.svelte | 16 ++++++++++++++-- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/docs/src/lib/components/general/Footer.svelte b/docs/src/lib/components/general/Footer.svelte index 459c2aa..d533c66 100644 --- a/docs/src/lib/components/general/Footer.svelte +++ b/docs/src/lib/components/general/Footer.svelte @@ -1,5 +1,8 @@ -