@lukoweb/apitogo 0.1.51 → 0.1.54

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 (37) hide show
  1. package/dist/cli/cli.js +467 -197
  2. package/dist/declarations/config/apitogo-config-core.d.ts +35 -0
  3. package/dist/declarations/config/apitogo-config-loader.d.ts +2 -7
  4. package/dist/declarations/config/apitogo-config-loader.node.d.ts +4 -0
  5. package/dist/declarations/config/build-apitogo-config.d.ts +5 -4
  6. package/dist/declarations/config/loader.d.ts +2 -0
  7. package/dist/declarations/config/local-manifest.d.ts +22 -8
  8. package/dist/declarations/config/validators/ModulesSchema.d.ts +38 -0
  9. package/dist/declarations/config/validators/ZudokuConfig.d.ts +43 -2
  10. package/dist/declarations/index.d.ts +1 -1
  11. package/dist/declarations/lib/authentication/providers/dev-portal-auth-pages.d.ts +15 -0
  12. package/dist/declarations/lib/authentication/providers/dev-portal-constants.d.ts +19 -1
  13. package/dist/declarations/lib/authentication/providers/dev-portal-utils.d.ts +5 -1
  14. package/dist/declarations/lib/authentication/providers/dev-portal.d.ts +5 -0
  15. package/dist/declarations/lib/core/plugins.d.ts +1 -0
  16. package/dist/flat-config.d.ts +53 -24
  17. package/docs/configuration/authentication.md +11 -9
  18. package/docs/configuration/manifest.md +75 -74
  19. package/package.json +1 -1
  20. package/src/config/apitogo-config-core.ts +601 -0
  21. package/src/config/apitogo-config-loader.node.ts +88 -0
  22. package/src/config/apitogo-config-loader.ts +6 -224
  23. package/src/config/build-apitogo-config.ts +26 -17
  24. package/src/config/loader.ts +23 -2
  25. package/src/config/local-manifest.ts +58 -59
  26. package/src/config/resolve-modules.ts +16 -13
  27. package/src/config/validators/ModulesSchema.ts +17 -0
  28. package/src/config/validators/ZudokuConfig.ts +25 -1
  29. package/src/index.ts +3 -2
  30. package/src/lib/authentication/providers/dev-portal-auth-pages.tsx +439 -0
  31. package/src/lib/authentication/providers/dev-portal-constants.ts +15 -1
  32. package/src/lib/authentication/providers/dev-portal-utils.ts +124 -4
  33. package/src/lib/authentication/providers/dev-portal.tsx +41 -6
  34. package/src/lib/core/plugins.ts +1 -0
  35. package/src/vite/dev-portal-env.ts +12 -4
  36. package/src/vite/plugin-config.ts +20 -3
  37. package/src/vite/plugin-local-manifest.ts +15 -0
@@ -1,224 +1,6 @@
1
- import { existsSync, readFileSync } from "node:fs";
2
- import path from "node:path";
3
-
4
- export const APITOGO_CONFIG_FILENAME = "apitogo.config.json";
5
-
6
- const ENV_PREFIX = "APITOGO_CONFIG_";
7
-
8
- const MANIFEST_KEYS = [
9
- "siteName",
10
- "branding",
11
- "pricing",
12
- "authentication",
13
- "plans",
14
- "backend",
15
- "gateway",
16
- "projectId",
17
- "projectName",
18
- ] as const;
19
-
20
- const SITE_CONFIG_KEYS = [
21
- "site",
22
- "metadata",
23
- "docs",
24
- "modules",
25
- "navigation",
26
- "apis",
27
- "redirects",
28
- "theme",
29
- "footer",
30
- "search",
31
- "llms",
32
- "protectedRoutes",
33
- "slots",
34
- "mdx",
35
- "apiKeys",
36
- "catalog",
37
- "versions",
38
- "options",
39
- ] as const;
40
-
41
- export type ApitogoJsonConfig = Record<string, unknown>;
42
-
43
- function parseEnvValue(raw: string | undefined): unknown {
44
- if (raw === undefined) {
45
- return raw;
46
- }
47
-
48
- const trimmed = raw.trim();
49
- if (trimmed === "true") {
50
- return true;
51
- }
52
- if (trimmed === "false") {
53
- return false;
54
- }
55
- if (trimmed === "null") {
56
- return null;
57
- }
58
-
59
- if (/^-?\d+(\.\d+)?$/.test(trimmed)) {
60
- return Number(trimmed);
61
- }
62
-
63
- if (
64
- (trimmed.startsWith("{") && trimmed.endsWith("}")) ||
65
- (trimmed.startsWith("[") && trimmed.endsWith("]"))
66
- ) {
67
- try {
68
- return JSON.parse(trimmed) as unknown;
69
- } catch {
70
- return trimmed;
71
- }
72
- }
73
-
74
- return trimmed;
75
- }
76
-
77
- function setNestedValue(
78
- target: Record<string, unknown>,
79
- pathParts: string[],
80
- value: unknown,
81
- ): void {
82
- if (pathParts.length === 0) {
83
- return;
84
- }
85
-
86
- let current: Record<string, unknown> = target;
87
- for (let index = 0; index < pathParts.length - 1; index += 1) {
88
- const key = pathParts[index];
89
- if (!key) {
90
- return;
91
- }
92
- const next = current[key];
93
- if (
94
- next === undefined ||
95
- next === null ||
96
- typeof next !== "object" ||
97
- Array.isArray(next)
98
- ) {
99
- current[key] = {};
100
- }
101
- current = current[key] as Record<string, unknown>;
102
- }
103
-
104
- const leafKey = pathParts[pathParts.length - 1];
105
- if (!leafKey) {
106
- return;
107
- }
108
-
109
- current[leafKey] = value;
110
- }
111
-
112
- export function applyEnvOverrides(
113
- config: ApitogoJsonConfig,
114
- env: NodeJS.ProcessEnv = process.env,
115
- ): ApitogoJsonConfig {
116
- const result = structuredClone(config);
117
-
118
- for (const [key, value] of Object.entries(env)) {
119
- if (!key.startsWith(ENV_PREFIX) || value === undefined) {
120
- continue;
121
- }
122
-
123
- const pathParts = key
124
- .slice(ENV_PREFIX.length)
125
- .split("__")
126
- .filter(Boolean);
127
-
128
- if (pathParts.length === 0) {
129
- continue;
130
- }
131
-
132
- setNestedValue(result, pathParts, parseEnvValue(value));
133
- }
134
-
135
- return result;
136
- }
137
-
138
- export function loadApitogoConfig(rootDir = process.cwd()): ApitogoJsonConfig {
139
- const configPath = path.join(path.resolve(rootDir), APITOGO_CONFIG_FILENAME);
140
-
141
- if (!existsSync(configPath)) {
142
- throw new Error(
143
- `Missing ${APITOGO_CONFIG_FILENAME} in ${path.resolve(rootDir)}`,
144
- );
145
- }
146
-
147
- const raw = readFileSync(configPath, "utf8");
148
- const parsed = JSON.parse(raw) as ApitogoJsonConfig;
149
- return applyEnvOverrides(parsed, process.env);
150
- }
151
-
152
- export function extractManifest(
153
- config: ApitogoJsonConfig | null | undefined,
154
- ): ApitogoJsonConfig | null {
155
- if (!config || typeof config !== "object") {
156
- return null;
157
- }
158
-
159
- const manifest: ApitogoJsonConfig = {};
160
- for (const key of MANIFEST_KEYS) {
161
- if (config[key] !== undefined) {
162
- manifest[key] = config[key];
163
- }
164
- }
165
-
166
- const authentication = manifest.authentication;
167
- if (authentication && typeof authentication === "object") {
168
- const { enabled } = authentication as { enabled?: boolean };
169
- manifest.authentication =
170
- enabled === undefined ? undefined : { enabled };
171
- }
172
-
173
- return Object.keys(manifest).length > 0 ? manifest : null;
174
- }
175
-
176
- export function extractSiteConfig(
177
- config: ApitogoJsonConfig | null | undefined,
178
- ): ApitogoJsonConfig {
179
- if (!config || typeof config !== "object") {
180
- return {};
181
- }
182
-
183
- const siteConfig: ApitogoJsonConfig = {};
184
- for (const key of SITE_CONFIG_KEYS) {
185
- if (config[key] !== undefined) {
186
- siteConfig[key] = config[key];
187
- }
188
- }
189
-
190
- const auth = config.authentication;
191
- if (auth && typeof auth === "object") {
192
- const authRecord = auth as Record<string, unknown>;
193
- if (authRecord.type) {
194
- const { enabled: _enabled, ...authConfig } = authRecord;
195
- siteConfig.authentication = authConfig;
196
- }
197
- }
198
-
199
- const site = siteConfig.site;
200
- const branding = config.branding;
201
- const title =
202
- (site &&
203
- typeof site === "object" &&
204
- (site as Record<string, unknown>).title) ??
205
- (branding &&
206
- typeof branding === "object" &&
207
- (branding as Record<string, unknown>).title) ??
208
- config.siteName;
209
-
210
- if (title) {
211
- siteConfig.site = {
212
- ...(site && typeof site === "object"
213
- ? (site as Record<string, unknown>)
214
- : {}),
215
- title,
216
- };
217
- }
218
-
219
- return siteConfig;
220
- }
221
-
222
- export function apitogoConfigPath(rootDir: string): string {
223
- return path.join(path.resolve(rootDir), APITOGO_CONFIG_FILENAME);
224
- }
1
+ export * from "./apitogo-config-core.js";
2
+ export {
3
+ apitogoConfigPath,
4
+ loadApitogoConfig,
5
+ readAuthenticationSecrets,
6
+ } from "./apitogo-config-loader.node.js";
@@ -1,43 +1,52 @@
1
- import type { ApitogoConfig } from "./config.js";
1
+ import type { ApitogoPlugin } from "../lib/core/plugins.js";
2
2
  import {
3
3
  extractSiteConfig,
4
- loadApitogoConfig,
5
- } from "./apitogo-config-loader.js";
4
+ normalizeApitogoConfig,
5
+ type ApitogoJsonConfig,
6
+ } from "./apitogo-config-core.js";
7
+ import type { ApitogoConfig } from "./config.js";
6
8
 
7
- type BuildApitogoConfigOptions = {
8
- rootDir?: string;
9
- overrides?: Partial<ApitogoConfig>;
10
- };
9
+ type BuildApitogoConfigOptions = Partial<ApitogoConfig> &
10
+ ApitogoJsonConfig & {
11
+ plugins?: ApitogoPlugin[];
12
+ };
11
13
 
12
14
  export function buildApitogoConfig({
13
- rootDir,
14
- overrides = {},
15
+ plugins,
16
+ plans: _legacyPlans,
17
+ ...jsonOverrides
15
18
  }: BuildApitogoConfigOptions = {}): ApitogoConfig {
16
- const jsonConfig = loadApitogoConfig(rootDir);
19
+ const jsonConfig = normalizeApitogoConfig({
20
+ ...jsonOverrides,
21
+ ...(_legacyPlans !== undefined ? { plans: _legacyPlans } : {}),
22
+ } as ApitogoJsonConfig);
17
23
  const siteConfig = extractSiteConfig(jsonConfig);
24
+ const { plans: _rootPlans, ...restOverrides } =
25
+ jsonOverrides as ApitogoJsonConfig;
18
26
 
19
27
  return {
20
28
  ...siteConfig,
21
- ...overrides,
29
+ ...restOverrides,
30
+ plugins,
22
31
  authentication: {
23
32
  ...(siteConfig.authentication as ApitogoConfig["authentication"]),
24
- ...overrides.authentication,
33
+ ...jsonOverrides.authentication,
25
34
  },
26
35
  site: {
27
36
  ...(siteConfig.site as ApitogoConfig["site"]),
28
- ...overrides.site,
37
+ ...jsonOverrides.site,
29
38
  },
30
39
  metadata: {
31
40
  ...(siteConfig.metadata as ApitogoConfig["metadata"]),
32
- ...overrides.metadata,
41
+ ...jsonOverrides.metadata,
33
42
  },
34
43
  docs: {
35
44
  ...(siteConfig.docs as ApitogoConfig["docs"]),
36
- ...overrides.docs,
45
+ ...jsonOverrides.docs,
37
46
  },
38
47
  modules: {
39
- ...(siteConfig.modules as ApitogoConfig["modules"]),
40
- ...overrides.modules,
48
+ ...(jsonConfig.modules as ApitogoConfig["modules"]),
49
+ ...jsonOverrides.modules,
41
50
  },
42
51
  } as ApitogoConfig;
43
52
  }
@@ -13,13 +13,21 @@ import { getZudokuRootDir } from "../cli/common/package-json.js";
13
13
  import { applyAuthenticationHeaderNav } from "../lib/authentication/auth-header-nav.js";
14
14
  import { runPluginTransformConfig } from "../lib/core/transform-config.js";
15
15
  import invariant from "../lib/util/invariant.js";
16
+ import {
17
+ isBillingEnabled,
18
+ loadApitogoConfig,
19
+ readPlanCatalogItems,
20
+ } from "./apitogo-config-loader.js";
16
21
  import { fileExists } from "./file-exists.js";
17
22
  import {
18
23
  isAuthenticationEnabled,
19
- isPricingEnabled,
20
24
  manifestPathsForEnv,
25
+ manifestToPreviewCatalog,
26
+ plansToPreviewCatalog,
21
27
  readEffectiveManifest,
22
28
  resolveManifestEnv,
29
+ type DevPortalPreviewPlan,
30
+ type ManifestPlanInput,
23
31
  } from "./local-manifest.js";
24
32
  import { resolveModulesConfig } from "./resolve-modules.js";
25
33
  import type { ResolvedModulesConfig } from "./validators/ModulesSchema.js";
@@ -35,6 +43,7 @@ export type ConfigWithMeta = ZudokuConfig & {
35
43
  dependencies: string[];
36
44
  pricingEnabled?: boolean;
37
45
  authenticationEnabled?: boolean;
46
+ previewPlans?: DevPortalPreviewPlan[];
38
47
  };
39
48
  __resolvedModules: ResolvedModulesConfig;
40
49
  };
@@ -254,12 +263,20 @@ export async function loadZudokuConfig(
254
263
  const loadedConfig = await loadZudokuConfigWithMeta(rootDir);
255
264
  const manifestEnv = resolveManifestEnv(configEnv.mode);
256
265
  const manifest = await readEffectiveManifest(rootDir, manifestEnv);
266
+ const previewCatalog = manifestToPreviewCatalog(manifest);
267
+ const jsonConfig = loadApitogoConfig(rootDir);
268
+ const previewPlans = previewCatalog.plans.length
269
+ ? previewCatalog.plans
270
+ : plansToPreviewCatalog(
271
+ readPlanCatalogItems(jsonConfig) as ManifestPlanInput[],
272
+ ).plans;
257
273
  const configWithManifestMeta: ConfigWithMeta = {
258
274
  ...loadedConfig,
259
275
  __meta: {
260
276
  ...loadedConfig.__meta,
261
- pricingEnabled: isPricingEnabled(manifest),
277
+ pricingEnabled: isBillingEnabled(jsonConfig),
262
278
  authenticationEnabled: isAuthenticationEnabled(manifest),
279
+ previewPlans,
263
280
  },
264
281
  };
265
282
  const transformedConfig = applyAuthenticationHeaderNav(
@@ -267,6 +284,10 @@ export async function loadZudokuConfig(
267
284
  );
268
285
  config = {
269
286
  ...transformedConfig,
287
+ __meta: {
288
+ ...transformedConfig.__meta,
289
+ ...configWithManifestMeta.__meta,
290
+ },
270
291
  __resolvedModules: resolveModulesConfig(transformedConfig),
271
292
  };
272
293
 
@@ -4,16 +4,15 @@ import { z } from "zod";
4
4
  import {
5
5
  APITOGO_CONFIG_FILENAME,
6
6
  extractManifest,
7
+ isBillingEnabled,
7
8
  loadApitogoConfig,
9
+ readPlanCatalogItems,
8
10
  } from "./apitogo-config-loader.js";
9
11
 
10
- /** @deprecated Use {@link APITOGO_CONFIG_FILENAME}. */
11
- export const DEV_PORTAL_MANIFEST_FILENAME = "apitogo.local.json";
12
-
13
- /** @deprecated Use {@link APITOGO_CONFIG_FILENAME}. */
12
+ /** @deprecated Legacy manifest filenames — apitogo.config.json is the only supported source. */
14
13
  export const APITOGO_MANIFEST_BASE_FILENAME = "apitogo.json";
15
14
 
16
- /** @deprecated Per-environment manifest overrides were replaced by apitogo.config.json + .env. */
15
+ /** @deprecated Legacy per-environment manifest overrides. */
17
16
  export const APITOGO_MANIFEST_ENV_FILES = {
18
17
  development: "apitogo.local.json",
19
18
  production: "apitogo.prod.json",
@@ -28,14 +27,18 @@ const BillingPeriodSchema = z.enum(["month", "year", "monthly", "annual"]);
28
27
  export const ManifestPlanInputSchema = z.object({
29
28
  sku: z.string().min(1),
30
29
  displayName: z.string().min(1),
31
- isFree: z.boolean(),
32
- priceAmount: z.number().optional(),
30
+ priceAmount: z.number().default(0),
33
31
  priceCurrency: z.string().optional(),
34
32
  billingPeriod: BillingPeriodSchema.optional(),
35
33
  limitsJson: z.string().optional(),
36
34
  includedActionKeys: z.array(z.string()).optional(),
37
35
  });
38
36
 
37
+ /** Free plans have zero price; paid plans have priceAmount greater than zero. */
38
+ export function isPlanFree(plan: { priceAmount?: number }): boolean {
39
+ return (plan.priceAmount ?? 0) <= 0;
40
+ }
41
+
39
42
  export const DevPortalLocalManifestSchema = z.object({
40
43
  siteName: z.string().optional(),
41
44
  branding: z
@@ -52,6 +55,29 @@ export const DevPortalLocalManifestSchema = z.object({
52
55
  authentication: z
53
56
  .object({
54
57
  enabled: z.boolean().optional(),
58
+ apiBaseUrl: z.string().optional(),
59
+ native: z
60
+ .object({
61
+ enabled: z.boolean().optional(),
62
+ })
63
+ .optional(),
64
+ email: z
65
+ .object({
66
+ provider: z.string().optional(),
67
+ from: z.string().optional(),
68
+ })
69
+ .optional(),
70
+ providers: z
71
+ .array(
72
+ z.object({
73
+ id: z.string().min(1),
74
+ displayName: z.string().optional(),
75
+ authority: z.string().optional(),
76
+ clientId: z.string().optional(),
77
+ scopes: z.string().optional(),
78
+ }),
79
+ )
80
+ .optional(),
55
81
  })
56
82
  .optional(),
57
83
  plans: z.array(ManifestPlanInputSchema).optional(),
@@ -76,7 +102,6 @@ export const DevPortalLocalManifestSchema = z.object({
76
102
  })
77
103
  .optional(),
78
104
  projectId: z.string().optional(),
79
- projectName: z.string().optional(),
80
105
  });
81
106
 
82
107
  export type ManifestPlanInput = z.infer<typeof ManifestPlanInputSchema>;
@@ -87,7 +112,6 @@ export type DevPortalLocalManifest = z.infer<
87
112
  export type DevPortalPreviewPlan = {
88
113
  sku: string;
89
114
  displayName: string;
90
- isFree: boolean;
91
115
  priceAmount: number;
92
116
  priceCurrency: string;
93
117
  billingPeriod: string;
@@ -113,6 +137,16 @@ export function isAuthenticationEnabled(
113
137
  return manifest?.authentication?.enabled === true;
114
138
  }
115
139
 
140
+ export function isNativeAuthEnabledInManifest(
141
+ manifest: DevPortalLocalManifest | null | undefined,
142
+ ): boolean {
143
+ if (!isAuthenticationEnabled(manifest)) {
144
+ return false;
145
+ }
146
+
147
+ return manifest?.authentication?.native?.enabled !== false;
148
+ }
149
+
116
150
  export function manifestToPreviewCatalog(
117
151
  manifest: DevPortalLocalManifest | null | undefined,
118
152
  ): DevPortalPreviewPlanCatalog {
@@ -151,7 +185,6 @@ export function plansToPreviewCatalog(
151
185
  plans: plans.map((plan) => ({
152
186
  sku: plan.sku,
153
187
  displayName: plan.displayName,
154
- isFree: plan.isFree,
155
188
  priceAmount: plan.priceAmount ?? 0,
156
189
  priceCurrency: plan.priceCurrency ?? "usd",
157
190
  billingPeriod: normalizeBillingPeriod(plan.billingPeriod),
@@ -212,10 +245,7 @@ export function mergeManifest(
212
245
  }
213
246
 
214
247
  export function manifestFilePath(rootDir: string, filename?: string): string {
215
- return path.join(
216
- path.resolve(rootDir),
217
- filename ?? DEV_PORTAL_MANIFEST_FILENAME,
218
- );
248
+ return path.join(path.resolve(rootDir), filename ?? APITOGO_CONFIG_FILENAME);
219
249
  }
220
250
 
221
251
  export function manifestPathsForEnv(
@@ -283,60 +313,29 @@ function parseManifestRecord(
283
313
 
284
314
  export async function readEffectiveManifest(
285
315
  rootDir: string,
286
- env: ApitogoManifestEnv,
316
+ _env: ApitogoManifestEnv,
287
317
  ): Promise<DevPortalLocalManifest | null> {
288
318
  try {
289
319
  const config = loadApitogoConfig(rootDir);
290
- return parseManifestRecord(extractManifest(config));
291
- } catch {
292
- return readLegacyEffectiveManifest(rootDir, env);
293
- }
294
- }
295
-
296
- async function readLegacyEffectiveManifest(
297
- rootDir: string,
298
- env: ApitogoManifestEnv,
299
- ): Promise<DevPortalLocalManifest | null> {
300
- const resolvedRoot = path.resolve(rootDir);
301
- const basePath = path.join(resolvedRoot, APITOGO_MANIFEST_BASE_FILENAME);
302
- const overridePath = path.join(resolvedRoot, APITOGO_MANIFEST_ENV_FILES[env]);
303
- const hasBase = await fileExists(basePath);
304
- const hasOverride = await fileExists(overridePath);
305
-
306
- if (!hasBase) {
307
- const legacy = await readManifestFile(
308
- rootDir,
309
- APITOGO_MANIFEST_ENV_FILES.development,
310
- );
311
- if (legacy) {
312
- return legacy;
320
+ const manifest = parseManifestRecord(extractManifest(config));
321
+ if (!manifest) {
322
+ return null;
313
323
  }
314
324
 
315
- if (hasOverride) {
316
- return readManifestFile(rootDir, APITOGO_MANIFEST_ENV_FILES[env]);
325
+ if (!manifest.plans?.length) {
326
+ const items = readPlanCatalogItems(config);
327
+ if (items.length) {
328
+ manifest.plans = items as ManifestPlanInput[];
329
+ }
317
330
  }
318
331
 
332
+ return {
333
+ ...manifest,
334
+ pricing: { ...manifest.pricing, enabled: isBillingEnabled(config) },
335
+ };
336
+ } catch {
319
337
  return null;
320
338
  }
321
-
322
- const base = await readManifestFile(rootDir, APITOGO_MANIFEST_BASE_FILENAME);
323
- if (!base) {
324
- return null;
325
- }
326
-
327
- if (!hasOverride) {
328
- return base;
329
- }
330
-
331
- const override = await readManifestFile(
332
- rootDir,
333
- APITOGO_MANIFEST_ENV_FILES[env],
334
- );
335
- if (!override) {
336
- return base;
337
- }
338
-
339
- return mergeManifest(base, override);
340
339
  }
341
340
 
342
341
  export async function readLocalManifest(
@@ -141,10 +141,14 @@ export const resolveModulesConfig = (
141
141
  : !!config.authentication;
142
142
 
143
143
  const apiKeysEnabled = explicit
144
- ? (apiKeysModuleConfig?.enabled ?? config.apiKeys?.enabled ?? false)
145
- : (config.apiKeys?.enabled ?? false);
144
+ ? (apiKeysModuleConfig?.enabled ?? false)
145
+ : false;
146
146
 
147
- const plansEnabled = plansConfig?.enabled ?? false;
147
+ const billingEnabled =
148
+ (config as { __meta?: { pricingEnabled?: boolean } }).__meta
149
+ ?.pricingEnabled === true || plansConfig?.enabled === true;
150
+
151
+ const plansEnabled = billingEnabled;
148
152
 
149
153
  const userPanelEnabled =
150
154
  userPanelModule?.enabled ??
@@ -158,11 +162,10 @@ export const resolveModulesConfig = (
158
162
  return {
159
163
  docs: {
160
164
  enabled: docsEnabled,
161
- files: docsModule?.files ?? config.docs?.files,
162
- publishMarkdown:
163
- docsModule?.publishMarkdown ?? config.docs?.publishMarkdown,
164
- defaultOptions: docsModule?.defaultOptions ?? config.docs?.defaultOptions,
165
- llms: docsModule?.llms ?? config.docs?.llms,
165
+ files: docsModule?.files,
166
+ publishMarkdown: docsModule?.publishMarkdown,
167
+ defaultOptions: docsModule?.defaultOptions,
168
+ llms: docsModule?.llms,
166
169
  },
167
170
  landing: {
168
171
  enabled: landingEnabled,
@@ -203,6 +206,7 @@ export const resolveModulesConfig = (
203
206
  enabled: plansEnabled,
204
207
  path: joinPanelPath(basePath, plansConfig?.path ?? "plans"),
205
208
  content: resolvePlansContent(plansConfig?.content),
209
+ items: plansConfig?.items,
206
210
  },
207
211
  },
208
212
  };
@@ -242,11 +246,10 @@ export const getEffectiveDocsConfig = (
242
246
  }
243
247
 
244
248
  return {
245
- files: resolved.docs.files ?? config.docs?.files ?? [DEFAULT_DOCS_FILES],
246
- publishMarkdown:
247
- resolved.docs.publishMarkdown ?? config.docs?.publishMarkdown ?? false,
248
- defaultOptions: resolved.docs.defaultOptions ?? config.docs?.defaultOptions,
249
- llms: resolved.docs.llms ?? config.docs?.llms,
249
+ files: resolved.docs.files ?? [DEFAULT_DOCS_FILES],
250
+ publishMarkdown: resolved.docs.publishMarkdown ?? false,
251
+ defaultOptions: resolved.docs.defaultOptions,
252
+ llms: resolved.docs.llms,
250
253
  };
251
254
  };
252
255
 
@@ -154,6 +154,16 @@ export const ApiKeysModuleSchema = ModuleToggleSchema.extend({
154
154
  .describe("Route path for API key management."),
155
155
  });
156
156
 
157
+ const PlanCatalogItemSchema = z.object({
158
+ sku: z.string(),
159
+ displayName: z.string(),
160
+ priceAmount: z.number().default(0),
161
+ priceCurrency: z.string().optional(),
162
+ billingPeriod: z.string().optional(),
163
+ limitsJson: z.string().optional(),
164
+ includedActionKeys: z.array(z.string()).optional(),
165
+ });
166
+
157
167
  export const PlansModuleSchema = ModuleToggleSchema.extend({
158
168
  path: z
159
169
  .string()
@@ -164,6 +174,12 @@ export const PlansModuleSchema = ModuleToggleSchema.extend({
164
174
  content: PlansContentSchema.describe(
165
175
  "Default plans page content when no custom component is provided.",
166
176
  ),
177
+ items: z
178
+ .array(PlanCatalogItemSchema)
179
+ .optional()
180
+ .describe(
181
+ "Gateway plan catalog for /pricing preview and publish (canonical storage).",
182
+ ),
167
183
  });
168
184
 
169
185
  export const DashboardModuleSchema = ModuleToggleSchema.extend({
@@ -288,6 +304,7 @@ export type ResolvedPlansModule = {
288
304
  enabled: boolean;
289
305
  path: string;
290
306
  content: ResolvedPlansContent;
307
+ items?: z.infer<typeof PlanCatalogItemSchema>[];
291
308
  };
292
309
 
293
310
  export type ResolvedDashboardModule = {
@@ -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(),
package/src/index.ts CHANGED
@@ -77,5 +77,6 @@ export {
77
77
  applyEnvOverrides,
78
78
  extractManifest,
79
79
  extractSiteConfig,
80
- loadApitogoConfig,
81
- } from "./config/apitogo-config-loader.js";
80
+ normalizeApitogoConfig,
81
+ resolveProjectDisplayName,
82
+ } from "./config/apitogo-config-core.js";