@lukoweb/apitogo 0.1.54 → 0.1.56
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 +831 -398
- package/dist/declarations/config/default-landing-content.d.ts +2 -0
- package/dist/declarations/config/landing-manifest.d.ts +82 -0
- package/dist/declarations/config/local-manifest.d.ts +82 -0
- package/dist/declarations/config/resolve-modules.d.ts +3 -1
- package/dist/declarations/config/validators/ModulesSchema.d.ts +259 -0
- package/dist/declarations/config/validators/ZudokuConfig.d.ts +57 -0
- package/dist/declarations/lib/authentication/header-nav-filter.d.ts +4 -0
- package/dist/declarations/lib/authentication/providers/dev-portal-auth-pages.d.ts +5 -5
- package/dist/declarations/lib/authentication/providers/dev-portal-utils.d.ts +1 -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 +95 -38
- package/package.json +1 -1
- package/src/app/main.css +2 -2
- package/src/config/apitogo-config-core.ts +13 -0
- package/src/config/apitogo-config-loader.browser.ts +17 -0
- package/src/config/build-apitogo-config.ts +13 -4
- package/src/config/default-landing-content.ts +154 -0
- 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 +114 -10
- package/src/config/resolve-modules.ts +121 -32
- package/src/config/validators/ModulesSchema.ts +109 -0
- package/src/config/validators/ZudokuConfig.ts +1 -0
- 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 +95 -35
- package/src/lib/authentication/providers/dev-portal-utils.ts +26 -7
- package/src/lib/authentication/providers/dev-portal.tsx +18 -8
- 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
|
@@ -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
|
@@ -64,7 +64,7 @@ export type _Schema15 = boolean
|
|
|
64
64
|
/**
|
|
65
65
|
* Gateway plan catalog for /pricing preview and publish (canonical storage).
|
|
66
66
|
*/
|
|
67
|
-
export type
|
|
67
|
+
export type _Schema37 = {
|
|
68
68
|
sku: string
|
|
69
69
|
displayName: string
|
|
70
70
|
priceAmount?: number
|
|
@@ -73,31 +73,31 @@ export type _Schema35 = {
|
|
|
73
73
|
limitsJson?: string
|
|
74
74
|
includedActionKeys?: string[]
|
|
75
75
|
}[]
|
|
76
|
-
export type
|
|
76
|
+
export type _Schema38 = ({
|
|
77
77
|
type: "url"
|
|
78
|
-
input: (string |
|
|
79
|
-
server?:
|
|
80
|
-
path?:
|
|
81
|
-
categories?:
|
|
82
|
-
options?:
|
|
78
|
+
input: (string | _Schema39[])
|
|
79
|
+
server?: _Schema40
|
|
80
|
+
path?: _Schema41
|
|
81
|
+
categories?: _Schema42
|
|
82
|
+
options?: _Schema44
|
|
83
83
|
} | {
|
|
84
84
|
type: "file"
|
|
85
|
-
input: (string | (string |
|
|
86
|
-
server?:
|
|
87
|
-
path?:
|
|
88
|
-
categories?:
|
|
89
|
-
options?:
|
|
85
|
+
input: (string | (string | _Schema39)[])
|
|
86
|
+
server?: _Schema40
|
|
87
|
+
path?: _Schema41
|
|
88
|
+
categories?: _Schema42
|
|
89
|
+
options?: _Schema44
|
|
90
90
|
} | {
|
|
91
91
|
type: "raw"
|
|
92
92
|
input: string
|
|
93
|
-
server?:
|
|
94
|
-
path?:
|
|
95
|
-
categories?:
|
|
96
|
-
options?:
|
|
93
|
+
server?: _Schema40
|
|
94
|
+
path?: _Schema41
|
|
95
|
+
categories?: _Schema42
|
|
96
|
+
options?: _Schema44
|
|
97
97
|
})
|
|
98
|
-
export type
|
|
99
|
-
export type
|
|
100
|
-
export type
|
|
98
|
+
export type _Schema40 = string
|
|
99
|
+
export type _Schema41 = string
|
|
100
|
+
export type _Schema42 = {
|
|
101
101
|
label: string
|
|
102
102
|
tags: string[]
|
|
103
103
|
}[]
|
|
@@ -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
|
|
@@ -395,10 +396,10 @@ export interface FlatZudokuConfig {
|
|
|
395
396
|
modules?: {
|
|
396
397
|
docs?: _Schema14
|
|
397
398
|
landing?: _Schema16
|
|
398
|
-
userPanel?:
|
|
399
|
+
userPanel?: _Schema25
|
|
399
400
|
}
|
|
400
|
-
apis?: (
|
|
401
|
-
catalogs?: (
|
|
401
|
+
apis?: (_Schema38 | _Schema38[])
|
|
402
|
+
catalogs?: (_Schema45 | _Schema45[])
|
|
402
403
|
apiKeys?: {
|
|
403
404
|
enabled: boolean
|
|
404
405
|
getConsumers?: unknown
|
|
@@ -426,7 +427,7 @@ export interface FlatZudokuConfig {
|
|
|
426
427
|
}
|
|
427
428
|
enableStatusPages?: boolean
|
|
428
429
|
defaults?: {
|
|
429
|
-
apis:
|
|
430
|
+
apis: _Schema44
|
|
430
431
|
examplesLanguage?: string
|
|
431
432
|
}
|
|
432
433
|
__pluginDirs?: string[]
|
|
@@ -524,6 +525,10 @@ export interface _Schema16 {
|
|
|
524
525
|
*/
|
|
525
526
|
layout?: ("default" | "landing" | "none")
|
|
526
527
|
content?: _Schema21
|
|
528
|
+
/**
|
|
529
|
+
* When true, merge DEFAULT_LANDING_CONTENT for unset sections. When false or omitted, only configured sections are shown.
|
|
530
|
+
*/
|
|
531
|
+
useDefaults?: boolean
|
|
527
532
|
}
|
|
528
533
|
/**
|
|
529
534
|
* Default landing page content when no custom component is provided.
|
|
@@ -531,25 +536,77 @@ export interface _Schema16 {
|
|
|
531
536
|
export interface _Schema21 {
|
|
532
537
|
hero?: {
|
|
533
538
|
title?: string
|
|
539
|
+
/**
|
|
540
|
+
* Accent-colored word or phrase shown on a new line after the title.
|
|
541
|
+
*/
|
|
542
|
+
titleAccent?: string
|
|
534
543
|
subtitle?: string
|
|
535
544
|
description?: string
|
|
545
|
+
badge?: string
|
|
546
|
+
highlights?: string[]
|
|
547
|
+
codeExample?: {
|
|
548
|
+
filename?: string
|
|
549
|
+
language?: string
|
|
550
|
+
code: string
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
featuresSection?: {
|
|
554
|
+
label?: string
|
|
555
|
+
title?: string
|
|
556
|
+
description?: string
|
|
536
557
|
}
|
|
537
558
|
features?: {
|
|
538
559
|
title: string
|
|
539
560
|
description: string
|
|
561
|
+
icon?: string
|
|
540
562
|
}[]
|
|
541
|
-
primaryAction?:
|
|
542
|
-
secondaryAction?:
|
|
563
|
+
primaryAction?: _Schema23
|
|
564
|
+
secondaryAction?: _Schema23
|
|
565
|
+
ticker?: {
|
|
566
|
+
enabled?: boolean
|
|
567
|
+
items?: {
|
|
568
|
+
label: string
|
|
569
|
+
value: string
|
|
570
|
+
change?: string
|
|
571
|
+
}[]
|
|
572
|
+
}
|
|
573
|
+
trust?: {
|
|
574
|
+
enabled?: boolean
|
|
575
|
+
title?: string
|
|
576
|
+
logos?: string[]
|
|
577
|
+
}
|
|
578
|
+
apiShowcase?: {
|
|
579
|
+
enabled?: boolean
|
|
580
|
+
title?: string
|
|
581
|
+
subtitle?: string
|
|
582
|
+
browseAllHref?: string
|
|
583
|
+
browseAllLabel?: string
|
|
584
|
+
autoFromOpenApi?: boolean
|
|
585
|
+
items?: {
|
|
586
|
+
name: string
|
|
587
|
+
path: string
|
|
588
|
+
icon?: string
|
|
589
|
+
latency?: string
|
|
590
|
+
summary?: string
|
|
591
|
+
}[]
|
|
592
|
+
}
|
|
593
|
+
pricingCta?: {
|
|
594
|
+
enabled?: boolean
|
|
595
|
+
title?: string
|
|
596
|
+
description?: string
|
|
597
|
+
primaryAction?: _Schema23
|
|
598
|
+
secondaryAction?: _Schema23
|
|
599
|
+
}
|
|
543
600
|
showPoweredBy?: boolean
|
|
544
601
|
}
|
|
545
|
-
export interface
|
|
602
|
+
export interface _Schema23 {
|
|
546
603
|
label: string
|
|
547
604
|
href: string
|
|
548
605
|
}
|
|
549
606
|
/**
|
|
550
607
|
* User panel module — authenticated user area with sub-modules.
|
|
551
608
|
*/
|
|
552
|
-
export interface
|
|
609
|
+
export interface _Schema25 {
|
|
553
610
|
enabled?: _Schema15
|
|
554
611
|
/**
|
|
555
612
|
* Base path prefix for user panel routes.
|
|
@@ -571,7 +628,7 @@ export interface _Schema23 {
|
|
|
571
628
|
component?: {
|
|
572
629
|
[k: string]: unknown
|
|
573
630
|
}
|
|
574
|
-
content?:
|
|
631
|
+
content?: _Schema32
|
|
575
632
|
}
|
|
576
633
|
userManagement?: {
|
|
577
634
|
enabled?: _Schema15
|
|
@@ -591,7 +648,7 @@ export interface _Schema23 {
|
|
|
591
648
|
component?: {
|
|
592
649
|
[k: string]: unknown
|
|
593
650
|
}
|
|
594
|
-
content?:
|
|
651
|
+
content?: _Schema33
|
|
595
652
|
}
|
|
596
653
|
apiKeys?: {
|
|
597
654
|
enabled?: _Schema15
|
|
@@ -616,30 +673,30 @@ export interface _Schema23 {
|
|
|
616
673
|
component?: {
|
|
617
674
|
[k: string]: unknown
|
|
618
675
|
}
|
|
619
|
-
content?:
|
|
620
|
-
items?:
|
|
676
|
+
content?: _Schema36
|
|
677
|
+
items?: _Schema37
|
|
621
678
|
}
|
|
622
679
|
}
|
|
623
680
|
/**
|
|
624
681
|
* Default dashboard page content when no custom component is provided.
|
|
625
682
|
*/
|
|
626
|
-
export interface
|
|
683
|
+
export interface _Schema32 {
|
|
627
684
|
title?: string
|
|
628
685
|
subtitle?: string
|
|
629
686
|
description?: string
|
|
630
|
-
quickLinks?:
|
|
687
|
+
quickLinks?: _Schema23[]
|
|
631
688
|
}
|
|
632
689
|
/**
|
|
633
690
|
* Default profile page content when no custom component is provided.
|
|
634
691
|
*/
|
|
635
|
-
export interface
|
|
692
|
+
export interface _Schema33 {
|
|
636
693
|
title?: string
|
|
637
694
|
description?: string
|
|
638
695
|
}
|
|
639
696
|
/**
|
|
640
697
|
* Default plans page content when no custom component is provided.
|
|
641
698
|
*/
|
|
642
|
-
export interface
|
|
699
|
+
export interface _Schema36 {
|
|
643
700
|
title?: string
|
|
644
701
|
description?: string
|
|
645
702
|
tiers?: {
|
|
@@ -648,12 +705,12 @@ export interface _Schema34 {
|
|
|
648
705
|
description?: string
|
|
649
706
|
}[]
|
|
650
707
|
}
|
|
651
|
-
export interface
|
|
708
|
+
export interface _Schema39 {
|
|
652
709
|
path: string
|
|
653
710
|
input: string
|
|
654
711
|
label?: string
|
|
655
712
|
}
|
|
656
|
-
export interface
|
|
713
|
+
export interface _Schema44 {
|
|
657
714
|
examplesLanguage?: string
|
|
658
715
|
supportedLanguages?: {
|
|
659
716
|
value: string
|
|
@@ -670,7 +727,7 @@ export interface _Schema42 {
|
|
|
670
727
|
transformExamples?: unknown
|
|
671
728
|
generateCodeSnippet?: unknown
|
|
672
729
|
}
|
|
673
|
-
export interface
|
|
730
|
+
export interface _Schema45 {
|
|
674
731
|
path: string
|
|
675
732
|
label: string
|
|
676
733
|
items?: string[]
|
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 = [
|
|
@@ -542,6 +543,18 @@ export function deriveManifestFields(
|
|
|
542
543
|
}
|
|
543
544
|
continue;
|
|
544
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
|
+
}
|
|
545
558
|
if (normalized[key] !== undefined) {
|
|
546
559
|
manifest[key] = normalized[key];
|
|
547
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
|
+
}
|
|
@@ -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,154 @@
|
|
|
1
|
+
import type { ResolvedLandingContent } from "./validators/ModulesSchema.js";
|
|
2
|
+
|
|
3
|
+
/** Default landing sections aligned with webapp-template/APIToGo.dc.html */
|
|
4
|
+
export const DEFAULT_LANDING_CONTENT: ResolvedLandingContent = {
|
|
5
|
+
hero: {
|
|
6
|
+
badge: "99.98% uptime",
|
|
7
|
+
subtitle: "Production-ready APIs, one endpoint away",
|
|
8
|
+
title: "Developer APIs that ship",
|
|
9
|
+
titleAccent: "today.",
|
|
10
|
+
description:
|
|
11
|
+
"Documentation, interactive API reference, auth, and billing — sub-40ms responses, one API key, generous free tier.",
|
|
12
|
+
highlights: ["No credit card", "10k requests free", "SDKs for 6 languages"],
|
|
13
|
+
codeExample: {
|
|
14
|
+
filename: "example.sh",
|
|
15
|
+
language: "bash",
|
|
16
|
+
code: `$ curl https://api.example.com/v1/items \\
|
|
17
|
+
-H "Authorization: Bearer YOUR_API_KEY" \\
|
|
18
|
+
-d "limit=10"
|
|
19
|
+
|
|
20
|
+
{
|
|
21
|
+
"items": [
|
|
22
|
+
{ "id": "item_1", "name": "Widget", "status": "active" },
|
|
23
|
+
{ "id": "item_2", "name": "Gadget", "status": "active" }
|
|
24
|
+
]
|
|
25
|
+
} // 28ms`,
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
primaryAction: { label: "Get your API key", href: "/register" },
|
|
29
|
+
secondaryAction: { label: "Read the docs", href: "/introduction" },
|
|
30
|
+
ticker: {
|
|
31
|
+
items: [
|
|
32
|
+
{ label: "GET", value: "/v1/items", change: "28ms" },
|
|
33
|
+
{ label: "POST", value: "/v1/items", change: "34ms" },
|
|
34
|
+
{ label: "GET", value: "/v1/users", change: "19ms" },
|
|
35
|
+
{ label: "PATCH", value: "/v1/users/{id}", change: "41ms" },
|
|
36
|
+
{ label: "GET", value: "/v1/health", change: "16ms" },
|
|
37
|
+
{ label: "DELETE", value: "/v1/items/{id}", change: "22ms" },
|
|
38
|
+
{ label: "GET", value: "/v1/search", change: "47ms" },
|
|
39
|
+
{ label: "POST", value: "/v1/auth/token", change: "52ms" },
|
|
40
|
+
],
|
|
41
|
+
},
|
|
42
|
+
trust: {
|
|
43
|
+
title: "Trusted by teams shipping production APIs",
|
|
44
|
+
logos: ["Acme Corp", "BuildFast", "DataFlow", "ShipKit", "DevStack", "LaunchPad"],
|
|
45
|
+
},
|
|
46
|
+
featuresSection: {
|
|
47
|
+
label: "// why us",
|
|
48
|
+
title: "Everything you need to build on your API",
|
|
49
|
+
description:
|
|
50
|
+
"Skip the infra. We handle docs, auth, keys, and billing so you can focus on your product.",
|
|
51
|
+
},
|
|
52
|
+
features: [
|
|
53
|
+
{
|
|
54
|
+
icon: "gauge",
|
|
55
|
+
title: "Sub-40ms responses",
|
|
56
|
+
description:
|
|
57
|
+
"Edge-cached and globally distributed, so your app feels instant anywhere.",
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
icon: "radio",
|
|
61
|
+
title: "Real-time streams",
|
|
62
|
+
description:
|
|
63
|
+
"WebSocket feeds with automatic reconnection for live data and events.",
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
icon: "blocks",
|
|
67
|
+
title: "Unified schema",
|
|
68
|
+
description:
|
|
69
|
+
"One consistent API design — no per-endpoint glue code or custom adapters.",
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
icon: "shield-check",
|
|
73
|
+
title: "99.98% uptime",
|
|
74
|
+
description:
|
|
75
|
+
"Redundant infra with a public status page and an SLA on paid plans.",
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
icon: "code",
|
|
79
|
+
title: "SDKs for 6 languages",
|
|
80
|
+
description:
|
|
81
|
+
"Typed clients for JS, Python, Go, Rust, Ruby and PHP. Copy, paste, ship.",
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
icon: "webhook",
|
|
85
|
+
title: "Webhooks & alerts",
|
|
86
|
+
description:
|
|
87
|
+
"Push events and threshold alerts straight to your endpoints.",
|
|
88
|
+
},
|
|
89
|
+
],
|
|
90
|
+
apiShowcase: {
|
|
91
|
+
title: "Production APIs. One key.",
|
|
92
|
+
browseAllLabel: "Browse all APIs",
|
|
93
|
+
browseAllHref: "/api",
|
|
94
|
+
autoFromOpenApi: true,
|
|
95
|
+
items: [
|
|
96
|
+
{
|
|
97
|
+
name: "List resources",
|
|
98
|
+
path: "/v1/items",
|
|
99
|
+
icon: "boxes",
|
|
100
|
+
latency: "28ms",
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
name: "Create resource",
|
|
104
|
+
path: "/v1/items",
|
|
105
|
+
icon: "badge-dollar-sign",
|
|
106
|
+
latency: "34ms",
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
name: "Get resource",
|
|
110
|
+
path: "/v1/items/{id}",
|
|
111
|
+
icon: "layers",
|
|
112
|
+
latency: "19ms",
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
name: "Search",
|
|
116
|
+
path: "/v1/search",
|
|
117
|
+
icon: "arrow-left-right",
|
|
118
|
+
latency: "41ms",
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
name: "User profile",
|
|
122
|
+
path: "/v1/users/me",
|
|
123
|
+
icon: "wallet",
|
|
124
|
+
latency: "52ms",
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
name: "Health check",
|
|
128
|
+
path: "/v1/health",
|
|
129
|
+
icon: "activity",
|
|
130
|
+
latency: "16ms",
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
name: "Metadata",
|
|
134
|
+
path: "/v1/meta",
|
|
135
|
+
icon: "coins",
|
|
136
|
+
latency: "22ms",
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
name: "Rate limits",
|
|
140
|
+
path: "/v1/limits",
|
|
141
|
+
icon: "gauge",
|
|
142
|
+
latency: "16ms",
|
|
143
|
+
},
|
|
144
|
+
],
|
|
145
|
+
},
|
|
146
|
+
pricingCta: {
|
|
147
|
+
title: "Start free. Scale when you do.",
|
|
148
|
+
description:
|
|
149
|
+
"10,000 requests a month, free forever. Upgrade only when your app takes off.",
|
|
150
|
+
primaryAction: { label: "Create free account", href: "/register" },
|
|
151
|
+
secondaryAction: { label: "See pricing", href: "/pricing" },
|
|
152
|
+
},
|
|
153
|
+
showPoweredBy: true,
|
|
154
|
+
};
|
|
@@ -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
|
+
}
|