@lukoweb/apitogo 0.1.51 → 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 CHANGED
@@ -174,18 +174,7 @@ var init_invariant = __esm({
174
174
  }
175
175
  });
176
176
 
177
- // src/config/file-exists.ts
178
- import { stat } from "node:fs/promises";
179
- var fileExists;
180
- var init_file_exists = __esm({
181
- "src/config/file-exists.ts"() {
182
- fileExists = (path39) => stat(path39).then(() => true).catch(() => false);
183
- }
184
- });
185
-
186
- // src/config/apitogo-config-loader.ts
187
- import { existsSync, readFileSync as readFileSync2 } from "node:fs";
188
- import path2 from "node:path";
177
+ // src/config/apitogo-config-core.ts
189
178
  function parseEnvValue(raw) {
190
179
  if (raw === void 0) {
191
180
  return raw;
@@ -234,8 +223,12 @@ function setNestedValue(target, pathParts, value) {
234
223
  }
235
224
  current[leafKey] = value;
236
225
  }
226
+ function stripRuntimeConfigFields(config2) {
227
+ const { plugins: _plugins, ...jsonOnly } = config2;
228
+ return jsonOnly;
229
+ }
237
230
  function applyEnvOverrides(config2, env = process.env) {
238
- const result = structuredClone(config2);
231
+ const result = structuredClone(stripRuntimeConfigFields(config2));
239
232
  for (const [key, value] of Object.entries(env)) {
240
233
  if (!key.startsWith(ENV_PREFIX) || value === void 0) {
241
234
  continue;
@@ -248,40 +241,244 @@ function applyEnvOverrides(config2, env = process.env) {
248
241
  }
249
242
  return result;
250
243
  }
251
- function loadApitogoConfig(rootDir = process.cwd()) {
252
- const configPath = path2.join(path2.resolve(rootDir), APITOGO_CONFIG_FILENAME);
253
- if (!existsSync(configPath)) {
254
- throw new Error(
255
- `Missing ${APITOGO_CONFIG_FILENAME} in ${path2.resolve(rootDir)}`
256
- );
244
+ function asRecord(value) {
245
+ return value !== void 0 && value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
246
+ }
247
+ function readSiteTitle(config2) {
248
+ const site = asRecord(config2.site);
249
+ const branding = asRecord(config2.branding);
250
+ const siteTitle = site?.title;
251
+ const brandingTitle = branding?.title;
252
+ const legacySiteName = config2.siteName;
253
+ if (typeof siteTitle === "string" && siteTitle.trim()) {
254
+ return siteTitle.trim();
257
255
  }
258
- const raw = readFileSync2(configPath, "utf8");
259
- const parsed = JSON.parse(raw);
260
- return applyEnvOverrides(parsed, process.env);
256
+ if (typeof brandingTitle === "string" && brandingTitle.trim()) {
257
+ return brandingTitle.trim();
258
+ }
259
+ if (typeof legacySiteName === "string" && legacySiteName.trim()) {
260
+ return legacySiteName.trim();
261
+ }
262
+ return void 0;
261
263
  }
262
- function extractManifest(config2) {
263
- if (!config2 || typeof config2 !== "object") {
264
- return null;
264
+ function readLogoUrl(config2) {
265
+ const site = asRecord(config2.site);
266
+ const logo = asRecord(site?.logo);
267
+ const src = asRecord(logo?.src);
268
+ const branding = asRecord(config2.branding);
269
+ const fromSite = typeof src?.light === "string" && src.light || typeof src?.dark === "string" && src.dark || typeof logo?.logoUrl === "string" && logo.logoUrl || typeof site?.logoUrl === "string" && site.logoUrl;
270
+ if (fromSite) {
271
+ return fromSite;
272
+ }
273
+ return typeof branding?.logoUrl === "string" ? branding.logoUrl : void 0;
274
+ }
275
+ function firstApiInput(config2) {
276
+ const apis = config2.apis;
277
+ const list = Array.isArray(apis) ? apis : apis ? [apis] : [];
278
+ for (const entry of list) {
279
+ const api = asRecord(entry);
280
+ if (typeof api?.input === "string" && api.input.trim()) {
281
+ return api.input.trim();
282
+ }
283
+ }
284
+ return void 0;
285
+ }
286
+ function mergeAuthenticationGateway(config2) {
287
+ const authentication = asRecord(config2.authentication);
288
+ if (!authentication) {
289
+ return void 0;
290
+ }
291
+ const authGateway = asRecord(authentication.gateway);
292
+ const rootGateway = asRecord(config2.gateway);
293
+ if (!authGateway && !rootGateway) {
294
+ return authentication;
295
+ }
296
+ return {
297
+ ...authentication,
298
+ gateway: {
299
+ ...rootGateway,
300
+ ...authGateway
301
+ }
302
+ };
303
+ }
304
+ function ensureModules(config2) {
305
+ const modules = asRecord(config2.modules) ?? {};
306
+ config2.modules = modules;
307
+ return modules;
308
+ }
309
+ function mergePlansBySku(existing, patch) {
310
+ const bySku = /* @__PURE__ */ new Map();
311
+ for (const plan of existing) {
312
+ if (typeof plan.sku === "string") {
313
+ bySku.set(plan.sku, plan);
314
+ }
315
+ }
316
+ for (const plan of patch) {
317
+ if (typeof plan.sku !== "string") {
318
+ continue;
319
+ }
320
+ const prior = bySku.get(plan.sku);
321
+ bySku.set(plan.sku, prior ? { ...prior, ...plan } : plan);
322
+ }
323
+ return Array.from(bySku.values());
324
+ }
325
+ function readPlanCatalogItems(config2) {
326
+ const normalized = normalizeApitogoConfig(config2);
327
+ const items = asRecord(
328
+ asRecord(asRecord(normalized.modules)?.userPanel)?.plans
329
+ )?.items;
330
+ return Array.isArray(items) ? items : [];
331
+ }
332
+ function migratePlanCatalog(normalized) {
333
+ const modules = ensureModules(normalized);
334
+ const userPanel = asRecord(modules.userPanel) ?? {};
335
+ const plansModule = asRecord(userPanel.plans) ?? {};
336
+ const rootList = Array.isArray(normalized.plans) ? normalized.plans : [];
337
+ const existingItems = Array.isArray(plansModule.items) ? plansModule.items : [];
338
+ if (rootList.length) {
339
+ plansModule.items = mergePlansBySku(existingItems, rootList);
340
+ if (plansModule.enabled === void 0) {
341
+ plansModule.enabled = true;
342
+ }
343
+ userPanel.plans = plansModule;
344
+ modules.userPanel = userPanel;
345
+ }
346
+ delete normalized.plans;
347
+ }
348
+ function normalizeApitogoConfig(config2) {
349
+ const normalized = structuredClone(stripRuntimeConfigFields(config2));
350
+ const title = readSiteTitle(normalized);
351
+ const logoUrl = readLogoUrl(normalized);
352
+ const site = asRecord(normalized.site) ?? {};
353
+ if (title) {
354
+ site.title = title;
355
+ }
356
+ if (logoUrl && !asRecord(site.logo)?.src) {
357
+ site.logo = {
358
+ ...asRecord(site.logo) ?? {},
359
+ src: { light: logoUrl, dark: logoUrl }
360
+ };
265
361
  }
362
+ normalized.site = site;
363
+ const authentication = mergeAuthenticationGateway(normalized);
364
+ if (authentication) {
365
+ normalized.authentication = authentication;
366
+ }
367
+ delete normalized.gateway;
368
+ const modules = ensureModules(normalized);
369
+ const legacyDocs = asRecord(normalized.docs);
370
+ const moduleDocs = asRecord(modules.docs) ?? {};
371
+ if (legacyDocs) {
372
+ modules.docs = { ...legacyDocs, ...moduleDocs };
373
+ }
374
+ delete normalized.docs;
375
+ const legacyApiKeys = asRecord(normalized.apiKeys);
376
+ const userPanel = asRecord(modules.userPanel) ?? {};
377
+ const apiKeys = asRecord(userPanel.apiKeys) ?? {};
378
+ if (legacyApiKeys?.enabled === true && apiKeys.enabled === void 0) {
379
+ userPanel.apiKeys = { ...apiKeys, enabled: true };
380
+ modules.userPanel = userPanel;
381
+ }
382
+ delete normalized.apiKeys;
383
+ const apiInput = firstApiInput(normalized);
384
+ if (apiInput) {
385
+ const backend = asRecord(normalized.backend) ?? {};
386
+ if (!backend.openApiPath) {
387
+ backend.openApiPath = apiInput;
388
+ }
389
+ normalized.backend = backend;
390
+ }
391
+ delete normalized.siteName;
392
+ delete normalized.branding;
393
+ delete normalized.projectName;
394
+ migratePlanCatalog(normalized);
395
+ syncBillingEnabled(normalized);
396
+ return normalized;
397
+ }
398
+ function isBillingEnabled(config2) {
399
+ const normalized = normalizeApitogoConfig(config2);
400
+ const plans = asRecord(
401
+ asRecord(asRecord(normalized.modules)?.userPanel)?.plans
402
+ );
403
+ return plans?.enabled === true;
404
+ }
405
+ function syncBillingEnabled(normalized) {
406
+ const legacyPricing = asRecord(normalized.pricing);
407
+ const modules = ensureModules(normalized);
408
+ const userPanel = asRecord(modules.userPanel) ?? {};
409
+ const plans = asRecord(userPanel.plans) ?? {};
410
+ const enabled = plans.enabled === true || legacyPricing?.enabled === true;
411
+ if (enabled) {
412
+ userPanel.plans = { ...plans, enabled: true };
413
+ modules.userPanel = userPanel;
414
+ }
415
+ delete normalized.pricing;
416
+ }
417
+ function deriveManifestFields(config2) {
418
+ const normalized = normalizeApitogoConfig(config2);
266
419
  const manifest = {};
267
- for (const key of MANIFEST_KEYS) {
268
- if (config2[key] !== void 0) {
269
- manifest[key] = config2[key];
420
+ const title = readSiteTitle(normalized);
421
+ if (title) {
422
+ manifest.siteName = title;
423
+ const logoUrl = readLogoUrl(normalized);
424
+ manifest.branding = logoUrl ? { title, logoUrl } : { title };
425
+ }
426
+ for (const key of DERIVED_MANIFEST_KEYS) {
427
+ if (key === "siteName" || key === "branding") {
428
+ continue;
429
+ }
430
+ if (key === "authentication") {
431
+ const authentication2 = asRecord(normalized.authentication);
432
+ if (authentication2) {
433
+ const {
434
+ gateway: _gateway,
435
+ oidc: _oidc,
436
+ stripe: _stripe,
437
+ ...authPublic
438
+ } = authentication2;
439
+ manifest.authentication = authPublic;
440
+ }
441
+ continue;
442
+ }
443
+ if (key === "gateway") {
444
+ continue;
445
+ }
446
+ if (key === "pricing") {
447
+ if (isBillingEnabled(config2)) {
448
+ manifest.pricing = { enabled: true };
449
+ }
450
+ continue;
451
+ }
452
+ if (key === "plans") {
453
+ const items = readPlanCatalogItems(normalized);
454
+ if (items.length) {
455
+ manifest.plans = items;
456
+ }
457
+ continue;
458
+ }
459
+ if (normalized[key] !== void 0) {
460
+ manifest[key] = normalized[key];
270
461
  }
271
462
  }
272
- const authentication = manifest.authentication;
273
- if (authentication && typeof authentication === "object") {
274
- const { enabled } = authentication;
275
- manifest.authentication = enabled === void 0 ? void 0 : { enabled };
463
+ const authentication = asRecord(normalized.authentication);
464
+ if (authentication?.gateway) {
465
+ manifest.gateway = authentication.gateway;
466
+ }
467
+ return manifest;
468
+ }
469
+ function extractManifest(config2) {
470
+ if (!config2 || typeof config2 !== "object") {
471
+ return null;
276
472
  }
473
+ const manifest = deriveManifestFields(config2);
277
474
  return Object.keys(manifest).length > 0 ? manifest : null;
278
475
  }
279
- var APITOGO_CONFIG_FILENAME, ENV_PREFIX, MANIFEST_KEYS;
280
- var init_apitogo_config_loader = __esm({
281
- "src/config/apitogo-config-loader.ts"() {
476
+ var APITOGO_CONFIG_FILENAME, ENV_PREFIX, DERIVED_MANIFEST_KEYS;
477
+ var init_apitogo_config_core = __esm({
478
+ "src/config/apitogo-config-core.ts"() {
282
479
  APITOGO_CONFIG_FILENAME = "apitogo.config.json";
283
480
  ENV_PREFIX = "APITOGO_CONFIG_";
284
- MANIFEST_KEYS = [
481
+ DERIVED_MANIFEST_KEYS = [
285
482
  "siteName",
286
483
  "branding",
287
484
  "pricing",
@@ -289,14 +486,49 @@ var init_apitogo_config_loader = __esm({
289
486
  "plans",
290
487
  "backend",
291
488
  "gateway",
292
- "projectId",
293
- "projectName"
489
+ "projectId"
294
490
  ];
295
491
  }
296
492
  });
297
493
 
494
+ // src/config/apitogo-config-loader.node.ts
495
+ import { existsSync, readFileSync as readFileSync2 } from "node:fs";
496
+ import path2 from "node:path";
497
+ function loadApitogoConfig(rootDir = process.cwd()) {
498
+ const configPath = path2.join(path2.resolve(rootDir), APITOGO_CONFIG_FILENAME);
499
+ if (!existsSync(configPath)) {
500
+ throw new Error(
501
+ `Missing ${APITOGO_CONFIG_FILENAME} in ${path2.resolve(rootDir)}`
502
+ );
503
+ }
504
+ const raw = readFileSync2(configPath, "utf8");
505
+ const parsed = JSON.parse(raw);
506
+ return normalizeApitogoConfig(applyEnvOverrides(parsed, process.env));
507
+ }
508
+ var init_apitogo_config_loader_node = __esm({
509
+ "src/config/apitogo-config-loader.node.ts"() {
510
+ init_apitogo_config_core();
511
+ }
512
+ });
513
+
514
+ // src/config/apitogo-config-loader.ts
515
+ var init_apitogo_config_loader = __esm({
516
+ "src/config/apitogo-config-loader.ts"() {
517
+ init_apitogo_config_core();
518
+ init_apitogo_config_loader_node();
519
+ }
520
+ });
521
+
522
+ // src/config/file-exists.ts
523
+ import { stat } from "node:fs/promises";
524
+ var fileExists;
525
+ var init_file_exists = __esm({
526
+ "src/config/file-exists.ts"() {
527
+ fileExists = (path39) => stat(path39).then(() => true).catch(() => false);
528
+ }
529
+ });
530
+
298
531
  // src/config/local-manifest.ts
299
- import { access, readFile } from "node:fs/promises";
300
532
  import path3 from "node:path";
301
533
  import { z as z3 } from "zod";
302
534
  function isPricingEnabled(manifest) {
@@ -335,7 +567,6 @@ function plansToPreviewCatalog(plans) {
335
567
  plans: plans.map((plan) => ({
336
568
  sku: plan.sku,
337
569
  displayName: plan.displayName,
338
- isFree: plan.isFree,
339
570
  priceAmount: plan.priceAmount ?? 0,
340
571
  priceCurrency: plan.priceCurrency ?? "usd",
341
572
  billingPeriod: normalizeBillingPeriod(plan.billingPeriod),
@@ -347,48 +578,6 @@ function plansToPreviewCatalog(plans) {
347
578
  function resolveManifestEnv(viteMode) {
348
579
  return viteMode === "production" ? "production" : "development";
349
580
  }
350
- function mergePlansBySku(existing, patch) {
351
- const bySku = /* @__PURE__ */ new Map();
352
- for (const plan of existing ?? []) {
353
- bySku.set(plan.sku, plan);
354
- }
355
- for (const plan of patch) {
356
- const prior = bySku.get(plan.sku);
357
- bySku.set(plan.sku, prior ? { ...prior, ...plan } : plan);
358
- }
359
- return Array.from(bySku.values());
360
- }
361
- function mergeManifest(base, override) {
362
- const merged = { ...base, ...override };
363
- if (override.branding) {
364
- merged.branding = { ...base.branding, ...override.branding };
365
- }
366
- if (override.pricing) {
367
- merged.pricing = { ...base.pricing, ...override.pricing };
368
- }
369
- if (override.authentication) {
370
- merged.authentication = {
371
- ...base.authentication,
372
- ...override.authentication
373
- };
374
- }
375
- if (override.backend) {
376
- merged.backend = { ...base.backend, ...override.backend };
377
- }
378
- if (override.gateway) {
379
- merged.gateway = { ...base.gateway, ...override.gateway };
380
- }
381
- if (override.plans) {
382
- merged.plans = mergePlansBySku(base.plans, override.plans);
383
- }
384
- return merged;
385
- }
386
- function manifestFilePath(rootDir, filename) {
387
- return path3.join(
388
- path3.resolve(rootDir),
389
- filename ?? DEV_PORTAL_MANIFEST_FILENAME
390
- );
391
- }
392
581
  function manifestPathsForEnv(rootDir, _env) {
393
582
  const resolvedRoot = path3.resolve(rootDir);
394
583
  const configPath = path3.join(resolvedRoot, APITOGO_CONFIG_FILENAME);
@@ -398,35 +587,6 @@ function manifestPathsForEnv(rootDir, _env) {
398
587
  watchPaths: [configPath]
399
588
  };
400
589
  }
401
- function parseLocalManifestJson(raw) {
402
- try {
403
- const parsed = JSON.parse(raw);
404
- const result = DevPortalLocalManifestSchema.safeParse(parsed);
405
- if (!result.success) {
406
- return null;
407
- }
408
- return result.data;
409
- } catch {
410
- return null;
411
- }
412
- }
413
- async function fileExists2(filePath) {
414
- try {
415
- await access(filePath);
416
- return true;
417
- } catch {
418
- return false;
419
- }
420
- }
421
- async function readManifestFile(rootDir, filename) {
422
- const filePath = manifestFilePath(rootDir, filename);
423
- try {
424
- const raw = await readFile(filePath, "utf8");
425
- return parseLocalManifestJson(raw);
426
- } catch {
427
- return null;
428
- }
429
- }
430
590
  function parseManifestRecord(manifest) {
431
591
  if (!manifest) {
432
592
  return null;
@@ -434,65 +594,36 @@ function parseManifestRecord(manifest) {
434
594
  const result = DevPortalLocalManifestSchema.safeParse(manifest);
435
595
  return result.success ? result.data : null;
436
596
  }
437
- async function readEffectiveManifest(rootDir, env) {
597
+ async function readEffectiveManifest(rootDir, _env) {
438
598
  try {
439
599
  const config2 = loadApitogoConfig(rootDir);
440
- return parseManifestRecord(extractManifest(config2));
441
- } catch {
442
- return readLegacyEffectiveManifest(rootDir, env);
443
- }
444
- }
445
- async function readLegacyEffectiveManifest(rootDir, env) {
446
- const resolvedRoot = path3.resolve(rootDir);
447
- const basePath = path3.join(resolvedRoot, APITOGO_MANIFEST_BASE_FILENAME);
448
- const overridePath = path3.join(resolvedRoot, APITOGO_MANIFEST_ENV_FILES[env]);
449
- const hasBase = await fileExists2(basePath);
450
- const hasOverride = await fileExists2(overridePath);
451
- if (!hasBase) {
452
- const legacy = await readManifestFile(
453
- rootDir,
454
- APITOGO_MANIFEST_ENV_FILES.development
455
- );
456
- if (legacy) {
457
- return legacy;
600
+ const manifest = parseManifestRecord(extractManifest(config2));
601
+ if (!manifest) {
602
+ return null;
458
603
  }
459
- if (hasOverride) {
460
- return readManifestFile(rootDir, APITOGO_MANIFEST_ENV_FILES[env]);
604
+ if (!manifest.plans?.length) {
605
+ const items = readPlanCatalogItems(config2);
606
+ if (items.length) {
607
+ manifest.plans = items;
608
+ }
461
609
  }
610
+ return {
611
+ ...manifest,
612
+ pricing: { ...manifest.pricing, enabled: isBillingEnabled(config2) }
613
+ };
614
+ } catch {
462
615
  return null;
463
616
  }
464
- const base = await readManifestFile(rootDir, APITOGO_MANIFEST_BASE_FILENAME);
465
- if (!base) {
466
- return null;
467
- }
468
- if (!hasOverride) {
469
- return base;
470
- }
471
- const override = await readManifestFile(
472
- rootDir,
473
- APITOGO_MANIFEST_ENV_FILES[env]
474
- );
475
- if (!override) {
476
- return base;
477
- }
478
- return mergeManifest(base, override);
479
617
  }
480
- var DEV_PORTAL_MANIFEST_FILENAME, APITOGO_MANIFEST_BASE_FILENAME, APITOGO_MANIFEST_ENV_FILES, BillingPeriodSchema, ManifestPlanInputSchema, DevPortalLocalManifestSchema;
618
+ var BillingPeriodSchema, ManifestPlanInputSchema, DevPortalLocalManifestSchema;
481
619
  var init_local_manifest = __esm({
482
620
  "src/config/local-manifest.ts"() {
483
621
  init_apitogo_config_loader();
484
- DEV_PORTAL_MANIFEST_FILENAME = "apitogo.local.json";
485
- APITOGO_MANIFEST_BASE_FILENAME = "apitogo.json";
486
- APITOGO_MANIFEST_ENV_FILES = {
487
- development: "apitogo.local.json",
488
- production: "apitogo.prod.json"
489
- };
490
622
  BillingPeriodSchema = z3.enum(["month", "year", "monthly", "annual"]);
491
623
  ManifestPlanInputSchema = z3.object({
492
624
  sku: z3.string().min(1),
493
625
  displayName: z3.string().min(1),
494
- isFree: z3.boolean(),
495
- priceAmount: z3.number().optional(),
626
+ priceAmount: z3.number().default(0),
496
627
  priceCurrency: z3.string().optional(),
497
628
  billingPeriod: BillingPeriodSchema.optional(),
498
629
  limitsJson: z3.string().optional(),
@@ -508,7 +639,9 @@ var init_local_manifest = __esm({
508
639
  enabled: z3.boolean().optional()
509
640
  }).optional(),
510
641
  authentication: z3.object({
511
- enabled: z3.boolean().optional()
642
+ enabled: z3.boolean().optional(),
643
+ type: z3.string().optional(),
644
+ apiBaseUrl: z3.string().optional()
512
645
  }).optional(),
513
646
  plans: z3.array(ManifestPlanInputSchema).optional(),
514
647
  backend: z3.object({
@@ -527,15 +660,14 @@ var init_local_manifest = __esm({
527
660
  limitsJson: z3.string().optional(),
528
661
  publicHealthActionKey: z3.string().optional()
529
662
  }).optional(),
530
- projectId: z3.string().optional(),
531
- projectName: z3.string().optional()
663
+ projectId: z3.string().optional()
532
664
  });
533
665
  }
534
666
  });
535
667
 
536
668
  // src/config/validators/ModulesSchema.ts
537
669
  import { z as z4 } from "zod";
538
- var ModuleToggleSchema, DocsModuleSchema, LandingLinkSchema, LandingFeatureSchema, LandingHeroSchema, LandingContentSchema, DashboardContentSchema, ProfileContentSchema, PlanTierSchema, PlansContentSchema, SubmodulePageFields, LandingModuleSchema, UserManagementModuleSchema, ApiKeysModuleSchema, PlansModuleSchema, DashboardModuleSchema, UserPanelModuleSchema, ModulesSchema;
670
+ var ModuleToggleSchema, DocsModuleSchema, LandingLinkSchema, LandingFeatureSchema, LandingHeroSchema, LandingContentSchema, DashboardContentSchema, ProfileContentSchema, PlanTierSchema, PlansContentSchema, SubmodulePageFields, LandingModuleSchema, UserManagementModuleSchema, ApiKeysModuleSchema, PlanCatalogItemSchema, PlansModuleSchema, DashboardModuleSchema, UserPanelModuleSchema, ModulesSchema;
539
671
  var init_ModulesSchema = __esm({
540
672
  "src/config/validators/ModulesSchema.ts"() {
541
673
  ModuleToggleSchema = z4.object({
@@ -635,12 +767,24 @@ var init_ModulesSchema = __esm({
635
767
  ApiKeysModuleSchema = ModuleToggleSchema.extend({
636
768
  path: z4.string().default("settings/api-keys").describe("Route path for API key management.")
637
769
  });
770
+ PlanCatalogItemSchema = z4.object({
771
+ sku: z4.string(),
772
+ displayName: z4.string(),
773
+ priceAmount: z4.number().default(0),
774
+ priceCurrency: z4.string().optional(),
775
+ billingPeriod: z4.string().optional(),
776
+ limitsJson: z4.string().optional(),
777
+ includedActionKeys: z4.array(z4.string()).optional()
778
+ });
638
779
  PlansModuleSchema = ModuleToggleSchema.extend({
639
780
  path: z4.string().default("plans").describe("Route path for plans and subscription management."),
640
781
  page: SubmodulePageFields.page,
641
782
  component: SubmodulePageFields.component,
642
783
  content: PlansContentSchema.describe(
643
784
  "Default plans page content when no custom component is provided."
785
+ ),
786
+ items: z4.array(PlanCatalogItemSchema).optional().describe(
787
+ "Gateway plan catalog for /pricing preview and publish (canonical storage)."
644
788
  )
645
789
  });
646
790
  DashboardModuleSchema = ModuleToggleSchema.extend({
@@ -757,17 +901,18 @@ var init_resolve_modules = __esm({
757
901
  const plansConfig = userPanelModule?.plans;
758
902
  const dashboardEnabled = dashboardConfig?.enabled ?? (explicit ? false : false);
759
903
  const userManagementEnabled = explicit ? userManagementConfig?.enabled ?? !!config2.authentication : !!config2.authentication;
760
- const apiKeysEnabled = explicit ? apiKeysModuleConfig?.enabled ?? config2.apiKeys?.enabled ?? false : config2.apiKeys?.enabled ?? false;
761
- const plansEnabled = plansConfig?.enabled ?? false;
904
+ const apiKeysEnabled = explicit ? apiKeysModuleConfig?.enabled ?? false : false;
905
+ const billingEnabled = config2.__meta?.pricingEnabled === true || plansConfig?.enabled === true;
906
+ const plansEnabled = billingEnabled;
762
907
  const userPanelEnabled = userPanelModule?.enabled ?? (dashboardEnabled || userManagementEnabled || apiKeysEnabled || plansEnabled);
763
908
  const basePath = userPanelModule?.basePath ?? "/account";
764
909
  return {
765
910
  docs: {
766
911
  enabled: docsEnabled,
767
- files: docsModule?.files ?? config2.docs?.files,
768
- publishMarkdown: docsModule?.publishMarkdown ?? config2.docs?.publishMarkdown,
769
- defaultOptions: docsModule?.defaultOptions ?? config2.docs?.defaultOptions,
770
- llms: docsModule?.llms ?? config2.docs?.llms
912
+ files: docsModule?.files,
913
+ publishMarkdown: docsModule?.publishMarkdown,
914
+ defaultOptions: docsModule?.defaultOptions,
915
+ llms: docsModule?.llms
771
916
  },
772
917
  landing: {
773
918
  enabled: landingEnabled,
@@ -807,7 +952,8 @@ var init_resolve_modules = __esm({
807
952
  plans: {
808
953
  enabled: plansEnabled,
809
954
  path: joinPanelPath(basePath, plansConfig?.path ?? "plans"),
810
- content: resolvePlansContent(plansConfig?.content)
955
+ content: resolvePlansContent(plansConfig?.content),
956
+ items: plansConfig?.items
811
957
  }
812
958
  }
813
959
  };
@@ -4245,12 +4391,18 @@ async function loadZudokuConfig(configEnv, rootDir) {
4245
4391
  const loadedConfig = await loadZudokuConfigWithMeta(rootDir);
4246
4392
  const manifestEnv = resolveManifestEnv(configEnv.mode);
4247
4393
  const manifest = await readEffectiveManifest(rootDir, manifestEnv);
4394
+ const previewCatalog = manifestToPreviewCatalog(manifest);
4395
+ const jsonConfig = loadApitogoConfig(rootDir);
4396
+ const previewPlans = previewCatalog.plans.length ? previewCatalog.plans : plansToPreviewCatalog(
4397
+ readPlanCatalogItems(jsonConfig)
4398
+ ).plans;
4248
4399
  const configWithManifestMeta = {
4249
4400
  ...loadedConfig,
4250
4401
  __meta: {
4251
4402
  ...loadedConfig.__meta,
4252
- pricingEnabled: isPricingEnabled(manifest),
4253
- authenticationEnabled: isAuthenticationEnabled(manifest)
4403
+ pricingEnabled: isBillingEnabled(jsonConfig),
4404
+ authenticationEnabled: isAuthenticationEnabled(manifest),
4405
+ previewPlans
4254
4406
  }
4255
4407
  };
4256
4408
  const transformedConfig = applyAuthenticationHeaderNav(
@@ -4258,6 +4410,10 @@ async function loadZudokuConfig(configEnv, rootDir) {
4258
4410
  );
4259
4411
  config = {
4260
4412
  ...transformedConfig,
4413
+ __meta: {
4414
+ ...transformedConfig.__meta,
4415
+ ...configWithManifestMeta.__meta
4416
+ },
4261
4417
  __resolvedModules: resolveModulesConfig(transformedConfig)
4262
4418
  };
4263
4419
  if (!process.env.APITOGO_JSON_ONLY) {
@@ -4285,6 +4441,7 @@ var init_loader = __esm({
4285
4441
  init_auth_header_nav();
4286
4442
  init_transform_config();
4287
4443
  init_invariant();
4444
+ init_apitogo_config_loader();
4288
4445
  init_file_exists();
4289
4446
  init_local_manifest();
4290
4447
  init_resolve_modules();
@@ -4411,7 +4568,7 @@ import yargs from "yargs/yargs";
4411
4568
  import path30 from "node:path";
4412
4569
 
4413
4570
  // src/vite/build.ts
4414
- import { mkdir as mkdir5, readFile as readFile4, rename, rm as rm2, writeFile as writeFile5 } from "node:fs/promises";
4571
+ import { mkdir as mkdir5, readFile as readFile3, rename, rm as rm2, writeFile as writeFile5 } from "node:fs/promises";
4415
4572
  import path28 from "node:path";
4416
4573
  import { build as esbuild } from "esbuild";
4417
4574
  import { build as viteBuild, mergeConfig as mergeConfig3 } from "vite";
@@ -4559,7 +4716,7 @@ import {
4559
4716
  // package.json
4560
4717
  var package_default = {
4561
4718
  name: "@lukoweb/apitogo",
4562
- version: "0.1.51",
4719
+ version: "0.1.53",
4563
4720
  type: "module",
4564
4721
  sideEffects: [
4565
4722
  "**/*.css",
@@ -4966,12 +5123,14 @@ init_joinUrl();
4966
5123
 
4967
5124
  // src/vite/dev-portal-env.ts
4968
5125
  import { loadEnv as loadEnv2 } from "vite";
5126
+ var AUTHENTICATION_API_BASE_URL_ENV = "VITE_APITOGO_AUTHENTICATION_API_BASE_URL";
5127
+ var LEGACY_DEV_PORTAL_API_URL_ENV = "VITE_APITOGO_DEV_PORTAL_API_URL";
4969
5128
  async function loadDevPortalViteDefines(rootDir, mode) {
4970
5129
  const env = loadEnv2(mode, rootDir, "VITE_");
4971
5130
  const defines = {};
4972
- const apiUrl = env.VITE_APITOGO_DEV_PORTAL_API_URL?.trim();
5131
+ const apiUrl = env[AUTHENTICATION_API_BASE_URL_ENV]?.trim() || env[LEGACY_DEV_PORTAL_API_URL_ENV]?.trim();
4973
5132
  if (apiUrl) {
4974
- defines.VITE_APITOGO_DEV_PORTAL_API_URL = JSON.stringify(apiUrl);
5133
+ defines[AUTHENTICATION_API_BASE_URL_ENV] = JSON.stringify(apiUrl);
4975
5134
  }
4976
5135
  return defines;
4977
5136
  }
@@ -6613,7 +6772,7 @@ import { mdxFromMarkdown } from "mdast-util-mdx";
6613
6772
  import { mdxjs } from "micromark-extension-mdxjs";
6614
6773
 
6615
6774
  // src/lib/util/readFrontmatter.ts
6616
- import { readFile as readFile2 } from "node:fs/promises";
6775
+ import { readFile } from "node:fs/promises";
6617
6776
  import matter from "gray-matter";
6618
6777
  import { parse, stringify as stringify2 } from "yaml";
6619
6778
  var yaml = {
@@ -6621,7 +6780,7 @@ var yaml = {
6621
6780
  stringify: (obj) => stringify2(obj)
6622
6781
  };
6623
6782
  var readFrontmatter = async (filePath) => {
6624
- const content = await readFile2(filePath, "utf-8");
6783
+ const content = await readFile(filePath, "utf-8");
6625
6784
  const normalizedContent = content.replace(/\r\n/g, "\n");
6626
6785
  return matter(normalizedContent, { engines: { yaml } });
6627
6786
  };
@@ -7237,17 +7396,33 @@ var viteConfigPlugin = () => {
7237
7396
  configResolved(resolvedConfig) {
7238
7397
  viteConfig = resolvedConfig;
7239
7398
  },
7240
- load(id) {
7399
+ async load(id) {
7241
7400
  if (id !== resolvedVirtualModuleId3) return;
7242
7401
  const configPath = getCurrentConfig().__meta.configPath;
7243
7402
  if (!configPath) {
7244
7403
  return `export default {};`;
7245
7404
  }
7405
+ const { config: loadedConfig } = await loadZudokuConfig(
7406
+ {
7407
+ mode: viteConfig.mode,
7408
+ command: viteConfig.command
7409
+ },
7410
+ viteConfig.root
7411
+ );
7412
+ const clientMeta = {
7413
+ rootDir: loadedConfig.__meta.rootDir,
7414
+ pricingEnabled: loadedConfig.__meta.pricingEnabled,
7415
+ authenticationEnabled: loadedConfig.__meta.authenticationEnabled,
7416
+ previewPlans: loadedConfig.__meta.previewPlans
7417
+ };
7246
7418
  return `
7247
7419
  import rawConfig from "${normalizePath(configPath)}";
7248
7420
  import { runPluginTransformConfig } from "@lukoweb/apitogo/plugins";
7249
7421
 
7250
- const config = await runPluginTransformConfig(rawConfig);
7422
+ const config = await runPluginTransformConfig({
7423
+ ...rawConfig,
7424
+ __meta: ${JSON.stringify(clientMeta)},
7425
+ });
7251
7426
  export default config;
7252
7427
  `;
7253
7428
  },
@@ -7522,6 +7697,7 @@ var viteDocsPlugin = () => {
7522
7697
  var plugin_docs_default = viteDocsPlugin;
7523
7698
 
7524
7699
  // src/vite/plugin-local-manifest.ts
7700
+ init_apitogo_config_loader();
7525
7701
  init_local_manifest();
7526
7702
  import path17 from "node:path";
7527
7703
  var APITOGO_LOCAL_MANIFEST_VIRTUAL_ID = "virtual:apitogo-local-manifest";
@@ -7536,6 +7712,15 @@ var viteLocalManifestPlugin = () => {
7536
7712
  try {
7537
7713
  const manifest = await readEffectiveManifest(rootDir, env);
7538
7714
  catalog = manifestToPreviewCatalog(manifest);
7715
+ if (!catalog.plans.length) {
7716
+ const config2 = loadApitogoConfig(rootDir);
7717
+ catalog = {
7718
+ ...catalog,
7719
+ ...plansToPreviewCatalog(
7720
+ readPlanCatalogItems(config2)
7721
+ )
7722
+ };
7723
+ }
7539
7724
  } catch {
7540
7725
  }
7541
7726
  return `export default ${JSON.stringify(catalog)};`;
@@ -8766,7 +8951,7 @@ async function writeOutput(dir, {
8766
8951
  // src/vite/prerender/prerender.ts
8767
8952
  init_logger();
8768
8953
  init_file_exists();
8769
- import { readFile as readFile3, rm } from "node:fs/promises";
8954
+ import { readFile as readFile2, rm } from "node:fs/promises";
8770
8955
  import os from "node:os";
8771
8956
  import path27 from "node:path";
8772
8957
  import { pathToFileURL } from "node:url";
@@ -9042,7 +9227,7 @@ var prerender = async ({
9042
9227
  );
9043
9228
  let markdownFileInfos = [];
9044
9229
  if (await fileExists(markdownInfoPath)) {
9045
- const markdownInfoContent = await readFile3(markdownInfoPath, "utf-8");
9230
+ const markdownInfoContent = await readFile2(markdownInfoPath, "utf-8");
9046
9231
  markdownFileInfos = JSON.parse(markdownInfoContent);
9047
9232
  }
9048
9233
  if (llmsConfig.llmsTxt || llmsConfig.llmsTxtFull) {
@@ -9193,7 +9378,7 @@ var bundleSSREntry = async (options) => {
9193
9378
  const { dir, adapter, serverOutDir, serverConfigFilename, html, basePath } = options;
9194
9379
  const tempEntryPath = path28.join(dir, "__ssr-entry.ts");
9195
9380
  const packageRoot = getZudokuRootDir();
9196
- const templateContent = await readFile4(
9381
+ const templateContent = await readFile3(
9197
9382
  path28.join(packageRoot, "src/vite/ssr-templates", `${adapter}.ts`),
9198
9383
  "utf-8"
9199
9384
  );
@@ -9470,7 +9655,7 @@ var build_default = {
9470
9655
 
9471
9656
  // src/cli/configure-github-workflow/handler.ts
9472
9657
  import { constants } from "node:fs";
9473
- import { access as access2, mkdir as mkdir6, writeFile as writeFile6 } from "node:fs/promises";
9658
+ import { access, mkdir as mkdir6, writeFile as writeFile6 } from "node:fs/promises";
9474
9659
  import path31 from "node:path";
9475
9660
  var APITOGO_DEPLOY_WORKFLOW_YAML = `name: Build and Deploy
9476
9661
 
@@ -9587,7 +9772,7 @@ async function configureGithubWorkflow(argv) {
9587
9772
  const workflowPath = path31.join(workflowDir, "apitogo-deploy.yml");
9588
9773
  try {
9589
9774
  try {
9590
- await access2(workflowPath, constants.F_OK);
9775
+ await access(workflowPath, constants.F_OK);
9591
9776
  if (!argv.force) {
9592
9777
  await printCriticalFailureToConsoleAndExit(
9593
9778
  `Workflow file already exists: ${workflowPath}. Pass --force to overwrite.`
@@ -9656,14 +9841,14 @@ function applyJsonOnlyDeployQuietMode() {
9656
9841
  }
9657
9842
 
9658
9843
  // src/cli/deploy/handler.ts
9659
- import { access as access3, readFile as readFile5 } from "node:fs/promises";
9844
+ import { access as access2, readFile as readFile4 } from "node:fs/promises";
9660
9845
  import path32 from "node:path";
9661
9846
  import { create as createTar } from "tar";
9662
9847
  var DEPLOY_ENDPOINT = "https://deploy-server-dotnet2-b3fkcaaraydbckfe.canadacentral-01.azurewebsites.net/deploy";
9663
9848
  var ARCHIVE_NAME = "dist.tgz";
9664
9849
  var createDeploymentArchive = async (dir) => {
9665
9850
  const distDir = path32.join(dir, "dist");
9666
- await access3(distDir);
9851
+ await access2(distDir);
9667
9852
  const archivePath = path32.join(dir, ARCHIVE_NAME);
9668
9853
  await createTar(
9669
9854
  {
@@ -9694,7 +9879,7 @@ async function deploy(argv) {
9694
9879
  `Uploading ${path32.basename(archivePath)} to ${env}...`
9695
9880
  );
9696
9881
  }
9697
- const archiveBuffer = await readFile5(archivePath);
9882
+ const archiveBuffer = await readFile4(archivePath);
9698
9883
  const formData = new FormData();
9699
9884
  formData.append(
9700
9885
  "file",
@@ -10181,14 +10366,14 @@ var dev_default = {
10181
10366
 
10182
10367
  // src/cli/make-config/handler.ts
10183
10368
  import { constants as constants3 } from "node:fs";
10184
- import { access as access5, mkdir as mkdir7, readFile as readFile7, writeFile as writeFile8 } from "node:fs/promises";
10369
+ import { access as access4, mkdir as mkdir7, readFile as readFile6, writeFile as writeFile8 } from "node:fs/promises";
10185
10370
  import path37 from "node:path";
10186
10371
  import { glob as glob5 } from "glob";
10187
10372
 
10188
10373
  // src/cli/make-config/scaffold-project.ts
10189
10374
  init_package_json();
10190
10375
  import { constants as constants2 } from "node:fs";
10191
- import { access as access4, readFile as readFile6, writeFile as writeFile7 } from "node:fs/promises";
10376
+ import { access as access3, readFile as readFile5, writeFile as writeFile7 } from "node:fs/promises";
10192
10377
  import path36 from "node:path";
10193
10378
  import { glob as glob4 } from "glob";
10194
10379
  var OPENAPI_GLOBS = [
@@ -10263,7 +10448,7 @@ async function findOpenApiSpecPath(dir) {
10263
10448
  for (const rel of OPENAPI_GLOBS) {
10264
10449
  const full = path36.join(dir, rel);
10265
10450
  try {
10266
- await access4(full, constants2.R_OK);
10451
+ await access3(full, constants2.R_OK);
10267
10452
  return toPosix(rel);
10268
10453
  } catch {
10269
10454
  }
@@ -10289,7 +10474,7 @@ async function ensurePackageJson(dir) {
10289
10474
  preview: "apitogo preview"
10290
10475
  };
10291
10476
  try {
10292
- await access4(pkgPath, constants2.R_OK);
10477
+ await access3(pkgPath, constants2.R_OK);
10293
10478
  } catch (err) {
10294
10479
  const code = err.code;
10295
10480
  if (code !== "ENOENT") throw err;
@@ -10311,7 +10496,7 @@ async function ensurePackageJson(dir) {
10311
10496
  `, "utf8");
10312
10497
  return true;
10313
10498
  }
10314
- const raw = await readFile6(pkgPath, "utf8");
10499
+ const raw = await readFile5(pkgPath, "utf8");
10315
10500
  let pkg;
10316
10501
  try {
10317
10502
  pkg = JSON.parse(raw);
@@ -10341,7 +10526,7 @@ async function ensurePackageJson(dir) {
10341
10526
  async function ensureTsconfigJson(dir) {
10342
10527
  const tsPath = path36.join(dir, "tsconfig.json");
10343
10528
  try {
10344
- await access4(tsPath, constants2.R_OK);
10529
+ await access3(tsPath, constants2.R_OK);
10345
10530
  return false;
10346
10531
  } catch {
10347
10532
  const tsconfig = {
@@ -10673,7 +10858,7 @@ var ensureApitogoDeployWorkflow = async (dir) => {
10673
10858
  "apitogo-deploy.yml"
10674
10859
  );
10675
10860
  try {
10676
- await access5(workflowPath, constants3.F_OK);
10861
+ await access4(workflowPath, constants3.F_OK);
10677
10862
  return false;
10678
10863
  } catch {
10679
10864
  }
@@ -10685,7 +10870,7 @@ var findExistingConfigPath = async (dir) => {
10685
10870
  for (const filename of CONFIG_FILENAMES) {
10686
10871
  const fullPath = path37.join(dir, filename);
10687
10872
  try {
10688
- await readFile7(fullPath, "utf8");
10873
+ await readFile6(fullPath, "utf8");
10689
10874
  return fullPath;
10690
10875
  } catch {
10691
10876
  }
@@ -10732,7 +10917,7 @@ async function makeConfig(argv) {
10732
10917
  if (routePaths.length === 0) {
10733
10918
  throw new Error(`No .md or .mdx files found under '${pagesDir}'.`);
10734
10919
  }
10735
- let originalConfig = await readFile7(configPath, "utf8");
10920
+ let originalConfig = await readFile6(configPath, "utf8");
10736
10921
  if (!createdNew && openApiSpecPath) {
10737
10922
  originalConfig = insertApisIfMissing(
10738
10923
  originalConfig,
@@ -10896,7 +11081,7 @@ var remove_default = {
10896
11081
 
10897
11082
  // src/cli/common/outdated.ts
10898
11083
  import { existsSync as existsSync3, mkdirSync } from "node:fs";
10899
- import { readFile as readFile8, writeFile as writeFile9 } from "node:fs/promises";
11084
+ import { readFile as readFile7, writeFile as writeFile9 } from "node:fs/promises";
10900
11085
  import { join } from "node:path";
10901
11086
  import colors10 from "picocolors";
10902
11087
  import { gt } from "semver";
@@ -11026,7 +11211,7 @@ async function getVersionCheckInfo() {
11026
11211
  let versionCheckInfo;
11027
11212
  if (existsSync3(versionCheckPath)) {
11028
11213
  try {
11029
- versionCheckInfo = await readFile8(versionCheckPath, "utf-8").then(
11214
+ versionCheckInfo = await readFile7(versionCheckPath, "utf-8").then(
11030
11215
  JSON.parse
11031
11216
  );
11032
11217
  } catch {