zudoku 0.75.0 → 0.75.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/dist/cli/cli.js +35 -9
- package/dist/declarations/lib/authentication/providers/openid.d.ts +1 -0
- package/dist/declarations/lib/authentication/state.d.ts +6 -1
- package/dist/declarations/lib/plugins/api-catalog/Catalog.d.ts +2 -4
- package/dist/declarations/lib/plugins/api-catalog/index.d.ts +3 -8
- package/dist/declarations/lib/plugins/openapi/schema/SchemaRefLink.d.ts +5 -0
- package/dist/declarations/lib/plugins/openapi/schema/SchemaView.d.ts +2 -1
- package/dist/declarations/lib/plugins/openapi/schema/utils.d.ts +1 -0
- package/dist/declarations/lib/ui/HoverCard.d.ts +5 -5
- package/dist/declarations/lib/ui/Toggle.d.ts +2 -6
- package/dist/declarations/lib/ui/ToggleGroup.d.ts +7 -9
- package/docs/deploy/github-pages.md +12 -0
- package/docs/guides/redirects.md +186 -0
- package/package.json +8 -7
- package/src/lib/authentication/providers/openid.tsx +18 -15
- package/src/lib/authentication/state.ts +14 -1
- package/src/lib/components/Meta.tsx +10 -1
- package/src/lib/hooks/useHotkey.ts +10 -0
- package/src/lib/oas/graphql/circular.ts +11 -0
- package/src/lib/plugins/api-catalog/Catalog.tsx +255 -52
- package/src/lib/plugins/api-catalog/index.tsx +7 -64
- package/src/lib/plugins/openapi/ParamInfos.tsx +20 -7
- package/src/lib/plugins/openapi/SchemaList.tsx +1 -1
- package/src/lib/plugins/openapi/SecurityRequirements.tsx +86 -68
- package/src/lib/plugins/openapi/playground/Playground.tsx +7 -7
- package/src/lib/plugins/openapi/schema/SchemaRefLink.tsx +21 -0
- package/src/lib/plugins/openapi/schema/SchemaView.tsx +22 -4
- package/src/lib/plugins/openapi/schema/utils.ts +21 -0
- package/src/lib/ui/Badge.tsx +1 -1
- package/src/lib/ui/HoverCard.tsx +35 -21
- package/src/lib/ui/Toggle.tsx +21 -21
- package/src/lib/ui/ToggleGroup.tsx +59 -32
- package/src/vite/build.ts +3 -1
- package/src/vite/plugin-api.ts +32 -0
- package/src/zuplo/enrich-with-zuplo-mcp.ts +37 -41
package/dist/cli/cli.js
CHANGED
|
@@ -3813,7 +3813,7 @@ import {
|
|
|
3813
3813
|
// package.json
|
|
3814
3814
|
var package_default = {
|
|
3815
3815
|
name: "zudoku",
|
|
3816
|
-
version: "0.
|
|
3816
|
+
version: "0.75.0",
|
|
3817
3817
|
type: "module",
|
|
3818
3818
|
sideEffects: [
|
|
3819
3819
|
"**/*.css",
|
|
@@ -3891,6 +3891,7 @@ var package_default = {
|
|
|
3891
3891
|
},
|
|
3892
3892
|
dependencies: {
|
|
3893
3893
|
"@apidevtools/json-schema-ref-parser": "15.3.1",
|
|
3894
|
+
"@base-ui/react": "^1.4.0",
|
|
3894
3895
|
"@envelop/core": "5.5.1",
|
|
3895
3896
|
"@graphql-typed-document-node/core": "3.2.0",
|
|
3896
3897
|
"@hono/node-server": "1.19.13",
|
|
@@ -3958,7 +3959,7 @@ var package_default = {
|
|
|
3958
3959
|
"hast-util-heading-rank": "3.0.0",
|
|
3959
3960
|
"hast-util-to-jsx-runtime": "2.3.6",
|
|
3960
3961
|
"hast-util-to-string": "3.0.1",
|
|
3961
|
-
hono: "4.12.
|
|
3962
|
+
hono: "4.12.14",
|
|
3962
3963
|
"http-terminator": "3.2.0",
|
|
3963
3964
|
"javascript-stringify": "2.1.0",
|
|
3964
3965
|
"json-schema-to-typescript-lite": "15.0.0",
|
|
@@ -3973,7 +3974,7 @@ var package_default = {
|
|
|
3973
3974
|
"next-themes": "0.4.6",
|
|
3974
3975
|
oauth4webapi: "3.8.5",
|
|
3975
3976
|
"openapi-types": "12.1.3",
|
|
3976
|
-
pagefind: "1.5.
|
|
3977
|
+
pagefind: "1.5.2",
|
|
3977
3978
|
picocolors: "1.1.1",
|
|
3978
3979
|
piscina: "5.1.4",
|
|
3979
3980
|
"posthog-node": "5.26.0",
|
|
@@ -4010,9 +4011,9 @@ var package_default = {
|
|
|
4010
4011
|
zustand: "5.0.12"
|
|
4011
4012
|
},
|
|
4012
4013
|
devDependencies: {
|
|
4013
|
-
"@clerk/clerk-js": "^6.
|
|
4014
|
-
"@graphql-codegen/cli": "6.
|
|
4015
|
-
"@inkeep/cxkit-types": "0.5.
|
|
4014
|
+
"@clerk/clerk-js": "^6.7.4",
|
|
4015
|
+
"@graphql-codegen/cli": "6.3.0",
|
|
4016
|
+
"@inkeep/cxkit-types": "0.5.117",
|
|
4016
4017
|
"@testing-library/dom": "catalog:",
|
|
4017
4018
|
"@testing-library/jest-dom": "catalog:",
|
|
4018
4019
|
"@testing-library/react": "catalog:",
|
|
@@ -4030,7 +4031,7 @@ var package_default = {
|
|
|
4030
4031
|
"@types/yargs": "17.0.35",
|
|
4031
4032
|
"@vitest/coverage-v8": "4.0.18",
|
|
4032
4033
|
"happy-dom": "catalog:",
|
|
4033
|
-
"oxc-parser": "^0.
|
|
4034
|
+
"oxc-parser": "^0.126.0",
|
|
4034
4035
|
react: "catalog:",
|
|
4035
4036
|
"react-dom": "catalog:",
|
|
4036
4037
|
tsx: "4.21.0",
|
|
@@ -4730,6 +4731,9 @@ var handleCircularRefs = (obj, currentPath = /* @__PURE__ */ new WeakSet(), refs
|
|
|
4730
4731
|
const result = Array.isArray(obj) ? obj.map((item, i) => recurse(item, i.toString())) : Object.fromEntries(
|
|
4731
4732
|
Object.entries(obj).map(([k, v]) => [k, recurse(v, k)])
|
|
4732
4733
|
);
|
|
4734
|
+
if (!Array.isArray(result) && typeof refPath === "string" && !("__$ref" in result)) {
|
|
4735
|
+
result.__$ref = refPath;
|
|
4736
|
+
}
|
|
4733
4737
|
refs.set(obj, result);
|
|
4734
4738
|
currentPath.delete(obj);
|
|
4735
4739
|
if (typeof refPath === "string") currentRefPaths.delete(refPath);
|
|
@@ -6224,15 +6228,37 @@ var viteApiPlugin = async () => {
|
|
|
6224
6228
|
);
|
|
6225
6229
|
const apis = ensureArray(config2.apis);
|
|
6226
6230
|
const apiMetadata = [];
|
|
6231
|
+
const httpMethods = /* @__PURE__ */ new Set([
|
|
6232
|
+
"get",
|
|
6233
|
+
"post",
|
|
6234
|
+
"put",
|
|
6235
|
+
"patch",
|
|
6236
|
+
"delete",
|
|
6237
|
+
"options",
|
|
6238
|
+
"head",
|
|
6239
|
+
"trace"
|
|
6240
|
+
]);
|
|
6227
6241
|
for (const apiConfig of apis) {
|
|
6228
6242
|
if (apiConfig.type === "file" && apiConfig.path) {
|
|
6229
6243
|
const latestSchema = schemaManager.getLatestSchema(apiConfig.path);
|
|
6230
6244
|
if (!latestSchema?.schema.info) continue;
|
|
6245
|
+
const operationCount = Object.values(
|
|
6246
|
+
latestSchema.schema.paths ?? {}
|
|
6247
|
+
).reduce((sum, pathItem) => {
|
|
6248
|
+
if (!pathItem || typeof pathItem !== "object") return sum;
|
|
6249
|
+
return sum + Object.keys(pathItem).filter(
|
|
6250
|
+
(m) => httpMethods.has(m.toLowerCase())
|
|
6251
|
+
).length;
|
|
6252
|
+
}, 0);
|
|
6253
|
+
const rawVersion = latestSchema.schema.info.version;
|
|
6254
|
+
const version = rawVersion ? rawVersion.startsWith("v") || rawVersion.startsWith("V") ? rawVersion : `v${rawVersion}` : void 0;
|
|
6231
6255
|
apiMetadata.push({
|
|
6232
6256
|
path: apiConfig.path,
|
|
6233
6257
|
label: latestSchema.schema.info.title,
|
|
6234
6258
|
description: latestSchema.schema.info.description ?? "",
|
|
6235
|
-
categories: apiConfig.categories ?? []
|
|
6259
|
+
categories: apiConfig.categories ?? [],
|
|
6260
|
+
version,
|
|
6261
|
+
operationCount
|
|
6236
6262
|
});
|
|
6237
6263
|
}
|
|
6238
6264
|
}
|
|
@@ -8232,7 +8258,7 @@ var runPrerender = async (options) => {
|
|
|
8232
8258
|
await writeFile5(indexHtml, html, "utf-8");
|
|
8233
8259
|
}
|
|
8234
8260
|
const statusPages = workerResults.flatMap(
|
|
8235
|
-
(r) =>
|
|
8261
|
+
(r) => /^(400|404|500)\.html$/.test(path22.basename(r.outputPath)) ? r.outputPath : []
|
|
8236
8262
|
);
|
|
8237
8263
|
for (const statusPage of statusPages) {
|
|
8238
8264
|
await rename(
|
|
@@ -43,6 +43,7 @@ export declare class OpenIDAuthenticationProvider extends CoreAuthenticationPlug
|
|
|
43
43
|
replace?: boolean;
|
|
44
44
|
}): Promise<void>;
|
|
45
45
|
signIn(_: AuthActionContext, { redirectTo, replace }: AuthActionOptions): Promise<void>;
|
|
46
|
+
private buildUserProfile;
|
|
46
47
|
refreshUserProfile(): Promise<boolean>;
|
|
47
48
|
private authorize;
|
|
48
49
|
getAccessToken(): Promise<string>;
|
|
@@ -39,11 +39,16 @@ export declare const useAuthState: import("zustand").UseBoundStore<Omit<import("
|
|
|
39
39
|
getOptions: () => Partial<import("zustand/middleware").PersistOptions<AuthState, unknown, unknown>>;
|
|
40
40
|
};
|
|
41
41
|
}>;
|
|
42
|
+
export type CustomClaim = string | number | boolean | null | CustomClaimRecord | CustomClaimArray | undefined;
|
|
43
|
+
export interface CustomClaimRecord {
|
|
44
|
+
[key: string]: CustomClaim;
|
|
45
|
+
}
|
|
46
|
+
export type CustomClaimArray = CustomClaim[];
|
|
42
47
|
export interface UserProfile {
|
|
43
48
|
sub: string;
|
|
44
49
|
email: string | undefined;
|
|
45
50
|
emailVerified: boolean;
|
|
46
51
|
name: string | undefined;
|
|
47
52
|
pictureUrl: string | undefined;
|
|
48
|
-
[key: string]:
|
|
53
|
+
[key: string]: CustomClaim;
|
|
49
54
|
}
|
|
@@ -1,4 +1,2 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export declare const Catalog: ({ items, filterCatalogItems, label,
|
|
3
|
-
categoryLabel?: string;
|
|
4
|
-
}) => import("react/jsx-runtime").JSX.Element;
|
|
1
|
+
import type { ApiCatalogPluginOptions } from "./index.js";
|
|
2
|
+
export declare const Catalog: ({ items, filterCatalogItems, label, }: Omit<ApiCatalogPluginOptions, "path">) => import("react/jsx-runtime").JSX.Element;
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import type { AuthState } from "../../authentication/state.js";
|
|
2
2
|
import type { ZudokuPlugin } from "../../core/plugins.js";
|
|
3
|
-
export declare const getKey: (category: string, tag: string) => string;
|
|
4
3
|
export type ApiCatalogItem = {
|
|
5
4
|
path: string;
|
|
6
5
|
label: string;
|
|
7
6
|
description: string;
|
|
8
7
|
categories: CatalogCategory[];
|
|
8
|
+
version?: string;
|
|
9
|
+
operationCount?: number;
|
|
9
10
|
};
|
|
10
11
|
export type CatalogCategory = {
|
|
11
12
|
label: string;
|
|
@@ -22,10 +23,4 @@ export type CatalogContext = {
|
|
|
22
23
|
auth: AuthState;
|
|
23
24
|
};
|
|
24
25
|
export type FilterCatalogItemsFn = (items: ApiCatalogItem[], { auth }: CatalogContext) => ApiCatalogItem[];
|
|
25
|
-
export declare const apiCatalogPlugin: ({ path, items, label, categories, filterCatalogItems, }:
|
|
26
|
-
path: string;
|
|
27
|
-
label: string;
|
|
28
|
-
categories?: CatalogCategory[];
|
|
29
|
-
items: ApiCatalogItem[];
|
|
30
|
-
filterCatalogItems?: FilterCatalogItemsFn;
|
|
31
|
-
}) => ZudokuPlugin;
|
|
26
|
+
export declare const apiCatalogPlugin: ({ path, items, label, categories, filterCatalogItems, }: ApiCatalogPluginOptions) => ZudokuPlugin;
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import type { SchemaObject } from "../../../oas/parser/index.js";
|
|
2
|
-
export declare const SchemaView: ({ schema, defaultOpen, cardHeader, embedded, }: {
|
|
2
|
+
export declare const SchemaView: ({ schema, defaultOpen, cardHeader, embedded, hideRootRef, }: {
|
|
3
3
|
schema?: SchemaObject | null;
|
|
4
4
|
defaultOpen?: boolean;
|
|
5
5
|
cardHeader?: React.ReactNode;
|
|
6
6
|
embedded?: boolean;
|
|
7
|
+
hideRootRef?: boolean;
|
|
7
8
|
}) => import("react/jsx-runtime").JSX.Element | undefined;
|
|
@@ -6,4 +6,5 @@ export declare const isCircularRef: (schema: unknown) => schema is string;
|
|
|
6
6
|
export declare const isArrayCircularRef: (schema: SchemaObject) => schema is SchemaObject & {
|
|
7
7
|
items: SchemaObject;
|
|
8
8
|
};
|
|
9
|
+
export declare const getSchemaRefName: (schema?: SchemaObject | null) => string | undefined;
|
|
9
10
|
export declare const extractCircularRefInfo: (ref?: string | SchemaObject) => string | undefined;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as HoverCardPrimitive from "@radix-ui/react-hover-card";
|
|
2
|
-
import * as React from "react";
|
|
3
|
-
declare
|
|
4
|
-
declare
|
|
5
|
-
declare
|
|
6
|
-
export { HoverCard,
|
|
2
|
+
import type * as React from "react";
|
|
3
|
+
declare function HoverCard({ ...props }: React.ComponentProps<typeof HoverCardPrimitive.Root>): import("react/jsx-runtime").JSX.Element;
|
|
4
|
+
declare function HoverCardTrigger({ ...props }: React.ComponentProps<typeof HoverCardPrimitive.Trigger>): import("react/jsx-runtime").JSX.Element;
|
|
5
|
+
declare function HoverCardContent({ className, align, sideOffset, ...props }: React.ComponentProps<typeof HoverCardPrimitive.Content>): import("react/jsx-runtime").JSX.Element;
|
|
6
|
+
export { HoverCard, HoverCardTrigger, HoverCardContent };
|
|
@@ -1,12 +1,8 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { Toggle as TogglePrimitive } from "@base-ui/react/toggle";
|
|
2
2
|
import { type VariantProps } from "class-variance-authority";
|
|
3
|
-
import * as React from "react";
|
|
4
3
|
declare const toggleVariants: (props?: ({
|
|
5
4
|
variant?: "default" | "outline" | null | undefined;
|
|
6
5
|
size?: "default" | "sm" | "lg" | null | undefined;
|
|
7
6
|
} & import("class-variance-authority/types").ClassProp) | undefined) => string;
|
|
8
|
-
declare
|
|
9
|
-
variant?: "default" | "outline" | null | undefined;
|
|
10
|
-
size?: "default" | "sm" | "lg" | null | undefined;
|
|
11
|
-
} & import("class-variance-authority/types").ClassProp) | undefined) => string> & React.RefAttributes<HTMLButtonElement>>;
|
|
7
|
+
declare function Toggle({ className, variant, size, ...props }: TogglePrimitive.Props & VariantProps<typeof toggleVariants>): import("react/jsx-runtime").JSX.Element;
|
|
12
8
|
export { Toggle, toggleVariants };
|
|
@@ -1,12 +1,10 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { ToggleGroup as ToggleGroupPrimitive } from "@base-ui/react/toggle-group";
|
|
2
2
|
import type { VariantProps } from "class-variance-authority";
|
|
3
3
|
import * as React from "react";
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
size?: "default" | "sm" | "lg" | null | undefined;
|
|
11
|
-
} & import("class-variance-authority/types").ClassProp) | undefined) => string> & React.RefAttributes<HTMLButtonElement>>;
|
|
4
|
+
import { Toggle, toggleVariants } from "./Toggle.js";
|
|
5
|
+
declare function ToggleGroup({ className, variant, size, spacing, orientation, children, ...props }: ToggleGroupPrimitive.Props & VariantProps<typeof toggleVariants> & {
|
|
6
|
+
spacing?: number;
|
|
7
|
+
orientation?: "horizontal" | "vertical";
|
|
8
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
9
|
+
declare function ToggleGroupItem({ className, children, variant, size, ...props }: React.ComponentProps<typeof Toggle>): import("react/jsx-runtime").JSX.Element;
|
|
12
10
|
export { ToggleGroup, ToggleGroupItem };
|
|
@@ -30,6 +30,18 @@ const config: ZudokuConfig = {
|
|
|
30
30
|
};
|
|
31
31
|
```
|
|
32
32
|
|
|
33
|
+
When `basePath` is set, Zudoku writes the site into `dist/<basePath>/`. Upload that nested directory
|
|
34
|
+
as your Pages artifact, not `dist/` itself. For example in a GitHub Actions workflow:
|
|
35
|
+
|
|
36
|
+
```yaml
|
|
37
|
+
- uses: actions/upload-pages-artifact@v3
|
|
38
|
+
with:
|
|
39
|
+
path: dist/your-repo
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Uploading the outer `dist/` would nest the site under `/your-repo/your-repo/` and every asset
|
|
43
|
+
would 404.
|
|
44
|
+
|
|
33
45
|
## Accurate Last Modified Dates
|
|
34
46
|
|
|
35
47
|
If you have enabled the [`showLastModified`](/docs/configuration/docs#showlastmodified) option,
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Redirects
|
|
3
|
+
sidebar_icon: arrow-right-left
|
|
4
|
+
description:
|
|
5
|
+
Configure URL redirects in your Zudoku developer portal to set landing pages, maintain backward
|
|
6
|
+
compatibility, and handle URL changes. Covers redirect behavior, basePath interaction, and
|
|
7
|
+
troubleshooting.
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
Redirects let you automatically send visitors from one URL to another in your developer portal. They
|
|
11
|
+
are useful when you need to:
|
|
12
|
+
|
|
13
|
+
- Set a custom landing page for the root path (`/`)
|
|
14
|
+
- Maintain backward compatibility after restructuring documentation
|
|
15
|
+
- Point old API endpoint paths to their new locations
|
|
16
|
+
- Redirect from deprecated pages to their replacements
|
|
17
|
+
|
|
18
|
+
## Basic configuration
|
|
19
|
+
|
|
20
|
+
Add a `redirects` array to your `zudoku.config.ts` file. Each entry has a `from` path and a `to`
|
|
21
|
+
path:
|
|
22
|
+
|
|
23
|
+
```ts title="zudoku.config.ts"
|
|
24
|
+
import type { ZudokuConfig } from "zudoku";
|
|
25
|
+
|
|
26
|
+
const config: ZudokuConfig = {
|
|
27
|
+
// ... other config
|
|
28
|
+
redirects: [
|
|
29
|
+
{ from: "/", to: "/introduction" },
|
|
30
|
+
{ from: "/getting-started", to: "/quickstart" },
|
|
31
|
+
],
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export default config;
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
When a visitor navigates to the `from` path, they are automatically redirected to the `to` path.
|
|
38
|
+
|
|
39
|
+
## Redirect properties
|
|
40
|
+
|
|
41
|
+
Each redirect object accepts two properties:
|
|
42
|
+
|
|
43
|
+
- **`from`** — The path you want to redirect away from. An absolute path starting with `/` is
|
|
44
|
+
recommended. If you omit the leading slash, it is normalized automatically.
|
|
45
|
+
- **`to`** — The destination path where visitors will be sent. This can be any valid path in your
|
|
46
|
+
portal.
|
|
47
|
+
|
|
48
|
+
```ts
|
|
49
|
+
redirects: [{ from: "/old-page", to: "/new-page" }];
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Trailing slashes in the `from` path are normalized automatically. Both `/old-page` and `/old-page/`
|
|
53
|
+
will match the same redirect rule.
|
|
54
|
+
|
|
55
|
+
## How redirects work
|
|
56
|
+
|
|
57
|
+
Zudoku redirects operate at two levels depending on how the visitor reaches the page:
|
|
58
|
+
|
|
59
|
+
### Server-side behavior
|
|
60
|
+
|
|
61
|
+
When a visitor loads a redirect path directly (for example, by typing the URL in the browser address
|
|
62
|
+
bar or following an external link), the exact response depends on your deployment target:
|
|
63
|
+
|
|
64
|
+
- **Zuplo** and **Vercel** (or any platform that reads the Vercel Build Output API): the platform
|
|
65
|
+
returns an HTTP **301 (Moved Permanently)** with a `Location` header, so browsers and search
|
|
66
|
+
engines see a true permanent redirect.
|
|
67
|
+
- **SSR deployments**: the server returns a real HTTP 301 from the router loader.
|
|
68
|
+
- **Other static hosts** (Netlify, Cloudflare Pages, GitHub Pages, S3, nginx, etc.): Zudoku
|
|
69
|
+
prerenders each redirect source path as a small HTML file containing a JavaScript redirect. The
|
|
70
|
+
file is served with a 200 response and the browser follows the redirect once the script runs.
|
|
71
|
+
JavaScript must be enabled for the redirect to fire.
|
|
72
|
+
|
|
73
|
+
### Client-side behavior
|
|
74
|
+
|
|
75
|
+
When a visitor clicks an internal link that points to a redirect path, Zudoku handles the redirect
|
|
76
|
+
entirely in the browser using client-side routing. The visitor is navigated to the destination
|
|
77
|
+
without a full page reload, providing a seamless experience.
|
|
78
|
+
|
|
79
|
+
Both behaviors land the visitor on the destination page. For search engines, only the 301 variants
|
|
80
|
+
signal a permanent move; the JavaScript redirect used on generic static hosts is weaker for SEO.
|
|
81
|
+
|
|
82
|
+
## Common patterns
|
|
83
|
+
|
|
84
|
+
### Setting a landing page
|
|
85
|
+
|
|
86
|
+
The most common use of redirects is setting a landing page for the root path of your portal:
|
|
87
|
+
|
|
88
|
+
```ts
|
|
89
|
+
redirects: [{ from: "/", to: "/docs/introduction" }];
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
This sends visitors who arrive at your portal's root URL to your introduction page.
|
|
93
|
+
|
|
94
|
+
### Reorganizing documentation
|
|
95
|
+
|
|
96
|
+
When you restructure your documentation, add redirects from the old paths so existing bookmarks and
|
|
97
|
+
external links continue to work:
|
|
98
|
+
|
|
99
|
+
```ts
|
|
100
|
+
redirects: [
|
|
101
|
+
{ from: "/api/authentication", to: "/guides/auth-overview" },
|
|
102
|
+
{ from: "/api/getting-started", to: "/docs/quickstart" },
|
|
103
|
+
{ from: "/reference", to: "/api" },
|
|
104
|
+
];
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### Redirecting to specific sections
|
|
108
|
+
|
|
109
|
+
You can redirect to a specific section of a page using a hash fragment in the `to` path:
|
|
110
|
+
|
|
111
|
+
```ts
|
|
112
|
+
redirects: [
|
|
113
|
+
{
|
|
114
|
+
from: "/api-shipments/create-shipment",
|
|
115
|
+
to: "/api-shipments/shipment-management#post-shipments",
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
from: "/api-shipments/get-rates",
|
|
119
|
+
to: "/api-shipments/rates-and-billing#post-shipments-shipmentid-rates",
|
|
120
|
+
},
|
|
121
|
+
];
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
This is useful when multiple old pages have been consolidated into a single page with distinct
|
|
125
|
+
sections.
|
|
126
|
+
|
|
127
|
+
### Redirecting category paths
|
|
128
|
+
|
|
129
|
+
If your API reference groups endpoints into categories, you may want the category path to redirect
|
|
130
|
+
to a specific endpoint or overview page:
|
|
131
|
+
|
|
132
|
+
```ts
|
|
133
|
+
redirects: [
|
|
134
|
+
{ from: "/api/billing", to: "/api/billing/get-invoices" },
|
|
135
|
+
{ from: "/api/users", to: "/api/users/list-users" },
|
|
136
|
+
];
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
## Redirects and the `basePath` option
|
|
140
|
+
|
|
141
|
+
If your portal uses a [`basePath`](/docs/configuration/overview#basepath), redirects are defined
|
|
142
|
+
_relative to the base path_. Zudoku automatically prepends the base path to both the matched `from`
|
|
143
|
+
and the emitted `Location`.
|
|
144
|
+
|
|
145
|
+
For example, with `basePath: "/docs"`:
|
|
146
|
+
|
|
147
|
+
```ts
|
|
148
|
+
redirects: [{ from: "/", to: "/getting-started" }];
|
|
149
|
+
// Visitors to /docs/ are redirected to /docs/getting-started
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
You do not need to include the base path in your `from` or `to` values.
|
|
153
|
+
|
|
154
|
+
## Redirects vs. navigation rules
|
|
155
|
+
|
|
156
|
+
Zudoku offers two features that can change where visitors end up: [redirects](#basic-configuration)
|
|
157
|
+
and [navigation rules](/docs/guides/navigation-rules). They serve different purposes:
|
|
158
|
+
|
|
159
|
+
- **Redirects** map one URL to another. Use them when a page has moved or when you need a specific
|
|
160
|
+
URL to point somewhere else. They affect both direct visits and internal link clicks.
|
|
161
|
+
- **Navigation rules** customize the _sidebar_ generated by plugins like the OpenAPI plugin. Use
|
|
162
|
+
them to insert, reorder, modify, or remove items in the sidebar without changing the underlying
|
|
163
|
+
page URLs.
|
|
164
|
+
|
|
165
|
+
If you want to change where a URL takes visitors, use a redirect. If you want to change what appears
|
|
166
|
+
in the sidebar navigation, use a navigation rule.
|
|
167
|
+
|
|
168
|
+
## Redirects and sitemaps
|
|
169
|
+
|
|
170
|
+
Redirect source paths are automatically excluded from your
|
|
171
|
+
[sitemap](/docs/configuration/overview#sitemap). Only the destination pages appear in the generated
|
|
172
|
+
`sitemap.xml`, which prevents search engines from indexing the old URLs.
|
|
173
|
+
|
|
174
|
+
## Troubleshooting
|
|
175
|
+
|
|
176
|
+
### Redirect returns a 404
|
|
177
|
+
|
|
178
|
+
Make sure the `to` path points to a page that actually exists in your portal. If the destination is
|
|
179
|
+
an API reference page generated from an OpenAPI spec, verify that the path matches the generated
|
|
180
|
+
route. You can check available routes by running your dev server and navigating manually.
|
|
181
|
+
|
|
182
|
+
### Redirect conflicts with another route
|
|
183
|
+
|
|
184
|
+
Redirects are registered before page routes, so when a redirect `from` path exactly matches another
|
|
185
|
+
route, the redirect wins. If you want a page to be reachable at a given path, remove the conflicting
|
|
186
|
+
redirect rather than relying on route priority.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "zudoku",
|
|
3
|
-
"version": "0.75.
|
|
3
|
+
"version": "0.75.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"sideEffects": [
|
|
6
6
|
"**/*.css",
|
|
@@ -145,6 +145,7 @@
|
|
|
145
145
|
},
|
|
146
146
|
"dependencies": {
|
|
147
147
|
"@apidevtools/json-schema-ref-parser": "15.3.1",
|
|
148
|
+
"@base-ui/react": "^1.4.0",
|
|
148
149
|
"@envelop/core": "5.5.1",
|
|
149
150
|
"@graphql-typed-document-node/core": "3.2.0",
|
|
150
151
|
"@hono/node-server": "1.19.13",
|
|
@@ -212,7 +213,7 @@
|
|
|
212
213
|
"hast-util-heading-rank": "3.0.0",
|
|
213
214
|
"hast-util-to-jsx-runtime": "2.3.6",
|
|
214
215
|
"hast-util-to-string": "3.0.1",
|
|
215
|
-
"hono": "4.12.
|
|
216
|
+
"hono": "4.12.14",
|
|
216
217
|
"http-terminator": "3.2.0",
|
|
217
218
|
"javascript-stringify": "2.1.0",
|
|
218
219
|
"json-schema-to-typescript-lite": "15.0.0",
|
|
@@ -227,7 +228,7 @@
|
|
|
227
228
|
"next-themes": "0.4.6",
|
|
228
229
|
"oauth4webapi": "3.8.5",
|
|
229
230
|
"openapi-types": "12.1.3",
|
|
230
|
-
"pagefind": "1.5.
|
|
231
|
+
"pagefind": "1.5.2",
|
|
231
232
|
"picocolors": "1.1.1",
|
|
232
233
|
"piscina": "5.1.4",
|
|
233
234
|
"posthog-node": "5.26.0",
|
|
@@ -264,9 +265,9 @@
|
|
|
264
265
|
"zustand": "5.0.12"
|
|
265
266
|
},
|
|
266
267
|
"devDependencies": {
|
|
267
|
-
"@clerk/clerk-js": "^6.
|
|
268
|
-
"@graphql-codegen/cli": "6.
|
|
269
|
-
"@inkeep/cxkit-types": "0.5.
|
|
268
|
+
"@clerk/clerk-js": "^6.7.4",
|
|
269
|
+
"@graphql-codegen/cli": "6.3.0",
|
|
270
|
+
"@inkeep/cxkit-types": "0.5.117",
|
|
270
271
|
"@testing-library/dom": "10.4.1",
|
|
271
272
|
"@testing-library/jest-dom": "6.9.1",
|
|
272
273
|
"@testing-library/react": "16.3.2",
|
|
@@ -284,7 +285,7 @@
|
|
|
284
285
|
"@types/yargs": "17.0.35",
|
|
285
286
|
"@vitest/coverage-v8": "4.0.18",
|
|
286
287
|
"happy-dom": "20.8.9",
|
|
287
|
-
"oxc-parser": "^0.
|
|
288
|
+
"oxc-parser": "^0.126.0",
|
|
288
289
|
"react": "19.2.4",
|
|
289
290
|
"react-dom": "19.2.4",
|
|
290
291
|
"tsx": "4.21.0",
|
|
@@ -183,6 +183,22 @@ export class OpenIDAuthenticationProvider
|
|
|
183
183
|
});
|
|
184
184
|
}
|
|
185
185
|
|
|
186
|
+
private buildUserProfile(
|
|
187
|
+
userInfo: oauth.UserInfoResponse,
|
|
188
|
+
fallbackEmailVerified: oauth.JsonValue | undefined,
|
|
189
|
+
): UserProfile {
|
|
190
|
+
const emailVerified =
|
|
191
|
+
userInfo.email_verified ?? fallbackEmailVerified ?? false;
|
|
192
|
+
return {
|
|
193
|
+
...userInfo,
|
|
194
|
+
sub: userInfo.sub,
|
|
195
|
+
email: userInfo.email,
|
|
196
|
+
name: userInfo.name,
|
|
197
|
+
emailVerified: Boolean(emailVerified),
|
|
198
|
+
pictureUrl: userInfo.picture,
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
|
|
186
202
|
public async refreshUserProfile(): Promise<boolean> {
|
|
187
203
|
const accessToken = await this.getAccessToken();
|
|
188
204
|
const authServer = await this.getAuthServer();
|
|
@@ -200,13 +216,7 @@ export class OpenIDAuthenticationProvider
|
|
|
200
216
|
? providerData.claims?.email_verified
|
|
201
217
|
: undefined;
|
|
202
218
|
|
|
203
|
-
const profile
|
|
204
|
-
sub: userInfo.sub,
|
|
205
|
-
email: userInfo.email,
|
|
206
|
-
name: userInfo.name,
|
|
207
|
-
emailVerified: userInfo.email_verified ?? emailVerified ?? false,
|
|
208
|
-
pictureUrl: userInfo.picture,
|
|
209
|
-
};
|
|
219
|
+
const profile = this.buildUserProfile(userInfo, emailVerified);
|
|
210
220
|
|
|
211
221
|
useAuthState.setState({
|
|
212
222
|
isAuthenticated: true,
|
|
@@ -494,14 +504,7 @@ export class OpenIDAuthenticationProvider
|
|
|
494
504
|
);
|
|
495
505
|
const userInfo = await userInfoResponse.json();
|
|
496
506
|
|
|
497
|
-
const profile
|
|
498
|
-
...userInfo,
|
|
499
|
-
sub: userInfo.sub,
|
|
500
|
-
email: userInfo.email,
|
|
501
|
-
name: userInfo.name,
|
|
502
|
-
emailVerified: userInfo.email_verified ?? claims?.email_verified ?? false,
|
|
503
|
-
pictureUrl: userInfo.picture,
|
|
504
|
-
};
|
|
507
|
+
const profile = this.buildUserProfile(userInfo, claims?.email_verified);
|
|
505
508
|
|
|
506
509
|
useAuthState.setState({
|
|
507
510
|
isAuthenticated: true,
|
|
@@ -81,11 +81,24 @@ syncZustandState(authState);
|
|
|
81
81
|
|
|
82
82
|
export const useAuthState = authState;
|
|
83
83
|
|
|
84
|
+
export type CustomClaim =
|
|
85
|
+
| string
|
|
86
|
+
| number
|
|
87
|
+
| boolean
|
|
88
|
+
| null
|
|
89
|
+
| CustomClaimRecord
|
|
90
|
+
| CustomClaimArray
|
|
91
|
+
| undefined;
|
|
92
|
+
export interface CustomClaimRecord {
|
|
93
|
+
[key: string]: CustomClaim;
|
|
94
|
+
}
|
|
95
|
+
export type CustomClaimArray = CustomClaim[];
|
|
96
|
+
|
|
84
97
|
export interface UserProfile {
|
|
85
98
|
sub: string;
|
|
86
99
|
email: string | undefined;
|
|
87
100
|
emailVerified: boolean;
|
|
88
101
|
name: string | undefined;
|
|
89
102
|
pictureUrl: string | undefined;
|
|
90
|
-
[key: string]:
|
|
103
|
+
[key: string]: CustomClaim;
|
|
91
104
|
}
|
|
@@ -25,7 +25,16 @@ export const Meta = ({ children }: PropsWithChildren) => {
|
|
|
25
25
|
{meta?.description && (
|
|
26
26
|
<meta name="description" content={meta.description} />
|
|
27
27
|
)}
|
|
28
|
-
{meta?.favicon &&
|
|
28
|
+
{meta?.favicon && (
|
|
29
|
+
<link
|
|
30
|
+
rel="icon"
|
|
31
|
+
href={
|
|
32
|
+
/^https?:\/\//.test(meta.favicon)
|
|
33
|
+
? meta.favicon
|
|
34
|
+
: joinUrl(options.basePath, meta.favicon)
|
|
35
|
+
}
|
|
36
|
+
/>
|
|
37
|
+
)}
|
|
29
38
|
{meta?.generator && <meta name="generator" content={meta.generator} />}
|
|
30
39
|
{meta?.applicationName && (
|
|
31
40
|
<meta name="application-name" content={meta.applicationName} />
|
|
@@ -35,7 +35,17 @@ export const useHotkey = (hotkey: string, callback: () => void) => {
|
|
|
35
35
|
}, [callback]);
|
|
36
36
|
|
|
37
37
|
useEffect(() => {
|
|
38
|
+
const hasModifier = meta || shift || alt || ctrl;
|
|
38
39
|
const handler = (event: KeyboardEvent) => {
|
|
40
|
+
if (
|
|
41
|
+
!hasModifier &&
|
|
42
|
+
event.target instanceof HTMLElement &&
|
|
43
|
+
(event.target.tagName === "INPUT" ||
|
|
44
|
+
event.target.tagName === "TEXTAREA" ||
|
|
45
|
+
event.target.isContentEditable)
|
|
46
|
+
) {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
39
49
|
if (
|
|
40
50
|
(event.code === `Key${key?.toUpperCase()}` ||
|
|
41
51
|
event.code.toLowerCase() === key?.toLowerCase()) &&
|
|
@@ -53,6 +53,17 @@ export const handleCircularRefs = (
|
|
|
53
53
|
Object.entries(obj).map(([k, v]) => [k, recurse(v, k)]),
|
|
54
54
|
);
|
|
55
55
|
|
|
56
|
+
// __$ref is defined non-enumerable at build-time codegen (schema-codegen.ts)
|
|
57
|
+
// to keep it out of snapshots and deep equality. Copy it enumerably onto
|
|
58
|
+
// the serialized result so the client receives it.
|
|
59
|
+
if (
|
|
60
|
+
!Array.isArray(result) &&
|
|
61
|
+
typeof refPath === "string" &&
|
|
62
|
+
!("__$ref" in result)
|
|
63
|
+
) {
|
|
64
|
+
(result as Record<string, unknown>).__$ref = refPath;
|
|
65
|
+
}
|
|
66
|
+
|
|
56
67
|
refs.set(obj, result);
|
|
57
68
|
currentPath.delete(obj);
|
|
58
69
|
if (typeof refPath === "string") currentRefPaths.delete(refPath);
|