@lukoweb/apitogo 0.1.53 → 0.1.55
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 +686 -373
- package/dist/declarations/config/apitogo-config-core.d.ts +14 -4
- package/dist/declarations/config/landing-manifest.d.ts +76 -0
- package/dist/declarations/config/local-manifest.d.ts +91 -1
- package/dist/declarations/config/validators/ModulesSchema.d.ts +227 -0
- package/dist/declarations/config/validators/ZudokuConfig.d.ts +84 -2
- package/dist/declarations/lib/authentication/header-nav-filter.d.ts +4 -0
- package/dist/declarations/lib/authentication/providers/dev-portal-auth-pages.d.ts +15 -0
- package/dist/declarations/lib/authentication/providers/dev-portal-constants.d.ts +19 -1
- package/dist/declarations/lib/authentication/providers/dev-portal-utils.d.ts +5 -1
- package/dist/declarations/lib/authentication/providers/dev-portal.d.ts +5 -0
- package/dist/declarations/lib/components/HeaderAuthActions.d.ts +3 -0
- package/dist/declarations/lib/components/SiteTitle.d.ts +6 -0
- package/dist/declarations/lib/components/ThemeSwitch.d.ts +3 -1
- package/dist/declarations/lib/components/TopNavigation.d.ts +1 -1
- package/dist/declarations/lib/core/ZudokuContext.d.ts +1 -0
- package/dist/flat-config.d.ts +61 -1
- package/package.json +1 -1
- package/src/app/main.css +2 -2
- package/src/config/apitogo-config-core.ts +140 -11
- package/src/config/apitogo-config-loader.browser.ts +17 -0
- package/src/config/apitogo-config-loader.node.ts +28 -8
- package/src/config/build-apitogo-config.ts +13 -4
- package/src/config/landing-manifest.ts +11 -0
- package/src/config/landing-openapi-showcase.ts +115 -0
- package/src/config/loader.ts +17 -3
- package/src/config/local-manifest.ts +146 -11
- package/src/config/resolve-modules.ts +22 -1
- package/src/config/validators/ModulesSchema.ts +84 -0
- package/src/config/validators/ZudokuConfig.ts +26 -1
- package/src/lib/authentication/auth-header-nav.ts +5 -15
- package/src/lib/authentication/header-nav-filter.ts +36 -0
- package/src/lib/authentication/providers/dev-portal-auth-pages.tsx +499 -0
- package/src/lib/authentication/providers/dev-portal-constants.ts +15 -1
- package/src/lib/authentication/providers/dev-portal-utils.ts +128 -3
- package/src/lib/authentication/providers/dev-portal.tsx +51 -6
- package/src/lib/components/Header.tsx +49 -39
- package/src/lib/components/HeaderAuthActions.tsx +36 -0
- package/src/lib/components/HeaderNavigation.tsx +11 -9
- package/src/lib/components/MobileTopNavigation.tsx +43 -24
- package/src/lib/components/SiteTitle.tsx +20 -0
- package/src/lib/components/ThemeSwitch.tsx +36 -2
- package/src/lib/components/TopNavigation.tsx +20 -33
- package/src/lib/core/ZudokuContext.ts +1 -0
- package/src/vite/config.ts +21 -0
- package/src/vite/plugin-auth.ts +4 -1
- package/src/vite/plugin-config.ts +39 -4
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export declare function DevPortalLoginPage({ apiBaseUrl }: {
|
|
2
|
+
apiBaseUrl?: string;
|
|
3
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
4
|
+
export declare function DevPortalRegisterPage({ apiBaseUrl }: {
|
|
5
|
+
apiBaseUrl?: string;
|
|
6
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
7
|
+
export declare function DevPortalVerifyEmailPage({ apiBaseUrl, }: {
|
|
8
|
+
apiBaseUrl?: string;
|
|
9
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
10
|
+
export declare function DevPortalForgotPasswordPage({ apiBaseUrl, }: {
|
|
11
|
+
apiBaseUrl?: string;
|
|
12
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
13
|
+
export declare function DevPortalResetPasswordPage({ apiBaseUrl, }: {
|
|
14
|
+
apiBaseUrl?: string;
|
|
15
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
@@ -14,5 +14,23 @@ export type EndUserMeResponse = {
|
|
|
14
14
|
};
|
|
15
15
|
export type DevPortalAuthStatusResponse = {
|
|
16
16
|
available: boolean;
|
|
17
|
-
|
|
17
|
+
nativeEnabled?: boolean;
|
|
18
|
+
oidcConfigured?: boolean;
|
|
19
|
+
providerCount?: number;
|
|
20
|
+
};
|
|
21
|
+
export type DevPortalAuthConfigResponse = {
|
|
22
|
+
native: {
|
|
23
|
+
enabled: boolean;
|
|
24
|
+
};
|
|
25
|
+
email?: {
|
|
26
|
+
provider: string;
|
|
27
|
+
from: string;
|
|
28
|
+
} | null;
|
|
29
|
+
providers: Array<{
|
|
30
|
+
id: string;
|
|
31
|
+
displayName: string;
|
|
32
|
+
authority: string;
|
|
33
|
+
clientId: string;
|
|
34
|
+
scopes: string[];
|
|
35
|
+
}>;
|
|
18
36
|
};
|
|
@@ -3,7 +3,11 @@ import { type EndUserMeResponse } from "./dev-portal-constants.js";
|
|
|
3
3
|
export declare function resolveDevPortalApiBaseUrl(config: Pick<DevPortalAuthenticationConfig, "apiBaseUrl">): string | undefined;
|
|
4
4
|
export declare function isPlaceholderDevPortalApiUrl(apiBaseUrl: string | undefined): boolean;
|
|
5
5
|
export declare function toAbsoluteReturnUrl(pathOrUrl: string): string;
|
|
6
|
-
export declare function
|
|
6
|
+
export declare function buildDevPortalOidcChallengeUrl(apiBaseUrl: string, providerId: string, returnUrl: string): string;
|
|
7
7
|
export declare function buildDevPortalLogoutUrl(apiBaseUrl: string, returnUrl: string): string;
|
|
8
|
+
export declare function normalizeDevPortalAuthConfig(raw: unknown): import("./dev-portal-constants.js").DevPortalAuthConfigResponse;
|
|
9
|
+
export declare function isNativeAuthEnabled(config: import("./dev-portal-constants.js").DevPortalAuthConfigResponse | null | undefined): boolean;
|
|
10
|
+
export declare function fetchDevPortalAuthConfig(apiBaseUrl: string, fetchImpl?: typeof fetch): Promise<import("./dev-portal-constants.js").DevPortalAuthConfigResponse>;
|
|
11
|
+
export declare function postDevPortalAuth<T>(apiBaseUrl: string, path: string, body: unknown, fetchImpl?: typeof fetch): Promise<T>;
|
|
8
12
|
export declare function probeDevPortalBackend(apiBaseUrl: string, fetchImpl?: typeof fetch): Promise<boolean>;
|
|
9
13
|
export declare function mapEndUserMeToProfile(me: EndUserMeResponse): import("../state.js").UserProfile;
|
|
@@ -21,7 +21,12 @@ export declare class DevPortalAuthenticationProvider extends CoreAuthenticationP
|
|
|
21
21
|
private backendAvailable;
|
|
22
22
|
private refreshGeneration;
|
|
23
23
|
private refreshInFlight;
|
|
24
|
+
private authConfigNativeEnabled;
|
|
24
25
|
constructor({ apiBaseUrl, redirectToAfterSignIn, redirectToAfterSignUp, redirectToAfterSignOut, }: DevPortalAuthenticationConfig);
|
|
26
|
+
getRoutes(): {
|
|
27
|
+
path: string;
|
|
28
|
+
element: import("react/jsx-runtime").JSX.Element;
|
|
29
|
+
}[];
|
|
25
30
|
initialize(_context: ZudokuContext): Promise<void>;
|
|
26
31
|
onPageLoad: () => Promise<void>;
|
|
27
32
|
private syncBackendAvailability;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type NavLinkProps } from "react-router";
|
|
2
2
|
import type { NavigationItem } from "../../config/validators/NavigationSchema.js";
|
|
3
|
-
export declare const TopNavigation: () => import("react/jsx-runtime").JSX.Element;
|
|
3
|
+
export declare const TopNavigation: () => import("react/jsx-runtime").JSX.Element | null;
|
|
4
4
|
export declare const TopNavLink: ({ isActive, children, ...props }: {
|
|
5
5
|
isActive?: boolean;
|
|
6
6
|
children: React.ReactNode;
|
package/dist/flat-config.d.ts
CHANGED
|
@@ -137,6 +137,7 @@ export interface FlatZudokuConfig {
|
|
|
137
137
|
}
|
|
138
138
|
site?: {
|
|
139
139
|
title?: string
|
|
140
|
+
showTitleInHeader?: boolean
|
|
140
141
|
logoUrl?: string
|
|
141
142
|
dir?: ("ltr" | "rtl")
|
|
142
143
|
logo?: _Schema0
|
|
@@ -319,8 +320,24 @@ export interface FlatZudokuConfig {
|
|
|
319
320
|
redirectToAfterSignIn?: string
|
|
320
321
|
redirectToAfterSignOut?: string
|
|
321
322
|
} | {
|
|
322
|
-
type
|
|
323
|
+
type?: "dev-portal"
|
|
323
324
|
apiBaseUrl?: string
|
|
325
|
+
native?: {
|
|
326
|
+
enabled?: boolean
|
|
327
|
+
}
|
|
328
|
+
email?: {
|
|
329
|
+
provider?: string
|
|
330
|
+
from?: string
|
|
331
|
+
connectionString?: string
|
|
332
|
+
}
|
|
333
|
+
providers?: {
|
|
334
|
+
id: string
|
|
335
|
+
displayName?: string
|
|
336
|
+
authority?: string
|
|
337
|
+
clientId?: string
|
|
338
|
+
clientSecret?: string
|
|
339
|
+
scopes?: string
|
|
340
|
+
}[]
|
|
324
341
|
redirectToAfterSignUp?: string
|
|
325
342
|
redirectToAfterSignIn?: string
|
|
326
343
|
redirectToAfterSignOut?: string
|
|
@@ -517,13 +534,56 @@ export interface _Schema21 {
|
|
|
517
534
|
title?: string
|
|
518
535
|
subtitle?: string
|
|
519
536
|
description?: string
|
|
537
|
+
badge?: string
|
|
538
|
+
highlights?: string[]
|
|
539
|
+
codeExample?: {
|
|
540
|
+
filename?: string
|
|
541
|
+
language?: string
|
|
542
|
+
code: string
|
|
543
|
+
}
|
|
520
544
|
}
|
|
521
545
|
features?: {
|
|
522
546
|
title: string
|
|
523
547
|
description: string
|
|
548
|
+
icon?: string
|
|
524
549
|
}[]
|
|
525
550
|
primaryAction?: _Schema22
|
|
526
551
|
secondaryAction?: _Schema22
|
|
552
|
+
ticker?: {
|
|
553
|
+
enabled?: boolean
|
|
554
|
+
items?: {
|
|
555
|
+
label: string
|
|
556
|
+
value: string
|
|
557
|
+
change?: string
|
|
558
|
+
}[]
|
|
559
|
+
}
|
|
560
|
+
trust?: {
|
|
561
|
+
enabled?: boolean
|
|
562
|
+
title?: string
|
|
563
|
+
logos?: string[]
|
|
564
|
+
}
|
|
565
|
+
apiShowcase?: {
|
|
566
|
+
enabled?: boolean
|
|
567
|
+
title?: string
|
|
568
|
+
subtitle?: string
|
|
569
|
+
browseAllHref?: string
|
|
570
|
+
browseAllLabel?: string
|
|
571
|
+
autoFromOpenApi?: boolean
|
|
572
|
+
items?: {
|
|
573
|
+
name: string
|
|
574
|
+
path: string
|
|
575
|
+
icon?: string
|
|
576
|
+
latency?: string
|
|
577
|
+
summary?: string
|
|
578
|
+
}[]
|
|
579
|
+
}
|
|
580
|
+
pricingCta?: {
|
|
581
|
+
enabled?: boolean
|
|
582
|
+
title?: string
|
|
583
|
+
description?: string
|
|
584
|
+
primaryAction?: _Schema22
|
|
585
|
+
secondaryAction?: _Schema22
|
|
586
|
+
}
|
|
527
587
|
showPoweredBy?: boolean
|
|
528
588
|
}
|
|
529
589
|
export interface _Schema22 {
|
package/package.json
CHANGED
package/src/app/main.css
CHANGED
|
@@ -102,8 +102,8 @@
|
|
|
102
102
|
|
|
103
103
|
@layer base {
|
|
104
104
|
:root {
|
|
105
|
-
--top-header-height:
|
|
106
|
-
--top-nav-height:
|
|
105
|
+
--top-header-height: 64px;
|
|
106
|
+
--top-nav-height: 0px;
|
|
107
107
|
--banner-height: 35px;
|
|
108
108
|
--header-height: calc(
|
|
109
109
|
var(--top-header-height) + var(--top-nav-height) + var(--banner-height)
|
|
@@ -12,6 +12,7 @@ const DERIVED_MANIFEST_KEYS = [
|
|
|
12
12
|
"backend",
|
|
13
13
|
"gateway",
|
|
14
14
|
"projectId",
|
|
15
|
+
"landing",
|
|
15
16
|
] as const;
|
|
16
17
|
|
|
17
18
|
const SITE_CONFIG_KEYS = [
|
|
@@ -34,12 +35,20 @@ const SITE_CONFIG_KEYS = [
|
|
|
34
35
|
|
|
35
36
|
export type ApitogoJsonConfig = Record<string, unknown>;
|
|
36
37
|
|
|
38
|
+
export type AuthProviderPublicConfig = {
|
|
39
|
+
id: string;
|
|
40
|
+
displayName?: string;
|
|
41
|
+
authority?: string;
|
|
42
|
+
clientId?: string;
|
|
43
|
+
scopes?: string;
|
|
44
|
+
};
|
|
45
|
+
|
|
37
46
|
export type AuthenticationSecrets = {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
clientId?: string;
|
|
41
|
-
clientSecret?: string;
|
|
47
|
+
email?: {
|
|
48
|
+
connectionString?: string;
|
|
42
49
|
};
|
|
50
|
+
/** Provider secrets keyed by provider `id` (from env: `authentication__providers__{id}__clientSecret`). */
|
|
51
|
+
providers?: Record<string, { clientSecret?: string }>;
|
|
43
52
|
stripe?: {
|
|
44
53
|
secretKey?: string;
|
|
45
54
|
webhookSecret?: string;
|
|
@@ -351,6 +360,7 @@ export function normalizeApitogoConfig(
|
|
|
351
360
|
|
|
352
361
|
migratePlanCatalog(normalized);
|
|
353
362
|
syncBillingEnabled(normalized);
|
|
363
|
+
syncAuthentication(normalized);
|
|
354
364
|
|
|
355
365
|
return normalized;
|
|
356
366
|
}
|
|
@@ -364,6 +374,119 @@ export function isBillingEnabled(config: ApitogoJsonConfig): boolean {
|
|
|
364
374
|
return plans?.enabled === true;
|
|
365
375
|
}
|
|
366
376
|
|
|
377
|
+
function isUserPanelEnabled(normalized: ApitogoJsonConfig): boolean {
|
|
378
|
+
const modules = asRecord(normalized.modules);
|
|
379
|
+
const userPanel = asRecord(modules?.userPanel);
|
|
380
|
+
if (userPanel?.enabled === true) {
|
|
381
|
+
return true;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
const dashboard = asRecord(userPanel?.dashboard);
|
|
385
|
+
const userManagement = asRecord(userPanel?.userManagement);
|
|
386
|
+
const apiKeys = asRecord(userPanel?.apiKeys);
|
|
387
|
+
const plans = asRecord(userPanel?.plans);
|
|
388
|
+
|
|
389
|
+
return (
|
|
390
|
+
dashboard?.enabled === true ||
|
|
391
|
+
userManagement?.enabled === true ||
|
|
392
|
+
apiKeys?.enabled === true ||
|
|
393
|
+
plans?.enabled === true
|
|
394
|
+
);
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
/** Read public OIDC provider entries from `authentication.providers` array. */
|
|
398
|
+
export function readAuthProviders(
|
|
399
|
+
config: ApitogoJsonConfig,
|
|
400
|
+
): AuthProviderPublicConfig[] {
|
|
401
|
+
const normalized = normalizeApitogoConfig(config);
|
|
402
|
+
const authentication = asRecord(normalized.authentication);
|
|
403
|
+
const providers = authentication?.providers;
|
|
404
|
+
if (!Array.isArray(providers)) {
|
|
405
|
+
return [];
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
return providers
|
|
409
|
+
.map((entry) => asRecord(entry))
|
|
410
|
+
.filter((entry): entry is Record<string, unknown> => entry !== undefined)
|
|
411
|
+
.filter((entry) => typeof entry.id === "string" && entry.id.trim())
|
|
412
|
+
.map((entry) => ({
|
|
413
|
+
id: String(entry.id).trim(),
|
|
414
|
+
displayName:
|
|
415
|
+
typeof entry.displayName === "string" ? entry.displayName : undefined,
|
|
416
|
+
authority:
|
|
417
|
+
typeof entry.authority === "string" ? entry.authority : undefined,
|
|
418
|
+
clientId: typeof entry.clientId === "string" ? entry.clientId : undefined,
|
|
419
|
+
scopes: typeof entry.scopes === "string" ? entry.scopes : undefined,
|
|
420
|
+
}));
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
export function isNativeAuthEnabled(config: ApitogoJsonConfig): boolean {
|
|
424
|
+
const normalized = normalizeApitogoConfig(config);
|
|
425
|
+
if (!isUserPanelEnabled(normalized)) {
|
|
426
|
+
return false;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
const native = asRecord(asRecord(normalized.authentication)?.native);
|
|
430
|
+
return native?.enabled !== false;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
function syncAuthentication(normalized: ApitogoJsonConfig): void {
|
|
434
|
+
const userPanelEnabled = isUserPanelEnabled(normalized);
|
|
435
|
+
const authentication = asRecord(normalized.authentication);
|
|
436
|
+
|
|
437
|
+
if (!userPanelEnabled && !authentication) {
|
|
438
|
+
return;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
const auth = authentication ?? {};
|
|
442
|
+
auth.enabled = userPanelEnabled;
|
|
443
|
+
auth.type = "dev-portal";
|
|
444
|
+
|
|
445
|
+
if (userPanelEnabled) {
|
|
446
|
+
const native = asRecord(auth.native) ?? {};
|
|
447
|
+
if (native.enabled === undefined) {
|
|
448
|
+
native.enabled = true;
|
|
449
|
+
}
|
|
450
|
+
auth.native = native;
|
|
451
|
+
|
|
452
|
+
if (!Array.isArray(auth.providers)) {
|
|
453
|
+
auth.providers = [];
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
normalized.authentication = auth;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
function stripAuthenticationSecrets(
|
|
461
|
+
authentication: Record<string, unknown>,
|
|
462
|
+
): Record<string, unknown> {
|
|
463
|
+
const {
|
|
464
|
+
gateway: _gateway,
|
|
465
|
+
oidc: _oidc,
|
|
466
|
+
stripe: _stripe,
|
|
467
|
+
type: _type,
|
|
468
|
+
email,
|
|
469
|
+
providers,
|
|
470
|
+
...authPublic
|
|
471
|
+
} = authentication;
|
|
472
|
+
|
|
473
|
+
const emailRecord = asRecord(email);
|
|
474
|
+
if (emailRecord) {
|
|
475
|
+
const { connectionString: _connectionString, ...emailPublic } = emailRecord;
|
|
476
|
+
authPublic.email = emailPublic;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
if (Array.isArray(providers)) {
|
|
480
|
+
authPublic.providers = providers.map((entry) => {
|
|
481
|
+
const provider = asRecord(entry) ?? {};
|
|
482
|
+
const { clientSecret: _clientSecret, ...providerPublic } = provider;
|
|
483
|
+
return providerPublic;
|
|
484
|
+
});
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
return authPublic;
|
|
488
|
+
}
|
|
489
|
+
|
|
367
490
|
function syncBillingEnabled(normalized: ApitogoJsonConfig): void {
|
|
368
491
|
const legacyPricing = asRecord(normalized.pricing);
|
|
369
492
|
const modules = ensureModules(normalized);
|
|
@@ -400,13 +523,7 @@ export function deriveManifestFields(
|
|
|
400
523
|
if (key === "authentication") {
|
|
401
524
|
const authentication = asRecord(normalized.authentication);
|
|
402
525
|
if (authentication) {
|
|
403
|
-
|
|
404
|
-
gateway: _gateway,
|
|
405
|
-
oidc: _oidc,
|
|
406
|
-
stripe: _stripe,
|
|
407
|
-
...authPublic
|
|
408
|
-
} = authentication;
|
|
409
|
-
manifest.authentication = authPublic;
|
|
526
|
+
manifest.authentication = stripAuthenticationSecrets(authentication);
|
|
410
527
|
}
|
|
411
528
|
continue;
|
|
412
529
|
}
|
|
@@ -426,6 +543,18 @@ export function deriveManifestFields(
|
|
|
426
543
|
}
|
|
427
544
|
continue;
|
|
428
545
|
}
|
|
546
|
+
if (key === "landing") {
|
|
547
|
+
const modules = asRecord(normalized.modules);
|
|
548
|
+
const landing = asRecord(modules?.landing);
|
|
549
|
+
if (landing?.content || landing?.enabled !== undefined) {
|
|
550
|
+
manifest.landing = {
|
|
551
|
+
enabled:
|
|
552
|
+
typeof landing.enabled === "boolean" ? landing.enabled : undefined,
|
|
553
|
+
content: landing.content,
|
|
554
|
+
};
|
|
555
|
+
}
|
|
556
|
+
continue;
|
|
557
|
+
}
|
|
429
558
|
if (normalized[key] !== undefined) {
|
|
430
559
|
manifest[key] = normalized[key];
|
|
431
560
|
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
ApitogoJsonConfig,
|
|
3
|
+
AuthenticationSecrets,
|
|
4
|
+
} from "./apitogo-config-core.js";
|
|
5
|
+
|
|
6
|
+
/** Browser stub — JSON config is embedded by the Vite config plugin on the client. */
|
|
7
|
+
export function loadApitogoConfig(_rootDir?: string): ApitogoJsonConfig {
|
|
8
|
+
return {};
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function readAuthenticationSecrets(): AuthenticationSecrets {
|
|
12
|
+
return {};
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function apitogoConfigPath(rootDir: string): string {
|
|
16
|
+
return rootDir;
|
|
17
|
+
}
|
|
@@ -36,19 +36,39 @@ export function readAuthenticationSecrets(
|
|
|
36
36
|
): AuthenticationSecrets {
|
|
37
37
|
const overrides = applyEnvOverrides({}, env);
|
|
38
38
|
const authentication = asRecord(overrides.authentication);
|
|
39
|
-
const
|
|
39
|
+
const email = asRecord(authentication?.email);
|
|
40
|
+
const providers = asRecord(authentication?.providers);
|
|
40
41
|
const stripe = asRecord(authentication?.stripe);
|
|
41
42
|
|
|
42
43
|
const secrets: AuthenticationSecrets = {};
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
44
|
+
|
|
45
|
+
if (email?.connectionString) {
|
|
46
|
+
secrets.email = {
|
|
47
|
+
connectionString:
|
|
48
|
+
typeof email.connectionString === "string"
|
|
49
|
+
? email.connectionString
|
|
50
|
+
: undefined,
|
|
50
51
|
};
|
|
51
52
|
}
|
|
53
|
+
|
|
54
|
+
if (providers) {
|
|
55
|
+
const providerSecrets: Record<string, { clientSecret?: string }> = {};
|
|
56
|
+
for (const [id, value] of Object.entries(providers)) {
|
|
57
|
+
const provider = asRecord(value);
|
|
58
|
+
if (provider?.clientSecret) {
|
|
59
|
+
providerSecrets[id] = {
|
|
60
|
+
clientSecret:
|
|
61
|
+
typeof provider.clientSecret === "string"
|
|
62
|
+
? provider.clientSecret
|
|
63
|
+
: undefined,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
if (Object.keys(providerSecrets).length > 0) {
|
|
68
|
+
secrets.providers = providerSecrets;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
52
72
|
if (stripe) {
|
|
53
73
|
secrets.stripe = {
|
|
54
74
|
secretKey:
|
|
@@ -11,6 +11,13 @@ type BuildApitogoConfigOptions = Partial<ApitogoConfig> &
|
|
|
11
11
|
plugins?: ApitogoPlugin[];
|
|
12
12
|
};
|
|
13
13
|
|
|
14
|
+
function mergeOptionalRecord<T extends Record<string, unknown>>(
|
|
15
|
+
...records: Array<T | undefined>
|
|
16
|
+
): T | undefined {
|
|
17
|
+
const merged = Object.assign({}, ...records.filter(Boolean)) as T;
|
|
18
|
+
return Object.keys(merged).length > 0 ? merged : undefined;
|
|
19
|
+
}
|
|
20
|
+
|
|
14
21
|
export function buildApitogoConfig({
|
|
15
22
|
plugins,
|
|
16
23
|
plans: _legacyPlans,
|
|
@@ -24,14 +31,16 @@ export function buildApitogoConfig({
|
|
|
24
31
|
const { plans: _rootPlans, ...restOverrides } =
|
|
25
32
|
jsonOverrides as ApitogoJsonConfig;
|
|
26
33
|
|
|
34
|
+
const authentication = mergeOptionalRecord(
|
|
35
|
+
siteConfig.authentication as ApitogoConfig["authentication"],
|
|
36
|
+
jsonOverrides.authentication as ApitogoConfig["authentication"],
|
|
37
|
+
);
|
|
38
|
+
|
|
27
39
|
return {
|
|
28
40
|
...siteConfig,
|
|
29
41
|
...restOverrides,
|
|
30
42
|
plugins,
|
|
31
|
-
authentication: {
|
|
32
|
-
...(siteConfig.authentication as ApitogoConfig["authentication"]),
|
|
33
|
-
...jsonOverrides.authentication,
|
|
34
|
-
},
|
|
43
|
+
...(authentication ? { authentication } : {}),
|
|
35
44
|
site: {
|
|
36
45
|
...(siteConfig.site as ApitogoConfig["site"]),
|
|
37
46
|
...jsonOverrides.site,
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { LandingContentSchema } from "./validators/ModulesSchema.js";
|
|
3
|
+
|
|
4
|
+
export const LandingManifestContentSchema = LandingContentSchema;
|
|
5
|
+
|
|
6
|
+
export type LandingManifestContent = z.infer<typeof LandingContentSchema>;
|
|
7
|
+
|
|
8
|
+
export type LandingManifestInput = {
|
|
9
|
+
enabled?: boolean;
|
|
10
|
+
content?: LandingManifestContent;
|
|
11
|
+
};
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import type { LandingApiShowcaseItemConfig } from "./validators/ModulesSchema.js";
|
|
4
|
+
import type { ZudokuConfig } from "./validators/ZudokuConfig.js";
|
|
5
|
+
|
|
6
|
+
const HTTP_METHODS = [
|
|
7
|
+
"get",
|
|
8
|
+
"post",
|
|
9
|
+
"put",
|
|
10
|
+
"patch",
|
|
11
|
+
"delete",
|
|
12
|
+
"head",
|
|
13
|
+
"options",
|
|
14
|
+
] as const;
|
|
15
|
+
|
|
16
|
+
type OpenApiDocument = {
|
|
17
|
+
paths?: Record<
|
|
18
|
+
string,
|
|
19
|
+
Partial<Record<(typeof HTTP_METHODS)[number], { summary?: string }>>
|
|
20
|
+
>;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
function readOpenApiDocument(
|
|
24
|
+
rootDir: string,
|
|
25
|
+
openApiPath: string,
|
|
26
|
+
): OpenApiDocument | null {
|
|
27
|
+
try {
|
|
28
|
+
const absolutePath = path.isAbsolute(openApiPath)
|
|
29
|
+
? openApiPath
|
|
30
|
+
: path.resolve(rootDir, openApiPath);
|
|
31
|
+
const raw = readFileSync(absolutePath, "utf8");
|
|
32
|
+
return JSON.parse(raw) as OpenApiDocument;
|
|
33
|
+
} catch {
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function resolveOpenApiPath(config: ZudokuConfig): string | undefined {
|
|
39
|
+
const backend = (config as { backend?: { openApiPath?: string } }).backend;
|
|
40
|
+
if (backend?.openApiPath) {
|
|
41
|
+
return backend.openApiPath;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const apis = config.apis;
|
|
45
|
+
if (!apis) {
|
|
46
|
+
return undefined;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const entries = Array.isArray(apis) ? apis : [apis];
|
|
50
|
+
for (const entry of entries) {
|
|
51
|
+
if (typeof entry === "object" && entry !== null && "type" in entry) {
|
|
52
|
+
const api = entry as { type?: string; input?: string };
|
|
53
|
+
if (api.type === "file" && typeof api.input === "string") {
|
|
54
|
+
return api.input;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return undefined;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function extractApiShowcaseItemsFromOpenApi(
|
|
63
|
+
config: ZudokuConfig,
|
|
64
|
+
rootDir: string,
|
|
65
|
+
limit = 8,
|
|
66
|
+
): LandingApiShowcaseItemConfig[] {
|
|
67
|
+
const openApiPath = resolveOpenApiPath(config);
|
|
68
|
+
if (!openApiPath) {
|
|
69
|
+
return [];
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const document = readOpenApiDocument(rootDir, openApiPath);
|
|
73
|
+
if (!document?.paths) {
|
|
74
|
+
return [];
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const items: LandingApiShowcaseItemConfig[] = [];
|
|
78
|
+
|
|
79
|
+
for (const [routePath, methods] of Object.entries(document.paths)) {
|
|
80
|
+
for (const method of HTTP_METHODS) {
|
|
81
|
+
const operation = methods?.[method];
|
|
82
|
+
if (!operation) {
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
items.push({
|
|
87
|
+
name: operation.summary ?? `${method.toUpperCase()} ${routePath}`,
|
|
88
|
+
path: routePath,
|
|
89
|
+
summary: operation.summary,
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
if (items.length >= limit) {
|
|
93
|
+
return items;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return items;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export function enrichResolvedLandingApiShowcase(
|
|
102
|
+
config: ZudokuConfig,
|
|
103
|
+
resolved: { landing: { content: { apiShowcase?: { autoFromOpenApi?: boolean; items?: LandingApiShowcaseItemConfig[] } } } },
|
|
104
|
+
rootDir: string,
|
|
105
|
+
): void {
|
|
106
|
+
const showcase = resolved.landing.content.apiShowcase;
|
|
107
|
+
if (
|
|
108
|
+
!showcase?.autoFromOpenApi ||
|
|
109
|
+
(showcase.items && showcase.items.length > 0)
|
|
110
|
+
) {
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
showcase.items = extractApiShowcaseItemsFromOpenApi(config, rootDir);
|
|
115
|
+
}
|
package/src/config/loader.ts
CHANGED
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
loadApitogoConfig,
|
|
19
19
|
readPlanCatalogItems,
|
|
20
20
|
} from "./apitogo-config-loader.js";
|
|
21
|
+
import { buildApitogoConfig } from "./build-apitogo-config.js";
|
|
21
22
|
import { fileExists } from "./file-exists.js";
|
|
22
23
|
import {
|
|
23
24
|
isAuthenticationEnabled,
|
|
@@ -117,7 +118,18 @@ async function loadZudokuConfigWithMeta(
|
|
|
117
118
|
},
|
|
118
119
|
});
|
|
119
120
|
|
|
120
|
-
const
|
|
121
|
+
const importedConfig = module.default;
|
|
122
|
+
|
|
123
|
+
let config: ZudokuConfig;
|
|
124
|
+
try {
|
|
125
|
+
const fileConfig = loadApitogoConfig(rootDir);
|
|
126
|
+
config = buildApitogoConfig({
|
|
127
|
+
...fileConfig,
|
|
128
|
+
plugins: importedConfig.plugins,
|
|
129
|
+
});
|
|
130
|
+
} catch {
|
|
131
|
+
config = importedConfig;
|
|
132
|
+
}
|
|
121
133
|
|
|
122
134
|
validateConfig(config, configPath);
|
|
123
135
|
|
|
@@ -130,7 +142,7 @@ async function loadZudokuConfigWithMeta(
|
|
|
130
142
|
dependencies,
|
|
131
143
|
configPath,
|
|
132
144
|
},
|
|
133
|
-
__resolvedModules: resolveModulesConfig(config),
|
|
145
|
+
__resolvedModules: resolveModulesConfig(config, { rootDir }),
|
|
134
146
|
};
|
|
135
147
|
|
|
136
148
|
return configWithMetadata;
|
|
@@ -288,7 +300,9 @@ export async function loadZudokuConfig(
|
|
|
288
300
|
...transformedConfig.__meta,
|
|
289
301
|
...configWithManifestMeta.__meta,
|
|
290
302
|
},
|
|
291
|
-
__resolvedModules: resolveModulesConfig(transformedConfig
|
|
303
|
+
__resolvedModules: resolveModulesConfig(transformedConfig, {
|
|
304
|
+
rootDir: transformedConfig.__meta.rootDir,
|
|
305
|
+
}),
|
|
292
306
|
};
|
|
293
307
|
|
|
294
308
|
if (!process.env.APITOGO_JSON_ONLY) {
|