zudoku 0.1.1-dev.17 → 0.1.1-dev.19
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/dist/lib/plugins/openapi/worker/createSharedWorkerClient.js +2 -3
- package/dist/lib/plugins/openapi/worker/createSharedWorkerClient.js.map +1 -1
- package/dist/lib/plugins/openapi/worker/shared-worker.d.ts +1 -0
- package/dist/lib/plugins/openapi/worker/shared-worker.js +6 -0
- package/dist/lib/plugins/openapi/worker/shared-worker.js.map +1 -0
- package/dist/vite/config.d.ts +1 -1
- package/dist/vite/config.js +13 -13
- package/dist/vite/config.js.map +1 -1
- package/dist/vite/dev-server.js +1 -1
- package/dist/vite/dev-server.js.map +1 -1
- package/lib/DevPortal-DqcnbwLT.js +12967 -0
- package/lib/assets/index-BPdJm2ty.js +4764 -0
- package/lib/assets/worker-CnXQsqxH.js +14511 -0
- package/lib/prism-bash.min-DadFsM4Z.js +6 -0
- package/lib/prism-java.min-d5iT_mOd.js +6 -0
- package/lib/prism-json.min-B1GJqK1k.js +1 -0
- package/lib/prism-markup-templating-DZrrEs0A.js +61 -0
- package/lib/prism-php.min-o7FpoMP_.js +10 -0
- package/lib/prism-ruby.min-C7LwcKyz.js +9 -0
- package/lib/zudoku.auth.js +19712 -0
- package/lib/zudoku.components.js +6 -0
- package/lib/zudoku.openapi-worker.js +12 -0
- package/lib/zudoku.plugins.js +17382 -0
- package/package.json +6 -2
- package/src/cli/build/handler.ts +14 -0
- package/src/cli/cli.ts +77 -0
- package/src/cli/cmds/build.ts +24 -0
- package/src/cli/cmds/dev.ts +29 -0
- package/src/cli/common/analytics/lib.ts +89 -0
- package/src/cli/common/constants.ts +10 -0
- package/src/cli/common/logger.ts +5 -0
- package/src/cli/common/machine-id/lib.ts +85 -0
- package/src/cli/common/outdated.ts +102 -0
- package/src/cli/common/output.ts +86 -0
- package/src/cli/common/utils/box.license.txt +202 -0
- package/src/cli/common/utils/box.ts +116 -0
- package/src/cli/common/utils/ports.ts +21 -0
- package/src/cli/common/validators/lib.ts +43 -0
- package/src/cli/common/xdg/lib.ts +36 -0
- package/src/cli/dev/handler.ts +42 -0
- package/src/config/config.ts +56 -0
- package/src/index.ts +8 -0
- package/src/lib/DevPortal.tsx +93 -0
- package/src/lib/Heading.tsx +60 -0
- package/src/lib/Router.tsx +28 -0
- package/src/lib/auth.ts +1 -0
- package/src/lib/authentication/authentication.ts +18 -0
- package/src/lib/authentication/clerk.ts +45 -0
- package/src/lib/authentication/openid.ts +192 -0
- package/src/lib/components/AnchorLink.tsx +19 -0
- package/src/lib/components/CategoryHeading.tsx +16 -0
- package/src/lib/components/Dialog.tsx +119 -0
- package/src/lib/components/DynamicIcon.tsx +60 -0
- package/src/lib/components/Header.tsx +69 -0
- package/src/lib/components/Input.tsx +24 -0
- package/src/lib/components/Layout.tsx +56 -0
- package/src/lib/components/Markdown.tsx +37 -0
- package/src/lib/components/SyntaxHighlight.tsx +94 -0
- package/src/lib/components/TopNavigation.tsx +32 -0
- package/src/lib/components/context/ComponentsContext.tsx +24 -0
- package/src/lib/components/context/DevPortalProvider.ts +54 -0
- package/src/lib/components/context/PluginSystem.ts +0 -0
- package/src/lib/components/context/ThemeContext.tsx +46 -0
- package/src/lib/components/context/ViewportAnchorContext.tsx +139 -0
- package/src/lib/components/navigation/SideNavigation.tsx +18 -0
- package/src/lib/components/navigation/SideNavigationCategory.tsx +74 -0
- package/src/lib/components/navigation/SideNavigationItem.tsx +143 -0
- package/src/lib/components/navigation/SideNavigationWrapper.tsx +15 -0
- package/src/lib/components/navigation/useNavigationCollapsibleState.ts +27 -0
- package/src/lib/components/navigation/util.ts +38 -0
- package/src/lib/components.ts +3 -0
- package/src/lib/core/DevPortalContext.ts +164 -0
- package/src/lib/core/helmet.ts +5 -0
- package/src/lib/core/icons.tsx +1 -0
- package/src/lib/core/plugins.ts +43 -0
- package/src/lib/core/router.tsx +1 -0
- package/src/lib/core/types/combine.ts +16 -0
- package/src/lib/oas/graphql/index.ts +422 -0
- package/src/lib/oas/graphql/server.ts +10 -0
- package/src/lib/oas/parser/dereference/index.ts +59 -0
- package/src/lib/oas/parser/dereference/resolveRef.ts +32 -0
- package/src/lib/oas/parser/index.ts +94 -0
- package/src/lib/oas/parser/schemas/v3.0.json +1489 -0
- package/src/lib/oas/parser/schemas/v3.1.json +1298 -0
- package/src/lib/oas/parser/upgrade/index.ts +108 -0
- package/src/lib/plugins/api-key/SettingsApiKeys.tsx +22 -0
- package/src/lib/plugins/api-key/index.tsx +123 -0
- package/src/lib/plugins/markdown/MdxPage.tsx +128 -0
- package/src/lib/plugins/markdown/Toc.tsx +122 -0
- package/src/lib/plugins/markdown/generateRoutes.tsx +72 -0
- package/src/lib/plugins/markdown/index.tsx +31 -0
- package/src/lib/plugins/openapi/ColorizedParam.tsx +82 -0
- package/src/lib/plugins/openapi/MakeRequest.tsx +49 -0
- package/src/lib/plugins/openapi/MethodBadge.tsx +36 -0
- package/src/lib/plugins/openapi/OperationList.tsx +117 -0
- package/src/lib/plugins/openapi/OperationListItem.tsx +55 -0
- package/src/lib/plugins/openapi/ParameterList.tsx +32 -0
- package/src/lib/plugins/openapi/ParameterListItem.tsx +60 -0
- package/src/lib/plugins/openapi/RequestBodySidecarBox.tsx +51 -0
- package/src/lib/plugins/openapi/ResponsesSidecarBox.tsx +60 -0
- package/src/lib/plugins/openapi/Select.tsx +35 -0
- package/src/lib/plugins/openapi/Sidecar.tsx +160 -0
- package/src/lib/plugins/openapi/SidecarBox.tsx +36 -0
- package/src/lib/plugins/openapi/graphql/fragment-masking.ts +111 -0
- package/src/lib/plugins/openapi/graphql/gql.ts +70 -0
- package/src/lib/plugins/openapi/graphql/graphql.ts +795 -0
- package/src/lib/plugins/openapi/graphql/index.ts +2 -0
- package/src/lib/plugins/openapi/index.tsx +142 -0
- package/src/lib/plugins/openapi/playground/Playground.tsx +309 -0
- package/src/lib/plugins/openapi/queries.graphql +6 -0
- package/src/lib/plugins/openapi/util/generateSchemaExample.ts +59 -0
- package/src/lib/plugins/openapi/util/urql.ts +8 -0
- package/src/lib/plugins/openapi/worker/createSharedWorkerClient.ts +60 -0
- package/src/lib/plugins/openapi/worker/shared-worker.ts +5 -0
- package/src/lib/plugins/openapi/worker/worker.ts +30 -0
- package/src/lib/plugins/redirect/index.tsx +20 -0
- package/src/lib/plugins.ts +5 -0
- package/src/lib/ui/Button.tsx +56 -0
- package/src/lib/ui/Callout.tsx +87 -0
- package/src/lib/ui/Card.tsx +82 -0
- package/src/lib/ui/Note.tsx +58 -0
- package/src/lib/ui/Tabs.tsx +52 -0
- package/src/lib/util/MdxComponents.tsx +70 -0
- package/src/lib/util/cn.ts +6 -0
- package/src/lib/util/createVariantComponent.tsx +30 -0
- package/src/lib/util/createWaitForNotify.ts +18 -0
- package/src/lib/util/groupBy.ts +24 -0
- package/src/lib/util/joinPath.tsx +10 -0
- package/src/lib/util/pastellize.ts +25 -0
- package/src/lib/util/slugify.ts +3 -0
- package/src/lib/util/traverseNavigation.ts +55 -0
- package/src/lib/util/useScrollToAnchor.ts +38 -0
- package/src/lib/util/useScrollToTop.ts +13 -0
- package/src/ts.ts +94 -0
- package/src/types.d.ts +24 -0
- package/src/vite/build.ts +33 -0
- package/src/vite/config.test.ts +10 -0
- package/src/vite/config.ts +183 -0
- package/src/vite/dev-server.ts +64 -0
- package/src/vite/html.ts +37 -0
- package/src/vite/plugin-api.ts +57 -0
- package/src/vite/plugin-auth.ts +32 -0
- package/src/vite/plugin-component.ts +26 -0
- package/src/vite/plugin-config.ts +31 -0
- package/src/vite/plugin-docs.test.ts +32 -0
- package/src/vite/plugin-docs.ts +52 -0
- package/src/vite/plugin-html.ts +50 -0
- package/src/vite/plugin-mdx.ts +74 -0
- package/src/vite/plugin-metadata.ts +30 -0
- package/src/vite/plugin.ts +23 -0
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { createContext, useContext } from "react";
|
|
2
|
+
import {
|
|
3
|
+
type DevPortalPlugin,
|
|
4
|
+
type PluginNavigationCategory,
|
|
5
|
+
} from "../../core/plugins.js";
|
|
6
|
+
import { Outlet, matchPath, type RouteObject } from "../../core/router.js";
|
|
7
|
+
import { MethodBadge } from "./MethodBadge.js";
|
|
8
|
+
import { OperationList } from "./OperationList.js";
|
|
9
|
+
import { graphql } from "./graphql/index.js";
|
|
10
|
+
import {
|
|
11
|
+
Provider,
|
|
12
|
+
Client as UrqlClient,
|
|
13
|
+
cacheExchange,
|
|
14
|
+
fetchExchange,
|
|
15
|
+
} from "./util/urql.js";
|
|
16
|
+
import { createSharedWorkerClient } from "./worker/createSharedWorkerClient.js";
|
|
17
|
+
|
|
18
|
+
const OasContext = createContext<{ config: OasPluginConfig } | undefined>(
|
|
19
|
+
undefined,
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
export const OasConfigProvider = OasContext.Provider;
|
|
23
|
+
|
|
24
|
+
export const useOasConfig = () => {
|
|
25
|
+
const ctx = useContext(OasContext);
|
|
26
|
+
if (!ctx) {
|
|
27
|
+
throw new Error("useOasConfig must be used within a OasConfigProvider");
|
|
28
|
+
}
|
|
29
|
+
return ctx.config;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
type OasSource =
|
|
33
|
+
| { type: "url"; input: string }
|
|
34
|
+
| { type: "yaml"; input: string }
|
|
35
|
+
| { type: "json"; input: object };
|
|
36
|
+
|
|
37
|
+
export type OasPluginConfig = {
|
|
38
|
+
server?: string;
|
|
39
|
+
path?: string;
|
|
40
|
+
} & OasSource;
|
|
41
|
+
|
|
42
|
+
const OasContextProvider = ({
|
|
43
|
+
config,
|
|
44
|
+
client,
|
|
45
|
+
}: {
|
|
46
|
+
config: OasPluginConfig;
|
|
47
|
+
client: typeof UrqlClient;
|
|
48
|
+
}) => {
|
|
49
|
+
return (
|
|
50
|
+
<Provider value={client}>
|
|
51
|
+
<OasConfigProvider value={{ config }}>
|
|
52
|
+
<Outlet />
|
|
53
|
+
</OasConfigProvider>
|
|
54
|
+
</Provider>
|
|
55
|
+
);
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const GetCategoriesQuery = graphql(`
|
|
59
|
+
query GetCategories($input: JSON!, $type: SchemaType!) {
|
|
60
|
+
schema(input: $input, type: $type) {
|
|
61
|
+
tags {
|
|
62
|
+
__typename
|
|
63
|
+
name
|
|
64
|
+
operations {
|
|
65
|
+
__typename
|
|
66
|
+
slug
|
|
67
|
+
method
|
|
68
|
+
summary
|
|
69
|
+
operationId
|
|
70
|
+
path
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
`);
|
|
76
|
+
|
|
77
|
+
export const openApiPlugin = (config: OasPluginConfig): DevPortalPlugin => {
|
|
78
|
+
const basePath = config.path ?? "/reference";
|
|
79
|
+
|
|
80
|
+
const client = config.server
|
|
81
|
+
? new UrqlClient({
|
|
82
|
+
url: config.server,
|
|
83
|
+
exchanges: [cacheExchange, fetchExchange],
|
|
84
|
+
})
|
|
85
|
+
: createSharedWorkerClient();
|
|
86
|
+
|
|
87
|
+
return {
|
|
88
|
+
getNavigation: async (path: string) => {
|
|
89
|
+
if (!matchPath({ path: basePath, end: false }, path)) {
|
|
90
|
+
return [];
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const { data } = await client.query(GetCategoriesQuery, {
|
|
94
|
+
input: config.input,
|
|
95
|
+
type: config.type,
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
if (!data) return [];
|
|
99
|
+
|
|
100
|
+
const categories = data.schema.tags
|
|
101
|
+
.filter((tag) => tag.operations.length > 0)
|
|
102
|
+
.map<PluginNavigationCategory>((tag) => ({
|
|
103
|
+
path,
|
|
104
|
+
label: tag.name ?? "",
|
|
105
|
+
collapsible: false,
|
|
106
|
+
children: tag.operations.map((operation) => ({
|
|
107
|
+
path: `#${operation.slug}`,
|
|
108
|
+
title: operation.summary ?? operation.path,
|
|
109
|
+
label: (
|
|
110
|
+
<div className="flex flex-1 min-w-0 justify-between gap-2">
|
|
111
|
+
<span className="truncate">
|
|
112
|
+
{operation.summary ?? operation.path}
|
|
113
|
+
</span>
|
|
114
|
+
<MethodBadge method={operation.method} />
|
|
115
|
+
</div>
|
|
116
|
+
),
|
|
117
|
+
})),
|
|
118
|
+
}));
|
|
119
|
+
|
|
120
|
+
categories.unshift({
|
|
121
|
+
path,
|
|
122
|
+
label: "Overview",
|
|
123
|
+
collapsible: false,
|
|
124
|
+
children: [{ path: "#description", label: "Description" }],
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
return categories;
|
|
128
|
+
},
|
|
129
|
+
getRoutes: () =>
|
|
130
|
+
[
|
|
131
|
+
{
|
|
132
|
+
element: <OasContextProvider config={config} client={client} />,
|
|
133
|
+
children: [
|
|
134
|
+
{
|
|
135
|
+
path: basePath,
|
|
136
|
+
children: [{ index: true, element: <OperationList /> }],
|
|
137
|
+
},
|
|
138
|
+
],
|
|
139
|
+
},
|
|
140
|
+
] satisfies RouteObject[],
|
|
141
|
+
};
|
|
142
|
+
};
|
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Dialog,
|
|
3
|
+
DialogContent,
|
|
4
|
+
DialogDescription,
|
|
5
|
+
DialogHeader,
|
|
6
|
+
DialogTitle,
|
|
7
|
+
DialogTrigger,
|
|
8
|
+
} from "../../../components/Dialog.js";
|
|
9
|
+
import { Card, CardContent } from "../../../ui/Card.js";
|
|
10
|
+
import { Button } from "../../../ui/Button.js";
|
|
11
|
+
import { Tabs, TabsContent, TabsList, TabsTrigger } from "../../../ui/Tabs.js";
|
|
12
|
+
import { Header } from "har-format";
|
|
13
|
+
import {
|
|
14
|
+
Fragment,
|
|
15
|
+
InputHTMLAttributes,
|
|
16
|
+
useEffect,
|
|
17
|
+
useRef,
|
|
18
|
+
useState,
|
|
19
|
+
} from "react";
|
|
20
|
+
import { TrashIcon } from "lucide-react";
|
|
21
|
+
import createVariantComponent from "../../../util/createVariantComponent.js";
|
|
22
|
+
import { SyntaxHighlight } from "../../../components/SyntaxHighlight.js";
|
|
23
|
+
import {
|
|
24
|
+
ColorizedParam,
|
|
25
|
+
DATA_ATTR,
|
|
26
|
+
usePastellizedColor,
|
|
27
|
+
} from "../ColorizedParam.js";
|
|
28
|
+
import { cn } from "../../../util/cn.js";
|
|
29
|
+
import { useMutation } from "@tanstack/react-query";
|
|
30
|
+
|
|
31
|
+
const InlineInput = createVariantComponent(
|
|
32
|
+
"input",
|
|
33
|
+
"px-2 bg-transparent h-8 font-mono text-xs m-2",
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
const ParameterValue = ({
|
|
37
|
+
part,
|
|
38
|
+
className,
|
|
39
|
+
...props
|
|
40
|
+
}: {
|
|
41
|
+
part: string;
|
|
42
|
+
} & InputHTMLAttributes<HTMLInputElement>) => {
|
|
43
|
+
const color = usePastellizedColor(part);
|
|
44
|
+
return (
|
|
45
|
+
<InlineInput
|
|
46
|
+
{...props}
|
|
47
|
+
className={cn(className, "opacity-80 data-[active=true]:opacity-100")}
|
|
48
|
+
style={{ color: `hsl(${color})` }}
|
|
49
|
+
/>
|
|
50
|
+
);
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const Playground = ({
|
|
54
|
+
url,
|
|
55
|
+
host,
|
|
56
|
+
method,
|
|
57
|
+
defaultHeaders,
|
|
58
|
+
}: {
|
|
59
|
+
host: string;
|
|
60
|
+
url: string;
|
|
61
|
+
method: string;
|
|
62
|
+
defaultHeaders?: Header[];
|
|
63
|
+
}) => {
|
|
64
|
+
const [headers, setHeaders] = useState<Header[]>(
|
|
65
|
+
defaultHeaders ?? [{ name: "", value: "" }],
|
|
66
|
+
);
|
|
67
|
+
const [urlParts, setUrlParts] = useState<Header[]>(
|
|
68
|
+
url
|
|
69
|
+
.split("/")
|
|
70
|
+
.filter((part) => part.startsWith("{") && part.endsWith("}"))
|
|
71
|
+
.map((part) => ({ name: part, value: "" })),
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
useEffect(() => {
|
|
75
|
+
const lastHeader = headers.at(-1);
|
|
76
|
+
if (lastHeader?.value !== "" || lastHeader?.name !== "") {
|
|
77
|
+
setHeaders([...headers, { name: "", value: "" }]);
|
|
78
|
+
}
|
|
79
|
+
}, [headers]);
|
|
80
|
+
|
|
81
|
+
const x = useMutation({
|
|
82
|
+
mutationFn: async () => {
|
|
83
|
+
const fullUrl =
|
|
84
|
+
host +
|
|
85
|
+
url
|
|
86
|
+
.split("/")
|
|
87
|
+
.map((v) => urlParts.find((part) => part.name === v)?.value ?? v)
|
|
88
|
+
.join("/");
|
|
89
|
+
console.log(
|
|
90
|
+
fullUrl,
|
|
91
|
+
Object.fromEntries(
|
|
92
|
+
headers.map((header) => [header.name, header.value]),
|
|
93
|
+
),
|
|
94
|
+
);
|
|
95
|
+
const response = await fetch(fullUrl, {
|
|
96
|
+
// method,
|
|
97
|
+
headers: Object.fromEntries(
|
|
98
|
+
headers
|
|
99
|
+
.filter((h) => h.name)
|
|
100
|
+
.map((header) => [header.name, header.value]),
|
|
101
|
+
),
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
return {
|
|
105
|
+
status: response.status,
|
|
106
|
+
body: await response.text(),
|
|
107
|
+
};
|
|
108
|
+
},
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
const path = url.split("/").map((part) => (
|
|
112
|
+
<Fragment key={part}>
|
|
113
|
+
{part.startsWith("{") && part.endsWith("}") ? (
|
|
114
|
+
<ColorizedParam
|
|
115
|
+
name={part.slice(1, -1)}
|
|
116
|
+
backgroundOpacity="0"
|
|
117
|
+
onClick={() => {
|
|
118
|
+
console.log("asd");
|
|
119
|
+
}}
|
|
120
|
+
slug={part.slice(1, -1)}
|
|
121
|
+
>
|
|
122
|
+
{urlParts.find((p) => p.name === part)?.value}
|
|
123
|
+
</ColorizedParam>
|
|
124
|
+
) : (
|
|
125
|
+
part
|
|
126
|
+
)}
|
|
127
|
+
/
|
|
128
|
+
<wbr />
|
|
129
|
+
</Fragment>
|
|
130
|
+
));
|
|
131
|
+
|
|
132
|
+
return (
|
|
133
|
+
<Dialog>
|
|
134
|
+
<DialogTrigger>Open</DialogTrigger>
|
|
135
|
+
<DialogContent className="max-w-screen-xl">
|
|
136
|
+
<DialogHeader>
|
|
137
|
+
<DialogTitle>API Playground</DialogTitle>
|
|
138
|
+
<DialogDescription>
|
|
139
|
+
<div className="grid grid-cols-2 gap-2">
|
|
140
|
+
<Card>
|
|
141
|
+
<CardContent className="border-b border-border pt-4">
|
|
142
|
+
<div>URL</div>
|
|
143
|
+
<div className="flex gap-2">
|
|
144
|
+
<div className="border rounded border-border p-1.5 w-full">
|
|
145
|
+
{path}
|
|
146
|
+
</div>
|
|
147
|
+
<Button onClick={() => x.mutateAsync()}>Send</Button>
|
|
148
|
+
</div>
|
|
149
|
+
</CardContent>
|
|
150
|
+
<Tabs defaultValue="headers">
|
|
151
|
+
<CardContent className="border-b border-border py-4">
|
|
152
|
+
<TabsList>
|
|
153
|
+
<TabsTrigger value="headers">
|
|
154
|
+
Headers ({headers.length})
|
|
155
|
+
</TabsTrigger>
|
|
156
|
+
<TabsTrigger value="parameters">Parameters</TabsTrigger>
|
|
157
|
+
<TabsTrigger value="auth">Auth</TabsTrigger>
|
|
158
|
+
</TabsList>
|
|
159
|
+
</CardContent>
|
|
160
|
+
<CardContent>
|
|
161
|
+
<TabsContent value="headers">
|
|
162
|
+
<div className="grid grid-cols-[1fr_1fr_auto]">
|
|
163
|
+
{headers.map((header, i) => (
|
|
164
|
+
<div
|
|
165
|
+
key={i}
|
|
166
|
+
className="grid-cols-subgrid col-span-full grid items-center gap-x-2 has-[:focus]:bg-muted hover:bg-muted rounded overflow-hidden group"
|
|
167
|
+
>
|
|
168
|
+
{/*<input type="checkbox" />*/}
|
|
169
|
+
<InlineInput
|
|
170
|
+
onChange={(e) =>
|
|
171
|
+
setHeaders((headers) => {
|
|
172
|
+
const newHeaders = [...headers];
|
|
173
|
+
newHeaders[i] = {
|
|
174
|
+
...headers[i],
|
|
175
|
+
name: e.target.value,
|
|
176
|
+
};
|
|
177
|
+
return newHeaders;
|
|
178
|
+
})
|
|
179
|
+
}
|
|
180
|
+
value={header.name}
|
|
181
|
+
placeholder={"Name"}
|
|
182
|
+
className="peer"
|
|
183
|
+
/>
|
|
184
|
+
<InlineInput
|
|
185
|
+
onChange={(e) =>
|
|
186
|
+
setHeaders((headers) => {
|
|
187
|
+
const newHeaders = [...headers];
|
|
188
|
+
newHeaders[i] = {
|
|
189
|
+
name: "",
|
|
190
|
+
...headers.at(i),
|
|
191
|
+
value: e.target.value,
|
|
192
|
+
};
|
|
193
|
+
return newHeaders;
|
|
194
|
+
})
|
|
195
|
+
}
|
|
196
|
+
value={header.value}
|
|
197
|
+
placeholder={"Value"}
|
|
198
|
+
className="peer"
|
|
199
|
+
/>
|
|
200
|
+
<button
|
|
201
|
+
className="hover:bg-black/5 p-1 rounded mr-2 text-muted-foreground invisible group-hover:visible peer-focus:visible"
|
|
202
|
+
onClick={() => {
|
|
203
|
+
setHeaders((headers) =>
|
|
204
|
+
[...headers].toSpliced(i, 1),
|
|
205
|
+
);
|
|
206
|
+
}}
|
|
207
|
+
>
|
|
208
|
+
<TrashIcon size={16} />
|
|
209
|
+
</button>
|
|
210
|
+
<div className="col-span-full border-b border-border"></div>
|
|
211
|
+
</div>
|
|
212
|
+
))}
|
|
213
|
+
</div>
|
|
214
|
+
</TabsContent>
|
|
215
|
+
<TabsContent value="parameters">
|
|
216
|
+
Parameters
|
|
217
|
+
<div className="grid grid-cols-[1fr_1fr_auto]">
|
|
218
|
+
{urlParts.map(({ name, value }, i) => (
|
|
219
|
+
<div
|
|
220
|
+
key={i}
|
|
221
|
+
className="grid-cols-subgrid col-span-full grid items-center gap-x-2 has-[:focus]:bg-muted hover:bg-muted rounded overflow-hidden group"
|
|
222
|
+
>
|
|
223
|
+
{/*<input type="checkbox" />*/}
|
|
224
|
+
<InlineInput
|
|
225
|
+
onChange={(e) =>
|
|
226
|
+
setHeaders((headers) => {
|
|
227
|
+
const newHeaders = [...headers];
|
|
228
|
+
newHeaders[i] = {
|
|
229
|
+
...headers[i],
|
|
230
|
+
name: e.target.value,
|
|
231
|
+
};
|
|
232
|
+
return newHeaders;
|
|
233
|
+
})
|
|
234
|
+
}
|
|
235
|
+
value={name.slice(1, -1)}
|
|
236
|
+
disabled
|
|
237
|
+
placeholder={"Name"}
|
|
238
|
+
className="peer"
|
|
239
|
+
/>
|
|
240
|
+
<ParameterValue
|
|
241
|
+
part={name.slice(1, -1)}
|
|
242
|
+
onChange={(e) =>
|
|
243
|
+
setUrlParts((parts) => {
|
|
244
|
+
const newParts = [...parts];
|
|
245
|
+
newParts[i] = {
|
|
246
|
+
name: "",
|
|
247
|
+
...parts.at(i),
|
|
248
|
+
value: e.target.value,
|
|
249
|
+
};
|
|
250
|
+
return newParts;
|
|
251
|
+
})
|
|
252
|
+
}
|
|
253
|
+
{...{ [DATA_ATTR]: name.slice(1, -1) }}
|
|
254
|
+
value={value}
|
|
255
|
+
placeholder={"Value"}
|
|
256
|
+
className="peer"
|
|
257
|
+
/>
|
|
258
|
+
|
|
259
|
+
<div className="col-span-full border-b border-border"></div>
|
|
260
|
+
</div>
|
|
261
|
+
))}
|
|
262
|
+
</div>
|
|
263
|
+
</TabsContent>
|
|
264
|
+
<TabsContent value="auth">
|
|
265
|
+
<CardContent>
|
|
266
|
+
{url
|
|
267
|
+
.split("/")
|
|
268
|
+
.map((part, i) =>
|
|
269
|
+
part.startsWith("{") && part.endsWith("}")
|
|
270
|
+
? urlParts[i]
|
|
271
|
+
: part,
|
|
272
|
+
)
|
|
273
|
+
.join("/")}
|
|
274
|
+
</CardContent>
|
|
275
|
+
Change your password here.
|
|
276
|
+
</TabsContent>
|
|
277
|
+
</CardContent>
|
|
278
|
+
</Tabs>
|
|
279
|
+
</Card>
|
|
280
|
+
<Card>
|
|
281
|
+
<CardContent>
|
|
282
|
+
{method.toUpperCase()}
|
|
283
|
+
{url
|
|
284
|
+
.split("/")
|
|
285
|
+
.map((v) =>
|
|
286
|
+
v.startsWith("{") && v.endsWith("}")
|
|
287
|
+
? urlParts.find((part) => part.name === v)?.value ?? v
|
|
288
|
+
: v,
|
|
289
|
+
)
|
|
290
|
+
.join("/")}
|
|
291
|
+
<SyntaxHighlight
|
|
292
|
+
language="json"
|
|
293
|
+
noBackground
|
|
294
|
+
copyable={false}
|
|
295
|
+
className="text-xs"
|
|
296
|
+
code={x.data?.body ?? JSON.stringify("")}
|
|
297
|
+
/>
|
|
298
|
+
</CardContent>
|
|
299
|
+
{x.data?.status}
|
|
300
|
+
</Card>
|
|
301
|
+
</div>
|
|
302
|
+
</DialogDescription>
|
|
303
|
+
</DialogHeader>
|
|
304
|
+
</DialogContent>
|
|
305
|
+
</Dialog>
|
|
306
|
+
);
|
|
307
|
+
};
|
|
308
|
+
|
|
309
|
+
export { Playground };
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
+
import { type SchemaObject } from "../../../oas/graphql/index.js";
|
|
3
|
+
|
|
4
|
+
export const isObject = (value: unknown): boolean =>
|
|
5
|
+
typeof value === "object" && value !== null && !Array.isArray(value);
|
|
6
|
+
|
|
7
|
+
export const generateSchemaExample = (schema: SchemaObject): any => {
|
|
8
|
+
// Directly return the example or default if they exist
|
|
9
|
+
if (schema.example !== undefined) {
|
|
10
|
+
return schema.example;
|
|
11
|
+
} else if (schema.default !== undefined) {
|
|
12
|
+
return schema.default;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// Process examples object
|
|
16
|
+
if (schema.examples && isObject(schema.examples)) {
|
|
17
|
+
return Object.values(schema.examples)[0];
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Recursively process objects and arrays
|
|
21
|
+
return processComplexTypes(schema);
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
function processComplexTypes(schema: SchemaObject): any {
|
|
25
|
+
if (schema.type === "object" && schema.properties) {
|
|
26
|
+
const obj: { [key: string]: any } = {};
|
|
27
|
+
Object.keys(schema.properties!).forEach((key) => {
|
|
28
|
+
obj[key] = generateSchemaExample(schema.properties![key]);
|
|
29
|
+
});
|
|
30
|
+
return obj;
|
|
31
|
+
} else if (schema.type === "array" && schema.items) {
|
|
32
|
+
return [generateSchemaExample(schema.items)];
|
|
33
|
+
}
|
|
34
|
+
// Fallback for missing or undefined types
|
|
35
|
+
return getDefaultForType(schema.type);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function getDefaultForType(type?: string | string[]): any {
|
|
39
|
+
if (Array.isArray(type)) {
|
|
40
|
+
return getDefaultForSingleType(type[0]);
|
|
41
|
+
}
|
|
42
|
+
return getDefaultForSingleType(type);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function getDefaultForSingleType(type?: string): any {
|
|
46
|
+
switch (type) {
|
|
47
|
+
case "string":
|
|
48
|
+
return "";
|
|
49
|
+
case "number":
|
|
50
|
+
case "integer":
|
|
51
|
+
return 0;
|
|
52
|
+
case "boolean":
|
|
53
|
+
return false;
|
|
54
|
+
case "null":
|
|
55
|
+
return null;
|
|
56
|
+
default:
|
|
57
|
+
return undefined;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import * as urql from "urql";
|
|
2
|
+
|
|
3
|
+
export const useQuery = urql.useQuery;
|
|
4
|
+
export const Provider = urql.Provider;
|
|
5
|
+
export const cacheExchange = urql.cacheExchange;
|
|
6
|
+
export const fetchExchange = urql.fetchExchange;
|
|
7
|
+
export const mapExchange = urql.mapExchange;
|
|
8
|
+
export const Client = urql.Client;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { monotonicFactory } from "ulid";
|
|
2
|
+
import { createWaitForNotify } from "../../../util/createWaitForNotify.js";
|
|
3
|
+
import {
|
|
4
|
+
cacheExchange,
|
|
5
|
+
Client,
|
|
6
|
+
fetchExchange,
|
|
7
|
+
mapExchange,
|
|
8
|
+
} from "../util/urql.js";
|
|
9
|
+
|
|
10
|
+
import { createSharedWorker } from "zudoku/open-api-worker";
|
|
11
|
+
|
|
12
|
+
export type WorkerGraphQLMessage = { id: string; body: string };
|
|
13
|
+
|
|
14
|
+
const ulid = monotonicFactory();
|
|
15
|
+
export const createSharedWorkerClient = () => {
|
|
16
|
+
const worker = createSharedWorker();
|
|
17
|
+
|
|
18
|
+
worker.port.start();
|
|
19
|
+
|
|
20
|
+
const [waitFor, notify] = createWaitForNotify<string>();
|
|
21
|
+
|
|
22
|
+
worker.port.onmessage = (e: MessageEvent<WorkerGraphQLMessage>) => {
|
|
23
|
+
notify(e.data.id, e.data.body);
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
return new Client({
|
|
27
|
+
url: "/__z/graphql",
|
|
28
|
+
// Custom fetch to send the GraphQL request to the worker and convert the response back to a `Response` object
|
|
29
|
+
fetch: async (_req, init) => {
|
|
30
|
+
if (!init?.body) throw new Error("No body");
|
|
31
|
+
|
|
32
|
+
const id = ulid();
|
|
33
|
+
worker.port.postMessage({
|
|
34
|
+
id,
|
|
35
|
+
body: init.body as string,
|
|
36
|
+
} satisfies WorkerGraphQLMessage);
|
|
37
|
+
|
|
38
|
+
const body = await waitFor(id);
|
|
39
|
+
|
|
40
|
+
return new Response(body, {
|
|
41
|
+
headers: {
|
|
42
|
+
"Content-Type": "application/json",
|
|
43
|
+
},
|
|
44
|
+
});
|
|
45
|
+
},
|
|
46
|
+
exchanges: [
|
|
47
|
+
cacheExchange,
|
|
48
|
+
mapExchange({
|
|
49
|
+
onError(error, operation) {
|
|
50
|
+
console.error(error);
|
|
51
|
+
console.groupCollapsed("Operation info");
|
|
52
|
+
console.log("body", operation.query.loc?.source.body.trim());
|
|
53
|
+
console.log("variables", operation.variables);
|
|
54
|
+
console.groupEnd();
|
|
55
|
+
},
|
|
56
|
+
}),
|
|
57
|
+
fetchExchange,
|
|
58
|
+
],
|
|
59
|
+
});
|
|
60
|
+
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { createGraphQLServer } from "../../../oas/graphql/index.js";
|
|
2
|
+
import { type WorkerGraphQLMessage } from "./createSharedWorkerClient.js";
|
|
3
|
+
|
|
4
|
+
const localServer = createGraphQLServer();
|
|
5
|
+
|
|
6
|
+
const worker = self as unknown as SharedWorkerGlobalScope;
|
|
7
|
+
|
|
8
|
+
worker.addEventListener(
|
|
9
|
+
"connect",
|
|
10
|
+
function (event: MessageEvent<{ id: string; body: string }>) {
|
|
11
|
+
const port = event.ports[0];
|
|
12
|
+
|
|
13
|
+
port.onmessage = async function (e) {
|
|
14
|
+
const response = await localServer.fetch(
|
|
15
|
+
new Request("/__z/graphql", {
|
|
16
|
+
method: "POST",
|
|
17
|
+
body: e.data.body,
|
|
18
|
+
headers: {
|
|
19
|
+
"Content-Type": "application/json",
|
|
20
|
+
},
|
|
21
|
+
}),
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
port.postMessage({
|
|
25
|
+
id: e.data.id,
|
|
26
|
+
body: await response.text(),
|
|
27
|
+
} satisfies WorkerGraphQLMessage);
|
|
28
|
+
};
|
|
29
|
+
},
|
|
30
|
+
);
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { Navigate } from "react-router-dom";
|
|
2
|
+
import type { DevPortalPlugin } from "../../core/plugins.js";
|
|
3
|
+
|
|
4
|
+
type Redirect = {
|
|
5
|
+
from: string;
|
|
6
|
+
to: string;
|
|
7
|
+
replace?: boolean;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export const redirectPlugin = (options: {
|
|
11
|
+
redirects: Redirect[];
|
|
12
|
+
}): DevPortalPlugin => {
|
|
13
|
+
return {
|
|
14
|
+
getRoutes: () =>
|
|
15
|
+
options.redirects.map(({ from, to, replace }) => ({
|
|
16
|
+
path: from,
|
|
17
|
+
element: <Navigate to={to} replace={replace} />,
|
|
18
|
+
})),
|
|
19
|
+
};
|
|
20
|
+
};
|