zudoku 0.82.2 → 0.82.4

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 (39) hide show
  1. package/dist/cli/cli.js +283 -225
  2. package/dist/cli/worker.js +23 -0
  3. package/dist/declarations/app/wrapProtectedRoutes.d.ts +5 -0
  4. package/dist/declarations/config/config.d.ts +3 -0
  5. package/dist/declarations/config/plugin-versions.d.ts +5 -0
  6. package/dist/declarations/config/validators/ZudokuConfig.d.ts +38 -0
  7. package/dist/declarations/lib/authentication/providers/entra.d.ts +13 -0
  8. package/dist/declarations/lib/authentication/providers/openid.d.ts +4 -1
  9. package/dist/declarations/lib/authentication/providers/util.d.ts +4 -0
  10. package/dist/declarations/lib/plugins/openapi/McpClientLogos.d.ts +4 -0
  11. package/dist/declarations/lib/plugins/openapi/mcp-configs.d.ts +3 -0
  12. package/dist/declarations/vite/package-root.d.ts +1 -0
  13. package/dist/flat-config.d.ts +17 -0
  14. package/docs/components/landing-page.mdx +1 -1
  15. package/docs/configuration/authentication-azure-ad.md +17 -15
  16. package/docs/configuration/authentication.md +22 -2
  17. package/docs/guides/mcp-servers.md +26 -0
  18. package/package.json +21 -17
  19. package/src/app/entry.server.tsx +9 -5
  20. package/src/app/wrapProtectedRoutes.ts +25 -0
  21. package/src/config/config.ts +4 -0
  22. package/src/config/loader.ts +15 -0
  23. package/src/config/plugin-versions.ts +38 -0
  24. package/src/config/validators/ZudokuConfig.ts +19 -0
  25. package/src/lib/auth/issuer.ts +7 -1
  26. package/src/lib/authentication/providers/entra.tsx +97 -0
  27. package/src/lib/authentication/providers/openid.tsx +29 -13
  28. package/src/lib/authentication/providers/util.ts +7 -0
  29. package/src/lib/components/navigation/NavigationCategory.tsx +3 -2
  30. package/src/lib/components/navigation/StackRows.tsx +1 -1
  31. package/src/lib/plugins/openapi/MCPEndpoint.tsx +347 -228
  32. package/src/lib/plugins/openapi/McpClientLogos.tsx +72 -0
  33. package/src/lib/plugins/openapi/mcp-configs.ts +55 -0
  34. package/src/vite/api/SchemaManager.ts +11 -25
  35. package/src/vite/plugin-mdx.ts +3 -0
  36. package/src/vite/prerender/prerender.ts +28 -3
  37. package/src/vite/prerender/utils.ts +33 -0
  38. package/src/vite/prerender/worker.ts +8 -1
  39. package/src/zuplo/enrich-with-zuplo-mcp.ts +14 -2
@@ -2617,6 +2617,25 @@ var AuthenticationSchema = z4.discriminatedUnion("type", [
2617
2617
  authorizationParams: z4.record(z4.string(), z4.string()).optional(),
2618
2618
  forwardAuthorizationParams: z4.array(z4.string()).optional()
2619
2619
  }),
2620
+ z4.object({
2621
+ type: z4.literal("entra"),
2622
+ basePath: z4.string().optional(),
2623
+ clientId: z4.string(),
2624
+ // Tenant id or one of Entra's multi-tenant authorities
2625
+ // (common/organizations/consumers). Defaults to "common".
2626
+ tenantId: z4.string().optional(),
2627
+ // Full issuer override, e.g. for CIAM tenants on *.ciamlogin.com.
2628
+ issuer: z4.string().optional(),
2629
+ audience: z4.string().optional(),
2630
+ scopes: z4.array(z4.string()).optional(),
2631
+ redirectToAfterSignUp: z4.string().optional(),
2632
+ redirectToAfterSignIn: z4.string().optional(),
2633
+ redirectToAfterSignOut: z4.string().optional(),
2634
+ signUp: SignUpOpenIdSchema.optional(),
2635
+ disableSignUp: z4.boolean().optional(),
2636
+ authorizationParams: z4.record(z4.string(), z4.string()).optional(),
2637
+ forwardAuthorizationParams: z4.array(z4.string()).optional()
2638
+ }),
2620
2639
  z4.object({
2621
2640
  type: z4.literal("azureb2c"),
2622
2641
  basePath: z4.string().optional(),
@@ -2921,6 +2940,7 @@ var renderPage = async ({ urlPath }) => {
2921
2940
  outputPath,
2922
2941
  redirect: { from: pathname, to: redirectTo },
2923
2942
  statusCode: response.status,
2943
+ indexStatusCode: response.status,
2924
2944
  html: ""
2925
2945
  };
2926
2946
  }
@@ -2931,6 +2951,7 @@ var renderPage = async ({ urlPath }) => {
2931
2951
  }
2932
2952
  const fileContent = response.body ? await response.text() : "";
2933
2953
  let html = fileContent;
2954
+ let indexStatusCode = response.status;
2934
2955
  if (isProtectedRoute) {
2935
2956
  const bypassRequest = new Request(url);
2936
2957
  const bypassResponse = await server.handleRequest({
@@ -2946,12 +2967,14 @@ var renderPage = async ({ urlPath }) => {
2946
2967
  );
2947
2968
  }
2948
2969
  html = bypassResponse.body ? await bypassResponse.text() : "";
2970
+ indexStatusCode = bypassResponse.status;
2949
2971
  }
2950
2972
  await fs.mkdir(path.dirname(outputPath), { recursive: true });
2951
2973
  await fs.writeFile(outputPath, fileContent);
2952
2974
  return {
2953
2975
  outputPath,
2954
2976
  statusCode: response.status,
2977
+ indexStatusCode,
2955
2978
  redirect: void 0,
2956
2979
  html
2957
2980
  };
@@ -1,4 +1,9 @@
1
1
  import type { RouteObject } from "react-router";
2
2
  import type { ProtectedRoutesInput } from "../config/validators/ProtectedRoutesSchema.js";
3
3
  export declare const wrapProtectedRoutes: (routes: RouteObject[], protectedRoutes: ProtectedRoutesInput, isAuthenticated: boolean, basePath?: string) => RouteObject[];
4
+ export declare const wrapProtectedRoutesForRender: (routes: RouteObject[], protectedRoutes: ProtectedRoutesInput, { isAuthenticated, bypassProtection, basePath, }: {
5
+ isAuthenticated: boolean;
6
+ bypassProtection?: boolean;
7
+ basePath?: string;
8
+ }) => RouteObject[];
4
9
  export declare const warnInlineProtectedRoutes: (routes: RouteObject[], protectedRoutes: ProtectedRoutesInput, basePath?: string) => void;
@@ -22,3 +22,6 @@ export type FirebaseAuthenticationConfig = Extract<AuthenticationConfig, {
22
22
  export type AzureB2CAuthenticationConfig = Extract<AuthenticationConfig, {
23
23
  type: "azureb2c";
24
24
  }>;
25
+ export type EntraAuthenticationConfig = Extract<AuthenticationConfig, {
26
+ type: "entra";
27
+ }>;
@@ -0,0 +1,5 @@
1
+ export type PluginVersion = {
2
+ name: string;
3
+ version: string;
4
+ };
5
+ export declare const getPluginVersions: (pluginDirs: readonly string[]) => Promise<PluginVersion[]>;
@@ -248,6 +248,25 @@ declare const AuthenticationSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
248
248
  disableSignUp: z.ZodOptional<z.ZodBoolean>;
249
249
  authorizationParams: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
250
250
  forwardAuthorizationParams: z.ZodOptional<z.ZodArray<z.ZodString>>;
251
+ }, z.core.$strip>, z.ZodObject<{
252
+ type: z.ZodLiteral<"entra">;
253
+ basePath: z.ZodOptional<z.ZodString>;
254
+ clientId: z.ZodString;
255
+ tenantId: z.ZodOptional<z.ZodString>;
256
+ issuer: z.ZodOptional<z.ZodString>;
257
+ audience: z.ZodOptional<z.ZodString>;
258
+ scopes: z.ZodOptional<z.ZodArray<z.ZodString>>;
259
+ redirectToAfterSignUp: z.ZodOptional<z.ZodString>;
260
+ redirectToAfterSignIn: z.ZodOptional<z.ZodString>;
261
+ redirectToAfterSignOut: z.ZodOptional<z.ZodString>;
262
+ signUp: z.ZodOptional<z.ZodUnion<readonly [z.ZodObject<{
263
+ url: z.ZodString;
264
+ }, z.core.$strict>, z.ZodObject<{
265
+ authorizationParams: z.ZodRecord<z.ZodString, z.ZodString>;
266
+ }, z.core.$strict>]>>;
267
+ disableSignUp: z.ZodOptional<z.ZodBoolean>;
268
+ authorizationParams: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
269
+ forwardAuthorizationParams: z.ZodOptional<z.ZodArray<z.ZodString>>;
251
270
  }, z.core.$strip>, z.ZodObject<{
252
271
  type: z.ZodLiteral<"azureb2c">;
253
272
  basePath: z.ZodOptional<z.ZodString>;
@@ -7040,6 +7059,25 @@ export declare const ZudokuConfig: z.ZodObject<{
7040
7059
  disableSignUp: z.ZodOptional<z.ZodBoolean>;
7041
7060
  authorizationParams: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
7042
7061
  forwardAuthorizationParams: z.ZodOptional<z.ZodArray<z.ZodString>>;
7062
+ }, z.core.$strip>, z.ZodObject<{
7063
+ type: z.ZodLiteral<"entra">;
7064
+ basePath: z.ZodOptional<z.ZodString>;
7065
+ clientId: z.ZodString;
7066
+ tenantId: z.ZodOptional<z.ZodString>;
7067
+ issuer: z.ZodOptional<z.ZodString>;
7068
+ audience: z.ZodOptional<z.ZodString>;
7069
+ scopes: z.ZodOptional<z.ZodArray<z.ZodString>>;
7070
+ redirectToAfterSignUp: z.ZodOptional<z.ZodString>;
7071
+ redirectToAfterSignIn: z.ZodOptional<z.ZodString>;
7072
+ redirectToAfterSignOut: z.ZodOptional<z.ZodString>;
7073
+ signUp: z.ZodOptional<z.ZodUnion<readonly [z.ZodObject<{
7074
+ url: z.ZodString;
7075
+ }, z.core.$strict>, z.ZodObject<{
7076
+ authorizationParams: z.ZodRecord<z.ZodString, z.ZodString>;
7077
+ }, z.core.$strict>]>>;
7078
+ disableSignUp: z.ZodOptional<z.ZodBoolean>;
7079
+ authorizationParams: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
7080
+ forwardAuthorizationParams: z.ZodOptional<z.ZodArray<z.ZodString>>;
7043
7081
  }, z.core.$strip>, z.ZodObject<{
7044
7082
  type: z.ZodLiteral<"azureb2c">;
7045
7083
  basePath: z.ZodOptional<z.ZodString>;
@@ -0,0 +1,13 @@
1
+ import type * as oauth from "oauth4webapi";
2
+ import type { EntraAuthenticationConfig } from "../../../config/config.js";
3
+ import type { AuthenticationPlugin, AuthenticationProviderInitializer } from "../authentication.js";
4
+ import type { UserProfile } from "../state.js";
5
+ import { OpenIDAuthenticationProvider } from "./openid.js";
6
+ export declare class EntraAuthenticationProvider extends OpenIDAuthenticationProvider implements AuthenticationPlugin {
7
+ constructor(config: EntraAuthenticationConfig);
8
+ protected getExpectedDiscoveryIssuer(issuerUrl: URL, response: Response): Promise<URL>;
9
+ protected resolveTokenIssuer(as: oauth.AuthorizationServer, response: Response): Promise<oauth.AuthorizationServer>;
10
+ protected buildUserProfile(userInfo: oauth.UserInfoResponse, claims: oauth.IDToken | undefined): UserProfile;
11
+ }
12
+ declare const entraAuth: AuthenticationProviderInitializer<EntraAuthenticationConfig>;
13
+ export default entraAuth;
@@ -3,6 +3,7 @@ import type { NavigateFunction } from "react-router";
3
3
  import type { OpenIDAuthenticationConfig } from "../../../config/config.js";
4
4
  import type { AuthActionContext, AuthActionOptions, AuthenticationPlugin, AuthenticationProviderInitializer, VerifyAccessTokenResult } from "../authentication.js";
5
5
  import { CoreAuthenticationPlugin } from "../AuthenticationPlugin.js";
6
+ import { type UserProfile } from "../state.js";
6
7
  export interface OpenIdProviderData {
7
8
  type: "openid" | undefined;
8
9
  accessToken: string;
@@ -48,6 +49,8 @@ export declare class OpenIDAuthenticationProvider extends CoreAuthenticationPlug
48
49
  [oauth.allowInsecureRequests]?: undefined;
49
50
  };
50
51
  protected getAuthServer(): Promise<oauth.AuthorizationServer>;
52
+ protected getExpectedDiscoveryIssuer(issuerUrl: URL, _response: Response): Promise<URL>;
53
+ protected resolveTokenIssuer(as: oauth.AuthorizationServer, _response: Response): Promise<oauth.AuthorizationServer>;
51
54
  protected setTokensFromResponse(response: oauth.TokenEndpointResponse): void;
52
55
  signUp({ navigate }: {
53
56
  navigate: NavigateFunction;
@@ -56,7 +59,7 @@ export declare class OpenIDAuthenticationProvider extends CoreAuthenticationPlug
56
59
  replace?: boolean;
57
60
  }): Promise<void>;
58
61
  signIn(_: AuthActionContext, { redirectTo, replace }: AuthActionOptions): Promise<void>;
59
- private buildUserProfile;
62
+ protected buildUserProfile(userInfo: oauth.UserInfoResponse, claims: oauth.IDToken | undefined): UserProfile;
60
63
  verifyAccessToken(token: string): Promise<VerifyAccessTokenResult>;
61
64
  refreshUserProfile(): Promise<boolean>;
62
65
  private authorize;
@@ -1,3 +1,7 @@
1
1
  import type { NavigateFunction } from "react-router";
2
2
  export declare const redirectToSignUpUrl: (url: string, navigate: NavigateFunction, replace?: boolean) => void;
3
+ export declare const getEntraIssuer: (config: {
4
+ issuer?: string;
5
+ tenantId?: string;
6
+ }) => string;
3
7
  export declare const getClerkFrontendApi: (publishableKey: string) => string;
@@ -0,0 +1,4 @@
1
+ export declare const McpClientLogo: ({ appId, className, }: {
2
+ appId: string;
3
+ className?: string;
4
+ }) => import("react").JSX.Element;
@@ -26,3 +26,6 @@ export declare const getCursorConfig: (name: string, mcpUrl: string, auth?: Auth
26
26
  export declare const getVscodeConfig: (name: string, mcpUrl: string, auth?: AuthHeader) => string;
27
27
  export declare const getCodexConfig: (name: string, mcpUrl: string, auth?: AuthHeader) => string;
28
28
  export declare const getGenericConfig: (name: string, mcpUrl: string, auth?: AuthHeader) => string;
29
+ export declare const getCursorDeepLink: (name: string, mcpUrl: string, auth?: AuthHeader) => string;
30
+ export declare const getVscodeDeepLink: (name: string, mcpUrl: string, auth?: AuthHeader) => string;
31
+ export declare const CLAUDE_CONNECTORS_URL = "https://claude.ai/customize/connectors?modal=add-custom-connector";
@@ -0,0 +1 @@
1
+ export declare const findPackageRoot: (startDir: string) => Promise<string | undefined>;
@@ -303,6 +303,23 @@ export interface FlatZudokuConfig {
303
303
  [k: string]: string
304
304
  }
305
305
  forwardAuthorizationParams?: string[]
306
+ } | {
307
+ type: "entra"
308
+ basePath?: string
309
+ clientId: string
310
+ tenantId?: string
311
+ issuer?: string
312
+ audience?: string
313
+ scopes?: string[]
314
+ redirectToAfterSignUp?: string
315
+ redirectToAfterSignIn?: string
316
+ redirectToAfterSignOut?: string
317
+ signUp?: _Schema13
318
+ disableSignUp?: boolean
319
+ authorizationParams?: {
320
+ [k: string]: string
321
+ }
322
+ forwardAuthorizationParams?: string[]
306
323
  } | {
307
324
  type: "azureb2c"
308
325
  basePath?: string
@@ -10,7 +10,7 @@ A ready-made, customizable landing page for your developer portal. It comes in t
10
10
  covering the most common layouts: a centered hero, a two-column split hero, and a compact
11
11
  documentation hub.
12
12
 
13
- Use it as the `element` of a [custom page](/docs/custom-pages) — typically your home page:
13
+ Use it as the `element` of a [custom page](/docs/guides/custom-pages) — typically your home page:
14
14
 
15
15
  ```tsx title=zudoku.config.tsx
16
16
  import { LandingPage } from "zudoku/components";
@@ -72,9 +72,9 @@ to integrate Azure AD with your Zudoku documentation site.
72
72
  export default {
73
73
  // ... other configuration
74
74
  authentication: {
75
- type: "openid",
75
+ type: "entra",
76
76
  clientId: "<your-application-client-id>",
77
- issuer: "https://login.microsoftonline.com/<your-tenant-id>/v2.0",
77
+ tenantId: "<your-tenant-id>",
78
78
  scopes: ["openid", "profile", "email"], // Optional: customize scopes
79
79
  },
80
80
  // ... other configuration
@@ -91,20 +91,21 @@ For single tenant (organization-only access):
91
91
 
92
92
  ```typescript
93
93
  authentication: {
94
- type: "openid",
94
+ type: "entra",
95
95
  clientId: "<your-application-client-id>",
96
- issuer: "https://login.microsoftonline.com/<your-tenant-id>/v2.0",
96
+ tenantId: "<your-tenant-id>",
97
97
  scopes: ["openid", "profile", "email"],
98
98
  }
99
99
  ```
100
100
 
101
- For multitenant (any Azure AD organization):
101
+ For multitenant (any Azure AD organization), use `tenantId: "common"` or `"organizations"` — this is
102
+ also the default if `tenantId` is omitted:
102
103
 
103
104
  ```typescript
104
105
  authentication: {
105
- type: "openid",
106
+ type: "entra",
106
107
  clientId: "<your-application-client-id>",
107
- issuer: "https://login.microsoftonline.com/common/v2.0",
108
+ tenantId: "common",
108
109
  scopes: ["openid", "profile", "email"],
109
110
  }
110
111
  ```
@@ -115,9 +116,9 @@ Request additional Microsoft Graph API scopes:
115
116
 
116
117
  ```typescript
117
118
  authentication: {
118
- type: "openid",
119
+ type: "entra",
119
120
  clientId: "<your-application-client-id>",
120
- issuer: "https://login.microsoftonline.com/<your-tenant-id>/v2.0",
121
+ tenantId: "<your-tenant-id>",
121
122
  scopes: [
122
123
  "openid",
123
124
  "profile",
@@ -136,7 +137,7 @@ Protect specific documentation routes using the `protectedRoutes` configuration:
136
137
  {
137
138
  // ... other configuration
138
139
  authentication: {
139
- type: "openid",
140
+ type: "entra",
140
141
  // ... Azure AD config
141
142
  },
142
143
  protectedRoutes: [
@@ -217,16 +218,17 @@ Azure AD provides rich user profile data through OpenID Connect:
217
218
  2. **Redirect URI Mismatch**: The redirect URI must exactly match one configured in Azure AD,
218
219
  including protocol and path.
219
220
 
220
- 3. **Tenant Access Issues**: For single-tenant apps, ensure users are from the correct tenant. For
221
- multi-tenant, verify the issuer URL uses "common" or "organizations".
221
+ 3. **Tenant Access Issues**: For single-tenant apps, ensure users are from the correct tenant and
222
+ `tenantId` is set to that tenant's GUID. For multi-tenant, use `tenantId: "common"` or
223
+ `"organizations"`.
222
224
 
223
225
  4. **Missing User Information**: Check that required API permissions are granted and admin consent
224
226
  is provided if needed.
225
227
 
226
- 5. **Token Validation Errors**: Ensure your issuer URL is correct and includes the `/v2.0` endpoint
227
- for the Microsoft identity platform.
228
+ 5. **Token Validation Errors**: Ensure your `tenantId` is correct. If you use a CIAM tenant
229
+ (`*.ciamlogin.com`), set the `issuer` option to override the default issuer instead.
228
230
 
229
- 6. **Authentication Not Working**: Verify your issuer URL and client ID are correct, and that your
231
+ 6. **Authentication Not Working**: Verify your `tenantId` and client ID are correct, and that your
230
232
  app registration is configured as a Single-page application (SPA) with the correct redirect URIs.
231
233
 
232
234
  ## Security Best Practices
@@ -15,8 +15,8 @@ authentication provider you use.
15
15
 
16
16
  ## Authentication Providers
17
17
 
18
- Zudoku supports Clerk, Auth0, Supabase, Firebase, Azure B2C, and any OpenID Connect provider
19
- (including Okta, Keycloak, Authentik, and PingFederate).
18
+ Zudoku supports Clerk, Auth0, Supabase, Firebase, Microsoft Entra ID, Azure B2C, and any OpenID
19
+ Connect provider (including Okta, Keycloak, Authentik, and PingFederate).
20
20
 
21
21
  Not seeing your authentication provider? [Let us know](https://github.com/zuplo/zudoku/issues)
22
22
 
@@ -99,6 +99,26 @@ providing your own array of scopes.
99
99
  For provider-specific guides (Okta, Keycloak, etc.), see the
100
100
  [OpenID Connect setup page](./authentication-openid.md).
101
101
 
102
+ ### Microsoft Entra ID
103
+
104
+ For Microsoft Entra ID (formerly Azure AD), you will need the `clientId` from your app registration
105
+ and your `tenantId`.
106
+
107
+ ```typescript
108
+ {
109
+ // ...
110
+ authentication: {
111
+ type: "entra",
112
+ clientId: "<your-application-client-id>",
113
+ tenantId: "<your-tenant-id>", // Or "common" for multitenant. Defaults to "common".
114
+ },
115
+ // ...
116
+ }
117
+ ```
118
+
119
+ For full setup instructions, see the
120
+ [Azure AD / Entra ID setup guide](./authentication-azure-ad.md).
121
+
102
122
  ### Firebase
103
123
 
104
124
  For Firebase authentication, you will need your Firebase project configuration. You can find this in
@@ -187,3 +187,29 @@ since they use a different interaction model.
187
187
  If you are using [Zuplo](https://zuplo.com) to host your API, the `x-mcp-server` extension is
188
188
  automatically added to POST operations that use the `mcpServerHandler`. No manual schema changes are
189
189
  needed. See the [Zuplo MCP documentation](https://zuplo.com/docs/handlers/mcp-server) for details.
190
+
191
+ The server name shown in the install snippets (for example `claude mcp add … 'MCP Server' …`) is
192
+ taken from the handler's `name` option — the same name your MCP server advertises to clients. Set it
193
+ to override the default `"MCP Server"` title:
194
+
195
+ ```json title="config/routes.oas.json (paths section)"
196
+ {
197
+ "/mcp": {
198
+ "post": {
199
+ "operationId": "mcpServerHandler",
200
+ "x-zuplo-route": {
201
+ "handler": {
202
+ "export": "mcpServerHandler",
203
+ "module": "$import(@zuplo/runtime)",
204
+ "options": {
205
+ "name": "Acme API",
206
+ "operations": [{ "file": "./config/routes.oas.json", "id": "get-users" }]
207
+ }
208
+ }
209
+ }
210
+ }
211
+ }
212
+ }
213
+ ```
214
+
215
+ With this configuration the snippets read `claude mcp add --transport http 'Acme API' …`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zudoku",
3
- "version": "0.82.2",
3
+ "version": "0.82.4",
4
4
  "type": "module",
5
5
  "engines": {
6
6
  "node": ">=20.19.0 <21.0.0 || >=22.12.0"
@@ -57,6 +57,10 @@
57
57
  "types": "./dist/declarations/lib/authentication/providers/openid.d.ts",
58
58
  "default": "./src/lib/authentication/providers/openid.tsx"
59
59
  },
60
+ "./auth/entra": {
61
+ "types": "./dist/declarations/lib/authentication/providers/entra.d.ts",
62
+ "default": "./src/lib/authentication/providers/entra.tsx"
63
+ },
60
64
  "./auth/supabase": {
61
65
  "types": "./dist/declarations/lib/authentication/providers/supabase.d.ts",
62
66
  "default": "./src/lib/authentication/providers/supabase.tsx"
@@ -173,12 +177,13 @@
173
177
  },
174
178
  "dependencies": {
175
179
  "@apidevtools/json-schema-ref-parser": "15.3.5",
176
- "@base-ui/react": "^1.4.1",
180
+ "@base-ui/react": "^1.6.0",
177
181
  "@envelop/core": "5.5.1",
178
182
  "@graphiql/toolkit": "0.12.1",
179
183
  "@graphql-typed-document-node/core": "3.2.0",
180
- "@hono/node-server": "2.0.3",
184
+ "@hono/node-server": "2.0.8",
181
185
  "@lekoarts/rehype-meta-as-attributes": "3.0.3",
186
+ "@mdx-js/mdx": "3.1.1",
182
187
  "@mdx-js/react": "3.1.1",
183
188
  "@mdx-js/rollup": "3.1.1",
184
189
  "@pothos/core": "4.12.0",
@@ -206,21 +211,20 @@
206
211
  "@radix-ui/react-toggle-group": "1.1.11",
207
212
  "@radix-ui/react-tooltip": "1.2.8",
208
213
  "@radix-ui/react-visually-hidden": "1.2.4",
209
- "@scalar/openapi-parser": "0.23.13",
214
+ "@scalar/openapi-parser": "0.28.4",
210
215
  "@scalar/snippetz": "0.9.11",
211
- "@sentry/node": "10.52.0",
212
216
  "@shikijs/langs": "4.2.0",
213
217
  "@shikijs/rehype": "4.2.0",
214
218
  "@shikijs/themes": "4.2.0",
215
219
  "@shikijs/transformers": "4.2.0",
216
- "@tailwindcss/typography": "0.5.19",
220
+ "@tailwindcss/typography": "0.5.20",
217
221
  "@tailwindcss/vite": "4.3.0",
218
222
  "@tanem/react-nprogress": "6.0.3",
219
223
  "@tanstack/react-query": "5.101.0",
220
224
  "@types/react": "19.2.17",
221
225
  "@types/react-dom": "19.2.3",
222
- "@vitejs/plugin-react": "6.0.2",
223
- "@x0k/json-schema-merge": "1.0.3",
226
+ "@vitejs/plugin-react": "6.0.3",
227
+ "@x0k/json-schema-merge": "1.0.4",
224
228
  "@unhead/react": "3.1.3",
225
229
  "@zuplo/mcp": "0.0.32",
226
230
  "bs58": "6.0.0",
@@ -242,14 +246,14 @@
242
246
  "hast-util-heading-rank": "3.0.0",
243
247
  "hast-util-to-jsx-runtime": "2.3.6",
244
248
  "hast-util-to-string": "3.0.1",
245
- "hono": "4.12.25",
249
+ "hono": "4.12.27",
246
250
  "http-terminator": "3.2.0",
247
251
  "javascript-stringify": "2.1.0",
248
252
  "jose": "6.2.3",
249
253
  "json-schema-to-typescript-lite": "15.0.0",
250
254
  "loglevel": "1.9.2",
251
255
  "lucide-react": "1.16.0",
252
- "mdast-util-from-markdown": "2.0.2",
256
+ "mdast-util-from-markdown": "2.0.3",
253
257
  "mdast-util-mdx": "3.0.0",
254
258
  "mdast-util-mdx-jsx": "3.2.0",
255
259
  "micromark-extension-mdxjs": "3.0.0",
@@ -261,7 +265,7 @@
261
265
  "pagefind": "1.5.2",
262
266
  "picocolors": "1.1.1",
263
267
  "piscina": "5.2.0",
264
- "posthog-node": "5.36.2",
268
+ "posthog-node": "5.40.0",
265
269
  "quick-lru": "7.3.0",
266
270
  "react-error-boundary": "6.1.1",
267
271
  "react-hook-form": "7.76.1",
@@ -277,7 +281,7 @@
277
281
  "remark-frontmatter": "5.0.0",
278
282
  "remark-gfm": "4.0.1",
279
283
  "remark-mdx-frontmatter": "5.2.0",
280
- "semver": "7.8.2",
284
+ "semver": "7.8.5",
281
285
  "shiki": "4.2.0",
282
286
  "sitemap": "9.0.1",
283
287
  "strip-ansi": "7.2.0",
@@ -295,7 +299,7 @@
295
299
  "zustand": "5.0.14"
296
300
  },
297
301
  "devDependencies": {
298
- "@graphql-codegen/cli": "7.1.1",
302
+ "@graphql-codegen/cli": "7.1.3",
299
303
  "@inkeep/cxkit-types": "0.5.119",
300
304
  "@testing-library/dom": "10.4.1",
301
305
  "@testing-library/jest-dom": "6.9.1",
@@ -307,15 +311,15 @@
307
311
  "@types/hast": "3.0.4",
308
312
  "@types/json-schema": "7.0.15",
309
313
  "@types/mdast": "4.0.4",
310
- "@types/mdx": "2.0.13",
314
+ "@types/mdx": "2.0.14",
311
315
  "@types/node": "22.19.1",
312
316
  "@types/react-is": "19.2.0",
313
317
  "@types/semver": "7.7.1",
314
318
  "@types/unist": "3.0.3",
315
319
  "@types/yargs": "17.0.35",
316
- "@typescript/native-preview": "7.0.0-dev.20260610.1",
317
- "@vitest/coverage-v8": "4.1.8",
318
- "happy-dom": "20.10.2",
320
+ "@typescript/native-preview": "7.0.0-dev.20260624.1",
321
+ "@vitest/coverage-v8": "4.1.9",
322
+ "happy-dom": "20.10.6",
319
323
  "oxc-parser": "^0.135.0",
320
324
  "react": "19.2.7",
321
325
  "react-dom": "19.2.7",
@@ -26,7 +26,7 @@ import type { Adapter } from "./adapter.js";
26
26
  import { getRoutesByConfig } from "./main.js";
27
27
  import { protectChunks as rawProtectChunks } from "./protectChunks.js";
28
28
  import { getSsrCacheControl } from "./ssrCacheControl.js";
29
- import { wrapProtectedRoutes } from "./wrapProtectedRoutes.js";
29
+ import { wrapProtectedRoutesForRender } from "./wrapProtectedRoutes.js";
30
30
 
31
31
  export { getRoutesByConfig };
32
32
  export type { Adapter, AdapterContext } from "./adapter.js";
@@ -104,12 +104,16 @@ export const handleRequest = async ({
104
104
  const ssrAuth = await resolveSsrAuth(request);
105
105
 
106
106
  // No-op lazy() on protected subtrees for unauthed requests so loaders
107
- // don't run for a 401 render.
108
- const effectiveRoutes = wrapProtectedRoutes(
107
+ // don't run for a 401 render. A bypass render (search-index pass) keeps the
108
+ // real content so Pagefind can index protected routes.
109
+ const effectiveRoutes = wrapProtectedRoutesForRender(
109
110
  routes,
110
111
  config.protectedRoutes,
111
- !!ssrAuth?.profile,
112
- basePath,
112
+ {
113
+ isAuthenticated: !!ssrAuth?.profile,
114
+ bypassProtection,
115
+ basePath,
116
+ },
113
117
  );
114
118
 
115
119
  const { query, dataRoutes } = createStaticHandler(effectiveRoutes, {
@@ -47,6 +47,31 @@ export const wrapProtectedRoutes = (
47
47
  });
48
48
  };
49
49
 
50
+ // Route-wrapping decision for an SSR/SSG render. The prerender does a second
51
+ // "bypass" pass over protected routes to build the search index; that pass must
52
+ // render the real (unstubbed) protected content, so it is treated as
53
+ // authenticated here. Without this, the bypass pass renders an empty shell and
54
+ // the Pagefind index for protected routes ends up empty (issue #2672).
55
+ export const wrapProtectedRoutesForRender = (
56
+ routes: RouteObject[],
57
+ protectedRoutes: ProtectedRoutesInput,
58
+ {
59
+ isAuthenticated,
60
+ bypassProtection,
61
+ basePath,
62
+ }: {
63
+ isAuthenticated: boolean;
64
+ bypassProtection?: boolean;
65
+ basePath?: string;
66
+ },
67
+ ): RouteObject[] =>
68
+ wrapProtectedRoutes(
69
+ routes,
70
+ protectedRoutes,
71
+ isAuthenticated || bypassProtection === true,
72
+ basePath,
73
+ );
74
+
50
75
  // Inline elements can't be chunk-isolated; RouteGuard still blocks render,
51
76
  // but the JS ships in the main bundle. Only meaningful in dev.
52
77
  export const warnInlineProtectedRoutes = (
@@ -33,3 +33,7 @@ export type AzureB2CAuthenticationConfig = Extract<
33
33
  AuthenticationConfig,
34
34
  { type: "azureb2c" }
35
35
  >;
36
+ export type EntraAuthenticationConfig = Extract<
37
+ AuthenticationConfig,
38
+ { type: "entra" }
39
+ >;
@@ -12,6 +12,7 @@ import { getZudokuRootDir } from "../cli/common/package-json.js";
12
12
  import { runPluginTransformConfig } from "../lib/core/transform-config.js";
13
13
  import invariant from "../lib/util/invariant.js";
14
14
  import { fileExists } from "./file-exists.js";
15
+ import { getPluginVersions } from "./plugin-versions.js";
15
16
  import type {
16
17
  ResolvedZudokuConfig,
17
18
  ZudokuConfig,
@@ -240,6 +241,20 @@ export async function loadZudokuConfig(
240
241
  { timestamp: true },
241
242
  );
242
243
 
244
+ // Surface which versions of external plugins (created via `createPlugin`)
245
+ // the build is running, so build logs can pin down e.g. the monetization
246
+ // plugin version without each plugin having to log it itself.
247
+ const pluginVersions = await getPluginVersions(config.__pluginDirs ?? []);
248
+ if (pluginVersions.length > 0) {
249
+ logger.info(
250
+ colors.cyan(`loaded plugins `) +
251
+ colors.dim(
252
+ pluginVersions.map((p) => `${p.name}@${p.version}`).join(", "),
253
+ ),
254
+ { timestamp: true },
255
+ );
256
+ }
257
+
243
258
  return { config, envPrefix, publicEnv };
244
259
  } catch (error) {
245
260
  const lastValid = getConfig();
@@ -0,0 +1,38 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { findPackageRoot } from "../vite/package-root.js";
4
+
5
+ export type PluginVersion = { name: string; version: string };
6
+
7
+ // Reads the `name`@`version` from each plugin dir's nearest package.json,
8
+ // deduped by package name.
9
+ export const getPluginVersions = async (
10
+ pluginDirs: readonly string[],
11
+ ): Promise<PluginVersion[]> => {
12
+ const roots = await Promise.all(
13
+ [...new Set(pluginDirs)].map(findPackageRoot),
14
+ );
15
+
16
+ const resolved = await Promise.all(
17
+ roots.map(async (root): Promise<PluginVersion | undefined> => {
18
+ if (!root) return undefined;
19
+ try {
20
+ const pkg = JSON.parse(
21
+ await readFile(path.join(root, "package.json"), "utf-8"),
22
+ ) as { name?: string; version?: string };
23
+ if (!pkg.name) return undefined;
24
+ return { name: pkg.name, version: pkg.version ?? "unknown" };
25
+ } catch {
26
+ // A plugin without a readable package.json simply isn't reported.
27
+ return undefined;
28
+ }
29
+ }),
30
+ );
31
+
32
+ const byName = new Map<string, PluginVersion>();
33
+ for (const version of resolved) {
34
+ if (version && !byName.has(version.name)) byName.set(version.name, version);
35
+ }
36
+
37
+ return [...byName.values()];
38
+ };