@clarify-labs/cli 0.7.2 → 0.8.2
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 +7 -0
- package/output/cli/program.d.ts +2 -0
- package/output/cli.cjs +41 -11
- package/output/cli.cjs.map +1 -1
- package/output/cli.js +41 -11
- package/output/cli.js.map +1 -1
- package/output/core/config-schema.d.ts +12 -16
- package/output/core/content.d.ts +5 -0
- package/output/core/env-types.d.ts +8 -0
- package/output/core/hooks.d.ts +3 -2
- package/output/core/options.d.ts +1 -1
- package/output/core/plugin.d.ts +0 -1
- package/output/core/ssg.d.ts +1 -1
- package/output/core/virtual-modules.d.ts +29 -16
- package/output/index.cjs +1 -1
- package/output/index.d.ts +1 -0
- package/output/index.js +1 -1
- package/output/parsers/content.d.ts +11 -0
- package/output/parsers/routes.d.ts +9 -3
- package/output/parsers/variables.d.ts +2 -0
- package/output/{plugin-o1rP_gts.js → plugin-D5EF3pYl.js} +620 -224
- package/output/plugin-D5EF3pYl.js.map +1 -0
- package/output/{plugin-DFpkIm9j.cjs → plugin-DyceHdXd.cjs} +620 -224
- package/output/plugin-DyceHdXd.cjs.map +1 -0
- package/output/plugins/content-artifacts/artifacts.d.ts +1 -3
- package/output/plugins/openapi/parser.d.ts +4 -1
- package/output/plugins/openapi/virtual-modules.d.ts +15 -3
- package/output/plugins/seo/artifacts.d.ts +0 -1
- package/output/plugins/variables/index.d.ts +2 -0
- package/output/types.d.ts +35 -7
- package/package.json +6 -3
- package/output/plugin-DFpkIm9j.cjs.map +0 -1
- package/output/plugin-o1rP_gts.js.map +0 -1
|
@@ -35,15 +35,15 @@ let shiki = require("shiki");
|
|
|
35
35
|
shiki = __toESM(shiki, 1);
|
|
36
36
|
let unist_util_visit = require("unist-util-visit");
|
|
37
37
|
let _clarify_labs_renderer = require("@clarify-labs/renderer");
|
|
38
|
-
let
|
|
38
|
+
let yaml = require("yaml");
|
|
39
39
|
let gray_matter = require("gray-matter");
|
|
40
40
|
gray_matter = __toESM(gray_matter, 1);
|
|
41
|
+
let remark = require("remark");
|
|
42
|
+
let node_url = require("node:url");
|
|
41
43
|
let _apidevtools_swagger_parser = require("@apidevtools/swagger-parser");
|
|
42
44
|
_apidevtools_swagger_parser = __toESM(_apidevtools_swagger_parser, 1);
|
|
43
45
|
let node_os = require("node:os");
|
|
44
|
-
let node_fs_promises = require("node:fs/promises");
|
|
45
46
|
let zod = require("zod");
|
|
46
|
-
let node_url = require("node:url");
|
|
47
47
|
let vite = require("vite");
|
|
48
48
|
let node_module = require("node:module");
|
|
49
49
|
//#region source/core/utils.ts
|
|
@@ -150,17 +150,16 @@ function withUtf8Signature(content) {
|
|
|
150
150
|
}
|
|
151
151
|
function shouldUseUtf8Signature(route) {
|
|
152
152
|
if (route.kind === "mdx") return true;
|
|
153
|
-
return
|
|
153
|
+
return false;
|
|
154
154
|
}
|
|
155
155
|
function routeToMarkdownArtifactUrl(routePath) {
|
|
156
156
|
return `${routePath === "/" ? "/index" : routePath.replace(/\/$/, "")}.md`;
|
|
157
157
|
}
|
|
158
|
-
function routeToOpenAPIArtifactUrl(routePath
|
|
159
|
-
|
|
160
|
-
return `${routePath === "/" ? "/index" : routePath.replace(/\/$/, "")}.openapi${extension}`;
|
|
158
|
+
function routeToOpenAPIArtifactUrl(routePath) {
|
|
159
|
+
return `${routePath === "/" ? "/index" : routePath.replace(/\/$/, "")}.openapi.json`;
|
|
161
160
|
}
|
|
162
161
|
function attachContentArtifactUrls(routes) {
|
|
163
|
-
for (const route of routes) route.contentArtifactUrl = route.kind === "openapi" ? routeToOpenAPIArtifactUrl(route.path
|
|
162
|
+
for (const route of routes) route.contentArtifactUrl = route.kind === "openapi" ? routeToOpenAPIArtifactUrl(route.path) : routeToMarkdownArtifactUrl(route.path);
|
|
164
163
|
}
|
|
165
164
|
function readRouteContent(route) {
|
|
166
165
|
if (route.content !== void 0) return route.content;
|
|
@@ -170,40 +169,81 @@ function readRouteArtifactContent(route) {
|
|
|
170
169
|
const content = readRouteContent(route);
|
|
171
170
|
return shouldUseUtf8Signature(route) ? withUtf8Signature(content) : content;
|
|
172
171
|
}
|
|
173
|
-
function
|
|
172
|
+
function isLlmsTxtRoute(route) {
|
|
173
|
+
return !route.path.split("/").includes("404");
|
|
174
|
+
}
|
|
175
|
+
function llmsTxtDescription(route) {
|
|
176
|
+
if (route.description) return route.description;
|
|
177
|
+
const sections = route.sections?.filter((section) => section.level === 2).slice(0, 3).map((section) => section.title);
|
|
178
|
+
if (sections?.length) return `Covers ${sections.join(", ")}.`;
|
|
179
|
+
if (route.keywords?.length) return `Related topics: ${route.keywords.join(", ")}.`;
|
|
180
|
+
if (route.kind === "openapi") return "OpenAPI artifact for machine-readable API reference data.";
|
|
181
|
+
}
|
|
182
|
+
function llmsTxtListItem(route, basePath) {
|
|
183
|
+
if (!route.contentArtifactUrl) return void 0;
|
|
184
|
+
const description = llmsTxtDescription(route);
|
|
185
|
+
return description ? `- [${route.title}](${basePath}${route.contentArtifactUrl}): ${description}` : `- [${route.title}](${basePath}${route.contentArtifactUrl})`;
|
|
186
|
+
}
|
|
187
|
+
function llmsTxtLocaleLabel(locale, projectConfig) {
|
|
188
|
+
return projectConfig.i18n?.locales.find((item) => item.code === locale)?.label ?? locale;
|
|
189
|
+
}
|
|
190
|
+
function groupRoutesByLocale(routes) {
|
|
191
|
+
const groups = /* @__PURE__ */ new Map();
|
|
174
192
|
for (const route of routes) {
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
(0, node_fs.mkdirSync)((0, node_path.dirname)(outFile), { recursive: true });
|
|
178
|
-
(0, node_fs.writeFileSync)(outFile, readRouteArtifactContent(route), "utf-8");
|
|
193
|
+
const key = route.locale ?? "default";
|
|
194
|
+
groups.set(key, [...groups.get(key) ?? [], route]);
|
|
179
195
|
}
|
|
196
|
+
return groups;
|
|
197
|
+
}
|
|
198
|
+
function groupLlmsTxtRoutesByLocale(routes, projectConfig) {
|
|
199
|
+
const routesBySource = /* @__PURE__ */ new Map();
|
|
200
|
+
const defaultLocale = projectConfig.i18n?.defaultLocale;
|
|
201
|
+
for (const route of routes) {
|
|
202
|
+
const sourceKey = `${route.locale ?? defaultLocale ?? "default"}:${route.basePath ?? route.contentArtifactUrl ?? route.path}`;
|
|
203
|
+
const previousRoute = routesBySource.get(sourceKey);
|
|
204
|
+
if (!previousRoute || previousRoute.locale && !route.locale) routesBySource.set(sourceKey, route);
|
|
205
|
+
}
|
|
206
|
+
return groupRoutesByLocale([...routesBySource.values()].map((route) => ({
|
|
207
|
+
...route,
|
|
208
|
+
locale: route.locale ?? defaultLocale
|
|
209
|
+
})));
|
|
210
|
+
}
|
|
211
|
+
function llmsTxtDocsSectionTitle(locale, groupCount, projectConfig) {
|
|
212
|
+
if (groupCount <= 1) return "Docs";
|
|
213
|
+
return `Docs - ${locale === "default" ? "Default" : llmsTxtLocaleLabel(locale, projectConfig)}`;
|
|
180
214
|
}
|
|
181
215
|
function createLlmsTxt(routes, projectConfig) {
|
|
182
216
|
const basePath = normalizeBasePath(projectConfig.routePrefix);
|
|
183
217
|
const lines = [`# ${projectConfig.title}`, ""];
|
|
184
218
|
if (projectConfig.description) lines.push(`> ${projectConfig.description}`, "");
|
|
185
|
-
lines.push("
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
219
|
+
lines.push("This file lists the source-ready Markdown and OpenAPI artifacts for this documentation site.", "");
|
|
220
|
+
const mdxRoutes = routes.filter((route) => route.kind === "mdx" && isLlmsTxtRoute(route));
|
|
221
|
+
if (mdxRoutes.length > 0) {
|
|
222
|
+
const localizedGroups = groupLlmsTxtRoutesByLocale(mdxRoutes, projectConfig);
|
|
223
|
+
for (const [locale, localeRoutes] of localizedGroups) {
|
|
224
|
+
lines.push(`## ${llmsTxtDocsSectionTitle(locale, localizedGroups.size, projectConfig)}`);
|
|
225
|
+
for (const route of localeRoutes) {
|
|
226
|
+
const item = llmsTxtListItem(route, basePath);
|
|
227
|
+
if (item) lines.push(item);
|
|
228
|
+
}
|
|
229
|
+
lines.push("");
|
|
230
|
+
}
|
|
189
231
|
}
|
|
190
|
-
const openApiRoutes = routes.filter((route) => route.kind === "openapi");
|
|
232
|
+
const openApiRoutes = routes.filter((route) => route.kind === "openapi" && isLlmsTxtRoute(route));
|
|
191
233
|
if (openApiRoutes.length > 0) {
|
|
192
|
-
lines.
|
|
234
|
+
if (lines.at(-1) !== "") lines.push("");
|
|
235
|
+
lines.push("## OpenAPI");
|
|
193
236
|
for (const route of openApiRoutes) {
|
|
194
|
-
|
|
195
|
-
lines.push(
|
|
237
|
+
const item = llmsTxtListItem(route, basePath);
|
|
238
|
+
if (item) lines.push(item);
|
|
196
239
|
}
|
|
197
240
|
}
|
|
241
|
+
while (lines.at(-1) === "") lines.pop();
|
|
198
242
|
return `${lines.join("\n")}\n`;
|
|
199
243
|
}
|
|
200
244
|
function createLlmsTxtArtifact(routes, projectConfig) {
|
|
201
245
|
return withUtf8Signature(createLlmsTxt(routes, projectConfig));
|
|
202
246
|
}
|
|
203
|
-
function writeLlmsTxt(routes, projectConfig, outputDirectory) {
|
|
204
|
-
(0, node_fs.mkdirSync)(outputDirectory, { recursive: true });
|
|
205
|
-
(0, node_fs.writeFileSync)((0, node_path.join)(outputDirectory, "llms.txt"), createLlmsTxtArtifact(routes, projectConfig), "utf-8");
|
|
206
|
-
}
|
|
207
247
|
//#endregion
|
|
208
248
|
//#region source/plugins/content-artifacts/server.ts
|
|
209
249
|
function normalizeRoutePrefix$1(routePrefix) {
|
|
@@ -216,7 +256,6 @@ function resolveContentArtifactPath(url, projectConfig) {
|
|
|
216
256
|
return basePath && requestPath.startsWith(basePath) ? requestPath.slice(basePath.length) || "/" : requestPath;
|
|
217
257
|
}
|
|
218
258
|
function resolveContentArtifactType(route) {
|
|
219
|
-
if (route.kind === "openapi" && /\.ya?ml$/i.test(route.contentArtifactUrl ?? "")) return "text/yaml; charset=utf-8";
|
|
220
259
|
return route.kind === "openapi" ? "application/json; charset=utf-8" : "text/markdown; charset=utf-8";
|
|
221
260
|
}
|
|
222
261
|
function serveContentArtifacts(req, res, projectConfig, routes) {
|
|
@@ -227,6 +266,18 @@ function serveContentArtifacts(req, res, projectConfig, routes) {
|
|
|
227
266
|
res.end(createLlmsTxtArtifact(routes, projectConfig), "utf8");
|
|
228
267
|
return true;
|
|
229
268
|
}
|
|
269
|
+
if (contentPath.endsWith(".openapi.yaml")) {
|
|
270
|
+
const jsonPath = contentPath.replace(/\.yaml$/, ".json");
|
|
271
|
+
const route = routes.find((r) => r.contentArtifactUrl === jsonPath);
|
|
272
|
+
if (route?.kind === "openapi" && route.content) {
|
|
273
|
+
const spec = JSON.parse(route.content);
|
|
274
|
+
res.statusCode = 200;
|
|
275
|
+
res.setHeader("Content-Type", "text/yaml; charset=utf-8");
|
|
276
|
+
res.end((0, yaml.stringify)(spec, { lineWidth: 0 }), "utf8");
|
|
277
|
+
return true;
|
|
278
|
+
}
|
|
279
|
+
return false;
|
|
280
|
+
}
|
|
230
281
|
const route = routes.find((route) => route.contentArtifactUrl === contentPath);
|
|
231
282
|
if (!route) return false;
|
|
232
283
|
res.statusCode = 200;
|
|
@@ -250,12 +301,28 @@ function createContentArtifactsPlugin() {
|
|
|
250
301
|
next();
|
|
251
302
|
});
|
|
252
303
|
},
|
|
253
|
-
"build:
|
|
254
|
-
const
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
304
|
+
"build:assets": (ctx) => {
|
|
305
|
+
const assets = [];
|
|
306
|
+
for (const route of ctx.routes) {
|
|
307
|
+
if (!route.contentArtifactUrl) continue;
|
|
308
|
+
assets.push({
|
|
309
|
+
fileName: route.contentArtifactUrl.replace(/^\//, ""),
|
|
310
|
+
source: readRouteArtifactContent(route)
|
|
311
|
+
});
|
|
312
|
+
if (route.kind === "openapi" && route.content) {
|
|
313
|
+
const yamlFileName = route.contentArtifactUrl.replace(/\.json$/, ".yaml");
|
|
314
|
+
const spec = JSON.parse(route.content);
|
|
315
|
+
assets.push({
|
|
316
|
+
fileName: yamlFileName.replace(/^\//, ""),
|
|
317
|
+
source: (0, yaml.stringify)(spec, { lineWidth: 0 })
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
assets.push({
|
|
322
|
+
fileName: "llms.txt",
|
|
323
|
+
source: createLlmsTxtArtifact(ctx.routes, ctx.projectConfig)
|
|
324
|
+
});
|
|
325
|
+
return assets;
|
|
259
326
|
}
|
|
260
327
|
}
|
|
261
328
|
};
|
|
@@ -331,8 +398,103 @@ function parseFrontmatter(content) {
|
|
|
331
398
|
};
|
|
332
399
|
}
|
|
333
400
|
//#endregion
|
|
401
|
+
//#region source/parsers/content.ts
|
|
402
|
+
async function transformContent(transform, input) {
|
|
403
|
+
return transform ? await transform(input) : input;
|
|
404
|
+
}
|
|
405
|
+
function createContentProcessor(transform) {
|
|
406
|
+
return {
|
|
407
|
+
async processMdx(source, filePath) {
|
|
408
|
+
const { frontmatter, content } = parseFrontmatter(source);
|
|
409
|
+
const transformed = await transformContent(transform, {
|
|
410
|
+
kind: "mdx",
|
|
411
|
+
source,
|
|
412
|
+
filePath,
|
|
413
|
+
frontmatter,
|
|
414
|
+
content
|
|
415
|
+
});
|
|
416
|
+
return {
|
|
417
|
+
frontmatter: transformed.frontmatter,
|
|
418
|
+
content: transformed.content
|
|
419
|
+
};
|
|
420
|
+
},
|
|
421
|
+
async processText(source, kind, filePath) {
|
|
422
|
+
return (await transformContent(transform, {
|
|
423
|
+
kind,
|
|
424
|
+
source,
|
|
425
|
+
filePath,
|
|
426
|
+
frontmatter: {},
|
|
427
|
+
content: source
|
|
428
|
+
})).content;
|
|
429
|
+
}
|
|
430
|
+
};
|
|
431
|
+
}
|
|
432
|
+
//#endregion
|
|
433
|
+
//#region source/core/hooks.ts
|
|
434
|
+
async function runHooks(plugins, hookName, input, ctx) {
|
|
435
|
+
let result = input;
|
|
436
|
+
for (const plugin of plugins) {
|
|
437
|
+
const hook = plugin.hooks?.[hookName];
|
|
438
|
+
if (!hook) continue;
|
|
439
|
+
try {
|
|
440
|
+
result = await hook(result, ctx);
|
|
441
|
+
} catch (err) {
|
|
442
|
+
throw new Error(`[clarify] plugin "${plugin.name}" hook "${hookName}" failed: ${err}`, { cause: err });
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
return result;
|
|
446
|
+
}
|
|
447
|
+
async function runDevConfigureServerHooks(plugins, server, ctx) {
|
|
448
|
+
for (const plugin of plugins) {
|
|
449
|
+
const hook = plugin.hooks?.["dev:configureServer"];
|
|
450
|
+
if (!hook) continue;
|
|
451
|
+
try {
|
|
452
|
+
await hook(server, ctx);
|
|
453
|
+
} catch (err) {
|
|
454
|
+
throw new Error(`[clarify] plugin "${plugin.name}" hook "dev:configureServer" failed: ${err}`, { cause: err });
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
async function runBuildAssetsHooks(plugins, ctx) {
|
|
459
|
+
const assets = [];
|
|
460
|
+
for (const plugin of plugins) {
|
|
461
|
+
const hook = plugin.hooks?.["build:assets"];
|
|
462
|
+
if (!hook) continue;
|
|
463
|
+
try {
|
|
464
|
+
const pluginAssets = await hook(ctx);
|
|
465
|
+
assets.push(...pluginAssets);
|
|
466
|
+
} catch (err) {
|
|
467
|
+
throw new Error(`[clarify] plugin "${plugin.name}" hook "build:assets" failed: ${err}`, { cause: err });
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
return assets;
|
|
471
|
+
}
|
|
472
|
+
async function runBuildDoneHooks(plugins, ctx) {
|
|
473
|
+
for (const plugin of plugins) {
|
|
474
|
+
const hook = plugin.hooks?.["build:done"];
|
|
475
|
+
if (!hook) continue;
|
|
476
|
+
try {
|
|
477
|
+
await hook(ctx);
|
|
478
|
+
} catch (err) {
|
|
479
|
+
throw new Error(`[clarify] plugin "${plugin.name}" hook "build:done" failed: ${err}`, { cause: err });
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
//#endregion
|
|
484
|
+
//#region source/core/content.ts
|
|
485
|
+
var contentProcessors = /* @__PURE__ */ new WeakMap();
|
|
486
|
+
function createProjectContentProcessor(plugins, ctx) {
|
|
487
|
+
return createContentProcessor((input) => runHooks(plugins, "content:transform", input, ctx));
|
|
488
|
+
}
|
|
489
|
+
function setProjectContentProcessor(ctx, processor) {
|
|
490
|
+
contentProcessors.set(ctx, processor);
|
|
491
|
+
}
|
|
492
|
+
function getProjectContentProcessor(ctx) {
|
|
493
|
+
return contentProcessors.get(ctx);
|
|
494
|
+
}
|
|
495
|
+
//#endregion
|
|
334
496
|
//#region source/parsers/routes.ts
|
|
335
|
-
function kebabToTitle
|
|
497
|
+
function kebabToTitle(str) {
|
|
336
498
|
return str.split("-").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
|
|
337
499
|
}
|
|
338
500
|
function parseMdxTree(content) {
|
|
@@ -397,32 +559,33 @@ function routePathFromRef(ref, explicitPath) {
|
|
|
397
559
|
function virtualModuleIdFromRef(ref) {
|
|
398
560
|
return "virtual:clarify-page/" + ref.replace(/\.mdx?$/, "").replace(/\.openapi\.(json|yaml|yml)$/, "").replace(/\/+/g, "/");
|
|
399
561
|
}
|
|
400
|
-
function localizedRoutePath(basePath, locale,
|
|
562
|
+
function localizedRoutePath(basePath, locale, _i18n) {
|
|
401
563
|
const normalizedBasePath = normalizePath(basePath);
|
|
402
|
-
if (locale === i18n.defaultLocale) return normalizedBasePath;
|
|
403
564
|
const prefix = normalizePath(locale);
|
|
404
565
|
return normalizedBasePath === "/" ? prefix : normalizePath(`${prefix}${normalizedBasePath}`);
|
|
405
566
|
}
|
|
406
567
|
function resolveLocalizedText(text, locale, fallbackLocale) {
|
|
407
|
-
if (typeof text === "string"
|
|
568
|
+
if (typeof text === "string") return text;
|
|
569
|
+
if (text === void 0) return void 0;
|
|
408
570
|
return text[locale] ?? (fallbackLocale ? text[fallbackLocale] : void 0) ?? Object.values(text)[0];
|
|
409
571
|
}
|
|
410
|
-
function findContentRoutes(dir, base = dir) {
|
|
572
|
+
async function findContentRoutes(dir, base = dir, options = {}) {
|
|
411
573
|
const routes = [];
|
|
412
574
|
if (!(0, node_fs.existsSync)(dir)) return routes;
|
|
413
575
|
const entries = (0, node_fs.readdirSync)(dir, { withFileTypes: true });
|
|
414
576
|
for (const entry of entries) {
|
|
415
577
|
const fullPath = (0, node_path.join)(dir, entry.name);
|
|
416
|
-
if (entry.isDirectory()) routes.push(...findContentRoutes(fullPath, base));
|
|
578
|
+
if (entry.isDirectory()) routes.push(...await findContentRoutes(fullPath, base, options));
|
|
417
579
|
else if (entry.isFile() && /\.mdx?$/.test(entry.name)) {
|
|
418
580
|
const relativePath = (0, node_path.relative)(base, fullPath);
|
|
419
581
|
const pathParts = relativePath.replace(/\.mdx?$/, "").split("/");
|
|
420
582
|
const cleanPath = ("/" + pathParts.map((p) => p === "index" ? "" : p).filter(Boolean).join("/")).replace(/\/+/g, "/").replace(/\/$/, "") || "/";
|
|
421
|
-
const
|
|
583
|
+
const source = (0, node_fs.readFileSync)(fullPath, "utf-8");
|
|
584
|
+
const { frontmatter, content } = await (options.contentProcessor ?? createContentProcessor()).processMdx(source, fullPath);
|
|
422
585
|
let title = typeof frontmatter.title === "string" ? frontmatter.title : "";
|
|
423
586
|
if (!title) {
|
|
424
587
|
const lastPart = pathParts[pathParts.length - 1] ?? "";
|
|
425
|
-
title = kebabToTitle
|
|
588
|
+
title = kebabToTitle(lastPart === "index" ? pathParts.length >= 2 ? pathParts[pathParts.length - 2] : extractH1(content) : lastPart) || "Untitled";
|
|
426
589
|
}
|
|
427
590
|
routes.push({
|
|
428
591
|
path: cleanPath,
|
|
@@ -446,9 +609,9 @@ function frontmatterKeywords(frontmatter) {
|
|
|
446
609
|
const keywords = Array.isArray(value) ? value.filter((item) => typeof item === "string") : typeof value === "string" ? value.split(",").map((keyword) => keyword.trim()).filter(Boolean) : [];
|
|
447
610
|
return keywords.length > 0 ? keywords : void 0;
|
|
448
611
|
}
|
|
449
|
-
function withAlternates
|
|
612
|
+
function withAlternates(route, routes, i18n) {
|
|
450
613
|
const basePath = route.basePath ?? route.path;
|
|
451
|
-
const routeByLocaleAndBase = new Map(routes.map((
|
|
614
|
+
const routeByLocaleAndBase = new Map(routes.filter((r) => !r.locale || r.path === `/${r.locale}` || r.path.startsWith(`/${r.locale}/`)).map((r) => [`${r.locale ?? ""}:${r.basePath ?? r.path}`, r]));
|
|
452
615
|
const alternates = Object.fromEntries(i18n.locales.flatMap((locale) => {
|
|
453
616
|
const alternate = routeByLocaleAndBase.get(`${locale.code}:${basePath}`);
|
|
454
617
|
return alternate ? [[locale.code, alternate.path]] : [];
|
|
@@ -470,7 +633,7 @@ function buildNavigation(routes) {
|
|
|
470
633
|
if (!node) {
|
|
471
634
|
node = {
|
|
472
635
|
path: pathSoFar,
|
|
473
|
-
title: i === parts.length - 1 ? route.title : kebabToTitle
|
|
636
|
+
title: i === parts.length - 1 ? route.title : kebabToTitle(parts[i]),
|
|
474
637
|
children: []
|
|
475
638
|
};
|
|
476
639
|
current.push(node);
|
|
@@ -542,7 +705,21 @@ function applyConfiguredPageRoutePaths(routes, tabs, i18n) {
|
|
|
542
705
|
}
|
|
543
706
|
}
|
|
544
707
|
const nextRoutes = [...routes, ...additions];
|
|
545
|
-
|
|
708
|
+
const routesWithAlternates = i18n ? nextRoutes.map((route) => withAlternates(route, nextRoutes, i18n)) : nextRoutes;
|
|
709
|
+
if (!i18n) return routesWithAlternates;
|
|
710
|
+
const bareRoutes = [];
|
|
711
|
+
const seenBare = new Set(routesWithAlternates.map((r) => r.path));
|
|
712
|
+
for (const route of routesWithAlternates) {
|
|
713
|
+
if (route.locale !== i18n.defaultLocale) continue;
|
|
714
|
+
const bp = route.basePath ?? route.path;
|
|
715
|
+
if (bp === route.path || seenBare.has(bp)) continue;
|
|
716
|
+
seenBare.add(bp);
|
|
717
|
+
bareRoutes.push({
|
|
718
|
+
...route,
|
|
719
|
+
path: bp
|
|
720
|
+
});
|
|
721
|
+
}
|
|
722
|
+
return [...routesWithAlternates, ...bareRoutes];
|
|
546
723
|
}
|
|
547
724
|
function buildNavigationFromPagesConfig(routes, config) {
|
|
548
725
|
if (!config || config === "FileTree") return buildNavigation(routes);
|
|
@@ -570,7 +747,7 @@ function buildNavigationFromConfig(routes, config) {
|
|
|
570
747
|
const route = routeMap.get(path);
|
|
571
748
|
return {
|
|
572
749
|
path,
|
|
573
|
-
title: resolveLocalizedText(title, "", "") ?? route?.title ?? kebabToTitle
|
|
750
|
+
title: resolveLocalizedText(title, "", "") ?? route?.title ?? kebabToTitle(path.split("/").pop() ?? openapiRef),
|
|
574
751
|
icon,
|
|
575
752
|
sections: route?.sections ? navigationSections(route.sections) : void 0
|
|
576
753
|
};
|
|
@@ -580,7 +757,7 @@ function buildNavigationFromConfig(routes, config) {
|
|
|
580
757
|
const route = routeMap.get(path);
|
|
581
758
|
return {
|
|
582
759
|
path: redirect ? normalizePath(redirect) : path,
|
|
583
|
-
title: resolveLocalizedText(title, "", "") ?? route?.title ?? kebabToTitle
|
|
760
|
+
title: resolveLocalizedText(title, "", "") ?? route?.title ?? kebabToTitle(path.split("/").pop() ?? ref),
|
|
584
761
|
icon,
|
|
585
762
|
sections: route?.sections ? navigationSections(route.sections) : void 0
|
|
586
763
|
};
|
|
@@ -615,7 +792,7 @@ function buildLocalizedNavigationForLocale(routes, config, locale, i18n) {
|
|
|
615
792
|
const path = route?.path ?? localizedRoutePath(basePath, locale, i18n);
|
|
616
793
|
return {
|
|
617
794
|
path: (redirect ? localizedRoutePath(routePathFromRef(redirect), locale, i18n) : void 0) ?? path,
|
|
618
|
-
title: resolveLocalizedText(title, locale, i18n.defaultLocale) ?? route?.title ?? kebabToTitle
|
|
795
|
+
title: resolveLocalizedText(title, locale, i18n.defaultLocale) ?? route?.title ?? kebabToTitle(basePath.split("/").pop() ?? ref),
|
|
619
796
|
icon,
|
|
620
797
|
sections: route?.sections ? navigationSections(route.sections) : void 0
|
|
621
798
|
};
|
|
@@ -661,8 +838,12 @@ var OPENAPI_HTTP_METHODS = [
|
|
|
661
838
|
function errorMessage(error) {
|
|
662
839
|
return error instanceof Error ? error.message : String(error);
|
|
663
840
|
}
|
|
664
|
-
function
|
|
665
|
-
|
|
841
|
+
function isSameOpenAPIFile(url, filePath) {
|
|
842
|
+
try {
|
|
843
|
+
return (0, node_path.resolve)((0, node_url.fileURLToPath)(url)) === (0, node_path.resolve)(filePath);
|
|
844
|
+
} catch {
|
|
845
|
+
return (0, node_path.resolve)(url) === (0, node_path.resolve)(filePath);
|
|
846
|
+
}
|
|
666
847
|
}
|
|
667
848
|
function findOpenAPIRoutes(dir, base = dir) {
|
|
668
849
|
const routes = [];
|
|
@@ -683,8 +864,7 @@ function findOpenAPIRoutes(dir, base = dir) {
|
|
|
683
864
|
filePath: fullPath,
|
|
684
865
|
virtualModuleId: virtualModuleIdFromRef(ref),
|
|
685
866
|
title: kebabToTitle(cleanPath.split("/").pop() ?? "API"),
|
|
686
|
-
kind: "openapi"
|
|
687
|
-
content: (0, node_fs.readFileSync)(fullPath, "utf-8")
|
|
867
|
+
kind: "openapi"
|
|
688
868
|
});
|
|
689
869
|
}
|
|
690
870
|
return routes;
|
|
@@ -693,6 +873,20 @@ function operationMatchesTags(operationTags, filterTags) {
|
|
|
693
873
|
if (!filterTags?.length) return true;
|
|
694
874
|
return operationTags?.some((tag) => filterTags.includes(tag)) ?? false;
|
|
695
875
|
}
|
|
876
|
+
/** Filter an OpenAPI spec to only include paths that have at least one operation matching the given tags. */
|
|
877
|
+
function filterSpecByTags(spec, tags) {
|
|
878
|
+
const filteredPaths = {};
|
|
879
|
+
const paths = spec.paths ?? {};
|
|
880
|
+
for (const path of Object.keys(paths)) {
|
|
881
|
+
const pathItem = paths[path];
|
|
882
|
+
if (!pathItem) continue;
|
|
883
|
+
if (Object.values(pathItem).some((op) => op && typeof op === "object" && operationMatchesTags(op.tags, tags))) filteredPaths[path] = pathItem;
|
|
884
|
+
}
|
|
885
|
+
return {
|
|
886
|
+
...spec,
|
|
887
|
+
paths: filteredPaths
|
|
888
|
+
};
|
|
889
|
+
}
|
|
696
890
|
/** Extract endpoint operations from an OpenAPI spec as page sections. */
|
|
697
891
|
function extractOpenAPISections(spec, filterTags) {
|
|
698
892
|
const sections = [];
|
|
@@ -714,12 +908,26 @@ function extractOpenAPISections(spec, filterTags) {
|
|
|
714
908
|
}
|
|
715
909
|
return sections;
|
|
716
910
|
}
|
|
717
|
-
async function readOpenAPISpec(filePath) {
|
|
911
|
+
async function readOpenAPISpec(filePath, contentProcessor) {
|
|
718
912
|
try {
|
|
719
|
-
return {
|
|
913
|
+
if (!contentProcessor) return {
|
|
914
|
+
ok: true,
|
|
915
|
+
spec: await _apidevtools_swagger_parser.default.dereference(filePath)
|
|
916
|
+
};
|
|
917
|
+
const source = (0, node_fs.readFileSync)(filePath, "utf-8");
|
|
918
|
+
const transformedSource = await contentProcessor.processText(source, "openapi", filePath);
|
|
919
|
+
if (transformedSource === source) return {
|
|
720
920
|
ok: true,
|
|
721
921
|
spec: await _apidevtools_swagger_parser.default.dereference(filePath)
|
|
722
922
|
};
|
|
923
|
+
return {
|
|
924
|
+
ok: true,
|
|
925
|
+
spec: await _apidevtools_swagger_parser.default.dereference(filePath, { resolve: { transformedOpenAPISource: {
|
|
926
|
+
order: 1,
|
|
927
|
+
canRead: (file) => isSameOpenAPIFile(file.url, filePath),
|
|
928
|
+
read: () => transformedSource
|
|
929
|
+
} } })
|
|
930
|
+
};
|
|
723
931
|
} catch (error) {
|
|
724
932
|
return {
|
|
725
933
|
ok: false,
|
|
@@ -734,12 +942,14 @@ async function readOpenAPISpec(filePath) {
|
|
|
734
942
|
}
|
|
735
943
|
//#endregion
|
|
736
944
|
//#region source/core/virtual-modules.ts
|
|
737
|
-
var VIRTUAL_CONFIG = "virtual:clarify
|
|
738
|
-
var VIRTUAL_ROUTES = "virtual:clarify
|
|
739
|
-
var VIRTUAL_SERVER_ROUTES = "virtual:clarify
|
|
740
|
-
var
|
|
741
|
-
var
|
|
742
|
-
var
|
|
945
|
+
var VIRTUAL_CONFIG = "virtual:clarify/config";
|
|
946
|
+
var VIRTUAL_ROUTES = "virtual:clarify/routes";
|
|
947
|
+
var VIRTUAL_SERVER_ROUTES = "virtual:clarify/routes/server";
|
|
948
|
+
var VIRTUAL_OPENAPI = "virtual:clarify/openapi";
|
|
949
|
+
var VIRTUAL_SLOTS = "virtual:clarify/slots";
|
|
950
|
+
var VIRTUAL_SLOT = "virtual:clarify/slot";
|
|
951
|
+
var VIRTUAL_CLIENT_ENTRY = "virtual:clarify/entry-client";
|
|
952
|
+
var RESOLVED_CLIENT_ENTRY = "\0virtual:clarify/entry-client";
|
|
743
953
|
function resolveVirtualId(id) {
|
|
744
954
|
return "\0" + id;
|
|
745
955
|
}
|
|
@@ -780,65 +990,117 @@ function generateRoutesModule(routes, resolvedNavigation, projectConfig, mode =
|
|
|
780
990
|
const navigation = resolvedNavigation ?? (projectConfig?.tabs ? projectConfig.i18n ? buildLocalizedNavigationFromTabsConfig(routes, projectConfig.tabs, projectConfig.i18n) ?? {} : buildNavigationFromTabsConfig(routes, projectConfig.tabs) : buildNavigation(routes));
|
|
781
991
|
return `${imports}\n\nexport const routes = [\n${routesArray}\n];\n\nexport const navigation = ${JSON.stringify(navigation, null, 2)};\n`;
|
|
782
992
|
}
|
|
783
|
-
function createRuntimeModule(options = {}) {
|
|
784
|
-
const imports = [options.bannerComponentSource === "path" && options.imports?.bannerComponent ? `import BannerComponent from ${moduleSpecifier(options.imports.bannerComponent)};` : void 0, options.footerComponentSource === "path" && options.imports?.footerComponent ? `import FooterComponent from ${moduleSpecifier(options.imports.footerComponent)};` : void 0].filter(Boolean).join("\n");
|
|
785
|
-
const exports = `export const openApis = {};
|
|
786
|
-
export const bannerComponent = ${options.bannerComponentSource === "path" ? "BannerComponent" : "undefined"};
|
|
787
|
-
export const footerComponent = ${options.footerComponentSource === "path" ? "FooterComponent" : "undefined"};`;
|
|
788
|
-
return imports ? `${imports}\n${exports}` : exports;
|
|
789
|
-
}
|
|
790
993
|
function createClientEntryModule(options = {}) {
|
|
791
994
|
return `
|
|
792
995
|
import '@clarify-labs/renderer/style.css';
|
|
793
996
|
import { render } from '@clarify-labs/renderer/client';
|
|
794
997
|
import { routes, navigation } from '${VIRTUAL_ROUTES}';
|
|
795
998
|
import { config } from '${VIRTUAL_CONFIG}';
|
|
796
|
-
import { openApis
|
|
797
|
-
|
|
999
|
+
import { openApis } from '${VIRTUAL_OPENAPI}';
|
|
1000
|
+
import { runtimeSlots } from '${VIRTUAL_SLOTS}';
|
|
1001
|
+
render({ config, routes, navigation, openApis, runtimeSlots, themeEditor: ${JSON.stringify(options.themeEditor ?? false)} });`;
|
|
1002
|
+
}
|
|
1003
|
+
/**
|
|
1004
|
+
* Collect every plugin's `slots` declarations into a single runtime registry
|
|
1005
|
+
* module. Component modules are exported as lazy import factories so the
|
|
1006
|
+
* renderer can decide whether to code-split (client) or pre-resolve (SSR).
|
|
1007
|
+
*
|
|
1008
|
+
* Relative component paths are resolved against the project `root` so that
|
|
1009
|
+
* the generated `import()` call uses an absolute specifier — Vite cannot
|
|
1010
|
+
* resolve relative imports from virtual modules.
|
|
1011
|
+
*
|
|
1012
|
+
* Slot name validity is enforced at compile time by the `UISlotName`
|
|
1013
|
+
* type — no runtime validation is needed.
|
|
1014
|
+
*/
|
|
1015
|
+
function createRuntimeSlotsModule(plugins = [], root = process.cwd()) {
|
|
1016
|
+
const registrations = [];
|
|
1017
|
+
for (const plugin of plugins) for (const slot of plugin.slots ?? []) registrations.push({
|
|
1018
|
+
plugin: plugin.name,
|
|
1019
|
+
slot
|
|
1020
|
+
});
|
|
1021
|
+
if (registrations.length === 0) return "export const runtimeSlots = {};\n";
|
|
1022
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
1023
|
+
for (const { plugin, slot } of registrations) {
|
|
1024
|
+
if (slot.name.endsWith(".replace")) {
|
|
1025
|
+
const existing = grouped.get(slot.name);
|
|
1026
|
+
if (existing) console.warn(`[clarify] Multiple plugins register slot "${slot.name}": "${plugin}" overrides "${existing[existing.length - 1]}". Only the last registration is used.`);
|
|
1027
|
+
}
|
|
1028
|
+
if (!slot.component.startsWith("/")) throw new Error(`[clarify] Plugin "${plugin}" slot "${slot.name}" has an invalid component path "${slot.component}". Component paths must start with "/" to reference the project root.`);
|
|
1029
|
+
const componentPath = root + slot.component;
|
|
1030
|
+
const entry = `{ plugin: ${JSON.stringify(plugin)}, component: () => import(${moduleSpecifier(componentPath)}) }`;
|
|
1031
|
+
const list = grouped.get(slot.name) ?? [];
|
|
1032
|
+
list.push(entry);
|
|
1033
|
+
grouped.set(slot.name, list);
|
|
1034
|
+
}
|
|
1035
|
+
return `export const runtimeSlots = {\n${[...grouped.entries()].map(([name, list]) => ` ${JSON.stringify(name)}: [\n ${list.join(",\n ")}\n ]`).join(",\n")}\n};\n`;
|
|
1036
|
+
}
|
|
1037
|
+
/**
|
|
1038
|
+
* Re-exports the slot context hook from the renderer so plugin components can
|
|
1039
|
+
* `import { useSlot } from 'virtual:clarify/slot'` without depending on
|
|
1040
|
+
* renderer internals directly.
|
|
1041
|
+
*/
|
|
1042
|
+
function createSlotModule() {
|
|
1043
|
+
return `export { useSlot } from '@clarify-labs/renderer';\n`;
|
|
798
1044
|
}
|
|
799
1045
|
function buildVirtualModules(args) {
|
|
800
1046
|
const modules = /* @__PURE__ */ new Map();
|
|
801
1047
|
const clientEntryModule = createClientEntryModule({ themeEditor: args.themeEditor });
|
|
802
|
-
const
|
|
803
|
-
const bannerComponentSource = typeof bannerComponent === "string" ? "path" : void 0;
|
|
804
|
-
const bannerComponentImport = typeof bannerComponent === "string" ? bannerComponent : void 0;
|
|
805
|
-
const footerComponent = args.projectConfig.footer;
|
|
806
|
-
const footerComponentSource = typeof footerComponent === "string" ? "path" : void 0;
|
|
807
|
-
const footerComponentImport = typeof footerComponent === "string" ? footerComponent : void 0;
|
|
1048
|
+
const allPlugins = [...args.plugins ?? []];
|
|
808
1049
|
modules.set(VIRTUAL_CONFIG, generateConfigModule(args.projectConfig, args.generateOptions));
|
|
809
1050
|
modules.set(VIRTUAL_ROUTES, generateRoutesModule(args.routes, args.navigation, args.projectConfig, "client"));
|
|
810
1051
|
modules.set(VIRTUAL_SERVER_ROUTES, generateRoutesModule(args.routes, args.navigation, args.projectConfig, "server"));
|
|
811
|
-
modules.set(
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
imports: {
|
|
815
|
-
bannerComponent: bannerComponentImport,
|
|
816
|
-
footerComponent: footerComponentImport
|
|
817
|
-
}
|
|
818
|
-
}));
|
|
1052
|
+
modules.set(VIRTUAL_SLOTS, createRuntimeSlotsModule(allPlugins, args.generateOptions.projectRoot));
|
|
1053
|
+
modules.set(VIRTUAL_OPENAPI, generateOpenApiModule());
|
|
1054
|
+
modules.set(VIRTUAL_SLOT, createSlotModule());
|
|
819
1055
|
modules.set(VIRTUAL_CLIENT_ENTRY, clientEntryModule);
|
|
820
1056
|
modules.set(RESOLVED_CLIENT_ENTRY, clientEntryModule);
|
|
821
1057
|
for (const route of args.routes) modules.set(route.virtualModuleId, `export { default } from ${moduleSpecifier(route.filePath)};`);
|
|
822
1058
|
return modules;
|
|
823
1059
|
}
|
|
1060
|
+
function generateOpenApiModule() {
|
|
1061
|
+
return `export const openApis = {};`;
|
|
1062
|
+
}
|
|
824
1063
|
//#endregion
|
|
825
1064
|
//#region source/plugins/openapi/virtual-modules.ts
|
|
826
|
-
var openApiRegistryModuleId =
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
return
|
|
833
|
-
}
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
}
|
|
1065
|
+
var openApiRegistryModuleId = VIRTUAL_OPENAPI;
|
|
1066
|
+
var OPENAPI_SPEC_PREFIX = "virtual:clarify/openapi-spec/";
|
|
1067
|
+
function specVirtualModuleId(specKey) {
|
|
1068
|
+
return `${OPENAPI_SPEC_PREFIX}${specKey}`;
|
|
1069
|
+
}
|
|
1070
|
+
function generateOpenAPIRegistryModule(openApis) {
|
|
1071
|
+
return `export const openApis = ${JSON.stringify(openApis)};`;
|
|
1072
|
+
}
|
|
1073
|
+
/** Generate a virtual module that exports a single spec as default. */
|
|
1074
|
+
function generateOpenAPISpecModule(spec) {
|
|
1075
|
+
return `export default ${JSON.stringify(spec)};`;
|
|
1076
|
+
}
|
|
1077
|
+
function generateOpenAPIPageModule(opts) {
|
|
1078
|
+
const { specKey, specRegistryKey, tagFilter } = opts;
|
|
1079
|
+
return [
|
|
1080
|
+
`import { createElement, useState, useEffect, useRef } from 'react';`,
|
|
1081
|
+
`import { OpenApiDocument, useOpenApis } from '@clarify-labs/renderer';`,
|
|
1082
|
+
`const SPEC_KEY = ${JSON.stringify(specRegistryKey)};`,
|
|
1083
|
+
`const TAG_FILTER = ${JSON.stringify(tagFilter ?? void 0)};`,
|
|
1084
|
+
`let loadPromise = null;`,
|
|
1085
|
+
`function loadSpec() {`,
|
|
1086
|
+
` if (!loadPromise) loadPromise = import(${JSON.stringify(specVirtualModuleId(specKey))}).then(function(m) { return m.default; });`,
|
|
1087
|
+
` return loadPromise;`,
|
|
1088
|
+
`}`,
|
|
1089
|
+
`export default function OpenApiRoutePage() {`,
|
|
1090
|
+
` const specs = useOpenApis();`,
|
|
1091
|
+
` const serverSpec = specs[SPEC_KEY];`,
|
|
1092
|
+
` const [spec, setSpec] = useState(serverSpec || null);`,
|
|
1093
|
+
` const mountedRef = useRef(false);`,
|
|
1094
|
+
` useEffect(function() {`,
|
|
1095
|
+
` if (mountedRef.current) return;`,
|
|
1096
|
+
` mountedRef.current = true;`,
|
|
1097
|
+
` if (spec) return;`,
|
|
1098
|
+
` loadSpec().then(setSpec);`,
|
|
1099
|
+
` }, []);`,
|
|
1100
|
+
` if (!spec) return null;`,
|
|
1101
|
+
` return createElement(OpenApiDocument, { spec: spec, tagFilter: TAG_FILTER });`,
|
|
1102
|
+
`}`
|
|
1103
|
+
].join("\n");
|
|
842
1104
|
}
|
|
843
1105
|
function generateOpenAPIErrorModule(diagnostic) {
|
|
844
1106
|
return `import { createElement } from 'react';
|
|
@@ -872,18 +1134,6 @@ function collectOpenAPIPageItems(config) {
|
|
|
872
1134
|
for (const tab of config.tabs ?? []) visitPages(tab.pages);
|
|
873
1135
|
return items;
|
|
874
1136
|
}
|
|
875
|
-
function withAlternates$1(route, routes, i18n) {
|
|
876
|
-
const basePath = route.basePath ?? route.path;
|
|
877
|
-
const routeByLocaleAndBase = new Map(routes.map((route) => [`${route.locale ?? ""}:${route.basePath ?? route.path}`, route]));
|
|
878
|
-
const alternates = Object.fromEntries(i18n.locales.flatMap((locale) => {
|
|
879
|
-
const alternate = routeByLocaleAndBase.get(`${locale.code}:${basePath}`);
|
|
880
|
-
return alternate ? [[locale.code, alternate.path]] : [];
|
|
881
|
-
}));
|
|
882
|
-
return {
|
|
883
|
-
...route,
|
|
884
|
-
alternates
|
|
885
|
-
};
|
|
886
|
-
}
|
|
887
1137
|
function virtualModuleIdFromPath(path) {
|
|
888
1138
|
return `virtual:clarify-page/${path.replace(/^\/+/, "") || "index"}`;
|
|
889
1139
|
}
|
|
@@ -913,10 +1163,15 @@ function createConfiguredOpenAPIRoutes(routes, config) {
|
|
|
913
1163
|
}
|
|
914
1164
|
}
|
|
915
1165
|
const nextRoutes = [...routes, ...additions];
|
|
916
|
-
return config.i18n ? nextRoutes.map((route) => withAlternates
|
|
1166
|
+
return config.i18n ? nextRoutes.map((route) => withAlternates(route, nextRoutes, config.i18n)) : nextRoutes;
|
|
1167
|
+
}
|
|
1168
|
+
/** Derive a stable, deduplicated key from an absolute spec file path. */
|
|
1169
|
+
function specFileKeyFromPath(filePath, projectRoot) {
|
|
1170
|
+
return (0, node_path.relative)(projectRoot, filePath).replace(/[^a-zA-Z0-9_-]/g, "_").replace(/_+/g, "_").replace(/^_|_$/g, "");
|
|
917
1171
|
}
|
|
918
1172
|
function createOpenAPIPlugin() {
|
|
919
|
-
|
|
1173
|
+
/** specs keyed by specFileKey (deduplicated per source file). */
|
|
1174
|
+
const specs = /* @__PURE__ */ new Map();
|
|
920
1175
|
return {
|
|
921
1176
|
name: "clarify:openapi",
|
|
922
1177
|
hooks: {
|
|
@@ -925,7 +1180,7 @@ function createOpenAPIPlugin() {
|
|
|
925
1180
|
routes: [...input.routes, ...findOpenAPIRoutes(input.contentRoot)]
|
|
926
1181
|
}),
|
|
927
1182
|
"routes:discovered": async (routes, ctx) => {
|
|
928
|
-
|
|
1183
|
+
specs.clear();
|
|
929
1184
|
const nextRoutes = createConfiguredOpenAPIRoutes(routes, ctx.projectConfig);
|
|
930
1185
|
const specByFilePath = /* @__PURE__ */ new Map();
|
|
931
1186
|
for (const route of nextRoutes.filter((route) => route.kind === "openapi")) {
|
|
@@ -933,7 +1188,7 @@ function createOpenAPIPlugin() {
|
|
|
933
1188
|
const result = specFromCache ? {
|
|
934
1189
|
ok: true,
|
|
935
1190
|
spec: specFromCache
|
|
936
|
-
} : await readOpenAPISpec(route.filePath);
|
|
1191
|
+
} : await readOpenAPISpec(route.filePath, getProjectContentProcessor(ctx));
|
|
937
1192
|
if (!result.ok) {
|
|
938
1193
|
route.diagnostic = result.diagnostic;
|
|
939
1194
|
route.title = route.title || "OpenAPI parse error";
|
|
@@ -942,24 +1197,45 @@ function createOpenAPIPlugin() {
|
|
|
942
1197
|
}
|
|
943
1198
|
const spec = result.spec;
|
|
944
1199
|
specByFilePath.set(route.filePath, spec);
|
|
945
|
-
|
|
1200
|
+
const specKey = specFileKeyFromPath(route.filePath, ctx.generateOptions.projectRoot);
|
|
1201
|
+
route.specFileKey = specKey;
|
|
1202
|
+
specs.set(specKey, {
|
|
946
1203
|
spec,
|
|
947
|
-
|
|
948
|
-
};
|
|
949
|
-
specs[`virtual:clarify-page/${route.path.replace(/^\//, "")}`] = {
|
|
950
|
-
spec,
|
|
951
|
-
tagFilter: route.openapiTagFilter
|
|
952
|
-
};
|
|
1204
|
+
filePath: route.filePath
|
|
1205
|
+
});
|
|
953
1206
|
route.title = spec.info?.title ?? route.title;
|
|
954
1207
|
route.description = spec.info?.description ?? route.description;
|
|
955
1208
|
route.sections = extractOpenAPISections(spec, route.openapiTagFilter);
|
|
1209
|
+
const pageSpec = route.openapiTagFilter?.length ? filterSpecByTags(spec, route.openapiTagFilter) : spec;
|
|
1210
|
+
route.content = JSON.stringify(pageSpec);
|
|
956
1211
|
}
|
|
957
1212
|
return nextRoutes;
|
|
958
1213
|
},
|
|
959
1214
|
"modules:before": (modules, ctx) => {
|
|
960
|
-
|
|
961
|
-
for (const
|
|
962
|
-
|
|
1215
|
+
const registryEntries = {};
|
|
1216
|
+
for (const route of ctx.routes) {
|
|
1217
|
+
if (route.kind !== "openapi" || !route.specFileKey) continue;
|
|
1218
|
+
const spec = specs.get(route.specFileKey)?.spec;
|
|
1219
|
+
if (!spec) continue;
|
|
1220
|
+
const base = (route.basePath ?? route.path).replace(/^\//, "");
|
|
1221
|
+
const registryKey = route.locale ? `virtual:clarify-page/${route.locale}/${base}` : `virtual:clarify-page/${base}`;
|
|
1222
|
+
registryEntries[registryKey] = spec;
|
|
1223
|
+
}
|
|
1224
|
+
modules.set(openApiRegistryModuleId, generateOpenAPIRegistryModule(registryEntries));
|
|
1225
|
+
for (const [specKey, entry] of specs) modules.set(specVirtualModuleId(specKey), generateOpenAPISpecModule(entry.spec));
|
|
1226
|
+
for (const route of ctx.routes) {
|
|
1227
|
+
if (route.kind !== "openapi") continue;
|
|
1228
|
+
if (route.diagnostic) modules.set(route.virtualModuleId, generateOpenAPIErrorModule(route.diagnostic));
|
|
1229
|
+
else if (route.specFileKey) {
|
|
1230
|
+
const base = (route.basePath ?? route.path).replace(/^\//, "");
|
|
1231
|
+
const specRegistryKey = route.locale ? `virtual:clarify-page/${route.locale}/${base}` : `virtual:clarify-page/${base}`;
|
|
1232
|
+
modules.set(route.virtualModuleId, generateOpenAPIPageModule({
|
|
1233
|
+
specKey: route.specFileKey,
|
|
1234
|
+
specRegistryKey,
|
|
1235
|
+
tagFilter: route.openapiTagFilter
|
|
1236
|
+
}));
|
|
1237
|
+
}
|
|
1238
|
+
}
|
|
963
1239
|
return modules;
|
|
964
1240
|
}
|
|
965
1241
|
}
|
|
@@ -1177,22 +1453,22 @@ function createRobotsTxt(config) {
|
|
|
1177
1453
|
""
|
|
1178
1454
|
].join("\n");
|
|
1179
1455
|
}
|
|
1180
|
-
async function writeSeoFiles(outputRoot, routes, config) {
|
|
1181
|
-
const sitemap = createSitemapXml(routes, config);
|
|
1182
|
-
const robots = createRobotsTxt(config);
|
|
1183
|
-
if (!sitemap || !robots) return;
|
|
1184
|
-
await (0, node_fs_promises.mkdir)(outputRoot, { recursive: true });
|
|
1185
|
-
await Promise.all([(0, node_fs_promises.writeFile)((0, node_path.join)(outputRoot, "sitemap.xml"), sitemap, "utf-8"), (0, node_fs_promises.writeFile)((0, node_path.join)(outputRoot, "robots.txt"), robots, "utf-8")]);
|
|
1186
|
-
}
|
|
1187
1456
|
//#endregion
|
|
1188
1457
|
//#region source/plugins/seo/index.ts
|
|
1189
1458
|
function createSeoPlugin() {
|
|
1190
1459
|
return {
|
|
1191
1460
|
name: "clarify:seo",
|
|
1192
|
-
hooks: {
|
|
1193
|
-
const
|
|
1194
|
-
|
|
1195
|
-
|
|
1461
|
+
hooks: { "build:assets"(ctx) {
|
|
1462
|
+
const sitemap = createSitemapXml(ctx.routes, ctx.projectConfig);
|
|
1463
|
+
const robots = createRobotsTxt(ctx.projectConfig);
|
|
1464
|
+
if (!sitemap || !robots) return [];
|
|
1465
|
+
return [{
|
|
1466
|
+
fileName: "sitemap.xml",
|
|
1467
|
+
source: sitemap
|
|
1468
|
+
}, {
|
|
1469
|
+
fileName: "robots.txt",
|
|
1470
|
+
source: robots
|
|
1471
|
+
}];
|
|
1196
1472
|
} }
|
|
1197
1473
|
};
|
|
1198
1474
|
}
|
|
@@ -1229,9 +1505,51 @@ function createSourceLinksPlugin() {
|
|
|
1229
1505
|
};
|
|
1230
1506
|
}
|
|
1231
1507
|
//#endregion
|
|
1508
|
+
//#region source/parsers/variables.ts
|
|
1509
|
+
var variablePattern = /{{\s*([A-Za-z0-9_-]+(?:\.[A-Za-z0-9_-]+)*)\s*}}/g;
|
|
1510
|
+
function getVariableValue(variables, path) {
|
|
1511
|
+
let current = variables;
|
|
1512
|
+
for (const segment of path.split(".")) {
|
|
1513
|
+
if (!current || typeof current !== "object" || Array.isArray(current)) return void 0;
|
|
1514
|
+
current = current[segment];
|
|
1515
|
+
}
|
|
1516
|
+
return current;
|
|
1517
|
+
}
|
|
1518
|
+
function stringifyVariableValue(variables, path, stack) {
|
|
1519
|
+
if (stack.includes(path)) throw new Error(`[clarify] variable "${path}" contains a circular reference: ${[...stack, path].join(" -> ")}`);
|
|
1520
|
+
const value = getVariableValue(variables, path);
|
|
1521
|
+
if (value === void 0 || typeof value === "object") return void 0;
|
|
1522
|
+
if (typeof value !== "string") return String(value);
|
|
1523
|
+
return value.replace(variablePattern, (match, nestedPath) => stringifyVariableValue(variables, nestedPath, [...stack, path]) ?? match);
|
|
1524
|
+
}
|
|
1525
|
+
function applyVariables(content, variables = {}) {
|
|
1526
|
+
return content.replace(variablePattern, (match, path) => stringifyVariableValue(variables, path, []) ?? match);
|
|
1527
|
+
}
|
|
1528
|
+
//#endregion
|
|
1529
|
+
//#region source/plugins/variables/index.ts
|
|
1530
|
+
function applyVariablesToFrontmatter(frontmatter, variables) {
|
|
1531
|
+
const nextFrontmatter = { ...frontmatter };
|
|
1532
|
+
for (const key of ["title", "description"]) {
|
|
1533
|
+
if (typeof nextFrontmatter[key] !== "string") continue;
|
|
1534
|
+
nextFrontmatter[key] = applyVariables(nextFrontmatter[key], variables);
|
|
1535
|
+
}
|
|
1536
|
+
return nextFrontmatter;
|
|
1537
|
+
}
|
|
1538
|
+
function createVariablesPlugin() {
|
|
1539
|
+
return {
|
|
1540
|
+
name: "clarify:variables",
|
|
1541
|
+
hooks: { "content:transform": (input, ctx) => ({
|
|
1542
|
+
...input,
|
|
1543
|
+
frontmatter: applyVariablesToFrontmatter(input.frontmatter, ctx.projectConfig.variables),
|
|
1544
|
+
content: applyVariables(input.content, ctx.projectConfig.variables)
|
|
1545
|
+
}) }
|
|
1546
|
+
};
|
|
1547
|
+
}
|
|
1548
|
+
//#endregion
|
|
1232
1549
|
//#region source/core/builtin.ts
|
|
1233
1550
|
function createBuiltinPlugins(options = {}) {
|
|
1234
1551
|
const plugins = [
|
|
1552
|
+
createVariablesPlugin(),
|
|
1235
1553
|
createOpenAPIPlugin(),
|
|
1236
1554
|
createSourceLinksPlugin(),
|
|
1237
1555
|
createContentArtifactsPlugin(),
|
|
@@ -1290,18 +1608,23 @@ var clarifyNavbarLinkSchema = zod.z.object({
|
|
|
1290
1608
|
href: zod.z.string(),
|
|
1291
1609
|
external: zod.z.boolean().optional()
|
|
1292
1610
|
});
|
|
1293
|
-
var
|
|
1611
|
+
var clarifyBannerConfigSchema = zod.z.object({
|
|
1294
1612
|
content: clarifyLocalizedTextSchema,
|
|
1295
1613
|
dismissible: zod.z.boolean().optional(),
|
|
1296
1614
|
link: clarifyNavbarLinkSchema.optional()
|
|
1297
1615
|
}).strict();
|
|
1298
|
-
var
|
|
1299
|
-
var clarifyFooterLinksConfigSchema = zod.z.object({
|
|
1616
|
+
var clarifyFooterConfigSchema = zod.z.object({
|
|
1300
1617
|
links: zod.z.array(clarifyNavbarLinkSchema).optional(),
|
|
1301
1618
|
socials: zod.z.record(zod.z.string(), zod.z.string()).optional(),
|
|
1302
1619
|
copyright: clarifyLocalizedTextSchema.optional()
|
|
1303
1620
|
}).strict();
|
|
1304
|
-
var
|
|
1621
|
+
var clarifyVariableValueSchema = zod.z.lazy(() => zod.z.union([
|
|
1622
|
+
zod.z.string(),
|
|
1623
|
+
zod.z.number(),
|
|
1624
|
+
zod.z.boolean(),
|
|
1625
|
+
zod.z.record(zod.z.string(), clarifyVariableValueSchema)
|
|
1626
|
+
]));
|
|
1627
|
+
var clarifyVariablesConfigSchema = zod.z.record(zod.z.string(), clarifyVariableValueSchema);
|
|
1305
1628
|
var clarifySourceConfigSchema = zod.z.object({
|
|
1306
1629
|
repository: zod.z.string(),
|
|
1307
1630
|
branch: zod.z.string().optional(),
|
|
@@ -1379,9 +1702,11 @@ var clarifyProjectConfigSchema = zod.z.object({
|
|
|
1379
1702
|
favicon: clarifyFaviconConfigSchema.optional(),
|
|
1380
1703
|
theme: clarifyThemeConfigSchema.optional(),
|
|
1381
1704
|
routePrefix: zod.z.string().optional(),
|
|
1705
|
+
assetPrefix: zod.z.string().optional(),
|
|
1382
1706
|
navbar: zod.z.object({ links: zod.z.array(clarifyNavbarLinkSchema).optional() }).optional(),
|
|
1383
1707
|
banner: clarifyBannerConfigSchema.optional(),
|
|
1384
1708
|
footer: clarifyFooterConfigSchema.optional(),
|
|
1709
|
+
variables: clarifyVariablesConfigSchema.optional(),
|
|
1385
1710
|
i18n: clarifyI18nConfigSchema.optional(),
|
|
1386
1711
|
tabs: clarifyTabsConfigSchema.optional()
|
|
1387
1712
|
});
|
|
@@ -1441,7 +1766,22 @@ function resolveI18nConfig(i18n) {
|
|
|
1441
1766
|
locales: i18n.locales
|
|
1442
1767
|
};
|
|
1443
1768
|
}
|
|
1769
|
+
function resolveRoutePrefix(routePrefix) {
|
|
1770
|
+
const trimmed = routePrefix?.trim();
|
|
1771
|
+
if (!trimmed || trimmed === "/") return "/";
|
|
1772
|
+
return `/${trimmed.replace(/^\/+|\/+$/g, "")}/`;
|
|
1773
|
+
}
|
|
1774
|
+
function resolveAssetPrefix(assetPrefix, routePrefix) {
|
|
1775
|
+
const trimmed = assetPrefix?.trim();
|
|
1776
|
+
if (trimmed === void 0) return routePrefix;
|
|
1777
|
+
if (trimmed === "./" || trimmed === "") return trimmed || "/";
|
|
1778
|
+
if (!trimmed || trimmed === "/") return "/";
|
|
1779
|
+
if (/^[a-z][a-z\d+.-]*:\/\//i.test(trimmed)) return trimmed.endsWith("/") ? trimmed : `${trimmed}/`;
|
|
1780
|
+
if (trimmed.startsWith("./") || trimmed.startsWith("../")) return trimmed.endsWith("/") ? trimmed : `${trimmed}/`;
|
|
1781
|
+
return `/${trimmed.replace(/^\/+|\/+$/g, "")}/`;
|
|
1782
|
+
}
|
|
1444
1783
|
function resolveProjectConfig(config = {}) {
|
|
1784
|
+
const routePrefix = resolveRoutePrefix(config.routePrefix);
|
|
1445
1785
|
return {
|
|
1446
1786
|
title: config.title ?? "Clarify Docs",
|
|
1447
1787
|
description: config.description ?? "",
|
|
@@ -1450,51 +1790,64 @@ function resolveProjectConfig(config = {}) {
|
|
|
1450
1790
|
logo: config.logo,
|
|
1451
1791
|
homeUrl: config.homeUrl,
|
|
1452
1792
|
favicon: config.favicon,
|
|
1453
|
-
routePrefix
|
|
1793
|
+
routePrefix,
|
|
1794
|
+
assetPrefix: resolveAssetPrefix(config.assetPrefix, routePrefix),
|
|
1454
1795
|
theme: resolveThemeConfig(config.theme),
|
|
1455
1796
|
navbar: config.navbar,
|
|
1456
1797
|
banner: config.banner,
|
|
1457
1798
|
footer: config.footer,
|
|
1799
|
+
variables: config.variables ?? {},
|
|
1458
1800
|
i18n: resolveI18nConfig(config.i18n),
|
|
1459
1801
|
tabs: config.tabs
|
|
1460
1802
|
};
|
|
1461
1803
|
}
|
|
1462
1804
|
//#endregion
|
|
1463
|
-
//#region source/core/
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1805
|
+
//#region source/core/env-types.ts
|
|
1806
|
+
var GENERATED_FILE_NAME = "clarify-env.d.ts";
|
|
1807
|
+
/**
|
|
1808
|
+
* Template for the generated `clarify-env.d.ts` file.
|
|
1809
|
+
* Edit this template directly if you need to change the type declarations.
|
|
1810
|
+
* This avoids the complexity of reading external files at runtime after bundling.
|
|
1811
|
+
*/
|
|
1812
|
+
var CLARIFY_ENV_TEMPLATE = `// This file is generated by Clarify. Do not edit it manually.
|
|
1813
|
+
// It provides ambient type declarations for Clarify virtual modules used in
|
|
1814
|
+
// plugin and slot code, so the TypeScript language service can resolve them.
|
|
1815
|
+
|
|
1816
|
+
declare module 'virtual:clarify/slot' {
|
|
1817
|
+
import type { SlotContext } from '@clarify-labs/cli'
|
|
1818
|
+
|
|
1819
|
+
export function useSlot(): SlotContext
|
|
1476
1820
|
}
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1821
|
+
|
|
1822
|
+
declare module 'virtual:clarify/slots' {
|
|
1823
|
+
import type { ComponentType } from 'react'
|
|
1824
|
+
|
|
1825
|
+
export type RuntimeSlotEntry = {
|
|
1826
|
+
plugin: string
|
|
1827
|
+
/** Lazy import factory — yields the default-exported React component. */
|
|
1828
|
+
component: () => Promise<{ default: ComponentType }>
|
|
1829
|
+
}
|
|
1830
|
+
|
|
1831
|
+
export const runtimeSlots: Record<string, RuntimeSlotEntry[]>
|
|
1487
1832
|
}
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1833
|
+
`;
|
|
1834
|
+
function generateClarifyEnvDts(_plugins) {
|
|
1835
|
+
return CLARIFY_ENV_TEMPLATE;
|
|
1836
|
+
}
|
|
1837
|
+
/**
|
|
1838
|
+
* Writes `clarify-env.d.ts` into the project root. Only rewrites when content
|
|
1839
|
+
* changes to avoid triggering unnecessary file watchers, and never overwrites a
|
|
1840
|
+
* user-authored file that does not carry the generated marker.
|
|
1841
|
+
*/
|
|
1842
|
+
function writeClarifyEnvDts(projectRoot, plugins) {
|
|
1843
|
+
const filePath = (0, node_path.join)(projectRoot, GENERATED_FILE_NAME);
|
|
1844
|
+
const next = generateClarifyEnvDts(plugins);
|
|
1845
|
+
if ((0, node_fs.existsSync)(filePath)) {
|
|
1846
|
+
const current = (0, node_fs.readFileSync)(filePath, "utf-8");
|
|
1847
|
+
if (current === next) return;
|
|
1848
|
+
if (!current.startsWith("// This file is generated by Clarify.")) return;
|
|
1497
1849
|
}
|
|
1850
|
+
(0, node_fs.writeFileSync)(filePath, next, "utf-8");
|
|
1498
1851
|
}
|
|
1499
1852
|
//#endregion
|
|
1500
1853
|
//#region source/core/options.ts
|
|
@@ -1508,23 +1861,11 @@ function resolveBuildOptions(options = {}) {
|
|
|
1508
1861
|
}
|
|
1509
1862
|
//#endregion
|
|
1510
1863
|
//#region source/core/site.ts
|
|
1511
|
-
function withAlternates(route, routeList, i18n) {
|
|
1512
|
-
const basePath = route.basePath ?? route.path;
|
|
1513
|
-
const routeByLocaleAndBase = new Map(routeList.map((route) => [`${route.locale ?? ""}:${route.basePath ?? route.path}`, route]));
|
|
1514
|
-
const alternates = Object.fromEntries(i18n.locales.flatMap((locale) => {
|
|
1515
|
-
const alternate = routeByLocaleAndBase.get(`${locale.code}:${basePath}`);
|
|
1516
|
-
return alternate ? [[locale.code, alternate.path]] : [];
|
|
1517
|
-
}));
|
|
1518
|
-
return {
|
|
1519
|
-
...route,
|
|
1520
|
-
alternates
|
|
1521
|
-
};
|
|
1522
|
-
}
|
|
1523
1864
|
async function discoverRoutesForRoot(routeRoot, locale, plugins, ctx) {
|
|
1524
1865
|
return (await runHooks(plugins, "routes:discover", {
|
|
1525
1866
|
contentRoot: routeRoot,
|
|
1526
1867
|
locale,
|
|
1527
|
-
routes: findContentRoutes(routeRoot)
|
|
1868
|
+
routes: await findContentRoutes(routeRoot, routeRoot, { contentProcessor: getProjectContentProcessor(ctx) })
|
|
1528
1869
|
}, ctx)).routes;
|
|
1529
1870
|
}
|
|
1530
1871
|
async function discoverRoutes(root, contentRoot, plugins, ctx) {
|
|
@@ -1561,7 +1902,21 @@ async function discoverRoutes(root, contentRoot, plugins, ctx) {
|
|
|
1561
1902
|
}
|
|
1562
1903
|
}
|
|
1563
1904
|
}
|
|
1564
|
-
|
|
1905
|
+
const routesWithAlternates = localizedRoutes.map((route) => withAlternates(route, localizedRoutes, i18n));
|
|
1906
|
+
const defaultLocale = i18n.defaultLocale;
|
|
1907
|
+
const bareRoutes = [];
|
|
1908
|
+
const seenBare = new Set(routesWithAlternates.map((r) => r.path));
|
|
1909
|
+
for (const route of routesWithAlternates) {
|
|
1910
|
+
if (route.locale !== defaultLocale) continue;
|
|
1911
|
+
const bp = route.basePath ?? route.path;
|
|
1912
|
+
if (bp === route.path || seenBare.has(bp)) continue;
|
|
1913
|
+
seenBare.add(bp);
|
|
1914
|
+
bareRoutes.push({
|
|
1915
|
+
...route,
|
|
1916
|
+
path: bp
|
|
1917
|
+
});
|
|
1918
|
+
}
|
|
1919
|
+
return [...routesWithAlternates, ...bareRoutes];
|
|
1565
1920
|
}
|
|
1566
1921
|
async function resolveClarifySite(options = {}, resolveOptions = {}) {
|
|
1567
1922
|
const root = options.projectRoot ?? process.cwd();
|
|
@@ -1575,6 +1930,7 @@ async function resolveClarifySite(options = {}, resolveOptions = {}) {
|
|
|
1575
1930
|
routes: [],
|
|
1576
1931
|
navigation: []
|
|
1577
1932
|
};
|
|
1933
|
+
setProjectContentProcessor(ctx, createProjectContentProcessor(plugins, ctx));
|
|
1578
1934
|
let routes = await discoverRoutes(root, contentRoot, plugins, ctx);
|
|
1579
1935
|
routes = await runHooks(plugins, "routes:discovered", routes, ctx);
|
|
1580
1936
|
routes = applyConfiguredPageRoutePaths(routes, projectConfig.tabs, projectConfig.i18n);
|
|
@@ -1637,13 +1993,21 @@ function createClarifyRuntimeAliases() {
|
|
|
1637
1993
|
find: /^@clarify-labs\/renderer$/,
|
|
1638
1994
|
replacement: resolvePackageExport("@clarify-labs/renderer", "")
|
|
1639
1995
|
},
|
|
1996
|
+
{
|
|
1997
|
+
find: /^react\/jsx-dev-runtime$/,
|
|
1998
|
+
replacement: resolveCliDependency("react/jsx-dev-runtime")
|
|
1999
|
+
},
|
|
1640
2000
|
{
|
|
1641
2001
|
find: /^react\/jsx-runtime$/,
|
|
1642
2002
|
replacement: resolveCliDependency("react/jsx-runtime")
|
|
1643
2003
|
},
|
|
1644
2004
|
{
|
|
1645
|
-
find: /^
|
|
1646
|
-
replacement: resolveCliDependency("
|
|
2005
|
+
find: /^mermaid$/,
|
|
2006
|
+
replacement: resolveCliDependency("mermaid")
|
|
2007
|
+
},
|
|
2008
|
+
{
|
|
2009
|
+
find: /^react-zoom-pan-pinch$/,
|
|
2010
|
+
replacement: resolveCliDependency("react-zoom-pan-pinch")
|
|
1647
2011
|
},
|
|
1648
2012
|
{
|
|
1649
2013
|
find: /^react$/,
|
|
@@ -1685,11 +2049,24 @@ function setNamedMeta(html, name, content) {
|
|
|
1685
2049
|
if (metaPattern.test(html)) return html.replace(metaPattern, meta);
|
|
1686
2050
|
return html.replace("</head>", ` ${meta}\n </head>`);
|
|
1687
2051
|
}
|
|
2052
|
+
function canonicalUrl(projectConfig, route) {
|
|
2053
|
+
if (!projectConfig.siteUrl || !route.alternates || !route.locale) return void 0;
|
|
2054
|
+
const prefixedPath = route.alternates[route.locale];
|
|
2055
|
+
if (!prefixedPath || prefixedPath === route.path) return void 0;
|
|
2056
|
+
return `${projectConfig.siteUrl.replace(/\/+$/, "")}${!projectConfig.routePrefix || projectConfig.routePrefix === "/" ? "" : `/${projectConfig.routePrefix.replace(/^\/+|\/+$/g, "")}`}${prefixedPath}`;
|
|
2057
|
+
}
|
|
2058
|
+
function injectCanonicalUrl(html, projectConfig, route) {
|
|
2059
|
+
const url = route ? canonicalUrl(projectConfig, route) : void 0;
|
|
2060
|
+
html = html.replace(/<link\b[^>]*\brel=["']canonical["'][^>]*\/?>\n?/gi, "");
|
|
2061
|
+
if (!url) return html;
|
|
2062
|
+
return html.replace("</head>", ` <link rel="canonical" href="${escapeHtml(url)}" />\n </head>`);
|
|
2063
|
+
}
|
|
1688
2064
|
function injectSSRIntoTemplate(template, appHtml, projectConfig, route) {
|
|
1689
2065
|
let html = injectHtmlLocaleAttributes(template, projectConfig, route);
|
|
1690
2066
|
html = html.replace(/<title>.*?<\/title>/, `<title>${escapeHtml(routeTitle(projectConfig, route))}</title>`);
|
|
1691
2067
|
html = setNamedMeta(html, "description", route?.description ?? projectConfig.description);
|
|
1692
2068
|
html = setNamedMeta(html, "keywords", route?.keywords?.join(", "));
|
|
2069
|
+
html = injectCanonicalUrl(html, projectConfig, route);
|
|
1693
2070
|
html = html.replace(/<div id="root">[\s\S]*?<\/div>/, `<div id="root">${appHtml}</div>`);
|
|
1694
2071
|
return html;
|
|
1695
2072
|
}
|
|
@@ -1702,12 +2079,20 @@ function routeOutputFiles(outputDirectory, route) {
|
|
|
1702
2079
|
return files;
|
|
1703
2080
|
}
|
|
1704
2081
|
var SSR_ENTRY_CODE = `import { renderToHTML } from '@clarify-labs/renderer/server';
|
|
1705
|
-
import { routes, navigation } from 'virtual:clarify
|
|
1706
|
-
import { config } from 'virtual:clarify
|
|
1707
|
-
import { openApis
|
|
2082
|
+
import { routes, navigation } from 'virtual:clarify/routes/server';
|
|
2083
|
+
import { config } from 'virtual:clarify/config';
|
|
2084
|
+
import { openApis } from 'virtual:clarify/openapi';
|
|
2085
|
+
import { runtimeSlots } from 'virtual:clarify/slots';
|
|
1708
2086
|
|
|
1709
|
-
export function render(url) {
|
|
1710
|
-
|
|
2087
|
+
export async function render(url) {
|
|
2088
|
+
// Pre-resolve every slot component factory so renderToString can use
|
|
2089
|
+
// them synchronously (renderToString has no Suspense support).
|
|
2090
|
+
const entries = Object.values(runtimeSlots).flat()
|
|
2091
|
+
await Promise.all(entries.map(async (entry) => {
|
|
2092
|
+
const mod = await entry.component()
|
|
2093
|
+
entry._resolved = mod.default
|
|
2094
|
+
}))
|
|
2095
|
+
return renderToHTML({ config, routes, navigation, openApis, runtimeSlots, url, themeEditor: config.theme.editor });
|
|
1711
2096
|
}`;
|
|
1712
2097
|
function createTempEntryFile(content) {
|
|
1713
2098
|
const entryPath = (0, node_path.join)(createClarifyTempDir("ssr"), "entry-server.ts");
|
|
@@ -1737,7 +2122,7 @@ async function renderSSGRoutes(routes, projectConfig, outputDirectory, ssrBundle
|
|
|
1737
2122
|
const template = readIndexHtml(outputDirectory);
|
|
1738
2123
|
if (!template) throw new Error(`[clarify] index.html not found in outputDirectory "${outputDirectory}". Make sure Vite build produces it.`);
|
|
1739
2124
|
for (const route of routes) try {
|
|
1740
|
-
const finalHtml = injectSSRIntoTemplate(template, render(route.path), projectConfig, route);
|
|
2125
|
+
const finalHtml = injectSSRIntoTemplate(template, await render(route.path), projectConfig, route);
|
|
1741
2126
|
for (const outFile of routeOutputFiles(outputDirectory, route)) {
|
|
1742
2127
|
(0, node_fs.mkdirSync)((0, node_path.dirname)(outFile), { recursive: true });
|
|
1743
2128
|
(0, node_fs.writeFileSync)(outFile, finalHtml, "utf-8");
|
|
@@ -1806,23 +2191,8 @@ function clarifyPlugin(options = {}) {
|
|
|
1806
2191
|
let viteConfig;
|
|
1807
2192
|
let resolvedNavigation = [];
|
|
1808
2193
|
let virtualModules = /* @__PURE__ */ new Map();
|
|
1809
|
-
async function
|
|
1810
|
-
const
|
|
1811
|
-
...await loadClarifyConfig(root, {
|
|
1812
|
-
command: "serve",
|
|
1813
|
-
mode: viteConfig.mode
|
|
1814
|
-
}),
|
|
1815
|
-
projectRoot: options.projectRoot,
|
|
1816
|
-
rootDirectory: options.rootDirectory,
|
|
1817
|
-
outputDirectory: options.outputDirectory
|
|
1818
|
-
};
|
|
1819
|
-
projectConfig = resolveProjectConfig(buildOptions);
|
|
1820
|
-
generateOptions = resolveBuildOptions(buildOptions);
|
|
1821
|
-
ctx.projectConfig = projectConfig;
|
|
1822
|
-
ctx.generateOptions = generateOptions;
|
|
1823
|
-
}
|
|
1824
|
-
async function resolveRoutesAndSpecs() {
|
|
1825
|
-
const site = await resolveClarifySite(options);
|
|
2194
|
+
async function resolveRoutesAndSpecs(overrides) {
|
|
2195
|
+
const site = await resolveClarifySite(overrides ?? options);
|
|
1826
2196
|
projectConfig = site.projectConfig;
|
|
1827
2197
|
generateOptions = site.generateOptions;
|
|
1828
2198
|
routes = site.routes;
|
|
@@ -1839,6 +2209,7 @@ function clarifyPlugin(options = {}) {
|
|
|
1839
2209
|
generateOptions,
|
|
1840
2210
|
routes,
|
|
1841
2211
|
navigation: resolvedNavigation,
|
|
2212
|
+
plugins: clarifyPlugins,
|
|
1842
2213
|
themeEditor: viteConfig.command === "serve" || projectConfig.theme.editor
|
|
1843
2214
|
});
|
|
1844
2215
|
virtualModules = await runHooks(clarifyPlugins, "modules:before", virtualModules, ctx);
|
|
@@ -1888,7 +2259,7 @@ function clarifyPlugin(options = {}) {
|
|
|
1888
2259
|
async config() {
|
|
1889
2260
|
await resolveRoutesAndSpecs();
|
|
1890
2261
|
return {
|
|
1891
|
-
base: projectConfig.
|
|
2262
|
+
base: projectConfig.assetPrefix,
|
|
1892
2263
|
build: {
|
|
1893
2264
|
...generateOptions.outputDirectory ? { outDir: generateOptions.outputDirectory } : {},
|
|
1894
2265
|
manifest: true
|
|
@@ -1901,12 +2272,23 @@ function clarifyPlugin(options = {}) {
|
|
|
1901
2272
|
},
|
|
1902
2273
|
async buildStart() {
|
|
1903
2274
|
await rebuildVirtualModules();
|
|
2275
|
+
writeClarifyEnvDts(root, clarifyPlugins);
|
|
1904
2276
|
},
|
|
1905
2277
|
async handleHotUpdate(ctx) {
|
|
1906
2278
|
const changedFile = (0, node_path.isAbsolute)(ctx.file) ? ctx.file : (0, node_path.join)(root, ctx.file);
|
|
1907
2279
|
if (configFilePath && changedFile === configFilePath) {
|
|
1908
|
-
await
|
|
1909
|
-
|
|
2280
|
+
await resolveRoutesAndSpecs({
|
|
2281
|
+
...await loadClarifyConfig(root, {
|
|
2282
|
+
command: "serve",
|
|
2283
|
+
mode: viteConfig.mode
|
|
2284
|
+
}),
|
|
2285
|
+
projectRoot: options.projectRoot,
|
|
2286
|
+
rootDirectory: options.rootDirectory,
|
|
2287
|
+
outputDirectory: options.outputDirectory
|
|
2288
|
+
});
|
|
2289
|
+
await rebuildVirtualModules();
|
|
2290
|
+
invalidateVirtualModules(ctx.server);
|
|
2291
|
+
ctx.server.ws.send({ type: "full-reload" });
|
|
1910
2292
|
return [];
|
|
1911
2293
|
}
|
|
1912
2294
|
const relativeContentFile = (0, node_path.relative)(contentRoot, changedFile);
|
|
@@ -1915,10 +2297,13 @@ function clarifyPlugin(options = {}) {
|
|
|
1915
2297
|
return [];
|
|
1916
2298
|
},
|
|
1917
2299
|
resolveId(id) {
|
|
1918
|
-
if (id === "virtual:clarify
|
|
1919
|
-
if (id === "virtual:clarify
|
|
1920
|
-
if (id === "virtual:clarify
|
|
1921
|
-
if (id === "virtual:clarify
|
|
2300
|
+
if (id === "virtual:clarify/entry-client" || id === "\0virtual:clarify/entry-client") return RESOLVED_CLIENT_ENTRY;
|
|
2301
|
+
if (id === "virtual:clarify/config" || id === resolveVirtualId("virtual:clarify/config")) return resolveVirtualId(VIRTUAL_CONFIG);
|
|
2302
|
+
if (id === "virtual:clarify/routes" || id === resolveVirtualId("virtual:clarify/routes")) return resolveVirtualId(VIRTUAL_ROUTES);
|
|
2303
|
+
if (id === "virtual:clarify/routes/server" || id === resolveVirtualId("virtual:clarify/routes/server")) return resolveVirtualId(VIRTUAL_SERVER_ROUTES);
|
|
2304
|
+
if (id === "virtual:clarify/openapi" || id === resolveVirtualId("virtual:clarify/openapi")) return resolveVirtualId(VIRTUAL_OPENAPI);
|
|
2305
|
+
if (id === "virtual:clarify/slots" || id === resolveVirtualId("virtual:clarify/slots")) return resolveVirtualId(VIRTUAL_SLOTS);
|
|
2306
|
+
if (id === "virtual:clarify/slot" || id === resolveVirtualId("virtual:clarify/slot")) return resolveVirtualId(VIRTUAL_SLOT);
|
|
1922
2307
|
const moduleId = stripVirtualPrefix(id);
|
|
1923
2308
|
if (virtualModules.has(moduleId)) return resolveVirtualId(moduleId);
|
|
1924
2309
|
const route = routes.find((r) => r.virtualModuleId === id || r.virtualModuleId === moduleId);
|
|
@@ -1956,6 +2341,14 @@ function clarifyPlugin(options = {}) {
|
|
|
1956
2341
|
};
|
|
1957
2342
|
}
|
|
1958
2343
|
},
|
|
2344
|
+
async generateBundle(_opts, _bundle) {
|
|
2345
|
+
const assets = await runBuildAssetsHooks(clarifyPlugins, ctx);
|
|
2346
|
+
for (const asset of assets) this.emitFile({
|
|
2347
|
+
type: "asset",
|
|
2348
|
+
fileName: asset.fileName,
|
|
2349
|
+
source: asset.source
|
|
2350
|
+
});
|
|
2351
|
+
},
|
|
1959
2352
|
async closeBundle() {
|
|
1960
2353
|
if (process.env.SKIP_CLARIFY_SSG) {
|
|
1961
2354
|
await runBuildDoneHooks(clarifyPlugins, ctx);
|
|
@@ -1970,11 +2363,14 @@ function clarifyPlugin(options = {}) {
|
|
|
1970
2363
|
{
|
|
1971
2364
|
name: "clarify:virtual-ssg",
|
|
1972
2365
|
resolveId(id) {
|
|
1973
|
-
if (id === "virtual:clarify
|
|
1974
|
-
if (id === "\0virtual:clarify
|
|
1975
|
-
if (id === "virtual:clarify
|
|
1976
|
-
if (id === "virtual:clarify
|
|
1977
|
-
if (id === "virtual:clarify
|
|
2366
|
+
if (id === "virtual:clarify/entry-client") return RESOLVED_CLIENT_ENTRY;
|
|
2367
|
+
if (id === "\0virtual:clarify/entry-client") return RESOLVED_CLIENT_ENTRY;
|
|
2368
|
+
if (id === "virtual:clarify/routes/server") return id;
|
|
2369
|
+
if (id === "virtual:clarify/openapi") return id;
|
|
2370
|
+
if (id === "virtual:clarify/config") return id;
|
|
2371
|
+
if (id === "virtual:clarify/routes") return id;
|
|
2372
|
+
if (id === "virtual:clarify/slots") return id;
|
|
2373
|
+
if (id === "virtual:clarify/slot") return id;
|
|
1978
2374
|
if (virtualModules.has(stripVirtualPrefix(id))) return stripVirtualPrefix(id);
|
|
1979
2375
|
if (routes.find((r) => r.virtualModuleId === id)) return id;
|
|
1980
2376
|
return null;
|
|
@@ -2038,4 +2434,4 @@ Object.defineProperty(exports, "resolveClarifySite", {
|
|
|
2038
2434
|
}
|
|
2039
2435
|
});
|
|
2040
2436
|
|
|
2041
|
-
//# sourceMappingURL=plugin-
|
|
2437
|
+
//# sourceMappingURL=plugin-DyceHdXd.cjs.map
|