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
3 changes: 3 additions & 0 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ jobs:
- name: Install dependencies
run: npm ci

- name: Generate sitemap
run: node scripts/generate-sitemap.mjs

- name: Build
run: npx nuxi build

Expand Down
5 changes: 4 additions & 1 deletion .github/workflows/sync-sdk-docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,13 @@ jobs:
cp tmp/laravel-repo/docs/*.md tmp/laravel-docs/ 2>/dev/null || true
node scripts/transform-laravel-docs.mjs tmp/laravel-docs content/3.packages/2.laravel

- name: Regenerate sitemap
run: node scripts/generate-sitemap.mjs

- name: Check for changes
id: changes
run: |
git add content/3.packages/
git add content/3.packages/ public/sitemap.xml
if git diff --staged --quiet; then
echo "changed=false" >> $GITHUB_OUTPUT
else
Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
"dev": "nuxt dev --extends docus",
"cache:clear": "rm -rf .nuxt .data node_modules/.cache",
"cache:clear:dev": "npm run cache:clear && npm run dev",
"sitemap": "node scripts/generate-sitemap.mjs",
"prebuild": "node scripts/generate-sitemap.mjs",
"build": "nuxt build --extends docus",
"postinstall": "cp app/components/OgImage/OgImageDocs.vue node_modules/docus/app/components/OgImage/OgImageDocs.vue",
"lint:md": "npx markdownlint-cli2 'content/**/*.md'"
Expand Down
19 changes: 13 additions & 6 deletions public/sitemap.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,25 @@
<url><loc>https://docs.vatly.com/</loc><priority>1.0</priority></url>
<url><loc>https://docs.vatly.com/introduction</loc><priority>0.9</priority></url>
<url><loc>https://docs.vatly.com/guides/quickstart</loc><priority>0.8</priority></url>
<url><loc>https://docs.vatly.com/guides/selling-workflow</loc><priority>0.8</priority></url>
<url><loc>https://docs.vatly.com/guides/sdks</loc><priority>0.8</priority></url>
<url><loc>https://docs.vatly.com/guides/authentication</loc><priority>0.8</priority></url>
<url><loc>https://docs.vatly.com/guides/pagination</loc><priority>0.7</priority></url>
<url><loc>https://docs.vatly.com/guides/errors</loc><priority>0.7</priority></url>
<url><loc>https://docs.vatly.com/guides/pagination</loc><priority>0.8</priority></url>
<url><loc>https://docs.vatly.com/guides/errors</loc><priority>0.8</priority></url>
<url><loc>https://docs.vatly.com/guides/webhooks</loc><priority>0.8</priority></url>
<url><loc>https://docs.vatly.com/guides/idempotency</loc><priority>0.7</priority></url>
<url><loc>https://docs.vatly.com/guides/idempotency</loc><priority>0.8</priority></url>
<url><loc>https://docs.vatly.com/guides/testing</loc><priority>0.8</priority></url>
<url><loc>https://docs.vatly.com/api-reference/customers</loc><priority>0.8</priority></url>
<url><loc>https://docs.vatly.com/api-reference/checkouts</loc><priority>0.8</priority></url>
<url><loc>https://docs.vatly.com/api-reference/one-off-products</loc><priority>0.8</priority></url>
<url><loc>https://docs.vatly.com/api-reference/subscriptions</loc><priority>0.8</priority></url>
<url><loc>https://docs.vatly.com/api-reference/subscription-plans</loc><priority>0.8</priority></url>
<url><loc>https://docs.vatly.com/api-reference/orders</loc><priority>0.8</priority></url>
<url><loc>https://docs.vatly.com/api-reference/refunds</loc><priority>0.8</priority></url>
<url><loc>https://docs.vatly.com/api-reference/global-refunds</loc><priority>0.7</priority></url>
<url><loc>https://docs.vatly.com/api-reference/chargebacks</loc><priority>0.7</priority></url>
<url><loc>https://docs.vatly.com/api-reference/one-off-products</loc><priority>0.8</priority></url>
<url><loc>https://docs.vatly.com/api-reference/global-refunds</loc><priority>0.8</priority></url>
<url><loc>https://docs.vatly.com/api-reference/chargebacks</loc><priority>0.8</priority></url>
<url><loc>https://docs.vatly.com/api-reference/test-helpers</loc><priority>0.8</priority></url>
<url><loc>https://docs.vatly.com/api-reference/webhook-events</loc><priority>0.8</priority></url>
<url><loc>https://docs.vatly.com/packages</loc><priority>0.7</priority></url>
<url><loc>https://docs.vatly.com/packages/php/readme</loc><priority>0.7</priority></url>
<url><loc>https://docs.vatly.com/packages/php/customers</loc><priority>0.6</priority></url>
Expand All @@ -35,5 +39,8 @@
<url><loc>https://docs.vatly.com/packages/laravel/subscriptions</loc><priority>0.6</priority></url>
<url><loc>https://docs.vatly.com/packages/laravel/orders</loc><priority>0.6</priority></url>
<url><loc>https://docs.vatly.com/packages/laravel/webhooks</loc><priority>0.6</priority></url>
<url><loc>https://docs.vatly.com/packages/laravel/comparison</loc><priority>0.6</priority></url>
<url><loc>https://docs.vatly.com/packages/laravel/migrating-to-vatly</loc><priority>0.6</priority></url>
<url><loc>https://docs.vatly.com/packages/laravel/configuration</loc><priority>0.6</priority></url>
<url><loc>https://docs.vatly.com/integrations</loc><priority>0.7</priority></url>
</urlset>
85 changes: 85 additions & 0 deletions scripts/generate-sitemap.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
#!/usr/bin/env node
/**
* Generate public/sitemap.xml from the content/ directory.
*
* The sitemap used to be hand-maintained, which meant every newly-added page —
* and especially pages synced in from the vatly-laravel / vatly-api-php repos —
* silently went missing from it. This walks content/ and derives the exact
* route Docus serves for each page, so the sitemap can never drift again.
*
* Route derivation (verified against the live site):
* - Strip the leading "N." ordering prefix from every path segment.
* - A file named `index.md` maps to its parent directory's route.
* - `readme.md` is NOT special — it stays as `/.../readme`.
* - content/index.md -> "/" (home)
*
* Run: node scripts/generate-sitemap.mjs
*/

import { readdirSync, writeFileSync } from 'fs'
import { join, dirname } from 'path'
import { fileURLToPath } from 'url'

const ROOT = join(dirname(fileURLToPath(import.meta.url)), '..')
const CONTENT_DIR = join(ROOT, 'content')
const OUTPUT = join(ROOT, 'public', 'sitemap.xml')
const BASE_URL = 'https://docs.vatly.com'

// Strip the leading "N." ordering prefix (e.g. "7.migrating-to-vatly" -> "migrating-to-vatly").
const stripOrder = (name) => name.replace(/^\d+\./, '')

// Numeric sort key from the leading "N." prefix. `index` sorts first so a
// directory's own page leads its children; unprefixed files sort last.
const orderKey = (name) => {
const base = name.replace(/\.md$/, '')
if (base === 'index') return -1
const m = name.match(/^(\d+)\./)
return m ? parseInt(m[1], 10) : 1e9
}

// SEO priority, derived from route depth so it needs no per-page upkeep.
const priorityFor = (segments) => {
if (segments.length === 0) return '1.0' // home
if (segments.length === 1) return segments[0] === 'introduction' ? '0.9' : '0.7'
if (segments[0] === 'packages') {
return segments[segments.length - 1] === 'readme' ? '0.7' : '0.6'
}
return '0.8' // guides/*, api-reference/*
}

/** Recursively collect route segment-arrays from a content directory. */
function collect(dir, segments, routes) {
const entries = readdirSync(dir, { withFileTypes: true })
.filter((e) => e.isDirectory() || e.name.endsWith('.md'))
.sort((a, b) => orderKey(a.name) - orderKey(b.name) || a.name.localeCompare(b.name))

for (const entry of entries) {
const full = join(dir, entry.name)
if (entry.isDirectory()) {
collect(full, [...segments, stripOrder(entry.name)], routes)
} else {
const base = stripOrder(entry.name.replace(/\.md$/, ''))
routes.push(base === 'index' ? segments : [...segments, base])
}
}
}

const routes = []
collect(CONTENT_DIR, [], routes)

const urls = routes.map((segments) => {
const loc = segments.length ? `${BASE_URL}/${segments.join('/')}` : `${BASE_URL}/`
return ` <url><loc>${loc}</loc><priority>${priorityFor(segments)}</priority></url>`
})

const xml = `<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
${urls.join('\n')}
</urlset>
`

writeFileSync(OUTPUT, xml)
console.log(`Wrote ${routes.length} URLs to ${OUTPUT.replace(ROOT + '/', '')}`)
for (const segments of routes) {
console.log(` /${segments.join('/')}`)
}
Loading