@lukoweb/apitogo 0.1.47 → 0.1.48

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
@@ -3985,7 +3985,7 @@ import yargs from "yargs/yargs";
3985
3985
  import path29 from "node:path";
3986
3986
 
3987
3987
  // src/vite/build.ts
3988
- import { mkdir as mkdir5, readFile as readFile3, rename, rm as rm2, writeFile as writeFile5 } from "node:fs/promises";
3988
+ import { mkdir as mkdir5, readFile as readFile4, rename, rm as rm2, writeFile as writeFile5 } from "node:fs/promises";
3989
3989
  import path27 from "node:path";
3990
3990
  import { build as esbuild } from "esbuild";
3991
3991
  import { build as viteBuild, mergeConfig as mergeConfig3 } from "vite";
@@ -4133,7 +4133,7 @@ import {
4133
4133
  // package.json
4134
4134
  var package_default = {
4135
4135
  name: "@lukoweb/apitogo",
4136
- version: "0.1.47",
4136
+ version: "0.1.48",
4137
4137
  type: "module",
4138
4138
  sideEffects: [
4139
4139
  "**/*.css",
@@ -7085,12 +7085,270 @@ var viteDocsPlugin = () => {
7085
7085
  };
7086
7086
  var plugin_docs_default = viteDocsPlugin;
7087
7087
 
7088
+ // src/vite/plugin-local-manifest.ts
7089
+ import path16 from "node:path";
7090
+
7091
+ // src/config/local-manifest.ts
7092
+ import { access, readFile as readFile2 } from "node:fs/promises";
7093
+ import path15 from "node:path";
7094
+ import { z as z10 } from "zod";
7095
+ var DEV_PORTAL_MANIFEST_FILENAME = "apitogo.local.json";
7096
+ var APITOGO_MANIFEST_BASE_FILENAME = "apitogo.json";
7097
+ var APITOGO_MANIFEST_ENV_FILES = {
7098
+ development: "apitogo.local.json",
7099
+ production: "apitogo.prod.json"
7100
+ };
7101
+ var BillingPeriodSchema = z10.enum(["month", "year", "monthly", "annual"]);
7102
+ var ManifestPlanInputSchema = z10.object({
7103
+ sku: z10.string().min(1),
7104
+ displayName: z10.string().min(1),
7105
+ isFree: z10.boolean(),
7106
+ priceAmount: z10.number().optional(),
7107
+ priceCurrency: z10.string().optional(),
7108
+ billingPeriod: BillingPeriodSchema.optional(),
7109
+ limitsJson: z10.string().optional(),
7110
+ includedActionKeys: z10.array(z10.string()).optional()
7111
+ });
7112
+ var DevPortalLocalManifestSchema = z10.object({
7113
+ siteName: z10.string().optional(),
7114
+ branding: z10.object({
7115
+ title: z10.string().optional(),
7116
+ logoUrl: z10.string().optional()
7117
+ }).optional(),
7118
+ plans: z10.array(ManifestPlanInputSchema).optional(),
7119
+ backend: z10.object({
7120
+ sourceDirectory: z10.string().optional(),
7121
+ publishDirectory: z10.string().optional(),
7122
+ openApiPath: z10.string().optional(),
7123
+ runtime: z10.string().optional(),
7124
+ runtimeVersion: z10.string().optional(),
7125
+ deployed: z10.boolean().optional()
7126
+ }).optional(),
7127
+ gateway: z10.object({
7128
+ authMode: z10.enum(["platformManagedEntra", "bringYourOwnOidc"]).optional(),
7129
+ oidcMetadataUrl: z10.string().optional(),
7130
+ oidcAllowedIssuers: z10.array(z10.string()).optional(),
7131
+ oidcAudiences: z10.array(z10.string()).optional(),
7132
+ limitsJson: z10.string().optional(),
7133
+ publicHealthActionKey: z10.string().optional()
7134
+ }).optional(),
7135
+ projectId: z10.string().optional(),
7136
+ projectName: z10.string().optional()
7137
+ });
7138
+ function normalizeBillingPeriod(period) {
7139
+ if (!period) {
7140
+ return "monthly";
7141
+ }
7142
+ const normalized = period.toLowerCase();
7143
+ if (normalized === "month") {
7144
+ return "monthly";
7145
+ }
7146
+ if (normalized === "year") {
7147
+ return "annual";
7148
+ }
7149
+ return normalized;
7150
+ }
7151
+ function plansToPreviewCatalog(plans) {
7152
+ if (!plans?.length) {
7153
+ return { plans: [] };
7154
+ }
7155
+ return {
7156
+ plans: plans.map((plan) => ({
7157
+ sku: plan.sku,
7158
+ displayName: plan.displayName,
7159
+ isFree: plan.isFree,
7160
+ priceAmount: plan.priceAmount ?? 0,
7161
+ priceCurrency: plan.priceCurrency ?? "usd",
7162
+ billingPeriod: normalizeBillingPeriod(plan.billingPeriod),
7163
+ limitsJson: plan.limitsJson ?? null,
7164
+ includedActionKeys: plan.includedActionKeys ?? []
7165
+ }))
7166
+ };
7167
+ }
7168
+ function resolveManifestEnv(viteMode) {
7169
+ return viteMode === "production" ? "production" : "development";
7170
+ }
7171
+ function mergePlansBySku(existing, patch) {
7172
+ const bySku = /* @__PURE__ */ new Map();
7173
+ for (const plan of existing ?? []) {
7174
+ bySku.set(plan.sku, plan);
7175
+ }
7176
+ for (const plan of patch) {
7177
+ const prior = bySku.get(plan.sku);
7178
+ bySku.set(plan.sku, prior ? { ...prior, ...plan } : plan);
7179
+ }
7180
+ return Array.from(bySku.values());
7181
+ }
7182
+ function mergeManifest(base, override) {
7183
+ const merged = { ...base, ...override };
7184
+ if (override.branding) {
7185
+ merged.branding = { ...base.branding, ...override.branding };
7186
+ }
7187
+ if (override.backend) {
7188
+ merged.backend = { ...base.backend, ...override.backend };
7189
+ }
7190
+ if (override.gateway) {
7191
+ merged.gateway = { ...base.gateway, ...override.gateway };
7192
+ }
7193
+ if (override.plans) {
7194
+ merged.plans = mergePlansBySku(base.plans, override.plans);
7195
+ }
7196
+ return merged;
7197
+ }
7198
+ function manifestFilePath(rootDir, filename) {
7199
+ return path15.join(
7200
+ path15.resolve(rootDir),
7201
+ filename ?? DEV_PORTAL_MANIFEST_FILENAME
7202
+ );
7203
+ }
7204
+ function manifestPathsForEnv(rootDir, env) {
7205
+ const resolvedRoot = path15.resolve(rootDir);
7206
+ const basePath = path15.join(resolvedRoot, APITOGO_MANIFEST_BASE_FILENAME);
7207
+ const overridePath = path15.join(
7208
+ resolvedRoot,
7209
+ APITOGO_MANIFEST_ENV_FILES[env]
7210
+ );
7211
+ return {
7212
+ basePath,
7213
+ overridePath,
7214
+ watchPaths: [basePath, overridePath]
7215
+ };
7216
+ }
7217
+ function parseLocalManifestJson(raw) {
7218
+ try {
7219
+ const parsed = JSON.parse(raw);
7220
+ const result = DevPortalLocalManifestSchema.safeParse(parsed);
7221
+ if (!result.success) {
7222
+ return null;
7223
+ }
7224
+ return result.data;
7225
+ } catch {
7226
+ return null;
7227
+ }
7228
+ }
7229
+ async function fileExists2(filePath) {
7230
+ try {
7231
+ await access(filePath);
7232
+ return true;
7233
+ } catch {
7234
+ return false;
7235
+ }
7236
+ }
7237
+ async function readManifestFile(rootDir, filename) {
7238
+ const filePath = manifestFilePath(rootDir, filename);
7239
+ try {
7240
+ const raw = await readFile2(filePath, "utf8");
7241
+ return parseLocalManifestJson(raw);
7242
+ } catch {
7243
+ return null;
7244
+ }
7245
+ }
7246
+ async function readEffectiveManifest(rootDir, env) {
7247
+ const { basePath, overridePath } = manifestPathsForEnv(rootDir, env);
7248
+ const hasBase = await fileExists2(basePath);
7249
+ const hasOverride = await fileExists2(overridePath);
7250
+ if (!hasBase) {
7251
+ const legacy = await readManifestFile(
7252
+ rootDir,
7253
+ APITOGO_MANIFEST_ENV_FILES.development
7254
+ );
7255
+ if (legacy) {
7256
+ return legacy;
7257
+ }
7258
+ if (hasOverride) {
7259
+ return readManifestFile(rootDir, APITOGO_MANIFEST_ENV_FILES[env]);
7260
+ }
7261
+ return null;
7262
+ }
7263
+ const base = await readManifestFile(rootDir, APITOGO_MANIFEST_BASE_FILENAME);
7264
+ if (!base) {
7265
+ return null;
7266
+ }
7267
+ if (!hasOverride) {
7268
+ return base;
7269
+ }
7270
+ const override = await readManifestFile(
7271
+ rootDir,
7272
+ APITOGO_MANIFEST_ENV_FILES[env]
7273
+ );
7274
+ if (!override) {
7275
+ return base;
7276
+ }
7277
+ return mergeManifest(base, override);
7278
+ }
7279
+
7280
+ // src/vite/plugin-local-manifest.ts
7281
+ var APITOGO_LOCAL_MANIFEST_VIRTUAL_ID = "virtual:apitogo-local-manifest";
7282
+ var resolvedVirtualModuleId4 = `\0${APITOGO_LOCAL_MANIFEST_VIRTUAL_ID}`;
7283
+ var viteLocalManifestPlugin = () => {
7284
+ let rootDir = "";
7285
+ let viteMode = "development";
7286
+ let watchPaths = [];
7287
+ const loadManifestModule = async () => {
7288
+ const env = resolveManifestEnv(viteMode);
7289
+ let catalog = {
7290
+ plans: []
7291
+ };
7292
+ try {
7293
+ const manifest = await readEffectiveManifest(rootDir, env);
7294
+ catalog = plansToPreviewCatalog(manifest?.plans);
7295
+ } catch {
7296
+ }
7297
+ return `export default ${JSON.stringify(catalog)};`;
7298
+ };
7299
+ return {
7300
+ name: "apitogo-local-manifest-plugin",
7301
+ configResolved(resolvedConfig) {
7302
+ rootDir = resolvedConfig.root;
7303
+ viteMode = resolvedConfig.mode;
7304
+ const env = resolveManifestEnv(viteMode);
7305
+ watchPaths = manifestPathsForEnv(rootDir, env).watchPaths;
7306
+ },
7307
+ resolveId(id) {
7308
+ if (id === APITOGO_LOCAL_MANIFEST_VIRTUAL_ID) {
7309
+ return resolvedVirtualModuleId4;
7310
+ }
7311
+ },
7312
+ async load(id) {
7313
+ if (id !== resolvedVirtualModuleId4) {
7314
+ return;
7315
+ }
7316
+ return loadManifestModule();
7317
+ },
7318
+ configureServer(server) {
7319
+ for (const watchPath of watchPaths) {
7320
+ server.watcher.add(watchPath);
7321
+ }
7322
+ server.watcher.on("change", async (changedPath) => {
7323
+ const resolvedChanged = path16.resolve(changedPath);
7324
+ if (!watchPaths.some((p) => path16.resolve(p) === resolvedChanged)) {
7325
+ return;
7326
+ }
7327
+ const module = server.moduleGraph.getModuleById(
7328
+ resolvedVirtualModuleId4
7329
+ );
7330
+ if (module) {
7331
+ server.moduleGraph.invalidateModule(module);
7332
+ }
7333
+ const mod = await server.moduleGraph.getModuleByUrl(
7334
+ APITOGO_LOCAL_MANIFEST_VIRTUAL_ID
7335
+ );
7336
+ if (mod) {
7337
+ server.reloadModule(mod);
7338
+ }
7339
+ server.ws.send({ type: "full-reload", path: "*" });
7340
+ });
7341
+ }
7342
+ };
7343
+ };
7344
+ var plugin_local_manifest_default = viteLocalManifestPlugin;
7345
+
7088
7346
  // src/vite/plugin-markdown-export.ts
7089
7347
  init_loader();
7090
7348
  init_ProtectedRoutesSchema();
7091
7349
  init_joinUrl();
7092
7350
  import { mkdir as mkdir2, writeFile as writeFile2 } from "node:fs/promises";
7093
- import path15 from "node:path";
7351
+ import path17 from "node:path";
7094
7352
  import { matchPath } from "react-router";
7095
7353
  var processMarkdownFile = async (filePath) => {
7096
7354
  const { content: markdownContent, data: frontmatter } = await readFrontmatter(filePath);
@@ -7178,7 +7436,7 @@ var viteMarkdownExportPlugin = () => {
7178
7436
  if (process.env.NODE_ENV !== "production" || Object.keys(markdownFiles).length === 0 || !needsMdFiles) {
7179
7437
  return;
7180
7438
  }
7181
- const distDir = path15.join(
7439
+ const distDir = path17.join(
7182
7440
  config2.__meta.rootDir,
7183
7441
  "dist",
7184
7442
  config2.basePath ?? ""
@@ -7199,15 +7457,15 @@ var viteMarkdownExportPlugin = () => {
7199
7457
  content: finalMarkdown
7200
7458
  });
7201
7459
  const segments = routePath === "/" ? ["index"] : routePath.split("/").filter(Boolean);
7202
- const outputPath = `${path15.join(distDir, ...segments)}.md`;
7203
- await mkdir2(path15.dirname(outputPath), { recursive: true });
7460
+ const outputPath = `${path17.join(distDir, ...segments)}.md`;
7461
+ await mkdir2(path17.dirname(outputPath), { recursive: true });
7204
7462
  await writeFile2(outputPath, finalMarkdown, "utf-8");
7205
7463
  } catch (error) {
7206
7464
  console.warn(`Failed to export markdown for ${routePath}:`, error);
7207
7465
  }
7208
7466
  }
7209
7467
  if (config2.docs?.llms?.llmsTxt || config2.docs?.llms?.llmsTxtFull) {
7210
- const markdownInfoPath = path15.join(
7468
+ const markdownInfoPath = path17.join(
7211
7469
  config2.__meta.rootDir,
7212
7470
  "node_modules/.apitogo/markdown-info.json"
7213
7471
  );
@@ -7359,9 +7617,9 @@ var remarkCodeTabs = () => (tree) => {
7359
7617
  };
7360
7618
 
7361
7619
  // src/vite/mdx/remark-inject-filepath.ts
7362
- import path16 from "node:path";
7620
+ import path18 from "node:path";
7363
7621
  var remarkInjectFilepath = (rootDir) => (tree, vfile) => {
7364
- const relativePath = path16.relative(rootDir, vfile.path).split(path16.sep).join(path16.posix.sep);
7622
+ const relativePath = path18.relative(rootDir, vfile.path).split(path18.sep).join(path18.posix.sep);
7365
7623
  tree.children.unshift(exportMdxjsConst("__filepath", relativePath));
7366
7624
  };
7367
7625
 
@@ -7448,18 +7706,18 @@ var remarkLastModified = () => {
7448
7706
  };
7449
7707
 
7450
7708
  // src/vite/mdx/remark-link-rewrite.ts
7451
- import path17 from "node:path";
7709
+ import path19 from "node:path";
7452
7710
  import { visit as visit5 } from "unist-util-visit";
7453
7711
  var remarkLinkRewrite = (basePath = "") => (tree) => {
7454
7712
  visit5(tree, "link", (node) => {
7455
7713
  if (!node.url) return;
7456
7714
  if (node.url.startsWith("http") || node.url.startsWith("mailto:")) return;
7457
7715
  node.url = node.url.replace(/\\/g, "/");
7458
- const base = path17.posix.join(basePath);
7716
+ const base = path19.posix.join(basePath);
7459
7717
  if (basePath && node.url.startsWith(base)) {
7460
7718
  node.url = node.url.slice(base.length);
7461
7719
  } else if (!node.url.startsWith("/") && !node.url.startsWith("#")) {
7462
- node.url = path17.posix.join("..", node.url);
7720
+ node.url = path19.posix.join("..", node.url);
7463
7721
  }
7464
7722
  node.url = node.url.replace(/\.mdx?(#.*)?$/, "$1");
7465
7723
  });
@@ -7679,11 +7937,11 @@ var plugin_mdx_default = viteMdxPlugin;
7679
7937
 
7680
7938
  // src/vite/plugin-modules.ts
7681
7939
  init_loader();
7682
- import path18 from "node:path";
7940
+ import path20 from "node:path";
7683
7941
  import { normalizePath as normalizePath2 } from "vite";
7684
- var getLandingModuleImport = (moduleDir, mode) => mode === "internal" ? normalizePath2(path18.join(moduleDir, "../module-landing/src/index.tsx")) : "@lukoweb/apitogo-module-landing";
7685
- var getUserPanelModuleImport = (moduleDir, mode) => mode === "internal" ? normalizePath2(path18.join(moduleDir, "../module-user-panel/src/index.tsx")) : "@lukoweb/apitogo-module-user-panel";
7686
- var resolveProjectPageImport = (rootDir, pagePath) => normalizePath2(path18.resolve(rootDir, pagePath));
7942
+ var getLandingModuleImport = (moduleDir, mode) => mode === "internal" ? normalizePath2(path20.join(moduleDir, "../module-landing/src/index.tsx")) : "@lukoweb/apitogo-module-landing";
7943
+ var getUserPanelModuleImport = (moduleDir, mode) => mode === "internal" ? normalizePath2(path20.join(moduleDir, "../module-user-panel/src/index.tsx")) : "@lukoweb/apitogo-module-user-panel";
7944
+ var resolveProjectPageImport = (rootDir, pagePath) => normalizePath2(path20.resolve(rootDir, pagePath));
7687
7945
  var viteModulesPlugin = () => {
7688
7946
  const virtualModuleId4 = "virtual:zudoku-modules-plugin";
7689
7947
  const resolvedVirtualModuleId5 = `\0${virtualModuleId4}`;
@@ -7804,7 +8062,7 @@ var plugin_modules_default = viteModulesPlugin;
7804
8062
 
7805
8063
  // src/vite/plugin-search.ts
7806
8064
  init_loader();
7807
- import path19 from "node:path";
8065
+ import path21 from "node:path";
7808
8066
  var viteSearchPlugin = () => {
7809
8067
  const virtualModuleId4 = "virtual:zudoku-search-plugin";
7810
8068
  const resolvedVirtualModuleId5 = `\0${virtualModuleId4}`;
@@ -7822,7 +8080,7 @@ var viteSearchPlugin = () => {
7822
8080
  return resolvedVirtualModuleId5;
7823
8081
  }
7824
8082
  if (id === "/pagefind/pagefind.js" && resolvedViteConfig?.publicDir) {
7825
- return path19.join(resolvedViteConfig.publicDir, "pagefind/pagefind.js");
8083
+ return path21.join(resolvedViteConfig.publicDir, "pagefind/pagefind.js");
7826
8084
  }
7827
8085
  },
7828
8086
  async load(id) {
@@ -7933,161 +8191,6 @@ var viteShikiRegisterPlugin = () => {
7933
8191
  };
7934
8192
  };
7935
8193
 
7936
- // src/vite/plugin-local-manifest.ts
7937
- import path21 from "node:path";
7938
-
7939
- // src/config/local-manifest.ts
7940
- import path20 from "node:path";
7941
- import { z as z10 } from "zod";
7942
- var DEV_PORTAL_MANIFEST_FILENAME = "apitogo.local.json";
7943
- var BillingPeriodSchema = z10.enum([
7944
- "month",
7945
- "year",
7946
- "monthly",
7947
- "annual"
7948
- ]);
7949
- var ManifestPlanInputSchema = z10.object({
7950
- sku: z10.string().min(1),
7951
- displayName: z10.string().min(1),
7952
- isFree: z10.boolean(),
7953
- priceAmount: z10.number().optional(),
7954
- priceCurrency: z10.string().optional(),
7955
- billingPeriod: BillingPeriodSchema.optional(),
7956
- limitsJson: z10.string().optional(),
7957
- includedActionKeys: z10.array(z10.string()).optional()
7958
- });
7959
- var DevPortalLocalManifestSchema = z10.object({
7960
- siteName: z10.string().optional(),
7961
- branding: z10.object({
7962
- title: z10.string().optional(),
7963
- logoUrl: z10.string().optional()
7964
- }).optional(),
7965
- plans: z10.array(ManifestPlanInputSchema).optional(),
7966
- backend: z10.object({
7967
- sourceDirectory: z10.string().optional(),
7968
- publishDirectory: z10.string().optional(),
7969
- openApiPath: z10.string().optional(),
7970
- runtime: z10.string().optional(),
7971
- runtimeVersion: z10.string().optional(),
7972
- deployed: z10.boolean().optional()
7973
- }).optional(),
7974
- gateway: z10.object({
7975
- authMode: z10.enum(["platformManagedEntra", "bringYourOwnOidc"]).optional(),
7976
- oidcMetadataUrl: z10.string().optional(),
7977
- oidcAllowedIssuers: z10.array(z10.string()).optional(),
7978
- oidcAudiences: z10.array(z10.string()).optional(),
7979
- limitsJson: z10.string().optional(),
7980
- publicHealthActionKey: z10.string().optional()
7981
- }).optional(),
7982
- projectId: z10.string().optional(),
7983
- projectName: z10.string().optional()
7984
- });
7985
- function normalizeBillingPeriod(period) {
7986
- if (!period) {
7987
- return "monthly";
7988
- }
7989
- const normalized = period.toLowerCase();
7990
- if (normalized === "month") {
7991
- return "monthly";
7992
- }
7993
- if (normalized === "year") {
7994
- return "annual";
7995
- }
7996
- return normalized;
7997
- }
7998
- function plansToPreviewCatalog(plans) {
7999
- if (!plans?.length) {
8000
- return { plans: [] };
8001
- }
8002
- return {
8003
- plans: plans.map((plan) => ({
8004
- sku: plan.sku,
8005
- displayName: plan.displayName,
8006
- isFree: plan.isFree,
8007
- priceAmount: plan.priceAmount ?? 0,
8008
- priceCurrency: plan.priceCurrency ?? "usd",
8009
- billingPeriod: normalizeBillingPeriod(plan.billingPeriod),
8010
- limitsJson: plan.limitsJson ?? null,
8011
- includedActionKeys: plan.includedActionKeys ?? []
8012
- }))
8013
- };
8014
- }
8015
- function manifestFilePath(rootDir) {
8016
- return path20.join(path20.resolve(rootDir), DEV_PORTAL_MANIFEST_FILENAME);
8017
- }
8018
- function parseLocalManifestJson(raw) {
8019
- try {
8020
- const parsed = JSON.parse(raw);
8021
- const result = DevPortalLocalManifestSchema.safeParse(parsed);
8022
- if (!result.success) {
8023
- return null;
8024
- }
8025
- return result.data;
8026
- } catch {
8027
- return null;
8028
- }
8029
- }
8030
-
8031
- // src/vite/plugin-local-manifest.ts
8032
- var APITOGO_LOCAL_MANIFEST_VIRTUAL_ID = "virtual:apitogo-local-manifest";
8033
- var resolvedVirtualModuleId4 = `\0${APITOGO_LOCAL_MANIFEST_VIRTUAL_ID}`;
8034
- var viteLocalManifestPlugin = () => {
8035
- let rootDir = "";
8036
- let manifestPath = "";
8037
- const loadManifestModule = async () => {
8038
- const { readFile: readFile8 } = await import("node:fs/promises");
8039
- let catalog = { plans: [] };
8040
- try {
8041
- const raw = await readFile8(manifestPath, "utf8");
8042
- const manifest = parseLocalManifestJson(raw);
8043
- catalog = plansToPreviewCatalog(manifest?.plans);
8044
- } catch {
8045
- }
8046
- return `export default ${JSON.stringify(catalog)};`;
8047
- };
8048
- return {
8049
- name: "apitogo-local-manifest-plugin",
8050
- configResolved(resolvedConfig) {
8051
- rootDir = resolvedConfig.root;
8052
- manifestPath = manifestFilePath(rootDir);
8053
- },
8054
- resolveId(id) {
8055
- if (id === APITOGO_LOCAL_MANIFEST_VIRTUAL_ID) {
8056
- return resolvedVirtualModuleId4;
8057
- }
8058
- },
8059
- async load(id) {
8060
- if (id !== resolvedVirtualModuleId4) {
8061
- return;
8062
- }
8063
- return loadManifestModule();
8064
- },
8065
- configureServer(server) {
8066
- const watchPath = path21.join(rootDir, DEV_PORTAL_MANIFEST_FILENAME);
8067
- server.watcher.add(watchPath);
8068
- server.watcher.on("change", async (changedPath) => {
8069
- if (path21.resolve(changedPath) !== path21.resolve(manifestPath)) {
8070
- return;
8071
- }
8072
- const module = server.moduleGraph.getModuleById(
8073
- resolvedVirtualModuleId4
8074
- );
8075
- if (module) {
8076
- server.moduleGraph.invalidateModule(module);
8077
- }
8078
- const mod = await server.moduleGraph.getModuleByUrl(
8079
- APITOGO_LOCAL_MANIFEST_VIRTUAL_ID
8080
- );
8081
- if (mod) {
8082
- server.reloadModule(mod);
8083
- }
8084
- server.ws.send({ type: "full-reload", path: "*" });
8085
- });
8086
- }
8087
- };
8088
- };
8089
- var plugin_local_manifest_default = viteLocalManifestPlugin;
8090
-
8091
8194
  // src/vite/plugin.ts
8092
8195
  init_plugin_theme();
8093
8196
  function vitePlugin() {
@@ -8419,7 +8522,7 @@ async function writeOutput(dir, {
8419
8522
  // src/vite/prerender/prerender.ts
8420
8523
  init_logger();
8421
8524
  init_file_exists();
8422
- import { readFile as readFile2, rm } from "node:fs/promises";
8525
+ import { readFile as readFile3, rm } from "node:fs/promises";
8423
8526
  import os from "node:os";
8424
8527
  import path26 from "node:path";
8425
8528
  import { pathToFileURL } from "node:url";
@@ -8695,7 +8798,7 @@ var prerender = async ({
8695
8798
  );
8696
8799
  let markdownFileInfos = [];
8697
8800
  if (await fileExists(markdownInfoPath)) {
8698
- const markdownInfoContent = await readFile2(markdownInfoPath, "utf-8");
8801
+ const markdownInfoContent = await readFile3(markdownInfoPath, "utf-8");
8699
8802
  markdownFileInfos = JSON.parse(markdownInfoContent);
8700
8803
  }
8701
8804
  if (llmsConfig.llmsTxt || llmsConfig.llmsTxtFull) {
@@ -8846,7 +8949,7 @@ var bundleSSREntry = async (options) => {
8846
8949
  const { dir, adapter, serverOutDir, serverConfigFilename, html, basePath } = options;
8847
8950
  const tempEntryPath = path27.join(dir, "__ssr-entry.ts");
8848
8951
  const packageRoot = getZudokuRootDir();
8849
- const templateContent = await readFile3(
8952
+ const templateContent = await readFile4(
8850
8953
  path27.join(packageRoot, "src/vite/ssr-templates", `${adapter}.ts`),
8851
8954
  "utf-8"
8852
8955
  );
@@ -9123,7 +9226,7 @@ var build_default = {
9123
9226
 
9124
9227
  // src/cli/configure-github-workflow/handler.ts
9125
9228
  import { constants } from "node:fs";
9126
- import { access, mkdir as mkdir6, writeFile as writeFile6 } from "node:fs/promises";
9229
+ import { access as access2, mkdir as mkdir6, writeFile as writeFile6 } from "node:fs/promises";
9127
9230
  import path30 from "node:path";
9128
9231
  var APITOGO_DEPLOY_WORKFLOW_YAML = `name: Build and Deploy
9129
9232
 
@@ -9240,7 +9343,7 @@ async function configureGithubWorkflow(argv) {
9240
9343
  const workflowPath = path30.join(workflowDir, "apitogo-deploy.yml");
9241
9344
  try {
9242
9345
  try {
9243
- await access(workflowPath, constants.F_OK);
9346
+ await access2(workflowPath, constants.F_OK);
9244
9347
  if (!argv.force) {
9245
9348
  await printCriticalFailureToConsoleAndExit(
9246
9349
  `Workflow file already exists: ${workflowPath}. Pass --force to overwrite.`
@@ -9309,14 +9412,14 @@ function applyJsonOnlyDeployQuietMode() {
9309
9412
  }
9310
9413
 
9311
9414
  // src/cli/deploy/handler.ts
9312
- import { access as access2, readFile as readFile4 } from "node:fs/promises";
9415
+ import { access as access3, readFile as readFile5 } from "node:fs/promises";
9313
9416
  import path31 from "node:path";
9314
9417
  import { create as createTar } from "tar";
9315
9418
  var DEPLOY_ENDPOINT = "https://deploy-server-dotnet2-b3fkcaaraydbckfe.canadacentral-01.azurewebsites.net/deploy";
9316
9419
  var ARCHIVE_NAME = "dist.tgz";
9317
9420
  var createDeploymentArchive = async (dir) => {
9318
9421
  const distDir = path31.join(dir, "dist");
9319
- await access2(distDir);
9422
+ await access3(distDir);
9320
9423
  const archivePath = path31.join(dir, ARCHIVE_NAME);
9321
9424
  await createTar(
9322
9425
  {
@@ -9347,7 +9450,7 @@ async function deploy(argv) {
9347
9450
  `Uploading ${path31.basename(archivePath)} to ${env}...`
9348
9451
  );
9349
9452
  }
9350
- const archiveBuffer = await readFile4(archivePath);
9453
+ const archiveBuffer = await readFile5(archivePath);
9351
9454
  const formData = new FormData();
9352
9455
  formData.append(
9353
9456
  "file",
@@ -9834,14 +9937,14 @@ var dev_default = {
9834
9937
 
9835
9938
  // src/cli/make-config/handler.ts
9836
9939
  import { constants as constants3 } from "node:fs";
9837
- import { access as access4, mkdir as mkdir7, readFile as readFile6, writeFile as writeFile8 } from "node:fs/promises";
9940
+ import { access as access5, mkdir as mkdir7, readFile as readFile7, writeFile as writeFile8 } from "node:fs/promises";
9838
9941
  import path36 from "node:path";
9839
9942
  import { glob as glob5 } from "glob";
9840
9943
 
9841
9944
  // src/cli/make-config/scaffold-project.ts
9842
9945
  init_package_json();
9843
9946
  import { constants as constants2 } from "node:fs";
9844
- import { access as access3, readFile as readFile5, writeFile as writeFile7 } from "node:fs/promises";
9947
+ import { access as access4, readFile as readFile6, writeFile as writeFile7 } from "node:fs/promises";
9845
9948
  import path35 from "node:path";
9846
9949
  import { glob as glob4 } from "glob";
9847
9950
  var OPENAPI_GLOBS = [
@@ -9916,7 +10019,7 @@ async function findOpenApiSpecPath(dir) {
9916
10019
  for (const rel of OPENAPI_GLOBS) {
9917
10020
  const full = path35.join(dir, rel);
9918
10021
  try {
9919
- await access3(full, constants2.R_OK);
10022
+ await access4(full, constants2.R_OK);
9920
10023
  return toPosix(rel);
9921
10024
  } catch {
9922
10025
  }
@@ -9942,7 +10045,7 @@ async function ensurePackageJson(dir) {
9942
10045
  preview: "apitogo preview"
9943
10046
  };
9944
10047
  try {
9945
- await access3(pkgPath, constants2.R_OK);
10048
+ await access4(pkgPath, constants2.R_OK);
9946
10049
  } catch (err) {
9947
10050
  const code = err.code;
9948
10051
  if (code !== "ENOENT") throw err;
@@ -9964,7 +10067,7 @@ async function ensurePackageJson(dir) {
9964
10067
  `, "utf8");
9965
10068
  return true;
9966
10069
  }
9967
- const raw = await readFile5(pkgPath, "utf8");
10070
+ const raw = await readFile6(pkgPath, "utf8");
9968
10071
  let pkg;
9969
10072
  try {
9970
10073
  pkg = JSON.parse(raw);
@@ -9994,7 +10097,7 @@ async function ensurePackageJson(dir) {
9994
10097
  async function ensureTsconfigJson(dir) {
9995
10098
  const tsPath = path35.join(dir, "tsconfig.json");
9996
10099
  try {
9997
- await access3(tsPath, constants2.R_OK);
10100
+ await access4(tsPath, constants2.R_OK);
9998
10101
  return false;
9999
10102
  } catch {
10000
10103
  const tsconfig = {
@@ -10326,7 +10429,7 @@ var ensureApitogoDeployWorkflow = async (dir) => {
10326
10429
  "apitogo-deploy.yml"
10327
10430
  );
10328
10431
  try {
10329
- await access4(workflowPath, constants3.F_OK);
10432
+ await access5(workflowPath, constants3.F_OK);
10330
10433
  return false;
10331
10434
  } catch {
10332
10435
  }
@@ -10338,7 +10441,7 @@ var findExistingConfigPath = async (dir) => {
10338
10441
  for (const filename of CONFIG_FILENAMES) {
10339
10442
  const fullPath = path36.join(dir, filename);
10340
10443
  try {
10341
- await readFile6(fullPath, "utf8");
10444
+ await readFile7(fullPath, "utf8");
10342
10445
  return fullPath;
10343
10446
  } catch {
10344
10447
  }
@@ -10385,7 +10488,7 @@ async function makeConfig(argv) {
10385
10488
  if (routePaths.length === 0) {
10386
10489
  throw new Error(`No .md or .mdx files found under '${pagesDir}'.`);
10387
10490
  }
10388
- let originalConfig = await readFile6(configPath, "utf8");
10491
+ let originalConfig = await readFile7(configPath, "utf8");
10389
10492
  if (!createdNew && openApiSpecPath) {
10390
10493
  originalConfig = insertApisIfMissing(
10391
10494
  originalConfig,
@@ -10549,7 +10652,7 @@ var remove_default = {
10549
10652
 
10550
10653
  // src/cli/common/outdated.ts
10551
10654
  import { existsSync as existsSync2, mkdirSync } from "node:fs";
10552
- import { readFile as readFile7, writeFile as writeFile9 } from "node:fs/promises";
10655
+ import { readFile as readFile8, writeFile as writeFile9 } from "node:fs/promises";
10553
10656
  import { join } from "node:path";
10554
10657
  import colors10 from "picocolors";
10555
10658
  import { gt } from "semver";
@@ -10679,7 +10782,7 @@ async function getVersionCheckInfo() {
10679
10782
  let versionCheckInfo;
10680
10783
  if (existsSync2(versionCheckPath)) {
10681
10784
  try {
10682
- versionCheckInfo = await readFile7(versionCheckPath, "utf-8").then(
10785
+ versionCheckInfo = await readFile8(versionCheckPath, "utf-8").then(
10683
10786
  JSON.parse
10684
10787
  );
10685
10788
  } catch {
@@ -1,5 +1,11 @@
1
1
  import { z } from "zod";
2
2
  export declare const DEV_PORTAL_MANIFEST_FILENAME = "apitogo.local.json";
3
+ export declare const APITOGO_MANIFEST_BASE_FILENAME = "apitogo.json";
4
+ export declare const APITOGO_MANIFEST_ENV_FILES: {
5
+ readonly development: "apitogo.local.json";
6
+ readonly production: "apitogo.prod.json";
7
+ };
8
+ export type ApitogoManifestEnv = keyof typeof APITOGO_MANIFEST_ENV_FILES;
3
9
  export declare const ManifestPlanInputSchema: z.ZodObject<{
4
10
  sku: z.ZodString;
5
11
  displayName: z.ZodString;
@@ -75,6 +81,16 @@ export type DevPortalPreviewPlanCatalog = {
75
81
  };
76
82
  export declare function normalizeBillingPeriod(period?: string): string;
77
83
  export declare function plansToPreviewCatalog(plans: ManifestPlanInput[] | undefined): DevPortalPreviewPlanCatalog;
78
- export declare function manifestFilePath(rootDir: string): string;
84
+ export declare function resolveManifestEnv(viteMode: string): ApitogoManifestEnv;
85
+ export declare function mergePlansBySku(existing: ManifestPlanInput[] | undefined, patch: ManifestPlanInput[]): ManifestPlanInput[];
86
+ export declare function mergeManifest(base: DevPortalLocalManifest, override: DevPortalLocalManifest): DevPortalLocalManifest;
87
+ export declare function manifestFilePath(rootDir: string, filename?: string): string;
88
+ export declare function manifestPathsForEnv(rootDir: string, env: ApitogoManifestEnv): {
89
+ basePath: string;
90
+ overridePath: string;
91
+ watchPaths: string[];
92
+ };
79
93
  export declare function parseLocalManifestJson(raw: string): DevPortalLocalManifest | null;
94
+ export declare function readManifestFile(rootDir: string, filename: string): Promise<DevPortalLocalManifest | null>;
95
+ export declare function readEffectiveManifest(rootDir: string, env: ApitogoManifestEnv): Promise<DevPortalLocalManifest | null>;
80
96
  export declare function readLocalManifest(rootDir: string): Promise<DevPortalLocalManifest | null>;
@@ -46,16 +46,16 @@ VITE_APITOGO_DEV_PORTAL_API_URL=https://your-dev-portal-api.example.com
46
46
 
47
47
  **Local preview:** When the backend URL is a placeholder or unreachable, the account area shows a
48
48
  message that sign-in unlocks after publish. Docs and API reference remain public. Plans on
49
- `/pricing` load from `apitogo.local.json` (including `includedActionKeys` per plan). Plan edits
50
- hot-reload without restarting the dev server.
49
+ `/pricing` load from `apitogo.json` with optional `apitogo.local.json` overrides (including
50
+ `includedActionKeys` per plan). Plan edits hot-reload without restarting the dev server.
51
51
 
52
52
  ### Dev portal configuration files
53
53
 
54
- | Concern | File |
55
- | --- | --- |
56
- | Site nav, docs, plugins, auth wiring | `apitogo.config.tsx` |
57
- | Plans, rate limits, `includedActionKeys`, project/deploy metadata | `apitogo.local.json` |
58
- | OIDC and Stripe for publish (MCP only) | `apitogo.local.secrets.json` |
54
+ | Concern | File |
55
+ | ----------------------------------------------------------------- | ---------------------------- |
56
+ | Site nav, docs, plugins, auth wiring | `apitogo.config.tsx` |
57
+ | Plans, rate limits, `includedActionKeys`, project/deploy metadata | `apitogo.json` (+ env overrides — see [Manifest](./manifest.md)) |
58
+ | OIDC and Stripe for publish (MCP only) | `apitogo.local.secrets.json` |
59
59
 
60
60
  **Production:** Register one OIDC app with redirect URI `https://{backend}/signin-oidc` (see MCP
61
61
  `manualSteps.oidcRedirectUri`). The frontend uses cookie sessions via `credentials: "include"` on
@@ -0,0 +1,116 @@
1
+ ---
2
+ title: Manifest (apitogo.json)
3
+ sidebar_icon: file-json
4
+ ---
5
+
6
+ Dev portal sites provisioned through APItoGo use JSON manifest files for plans, branding, backend
7
+ paths, and gateway settings. Shared defaults live in **`apitogo.json`**; optional per-environment
8
+ files override values without duplicating the full config.
9
+
10
+ ## Files
11
+
12
+ | File | When used | Purpose |
13
+ | ---- | --------- | ------- |
14
+ | `apitogo.json` | Always (base) | Committed defaults: `siteName`, `branding`, `plans`, `backend`, `gateway` |
15
+ | `apitogo.local.json` | `npm run dev` | Local-only overrides (optional) |
16
+ | `apitogo.prod.json` | `npm run build` | Production build overrides (optional) |
17
+ | `apitogo.local.secrets.json` | MCP publish only | OIDC and Stripe credentials — never loaded by the dev server |
18
+
19
+ New scaffolds ship `apitogo.json` plus `apitogo.local.example.json` and `apitogo.prod.example.json`
20
+ to show how overrides work. Copy or rename the examples when you need environment-specific values.
21
+
22
+ ## Resolution
23
+
24
+ At dev and build time the tooling merges the base file with the environment override:
25
+
26
+ ```mermaid
27
+ flowchart LR
28
+ base["apitogo.json"]
29
+ local["apitogo.local.json"]
30
+ prod["apitogo.prod.json"]
31
+ devMerge["merge"]
32
+ buildMerge["merge"]
33
+ viteDev["npm run dev"]
34
+ viteBuild["npm run build"]
35
+
36
+ base --> devMerge
37
+ local --> devMerge
38
+ base --> buildMerge
39
+ prod --> buildMerge
40
+ viteDev --> devMerge
41
+ buildMerge --> viteBuild
42
+ ```
43
+
44
+ - **Local preview** (`npm run dev`): `apitogo.json` + `apitogo.local.json`
45
+ - **Production build** (`npm run build`): `apitogo.json` + `apitogo.prod.json`
46
+ - **Deployed site**: plans are served from the live dev-portal API (`GET /api/v1/plans`), not from
47
+ JSON files in the browser bundle
48
+
49
+ If `apitogo.json` is missing but `apitogo.local.json` exists, the local file is used as the full
50
+ manifest (legacy single-file projects).
51
+
52
+ ## Merge rules
53
+
54
+ Override files patch the base manifest:
55
+
56
+ - Top-level keys: override wins
57
+ - `branding`, `backend`, `gateway`: deep-merged (override fields patch nested objects)
58
+ - `plans`: merged by `sku` — an override plan with the same SKU updates only the fields you specify
59
+
60
+ Example base (`apitogo.json`):
61
+
62
+ ```json
63
+ {
64
+ "siteName": "My API",
65
+ "branding": { "title": "My API" },
66
+ "plans": [
67
+ {
68
+ "sku": "pro",
69
+ "displayName": "Pro",
70
+ "isFree": false,
71
+ "priceAmount": 9.99,
72
+ "billingPeriod": "month"
73
+ }
74
+ ]
75
+ }
76
+ ```
77
+
78
+ Example local override (`apitogo.local.json`):
79
+
80
+ ```json
81
+ {
82
+ "projectId": "your-local-project-id",
83
+ "backend": { "sourceDirectory": "../my-api" }
84
+ }
85
+ ```
86
+
87
+ Example production override (`apitogo.prod.json`):
88
+
89
+ ```json
90
+ {
91
+ "plans": [{ "sku": "pro", "priceAmount": 19.99 }],
92
+ "backend": { "deployed": true }
93
+ }
94
+ ```
95
+
96
+ ## What to put where
97
+
98
+ | Concern | Recommended file |
99
+ | ------- | ---------------- |
100
+ | Plans, rate limits, `includedActionKeys` | `apitogo.json` (shared) |
101
+ | Branding, site name | `apitogo.json` |
102
+ | Backend paths, OpenAPI location | `apitogo.json`; machine-specific paths in `apitogo.local.json` |
103
+ | `projectId` after bootstrap | `apitogo.local.json` until MCP writes to the correct file |
104
+ | Production-only plan pricing or deploy flags | `apitogo.prod.json` |
105
+ | OIDC / Stripe | `apitogo.local.secrets.json` (see [Authentication](./authentication.md)) |
106
+
107
+ ## Hot reload
108
+
109
+ During `npm run dev`, changes to `apitogo.json` or `apitogo.local.json` hot-reload on `/pricing`
110
+ without restarting the server. Restart the dev server after changing `VITE_APITOGO_DEV_PORTAL_API_URL`
111
+ in `.env`.
112
+
113
+ ## Related configuration
114
+
115
+ - Site navigation, docs, and plugins: [`apitogo.config.tsx`](./overview.md)
116
+ - Authentication for dev-portal sites: [Authentication](./authentication.md)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lukoweb/apitogo",
3
- "version": "0.1.47",
3
+ "version": "0.1.48",
4
4
  "type": "module",
5
5
  "sideEffects": [
6
6
  "**/*.css",
@@ -1,15 +1,20 @@
1
- import { readFile } from "node:fs/promises";
1
+ import { access, readFile } from "node:fs/promises";
2
2
  import path from "node:path";
3
3
  import { z } from "zod";
4
4
 
5
+ /** @deprecated Use {@link APITOGO_MANIFEST_ENV_FILES.development} — kept for external consumers. */
5
6
  export const DEV_PORTAL_MANIFEST_FILENAME = "apitogo.local.json";
6
7
 
7
- const BillingPeriodSchema = z.enum([
8
- "month",
9
- "year",
10
- "monthly",
11
- "annual",
12
- ]);
8
+ export const APITOGO_MANIFEST_BASE_FILENAME = "apitogo.json";
9
+
10
+ export const APITOGO_MANIFEST_ENV_FILES = {
11
+ development: "apitogo.local.json",
12
+ production: "apitogo.prod.json",
13
+ } as const;
14
+
15
+ export type ApitogoManifestEnv = keyof typeof APITOGO_MANIFEST_ENV_FILES;
16
+
17
+ const BillingPeriodSchema = z.enum(["month", "year", "monthly", "annual"]);
13
18
 
14
19
  export const ManifestPlanInputSchema = z.object({
15
20
  sku: z.string().min(1),
@@ -56,7 +61,9 @@ export const DevPortalLocalManifestSchema = z.object({
56
61
  });
57
62
 
58
63
  export type ManifestPlanInput = z.infer<typeof ManifestPlanInputSchema>;
59
- export type DevPortalLocalManifest = z.infer<typeof DevPortalLocalManifestSchema>;
64
+ export type DevPortalLocalManifest = z.infer<
65
+ typeof DevPortalLocalManifestSchema
66
+ >;
60
67
 
61
68
  export type DevPortalPreviewPlan = {
62
69
  sku: string;
@@ -109,8 +116,70 @@ export function plansToPreviewCatalog(
109
116
  };
110
117
  }
111
118
 
112
- export function manifestFilePath(rootDir: string): string {
113
- return path.join(path.resolve(rootDir), DEV_PORTAL_MANIFEST_FILENAME);
119
+ export function resolveManifestEnv(viteMode: string): ApitogoManifestEnv {
120
+ return viteMode === "production" ? "production" : "development";
121
+ }
122
+
123
+ export function mergePlansBySku(
124
+ existing: ManifestPlanInput[] | undefined,
125
+ patch: ManifestPlanInput[],
126
+ ): ManifestPlanInput[] {
127
+ const bySku = new Map<string, ManifestPlanInput>();
128
+ for (const plan of existing ?? []) {
129
+ bySku.set(plan.sku, plan);
130
+ }
131
+ for (const plan of patch) {
132
+ const prior = bySku.get(plan.sku);
133
+ bySku.set(plan.sku, prior ? { ...prior, ...plan } : plan);
134
+ }
135
+ return Array.from(bySku.values());
136
+ }
137
+
138
+ export function mergeManifest(
139
+ base: DevPortalLocalManifest,
140
+ override: DevPortalLocalManifest,
141
+ ): DevPortalLocalManifest {
142
+ const merged: DevPortalLocalManifest = { ...base, ...override };
143
+
144
+ if (override.branding) {
145
+ merged.branding = { ...base.branding, ...override.branding };
146
+ }
147
+ if (override.backend) {
148
+ merged.backend = { ...base.backend, ...override.backend };
149
+ }
150
+ if (override.gateway) {
151
+ merged.gateway = { ...base.gateway, ...override.gateway };
152
+ }
153
+ if (override.plans) {
154
+ merged.plans = mergePlansBySku(base.plans, override.plans);
155
+ }
156
+
157
+ return merged;
158
+ }
159
+
160
+ export function manifestFilePath(rootDir: string, filename?: string): string {
161
+ return path.join(
162
+ path.resolve(rootDir),
163
+ filename ?? DEV_PORTAL_MANIFEST_FILENAME,
164
+ );
165
+ }
166
+
167
+ export function manifestPathsForEnv(
168
+ rootDir: string,
169
+ env: ApitogoManifestEnv,
170
+ ): { basePath: string; overridePath: string; watchPaths: string[] } {
171
+ const resolvedRoot = path.resolve(rootDir);
172
+ const basePath = path.join(resolvedRoot, APITOGO_MANIFEST_BASE_FILENAME);
173
+ const overridePath = path.join(
174
+ resolvedRoot,
175
+ APITOGO_MANIFEST_ENV_FILES[env],
176
+ );
177
+
178
+ return {
179
+ basePath,
180
+ overridePath,
181
+ watchPaths: [basePath, overridePath],
182
+ };
114
183
  }
115
184
 
116
185
  export function parseLocalManifestJson(
@@ -128,10 +197,20 @@ export function parseLocalManifestJson(
128
197
  }
129
198
  }
130
199
 
131
- export async function readLocalManifest(
200
+ async function fileExists(filePath: string): Promise<boolean> {
201
+ try {
202
+ await access(filePath);
203
+ return true;
204
+ } catch {
205
+ return false;
206
+ }
207
+ }
208
+
209
+ export async function readManifestFile(
132
210
  rootDir: string,
211
+ filename: string,
133
212
  ): Promise<DevPortalLocalManifest | null> {
134
- const filePath = manifestFilePath(rootDir);
213
+ const filePath = manifestFilePath(rootDir, filename);
135
214
 
136
215
  try {
137
216
  const raw = await readFile(filePath, "utf8");
@@ -140,3 +219,53 @@ export async function readLocalManifest(
140
219
  return null;
141
220
  }
142
221
  }
222
+
223
+ export async function readEffectiveManifest(
224
+ rootDir: string,
225
+ env: ApitogoManifestEnv,
226
+ ): Promise<DevPortalLocalManifest | null> {
227
+ const { basePath, overridePath } = manifestPathsForEnv(rootDir, env);
228
+ const hasBase = await fileExists(basePath);
229
+ const hasOverride = await fileExists(overridePath);
230
+
231
+ if (!hasBase) {
232
+ const legacy = await readManifestFile(
233
+ rootDir,
234
+ APITOGO_MANIFEST_ENV_FILES.development,
235
+ );
236
+ if (legacy) {
237
+ return legacy;
238
+ }
239
+
240
+ if (hasOverride) {
241
+ return readManifestFile(rootDir, APITOGO_MANIFEST_ENV_FILES[env]);
242
+ }
243
+
244
+ return null;
245
+ }
246
+
247
+ const base = await readManifestFile(rootDir, APITOGO_MANIFEST_BASE_FILENAME);
248
+ if (!base) {
249
+ return null;
250
+ }
251
+
252
+ if (!hasOverride) {
253
+ return base;
254
+ }
255
+
256
+ const override = await readManifestFile(
257
+ rootDir,
258
+ APITOGO_MANIFEST_ENV_FILES[env],
259
+ );
260
+ if (!override) {
261
+ return base;
262
+ }
263
+
264
+ return mergeManifest(base, override);
265
+ }
266
+
267
+ export async function readLocalManifest(
268
+ rootDir: string,
269
+ ): Promise<DevPortalLocalManifest | null> {
270
+ return readEffectiveManifest(rootDir, "development");
271
+ }
@@ -1,4 +1,4 @@
1
- import path from "node:path";
1
+
2
2
  import { loadEnv } from "vite";
3
3
 
4
4
  export const DEV_PORTAL_VITE_ENV_KEYS = [
@@ -1,26 +1,30 @@
1
1
  import path from "node:path";
2
2
  import type { Plugin, ResolvedConfig } from "vite";
3
3
  import {
4
- DEV_PORTAL_MANIFEST_FILENAME,
5
- manifestFilePath,
6
- parseLocalManifestJson,
4
+ manifestPathsForEnv,
7
5
  plansToPreviewCatalog,
6
+ readEffectiveManifest,
7
+ resolveManifestEnv,
8
8
  } from "../config/local-manifest.js";
9
9
 
10
- export const APITOGO_LOCAL_MANIFEST_VIRTUAL_ID = "virtual:apitogo-local-manifest";
10
+ export const APITOGO_LOCAL_MANIFEST_VIRTUAL_ID =
11
+ "virtual:apitogo-local-manifest";
12
+
11
13
  const resolvedVirtualModuleId = `\0${APITOGO_LOCAL_MANIFEST_VIRTUAL_ID}`;
12
14
 
13
15
  const viteLocalManifestPlugin = (): Plugin => {
14
16
  let rootDir = "";
15
- let manifestPath = "";
17
+ let viteMode = "development";
18
+ let watchPaths: string[] = [];
16
19
 
17
20
  const loadManifestModule = async (): Promise<string> => {
18
- const { readFile } = await import("node:fs/promises");
19
- let catalog = { plans: [] as ReturnType<typeof plansToPreviewCatalog>["plans"] };
21
+ const env = resolveManifestEnv(viteMode);
22
+ let catalog = {
23
+ plans: [] as ReturnType<typeof plansToPreviewCatalog>["plans"],
24
+ };
20
25
 
21
26
  try {
22
- const raw = await readFile(manifestPath, "utf8");
23
- const manifest = parseLocalManifestJson(raw);
27
+ const manifest = await readEffectiveManifest(rootDir, env);
24
28
  catalog = plansToPreviewCatalog(manifest?.plans);
25
29
  } catch {
26
30
  // Missing or invalid manifest — empty catalog for preview.
@@ -33,7 +37,9 @@ const viteLocalManifestPlugin = (): Plugin => {
33
37
  name: "apitogo-local-manifest-plugin",
34
38
  configResolved(resolvedConfig: ResolvedConfig) {
35
39
  rootDir = resolvedConfig.root;
36
- manifestPath = manifestFilePath(rootDir);
40
+ viteMode = resolvedConfig.mode;
41
+ const env = resolveManifestEnv(viteMode);
42
+ watchPaths = manifestPathsForEnv(rootDir, env).watchPaths;
37
43
  },
38
44
  resolveId(id) {
39
45
  if (id === APITOGO_LOCAL_MANIFEST_VIRTUAL_ID) {
@@ -48,11 +54,13 @@ const viteLocalManifestPlugin = (): Plugin => {
48
54
  return loadManifestModule();
49
55
  },
50
56
  configureServer(server) {
51
- const watchPath = path.join(rootDir, DEV_PORTAL_MANIFEST_FILENAME);
57
+ for (const watchPath of watchPaths) {
58
+ server.watcher.add(watchPath);
59
+ }
52
60
 
53
- server.watcher.add(watchPath);
54
61
  server.watcher.on("change", async (changedPath) => {
55
- if (path.resolve(changedPath) !== path.resolve(manifestPath)) {
62
+ const resolvedChanged = path.resolve(changedPath);
63
+ if (!watchPaths.some((p) => path.resolve(p) === resolvedChanged)) {
56
64
  return;
57
65
  }
58
66
 
@@ -11,13 +11,13 @@ import viteConfigPlugin from "./plugin-config.js";
11
11
  import viteCustomPagesPlugin from "./plugin-custom-pages.js";
12
12
  import { viteDocMetadataPlugin } from "./plugin-doc-metadata.js";
13
13
  import viteDocsPlugin from "./plugin-docs.js";
14
+ import viteLocalManifestPlugin from "./plugin-local-manifest.js";
14
15
  import viteMarkdownExportPlugin from "./plugin-markdown-export.js";
15
16
  import viteMdxPlugin from "./plugin-mdx.js";
16
17
  import viteModulesPlugin from "./plugin-modules.js";
17
18
  import { viteNavigationPlugin } from "./plugin-navigation.js";
18
19
  import { viteSearchPlugin } from "./plugin-search.js";
19
20
  import { viteShikiRegisterPlugin } from "./plugin-shiki-register.js";
20
- import viteLocalManifestPlugin from "./plugin-local-manifest.js";
21
21
  import { viteThemePlugin } from "./plugin-theme.js";
22
22
 
23
23
  export default function vitePlugin(): PluginOption {