Repository navigation
feat(seo): make sitemap and RSS generation deterministic (#189) - #211
truthixify merged 6 commits into
Conversation
|
@Promazin is attempting to deploy a commit to the truthixify's projects Team on Vercel. A member of the Team first needs to authorize it. |
|
@Promazin Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits. You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀 |
truthixify
left a comment
There was a problem hiding this comment.
The sitemap is deterministic only in a full-history clone. scripts/sitemap.ts:36-44 derives static dates with git log, while every CI checkout in .github/workflows/ci.yml uses actions/checkout@v4 with its default fetch-depth: 1. In that shallow checkout, path history is missing (or collapses to the synthetic PR commit), so check:sitemap regenerates different XML and the required build job currently fails as stale; the same command passes in a full-history worktree. Either fetch full history anywhere the generator/check runs, or derive dates from repository content rather than Git history, then regenerate and commit the output. Please also rebase onto #210's EPIPE fix so the unit job is green.
…ocol#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.
…temap 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.
f03d06f to
8a776c3
Compare
truthixify
left a comment
There was a problem hiding this comment.
High severity: scripts/sitemap.ts:38-40 interpolates repository-controlled paths into an execSync shell string. getBlogRoutes feeds it filenames returned from src/content/blog, so a PR can add a crafted filename containing a quote and shell metacharacters and execute commands during pnpm build/CI. Use execFileSync or spawnSync with an argument array such as git, [log, -1, --format=%cd, --date=short, --, ...repoPaths] and add a filename-with-metacharacters regression test. The shallow-checkout fix is otherwise correct.
truthixify
left a comment
There was a problem hiding this comment.
Approved exact head 8269785 after repeating the remote full-diff malware scan. gitLastModified now invokes git with an argument array rather than a shell, the malicious-filename regression coverage is present, the preview request-drain fix is bounded, toml is patched, and all repository-owned build, unit, smoke, link, Lighthouse, and accessibility gates are green. The remaining Vercel status is an external authorization failure.
Summary
Closes #189
Every build was stamping all sitemap
<lastmod>entries with today's date and theRSS
<lastBuildDate>with the current time, dirtying tracked files on every runeven when no content changed.
Changes
scripts/sitemap.ts — rewritten, now the single entrypoint
scripts/gen-sitemap.mjsis retired. All callers (pnpm build,pnpm generate:sitemap)now go through
tsx scripts/sitemap.ts.Deterministic date derivation per route type:
datefrontmatter in the MDX fileintegrationDatefield incase-studies.jsongit log -1 --date=shorton the page's source file(s)--checkmode: compares the regenerated XML againstpublic/sitemap.xmlinmemory and exits 1 if they differ, without touching the file.
scripts/gen-rss.mjs
Replaced
new Date().toISOString()in<lastBuildDate>withposts[0].publishedAt(the most-recent post's publication date). Feed files are now stable across builds
when content is unchanged.
package.json
generate:sitemap→tsx scripts/sitemap.ts(wasnode scripts/gen-sitemap.mjs)check:sitemap→tsx scripts/sitemap.ts --checkCI (.github/workflows/ci.yml)
Added
pnpm check:sitemapto the build job so a stalepublic/sitemap.xmlfailsthe pull request before it merges.
Tests
src/__tests__/sitemap.test.ts— new: covers<lastmod>omission, correct rootURL, byte-identical output, no wall-clock date leakage
src/__tests__/rss.test.ts— two new cases:lastBuildDatefrom newest post,byte-identical output across calls
Generated output (re-committed)
public/sitemap.xmlandpublic/feed.xml+ all tag feeds regenerated with the newdeterministic logic. Running either generator again produces no diff.
Tested
npx tsx scripts/sitemap.ts→ 45 routes, succeedsnpx tsx scripts/sitemap.ts --check→ passes on fresh output, exits 1 on stalenode scripts/gen-rss.mjsrun twice → byte-identicalpublic/feed.xml<lastBuildDate>is nowWed, 12 Aug 2026(newest post date), not wall-clock time