@clarify-labs/cli 0.11.19 → 0.11.20
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/output/cli.cjs +1 -1
- package/output/cli.js +1 -1
- package/output/core/engine/engine.d.ts +3 -2
- package/output/{engine-DH8Hi6Fr.js → engine-BgfGFPKK.js} +696 -109
- package/output/engine-BgfGFPKK.js.map +1 -0
- package/output/{engine-0ROqtF4o.cjs → engine-CAis1rme.cjs} +696 -110
- package/output/engine-CAis1rme.cjs.map +1 -0
- package/output/index.cjs +1 -1
- package/output/index.js +1 -1
- package/output/parsers/content/content.d.ts +1 -0
- package/output/parsers/markdown/frontmatter.d.ts +1 -0
- package/output/parsers/markdown/headings.d.ts +24 -0
- package/output/parsers/markdown/mdx.d.ts +1 -0
- package/output/parsers/routes/heading-metadata.d.ts +7 -0
- package/output/parsers/routes/routes.d.ts +2 -1
- package/output/plugins/content-artifacts/provider.d.ts +3 -0
- package/output/plugins/openapi/aggregation.d.ts +12 -0
- package/output/plugins/openapi/artifacts.d.ts +6 -0
- package/output/types.d.ts +7 -1
- package/package.json +3 -3
- package/output/engine-0ROqtF4o.cjs.map +0 -1
- package/output/engine-DH8Hi6Fr.js.map +0 -1
|
@@ -30,18 +30,14 @@ _vitejs_plugin_react = __toESM(_vitejs_plugin_react, 1);
|
|
|
30
30
|
let rehype_raw = require("rehype-raw");
|
|
31
31
|
rehype_raw = __toESM(rehype_raw, 1);
|
|
32
32
|
let _mdx_js_mdx = require("@mdx-js/mdx");
|
|
33
|
+
let github_slugger = require("github-slugger");
|
|
34
|
+
github_slugger = __toESM(github_slugger, 1);
|
|
35
|
+
let _clarify_labs_renderer = require("@clarify-labs/renderer");
|
|
33
36
|
let _shikijs_rehype = require("@shikijs/rehype");
|
|
34
37
|
_shikijs_rehype = __toESM(_shikijs_rehype, 1);
|
|
35
|
-
let rehype_slug = require("rehype-slug");
|
|
36
|
-
rehype_slug = __toESM(rehype_slug, 1);
|
|
37
38
|
let shiki = require("shiki");
|
|
38
39
|
let unist_util_visit = require("unist-util-visit");
|
|
39
|
-
let _clarify_labs_renderer = require("@clarify-labs/renderer");
|
|
40
40
|
let node_fs_promises = require("node:fs/promises");
|
|
41
|
-
let github_slugger = require("github-slugger");
|
|
42
|
-
github_slugger = __toESM(github_slugger, 1);
|
|
43
|
-
let mdast_util_to_string = require("mdast-util-to-string");
|
|
44
|
-
let remark = require("remark");
|
|
45
41
|
let gray_matter = require("gray-matter");
|
|
46
42
|
gray_matter = __toESM(gray_matter, 1);
|
|
47
43
|
let node_child_process = require("node:child_process");
|
|
@@ -76,6 +72,402 @@ function createContentDiagnostic(options) {
|
|
|
76
72
|
};
|
|
77
73
|
}
|
|
78
74
|
//#endregion
|
|
75
|
+
//#region source/parsers/markdown/headings.ts
|
|
76
|
+
var HEADING_ID_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
77
|
+
var INTERNAL_HEADING_ID_PREFIX = "clarify-internal-heading-id:";
|
|
78
|
+
var EXPLICIT_ID_PATTERN = /\s+\{#([^{}]*)\}$/;
|
|
79
|
+
var HEADING_REMARK_PLUGINS = _clarify_labs_renderer.markdownRemarkPlugins;
|
|
80
|
+
function visitHeadings(node, callback) {
|
|
81
|
+
if (node.type === "heading") callback(node);
|
|
82
|
+
for (const child of node.children ?? []) visitHeadings(child, callback);
|
|
83
|
+
}
|
|
84
|
+
function visitHtml(node, callback) {
|
|
85
|
+
if (node.type === "html") callback(node);
|
|
86
|
+
for (const child of node.children ?? []) visitHtml(child, callback);
|
|
87
|
+
}
|
|
88
|
+
function visitMdxIntrinsicHeadings(node, callback) {
|
|
89
|
+
if ((node.type === "mdxJsxFlowElement" || node.type === "mdxJsxTextElement") && node.name && /^h[1-6]$/.test(node.name)) callback(node);
|
|
90
|
+
for (const child of node.children ?? []) visitMdxIntrinsicHeadings(child, callback);
|
|
91
|
+
}
|
|
92
|
+
function visitHastHeadings(node, callback) {
|
|
93
|
+
if (node.type === "element" && node.tagName && /^h[1-6]$/.test(node.tagName)) callback(node);
|
|
94
|
+
for (const child of node.children ?? []) visitHastHeadings(child, callback);
|
|
95
|
+
}
|
|
96
|
+
function hastText$1(node) {
|
|
97
|
+
if (node.type === "text") return node.value ?? "";
|
|
98
|
+
return (node.children ?? []).map(hastText$1).join("");
|
|
99
|
+
}
|
|
100
|
+
function applySourceEdits(content, edits) {
|
|
101
|
+
return edits.sort((left, right) => right.start - left.start).reduce((result, edit) => result.slice(0, edit.start) + edit.replacement + result.slice(edit.end), content);
|
|
102
|
+
}
|
|
103
|
+
function headingPosition(node, lineOffset) {
|
|
104
|
+
return {
|
|
105
|
+
line: (node.position?.start.line ?? 1) + lineOffset,
|
|
106
|
+
column: node.position?.start.column ?? 1
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
function formatPosition(position) {
|
|
110
|
+
return `${position.line}:${position.column}`;
|
|
111
|
+
}
|
|
112
|
+
function finalTextNode(heading) {
|
|
113
|
+
const node = heading.children?.at(-1);
|
|
114
|
+
return node?.type === "text" && typeof node.value === "string" ? node : void 0;
|
|
115
|
+
}
|
|
116
|
+
function contentEndOffset(heading) {
|
|
117
|
+
return (heading.children?.at(-1))?.position?.end.offset ?? heading.position?.end.offset;
|
|
118
|
+
}
|
|
119
|
+
function explicitIdInfo(heading, content) {
|
|
120
|
+
const text = finalTextNode(heading);
|
|
121
|
+
if (!text?.position) return void 0;
|
|
122
|
+
const startOffset = text.position.start.offset;
|
|
123
|
+
const endOffset = text.position.end.offset;
|
|
124
|
+
if (startOffset === void 0 || endOffset === void 0) return void 0;
|
|
125
|
+
const match = content.slice(startOffset, endOffset).match(EXPLICIT_ID_PATTERN);
|
|
126
|
+
if (!match || match.index === void 0) return void 0;
|
|
127
|
+
return {
|
|
128
|
+
id: match[1] ?? "",
|
|
129
|
+
start: startOffset + match.index,
|
|
130
|
+
end: startOffset + match.index + match[0].length
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
function maskExplicitHeadingMarkers(content) {
|
|
134
|
+
const tree = (0, _mdx_js_mdx.createProcessor)({
|
|
135
|
+
format: "md",
|
|
136
|
+
remarkPlugins: HEADING_REMARK_PLUGINS
|
|
137
|
+
}).parse(content);
|
|
138
|
+
const edits = [];
|
|
139
|
+
visitHeadings(tree, (heading) => {
|
|
140
|
+
const marker = explicitIdInfo(heading, content);
|
|
141
|
+
if (marker) edits.push({
|
|
142
|
+
start: marker.start,
|
|
143
|
+
end: marker.end,
|
|
144
|
+
replacement: "x".repeat(marker.end - marker.start)
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
return applySourceEdits(content, edits);
|
|
148
|
+
}
|
|
149
|
+
function parseHeadingTree(content, kind) {
|
|
150
|
+
if (kind === "markdown") return (0, _mdx_js_mdx.createProcessor)({
|
|
151
|
+
format: "md",
|
|
152
|
+
remarkPlugins: HEADING_REMARK_PLUGINS
|
|
153
|
+
}).parse(content);
|
|
154
|
+
const maskedContent = maskExplicitHeadingMarkers(content);
|
|
155
|
+
try {
|
|
156
|
+
return (0, _mdx_js_mdx.createProcessor)({
|
|
157
|
+
format: "mdx",
|
|
158
|
+
remarkPlugins: HEADING_REMARK_PLUGINS
|
|
159
|
+
}).parse(maskedContent);
|
|
160
|
+
} catch {
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
function renderedMarkdownHeadingTitles(tree, content, kind) {
|
|
165
|
+
const originalText = /* @__PURE__ */ new Map();
|
|
166
|
+
visitHeadings(tree, (heading) => {
|
|
167
|
+
const marker = explicitIdInfo(heading, content);
|
|
168
|
+
const text = finalTextNode(heading);
|
|
169
|
+
if (!marker || !text || text.value === void 0) return;
|
|
170
|
+
originalText.set(text, text.value);
|
|
171
|
+
text.value = text.value.slice(0, -(marker.end - marker.start)).replace(/\s+$/, "");
|
|
172
|
+
});
|
|
173
|
+
try {
|
|
174
|
+
let hast;
|
|
175
|
+
const captureHast = () => (tree) => {
|
|
176
|
+
hast = tree;
|
|
177
|
+
};
|
|
178
|
+
(0, _mdx_js_mdx.createProcessor)({
|
|
179
|
+
format: kind === "markdown" ? "md" : "mdx",
|
|
180
|
+
remarkPlugins: HEADING_REMARK_PLUGINS,
|
|
181
|
+
...kind === "markdown" ? { remarkRehypeOptions: { allowDangerousHtml: true } } : {},
|
|
182
|
+
rehypePlugins: kind === "markdown" ? [rehype_raw.default, captureHast] : [captureHast]
|
|
183
|
+
}).runSync(tree, { value: content });
|
|
184
|
+
const titles = /* @__PURE__ */ new Map();
|
|
185
|
+
if (!hast) return titles;
|
|
186
|
+
visitHastHeadings(hast, (heading) => {
|
|
187
|
+
const offset = heading.position?.start.offset;
|
|
188
|
+
if (offset !== void 0) titles.set(offset, hastText$1(heading));
|
|
189
|
+
});
|
|
190
|
+
return titles;
|
|
191
|
+
} finally {
|
|
192
|
+
for (const [text, value] of originalText) text.value = value;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
function isHtmlWhitespace(character) {
|
|
196
|
+
return character === " " || character === "\n" || character === "\f" || character === "\r" || character === " ";
|
|
197
|
+
}
|
|
198
|
+
function isMdxWhitespace(character) {
|
|
199
|
+
return /\s/u.test(character ?? "");
|
|
200
|
+
}
|
|
201
|
+
function openingTagAttributeOffset(content, start, end, isWhitespace) {
|
|
202
|
+
let state = "tagName";
|
|
203
|
+
let quote;
|
|
204
|
+
for (let index = start; index < end; index++) {
|
|
205
|
+
const character = content[index];
|
|
206
|
+
const closesTag = character === ">";
|
|
207
|
+
const selfClosesTag = character === "/" && content[index + 1] === ">";
|
|
208
|
+
if (state === "quotedAttributeValue") {
|
|
209
|
+
if (character === quote) {
|
|
210
|
+
quote = void 0;
|
|
211
|
+
state = "afterQuotedAttributeValue";
|
|
212
|
+
}
|
|
213
|
+
continue;
|
|
214
|
+
}
|
|
215
|
+
if (state === "unquotedAttributeValue") {
|
|
216
|
+
if (closesTag) return index;
|
|
217
|
+
if (isWhitespace(character)) state = "beforeAttributeName";
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
220
|
+
if (closesTag) return index;
|
|
221
|
+
if (selfClosesTag && state !== "beforeAttributeValue") {
|
|
222
|
+
let cursor = index;
|
|
223
|
+
while (cursor > start && isWhitespace(content[cursor - 1])) cursor--;
|
|
224
|
+
return cursor;
|
|
225
|
+
}
|
|
226
|
+
if (state === "tagName") {
|
|
227
|
+
if (isWhitespace(character)) state = "beforeAttributeName";
|
|
228
|
+
} else if (state === "beforeAttributeName") {
|
|
229
|
+
if (!isWhitespace(character)) state = "attributeName";
|
|
230
|
+
} else if (state === "attributeName") {
|
|
231
|
+
if (character === "=") state = "beforeAttributeValue";
|
|
232
|
+
else if (isWhitespace(character)) state = "afterAttributeName";
|
|
233
|
+
} else if (state === "afterAttributeName") {
|
|
234
|
+
if (character === "=") state = "beforeAttributeValue";
|
|
235
|
+
else if (!isWhitespace(character)) state = "attributeName";
|
|
236
|
+
} else if (state === "beforeAttributeValue") {
|
|
237
|
+
if (character === "\"" || character === "'") {
|
|
238
|
+
quote = character;
|
|
239
|
+
state = "quotedAttributeValue";
|
|
240
|
+
} else if (!isWhitespace(character)) state = "unquotedAttributeValue";
|
|
241
|
+
} else if (state === "afterQuotedAttributeValue" && isWhitespace(character)) state = "beforeAttributeName";
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
function rawHtmlHeadings(tree, content, lineOffset) {
|
|
245
|
+
let hasRawHtml = false;
|
|
246
|
+
visitHtml(tree, () => {
|
|
247
|
+
hasRawHtml = true;
|
|
248
|
+
});
|
|
249
|
+
if (!hasRawHtml) return [];
|
|
250
|
+
const markdownOffsets = /* @__PURE__ */ new Set();
|
|
251
|
+
visitHeadings(tree, (heading) => {
|
|
252
|
+
const offset = heading.position?.start.offset;
|
|
253
|
+
if (offset !== void 0) markdownOffsets.add(offset);
|
|
254
|
+
});
|
|
255
|
+
let hast;
|
|
256
|
+
const captureHast = () => (tree) => {
|
|
257
|
+
hast = tree;
|
|
258
|
+
};
|
|
259
|
+
(0, _mdx_js_mdx.createProcessor)({
|
|
260
|
+
format: "md",
|
|
261
|
+
remarkRehypeOptions: { allowDangerousHtml: true },
|
|
262
|
+
rehypePlugins: [rehype_raw.default, captureHast]
|
|
263
|
+
}).runSync(tree, { value: content });
|
|
264
|
+
if (!hast) return [];
|
|
265
|
+
const headings = [];
|
|
266
|
+
visitHastHeadings(hast, (heading) => {
|
|
267
|
+
const offset = heading.position?.start.offset;
|
|
268
|
+
const headingEnd = heading.position?.end.offset;
|
|
269
|
+
if (offset === void 0 || headingEnd === void 0 || markdownOffsets.has(offset)) return;
|
|
270
|
+
const tagEnd = openingTagAttributeOffset(content, offset, headingEnd, isHtmlWhitespace);
|
|
271
|
+
if (tagEnd === void 0) return;
|
|
272
|
+
const rawId = heading.properties?.id;
|
|
273
|
+
headings.push({
|
|
274
|
+
title: hastText$1(heading),
|
|
275
|
+
...typeof rawId === "string" && rawId ? { id: rawId } : {},
|
|
276
|
+
position: {
|
|
277
|
+
line: (heading.position?.start.line ?? 1) + lineOffset,
|
|
278
|
+
column: heading.position?.start.column ?? 1
|
|
279
|
+
},
|
|
280
|
+
offset,
|
|
281
|
+
attributeOffset: tagEnd
|
|
282
|
+
});
|
|
283
|
+
});
|
|
284
|
+
return headings;
|
|
285
|
+
}
|
|
286
|
+
function staticMdxText(node) {
|
|
287
|
+
if (node.type === "text" || node.type === "inlineCode") return node.value ?? "";
|
|
288
|
+
if (node.type === "mdxJsxFlowElement" || node.type === "mdxJsxTextElement") {
|
|
289
|
+
if (!node.name || !/^[a-z][a-z0-9-]*$/.test(node.name)) return void 0;
|
|
290
|
+
} else if (!node.children) return;
|
|
291
|
+
const parts = [];
|
|
292
|
+
for (const child of node.children ?? []) {
|
|
293
|
+
const text = staticMdxText(child);
|
|
294
|
+
if (text === void 0) return void 0;
|
|
295
|
+
parts.push(text);
|
|
296
|
+
}
|
|
297
|
+
return parts.join("");
|
|
298
|
+
}
|
|
299
|
+
function literalMdxId(node) {
|
|
300
|
+
let result = { kind: "missing" };
|
|
301
|
+
for (const attribute of node.attributes ?? []) {
|
|
302
|
+
if (attribute.type !== "mdxJsxAttribute") {
|
|
303
|
+
result = { kind: "unknown" };
|
|
304
|
+
continue;
|
|
305
|
+
}
|
|
306
|
+
if (attribute.name !== "id") continue;
|
|
307
|
+
result = typeof attribute.value === "string" ? {
|
|
308
|
+
kind: "literal",
|
|
309
|
+
id: attribute.value
|
|
310
|
+
} : { kind: "unknown" };
|
|
311
|
+
}
|
|
312
|
+
return result;
|
|
313
|
+
}
|
|
314
|
+
function mdxIntrinsicHeadings(tree, content, lineOffset) {
|
|
315
|
+
const headings = [];
|
|
316
|
+
visitMdxIntrinsicHeadings(tree, (heading) => {
|
|
317
|
+
const position = heading.position;
|
|
318
|
+
const offset = position?.start.offset;
|
|
319
|
+
const end = position?.end.offset;
|
|
320
|
+
const title = staticMdxText(heading);
|
|
321
|
+
const literalId = literalMdxId(heading);
|
|
322
|
+
if (!position || offset === void 0 || end === void 0 || literalId.kind !== "literal") return;
|
|
323
|
+
const tagEnd = openingTagAttributeOffset(content, offset, end, isMdxWhitespace);
|
|
324
|
+
if (tagEnd === void 0) return;
|
|
325
|
+
headings.push({
|
|
326
|
+
...title !== void 0 ? { title } : {},
|
|
327
|
+
id: literalId.id,
|
|
328
|
+
position: {
|
|
329
|
+
line: position.start.line + lineOffset,
|
|
330
|
+
column: position.start.column
|
|
331
|
+
},
|
|
332
|
+
offset,
|
|
333
|
+
attributeOffset: tagEnd
|
|
334
|
+
});
|
|
335
|
+
});
|
|
336
|
+
return headings;
|
|
337
|
+
}
|
|
338
|
+
function escapeHtmlAttribute(value) {
|
|
339
|
+
return value.replaceAll("&", "&").replaceAll("\"", """);
|
|
340
|
+
}
|
|
341
|
+
function analyzeHeadings(content, options) {
|
|
342
|
+
const lineOffset = options.lineOffset ?? 0;
|
|
343
|
+
const headings = [];
|
|
344
|
+
const edits = [];
|
|
345
|
+
const errors = [];
|
|
346
|
+
const ids = /* @__PURE__ */ new Map();
|
|
347
|
+
const documentHeadings = [];
|
|
348
|
+
const markdownHeadingInputs = /* @__PURE__ */ new Map();
|
|
349
|
+
const tree = parseHeadingTree(content, options.kind);
|
|
350
|
+
if (!tree) return {
|
|
351
|
+
headings,
|
|
352
|
+
normalizedContent: content
|
|
353
|
+
};
|
|
354
|
+
const renderedTitles = renderedMarkdownHeadingTitles(tree, content, options.kind);
|
|
355
|
+
const claimId = (id, position) => {
|
|
356
|
+
const owner = ids.get(id);
|
|
357
|
+
if (owner) {
|
|
358
|
+
errors.push(`Heading ID "${id}" at ${formatPosition(position)} conflicts with the heading at ${formatPosition(owner.position)}.`);
|
|
359
|
+
return;
|
|
360
|
+
}
|
|
361
|
+
ids.set(id, {
|
|
362
|
+
id,
|
|
363
|
+
position
|
|
364
|
+
});
|
|
365
|
+
};
|
|
366
|
+
visitHeadings(tree, (node) => {
|
|
367
|
+
const marker = explicitIdInfo(node, content);
|
|
368
|
+
const title = renderedTitles.get(node.position?.start.offset ?? 0) ?? "";
|
|
369
|
+
const hasValidExplicitId = marker !== void 0 && HEADING_ID_PATTERN.test(marker.id);
|
|
370
|
+
const heading = {
|
|
371
|
+
level: node.depth ?? 1,
|
|
372
|
+
title,
|
|
373
|
+
id: hasValidExplicitId ? marker.id : "",
|
|
374
|
+
position: headingPosition(node, lineOffset)
|
|
375
|
+
};
|
|
376
|
+
if (marker && !hasValidExplicitId) errors.push(`Invalid heading ID "${marker.id}" at ${formatPosition(heading.position)}. IDs must match [a-z0-9]+(?:-[a-z0-9]+)*.`);
|
|
377
|
+
headings.push(heading);
|
|
378
|
+
markdownHeadingInputs.set(heading, {
|
|
379
|
+
...marker ? { marker } : {},
|
|
380
|
+
hasValidExplicitId,
|
|
381
|
+
contentEnd: contentEndOffset(node)
|
|
382
|
+
});
|
|
383
|
+
documentHeadings.push({
|
|
384
|
+
title,
|
|
385
|
+
offset: node.position?.start.offset ?? 0,
|
|
386
|
+
markdown: heading
|
|
387
|
+
});
|
|
388
|
+
});
|
|
389
|
+
const renderedHeadings = options.kind === "markdown" ? rawHtmlHeadings(tree, content, lineOffset) : mdxIntrinsicHeadings(tree, content, lineOffset);
|
|
390
|
+
for (const rendered of renderedHeadings) documentHeadings.push({
|
|
391
|
+
...rendered.title !== void 0 ? { title: rendered.title } : {},
|
|
392
|
+
offset: rendered.offset,
|
|
393
|
+
rendered
|
|
394
|
+
});
|
|
395
|
+
documentHeadings.sort((left, right) => left.offset - right.offset);
|
|
396
|
+
const slugger = new github_slugger.default();
|
|
397
|
+
for (const documentHeading of documentHeadings) {
|
|
398
|
+
if (documentHeading.markdown) {
|
|
399
|
+
const heading = documentHeading.markdown;
|
|
400
|
+
const input = markdownHeadingInputs.get(heading);
|
|
401
|
+
if (input.hasValidExplicitId) {
|
|
402
|
+
edits.push({
|
|
403
|
+
start: input.marker.start,
|
|
404
|
+
end: input.marker.end,
|
|
405
|
+
replacement: ` [](clarify-internal-heading-id:${heading.id})`
|
|
406
|
+
});
|
|
407
|
+
continue;
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
if (documentHeading.rendered?.id !== void 0) continue;
|
|
411
|
+
if (documentHeading.title === void 0) continue;
|
|
412
|
+
const generatedId = slugger.slug(documentHeading.title);
|
|
413
|
+
if (documentHeading.markdown) {
|
|
414
|
+
const heading = documentHeading.markdown;
|
|
415
|
+
const input = markdownHeadingInputs.get(heading);
|
|
416
|
+
heading.id = generatedId;
|
|
417
|
+
const link = ` [](clarify-internal-heading-id:${heading.id})`;
|
|
418
|
+
if (input.marker) edits.push({
|
|
419
|
+
start: input.marker.start,
|
|
420
|
+
end: input.marker.end,
|
|
421
|
+
replacement: link
|
|
422
|
+
});
|
|
423
|
+
else if (input.contentEnd !== void 0) edits.push({
|
|
424
|
+
start: input.contentEnd,
|
|
425
|
+
end: input.contentEnd,
|
|
426
|
+
replacement: link
|
|
427
|
+
});
|
|
428
|
+
} else if (documentHeading.rendered && documentHeading.rendered.id === void 0) {
|
|
429
|
+
documentHeading.rendered.id = generatedId;
|
|
430
|
+
edits.push({
|
|
431
|
+
start: documentHeading.rendered.attributeOffset,
|
|
432
|
+
end: documentHeading.rendered.attributeOffset,
|
|
433
|
+
replacement: ` id="${escapeHtmlAttribute(generatedId)}"`
|
|
434
|
+
});
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
for (const documentHeading of documentHeadings) if (documentHeading.markdown) claimId(documentHeading.markdown.id, documentHeading.markdown.position);
|
|
438
|
+
else if (documentHeading.rendered?.id) claimId(documentHeading.rendered.id, documentHeading.rendered.position);
|
|
439
|
+
const diagnostic = errors.length === 0 ? void 0 : createContentDiagnostic({
|
|
440
|
+
kind: options.kind,
|
|
441
|
+
title: "Heading ID error",
|
|
442
|
+
message: "This page has invalid or conflicting heading IDs. Fix the IDs below, then reload the route.",
|
|
443
|
+
error: new Error(errors.join("\n")),
|
|
444
|
+
filePath: options.filePath,
|
|
445
|
+
projectRoot: options.projectRoot
|
|
446
|
+
});
|
|
447
|
+
return {
|
|
448
|
+
headings,
|
|
449
|
+
normalizedContent: applySourceEdits(content, edits),
|
|
450
|
+
diagnostic
|
|
451
|
+
};
|
|
452
|
+
}
|
|
453
|
+
function remarkApplyHeadingIds() {
|
|
454
|
+
return (tree) => {
|
|
455
|
+
visitHeadings(tree, (heading) => {
|
|
456
|
+
const finalNode = heading.children?.at(-1);
|
|
457
|
+
if (finalNode?.type !== "link" || finalNode.children?.length !== 0 || typeof finalNode.url !== "string" || !finalNode.url.startsWith(INTERNAL_HEADING_ID_PREFIX)) return;
|
|
458
|
+
const id = finalNode.url.slice(28);
|
|
459
|
+
heading.children?.pop();
|
|
460
|
+
const previousNode = heading.children?.at(-1);
|
|
461
|
+
if (previousNode?.type === "text" && typeof previousNode.value === "string") previousNode.value = previousNode.value.replace(/ $/, "");
|
|
462
|
+
heading.data = heading.data ?? {};
|
|
463
|
+
heading.data.hProperties = {
|
|
464
|
+
...heading.data.hProperties,
|
|
465
|
+
id
|
|
466
|
+
};
|
|
467
|
+
});
|
|
468
|
+
};
|
|
469
|
+
}
|
|
470
|
+
//#endregion
|
|
79
471
|
//#region source/parsers/markdown/mdx.ts
|
|
80
472
|
function getLanguage(className) {
|
|
81
473
|
return (Array.isArray(className) ? className : typeof className === "string" ? className.split(/\s+/) : []).find((item) => typeof item === "string" && item.startsWith("language-"))?.replace(/^language-/, "") || "txt";
|
|
@@ -98,9 +490,34 @@ function rehypeParseCodeBlocks() {
|
|
|
98
490
|
});
|
|
99
491
|
};
|
|
100
492
|
}
|
|
101
|
-
|
|
493
|
+
function hastText(node) {
|
|
494
|
+
if (node.type === "text") return node.value ?? "";
|
|
495
|
+
return (node.children ?? []).map(hastText).join("");
|
|
496
|
+
}
|
|
497
|
+
function isHeadingElement(node) {
|
|
498
|
+
return Boolean(node.tagName && /^h[1-6]$/.test(node.tagName));
|
|
499
|
+
}
|
|
500
|
+
function rehypeAddMissingHeadingIds() {
|
|
501
|
+
return (tree) => {
|
|
502
|
+
const slugger = new github_slugger.default();
|
|
503
|
+
const reservedIds = /* @__PURE__ */ new Set();
|
|
504
|
+
(0, unist_util_visit.visit)(tree, "element", (node) => {
|
|
505
|
+
if (isHeadingElement(node) && typeof node.properties?.id === "string" && node.properties.id) reservedIds.add(node.properties.id);
|
|
506
|
+
});
|
|
507
|
+
(0, unist_util_visit.visit)(tree, "element", (node) => {
|
|
508
|
+
if (!isHeadingElement(node)) return;
|
|
509
|
+
const title = hastText(node);
|
|
510
|
+
let fallbackId = slugger.slug(title);
|
|
511
|
+
if (node.properties?.id) return;
|
|
512
|
+
while (reservedIds.has(fallbackId)) fallbackId = slugger.slug(title);
|
|
513
|
+
node.properties = node.properties ?? {};
|
|
514
|
+
node.properties.id = fallbackId;
|
|
515
|
+
});
|
|
516
|
+
};
|
|
517
|
+
}
|
|
518
|
+
var remarkPlugins = [remarkApplyHeadingIds, ..._clarify_labs_renderer.markdownRemarkPlugins];
|
|
102
519
|
var rehypePlugins = [
|
|
103
|
-
|
|
520
|
+
rehypeAddMissingHeadingIds,
|
|
104
521
|
rehypeParseCodeBlocks,
|
|
105
522
|
[_shikijs_rehype.default, {
|
|
106
523
|
theme: cssVariablesTheme,
|
|
@@ -255,9 +672,12 @@ async function runBuildDoneHooks(plugins, ctx) {
|
|
|
255
672
|
*/
|
|
256
673
|
function parseFrontmatter(content) {
|
|
257
674
|
const parsed = (0, gray_matter.default)(content);
|
|
675
|
+
const body = parsed.content.replace(/^\r?\n/, "");
|
|
676
|
+
const prefix = content.slice(0, content.length - body.length);
|
|
258
677
|
return {
|
|
259
678
|
frontmatter: parsed.data,
|
|
260
|
-
content:
|
|
679
|
+
content: body,
|
|
680
|
+
lineOffset: [...prefix.matchAll(/\r\n|\r|\n/g)].length
|
|
261
681
|
};
|
|
262
682
|
}
|
|
263
683
|
//#endregion
|
|
@@ -268,7 +688,7 @@ async function transformContent(transform, input) {
|
|
|
268
688
|
function createContentProcessor(transform) {
|
|
269
689
|
return {
|
|
270
690
|
async processMdx(source, filePath) {
|
|
271
|
-
const { frontmatter, content } = parseFrontmatter(source);
|
|
691
|
+
const { frontmatter, content, lineOffset } = parseFrontmatter(source);
|
|
272
692
|
const transformed = await transformContent(transform, {
|
|
273
693
|
kind: "markdown+jsx",
|
|
274
694
|
source,
|
|
@@ -278,7 +698,8 @@ function createContentProcessor(transform) {
|
|
|
278
698
|
});
|
|
279
699
|
return {
|
|
280
700
|
frontmatter: transformed.frontmatter,
|
|
281
|
-
content: transformed.content
|
|
701
|
+
content: transformed.content,
|
|
702
|
+
lineOffset
|
|
282
703
|
};
|
|
283
704
|
},
|
|
284
705
|
async processText(source, kind, filePath) {
|
|
@@ -388,13 +809,47 @@ function resolveUpdatedAt(frontmatter, detectedAt) {
|
|
|
388
809
|
return normalizeDate(typeof frontmatter.updatedAt === "string" ? frontmatter.updatedAt : void 0) ?? detectedAt;
|
|
389
810
|
}
|
|
390
811
|
//#endregion
|
|
391
|
-
//#region source/parsers/routes/
|
|
812
|
+
//#region source/parsers/routes/heading-metadata.ts
|
|
392
813
|
function kebabToTitle(str) {
|
|
393
|
-
return str.split("-").map((
|
|
394
|
-
}
|
|
395
|
-
function
|
|
396
|
-
return
|
|
814
|
+
return str.split("-").map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
|
|
815
|
+
}
|
|
816
|
+
function headingMetadata(analysis) {
|
|
817
|
+
return { sections: analysis.headings.filter((heading) => heading.level === 2 || heading.level === 3).map((heading) => ({
|
|
818
|
+
id: heading.id,
|
|
819
|
+
title: heading.title,
|
|
820
|
+
level: heading.level
|
|
821
|
+
})) };
|
|
822
|
+
}
|
|
823
|
+
function resolvedRouteTitle(route, analysis) {
|
|
824
|
+
const frontmatterTitle = route.source.frontmatter?.title;
|
|
825
|
+
if (typeof frontmatterTitle === "string" && frontmatterTitle) return frontmatterTitle;
|
|
826
|
+
const routePath = route.basePath ?? route.path;
|
|
827
|
+
return kebabToTitle(routePath === "/" ? analysis.headings.find((heading) => heading.level === 1)?.title ?? "" : routePath.split("/").filter(Boolean).at(-1) ?? "") || "Untitled";
|
|
828
|
+
}
|
|
829
|
+
async function reanalyzeMarkdownRoute(route, projectRoot) {
|
|
830
|
+
if (route.kind !== "markdown" && route.kind !== "markdown+jsx") return route;
|
|
831
|
+
const analysis = analyzeHeadings(route.source.content ?? "", {
|
|
832
|
+
kind: route.kind,
|
|
833
|
+
filePath: route.source.filePath,
|
|
834
|
+
projectRoot,
|
|
835
|
+
lineOffset: route.source.lineOffset
|
|
836
|
+
});
|
|
837
|
+
const result = await (route.kind === "markdown" ? compileMarkdownContent : compileMdxContent)(analysis.normalizedContent, {
|
|
838
|
+
filePath: route.source.filePath,
|
|
839
|
+
projectRoot
|
|
840
|
+
});
|
|
841
|
+
return {
|
|
842
|
+
...route,
|
|
843
|
+
meta: {
|
|
844
|
+
...route.meta,
|
|
845
|
+
title: resolvedRouteTitle(route, analysis),
|
|
846
|
+
...headingMetadata(analysis)
|
|
847
|
+
},
|
|
848
|
+
diagnostic: analysis.diagnostic ?? (result.ok ? void 0 : result.diagnostic)
|
|
849
|
+
};
|
|
397
850
|
}
|
|
851
|
+
//#endregion
|
|
852
|
+
//#region source/parsers/routes/routes.ts
|
|
398
853
|
async function compileRouteDiagnostic(content, filePath, baseDir) {
|
|
399
854
|
const result = await ((0, node_path.extname)(filePath).toLowerCase() === ".md" ? compileMarkdownContent : compileMdxContent)(content, {
|
|
400
855
|
filePath,
|
|
@@ -402,30 +857,6 @@ async function compileRouteDiagnostic(content, filePath, baseDir) {
|
|
|
402
857
|
});
|
|
403
858
|
return result.ok ? void 0 : result.diagnostic;
|
|
404
859
|
}
|
|
405
|
-
/** 从内容中提取第一个 H1 标题 */
|
|
406
|
-
function extractH1(content) {
|
|
407
|
-
const tree = parseMdxTree(content);
|
|
408
|
-
let title = "";
|
|
409
|
-
(0, unist_util_visit.visit)(tree, "heading", (node) => {
|
|
410
|
-
if (!title && node.depth === 1) title = (0, mdast_util_to_string.toString)(node);
|
|
411
|
-
});
|
|
412
|
-
return title;
|
|
413
|
-
}
|
|
414
|
-
/** 从 MDX/Markdown 内容中提取 H2/H3 章节 */
|
|
415
|
-
function extractMdxSections(content) {
|
|
416
|
-
const sections = [];
|
|
417
|
-
const slugger = new github_slugger.default();
|
|
418
|
-
(0, unist_util_visit.visit)(parseMdxTree(content), "heading", (node) => {
|
|
419
|
-
if (node.depth !== 2 && node.depth !== 3) return;
|
|
420
|
-
const title = (0, mdast_util_to_string.toString)(node);
|
|
421
|
-
sections.push({
|
|
422
|
-
id: slugger.slug(title),
|
|
423
|
-
title,
|
|
424
|
-
level: node.depth
|
|
425
|
-
});
|
|
426
|
-
});
|
|
427
|
-
return sections;
|
|
428
|
-
}
|
|
429
860
|
function navigationSections(sections) {
|
|
430
861
|
return sections.map((section) => ({
|
|
431
862
|
id: section.id,
|
|
@@ -482,11 +913,12 @@ async function findContentRoutes(dir, base = dir, options = {}) {
|
|
|
482
913
|
const fullPath = (0, node_path.join)(dir, entry.name);
|
|
483
914
|
if (entry.isDirectory()) routes.push(...await findContentRoutes(fullPath, base, options));
|
|
484
915
|
else if (entry.isFile() && /\.mdx?$/.test(entry.name)) {
|
|
916
|
+
const kind = entry.name.toLowerCase().endsWith(".mdx") ? "markdown+jsx" : "markdown";
|
|
485
917
|
const relativePath = (0, node_path.relative)(base, fullPath);
|
|
486
918
|
const pathParts = relativePath.replace(/\.mdx?$/, "").split("/");
|
|
487
919
|
const cleanPath = ("/" + pathParts.map((p) => p === "index" ? "" : p).filter(Boolean).join("/")).replace(/\/+/g, "/").replace(/\/$/, "") || "/";
|
|
488
920
|
const source = await (0, node_fs_promises.readFile)(fullPath, "utf-8");
|
|
489
|
-
const { frontmatter, content } = await (options.contentProcessor ?? createContentProcessor()).processMdx(source, fullPath);
|
|
921
|
+
const { frontmatter, content, lineOffset } = await (options.contentProcessor ?? createContentProcessor()).processMdx(source, fullPath);
|
|
490
922
|
const updatedAt = resolveUpdatedAt(frontmatter, await getFileUpdatedAt(fullPath, base));
|
|
491
923
|
const page = {
|
|
492
924
|
path: cleanPath,
|
|
@@ -494,14 +926,21 @@ async function findContentRoutes(dir, base = dir, options = {}) {
|
|
|
494
926
|
frontmatter,
|
|
495
927
|
content
|
|
496
928
|
};
|
|
497
|
-
const
|
|
929
|
+
const analysis = analyzeHeadings(page.content, {
|
|
930
|
+
kind,
|
|
931
|
+
filePath: fullPath,
|
|
932
|
+
projectRoot: base,
|
|
933
|
+
lineOffset
|
|
934
|
+
});
|
|
935
|
+
const compileDiagnostic = await compileRouteDiagnostic(analysis.normalizedContent, fullPath, base);
|
|
936
|
+
const { sections } = headingMetadata(analysis);
|
|
498
937
|
let title = typeof page.frontmatter.title === "string" ? page.frontmatter.title : "";
|
|
499
938
|
if (!title) {
|
|
500
939
|
const lastPart = pathParts[pathParts.length - 1] ?? "";
|
|
501
|
-
title = kebabToTitle(lastPart === "index" ? pathParts.length >= 2 ? pathParts[pathParts.length - 2] :
|
|
940
|
+
title = kebabToTitle(lastPart === "index" ? pathParts.length >= 2 ? pathParts[pathParts.length - 2] : analysis.headings.find((heading) => heading.level === 1)?.title ?? "" : lastPart) || "Untitled";
|
|
502
941
|
}
|
|
503
942
|
routes.push({
|
|
504
|
-
kind
|
|
943
|
+
kind,
|
|
505
944
|
path: cleanPath,
|
|
506
945
|
basePath: cleanPath,
|
|
507
946
|
meta: {
|
|
@@ -511,7 +950,7 @@ async function findContentRoutes(dir, base = dir, options = {}) {
|
|
|
511
950
|
group: typeof page.frontmatter.group === "string" ? page.frontmatter.group : void 0,
|
|
512
951
|
layout: page.frontmatter.layout === "documentation" || page.frontmatter.layout === "blog" ? page.frontmatter.layout : void 0,
|
|
513
952
|
updatedAt,
|
|
514
|
-
sections
|
|
953
|
+
sections
|
|
515
954
|
},
|
|
516
955
|
module: {
|
|
517
956
|
pageVirtualModuleId: pageVirtualModuleId(relativePath.replace(/\.mdx?$/, "")),
|
|
@@ -520,9 +959,10 @@ async function findContentRoutes(dir, base = dir, options = {}) {
|
|
|
520
959
|
source: {
|
|
521
960
|
filePath: fullPath,
|
|
522
961
|
frontmatter: page.frontmatter,
|
|
523
|
-
content: page.content
|
|
962
|
+
content: page.content,
|
|
963
|
+
lineOffset
|
|
524
964
|
},
|
|
525
|
-
diagnostic
|
|
965
|
+
diagnostic: analysis.diagnostic ?? compileDiagnostic
|
|
526
966
|
});
|
|
527
967
|
}
|
|
528
968
|
}
|
|
@@ -1628,10 +2068,14 @@ function createNormalizedContentPlugin(engine) {
|
|
|
1628
2068
|
transform(_code, id) {
|
|
1629
2069
|
if (!/\.mdx?(?:\?|$)/.test(id)) return null;
|
|
1630
2070
|
const filePath = id.replace(/\?.*$/, "");
|
|
1631
|
-
const route = engine.routes.find((route) =>
|
|
1632
|
-
if (!route || route.source.content === void 0) return null;
|
|
2071
|
+
const route = engine.routes.find((route) => route.source.filePath === filePath);
|
|
2072
|
+
if (!route || route.kind !== "markdown+jsx" && route.kind !== "markdown" || route.source.content === void 0) return null;
|
|
1633
2073
|
return {
|
|
1634
|
-
code: route.source.content,
|
|
2074
|
+
code: analyzeHeadings(route.source.content, {
|
|
2075
|
+
kind: route.kind,
|
|
2076
|
+
filePath,
|
|
2077
|
+
projectRoot: engine.root
|
|
2078
|
+
}).normalizedContent,
|
|
1635
2079
|
map: null
|
|
1636
2080
|
};
|
|
1637
2081
|
}
|
|
@@ -1821,7 +2265,7 @@ function createViteAdapter(engine) {
|
|
|
1821
2265
|
}
|
|
1822
2266
|
var cliPackageVersion = {
|
|
1823
2267
|
name: "@clarify-labs/cli",
|
|
1824
|
-
version: "0.11.
|
|
2268
|
+
version: "0.11.20",
|
|
1825
2269
|
license: "AGPL-3.0-only",
|
|
1826
2270
|
type: "module",
|
|
1827
2271
|
repository: {
|
|
@@ -2024,6 +2468,191 @@ function createLlmsTxtArtifact(routes, projectConfig) {
|
|
|
2024
2468
|
return withUtf8Signature(createLlmsTxt(routes, projectConfig));
|
|
2025
2469
|
}
|
|
2026
2470
|
//#endregion
|
|
2471
|
+
//#region source/plugins/openapi/aggregation.ts
|
|
2472
|
+
function mergeOpenAPIRecord(target, source, section, filePath) {
|
|
2473
|
+
if (!source) return;
|
|
2474
|
+
for (const [key, value] of Object.entries(source)) {
|
|
2475
|
+
const existing = target[key];
|
|
2476
|
+
if (existing !== void 0 && JSON.stringify(existing) !== JSON.stringify(value)) throw new Error(`[clarify] Cannot aggregate OpenAPI specs: conflicting ${section} entry "${key}" in ${filePath}.`);
|
|
2477
|
+
target[key] = value;
|
|
2478
|
+
}
|
|
2479
|
+
}
|
|
2480
|
+
function openAPIVersionFamily(version) {
|
|
2481
|
+
return version.split(".").slice(0, 2).join(".");
|
|
2482
|
+
}
|
|
2483
|
+
var OPENAPI_OPERATION_METHODS = /* @__PURE__ */ new Set([
|
|
2484
|
+
"get",
|
|
2485
|
+
"put",
|
|
2486
|
+
"post",
|
|
2487
|
+
"delete",
|
|
2488
|
+
"options",
|
|
2489
|
+
"head",
|
|
2490
|
+
"patch",
|
|
2491
|
+
"trace"
|
|
2492
|
+
]);
|
|
2493
|
+
function sourceComponentPrefix(source) {
|
|
2494
|
+
return (source.id || source.filePath.split(/[\\/]/).pop()?.replace(/\.openapi\.(?:json|ya?ml)$/i, "") || "service").replace(/[^a-zA-Z0-9._-]/g, "_") || "service";
|
|
2495
|
+
}
|
|
2496
|
+
function securitySchemeNameMap(source, sourceSchemes, targetSchemes) {
|
|
2497
|
+
const names = /* @__PURE__ */ new Map();
|
|
2498
|
+
for (const [name, definition] of Object.entries(sourceSchemes)) {
|
|
2499
|
+
const existing = targetSchemes[name];
|
|
2500
|
+
if (existing === void 0 || JSON.stringify(existing) === JSON.stringify(definition)) {
|
|
2501
|
+
targetSchemes[name] = definition;
|
|
2502
|
+
names.set(name, name);
|
|
2503
|
+
continue;
|
|
2504
|
+
}
|
|
2505
|
+
const prefix = sourceComponentPrefix(source);
|
|
2506
|
+
let candidate = `${prefix}__${name}`;
|
|
2507
|
+
let suffix = 2;
|
|
2508
|
+
while (targetSchemes[candidate] !== void 0 && JSON.stringify(targetSchemes[candidate]) !== JSON.stringify(definition)) candidate = `${prefix}__${name}_${suffix++}`;
|
|
2509
|
+
targetSchemes[candidate] = definition;
|
|
2510
|
+
names.set(name, candidate);
|
|
2511
|
+
}
|
|
2512
|
+
return names;
|
|
2513
|
+
}
|
|
2514
|
+
function rewriteSecurityRequirements(value, schemeNames) {
|
|
2515
|
+
if (!Array.isArray(value)) return value;
|
|
2516
|
+
return value.map((requirement) => {
|
|
2517
|
+
if (!requirement || typeof requirement !== "object" || Array.isArray(requirement)) return requirement;
|
|
2518
|
+
return Object.fromEntries(Object.entries(requirement).map(([name, scopes]) => [schemeNames.get(name) ?? name, scopes]));
|
|
2519
|
+
});
|
|
2520
|
+
}
|
|
2521
|
+
function localizePathItemContext(pathItemValue, documentServers, documentSecurity, hasDocumentSecurity, schemeNames) {
|
|
2522
|
+
if (!pathItemValue || typeof pathItemValue !== "object" || Array.isArray(pathItemValue)) return pathItemValue;
|
|
2523
|
+
const pathItem = structuredClone(pathItemValue);
|
|
2524
|
+
const pathServers = Object.hasOwn(pathItem, "servers") ? pathItem.servers : documentServers;
|
|
2525
|
+
for (const [method, operationValue] of Object.entries(pathItem)) {
|
|
2526
|
+
if (!OPENAPI_OPERATION_METHODS.has(method) || !operationValue || typeof operationValue !== "object" || Array.isArray(operationValue)) continue;
|
|
2527
|
+
const operation = operationValue;
|
|
2528
|
+
if (!Object.hasOwn(operation, "servers") && pathServers !== void 0) operation.servers = structuredClone(pathServers);
|
|
2529
|
+
if (Object.hasOwn(operation, "security")) operation.security = rewriteSecurityRequirements(operation.security, schemeNames);
|
|
2530
|
+
else if (hasDocumentSecurity) operation.security = rewriteSecurityRequirements(structuredClone(documentSecurity), schemeNames);
|
|
2531
|
+
}
|
|
2532
|
+
delete pathItem.servers;
|
|
2533
|
+
return pathItem;
|
|
2534
|
+
}
|
|
2535
|
+
function localizeOpenAPIContexts(spec, schemeNames) {
|
|
2536
|
+
const document = spec;
|
|
2537
|
+
const hasDocumentSecurity = Object.hasOwn(document, "security");
|
|
2538
|
+
const localizeEntries = (entries) => entries && Object.fromEntries(Object.entries(entries).map(([name, pathItem]) => [name, localizePathItemContext(pathItem, document.servers, document.security, hasDocumentSecurity, schemeNames)]));
|
|
2539
|
+
return {
|
|
2540
|
+
paths: localizeEntries(spec.paths) ?? {},
|
|
2541
|
+
webhooks: localizeEntries("webhooks" in spec ? spec.webhooks : void 0)
|
|
2542
|
+
};
|
|
2543
|
+
}
|
|
2544
|
+
function aggregateOpenAPISources(sources, info) {
|
|
2545
|
+
const paths = {};
|
|
2546
|
+
const webhooks = {};
|
|
2547
|
+
const components = {};
|
|
2548
|
+
const tags = /* @__PURE__ */ new Map();
|
|
2549
|
+
let openapiVersion;
|
|
2550
|
+
for (const source of sources) {
|
|
2551
|
+
const { document: spec, filePath } = source;
|
|
2552
|
+
if (openapiVersion && openAPIVersionFamily(openapiVersion) !== openAPIVersionFamily(spec.openapi)) throw new Error(`[clarify] Cannot aggregate OpenAPI specs: incompatible versions "${openapiVersion}" and "${spec.openapi}" in ${filePath}.`);
|
|
2553
|
+
openapiVersion ??= spec.openapi;
|
|
2554
|
+
components.securitySchemes ??= {};
|
|
2555
|
+
const sourceComponents = spec.components;
|
|
2556
|
+
const localized = localizeOpenAPIContexts(spec, securitySchemeNameMap(source, sourceComponents?.securitySchemes ?? {}, components.securitySchemes));
|
|
2557
|
+
mergeOpenAPIRecord(paths, localized.paths, "paths", filePath);
|
|
2558
|
+
mergeOpenAPIRecord(webhooks, localized.webhooks, "webhooks", filePath);
|
|
2559
|
+
for (const [section, entries] of Object.entries(sourceComponents ?? {})) {
|
|
2560
|
+
if (section === "securitySchemes") continue;
|
|
2561
|
+
components[section] ??= {};
|
|
2562
|
+
mergeOpenAPIRecord(components[section], entries, `components.${section}`, filePath);
|
|
2563
|
+
}
|
|
2564
|
+
for (const tag of spec.tags ?? []) {
|
|
2565
|
+
const existing = tags.get(tag.name);
|
|
2566
|
+
if (existing !== void 0 && JSON.stringify(existing) !== JSON.stringify(tag)) throw new Error(`[clarify] Cannot aggregate OpenAPI specs: conflicting tag "${tag.name}" in ${filePath}.`);
|
|
2567
|
+
tags.set(tag.name, tag);
|
|
2568
|
+
}
|
|
2569
|
+
}
|
|
2570
|
+
if (components.securitySchemes && Object.keys(components.securitySchemes).length === 0) delete components.securitySchemes;
|
|
2571
|
+
return {
|
|
2572
|
+
openapi: openapiVersion ?? "3.1.0",
|
|
2573
|
+
info: {
|
|
2574
|
+
title: info.title,
|
|
2575
|
+
description: info.description,
|
|
2576
|
+
version: info.version ?? "1.0.0"
|
|
2577
|
+
},
|
|
2578
|
+
paths,
|
|
2579
|
+
...Object.keys(webhooks).length > 0 ? { webhooks } : {},
|
|
2580
|
+
...Object.keys(components).length > 0 ? { components } : {},
|
|
2581
|
+
...tags.size > 0 ? { tags: [...tags.values()] } : {}
|
|
2582
|
+
};
|
|
2583
|
+
}
|
|
2584
|
+
//#endregion
|
|
2585
|
+
//#region source/plugins/openapi/artifacts.ts
|
|
2586
|
+
function selectRootOpenAPISources(routes, projectConfig) {
|
|
2587
|
+
const defaultLocale = projectConfig.locales?.default;
|
|
2588
|
+
const sourcesByFilePath = /* @__PURE__ */ new Map();
|
|
2589
|
+
for (const route of routes) {
|
|
2590
|
+
if (route.kind !== "openapi" || route.diagnostic || !route.source.content || route.openapi?.tagFilter?.length || route.isBareAlias || defaultLocale && route.locale && route.locale !== defaultLocale) continue;
|
|
2591
|
+
sourcesByFilePath.set(route.source.filePath, {
|
|
2592
|
+
id: route.openapi?.sourceSpecId ?? route.source.filePath.split(/[\\/]/).pop()?.replace(/\.openapi\.(?:json|ya?ml)$/i, "") ?? "service",
|
|
2593
|
+
filePath: route.source.filePath,
|
|
2594
|
+
document: JSON.parse(route.source.content)
|
|
2595
|
+
});
|
|
2596
|
+
}
|
|
2597
|
+
return [...sourcesByFilePath.values()];
|
|
2598
|
+
}
|
|
2599
|
+
function createRootOpenAPISpec(routes, projectConfig) {
|
|
2600
|
+
return aggregateOpenAPISources(selectRootOpenAPISources(routes, projectConfig), {
|
|
2601
|
+
title: projectConfig.title,
|
|
2602
|
+
description: projectConfig.description
|
|
2603
|
+
});
|
|
2604
|
+
}
|
|
2605
|
+
function createOpenAPIRouteArtifacts(route) {
|
|
2606
|
+
if (route.kind !== "openapi" || !route.source.content) return [];
|
|
2607
|
+
const contentArtifactUrl = route.artifacts?.contentArtifactUrl;
|
|
2608
|
+
if (!contentArtifactUrl) return [];
|
|
2609
|
+
return [{
|
|
2610
|
+
fileName: contentArtifactUrl.replace(/^\//, ""),
|
|
2611
|
+
contentType: "application/json; charset=utf-8",
|
|
2612
|
+
source: route.source.content
|
|
2613
|
+
}, {
|
|
2614
|
+
fileName: contentArtifactUrl.replace(/\.json$/, ".yaml").replace(/^\//, ""),
|
|
2615
|
+
contentType: "text/yaml; charset=utf-8",
|
|
2616
|
+
source: (0, yaml.stringify)(JSON.parse(route.source.content), { lineWidth: 0 })
|
|
2617
|
+
}];
|
|
2618
|
+
}
|
|
2619
|
+
function createRootOpenAPIArtifact(routes, projectConfig) {
|
|
2620
|
+
return {
|
|
2621
|
+
fileName: "openapi.json",
|
|
2622
|
+
contentType: "application/json; charset=utf-8",
|
|
2623
|
+
source: JSON.stringify(createRootOpenAPISpec(routes, projectConfig), null, 2)
|
|
2624
|
+
};
|
|
2625
|
+
}
|
|
2626
|
+
//#endregion
|
|
2627
|
+
//#region source/plugins/content-artifacts/provider.ts
|
|
2628
|
+
function createContentArtifacts(routes, projectConfig) {
|
|
2629
|
+
const artifacts = [];
|
|
2630
|
+
for (const route of routes) {
|
|
2631
|
+
if (route.kind === "openapi") {
|
|
2632
|
+
artifacts.push(...createOpenAPIRouteArtifacts(route));
|
|
2633
|
+
continue;
|
|
2634
|
+
}
|
|
2635
|
+
const contentArtifactUrl = route.artifacts?.contentArtifactUrl;
|
|
2636
|
+
if (!contentArtifactUrl) continue;
|
|
2637
|
+
artifacts.push({
|
|
2638
|
+
fileName: contentArtifactUrl.replace(/^\//, ""),
|
|
2639
|
+
contentType: "text/markdown; charset=utf-8",
|
|
2640
|
+
source: readRouteArtifactContent(route)
|
|
2641
|
+
});
|
|
2642
|
+
}
|
|
2643
|
+
artifacts.push({
|
|
2644
|
+
fileName: "llms.txt",
|
|
2645
|
+
contentType: "text/plain; charset=utf-8",
|
|
2646
|
+
source: createLlmsTxtArtifact(routes, projectConfig)
|
|
2647
|
+
});
|
|
2648
|
+
artifacts.push(createRootOpenAPIArtifact(routes, projectConfig));
|
|
2649
|
+
return artifacts;
|
|
2650
|
+
}
|
|
2651
|
+
function findContentArtifact(path, routes, projectConfig) {
|
|
2652
|
+
const fileName = path.replace(/^\/+/, "");
|
|
2653
|
+
return createContentArtifacts(routes, projectConfig).find((artifact) => artifact.fileName === fileName);
|
|
2654
|
+
}
|
|
2655
|
+
//#endregion
|
|
2027
2656
|
//#region source/plugins/content-artifacts/server.ts
|
|
2028
2657
|
function normalizeRoutePrefix$1(routePrefix) {
|
|
2029
2658
|
if (!routePrefix || routePrefix === "/") return "";
|
|
@@ -2035,34 +2664,12 @@ function resolveContentArtifactPath(url, projectConfig) {
|
|
|
2035
2664
|
const hasBasePath = requestPath === basePath || requestPath.startsWith(`${basePath}/`);
|
|
2036
2665
|
return basePath && hasBasePath ? requestPath.slice(basePath.length) || "/" : requestPath;
|
|
2037
2666
|
}
|
|
2038
|
-
function resolveContentArtifactType(route) {
|
|
2039
|
-
return route.kind === "openapi" ? "application/json; charset=utf-8" : "text/markdown; charset=utf-8";
|
|
2040
|
-
}
|
|
2041
2667
|
function serveContentArtifacts(req, res, projectConfig, routes) {
|
|
2042
|
-
const
|
|
2043
|
-
if (
|
|
2044
|
-
res.statusCode = 200;
|
|
2045
|
-
res.setHeader("Content-Type", "text/plain; charset=utf-8");
|
|
2046
|
-
res.end(createLlmsTxtArtifact(routes, projectConfig), "utf8");
|
|
2047
|
-
return true;
|
|
2048
|
-
}
|
|
2049
|
-
if (contentPath.endsWith(".openapi.yaml")) {
|
|
2050
|
-
const jsonPath = contentPath.replace(/\.yaml$/, ".json");
|
|
2051
|
-
const route = routes.find((route) => route.artifacts?.contentArtifactUrl === jsonPath);
|
|
2052
|
-
if (route?.kind === "openapi" && route.source.content) {
|
|
2053
|
-
const spec = JSON.parse(route.source.content);
|
|
2054
|
-
res.statusCode = 200;
|
|
2055
|
-
res.setHeader("Content-Type", "text/yaml; charset=utf-8");
|
|
2056
|
-
res.end((0, yaml.stringify)(spec, { lineWidth: 0 }), "utf8");
|
|
2057
|
-
return true;
|
|
2058
|
-
}
|
|
2059
|
-
return false;
|
|
2060
|
-
}
|
|
2061
|
-
const route = routes.find((route) => route.artifacts?.contentArtifactUrl === contentPath);
|
|
2062
|
-
if (!route) return false;
|
|
2668
|
+
const artifact = findContentArtifact(resolveContentArtifactPath(req.url, projectConfig), routes, projectConfig);
|
|
2669
|
+
if (!artifact) return false;
|
|
2063
2670
|
res.statusCode = 200;
|
|
2064
|
-
res.setHeader("Content-Type",
|
|
2065
|
-
res.end(
|
|
2671
|
+
res.setHeader("Content-Type", artifact.contentType);
|
|
2672
|
+
res.end(artifact.source);
|
|
2066
2673
|
return true;
|
|
2067
2674
|
}
|
|
2068
2675
|
//#endregion
|
|
@@ -2083,30 +2690,7 @@ function createContentArtifactsPlugin() {
|
|
|
2083
2690
|
});
|
|
2084
2691
|
};
|
|
2085
2692
|
},
|
|
2086
|
-
"build:assets": (ctx) =>
|
|
2087
|
-
const assets = [];
|
|
2088
|
-
for (const route of ctx.routes) {
|
|
2089
|
-
const contentArtifactUrl = route.artifacts?.contentArtifactUrl;
|
|
2090
|
-
if (!contentArtifactUrl) continue;
|
|
2091
|
-
assets.push({
|
|
2092
|
-
fileName: contentArtifactUrl.replace(/^\//, ""),
|
|
2093
|
-
source: readRouteArtifactContent(route)
|
|
2094
|
-
});
|
|
2095
|
-
if (route.kind === "openapi" && route.source.content) {
|
|
2096
|
-
const yamlFileName = contentArtifactUrl.replace(/\.json$/, ".yaml");
|
|
2097
|
-
const spec = JSON.parse(route.source.content);
|
|
2098
|
-
assets.push({
|
|
2099
|
-
fileName: yamlFileName.replace(/^\//, ""),
|
|
2100
|
-
source: (0, yaml.stringify)(spec, { lineWidth: 0 })
|
|
2101
|
-
});
|
|
2102
|
-
}
|
|
2103
|
-
}
|
|
2104
|
-
assets.push({
|
|
2105
|
-
fileName: "llms.txt",
|
|
2106
|
-
source: createLlmsTxtArtifact(ctx.routes, ctx.projectConfig)
|
|
2107
|
-
});
|
|
2108
|
-
return assets;
|
|
2109
|
-
}
|
|
2693
|
+
"build:assets": (ctx) => createContentArtifacts(ctx.routes, ctx.projectConfig)
|
|
2110
2694
|
}
|
|
2111
2695
|
};
|
|
2112
2696
|
}
|
|
@@ -3447,7 +4031,7 @@ async function resolveRoutePages(routes, plugins, ctx) {
|
|
|
3447
4031
|
pageByRouteIndex.set(routeIndex, page);
|
|
3448
4032
|
}
|
|
3449
4033
|
if (pageByRouteIndex.size !== routes.length) throw new Error("[clarify] pages:resolved hooks must return exactly one page for each route");
|
|
3450
|
-
|
|
4034
|
+
const updatedRoutes = routes.map((route, routeIndex) => {
|
|
3451
4035
|
const page = pageByRouteIndex.get(routeIndex);
|
|
3452
4036
|
return {
|
|
3453
4037
|
...route,
|
|
@@ -3458,6 +4042,7 @@ async function resolveRoutePages(routes, plugins, ctx) {
|
|
|
3458
4042
|
}
|
|
3459
4043
|
};
|
|
3460
4044
|
});
|
|
4045
|
+
return Promise.all(updatedRoutes.map((route) => reanalyzeMarkdownRoute(route, ctx.projectRoot)));
|
|
3461
4046
|
}
|
|
3462
4047
|
function buildNavigationTree(routes, ctx) {
|
|
3463
4048
|
const tabs = ctx.projectConfig.navigation?.tabs;
|
|
@@ -4055,8 +4640,9 @@ var ClarifyEngine = class {
|
|
|
4055
4640
|
* - `skipModules`: skip `buildModules()` (check command).
|
|
4056
4641
|
* - `skipHints`: skip `logStartupHints()` (check command).
|
|
4057
4642
|
*
|
|
4058
|
-
*
|
|
4059
|
-
*
|
|
4643
|
+
* Configuration initialization is cached unless `force` is set. Discovery
|
|
4644
|
+
* and virtual module generation intentionally run on every call so callers
|
|
4645
|
+
* observe content changes without rebuilding the Engine instance.
|
|
4060
4646
|
*/
|
|
4061
4647
|
async prepare(env = {
|
|
4062
4648
|
command: this.runtime.command,
|
|
@@ -4176,10 +4762,11 @@ var ClarifyEngine = class {
|
|
|
4176
4762
|
assertNoContentDiagnostics(this.routes);
|
|
4177
4763
|
const outputDir = this.runtime.outputDirectory ?? this.generateOptions.outputDirectory;
|
|
4178
4764
|
if (!outputDir) throw new Error("[clarify] outputDirectory is required before SSG runs");
|
|
4179
|
-
|
|
4765
|
+
let ssrOutputDir;
|
|
4180
4766
|
let tempEntryPath;
|
|
4181
4767
|
try {
|
|
4182
4768
|
tempEntryPath = createTempEntryFile(SSR_ENTRY_CODE);
|
|
4769
|
+
ssrOutputDir = createClarifyTempDir("ssr-output");
|
|
4183
4770
|
await (this.runtime.buildSSRBundle ?? ((options) => buildSSRBundle(options.root, options.ssrEntry, options.ssrOutDir, options.plugins)))({
|
|
4184
4771
|
root: this.root,
|
|
4185
4772
|
ssrEntry: tempEntryPath,
|
|
@@ -4192,9 +4779,8 @@ var ClarifyEngine = class {
|
|
|
4192
4779
|
console.error("[clarify] SSG failed:", err);
|
|
4193
4780
|
throw err;
|
|
4194
4781
|
} finally {
|
|
4195
|
-
|
|
4196
|
-
|
|
4197
|
-
} catch {}
|
|
4782
|
+
removeClarifyTempDir(tempEntryPath ? (0, node_path.resolve)(tempEntryPath, "..") : void 0);
|
|
4783
|
+
removeClarifyTempDir(ssrOutputDir);
|
|
4198
4784
|
}
|
|
4199
4785
|
});
|
|
4200
4786
|
await this.runBuildDone();
|
|
@@ -4305,4 +4891,4 @@ Object.defineProperty(exports, "searchMcpIndex", {
|
|
|
4305
4891
|
}
|
|
4306
4892
|
});
|
|
4307
4893
|
|
|
4308
|
-
//# sourceMappingURL=engine-
|
|
4894
|
+
//# sourceMappingURL=engine-CAis1rme.cjs.map
|