zudoku 0.1.1-dev.18 → 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.
Files changed (139) hide show
  1. package/dist/lib/plugins/openapi/worker/createSharedWorkerClient.js +2 -3
  2. package/dist/lib/plugins/openapi/worker/createSharedWorkerClient.js.map +1 -1
  3. package/dist/lib/plugins/openapi/worker/shared-worker.d.ts +1 -0
  4. package/dist/lib/plugins/openapi/worker/shared-worker.js +6 -0
  5. package/dist/lib/plugins/openapi/worker/shared-worker.js.map +1 -0
  6. package/dist/vite/config.d.ts +1 -1
  7. package/dist/vite/config.js +13 -13
  8. package/dist/vite/config.js.map +1 -1
  9. package/dist/vite/dev-server.js +1 -1
  10. package/dist/vite/dev-server.js.map +1 -1
  11. package/lib/zudoku.openapi-worker.js +12 -0
  12. package/lib/zudoku.plugins.js +1318 -1323
  13. package/package.json +5 -2
  14. package/src/cli/build/handler.ts +14 -0
  15. package/src/cli/cli.ts +77 -0
  16. package/src/cli/cmds/build.ts +24 -0
  17. package/src/cli/cmds/dev.ts +29 -0
  18. package/src/cli/common/analytics/lib.ts +89 -0
  19. package/src/cli/common/constants.ts +10 -0
  20. package/src/cli/common/logger.ts +5 -0
  21. package/src/cli/common/machine-id/lib.ts +85 -0
  22. package/src/cli/common/outdated.ts +102 -0
  23. package/src/cli/common/output.ts +86 -0
  24. package/src/cli/common/utils/box.license.txt +202 -0
  25. package/src/cli/common/utils/box.ts +116 -0
  26. package/src/cli/common/utils/ports.ts +21 -0
  27. package/src/cli/common/validators/lib.ts +43 -0
  28. package/src/cli/common/xdg/lib.ts +36 -0
  29. package/src/cli/dev/handler.ts +42 -0
  30. package/src/config/config.ts +56 -0
  31. package/src/index.ts +8 -0
  32. package/src/lib/DevPortal.tsx +93 -0
  33. package/src/lib/Heading.tsx +60 -0
  34. package/src/lib/Router.tsx +28 -0
  35. package/src/lib/auth.ts +1 -0
  36. package/src/lib/authentication/authentication.ts +18 -0
  37. package/src/lib/authentication/clerk.ts +45 -0
  38. package/src/lib/authentication/openid.ts +192 -0
  39. package/src/lib/components/AnchorLink.tsx +19 -0
  40. package/src/lib/components/CategoryHeading.tsx +16 -0
  41. package/src/lib/components/Dialog.tsx +119 -0
  42. package/src/lib/components/DynamicIcon.tsx +60 -0
  43. package/src/lib/components/Header.tsx +69 -0
  44. package/src/lib/components/Input.tsx +24 -0
  45. package/src/lib/components/Layout.tsx +56 -0
  46. package/src/lib/components/Markdown.tsx +37 -0
  47. package/src/lib/components/SyntaxHighlight.tsx +94 -0
  48. package/src/lib/components/TopNavigation.tsx +32 -0
  49. package/src/lib/components/context/ComponentsContext.tsx +24 -0
  50. package/src/lib/components/context/DevPortalProvider.ts +54 -0
  51. package/src/lib/components/context/PluginSystem.ts +0 -0
  52. package/src/lib/components/context/ThemeContext.tsx +46 -0
  53. package/src/lib/components/context/ViewportAnchorContext.tsx +139 -0
  54. package/src/lib/components/navigation/SideNavigation.tsx +18 -0
  55. package/src/lib/components/navigation/SideNavigationCategory.tsx +74 -0
  56. package/src/lib/components/navigation/SideNavigationItem.tsx +143 -0
  57. package/src/lib/components/navigation/SideNavigationWrapper.tsx +15 -0
  58. package/src/lib/components/navigation/useNavigationCollapsibleState.ts +27 -0
  59. package/src/lib/components/navigation/util.ts +38 -0
  60. package/src/lib/components.ts +3 -0
  61. package/src/lib/core/DevPortalContext.ts +164 -0
  62. package/src/lib/core/helmet.ts +5 -0
  63. package/src/lib/core/icons.tsx +1 -0
  64. package/src/lib/core/plugins.ts +43 -0
  65. package/src/lib/core/router.tsx +1 -0
  66. package/src/lib/core/types/combine.ts +16 -0
  67. package/src/lib/oas/graphql/index.ts +422 -0
  68. package/src/lib/oas/graphql/server.ts +10 -0
  69. package/src/lib/oas/parser/dereference/index.ts +59 -0
  70. package/src/lib/oas/parser/dereference/resolveRef.ts +32 -0
  71. package/src/lib/oas/parser/index.ts +94 -0
  72. package/src/lib/oas/parser/schemas/v3.0.json +1489 -0
  73. package/src/lib/oas/parser/schemas/v3.1.json +1298 -0
  74. package/src/lib/oas/parser/upgrade/index.ts +108 -0
  75. package/src/lib/plugins/api-key/SettingsApiKeys.tsx +22 -0
  76. package/src/lib/plugins/api-key/index.tsx +123 -0
  77. package/src/lib/plugins/markdown/MdxPage.tsx +128 -0
  78. package/src/lib/plugins/markdown/Toc.tsx +122 -0
  79. package/src/lib/plugins/markdown/generateRoutes.tsx +72 -0
  80. package/src/lib/plugins/markdown/index.tsx +31 -0
  81. package/src/lib/plugins/openapi/ColorizedParam.tsx +82 -0
  82. package/src/lib/plugins/openapi/MakeRequest.tsx +49 -0
  83. package/src/lib/plugins/openapi/MethodBadge.tsx +36 -0
  84. package/src/lib/plugins/openapi/OperationList.tsx +117 -0
  85. package/src/lib/plugins/openapi/OperationListItem.tsx +55 -0
  86. package/src/lib/plugins/openapi/ParameterList.tsx +32 -0
  87. package/src/lib/plugins/openapi/ParameterListItem.tsx +60 -0
  88. package/src/lib/plugins/openapi/RequestBodySidecarBox.tsx +51 -0
  89. package/src/lib/plugins/openapi/ResponsesSidecarBox.tsx +60 -0
  90. package/src/lib/plugins/openapi/Select.tsx +35 -0
  91. package/src/lib/plugins/openapi/Sidecar.tsx +160 -0
  92. package/src/lib/plugins/openapi/SidecarBox.tsx +36 -0
  93. package/src/lib/plugins/openapi/graphql/fragment-masking.ts +111 -0
  94. package/src/lib/plugins/openapi/graphql/gql.ts +70 -0
  95. package/src/lib/plugins/openapi/graphql/graphql.ts +795 -0
  96. package/src/lib/plugins/openapi/graphql/index.ts +2 -0
  97. package/src/lib/plugins/openapi/index.tsx +142 -0
  98. package/src/lib/plugins/openapi/playground/Playground.tsx +309 -0
  99. package/src/lib/plugins/openapi/queries.graphql +6 -0
  100. package/src/lib/plugins/openapi/util/generateSchemaExample.ts +59 -0
  101. package/src/lib/plugins/openapi/util/urql.ts +8 -0
  102. package/src/lib/plugins/openapi/worker/createSharedWorkerClient.ts +60 -0
  103. package/src/lib/plugins/openapi/worker/shared-worker.ts +5 -0
  104. package/src/lib/plugins/openapi/worker/worker.ts +30 -0
  105. package/src/lib/plugins/redirect/index.tsx +20 -0
  106. package/src/lib/plugins.ts +5 -0
  107. package/src/lib/ui/Button.tsx +56 -0
  108. package/src/lib/ui/Callout.tsx +87 -0
  109. package/src/lib/ui/Card.tsx +82 -0
  110. package/src/lib/ui/Note.tsx +58 -0
  111. package/src/lib/ui/Tabs.tsx +52 -0
  112. package/src/lib/util/MdxComponents.tsx +70 -0
  113. package/src/lib/util/cn.ts +6 -0
  114. package/src/lib/util/createVariantComponent.tsx +30 -0
  115. package/src/lib/util/createWaitForNotify.ts +18 -0
  116. package/src/lib/util/groupBy.ts +24 -0
  117. package/src/lib/util/joinPath.tsx +10 -0
  118. package/src/lib/util/pastellize.ts +25 -0
  119. package/src/lib/util/slugify.ts +3 -0
  120. package/src/lib/util/traverseNavigation.ts +55 -0
  121. package/src/lib/util/useScrollToAnchor.ts +38 -0
  122. package/src/lib/util/useScrollToTop.ts +13 -0
  123. package/src/ts.ts +94 -0
  124. package/src/types.d.ts +24 -0
  125. package/src/vite/build.ts +33 -0
  126. package/src/vite/config.test.ts +10 -0
  127. package/src/vite/config.ts +183 -0
  128. package/src/vite/dev-server.ts +64 -0
  129. package/src/vite/html.ts +37 -0
  130. package/src/vite/plugin-api.ts +57 -0
  131. package/src/vite/plugin-auth.ts +32 -0
  132. package/src/vite/plugin-component.ts +26 -0
  133. package/src/vite/plugin-config.ts +31 -0
  134. package/src/vite/plugin-docs.test.ts +32 -0
  135. package/src/vite/plugin-docs.ts +52 -0
  136. package/src/vite/plugin-html.ts +50 -0
  137. package/src/vite/plugin-mdx.ts +74 -0
  138. package/src/vite/plugin-metadata.ts +30 -0
  139. package/src/vite/plugin.ts +23 -0
@@ -0,0 +1,49 @@
1
+ import { useApiIdentities } from "../../components/context/DevPortalProvider.js";
2
+ import type { OperationListItemResult } from "./OperationList.js";
3
+ import { Playground } from "./playground/Playground.js";
4
+ import { useOasConfig } from "./index.js";
5
+ import { gql, useQuery } from "urql";
6
+
7
+ const getServerQuery = gql`
8
+ query getServerQuery($input: JSON!, $type: SchemaType!) {
9
+ schema(input: $input, type: $type) {
10
+ url
11
+ }
12
+ }
13
+ `;
14
+
15
+ export const MakeRequest = ({
16
+ operation,
17
+ }: {
18
+ operation: OperationListItemResult;
19
+ }) => {
20
+ const identities = useApiIdentities();
21
+ const variables = useOasConfig();
22
+ const [server] = useQuery({ query: getServerQuery, variables });
23
+
24
+ return (
25
+ <div>
26
+ <Playground
27
+ host={server.data?.schema.url}
28
+ method={operation.method}
29
+ url={operation.path}
30
+ defaultHeaders={[]}
31
+ />
32
+ {identities.data.map((identity) => (
33
+ <button
34
+ key={identity.id}
35
+ onClick={() => {
36
+ const test = new Request(
37
+ "https://zudoku-customer-main-b36fa2f.d2.zuplo.dev" +
38
+ operation.path,
39
+ );
40
+ void fetch(identity.authorizeRequest(test));
41
+ }}
42
+ className="p-2 border border-border rounded hover:bg-accent"
43
+ >
44
+ Test Request {operation.path} ({identity.name})
45
+ </button>
46
+ ))}
47
+ </div>
48
+ );
49
+ };
@@ -0,0 +1,36 @@
1
+ import { cn } from "../../util/cn.js";
2
+
3
+ export const MethodTextColorMap = {
4
+ get: "text-green-600",
5
+ post: "text-sky-600",
6
+ put: "text-yellow-600",
7
+ delete: "text-red-600",
8
+ patch: "text-purple-600",
9
+ options: "text-indigo-600",
10
+ head: "text-gray-600",
11
+ trace: "text-gray-600",
12
+ };
13
+
14
+ const MethodColorMap = {
15
+ get: "bg-green-400 dark:bg-green-800",
16
+ post: "bg-sky-400 dark:bg-sky-800",
17
+ put: "bg-yellow-400 dark:bg-yellow-800",
18
+ delete: "bg-red-400 dark:bg-red-800",
19
+ patch: "bg-purple-400 dark:bg-purple-600",
20
+ options: "bg-indigo-400 dark:bg-indigo-600",
21
+ head: "bg-gray-400 dark:bg-gray-600",
22
+ trace: "bg-gray-400 dark:bg-gray-600",
23
+ };
24
+
25
+ export const MethodBadge = ({ method }: { method: string }) => {
26
+ return (
27
+ <span
28
+ className={cn(
29
+ "mt-0.5 flex items-center duration-200 transition-opacity text-center uppercase font-mono text-[0.65rem] font-bold rounded text-background dark:text-zinc-50 h-4 px-1",
30
+ MethodColorMap[method as keyof typeof MethodColorMap],
31
+ )}
32
+ >
33
+ {method}
34
+ </span>
35
+ );
36
+ };
@@ -0,0 +1,117 @@
1
+ import { Heading } from "../../Heading.js";
2
+ import { CategoryHeading } from "../../components/CategoryHeading.js";
3
+ import { Markdown, ProseClasses } from "../../components/Markdown.js";
4
+ import { cn } from "../../util/cn.js";
5
+ import { OperationListItem } from "./OperationListItem.js";
6
+ import { useOasConfig } from "./index.js";
7
+ import { graphql } from "./graphql/index.js";
8
+ import { useQuery } from "./util/urql.js";
9
+ import { ResultOf } from "@graphql-typed-document-node/core";
10
+
11
+ export const OperationsFragment = graphql(/* GraphQL */ `
12
+ fragment OperationsFragment on OperationItem {
13
+ slug
14
+ summary
15
+ method
16
+ description
17
+ operationId
18
+ contentTypes
19
+ path
20
+ parameters {
21
+ name
22
+ in
23
+ description
24
+ required
25
+ schema
26
+ style
27
+ }
28
+ requestBody {
29
+ content {
30
+ mediaType
31
+ encoding {
32
+ name
33
+ }
34
+ schema
35
+ }
36
+ description
37
+ required
38
+ }
39
+ responses {
40
+ statusCode
41
+ links
42
+ description
43
+ content {
44
+ mediaType
45
+ encoding {
46
+ name
47
+ }
48
+ schema
49
+ }
50
+ }
51
+ }
52
+ `);
53
+
54
+ export type OperationListItemResult = ResultOf<typeof OperationsFragment>;
55
+
56
+ const AllOperationsQuery = graphql(/* GraphQL */ `
57
+ query AllOperations($input: JSON!, $type: SchemaType!) {
58
+ schema(input: $input, type: $type) {
59
+ description
60
+ title
61
+ url
62
+ version
63
+ tags {
64
+ name
65
+ description
66
+ operations {
67
+ slug
68
+ ...OperationsFragment
69
+ }
70
+ }
71
+ }
72
+ }
73
+ `);
74
+
75
+ export const OperationList = () => {
76
+ const { type, input } = useOasConfig();
77
+
78
+ const [result] = useQuery({
79
+ query: AllOperationsQuery,
80
+ variables: { type, input },
81
+ });
82
+
83
+ if (!result.data) return null;
84
+
85
+ return (
86
+ <div className="pt-[--padding-content-top]">
87
+ <div className={cn(ProseClasses, "mb-16")}>
88
+ <CategoryHeading>Overview</CategoryHeading>
89
+ <Heading level={1} id="description" registerSidebarAnchor>
90
+ {result.data.schema.title}
91
+ </Heading>
92
+ <Markdown content={result.data.schema.description ?? ""} />
93
+ </div>
94
+ {result.data?.schema.tags
95
+ .filter((tag) => tag.operations.length > 0)
96
+ .map((tag) => (
97
+ <div key={tag.name}>
98
+ {tag.name && <CategoryHeading>{tag.name}</CategoryHeading>}
99
+ {tag.description && (
100
+ <Markdown
101
+ className={`${ProseClasses} mt-2 mb-12`}
102
+ content={tag.description}
103
+ />
104
+ )}
105
+ <div className="operation mb-12">
106
+ {tag.operations.map((fragment) => (
107
+ <OperationListItem
108
+ key={fragment.slug}
109
+ operationFragment={fragment}
110
+ />
111
+ ))}
112
+ </div>
113
+ </div>
114
+ ))}
115
+ </div>
116
+ );
117
+ };
@@ -0,0 +1,55 @@
1
+ import { Heading } from "../../Heading.js";
2
+ import { Markdown, ProseClasses } from "../../components/Markdown.js";
3
+ import { groupBy } from "../../util/groupBy.js";
4
+ import { OperationsFragment } from "./OperationList.js";
5
+ import { ParameterList } from "./ParameterList.js";
6
+ import { Sidecar } from "./Sidecar.js";
7
+ import { FragmentType, useFragment } from "./graphql/index.js";
8
+
9
+ export const PARAM_GROUPS = ["path", "query", "header", "cookie"] as const;
10
+ export type ParameterGroup = (typeof PARAM_GROUPS)[number];
11
+
12
+ export const OperationListItem = ({
13
+ operationFragment,
14
+ }: {
15
+ operationFragment: FragmentType<typeof OperationsFragment>;
16
+ }) => {
17
+ const operation = useFragment(OperationsFragment, operationFragment);
18
+ const groupedParameters = groupBy(operation?.parameters ?? [], "in");
19
+
20
+ return (
21
+ <div
22
+ key={operation.operationId}
23
+ className="grid grid-cols-2 gap-8 items-start border-b-2 mb-16 pb-16 border-border"
24
+ >
25
+ <div className={ProseClasses}>
26
+ <Heading
27
+ level={2}
28
+ className="mt-0"
29
+ id={operation.slug}
30
+ registerSidebarAnchor
31
+ >
32
+ {operation.summary}
33
+ </Heading>
34
+ {operation.description && <Markdown content={operation.description} />}
35
+ {operation.parameters && operation.parameters.length > 0 && (
36
+ <div className="mt-4">
37
+ {PARAM_GROUPS.flatMap((group) =>
38
+ groupedParameters?.[group]?.length ? (
39
+ <ParameterList
40
+ key={group}
41
+ id={operation.slug}
42
+ groupedParameters={groupedParameters}
43
+ group={group}
44
+ />
45
+ ) : (
46
+ []
47
+ ),
48
+ )}
49
+ </div>
50
+ )}
51
+ </div>
52
+ <Sidecar operation={operation} />
53
+ </div>
54
+ );
55
+ };
@@ -0,0 +1,32 @@
1
+ import { Heading } from "../../Heading.js";
2
+ import type { ParameterGroup } from "./OperationListItem.js";
3
+ import {
4
+ ParameterListItem,
5
+ type ParameterListItemResult,
6
+ } from "./ParameterListItem.js";
7
+
8
+ export const ParameterList = ({
9
+ groupedParameters,
10
+ group,
11
+ id,
12
+ }: {
13
+ groupedParameters: Record<ParameterGroup, ParameterListItemResult[]>;
14
+ group: ParameterGroup;
15
+ id: string;
16
+ }) => (
17
+ <>
18
+ <Heading level={3} id={`${id}/${group}-parameters`} className="capitalize">
19
+ {group === "header" ? "Headers" : `${group} Parameters`}
20
+ </Heading>
21
+ <ul className="list-none m-0 px-0 overflow-hidden">
22
+ {groupedParameters[group].map((parameter) => (
23
+ <ParameterListItem
24
+ key={`${parameter.name}-${parameter.in}`}
25
+ parameter={parameter}
26
+ id={id}
27
+ group={group}
28
+ />
29
+ ))}
30
+ </ul>
31
+ </>
32
+ );
@@ -0,0 +1,60 @@
1
+ import { Markdown } from "../../components/Markdown.js";
2
+ import { type SchemaObject } from "../../oas/graphql/index.js";
3
+ import { ColorizedParam } from "./ColorizedParam.js";
4
+ import type { OperationListItemResult } from "./OperationList.js";
5
+ import type { ParameterGroup } from "./OperationListItem.js";
6
+
7
+ const getParameterSchema = (
8
+ parameter: ParameterListItemResult,
9
+ ): SchemaObject => {
10
+ if (parameter.schema != null && typeof parameter.schema === "object") {
11
+ return parameter.schema;
12
+ }
13
+ return {
14
+ type: "string",
15
+ };
16
+ };
17
+
18
+ export type ParameterListItemResult = NonNullable<
19
+ OperationListItemResult["parameters"]
20
+ >[number];
21
+
22
+ export const ParameterListItem = ({
23
+ parameter,
24
+ group,
25
+ id,
26
+ }: {
27
+ parameter: ParameterListItemResult;
28
+ group: ParameterGroup;
29
+ id: string;
30
+ }) => (
31
+ <li className="not-prose px-2 py-4 border-t border-border bg-border/20">
32
+ <div className="flex items-center gap-2">
33
+ <code>
34
+ {group === "path" ? (
35
+ <ColorizedParam
36
+ name={parameter.name}
37
+ backgroundOpacity="15%"
38
+ className="px-1"
39
+ slug={id + "-" + parameter.name.toLocaleLowerCase()}
40
+ />
41
+ ) : (
42
+ parameter.name
43
+ )}
44
+ </code>
45
+ {parameter.required && (
46
+ <span className="py-px px-1.5 font-medium text-xs bg-primary/75 text-muted rounded-lg">
47
+ required
48
+ </span>
49
+ )}
50
+ {getParameterSchema(parameter).type && (
51
+ <span className="text-xs text-muted-foreground">
52
+ {getParameterSchema(parameter).type}
53
+ </span>
54
+ )}
55
+ </div>
56
+ {parameter.description && (
57
+ <Markdown content={parameter.description} className="prose-p:my-1" />
58
+ )}
59
+ </li>
60
+ );
@@ -0,0 +1,51 @@
1
+ import { useState } from "react";
2
+ import { SyntaxHighlight } from "../../components/SyntaxHighlight.js";
3
+ import { type SchemaObject } from "../../oas/graphql/index.js";
4
+ import type { OperationListItemResult } from "./OperationList.js";
5
+ import { Select } from "./Select.js";
6
+ import * as SidecarBox from "./SidecarBox.js";
7
+ import { generateSchemaExample } from "./util/generateSchemaExample.js";
8
+
9
+ type Content = NonNullable<
10
+ NonNullable<OperationListItemResult["requestBody"]>["content"]
11
+ >;
12
+
13
+ // @todo should we handle multiple content types?
14
+ export const RequestBodySidecarBox = ({ content }: { content: Content }) => {
15
+ const [selected, setSelected] = useState("example");
16
+
17
+ if (!content.length) return null;
18
+
19
+ return (
20
+ <>
21
+ <div>lol</div>
22
+ <SidecarBox.Root>
23
+ <SidecarBox.Head className="text-xs flex justify-between items-center">
24
+ <span className="font-mono">Request Body</span>
25
+ <Select
26
+ onChange={(e) => setSelected(e.target.value)}
27
+ options={[
28
+ { value: "example", label: "Example" },
29
+ { value: "schema", label: "Schema" },
30
+ ]}
31
+ />
32
+ </SidecarBox.Head>
33
+ <SidecarBox.Body>
34
+ <SyntaxHighlight
35
+ language="json"
36
+ noBackground
37
+ copyable={false}
38
+ className="text-xs"
39
+ code={JSON.stringify(
40
+ selected === "example"
41
+ ? generateSchemaExample(content[0].schema as SchemaObject)
42
+ : content[0].schema,
43
+ null,
44
+ 2,
45
+ )}
46
+ />
47
+ </SidecarBox.Body>
48
+ </SidecarBox.Root>
49
+ </>
50
+ );
51
+ };
@@ -0,0 +1,60 @@
1
+ import { useState } from "react";
2
+ import { SyntaxHighlight } from "../../components/SyntaxHighlight.js";
3
+ import { type SchemaObject } from "../../oas/graphql/index.js";
4
+ import { cn } from "../../util/cn.js";
5
+ import type { OperationListItemResult } from "./OperationList.js";
6
+ import * as SidecarBox from "./SidecarBox.js";
7
+ import { generateSchemaExample } from "./util/generateSchemaExample.js";
8
+
9
+ type Responses = OperationListItemResult["responses"];
10
+ export const ResponsesSidecarBox = ({
11
+ responses,
12
+ }: {
13
+ responses: Responses;
14
+ }) => {
15
+ const [tabIndex, setTabIndex] = useState(0);
16
+
17
+ const activeTab = responses[tabIndex];
18
+ const schema = activeTab?.content?.[0]?.schema as SchemaObject | undefined;
19
+
20
+ return (
21
+ <SidecarBox.Root>
22
+ <SidecarBox.Head className="text-xs grid grid-rows-2 pb-0">
23
+ <span className="font-mono">Responses</span>
24
+ <div className="flex gap-2">
25
+ {responses.map((response, index) => (
26
+ <div
27
+ key={response.statusCode}
28
+ onClick={() => setTabIndex(index)}
29
+ className={cn(
30
+ "text-xs font-mono px-1.5 py-1 pb-px translate-y-px border-b-2 border-transparent rounded-t cursor-pointer",
31
+ tabIndex === index
32
+ ? "text-primary dark:text-inherit border-primary"
33
+ : "hover:border-accent-foreground/25",
34
+ )}
35
+ >
36
+ {response.statusCode}
37
+ </div>
38
+ ))}
39
+ </div>
40
+ </SidecarBox.Head>
41
+ <SidecarBox.Body>
42
+ {schema ? (
43
+ <SyntaxHighlight
44
+ language="json"
45
+ noBackground
46
+ copyable={false}
47
+ className="text-xs"
48
+ code={JSON.stringify(generateSchemaExample(schema), null, 2)}
49
+ />
50
+ ) : (
51
+ <span className="text-muted-foreground font-mono italic text-xs">
52
+ Empty Response
53
+ </span>
54
+ )}
55
+ <hr className="border-border my-1" />
56
+ <div className="text-xs">{responses[tabIndex].description}</div>
57
+ </SidecarBox.Body>
58
+ </SidecarBox.Root>
59
+ );
60
+ };
@@ -0,0 +1,35 @@
1
+ import type { ChangeEventHandler } from "react";
2
+ import { ChevronsUpDownIcon } from "../../core/icons.js";
3
+ import { cn } from "../../util/cn.js";
4
+
5
+ export const Select = ({
6
+ onChange,
7
+ className,
8
+ options,
9
+ }: {
10
+ onChange?: ChangeEventHandler<HTMLSelectElement>;
11
+ className?: string;
12
+ options: {
13
+ value: string;
14
+ label: string;
15
+ }[];
16
+ }) => (
17
+ <div className={cn("grid", className)}>
18
+ <select
19
+ className={cn(
20
+ "row-start-1 col-start-1 border border-input text-foreground px-2 py-1 pe-6",
21
+ "rounded-md appearance-none bg-zinc-50 hover:bg-white dark:bg-zinc-800 hover:dark:bg-zinc-800/75",
22
+ )}
23
+ onChange={onChange}
24
+ >
25
+ {options.map((option) => (
26
+ <option value={option.value} key={option.value}>
27
+ {option.label}
28
+ </option>
29
+ ))}
30
+ </select>
31
+ <div className="row-start-1 col-start-1 self-center justify-self-end relative end-2 pointer-events-none">
32
+ <ChevronsUpDownIcon size={14} />
33
+ </div>
34
+ </div>
35
+ );
@@ -0,0 +1,160 @@
1
+ import { Fragment, useMemo, useState } from "react";
2
+ import { SyntaxHighlight } from "../../components/SyntaxHighlight.js";
3
+ import { cn } from "../../util/cn.js";
4
+ import { ColorizedParam } from "./ColorizedParam.js";
5
+ import { MakeRequest } from "./MakeRequest.js";
6
+ import { MethodTextColorMap } from "./MethodBadge.js";
7
+ import type { OperationListItemResult } from "./OperationList.js";
8
+ import { RequestBodySidecarBox } from "./RequestBodySidecarBox.js";
9
+ import { ResponsesSidecarBox } from "./ResponsesSidecarBox.js";
10
+ import { Select } from "./Select.js";
11
+ import * as SidecarBox from "./SidecarBox.js";
12
+ import { HTTPSnippet } from "@zudoku/httpsnippet";
13
+ import { generateSchemaExample } from "./util/generateSchemaExample.js";
14
+ import type { SchemaObject } from "../../oas/parser/index.js";
15
+
16
+ const getConverted = (snippet: HTTPSnippet, option: string) => {
17
+ let converted;
18
+ switch (option) {
19
+ case "shell":
20
+ converted = snippet.convert("shell", "curl");
21
+ break;
22
+ case "js":
23
+ converted = snippet.convert("javascript", "fetch");
24
+ break;
25
+ case "python":
26
+ converted = snippet.convert("python", "requests");
27
+ break;
28
+ case "java":
29
+ converted = snippet.convert("java", "okhttp");
30
+ break;
31
+ case "go":
32
+ converted = snippet.convert("go", "native");
33
+ break;
34
+ case "csharp":
35
+ converted = snippet.convert("csharp", "httpclient");
36
+ break;
37
+ case "kotlin":
38
+ converted = snippet.convert("kotlin", "okhttp");
39
+ break;
40
+ case "objc":
41
+ converted = snippet.convert("objc", "nsurlsession");
42
+ break;
43
+ case "php":
44
+ converted = snippet.convert("php", "http2");
45
+ break;
46
+ case "ruby":
47
+ converted = snippet.convert("ruby");
48
+ break;
49
+ case "swift":
50
+ converted = snippet.convert("swift");
51
+ break;
52
+ default:
53
+ converted = snippet.convert("shell");
54
+ break;
55
+ }
56
+
57
+ return converted ? converted[0] : "";
58
+ };
59
+
60
+ export const Sidecar = ({
61
+ operation,
62
+ }: {
63
+ operation: OperationListItemResult;
64
+ }) => {
65
+ const methodTextColor =
66
+ MethodTextColorMap[operation.method as keyof typeof MethodTextColorMap];
67
+ const [option, setOption] = useState("curl");
68
+ const requestBodyContent = operation.requestBody?.content;
69
+
70
+ const path = operation.path.split("/").map((part) => (
71
+ <Fragment key={part}>
72
+ {part.startsWith("{") && part.endsWith("}") ? (
73
+ <ColorizedParam
74
+ name={part.slice(1, -1)}
75
+ backgroundOpacity="0"
76
+ // same as in `ParameterListItem`
77
+ slug={operation.slug + "-" + part.slice(1, -1).toLocaleLowerCase()}
78
+ >
79
+ {part}
80
+ </ColorizedParam>
81
+ ) : (
82
+ part
83
+ )}
84
+ /
85
+ <wbr />
86
+ </Fragment>
87
+ ));
88
+
89
+ const code = useMemo(() => {
90
+ const example = requestBodyContent?.[0]?.schema
91
+ ? generateSchemaExample(requestBodyContent[0].schema as SchemaObject)
92
+ : undefined;
93
+
94
+ const snippet = new HTTPSnippet(
95
+ {
96
+ method: operation.method.toLocaleUpperCase(),
97
+ url: operation.path.replaceAll("{", ":").replaceAll("}", ""),
98
+ headers: [{ name: "Authorization", value: "Bearer <token>" }],
99
+ postData: example
100
+ ? {
101
+ text: JSON.stringify(example, null, 2),
102
+ mimeType: "application/json",
103
+ }
104
+ : {},
105
+ } as never, // 👈 never touch this
106
+ );
107
+
108
+ return getConverted(snippet, option) ?? "";
109
+ }, [option, operation.method, operation.path, requestBodyContent]);
110
+
111
+ return (
112
+ <aside className="flex flex-col overflow-hidden sticky top-[--scroll-padding] gap-4">
113
+ <SidecarBox.Root>
114
+ <MakeRequest operation={operation} />
115
+ <SidecarBox.Head className="flex justify-between items-center flex-nowrap p-2 gap-2 text-xs">
116
+ <span className="font-mono break-words">
117
+ <span className={cn("font-semibold", methodTextColor)}>
118
+ {operation.method.toLocaleUpperCase()}
119
+ </span>
120
+ &nbsp;
121
+ {path}
122
+ </span>
123
+ <Select
124
+ className="self-start"
125
+ onChange={(e) => setOption(e.target.value)}
126
+ options={[
127
+ { value: "shell", label: "cURL" },
128
+ { value: "js", label: "Javascript" },
129
+ { value: "python", label: "Python" },
130
+ { value: "java", label: "Java" },
131
+ { value: "go", label: "Go" },
132
+ { value: "csharp", label: "C#" },
133
+ { value: "kotlin", label: "Kotlin" },
134
+ { value: "objc", label: "Objective C" },
135
+ { value: "php", label: "PHP" },
136
+ { value: "ruby", label: "Ruby" },
137
+ { value: "swift", label: "Swift" },
138
+ ]}
139
+ />
140
+ </SidecarBox.Head>
141
+ <SidecarBox.Body>
142
+ <SyntaxHighlight
143
+ language={option}
144
+ copyable={false}
145
+ noBackground
146
+ className="text-xs"
147
+ code={code}
148
+ />
149
+ </SidecarBox.Body>
150
+ </SidecarBox.Root>
151
+ {/*<MakeRequest />*/}
152
+ {requestBodyContent && (
153
+ <RequestBodySidecarBox content={requestBodyContent} />
154
+ )}
155
+ {operation.responses.length > 0 && (
156
+ <ResponsesSidecarBox responses={operation.responses} />
157
+ )}
158
+ </aside>
159
+ );
160
+ };