Skip to content

Commit ef18bda

Browse files
authored
feat(seo): make sitemap and RSS generation deterministic (#189) (#211)
* feat(seo): make sitemap and RSS generation deterministic (#189) - Rewrite scripts/sitemap.ts as the single entrypoint for both the build pipeline and pnpm generate:sitemap. scripts/gen-sitemap.mjs is retired; all callers now go through tsx scripts/sitemap.ts. - Derive per-route lastmod from trustworthy sources only: • blog posts → date frontmatter in the MDX file • case studies → integrationDate field in case-studies.json • static pages → git log date of the page's source file(s) • tag archives / author pages → omitted entirely (no single date) Running the generator twice on unchanged content now produces byte-identical output. - Fix RSS lastBuildDate: use the publication date of the most-recent post instead of new Date().toISOString(), so feed.xml is stable across builds when no post has been added or changed. - Add --check mode to scripts/sitemap.ts: exits 1 when public/sitemap.xml is stale. Hook it into the CI build job via pnpm check:sitemap so a stale file fails the pull request. - Add pnpm generate:sitemap and pnpm check:sitemap scripts to package.json. - Add src/__tests__/sitemap.test.ts covering lastmod omission, deterministic output, and correct root URL rendering. - Extend src/__tests__/rss.test.ts with two new cases: lastBuildDate derived from the newest post, and byte-identical output across calls. - Regenerate public/sitemap.xml and public/feed.xml (+ tag feeds) with the new deterministic logic. - Update README.md to document the new scripts and check mode. * fix(test): correct day-of-week in lastBuildDate assertion and apply prettier * fix(ci): fetch full history for sitemap determinism and regenerate sitemap actions/checkout@v4 defaults to fetch-depth: 1 (shallow clone), which means git log -1 -- <path> in gitLastModified() either returns empty or resolves to the synthetic PR merge commit. check:sitemap then regenerates different XML and the build job fails as stale. Add fetch-depth: 0 to the unit-tests and build job checkouts so every git log call in scripts/sitemap.ts sees the real commit history. Regenerate public/sitemap.xml against the full local history so the committed file matches what CI will produce after this fix. * fix(sitemap): prevent command injection via crafted filenames in gitLastModified (#189) * fix(deps): bump toml override to 5.0.1 to resolve high-severity advisories * fix(test): drain preview request stream and handle socket reset in chunked body test
1 parent cc48495 commit ef18bda

21 files changed

Lines changed: 865 additions & 194 deletions

‎.github/workflows/ci.yml‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ jobs:
1212
runs-on: ubuntu-latest
1313
steps:
1414
- uses: actions/checkout@v4
15+
with:
16+
fetch-depth: 0
1517
- uses: pnpm/action-setup@v4
1618
- uses: actions/setup-node@v4
1719
with:
@@ -27,6 +29,8 @@ jobs:
2729
runs-on: ubuntu-latest
2830
steps:
2931
- uses: actions/checkout@v4
32+
with:
33+
fetch-depth: 0
3034
- uses: pnpm/action-setup@v4
3135
- uses: actions/setup-node@v4
3236
with:
@@ -38,6 +42,7 @@ jobs:
3842
- run: pnpm format:check
3943
- run: pnpm lint:i18n
4044
- run: pnpm validate:content
45+
- run: pnpm check:sitemap
4146
- run: pnpm test:a11y
4247
- run: pnpm build
4348
- run: pnpm validate:jsonld

‎README.md‎

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,25 @@ The build pipeline performs the following steps:
2525

2626
1. Compiles TypeScript and builds the client production assets via Vite.
2727
2. Runs the Open Graph image generator (`scripts/og.ts`) to pre-render cards for all routes and patches the output HTML files with page-specific titles, descriptions, and JSON-LD breadcrumb schemas.
28-
3. Runs the sitemap generator (`scripts/gen-sitemap.mjs`) to generate a fresh `sitemap.xml` covering all static and dynamic routes.
28+
3. Runs the sitemap generator (`scripts/sitemap.ts`) to generate a fresh `sitemap.xml` covering all static and dynamic routes.
29+
4. Runs the RSS feed generator (`scripts/gen-rss.mjs`) to generate `feed.xml` and per-tag feeds under `public/feed/tag/`.
30+
31+
## Sitemap and RSS generation
32+
33+
Both generators are **deterministic**: running them twice on the same source produces byte-identical output.
34+
35+
**Sitemap** — `scripts/sitemap.ts` is the single entrypoint for both the build pipeline and manual generation:
36+
37+
```bash
38+
pnpm generate:sitemap # regenerate public/sitemap.xml
39+
pnpm check:sitemap # fail if public/sitemap.xml is stale (used in CI)
40+
```
41+
42+
`lastmod` dates are derived from content metadata (blog `date` frontmatter, case-study `integrationDate`) or the git commit that last touched each route's source files. Routes with no trustworthy date (tag archives, author pages) omit `<lastmod>` entirely rather than stamping today's date.
43+
44+
`scripts/gen-sitemap.mjs` has been retired. All callers should use `scripts/sitemap.ts` via `tsx`.
45+
46+
**RSS** — `scripts/gen-rss.mjs` generates `public/feed.xml` and per-tag feeds. The `<lastBuildDate>` is set to the publication date of the most-recent post, not the wall-clock time, so repeated builds on unchanged content produce the same file.
2947

3048
## Serverless routes
3149

‎package.json‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@
99
"check:chunk-budget": "node scripts/check-chunk-budget.mjs",
1010
"generate:sdk-size": "node scripts/check-sdk-size.mjs --write",
1111
"og:generate": "tsx scripts/og.ts",
12-
"generate:sitemap": "node scripts/gen-sitemap.mjs",
12+
"generate:sitemap": "tsx scripts/sitemap.ts",
13+
"check:sitemap": "tsx scripts/sitemap.ts --check",
1314
"preview": "vite preview",
1415
"preview:functions": "node --import tsx scripts/preview-server.ts",
1516
"test": "vitest run",

‎pnpm-lock.yaml‎

Lines changed: 6 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎pnpm-workspace.yaml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
overrides:
22
axios@<1.20.0: 1.20.0
3-
toml@<4.2.0: 4.2.0
3+
toml@<5.0.1: 5.0.1
44
ws@<8.21.0: 8.22.0

‎public/feed.xml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
<link>https://usewraith.xyz/blog</link>
66
<description>Notes on stealth payments, private infrastructure, and the Wraith ecosystem.</description>
77
<language>en-us</language>
8-
<lastBuildDate>Mon, 19 Oct 2026 15:57:04 GMT</lastBuildDate>
8+
<lastBuildDate>Wed, 12 Aug 2026 00:00:00 GMT</lastBuildDate>
99
<atom:link href="https://usewraith.xyz/feed.xml" rel="self" type="application/rss+xml" />
1010

1111
<item>

‎public/feed/tag/announcements.xml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
<link>https://usewraith.xyz/blog/tag/announcements</link>
66
<description>Notes on stealth payments, private infrastructure, and the Wraith ecosystem.</description>
77
<language>en-us</language>
8-
<lastBuildDate>Mon, 19 Oct 2026 15:57:04 GMT</lastBuildDate>
8+
<lastBuildDate>Mon, 27 Jul 2026 00:00:00 GMT</lastBuildDate>
99
<atom:link href="https://usewraith.xyz/feed/tag/announcements.xml" rel="self" type="application/rss+xml" />
1010

1111
<item>

‎public/feed/tag/cryptography.xml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
<link>https://usewraith.xyz/blog/tag/cryptography</link>
66
<description>Notes on stealth payments, private infrastructure, and the Wraith ecosystem.</description>
77
<language>en-us</language>
8-
<lastBuildDate>Mon, 19 Oct 2026 15:57:04 GMT</lastBuildDate>
8+
<lastBuildDate>Wed, 12 Aug 2026 00:00:00 GMT</lastBuildDate>
99
<atom:link href="https://usewraith.xyz/feed/tag/cryptography.xml" rel="self" type="application/rss+xml" />
1010

1111
<item>

‎public/feed/tag/privacy.xml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
<link>https://usewraith.xyz/blog/tag/privacy</link>
66
<description>Notes on stealth payments, private infrastructure, and the Wraith ecosystem.</description>
77
<language>en-us</language>
8-
<lastBuildDate>Mon, 19 Oct 2026 15:57:04 GMT</lastBuildDate>
8+
<lastBuildDate>Wed, 12 Aug 2026 00:00:00 GMT</lastBuildDate>
99
<atom:link href="https://usewraith.xyz/feed/tag/privacy.xml" rel="self" type="application/rss+xml" />
1010

1111
<item>

‎public/feed/tag/sdk.xml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
<link>https://usewraith.xyz/blog/tag/sdk</link>
66
<description>Notes on stealth payments, private infrastructure, and the Wraith ecosystem.</description>
77
<language>en-us</language>
8-
<lastBuildDate>Mon, 19 Oct 2026 15:57:04 GMT</lastBuildDate>
8+
<lastBuildDate>Mon, 27 Jul 2026 00:00:00 GMT</lastBuildDate>
99
<atom:link href="https://usewraith.xyz/feed/tag/sdk.xml" rel="self" type="application/rss+xml" />
1010

1111
<item>

0 commit comments

Comments
 (0)