radiant-docs 0.1.62 → 0.1.64
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/package.json +1 -1
- package/template/astro.config.mjs +38 -27
- package/template/package-lock.json +2857 -1145
- package/template/package.json +16 -20
- package/template/scripts/generate-proxy-allowed-origins.mjs +10 -187
- package/template/scripts/publish-shiki-platform-assets.mjs +32 -6
- package/template/src/components/chat/AssistantEmbedPanel.tsx +133 -1
- package/template/src/components/endpoint/PlaygroundForm.astro +69 -55
- package/template/src/components/endpoint/ResponseDisplay.astro +2 -2
- package/template/src/generated/shiki-platform-assets.json +8 -8
- package/template/src/lib/assistant-panel-config.ts +2 -57
- package/template/src/lib/assistant-shiki-client.ts +16 -0
- package/template/src/lib/client-shiki-config.ts +63 -0
- package/template/src/lib/dev-playground-proxy.mjs +597 -0
- package/template/src/lib/proxy-allowed-origins.mjs +189 -0
- package/template/src/styles/global.css +4 -4
- package/template/src/components/ui/demo/CodeDemo.astro +0 -15
- package/template/src/components/ui/demo/Demo.astro +0 -3
- package/template/src/components/ui/demo/UiDisplay.astro +0 -13
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import YAML from "yaml";
|
|
4
|
+
|
|
5
|
+
export function isRecord(value) {
|
|
6
|
+
return typeof value === "object" && value !== null;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function isHttpUrl(value) {
|
|
10
|
+
return /^https?:\/\//i.test(String(value).trim());
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function collectOpenApiSourcesFromNavigation(navigationNode, target) {
|
|
14
|
+
if (!isRecord(navigationNode)) {
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const openApiConfig = navigationNode.openapi;
|
|
19
|
+
if (typeof openApiConfig === "string") {
|
|
20
|
+
const trimmed = openApiConfig.trim();
|
|
21
|
+
if (trimmed) {
|
|
22
|
+
target.add(trimmed);
|
|
23
|
+
}
|
|
24
|
+
} else if (
|
|
25
|
+
isRecord(openApiConfig) &&
|
|
26
|
+
typeof openApiConfig.source === "string"
|
|
27
|
+
) {
|
|
28
|
+
const trimmed = openApiConfig.source.trim();
|
|
29
|
+
if (trimmed) {
|
|
30
|
+
target.add(trimmed);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const menu = navigationNode.menu;
|
|
35
|
+
if (isRecord(menu) && Array.isArray(menu.items)) {
|
|
36
|
+
for (const menuItem of menu.items) {
|
|
37
|
+
if (!isRecord(menuItem)) {
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
collectOpenApiSourcesFromNavigation(menuItem, target);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const tabs = navigationNode.tabs;
|
|
45
|
+
if (isRecord(tabs) && Array.isArray(tabs.items)) {
|
|
46
|
+
for (const tabItem of tabs.items) {
|
|
47
|
+
if (!isRecord(tabItem)) {
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
collectOpenApiSourcesFromNavigation(tabItem, target);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function resolveServerTemplateUrl(rawUrl, rawVariables) {
|
|
56
|
+
let unresolved = false;
|
|
57
|
+
const variables = isRecord(rawVariables) ? rawVariables : null;
|
|
58
|
+
|
|
59
|
+
const resolved = rawUrl.replace(/\{([^}]+)\}/g, (_match, tokenName) => {
|
|
60
|
+
if (!variables) {
|
|
61
|
+
unresolved = true;
|
|
62
|
+
return "";
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const variableConfig = variables[tokenName];
|
|
66
|
+
if (
|
|
67
|
+
!isRecord(variableConfig) ||
|
|
68
|
+
typeof variableConfig.default !== "string"
|
|
69
|
+
) {
|
|
70
|
+
unresolved = true;
|
|
71
|
+
return "";
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const defaultValue = variableConfig.default.trim();
|
|
75
|
+
if (!defaultValue) {
|
|
76
|
+
unresolved = true;
|
|
77
|
+
return "";
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return defaultValue;
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
if (unresolved) {
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return resolved;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function normalizeAllowedOrigin(rawUrl) {
|
|
91
|
+
let parsed;
|
|
92
|
+
try {
|
|
93
|
+
parsed = new URL(rawUrl);
|
|
94
|
+
} catch {
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (parsed.protocol !== "https:") {
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (!parsed.hostname || parsed.username || parsed.password) {
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return parsed.origin.toLowerCase();
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
async function loadOpenApiSpec(source, { docsDir, fetchImpl }) {
|
|
110
|
+
let fileContent;
|
|
111
|
+
|
|
112
|
+
if (isHttpUrl(source)) {
|
|
113
|
+
const response = await fetchImpl(source);
|
|
114
|
+
if (!response.ok) {
|
|
115
|
+
throw new Error(
|
|
116
|
+
`Failed to fetch OpenAPI spec (${response.status} ${response.statusText})`,
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
fileContent = await response.text();
|
|
120
|
+
} else {
|
|
121
|
+
const absolutePath = path.join(docsDir, source);
|
|
122
|
+
fileContent = await fs.readFile(absolutePath, "utf8");
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const trimmed = fileContent.trim();
|
|
126
|
+
if (trimmed.startsWith("<!DOCTYPE") || trimmed.startsWith("<html")) {
|
|
127
|
+
throw new Error("OpenAPI source returned HTML instead of JSON or YAML");
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
try {
|
|
131
|
+
return JSON.parse(fileContent);
|
|
132
|
+
} catch {
|
|
133
|
+
return YAML.parse(fileContent);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export async function buildAllowedOrigins({
|
|
138
|
+
cwd = process.cwd(),
|
|
139
|
+
docsDir = path.join(cwd, "src/content/docs"),
|
|
140
|
+
docsConfigPath = path.join(docsDir, "docs.json"),
|
|
141
|
+
fetchImpl = fetch,
|
|
142
|
+
onSourceError = undefined,
|
|
143
|
+
} = {}) {
|
|
144
|
+
const docsConfigRaw = await fs.readFile(docsConfigPath, "utf8");
|
|
145
|
+
const docsConfig = JSON.parse(docsConfigRaw);
|
|
146
|
+
|
|
147
|
+
if (!isRecord(docsConfig) || !isRecord(docsConfig.navigation)) {
|
|
148
|
+
return [];
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const sources = new Set();
|
|
152
|
+
collectOpenApiSourcesFromNavigation(docsConfig.navigation, sources);
|
|
153
|
+
if (sources.size === 0) {
|
|
154
|
+
return [];
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const allowedOrigins = new Set();
|
|
158
|
+
|
|
159
|
+
for (const source of sources) {
|
|
160
|
+
try {
|
|
161
|
+
const spec = await loadOpenApiSpec(source, { docsDir, fetchImpl });
|
|
162
|
+
const servers =
|
|
163
|
+
isRecord(spec) && Array.isArray(spec.servers) ? spec.servers : [];
|
|
164
|
+
|
|
165
|
+
for (const server of servers) {
|
|
166
|
+
if (!isRecord(server) || typeof server.url !== "string") {
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const resolvedUrl = resolveServerTemplateUrl(
|
|
171
|
+
server.url,
|
|
172
|
+
server.variables,
|
|
173
|
+
);
|
|
174
|
+
if (!resolvedUrl) {
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const normalizedOrigin = normalizeAllowedOrigin(resolvedUrl);
|
|
179
|
+
if (normalizedOrigin) {
|
|
180
|
+
allowedOrigins.add(normalizedOrigin);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
} catch (error) {
|
|
184
|
+
onSourceError?.(source, error);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
return Array.from(allowedOrigins).sort();
|
|
189
|
+
}
|
|
@@ -75,14 +75,14 @@
|
|
|
75
75
|
--rd-panel-transition-easing: cubic-bezier(0.22, 1, 0.36, 1);
|
|
76
76
|
--rd-code-surface: color-mix(
|
|
77
77
|
in srgb,
|
|
78
|
-
var(--color-neutral-100)
|
|
79
|
-
var(--background)
|
|
78
|
+
var(--color-neutral-100) 70%,
|
|
79
|
+
var(--background) 30%
|
|
80
80
|
);
|
|
81
81
|
--rd-code-tab-edge-bg: var(--rd-code-surface);
|
|
82
82
|
--rd-code-tab-edge-border: color-mix(
|
|
83
83
|
in oklab,
|
|
84
|
-
var(--color-neutral-900)
|
|
85
|
-
var(--color-white)
|
|
84
|
+
var(--color-neutral-900) 6%,
|
|
85
|
+
var(--color-white) 94%
|
|
86
86
|
);
|
|
87
87
|
}
|
|
88
88
|
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
<span class="demo-component">
|
|
2
|
-
<slot />
|
|
3
|
-
</span>
|
|
4
|
-
|
|
5
|
-
<style>
|
|
6
|
-
@reference "../../../styles/global.css";
|
|
7
|
-
|
|
8
|
-
:global(.demo-component .frame) {
|
|
9
|
-
@apply shadow-none! my-0!;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
:global(.demo-component .frame pre) {
|
|
13
|
-
@apply border-none! rounded-none rounded-b-xl bg-neutral-50! inset-shadow-xs;
|
|
14
|
-
}
|
|
15
|
-
</style>
|