diff --git a/eslint.config.mjs b/eslint.config.mjs index dc16add9..a29871f1 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -1,16 +1,7 @@ -import { dirname } from "path"; -import { fileURLToPath } from "url"; -import { FlatCompat } from "@eslint/eslintrc"; - -const __filename = fileURLToPath(import.meta.url); -const __dirname = dirname(__filename); - -const compat = new FlatCompat({ - baseDirectory: __dirname, -}); +import coreWebVitals from "eslint-config-next/core-web-vitals"; const eslintConfig = [ - ...compat.extends("next/core-web-vitals"), + ...coreWebVitals, ]; export default eslintConfig; diff --git a/mdx/rehype.mjs b/mdx/rehype.mjs index 593f8b36..0ddd7c78 100644 --- a/mdx/rehype.mjs +++ b/mdx/rehype.mjs @@ -36,6 +36,20 @@ function rehypeInsertLastUpdated() { const formatted = formatLastUpdated(iso) if (!formatted) return + // Exposed to pageProps via recma-nextjs-static-props so _app.jsx can emit + // per-page SEO metadata without shipping the full route→date map client-side. + const exportStr = `export const lastUpdated = ${JSON.stringify(iso)}` + tree.children.push({ + type: 'mdxjsEsm', + value: exportStr, + data: { + estree: acorn.parse(exportStr, { + sourceType: 'module', + ecmaVersion: 'latest', + }), + }, + }) + const node = { type: 'element', tagName: 'p', diff --git a/scripts/generate-github-routes.mjs b/scripts/generate-github-routes.mjs index 1ceb8c29..96827585 100644 --- a/scripts/generate-github-routes.mjs +++ b/scripts/generate-github-routes.mjs @@ -1,7 +1,9 @@ #!/usr/bin/env node /** - * Generates src/lib/edit-on-github-routes.js by scanning src/pages for index.mdx. - * Used by Layout.jsx so "Edit on GitHub" links point to .../index.mdx for those routes. + * Generates src/lib/edit-on-github-routes.js by scanning src/pages for .mdx files. + * Used by Layout.jsx so "Edit on GitHub" links point to .../index.mdx for index + * routes, and so GitHub links render only on routes backed by a real MDX file + * (e.g. not on the prerendered 404/500 pages, where pathname is /_error). * Run automatically with dev and build. */ @@ -14,29 +16,37 @@ const ROOT = path.join(__dirname, '..') const PAGES_DIR = path.join(ROOT, 'src/pages') const OUT_PATH = path.join(ROOT, 'src/lib/edit-on-github-routes.js') -function findIndexMdxRoutes(dir, basePath = '') { +function findMdxRoutes(dir, basePath = '') { const entries = fs.readdirSync(dir, { withFileTypes: true }) - const routes = [] + const indexRoutes = [] + const allRoutes = [] for (const e of entries) { const rel = basePath ? `${basePath}/${e.name}` : e.name if (e.isDirectory()) { - routes.push(...findIndexMdxRoutes(path.join(dir, e.name), rel)) + const nested = findMdxRoutes(path.join(dir, e.name), rel) + indexRoutes.push(...nested.indexRoutes) + allRoutes.push(...nested.allRoutes) } else if (e.name === 'index.mdx') { - routes.push('/' + basePath) + indexRoutes.push('/' + basePath) + allRoutes.push('/' + basePath) + } else if (e.name.endsWith('.mdx')) { + allRoutes.push('/' + rel.replace(/\.mdx$/, '')) } } - return routes + return { indexRoutes, allRoutes } } -const routes = findIndexMdxRoutes(PAGES_DIR) - .filter((r) => r !== '') // ignore root index if any - .sort() +const { indexRoutes, allRoutes } = findMdxRoutes(PAGES_DIR) +const routes = indexRoutes.filter((r) => r !== '/').sort() // ignore root index if any +const mdxRoutes = allRoutes.filter((r) => r !== '/').sort() const content = `// Auto-generated by scripts/generate-github-routes.mjs – do not edit /** Pathnames served by src/pages/.../index.mdx (Edit on GitHub must link to /index.mdx). */ export const EDIT_ON_GITHUB_INDEX_ROUTES = new Set(${JSON.stringify(routes)}); +/** Every pathname backed by an .mdx file under src/pages. GitHub links are hidden elsewhere. */ +export const MDX_PAGE_ROUTES = new Set(${JSON.stringify(mdxRoutes)}); ` fs.mkdirSync(path.dirname(OUT_PATH), { recursive: true }) fs.writeFileSync(OUT_PATH, content, 'utf8') -console.log('Generated', OUT_PATH, 'with', routes.length, 'index routes') +console.log('Generated', OUT_PATH, 'with', routes.length, 'index routes,', mdxRoutes.length, 'mdx routes') diff --git a/scripts/generate-last-updated.mjs b/scripts/generate-last-updated.mjs index 1425f554..f4d18a76 100644 --- a/scripts/generate-last-updated.mjs +++ b/scripts/generate-last-updated.mjs @@ -60,3 +60,19 @@ export const LAST_UPDATED_BY_ROUTE = ${JSON.stringify(map, null, 2)}; fs.mkdirSync(path.dirname(OUT_PATH), { recursive: true }) fs.writeFileSync(OUT_PATH, content, 'utf8') console.log('Generated', OUT_PATH, 'with', Object.keys(map).length, 'dated routes') + +if (Object.keys(map).length === 0) { + const msg = + '[last-updated] no routes received a date, git history is unavailable or the clone is shallow' + // Hard-fail only where full history is guaranteed (the GitHub workflow + // checks out with fetch-depth: 0). Other CI environments like Vercel + // shallow-clone by design, so there a missing map is expected: warn and + // ship pages without "Updated" dates instead of breaking the deploy. + if (process.env.GITHUB_ACTIONS) { + console.error( + `${msg}. Failing the build: the workflow checks out with fetch-depth: 0, so an empty map means something is broken.` + ) + process.exit(1) + } + console.warn(`${msg}. Pages will render without an "Updated" date.`) +} diff --git a/src/components/Layout.jsx b/src/components/Layout.jsx index c2ab7843..a54a6e11 100644 --- a/src/components/Layout.jsx +++ b/src/components/Layout.jsx @@ -12,12 +12,12 @@ import {NavigationAPI} from "@/components/NavigationAPI"; import {motion} from "framer-motion"; import {Footer} from "@/components/Footer"; import {FontAwesomeIcon} from "@fortawesome/react-fontawesome"; -import { faPaperclip } from '@fortawesome/free-solid-svg-icons'; +import { faPaperclip, faClockRotateLeft } from '@fortawesome/free-solid-svg-icons'; import { faGithub } from '@fortawesome/free-brands-svg-icons'; import {toast} from "react-toastify"; import {AnnouncementBanner} from "@/components/announcement-banner/AnnouncementBanner"; import {useAnnouncements} from "@/components/announcement-banner/AnnouncementBannerProvider"; -import { EDIT_ON_GITHUB_INDEX_ROUTES } from '@/lib/edit-on-github-routes' +import { EDIT_ON_GITHUB_INDEX_ROUTES, MDX_PAGE_ROUTES } from '@/lib/edit-on-github-routes' const navigation = [ { @@ -227,16 +227,30 @@ export function Layout({ children, title, tableOfContents }) { Copy link -
  • - - - Edit on Github - -
  • + {MDX_PAGE_ROUTES.has(router.pathname) && ( + <> +
  • + + + Edit on GitHub + +
  • +
  • + + + View history + +
  • + + )}