@marlinjai/clearify 1.11.2 → 1.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/dist/config-schema.json +225 -0
- package/dist/node/{chunk-PVTUVUBK.js → chunk-2WML2D77.js} +162 -11
- package/dist/node/{chunk-QTGT76RY.js → chunk-CTL4YL7C.js} +21 -0
- package/dist/node/{chunk-PFX7LHRZ.js → chunk-GWYMXKDL.js} +134 -8
- package/dist/node/{chunk-NGWAMBXS.js → chunk-HZRIVY25.js} +2 -2
- package/dist/node/{chunk-EMM4VLF7.js → chunk-XR4WG237.js} +5 -4
- package/dist/node/core/config.d.ts +78 -2
- package/dist/node/core/config.js +11 -3
- package/dist/node/core/navigation.js +1 -1
- package/dist/node/core/search.d.ts +1 -0
- package/dist/node/node/build.js +4 -4
- package/dist/node/node/check.js +3 -3
- package/dist/node/node/index.js +5 -5
- package/dist/node/types/index.d.ts +24 -2
- package/dist/node/vite-plugin/index.js +3 -3
- package/package.json +4 -2
- package/src/client/App.tsx +59 -16
- package/src/theme/Header.tsx +32 -0
- package/src/theme/admin/AdminDashboard.tsx +129 -0
- package/src/theme/admin/AdminLayout.tsx +240 -0
- package/src/theme/admin/ProjectsManager.tsx +546 -0
- package/src/theme/admin/SectionsManager.tsx +782 -0
- package/src/theme/admin/SiteSettings.tsx +414 -0
- package/src/theme/admin/components/ConfigPreview.tsx +166 -0
- package/src/theme/admin/components/ConfirmDialog.tsx +73 -0
- package/src/theme/admin/components/FormField.tsx +39 -0
- package/src/theme/admin/components/Modal.tsx +93 -0
- package/src/theme/admin/components/Toast.tsx +59 -0
- package/src/theme/admin/components/index.ts +4 -0
package/README.md
CHANGED
|
@@ -14,6 +14,7 @@ An open-source documentation site generator. Turn markdown into beautiful docs.
|
|
|
14
14
|
- **OpenAPI API Reference** — custom-built renderer for OpenAPI specs with code examples, schema viewer, and full design system integration
|
|
15
15
|
- **Multi-section support** — pill-based section switcher (e.g. public docs + internal docs)
|
|
16
16
|
- **Auto changelog** — drop a `CHANGELOG.md` in your project root, it appears as `/changelog`
|
|
17
|
+
- **README as landing page** — optionally include your project's `README.md` as the docs overview page
|
|
17
18
|
|
|
18
19
|
## Quick Start
|
|
19
20
|
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"type": "object",
|
|
4
|
+
"properties": {
|
|
5
|
+
"name": {
|
|
6
|
+
"type": "string"
|
|
7
|
+
},
|
|
8
|
+
"siteUrl": {
|
|
9
|
+
"type": "string"
|
|
10
|
+
},
|
|
11
|
+
"theme": {
|
|
12
|
+
"type": "object",
|
|
13
|
+
"properties": {
|
|
14
|
+
"primaryColor": {
|
|
15
|
+
"type": "string"
|
|
16
|
+
},
|
|
17
|
+
"mode": {
|
|
18
|
+
"type": "string",
|
|
19
|
+
"enum": [
|
|
20
|
+
"light",
|
|
21
|
+
"dark",
|
|
22
|
+
"auto"
|
|
23
|
+
]
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
"additionalProperties": false
|
|
27
|
+
},
|
|
28
|
+
"logo": {
|
|
29
|
+
"type": "object",
|
|
30
|
+
"properties": {
|
|
31
|
+
"light": {
|
|
32
|
+
"type": "string"
|
|
33
|
+
},
|
|
34
|
+
"dark": {
|
|
35
|
+
"type": "string"
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
"additionalProperties": false
|
|
39
|
+
},
|
|
40
|
+
"links": {
|
|
41
|
+
"type": "object",
|
|
42
|
+
"propertyNames": {
|
|
43
|
+
"type": "string"
|
|
44
|
+
},
|
|
45
|
+
"additionalProperties": {
|
|
46
|
+
"type": "string"
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
"sections": {
|
|
50
|
+
"type": "array",
|
|
51
|
+
"items": {
|
|
52
|
+
"type": "object",
|
|
53
|
+
"properties": {
|
|
54
|
+
"label": {
|
|
55
|
+
"type": "string"
|
|
56
|
+
},
|
|
57
|
+
"docsDir": {
|
|
58
|
+
"type": "string"
|
|
59
|
+
},
|
|
60
|
+
"basePath": {
|
|
61
|
+
"type": "string"
|
|
62
|
+
},
|
|
63
|
+
"draft": {
|
|
64
|
+
"type": "boolean"
|
|
65
|
+
},
|
|
66
|
+
"sitemap": {
|
|
67
|
+
"type": "boolean"
|
|
68
|
+
},
|
|
69
|
+
"exclude": {
|
|
70
|
+
"type": "array",
|
|
71
|
+
"items": {
|
|
72
|
+
"type": "string"
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
"git": {
|
|
76
|
+
"type": "object",
|
|
77
|
+
"properties": {
|
|
78
|
+
"repo": {
|
|
79
|
+
"type": "string"
|
|
80
|
+
},
|
|
81
|
+
"ref": {
|
|
82
|
+
"type": "string"
|
|
83
|
+
},
|
|
84
|
+
"path": {
|
|
85
|
+
"type": "string"
|
|
86
|
+
},
|
|
87
|
+
"sparse": {
|
|
88
|
+
"type": "boolean"
|
|
89
|
+
}
|
|
90
|
+
},
|
|
91
|
+
"required": [
|
|
92
|
+
"repo"
|
|
93
|
+
],
|
|
94
|
+
"additionalProperties": false
|
|
95
|
+
}
|
|
96
|
+
},
|
|
97
|
+
"required": [
|
|
98
|
+
"label",
|
|
99
|
+
"docsDir"
|
|
100
|
+
],
|
|
101
|
+
"additionalProperties": false
|
|
102
|
+
}
|
|
103
|
+
},
|
|
104
|
+
"hub": {
|
|
105
|
+
"type": "object",
|
|
106
|
+
"properties": {
|
|
107
|
+
"projects": {
|
|
108
|
+
"default": [],
|
|
109
|
+
"type": "array",
|
|
110
|
+
"items": {
|
|
111
|
+
"type": "object",
|
|
112
|
+
"properties": {
|
|
113
|
+
"name": {
|
|
114
|
+
"type": "string"
|
|
115
|
+
},
|
|
116
|
+
"description": {
|
|
117
|
+
"type": "string"
|
|
118
|
+
},
|
|
119
|
+
"href": {
|
|
120
|
+
"type": "string"
|
|
121
|
+
},
|
|
122
|
+
"repo": {
|
|
123
|
+
"type": "string"
|
|
124
|
+
},
|
|
125
|
+
"status": {
|
|
126
|
+
"default": "active",
|
|
127
|
+
"type": "string",
|
|
128
|
+
"enum": [
|
|
129
|
+
"active",
|
|
130
|
+
"beta",
|
|
131
|
+
"planned",
|
|
132
|
+
"deprecated"
|
|
133
|
+
]
|
|
134
|
+
},
|
|
135
|
+
"icon": {
|
|
136
|
+
"type": "string"
|
|
137
|
+
},
|
|
138
|
+
"tags": {
|
|
139
|
+
"type": "array",
|
|
140
|
+
"items": {
|
|
141
|
+
"type": "string"
|
|
142
|
+
}
|
|
143
|
+
},
|
|
144
|
+
"group": {
|
|
145
|
+
"type": "string"
|
|
146
|
+
},
|
|
147
|
+
"mode": {
|
|
148
|
+
"type": "string",
|
|
149
|
+
"enum": [
|
|
150
|
+
"link",
|
|
151
|
+
"embed",
|
|
152
|
+
"inject"
|
|
153
|
+
]
|
|
154
|
+
},
|
|
155
|
+
"git": {
|
|
156
|
+
"type": "object",
|
|
157
|
+
"properties": {
|
|
158
|
+
"repo": {
|
|
159
|
+
"type": "string"
|
|
160
|
+
},
|
|
161
|
+
"ref": {
|
|
162
|
+
"type": "string"
|
|
163
|
+
},
|
|
164
|
+
"path": {
|
|
165
|
+
"type": "string"
|
|
166
|
+
},
|
|
167
|
+
"sparse": {
|
|
168
|
+
"type": "boolean"
|
|
169
|
+
}
|
|
170
|
+
},
|
|
171
|
+
"required": [
|
|
172
|
+
"repo"
|
|
173
|
+
],
|
|
174
|
+
"additionalProperties": false
|
|
175
|
+
},
|
|
176
|
+
"embedSections": {
|
|
177
|
+
"anyOf": [
|
|
178
|
+
{
|
|
179
|
+
"type": "string",
|
|
180
|
+
"const": "all"
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
"type": "string",
|
|
184
|
+
"const": "public"
|
|
185
|
+
},
|
|
186
|
+
{
|
|
187
|
+
"type": "array",
|
|
188
|
+
"items": {
|
|
189
|
+
"type": "string"
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
]
|
|
193
|
+
},
|
|
194
|
+
"injectInto": {
|
|
195
|
+
"type": "string"
|
|
196
|
+
},
|
|
197
|
+
"docsPath": {
|
|
198
|
+
"type": "string"
|
|
199
|
+
}
|
|
200
|
+
},
|
|
201
|
+
"required": [
|
|
202
|
+
"name",
|
|
203
|
+
"description",
|
|
204
|
+
"status"
|
|
205
|
+
],
|
|
206
|
+
"additionalProperties": false
|
|
207
|
+
}
|
|
208
|
+
},
|
|
209
|
+
"scan": {
|
|
210
|
+
"type": "string"
|
|
211
|
+
},
|
|
212
|
+
"cacheDir": {
|
|
213
|
+
"type": "string"
|
|
214
|
+
}
|
|
215
|
+
},
|
|
216
|
+
"required": [
|
|
217
|
+
"projects"
|
|
218
|
+
],
|
|
219
|
+
"additionalProperties": false
|
|
220
|
+
}
|
|
221
|
+
},
|
|
222
|
+
"additionalProperties": false,
|
|
223
|
+
"$id": "https://docs.lumitra.co/schemas/clearify-data.json",
|
|
224
|
+
"title": "Clearify Data Config"
|
|
225
|
+
}
|
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
import { z } from "zod";
|
|
7
7
|
import { resolve, basename, dirname } from "path";
|
|
8
8
|
import { pathToFileURL } from "url";
|
|
9
|
-
import { existsSync, readFileSync, rmSync } from "fs";
|
|
9
|
+
import { existsSync, readFileSync, writeFileSync, renameSync, mkdirSync, rmSync, symlinkSync, readdirSync, statSync } from "fs";
|
|
10
10
|
import { tmpdir } from "os";
|
|
11
11
|
import { randomBytes } from "crypto";
|
|
12
12
|
var NavigationItemSchema = z.lazy(
|
|
@@ -51,13 +51,15 @@ var HubProjectSchema = z.object({
|
|
|
51
51
|
icon: z.string().optional(),
|
|
52
52
|
tags: z.array(z.string()).optional(),
|
|
53
53
|
group: z.string().optional(),
|
|
54
|
-
mode: z.enum(["link", "embed"]).optional(),
|
|
54
|
+
mode: z.enum(["link", "embed", "inject"]).optional(),
|
|
55
55
|
git: RemoteGitSourceSchema.optional(),
|
|
56
56
|
embedSections: z.union([
|
|
57
57
|
z.literal("all"),
|
|
58
58
|
z.literal("public"),
|
|
59
59
|
z.array(z.string())
|
|
60
|
-
]).optional()
|
|
60
|
+
]).optional(),
|
|
61
|
+
injectInto: z.string().optional(),
|
|
62
|
+
docsPath: z.string().optional()
|
|
61
63
|
});
|
|
62
64
|
var HubProjectPartialSchema = z.object({
|
|
63
65
|
description: z.string(),
|
|
@@ -98,8 +100,55 @@ var ClearifyConfigSchema = z.object({
|
|
|
98
100
|
hub: HubConfigSchema,
|
|
99
101
|
hubProject: HubProjectPartialSchema,
|
|
100
102
|
customCss: z.string().optional(),
|
|
101
|
-
headTags: z.array(z.string()).optional()
|
|
103
|
+
headTags: z.array(z.string()).optional(),
|
|
104
|
+
includeReadme: z.boolean().default(false)
|
|
102
105
|
});
|
|
106
|
+
var ClearifyDataSchema = z.object({
|
|
107
|
+
name: z.string().optional(),
|
|
108
|
+
siteUrl: z.string().optional(),
|
|
109
|
+
theme: z.object({
|
|
110
|
+
primaryColor: z.string().optional(),
|
|
111
|
+
mode: z.enum(["light", "dark", "auto"]).optional()
|
|
112
|
+
}).optional(),
|
|
113
|
+
logo: z.object({
|
|
114
|
+
light: z.string().optional(),
|
|
115
|
+
dark: z.string().optional()
|
|
116
|
+
}).optional(),
|
|
117
|
+
links: z.record(z.string(), z.string()).optional(),
|
|
118
|
+
sections: z.array(SectionConfigSchema).optional(),
|
|
119
|
+
hub: HubConfigSchema
|
|
120
|
+
});
|
|
121
|
+
function deepMergeJsonWins(base, json) {
|
|
122
|
+
const result = { ...base };
|
|
123
|
+
for (const key of Object.keys(json)) {
|
|
124
|
+
const jsonVal = json[key];
|
|
125
|
+
if (jsonVal === void 0) continue;
|
|
126
|
+
const baseVal = base[key];
|
|
127
|
+
if (baseVal !== null && jsonVal !== null && typeof baseVal === "object" && typeof jsonVal === "object" && !Array.isArray(baseVal) && !Array.isArray(jsonVal)) {
|
|
128
|
+
result[key] = deepMergeJsonWins(baseVal, jsonVal);
|
|
129
|
+
} else {
|
|
130
|
+
result[key] = jsonVal;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
return result;
|
|
134
|
+
}
|
|
135
|
+
function loadDataConfig(root) {
|
|
136
|
+
const dataPath = resolve(root, "clearify.data.json");
|
|
137
|
+
if (!existsSync(dataPath)) return {};
|
|
138
|
+
try {
|
|
139
|
+
const raw = JSON.parse(readFileSync(dataPath, "utf-8"));
|
|
140
|
+
return ClearifyDataSchema.parse(raw);
|
|
141
|
+
} catch {
|
|
142
|
+
return {};
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
function writeDataConfig(root, data) {
|
|
146
|
+
const validated = ClearifyDataSchema.parse(data);
|
|
147
|
+
const dataPath = resolve(root, "clearify.data.json");
|
|
148
|
+
const tmpPath = dataPath + ".tmp";
|
|
149
|
+
writeFileSync(tmpPath, JSON.stringify(validated, null, 2) + "\n", "utf-8");
|
|
150
|
+
renameSync(tmpPath, dataPath);
|
|
151
|
+
}
|
|
103
152
|
var defaultConfig = {
|
|
104
153
|
name: "Documentation",
|
|
105
154
|
docsDir: "./docs",
|
|
@@ -176,20 +225,24 @@ async function loadUserConfig(root) {
|
|
|
176
225
|
"clearify.config.js",
|
|
177
226
|
"clearify.config.mjs"
|
|
178
227
|
];
|
|
228
|
+
let tsConfig = {};
|
|
179
229
|
for (const file of configFiles) {
|
|
180
230
|
const configPath = resolve(root, file);
|
|
181
231
|
if (existsSync(configPath)) {
|
|
182
232
|
try {
|
|
183
233
|
if (file.endsWith(".ts")) {
|
|
184
|
-
|
|
234
|
+
tsConfig = await loadTsConfig(configPath);
|
|
235
|
+
} else {
|
|
236
|
+
const mod = await import(pathToFileURL(configPath).href);
|
|
237
|
+
tsConfig = mod.default ?? mod;
|
|
185
238
|
}
|
|
186
|
-
|
|
187
|
-
return mod.default ?? mod;
|
|
239
|
+
break;
|
|
188
240
|
} catch {
|
|
189
241
|
}
|
|
190
242
|
}
|
|
191
243
|
}
|
|
192
|
-
|
|
244
|
+
const jsonData = loadDataConfig(root);
|
|
245
|
+
return deepMergeJsonWins(tsConfig, jsonData);
|
|
193
246
|
}
|
|
194
247
|
function resolveConfig(userConfig = {}, root) {
|
|
195
248
|
const parsed = ClearifyConfigSchema.parse(userConfig);
|
|
@@ -247,8 +300,43 @@ async function resolveSections(config, root) {
|
|
|
247
300
|
}
|
|
248
301
|
];
|
|
249
302
|
}
|
|
303
|
+
function buildStagingOverlay(stagingDir, originalDir, injections) {
|
|
304
|
+
if (existsSync(stagingDir)) {
|
|
305
|
+
rmSync(stagingDir, { recursive: true, force: true });
|
|
306
|
+
}
|
|
307
|
+
mkdirSync(stagingDir, { recursive: true });
|
|
308
|
+
const ancestorDirs = /* @__PURE__ */ new Set();
|
|
309
|
+
for (const targetRelPath of injections.keys()) {
|
|
310
|
+
const parts = targetRelPath.split("/");
|
|
311
|
+
for (let i = 1; i <= parts.length; i++) {
|
|
312
|
+
ancestorDirs.add(parts.slice(0, i).join("/"));
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
function mirrorDir(srcDir, destDir, relPrefix) {
|
|
316
|
+
if (!existsSync(srcDir)) return;
|
|
317
|
+
const entries = readdirSync(srcDir);
|
|
318
|
+
for (const entry of entries) {
|
|
319
|
+
const srcPath = resolve(srcDir, entry);
|
|
320
|
+
const destPath = resolve(destDir, entry);
|
|
321
|
+
const relPath = relPrefix ? `${relPrefix}/${entry}` : entry;
|
|
322
|
+
if (ancestorDirs.has(relPath) && statSync(srcPath).isDirectory()) {
|
|
323
|
+
mkdirSync(destPath, { recursive: true });
|
|
324
|
+
mirrorDir(srcPath, destPath, relPath);
|
|
325
|
+
} else {
|
|
326
|
+
symlinkSync(srcPath, destPath);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
mirrorDir(originalDir, stagingDir, "");
|
|
331
|
+
for (const [relPath, sourcePath] of injections) {
|
|
332
|
+
const destPath = resolve(stagingDir, relPath);
|
|
333
|
+
const parentDir = dirname(destPath);
|
|
334
|
+
mkdirSync(parentDir, { recursive: true });
|
|
335
|
+
symlinkSync(sourcePath, destPath);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
250
338
|
async function scanHubProjects(config, root) {
|
|
251
|
-
if (!config.hub?.scan && !config.hub?.projects?.some((p) => p.mode === "embed")) {
|
|
339
|
+
if (!config.hub?.scan && !config.hub?.projects?.some((p) => p.mode === "embed" || p.mode === "inject")) {
|
|
252
340
|
return config;
|
|
253
341
|
}
|
|
254
342
|
const scannedProjects = [];
|
|
@@ -320,10 +408,69 @@ async function scanHubProjects(config, root) {
|
|
|
320
408
|
);
|
|
321
409
|
}
|
|
322
410
|
}
|
|
323
|
-
const
|
|
411
|
+
const injectionsBySection = /* @__PURE__ */ new Map();
|
|
412
|
+
for (const project of merged) {
|
|
413
|
+
if (project.mode !== "inject" || !project.git) continue;
|
|
414
|
+
const cacheDir = config.hub?.cacheDir ? resolve(root, config.hub.cacheDir) : resolve(root, "node_modules/.cache/clearify-remote");
|
|
415
|
+
try {
|
|
416
|
+
const { resolveRemoteSource } = await import("./core/remote.js");
|
|
417
|
+
const { localPath, cached } = await resolveRemoteSource(project.git, cacheDir);
|
|
418
|
+
const docsPath = project.docsPath ?? "docs";
|
|
419
|
+
const absoluteDocsPath = resolve(localPath, docsPath);
|
|
420
|
+
if (!existsSync(absoluteDocsPath)) {
|
|
421
|
+
console.warn(
|
|
422
|
+
` \u26A0 Hub inject "${project.name}": docs path "${docsPath}" not found in cloned repo.`
|
|
423
|
+
);
|
|
424
|
+
continue;
|
|
425
|
+
}
|
|
426
|
+
const targetLabel = project.injectInto;
|
|
427
|
+
if (!targetLabel) {
|
|
428
|
+
console.warn(` \u26A0 Hub inject "${project.name}": missing injectInto field.`);
|
|
429
|
+
continue;
|
|
430
|
+
}
|
|
431
|
+
if (!injectionsBySection.has(targetLabel)) {
|
|
432
|
+
injectionsBySection.set(targetLabel, /* @__PURE__ */ new Map());
|
|
433
|
+
}
|
|
434
|
+
const slugifiedName = slugify(project.name);
|
|
435
|
+
const relPath = project.group ? `projects/${project.group}/${slugifiedName}` : `projects/${slugifiedName}`;
|
|
436
|
+
injectionsBySection.get(targetLabel).set(relPath, absoluteDocsPath);
|
|
437
|
+
console.log(
|
|
438
|
+
` Hub inject "${project.name}": ${cached ? "cached" : "cloned"} \u2192 ${relPath}`
|
|
439
|
+
);
|
|
440
|
+
} catch (err) {
|
|
441
|
+
console.warn(
|
|
442
|
+
` \u26A0 Hub inject "${project.name}" failed:`,
|
|
443
|
+
err instanceof Error ? err.message : err
|
|
444
|
+
);
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
let modifiedSections = config.sections ? [...config.sections] : void 0;
|
|
448
|
+
if (injectionsBySection.size > 0 && modifiedSections) {
|
|
449
|
+
for (const [sectionLabel, injections] of injectionsBySection) {
|
|
450
|
+
const sectionIdx = modifiedSections.findIndex((s) => s.label === sectionLabel);
|
|
451
|
+
if (sectionIdx === -1) {
|
|
452
|
+
console.warn(` \u26A0 Hub inject: target section "${sectionLabel}" not found.`);
|
|
453
|
+
continue;
|
|
454
|
+
}
|
|
455
|
+
const section = modifiedSections[sectionIdx];
|
|
456
|
+
const sectionId = slugify(section.label);
|
|
457
|
+
const originalDocsDir = section.docsDir.startsWith("/") ? section.docsDir : resolve(root, section.docsDir);
|
|
458
|
+
const stagingDir = resolve(root, `node_modules/.cache/clearify-inject/${sectionId}`);
|
|
459
|
+
buildStagingOverlay(stagingDir, originalDocsDir, injections);
|
|
460
|
+
modifiedSections[sectionIdx] = {
|
|
461
|
+
...section,
|
|
462
|
+
docsDir: stagingDir
|
|
463
|
+
};
|
|
464
|
+
console.log(
|
|
465
|
+
` Hub inject: staging overlay for "${sectionLabel}" \u2192 ${stagingDir}`
|
|
466
|
+
);
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
const finalSections = [...modifiedSections ?? config.sections ?? [], ...embedSections];
|
|
470
|
+
const sectionsChanged = embedSections.length > 0 || injectionsBySection.size > 0;
|
|
324
471
|
return {
|
|
325
472
|
...config,
|
|
326
|
-
...
|
|
473
|
+
...sectionsChanged ? { sections: finalSections } : {},
|
|
327
474
|
hub: {
|
|
328
475
|
...config.hub,
|
|
329
476
|
projects: merged
|
|
@@ -332,6 +479,10 @@ async function scanHubProjects(config, root) {
|
|
|
332
479
|
}
|
|
333
480
|
|
|
334
481
|
export {
|
|
482
|
+
ClearifyDataSchema,
|
|
483
|
+
deepMergeJsonWins,
|
|
484
|
+
loadDataConfig,
|
|
485
|
+
writeDataConfig,
|
|
335
486
|
defaultConfig,
|
|
336
487
|
loadUserConfig,
|
|
337
488
|
resolveConfig,
|
|
@@ -133,9 +133,30 @@ function buildRoutes(docs, sectionId) {
|
|
|
133
133
|
...sectionId ? { sectionId } : {}
|
|
134
134
|
}));
|
|
135
135
|
}
|
|
136
|
+
function extractReadmeTitle(content) {
|
|
137
|
+
const match = content.match(/^#\s+(.+)$/m);
|
|
138
|
+
return match ? match[1].trim() : "Overview";
|
|
139
|
+
}
|
|
136
140
|
function buildSectionData(section, rootFiles, customNavigation) {
|
|
137
141
|
const docs = scanDocs(section.docsDir, section.exclude, section.basePath);
|
|
138
142
|
if (section.basePath === "/") {
|
|
143
|
+
if (rootFiles?.readmePath && existsSync(rootFiles.readmePath)) {
|
|
144
|
+
const hasIndex = docs.some((d) => d.routePath === "/");
|
|
145
|
+
if (!hasIndex) {
|
|
146
|
+
const content = readFileSync(rootFiles.readmePath, "utf-8");
|
|
147
|
+
const { data } = matter(content);
|
|
148
|
+
const title = extractReadmeTitle(content);
|
|
149
|
+
docs.unshift({
|
|
150
|
+
filePath: rootFiles.readmePath,
|
|
151
|
+
routePath: "/",
|
|
152
|
+
frontmatter: {
|
|
153
|
+
title: data.title ?? title,
|
|
154
|
+
description: data.description ?? "Project overview",
|
|
155
|
+
order: -1
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
}
|
|
139
160
|
if (rootFiles?.changelogPath && existsSync(rootFiles.changelogPath)) {
|
|
140
161
|
const content = readFileSync(rootFiles.changelogPath, "utf-8");
|
|
141
162
|
const { data } = matter(content);
|