From 8a9b5f0794192c422ded221685e5d01dae2fb430 Mon Sep 17 00:00:00 2001 From: Ronald Tse Date: Wed, 17 Jun 2026 16:54:41 +0800 Subject: [PATCH] fix(docs): scope link_checker, fix SPDX URLs, fix license categories, fix clean URLs 404 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Five fixes for the docs site, all discovered while investigating the perennially-failing link_checker job and downstream UX bugs. 1. URLs inside license/EULA text and descriptions (the Apple macOS EULA mentions adobe.com and mpegla.com ~4,259 times; a https://https/ typo appears in 26 Rubik YAMLs) are informational mentions quoted from upstream, not links we maintain. Set lychee include_verbatim=false so only //markdown links are checked. Also escape bare URLs in description and copyright fields via escapeBareUrls() so VitePress does not auto-link them on the website. 2. SPDX auto-generated URLs were 404ing in two ways: (a) OFL-1.1-NO-RFN in YAML is uppercase, but SPDX URLs are case-sensitive — the actual page is OFL-1.1-no-RFN.html. Now .replace('-NO-RFN','-no-RFN'). Eliminates 1,402 errors. (b) LicenseRef-* codes are vendor-specific references with no SPDX page. Now spdxUrl=null. Eliminates 2,161 errors. 3. License categorization was wrong because the SPDX branch fired before more specific checks. Moved macOS source-type check first (2,107 macOS fonts now correctly platform_restricted instead of open_source). Also mapped LicenseRef-* codes to categories by vendor prefix: Apple-* -> platform_restricted, Microsoft-fontpack-* -> freely_distributable (MS Web Fonts EULA), Microsoft-* -> bundled_software (Office/viewers), Adobe-* -> bundled_software. Stats now: open_source=2,143, platform_restricted=2,108, freely_distributable=8, bundled_software=24 (was 4,283/0/0/0). Fixes /browse/?license=freely_distributable and platform_restricted being empty. 4. /browse// URLs were 404ing on the live site because VitePress cleanUrls:true emits foo.html and expects the host to rewrite /foo/ -> /foo.html; GitHub Pages doesn't. Added a post-process step (in build.js for local/link_checker builds, and in docs.yml's combine job for production deploy) that converts each non-index foo.html to foo/index.html. --- .github/workflows/docs.yml | 14 ++++++ docs/build.js | 29 +++++++++++++ docs/generate.js | 88 +++++++++++++++++++++++++++----------- docs/lychee.toml | 5 ++- 4 files changed, 109 insertions(+), 27 deletions(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 39805e5a7..e23c22453 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -261,6 +261,20 @@ jobs: done shell: bash + - name: Rewrite clean URLs to directory style + # VitePress cleanUrls:true emits foo.html and expects the host to + # rewrite /foo/ -> /foo.html. GitHub Pages doesn't, so every browse + # page 404s on its directory-style URL. Convert each non-index .html + # to /index.html so /foo/ resolves natively. + run: | + find dist -type f -name "*.html" ! -name "index.html" | while read -r f; do + dir="$(dirname "$f")/$(basename "$f" .html)" + mkdir -p "$dir" + mv "$f" "$dir/index.html" + done + echo "Rewrote $(find dist -type d -name index.html -exec dirname {} \; | wc -l) pages" + shell: bash + - name: Upload combined artifact uses: actions/upload-pages-artifact@v4 with: diff --git a/docs/build.js b/docs/build.js index 52e5c166a..63ab27201 100644 --- a/docs/build.js +++ b/docs/build.js @@ -56,6 +56,31 @@ async function countHtmlFiles(dir) { return n; } +// VitePress with cleanUrls:true emits foo.html and expects the host to +// rewrite /foo/ -> /foo.html. GitHub Pages doesn't, so every browse page +// 404s on its directory-style URL. Convert each non-index .html to +// /index.html so /foo/ resolves natively. +async function rewriteCleanUrls(dir) { + let count = 0; + async function walk(d) { + const entries = await readdir(d, { withFileTypes: true }); + for (const e of entries) { + const full = join(d, e.name); + if (e.isDirectory()) { + await walk(full); + } else if (e.isFile() && e.name.endsWith(".html") && e.name !== "index.html") { + const baseName = e.name.slice(0, -5); + const newDir = join(d, baseName); + await mkdir(newDir, { recursive: true }); + await rename(full, join(newDir, "index.html")); + count++; + } + } + } + await walk(dir); + return count; +} + function run(cmd, args, env = process.env) { const result = spawnSync(cmd, args, { stdio: "inherit", @@ -157,6 +182,10 @@ async function main() { await rm(DIST_DIR, { recursive: true, force: true }); await rename(FINAL_DIR, DIST_DIR); + console.log("=== Rewriting clean URLs to directory style ==="); + const rewritten = await rewriteCleanUrls(DIST_DIR); + console.log(`Rewrote ${rewritten} pages`); + console.log(`\n=== Build complete: ${await countHtmlFiles(DIST_DIR)} total HTML pages ===`); } diff --git a/docs/generate.js b/docs/generate.js index fadacc143..d0403eb05 100644 --- a/docs/generate.js +++ b/docs/generate.js @@ -19,6 +19,15 @@ function escapeYAMLString(str) { return str.replace(/\\/g, "\\\\").replace(/"/g, '\\"'); } +// Prevent markdown autolinking of bare URLs in body text (descriptions, +// copyrights). The backslash escapes the colon so markdown-it renders the +// URL as plain text instead of an . License/EULA body text is +// already safe — it ships inside ```code blocks```. +function escapeBareUrls(str) { + if (!str) return ""; + return str.replace(/(https?:)(\/\/)/g, "$1\\$2"); +} + // Detect formula source type from path function detectSourceType(slug) { if (slug.startsWith("google/")) return "google"; @@ -85,16 +94,37 @@ function detectLicenseInfo(yaml, sourceType) { // Combine all text for license detection const allText = `${licenseUrl} ${openLicense} ${copyright}`.toLowerCase(); + // Platform-tied sources (macOS) win over SPDX detection: a macOS font is + // platform_restricted regardless of any SPDX code on the formula, because + // the SPDX code (often LicenseRef-Apple-EA1705) describes the EULA text, + // not the redistribution category. Without this guard, all 2,107 macOS + // fonts get miscategorized as open_source via the SPDX fallback below. + if (sourceType === "macos") { + return { + type: "macos", + name: "Apple-only License", + badge: svgBadge("License", "Apple-only", "#f0ad4e", 120), + docLink: "/licenses/apple-only", + spdxUrl: null, + category: "platform_restricted", + isOpen: false, + warning: "⚠️ **Platform Restricted**: These fonts are licensed for use on macOS only. Installation on other platforms may violate Apple's license terms.", + }; + } + // Fast path: use spdx_license field if available if (spdxLicense) { if (spdxLicense.startsWith("OFL-1.1")) { const isRfn = spdxLicense.includes("-RFN"); + // SPDX URLs are case-sensitive: OFL-1.1-RFN.html and OFL-1.1-no-RFN.html + // exist, but OFL-1.1-NO-RFN.html does not. YAML stores it uppercase. + const spdxPath = spdxLicense.replace("-NO-RFN", "-no-RFN"); return { type: "ofl", name: `SIL Open Font License 1.1${isRfn ? " (with RFN)" : ""}`, badge: svgBadge("License", isRfn ? "OFL 1.1-RFN" : "OFL 1.1", "#28a745", isRfn ? 140 : 120), docLink: "/licenses/ofl", - spdxUrl: `https://spdx.org/licenses/${spdxLicense}.html`, + spdxUrl: `https://spdx.org/licenses/${spdxPath}.html`, category: "open_source", isOpen: true, }; @@ -171,30 +201,36 @@ function detectLicenseInfo(yaml, sourceType) { category: "open_source", isOpen: true, }; } - // Generic fallback for known SPDX codes not matched above - return { - type: "spdx_other", name: spdxLicense, - badge: svgBadge("License", spdxLicense.slice(0, 15), "#28a745", 120), - docLink: null, spdxUrl: `https://spdx.org/licenses/${spdxLicense}.html`, - category: "open_source", isOpen: true, - }; - } - - // ============================================================ - // PLATFORM RESTRICTED - // ============================================================ - - // macOS fonts - if (sourceType === "macos") { + // Generic fallback for SPDX codes not matched above. + // LicenseRef-* codes are vendor-specific references with no SPDX page + // (spdxUrl=null). Categorize by vendor prefix so the browse page and + // stats reflect the right redistribution category instead of dumping + // everything into open_source. + const isLicenseRef = spdxLicense.startsWith("LICENSEREF-"); + let refType = "spdx_other"; + let refCategory = "open_source"; + let refBadgeText = spdxLicense.slice(0, 15); + let refBadgeColor = "#28a745"; + if (isLicenseRef) { + if (spdxLicense.startsWith("LICENSEREF-APPLE-")) { + refType = "macos"; refCategory = "platform_restricted"; + refBadgeText = "Apple-only"; refBadgeColor = "#f0ad4e"; + } else if (spdxLicense.startsWith("LICENSEREF-MICROSOFT-FONTPACK-")) { + refType = "ms_web_fonts"; refCategory = "freely_distributable"; + refBadgeText = "MS Web Fonts"; refBadgeColor = "#007bff"; + } else if (spdxLicense.startsWith("LICENSEREF-MICROSOFT-")) { + refType = "ms_office"; refCategory = "bundled_software"; + refBadgeText = "MS Software"; refBadgeColor = "#8b5cf6"; + } else if (spdxLicense.startsWith("LICENSEREF-ADOBE-")) { + refType = "adobe"; refCategory = "bundled_software"; + refBadgeText = "Adobe Software"; refBadgeColor = "#8b5cf6"; + } + } return { - type: "macos", - name: "Apple-only License", - badge: svgBadge("License", "Apple-only", "#f0ad4e", 120), - docLink: "/licenses/apple-only", - spdxUrl: null, - category: "platform_restricted", - isOpen: false, - warning: "⚠️ **Platform Restricted**: These fonts are licensed for use on macOS only. Installation on other platforms may violate Apple's license terms.", + type: refType, name: spdxLicense, + badge: svgBadge("License", refBadgeText, refBadgeColor, 120), + docLink: null, spdxUrl: isLicenseRef ? null : `https://spdx.org/licenses/${spdxLicense}.html`, + category: refCategory, isOpen: refCategory === "open_source", }; } @@ -899,7 +935,7 @@ ${licenseInfo.warning || ""}${licenseTextSection}${ copyrightSection = `## Copyright ${[...allCopyrights] - .map((c) => `- ${c.replace(//g, ">")}`) + .map((c) => `- ${escapeBareUrls(c.replace(//g, ">"))}`) .join("\n")} `; } @@ -950,7 +986,7 @@ outline: [2, 3] ${badges} -${yaml.description && yaml.description !== displayName ? yaml.description + "\n" : ""}## Quick Install +${yaml.description && yaml.description !== displayName ? escapeBareUrls(yaml.description) + "\n" : ""}## Quick Install \`\`\`bash ${installCmd} diff --git a/docs/lychee.toml b/docs/lychee.toml index 462b85b30..49b32def8 100644 --- a/docs/lychee.toml +++ b/docs/lychee.toml @@ -34,5 +34,8 @@ exclude_all_private = false include_mail = false ############################# Other ############################# -include_verbatim = true +# Only check URLs in //markdown links — skip URLs that appear +# verbatim in code blocks (license/EULA text) or plain text (descriptions). +# Those are informational mentions, not links we maintain. +include_verbatim = false index_files = ["index.html"]