zudoku 0.71.4 → 0.71.6

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 CHANGED
@@ -3794,7 +3794,7 @@ import {
3794
3794
  // package.json
3795
3795
  var package_default = {
3796
3796
  name: "zudoku",
3797
- version: "0.71.3",
3797
+ version: "0.71.5",
3798
3798
  type: "module",
3799
3799
  sideEffects: [
3800
3800
  "**/*.css",
@@ -5263,25 +5263,28 @@ var flattenAllOfProcessor = async ({ schema: schema2, file }) => {
5263
5263
  const parser = new $RefParser();
5264
5264
  await parser.resolve(schema2);
5265
5265
  const $refs = parser.$refs;
5266
- const flattened = traverse(schema2, (spec) => {
5266
+ const resolved = traverse(schema2, (spec) => {
5267
+ if (!spec || typeof spec !== "object" || Array.isArray(spec) || !("allOf" in spec) || !Array.isArray(spec.allOf)) {
5268
+ return spec;
5269
+ }
5270
+ const resolvedAllOf = spec.allOf.map((item) => {
5271
+ if (item && typeof item === "object" && "$ref" in item && typeof item.$ref === "string") {
5272
+ try {
5273
+ return $refs.get(item.$ref) ?? item;
5274
+ } catch {
5275
+ return item;
5276
+ }
5277
+ }
5278
+ return item;
5279
+ });
5280
+ return { ...spec, allOf: resolvedAllOf };
5281
+ });
5282
+ const flattened = traverse(resolved, (spec) => {
5267
5283
  if (!spec || typeof spec !== "object" || Array.isArray(spec)) {
5268
5284
  return spec;
5269
5285
  }
5270
5286
  const isSchemaObject = "type" in spec || "properties" in spec || "allOf" in spec || "anyOf" in spec || "oneOf" in spec;
5271
5287
  if (!isSchemaObject) return spec;
5272
- if ("allOf" in spec && Array.isArray(spec.allOf)) {
5273
- const resolvedAllOf = spec.allOf.map((item) => {
5274
- if (item && typeof item === "object" && "$ref" in item && typeof item.$ref === "string") {
5275
- try {
5276
- return $refs.get(item.$ref) ?? item;
5277
- } catch {
5278
- return item;
5279
- }
5280
- }
5281
- return item;
5282
- });
5283
- return flattenAllOf({ ...spec, allOf: resolvedAllOf });
5284
- }
5285
5288
  return flattenAllOf(spec);
5286
5289
  });
5287
5290
  return flattened;
@@ -0,0 +1,3 @@
1
+ import { type RouteObject } from "react-router";
2
+ import type { ZudokuRedirect } from "../../config/validators/validate.js";
3
+ export declare const createRedirectRoutes: (redirects?: ZudokuRedirect[]) => RouteObject[];
@@ -10,4 +10,5 @@ export type { MDXImport } from "./lib/plugins/markdown/index.js";
10
10
  export { defaultLanguages } from "./lib/shiki.js";
11
11
  export { cn } from "./lib/ui/util.js";
12
12
  export { joinUrl } from "./lib/util/joinUrl.js";
13
+ export { type ProblemJson, throwIfProblemJson, } from "./lib/util/problemJson.js";
13
14
  export type { MdxComponentsType } from "./lib/util/MdxComponents.js";
@@ -1,4 +1,5 @@
1
1
  import { type QueryKey } from "@tanstack/react-query";
2
+ import type { ZudokuRedirect } from "../../config/validators/validate.js";
2
3
  import type { ZudokuContextOptions } from "../core/ZudokuContext.js";
3
4
  type QueryData = {
4
5
  queryKey: QueryKey;
@@ -9,6 +10,7 @@ type StaticZudokuProps = ZudokuContextOptions & {
9
10
  queryData?: QueryData[];
10
11
  env?: Record<string, string>;
11
12
  isAuthenticated?: boolean;
13
+ redirects?: ZudokuRedirect[];
12
14
  };
13
- declare const StaticZudoku: ({ path, queryData, env, isAuthenticated, ...options }: StaticZudokuProps) => import("react/jsx-runtime").JSX.Element;
15
+ declare const StaticZudoku: ({ path, queryData, env, isAuthenticated, redirects, ...options }: StaticZudokuProps) => import("react/jsx-runtime").JSX.Element;
14
16
  export { StaticZudoku, type StaticZudokuProps, type QueryData };
@@ -0,0 +1,10 @@
1
+ export type ProblemJson = {
2
+ type: string;
3
+ title?: string;
4
+ status?: number;
5
+ detail?: string;
6
+ instance?: string;
7
+ [extension: string]: unknown;
8
+ };
9
+ export declare const getProblemJson: (response: Response) => Promise<ProblemJson | undefined>;
10
+ export declare const throwIfProblemJson: (response: Response) => Promise<void>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zudoku",
3
- "version": "0.71.4",
3
+ "version": "0.71.6",
4
4
  "type": "module",
5
5
  "sideEffects": [
6
6
  "**/*.css",
package/src/app/main.tsx CHANGED
@@ -29,7 +29,7 @@ import type { ZudokuContextOptions } from "../lib/core/ZudokuContext.js";
29
29
  import { RouterError } from "../lib/errors/RouterError.js";
30
30
  import { ZuploEnv } from "./env.js";
31
31
  import { processRoutes } from "./processRoutes.js";
32
- import { createRedirectLoader } from "./utils/createRedirectLoader.js";
32
+ import { createRedirectRoutes } from "./utils/createRedirectRoutes.js";
33
33
 
34
34
  export const shikiReady: Promise<HighlighterCore> =
35
35
  import("../lib/shiki.js").then(async ({ highlighterPromise }) => {
@@ -121,6 +121,7 @@ export const getRoutesByConfig = (config: ZudokuConfig): RouteObject[] => {
121
121
  );
122
122
 
123
123
  return [
124
+ ...createRedirectRoutes(config.redirects),
124
125
  {
125
126
  element: (
126
127
  <Zudoku {...options} env={import.meta.env}>
@@ -132,7 +133,6 @@ export const getRoutesByConfig = (config: ZudokuConfig): RouteObject[] => {
132
133
  </Zudoku>
133
134
  ),
134
135
  hydrateFallbackElement: <div>Loading...</div>,
135
- loader: createRedirectLoader(config.redirects, config.basePath),
136
136
  children: [
137
137
  {
138
138
  element: (
@@ -0,0 +1,11 @@
1
+ import { redirect, type RouteObject } from "react-router";
2
+ import type { ZudokuRedirect } from "../../config/validators/validate.js";
3
+ import { joinUrl } from "../../lib/util/joinUrl.js";
4
+
5
+ export const createRedirectRoutes = (
6
+ redirects?: ZudokuRedirect[],
7
+ ): RouteObject[] =>
8
+ (redirects ?? []).map((r) => ({
9
+ path: joinUrl(r.from),
10
+ loader: () => redirect(r.to, 301),
11
+ }));
package/src/index.ts CHANGED
@@ -26,4 +26,8 @@ export type { MDXImport } from "./lib/plugins/markdown/index.js";
26
26
  export { defaultLanguages } from "./lib/shiki.js";
27
27
  export { cn } from "./lib/ui/util.js";
28
28
  export { joinUrl } from "./lib/util/joinUrl.js";
29
+ export {
30
+ type ProblemJson,
31
+ throwIfProblemJson,
32
+ } from "./lib/util/problemJson.js";
29
33
  export type { MdxComponentsType } from "./lib/util/MdxComponents.js";
@@ -10,6 +10,7 @@ import type {
10
10
  import type { ZudokuContext } from "../../core/ZudokuContext.js";
11
11
  import invariant from "../../util/invariant.js";
12
12
  import { joinUrl } from "../../util/joinUrl.js";
13
+ import { throwIfProblemJson } from "../../util/problemJson.js";
13
14
  import { SettingsApiKeys } from "./SettingsApiKeys.js";
14
15
 
15
16
  const DEFAULT_GATEWAY_URL = "https://api.zuploedge.com";
@@ -70,24 +71,6 @@ export interface ApiConsumer {
70
71
  key?: ApiKey;
71
72
  }
72
73
 
73
- const parseJsonSafe = async (response: Response) => {
74
- try {
75
- return await response.json();
76
- } catch {
77
- return;
78
- }
79
- };
80
-
81
- const throwIfProblemJson = async (response: Response) => {
82
- const contentType = response.headers.get("content-type");
83
- if (!response.ok && contentType?.includes("application/problem+json")) {
84
- const data = await parseJsonSafe(response);
85
- if (data.type && data.title) {
86
- throw new Error(data.detail ?? data.title);
87
- }
88
- }
89
- };
90
-
91
74
  const developerHintOptions = {
92
75
  developerHint:
93
76
  "This project is not linked to a Zuplo deployment. Run `zuplo link` to get started with API Keys.",
@@ -260,7 +260,11 @@ export const SchemaInfo = () => {
260
260
  {tag.description && (
261
261
  <ItemDescription asChild>
262
262
  <Markdown
263
- components={{ p: ({ children }) => children }}
263
+ components={{
264
+ // Because the description is wrapped in a <p> and a <Link> already
265
+ p: ({ children }) => children,
266
+ a: (props) => <span {...props} />,
267
+ }}
264
268
  content={tag.description}
265
269
  className="prose-sm text-pretty"
266
270
  />
@@ -5,6 +5,8 @@ import {
5
5
  } from "@tanstack/react-query";
6
6
  import { HelmetProvider } from "@zudoku/react-helmet-async";
7
7
  import { createMemoryRouter, Outlet, RouterProvider } from "react-router";
8
+ import { createRedirectRoutes } from "../../app/utils/createRedirectRoutes.js";
9
+ import type { ZudokuRedirect } from "../../config/validators/validate.js";
8
10
  import type { AuthenticationPlugin } from "../authentication/authentication.js";
9
11
  import { useAuthState } from "../authentication/state.js";
10
12
  import { RenderContext } from "../components/context/RenderContext.js";
@@ -26,6 +28,7 @@ type StaticZudokuProps = ZudokuContextOptions & {
26
28
  queryData?: QueryData[];
27
29
  env?: Record<string, string>;
28
30
  isAuthenticated?: boolean;
31
+ redirects?: ZudokuRedirect[];
29
32
  };
30
33
 
31
34
  const getRoutesByOptions = (options: ZudokuContextOptions) => {
@@ -52,6 +55,7 @@ const StaticZudoku = ({
52
55
  queryData,
53
56
  env = {},
54
57
  isAuthenticated,
58
+ redirects,
55
59
  ...options
56
60
  }: StaticZudokuProps) => {
57
61
  if (isAuthenticated) {
@@ -93,6 +97,7 @@ const StaticZudoku = ({
93
97
  const routes = getRoutesByOptions(options);
94
98
  const router = createMemoryRouter(
95
99
  [
100
+ ...createRedirectRoutes(redirects),
96
101
  {
97
102
  element: (
98
103
  <Zudoku {...options} env={env}>
@@ -11,7 +11,42 @@ export const flattenAllOfProcessor: Processor = async ({ schema, file }) => {
11
11
  await parser.resolve(schema);
12
12
  const $refs = parser.$refs;
13
13
 
14
- const flattened = traverse(schema, (spec) => {
14
+ // Pre-resolve all $refs inside allOf items throughout the schema before
15
+ // flattening. flattenAllOf recurses internally into oneOf/anyOf/properties
16
+ // without going through $ref resolution, so we must ensure all allOf items
17
+ // are resolved upfront. This prevents stale $ref keys from being merged
18
+ // into parent schemas when they point to paths removed during flattening.
19
+ const resolved = traverse(schema, (spec) => {
20
+ if (
21
+ !spec ||
22
+ typeof spec !== "object" ||
23
+ Array.isArray(spec) ||
24
+ !("allOf" in spec) ||
25
+ !Array.isArray(spec.allOf)
26
+ ) {
27
+ return spec;
28
+ }
29
+
30
+ const resolvedAllOf = spec.allOf.map((item) => {
31
+ if (
32
+ item &&
33
+ typeof item === "object" &&
34
+ "$ref" in item &&
35
+ typeof item.$ref === "string"
36
+ ) {
37
+ try {
38
+ return $refs.get(item.$ref) ?? item;
39
+ } catch {
40
+ return item;
41
+ }
42
+ }
43
+ return item;
44
+ });
45
+
46
+ return { ...spec, allOf: resolvedAllOf };
47
+ }) as OpenAPIDocument;
48
+
49
+ const flattened = traverse(resolved, (spec) => {
15
50
  if (!spec || typeof spec !== "object" || Array.isArray(spec)) {
16
51
  return spec;
17
52
  }
@@ -25,25 +60,6 @@ export const flattenAllOfProcessor: Processor = async ({ schema, file }) => {
25
60
 
26
61
  if (!isSchemaObject) return spec;
27
62
 
28
- if ("allOf" in spec && Array.isArray(spec.allOf)) {
29
- const resolvedAllOf = spec.allOf.map((item) => {
30
- if (
31
- item &&
32
- typeof item === "object" &&
33
- "$ref" in item &&
34
- typeof item.$ref === "string"
35
- ) {
36
- try {
37
- return $refs.get(item.$ref) ?? item;
38
- } catch {
39
- return item;
40
- }
41
- }
42
- return item;
43
- });
44
- return flattenAllOf({ ...spec, allOf: resolvedAllOf }) as RecordAny;
45
- }
46
-
47
63
  return flattenAllOf(spec) as RecordAny;
48
64
  }) as OpenAPIDocument;
49
65
 
@@ -1,4 +0,0 @@
1
- import type { ZudokuRedirect } from "../../config/validators/validate.js";
2
- export declare const createRedirectLoader: (redirects?: ZudokuRedirect[], basePath?: string) => (({ request }: {
3
- request: Request;
4
- }) => Response | null) | undefined;
@@ -1,21 +0,0 @@
1
- import { redirect } from "react-router";
2
- import type { ZudokuRedirect } from "../../config/validators/validate.js";
3
- import { joinUrl } from "../../lib/util/joinUrl.js";
4
-
5
- export const createRedirectLoader = (
6
- redirects?: ZudokuRedirect[],
7
- basePath?: string,
8
- ) => {
9
- if (!redirects) return undefined;
10
-
11
- const prefix = basePath ? joinUrl(basePath) : "";
12
- const map = new Map(redirects.map((r) => [joinUrl(r.from), r.to]));
13
- return ({ request }: { request: Request }) => {
14
- let pathname = joinUrl(new URL(request.url).pathname);
15
- if (prefix && pathname.startsWith(prefix)) {
16
- pathname = pathname.slice(prefix.length) || "/";
17
- }
18
- const to = map.get(joinUrl(pathname));
19
- return to ? redirect(to, 301) : null;
20
- };
21
- };