sourcey 3.6.0 → 3.6.1
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 +5 -2
- package/dist/adapters/index.d.ts +3 -2
- package/dist/adapters/index.d.ts.map +1 -1
- package/dist/adapters/index.js +35 -3
- package/dist/adapters/types.d.ts +8 -2
- package/dist/adapters/types.d.ts.map +1 -1
- package/dist/cli.js +28 -34
- package/dist/client/search.js +181 -119
- package/dist/client/tabs.js +23 -0
- package/dist/components/layout/Head.js +2 -2
- package/dist/components/openapi/Introduction.d.ts.map +1 -1
- package/dist/components/openapi/Introduction.js +16 -1
- package/dist/components/openapi/Security.d.ts.map +1 -1
- package/dist/components/openapi/Security.js +18 -2
- package/dist/config.d.ts +63 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +21 -7
- package/dist/core/api-rendering.d.ts +47 -0
- package/dist/core/api-rendering.d.ts.map +1 -0
- package/dist/core/api-rendering.js +76 -0
- package/dist/core/doxygen-loader.d.ts.map +1 -1
- package/dist/core/doxygen-loader.js +35 -18
- package/dist/core/godoc-loader.d.ts.map +1 -1
- package/dist/core/godoc-loader.js +17 -6
- package/dist/core/markdown-loader.d.ts.map +1 -1
- package/dist/core/markdown-loader.js +8 -5
- package/dist/core/mcp-normalizer.d.ts.map +1 -1
- package/dist/core/mcp-normalizer.js +48 -34
- package/dist/core/parser.d.ts.map +1 -1
- package/dist/core/parser.js +75 -4
- package/dist/core/rustdoc-introspector.d.ts +29 -0
- package/dist/core/rustdoc-introspector.d.ts.map +1 -0
- package/dist/core/rustdoc-introspector.js +236 -0
- package/dist/core/rustdoc-loader.d.ts +25 -0
- package/dist/core/rustdoc-loader.d.ts.map +1 -0
- package/dist/core/rustdoc-loader.js +469 -0
- package/dist/core/rustdoc-render.d.ts +76 -0
- package/dist/core/rustdoc-render.d.ts.map +1 -0
- package/dist/core/rustdoc-render.js +785 -0
- package/dist/core/rustdoc-types.d.ts +314 -0
- package/dist/core/rustdoc-types.d.ts.map +1 -0
- package/dist/core/rustdoc-types.js +18 -0
- package/dist/core/search-indexer.js +1 -1
- package/dist/core/sourcey-rustdoc/Cargo.lock +705 -0
- package/dist/core/sourcey-rustdoc/Cargo.toml +38 -0
- package/dist/core/sourcey-rustdoc/README.md +59 -0
- package/dist/core/sourcey-rustdoc/src/diagnostics.rs +42 -0
- package/dist/core/sourcey-rustdoc/src/doctest.rs +268 -0
- package/dist/core/sourcey-rustdoc/src/extract.rs +492 -0
- package/dist/core/sourcey-rustdoc/src/lib.rs +58 -0
- package/dist/core/sourcey-rustdoc/src/links.rs +126 -0
- package/dist/core/sourcey-rustdoc/src/main.rs +256 -0
- package/dist/core/sourcey-rustdoc/src/signature.rs +415 -0
- package/dist/core/sourcey-rustdoc/src/spec.rs +390 -0
- package/dist/core/sourcey-rustdoc/tests/format_version.rs +45 -0
- package/dist/dev-server.d.ts.map +1 -1
- package/dist/dev-server.js +49 -0
- package/dist/index.d.ts +4 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +22 -13
- package/dist/init.js +2 -2
- package/dist/renderer/changelog-feed.d.ts.map +1 -1
- package/dist/renderer/changelog-feed.js +27 -19
- package/dist/renderer/html-builder.d.ts.map +1 -1
- package/dist/renderer/html-builder.js +4 -3
- package/dist/renderer/llms.js +57 -2
- package/dist/site-assembly.d.ts +5 -0
- package/dist/site-assembly.d.ts.map +1 -1
- package/dist/site-assembly.js +55 -1
- package/dist/themes/default/sourcey.css +300 -0
- package/dist/utils/code-samples.d.ts +1 -0
- package/dist/utils/code-samples.d.ts.map +1 -1
- package/dist/utils/code-samples.js +30 -22
- package/dist/utils/html.d.ts +10 -0
- package/dist/utils/html.d.ts.map +1 -0
- package/dist/utils/html.js +152 -0
- package/dist/utils/http.d.ts +0 -2
- package/dist/utils/http.d.ts.map +1 -1
- package/dist/utils/http.js +19 -34
- package/dist/utils/markdown.d.ts.map +1 -1
- package/dist/utils/markdown.js +102 -37
- package/package.json +9 -4
|
@@ -4,11 +4,11 @@ import { generateExample } from "./example-generator.js";
|
|
|
4
4
|
// ---------------------------------------------------------------------------
|
|
5
5
|
const generators = {
|
|
6
6
|
curl({ url, method, contentType, body }) {
|
|
7
|
-
const parts = [`curl -X ${method}
|
|
7
|
+
const parts = [`curl -X ${method} ${shellSingleQuoted(url)}`];
|
|
8
8
|
if (contentType)
|
|
9
|
-
parts.push(` -H
|
|
9
|
+
parts.push(` -H ${shellSingleQuoted(`Content-Type: ${contentType}`)}`);
|
|
10
10
|
if (body)
|
|
11
|
-
parts.push(` -d
|
|
11
|
+
parts.push(` -d ${shellSingleQuoted(indent(body, 2))}`);
|
|
12
12
|
return { lang: "bash", label: "cURL", source: parts.join(" \\\n") };
|
|
13
13
|
},
|
|
14
14
|
javascript({ url, method, contentType, body }) {
|
|
@@ -51,7 +51,7 @@ const generators = {
|
|
|
51
51
|
return { lang: "python", label: "Python", source: lines.join("\n") };
|
|
52
52
|
},
|
|
53
53
|
go({ url, method, body }) {
|
|
54
|
-
const lines = ["package main", "", "import (", ` "fmt"`, ` "io"`, ` "net/http"
|
|
54
|
+
const lines = ["package main", "", "import (", ` "fmt"`, ` "io"`, ` "net/http"`];
|
|
55
55
|
if (body)
|
|
56
56
|
lines.push(` "strings"`);
|
|
57
57
|
lines.push(")", "");
|
|
@@ -63,7 +63,8 @@ const generators = {
|
|
|
63
63
|
else {
|
|
64
64
|
lines.push(` req, _ := http.NewRequest("${method}", "${url}", nil)`);
|
|
65
65
|
}
|
|
66
|
-
|
|
66
|
+
if (body)
|
|
67
|
+
lines.push(` req.Header.Set("Content-Type", "application/json")`);
|
|
67
68
|
lines.push(` resp, _ := http.DefaultClient.Do(req)`);
|
|
68
69
|
lines.push(` defer resp.Body.Close()`);
|
|
69
70
|
lines.push(` data, _ := io.ReadAll(resp.Body)`);
|
|
@@ -76,21 +77,23 @@ const generators = {
|
|
|
76
77
|
`require 'net/http'`,
|
|
77
78
|
`require 'json'`,
|
|
78
79
|
``,
|
|
79
|
-
`uri = URI('${url}')`,
|
|
80
|
+
`uri = URI('${rubySingleQuoted(url)}')`,
|
|
80
81
|
];
|
|
81
82
|
const methodClass = rubyShortcutMethod(method);
|
|
82
83
|
if (methodClass) {
|
|
83
84
|
lines.push(`request = Net::HTTP::${methodClass}.new(uri)`);
|
|
84
85
|
if (contentType)
|
|
85
|
-
lines.push(`request['Content-Type'] = '${contentType}'`);
|
|
86
|
+
lines.push(`request['Content-Type'] = '${rubySingleQuoted(contentType)}'`);
|
|
86
87
|
if (body)
|
|
87
|
-
lines.push(`request.body = '${indent(body, 2)}'`);
|
|
88
|
+
lines.push(`request.body = '${rubySingleQuoted(indent(body, 2))}'`);
|
|
88
89
|
}
|
|
89
90
|
else {
|
|
90
|
-
const headers = contentType
|
|
91
|
+
const headers = contentType
|
|
92
|
+
? `{ 'Content-Type' => '${rubySingleQuoted(contentType)}' }`
|
|
93
|
+
: "{}";
|
|
91
94
|
lines.push(`request = Net::HTTPGenericRequest.new('${method}', ${body ? "true" : "false"}, true, uri.request_uri, ${headers})`);
|
|
92
95
|
if (body)
|
|
93
|
-
lines.push(`request.body = '${indent(body, 2)}'`);
|
|
96
|
+
lines.push(`request.body = '${rubySingleQuoted(indent(body, 2))}'`);
|
|
94
97
|
}
|
|
95
98
|
lines.push(``, `response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(request) }`);
|
|
96
99
|
lines.push(`data = JSON.parse(response.body)`);
|
|
@@ -121,16 +124,16 @@ const generators = {
|
|
|
121
124
|
return { lang: "java", label: "Java", source: lines.join("\n") };
|
|
122
125
|
},
|
|
123
126
|
php({ url, method, contentType, body }) {
|
|
124
|
-
const lines = [`<?php`, `$ch = curl_init('${url}');`];
|
|
127
|
+
const lines = [`<?php`, `$ch = curl_init('${phpSingleQuoted(url)}');`];
|
|
125
128
|
lines.push(`curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);`);
|
|
126
129
|
if (method !== "GET") {
|
|
127
|
-
lines.push(`curl_setopt($ch, CURLOPT_CUSTOMREQUEST, '${method}');`);
|
|
130
|
+
lines.push(`curl_setopt($ch, CURLOPT_CUSTOMREQUEST, '${phpSingleQuoted(method)}');`);
|
|
128
131
|
}
|
|
129
132
|
if (contentType) {
|
|
130
|
-
lines.push(`curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: ${contentType}']);`);
|
|
133
|
+
lines.push(`curl_setopt($ch, CURLOPT_HTTPHEADER, ['${phpSingleQuoted(`Content-Type: ${contentType}`)}']);`);
|
|
131
134
|
}
|
|
132
135
|
if (body) {
|
|
133
|
-
lines.push(`curl_setopt($ch, CURLOPT_POSTFIELDS, '${indent(body, 2)}');`);
|
|
136
|
+
lines.push(`curl_setopt($ch, CURLOPT_POSTFIELDS, '${phpSingleQuoted(indent(body, 2))}');`);
|
|
134
137
|
}
|
|
135
138
|
lines.push(``, `$response = curl_exec($ch);`, `curl_close($ch);`);
|
|
136
139
|
lines.push(`$data = json_decode($response, true);`);
|
|
@@ -165,10 +168,7 @@ const generators = {
|
|
|
165
168
|
return { lang: "rust", label: "Rust", source: lines.join("\n") };
|
|
166
169
|
},
|
|
167
170
|
csharp({ url, method, body }) {
|
|
168
|
-
const lines = [
|
|
169
|
-
`using var client = new HttpClient();`,
|
|
170
|
-
``,
|
|
171
|
-
];
|
|
171
|
+
const lines = [`using var client = new HttpClient();`, ``];
|
|
172
172
|
const httpMethod = csharpShortcutMethod(method)
|
|
173
173
|
? `HttpMethod.${method[0] + method.slice(1).toLowerCase()}`
|
|
174
174
|
: `new HttpMethod("${method}")`;
|
|
@@ -185,6 +185,7 @@ const generators = {
|
|
|
185
185
|
};
|
|
186
186
|
/** Default languages when none configured. */
|
|
187
187
|
export const DEFAULT_CODE_SAMPLE_LANGS = ["curl", "javascript", "python"];
|
|
188
|
+
export const SUPPORTED_CODE_SAMPLE_LANGS = Object.freeze(Object.keys(generators));
|
|
188
189
|
// ---------------------------------------------------------------------------
|
|
189
190
|
// Public API
|
|
190
191
|
// ---------------------------------------------------------------------------
|
|
@@ -203,14 +204,12 @@ export function generateCodeSamples(op, serverUrl, langs = DEFAULT_CODE_SAMPLE_L
|
|
|
203
204
|
const method = op.method.toUpperCase();
|
|
204
205
|
const hasBody = !!op.requestBody;
|
|
205
206
|
const contentType = hasBody
|
|
206
|
-
? Object.keys(op.requestBody.content)[0] ?? "application/json"
|
|
207
|
+
? (Object.keys(op.requestBody.content)[0] ?? "application/json")
|
|
207
208
|
: undefined;
|
|
208
209
|
const bodyExample = hasBody ? getRequestBodyExample(op) : undefined;
|
|
209
210
|
const body = bodyExample ? JSON.stringify(bodyExample, null, 2) : undefined;
|
|
210
211
|
const req = { url, method, contentType, body };
|
|
211
|
-
return langs
|
|
212
|
-
.map((lang) => generators[lang]?.(req))
|
|
213
|
-
.filter((s) => !!s);
|
|
212
|
+
return langs.map((lang) => generators[lang]?.(req)).filter((s) => !!s);
|
|
214
213
|
}
|
|
215
214
|
// ---------------------------------------------------------------------------
|
|
216
215
|
// Helpers
|
|
@@ -259,6 +258,15 @@ function rustShortcutMethod(method) {
|
|
|
259
258
|
function csharpShortcutMethod(method) {
|
|
260
259
|
return ["GET", "POST", "PUT", "DELETE", "PATCH"].includes(method);
|
|
261
260
|
}
|
|
261
|
+
function shellSingleQuoted(value) {
|
|
262
|
+
return `'${value.replace(/'/g, "'\\''")}'`;
|
|
263
|
+
}
|
|
264
|
+
function rubySingleQuoted(value) {
|
|
265
|
+
return value.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
|
|
266
|
+
}
|
|
267
|
+
function phpSingleQuoted(value) {
|
|
268
|
+
return value.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
|
|
269
|
+
}
|
|
262
270
|
/** Indent all lines of a multi-line string except the first. */
|
|
263
271
|
function indent(str, spaces) {
|
|
264
272
|
const pad = " ".repeat(spaces);
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export interface SafeUrlOptions {
|
|
2
|
+
allowedProtocols?: readonly string[];
|
|
3
|
+
allowRelative?: boolean;
|
|
4
|
+
allowAnchor?: boolean;
|
|
5
|
+
}
|
|
6
|
+
export declare function escapeHtml(value: string): string;
|
|
7
|
+
export declare function escapeAttr(value: string): string;
|
|
8
|
+
export declare function safeUrl(input: string, options?: SafeUrlOptions): string | null;
|
|
9
|
+
export declare function sanitizeRenderedHtml(input: string): string;
|
|
10
|
+
//# sourceMappingURL=html.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"html.d.ts","sourceRoot":"","sources":["../../src/utils/html.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,cAAc;IAC7B,gBAAgB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACrC,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAOhD;AAED,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEhD;AAED,wBAAgB,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,GAAE,cAAmB,GAAG,MAAM,GAAG,IAAI,CAkBlF;AAUD,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CA2G1D"}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import sanitizeHtml from "sanitize-html";
|
|
2
|
+
const BASE_URL = "https://sourcey.invalid/";
|
|
3
|
+
const EXPLICIT_SCHEME_RE = /^[a-z][a-z\d+.-]*:/i;
|
|
4
|
+
export function escapeHtml(value) {
|
|
5
|
+
return value
|
|
6
|
+
.replace(/&/g, "&")
|
|
7
|
+
.replace(/</g, "<")
|
|
8
|
+
.replace(/>/g, ">")
|
|
9
|
+
.replace(/"/g, """)
|
|
10
|
+
.replace(/'/g, "'");
|
|
11
|
+
}
|
|
12
|
+
export function escapeAttr(value) {
|
|
13
|
+
return escapeHtml(value);
|
|
14
|
+
}
|
|
15
|
+
export function safeUrl(input, options = {}) {
|
|
16
|
+
const allowedProtocols = options.allowedProtocols ?? ["http:", "https:", "mailto:"];
|
|
17
|
+
const allowRelative = options.allowRelative ?? true;
|
|
18
|
+
const allowAnchor = options.allowAnchor ?? true;
|
|
19
|
+
const value = input.trim();
|
|
20
|
+
if (!value || hasRawControlOrSpace(value))
|
|
21
|
+
return null;
|
|
22
|
+
if (value.startsWith("#"))
|
|
23
|
+
return allowAnchor ? value : null;
|
|
24
|
+
if (value.startsWith("//"))
|
|
25
|
+
return null;
|
|
26
|
+
try {
|
|
27
|
+
const parsed = new URL(value, BASE_URL);
|
|
28
|
+
const hasExplicitScheme = EXPLICIT_SCHEME_RE.test(value);
|
|
29
|
+
if (!hasExplicitScheme)
|
|
30
|
+
return allowRelative ? value : null;
|
|
31
|
+
return allowedProtocols.includes(parsed.protocol) ? parsed.href : null;
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
function hasRawControlOrSpace(value) {
|
|
38
|
+
for (const char of value) {
|
|
39
|
+
const code = char.charCodeAt(0);
|
|
40
|
+
if (code <= 0x20 || code === 0x7f)
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
export function sanitizeRenderedHtml(input) {
|
|
46
|
+
return sanitizeHtml(input, {
|
|
47
|
+
allowedTags: [
|
|
48
|
+
...sanitizeHtml.defaults.allowedTags,
|
|
49
|
+
"button",
|
|
50
|
+
"details",
|
|
51
|
+
"iframe",
|
|
52
|
+
"img",
|
|
53
|
+
"path",
|
|
54
|
+
"source",
|
|
55
|
+
"summary",
|
|
56
|
+
"svg",
|
|
57
|
+
"video",
|
|
58
|
+
],
|
|
59
|
+
allowedAttributes: {
|
|
60
|
+
"*": [
|
|
61
|
+
"class",
|
|
62
|
+
"id",
|
|
63
|
+
"role",
|
|
64
|
+
"tabindex",
|
|
65
|
+
"title",
|
|
66
|
+
"aria-expanded",
|
|
67
|
+
"aria-hidden",
|
|
68
|
+
"aria-label",
|
|
69
|
+
"aria-selected",
|
|
70
|
+
"data-clipboard-target",
|
|
71
|
+
"data-cols",
|
|
72
|
+
"data-copy-source",
|
|
73
|
+
"data-display",
|
|
74
|
+
"data-tab-group",
|
|
75
|
+
"data-tab-index",
|
|
76
|
+
"data-target",
|
|
77
|
+
"data-traverse-target",
|
|
78
|
+
],
|
|
79
|
+
a: ["href", "name", "target", "rel", "title", "class", "id"],
|
|
80
|
+
button: [
|
|
81
|
+
"type",
|
|
82
|
+
"class",
|
|
83
|
+
"aria-expanded",
|
|
84
|
+
"aria-label",
|
|
85
|
+
"aria-selected",
|
|
86
|
+
"data-copy-source",
|
|
87
|
+
"data-tab-group",
|
|
88
|
+
"data-tab-index",
|
|
89
|
+
],
|
|
90
|
+
div: [
|
|
91
|
+
"class",
|
|
92
|
+
"id",
|
|
93
|
+
"role",
|
|
94
|
+
"style",
|
|
95
|
+
"data-cols",
|
|
96
|
+
"data-tab-group",
|
|
97
|
+
"data-tab-index",
|
|
98
|
+
"data-traverse-target",
|
|
99
|
+
],
|
|
100
|
+
details: ["class", "id", "open"],
|
|
101
|
+
h1: ["id", "class"],
|
|
102
|
+
h2: ["id", "class"],
|
|
103
|
+
h3: ["id", "class"],
|
|
104
|
+
h4: ["id", "class"],
|
|
105
|
+
h5: ["id", "class"],
|
|
106
|
+
h6: ["id", "class"],
|
|
107
|
+
iframe: ["src", "title", "frameborder", "allow", "allowfullscreen", "loading", "class", "id"],
|
|
108
|
+
img: ["src", "alt", "title", "width", "height", "loading", "class", "id"],
|
|
109
|
+
path: ["d", "fill", "stroke", "stroke-width", "stroke-linecap", "stroke-linejoin"],
|
|
110
|
+
pre: ["class", "tabindex", "style"],
|
|
111
|
+
source: ["src", "type"],
|
|
112
|
+
span: ["class", "style", "title"],
|
|
113
|
+
svg: [
|
|
114
|
+
"xmlns",
|
|
115
|
+
"width",
|
|
116
|
+
"height",
|
|
117
|
+
"viewBox",
|
|
118
|
+
"fill",
|
|
119
|
+
"stroke",
|
|
120
|
+
"stroke-width",
|
|
121
|
+
"stroke-linecap",
|
|
122
|
+
"stroke-linejoin",
|
|
123
|
+
"class",
|
|
124
|
+
"aria-hidden",
|
|
125
|
+
"focusable",
|
|
126
|
+
],
|
|
127
|
+
table: ["class"],
|
|
128
|
+
td: ["class", "colspan", "rowspan"],
|
|
129
|
+
th: ["class", "colspan", "rowspan", "scope"],
|
|
130
|
+
video: ["controls", "preload", "title", "class", "id"],
|
|
131
|
+
},
|
|
132
|
+
allowedSchemes: ["http", "https", "mailto"],
|
|
133
|
+
allowedSchemesByTag: {
|
|
134
|
+
iframe: ["http", "https"],
|
|
135
|
+
img: ["http", "https"],
|
|
136
|
+
source: ["http", "https"],
|
|
137
|
+
},
|
|
138
|
+
allowProtocolRelative: false,
|
|
139
|
+
allowedStyles: {
|
|
140
|
+
"*": {
|
|
141
|
+
background: [/^transparent$/, /^#[0-9a-fA-F]{3,8}$/],
|
|
142
|
+
"background-color": [/^#[0-9a-fA-F]{3,8}$/],
|
|
143
|
+
color: [/^#[0-9a-fA-F]{3,8}$/],
|
|
144
|
+
height: [/^\d{1,4}px$/],
|
|
145
|
+
"--shiki-dark": [/^#[0-9a-fA-F]{3,8}$/],
|
|
146
|
+
"--shiki-dark-bg": [/^#[0-9a-fA-F]{3,8}$/],
|
|
147
|
+
"--shiki-light": [/^#[0-9a-fA-F]{3,8}$/],
|
|
148
|
+
"--shiki-light-bg": [/^#[0-9a-fA-F]{3,8}$/],
|
|
149
|
+
},
|
|
150
|
+
},
|
|
151
|
+
});
|
|
152
|
+
}
|
package/dist/utils/http.d.ts
CHANGED
|
@@ -9,6 +9,4 @@ export declare function schemaRange(schema: {
|
|
|
9
9
|
exclusiveMinimum?: number | boolean;
|
|
10
10
|
exclusiveMaximum?: number | boolean;
|
|
11
11
|
}): string | undefined;
|
|
12
|
-
/** HTTP method color classes for badges */
|
|
13
|
-
export declare function methodColor(method: string): string;
|
|
14
12
|
//# sourceMappingURL=http.d.ts.map
|
package/dist/utils/http.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../../src/utils/http.ts"],"names":[],"mappings":"AAiDA,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEnD;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE;IAClC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,gBAAgB,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IACpC,gBAAgB,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;CACrC,GAAG,MAAM,GAAG,SAAS,
|
|
1
|
+
{"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../../src/utils/http.ts"],"names":[],"mappings":"AAiDA,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEnD;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE;IAClC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,gBAAgB,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IACpC,gBAAgB,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;CACrC,GAAG,MAAM,GAAG,SAAS,CA4BrB"}
|
package/dist/utils/http.js
CHANGED
|
@@ -54,43 +54,28 @@ export function httpStatusText(code) {
|
|
|
54
54
|
* Fixes legacy bug where maximumExclusive was checked with minimumExclusive.
|
|
55
55
|
*/
|
|
56
56
|
export function schemaRange(schema) {
|
|
57
|
-
|
|
57
|
+
let min;
|
|
58
|
+
let max;
|
|
58
59
|
if (schema.minimum !== undefined) {
|
|
59
|
-
const exclusive = schema.exclusiveMinimum === true ||
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
parts.push(`${exclusive ? "(" : "["}${min}`);
|
|
60
|
+
const exclusive = schema.exclusiveMinimum === true || typeof schema.exclusiveMinimum === "number";
|
|
61
|
+
min = {
|
|
62
|
+
value: typeof schema.exclusiveMinimum === "number" ? schema.exclusiveMinimum : schema.minimum,
|
|
63
|
+
exclusive,
|
|
64
|
+
};
|
|
65
65
|
}
|
|
66
66
|
if (schema.maximum !== undefined) {
|
|
67
|
-
const exclusive = schema.exclusiveMaximum === true ||
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
parts.push(`${max}${exclusive ? ")" : "]"}`);
|
|
67
|
+
const exclusive = schema.exclusiveMaximum === true || typeof schema.exclusiveMaximum === "number";
|
|
68
|
+
max = {
|
|
69
|
+
value: typeof schema.exclusiveMaximum === "number" ? schema.exclusiveMaximum : schema.maximum,
|
|
70
|
+
exclusive,
|
|
71
|
+
};
|
|
73
72
|
}
|
|
74
|
-
|
|
75
|
-
}
|
|
76
|
-
/** HTTP method color classes for badges */
|
|
77
|
-
export function methodColor(method) {
|
|
78
|
-
switch (method.toLowerCase()) {
|
|
79
|
-
case "get":
|
|
80
|
-
return "bg-emerald-600";
|
|
81
|
-
case "post":
|
|
82
|
-
return "bg-blue-600";
|
|
83
|
-
case "put":
|
|
84
|
-
return "bg-amber-600";
|
|
85
|
-
case "delete":
|
|
86
|
-
return "bg-red-600";
|
|
87
|
-
case "patch":
|
|
88
|
-
return "bg-purple-600";
|
|
89
|
-
case "options":
|
|
90
|
-
return "bg-gray-500";
|
|
91
|
-
case "head":
|
|
92
|
-
return "bg-gray-600";
|
|
93
|
-
default:
|
|
94
|
-
return "bg-gray-500";
|
|
73
|
+
if (min && max) {
|
|
74
|
+
return `${min.exclusive ? "(" : "["}${min.value}, ${max.value}${max.exclusive ? ")" : "]"}`;
|
|
95
75
|
}
|
|
76
|
+
if (min)
|
|
77
|
+
return `${min.exclusive ? ">" : ">="} ${min.value}`;
|
|
78
|
+
if (max)
|
|
79
|
+
return `${max.exclusive ? "<" : "<="} ${max.value}`;
|
|
80
|
+
return undefined;
|
|
96
81
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"markdown.d.ts","sourceRoot":"","sources":["../../src/utils/markdown.ts"],"names":[],"mappings":"AAAA,OAAO,EAAU,KAAK,KAAK,EAAe,MAAM,QAAQ,CAAC;
|
|
1
|
+
{"version":3,"file":"markdown.d.ts","sourceRoot":"","sources":["../../src/utils/markdown.ts"],"names":[],"mappings":"AAAA,OAAO,EAAU,KAAK,KAAK,EAAe,MAAM,QAAQ,CAAC;AAMzD;;;GAGG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,CASnE;AAED,4CAA4C;AAC5C,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,CAAC,GAAG,CAAC,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;CACZ;AAkLD;;;GAGG;AACH,wBAAgB,WAAW,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,KAAK,EAAE,CAGnD;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,WAAW,EAAE,CAe5D;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAY5D;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CA2BzD;AAsBD;;GAEG;AACH,wBAAgB,cAAc,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAGrD;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAG3D"}
|
package/dist/utils/markdown.js
CHANGED
|
@@ -2,7 +2,7 @@ import { Marked } from "marked";
|
|
|
2
2
|
import { highlightCode } from "./highlighter.js";
|
|
3
3
|
import { COPY_ICON_SVG } from "./copy-svg.js";
|
|
4
4
|
import { htmlId } from "./html-id.js";
|
|
5
|
-
|
|
5
|
+
import { escapeAttr, safeUrl, sanitizeRenderedHtml } from "./html.js";
|
|
6
6
|
/**
|
|
7
7
|
* Shared code block renderer used by all markdown rendering.
|
|
8
8
|
* Outputs the prose-code-block wrapper with copy button and Shiki highlighting.
|
|
@@ -24,33 +24,46 @@ function parseDirectiveAttrs(raw) {
|
|
|
24
24
|
attrs[k] = v;
|
|
25
25
|
return attrs;
|
|
26
26
|
}
|
|
27
|
-
function
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
27
|
+
function parseVideoUrl(url) {
|
|
28
|
+
const webUrl = safeUrl(url, {
|
|
29
|
+
allowedProtocols: ["http:", "https:"],
|
|
30
|
+
allowRelative: false,
|
|
31
|
+
allowAnchor: false,
|
|
32
|
+
});
|
|
33
|
+
if (webUrl) {
|
|
34
|
+
const parsed = new URL(webUrl);
|
|
35
|
+
const hostname = parsed.hostname.replace(/^www\./, "");
|
|
36
|
+
if (hostname === "youtube.com" || hostname === "m.youtube.com") {
|
|
37
|
+
const id = parsed.searchParams.get("v");
|
|
38
|
+
if (id && /^[\w-]+$/.test(id)) {
|
|
39
|
+
return { src: `https://www.youtube-nocookie.com/embed/${id}`, type: "iframe" };
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
if (hostname === "youtu.be") {
|
|
43
|
+
const id = parsed.pathname.split("/").filter(Boolean)[0];
|
|
44
|
+
if (id && /^[\w-]+$/.test(id)) {
|
|
45
|
+
return { src: `https://www.youtube-nocookie.com/embed/${id}`, type: "iframe" };
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
if (hostname === "vimeo.com" || hostname.endsWith(".vimeo.com")) {
|
|
49
|
+
const id = parsed.pathname
|
|
50
|
+
.split("/")
|
|
51
|
+
.filter(Boolean)
|
|
52
|
+
.find((segment) => /^\d+$/.test(segment));
|
|
53
|
+
if (id)
|
|
54
|
+
return { src: `https://player.vimeo.com/video/${id}`, type: "iframe" };
|
|
55
|
+
}
|
|
36
56
|
}
|
|
37
|
-
|
|
57
|
+
const rawVideoUrl = safeUrl(url, {
|
|
58
|
+
allowedProtocols: ["http:", "https:"],
|
|
59
|
+
allowRelative: true,
|
|
60
|
+
allowAnchor: false,
|
|
61
|
+
});
|
|
62
|
+
if (!rawVideoUrl)
|
|
38
63
|
return null;
|
|
39
|
-
|
|
40
|
-
}
|
|
41
|
-
function parseVideoUrl(url) {
|
|
42
|
-
// YouTube
|
|
43
|
-
let m = url.match(/(?:youtube\.com\/watch\?v=|youtu\.be\/)([\w-]+)/);
|
|
44
|
-
if (m)
|
|
45
|
-
return { src: `https://www.youtube-nocookie.com/embed/${m[1]}`, type: "iframe" };
|
|
46
|
-
// Vimeo
|
|
47
|
-
m = url.match(/vimeo\.com\/(\d+)/);
|
|
48
|
-
if (m)
|
|
49
|
-
return { src: `https://player.vimeo.com/video/${m[1]}`, type: "iframe" };
|
|
50
|
-
// Raw video
|
|
51
|
-
const ext = url.split(".").pop()?.toLowerCase();
|
|
64
|
+
const ext = rawVideoUrl.split(/[?#]/)[0]?.split(".").pop()?.toLowerCase();
|
|
52
65
|
const mime = ext === "webm" ? "video/webm" : "video/mp4";
|
|
53
|
-
return { src:
|
|
66
|
+
return { src: rawVideoUrl, type: "video", mime };
|
|
54
67
|
}
|
|
55
68
|
const videoExtension = {
|
|
56
69
|
extensions: [
|
|
@@ -70,15 +83,17 @@ const videoExtension = {
|
|
|
70
83
|
renderer(token) {
|
|
71
84
|
const { url, title } = token;
|
|
72
85
|
const parsed = parseVideoUrl(url);
|
|
73
|
-
|
|
86
|
+
if (!parsed)
|
|
87
|
+
return `<p>[video: invalid URL]</p>\n`;
|
|
88
|
+
const safeTitle = escapeAttr(title);
|
|
74
89
|
if (parsed.type === "iframe") {
|
|
75
90
|
return `<div class="prose-video not-prose">
|
|
76
|
-
<iframe src="${parsed.src}" title="${safeTitle}" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen loading="lazy"></iframe>
|
|
91
|
+
<iframe src="${escapeAttr(parsed.src)}" title="${safeTitle}" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen loading="lazy"></iframe>
|
|
77
92
|
</div>\n`;
|
|
78
93
|
}
|
|
79
94
|
return `<div class="prose-video not-prose">
|
|
80
95
|
<video controls preload="metadata" title="${safeTitle}">
|
|
81
|
-
<source src="${parsed.src}" type="${parsed.mime}" />
|
|
96
|
+
<source src="${escapeAttr(parsed.src)}" type="${escapeAttr(parsed.mime ?? "video/mp4")}" />
|
|
82
97
|
</video>
|
|
83
98
|
</div>\n`;
|
|
84
99
|
},
|
|
@@ -109,11 +124,15 @@ const iframeExtension = {
|
|
|
109
124
|
},
|
|
110
125
|
renderer(token) {
|
|
111
126
|
const { url, title, height } = token;
|
|
112
|
-
const
|
|
113
|
-
|
|
127
|
+
const iframeUrl = safeUrl(url, {
|
|
128
|
+
allowedProtocols: ["http:", "https:"],
|
|
129
|
+
allowRelative: false,
|
|
130
|
+
allowAnchor: false,
|
|
131
|
+
});
|
|
132
|
+
if (!iframeUrl)
|
|
114
133
|
return `<p>[iframe: invalid URL]</p>\n`;
|
|
115
134
|
return `<div class="prose-iframe not-prose" style="height:${height}px">
|
|
116
|
-
<iframe src="${
|
|
135
|
+
<iframe src="${escapeAttr(iframeUrl)}" title="${escapeAttr(title)}" frameborder="0" loading="lazy" allowfullscreen></iframe>
|
|
117
136
|
</div>\n`;
|
|
118
137
|
},
|
|
119
138
|
},
|
|
@@ -141,10 +160,13 @@ const marked = new Marked(videoExtension, iframeExtension, {
|
|
|
141
160
|
},
|
|
142
161
|
link({ href, title, tokens }) {
|
|
143
162
|
const text = this.parser.parseInline(tokens);
|
|
144
|
-
const
|
|
145
|
-
|
|
163
|
+
const linkUrl = safeUrl(href);
|
|
164
|
+
if (!linkUrl)
|
|
165
|
+
return text;
|
|
166
|
+
const titleAttr = title ? ` title="${escapeAttr(title)}"` : "";
|
|
167
|
+
const isExternal = linkUrl.startsWith("http://") || linkUrl.startsWith("https://");
|
|
146
168
|
const target = isExternal ? ' target="_blank" rel="noopener noreferrer"' : "";
|
|
147
|
-
return `<a href="${
|
|
169
|
+
return `<a href="${escapeAttr(linkUrl)}"${titleAttr}${target}>${text}</a>`;
|
|
148
170
|
},
|
|
149
171
|
},
|
|
150
172
|
});
|
|
@@ -204,7 +226,50 @@ export function extractFirstParagraph(input) {
|
|
|
204
226
|
export function stripMarkdownLinks(input) {
|
|
205
227
|
if (!input)
|
|
206
228
|
return "";
|
|
207
|
-
|
|
229
|
+
let output = "";
|
|
230
|
+
let index = 0;
|
|
231
|
+
while (index < input.length) {
|
|
232
|
+
const isImage = input[index] === "!" && input[index + 1] === "[";
|
|
233
|
+
const isLink = input[index] === "[";
|
|
234
|
+
const labelStart = isImage ? index + 2 : isLink ? index + 1 : -1;
|
|
235
|
+
if (labelStart !== -1) {
|
|
236
|
+
const labelEnd = findMarkdownLabelEnd(input, labelStart);
|
|
237
|
+
if (labelEnd !== -1 && input[labelEnd + 1] === "(") {
|
|
238
|
+
const hrefEnd = findMarkdownHrefEnd(input, labelEnd + 2);
|
|
239
|
+
if (hrefEnd !== -1) {
|
|
240
|
+
output += input.slice(labelStart, labelEnd);
|
|
241
|
+
index = hrefEnd + 1;
|
|
242
|
+
continue;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
output += input[index];
|
|
247
|
+
index += 1;
|
|
248
|
+
}
|
|
249
|
+
return output;
|
|
250
|
+
}
|
|
251
|
+
function findMarkdownLabelEnd(input, start) {
|
|
252
|
+
let inCode = false;
|
|
253
|
+
for (let i = start; i < input.length; i += 1) {
|
|
254
|
+
const char = input[i];
|
|
255
|
+
if (char === "`")
|
|
256
|
+
inCode = !inCode;
|
|
257
|
+
if (char === "\n")
|
|
258
|
+
return -1;
|
|
259
|
+
if (char === "]" && !inCode)
|
|
260
|
+
return i;
|
|
261
|
+
}
|
|
262
|
+
return -1;
|
|
263
|
+
}
|
|
264
|
+
function findMarkdownHrefEnd(input, start) {
|
|
265
|
+
for (let i = start; i < input.length; i += 1) {
|
|
266
|
+
const char = input[i];
|
|
267
|
+
if (char === "\n")
|
|
268
|
+
return -1;
|
|
269
|
+
if (char === ")")
|
|
270
|
+
return i;
|
|
271
|
+
}
|
|
272
|
+
return -1;
|
|
208
273
|
}
|
|
209
274
|
/**
|
|
210
275
|
* Render Markdown to HTML.
|
|
@@ -212,7 +277,7 @@ export function stripMarkdownLinks(input) {
|
|
|
212
277
|
export function renderMarkdown(input) {
|
|
213
278
|
if (!input)
|
|
214
279
|
return "";
|
|
215
|
-
return marked.parse(input, { async: false });
|
|
280
|
+
return sanitizeRenderedHtml(marked.parse(input, { async: false }));
|
|
216
281
|
}
|
|
217
282
|
/**
|
|
218
283
|
* Render Markdown to inline HTML (strip wrapping <p> tags).
|
|
@@ -220,5 +285,5 @@ export function renderMarkdown(input) {
|
|
|
220
285
|
export function renderMarkdownInline(input) {
|
|
221
286
|
if (!input)
|
|
222
287
|
return "";
|
|
223
|
-
return marked.parseInline(input, { async: false });
|
|
288
|
+
return sanitizeRenderedHtml(marked.parseInline(input, { async: false }));
|
|
224
289
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sourcey",
|
|
3
|
-
"version": "3.6.
|
|
4
|
-
"description": "Precision documentation from OpenAPI, MCP, Doxygen, godoc, and Markdown. Static HTML you own.",
|
|
3
|
+
"version": "3.6.1",
|
|
4
|
+
"description": "Precision documentation from OpenAPI, MCP, Doxygen, godoc, rustdoc, and Markdown. Static HTML you own.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
7
7
|
"node": ">=20"
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"LICENSE"
|
|
21
21
|
],
|
|
22
22
|
"scripts": {
|
|
23
|
-
"build": "tsc && cp -r src/themes dist/ && cp src/client/*.js dist/client/ && cp -r src/og/fonts dist/og/ && rm -rf dist/core/sourcey-godoc dist/core/godoc-introspect && mkdir -p dist/core/sourcey-godoc && cp -R go/sourcey-godoc/go.mod go/sourcey-godoc/doc.go go/sourcey-godoc/cmd dist/core/sourcey-godoc/",
|
|
23
|
+
"build": "tsc && cp -r src/themes dist/ && cp src/client/*.js dist/client/ && cp -r src/og/fonts dist/og/ && rm -rf dist/core/sourcey-godoc dist/core/godoc-introspect && mkdir -p dist/core/sourcey-godoc && cp -R go/sourcey-godoc/go.mod go/sourcey-godoc/doc.go go/sourcey-godoc/cmd dist/core/sourcey-godoc/ && rm -rf dist/core/sourcey-rustdoc && mkdir -p dist/core/sourcey-rustdoc && cp -R rust/sourcey-rustdoc/Cargo.toml rust/sourcey-rustdoc/Cargo.lock rust/sourcey-rustdoc/README.md rust/sourcey-rustdoc/src rust/sourcey-rustdoc/tests dist/core/sourcey-rustdoc/",
|
|
24
24
|
"watch": "npm run build && tsc --watch --preserveWatchOutput",
|
|
25
25
|
"test": "vitest run",
|
|
26
26
|
"test:watch": "vitest",
|
|
@@ -37,7 +37,6 @@
|
|
|
37
37
|
"dependencies": {
|
|
38
38
|
"@apidevtools/json-schema-ref-parser": "^13.0.5",
|
|
39
39
|
"@preact/preset-vite": "^2.10.4",
|
|
40
|
-
"@readme/openapi-parser": "^4.0.0",
|
|
41
40
|
"@resvg/resvg-js": "^2.6.2",
|
|
42
41
|
"@tailwindcss/typography": "^0.5.19",
|
|
43
42
|
"@tailwindcss/vite": "^4.2.2",
|
|
@@ -50,6 +49,7 @@
|
|
|
50
49
|
"moxygen": "^2.1.8",
|
|
51
50
|
"preact": "^10.28.4",
|
|
52
51
|
"preact-render-to-string": "^6.6.6",
|
|
52
|
+
"sanitize-html": "^2.17.4",
|
|
53
53
|
"satori": "^0.26.0",
|
|
54
54
|
"shiki": "^4.0.2",
|
|
55
55
|
"swagger2openapi": "^7.0.8",
|
|
@@ -57,8 +57,10 @@
|
|
|
57
57
|
"vite": "^8.0.9"
|
|
58
58
|
},
|
|
59
59
|
"devDependencies": {
|
|
60
|
+
"@eslint/js": "^9.39.4",
|
|
60
61
|
"@types/js-yaml": "^4.0.9",
|
|
61
62
|
"@types/node": "^22.19.15",
|
|
63
|
+
"@types/sanitize-html": "^2.16.1",
|
|
62
64
|
"eslint": "^9.0.0",
|
|
63
65
|
"eslint-config-prettier": "^10.0.0",
|
|
64
66
|
"prettier": "^3.4.0",
|
|
@@ -78,9 +80,12 @@
|
|
|
78
80
|
"developer-documentation",
|
|
79
81
|
"doxygen",
|
|
80
82
|
"godoc",
|
|
83
|
+
"rustdoc",
|
|
81
84
|
"golang",
|
|
85
|
+
"rust",
|
|
82
86
|
"go-docs",
|
|
83
87
|
"go-documentation",
|
|
88
|
+
"rust-docs",
|
|
84
89
|
"mcp",
|
|
85
90
|
"llms-txt",
|
|
86
91
|
"static-docs"
|