@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.
- package/dist/cli/cli.js +467 -197
- package/dist/declarations/config/apitogo-config-core.d.ts +35 -0
- package/dist/declarations/config/apitogo-config-loader.d.ts +2 -7
- package/dist/declarations/config/apitogo-config-loader.node.d.ts +4 -0
- package/dist/declarations/config/build-apitogo-config.d.ts +5 -4
- package/dist/declarations/config/loader.d.ts +2 -0
- package/dist/declarations/config/local-manifest.d.ts +22 -8
- package/dist/declarations/config/validators/ModulesSchema.d.ts +38 -0
- package/dist/declarations/config/validators/ZudokuConfig.d.ts +43 -2
- package/dist/declarations/index.d.ts +1 -1
- package/dist/declarations/lib/authentication/providers/dev-portal-auth-pages.d.ts +15 -0
- package/dist/declarations/lib/authentication/providers/dev-portal-constants.d.ts +19 -1
- package/dist/declarations/lib/authentication/providers/dev-portal-utils.d.ts +5 -1
- package/dist/declarations/lib/authentication/providers/dev-portal.d.ts +5 -0
- package/dist/declarations/lib/core/plugins.d.ts +1 -0
- package/dist/flat-config.d.ts +53 -24
- package/docs/configuration/authentication.md +11 -9
- package/docs/configuration/manifest.md +75 -74
- package/package.json +1 -1
- package/src/config/apitogo-config-core.ts +601 -0
- package/src/config/apitogo-config-loader.node.ts +88 -0
- package/src/config/apitogo-config-loader.ts +6 -224
- package/src/config/build-apitogo-config.ts +26 -17
- package/src/config/loader.ts +23 -2
- package/src/config/local-manifest.ts +58 -59
- package/src/config/resolve-modules.ts +16 -13
- package/src/config/validators/ModulesSchema.ts +17 -0
- package/src/config/validators/ZudokuConfig.ts +25 -1
- package/src/index.ts +3 -2
- package/src/lib/authentication/providers/dev-portal-auth-pages.tsx +439 -0
- package/src/lib/authentication/providers/dev-portal-constants.ts +15 -1
- package/src/lib/authentication/providers/dev-portal-utils.ts +124 -4
- package/src/lib/authentication/providers/dev-portal.tsx +41 -6
- package/src/lib/core/plugins.ts +1 -0
- package/src/vite/dev-portal-env.ts +12 -4
- package/src/vite/plugin-config.ts +20 -3
- package/src/vite/plugin-local-manifest.ts +15 -0
|
@@ -1,224 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
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 {
|
|
1
|
+
import type { ApitogoPlugin } from "../lib/core/plugins.js";
|
|
2
2
|
import {
|
|
3
3
|
extractSiteConfig,
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
normalizeApitogoConfig,
|
|
5
|
+
type ApitogoJsonConfig,
|
|
6
|
+
} from "./apitogo-config-core.js";
|
|
7
|
+
import type { ApitogoConfig } from "./config.js";
|
|
6
8
|
|
|
7
|
-
type BuildApitogoConfigOptions =
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
};
|
|
9
|
+
type BuildApitogoConfigOptions = Partial<ApitogoConfig> &
|
|
10
|
+
ApitogoJsonConfig & {
|
|
11
|
+
plugins?: ApitogoPlugin[];
|
|
12
|
+
};
|
|
11
13
|
|
|
12
14
|
export function buildApitogoConfig({
|
|
13
|
-
|
|
14
|
-
|
|
15
|
+
plugins,
|
|
16
|
+
plans: _legacyPlans,
|
|
17
|
+
...jsonOverrides
|
|
15
18
|
}: BuildApitogoConfigOptions = {}): ApitogoConfig {
|
|
16
|
-
const jsonConfig =
|
|
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
|
-
...
|
|
29
|
+
...restOverrides,
|
|
30
|
+
plugins,
|
|
22
31
|
authentication: {
|
|
23
32
|
...(siteConfig.authentication as ApitogoConfig["authentication"]),
|
|
24
|
-
...
|
|
33
|
+
...jsonOverrides.authentication,
|
|
25
34
|
},
|
|
26
35
|
site: {
|
|
27
36
|
...(siteConfig.site as ApitogoConfig["site"]),
|
|
28
|
-
...
|
|
37
|
+
...jsonOverrides.site,
|
|
29
38
|
},
|
|
30
39
|
metadata: {
|
|
31
40
|
...(siteConfig.metadata as ApitogoConfig["metadata"]),
|
|
32
|
-
...
|
|
41
|
+
...jsonOverrides.metadata,
|
|
33
42
|
},
|
|
34
43
|
docs: {
|
|
35
44
|
...(siteConfig.docs as ApitogoConfig["docs"]),
|
|
36
|
-
...
|
|
45
|
+
...jsonOverrides.docs,
|
|
37
46
|
},
|
|
38
47
|
modules: {
|
|
39
|
-
...(
|
|
40
|
-
...
|
|
48
|
+
...(jsonConfig.modules as ApitogoConfig["modules"]),
|
|
49
|
+
...jsonOverrides.modules,
|
|
41
50
|
},
|
|
42
51
|
} as ApitogoConfig;
|
|
43
52
|
}
|
package/src/config/loader.ts
CHANGED
|
@@ -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:
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
316
|
+
_env: ApitogoManifestEnv,
|
|
287
317
|
): Promise<DevPortalLocalManifest | null> {
|
|
288
318
|
try {
|
|
289
319
|
const config = loadApitogoConfig(rootDir);
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
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 (
|
|
316
|
-
|
|
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 ??
|
|
145
|
-
:
|
|
144
|
+
? (apiKeysModuleConfig?.enabled ?? false)
|
|
145
|
+
: false;
|
|
146
146
|
|
|
147
|
-
const
|
|
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
|
|
162
|
-
publishMarkdown:
|
|
163
|
-
|
|
164
|
-
|
|
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 ??
|
|
246
|
-
publishMarkdown:
|
|
247
|
-
|
|
248
|
-
|
|
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