rapidoc 11.0.1 → 12.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +171 -54
- package/dist/rapidoc-flow-min.js +580 -0
- package/dist/rapidoc-flow-min.js.LICENSE.txt +171 -0
- package/dist/rapidoc-min.js +645 -202
- package/dist/rapidoc-min.js.LICENSE.txt +381 -0
- package/package.json +57 -42
- package/src/README.md +150 -0
- package/src/components/README.md +91 -0
- package/src/components/api-request.ts +912 -0
- package/src/components/api-response.ts +514 -0
- package/src/components/json-tree.ts +171 -0
- package/src/components/schema-base-element.ts +329 -0
- package/src/components/schema-table.ts +475 -0
- package/src/components/schema-tree.ts +393 -0
- package/src/components/tag-input.ts +180 -0
- package/src/flow.js +3 -0
- package/src/index.js +6 -0
- package/src/oauth-receiver.ts +29 -0
- package/src/rapi-flow.ts +1178 -0
- package/src/rapidoc-mini.ts +603 -0
- package/src/rapidoc.ts +1854 -0
- package/src/styles/README.md +96 -0
- package/src/styles/auth-styles.js +318 -0
- package/src/styles/border-styles.js +24 -0
- package/src/styles/css-bundle.js +129 -0
- package/src/styles/custom-styles.js +8 -0
- package/src/styles/dialog-box-styles.js +102 -0
- package/src/styles/endpoint-styles.js +564 -0
- package/src/styles/flex-styles.js +16 -0
- package/src/styles/flow-styles.js +248 -0
- package/src/styles/font-styles.js +432 -0
- package/src/styles/info-styles.js +36 -0
- package/src/styles/input-styles.js +555 -0
- package/src/styles/layout-styles.js +396 -0
- package/src/styles/microlighter-styles.js +99 -0
- package/src/styles/nav-styles.js +633 -0
- package/src/styles/param-list-styles.js +261 -0
- package/src/styles/schema-styles.js +296 -0
- package/src/styles/tab-styles.js +106 -0
- package/src/styles/theme-tokens.js +582 -0
- package/src/templates/README.md +89 -0
- package/src/templates/advanced-search-dialog-template.js +94 -0
- package/src/templates/api-response-template.js +223 -0
- package/src/templates/callback-template.js +86 -0
- package/src/templates/code-samples-template.js +52 -0
- package/src/templates/components-template.js +86 -0
- package/src/templates/downloads-template.js +70 -0
- package/src/templates/endpoint-template.js +377 -0
- package/src/templates/expanded-endpoint-template.js +270 -0
- package/src/templates/focused-endpoint-template.js +146 -0
- package/src/templates/header-template.js +92 -0
- package/src/templates/logo-template.js +21 -0
- package/src/templates/main-body-template.js +102 -0
- package/src/templates/navbar-template.js +498 -0
- package/src/templates/overview-template.js +104 -0
- package/src/templates/request-body-template.js +673 -0
- package/src/templates/request-params-template.js +485 -0
- package/src/templates/security-scheme-template.js +1272 -0
- package/src/templates/server-template.js +153 -0
- package/src/utils/README.md +146 -0
- package/src/utils/arazzo/README.md +119 -0
- package/src/utils/arazzo/components.js +15 -0
- package/src/utils/arazzo/graph.js +269 -0
- package/src/utils/arazzo/index.js +52 -0
- package/src/utils/arazzo/operations.js +106 -0
- package/src/utils/arazzo/parse.js +196 -0
- package/src/utils/arazzo/refs.js +40 -0
- package/src/utils/arazzo/svg.js +707 -0
- package/src/utils/auto-color-scheme.js +99 -0
- package/src/utils/bind-navbar.js +175 -0
- package/src/utils/color-utils.js +59 -0
- package/src/utils/common-utils.js +191 -0
- package/src/utils/curl-utils.js +126 -0
- package/src/utils/highlighter.js +419 -0
- package/src/utils/markdown-utils.js +110 -0
- package/src/utils/mcp/README.md +155 -0
- package/src/utils/mcp/common.js +166 -0
- package/src/utils/mcp/manifest.js +382 -0
- package/src/utils/mcp/project/server.ts +53 -0
- package/src/utils/mcp/project.js +88 -0
- package/src/utils/mcp/runtime.js +289 -0
- package/src/utils/mcp/schema.js +98 -0
- package/src/utils/mcp/workflow-runtime.js +374 -0
- package/src/utils/mcp/workflows.js +240 -0
- package/src/utils/mcp/zip.js +102 -0
- package/src/utils/mcp-generator.js +47 -0
- package/src/utils/mock-interceptor.js +557 -0
- package/src/utils/oauth-utils.js +87 -0
- package/src/utils/request-utils.js +264 -0
- package/src/utils/response-utils.js +203 -0
- package/src/utils/sanitize.js +57 -0
- package/src/utils/schema-utils.js +1954 -0
- package/src/utils/slugger.js +187 -0
- package/src/utils/spec-parser.js +941 -0
- package/src/utils/url-utils.js +246 -0
- package/src/utils/xml-utils.js +61 -0
|
@@ -0,0 +1,419 @@
|
|
|
1
|
+
// CSS Custom Highlight API-based Syntax Highlighter using bundled TextMate grammars
|
|
2
|
+
|
|
3
|
+
import astro from "microlighter/grammars/astro.js";
|
|
4
|
+
import bash from "microlighter/grammars/bash.js";
|
|
5
|
+
import c from "microlighter/grammars/c.js";
|
|
6
|
+
import cpp from "microlighter/grammars/cpp.js";
|
|
7
|
+
import csharp from "microlighter/grammars/csharp.js";
|
|
8
|
+
import cssGrammar from "microlighter/grammars/css.js";
|
|
9
|
+
import go from "microlighter/grammars/go.js";
|
|
10
|
+
import html from "microlighter/grammars/html.js";
|
|
11
|
+
import java from "microlighter/grammars/java.js";
|
|
12
|
+
import javascript from "microlighter/grammars/javascript.js";
|
|
13
|
+
import json from "microlighter/grammars/json.js";
|
|
14
|
+
import kotlin from "microlighter/grammars/kotlin.js";
|
|
15
|
+
import markdown from "microlighter/grammars/markdown.js";
|
|
16
|
+
import objectiveC from "microlighter/grammars/objective-c.js";
|
|
17
|
+
import perl from "microlighter/grammars/perl.js";
|
|
18
|
+
import php from "microlighter/grammars/php.js";
|
|
19
|
+
import powershell from "microlighter/grammars/powershell.js";
|
|
20
|
+
import python from "microlighter/grammars/python.js";
|
|
21
|
+
import r from "microlighter/grammars/r.js";
|
|
22
|
+
import ruby from "microlighter/grammars/ruby.js";
|
|
23
|
+
import rust from "microlighter/grammars/rust.js";
|
|
24
|
+
import svelte from "microlighter/grammars/svelte.js";
|
|
25
|
+
import toml from "microlighter/grammars/toml.js";
|
|
26
|
+
import tsx from "microlighter/grammars/tsx.js";
|
|
27
|
+
import typescript from "microlighter/grammars/typescript.js";
|
|
28
|
+
import vue from "microlighter/grammars/vue.js";
|
|
29
|
+
import yaml from "microlighter/grammars/yaml.js";
|
|
30
|
+
|
|
31
|
+
const httpGrammar = {
|
|
32
|
+
scopeName: "source.http",
|
|
33
|
+
patterns: [
|
|
34
|
+
{
|
|
35
|
+
match:
|
|
36
|
+
"^(GET|POST|PUT|DELETE|PATCH|HEAD|OPTIONS|TRACE|CONNECT)\\s+([^\\s]+)\\s+(HTTP\\/[0-9.]+)",
|
|
37
|
+
captures: {
|
|
38
|
+
1: { name: "keyword.control.http" },
|
|
39
|
+
2: { name: "string.unquoted.http" },
|
|
40
|
+
3: { name: "keyword.other.http" },
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
match: "^(HTTP\\/[0-9.]+)\\s+([0-9]{3})(?:\\s+(.*))?",
|
|
45
|
+
captures: {
|
|
46
|
+
1: { name: "keyword.other.http" },
|
|
47
|
+
2: { name: "constant.numeric.http" },
|
|
48
|
+
3: { name: "string.unquoted.http" },
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
match: "^([a-zA-Z0-9_-]+):\\s*(.*)",
|
|
53
|
+
captures: {
|
|
54
|
+
1: { name: "support.type.property-name.http" },
|
|
55
|
+
2: { name: "string.unquoted.http" },
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
],
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
const grammars = {
|
|
62
|
+
astro,
|
|
63
|
+
bash,
|
|
64
|
+
c,
|
|
65
|
+
cpp,
|
|
66
|
+
csharp,
|
|
67
|
+
css: cssGrammar,
|
|
68
|
+
go,
|
|
69
|
+
html,
|
|
70
|
+
http: httpGrammar,
|
|
71
|
+
java,
|
|
72
|
+
javascript,
|
|
73
|
+
json,
|
|
74
|
+
kotlin,
|
|
75
|
+
markdown,
|
|
76
|
+
"objective-c": objectiveC,
|
|
77
|
+
perl,
|
|
78
|
+
php,
|
|
79
|
+
powershell,
|
|
80
|
+
python,
|
|
81
|
+
r,
|
|
82
|
+
ruby,
|
|
83
|
+
rust,
|
|
84
|
+
svelte,
|
|
85
|
+
toml,
|
|
86
|
+
tsx,
|
|
87
|
+
typescript,
|
|
88
|
+
vue,
|
|
89
|
+
yaml,
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
const scopes = new Map();
|
|
93
|
+
Object.values(grammars).forEach((grammar) => {
|
|
94
|
+
if (grammar?.scopeName) {
|
|
95
|
+
scopes.set(grammar.scopeName, grammar);
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
const languageAliases = {
|
|
100
|
+
"c++": "cpp",
|
|
101
|
+
cs: "csharp",
|
|
102
|
+
curl: "bash",
|
|
103
|
+
js: "javascript",
|
|
104
|
+
jsx: "javascript",
|
|
105
|
+
markup: "html",
|
|
106
|
+
md: "markdown",
|
|
107
|
+
objc: "objective-c",
|
|
108
|
+
ps1: "powershell",
|
|
109
|
+
py: "python",
|
|
110
|
+
rb: "ruby",
|
|
111
|
+
sh: "bash",
|
|
112
|
+
shell: "bash",
|
|
113
|
+
svg: "html",
|
|
114
|
+
ts: "typescript",
|
|
115
|
+
xml: "html",
|
|
116
|
+
yml: "yaml",
|
|
117
|
+
zsh: "bash",
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
const noMatch = { indices: [[Infinity, Infinity]] };
|
|
121
|
+
const highlights = new Map();
|
|
122
|
+
|
|
123
|
+
const getCategory = (scope) => {
|
|
124
|
+
const parts = scope.split(".");
|
|
125
|
+
const [first, second, third] = parts;
|
|
126
|
+
const last = parts.at(-1);
|
|
127
|
+
|
|
128
|
+
if (first === "markup" && ["quote", "inserted", "deleted", "raw"].includes(second)) return second;
|
|
129
|
+
if (first === "entity" && second === "name") return third;
|
|
130
|
+
if (scope.startsWith("constant.character.entity")) return "character-entity";
|
|
131
|
+
if (parts.includes("numeric")) return "numeric";
|
|
132
|
+
if (scope.startsWith("support.type.property-name")) return "property";
|
|
133
|
+
if (parts.includes("attribute-value")) return "attribute-value";
|
|
134
|
+
if (scope.startsWith("string.other.link")) return "link";
|
|
135
|
+
|
|
136
|
+
if (
|
|
137
|
+
[
|
|
138
|
+
"doctype",
|
|
139
|
+
"at-rule",
|
|
140
|
+
"important",
|
|
141
|
+
"regexp",
|
|
142
|
+
"boolean",
|
|
143
|
+
"symbol",
|
|
144
|
+
"operator",
|
|
145
|
+
"attribute-name",
|
|
146
|
+
].includes(last)
|
|
147
|
+
)
|
|
148
|
+
return last;
|
|
149
|
+
|
|
150
|
+
if (
|
|
151
|
+
[
|
|
152
|
+
"comment",
|
|
153
|
+
"string",
|
|
154
|
+
"constant",
|
|
155
|
+
"storage",
|
|
156
|
+
"keyword",
|
|
157
|
+
"variable",
|
|
158
|
+
"punctuation",
|
|
159
|
+
"entity",
|
|
160
|
+
"support",
|
|
161
|
+
].includes(first)
|
|
162
|
+
)
|
|
163
|
+
return first;
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
const addRange = (node, start, end, scope) => {
|
|
167
|
+
const category = getCategory(scope);
|
|
168
|
+
if (!category || start === end) return;
|
|
169
|
+
|
|
170
|
+
const range = new Range();
|
|
171
|
+
range.setStart(node, start);
|
|
172
|
+
range.setEnd(node, end);
|
|
173
|
+
|
|
174
|
+
if (!highlights.has(category)) {
|
|
175
|
+
highlights.set(category, new Highlight());
|
|
176
|
+
}
|
|
177
|
+
highlights.get(category).add(range);
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
const addCaptures = (node, match, captures = {}) => {
|
|
181
|
+
Object.entries(captures).forEach(([index, capture]) => {
|
|
182
|
+
const offsets = match.indices[+index];
|
|
183
|
+
if (offsets) addRange(node, ...offsets, capture.name);
|
|
184
|
+
});
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
const escapeRegex = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
188
|
+
|
|
189
|
+
const expandEnd = (pattern, beginMatch) =>
|
|
190
|
+
pattern.replace(/\\(\d+)/g, (reference, index) =>
|
|
191
|
+
beginMatch[index] === undefined ? reference : escapeRegex(beginMatch[index]),
|
|
192
|
+
);
|
|
193
|
+
|
|
194
|
+
const getRules = (rule) => {
|
|
195
|
+
if (!rule) return [];
|
|
196
|
+
if (Array.isArray(rule)) return rule;
|
|
197
|
+
if (rule.match || rule.begin || rule.include) return [rule];
|
|
198
|
+
return rule.patterns || [];
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
const resolveInclude = (include, grammar, baseGrammar) => {
|
|
202
|
+
if (include === "$self") return { grammar, rules: grammar.patterns };
|
|
203
|
+
if (include === "$base") return { grammar: baseGrammar, rules: baseGrammar.patterns };
|
|
204
|
+
|
|
205
|
+
if (include[0] === "#") {
|
|
206
|
+
return { grammar, rules: getRules(grammar.repository?.[include.slice(1)]) };
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const [scopeName, repositoryName] = include.split("#");
|
|
210
|
+
const includedGrammar = scopes.get(scopeName);
|
|
211
|
+
if (!includedGrammar) return null;
|
|
212
|
+
|
|
213
|
+
return {
|
|
214
|
+
grammar: includedGrammar,
|
|
215
|
+
rules: repositoryName
|
|
216
|
+
? getRules(includedGrammar.repository?.[repositoryName])
|
|
217
|
+
: includedGrammar.patterns,
|
|
218
|
+
};
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
const expandRules = (rules, grammar, baseGrammar, activeIncludes = new Set()) => {
|
|
222
|
+
const expanded = [];
|
|
223
|
+
rules.forEach((rule) => {
|
|
224
|
+
if (rule.include) {
|
|
225
|
+
const includeKey = `${grammar.scopeName}:${rule.include}`;
|
|
226
|
+
if (activeIncludes.has(includeKey)) return;
|
|
227
|
+
|
|
228
|
+
const included = resolveInclude(rule.include, grammar, baseGrammar);
|
|
229
|
+
if (!included) return;
|
|
230
|
+
|
|
231
|
+
const nestedIncludes = new Set(activeIncludes);
|
|
232
|
+
nestedIncludes.add(includeKey);
|
|
233
|
+
expanded.push(...expandRules(included.rules, included.grammar, baseGrammar, nestedIncludes));
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
if (rule.match || (rule.begin && rule.end)) expanded.push({ grammar, rule });
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
return expanded;
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
const regexes = new Map();
|
|
244
|
+
let matches = new Map();
|
|
245
|
+
|
|
246
|
+
const exec = (pattern, node, start, end) => {
|
|
247
|
+
let match = matches.get(pattern);
|
|
248
|
+
if (!match || match.indices[0][0] < start) {
|
|
249
|
+
if (!regexes.has(pattern)) regexes.set(pattern, new RegExp(pattern, "dgm"));
|
|
250
|
+
const regex = regexes.get(pattern);
|
|
251
|
+
regex.lastIndex = start;
|
|
252
|
+
match = regex.exec(node.data) || noMatch;
|
|
253
|
+
matches.set(pattern, match);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
return match.indices[0][0] < end && match.indices[0][1] <= end ? match : null;
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
const nextRule = (node, contexts, start, end) => {
|
|
260
|
+
let winner = null;
|
|
261
|
+
contexts.forEach((context) => {
|
|
262
|
+
const pattern = context.rule.match || context.rule.begin;
|
|
263
|
+
const match = exec(pattern, node, start, end);
|
|
264
|
+
if (!match) return;
|
|
265
|
+
|
|
266
|
+
if (!winner || match.indices[0][0] < winner.match.indices[0][0]) {
|
|
267
|
+
winner = { ...context, match };
|
|
268
|
+
}
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
return winner;
|
|
272
|
+
};
|
|
273
|
+
|
|
274
|
+
const scanRegion = (node, rules, start, end, grammar, baseGrammar = grammar, closing = null) => {
|
|
275
|
+
const contexts = expandRules(rules, grammar, baseGrammar);
|
|
276
|
+
let cursor = start;
|
|
277
|
+
|
|
278
|
+
while (cursor < end) {
|
|
279
|
+
const candidate = nextRule(node, contexts, cursor, end);
|
|
280
|
+
const endMatch = closing ? exec(closing.pattern, node, cursor, end) : null;
|
|
281
|
+
const candidateStart = candidate?.match.indices[0][0] ?? Infinity;
|
|
282
|
+
const endStart = endMatch?.indices[0][0] ?? Infinity;
|
|
283
|
+
|
|
284
|
+
if (
|
|
285
|
+
endMatch &&
|
|
286
|
+
(endStart < candidateStart || (endStart === candidateStart && !closing?.applyEndPatternLast))
|
|
287
|
+
) {
|
|
288
|
+
return { contentEnd: endStart, end: endMatch.indices[0][1], match: endMatch };
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
if (!candidate) return { contentEnd: end, end, match: null };
|
|
292
|
+
|
|
293
|
+
const { rule, match, grammar: ruleGrammar } = candidate;
|
|
294
|
+
if (rule.match) {
|
|
295
|
+
if (rule.name) addRange(node, ...match.indices[0], rule.name);
|
|
296
|
+
addCaptures(node, match, rule.captures);
|
|
297
|
+
cursor = match.indices[0][1] > cursor ? match.indices[0][1] : cursor + 1;
|
|
298
|
+
continue;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
const nested = scanRegion(
|
|
302
|
+
node,
|
|
303
|
+
rule.patterns || [],
|
|
304
|
+
match.indices[0][1],
|
|
305
|
+
end,
|
|
306
|
+
ruleGrammar,
|
|
307
|
+
baseGrammar,
|
|
308
|
+
{
|
|
309
|
+
pattern: expandEnd(rule.end, match),
|
|
310
|
+
applyEndPatternLast: rule.applyEndPatternLast,
|
|
311
|
+
},
|
|
312
|
+
);
|
|
313
|
+
|
|
314
|
+
if (rule.name) addRange(node, match.indices[0][0], nested.end, rule.name);
|
|
315
|
+
if (rule.contentName) addRange(node, match.indices[0][1], nested.contentEnd, rule.contentName);
|
|
316
|
+
addCaptures(node, match, rule.beginCaptures || rule.captures);
|
|
317
|
+
if (nested.match) addCaptures(node, nested.match, rule.endCaptures || rule.captures);
|
|
318
|
+
|
|
319
|
+
cursor = nested.end > cursor ? nested.end : cursor + 1;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
return { contentEnd: end, end, match: null };
|
|
323
|
+
};
|
|
324
|
+
|
|
325
|
+
export const getLanguage = (codeBlock) => {
|
|
326
|
+
const pre = codeBlock.parentElement;
|
|
327
|
+
const getLangClass = (el) => {
|
|
328
|
+
if (!el || !el.classList) return null;
|
|
329
|
+
return [...el.classList].find((c) => c.startsWith("language-"))?.slice("language-".length);
|
|
330
|
+
};
|
|
331
|
+
const lang =
|
|
332
|
+
getLangClass(codeBlock) ||
|
|
333
|
+
codeBlock.dataset?.language ||
|
|
334
|
+
getLangClass(pre) ||
|
|
335
|
+
pre?.dataset?.language ||
|
|
336
|
+
pre?.getAttribute?.("lang") ||
|
|
337
|
+
"";
|
|
338
|
+
return lang.toLowerCase();
|
|
339
|
+
};
|
|
340
|
+
|
|
341
|
+
export const normalizeLanguage = (lang) => languageAliases[lang] || lang;
|
|
342
|
+
|
|
343
|
+
export const getAllCodeBlocks = (rootNode = document) => {
|
|
344
|
+
const blocks = [];
|
|
345
|
+
const visitedRoots = new Set();
|
|
346
|
+
|
|
347
|
+
const traverse = (node) => {
|
|
348
|
+
if (!node || visitedRoots.has(node)) return;
|
|
349
|
+
visitedRoots.add(node);
|
|
350
|
+
|
|
351
|
+
if (node.querySelectorAll) {
|
|
352
|
+
const codeEls = node.querySelectorAll("pre > code");
|
|
353
|
+
blocks.push(...codeEls);
|
|
354
|
+
|
|
355
|
+
const allChildren = node.querySelectorAll("*");
|
|
356
|
+
for (let i = 0; i < allChildren.length; i++) {
|
|
357
|
+
const child = allChildren[i];
|
|
358
|
+
if (child.shadowRoot) {
|
|
359
|
+
traverse(child.shadowRoot);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
};
|
|
364
|
+
|
|
365
|
+
traverse(rootNode);
|
|
366
|
+
return blocks;
|
|
367
|
+
};
|
|
368
|
+
|
|
369
|
+
let highlightScheduled = false;
|
|
370
|
+
|
|
371
|
+
export const highlightAllCode = (rootNode = document) => {
|
|
372
|
+
if (typeof window === "undefined" || typeof CSS === "undefined" || !CSS.highlights) {
|
|
373
|
+
return;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
// Find all code blocks across the root and any nested shadow roots
|
|
377
|
+
const codeBlocks = getAllCodeBlocks(rootNode).filter(getLanguage);
|
|
378
|
+
if (codeBlocks.length === 0) return;
|
|
379
|
+
|
|
380
|
+
// Clear previous ranges
|
|
381
|
+
highlights.forEach((ranges, category) => {
|
|
382
|
+
if (CSS.highlights.get(category) === ranges) CSS.highlights.delete(category);
|
|
383
|
+
ranges.clear();
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
codeBlocks.forEach((codeBlock) => {
|
|
387
|
+
const rawLang = getLanguage(codeBlock);
|
|
388
|
+
const lang = normalizeLanguage(rawLang);
|
|
389
|
+
const grammar = grammars[lang];
|
|
390
|
+
if (!grammar) return;
|
|
391
|
+
|
|
392
|
+
// Scan all text nodes without mutating or removing Lit marker comment nodes
|
|
393
|
+
const textNodes = Array.from(codeBlock.childNodes).filter(
|
|
394
|
+
(child) => child.nodeType === Node.TEXT_NODE && child.data?.length > 0,
|
|
395
|
+
);
|
|
396
|
+
if (textNodes.length === 0) return;
|
|
397
|
+
|
|
398
|
+
matches = new Map();
|
|
399
|
+
textNodes.forEach((node) => {
|
|
400
|
+
scanRegion(node, grammar.patterns, 0, node.data.length, grammar);
|
|
401
|
+
});
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
highlights.forEach((ranges, category) => {
|
|
405
|
+
if (ranges.size) CSS.highlights.set(category, ranges);
|
|
406
|
+
});
|
|
407
|
+
};
|
|
408
|
+
|
|
409
|
+
export const scheduleHighlight = (rootNode = document) => {
|
|
410
|
+
if (typeof window === "undefined" || typeof CSS === "undefined" || !CSS.highlights) {
|
|
411
|
+
return;
|
|
412
|
+
}
|
|
413
|
+
if (highlightScheduled) return;
|
|
414
|
+
highlightScheduled = true;
|
|
415
|
+
requestAnimationFrame(() => {
|
|
416
|
+
highlightScheduled = false;
|
|
417
|
+
highlightAllCode(rootNode);
|
|
418
|
+
});
|
|
419
|
+
};
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { marked } from "marked";
|
|
2
|
+
|
|
3
|
+
let configured = false;
|
|
4
|
+
|
|
5
|
+
function escapeAttribute(value) {
|
|
6
|
+
return value
|
|
7
|
+
.replace(/&/g, "&")
|
|
8
|
+
.replace(/"/g, """)
|
|
9
|
+
.replace(/</g, "<")
|
|
10
|
+
.replace(/>/g, ">");
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* When an HTML `<base>` tag is present in the page, a markdown in-page anchor link (e.g. `#link-to-a-section`) would
|
|
15
|
+
* resolve against the base URL instead of the current page. This renders such links with an absolute URL
|
|
16
|
+
* built from the current document location, and tags them with the `anchor-link` CSS class.
|
|
17
|
+
*
|
|
18
|
+
* @param {import('marked').Renderer} renderer
|
|
19
|
+
* @param {import('marked').Tokens.Link} token
|
|
20
|
+
* @returns {string | false} the rendered link, or `false` when the link is not an in-page anchor (use the default renderer)
|
|
21
|
+
* @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base#in-page_anchors
|
|
22
|
+
*/
|
|
23
|
+
function renderAnchorLink(renderer, { href, title, tokens }) {
|
|
24
|
+
if (!href?.startsWith("#") || typeof location === "undefined") {
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
const absoluteHref = `${location.href.replace(/#.*/, "")}${href}`;
|
|
28
|
+
const titleAttribute = title ? ` title="${escapeAttribute(title)}"` : "";
|
|
29
|
+
return `<a class="anchor-link" href="${escapeAttribute(absoluteHref)}"${titleAttribute}>${renderer.parser.parseInline(tokens)}</a>`;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Applies the in-page anchor fix to a custom renderer. A renderer passed per call, `marked(text, { renderer })`,
|
|
34
|
+
* replaces the global one configured by {@link configureMarkdown}, so custom renderers need it too.
|
|
35
|
+
*
|
|
36
|
+
* @param {import('marked').Renderer} renderer
|
|
37
|
+
*/
|
|
38
|
+
export function fixRenderedAnchorLinks(renderer) {
|
|
39
|
+
const defaultLink = renderer.link;
|
|
40
|
+
renderer.link = function link(token) {
|
|
41
|
+
const anchorLink = renderAnchorLink(this, token);
|
|
42
|
+
return anchorLink === false ? defaultLink.call(this, token) : anchorLink;
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Inline HTML for the first line of a paragraph-like token (paragraph, heading, list), with line breaks as spaces. */
|
|
47
|
+
export function firstLineHtml(token) {
|
|
48
|
+
// a list item's own text, without any nested list under it
|
|
49
|
+
const text =
|
|
50
|
+
token.type === "list"
|
|
51
|
+
? token.items[0]?.tokens?.find((t) => t.type === "text" || t.type === "paragraph")?.text
|
|
52
|
+
: token.text;
|
|
53
|
+
return marked.parseInline((text || "").replace(/\s*\n\s*/g, " ").trim());
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* One-line HTML stand-in for a markdown block that cannot be shown on a single line: a short placeholder
|
|
58
|
+
* for tables and code blocks, the plain text of an HTML block, and the first line of a blockquote.
|
|
59
|
+
*
|
|
60
|
+
* @param {import('marked').Token} token - a block token from `marked.lexer`
|
|
61
|
+
* @returns {string} HTML for the summary line, or '' when the block can be shown inline as it is
|
|
62
|
+
*/
|
|
63
|
+
export function blockSummary(token) {
|
|
64
|
+
switch (token?.type) {
|
|
65
|
+
case "table": {
|
|
66
|
+
const rows = token.rows.length;
|
|
67
|
+
return `<span class="descr-placeholder">Table · ${rows} ${rows === 1 ? "row" : "rows"}</span>`;
|
|
68
|
+
}
|
|
69
|
+
case "code":
|
|
70
|
+
return `<span class="descr-placeholder">Code block${token.lang ? ` · ${escapeAttribute(token.lang)}` : ""}</span>`;
|
|
71
|
+
case "html": {
|
|
72
|
+
const text = token.text
|
|
73
|
+
.replace(/<(script|style)\b[\s\S]*?<\/\1>/gi, "")
|
|
74
|
+
.replace(/<[^>]*>/g, " ")
|
|
75
|
+
.replace(/[<>]/g, "")
|
|
76
|
+
.replace(/\s+/g, " ")
|
|
77
|
+
.trim();
|
|
78
|
+
return text || '<span class="descr-placeholder">HTML content</span>';
|
|
79
|
+
}
|
|
80
|
+
case "blockquote": {
|
|
81
|
+
const first = token.tokens.find((t) => t.type !== "space");
|
|
82
|
+
if (!first) return "";
|
|
83
|
+
return ["paragraph", "heading", "list", "text"].includes(first.type)
|
|
84
|
+
? firstLineHtml(first)
|
|
85
|
+
: blockSummary(first);
|
|
86
|
+
}
|
|
87
|
+
default:
|
|
88
|
+
return "";
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Configures the global `marked` instance used by every `marked(...)` call that has no custom renderer.
|
|
94
|
+
* Idempotent: safe to import and call from several entry points.
|
|
95
|
+
*/
|
|
96
|
+
export function configureMarkdown() {
|
|
97
|
+
if (configured) {
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
configured = true;
|
|
101
|
+
marked.use({
|
|
102
|
+
renderer: {
|
|
103
|
+
link(token) {
|
|
104
|
+
return renderAnchorLink(this, token); // `false` falls back to marked's default link renderer
|
|
105
|
+
},
|
|
106
|
+
},
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
configureMarkdown();
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
# MCP Manifest, Runtime and Export
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
Turns an OpenAPI document into an **MCP manifest** (plain JSON: one tool per operation, with
|
|
6
|
+
JSON Schema arguments and MCP safety annotations) and executes that manifest with `fetch`. The
|
|
7
|
+
exported MCP server is the manifest plus two fixed files: `runtime.js` (this folder's runtime,
|
|
8
|
+
copied verbatim) and `project/server.ts` (a stdio server on `@modelcontextprotocol/sdk` that
|
|
9
|
+
registers the manifest's tools and hands calls to the runtime). The entry point is
|
|
10
|
+
`src/utils/mcp-generator.js`; `<rapi-doc>` (`src/rapidoc.ts`, `src/templates/downloads-template.js`)
|
|
11
|
+
uses it for its Export MCP Server button, and `packages/rapidoc-mcp/src/index.js` re-exports it as `@rapidoc/mcp`
|
|
12
|
+
(a separate package, Node only, no Lit) for the portal build, `@rapidoc/server` and the CLI.
|
|
13
|
+
|
|
14
|
+
Arazzo workflows become tools too: `workflows.js` compiles a parsed Arazzo document into
|
|
15
|
+
`manifest.workflows` (steps resolved to operations) and one tool per workflow, and
|
|
16
|
+
`workflow-runtime.js` runs them on top of `runtime.js`. The two runtimes are separate files so
|
|
17
|
+
`dist/rapidoc-min.js` (operation tools only) carries no Arazzo runtime, while
|
|
18
|
+
`dist/rapidoc-flow-min.js` and `@rapidoc/mcp` include both. Not owned here: Arazzo parsing and validation (`../arazzo/`), the `<rapi-doc>` attribute and
|
|
19
|
+
methods, and the network calls themselves (the runtime only builds and sends requests).
|
|
20
|
+
|
|
21
|
+
## Interface
|
|
22
|
+
|
|
23
|
+
- `compileWorkflows(arazzo, { sources, onProblem }) -> { tools, workflows }` [workflows.js]:
|
|
24
|
+
`sources` are operation indexes by Arazzo source name (`../arazzo/operations.js`). A workflow the
|
|
25
|
+
runtime cannot run is reported once through `onProblem` and left out. `buildMcpManifest` calls
|
|
26
|
+
it for `options.arazzo`, naming the manifest's source after the Arazzo document's first OpenAPI
|
|
27
|
+
source so steps resolve; `generateMcpServer` collects the problems into its result.
|
|
28
|
+
|
|
29
|
+
- `buildMcpManifest(spec, options) -> manifest` [manifest.js]: `spec` is a raw OpenAPI 3.x /
|
|
30
|
+
Swagger 2.0 object or `ProcessSpec`'s result (`tags[].paths[]`). Options: `serverName`,
|
|
31
|
+
`sourceName` (default `api`), `includeInternal`, `includeDeprecated`, `includeOperations` (`false`:
|
|
32
|
+
only the Arazzo workflows' tools, for a manifest of workflows). `toJsonSchema(schema,
|
|
33
|
+
root)` is the schema converter on its own; `MANIFEST_VERSION` is `1`.
|
|
34
|
+
- `createMcpRuntime(manifest, { fetch, credentials, baseUrls, runWorkflow })` [runtime.js]:
|
|
35
|
+
`listTools()`, `listResources()`, `callTool(name, args, hooks) -> { content, structuredContent?,
|
|
36
|
+
isError? }`, `execute(operation, args, { headers? })` (one request, for workflow runners; the
|
|
37
|
+
headers are added to the request). A workflow tool is handed to `runWorkflow`; without it the
|
|
38
|
+
call is an error result. `callTool`'s hooks reach the workflow runner: `onStepStart`, `onStep`,
|
|
39
|
+
`sleep`, and `requestHeaders({ workflowId, stepId, attempt, operation })`, whose result is sent
|
|
40
|
+
with that step's request (how `<rapi-flow>` asks `rapidoc mock` for a status with
|
|
41
|
+
`Prefer: code=`).
|
|
42
|
+
- `runWorkflow(runtime, tool, args, { onStepStart, onStep, sleep })` and
|
|
43
|
+
`createMcpWorkflowRuntime(manifest, options)` (createMcpRuntime with `runWorkflow` plugged in;
|
|
44
|
+
`options.sleep` waits between retries) [workflow-runtime.js], plus `evaluateExpression`,
|
|
45
|
+
`resolveValue` and `evaluateCondition`. `@rapidoc/mcp` exports `createMcpWorkflowRuntime` as its
|
|
46
|
+
`createMcpRuntime`.
|
|
47
|
+
`readResource(uri) -> { contents }`. `buildRequest(manifest, operation, args, options)` returns
|
|
48
|
+
the request without sending it; `credentialsFromEnv(manifest, env)` and `envNameFor(id)` map
|
|
49
|
+
security schemes to environment variables.
|
|
50
|
+
- `writeMcpProject(manifest, { serverName, workflowRuntime }) -> { [path]: text }` [project.js]:
|
|
51
|
+
`manifest.json`, `server.ts`, `runtime.js`, `package.json`, `README.md`,
|
|
52
|
+
`claude_desktop_config.json`, and `workflow-runtime.js` when the manifest has workflows and
|
|
53
|
+
`workflowRuntime` (its text) is given: `@rapidoc/mcp` and `<rapi-flow>` supply it,
|
|
54
|
+
`dist/rapidoc-min.js` does not carry it. `server.ts` imports it dynamically when present.
|
|
55
|
+
- `createZip(files) -> Uint8Array` [zip.js]: a stored (uncompressed) ZIP of text files.
|
|
56
|
+
- `generateMcpServer(spec, options) -> { manifest, files, budget, hostConfig }`
|
|
57
|
+
[../mcp-generator.js]: the three above in one call.
|
|
58
|
+
- `common.js`: `parseMcpEnabled(val)` (the `mcp` attribute), `toSnakeCase`, `sanitizeDescription`,
|
|
59
|
+
`deriveToolName`, `AUTH_TRANSPORT_HEADERS`, `estimateTokenCost(manifest)` and
|
|
60
|
+
`generateHostConfigs(serverName, { schemes })`.
|
|
61
|
+
|
|
62
|
+
## Invariants
|
|
63
|
+
|
|
64
|
+
- **Manifest shape.** `{ manifestVersion, server: { name, version, description }, sources:
|
|
65
|
+
{ [name]: { baseUrl, securitySchemes } }, tools, resources, workflows }`. A workflow tool has
|
|
66
|
+
`workflow` (its id) instead of `operation`; `workflows[id]` is `{ workflowId, summary,
|
|
67
|
+
description, inputs, steps, outputs }`, each step `{ stepId, operation, parameters, requestBody,
|
|
68
|
+
successCriteria, onSuccess, onFailure, outputs }` with the operation resolved (source, method,
|
|
69
|
+
path, params from the step's parameters and body, security from the OpenAPI operation) and
|
|
70
|
+
values kept as Arazzo expressions. A tool is
|
|
71
|
+
`{ name, description, inputSchema, annotations, operation }` with `operation = { source, method,
|
|
72
|
+
path, params: [{ name, in, arg?, contentType? }], security }`; `arg` names the argument when it
|
|
73
|
+
differs from the wire name, `contentType` is set for `in: "body"`. A resource has `uri`
|
|
74
|
+
(`reference://...`) and `mimeType` instead of `inputSchema` and `annotations`.
|
|
75
|
+
- **Exclusion** (`[manifest.js:buildMcpManifest]`): an operation is skipped when `x-internal` or
|
|
76
|
+
`x-hidden` is `true` on it or its path item (unless `includeInternal`), when `deprecated` (unless
|
|
77
|
+
`includeDeprecated`), or when it is a webhook.
|
|
78
|
+
- **Arguments.** Path, query, header and cookie parameters become properties by name (a clash
|
|
79
|
+
across locations gets `_<in>`); path parameters are required. The request body is one `body`
|
|
80
|
+
property with the first JSON content type's schema (else form-urlencoded, multipart, or the first
|
|
81
|
+
listed). Path-level parameters are merged unless the operation redefines them. Headers in
|
|
82
|
+
`AUTH_TRANSPORT_HEADERS` and any parameter that matches an `apiKey` scheme's `in` and `name`
|
|
83
|
+
are never arguments: the runtime sends them.
|
|
84
|
+
- **JSON Schema** (`[manifest.js:toJsonSchema]`): `$ref`s are inlined from the raw document;
|
|
85
|
+
a schema met again on the same branch is cut to `{ type }`; `nullable` folds into `type`;
|
|
86
|
+
`readOnly` properties (and their `required` entries) are dropped; OpenAPI 3.0 boolean
|
|
87
|
+
`exclusiveMinimum/Maximum` become numbers; `example`, `xml`, `x-*` and other non-schema keys
|
|
88
|
+
are dropped.
|
|
89
|
+
- **Naming**: `toSnakeCase(operationId)`, else `deriveToolName(method, path)`; workflow tools are
|
|
90
|
+
`toSnakeCase(workflowId)`, after the operations; a repeated name gets `_2`, `_3`, ... in that
|
|
91
|
+
order. `sanitizeDescription` deletes every `_`.
|
|
92
|
+
- **Annotations**: `readOnlyHint` for GET; `destructiveHint` for PUT, PATCH and DELETE;
|
|
93
|
+
`idempotentHint` for GET, PUT and DELETE. A workflow tool is read-only when every step is a
|
|
94
|
+
GET, destructive when any step is PUT, PATCH or DELETE, never idempotent. `estimateTokenCost.consequentialCount` counts tools
|
|
95
|
+
without `readOnlyHint`.
|
|
96
|
+
- **Resources**: a GET with no required argument whose path contains one of `/reference`,
|
|
97
|
+
`/currencies`, `/countries`, `/tiers`, `/categories`, `/status`, `/schema`.
|
|
98
|
+
- **Security.** `securitySchemes` keeps `type, description, in, name, scheme, bearerFormat,
|
|
99
|
+
openIdConnectUrl` and OAuth 2 `flows`; `<rapi-doc>`'s injected `_rapidoc_api_key` scheme is
|
|
100
|
+
left out. A tool's `security` is the operation's, else the document's. The runtime uses the
|
|
101
|
+
first alternative all of whose credentials are known; with none, nothing is sent when an
|
|
102
|
+
alternative is empty, else the first alternative as far as credentials go.
|
|
103
|
+
- **Runtime.** `runtime.js` imports nothing and uses only `fetch`, `URLSearchParams`,
|
|
104
|
+
`TextEncoder` and `btoa`, and `workflow-runtime.js` imports only `./runtime.js`, because the
|
|
105
|
+
project writer embeds their text (`?raw` imports, supported by Vite and
|
|
106
|
+
`scripts/test-loader.mjs`). Path parameters are percent-encoded; array query values
|
|
107
|
+
repeat the key; objects elsewhere are JSON; `Accept: application/json` unless set; a missing path
|
|
108
|
+
parameter is an error result, as is a transport failure or a non-2xx status (text `HTTP <status>`
|
|
109
|
+
plus the body). JSON responses are pretty-printed and, for objects, also `structuredContent`.
|
|
110
|
+
- **Workflows the runtime runs** (`[workflows.js:compileWorkflows]`): steps that name an
|
|
111
|
+
operation found in the sources, parameters in path, query, header or cookie, a request body
|
|
112
|
+
with `payload` and `replacements`, simple criteria only (`regex`, `jsonpath` and `xpath` are
|
|
113
|
+
reported), actions `end`, `goto` to a step and `retry` (`retryLimit` default 1, `retryAfter`
|
|
114
|
+
seconds) with simple criteria, and `$inputs` / `$steps` expressions. Steps that call another
|
|
115
|
+
workflow, actions that go to one, and `$workflows` expressions are reported. A step's actions
|
|
116
|
+
are its own `onSuccess` / `onFailure` followed by the workflow's it does not override by name.
|
|
117
|
+
- **Running a workflow** (`[workflow-runtime.js:runWorkflow]`): required inputs are checked first.
|
|
118
|
+
Each step's parameters and payload are evaluated against `$inputs` and earlier steps' outputs
|
|
119
|
+
(`resolveValue`: a whole-string expression keeps its type, `{$expr}` inside text is
|
|
120
|
+
interpolated), the request is sent, and every criterion must hold (`evaluateCondition`: `==`,
|
|
121
|
+
`!=`, `<`, `>`, `<=`, `>=`, `&&`, `||`, `!`, parentheses, quoted strings, numbers, `true`,
|
|
122
|
+
`false`, `null`; `==` compares numbers and strings as text and treats a missing value as
|
|
123
|
+
`null`). A step without criteria succeeds on a 2xx response. The first action whose criteria all hold runs: on success
|
|
124
|
+
`end` finishes, `goto` jumps, none falls through; on failure none or `end` fails the workflow
|
|
125
|
+
(the result names the step, the criterion and the response), `goto` jumps, `retry` re-runs the
|
|
126
|
+
step (or `stepId`) after `retryAfter` seconds up to `retryLimit` times. A transport error is a
|
|
127
|
+
failure with no response. More than 1000 step runs is an error. The result is the workflow's
|
|
128
|
+
`outputs` as `structuredContent`; `onStepStart` gets `{ workflowId, stepId, attempt }` before
|
|
129
|
+
each request and `onStep` gets `{ workflowId, stepId, attempt, outcome, request, response,
|
|
130
|
+
outputs, failedCriterion, action }` after it.
|
|
131
|
+
- **Environment.** `envNameFor("petstore_auth")` is `PETSTORE_AUTH`; `API_KEY` is the fallback
|
|
132
|
+
for every scheme; HTTP Basic reads `<NAME>` as `user:password` or `<NAME>_USERNAME` /
|
|
133
|
+
`<NAME>_PASSWORD`. `API_BASE_URL` in `server.ts` overrides every source's base URL.
|
|
134
|
+
- **Project.** `server.ts` and `runtime.js` are identical for every API; only `manifest.json`,
|
|
135
|
+
`package.json`, `README.md` and `claude_desktop_config.json` vary. The zip stores entries
|
|
136
|
+
uncompressed with a fixed 1980-01-01 timestamp and UTF-8 names.
|
|
137
|
+
|
|
138
|
+
## Side Effects
|
|
139
|
+
|
|
140
|
+
- None in this folder: no network, DOM, storage or console. The exported `server.ts` reads
|
|
141
|
+
`manifest.json`, the environment and stdio, and the runtime it calls makes HTTP requests.
|
|
142
|
+
|
|
143
|
+
## Verification
|
|
144
|
+
|
|
145
|
+
- `npm run test:unit` from the repo root. `tests/unit/mcp-generator.test.js` covers the manifest
|
|
146
|
+
(from `docs/public/specs/petstore.yaml` and small documents), the schema converter, the runtime
|
|
147
|
+
with a fake `fetch`, credentials from the environment, the project files and the zip.
|
|
148
|
+
`tests/unit/mcp-workflows.test.js` covers compiling `docs/public/specs/petstore.arazzo.yaml`,
|
|
149
|
+
expressions, conditions, and runs (outputs between steps, retry, goto, failure) with a fake
|
|
150
|
+
`fetch`. `tests/unit/mcp-project.test.js` runs the exported project for real: it starts `server.ts`
|
|
151
|
+
(through `scripts/test-loader.mjs` instead of `tsx`) under `.test-tmp/`, drives it with the MCP
|
|
152
|
+
SDK's stdio client (`@modelcontextprotocol/sdk`, a root dev dependency) and points
|
|
153
|
+
`API_BASE_URL` at `@rapidoc/mock` serving the same Petstore spec, workflow tools included.
|
|
154
|
+
- `/examples/mcp-server` on the docs site shows the manifest, `server.ts` and `runtime.js` and
|
|
155
|
+
downloads the project. After editing, `npm run lint` and `npm run format-fix`.
|