because I got bored of customising my CV for every job
1import type { ComponentType } from "react";
2
3/**
4 * Type guard to check if a value is a React component
5 */
6const isReactComponent = (value: unknown): value is ComponentType => {
7 return typeof value === "function";
8};
9
10/**
11 * Type guard to check if a value is an object with a default property
12 */
13const hasDefaultProperty = (value: unknown): value is { default: unknown } => {
14 return (
15 value !== null &&
16 typeof value === "object" &&
17 "default" in value &&
18 value.default !== undefined
19 );
20};
21
22/**
23 * Extract MDX component from a module (for both .md and .mdx files)
24 * Handles both direct component exports and default exports
25 */
26const extractMDXComponent = (module: unknown): ComponentType | null => {
27 if (isReactComponent(module)) {
28 return module;
29 }
30
31 if (hasDefaultProperty(module)) {
32 const defaultExport = module.default;
33 if (isReactComponent(defaultExport)) {
34 return defaultExport;
35 }
36 }
37
38 return null;
39};
40
41/**
42 * Auto-discovered markdown and MDX files as MDX components
43 * Both .md and .mdx files are processed by the MDX plugin
44 * Lazy loading enabled for better performance
45 */
46const mdxModules = import.meta.glob("../../content/**/*.{md,mdx}", {
47 eager: false,
48});
49
50/**
51 * Get the MDX component for a given documentation slug
52 *
53 * Normalizes the slug and searches for matching files in the content directory.
54 * Handles README files as index files (e.g., "api/readme" matches slug "api").
55 * Returns a promise that resolves to the component for lazy loading.
56 *
57 * @param slug - The documentation slug (e.g., "docs/architecture", "components/button")
58 * @returns Promise that resolves to the React component for the documentation page, or null if not found
59 */
60export const getDocComponent = async (
61 slug: string,
62): Promise<ComponentType | null> => {
63 const normalizedSlug = slug.toLowerCase().replace(/^\/+|\/+$/g, "");
64
65 for (const [path, moduleLoader] of Object.entries(mdxModules)) {
66 const normalizedPath = path
67 .replace(/^\.\.\/\.\.\/content\//, "")
68 .replace(/\.(md|mdx)$/, "")
69 .toLowerCase();
70
71 let matches = normalizedPath === normalizedSlug;
72
73 if (!matches && normalizedPath.endsWith("/readme")) {
74 const dirPath = normalizedPath.replace(/\/readme$/, "");
75 matches =
76 dirPath === normalizedSlug || dirPath === `${normalizedSlug}/readme`;
77 }
78
79 if (!matches && normalizedPath === `${normalizedSlug}/readme`) {
80 matches = true;
81 }
82
83 if (matches) {
84 const module = await moduleLoader();
85 const component = extractMDXComponent(module);
86
87 if (component) {
88 return component;
89 }
90 }
91 }
92
93 return null;
94};
95
96/**
97 * Get a list of all available documentation slugs
98 *
99 * Extracts slugs from all discovered markdown and MDX files,
100 * excluding README files and empty slugs.
101 *
102 * @returns Array of normalized documentation slugs
103 */
104export const getAvailableDocs = (): string[] => {
105 return Object.keys(mdxModules)
106 .map((path) => {
107 const normalizedPath = path
108 .replace(/^\.\.\/\.\.\/content\//, "")
109 .replace(/\.(md|mdx)$/, "");
110 return normalizedPath.toLowerCase();
111 })
112 .filter((slug) => {
113 const filename = slug.split("/").pop() ?? "";
114 return filename !== "readme" && slug.length > 0;
115 });
116};