@lukoweb/apitogo 0.1.54 → 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 +604 -376
- package/dist/declarations/config/landing-manifest.d.ts +76 -0
- package/dist/declarations/config/local-manifest.d.ts +76 -0
- package/dist/declarations/config/validators/ModulesSchema.d.ts +227 -0
- package/dist/declarations/config/validators/ZudokuConfig.d.ts +50 -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/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 +44 -0
- 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/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 +22 -1
- package/src/config/validators/ModulesSchema.ts +84 -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 +17 -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
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
|
|
@@ -533,13 +534,56 @@ export interface _Schema21 {
|
|
|
533
534
|
title?: string
|
|
534
535
|
subtitle?: string
|
|
535
536
|
description?: string
|
|
537
|
+
badge?: string
|
|
538
|
+
highlights?: string[]
|
|
539
|
+
codeExample?: {
|
|
540
|
+
filename?: string
|
|
541
|
+
language?: string
|
|
542
|
+
code: string
|
|
543
|
+
}
|
|
536
544
|
}
|
|
537
545
|
features?: {
|
|
538
546
|
title: string
|
|
539
547
|
description: string
|
|
548
|
+
icon?: string
|
|
540
549
|
}[]
|
|
541
550
|
primaryAction?: _Schema22
|
|
542
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
|
+
}
|
|
543
587
|
showPoweredBy?: boolean
|
|
544
588
|
}
|
|
545
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 = [
|
|
@@ -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,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) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { z } from "zod";
|
|
4
4
|
import {
|
|
@@ -8,6 +8,10 @@ import {
|
|
|
8
8
|
loadApitogoConfig,
|
|
9
9
|
readPlanCatalogItems,
|
|
10
10
|
} from "./apitogo-config-loader.js";
|
|
11
|
+
import {
|
|
12
|
+
type LandingManifestInput,
|
|
13
|
+
LandingManifestContentSchema,
|
|
14
|
+
} from "./landing-manifest.js";
|
|
11
15
|
|
|
12
16
|
/** @deprecated Legacy manifest filenames — apitogo.config.json is the only supported source. */
|
|
13
17
|
export const APITOGO_MANIFEST_BASE_FILENAME = "apitogo.json";
|
|
@@ -102,12 +106,19 @@ export const DevPortalLocalManifestSchema = z.object({
|
|
|
102
106
|
})
|
|
103
107
|
.optional(),
|
|
104
108
|
projectId: z.string().optional(),
|
|
109
|
+
landing: z
|
|
110
|
+
.object({
|
|
111
|
+
enabled: z.boolean().optional(),
|
|
112
|
+
content: LandingManifestContentSchema,
|
|
113
|
+
})
|
|
114
|
+
.optional(),
|
|
105
115
|
});
|
|
106
116
|
|
|
107
117
|
export type ManifestPlanInput = z.infer<typeof ManifestPlanInputSchema>;
|
|
108
118
|
export type DevPortalLocalManifest = z.infer<
|
|
109
119
|
typeof DevPortalLocalManifestSchema
|
|
110
120
|
>;
|
|
121
|
+
export type { LandingManifestInput };
|
|
111
122
|
|
|
112
123
|
export type DevPortalPreviewPlan = {
|
|
113
124
|
sku: string;
|
|
@@ -213,6 +224,105 @@ export function mergePlansBySku(
|
|
|
213
224
|
return Array.from(bySku.values());
|
|
214
225
|
}
|
|
215
226
|
|
|
227
|
+
export function mergeLandingContent(
|
|
228
|
+
existing: LandingManifestInput["content"] | undefined,
|
|
229
|
+
patch: LandingManifestInput["content"] | undefined,
|
|
230
|
+
): LandingManifestInput["content"] | undefined {
|
|
231
|
+
if (!patch) {
|
|
232
|
+
return existing;
|
|
233
|
+
}
|
|
234
|
+
if (!existing) {
|
|
235
|
+
return patch;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const merged = { ...existing, ...patch };
|
|
239
|
+
|
|
240
|
+
if (patch.hero) {
|
|
241
|
+
merged.hero = { ...existing.hero, ...patch.hero };
|
|
242
|
+
if (patch.hero.codeExample) {
|
|
243
|
+
merged.hero = {
|
|
244
|
+
...merged.hero,
|
|
245
|
+
codeExample: {
|
|
246
|
+
...existing.hero?.codeExample,
|
|
247
|
+
...patch.hero.codeExample,
|
|
248
|
+
},
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
if (patch.features) {
|
|
254
|
+
const byTitle = new Map(
|
|
255
|
+
(existing.features ?? []).map((feature) => [feature.title, feature]),
|
|
256
|
+
);
|
|
257
|
+
for (const feature of patch.features) {
|
|
258
|
+
const prior = byTitle.get(feature.title);
|
|
259
|
+
byTitle.set(feature.title, prior ? { ...prior, ...feature } : feature);
|
|
260
|
+
}
|
|
261
|
+
merged.features = Array.from(byTitle.values());
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
if (patch.ticker) {
|
|
265
|
+
merged.ticker = { ...existing.ticker, ...patch.ticker };
|
|
266
|
+
if (patch.ticker.items) {
|
|
267
|
+
merged.ticker.items = patch.ticker.items;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
if (patch.trust) {
|
|
272
|
+
merged.trust = { ...existing.trust, ...patch.trust };
|
|
273
|
+
if (patch.trust.logos) {
|
|
274
|
+
merged.trust.logos = patch.trust.logos;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
if (patch.apiShowcase) {
|
|
279
|
+
merged.apiShowcase = { ...existing.apiShowcase, ...patch.apiShowcase };
|
|
280
|
+
if (patch.apiShowcase.items) {
|
|
281
|
+
const byPath = new Map(
|
|
282
|
+
(existing.apiShowcase?.items ?? []).map((item) => [item.path, item]),
|
|
283
|
+
);
|
|
284
|
+
for (const item of patch.apiShowcase.items) {
|
|
285
|
+
const prior = byPath.get(item.path);
|
|
286
|
+
byPath.set(item.path, prior ? { ...prior, ...item } : item);
|
|
287
|
+
}
|
|
288
|
+
merged.apiShowcase.items = Array.from(byPath.values());
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
if (patch.pricingCta) {
|
|
293
|
+
merged.pricingCta = { ...existing.pricingCta, ...patch.pricingCta };
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
if (patch.primaryAction) {
|
|
297
|
+
merged.primaryAction = { ...existing.primaryAction, ...patch.primaryAction };
|
|
298
|
+
}
|
|
299
|
+
if (patch.secondaryAction) {
|
|
300
|
+
merged.secondaryAction = {
|
|
301
|
+
...existing.secondaryAction,
|
|
302
|
+
...patch.secondaryAction,
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
return merged;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
export function mergeLandingManifest(
|
|
310
|
+
existing: LandingManifestInput | undefined,
|
|
311
|
+
patch: LandingManifestInput | undefined,
|
|
312
|
+
): LandingManifestInput | undefined {
|
|
313
|
+
if (!patch) {
|
|
314
|
+
return existing;
|
|
315
|
+
}
|
|
316
|
+
if (!existing) {
|
|
317
|
+
return patch;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
return {
|
|
321
|
+
enabled: patch.enabled ?? existing.enabled,
|
|
322
|
+
content: mergeLandingContent(existing.content, patch.content),
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
|
|
216
326
|
export function mergeManifest(
|
|
217
327
|
base: DevPortalLocalManifest,
|
|
218
328
|
override: DevPortalLocalManifest,
|
|
@@ -240,6 +350,9 @@ export function mergeManifest(
|
|
|
240
350
|
if (override.plans) {
|
|
241
351
|
merged.plans = mergePlansBySku(base.plans, override.plans);
|
|
242
352
|
}
|
|
353
|
+
if (override.landing) {
|
|
354
|
+
merged.landing = mergeLandingManifest(base.landing, override.landing);
|
|
355
|
+
}
|
|
243
356
|
|
|
244
357
|
return merged;
|
|
245
358
|
}
|
|
@@ -277,15 +390,6 @@ export function parseLocalManifestJson(
|
|
|
277
390
|
}
|
|
278
391
|
}
|
|
279
392
|
|
|
280
|
-
async function fileExists(filePath: string): Promise<boolean> {
|
|
281
|
-
try {
|
|
282
|
-
await access(filePath);
|
|
283
|
-
return true;
|
|
284
|
-
} catch {
|
|
285
|
-
return false;
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
|
|
289
393
|
export async function readManifestFile(
|
|
290
394
|
rootDir: string,
|
|
291
395
|
filename: string,
|
|
@@ -57,7 +57,7 @@ export const resolveLandingContent = (
|
|
|
57
57
|
const siteTitle =
|
|
58
58
|
config.metadata?.applicationName ?? config.site?.title ?? "APIToGo";
|
|
59
59
|
|
|
60
|
-
|
|
60
|
+
const resolved: ResolvedLandingContent = {
|
|
61
61
|
hero: {
|
|
62
62
|
title: content?.hero?.title ?? siteTitle,
|
|
63
63
|
subtitle: content?.hero?.subtitle ?? "Developer portal",
|
|
@@ -65,6 +65,9 @@ export const resolveLandingContent = (
|
|
|
65
65
|
content?.hero?.description ??
|
|
66
66
|
config.metadata?.description ??
|
|
67
67
|
"Ship beautiful API documentation and developer experiences with APIToGo.",
|
|
68
|
+
badge: content?.hero?.badge,
|
|
69
|
+
highlights: content?.hero?.highlights,
|
|
70
|
+
codeExample: content?.hero?.codeExample,
|
|
68
71
|
},
|
|
69
72
|
features: content?.features ?? DEFAULT_LANDING_FEATURES,
|
|
70
73
|
primaryAction: content?.primaryAction ?? {
|
|
@@ -77,6 +80,24 @@ export const resolveLandingContent = (
|
|
|
77
80
|
},
|
|
78
81
|
showPoweredBy: content?.showPoweredBy,
|
|
79
82
|
};
|
|
83
|
+
|
|
84
|
+
if (content?.ticker && content.ticker.enabled !== false) {
|
|
85
|
+
resolved.ticker = content.ticker;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (content?.trust && content.trust.enabled !== false) {
|
|
89
|
+
resolved.trust = content.trust;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (content?.apiShowcase && content.apiShowcase.enabled !== false) {
|
|
93
|
+
resolved.apiShowcase = content.apiShowcase;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (content?.pricingCta && content.pricingCta.enabled !== false) {
|
|
97
|
+
resolved.pricingCta = content.pricingCta;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return resolved;
|
|
80
101
|
};
|
|
81
102
|
|
|
82
103
|
const DEFAULT_DASHBOARD_QUICK_LINKS: ResolvedDashboardContent["quickLinks"] = [
|
|
@@ -44,13 +44,76 @@ export const LandingLinkSchema = z.object({
|
|
|
44
44
|
export const LandingFeatureSchema = z.object({
|
|
45
45
|
title: z.string(),
|
|
46
46
|
description: z.string(),
|
|
47
|
+
icon: z.string().optional(),
|
|
47
48
|
});
|
|
48
49
|
|
|
50
|
+
export const LandingCodeExampleSchema = z
|
|
51
|
+
.object({
|
|
52
|
+
filename: z.string().optional(),
|
|
53
|
+
language: z.string().optional(),
|
|
54
|
+
code: z.string(),
|
|
55
|
+
})
|
|
56
|
+
.optional();
|
|
57
|
+
|
|
49
58
|
export const LandingHeroSchema = z
|
|
50
59
|
.object({
|
|
51
60
|
title: z.string().optional(),
|
|
52
61
|
subtitle: z.string().optional(),
|
|
53
62
|
description: z.string().optional(),
|
|
63
|
+
badge: z.string().optional(),
|
|
64
|
+
highlights: z.array(z.string()).optional(),
|
|
65
|
+
codeExample: LandingCodeExampleSchema,
|
|
66
|
+
})
|
|
67
|
+
.optional();
|
|
68
|
+
|
|
69
|
+
export const LandingTickerItemSchema = z.object({
|
|
70
|
+
label: z.string(),
|
|
71
|
+
value: z.string(),
|
|
72
|
+
change: z.string().optional(),
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
export const LandingTickerSchema = z
|
|
76
|
+
.object({
|
|
77
|
+
enabled: z.boolean().optional(),
|
|
78
|
+
items: z.array(LandingTickerItemSchema).optional(),
|
|
79
|
+
})
|
|
80
|
+
.optional();
|
|
81
|
+
|
|
82
|
+
export const LandingTrustSchema = z
|
|
83
|
+
.object({
|
|
84
|
+
enabled: z.boolean().optional(),
|
|
85
|
+
title: z.string().optional(),
|
|
86
|
+
logos: z.array(z.string()).optional(),
|
|
87
|
+
})
|
|
88
|
+
.optional();
|
|
89
|
+
|
|
90
|
+
export const LandingApiShowcaseItemSchema = z.object({
|
|
91
|
+
name: z.string(),
|
|
92
|
+
path: z.string(),
|
|
93
|
+
icon: z.string().optional(),
|
|
94
|
+
latency: z.string().optional(),
|
|
95
|
+
summary: z.string().optional(),
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
export const LandingApiShowcaseSchema = z
|
|
99
|
+
.object({
|
|
100
|
+
enabled: z.boolean().optional(),
|
|
101
|
+
title: z.string().optional(),
|
|
102
|
+
subtitle: z.string().optional(),
|
|
103
|
+
browseAllHref: z.string().optional(),
|
|
104
|
+
browseAllLabel: z.string().optional(),
|
|
105
|
+
autoFromOpenApi: z.boolean().optional(),
|
|
106
|
+
items: z.array(LandingApiShowcaseItemSchema).optional(),
|
|
107
|
+
})
|
|
108
|
+
.optional();
|
|
109
|
+
|
|
110
|
+
export const LandingPricingCtaSchema = z
|
|
111
|
+
.object({
|
|
112
|
+
enabled: z.boolean().optional(),
|
|
113
|
+
title: z.string().optional(),
|
|
114
|
+
description: z.string().optional(),
|
|
115
|
+
primaryAction: LandingLinkSchema.optional(),
|
|
116
|
+
secondaryAction: LandingLinkSchema.optional(),
|
|
54
117
|
})
|
|
55
118
|
.optional();
|
|
56
119
|
|
|
@@ -60,6 +123,10 @@ export const LandingContentSchema = z
|
|
|
60
123
|
features: z.array(LandingFeatureSchema).optional(),
|
|
61
124
|
primaryAction: LandingLinkSchema.optional(),
|
|
62
125
|
secondaryAction: LandingLinkSchema.optional(),
|
|
126
|
+
ticker: LandingTickerSchema,
|
|
127
|
+
trust: LandingTrustSchema,
|
|
128
|
+
apiShowcase: LandingApiShowcaseSchema,
|
|
129
|
+
pricingCta: LandingPricingCtaSchema,
|
|
63
130
|
showPoweredBy: z.boolean().optional(),
|
|
64
131
|
})
|
|
65
132
|
.optional();
|
|
@@ -246,15 +313,32 @@ export type ProfileContentConfig = z.infer<typeof ProfileContentSchema>;
|
|
|
246
313
|
export type PlansContentConfig = z.infer<typeof PlansContentSchema>;
|
|
247
314
|
export type PlanTierConfig = z.infer<typeof PlanTierSchema>;
|
|
248
315
|
|
|
316
|
+
export type LandingCodeExampleConfig = z.infer<typeof LandingCodeExampleSchema>;
|
|
317
|
+
export type LandingTickerItemConfig = z.infer<typeof LandingTickerItemSchema>;
|
|
318
|
+
export type LandingTickerConfig = z.infer<typeof LandingTickerSchema>;
|
|
319
|
+
export type LandingTrustConfig = z.infer<typeof LandingTrustSchema>;
|
|
320
|
+
export type LandingApiShowcaseItemConfig = z.infer<
|
|
321
|
+
typeof LandingApiShowcaseItemSchema
|
|
322
|
+
>;
|
|
323
|
+
export type LandingApiShowcaseConfig = z.infer<typeof LandingApiShowcaseSchema>;
|
|
324
|
+
export type LandingPricingCtaConfig = z.infer<typeof LandingPricingCtaSchema>;
|
|
325
|
+
|
|
249
326
|
export type ResolvedLandingContent = {
|
|
250
327
|
hero?: {
|
|
251
328
|
title?: string;
|
|
252
329
|
subtitle?: string;
|
|
253
330
|
description?: string;
|
|
331
|
+
badge?: string;
|
|
332
|
+
highlights?: string[];
|
|
333
|
+
codeExample?: NonNullable<LandingCodeExampleConfig>;
|
|
254
334
|
};
|
|
255
335
|
features?: LandingFeatureConfig[];
|
|
256
336
|
primaryAction?: LandingLinkConfig;
|
|
257
337
|
secondaryAction?: LandingLinkConfig;
|
|
338
|
+
ticker?: LandingTickerConfig;
|
|
339
|
+
trust?: LandingTrustConfig;
|
|
340
|
+
apiShowcase?: LandingApiShowcaseConfig;
|
|
341
|
+
pricingCta?: LandingPricingCtaConfig;
|
|
258
342
|
showPoweredBy?: boolean;
|
|
259
343
|
};
|
|
260
344
|
|