@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.
Files changed (47) hide show
  1. package/dist/cli/cli.js +686 -373
  2. package/dist/declarations/config/apitogo-config-core.d.ts +14 -4
  3. package/dist/declarations/config/landing-manifest.d.ts +76 -0
  4. package/dist/declarations/config/local-manifest.d.ts +91 -1
  5. package/dist/declarations/config/validators/ModulesSchema.d.ts +227 -0
  6. package/dist/declarations/config/validators/ZudokuConfig.d.ts +84 -2
  7. package/dist/declarations/lib/authentication/header-nav-filter.d.ts +4 -0
  8. package/dist/declarations/lib/authentication/providers/dev-portal-auth-pages.d.ts +15 -0
  9. package/dist/declarations/lib/authentication/providers/dev-portal-constants.d.ts +19 -1
  10. package/dist/declarations/lib/authentication/providers/dev-portal-utils.d.ts +5 -1
  11. package/dist/declarations/lib/authentication/providers/dev-portal.d.ts +5 -0
  12. package/dist/declarations/lib/components/HeaderAuthActions.d.ts +3 -0
  13. package/dist/declarations/lib/components/SiteTitle.d.ts +6 -0
  14. package/dist/declarations/lib/components/ThemeSwitch.d.ts +3 -1
  15. package/dist/declarations/lib/components/TopNavigation.d.ts +1 -1
  16. package/dist/declarations/lib/core/ZudokuContext.d.ts +1 -0
  17. package/dist/flat-config.d.ts +61 -1
  18. package/package.json +1 -1
  19. package/src/app/main.css +2 -2
  20. package/src/config/apitogo-config-core.ts +140 -11
  21. package/src/config/apitogo-config-loader.browser.ts +17 -0
  22. package/src/config/apitogo-config-loader.node.ts +28 -8
  23. package/src/config/build-apitogo-config.ts +13 -4
  24. package/src/config/landing-manifest.ts +11 -0
  25. package/src/config/landing-openapi-showcase.ts +115 -0
  26. package/src/config/loader.ts +17 -3
  27. package/src/config/local-manifest.ts +146 -11
  28. package/src/config/resolve-modules.ts +22 -1
  29. package/src/config/validators/ModulesSchema.ts +84 -0
  30. package/src/config/validators/ZudokuConfig.ts +26 -1
  31. package/src/lib/authentication/auth-header-nav.ts +5 -15
  32. package/src/lib/authentication/header-nav-filter.ts +36 -0
  33. package/src/lib/authentication/providers/dev-portal-auth-pages.tsx +499 -0
  34. package/src/lib/authentication/providers/dev-portal-constants.ts +15 -1
  35. package/src/lib/authentication/providers/dev-portal-utils.ts +128 -3
  36. package/src/lib/authentication/providers/dev-portal.tsx +51 -6
  37. package/src/lib/components/Header.tsx +49 -39
  38. package/src/lib/components/HeaderAuthActions.tsx +36 -0
  39. package/src/lib/components/HeaderNavigation.tsx +11 -9
  40. package/src/lib/components/MobileTopNavigation.tsx +43 -24
  41. package/src/lib/components/SiteTitle.tsx +20 -0
  42. package/src/lib/components/ThemeSwitch.tsx +36 -2
  43. package/src/lib/components/TopNavigation.tsx +20 -33
  44. package/src/lib/core/ZudokuContext.ts +1 -0
  45. package/src/vite/config.ts +21 -0
  46. package/src/vite/plugin-auth.ts +4 -1
  47. package/src/vite/plugin-config.ts +39 -4
@@ -1,4 +1,4 @@
1
- import { access, readFile } from "node:fs/promises";
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";
@@ -55,8 +59,29 @@ export const DevPortalLocalManifestSchema = z.object({
55
59
  authentication: z
56
60
  .object({
57
61
  enabled: z.boolean().optional(),
58
- type: z.string().optional(),
59
62
  apiBaseUrl: z.string().optional(),
63
+ native: z
64
+ .object({
65
+ enabled: z.boolean().optional(),
66
+ })
67
+ .optional(),
68
+ email: z
69
+ .object({
70
+ provider: z.string().optional(),
71
+ from: z.string().optional(),
72
+ })
73
+ .optional(),
74
+ providers: z
75
+ .array(
76
+ z.object({
77
+ id: z.string().min(1),
78
+ displayName: z.string().optional(),
79
+ authority: z.string().optional(),
80
+ clientId: z.string().optional(),
81
+ scopes: z.string().optional(),
82
+ }),
83
+ )
84
+ .optional(),
60
85
  })
61
86
  .optional(),
62
87
  plans: z.array(ManifestPlanInputSchema).optional(),
@@ -81,12 +106,19 @@ export const DevPortalLocalManifestSchema = z.object({
81
106
  })
82
107
  .optional(),
83
108
  projectId: z.string().optional(),
109
+ landing: z
110
+ .object({
111
+ enabled: z.boolean().optional(),
112
+ content: LandingManifestContentSchema,
113
+ })
114
+ .optional(),
84
115
  });
85
116
 
86
117
  export type ManifestPlanInput = z.infer<typeof ManifestPlanInputSchema>;
87
118
  export type DevPortalLocalManifest = z.infer<
88
119
  typeof DevPortalLocalManifestSchema
89
120
  >;
121
+ export type { LandingManifestInput };
90
122
 
91
123
  export type DevPortalPreviewPlan = {
92
124
  sku: string;
@@ -116,6 +148,16 @@ export function isAuthenticationEnabled(
116
148
  return manifest?.authentication?.enabled === true;
117
149
  }
118
150
 
151
+ export function isNativeAuthEnabledInManifest(
152
+ manifest: DevPortalLocalManifest | null | undefined,
153
+ ): boolean {
154
+ if (!isAuthenticationEnabled(manifest)) {
155
+ return false;
156
+ }
157
+
158
+ return manifest?.authentication?.native?.enabled !== false;
159
+ }
160
+
119
161
  export function manifestToPreviewCatalog(
120
162
  manifest: DevPortalLocalManifest | null | undefined,
121
163
  ): DevPortalPreviewPlanCatalog {
@@ -182,6 +224,105 @@ export function mergePlansBySku(
182
224
  return Array.from(bySku.values());
183
225
  }
184
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
+
185
326
  export function mergeManifest(
186
327
  base: DevPortalLocalManifest,
187
328
  override: DevPortalLocalManifest,
@@ -209,6 +350,9 @@ export function mergeManifest(
209
350
  if (override.plans) {
210
351
  merged.plans = mergePlansBySku(base.plans, override.plans);
211
352
  }
353
+ if (override.landing) {
354
+ merged.landing = mergeLandingManifest(base.landing, override.landing);
355
+ }
212
356
 
213
357
  return merged;
214
358
  }
@@ -246,15 +390,6 @@ export function parseLocalManifestJson(
246
390
  }
247
391
  }
248
392
 
249
- async function fileExists(filePath: string): Promise<boolean> {
250
- try {
251
- await access(filePath);
252
- return true;
253
- } catch {
254
- return false;
255
- }
256
- }
257
-
258
393
  export async function readManifestFile(
259
394
  rootDir: string,
260
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
- return {
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
 
@@ -492,8 +492,32 @@ const AuthenticationSchema = z.discriminatedUnion("type", [
492
492
  redirectToAfterSignOut: z.string().optional(),
493
493
  }),
494
494
  z.object({
495
- type: z.literal("dev-portal"),
495
+ type: z.literal("dev-portal").optional().default("dev-portal"),
496
496
  apiBaseUrl: z.string().optional(),
497
+ native: z
498
+ .object({
499
+ enabled: z.boolean().optional(),
500
+ })
501
+ .optional(),
502
+ email: z
503
+ .object({
504
+ provider: z.string().optional(),
505
+ from: z.string().optional(),
506
+ connectionString: z.string().optional(),
507
+ })
508
+ .optional(),
509
+ providers: z
510
+ .array(
511
+ z.object({
512
+ id: z.string().min(1),
513
+ displayName: z.string().optional(),
514
+ authority: z.string().optional(),
515
+ clientId: z.string().optional(),
516
+ clientSecret: z.string().optional(),
517
+ scopes: z.string().optional(),
518
+ }),
519
+ )
520
+ .optional(),
497
521
  redirectToAfterSignUp: z.string().optional(),
498
522
  redirectToAfterSignIn: z.string().optional(),
499
523
  redirectToAfterSignOut: z.string().optional(),
@@ -558,6 +582,7 @@ const ThemeConfigSchema = z.object({
558
582
  const SiteSchema = z
559
583
  .object({
560
584
  title: z.string(),
585
+ showTitleInHeader: z.boolean().optional(),
561
586
  logoUrl: z.string(),
562
587
  dir: z.enum(["ltr", "rtl"]).optional(),
563
588
  logo: LogoSchema,
@@ -27,22 +27,12 @@ export function applyAuthenticationHeaderNav<T extends ZudokuConfig>(
27
27
  const authNavEnabled = config.__meta?.authenticationEnabled === true;
28
28
  const navigation = config.header?.navigation ?? [];
29
29
 
30
- if (!authNavEnabled) {
31
- const filtered = withoutLoginNavigation(navigation);
32
- if (filtered.length === navigation.length) {
33
- return config;
34
- }
35
-
36
- return {
37
- ...config,
38
- header: {
39
- ...config.header,
40
- navigation: filtered,
41
- },
42
- };
30
+ if (authNavEnabled) {
31
+ return config;
43
32
  }
44
33
 
45
- if (navigation.some((item) => "to" in item && item.to === LOGIN_NAV_PATH)) {
34
+ const filtered = withoutLoginNavigation(navigation);
35
+ if (filtered.length === navigation.length) {
46
36
  return config;
47
37
  }
48
38
 
@@ -50,7 +40,7 @@ export function applyAuthenticationHeaderNav<T extends ZudokuConfig>(
50
40
  ...config,
51
41
  header: {
52
42
  ...config.header,
53
- navigation: [...navigation, LOGIN_HEADER_NAV_ITEM],
43
+ navigation: filtered,
54
44
  },
55
45
  };
56
46
  }
@@ -0,0 +1,36 @@
1
+ import type { HeaderNavigation } from "../../config/validators/HeaderNavigationSchema.js";
2
+ import { LOGIN_NAV_PATH } from "./auth-header-nav.js";
3
+
4
+ const AUTH_HEADER_PATHS = new Set([
5
+ LOGIN_NAV_PATH,
6
+ "/signin",
7
+ "/login",
8
+ "/signup",
9
+ "/register",
10
+ ]);
11
+
12
+ const PRICING_HEADER_PATHS = new Set(["/pricing"]);
13
+
14
+ export function withoutAuthHeaderNavigation(
15
+ navigation: HeaderNavigation,
16
+ ): HeaderNavigation {
17
+ return navigation.filter(
18
+ (item) => !("to" in item) || !AUTH_HEADER_PATHS.has(item.to),
19
+ );
20
+ }
21
+
22
+ export function withoutPricingHeaderNavigation(
23
+ navigation: HeaderNavigation,
24
+ ): HeaderNavigation {
25
+ return navigation.filter(
26
+ (item) => !("to" in item) || !PRICING_HEADER_PATHS.has(item.to),
27
+ );
28
+ }
29
+
30
+ export function withoutMarketingCtaHeaderNavigation(
31
+ navigation: HeaderNavigation,
32
+ ): HeaderNavigation {
33
+ return withoutPricingHeaderNavigation(
34
+ withoutAuthHeaderNavigation(navigation),
35
+ );
36
+ }