blodemd 0.0.8 → 0.0.10

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.
Files changed (68) hide show
  1. package/README.md +25 -9
  2. package/dev-server/app/[[...slug]]/page.tsx +1 -0
  3. package/dev-server/app/favicon.ico +0 -0
  4. package/dev-server/next.config.js +11 -13
  5. package/dev-server/package.json +1 -1
  6. package/dev-server/tsconfig.json +3 -0
  7. package/dist/cli.mjs +869 -184
  8. package/dist/cli.mjs.map +1 -1
  9. package/docs/app/globals.css +1 -1
  10. package/docs/components/animate-ui/primitives/buttons/button.tsx +14 -0
  11. package/docs/components/api/api-playground.tsx +255 -80
  12. package/docs/components/api/api-reference.tsx +11 -1
  13. package/docs/components/docs/contextual-menu.tsx +227 -142
  14. package/docs/components/docs/copy-page-menu.tsx +148 -85
  15. package/docs/components/docs/doc-header.tsx +13 -3
  16. package/docs/components/docs/doc-shell.tsx +25 -14
  17. package/docs/components/docs/mobile-nav.tsx +0 -6
  18. package/docs/components/mdx/code-group.tsx +171 -62
  19. package/docs/components/mdx/steps.tsx +1 -1
  20. package/docs/components/mdx/tabs.tsx +131 -26
  21. package/docs/components/ui/copy-button.tsx +122 -0
  22. package/docs/components/ui/input.tsx +0 -1
  23. package/docs/components/ui/search.tsx +241 -132
  24. package/docs/components/ui/site-footer.tsx +39 -0
  25. package/docs/lib/config.ts +7 -0
  26. package/docs/lib/content-root.ts +33 -0
  27. package/docs/lib/content-source.ts +70 -0
  28. package/docs/lib/contextual-options.ts +20 -0
  29. package/docs/lib/docs-runtime.tsx +595 -0
  30. package/docs/lib/edge-config.ts +95 -0
  31. package/docs/lib/env.ts +22 -0
  32. package/docs/lib/openapi-proxy.ts +88 -0
  33. package/docs/lib/platform-config.ts +6 -0
  34. package/docs/lib/routes.ts +39 -0
  35. package/docs/lib/supabase.ts +13 -0
  36. package/docs/lib/tenancy.ts +350 -0
  37. package/docs/lib/tenant-headers.ts +14 -0
  38. package/docs/lib/tenant-static.ts +529 -0
  39. package/docs/lib/tenant-utility-context.ts +62 -0
  40. package/docs/lib/tenants.ts +68 -0
  41. package/docs/lib/use-mobile.ts +19 -0
  42. package/package.json +3 -2
  43. package/packages/@repo/common/dist/index.d.ts +7 -0
  44. package/packages/@repo/common/dist/index.d.ts.map +1 -1
  45. package/packages/@repo/common/dist/index.js +42 -0
  46. package/packages/@repo/common/src/index.ts +50 -0
  47. package/packages/@repo/contracts/dist/project.d.ts +1 -1
  48. package/packages/@repo/contracts/dist/project.js +1 -1
  49. package/packages/@repo/contracts/src/project.ts +1 -1
  50. package/packages/@repo/models/dist/docs-config.d.ts +194 -29
  51. package/packages/@repo/models/dist/docs-config.d.ts.map +1 -1
  52. package/packages/@repo/models/dist/docs-config.js +3 -28
  53. package/packages/@repo/models/src/docs-config.ts +5 -31
  54. package/packages/@repo/previewing/dist/blob-source.d.ts.map +1 -1
  55. package/packages/@repo/previewing/dist/blob-source.js +7 -2
  56. package/packages/@repo/previewing/dist/fs-source.d.ts.map +1 -1
  57. package/packages/@repo/previewing/dist/fs-source.js +2 -3
  58. package/packages/@repo/previewing/dist/index.d.ts.map +1 -1
  59. package/packages/@repo/previewing/dist/index.js +20 -50
  60. package/packages/@repo/previewing/src/blob-source.ts +7 -4
  61. package/packages/@repo/previewing/src/fs-source.ts +2 -3
  62. package/packages/@repo/previewing/src/index.ts +29 -64
  63. package/packages/@repo/validation/dist/index.d.ts +2 -2
  64. package/packages/@repo/validation/dist/index.d.ts.map +1 -1
  65. package/packages/@repo/validation/dist/index.js +2 -2
  66. package/packages/@repo/validation/package.json +1 -0
  67. package/packages/@repo/validation/src/{mintlify-docs-schema.json → blodemd-docs-schema.json} +346 -1794
  68. package/packages/@repo/validation/src/index.ts +4 -4
@@ -1,38 +1,222 @@
1
1
  "use client";
2
2
 
3
- import { useCallback, useMemo, useState } from "react";
3
+ import { useCallback, useMemo, useReducer } from "react";
4
4
  import type { ChangeEvent } from "react";
5
5
 
6
6
  import { Button } from "@/components/ui/button";
7
7
  import { Field, FieldLabel } from "@/components/ui/field";
8
8
  import { Input } from "@/components/ui/input";
9
9
  import type { OpenApiEntry } from "@/lib/openapi";
10
+ import { TENANT_HEADERS } from "@/lib/tenant-headers";
11
+
12
+ interface OperationParameter {
13
+ description?: string;
14
+ name?: string;
15
+ required?: boolean;
16
+ }
10
17
 
11
18
  const extractParams = (entry: OpenApiEntry, location: "path" | "query") =>
12
19
  (entry.operation.parameters ?? []).filter(
13
20
  (param) => (param as { in?: string }).in === location
14
- ) as { name?: string; required?: boolean; description?: string }[];
21
+ ) as OperationParameter[];
22
+
23
+ interface PlaygroundState {
24
+ authToken: string;
25
+ body: string;
26
+ isLoading: boolean;
27
+ pathValues: Record<string, string>;
28
+ queryValues: Record<string, string>;
29
+ response: string | null;
30
+ serverIndex: number;
31
+ status: number | null;
32
+ useProxyPreference: boolean;
33
+ }
34
+
35
+ type PlaygroundAction =
36
+ | { type: "requestError"; response: string }
37
+ | { type: "requestStart" }
38
+ | { type: "requestSuccess"; response: string; status: number }
39
+ | { type: "setAuthToken"; value: string }
40
+ | { type: "setBody"; value: string }
41
+ | { type: "setPathValue"; name: string; value: string }
42
+ | { type: "setQueryValue"; name: string; value: string }
43
+ | { type: "setServerIndex"; value: number }
44
+ | { type: "setUseProxyPreference"; value: boolean };
45
+
46
+ const initialPlaygroundState: PlaygroundState = {
47
+ authToken: "",
48
+ body: "{}",
49
+ isLoading: false,
50
+ pathValues: {},
51
+ queryValues: {},
52
+ response: null,
53
+ serverIndex: 0,
54
+ status: null,
55
+ useProxyPreference: true,
56
+ };
57
+
58
+ const playgroundReducer = (
59
+ state: PlaygroundState,
60
+ action: PlaygroundAction
61
+ ) => {
62
+ switch (action.type) {
63
+ case "requestError": {
64
+ return {
65
+ ...state,
66
+ isLoading: false,
67
+ response: action.response,
68
+ status: 0,
69
+ };
70
+ }
71
+ case "requestStart": {
72
+ return {
73
+ ...state,
74
+ isLoading: true,
75
+ response: null,
76
+ status: null,
77
+ };
78
+ }
79
+ case "requestSuccess": {
80
+ return {
81
+ ...state,
82
+ isLoading: false,
83
+ response: action.response,
84
+ status: action.status,
85
+ };
86
+ }
87
+ case "setAuthToken": {
88
+ return {
89
+ ...state,
90
+ authToken: action.value,
91
+ };
92
+ }
93
+ case "setBody": {
94
+ return {
95
+ ...state,
96
+ body: action.value,
97
+ };
98
+ }
99
+ case "setPathValue": {
100
+ return {
101
+ ...state,
102
+ pathValues: {
103
+ ...state.pathValues,
104
+ [action.name]: action.value,
105
+ },
106
+ };
107
+ }
108
+ case "setQueryValue": {
109
+ return {
110
+ ...state,
111
+ queryValues: {
112
+ ...state.queryValues,
113
+ [action.name]: action.value,
114
+ },
115
+ };
116
+ }
117
+ case "setServerIndex": {
118
+ return {
119
+ ...state,
120
+ serverIndex: action.value,
121
+ };
122
+ }
123
+ case "setUseProxyPreference": {
124
+ return {
125
+ ...state,
126
+ useProxyPreference: action.value,
127
+ };
128
+ }
129
+ default: {
130
+ return state;
131
+ }
132
+ }
133
+ };
134
+
135
+ const ParameterFieldGrid = ({
136
+ idPrefix,
137
+ onChange,
138
+ parameters,
139
+ values,
140
+ }: {
141
+ idPrefix: "path" | "query";
142
+ onChange: (event: ChangeEvent<HTMLInputElement>) => void;
143
+ parameters: OperationParameter[];
144
+ values: Record<string, string>;
145
+ }) => {
146
+ if (!parameters.length) {
147
+ return null;
148
+ }
149
+
150
+ return (
151
+ <div className="grid gap-2.5 grid-cols-[repeat(auto-fit,minmax(180px,1fr))]">
152
+ {parameters.map((param) => (
153
+ <Field key={param.name}>
154
+ <FieldLabel htmlFor={`${idPrefix}-${param.name}`}>
155
+ {param.name}
156
+ </FieldLabel>
157
+ <Input
158
+ id={`${idPrefix}-${param.name}`}
159
+ name={param.name ?? ""}
160
+ onChange={onChange}
161
+ placeholder={param.required ? "Required" : "Optional"}
162
+ type="text"
163
+ value={values[param.name ?? ""] ?? ""}
164
+ />
165
+ </Field>
166
+ ))}
167
+ </div>
168
+ );
169
+ };
170
+
171
+ const ResponsePanel = ({
172
+ response,
173
+ status,
174
+ }: {
175
+ response: string | null;
176
+ status: number | null;
177
+ }) => {
178
+ if (response === null) {
179
+ return null;
180
+ }
181
+
182
+ return (
183
+ <div className="rounded-xl border border-border bg-primary/[0.08] p-3">
184
+ <div className="font-semibold">Status: {status}</div>
185
+ <pre className="mt-2 overflow-x-auto">{response}</pre>
186
+ </div>
187
+ );
188
+ };
15
189
 
16
190
  export const ApiPlayground = ({
17
191
  entry,
18
192
  proxyEnabled,
193
+ proxyPath = "/_internal/proxy",
194
+ tenantSlug,
19
195
  }: {
20
196
  entry: OpenApiEntry;
21
197
  proxyEnabled: boolean;
198
+ proxyPath?: string;
199
+ tenantSlug?: string;
22
200
  }) => {
23
201
  const servers = entry.spec.servers ?? [];
24
- const [serverIndex, setServerIndex] = useState(0);
25
- const [response, setResponse] = useState<string | null>(null);
26
- const [status, setStatus] = useState<number | null>(null);
27
- const [isLoading, setIsLoading] = useState(false);
28
- const [body, setBody] = useState("{}");
29
- const [authToken, setAuthToken] = useState("");
30
- const [useProxy, setUseProxy] = useState(proxyEnabled);
202
+ const [state, dispatch] = useReducer(
203
+ playgroundReducer,
204
+ initialPlaygroundState
205
+ );
206
+ const {
207
+ authToken,
208
+ body,
209
+ isLoading,
210
+ pathValues,
211
+ queryValues,
212
+ response,
213
+ serverIndex,
214
+ status,
215
+ } = state;
216
+ const useProxy = proxyEnabled && state.useProxyPreference;
31
217
 
32
218
  const pathParams = useMemo(() => extractParams(entry, "path"), [entry]);
33
219
  const queryParams = useMemo(() => extractParams(entry, "query"), [entry]);
34
- const [pathValues, setPathValues] = useState<Record<string, string>>({});
35
- const [queryValues, setQueryValues] = useState<Record<string, string>>({});
36
220
 
37
221
  const baseUrl = servers[serverIndex]?.url ?? "";
38
222
  const canSend = Boolean(baseUrl);
@@ -66,54 +250,64 @@ export const ApiPlayground = ({
66
250
 
67
251
  const handleUseProxyChange = useCallback(
68
252
  (event: ChangeEvent<HTMLInputElement>) => {
69
- setUseProxy(event.target.checked);
253
+ dispatch({
254
+ type: "setUseProxyPreference",
255
+ value: event.target.checked,
256
+ });
70
257
  },
71
258
  []
72
259
  );
73
260
  const handleServerChange = useCallback(
74
261
  (event: ChangeEvent<HTMLSelectElement>) => {
75
- setServerIndex(Number(event.target.value));
262
+ dispatch({
263
+ type: "setServerIndex",
264
+ value: Number(event.target.value),
265
+ });
76
266
  },
77
267
  []
78
268
  );
79
269
  const handlePathValueChange = useCallback(
80
270
  (event: ChangeEvent<HTMLInputElement>) => {
81
- const { name, value } = event.target;
82
- setPathValues((prev) => ({
83
- ...prev,
84
- [name]: value,
85
- }));
271
+ dispatch({
272
+ name: event.target.name,
273
+ type: "setPathValue",
274
+ value: event.target.value,
275
+ });
86
276
  },
87
277
  []
88
278
  );
89
279
  const handleQueryValueChange = useCallback(
90
280
  (event: ChangeEvent<HTMLInputElement>) => {
91
- const { name, value } = event.target;
92
- setQueryValues((prev) => ({
93
- ...prev,
94
- [name]: value,
95
- }));
281
+ dispatch({
282
+ name: event.target.name,
283
+ type: "setQueryValue",
284
+ value: event.target.value,
285
+ });
96
286
  },
97
287
  []
98
288
  );
99
289
  const handleAuthTokenChange = useCallback(
100
290
  (event: ChangeEvent<HTMLInputElement>) => {
101
- setAuthToken(event.target.value);
291
+ dispatch({
292
+ type: "setAuthToken",
293
+ value: event.target.value,
294
+ });
102
295
  },
103
296
  []
104
297
  );
105
298
  const handleBodyChange = useCallback(
106
299
  (event: ChangeEvent<HTMLTextAreaElement>) => {
107
- setBody(event.target.value);
300
+ dispatch({
301
+ type: "setBody",
302
+ value: event.target.value,
303
+ });
108
304
  },
109
305
  []
110
306
  );
111
307
 
112
308
  const handleSend = useCallback(async () => {
113
309
  const url = buildUrl();
114
- setIsLoading(true);
115
- setResponse(null);
116
- setStatus(null);
310
+ dispatch({ type: "requestStart" });
117
311
 
118
312
  try {
119
313
  const { method } = entry.operation;
@@ -129,11 +323,12 @@ export const ApiPlayground = ({
129
323
  url,
130
324
  };
131
325
 
132
- const requestUrl = useProxy ? "/blodemd-internal/proxy" : url;
326
+ const requestUrl = useProxy ? proxyPath : url;
133
327
  const requestMethod = useProxy ? "POST" : method;
134
328
  const requestHeadersToSend = useProxy
135
329
  ? {
136
330
  "Content-Type": "application/json",
331
+ ...(tenantSlug ? { [TENANT_HEADERS.SLUG]: tenantSlug } : {}),
137
332
  }
138
333
  : requestHeaders;
139
334
 
@@ -153,21 +348,32 @@ export const ApiPlayground = ({
153
348
  });
154
349
 
155
350
  const text = await res.text();
156
- setStatus(res.status);
157
351
  let formatted = text;
158
352
  try {
159
353
  formatted = JSON.stringify(JSON.parse(text), null, 2);
160
354
  } catch {
161
355
  formatted = text;
162
356
  }
163
- setResponse(formatted || "(empty response)");
357
+ dispatch({
358
+ response: formatted || "(empty response)",
359
+ status: res.status,
360
+ type: "requestSuccess",
361
+ });
164
362
  } catch (error) {
165
- setStatus(0);
166
- setResponse(error instanceof Error ? error.message : "Request failed.");
167
- } finally {
168
- setIsLoading(false);
363
+ dispatch({
364
+ response: error instanceof Error ? error.message : "Request failed.",
365
+ type: "requestError",
366
+ });
169
367
  }
170
- }, [authToken, body, buildUrl, entry.operation, useProxy]);
368
+ }, [
369
+ authToken,
370
+ body,
371
+ buildUrl,
372
+ entry.operation,
373
+ proxyPath,
374
+ tenantSlug,
375
+ useProxy,
376
+ ]);
171
377
 
172
378
  return (
173
379
  <section className="mt-7 grid gap-3">
@@ -205,45 +411,19 @@ export const ApiPlayground = ({
205
411
  </Field>
206
412
  ) : null}
207
413
 
208
- {pathParams.length ? (
209
- <div className="grid gap-2.5 grid-cols-[repeat(auto-fit,minmax(180px,1fr))]">
210
- {pathParams.map((param) => (
211
- <Field key={param.name}>
212
- <FieldLabel htmlFor={`path-${param.name}`}>
213
- {param.name}
214
- </FieldLabel>
215
- <Input
216
- id={`path-${param.name}`}
217
- name={param.name ?? ""}
218
- onChange={handlePathValueChange}
219
- placeholder={param.required ? "Required" : "Optional"}
220
- type="text"
221
- value={pathValues[param.name ?? ""] ?? ""}
222
- />
223
- </Field>
224
- ))}
225
- </div>
226
- ) : null}
414
+ <ParameterFieldGrid
415
+ idPrefix="path"
416
+ onChange={handlePathValueChange}
417
+ parameters={pathParams}
418
+ values={pathValues}
419
+ />
227
420
 
228
- {queryParams.length ? (
229
- <div className="grid gap-2.5 grid-cols-[repeat(auto-fit,minmax(180px,1fr))]">
230
- {queryParams.map((param) => (
231
- <Field key={param.name}>
232
- <FieldLabel htmlFor={`query-${param.name}`}>
233
- {param.name}
234
- </FieldLabel>
235
- <Input
236
- id={`query-${param.name}`}
237
- name={param.name ?? ""}
238
- onChange={handleQueryValueChange}
239
- placeholder={param.required ? "Required" : "Optional"}
240
- type="text"
241
- value={queryValues[param.name ?? ""] ?? ""}
242
- />
243
- </Field>
244
- ))}
245
- </div>
246
- ) : null}
421
+ <ParameterFieldGrid
422
+ idPrefix="query"
423
+ onChange={handleQueryValueChange}
424
+ parameters={queryParams}
425
+ values={queryValues}
426
+ />
247
427
 
248
428
  <Field>
249
429
  <FieldLabel htmlFor="auth-token">Auth token</FieldLabel>
@@ -283,12 +463,7 @@ export const ApiPlayground = ({
283
463
  </p>
284
464
  )}
285
465
 
286
- {response === null ? null : (
287
- <div className="rounded-xl border border-border bg-primary/[0.08] p-3">
288
- <div className="font-semibold">Status: {status}</div>
289
- <pre className="mt-2 overflow-x-auto">{response}</pre>
290
- </div>
291
- )}
466
+ <ResponsePanel response={response} status={status} />
292
467
  </div>
293
468
  </section>
294
469
  );
@@ -19,9 +19,13 @@ const methodColors: Record<string, string> = {
19
19
  export const ApiReference = ({
20
20
  entry,
21
21
  proxyEnabled,
22
+ proxyPath,
23
+ tenantSlug,
22
24
  }: {
23
25
  entry: OpenApiEntry;
24
26
  proxyEnabled: boolean;
27
+ proxyPath?: string;
28
+ tenantSlug?: string;
25
29
  }) => {
26
30
  const { operation } = entry;
27
31
  const parameters = operation.parameters ?? [];
@@ -115,7 +119,13 @@ export const ApiReference = ({
115
119
  </section>
116
120
  ) : null}
117
121
 
118
- <ApiPlayground entry={entry} proxyEnabled={proxyEnabled} />
122
+ <ApiPlayground
123
+ entry={entry}
124
+ key={`${entry.identifier}:${operation.method}:${operation.path}`}
125
+ proxyEnabled={proxyEnabled}
126
+ proxyPath={proxyPath}
127
+ tenantSlug={tenantSlug}
128
+ />
119
129
  </div>
120
130
  );
121
131
  };