@lukoweb/apitogo 0.1.50 → 0.1.53
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 +1339 -922
- package/dist/declarations/config/apitogo-config-core.d.ts +25 -0
- package/dist/declarations/config/apitogo-config-loader.d.ts +2 -0
- package/dist/declarations/config/apitogo-config-loader.node.d.ts +4 -0
- package/dist/declarations/config/build-apitogo-config.d.ts +8 -0
- package/dist/declarations/config/loader.d.ts +4 -0
- package/dist/declarations/config/local-manifest.d.ts +16 -9
- package/dist/declarations/config/validators/HeaderNavigationSchema.d.ts +24 -24
- package/dist/declarations/config/validators/ModulesSchema.d.ts +38 -0
- package/dist/declarations/config/validators/ZudokuConfig.d.ts +18 -9
- package/dist/declarations/index.d.ts +2 -1
- package/dist/declarations/lib/authentication/auth-header-nav.d.ts +10 -0
- package/dist/declarations/lib/core/plugins.d.ts +1 -0
- package/dist/declarations/lib/ui/Button.d.ts +1 -1
- package/dist/declarations/lib/ui/Item.d.ts +1 -1
- package/dist/flat-config.d.ts +36 -23
- package/docs/configuration/authentication.md +11 -8
- package/docs/configuration/manifest.md +83 -93
- package/package.json +17 -1
- package/src/config/apitogo-config-core.ts +485 -0
- package/src/config/apitogo-config-loader.node.ts +68 -0
- package/src/config/apitogo-config-loader.ts +6 -0
- package/src/config/build-apitogo-config.ts +52 -0
- package/src/config/loader.ts +61 -1
- package/src/config/local-manifest.ts +72 -52
- package/src/config/resolve-modules.ts +16 -13
- package/src/config/validators/ModulesSchema.ts +17 -0
- package/src/index.ts +9 -1
- package/src/lib/authentication/auth-header-nav.ts +56 -0
- package/src/lib/authentication/providers/dev-portal-utils.ts +7 -2
- package/src/lib/components/Header.tsx +48 -60
- package/src/lib/components/MobileTopNavigation.tsx +29 -40
- 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
package/src/config/loader.ts
CHANGED
|
@@ -10,9 +10,25 @@ import {
|
|
|
10
10
|
} from "vite";
|
|
11
11
|
import { logger } from "../cli/common/logger.js";
|
|
12
12
|
import { getZudokuRootDir } from "../cli/common/package-json.js";
|
|
13
|
+
import { applyAuthenticationHeaderNav } from "../lib/authentication/auth-header-nav.js";
|
|
13
14
|
import { runPluginTransformConfig } from "../lib/core/transform-config.js";
|
|
14
15
|
import invariant from "../lib/util/invariant.js";
|
|
16
|
+
import {
|
|
17
|
+
isBillingEnabled,
|
|
18
|
+
loadApitogoConfig,
|
|
19
|
+
readPlanCatalogItems,
|
|
20
|
+
} from "./apitogo-config-loader.js";
|
|
15
21
|
import { fileExists } from "./file-exists.js";
|
|
22
|
+
import {
|
|
23
|
+
isAuthenticationEnabled,
|
|
24
|
+
manifestPathsForEnv,
|
|
25
|
+
manifestToPreviewCatalog,
|
|
26
|
+
plansToPreviewCatalog,
|
|
27
|
+
readEffectiveManifest,
|
|
28
|
+
resolveManifestEnv,
|
|
29
|
+
type DevPortalPreviewPlan,
|
|
30
|
+
type ManifestPlanInput,
|
|
31
|
+
} from "./local-manifest.js";
|
|
16
32
|
import { resolveModulesConfig } from "./resolve-modules.js";
|
|
17
33
|
import type { ResolvedModulesConfig } from "./validators/ModulesSchema.js";
|
|
18
34
|
import type { ZudokuConfig } from "./validators/ZudokuConfig.js";
|
|
@@ -25,6 +41,9 @@ export type ConfigWithMeta = ZudokuConfig & {
|
|
|
25
41
|
configPath: string;
|
|
26
42
|
mode: typeof process.env.ZUDOKU_ENV;
|
|
27
43
|
dependencies: string[];
|
|
44
|
+
pricingEnabled?: boolean;
|
|
45
|
+
authenticationEnabled?: boolean;
|
|
46
|
+
previewPlans?: DevPortalPreviewPlan[];
|
|
28
47
|
};
|
|
29
48
|
__resolvedModules: ResolvedModulesConfig;
|
|
30
49
|
};
|
|
@@ -157,8 +176,17 @@ function isWatchableConfigDependency(depPath: string): boolean {
|
|
|
157
176
|
}
|
|
158
177
|
|
|
159
178
|
function getWatchableConfigDependencies(config: ConfigWithMeta): string[] {
|
|
179
|
+
const manifestEnv = resolveManifestEnv(
|
|
180
|
+
config.__meta.mode === "production" ? "production" : "development",
|
|
181
|
+
);
|
|
182
|
+
const { watchPaths } = manifestPathsForEnv(
|
|
183
|
+
config.__meta.rootDir,
|
|
184
|
+
manifestEnv,
|
|
185
|
+
);
|
|
186
|
+
|
|
160
187
|
return [
|
|
161
188
|
config.__meta.configPath,
|
|
189
|
+
...watchPaths,
|
|
162
190
|
...config.__meta.dependencies.filter(isWatchableConfigDependency),
|
|
163
191
|
];
|
|
164
192
|
}
|
|
@@ -171,6 +199,10 @@ async function hasConfigChanged() {
|
|
|
171
199
|
try {
|
|
172
200
|
const hasChanged = await Promise.all(
|
|
173
201
|
files.map(async (depPath) => {
|
|
202
|
+
if (!(await fileExists(depPath))) {
|
|
203
|
+
return false;
|
|
204
|
+
}
|
|
205
|
+
|
|
174
206
|
const depStat = await stat(depPath);
|
|
175
207
|
const cachedMtime = modifiedTimes?.get(depPath);
|
|
176
208
|
return !cachedMtime || depStat.mtimeMs !== cachedMtime;
|
|
@@ -192,6 +224,10 @@ async function updateModifiedTimes() {
|
|
|
192
224
|
|
|
193
225
|
await Promise.all(
|
|
194
226
|
files.map(async (depPath) => {
|
|
227
|
+
if (!(await fileExists(depPath))) {
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
|
|
195
231
|
const depStat = await stat(depPath);
|
|
196
232
|
modifiedTimes?.set(depPath, depStat.mtimeMs);
|
|
197
233
|
}),
|
|
@@ -225,9 +261,33 @@ export async function loadZudokuConfig(
|
|
|
225
261
|
|
|
226
262
|
try {
|
|
227
263
|
const loadedConfig = await loadZudokuConfigWithMeta(rootDir);
|
|
228
|
-
const
|
|
264
|
+
const manifestEnv = resolveManifestEnv(configEnv.mode);
|
|
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;
|
|
273
|
+
const configWithManifestMeta: ConfigWithMeta = {
|
|
274
|
+
...loadedConfig,
|
|
275
|
+
__meta: {
|
|
276
|
+
...loadedConfig.__meta,
|
|
277
|
+
pricingEnabled: isBillingEnabled(jsonConfig),
|
|
278
|
+
authenticationEnabled: isAuthenticationEnabled(manifest),
|
|
279
|
+
previewPlans,
|
|
280
|
+
},
|
|
281
|
+
};
|
|
282
|
+
const transformedConfig = applyAuthenticationHeaderNav(
|
|
283
|
+
await runPluginTransformConfig(configWithManifestMeta),
|
|
284
|
+
);
|
|
229
285
|
config = {
|
|
230
286
|
...transformedConfig,
|
|
287
|
+
__meta: {
|
|
288
|
+
...transformedConfig.__meta,
|
|
289
|
+
...configWithManifestMeta.__meta,
|
|
290
|
+
},
|
|
231
291
|
__resolvedModules: resolveModulesConfig(transformedConfig),
|
|
232
292
|
};
|
|
233
293
|
|
|
@@ -1,17 +1,25 @@
|
|
|
1
1
|
import { access, readFile } from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { z } from "zod";
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
4
|
+
import {
|
|
5
|
+
APITOGO_CONFIG_FILENAME,
|
|
6
|
+
extractManifest,
|
|
7
|
+
isBillingEnabled,
|
|
8
|
+
loadApitogoConfig,
|
|
9
|
+
readPlanCatalogItems,
|
|
10
|
+
} from "./apitogo-config-loader.js";
|
|
11
|
+
|
|
12
|
+
/** @deprecated Legacy manifest filenames — apitogo.config.json is the only supported source. */
|
|
8
13
|
export const APITOGO_MANIFEST_BASE_FILENAME = "apitogo.json";
|
|
9
14
|
|
|
15
|
+
/** @deprecated Legacy per-environment manifest overrides. */
|
|
10
16
|
export const APITOGO_MANIFEST_ENV_FILES = {
|
|
11
17
|
development: "apitogo.local.json",
|
|
12
18
|
production: "apitogo.prod.json",
|
|
13
19
|
} as const;
|
|
14
20
|
|
|
21
|
+
export { APITOGO_CONFIG_FILENAME };
|
|
22
|
+
|
|
15
23
|
export type ApitogoManifestEnv = keyof typeof APITOGO_MANIFEST_ENV_FILES;
|
|
16
24
|
|
|
17
25
|
const BillingPeriodSchema = z.enum(["month", "year", "monthly", "annual"]);
|
|
@@ -19,14 +27,18 @@ const BillingPeriodSchema = z.enum(["month", "year", "monthly", "annual"]);
|
|
|
19
27
|
export const ManifestPlanInputSchema = z.object({
|
|
20
28
|
sku: z.string().min(1),
|
|
21
29
|
displayName: z.string().min(1),
|
|
22
|
-
|
|
23
|
-
priceAmount: z.number().optional(),
|
|
30
|
+
priceAmount: z.number().default(0),
|
|
24
31
|
priceCurrency: z.string().optional(),
|
|
25
32
|
billingPeriod: BillingPeriodSchema.optional(),
|
|
26
33
|
limitsJson: z.string().optional(),
|
|
27
34
|
includedActionKeys: z.array(z.string()).optional(),
|
|
28
35
|
});
|
|
29
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
|
+
|
|
30
42
|
export const DevPortalLocalManifestSchema = z.object({
|
|
31
43
|
siteName: z.string().optional(),
|
|
32
44
|
branding: z
|
|
@@ -40,6 +52,13 @@ export const DevPortalLocalManifestSchema = z.object({
|
|
|
40
52
|
enabled: z.boolean().optional(),
|
|
41
53
|
})
|
|
42
54
|
.optional(),
|
|
55
|
+
authentication: z
|
|
56
|
+
.object({
|
|
57
|
+
enabled: z.boolean().optional(),
|
|
58
|
+
type: z.string().optional(),
|
|
59
|
+
apiBaseUrl: z.string().optional(),
|
|
60
|
+
})
|
|
61
|
+
.optional(),
|
|
43
62
|
plans: z.array(ManifestPlanInputSchema).optional(),
|
|
44
63
|
backend: z
|
|
45
64
|
.object({
|
|
@@ -62,7 +81,6 @@ export const DevPortalLocalManifestSchema = z.object({
|
|
|
62
81
|
})
|
|
63
82
|
.optional(),
|
|
64
83
|
projectId: z.string().optional(),
|
|
65
|
-
projectName: z.string().optional(),
|
|
66
84
|
});
|
|
67
85
|
|
|
68
86
|
export type ManifestPlanInput = z.infer<typeof ManifestPlanInputSchema>;
|
|
@@ -73,7 +91,6 @@ export type DevPortalLocalManifest = z.infer<
|
|
|
73
91
|
export type DevPortalPreviewPlan = {
|
|
74
92
|
sku: string;
|
|
75
93
|
displayName: string;
|
|
76
|
-
isFree: boolean;
|
|
77
94
|
priceAmount: number;
|
|
78
95
|
priceCurrency: string;
|
|
79
96
|
billingPeriod: string;
|
|
@@ -83,6 +100,7 @@ export type DevPortalPreviewPlan = {
|
|
|
83
100
|
|
|
84
101
|
export type DevPortalPreviewPlanCatalog = {
|
|
85
102
|
pricingEnabled: boolean;
|
|
103
|
+
authenticationEnabled: boolean;
|
|
86
104
|
plans: DevPortalPreviewPlan[];
|
|
87
105
|
};
|
|
88
106
|
|
|
@@ -92,6 +110,12 @@ export function isPricingEnabled(
|
|
|
92
110
|
return manifest?.pricing?.enabled === true;
|
|
93
111
|
}
|
|
94
112
|
|
|
113
|
+
export function isAuthenticationEnabled(
|
|
114
|
+
manifest: DevPortalLocalManifest | null | undefined,
|
|
115
|
+
): boolean {
|
|
116
|
+
return manifest?.authentication?.enabled === true;
|
|
117
|
+
}
|
|
118
|
+
|
|
95
119
|
export function manifestToPreviewCatalog(
|
|
96
120
|
manifest: DevPortalLocalManifest | null | undefined,
|
|
97
121
|
): DevPortalPreviewPlanCatalog {
|
|
@@ -99,6 +123,7 @@ export function manifestToPreviewCatalog(
|
|
|
99
123
|
const catalog = plansToPreviewCatalog(plans);
|
|
100
124
|
return {
|
|
101
125
|
pricingEnabled: isPricingEnabled(manifest),
|
|
126
|
+
authenticationEnabled: isAuthenticationEnabled(manifest),
|
|
102
127
|
plans: catalog.plans,
|
|
103
128
|
};
|
|
104
129
|
}
|
|
@@ -129,7 +154,6 @@ export function plansToPreviewCatalog(
|
|
|
129
154
|
plans: plans.map((plan) => ({
|
|
130
155
|
sku: plan.sku,
|
|
131
156
|
displayName: plan.displayName,
|
|
132
|
-
isFree: plan.isFree,
|
|
133
157
|
priceAmount: plan.priceAmount ?? 0,
|
|
134
158
|
priceCurrency: plan.priceCurrency ?? "usd",
|
|
135
159
|
billingPeriod: normalizeBillingPeriod(plan.billingPeriod),
|
|
@@ -170,6 +194,12 @@ export function mergeManifest(
|
|
|
170
194
|
if (override.pricing) {
|
|
171
195
|
merged.pricing = { ...base.pricing, ...override.pricing };
|
|
172
196
|
}
|
|
197
|
+
if (override.authentication) {
|
|
198
|
+
merged.authentication = {
|
|
199
|
+
...base.authentication,
|
|
200
|
+
...override.authentication,
|
|
201
|
+
};
|
|
202
|
+
}
|
|
173
203
|
if (override.backend) {
|
|
174
204
|
merged.backend = { ...base.backend, ...override.backend };
|
|
175
205
|
}
|
|
@@ -184,24 +214,20 @@ export function mergeManifest(
|
|
|
184
214
|
}
|
|
185
215
|
|
|
186
216
|
export function manifestFilePath(rootDir: string, filename?: string): string {
|
|
187
|
-
return path.join(
|
|
188
|
-
path.resolve(rootDir),
|
|
189
|
-
filename ?? DEV_PORTAL_MANIFEST_FILENAME,
|
|
190
|
-
);
|
|
217
|
+
return path.join(path.resolve(rootDir), filename ?? APITOGO_CONFIG_FILENAME);
|
|
191
218
|
}
|
|
192
219
|
|
|
193
220
|
export function manifestPathsForEnv(
|
|
194
221
|
rootDir: string,
|
|
195
|
-
|
|
222
|
+
_env: ApitogoManifestEnv,
|
|
196
223
|
): { basePath: string; overridePath: string; watchPaths: string[] } {
|
|
197
224
|
const resolvedRoot = path.resolve(rootDir);
|
|
198
|
-
const
|
|
199
|
-
const overridePath = path.join(resolvedRoot, APITOGO_MANIFEST_ENV_FILES[env]);
|
|
225
|
+
const configPath = path.join(resolvedRoot, APITOGO_CONFIG_FILENAME);
|
|
200
226
|
|
|
201
227
|
return {
|
|
202
|
-
basePath,
|
|
203
|
-
overridePath,
|
|
204
|
-
watchPaths: [
|
|
228
|
+
basePath: configPath,
|
|
229
|
+
overridePath: configPath,
|
|
230
|
+
watchPaths: [configPath],
|
|
205
231
|
};
|
|
206
232
|
}
|
|
207
233
|
|
|
@@ -243,48 +269,42 @@ export async function readManifestFile(
|
|
|
243
269
|
}
|
|
244
270
|
}
|
|
245
271
|
|
|
272
|
+
function parseManifestRecord(
|
|
273
|
+
manifest: ReturnType<typeof extractManifest>,
|
|
274
|
+
): DevPortalLocalManifest | null {
|
|
275
|
+
if (!manifest) {
|
|
276
|
+
return null;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
const result = DevPortalLocalManifestSchema.safeParse(manifest);
|
|
280
|
+
return result.success ? result.data : null;
|
|
281
|
+
}
|
|
282
|
+
|
|
246
283
|
export async function readEffectiveManifest(
|
|
247
284
|
rootDir: string,
|
|
248
|
-
|
|
285
|
+
_env: ApitogoManifestEnv,
|
|
249
286
|
): Promise<DevPortalLocalManifest | null> {
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
const legacy = await readManifestFile(
|
|
256
|
-
rootDir,
|
|
257
|
-
APITOGO_MANIFEST_ENV_FILES.development,
|
|
258
|
-
);
|
|
259
|
-
if (legacy) {
|
|
260
|
-
return legacy;
|
|
287
|
+
try {
|
|
288
|
+
const config = loadApitogoConfig(rootDir);
|
|
289
|
+
const manifest = parseManifestRecord(extractManifest(config));
|
|
290
|
+
if (!manifest) {
|
|
291
|
+
return null;
|
|
261
292
|
}
|
|
262
293
|
|
|
263
|
-
if (
|
|
264
|
-
|
|
294
|
+
if (!manifest.plans?.length) {
|
|
295
|
+
const items = readPlanCatalogItems(config);
|
|
296
|
+
if (items.length) {
|
|
297
|
+
manifest.plans = items as ManifestPlanInput[];
|
|
298
|
+
}
|
|
265
299
|
}
|
|
266
300
|
|
|
301
|
+
return {
|
|
302
|
+
...manifest,
|
|
303
|
+
pricing: { ...manifest.pricing, enabled: isBillingEnabled(config) },
|
|
304
|
+
};
|
|
305
|
+
} catch {
|
|
267
306
|
return null;
|
|
268
307
|
}
|
|
269
|
-
|
|
270
|
-
const base = await readManifestFile(rootDir, APITOGO_MANIFEST_BASE_FILENAME);
|
|
271
|
-
if (!base) {
|
|
272
|
-
return null;
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
if (!hasOverride) {
|
|
276
|
-
return base;
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
const override = await readManifestFile(
|
|
280
|
-
rootDir,
|
|
281
|
-
APITOGO_MANIFEST_ENV_FILES[env],
|
|
282
|
-
);
|
|
283
|
-
if (!override) {
|
|
284
|
-
return base;
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
return mergeManifest(base, override);
|
|
288
308
|
}
|
|
289
309
|
|
|
290
310
|
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 = {
|
package/src/index.ts
CHANGED
|
@@ -71,4 +71,12 @@ export {
|
|
|
71
71
|
type ProblemJson,
|
|
72
72
|
throwIfProblemJson,
|
|
73
73
|
} from "./lib/util/problemJson.js";
|
|
74
|
-
export
|
|
74
|
+
export { buildApitogoConfig } from "./config/build-apitogo-config.js";
|
|
75
|
+
export {
|
|
76
|
+
APITOGO_CONFIG_FILENAME,
|
|
77
|
+
applyEnvOverrides,
|
|
78
|
+
extractManifest,
|
|
79
|
+
extractSiteConfig,
|
|
80
|
+
normalizeApitogoConfig,
|
|
81
|
+
resolveProjectDisplayName,
|
|
82
|
+
} from "./config/apitogo-config-core.js";
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import type { HeaderNavigation } from "../../config/validators/HeaderNavigationSchema.js";
|
|
2
|
+
import type { ZudokuConfig } from "../../config/validators/ZudokuConfig.js";
|
|
3
|
+
|
|
4
|
+
export const LOGIN_NAV_PATH = "/signin";
|
|
5
|
+
|
|
6
|
+
export const LOGIN_HEADER_NAV_ITEM = {
|
|
7
|
+
label: "Login",
|
|
8
|
+
to: LOGIN_NAV_PATH,
|
|
9
|
+
display: "anon" as const,
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export function withoutLoginNavigation(
|
|
13
|
+
navigation: HeaderNavigation,
|
|
14
|
+
): HeaderNavigation {
|
|
15
|
+
return navigation.filter(
|
|
16
|
+
(item) => !("to" in item && item.to === LOGIN_NAV_PATH),
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function applyAuthenticationHeaderNav<T extends ZudokuConfig>(
|
|
21
|
+
config: T,
|
|
22
|
+
): T {
|
|
23
|
+
if (!config.authentication) {
|
|
24
|
+
return config;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const authNavEnabled = config.__meta?.authenticationEnabled === true;
|
|
28
|
+
const navigation = config.header?.navigation ?? [];
|
|
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
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (navigation.some((item) => "to" in item && item.to === LOGIN_NAV_PATH)) {
|
|
46
|
+
return config;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return {
|
|
50
|
+
...config,
|
|
51
|
+
header: {
|
|
52
|
+
...config.header,
|
|
53
|
+
navigation: [...navigation, LOGIN_HEADER_NAV_ITEM],
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
}
|
|
@@ -5,6 +5,8 @@ import {
|
|
|
5
5
|
type EndUserMeResponse,
|
|
6
6
|
} from "./dev-portal-constants.js";
|
|
7
7
|
|
|
8
|
+
declare const VITE_APITOGO_AUTHENTICATION_API_BASE_URL: string | undefined;
|
|
9
|
+
/** @deprecated Use {@link VITE_APITOGO_AUTHENTICATION_API_BASE_URL}. */
|
|
8
10
|
declare const VITE_APITOGO_DEV_PORTAL_API_URL: string | undefined;
|
|
9
11
|
|
|
10
12
|
export function resolveDevPortalApiBaseUrl(
|
|
@@ -16,9 +18,12 @@ export function resolveDevPortalApiBaseUrl(
|
|
|
16
18
|
}
|
|
17
19
|
|
|
18
20
|
const fromEnv =
|
|
19
|
-
typeof
|
|
21
|
+
(typeof VITE_APITOGO_AUTHENTICATION_API_BASE_URL === "string"
|
|
22
|
+
? VITE_APITOGO_AUTHENTICATION_API_BASE_URL.trim()
|
|
23
|
+
: "") ||
|
|
24
|
+
(typeof VITE_APITOGO_DEV_PORTAL_API_URL === "string"
|
|
20
25
|
? VITE_APITOGO_DEV_PORTAL_API_URL.trim()
|
|
21
|
-
: "";
|
|
26
|
+
: "");
|
|
22
27
|
|
|
23
28
|
return fromEnv ? fromEnv.replace(/\/+$/, "") : undefined;
|
|
24
29
|
}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { Button } from "@lukoweb/apitogo/ui/Button.js";
|
|
2
|
-
import { Skeleton } from "@lukoweb/apitogo/ui/Skeleton.js";
|
|
3
2
|
import { LogOutIcon } from "lucide-react";
|
|
4
3
|
import { lazy, memo, Suspense } from "react";
|
|
5
4
|
import { Link } from "react-router";
|
|
@@ -20,7 +19,6 @@ import {
|
|
|
20
19
|
import { cn } from "../util/cn.js";
|
|
21
20
|
import { joinUrl } from "../util/joinUrl.js";
|
|
22
21
|
import { Banner } from "./Banner.js";
|
|
23
|
-
import { ClientOnly } from "./ClientOnly.js";
|
|
24
22
|
import { useZudoku } from "./context/ZudokuContext.js";
|
|
25
23
|
import { MobileTopNavigation } from "./MobileTopNavigation.js";
|
|
26
24
|
import { PageProgress } from "./PageProgress.js";
|
|
@@ -73,68 +71,58 @@ const ProfileMenu = () => {
|
|
|
73
71
|
const context = useZudoku();
|
|
74
72
|
const profileItems = context.getProfileMenuItems();
|
|
75
73
|
const auth = useAuth();
|
|
76
|
-
const { isAuthEnabled, isAuthenticated,
|
|
74
|
+
const { isAuthEnabled, isAuthenticated, profile } = auth;
|
|
77
75
|
|
|
78
|
-
if (!isAuthEnabled) return null;
|
|
76
|
+
if (!isAuthEnabled || !isAuthenticated) return null;
|
|
79
77
|
|
|
80
78
|
return (
|
|
81
|
-
<
|
|
82
|
-
|
|
83
|
-
<
|
|
84
|
-
|
|
85
|
-
<Button size="lg" variant="ghost" onClick={() => auth.login()}>
|
|
86
|
-
Login
|
|
79
|
+
<DropdownMenu modal={false}>
|
|
80
|
+
<DropdownMenuTrigger asChild>
|
|
81
|
+
<Button size="lg" variant="ghost">
|
|
82
|
+
{profile?.name ?? "My Account"}
|
|
87
83
|
</Button>
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
{
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
{
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
<LogOutIcon size={16} strokeWidth={1} absoluteStrokeWidth />
|
|
131
|
-
Logout
|
|
132
|
-
</DropdownMenuItem>
|
|
133
|
-
</Link>
|
|
134
|
-
</DropdownMenuContent>
|
|
135
|
-
</DropdownMenu>
|
|
136
|
-
)}
|
|
137
|
-
</ClientOnly>
|
|
84
|
+
</DropdownMenuTrigger>
|
|
85
|
+
<DropdownMenuContent className="w-56">
|
|
86
|
+
<DropdownMenuLabel>
|
|
87
|
+
{profile?.name ?? "My Account"}
|
|
88
|
+
{profile?.email && profile.email !== profile?.name && (
|
|
89
|
+
<div className="font-normal text-muted-foreground">
|
|
90
|
+
{profile.email}
|
|
91
|
+
</div>
|
|
92
|
+
)}
|
|
93
|
+
</DropdownMenuLabel>
|
|
94
|
+
{profileItems.filter((i) => i.category === "top").length > 0 && (
|
|
95
|
+
<DropdownMenuSeparator />
|
|
96
|
+
)}
|
|
97
|
+
{profileItems
|
|
98
|
+
.filter((i) => i.category === "top")
|
|
99
|
+
.map((i) => (
|
|
100
|
+
<RecursiveMenu key={i.label} item={i} />
|
|
101
|
+
))}
|
|
102
|
+
{profileItems.filter((i) => !i.category || i.category === "middle")
|
|
103
|
+
.length > 0 && <DropdownMenuSeparator />}
|
|
104
|
+
{profileItems
|
|
105
|
+
.filter((i) => !i.category || i.category === "middle")
|
|
106
|
+
.map((i) => (
|
|
107
|
+
<RecursiveMenu key={i.label} item={i} />
|
|
108
|
+
))}
|
|
109
|
+
{profileItems.filter((i) => i.category === "bottom").length > 0 && (
|
|
110
|
+
<DropdownMenuSeparator />
|
|
111
|
+
)}
|
|
112
|
+
{profileItems
|
|
113
|
+
.filter((i) => i.category === "bottom")
|
|
114
|
+
.map((i) => (
|
|
115
|
+
<RecursiveMenu key={i.label} item={i} />
|
|
116
|
+
))}
|
|
117
|
+
<DropdownMenuSeparator />
|
|
118
|
+
<Link to="/signout">
|
|
119
|
+
<DropdownMenuItem className="flex gap-2">
|
|
120
|
+
<LogOutIcon size={16} strokeWidth={1} absoluteStrokeWidth />
|
|
121
|
+
Logout
|
|
122
|
+
</DropdownMenuItem>
|
|
123
|
+
</Link>
|
|
124
|
+
</DropdownMenuContent>
|
|
125
|
+
</DropdownMenu>
|
|
138
126
|
);
|
|
139
127
|
};
|
|
140
128
|
export const Header = memo(function HeaderInner() {
|