1 |
const { lstatSync, read, readdirSync, existsSync } = require("fs"); |
2 |
const { readFile, writeFile, cp } = require("fs/promises"); |
3 |
const { glob } = require("glob"); |
4 |
const path = require("path"); |
5 |
|
6 |
(async () => { |
7 |
const pages = await glob("app/**/page.{tsx,mdx}"); |
8 |
const index = []; |
9 |
|
10 |
async function loadDocsIndex( |
11 |
directory = path.resolve(__dirname, "app/(docs)"), |
12 |
href = "/", |
13 |
) { |
14 |
const data = []; |
15 |
const files = readdirSync(directory); |
16 |
|
17 |
for (const filename of files) { |
18 |
console.log("INDEXING", filename, directory); |
19 |
const file = path.resolve(directory, filename); |
20 |
const stat = lstatSync(file); |
21 |
|
22 |
if (stat.isDirectory()) { |
23 |
const info = await loadDocsIndex( |
24 |
file, |
25 |
`${href}${href === "/" ? "" : "/"}${filename}`, |
26 |
); |
27 |
const i = info.children.findIndex( |
28 |
c => c.name === "page.mdx" || c.name === "page.tsx", |
29 |
); |
30 |
const removed = i !== -1 ? info.children.splice(i, 1)[0] : null; |
31 |
|
32 |
data.push( |
33 |
i !== -1 |
34 |
? { |
35 |
...info, |
36 |
children: info.children, |
37 |
type: "page", |
38 |
data: info.data ?? removed.data, |
39 |
} |
40 |
: info, |
41 |
); |
42 |
|
43 |
continue; |
44 |
} |
45 |
|
46 |
if (filename !== "page.tsx" && filename !== "page.mdx") continue; |
47 |
|
48 |
const isMDX = file.endsWith(".mdx"); |
49 |
const info = isMDX |
50 |
? await generateIndexForMDXPage(file) |
51 |
: await generateIndexForTSXPage(file); |
52 |
|
53 |
data.push({ |
54 |
type: "page", |
55 |
name: path.basename(filename), |
56 |
url: file |
57 |
.replace(/[\/\\]\([a-z0-9A-Z_-]+\)/gi, "") |
58 |
.replace(/^app[\/\\]/gi, "") |
59 |
.replace(/page\.(ts|md)x$/gi, "") |
60 |
.replace(/\\/g, "/"), |
61 |
path: file.replace(/\\/g, "/"), |
62 |
data: { |
63 |
title: info.title, |
64 |
short_name: info.short_name, |
65 |
}, |
66 |
}); |
67 |
|
68 |
console.log("NESTED INDEXED", file); |
69 |
} |
70 |
|
71 |
const name = path.basename(directory); |
72 |
|
73 |
const dirdata = existsSync(path.join(directory, "metadata.json")) |
74 |
? JSON.parse( |
75 |
await readFile( |
76 |
path.join(directory, "metadata.json"), |
77 |
"utf-8", |
78 |
), |
79 |
) |
80 |
: null; |
81 |
const children = [...(dirdata?.entries ?? []), ...data]; |
82 |
|
83 |
children.sort((a, b) => { |
84 |
if (dirdata?.sort_as) { |
85 |
const aIndex = dirdata.sort_as.indexOf(a.name); |
86 |
const bIndex = dirdata.sort_as.indexOf(b.name); |
87 |
|
88 |
if (aIndex !== -1 && bIndex !== -1) { |
89 |
return aIndex - bIndex; |
90 |
} else if (aIndex !== -1) { |
91 |
return -1; |
92 |
} else if (bIndex !== -1) { |
93 |
return 1; |
94 |
} |
95 |
} |
96 |
|
97 |
return a.name.localeCompare(b.name); |
98 |
}); |
99 |
|
100 |
return { |
101 |
type: "directory", |
102 |
name: name === "(docs)" ? "/" : name, |
103 |
children: children, |
104 |
href, |
105 |
data: dirdata |
106 |
? { |
107 |
title: dirdata.title, |
108 |
short_name: dirdata.short_name, |
109 |
} |
110 |
: undefined, |
111 |
}; |
112 |
} |
113 |
|
114 |
for (const page of pages) { |
115 |
const isMDX = page.endsWith(".mdx"); |
116 |
|
117 |
index.push( |
118 |
isMDX |
119 |
? await generateIndexForMDXPage(page) |
120 |
: await generateIndexForTSXPage(page), |
121 |
); |
122 |
|
123 |
console.log("DONE ", page); |
124 |
} |
125 |
|
126 |
await writeFile(path.join(__dirname, "index.json"), JSON.stringify(index)); |
127 |
|
128 |
await writeFile( |
129 |
path.join(__dirname, "docs_index.json"), |
130 |
JSON.stringify(await loadDocsIndex(), null, 4), |
131 |
); |
132 |
|
133 |
try { |
134 |
await cp( |
135 |
path.join(__dirname, "index.json"), |
136 |
path.join(__dirname, ".next/server/index.json"), |
137 |
); |
138 |
} catch (error) { |
139 |
console.error(error); |
140 |
} |
141 |
})(); |
142 |
|
143 |
async function generateIndexForMDXPage(page) { |
144 |
const contents = await readFile(page, { |
145 |
encoding: "utf-8", |
146 |
}); |
147 |
let [frontmatter, data] = contents |
148 |
.substring(contents.startsWith("---") ? 3 : 0) |
149 |
.split("\n---\n"); |
150 |
|
151 |
if (!contents.trimStart().startsWith("---")) { |
152 |
data = frontmatter; |
153 |
frontmatter = null; |
154 |
} |
155 |
|
156 |
const entries = frontmatter |
157 |
?.split("\n") |
158 |
.filter(Boolean) |
159 |
.map(entry => |
160 |
entry |
161 |
.split(/:(.*)/s) |
162 |
.filter(Boolean) |
163 |
.map((a, i) => { |
164 |
const trimmed = a.trim(); |
165 |
return i === 1 && |
166 |
trimmed.startsWith('"') && |
167 |
trimmed.endsWith('"') |
168 |
? trimmed.substring(1, trimmed.length - 1).trim() |
169 |
: trimmed; |
170 |
}), |
171 |
); |
172 |
|
173 |
const frontmatterData = entries ? Object.fromEntries(entries) : null; |
174 |
|
175 |
return { |
176 |
...frontmatterData, |
177 |
data: (data ?? "") |
178 |
.replace(/^(([\s\r\n]*)import([^.]+);)+/gi, "") |
179 |
.replace(/^(([\s\r\n]*)export([^.]+);)+/gi, "") |
180 |
.replace(/([\s\r\n]*)export default ([^;]+);$/gi, "") |
181 |
.replace(/<\/?[^>]+(>|$)/g, ""), |
182 |
url: |
183 |
"/" + |
184 |
page |
185 |
.replace(/[\/\\]\([a-z0-9A-Z_-]+\)/gi, "") |
186 |
.replace(/^app[\/\\]/gi, "") |
187 |
.replace(/page\.(ts|md)x$/gi, "") |
188 |
.replace(/\\/g, "/"), |
189 |
path: page.replace(/\\/g, "/"), |
190 |
}; |
191 |
} |
192 |
|
193 |
async function generateIndexForTSXPage(page) { |
194 |
throw new Error("Not implemented"); |
195 |
} |