From dbcf785776412bd3bdfe6bb39b679b4a6e6e348c Mon Sep 17 00:00:00 2001 From: Metbcy Date: Fri, 29 May 2026 23:59:55 +0000 Subject: [PATCH] docs: wire test-urls.js into CI to catch broken edit links Builds on the existing packages/docs/test-urls.js helper by: - Switching it from flaky live HEAD requests to github.com over to a deterministic local filesystem check against packages/docs/src/routes (the false-positive previously reported on /docs/routing was a transient network error, not a real broken link). - Making the script exit non-zero on any broken link so CI fails loudly when a docs page is moved/renamed without updating on-this-page.tsx. - Adding a 'test.urls' script in packages/docs/package.json. - Adding a 'Check Docs Links' step in .github/workflows/ci.yml (Build Docs job) that runs the script before pnpm build.packages.docs. Closes #6333 --- .github/workflows/ci.yml | 2 + packages/docs/package.json | 1 + packages/docs/test-urls.js | 145 ++++++++++++++----------------------- 3 files changed, 59 insertions(+), 89 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 284b081cbf3..4405821245a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -594,6 +594,8 @@ jobs: mv artifact-qwikreact/lib packages/qwik-react/lib - run: pnpm install --frozen-lockfile + - name: Check Docs Links (issue #6333) + run: node packages/docs/test-urls.js - name: Build Qwik Docs run: pnpm run build.packages.docs && echo ok > docs-build-completed.txt diff --git a/packages/docs/package.json b/packages/docs/package.json index cf1809f79cf..c150219c993 100644 --- a/packages/docs/package.json +++ b/packages/docs/package.json @@ -71,6 +71,7 @@ "private": true, "scripts": { "build": "qwik build", + "test.urls": "node test-urls.js", "build.client": "vite build", "build.preview": "NODE_OPTIONS=--max-old-space-size=8192 vite build --ssr src/entry.preview.tsx", "build.repl-sw": "vite --config vite.config-repl-sw.mts build", diff --git a/packages/docs/test-urls.js b/packages/docs/test-urls.js index 1893ef1768e..7933f696ab2 100644 --- a/packages/docs/test-urls.js +++ b/packages/docs/test-urls.js @@ -1,7 +1,20 @@ -// Test script to verify all edit URLs are working +// Verify that all "edit on GitHub" edit URLs generated by makeEditPageUrl +// (see packages/docs/src/components/on-this-page/on-this-page.tsx) point to +// real source files in the docs tree. +// +// Run locally: node packages/docs/test-urls.js +// In CI: invoked from .github/workflows/ci.yml (see "Check Docs Links" step). +// +// The script intentionally validates against the local filesystem rather than +// HEAD-requesting github.com so that the check is deterministic and never +// flakes due to network/rate-limit issues. It exits non-zero on any failure +// so CI fails loudly when a doc is renamed or moved without updating these +// route group arrays (or vice versa). +// +// Refs: https://github.com/QwikDev/qwik/issues/6333 + const fs = require('fs'); const path = require('path'); -const https = require('https'); // Import the arrays from on-this-page.tsx (these are simplified copies for testing) const QWIK_GROUP = [ @@ -107,115 +120,69 @@ function makeEditPageUrl(url) { return segments.join('/'); } -// Check if a URL exists -function checkUrl(url) { - return new Promise((resolve) => { - const options = { - method: 'HEAD', - host: 'github.com', - path: url.replace('https://github.com', ''), - timeout: 5000, - }; - - const req = https.request(options, (res) => { - resolve({ - url, - status: res.statusCode, - ok: res.statusCode < 400, - }); - }); - - req.on('error', (err) => { - resolve({ - url, - status: 0, - ok: false, - error: err.message, - }); - }); - - req.on('timeout', () => { - req.destroy(); - resolve({ - url, - status: 0, - ok: false, - error: 'Timeout', - }); - }); - - req.end(); - }); +const ROUTES_DIR = path.join(__dirname, 'src', 'routes'); + +// Resolve an edit path (relative to packages/docs/src/routes) to an absolute +// local file path. The qwik docs use folder-routed index.mdx files; some +// segments contain characters that need no special handling because the FS +// supports them on every CI-supported platform (Linux/macOS/Windows runner +// images all allow `(`, `)` and `$` in filenames). +function resolveLocalMdx(editPath) { + return path.join(ROUTES_DIR, editPath, 'index.mdx'); } -// Generate paths for testing -async function testAllPaths() { - console.log('Testing URL paths for documentation pages...'); +function main() { + console.log('Verifying docs edit URLs resolve to real .mdx files...'); - // Generate test paths const testPaths = []; - - // Test QWIK_GROUP paths - for (const path of QWIK_GROUP) { - if (path === 'index') { - // Special case for index - testPaths.push('/docs'); - } else { - testPaths.push(`/docs/${path}`); - } + for (const p of QWIK_GROUP) { + testPaths.push(p === 'index' ? '/docs' : `/docs/${p}`); } - - // Test QWIK_ADVANCED_GROUP paths - for (const path of QWIK_ADVANCED_GROUP) { - testPaths.push(`/docs/advanced/${path}`); + for (const p of QWIK_ADVANCED_GROUP) { + testPaths.push(`/docs/advanced/${p}`); } - - // Test QWIKCITY_GROUP paths - for (const path of QWIKCITY_GROUP) { - testPaths.push(`/docs/${path}`); + for (const p of QWIKCITY_GROUP) { + testPaths.push(`/docs/${p}`); } - - // Test QWIKCITY_ADVANCED_GROUP paths - for (const path of QWIKCITY_ADVANCED_GROUP) { - testPaths.push(`/docs/advanced/${path}`); + for (const p of QWIKCITY_ADVANCED_GROUP) { + testPaths.push(`/docs/advanced/${p}`); } - // Test each path let failCount = 0; let successCount = 0; - let failedPaths = []; + const failedPaths = []; - console.log(`Testing ${testPaths.length} URLs...`); + console.log(`Checking ${testPaths.length} URLs...`); for (const testPath of testPaths) { const editPath = makeEditPageUrl(testPath); + const filePath = resolveLocalMdx(editPath); const editUrl = `https://github.com/QwikDev/qwik/blob/main/packages/docs/src/routes/${editPath}/index.mdx`; - try { - const result = await checkUrl(editUrl); - if (result.ok) { - console.log(`✅ ${editUrl}`); - successCount++; - } else { - console.error(`❌ ${editUrl} (Status: ${result.status})`); - failCount++; - failedPaths.push(testPath); - } - } catch (error) { - console.error(`❌ Error checking ${editUrl}: ${error.message}`); + if (fs.existsSync(filePath)) { + console.log(`✅ ${testPath} -> ${editPath}/index.mdx`); + successCount++; + } else { + console.error(`❌ ${testPath} -> ${editPath}/index.mdx (missing) [${editUrl}]`); failCount++; - failedPaths.push(testPath); + failedPaths.push({ testPath, editPath, filePath }); } } - console.log(`\nTest complete: ${successCount} successful, ${failCount} failed`); + console.log(`\nResult: ${successCount} ok, ${failCount} broken`); + if (failCount > 0) { - console.log('\nFailed paths:'); - failedPaths.forEach((path) => { - console.log(`- ${path}`); - }); + console.error('\nBroken edit URLs:'); + for (const f of failedPaths) { + console.error(` - ${f.testPath} (expected ${f.filePath})`); + } + console.error( + '\nFix: update QWIK_GROUP / QWIK_ADVANCED_GROUP / QWIKCITY_GROUP /' + + ' QWIKCITY_ADVANCED_GROUP in packages/docs/src/components/on-this-page/on-this-page.tsx' + + ' (and the mirrored arrays in packages/docs/test-urls.js) to match the docs tree.' + ); + process.exit(1); } } -// Run the tests -testAllPaths(); +main();