@marlinjai/clearify 1.11.2 → 1.12.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/dist/config-schema.json +225 -0
- package/dist/node/{chunk-PVTUVUBK.js → chunk-2WLFSQS7.js} +160 -10
- package/dist/node/{chunk-NGWAMBXS.js → chunk-6XHVO5AO.js} +1 -1
- package/dist/node/{chunk-EMM4VLF7.js → chunk-6YAEHWC7.js} +2 -2
- package/dist/node/{chunk-PFX7LHRZ.js → chunk-XYS5SV4M.js} +128 -4
- package/dist/node/core/config.d.ts +78 -2
- package/dist/node/core/config.js +11 -3
- package/dist/node/node/build.js +3 -3
- package/dist/node/node/check.js +2 -2
- package/dist/node/node/index.js +4 -4
- package/dist/node/types/index.d.ts +23 -2
- package/dist/node/vite-plugin/index.js +2 -2
- 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
|
@@ -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(),
|
|
@@ -100,6 +102,52 @@ var ClearifyConfigSchema = z.object({
|
|
|
100
102
|
customCss: z.string().optional(),
|
|
101
103
|
headTags: z.array(z.string()).optional()
|
|
102
104
|
});
|
|
105
|
+
var ClearifyDataSchema = z.object({
|
|
106
|
+
name: z.string().optional(),
|
|
107
|
+
siteUrl: z.string().optional(),
|
|
108
|
+
theme: z.object({
|
|
109
|
+
primaryColor: z.string().optional(),
|
|
110
|
+
mode: z.enum(["light", "dark", "auto"]).optional()
|
|
111
|
+
}).optional(),
|
|
112
|
+
logo: z.object({
|
|
113
|
+
light: z.string().optional(),
|
|
114
|
+
dark: z.string().optional()
|
|
115
|
+
}).optional(),
|
|
116
|
+
links: z.record(z.string(), z.string()).optional(),
|
|
117
|
+
sections: z.array(SectionConfigSchema).optional(),
|
|
118
|
+
hub: HubConfigSchema
|
|
119
|
+
});
|
|
120
|
+
function deepMergeJsonWins(base, json) {
|
|
121
|
+
const result = { ...base };
|
|
122
|
+
for (const key of Object.keys(json)) {
|
|
123
|
+
const jsonVal = json[key];
|
|
124
|
+
if (jsonVal === void 0) continue;
|
|
125
|
+
const baseVal = base[key];
|
|
126
|
+
if (baseVal !== null && jsonVal !== null && typeof baseVal === "object" && typeof jsonVal === "object" && !Array.isArray(baseVal) && !Array.isArray(jsonVal)) {
|
|
127
|
+
result[key] = deepMergeJsonWins(baseVal, jsonVal);
|
|
128
|
+
} else {
|
|
129
|
+
result[key] = jsonVal;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return result;
|
|
133
|
+
}
|
|
134
|
+
function loadDataConfig(root) {
|
|
135
|
+
const dataPath = resolve(root, "clearify.data.json");
|
|
136
|
+
if (!existsSync(dataPath)) return {};
|
|
137
|
+
try {
|
|
138
|
+
const raw = JSON.parse(readFileSync(dataPath, "utf-8"));
|
|
139
|
+
return ClearifyDataSchema.parse(raw);
|
|
140
|
+
} catch {
|
|
141
|
+
return {};
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
function writeDataConfig(root, data) {
|
|
145
|
+
const validated = ClearifyDataSchema.parse(data);
|
|
146
|
+
const dataPath = resolve(root, "clearify.data.json");
|
|
147
|
+
const tmpPath = dataPath + ".tmp";
|
|
148
|
+
writeFileSync(tmpPath, JSON.stringify(validated, null, 2) + "\n", "utf-8");
|
|
149
|
+
renameSync(tmpPath, dataPath);
|
|
150
|
+
}
|
|
103
151
|
var defaultConfig = {
|
|
104
152
|
name: "Documentation",
|
|
105
153
|
docsDir: "./docs",
|
|
@@ -176,20 +224,24 @@ async function loadUserConfig(root) {
|
|
|
176
224
|
"clearify.config.js",
|
|
177
225
|
"clearify.config.mjs"
|
|
178
226
|
];
|
|
227
|
+
let tsConfig = {};
|
|
179
228
|
for (const file of configFiles) {
|
|
180
229
|
const configPath = resolve(root, file);
|
|
181
230
|
if (existsSync(configPath)) {
|
|
182
231
|
try {
|
|
183
232
|
if (file.endsWith(".ts")) {
|
|
184
|
-
|
|
233
|
+
tsConfig = await loadTsConfig(configPath);
|
|
234
|
+
} else {
|
|
235
|
+
const mod = await import(pathToFileURL(configPath).href);
|
|
236
|
+
tsConfig = mod.default ?? mod;
|
|
185
237
|
}
|
|
186
|
-
|
|
187
|
-
return mod.default ?? mod;
|
|
238
|
+
break;
|
|
188
239
|
} catch {
|
|
189
240
|
}
|
|
190
241
|
}
|
|
191
242
|
}
|
|
192
|
-
|
|
243
|
+
const jsonData = loadDataConfig(root);
|
|
244
|
+
return deepMergeJsonWins(tsConfig, jsonData);
|
|
193
245
|
}
|
|
194
246
|
function resolveConfig(userConfig = {}, root) {
|
|
195
247
|
const parsed = ClearifyConfigSchema.parse(userConfig);
|
|
@@ -247,8 +299,43 @@ async function resolveSections(config, root) {
|
|
|
247
299
|
}
|
|
248
300
|
];
|
|
249
301
|
}
|
|
302
|
+
function buildStagingOverlay(stagingDir, originalDir, injections) {
|
|
303
|
+
if (existsSync(stagingDir)) {
|
|
304
|
+
rmSync(stagingDir, { recursive: true, force: true });
|
|
305
|
+
}
|
|
306
|
+
mkdirSync(stagingDir, { recursive: true });
|
|
307
|
+
const ancestorDirs = /* @__PURE__ */ new Set();
|
|
308
|
+
for (const targetRelPath of injections.keys()) {
|
|
309
|
+
const parts = targetRelPath.split("/");
|
|
310
|
+
for (let i = 1; i <= parts.length; i++) {
|
|
311
|
+
ancestorDirs.add(parts.slice(0, i).join("/"));
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
function mirrorDir(srcDir, destDir, relPrefix) {
|
|
315
|
+
if (!existsSync(srcDir)) return;
|
|
316
|
+
const entries = readdirSync(srcDir);
|
|
317
|
+
for (const entry of entries) {
|
|
318
|
+
const srcPath = resolve(srcDir, entry);
|
|
319
|
+
const destPath = resolve(destDir, entry);
|
|
320
|
+
const relPath = relPrefix ? `${relPrefix}/${entry}` : entry;
|
|
321
|
+
if (ancestorDirs.has(relPath) && statSync(srcPath).isDirectory()) {
|
|
322
|
+
mkdirSync(destPath, { recursive: true });
|
|
323
|
+
mirrorDir(srcPath, destPath, relPath);
|
|
324
|
+
} else {
|
|
325
|
+
symlinkSync(srcPath, destPath);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
mirrorDir(originalDir, stagingDir, "");
|
|
330
|
+
for (const [relPath, sourcePath] of injections) {
|
|
331
|
+
const destPath = resolve(stagingDir, relPath);
|
|
332
|
+
const parentDir = dirname(destPath);
|
|
333
|
+
mkdirSync(parentDir, { recursive: true });
|
|
334
|
+
symlinkSync(sourcePath, destPath);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
250
337
|
async function scanHubProjects(config, root) {
|
|
251
|
-
if (!config.hub?.scan && !config.hub?.projects?.some((p) => p.mode === "embed")) {
|
|
338
|
+
if (!config.hub?.scan && !config.hub?.projects?.some((p) => p.mode === "embed" || p.mode === "inject")) {
|
|
252
339
|
return config;
|
|
253
340
|
}
|
|
254
341
|
const scannedProjects = [];
|
|
@@ -320,10 +407,69 @@ async function scanHubProjects(config, root) {
|
|
|
320
407
|
);
|
|
321
408
|
}
|
|
322
409
|
}
|
|
323
|
-
const
|
|
410
|
+
const injectionsBySection = /* @__PURE__ */ new Map();
|
|
411
|
+
for (const project of merged) {
|
|
412
|
+
if (project.mode !== "inject" || !project.git) continue;
|
|
413
|
+
const cacheDir = config.hub?.cacheDir ? resolve(root, config.hub.cacheDir) : resolve(root, "node_modules/.cache/clearify-remote");
|
|
414
|
+
try {
|
|
415
|
+
const { resolveRemoteSource } = await import("./core/remote.js");
|
|
416
|
+
const { localPath, cached } = await resolveRemoteSource(project.git, cacheDir);
|
|
417
|
+
const docsPath = project.docsPath ?? "docs";
|
|
418
|
+
const absoluteDocsPath = resolve(localPath, docsPath);
|
|
419
|
+
if (!existsSync(absoluteDocsPath)) {
|
|
420
|
+
console.warn(
|
|
421
|
+
` \u26A0 Hub inject "${project.name}": docs path "${docsPath}" not found in cloned repo.`
|
|
422
|
+
);
|
|
423
|
+
continue;
|
|
424
|
+
}
|
|
425
|
+
const targetLabel = project.injectInto;
|
|
426
|
+
if (!targetLabel) {
|
|
427
|
+
console.warn(` \u26A0 Hub inject "${project.name}": missing injectInto field.`);
|
|
428
|
+
continue;
|
|
429
|
+
}
|
|
430
|
+
if (!injectionsBySection.has(targetLabel)) {
|
|
431
|
+
injectionsBySection.set(targetLabel, /* @__PURE__ */ new Map());
|
|
432
|
+
}
|
|
433
|
+
const slugifiedName = slugify(project.name);
|
|
434
|
+
const relPath = project.group ? `projects/${project.group}/${slugifiedName}` : `projects/${slugifiedName}`;
|
|
435
|
+
injectionsBySection.get(targetLabel).set(relPath, absoluteDocsPath);
|
|
436
|
+
console.log(
|
|
437
|
+
` Hub inject "${project.name}": ${cached ? "cached" : "cloned"} \u2192 ${relPath}`
|
|
438
|
+
);
|
|
439
|
+
} catch (err) {
|
|
440
|
+
console.warn(
|
|
441
|
+
` \u26A0 Hub inject "${project.name}" failed:`,
|
|
442
|
+
err instanceof Error ? err.message : err
|
|
443
|
+
);
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
let modifiedSections = config.sections ? [...config.sections] : void 0;
|
|
447
|
+
if (injectionsBySection.size > 0 && modifiedSections) {
|
|
448
|
+
for (const [sectionLabel, injections] of injectionsBySection) {
|
|
449
|
+
const sectionIdx = modifiedSections.findIndex((s) => s.label === sectionLabel);
|
|
450
|
+
if (sectionIdx === -1) {
|
|
451
|
+
console.warn(` \u26A0 Hub inject: target section "${sectionLabel}" not found.`);
|
|
452
|
+
continue;
|
|
453
|
+
}
|
|
454
|
+
const section = modifiedSections[sectionIdx];
|
|
455
|
+
const sectionId = slugify(section.label);
|
|
456
|
+
const originalDocsDir = section.docsDir.startsWith("/") ? section.docsDir : resolve(root, section.docsDir);
|
|
457
|
+
const stagingDir = resolve(root, `node_modules/.cache/clearify-inject/${sectionId}`);
|
|
458
|
+
buildStagingOverlay(stagingDir, originalDocsDir, injections);
|
|
459
|
+
modifiedSections[sectionIdx] = {
|
|
460
|
+
...section,
|
|
461
|
+
docsDir: stagingDir
|
|
462
|
+
};
|
|
463
|
+
console.log(
|
|
464
|
+
` Hub inject: staging overlay for "${sectionLabel}" \u2192 ${stagingDir}`
|
|
465
|
+
);
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
const finalSections = [...modifiedSections ?? config.sections ?? [], ...embedSections];
|
|
469
|
+
const sectionsChanged = embedSections.length > 0 || injectionsBySection.size > 0;
|
|
324
470
|
return {
|
|
325
471
|
...config,
|
|
326
|
-
...
|
|
472
|
+
...sectionsChanged ? { sections: finalSections } : {},
|
|
327
473
|
hub: {
|
|
328
474
|
...config.hub,
|
|
329
475
|
projects: merged
|
|
@@ -332,6 +478,10 @@ async function scanHubProjects(config, root) {
|
|
|
332
478
|
}
|
|
333
479
|
|
|
334
480
|
export {
|
|
481
|
+
ClearifyDataSchema,
|
|
482
|
+
deepMergeJsonWins,
|
|
483
|
+
loadDataConfig,
|
|
484
|
+
writeDataConfig,
|
|
335
485
|
defaultConfig,
|
|
336
486
|
loadUserConfig,
|
|
337
487
|
resolveConfig,
|
|
@@ -8,12 +8,12 @@ import {
|
|
|
8
8
|
} from "./chunk-SCZZB7OE.js";
|
|
9
9
|
import {
|
|
10
10
|
clearifyPlugin
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-XYS5SV4M.js";
|
|
12
12
|
import {
|
|
13
13
|
loadUserConfig,
|
|
14
14
|
resolveConfig,
|
|
15
15
|
resolveSections
|
|
16
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-2WLFSQS7.js";
|
|
17
17
|
import {
|
|
18
18
|
buildSectionData
|
|
19
19
|
} from "./chunk-QTGT76RY.js";
|
|
@@ -2,18 +2,23 @@ import {
|
|
|
2
2
|
parseOpenAPISpec
|
|
3
3
|
} from "./chunk-B2Q23JW3.js";
|
|
4
4
|
import {
|
|
5
|
+
ClearifyDataSchema,
|
|
6
|
+
deepMergeJsonWins,
|
|
7
|
+
loadDataConfig,
|
|
5
8
|
loadUserConfig,
|
|
6
9
|
resolveConfig,
|
|
7
10
|
resolveSections,
|
|
8
|
-
scanHubProjects
|
|
9
|
-
|
|
11
|
+
scanHubProjects,
|
|
12
|
+
writeDataConfig
|
|
13
|
+
} from "./chunk-2WLFSQS7.js";
|
|
10
14
|
import {
|
|
11
15
|
buildSectionData
|
|
12
16
|
} from "./chunk-QTGT76RY.js";
|
|
13
17
|
|
|
14
18
|
// src/vite-plugin/index.ts
|
|
15
|
-
import { resolve } from "path";
|
|
16
|
-
import {
|
|
19
|
+
import { resolve, dirname } from "path";
|
|
20
|
+
import { fileURLToPath } from "url";
|
|
21
|
+
import { existsSync, readFileSync, readdirSync, statSync } from "fs";
|
|
17
22
|
import { dereference } from "@scalar/openapi-parser";
|
|
18
23
|
var VIRTUAL_CONFIG = "virtual:clearify/config";
|
|
19
24
|
var RESOLVED_VIRTUAL_CONFIG = "\0" + VIRTUAL_CONFIG;
|
|
@@ -29,6 +34,8 @@ var VIRTUAL_OPENAPI_SPEC = "virtual:clearify/openapi-spec";
|
|
|
29
34
|
var RESOLVED_VIRTUAL_OPENAPI_SPEC = "\0" + VIRTUAL_OPENAPI_SPEC;
|
|
30
35
|
var VIRTUAL_HUB = "virtual:clearify/hub";
|
|
31
36
|
var RESOLVED_VIRTUAL_HUB = "\0" + VIRTUAL_HUB;
|
|
37
|
+
var VIRTUAL_ADMIN_ENABLED = "virtual:clearify/admin-enabled";
|
|
38
|
+
var RESOLVED_VIRTUAL_ADMIN_ENABLED = "\0" + VIRTUAL_ADMIN_ENABLED;
|
|
32
39
|
function findFirstNavPath(items) {
|
|
33
40
|
for (const item of items) {
|
|
34
41
|
if (item.path) return item.path;
|
|
@@ -241,6 +248,7 @@ ${routeEntries.join(",\n")}
|
|
|
241
248
|
if (id === VIRTUAL_MERMAID_SVGS) return RESOLVED_VIRTUAL_MERMAID_SVGS;
|
|
242
249
|
if (id === VIRTUAL_OPENAPI_SPEC) return RESOLVED_VIRTUAL_OPENAPI_SPEC;
|
|
243
250
|
if (id === VIRTUAL_HUB) return RESOLVED_VIRTUAL_HUB;
|
|
251
|
+
if (id === VIRTUAL_ADMIN_ENABLED) return RESOLVED_VIRTUAL_ADMIN_ENABLED;
|
|
244
252
|
return null;
|
|
245
253
|
},
|
|
246
254
|
async load(id) {
|
|
@@ -266,6 +274,10 @@ ${routeEntries.join(",\n")}
|
|
|
266
274
|
const hubData = config.hub ?? null;
|
|
267
275
|
return `export default ${JSON.stringify(hubData)};`;
|
|
268
276
|
}
|
|
277
|
+
if (id === RESOLVED_VIRTUAL_ADMIN_ENABLED) {
|
|
278
|
+
const enabled = resolvedViteConfig.command === "serve";
|
|
279
|
+
return `export default ${enabled};`;
|
|
280
|
+
}
|
|
269
281
|
return null;
|
|
270
282
|
},
|
|
271
283
|
configureServer(server) {
|
|
@@ -410,6 +422,118 @@ ${routeEntries.join(",\n")}
|
|
|
410
422
|
});
|
|
411
423
|
}
|
|
412
424
|
});
|
|
425
|
+
const dataJsonPath = resolve(userRoot, "clearify.data.json");
|
|
426
|
+
server.watcher.add(dataJsonPath);
|
|
427
|
+
server.watcher.on("change", (changedPath) => {
|
|
428
|
+
if (changedPath === dataJsonPath) {
|
|
429
|
+
for (const resolvedId of [
|
|
430
|
+
RESOLVED_VIRTUAL_CONFIG,
|
|
431
|
+
RESOLVED_VIRTUAL_ROUTES,
|
|
432
|
+
RESOLVED_VIRTUAL_NAVIGATION,
|
|
433
|
+
RESOLVED_VIRTUAL_SEARCH_INDEX
|
|
434
|
+
]) {
|
|
435
|
+
const mod = server.moduleGraph.getModuleById(resolvedId);
|
|
436
|
+
if (mod) server.moduleGraph.invalidateModule(mod);
|
|
437
|
+
}
|
|
438
|
+
server.ws.send({ type: "full-reload" });
|
|
439
|
+
}
|
|
440
|
+
});
|
|
441
|
+
function readJsonBody(req) {
|
|
442
|
+
return new Promise((resolve2, reject) => {
|
|
443
|
+
const chunks = [];
|
|
444
|
+
req.on("data", (chunk) => chunks.push(chunk));
|
|
445
|
+
req.on("end", () => {
|
|
446
|
+
try {
|
|
447
|
+
resolve2(JSON.parse(Buffer.concat(chunks).toString()));
|
|
448
|
+
} catch {
|
|
449
|
+
reject(new Error("Invalid JSON"));
|
|
450
|
+
}
|
|
451
|
+
});
|
|
452
|
+
req.on("error", reject);
|
|
453
|
+
});
|
|
454
|
+
}
|
|
455
|
+
server.middlewares.use(async (req, res, next) => {
|
|
456
|
+
const url = req.url?.split("?") ?? [""];
|
|
457
|
+
const pathname = url[0];
|
|
458
|
+
const query = new URLSearchParams(url[1] ?? "");
|
|
459
|
+
if (!pathname.startsWith("/__clearify/api/")) return next();
|
|
460
|
+
res.setHeader("Content-Type", "application/json");
|
|
461
|
+
try {
|
|
462
|
+
if (req.method === "GET" && pathname === "/__clearify/api/config") {
|
|
463
|
+
res.end(JSON.stringify(config));
|
|
464
|
+
return;
|
|
465
|
+
}
|
|
466
|
+
if (req.method === "GET" && pathname === "/__clearify/api/config/data") {
|
|
467
|
+
const data = loadDataConfig(userRoot);
|
|
468
|
+
res.end(JSON.stringify(data));
|
|
469
|
+
return;
|
|
470
|
+
}
|
|
471
|
+
if (req.method === "PUT" && pathname === "/__clearify/api/config/data") {
|
|
472
|
+
const body = await readJsonBody(req);
|
|
473
|
+
const result = ClearifyDataSchema.safeParse(body);
|
|
474
|
+
if (!result.success) {
|
|
475
|
+
res.statusCode = 400;
|
|
476
|
+
res.end(JSON.stringify({ error: "Validation failed", issues: result.error.issues }));
|
|
477
|
+
return;
|
|
478
|
+
}
|
|
479
|
+
writeDataConfig(userRoot, body);
|
|
480
|
+
res.end(JSON.stringify({ ok: true }));
|
|
481
|
+
return;
|
|
482
|
+
}
|
|
483
|
+
if (req.method === "PATCH" && pathname === "/__clearify/api/config/data") {
|
|
484
|
+
const body = await readJsonBody(req);
|
|
485
|
+
const existing = loadDataConfig(userRoot);
|
|
486
|
+
const merged = deepMergeJsonWins(existing, body);
|
|
487
|
+
const result = ClearifyDataSchema.safeParse(merged);
|
|
488
|
+
if (!result.success) {
|
|
489
|
+
res.statusCode = 400;
|
|
490
|
+
res.end(JSON.stringify({ error: "Validation failed", issues: result.error.issues }));
|
|
491
|
+
return;
|
|
492
|
+
}
|
|
493
|
+
writeDataConfig(userRoot, merged);
|
|
494
|
+
res.end(JSON.stringify({ ok: true }));
|
|
495
|
+
return;
|
|
496
|
+
}
|
|
497
|
+
if (req.method === "GET" && pathname === "/__clearify/api/config/schema") {
|
|
498
|
+
const pkgDir = dirname(fileURLToPath(import.meta.url));
|
|
499
|
+
const schemaPath = resolve(pkgDir, "../../dist/config-schema.json");
|
|
500
|
+
if (existsSync(schemaPath)) {
|
|
501
|
+
res.end(readFileSync(schemaPath, "utf-8"));
|
|
502
|
+
} else {
|
|
503
|
+
res.statusCode = 404;
|
|
504
|
+
res.end(JSON.stringify({ error: "Schema file not found" }));
|
|
505
|
+
}
|
|
506
|
+
return;
|
|
507
|
+
}
|
|
508
|
+
if (req.method === "GET" && pathname === "/__clearify/api/fs/dirs") {
|
|
509
|
+
const rootParam = query.get("root") ?? ".";
|
|
510
|
+
const target = resolve(userRoot, rootParam);
|
|
511
|
+
if (!target.startsWith(userRoot)) {
|
|
512
|
+
res.statusCode = 403;
|
|
513
|
+
res.end(JSON.stringify({ error: "Path outside project root" }));
|
|
514
|
+
return;
|
|
515
|
+
}
|
|
516
|
+
if (!existsSync(target)) {
|
|
517
|
+
res.end(JSON.stringify({ dirs: [] }));
|
|
518
|
+
return;
|
|
519
|
+
}
|
|
520
|
+
const dirs = readdirSync(target).filter((entry) => {
|
|
521
|
+
try {
|
|
522
|
+
return statSync(resolve(target, entry)).isDirectory();
|
|
523
|
+
} catch {
|
|
524
|
+
return false;
|
|
525
|
+
}
|
|
526
|
+
});
|
|
527
|
+
res.end(JSON.stringify({ dirs }));
|
|
528
|
+
return;
|
|
529
|
+
}
|
|
530
|
+
res.statusCode = 404;
|
|
531
|
+
res.end(JSON.stringify({ error: "Not found" }));
|
|
532
|
+
} catch (err) {
|
|
533
|
+
res.statusCode = err.message === "Invalid JSON" ? 400 : 500;
|
|
534
|
+
res.end(JSON.stringify({ error: err.message ?? "Internal server error" }));
|
|
535
|
+
}
|
|
536
|
+
});
|
|
413
537
|
return () => {
|
|
414
538
|
server.middlewares.use((req, _res, next) => {
|
|
415
539
|
if (req.method !== "GET" || !req.headers.accept?.includes("text/html")) {
|