diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7191565..be86b03 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -12,13 +12,17 @@ Thank you for helping improve the Hive Commons documentation site ## Before you open a PR -From a fresh checkout, install the exact dependency set and run the same local -validation commands used by CI: +From a fresh checkout, install the exact dependency set and run the local +validation commands that cover the PR gates: ```bash npm ci -npm run lint -npm run build +npm run lint:md # markdownlint for docs content +npm run type-check # TypeScript without emitting files +npm test # Vitest unit tests +npm run check-links # internal docs links +npm run lint # ESLint for src/ +npm run build # production build and doc-sync scripts ``` To preview the docs locally while editing, run: diff --git a/src/__tests__/proxy.test.ts b/src/__tests__/proxy.test.ts index 78957da..2ac2fa9 100644 --- a/src/__tests__/proxy.test.ts +++ b/src/__tests__/proxy.test.ts @@ -44,6 +44,22 @@ describe('proxy', () => { expect(proxy(request('/zh-TW/docs/readme')).headers.get('location')).toBe('https://docs.hivecommons.dev/docs/readme') }) + + it('redirects region-qualified docs locales to canonical docs paths', () => { + const response = proxy(request('/pt-BR/docs/hive/setup')) + + expect(response.status).toBe(307) + expect(response.headers.get('location')).toBe('https://docs.hivecommons.dev/docs/hive/setup') + }) + + it('does not treat three-letter prefixes as localized docs URLs', () => { + const response = proxy(request('/abc/docs/page')) + + expect(response.status).toBe(200) + expect(response.headers.get('x-middleware-rewrite')).toBe('https://docs.hivecommons.dev/en/abc/docs/page') + expect(response.headers.get('location')).toBeNull() + }) + it('keeps non-doc localized pages in the i18n middleware flow', () => { const response = proxy(request('/es/community')) diff --git a/src/__tests__/sitemap.test.ts b/src/__tests__/sitemap.test.ts index 393dc8a..83417a2 100644 --- a/src/__tests__/sitemap.test.ts +++ b/src/__tests__/sitemap.test.ts @@ -1,4 +1,6 @@ -import { describe, expect, it } from 'vitest' +import { afterEach, describe, expect, it, vi } from 'vitest' +import fs from 'fs' +import path from 'path' import sitemap from '../app/sitemap' const SITE_URL = 'https://docs.hivecommons.dev' @@ -39,3 +41,68 @@ describe('sitemap', () => { expect(urls.some((url) => url.includes('/images/'))).toBe(false) }) }) + + +describe('sitemap generated from fixture content', () => { + const realCwd = process.cwd() + let fixtureRoot: string | undefined + + function write(relPath: string, content = '# stub\n') { + if (!fixtureRoot) throw new Error('fixtureRoot not initialized') + const full = path.join(fixtureRoot, relPath) + fs.mkdirSync(path.dirname(full), { recursive: true }) + fs.writeFileSync(full, content) + return full + } + + afterEach(() => { + vi.restoreAllMocks() + if (fixtureRoot) { + fs.rmSync(fixtureRoot, { recursive: true, force: true }) + fixtureRoot = undefined + } + }) + + it('maps markdown files to routes, priorities, frequencies, and mtimes', () => { + fixtureRoot = fs.mkdtempSync(path.join(realCwd, '.sitemap-fixture-')) + const setupPath = write('docs/content/hive/guides/setup.md') + write('docs/content/hive/guides/advanced/scaling.mdx') + write('docs/content/pluk/cli/index.md') + vi.spyOn(process, 'cwd').mockReturnValue(fixtureRoot) + + const entries = sitemap() + const byUrl = new Map(entries.map((entry) => [entry.url, entry])) + + expect(byUrl.get(SITE_URL)?.priority).toBe(1.0) + expect(byUrl.get(`${SITE_URL}/en`)?.changeFrequency).toBe('monthly') + expect(byUrl.get(`${SITE_URL}/docs`)?.priority).toBe(0.9) + expect(byUrl.get(`${SITE_URL}/docs/hive`)?.priority).toBe(0.9) + expect(byUrl.get(`${SITE_URL}/docs/hive/guides/setup`)?.priority).toBe(0.7) + expect(byUrl.get(`${SITE_URL}/docs/hive/guides/setup`)?.changeFrequency).toBe('weekly') + expect(byUrl.get(`${SITE_URL}/docs/hive/guides/setup`)?.lastModified).toEqual(fs.statSync(setupPath).mtime) + expect(byUrl.has(`${SITE_URL}/docs/hive/guides/advanced/scaling`)).toBe(true) + expect(byUrl.has(`${SITE_URL}/docs/pluk/cli`)).toBe(true) + expect(byUrl.has(`${SITE_URL}/docs/pluk/cli/index`)).toBe(false) + }) + + it('omits generated routes for missing content while retaining project roots', () => { + fixtureRoot = fs.mkdtempSync(path.join(realCwd, '.sitemap-empty-')) + vi.spyOn(process, 'cwd').mockReturnValue(fixtureRoot) + + const entries = sitemap() + + expect(entries).toHaveLength(10) + expect(entries.map((entry) => entry.url)).toEqual(expect.arrayContaining([ + SITE_URL, + `${SITE_URL}/en`, + `${SITE_URL}/docs`, + `${SITE_URL}/docs/hive`, + `${SITE_URL}/docs/hotshot`, + `${SITE_URL}/docs/pluk`, + `${SITE_URL}/docs/rationguard`, + `${SITE_URL}/docs/promptargs`, + `${SITE_URL}/docs/dibs`, + `${SITE_URL}/docs/spektacular`, + ])) + }) +})