@lukoweb/apitogo 0.2.60 → 0.2.64

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/dist/cli/cli.js +1101 -589
  2. package/dist/declarations/cli/platform/client/platform-api.d.ts +3 -0
  3. package/dist/declarations/cli/platform/errors.d.ts +5 -0
  4. package/docs/cli/commands.md +6 -6
  5. package/docs/cli/go-live.md +12 -12
  6. package/docs/cli/overview.md +26 -27
  7. package/docs/cli/setup.md +38 -46
  8. package/docs/cli/shape.md +32 -35
  9. package/docs/cli/walkthrough.md +19 -19
  10. package/docs/components/slot.mdx +116 -116
  11. package/docs/configuration/api-reference.md +1 -1
  12. package/docs/configuration/authentication.md +3 -3
  13. package/docs/configuration/footer.mdx +89 -89
  14. package/docs/configuration/gateway.md +8 -8
  15. package/docs/configuration/make-config.mdx +20 -10
  16. package/docs/configuration/overview.md +3 -3
  17. package/docs/configuration/plans.md +5 -5
  18. package/docs/configuration/portal.md +36 -0
  19. package/docs/customization/colors-theme.mdx +255 -255
  20. package/docs/customization/fonts.md +105 -105
  21. package/docs/customization/landing-page.md +1 -1
  22. package/docs/deploy/apitogo.md +3 -3
  23. package/docs/deployment.md +3 -3
  24. package/docs/guides/custom-pages.md +3 -3
  25. package/docs/guides/customer-account.md +1 -1
  26. package/docs/guides/stripe-setup.mdx +9 -9
  27. package/docs/markdown/frontmatter.md +2 -2
  28. package/docs/mcp/go-live.md +7 -7
  29. package/docs/mcp/overview.md +58 -58
  30. package/docs/mcp/setup.md +20 -15
  31. package/docs/mcp/shape.md +2 -2
  32. package/docs/mcp/tools.md +57 -57
  33. package/docs/quickstart.md +6 -4
  34. package/docs/writing.mdx +68 -68
  35. package/package.json +1 -1
  36. package/src/config/apitogo-config-core.ts +11 -1
  37. package/src/config/v2/home-redirect.ts +5 -5
  38. package/src/config/v2/index.ts +5 -1
  39. package/src/config/v2/portal-profile.ts +17 -0
  40. package/src/config/v2/resolve.ts +19 -0
  41. package/src/config/v2/rules.ts +124 -0
  42. package/src/config/v2/schema.ts +28 -0
  43. package/src/config/v2/wrapper.ts +76 -13
  44. package/src/vite/config.ts +278 -273
  45. package/src/vite/markdown-output-path.ts +48 -0
  46. package/src/vite/plugin-markdown-export.ts +210 -214
  47. package/src/vite/prerender/prerender.ts +4 -4
  48. package/src/vite/watch-ignore.ts +19 -0
package/dist/cli/cli.js CHANGED
@@ -457,8 +457,12 @@ function syncAuthentication(normalized) {
457
457
  if (!userPanelEnabled && !authentication) {
458
458
  return;
459
459
  }
460
+ if (!userPanelEnabled && authentication?.enabled === false) {
461
+ delete normalized.authentication;
462
+ return;
463
+ }
460
464
  const auth = authentication ?? {};
461
- auth.enabled = userPanelEnabled;
465
+ if (userPanelEnabled) auth.enabled = true;
462
466
  const keepsOpenId = auth.type === "openid";
463
467
  if (!keepsOpenId) auth.type = "dev-portal";
464
468
  if (userPanelEnabled && !keepsOpenId) {
@@ -837,7 +841,7 @@ import { stat as stat4 } from "node:fs/promises";
837
841
  var fileExists;
838
842
  var init_file_exists = __esm({
839
843
  "src/config/file-exists.ts"() {
840
- fileExists = (path99) => stat4(path99).then(() => true).catch(() => false);
844
+ fileExists = (path100) => stat4(path100).then(() => true).catch(() => false);
841
845
  }
842
846
  });
843
847
 
@@ -5270,7 +5274,7 @@ __export(llms_exports, {
5270
5274
  generateLlmsTxtFiles: () => generateLlmsTxtFiles
5271
5275
  });
5272
5276
  import { writeFile as writeFile12 } from "node:fs/promises";
5273
- import path46 from "node:path";
5277
+ import path47 from "node:path";
5274
5278
  import colors10 from "picocolors";
5275
5279
  async function generateLlmsTxtFiles({
5276
5280
  markdownFileInfos,
@@ -5305,7 +5309,7 @@ async function generateLlmsTxtFiles({
5305
5309
  }
5306
5310
  }
5307
5311
  const llmsTxt2 = llmsTxtParts.join("\n");
5308
- await writeFile12(path46.join(baseOutputDir, "llms.txt"), llmsTxt2, "utf-8");
5312
+ await writeFile12(path47.join(baseOutputDir, "llms.txt"), llmsTxt2, "utf-8");
5309
5313
  if (process.env.APITOGO_JSON_ONLY !== "1") {
5310
5314
  console.log(colors10.blue("\u2713 generated llms.txt"));
5311
5315
  }
@@ -5333,7 +5337,7 @@ ${info.content}
5333
5337
  }
5334
5338
  const llmsFull = llmsFullParts.join("\n");
5335
5339
  await writeFile12(
5336
- path46.join(baseOutputDir, "llms-full.txt"),
5340
+ path47.join(baseOutputDir, "llms-full.txt"),
5337
5341
  llmsFull,
5338
5342
  "utf-8"
5339
5343
  );
@@ -5836,7 +5840,7 @@ Run the command with --help to see its options.`;
5836
5840
  import yargs from "yargs/yargs";
5837
5841
 
5838
5842
  // src/cli/services/handler.ts
5839
- import path54 from "node:path";
5843
+ import path55 from "node:path";
5840
5844
 
5841
5845
  // src/config/v2/billing.ts
5842
5846
  import { readFile as readFile2 } from "node:fs/promises";
@@ -5915,6 +5919,21 @@ var nonEmpty = z.string().min(1, { error: "must not be empty" });
5915
5919
  var path3 = nonEmpty;
5916
5920
  var pathList = z.array(nonEmpty);
5917
5921
  var enabled = z.boolean().optional();
5922
+ var headerLabel = z.string().trim().min(1, { error: "must not be empty" }).max(40, { error: "must be 40 characters or fewer" });
5923
+ var HeaderLinkSchema = z.union(
5924
+ [
5925
+ z.strictObject({ label: headerLabel, page: path3 }),
5926
+ z.strictObject({
5927
+ label: headerLabel,
5928
+ to: z.string().regex(/^(\/(?!\/)|https:\/\/)/, {
5929
+ error: 'must start with "/" or "https://"'
5930
+ })
5931
+ })
5932
+ ],
5933
+ {
5934
+ error: 'must be { "label": "...", "page": "path/to/page.tsx" } or { "label": "...", "to": "/path or https://..." }'
5935
+ }
5936
+ );
5918
5937
  var PortalSchema = z.strictObject({
5919
5938
  enabled,
5920
5939
  dir: path3.optional(),
@@ -5931,6 +5950,8 @@ var PortalSchema = z.strictObject({
5931
5950
  landing: path3.optional(),
5932
5951
  /** The footer: a React component file shown under every page except the console's (API-129). */
5933
5952
  footer: path3.optional(),
5953
+ /** Links of the header's top menu, after the built-in ones (Documentation, API Reference; the plans add Pricing). */
5954
+ header: z.strictObject({ links: z.array(HeaderLinkSchema) }).optional(),
5934
5955
  /**
5935
5956
  * The logo in the portal header: one image, or one per color scheme (API-129). `dark` is optional; without it the
5936
5957
  * light image is used in both.
@@ -6094,8 +6115,8 @@ var ConfigV2Schema = z.strictObject({
6094
6115
  var REMOVED_KEYS = {
6095
6116
  "plans.provider": "plans.provider was removed. Only plans.billing.provider (stripe or none) is selectable."
6096
6117
  };
6097
- function pathToString(path99) {
6098
- return path99.reduce(
6118
+ function pathToString(path100) {
6119
+ return path100.reduce(
6099
6120
  (acc, segment) => typeof segment === "number" ? `${acc}[${segment}]` : acc === "" ? String(segment) : `${acc}.${String(segment)}`,
6100
6121
  ""
6101
6122
  );
@@ -6115,9 +6136,9 @@ function step(schema2, segment) {
6115
6136
  }
6116
6137
  return void 0;
6117
6138
  }
6118
- function knownKeysAt(path99) {
6139
+ function knownKeysAt(path100) {
6119
6140
  let current = ConfigV2Schema;
6120
- for (const segment of path99) {
6141
+ for (const segment of path100) {
6121
6142
  if (!current) return [];
6122
6143
  current = step(current, segment);
6123
6144
  }
@@ -6153,7 +6174,7 @@ function unknownKeyHint(fullPath, key, known) {
6153
6174
  return known.length > 0 ? `Allowed keys: ${known.join(", ")}.` : void 0;
6154
6175
  }
6155
6176
  function mapIssue(issue4) {
6156
- const path99 = pathToString(issue4.path);
6177
+ const path100 = pathToString(issue4.path);
6157
6178
  switch (issue4.code) {
6158
6179
  case "unrecognized_keys":
6159
6180
  return issue4.keys.map((key) => {
@@ -6169,20 +6190,20 @@ function mapIssue(issue4) {
6169
6190
  return [
6170
6191
  {
6171
6192
  code: "CONFIG_INVALID_VALUE",
6172
- path: path99,
6193
+ path: path100,
6173
6194
  message: `must be one of: ${issue4.values.map((v) => JSON.stringify(v)).join(", ")}.`
6174
6195
  }
6175
6196
  ];
6176
6197
  case "custom": {
6177
6198
  const code = typeof issue4.params?.code === "string" ? issue4.params.code : "CONFIG_INVALID_VALUE";
6178
- return [{ code, path: path99, message: issue4.message ?? "is invalid." }];
6199
+ return [{ code, path: path100, message: issue4.message ?? "is invalid." }];
6179
6200
  }
6180
6201
  case "invalid_type":
6181
6202
  return [
6182
- /received undefined/.test(issue4.message) ? { code: "CONFIG_MISSING_FIELD", path: path99, message: "is required." } : { code: "CONFIG_INVALID_VALUE", path: path99, message: issue4.message }
6203
+ /received undefined/.test(issue4.message) ? { code: "CONFIG_MISSING_FIELD", path: path100, message: "is required." } : { code: "CONFIG_INVALID_VALUE", path: path100, message: issue4.message }
6183
6204
  ];
6184
6205
  default:
6185
- return [{ code: "CONFIG_INVALID_VALUE", path: path99, message: issue4.message }];
6206
+ return [{ code: "CONFIG_INVALID_VALUE", path: path100, message: issue4.message }];
6186
6207
  }
6187
6208
  }
6188
6209
  function isRecord(value) {
@@ -6478,6 +6499,7 @@ function toGlobPath(value) {
6478
6499
  function fromConfig(configDir, value) {
6479
6500
  return value.startsWith("!") ? `!${toGlobPath(path7.resolve(configDir, value.slice(1)))}` : toGlobPath(path7.resolve(configDir, value));
6480
6501
  }
6502
+ var headerPageRoute = (file) => `/${path7.basename(file).replace(/\.(mdx?|tsx)$/i, "")}`;
6481
6503
  var isLogoUrl = (value) => value.toLowerCase().startsWith("https://");
6482
6504
  var logoSource = (configDir, value) => isLogoUrl(value) ? value : path7.resolve(configDir, value);
6483
6505
  function resolveConfigV2(config2, configPath) {
@@ -6513,6 +6535,13 @@ function resolveConfigV2(config2, configPath) {
6513
6535
  theme: portal.theme ? path7.resolve(configDir, portal.theme) : path7.join(dir, "theme.json"),
6514
6536
  ...portal.landing ? { landing: path7.resolve(configDir, portal.landing) } : {},
6515
6537
  ...portal.footer ? { footer: path7.resolve(configDir, portal.footer) } : {},
6538
+ headerLinks: (portal.header?.links ?? []).map(
6539
+ (link) => {
6540
+ if ("to" in link) return { label: link.label, to: link.to };
6541
+ const page = path7.resolve(configDir, link.page);
6542
+ return { label: link.label, to: headerPageRoute(page), page };
6543
+ }
6544
+ ),
6516
6545
  ...portal.logo ? {
6517
6546
  logo: typeof portal.logo === "string" ? {
6518
6547
  light: logoSource(configDir, portal.logo),
@@ -6644,17 +6673,30 @@ function loadConfigV2(pathOrDir) {
6644
6673
  }
6645
6674
 
6646
6675
  // src/config/v2/rules.ts
6647
- import { readdir as readdir2, readFile as readFile4, stat as stat2 } from "node:fs/promises";
6648
- import path10 from "node:path";
6649
- import { glob as glob2 } from "glob";
6676
+ import { readdir as readdir2, readFile as readFile5, stat as stat2 } from "node:fs/promises";
6677
+ import path11 from "node:path";
6678
+ import { glob as glob3 } from "glob";
6650
6679
  import { parse as parseYaml } from "yaml";
6651
6680
 
6652
- // src/config/v2/tsx-pages.ts
6653
- import { readFile as readFile3 } from "node:fs/promises";
6681
+ // src/config/v2/home-redirect.ts
6654
6682
  import path9 from "node:path";
6655
6683
  import { glob } from "glob";
6656
6684
  import globParent from "glob-parent";
6657
6685
 
6686
+ // src/lib/util/readFrontmatter.ts
6687
+ import { readFile as readFile3 } from "node:fs/promises";
6688
+ import matter from "gray-matter";
6689
+ import { parse, stringify } from "yaml";
6690
+ var yaml = {
6691
+ parse: (input5) => parse(input5) ?? {},
6692
+ stringify: (obj) => stringify(obj)
6693
+ };
6694
+ var readFrontmatter = async (filePath) => {
6695
+ const content = await readFile3(filePath, "utf-8");
6696
+ const normalizedContent = content.replace(/\r\n/g, "\n");
6697
+ return matter(normalizedContent, { engines: { yaml } });
6698
+ };
6699
+
6658
6700
  // src/config/v2/page-order.ts
6659
6701
  var FIRST_PAGE_NAMES = [
6660
6702
  "introduction",
@@ -6696,6 +6738,128 @@ function tsxPageOrder(source) {
6696
6738
  return match?.[1] ? Number(match[1]) : void 0;
6697
6739
  }
6698
6740
 
6741
+ // src/config/v2/home-redirect.ts
6742
+ var IGNORED = ["**/node_modules/**", "**/dist/**", "**/.git/**"];
6743
+ async function docRoutes(patterns, options = {}) {
6744
+ const globs = patterns.map(toGlobPath);
6745
+ const excluded = globs.filter((pattern) => pattern.startsWith("!")).map((pattern) => pattern.slice(1));
6746
+ const included = globs.filter((pattern) => !pattern.startsWith("!"));
6747
+ const routes = await Promise.all(
6748
+ included.map(async (pattern) => {
6749
+ const files = await glob(pattern, {
6750
+ absolute: true,
6751
+ posix: true,
6752
+ ignore: [...IGNORED, ...excluded]
6753
+ });
6754
+ const parent = globParent(pattern);
6755
+ const published = options.includeDrafts ? files : (await Promise.all(
6756
+ files.map(async (file) => ({
6757
+ file,
6758
+ draft: (await readFrontmatter(file)).data.draft === true
6759
+ }))
6760
+ )).filter(({ draft }) => !draft).map(({ file }) => file);
6761
+ return published.map(
6762
+ (file) => `/${path9.posix.relative(parent, file.replace(/^\/\/\?\//, "")).replace(/\.mdx?$/, "")}`
6763
+ );
6764
+ })
6765
+ );
6766
+ return [...new Set(routes.flat())].sort();
6767
+ }
6768
+ function pickLandingRoute(routes, orders) {
6769
+ return firstPage(routes, orders);
6770
+ }
6771
+ async function docPageOrders(patterns) {
6772
+ const globs = patterns.map(toGlobPath);
6773
+ const excluded = globs.filter((pattern) => pattern.startsWith("!")).map((pattern) => pattern.slice(1));
6774
+ const orders = /* @__PURE__ */ new Map();
6775
+ for (const pattern of globs.filter((p) => !p.startsWith("!"))) {
6776
+ const parent = globParent(pattern);
6777
+ const files = await glob(pattern, {
6778
+ absolute: true,
6779
+ posix: true,
6780
+ ignore: [...IGNORED, ...excluded]
6781
+ });
6782
+ for (const raw of files) {
6783
+ const order = orderValue((await readFrontmatter(raw)).data.order);
6784
+ if (order === void 0) continue;
6785
+ const route = `/${path9.posix.relative(parent, raw.replace(/^\/\/\?\//, "")).replace(/\.mdx?$/, "")}`;
6786
+ if (!orders.has(route)) orders.set(route, order);
6787
+ }
6788
+ }
6789
+ return orders;
6790
+ }
6791
+ function homeRedirect(input5) {
6792
+ if (input5.hostsDocs) {
6793
+ if (input5.docRoutes.some((route) => route === "/" || route === "/index"))
6794
+ return void 0;
6795
+ const landing = pickLandingRoute(input5.docRoutes, input5.pageOrders);
6796
+ if (landing) return { from: "/", to: landing };
6797
+ if (input5.apiReferencePath)
6798
+ return { from: "/", to: input5.apiReferencePath };
6799
+ }
6800
+ if (!input5.hostsDocs && input5.externalDocsUrl && !input5.userPanel) {
6801
+ return { from: "/", to: input5.externalDocsUrl };
6802
+ }
6803
+ if (input5.userPanel) {
6804
+ return { from: "/", to: input5.pricingPath ?? "/account" };
6805
+ }
6806
+ return void 0;
6807
+ }
6808
+
6809
+ // src/config/v2/portal-profile.ts
6810
+ var NOT_BUILT = {
6811
+ enabled: false,
6812
+ docs: { enabled: false, provider: "apitogo", content: [] },
6813
+ console: { enabled: false },
6814
+ pipelines: { markdown: false, openapi: false, search: "none" },
6815
+ modules: { docs: false, userPanel: false }
6816
+ };
6817
+ function portalOnlyLinksExternalDocs(resolved) {
6818
+ const portal = resolved.portal;
6819
+ if (!portal?.enabled) return false;
6820
+ if (!portal.docs.enabled || portal.docs.provider !== "external") return false;
6821
+ const consoleFed = portal.console.enabled && (resolved.identity?.enabled === true || resolved.plans?.enabled === true);
6822
+ return !consoleFed;
6823
+ }
6824
+ function portalProfile(resolved) {
6825
+ const portal = resolved.portal;
6826
+ if (!portal?.enabled) return NOT_BUILT;
6827
+ const hostsDocs = portal.docs.enabled && portal.docs.provider === "apitogo";
6828
+ const externalDocs = portal.docs.enabled && portal.docs.provider === "external";
6829
+ const gatewayOn2 = resolved.gateway?.enabled === true;
6830
+ return {
6831
+ enabled: true,
6832
+ docs: {
6833
+ enabled: portal.docs.enabled,
6834
+ provider: portal.docs.provider,
6835
+ content: hostsDocs ? portal.docs.content : [],
6836
+ url: externalDocs ? portal.docs.url : void 0
6837
+ },
6838
+ console: { enabled: portal.console.enabled },
6839
+ pipelines: {
6840
+ markdown: hostsDocs,
6841
+ openapi: hostsDocs && gatewayOn2,
6842
+ search: hostsDocs ? portal.search : "none"
6843
+ },
6844
+ modules: { docs: hostsDocs, userPanel: portal.console.enabled },
6845
+ ...externalDocs ? {
6846
+ externalDocs: {
6847
+ url: portal.docs.url,
6848
+ openapiExport: gatewayOn2,
6849
+ cors: portal.console.enabled,
6850
+ consoleLinksInManifest: portal.console.enabled,
6851
+ whiteLabelConsole: portal.console.enabled
6852
+ }
6853
+ } : {}
6854
+ };
6855
+ }
6856
+
6857
+ // src/config/v2/tsx-pages.ts
6858
+ import { readFile as readFile4 } from "node:fs/promises";
6859
+ import path10 from "node:path";
6860
+ import { glob as glob2 } from "glob";
6861
+ import globParent2 from "glob-parent";
6862
+
6699
6863
  // src/config/v2/docs-navigation.ts
6700
6864
  var DOCS_NAVIGATION_LABEL = "Documentation";
6701
6865
  var emptyBranch = () => ({ pages: /* @__PURE__ */ new Map(), folders: /* @__PURE__ */ new Map() });
@@ -6766,7 +6930,7 @@ function docsNavigation(routes, customPages = [], orders = /* @__PURE__ */ new M
6766
6930
  }
6767
6931
 
6768
6932
  // src/config/v2/tsx-pages.ts
6769
- var IGNORED = ["**/node_modules/**", "**/dist/**", "**/.git/**"];
6933
+ var IGNORED2 = ["**/node_modules/**", "**/dist/**", "**/.git/**"];
6770
6934
  function isTsxHelper(relative) {
6771
6935
  return relative.split("/").some((part) => part.startsWith("_")) || /\.(test|spec|stories)\.tsx$/.test(relative) || relative.split("/").includes("node_modules");
6772
6936
  }
@@ -6777,7 +6941,7 @@ function tsxPagePatterns(contentGlobs) {
6777
6941
  return [
6778
6942
  ...new Set(
6779
6943
  included.map(
6780
- (pattern) => path9.posix.join(globParent(pattern), "**", "*.tsx")
6944
+ (pattern) => path10.posix.join(globParent2(pattern), "**", "*.tsx")
6781
6945
  )
6782
6946
  ),
6783
6947
  ...excluded
@@ -6787,21 +6951,21 @@ var TITLE = /export\s+const\s+title\s*(?::\s*string\s*)?=\s*(["'`])((?:\\.|(?!\1
6787
6951
  function tsxPageLabel(source, file) {
6788
6952
  const match = TITLE.exec(source);
6789
6953
  if (match?.[2]) return match[2].replace(/\\(.)/g, "$1");
6790
- const name = path9.basename(file, ".tsx");
6954
+ const name = path10.basename(file, ".tsx");
6791
6955
  return folderLabel(
6792
- name === "index" ? path9.basename(path9.dirname(file)) : name
6956
+ name === "index" ? path10.basename(path10.dirname(file)) : name
6793
6957
  );
6794
6958
  }
6795
6959
  async function findTsxPages(contentGlobs) {
6796
6960
  const patterns = tsxPagePatterns(contentGlobs);
6797
6961
  const ignore2 = [
6798
- ...IGNORED,
6962
+ ...IGNORED2,
6799
6963
  ...patterns.filter((pattern) => pattern.startsWith("!")).map((pattern) => pattern.slice(1))
6800
6964
  ];
6801
6965
  const found = /* @__PURE__ */ new Map();
6802
6966
  for (const pattern of patterns.filter((p) => !p.startsWith("!"))) {
6803
- const parent = globParent(pattern);
6804
- const files = await glob(pattern, {
6967
+ const parent = globParent2(pattern);
6968
+ const files = await glob2(pattern, {
6805
6969
  absolute: true,
6806
6970
  posix: true,
6807
6971
  nodir: true,
@@ -6809,11 +6973,11 @@ async function findTsxPages(contentGlobs) {
6809
6973
  });
6810
6974
  for (const raw of files.sort()) {
6811
6975
  const file = raw.replace(/^\/\/\?\//, "");
6812
- const relative = path9.posix.relative(parent, file);
6976
+ const relative = path10.posix.relative(parent, file);
6813
6977
  if (isTsxHelper(relative)) continue;
6814
6978
  const route = `/${relative.replace(/\.tsx$/, "")}`;
6815
6979
  if (found.has(route)) continue;
6816
- const source = await readFile3(file, "utf8").catch(() => "");
6980
+ const source = await readFile4(file, "utf8").catch(() => "");
6817
6981
  const order = tsxPageOrder(source);
6818
6982
  found.set(route, {
6819
6983
  route,
@@ -6857,11 +7021,11 @@ function collectSecretRefs(config2) {
6857
7021
  return found;
6858
7022
  }
6859
7023
  function dependencyRules(config2, resolved, out) {
6860
- const gatewayOn = resolved.gateway?.enabled === true;
7024
+ const gatewayOn2 = resolved.gateway?.enabled === true;
6861
7025
  const identityOn = resolved.identity?.enabled === true;
6862
- const oidcGateway = gatewayOn && resolved.gateway?.auth?.mode === "oidc";
7026
+ const oidcGateway = gatewayOn2 && resolved.gateway?.auth?.mode === "oidc";
6863
7027
  const hasIdentitySource = identityOn || oidcGateway;
6864
- if (identityOn && resolved.portal?.enabled !== true && !gatewayOn && !resolved.project.id) {
7028
+ if (identityOn && resolved.portal?.enabled !== true && !gatewayOn2 && !resolved.project.id) {
6865
7029
  out.error({
6866
7030
  code: "NOTHING_TO_HOST",
6867
7031
  path: "identity",
@@ -6878,7 +7042,7 @@ function dependencyRules(config2, resolved, out) {
6878
7042
  });
6879
7043
  }
6880
7044
  if (resolved.plans?.enabled) {
6881
- if (!gatewayOn) {
7045
+ if (!gatewayOn2) {
6882
7046
  out.error({
6883
7047
  code: "PLANS_GATEWAY_REQUIRED",
6884
7048
  path: "plans",
@@ -6974,44 +7138,53 @@ async function portalRules(config2, resolved, out) {
6974
7138
  hint: "Enable identity or plans, or enable portal.docs."
6975
7139
  });
6976
7140
  }
7141
+ if (portalOnlyLinksExternalDocs(resolved)) {
7142
+ out.warn({
7143
+ code: "PORTAL_NOTHING_TO_HOST",
7144
+ path: "portal",
7145
+ message: "docs are on an external site and no service feeds the console, so there is no portal to host: `project create` takes no website hosting for it and `deploy` skips it.",
7146
+ hint: "Enable identity or plans for a console next to your docs, or remove the portal section."
7147
+ });
7148
+ }
6977
7149
  if (portal.footer && !await isFile(portal.footer))
6978
7150
  out.error({
6979
7151
  code: "PORTAL_FILE_MISSING",
6980
7152
  path: "portal.footer",
6981
- message: `the file ${path10.relative(resolved.configDir, portal.footer)} does not exist.`,
7153
+ message: `the file ${path11.relative(resolved.configDir, portal.footer)} does not exist.`,
6982
7154
  hint: "Create it, or remove portal.footer."
6983
7155
  });
6984
- const publicDir = path10.join(portal.dir, "public");
7156
+ await headerLinksRule(portal, resolved, out);
7157
+ const publicDir = path11.join(portal.dir, "public");
6985
7158
  for (const file of new Set(
6986
7159
  [portal.logo?.light, portal.logo?.dark].filter(
6987
7160
  (value) => !!value && !isLogoUrl(value)
6988
7161
  )
6989
7162
  )) {
6990
- const inside = path10.relative(publicDir, file);
6991
- if (inside.startsWith("..") || path10.isAbsolute(inside))
7163
+ const inside = path11.relative(publicDir, file);
7164
+ if (inside.startsWith("..") || path11.isAbsolute(inside))
6992
7165
  out.error({
6993
7166
  code: "PORTAL_LOGO_OUTSIDE_PUBLIC",
6994
7167
  path: "portal.logo",
6995
- message: `the logo ${path10.relative(resolved.configDir, file)} is not in the portal's public folder, which is what the site serves.`,
6996
- hint: `Move it into ${path10.relative(resolved.configDir, publicDir)}/, or use an https:// address.`
7168
+ message: `the logo ${path11.relative(resolved.configDir, file)} is not in the portal's public folder, which is what the site serves.`,
7169
+ hint: `Move it into ${path11.relative(resolved.configDir, publicDir)}/, or use an https:// address.`
6997
7170
  });
6998
7171
  else if (!await isFile(file))
6999
7172
  out.error({
7000
7173
  code: "PORTAL_FILE_MISSING",
7001
7174
  path: "portal.logo",
7002
- message: `the file ${path10.relative(resolved.configDir, file)} does not exist.`,
7175
+ message: `the file ${path11.relative(resolved.configDir, file)} does not exist.`,
7003
7176
  hint: "Create it, or remove portal.logo."
7004
7177
  });
7005
7178
  }
7006
7179
  if (await isFile(portal.theme)) {
7007
7180
  try {
7008
- JSON.parse(await readFile4(portal.theme, "utf8"));
7181
+ JSON.parse(await readFile5(portal.theme, "utf8"));
7009
7182
  } catch (error) {
7010
7183
  out.error({
7011
7184
  code: "PORTAL_THEME_INVALID",
7012
7185
  path: "portal.theme",
7013
7186
  message: `the theme file is not valid JSON (${error instanceof Error ? error.message : String(error)}).`,
7014
- hint: path10.relative(resolved.configDir, portal.theme)
7187
+ hint: path11.relative(resolved.configDir, portal.theme)
7015
7188
  });
7016
7189
  }
7017
7190
  }
@@ -7020,12 +7193,21 @@ async function docsContentRule(config2, patterns, resolved, out) {
7020
7193
  const globs = patterns.map(toGlobPath);
7021
7194
  const include = globs.filter((pattern) => !pattern.startsWith("!"));
7022
7195
  const ignore2 = globs.filter((pattern) => pattern.startsWith("!")).map((pattern) => pattern.slice(1));
7023
- const matches = include.length > 0 ? await glob2(include, { nodir: true, ignore: ignore2, absolute: true }) : [];
7196
+ const matches = include.length > 0 ? await glob3(include, { nodir: true, ignore: ignore2, absolute: true }) : [];
7024
7197
  if (matches.length > 0) return;
7025
7198
  if ((await findTsxPages(patterns)).length > 0) return;
7026
7199
  const shown = config2.portal?.docs?.content ?? [
7027
- path10.relative(resolved.configDir, patterns[0] ?? "")
7200
+ path11.relative(resolved.configDir, patterns[0] ?? "")
7028
7201
  ];
7202
+ if (portalHasNothingToShow(resolved)) {
7203
+ out.error({
7204
+ code: "PORTAL_HOME_EMPTY",
7205
+ path: "portal.docs",
7206
+ message: `portal.docs matched 0 files (${shown.join(", ")}), and the portal has no API reference, console pages or home page of its own, so its home page would say "Page not found".`,
7207
+ hint: "Add a .md or .mdx page there (for example introduction.mdx), point docs.content at your existing docs, or enable the gateway with an OpenAPI file for an API reference."
7208
+ });
7209
+ return;
7210
+ }
7029
7211
  out.warn({
7030
7212
  code: "PORTAL_DOCS_EMPTY",
7031
7213
  path: "portal.docs",
@@ -7033,6 +7215,84 @@ async function docsContentRule(config2, patterns, resolved, out) {
7033
7215
  hint: "Add .md, .mdx or .tsx pages, or point docs.content at your existing docs."
7034
7216
  });
7035
7217
  }
7218
+ function portalHasNothingToShow(resolved) {
7219
+ const portal = resolved.portal;
7220
+ if (!portal?.enabled || portal.landing) return false;
7221
+ if (resolved.gateway?.enabled === true) return false;
7222
+ const consoleFed = portal.console.enabled && (resolved.identity?.enabled === true || resolved.plans?.enabled === true);
7223
+ return !consoleFed;
7224
+ }
7225
+ var RESERVED_HEADER_ROUTES = [
7226
+ "/",
7227
+ "/index",
7228
+ "/api",
7229
+ "/pricing",
7230
+ "/account",
7231
+ "/login",
7232
+ "/signup"
7233
+ ];
7234
+ async function headerLinksRule(portal, resolved, out) {
7235
+ const pages = portal.headerLinks.map((link, index) => ({ link, index })).filter(({ link }) => link.page !== void 0);
7236
+ if (pages.length === 0) return;
7237
+ const hostsDocs = portal.docs.enabled && portal.docs.provider === "apitogo";
7238
+ const docs = hostsDocs ? [
7239
+ ...await docRoutes(portal.docs.content, { includeDrafts: true }),
7240
+ ...(await findTsxPages(portal.docs.content)).map((page) => page.route)
7241
+ ] : [];
7242
+ const seen = /* @__PURE__ */ new Set();
7243
+ for (const { link, index } of pages) {
7244
+ const file = link.page;
7245
+ const at = `portal.header.links[${index}].page`;
7246
+ const shown = path11.relative(resolved.configDir, file);
7247
+ if (!hostsDocs) {
7248
+ out.error({
7249
+ code: "PORTAL_HEADER_PAGE_NO_DOCS",
7250
+ path: at,
7251
+ message: `the header page ${shown} needs hosted docs, which this portal does not have.`,
7252
+ hint: 'Header pages are served with the docs pages: set portal.docs.provider to "apitogo", or link an address with "to" instead.'
7253
+ });
7254
+ continue;
7255
+ }
7256
+ if (!/\.(mdx?|tsx)$/i.test(file)) {
7257
+ out.error({
7258
+ code: "PORTAL_HEADER_PAGE_INVALID",
7259
+ path: at,
7260
+ message: `the header page ${shown} is neither a React page nor a Markdown page.`,
7261
+ hint: "Use a .tsx file (its default export is the page) or a .md / .mdx file."
7262
+ });
7263
+ continue;
7264
+ }
7265
+ if (!await isFile(file)) {
7266
+ out.error({
7267
+ code: "PORTAL_FILE_MISSING",
7268
+ path: at,
7269
+ message: `the file ${shown} does not exist.`,
7270
+ hint: "Create it (`apitogo config --page-slug <name> --page-title <title> --page-header`), or remove the link."
7271
+ });
7272
+ continue;
7273
+ }
7274
+ const route = link.to;
7275
+ if (RESERVED_HEADER_ROUTES.includes(route.toLowerCase())) {
7276
+ out.error({
7277
+ code: "PORTAL_HEADER_PAGE_ROUTE_TAKEN",
7278
+ path: at,
7279
+ message: `the header page ${shown} would be served at ${route}, which the portal uses itself.`,
7280
+ hint: "Rename the file: its name is the page's address."
7281
+ });
7282
+ continue;
7283
+ }
7284
+ if (docs.includes(route) || seen.has(route)) {
7285
+ out.error({
7286
+ code: "PORTAL_HEADER_PAGE_ROUTE_TAKEN",
7287
+ path: at,
7288
+ message: `the header page ${shown} would be served at ${route}, which another page already has.`,
7289
+ hint: `Rename the file: its name is the page's address. A docs page that should be in the header too can be linked with "to" instead.`
7290
+ });
7291
+ continue;
7292
+ }
7293
+ seen.add(route);
7294
+ }
7295
+ }
7036
7296
  function isIpLiteral(hostname) {
7037
7297
  return /^\d{1,3}(\.\d{1,3}){3}$/.test(hostname) || hostname.startsWith("[");
7038
7298
  }
@@ -7097,8 +7357,8 @@ async function gatewayRules(resolved, out) {
7097
7357
  }
7098
7358
  }
7099
7359
  if (origin.source) {
7100
- const inside = path10.relative(resolved.configDir, origin.source);
7101
- if (inside.startsWith("..") || path10.isAbsolute(inside)) {
7360
+ const inside = path11.relative(resolved.configDir, origin.source);
7361
+ if (inside.startsWith("..") || path11.isAbsolute(inside)) {
7102
7362
  out.error({
7103
7363
  code: "GATEWAY_ORIGIN_INVALID",
7104
7364
  path: "gateway.origin.source",
@@ -7183,7 +7443,7 @@ async function gatewayRules(resolved, out) {
7183
7443
  }
7184
7444
  async function specRule(spec, index, resolved, out) {
7185
7445
  const at = `gateway.specs[${index}]`;
7186
- const shown = path10.relative(resolved.configDir, spec);
7446
+ const shown = path11.relative(resolved.configDir, spec);
7187
7447
  const info = await stat2(spec).catch(() => void 0);
7188
7448
  if (!info?.isFile()) {
7189
7449
  out.error({
@@ -7203,7 +7463,7 @@ async function specRule(spec, index, resolved, out) {
7203
7463
  }
7204
7464
  let document;
7205
7465
  try {
7206
- const text2 = await readFile4(spec, "utf8");
7466
+ const text2 = await readFile5(spec, "utf8");
7207
7467
  document = spec.toLowerCase().endsWith(".json") ? JSON.parse(text2) : parseYaml(text2);
7208
7468
  } catch (error) {
7209
7469
  out.error({
@@ -7352,12 +7612,12 @@ async function plansRules(resolved, out, paid) {
7352
7612
  out.error({
7353
7613
  code: "PLANS_FILE_INVALID",
7354
7614
  path: "plans.file",
7355
- message: `the plan catalog was not found: ${path10.relative(resolved.configDir, plans.file)}.`,
7615
+ message: `the plan catalog was not found: ${path11.relative(resolved.configDir, plans.file)}.`,
7356
7616
  hint: "Create it with `apitogo add plans`."
7357
7617
  });
7358
7618
  } else {
7359
7619
  try {
7360
- JSON.parse(await readFile4(plans.file, "utf8"));
7620
+ JSON.parse(await readFile5(plans.file, "utf8"));
7361
7621
  } catch (error) {
7362
7622
  out.error({
7363
7623
  code: "PLANS_FILE_INVALID",
@@ -7431,50 +7691,9 @@ async function checkConfigV2(config2, resolved, context = {}) {
7431
7691
  return { errors, warnings, skipped };
7432
7692
  }
7433
7693
 
7434
- // src/config/v2/portal-profile.ts
7435
- var NOT_BUILT = {
7436
- enabled: false,
7437
- docs: { enabled: false, provider: "apitogo", content: [] },
7438
- console: { enabled: false },
7439
- pipelines: { markdown: false, openapi: false, search: "none" },
7440
- modules: { docs: false, userPanel: false }
7441
- };
7442
- function portalProfile(resolved) {
7443
- const portal = resolved.portal;
7444
- if (!portal?.enabled) return NOT_BUILT;
7445
- const hostsDocs = portal.docs.enabled && portal.docs.provider === "apitogo";
7446
- const externalDocs = portal.docs.enabled && portal.docs.provider === "external";
7447
- const gatewayOn = resolved.gateway?.enabled === true;
7448
- return {
7449
- enabled: true,
7450
- docs: {
7451
- enabled: portal.docs.enabled,
7452
- provider: portal.docs.provider,
7453
- content: hostsDocs ? portal.docs.content : [],
7454
- url: externalDocs ? portal.docs.url : void 0
7455
- },
7456
- console: { enabled: portal.console.enabled },
7457
- pipelines: {
7458
- markdown: hostsDocs,
7459
- openapi: hostsDocs && gatewayOn,
7460
- search: hostsDocs ? portal.search : "none"
7461
- },
7462
- modules: { docs: hostsDocs, userPanel: portal.console.enabled },
7463
- ...externalDocs ? {
7464
- externalDocs: {
7465
- url: portal.docs.url,
7466
- openapiExport: gatewayOn,
7467
- cors: portal.console.enabled,
7468
- consoleLinksInManifest: portal.console.enabled,
7469
- whiteLabelConsole: portal.console.enabled
7470
- }
7471
- } : {}
7472
- };
7473
- }
7474
-
7475
7694
  // src/config/v2/manifest.ts
7476
- import { mkdir as mkdir3, readFile as readFile5, rename, rm, writeFile as writeFile4 } from "node:fs/promises";
7477
- import path11 from "node:path";
7695
+ import { mkdir as mkdir3, readFile as readFile6, rename, rm, writeFile as writeFile4 } from "node:fs/promises";
7696
+ import path12 from "node:path";
7478
7697
  import { z as z3 } from "zod";
7479
7698
  var SUPPORTED_MANIFEST_SCHEMA = 1;
7480
7699
  var Capabilities = z3.record(z3.string(), z3.boolean()).default({});
@@ -7525,7 +7744,7 @@ var ServiceManifestSchema = z3.looseObject({
7525
7744
  }).optional()
7526
7745
  }).default({})
7527
7746
  });
7528
- var issue = (code, path99, message, hint) => ({ code, path: path99, message, hint });
7747
+ var issue = (code, path100, message, hint) => ({ code, path: path100, message, hint });
7529
7748
  function parseManifest(raw, file = "services.json") {
7530
7749
  const version = typeof raw === "object" && raw !== null && "schemaVersion" in raw ? raw.schemaVersion : void 0;
7531
7750
  if (typeof version === "number" && version > SUPPORTED_MANIFEST_SCHEMA) {
@@ -7558,7 +7777,7 @@ async function readManifest(state) {
7558
7777
  const file = manifestPathFor(state);
7559
7778
  let text2;
7560
7779
  try {
7561
- text2 = await readFile5(file, "utf8");
7780
+ text2 = await readFile6(file, "utf8");
7562
7781
  } catch (error) {
7563
7782
  if (error.code === "ENOENT") return void 0;
7564
7783
  throw error;
@@ -7590,10 +7809,10 @@ function requiredServices(resolved) {
7590
7809
  if (!portal?.enabled) return [];
7591
7810
  const needs = [];
7592
7811
  const consoleOn = portal.console.enabled;
7593
- const gatewayOn = resolved.gateway?.enabled === true;
7812
+ const gatewayOn2 = resolved.gateway?.enabled === true;
7594
7813
  const identityOn = resolved.identity?.enabled === true;
7595
7814
  if (identityOn && (consoleOn || portal.requireAuth)) needs.push("identity");
7596
- if (gatewayOn && (consoleOn || portal.docs.enabled)) needs.push("gateway");
7815
+ if (gatewayOn2 && (consoleOn || portal.docs.enabled)) needs.push("gateway");
7597
7816
  if (resolved.plans?.enabled === true && consoleOn) needs.push("plans");
7598
7817
  return needs;
7599
7818
  }
@@ -7756,7 +7975,7 @@ function withPlansEntry(existing, info) {
7756
7975
  }
7757
7976
  async function writeManifest(state, manifest) {
7758
7977
  const file = manifestPathFor(state);
7759
- await mkdir3(path11.dirname(file), { recursive: true });
7978
+ await mkdir3(path12.dirname(file), { recursive: true });
7760
7979
  const temporary = `${file}.${process.pid}.tmp`;
7761
7980
  try {
7762
7981
  await writeFile4(
@@ -7787,8 +8006,8 @@ async function removeServicesFromManifest(state, names) {
7787
8006
  }
7788
8007
 
7789
8008
  // src/config/v2/gateway-reference.ts
7790
- import { readFile as readFile6 } from "node:fs/promises";
7791
- import { parse } from "yaml";
8009
+ import { readFile as readFile7 } from "node:fs/promises";
8010
+ import { parse as parse2 } from "yaml";
7792
8011
  var DEFAULT_KEY_HEADER = "X-API-Key";
7793
8012
  var KEY_HEADER_EXTENSION = "x-apitogo-key-header";
7794
8013
  var METHODS = [
@@ -7802,8 +8021,8 @@ var METHODS = [
7802
8021
  "trace"
7803
8022
  ];
7804
8023
  var isObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
7805
- function actionKey(method, path99) {
7806
- const trimmed = path99.trim();
8024
+ function actionKey(method, path100) {
8025
+ const trimmed = path100.trim();
7807
8026
  const withSlash = trimmed.startsWith("/") ? trimmed : `/${trimmed}`;
7808
8027
  const normalized = withSlash.length > 1 ? withSlash.replace(/\/+$/, "") || "/" : withSlash;
7809
8028
  return `${method.trim().toLowerCase()}:${normalized}`;
@@ -7840,15 +8059,15 @@ var ReferenceSpecError = class extends Error {
7840
8059
  };
7841
8060
  function operationsOf(paths) {
7842
8061
  return Object.entries(paths).flatMap(
7843
- ([path99, item]) => isObject(item) ? METHODS.filter((method) => isObject(item[method])).map(
7844
- (method) => [path99, item, method]
8062
+ ([path100, item]) => isObject(item) ? METHODS.filter((method) => isObject(item[method])).map(
8063
+ (method) => [path100, item, method]
7845
8064
  ) : []
7846
8065
  );
7847
8066
  }
7848
8067
  function buildReferenceSpec(specText, options) {
7849
8068
  let root;
7850
8069
  try {
7851
- root = parse(specText);
8070
+ root = parse2(specText);
7852
8071
  } catch {
7853
8072
  throw new ReferenceSpecError("The OpenAPI file could not be read.");
7854
8073
  }
@@ -7856,14 +8075,14 @@ function buildReferenceSpec(specText, options) {
7856
8075
  throw new ReferenceSpecError("The OpenAPI file could not be read.");
7857
8076
  const doc = structuredClone(root);
7858
8077
  const paths = isObject(doc.paths) ? doc.paths : {};
7859
- for (const [path99, item, method] of operationsOf(paths))
7860
- if (options.deniedOperations.has(actionKey(method, path99)))
8078
+ for (const [path100, item, method] of operationsOf(paths))
8079
+ if (options.deniedOperations.has(actionKey(method, path100)))
7861
8080
  delete item[method];
7862
- for (const [path99, item] of Object.entries(paths))
8081
+ for (const [path100, item] of Object.entries(paths))
7863
8082
  if (!isObject(item) || !METHODS.some((method) => method in item))
7864
- delete paths[path99];
7865
- for (const [path99, item, method] of operationsOf(paths))
7866
- if (options.publicOperations.has(actionKey(method, path99)))
8083
+ delete paths[path100];
8084
+ for (const [path100, item, method] of operationsOf(paths))
8085
+ if (options.publicOperations.has(actionKey(method, path100)))
7867
8086
  item[method].security = [];
7868
8087
  doc.servers = [{ url: options.gatewayBaseUrl.replace(/\/+$/, "") }];
7869
8088
  for (const item of Object.values(paths)) {
@@ -7903,11 +8122,11 @@ function buildReferenceSpec(specText, options) {
7903
8122
  if (Object.keys(components).length > 0) doc.components = components;
7904
8123
  else delete doc.components;
7905
8124
  doc.security = Object.keys(requirement).length > 0 ? [requirement] : [];
7906
- for (const [path99, item, method] of operationsOf(paths)) {
8125
+ for (const [path100, item, method] of operationsOf(paths)) {
7907
8126
  const operation = item[method];
7908
8127
  if (Array.isArray(operation.security) && operation.security.length > 0)
7909
8128
  delete operation.security;
7910
- if (options.apiKeyRequired && !options.publicOperations.has(actionKey(method, path99)))
8129
+ if (options.apiKeyRequired && !options.publicOperations.has(actionKey(method, path100)))
7911
8130
  operation[KEY_HEADER_EXTENSION] = options.apiKeyHeader;
7912
8131
  else delete operation[KEY_HEADER_EXTENSION];
7913
8132
  }
@@ -7929,7 +8148,7 @@ async function renderReferenceSpec(gateway, gatewayBaseUrl, keyHeaderName) {
7929
8148
  throw new ReferenceSpecError("gateway.specs lists no OpenAPI file.");
7930
8149
  let text2;
7931
8150
  try {
7932
- text2 = await readFile6(file, "utf8");
8151
+ text2 = await readFile7(file, "utf8");
7933
8152
  } catch {
7934
8153
  throw new ReferenceSpecError(`Cannot read the OpenAPI file ${file}.`);
7935
8154
  }
@@ -8006,7 +8225,7 @@ import path14 from "node:path";
8006
8225
 
8007
8226
  // src/cli/platform/helpers/openapi-action-keys.ts
8008
8227
  import { existsSync as existsSync3, readFileSync as readFileSync3 } from "node:fs";
8009
- import path12 from "node:path";
8228
+ import path13 from "node:path";
8010
8229
  import YAML from "yaml";
8011
8230
  var HTTP_METHODS = [
8012
8231
  "get",
@@ -8075,7 +8294,7 @@ function extractGatewayActionKeysFromOpenApiDocument(document) {
8075
8294
  function extractGatewayActionKeysFromOpenApiFile(filePath) {
8076
8295
  try {
8077
8296
  const raw = readFileSync3(filePath, "utf8");
8078
- const ext = path12.extname(filePath).toLowerCase();
8297
+ const ext = path13.extname(filePath).toLowerCase();
8079
8298
  const parsed = ext === ".json" ? JSON.parse(raw) : YAML.parse(raw);
8080
8299
  return extractGatewayActionKeysFromOpenApiDocument(parsed);
8081
8300
  } catch {
@@ -8083,92 +8302,6 @@ function extractGatewayActionKeysFromOpenApiFile(filePath) {
8083
8302
  }
8084
8303
  }
8085
8304
 
8086
- // src/config/v2/home-redirect.ts
8087
- import path13 from "node:path";
8088
- import { glob as glob3 } from "glob";
8089
- import globParent2 from "glob-parent";
8090
-
8091
- // src/lib/util/readFrontmatter.ts
8092
- import { readFile as readFile7 } from "node:fs/promises";
8093
- import matter from "gray-matter";
8094
- import { parse as parse2, stringify } from "yaml";
8095
- var yaml = {
8096
- parse: (input5) => parse2(input5) ?? {},
8097
- stringify: (obj) => stringify(obj)
8098
- };
8099
- var readFrontmatter = async (filePath) => {
8100
- const content = await readFile7(filePath, "utf-8");
8101
- const normalizedContent = content.replace(/\r\n/g, "\n");
8102
- return matter(normalizedContent, { engines: { yaml } });
8103
- };
8104
-
8105
- // src/config/v2/home-redirect.ts
8106
- var IGNORED2 = ["**/node_modules/**", "**/dist/**", "**/.git/**"];
8107
- async function docRoutes(patterns, options = {}) {
8108
- const globs = patterns.map(toGlobPath);
8109
- const excluded = globs.filter((pattern) => pattern.startsWith("!")).map((pattern) => pattern.slice(1));
8110
- const included = globs.filter((pattern) => !pattern.startsWith("!"));
8111
- const routes = await Promise.all(
8112
- included.map(async (pattern) => {
8113
- const files = await glob3(pattern, {
8114
- absolute: true,
8115
- posix: true,
8116
- ignore: [...IGNORED2, ...excluded]
8117
- });
8118
- const parent = globParent2(pattern);
8119
- const published = options.includeDrafts ? files : (await Promise.all(
8120
- files.map(async (file) => ({
8121
- file,
8122
- draft: (await readFrontmatter(file)).data.draft === true
8123
- }))
8124
- )).filter(({ draft }) => !draft).map(({ file }) => file);
8125
- return published.map(
8126
- (file) => `/${path13.posix.relative(parent, file.replace(/^\/\/\?\//, "")).replace(/\.mdx?$/, "")}`
8127
- );
8128
- })
8129
- );
8130
- return [...new Set(routes.flat())].sort();
8131
- }
8132
- function pickLandingRoute(routes, orders) {
8133
- return firstPage(routes, orders);
8134
- }
8135
- async function docPageOrders(patterns) {
8136
- const globs = patterns.map(toGlobPath);
8137
- const excluded = globs.filter((pattern) => pattern.startsWith("!")).map((pattern) => pattern.slice(1));
8138
- const orders = /* @__PURE__ */ new Map();
8139
- for (const pattern of globs.filter((p) => !p.startsWith("!"))) {
8140
- const parent = globParent2(pattern);
8141
- const files = await glob3(pattern, {
8142
- absolute: true,
8143
- posix: true,
8144
- ignore: [...IGNORED2, ...excluded]
8145
- });
8146
- for (const raw of files) {
8147
- const order = orderValue((await readFrontmatter(raw)).data.order);
8148
- if (order === void 0) continue;
8149
- const route = `/${path13.posix.relative(parent, raw.replace(/^\/\/\?\//, "")).replace(/\.mdx?$/, "")}`;
8150
- if (!orders.has(route)) orders.set(route, order);
8151
- }
8152
- }
8153
- return orders;
8154
- }
8155
- function homeRedirect(input5) {
8156
- if (input5.hostsDocs) {
8157
- if (input5.docRoutes.some((route) => route === "/" || route === "/index"))
8158
- return void 0;
8159
- const landing = pickLandingRoute(input5.docRoutes, input5.pageOrders);
8160
- if (landing) return { from: "/", to: landing };
8161
- return input5.apiReferencePath ? { from: "/", to: input5.apiReferencePath } : void 0;
8162
- }
8163
- if (input5.externalDocsUrl && !input5.userPanel) {
8164
- return { from: "/", to: input5.externalDocsUrl };
8165
- }
8166
- if (input5.userPanel) {
8167
- return { from: "/", to: input5.pricingPath ?? "/account" };
8168
- }
8169
- return void 0;
8170
- }
8171
-
8172
8305
  // src/config/v2/protected-routes.ts
8173
8306
  function protectedRoutesFor(input5) {
8174
8307
  const { requireAuth } = input5;
@@ -8346,11 +8479,38 @@ function planPortal(profile, manifest) {
8346
8479
  var SITE_CONFIG_FILE = "apitogo.config.json";
8347
8480
  var REFERENCE_SPEC_FILE = "openapi/gateway.json";
8348
8481
  var API_REFERENCE_PATH = "api";
8482
+ var MARKDOWN_PAGE = /\.mdx?$/i;
8483
+ var HEADER_PAGE_ELEMENT_PREFIX = "apitogo:header-page:";
8484
+ function headerTsxPages(resolved, plan2) {
8485
+ if (!plan2.docs) return [];
8486
+ return (resolved.portal?.headerLinks ?? []).flatMap(
8487
+ (link) => link.page && /\.tsx$/i.test(link.page) ? [{ route: link.to, label: link.label, file: link.page }] : []
8488
+ ).map((page, index) => ({
8489
+ ...page,
8490
+ id: `${HEADER_PAGE_ELEMENT_PREFIX}${index}`
8491
+ }));
8492
+ }
8349
8493
  function buildSiteConfig(resolved, plan2, theme, extras = {}) {
8350
8494
  const portal = resolved.portal;
8351
8495
  const redirects = extras.redirects ?? [];
8496
+ const headerLinks = portal?.headerLinks ?? [];
8497
+ const headerPages = plan2.docs ? headerLinks.flatMap(
8498
+ (link) => link.page && MARKDOWN_PAGE.test(link.page) ? [link.page] : []
8499
+ ) : [];
8500
+ const shownHeaderLinks = headerLinks.filter((link) => !link.page || plan2.docs).map((link) => ({
8501
+ label: link.label,
8502
+ to: link.to,
8503
+ ...link.to.startsWith("https://") ? { target: "_blank" } : {}
8504
+ }));
8352
8505
  const navigation = [
8353
8506
  ...extras.docsNavigation ?? [],
8507
+ ...headerTsxPages(resolved, plan2).map((page) => ({
8508
+ type: "custom-page",
8509
+ path: page.route,
8510
+ label: page.label,
8511
+ element: page.id,
8512
+ display: "hide"
8513
+ })),
8354
8514
  ...plan2.apiReference ? [
8355
8515
  {
8356
8516
  type: "link",
@@ -8389,9 +8549,10 @@ function buildSiteConfig(resolved, plan2, theme, extras = {}) {
8389
8549
  },
8390
8550
  ...plan2.docs ? {
8391
8551
  docs: {
8392
- files: portal?.docs.content.map(
8393
- (glob9) => contentGlob(resolved.configDir, glob9)
8394
- )
8552
+ files: [
8553
+ ...portal?.docs.content ?? [],
8554
+ ...headerPages.map(toGlobPath)
8555
+ ].map((glob9) => contentGlob(resolved.configDir, glob9))
8395
8556
  }
8396
8557
  } : {},
8397
8558
  ...plan2.search ? { search: { type: plan2.search } } : {},
@@ -8406,12 +8567,13 @@ function buildSiteConfig(resolved, plan2, theme, extras = {}) {
8406
8567
  } : {},
8407
8568
  ...navigation.length > 0 ? { navigation } : {},
8408
8569
  // The sidebar link above is only seen inside the docs; the header is what every page shows. A header navigation
8409
- // replaces the docs tab, so the docs get a header link of their own.
8410
- ...plan2.apiReference ? {
8570
+ // replaces the docs tab, so the docs get a header link of their own. The tenant's own links (portal.header) follow.
8571
+ ...plan2.apiReference || shownHeaderLinks.length > 0 ? {
8411
8572
  header: {
8412
8573
  navigation: [
8413
8574
  ...plan2.docs && extras.docsPath ? [{ label: "Documentation", to: extras.docsPath }] : [],
8414
- { label: "API Reference", to: `/${API_REFERENCE_PATH}` }
8575
+ ...plan2.apiReference ? [{ label: "API Reference", to: `/${API_REFERENCE_PATH}` }] : [],
8576
+ ...shownHeaderLinks
8415
8577
  ]
8416
8578
  }
8417
8579
  } : {},
@@ -8617,10 +8779,14 @@ async function preparePortal(resolved, manifest, mode, options = {}) {
8617
8779
  const tsxPages = plan2.docs ? (await findTsxPages(profile.docs.content)).filter(
8618
8780
  (page) => !markdownRoutes.includes(page.route)
8619
8781
  ) : [];
8782
+ const importPathOf = (file) => `./${CONTENT_LINK}/${path14.relative(resolved.configDir, file).split(path14.sep).join("/")}`;
8620
8783
  const wrapperPages = tsxPages.map((page, index) => ({
8621
8784
  id: `${TSX_PAGE_ELEMENT_PREFIX}${index}`,
8622
- importPath: `./${CONTENT_LINK}/${path14.relative(resolved.configDir, page.file).split(path14.sep).join("/")}`
8785
+ importPath: importPathOf(page.file)
8623
8786
  }));
8787
+ const headerWrapperPages = headerTsxPages(resolved, plan2).map(
8788
+ (page) => ({ id: page.id, importPath: importPathOf(page.file) })
8789
+ );
8624
8790
  const hostedDocRoutes = [
8625
8791
  .../* @__PURE__ */ new Set([...markdownRoutes, ...tsxPages.map((page) => page.route)])
8626
8792
  ].sort();
@@ -8691,14 +8857,20 @@ async function preparePortal(resolved, manifest, mode, options = {}) {
8691
8857
  protectedRoutes: protectedRoutesFor({
8692
8858
  requireAuth: resolved.portal?.requireAuth,
8693
8859
  hasAuth: !!plan2.auth,
8694
- docRoutes: hostedDocRoutes,
8860
+ // A header page is a docs page too, so a portal behind login keeps it behind login.
8861
+ docRoutes: plan2.docs ? [
8862
+ ...hostedDocRoutes,
8863
+ ...(resolved.portal?.headerLinks ?? []).flatMap(
8864
+ (link) => link.page ? [link.to] : []
8865
+ )
8866
+ ] : hostedDocRoutes,
8695
8867
  apiReferencePath: plan2.apiReference ? `/${API_REFERENCE_PATH}` : void 0
8696
8868
  })
8697
8869
  }
8698
8870
  );
8699
8871
  const wrapper = renderWrapper(
8700
8872
  plan2,
8701
- wrapperPages,
8873
+ [...wrapperPages, ...headerWrapperPages],
8702
8874
  resolved.portal?.footer ? wrapperImport(contentFile(resolved.configDir, resolved.portal.footer)) : void 0
8703
8875
  );
8704
8876
  const wrapperPath = path14.join(resolved.state.generatedDir, WRAPPER_FILE);
@@ -10105,7 +10277,7 @@ init_invariant();
10105
10277
  // src/cli/check/v2.ts
10106
10278
  init_apitogo_config_core();
10107
10279
  import { readFile as readFile18 } from "node:fs/promises";
10108
- import path51 from "node:path";
10280
+ import path52 from "node:path";
10109
10281
 
10110
10282
  // src/cli/platform/client/platform-api.ts
10111
10283
  import { basename } from "node:path";
@@ -10121,6 +10293,17 @@ var PlatformApiError = class extends Error {
10121
10293
  this.problem = problem;
10122
10294
  }
10123
10295
  };
10296
+ var ProjectGoneError = class extends PlatformApiError {
10297
+ constructor(projectId) {
10298
+ super(
10299
+ `The project ${projectId} no longer exists on the platform: it was deleted, or it is not in the account you are signed in to. Run \`apitogo project create\` to make a new project for this folder, or link another one with \`--project-id <id>\`.`,
10300
+ 410
10301
+ );
10302
+ this.projectId = projectId;
10303
+ this.name = "ProjectGoneError";
10304
+ }
10305
+ code = "PROJECT_GONE";
10306
+ };
10124
10307
  function formatProblemDetails(problem) {
10125
10308
  const parts = [];
10126
10309
  if (problem.title) parts.push(problem.title);
@@ -10212,14 +10395,19 @@ async function fetchWithRetry(input5, init3, options) {
10212
10395
  }
10213
10396
 
10214
10397
  // src/cli/platform/client/platform-api.ts
10398
+ function projectIdOfPath(path100) {
10399
+ return /^\/api\/v1\/projects\/([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})(?:[/?#]|$)/i.exec(
10400
+ path100
10401
+ )?.[1];
10402
+ }
10215
10403
  var MAX_RETRIES = 3;
10216
10404
  var UPLOAD_TIMEOUT_MS = 10 * 60 * 1e3;
10217
10405
  var PlatformApiClient = class {
10218
10406
  constructor(config2) {
10219
10407
  this.config = config2;
10220
10408
  }
10221
- url(path99) {
10222
- return `${this.config.apiUrl}${path99.startsWith("/") ? path99 : `/${path99}`}`;
10409
+ url(path100) {
10410
+ return `${this.config.apiUrl}${path100.startsWith("/") ? path100 : `/${path100}`}`;
10223
10411
  }
10224
10412
  headers(extra) {
10225
10413
  return {
@@ -10229,9 +10417,9 @@ var PlatformApiClient = class {
10229
10417
  ...extra
10230
10418
  };
10231
10419
  }
10232
- async request(method, path99, init3) {
10420
+ async request(method, path100, init3) {
10233
10421
  const response = await fetchWithRetry(
10234
- this.url(path99),
10422
+ this.url(path100),
10235
10423
  {
10236
10424
  method,
10237
10425
  ...init3,
@@ -10247,13 +10435,39 @@ var PlatformApiClient = class {
10247
10435
  }
10248
10436
  );
10249
10437
  if (!response.ok) {
10250
- throw await parseErrorResponse(response);
10438
+ throw await this.failure(response, path100);
10251
10439
  }
10252
10440
  if (response.status === 204) {
10253
10441
  return void 0;
10254
10442
  }
10255
10443
  return await response.json();
10256
10444
  }
10445
+ /**
10446
+ * The error for a failed answer. A 404 under a project may mean the project itself is gone; a bare "Not Found" hid
10447
+ * that (API-133), so the project is asked once and a missing one becomes {@link ProjectGoneError}.
10448
+ */
10449
+ async failure(response, path100) {
10450
+ const error = await parseErrorResponse(response);
10451
+ const projectId = response.status === 404 ? projectIdOfPath(path100) : void 0;
10452
+ if (projectId && await this.projectMissing(projectId, path100))
10453
+ return new ProjectGoneError(projectId);
10454
+ return error;
10455
+ }
10456
+ /** Whether the project answers 404 itself. A path that is the project needs no second call. */
10457
+ async projectMissing(projectId, path100) {
10458
+ const projectPath = `/api/v1/projects/${projectId}`;
10459
+ if (path100.replace(/[?#].*$/, "").replace(/\/+$/, "") === projectPath)
10460
+ return true;
10461
+ try {
10462
+ const response = await fetch(this.url(projectPath), {
10463
+ method: "GET",
10464
+ headers: this.headers()
10465
+ });
10466
+ return response.status === 404;
10467
+ } catch {
10468
+ return false;
10469
+ }
10470
+ }
10257
10471
  whoami() {
10258
10472
  return this.request("GET", "/api/v1/auth/me");
10259
10473
  }
@@ -10361,7 +10575,10 @@ var PlatformApiClient = class {
10361
10575
  { method: "POST", maxRetries: 0, retryMutatingOnServerError: false }
10362
10576
  );
10363
10577
  if (!response.ok) {
10364
- throw await parseErrorResponse(response);
10578
+ throw await this.failure(
10579
+ response,
10580
+ `/api/v1/projects/${projectId}/infrastructure/static-web/deployments`
10581
+ );
10365
10582
  }
10366
10583
  return await response.json();
10367
10584
  } catch (error) {
@@ -10437,7 +10654,10 @@ var PlatformApiClient = class {
10437
10654
  }
10438
10655
  );
10439
10656
  if (!response.ok) {
10440
- throw await parseErrorResponse(response);
10657
+ throw await this.failure(
10658
+ response,
10659
+ `/api/v1/projects/${projectId}/backend/openapi`
10660
+ );
10441
10661
  }
10442
10662
  return await response.json();
10443
10663
  }
@@ -10486,7 +10706,11 @@ var PlatformApiClient = class {
10486
10706
  maxRetries: MAX_RETRIES
10487
10707
  }
10488
10708
  );
10489
- if (!response.ok) throw await parseErrorResponse(response);
10709
+ if (!response.ok)
10710
+ throw await this.failure(
10711
+ response,
10712
+ `/api/v1/projects/${projectId}/gateway/openapi.json`
10713
+ );
10490
10714
  return response.text();
10491
10715
  }
10492
10716
  getGatewayCapabilities(projectId) {
@@ -10699,15 +10923,15 @@ var PlatformApiClient = class {
10699
10923
  }
10700
10924
  );
10701
10925
  }
10702
- deleteDevPortalPage(projectId, path99) {
10703
- const query = new URLSearchParams({ path: path99 });
10926
+ deleteDevPortalPage(projectId, path100) {
10927
+ const query = new URLSearchParams({ path: path100 });
10704
10928
  return this.request(
10705
10929
  "DELETE",
10706
10930
  `/api/v1/projects/${projectId}/dev-portal/pages/files?${query.toString()}`
10707
10931
  );
10708
10932
  }
10709
- renameDevPortalPage(projectId, path99, newName) {
10710
- const query = new URLSearchParams({ path: path99 });
10933
+ renameDevPortalPage(projectId, path100, newName) {
10934
+ const query = new URLSearchParams({ path: path100 });
10711
10935
  return this.request(
10712
10936
  "POST",
10713
10937
  `/api/v1/projects/${projectId}/dev-portal/pages/files/rename?${query.toString()}`,
@@ -10798,7 +11022,10 @@ var PlatformApiClient = class {
10798
11022
  }
10799
11023
  );
10800
11024
  if (!response.ok) {
10801
- throw await parseErrorResponse(response);
11025
+ throw await this.failure(
11026
+ response,
11027
+ `/api/v1/projects/${projectId}/backend/deployments`
11028
+ );
10802
11029
  }
10803
11030
  return await response.json();
10804
11031
  }
@@ -10823,8 +11050,8 @@ var PlatformApiClient = class {
10823
11050
  * the project-less endpoint is used; `planTier` overrides the tier used for paid-plan checks.
10824
11051
  */
10825
11052
  validateGatewayPlans(plans, options = {}) {
10826
- const path99 = options.projectId ? `/api/v1/projects/${options.projectId}/gateway/plans/validate` : "/api/v1/gateway/plans/validate";
10827
- return this.request("POST", path99, {
11053
+ const path100 = options.projectId ? `/api/v1/projects/${options.projectId}/gateway/plans/validate` : "/api/v1/gateway/plans/validate";
11054
+ return this.request("POST", path100, {
10828
11055
  headers: { "Content-Type": "application/json" },
10829
11056
  body: JSON.stringify({ plans, planTier: options.planTier })
10830
11057
  });
@@ -11052,11 +11279,11 @@ import {
11052
11279
  rm as rm8,
11053
11280
  writeFile as writeFile15
11054
11281
  } from "node:fs/promises";
11055
- import path50 from "node:path";
11282
+ import path51 from "node:path";
11056
11283
 
11057
11284
  // src/vite/build.ts
11058
11285
  import { mkdir as mkdir11, readFile as readFile13, rename as rename5, rm as rm7, writeFile as writeFile13 } from "node:fs/promises";
11059
- import path48 from "node:path";
11286
+ import path49 from "node:path";
11060
11287
  import { build as esbuild } from "esbuild";
11061
11288
  import { build as viteBuild, mergeConfig as mergeConfig3 } from "vite";
11062
11289
 
@@ -11192,7 +11419,7 @@ init_invariant();
11192
11419
  init_joinUrl();
11193
11420
 
11194
11421
  // src/vite/config.ts
11195
- import path43 from "node:path";
11422
+ import path44 from "node:path";
11196
11423
  import dotenv from "dotenv";
11197
11424
  import colors8 from "picocolors";
11198
11425
  import {
@@ -11203,7 +11430,7 @@ import {
11203
11430
  // package.json
11204
11431
  var package_default = {
11205
11432
  name: "@lukoweb/apitogo",
11206
- version: "0.2.60",
11433
+ version: "0.2.64",
11207
11434
  type: "module",
11208
11435
  sideEffects: [
11209
11436
  "**/*.css",
@@ -11988,14 +12215,14 @@ var flattenAllOf = (schema2) => {
11988
12215
  };
11989
12216
 
11990
12217
  // src/lib/util/traverse.ts
11991
- var traverse = (specification, transform, path99 = []) => {
11992
- const transformed = transform(specification, path99);
12218
+ var traverse = (specification, transform, path100 = []) => {
12219
+ const transformed = transform(specification, path100);
11993
12220
  if (typeof transformed !== "object" || transformed === null) {
11994
12221
  return transformed;
11995
12222
  }
11996
12223
  const result = Array.isArray(transformed) ? [] : {};
11997
12224
  for (const [key, value] of Object.entries(transformed)) {
11998
- const currentPath = [...path99, key];
12225
+ const currentPath = [...path100, key];
11999
12226
  if (Array.isArray(value)) {
12000
12227
  result[key] = value.map(
12001
12228
  (item, index) => typeof item === "object" && item != null ? traverse(item, transform, [...currentPath, index.toString()]) : item
@@ -12023,9 +12250,9 @@ var resolveLocalRef = (schema2, ref) => {
12023
12250
  if (schemaCache?.has(ref)) {
12024
12251
  return schemaCache.get(ref);
12025
12252
  }
12026
- const path99 = ref.split("/").slice(1);
12253
+ const path100 = ref.split("/").slice(1);
12027
12254
  let current = schema2;
12028
- for (const segment of path99) {
12255
+ for (const segment of path100) {
12029
12256
  if (!current || typeof current !== "object") {
12030
12257
  current = null;
12031
12258
  }
@@ -12044,7 +12271,7 @@ var dereference = async (schema2, resolvers = []) => {
12044
12271
  }
12045
12272
  const cloned = structuredClone(schema2);
12046
12273
  const visited = /* @__PURE__ */ new Set();
12047
- const resolve = async (current, path99) => {
12274
+ const resolve = async (current, path100) => {
12048
12275
  if (isIndexableObject(current)) {
12049
12276
  if (visited.has(current)) {
12050
12277
  return CIRCULAR_REF;
@@ -12052,7 +12279,7 @@ var dereference = async (schema2, resolvers = []) => {
12052
12279
  visited.add(current);
12053
12280
  if (Array.isArray(current)) {
12054
12281
  for (let index = 0; index < current.length; index++) {
12055
- current[index] = await resolve(current[index], `${path99}/${index}`);
12282
+ current[index] = await resolve(current[index], `${path100}/${index}`);
12056
12283
  }
12057
12284
  } else {
12058
12285
  if ("$ref" in current && typeof current.$ref === "string") {
@@ -12063,13 +12290,13 @@ var dereference = async (schema2, resolvers = []) => {
12063
12290
  for (const resolver of resolvers) {
12064
12291
  const resolved = await resolver($ref);
12065
12292
  if (resolved) {
12066
- result2 = await resolve(resolved, path99);
12293
+ result2 = await resolve(resolved, path100);
12067
12294
  break;
12068
12295
  }
12069
12296
  }
12070
12297
  if (result2 === void 0) {
12071
12298
  const resolved = await resolveLocalRef(cloned, $ref);
12072
- result2 = await resolve(resolved, path99);
12299
+ result2 = await resolve(resolved, path100);
12073
12300
  }
12074
12301
  if (hasSiblings) {
12075
12302
  if (result2 === CIRCULAR_REF) {
@@ -12082,7 +12309,7 @@ var dereference = async (schema2, resolvers = []) => {
12082
12309
  return result2;
12083
12310
  }
12084
12311
  for (const key in current) {
12085
- current[key] = await resolve(current[key], `${path99}/${key}`);
12312
+ current[key] = await resolve(current[key], `${path100}/${key}`);
12086
12313
  }
12087
12314
  }
12088
12315
  visited.delete(current);
@@ -12121,9 +12348,9 @@ var upgradeSchema = (schema2) => {
12121
12348
  }
12122
12349
  return sub;
12123
12350
  });
12124
- schema2 = traverse(schema2, (sub, path99) => {
12351
+ schema2 = traverse(schema2, (sub, path100) => {
12125
12352
  if (sub.example !== void 0) {
12126
- if (isSchemaPath(path99 ?? [])) {
12353
+ if (isSchemaPath(path100 ?? [])) {
12127
12354
  sub.examples = [sub.example];
12128
12355
  } else {
12129
12356
  sub.examples = {
@@ -12136,11 +12363,11 @@ var upgradeSchema = (schema2) => {
12136
12363
  }
12137
12364
  return sub;
12138
12365
  });
12139
- schema2 = traverse(schema2, (schema3, path99) => {
12366
+ schema2 = traverse(schema2, (schema3, path100) => {
12140
12367
  if (schema3.type === "object" && schema3.properties !== void 0) {
12141
- const parentPath = path99?.slice(0, -1);
12368
+ const parentPath = path100?.slice(0, -1);
12142
12369
  const isMultipart = parentPath?.some((segment, index) => {
12143
- return segment === "content" && path99?.[index + 1] === "multipart/form-data";
12370
+ return segment === "content" && path100?.[index + 1] === "multipart/form-data";
12144
12371
  });
12145
12372
  if (isMultipart) {
12146
12373
  const entries = Object.entries(schema3.properties);
@@ -12154,8 +12381,8 @@ var upgradeSchema = (schema2) => {
12154
12381
  }
12155
12382
  return schema3;
12156
12383
  });
12157
- schema2 = traverse(schema2, (schema3, path99) => {
12158
- if (path99?.includes("content") && path99.includes("application/octet-stream")) {
12384
+ schema2 = traverse(schema2, (schema3, path100) => {
12385
+ if (path100?.includes("content") && path100.includes("application/octet-stream")) {
12159
12386
  return {};
12160
12387
  }
12161
12388
  if (schema3.type === "string" && schema3.format === "binary") {
@@ -12181,11 +12408,11 @@ var upgradeSchema = (schema2) => {
12181
12408
  }
12182
12409
  return sub;
12183
12410
  });
12184
- schema2 = traverse(schema2, (schema3, path99) => {
12411
+ schema2 = traverse(schema2, (schema3, path100) => {
12185
12412
  if (schema3.type === "string" && schema3.format === "byte") {
12186
- const parentPath = path99?.slice(0, -1);
12413
+ const parentPath = path100?.slice(0, -1);
12187
12414
  const contentMediaType = parentPath?.find(
12188
- (_, index) => path99?.[index - 1] === "content"
12415
+ (_, index) => path100?.[index - 1] === "content"
12189
12416
  );
12190
12417
  return {
12191
12418
  type: "string",
@@ -12197,7 +12424,7 @@ var upgradeSchema = (schema2) => {
12197
12424
  });
12198
12425
  return schema2;
12199
12426
  };
12200
- function isSchemaPath(path99) {
12427
+ function isSchemaPath(path100) {
12201
12428
  const schemaLocations = [
12202
12429
  ["components", "schemas"],
12203
12430
  "properties",
@@ -12210,10 +12437,10 @@ function isSchemaPath(path99) {
12210
12437
  ];
12211
12438
  return schemaLocations.some((location) => {
12212
12439
  if (Array.isArray(location)) {
12213
- return location.every((segment, index) => path99[index] === segment);
12440
+ return location.every((segment, index) => path100[index] === segment);
12214
12441
  }
12215
- return path99.includes(location);
12216
- }) || path99.includes("schema") || path99.some((segment) => segment.endsWith("Schema"));
12442
+ return path100.includes(location);
12443
+ }) || path100.includes("schema") || path100.some((segment) => segment.endsWith("Schema"));
12217
12444
  }
12218
12445
 
12219
12446
  // src/lib/oas/parser/index.ts
@@ -12295,13 +12522,13 @@ var OPENAPI_PROPS = /* @__PURE__ */ new Set([
12295
12522
  "anyOf",
12296
12523
  "oneOf"
12297
12524
  ]);
12298
- var handleCircularRefs = (obj, currentPath = /* @__PURE__ */ new WeakSet(), refs = /* @__PURE__ */ new WeakMap(), path99 = [], currentRefPaths = /* @__PURE__ */ new Set()) => {
12525
+ var handleCircularRefs = (obj, currentPath = /* @__PURE__ */ new WeakSet(), refs = /* @__PURE__ */ new WeakMap(), path100 = [], currentRefPaths = /* @__PURE__ */ new Set()) => {
12299
12526
  if (obj === null || typeof obj !== "object") return obj;
12300
12527
  const refPath = obj.__$ref;
12301
12528
  const isCircular = currentPath.has(obj) || typeof refPath === "string" && currentRefPaths.has(refPath);
12302
12529
  if (isCircular) {
12303
12530
  if (typeof refPath === "string") return SCHEMA_REF_PREFIX + refPath;
12304
- const circularProp = path99.find((p) => !OPENAPI_PROPS.has(p)) || path99[0];
12531
+ const circularProp = path100.find((p) => !OPENAPI_PROPS.has(p)) || path100[0];
12305
12532
  return [CIRCULAR_REF, circularProp].filter(Boolean).join(":");
12306
12533
  }
12307
12534
  if (refs.has(obj)) return refs.get(obj);
@@ -12311,7 +12538,7 @@ var handleCircularRefs = (obj, currentPath = /* @__PURE__ */ new WeakSet(), refs
12311
12538
  value,
12312
12539
  currentPath,
12313
12540
  refs,
12314
- [...path99, key],
12541
+ [...path100, key],
12315
12542
  currentRefPaths
12316
12543
  );
12317
12544
  const result = Array.isArray(obj) ? obj.map((item, i) => recurse(item, i.toString())) : Object.fromEntries(
@@ -12346,7 +12573,7 @@ var resolveExtensions = (obj) => Object.fromEntries(
12346
12573
  var getAllTags = (schema2) => {
12347
12574
  const rootTags = schema2.tags ?? [];
12348
12575
  const operations = Object.values(schema2.paths ?? {}).flatMap(
12349
- (path99) => HttpMethods.map((k) => path99?.[k]).filter((op) => op != null)
12576
+ (path100) => HttpMethods.map((k) => path100?.[k]).filter((op) => op != null)
12350
12577
  );
12351
12578
  const operationTags = new Set(operations.flatMap((op) => op.tags ?? []));
12352
12579
  const hasUntaggedOperations = operations.some(
@@ -12401,7 +12628,7 @@ var getAllSlugs = (ops, schemaTags = []) => {
12401
12628
  var getOperationSlugKey = (op) => [op.path, op.method, op.operationId, op.summary].filter(Boolean).join("-");
12402
12629
  var getAllOperations = (paths) => {
12403
12630
  const operations = Object.entries(paths ?? {}).flatMap(
12404
- ([path99, value]) => HttpMethods.flatMap((method) => {
12631
+ ([path100, value]) => HttpMethods.flatMap((method) => {
12405
12632
  if (!value?.[method]) return [];
12406
12633
  const operation = value[method];
12407
12634
  const pathParameters = value.parameters ?? [];
@@ -12421,7 +12648,7 @@ var getAllOperations = (paths) => {
12421
12648
  return {
12422
12649
  ...operation,
12423
12650
  method,
12424
- path: path99,
12651
+ path: path100,
12425
12652
  parameters,
12426
12653
  servers,
12427
12654
  tags: operation.tags ?? []
@@ -12779,8 +13006,8 @@ var Schema = builder.objectRef("Schema").implement({
12779
13006
  }),
12780
13007
  paths: t.field({
12781
13008
  type: [PathItem],
12782
- resolve: (root) => Object.entries(root.paths ?? {}).map(([path99, value]) => ({
12783
- path: path99,
13009
+ resolve: (root) => Object.entries(root.paths ?? {}).map(([path100, value]) => ({
13010
+ path: path100,
12784
13011
  // biome-ignore lint/style/noNonNullAssertion: value is guaranteed to be defined
12785
13012
  methods: Object.keys(value)
12786
13013
  }))
@@ -12945,7 +13172,7 @@ init_joinUrl();
12945
13172
 
12946
13173
  // src/vite/api/schema-codegen.ts
12947
13174
  var unescapeJsonPointer = (uri) => decodeURIComponent(uri.replace(/~1/g, "/").replace(/~0/g, "~"));
12948
- var getSegmentsFromPath = (path99) => path99.split("/").slice(1).map(unescapeJsonPointer);
13175
+ var getSegmentsFromPath = (path100) => path100.split("/").slice(1).map(unescapeJsonPointer);
12949
13176
  var createLocalRefMap = (obj) => {
12950
13177
  const refMap = /* @__PURE__ */ new Map();
12951
13178
  const siblingsMap = /* @__PURE__ */ new Map();
@@ -12976,16 +13203,16 @@ var replaceMarkers = (code, mergedRefs) => code.replace(/"__refMap:(.*?)"/g, '__
12976
13203
  /"__refMap\+Siblings:(.*?)"/g,
12977
13204
  (_, key) => mergedRefs.get(key) ?? `__refMap["${key}"]`
12978
13205
  );
12979
- var lookup = (schema2, path99, filePath) => {
12980
- const parts = getSegmentsFromPath(path99);
13206
+ var lookup = (schema2, path100, filePath) => {
13207
+ const parts = getSegmentsFromPath(path100);
12981
13208
  let val = schema2;
12982
13209
  for (const part of parts) {
12983
13210
  while (val.$ref?.startsWith("#/")) {
12984
- val = val.$ref === path99 ? val : lookup(schema2, val.$ref, filePath);
13211
+ val = val.$ref === path100 ? val : lookup(schema2, val.$ref, filePath);
12985
13212
  }
12986
13213
  if (val[part] === void 0) {
12987
13214
  throw new Error(
12988
- `Error in ${filePath ?? "code generation"}: Could not find path segment ${part} in path: ${path99}`
13215
+ `Error in ${filePath ?? "code generation"}: Could not find path segment ${part} in path: ${path100}`
12989
13216
  );
12990
13217
  }
12991
13218
  val = val[part];
@@ -13232,8 +13459,8 @@ var SchemaManager = class {
13232
13459
  }
13233
13460
  }
13234
13461
  };
13235
- getLatestSchema = (path99) => this.processedSchemas[path99]?.at(0);
13236
- getSchemasForPath = (path99) => this.processedSchemas[path99];
13462
+ getLatestSchema = (path100) => this.processedSchemas[path100]?.at(0);
13463
+ getSchemasForPath = (path100) => this.processedSchemas[path100];
13237
13464
  getSchemaImports = () => Object.values(this.processedSchemas).flat().filter((s) => s.importKey);
13238
13465
  getUrlToFilePathMap = () => {
13239
13466
  const map = /* @__PURE__ */ new Map();
@@ -14357,8 +14584,32 @@ init_loader();
14357
14584
  init_ProtectedRoutesSchema();
14358
14585
  init_joinUrl();
14359
14586
  import { mkdir as mkdir8, writeFile as writeFile10 } from "node:fs/promises";
14360
- import path36 from "node:path";
14587
+ import path37 from "node:path";
14361
14588
  import { matchPath } from "react-router";
14589
+
14590
+ // src/vite/markdown-output-path.ts
14591
+ import { rm as rm6 } from "node:fs/promises";
14592
+ import path36 from "node:path";
14593
+ var markdownOutputPath = (distDir, routePath) => {
14594
+ const segments = routePath === "/" ? ["index"] : routePath.split("/").filter(Boolean);
14595
+ return `${path36.join(distDir, ...segments)}.md`;
14596
+ };
14597
+ var removableMarkdownOutput = (distDir, routePath) => {
14598
+ const output4 = path36.resolve(markdownOutputPath(distDir, routePath));
14599
+ const relative = path36.relative(path36.resolve(distDir), output4);
14600
+ if (!relative || relative.startsWith("..") || path36.isAbsolute(relative)) {
14601
+ return void 0;
14602
+ }
14603
+ return output4;
14604
+ };
14605
+ var removeExportedMarkdown = async (distDir, pages) => {
14606
+ await Promise.all(
14607
+ pages.map((page) => removableMarkdownOutput(distDir, page.routePath)).filter((output4) => output4 !== void 0).map((output4) => rm6(output4, { force: true }).catch(() => {
14608
+ }))
14609
+ );
14610
+ };
14611
+
14612
+ // src/vite/plugin-markdown-export.ts
14362
14613
  var processMarkdownFile = async (filePath) => {
14363
14614
  const { content: markdownContent, data: frontmatter } = await readFrontmatter(filePath);
14364
14615
  let finalMarkdown = markdownContent;
@@ -14445,7 +14696,7 @@ var viteMarkdownExportPlugin = () => {
14445
14696
  if (process.env.NODE_ENV !== "production" || Object.keys(markdownFiles).length === 0 || !needsMdFiles) {
14446
14697
  return;
14447
14698
  }
14448
- const distDir = path36.join(
14699
+ const distDir = path37.join(
14449
14700
  config2.__meta.rootDir,
14450
14701
  "dist",
14451
14702
  config2.basePath ?? ""
@@ -14465,16 +14716,15 @@ var viteMarkdownExportPlugin = () => {
14465
14716
  description,
14466
14717
  content: finalMarkdown
14467
14718
  });
14468
- const segments = routePath === "/" ? ["index"] : routePath.split("/").filter(Boolean);
14469
- const outputPath = `${path36.join(distDir, ...segments)}.md`;
14470
- await mkdir8(path36.dirname(outputPath), { recursive: true });
14719
+ const outputPath = markdownOutputPath(distDir, routePath);
14720
+ await mkdir8(path37.dirname(outputPath), { recursive: true });
14471
14721
  await writeFile10(outputPath, finalMarkdown, "utf-8");
14472
14722
  } catch (error) {
14473
14723
  console.warn(`Failed to export markdown for ${routePath}:`, error);
14474
14724
  }
14475
14725
  }
14476
14726
  if (config2.docs?.llms?.llmsTxt || config2.docs?.llms?.llmsTxtFull) {
14477
- const markdownInfoPath = path36.join(
14727
+ const markdownInfoPath = path37.join(
14478
14728
  config2.__meta.rootDir,
14479
14729
  "node_modules/.apitogo/markdown-info.json"
14480
14730
  );
@@ -14626,9 +14876,9 @@ var remarkCodeTabs = () => (tree) => {
14626
14876
  };
14627
14877
 
14628
14878
  // src/vite/mdx/remark-inject-filepath.ts
14629
- import path37 from "node:path";
14879
+ import path38 from "node:path";
14630
14880
  var remarkInjectFilepath = (rootDir) => (tree, vfile) => {
14631
- const relativePath = path37.relative(rootDir, vfile.path).split(path37.sep).join(path37.posix.sep);
14881
+ const relativePath = path38.relative(rootDir, vfile.path).split(path38.sep).join(path38.posix.sep);
14632
14882
  tree.children.unshift(exportMdxjsConst("__filepath", relativePath));
14633
14883
  };
14634
14884
 
@@ -14715,18 +14965,18 @@ var remarkLastModified = () => {
14715
14965
  };
14716
14966
 
14717
14967
  // src/vite/mdx/remark-link-rewrite.ts
14718
- import path38 from "node:path";
14968
+ import path39 from "node:path";
14719
14969
  import { visit as visit5 } from "unist-util-visit";
14720
14970
  var remarkLinkRewrite = (basePath = "") => (tree) => {
14721
14971
  visit5(tree, "link", (node) => {
14722
14972
  if (!node.url) return;
14723
14973
  if (node.url.startsWith("http") || node.url.startsWith("mailto:")) return;
14724
14974
  node.url = node.url.replace(/\\/g, "/");
14725
- const base = path38.posix.join(basePath);
14975
+ const base = path39.posix.join(basePath);
14726
14976
  if (basePath && node.url.startsWith(base)) {
14727
14977
  node.url = node.url.slice(base.length);
14728
14978
  } else if (!node.url.startsWith("/") && !node.url.startsWith("#")) {
14729
- node.url = path38.posix.join("..", node.url);
14979
+ node.url = path39.posix.join("..", node.url);
14730
14980
  }
14731
14981
  node.url = node.url.replace(/\.mdx?(#.*)?$/, "$1");
14732
14982
  });
@@ -14946,14 +15196,14 @@ var plugin_mdx_default = viteMdxPlugin;
14946
15196
 
14947
15197
  // src/vite/plugin-modules.ts
14948
15198
  init_loader();
14949
- import path39 from "node:path";
15199
+ import path40 from "node:path";
14950
15200
  import { normalizePath as normalizePath2 } from "vite";
14951
- var getLandingModuleImport = (moduleDir, mode) => mode === "internal" ? normalizePath2(path39.join(moduleDir, "../module-landing/src/index.tsx")) : "@lukoweb/apitogo-module-landing";
14952
- var getUserPanelModuleImport = (moduleDir, mode) => mode === "internal" ? normalizePath2(path39.join(moduleDir, "../module-user-panel/src/index.tsx")) : "@lukoweb/apitogo-module-user-panel";
15201
+ var getLandingModuleImport = (moduleDir, mode) => mode === "internal" ? normalizePath2(path40.join(moduleDir, "../module-landing/src/index.tsx")) : "@lukoweb/apitogo-module-landing";
15202
+ var getUserPanelModuleImport = (moduleDir, mode) => mode === "internal" ? normalizePath2(path40.join(moduleDir, "../module-user-panel/src/index.tsx")) : "@lukoweb/apitogo-module-user-panel";
14953
15203
  var getBillingPluginImport = (moduleDir, mode) => mode === "internal" ? normalizePath2(
14954
- path39.join(moduleDir, "../plugin-dev-portal-billing/src/index.ts")
15204
+ path40.join(moduleDir, "../plugin-dev-portal-billing/src/index.ts")
14955
15205
  ) : "@lukoweb/apitogo-plugin-dev-portal-billing";
14956
- var resolveProjectPageImport = (rootDir, pagePath) => normalizePath2(path39.resolve(rootDir, pagePath));
15206
+ var resolveProjectPageImport = (rootDir, pagePath) => normalizePath2(path40.resolve(rootDir, pagePath));
14957
15207
  var viteModulesPlugin = () => {
14958
15208
  const virtualModuleId4 = "virtual:zudoku-modules-plugin";
14959
15209
  const resolvedVirtualModuleId5 = `\0${virtualModuleId4}`;
@@ -15095,17 +15345,17 @@ var plugin_modules_default = viteModulesPlugin;
15095
15345
 
15096
15346
  // src/vite/plugin-offroot-resolve.ts
15097
15347
  init_loader();
15098
- import path40 from "node:path";
15348
+ import path41 from "node:path";
15099
15349
  function stripQuery(id) {
15100
15350
  const query = id.indexOf("?");
15101
15351
  return query === -1 ? id : id.slice(0, query);
15102
15352
  }
15103
15353
  function isInside(parent, child) {
15104
- const relative = path40.relative(path40.resolve(parent), path40.resolve(child));
15105
- return relative === "" || !relative.startsWith("..") && !path40.isAbsolute(relative);
15354
+ const relative = path41.relative(path41.resolve(parent), path41.resolve(child));
15355
+ return relative === "" || !relative.startsWith("..") && !path41.isAbsolute(relative);
15106
15356
  }
15107
15357
  function isBareSpecifier(source) {
15108
- return !source.startsWith("\0") && !source.startsWith(".") && !source.startsWith("/") && !path40.isAbsolute(source);
15358
+ return !source.startsWith("\0") && !source.startsWith(".") && !source.startsWith("/") && !path41.isAbsolute(source);
15109
15359
  }
15110
15360
  function isInsideNodeModules(filePath) {
15111
15361
  return filePath.split(/[\\/]/).includes("node_modules");
@@ -15129,7 +15379,7 @@ function viteOffrootResolvePlugin() {
15129
15379
  if (!shouldResolveFromSiteRoot(source, importer, rootDir)) {
15130
15380
  return;
15131
15381
  }
15132
- return this.resolve(source, path40.join(rootDir, "package.json"), {
15382
+ return this.resolve(source, path41.join(rootDir, "package.json"), {
15133
15383
  ...options,
15134
15384
  skipSelf: true
15135
15385
  });
@@ -15139,7 +15389,7 @@ function viteOffrootResolvePlugin() {
15139
15389
 
15140
15390
  // src/vite/plugin-search.ts
15141
15391
  init_loader();
15142
- import path41 from "node:path";
15392
+ import path42 from "node:path";
15143
15393
  var viteSearchPlugin = () => {
15144
15394
  const virtualModuleId4 = "virtual:zudoku-search-plugin";
15145
15395
  const resolvedVirtualModuleId5 = `\0${virtualModuleId4}`;
@@ -15157,7 +15407,7 @@ var viteSearchPlugin = () => {
15157
15407
  return resolvedVirtualModuleId5;
15158
15408
  }
15159
15409
  if (id === "/pagefind/pagefind.js" && resolvedViteConfig?.publicDir) {
15160
- return path41.join(resolvedViteConfig.publicDir, "pagefind/pagefind.js");
15410
+ return path42.join(resolvedViteConfig.publicDir, "pagefind/pagefind.js");
15161
15411
  }
15162
15412
  },
15163
15413
  async load(id) {
@@ -15298,27 +15548,27 @@ function vitePlugin() {
15298
15548
 
15299
15549
  // src/vite/watch-ignore.ts
15300
15550
  import { lstatSync, realpathSync } from "node:fs";
15301
- import path42 from "node:path";
15551
+ import path43 from "node:path";
15302
15552
  var STATE_INSIDE_CONTENT = /[\\/]content[\\/](?:\.apitogo|apitogo[\\/]\.state)(?:[\\/]|$)/;
15303
15553
  var within = (base, file) => {
15304
- const resolvedBase = path42.resolve(base);
15305
- const resolvedFile = path42.resolve(file);
15306
- return resolvedFile === resolvedBase || resolvedFile.startsWith(resolvedBase + path42.sep);
15554
+ const resolvedBase = path43.resolve(base);
15555
+ const resolvedFile = path43.resolve(file);
15556
+ return resolvedFile === resolvedBase || resolvedFile.startsWith(resolvedBase + path43.sep);
15307
15557
  };
15308
15558
  var unique = (paths) => [...new Set(paths)];
15309
15559
  function isWorkspace(root) {
15310
15560
  try {
15311
- return path42.basename(root) === "generated" && lstatSync(path42.join(root, "content")).isSymbolicLink();
15561
+ return path43.basename(root) === "generated" && lstatSync(path43.join(root, "content")).isSymbolicLink();
15312
15562
  } catch {
15313
15563
  return false;
15314
15564
  }
15315
15565
  }
15316
15566
  function tenantDir(root) {
15317
- return isWorkspace(root) ? realpathSync(path42.join(root, "content")) : void 0;
15567
+ return isWorkspace(root) ? realpathSync(path43.join(root, "content")) : void 0;
15318
15568
  }
15319
15569
  function stateIgnore(root) {
15320
15570
  if (!isWorkspace(root)) return void 0;
15321
- const state = path42.dirname(root);
15571
+ const state = path43.dirname(root);
15322
15572
  const states = unique([state, realpathSync(state)]);
15323
15573
  const roots = unique([root, realpathSync(root)]);
15324
15574
  return (file) => {
@@ -15327,19 +15577,28 @@ function stateIgnore(root) {
15327
15577
  return STATE_INSIDE_CONTENT.test(file);
15328
15578
  };
15329
15579
  }
15580
+ var STATE_FOLDER = /(?:^|[\\/])(?:\.apitogo|apitogo[\\/]\.state)(?:[\\/]|$)/;
15581
+ function nestedStateIgnore(root) {
15582
+ const resolvedRoot = path43.resolve(root);
15583
+ return (file) => {
15584
+ const relative = path43.relative(resolvedRoot, path43.resolve(file));
15585
+ if (relative.startsWith("..") || path43.isAbsolute(relative)) return false;
15586
+ return STATE_FOLDER.test(relative);
15587
+ };
15588
+ }
15330
15589
  function devServerAllow(root, moduleDir) {
15331
15590
  const tenant = tenantDir(root);
15332
- return [root, path42.dirname(root), moduleDir, ...tenant ? [tenant] : []];
15591
+ return [root, path43.dirname(root), moduleDir, ...tenant ? [tenant] : []];
15333
15592
  }
15334
15593
 
15335
15594
  // src/vite/config.ts
15336
15595
  var resolveMergedPublicDir = (rootDir, merged) => {
15337
15596
  if (merged.publicDir === false) return void 0;
15338
15597
  const rel2 = typeof merged.publicDir === "string" ? merged.publicDir : "public";
15339
- return path43.resolve(rootDir, rel2);
15598
+ return path44.resolve(rootDir, rel2);
15340
15599
  };
15341
- var getAppClientEntryPath = () => path43.posix.join(getZudokuRootDir(), "src/app/entry.client.tsx");
15342
- var getAppServerEntryPath = () => path43.posix.join(getZudokuRootDir(), "src/app/entry.server.tsx");
15600
+ var getAppClientEntryPath = () => path44.posix.join(getZudokuRootDir(), "src/app/entry.client.tsx");
15601
+ var getAppServerEntryPath = () => path44.posix.join(getZudokuRootDir(), "src/app/entry.server.tsx");
15343
15602
  var hasLoggedCdnInfo = false;
15344
15603
  var MEDIA_REGEX = /\.(a?png|jpe?g|gif|bmp|svg|webp|tiff|ico|webm|ogg|mp3|wav|m4a|avif|mp4)/i;
15345
15604
  var defineEnvVars = (vars) => Object.fromEntries(
@@ -15370,7 +15629,7 @@ async function getViteConfig(dir, configEnv) {
15370
15629
  const ignoreState = stateIgnore(dir);
15371
15630
  if (ZuploEnv.isZuplo) {
15372
15631
  dotenv.config({
15373
- path: path43.resolve(config2.__meta.rootDir, "../.env.zuplo"),
15632
+ path: path44.resolve(config2.__meta.rootDir, "../.env.zuplo"),
15374
15633
  quiet: true
15375
15634
  });
15376
15635
  }
@@ -15425,6 +15684,7 @@ async function getViteConfig(dir, configEnv) {
15425
15684
  `${dir}/.git`,
15426
15685
  `${config2.__meta.moduleDir}/src/vite`,
15427
15686
  `${config2.__meta.moduleDir}/src/cli`,
15687
+ nestedStateIgnore(dir),
15428
15688
  ...ignoreState ? [ignoreState] : []
15429
15689
  ]
15430
15690
  }
@@ -15433,8 +15693,8 @@ async function getViteConfig(dir, configEnv) {
15433
15693
  ssr: configEnv.isSsrBuild,
15434
15694
  sourcemap: true,
15435
15695
  target: "es2022",
15436
- outDir: path43.resolve(
15437
- path43.join(
15696
+ outDir: path44.resolve(
15697
+ path44.join(
15438
15698
  dir,
15439
15699
  "dist",
15440
15700
  config2.basePath ?? "",
@@ -15453,7 +15713,7 @@ async function getViteConfig(dir, configEnv) {
15453
15713
  },
15454
15714
  experimental: {
15455
15715
  renderBuiltUrl(filename) {
15456
- if (cdnUrl?.base && [".js", ".css"].includes(path43.extname(filename))) {
15716
+ if (cdnUrl?.base && [".js", ".css"].includes(path44.extname(filename))) {
15457
15717
  return joinUrl(cdnUrl.base, filename);
15458
15718
  }
15459
15719
  if (cdnUrl?.media && MEDIA_REGEX.test(filename)) {
@@ -15466,7 +15726,7 @@ async function getViteConfig(dir, configEnv) {
15466
15726
  esbuildOptions: {
15467
15727
  target: "es2022"
15468
15728
  },
15469
- entries: [path43.posix.join(getZudokuRootDir(), "src/{app,lib}/**")],
15729
+ entries: [path44.posix.join(getZudokuRootDir(), "src/{app,lib}/**")],
15470
15730
  exclude: [
15471
15731
  "@lukoweb/apitogo",
15472
15732
  "@lukoweb/apitogo-plugin-dev-portal-billing"
@@ -15570,7 +15830,7 @@ init_package_json();
15570
15830
  init_joinUrl();
15571
15831
  import assert from "node:assert";
15572
15832
  import { cp, mkdir as mkdir9, writeFile as writeFile11 } from "node:fs/promises";
15573
- import path44 from "node:path";
15833
+ import path45 from "node:path";
15574
15834
  var pkgJson = getZudokuPackageJson();
15575
15835
  function generateOutput({
15576
15836
  config: config2,
@@ -15627,9 +15887,9 @@ async function writeOutput(dir, {
15627
15887
  rewrites
15628
15888
  }) {
15629
15889
  const output4 = generateOutput({ config: config2, redirects, rewrites });
15630
- const outputDir = process.env.VERCEL ? path44.join(dir, ".vercel/output") : path44.join(dir, "dist/.output");
15890
+ const outputDir = process.env.VERCEL ? path45.join(dir, ".vercel/output") : path45.join(dir, "dist/.output");
15631
15891
  await mkdir9(outputDir, { recursive: true });
15632
- const outputFile = path44.join(outputDir, "config.json");
15892
+ const outputFile = path45.join(outputDir, "config.json");
15633
15893
  await writeFile11(outputFile, JSON.stringify(output4, null, 2), "utf-8");
15634
15894
  if (process.env.VERCEL) {
15635
15895
  console.log("Wrote Vercel output to", outputDir);
@@ -15639,9 +15899,9 @@ async function writeOutput(dir, {
15639
15899
  // src/vite/prerender/prerender.ts
15640
15900
  init_logger();
15641
15901
  init_file_exists();
15642
- import { readFile as readFile12, rm as rm6 } from "node:fs/promises";
15902
+ import { readFile as readFile12 } from "node:fs/promises";
15643
15903
  import os from "node:os";
15644
- import path47 from "node:path";
15904
+ import path48 from "node:path";
15645
15905
  import { pathToFileURL } from "node:url";
15646
15906
  import { createIndex } from "pagefind";
15647
15907
  import colors11 from "picocolors";
@@ -15683,7 +15943,7 @@ function throttle(fn) {
15683
15943
  init_joinUrl();
15684
15944
  import { createWriteStream, existsSync as existsSync5 } from "node:fs";
15685
15945
  import { mkdir as mkdir10 } from "node:fs/promises";
15686
- import path45 from "node:path";
15946
+ import path46 from "node:path";
15687
15947
  import colors9 from "picocolors";
15688
15948
  import { SitemapStream } from "sitemap";
15689
15949
  async function generateSitemap({
@@ -15697,11 +15957,11 @@ async function generateSitemap({
15697
15957
  return;
15698
15958
  }
15699
15959
  const sitemap = new SitemapStream({ hostname: config2.siteUrl });
15700
- const outputDir = path45.resolve(baseOutputDir, config2.outDir ?? "");
15960
+ const outputDir = path46.resolve(baseOutputDir, config2.outDir ?? "");
15701
15961
  if (!existsSync5(outputDir)) {
15702
15962
  await mkdir10(outputDir, { recursive: true });
15703
15963
  }
15704
- const sitemapOutputPath = path45.join(outputDir, "sitemap.xml");
15964
+ const sitemapOutputPath = path46.join(outputDir, "sitemap.xml");
15705
15965
  const writeStream = createWriteStream(sitemapOutputPath);
15706
15966
  sitemap.pipe(writeStream);
15707
15967
  let lastmod;
@@ -15731,14 +15991,14 @@ async function generateSitemap({
15731
15991
 
15732
15992
  // src/vite/prerender/utils.ts
15733
15993
  init_joinUrl();
15734
- var resolveRoutePath = (path99) => {
15735
- const segments = path99.split("/");
15994
+ var resolveRoutePath = (path100) => {
15995
+ const segments = path100.split("/");
15736
15996
  if (segments.some((s) => s.startsWith(":") && !s.endsWith("?"))) {
15737
15997
  return void 0;
15738
15998
  }
15739
15999
  return segments.filter((s) => !s.startsWith(":")).join("/") || void 0;
15740
16000
  };
15741
- var isSkipped = (path99) => path99.includes("*") || /^\d+$/.test(path99);
16001
+ var isSkipped = (path100) => path100.includes("*") || /^\d+$/.test(path100);
15742
16002
  var resolveRoutes = (routes, parentPath = "") => routes.flatMap((route) => {
15743
16003
  if (route.path && isSkipped(route.path)) return [];
15744
16004
  const routePath = route.path ? resolveRoutePath(route.path) : void 0;
@@ -15788,12 +16048,12 @@ var prerender = async ({
15788
16048
  serverConfigFilename,
15789
16049
  writeRedirects = true
15790
16050
  }) => {
15791
- const distDir = path47.join(dir, "dist", basePath);
16051
+ const distDir = path48.join(dir, "dist", basePath);
15792
16052
  const serverConfigPath = pathToFileURL(
15793
- path47.join(distDir, "server", serverConfigFilename)
16053
+ path48.join(distDir, "server", serverConfigFilename)
15794
16054
  ).href;
15795
16055
  const entryServerPath = pathToFileURL(
15796
- path47.join(distDir, "server/entry.server.js")
16056
+ path48.join(distDir, "server/entry.server.js")
15797
16057
  ).href;
15798
16058
  const rawConfig = await import(serverConfigPath).then(
15799
16059
  (m) => m.default
@@ -15878,7 +16138,7 @@ var prerender = async ({
15878
16138
  })
15879
16139
  );
15880
16140
  const pagefindWriteResult = await pagefindIndex?.writeFiles({
15881
- outputPath: path47.join(distDir, "pagefind")
16141
+ outputPath: path48.join(distDir, "pagefind")
15882
16142
  });
15883
16143
  const seconds = ((performance.now() - start) / 1e3).toFixed(1);
15884
16144
  const message = `\u2713 finished prerendering ${paths.length} routes in ${seconds} seconds using ${maxThreads} workers`;
@@ -15910,7 +16170,7 @@ var prerender = async ({
15910
16170
  const { generateLlmsTxtFiles: generateLlmsTxtFiles2 } = await Promise.resolve().then(() => (init_llms(), llms_exports));
15911
16171
  const docsConfig = DocsConfigSchema2.parse(config2.docs);
15912
16172
  const llmsConfig = docsConfig.llms ?? {};
15913
- const markdownInfoPath = path47.join(
16173
+ const markdownInfoPath = path48.join(
15914
16174
  dir,
15915
16175
  "node_modules/.apitogo/markdown-info.json"
15916
16176
  );
@@ -15932,10 +16192,7 @@ var prerender = async ({
15932
16192
  });
15933
16193
  }
15934
16194
  if (!docsConfig.publishMarkdown) {
15935
- await Promise.all(
15936
- markdownFileInfos.map((info) => rm6(info.filePath).catch(() => {
15937
- }))
15938
- );
16195
+ await removeExportedMarkdown(distDir, markdownFileInfos);
15939
16196
  }
15940
16197
  }
15941
16198
  return { workerResults, rewrites };
@@ -16001,7 +16258,7 @@ async function runBuild(options) {
16001
16258
  html,
16002
16259
  basePath: config2.basePath
16003
16260
  });
16004
- await rm7(path48.join(clientOutDir, "index.html"), { force: true });
16261
+ await rm7(path49.join(clientOutDir, "index.html"), { force: true });
16005
16262
  } else {
16006
16263
  await runPrerender({
16007
16264
  dir,
@@ -16025,7 +16282,7 @@ var runPrerender = async (options) => {
16025
16282
  serverConfigFilename,
16026
16283
  writeRedirects: process.env.VERCEL === void 0
16027
16284
  });
16028
- const indexHtml = path48.join(clientOutDir, "index.html");
16285
+ const indexHtml = path49.join(clientOutDir, "index.html");
16029
16286
  if (!workerResults.find((r) => r.outputPath === indexHtml)) {
16030
16287
  await writeFile13(indexHtml, html, "utf-8");
16031
16288
  }
@@ -16035,15 +16292,15 @@ var runPrerender = async (options) => {
16035
16292
  for (const statusPage of statusPages) {
16036
16293
  await rename5(
16037
16294
  statusPage,
16038
- path48.join(dir, DIST_DIR, path48.basename(statusPage))
16295
+ path49.join(dir, DIST_DIR, path49.basename(statusPage))
16039
16296
  );
16040
16297
  }
16041
16298
  await rm7(serverOutDir, { recursive: true, force: true });
16042
16299
  if (process.env.VERCEL) {
16043
- await mkdir11(path48.join(dir, ".vercel/output/static"), { recursive: true });
16300
+ await mkdir11(path49.join(dir, ".vercel/output/static"), { recursive: true });
16044
16301
  await rename5(
16045
- path48.join(dir, DIST_DIR),
16046
- path48.join(dir, ".vercel/output/static")
16302
+ path49.join(dir, DIST_DIR),
16303
+ path49.join(dir, ".vercel/output/static")
16047
16304
  );
16048
16305
  }
16049
16306
  await writeOutput(dir, {
@@ -16053,7 +16310,7 @@ var runPrerender = async (options) => {
16053
16310
  });
16054
16311
  if (ZuploEnv.isZuplo && issuer) {
16055
16312
  await writeFile13(
16056
- path48.join(dir, DIST_DIR, ".output/zuplo.json"),
16313
+ path49.join(dir, DIST_DIR, ".output/zuplo.json"),
16057
16314
  JSON.stringify({ issuer }, null, 2),
16058
16315
  "utf-8"
16059
16316
  );
@@ -16065,10 +16322,10 @@ var runPrerender = async (options) => {
16065
16322
  };
16066
16323
  var bundleSSREntry = async (options) => {
16067
16324
  const { dir, adapter, serverOutDir, serverConfigFilename, html, basePath } = options;
16068
- const tempEntryPath = path48.join(dir, "__ssr-entry.ts");
16325
+ const tempEntryPath = path49.join(dir, "__ssr-entry.ts");
16069
16326
  const packageRoot = getZudokuRootDir();
16070
16327
  const templateContent = await readFile13(
16071
- path48.join(packageRoot, "src/vite/ssr-templates", `${adapter}.ts`),
16328
+ path49.join(packageRoot, "src/vite/ssr-templates", `${adapter}.ts`),
16072
16329
  "utf-8"
16073
16330
  );
16074
16331
  const entryContent = templateContent.replace('"__TEMPLATE__"', JSON.stringify(html)).replace('"__CONFIG_FILE__"', JSON.stringify(`./${serverConfigFilename}`)).replace(
@@ -16083,9 +16340,9 @@ var bundleSSREntry = async (options) => {
16083
16340
  platform: adapter === "node" ? "node" : "neutral",
16084
16341
  target: "es2022",
16085
16342
  format: "esm",
16086
- outfile: path48.join(serverOutDir, "entry.js"),
16343
+ outfile: path49.join(serverOutDir, "entry.js"),
16087
16344
  external: ["./entry.server.js", `./${serverConfigFilename}`],
16088
- nodePaths: [path48.join(packageRoot, "node_modules")],
16345
+ nodePaths: [path49.join(packageRoot, "node_modules")],
16089
16346
  banner: { js: "// Bundled SSR entry" }
16090
16347
  });
16091
16348
  } finally {
@@ -16099,7 +16356,7 @@ init_package_json();
16099
16356
  // src/cli/init/v2-packages.ts
16100
16357
  import { existsSync as existsSync6 } from "node:fs";
16101
16358
  import { readFile as readFile14, writeFile as writeFile14 } from "node:fs/promises";
16102
- import path49 from "node:path";
16359
+ import path50 from "node:path";
16103
16360
  var CORE_PACKAGES = [
16104
16361
  "@lukoweb/apitogo",
16105
16362
  "react",
@@ -16116,7 +16373,7 @@ function packageNameFrom(name) {
16116
16373
  return slug || "portal";
16117
16374
  }
16118
16375
  async function ensurePackageJson(dir, name) {
16119
- const file = path49.join(dir, "package.json");
16376
+ const file = path50.join(dir, "package.json");
16120
16377
  if (existsSync6(file)) return false;
16121
16378
  await writeFile14(
16122
16379
  file,
@@ -16135,11 +16392,11 @@ async function ensurePackageJson(dir, name) {
16135
16392
  return true;
16136
16393
  }
16137
16394
  function isPackageInstalled(startDir, name) {
16138
- let dir = path49.resolve(startDir);
16395
+ let dir = path50.resolve(startDir);
16139
16396
  for (; ; ) {
16140
- if (existsSync6(path49.join(dir, "node_modules", name, "package.json")))
16397
+ if (existsSync6(path50.join(dir, "node_modules", name, "package.json")))
16141
16398
  return true;
16142
- const parent = path49.dirname(dir);
16399
+ const parent = path50.dirname(dir);
16143
16400
  if (parent === dir) return false;
16144
16401
  dir = parent;
16145
16402
  }
@@ -16196,7 +16453,7 @@ function portalBuildStep(context, options = {}, build2 = runBuild) {
16196
16453
  return {
16197
16454
  name: "build",
16198
16455
  hash: () => hash2,
16199
- stillValid: () => access4(path50.join(buildDir, "index.html")).then(
16456
+ stillValid: () => access4(path51.join(buildDir, "index.html")).then(
16200
16457
  () => true,
16201
16458
  () => false
16202
16459
  ),
@@ -16213,11 +16470,11 @@ function portalBuildStep(context, options = {}, build2 = runBuild) {
16213
16470
  else process.env.NODE_ENV = originalNodeEnv;
16214
16471
  }
16215
16472
  await rm8(buildDir, { recursive: true, force: true });
16216
- await mkdir12(path50.dirname(buildDir), { recursive: true });
16217
- await rename6(path50.join(root, "dist"), buildDir);
16473
+ await mkdir12(path51.dirname(buildDir), { recursive: true });
16474
+ await rename6(path51.join(root, "dist"), buildDir);
16218
16475
  if (prepared.publishedManifest) {
16219
16476
  await writeFile15(
16220
- path50.join(buildDir, MANIFEST_FILE),
16477
+ path51.join(buildDir, MANIFEST_FILE),
16221
16478
  `${JSON.stringify(prepared.publishedManifest, null, 2)}
16222
16479
  `
16223
16480
  );
@@ -16248,15 +16505,15 @@ async function buildV2(options) {
16248
16505
  // src/cli/check/gateway-online.ts
16249
16506
  import { readFile as readFile16 } from "node:fs/promises";
16250
16507
  var SPEC_SEND_LIMIT = 5 * 1024 * 1024 + 64 * 1024;
16251
- var issue2 = (code, path99, message, hint) => ({ code, path: path99, message, hint });
16508
+ var issue2 = (code, path100, message, hint) => ({ code, path: path100, message, hint });
16252
16509
  function capabilityIssues(gateway, answer) {
16253
16510
  const can = answer.capabilities;
16254
16511
  const plan2 = `your ${answer.tier} plan`;
16255
16512
  const found = [];
16256
- const unsupported = (path99, what, hint) => found.push(
16513
+ const unsupported = (path100, what, hint) => found.push(
16257
16514
  issue2(
16258
16515
  "GATEWAY_CAPABILITY_UNSUPPORTED",
16259
- path99,
16516
+ path100,
16260
16517
  `${what} is not available on ${plan2}.`,
16261
16518
  hint
16262
16519
  )
@@ -16467,9 +16724,9 @@ function toPlatformPlans(plans, options = {}) {
16467
16724
  }
16468
16725
 
16469
16726
  // src/cli/check/services-online.ts
16470
- var issue3 = (code, path99, message, hint) => ({
16727
+ var issue3 = (code, path100, message, hint) => ({
16471
16728
  code,
16472
- path: path99,
16729
+ path: path100,
16473
16730
  message,
16474
16731
  hint
16475
16732
  });
@@ -16581,7 +16838,7 @@ var withPlanName = (message, sku) => !sku || message.toLowerCase().startsWith(`p
16581
16838
  async function isConfigV2(dir) {
16582
16839
  try {
16583
16840
  return /"version"\s*:\s*2\b/.test(
16584
- await readFile18(path51.join(dir, APITOGO_CONFIG_FILENAME), "utf8")
16841
+ await readFile18(path52.join(dir, APITOGO_CONFIG_FILENAME), "utf8")
16585
16842
  );
16586
16843
  } catch {
16587
16844
  return false;
@@ -16590,7 +16847,7 @@ async function isConfigV2(dir) {
16590
16847
  async function isLegacyConfig(dir) {
16591
16848
  let text2;
16592
16849
  try {
16593
- text2 = await readFile18(path51.join(dir, APITOGO_CONFIG_FILENAME), "utf8");
16850
+ text2 = await readFile18(path52.join(dir, APITOGO_CONFIG_FILENAME), "utf8");
16594
16851
  } catch {
16595
16852
  return false;
16596
16853
  }
@@ -16738,7 +16995,7 @@ async function validatePortalBuild(dir) {
16738
16995
  }
16739
16996
  }
16740
16997
  async function runCheckV2(dir, args = {}, deps = {}) {
16741
- const configPath = path51.join(dir, APITOGO_CONFIG_FILENAME);
16998
+ const configPath = path52.join(dir, APITOGO_CONFIG_FILENAME);
16742
16999
  let report;
16743
17000
  try {
16744
17001
  const { config: config2, resolved } = loadConfigV2(dir);
@@ -16848,7 +17105,7 @@ async function checkV2(dir, args, deps = platformDependencies(args)) {
16848
17105
  init_package_json();
16849
17106
 
16850
17107
  // src/cli/init/v2-handler.ts
16851
- import path52 from "node:path";
17108
+ import path53 from "node:path";
16852
17109
  import { createInterface } from "node:readline/promises";
16853
17110
  function parseDocsFlag(value) {
16854
17111
  if (value === void 0) return void 0;
@@ -16870,15 +17127,15 @@ async function ask(question) {
16870
17127
  }
16871
17128
  function describePlan(plan2) {
16872
17129
  return {
16873
- willCreate: plan2.entries.filter((entry) => !plan2.keep.includes(entry.path)).map((entry) => path52.relative(plan2.dir, entry.path)),
16874
- willKeep: plan2.keep.map((target) => path52.relative(plan2.dir, target)),
17130
+ willCreate: plan2.entries.filter((entry) => !plan2.keep.includes(entry.path)).map((entry) => path53.relative(plan2.dir, entry.path)),
17131
+ willKeep: plan2.keep.map((target) => path53.relative(plan2.dir, target)),
16875
17132
  replacesConfig: plan2.replacesConfig
16876
17133
  };
16877
17134
  }
16878
17135
  async function initV2(argv) {
16879
17136
  const json = argv.json === true;
16880
17137
  try {
16881
- const dir = path52.resolve(process.cwd(), argv.dir ?? ".");
17138
+ const dir = path53.resolve(process.cwd(), argv.dir ?? ".");
16882
17139
  const plan2 = await planInit({
16883
17140
  dir,
16884
17141
  name: argv.name,
@@ -16919,8 +17176,8 @@ async function initV2(argv) {
16919
17176
  status: "created",
16920
17177
  dir,
16921
17178
  ...describePlan(plan2),
16922
- created: result.created.map((p) => path52.relative(dir, p)),
16923
- kept: result.kept.map((p) => path52.relative(dir, p))
17179
+ created: result.created.map((p) => path53.relative(dir, p)),
17180
+ kept: result.kept.map((p) => path53.relative(dir, p))
16924
17181
  },
16925
17182
  null,
16926
17183
  2
@@ -16929,7 +17186,7 @@ async function initV2(argv) {
16929
17186
  return;
16930
17187
  }
16931
17188
  printResultToConsole(
16932
- `Created ${path52.basename(plan2.configPath)} (config version 2) in ${dir}.`
17189
+ `Created ${path53.basename(plan2.configPath)} (config version 2) in ${dir}.`
16933
17190
  );
16934
17191
  printHintToConsole(
16935
17192
  "Next: add a service with `apitogo add <portal|gateway|identity|plans>`, or run `apitogo create` for a guided setup."
@@ -16956,7 +17213,7 @@ async function initV2(argv) {
16956
17213
 
16957
17214
  // src/cli/services/packages.ts
16958
17215
  import { existsSync as existsSync7 } from "node:fs";
16959
- import path53 from "node:path";
17216
+ import path54 from "node:path";
16960
17217
 
16961
17218
  // src/cli/common/spawn-command.ts
16962
17219
  import {
@@ -16989,18 +17246,18 @@ function packagesForConfig(config2) {
16989
17246
  ];
16990
17247
  }
16991
17248
  function isInstalled(dir, name) {
16992
- for (let current = path53.resolve(dir); ; current = path53.dirname(current)) {
16993
- if (existsSync7(path53.join(current, "node_modules", name, "package.json")))
17249
+ for (let current = path54.resolve(dir); ; current = path54.dirname(current)) {
17250
+ if (existsSync7(path54.join(current, "node_modules", name, "package.json")))
16994
17251
  return true;
16995
- if (path53.dirname(current) === current) return false;
17252
+ if (path54.dirname(current) === current) return false;
16996
17253
  }
16997
17254
  }
16998
17255
  function missingPackages(dir, names) {
16999
17256
  return names.filter((name) => !isInstalled(dir, name));
17000
17257
  }
17001
17258
  function detectPackageManager(dir) {
17002
- if (existsSync7(path53.join(dir, "pnpm-lock.yaml"))) return "pnpm";
17003
- if (existsSync7(path53.join(dir, "yarn.lock"))) return "yarn";
17259
+ if (existsSync7(path54.join(dir, "pnpm-lock.yaml"))) return "pnpm";
17260
+ if (existsSync7(path54.join(dir, "yarn.lock"))) return "yarn";
17004
17261
  return "npm";
17005
17262
  }
17006
17263
  function installSpecsCommand(manager, specs) {
@@ -17058,7 +17315,7 @@ async function installMissingPackages(dir, config2, version, options) {
17058
17315
  reason: "--no-install was given",
17059
17316
  command: shown
17060
17317
  };
17061
- await ensurePackageJson(dir, config2.project?.name ?? path53.basename(dir));
17318
+ await ensurePackageJson(dir, config2.project?.name ?? path54.basename(dir));
17062
17319
  await (options.installer ?? runInstaller)(dir, command2);
17063
17320
  return { status: "installed", packages: missing, command: shown };
17064
17321
  }
@@ -17121,7 +17378,7 @@ var list = (value) => {
17121
17378
  return items.length > 0 ? items : void 0;
17122
17379
  };
17123
17380
  function addOptionsFromArguments(service, argv) {
17124
- const dir = path54.resolve(process.cwd(), argv.dir ?? ".");
17381
+ const dir = path55.resolve(process.cwd(), argv.dir ?? ".");
17125
17382
  const issuers = list(argv.issuer);
17126
17383
  return {
17127
17384
  dir,
@@ -17175,8 +17432,8 @@ function describe(edit, dir) {
17175
17432
  return {
17176
17433
  service: edit.service,
17177
17434
  action: edit.action,
17178
- willCreate: edit.files.map((file) => path54.relative(dir, file.path)),
17179
- keep: edit.keep.map((file) => path54.relative(dir, file)),
17435
+ willCreate: edit.files.map((file) => path55.relative(dir, file.path)),
17436
+ keep: edit.keep.map((file) => path55.relative(dir, file)),
17180
17437
  notes: edit.notes
17181
17438
  };
17182
17439
  }
@@ -17190,7 +17447,7 @@ async function run(action, argv, plan2, deps = {}) {
17190
17447
  `Choose one of ${SERVICES.join(", ")}.`
17191
17448
  );
17192
17449
  }
17193
- const dir = path54.resolve(process.cwd(), argv.dir ?? ".");
17450
+ const dir = path55.resolve(process.cwd(), argv.dir ?? ".");
17194
17451
  if (!await isConfigV2(dir)) {
17195
17452
  throw new ServiceEditError(
17196
17453
  `${action} works with a config version 2 project, and there is none in ${dir}.`,
@@ -17258,7 +17515,7 @@ async function run(action, argv, plan2, deps = {}) {
17258
17515
  {
17259
17516
  status: action === "add" ? "added" : "removed",
17260
17517
  ...describe(edit, dir),
17261
- created: created.map((file) => path54.relative(dir, file)),
17518
+ created: created.map((file) => path55.relative(dir, file)),
17262
17519
  ...install ? { packages: install } : {}
17263
17520
  },
17264
17521
  null,
@@ -17268,7 +17525,7 @@ async function run(action, argv, plan2, deps = {}) {
17268
17525
  return;
17269
17526
  }
17270
17527
  printResultToConsole(
17271
- `${action === "add" ? "Added" : "Removed"} ${edit.service} in ${path54.basename(edit.configPath)}.`
17528
+ `${action === "add" ? "Added" : "Removed"} ${edit.service} in ${path55.basename(edit.configPath)}.`
17272
17529
  );
17273
17530
  } catch (error) {
17274
17531
  const message = error instanceof Error ? error.message : String(error);
@@ -17329,7 +17586,7 @@ var removeService = (argv) => run(
17329
17586
  "remove",
17330
17587
  argv,
17331
17588
  (service) => planRemoveService(service, {
17332
- dir: path54.resolve(process.cwd(), argv.dir ?? "."),
17589
+ dir: path55.resolve(process.cwd(), argv.dir ?? "."),
17333
17590
  deleteFiles: argv.deleteFiles === true
17334
17591
  })
17335
17592
  );
@@ -17481,16 +17738,16 @@ var add_default = {
17481
17738
  };
17482
17739
 
17483
17740
  // src/cli/build/handler.ts
17484
- import path56 from "node:path";
17741
+ import path57 from "node:path";
17485
17742
  init_logger();
17486
17743
  init_package_json();
17487
17744
 
17488
17745
  // src/cli/preview/handler.ts
17489
- import path55 from "node:path";
17746
+ import path56 from "node:path";
17490
17747
  import { preview as vitePreview } from "vite";
17491
17748
  var DEFAULT_PREVIEW_PORT = 4e3;
17492
17749
  async function preview(argv) {
17493
- const dir = path55.resolve(process.cwd(), argv.dir);
17750
+ const dir = path56.resolve(process.cwd(), argv.dir);
17494
17751
  const viteConfig = await getViteConfig(dir, {
17495
17752
  command: "serve",
17496
17753
  mode: "production",
@@ -17533,7 +17790,7 @@ async function build(argv) {
17533
17790
  printDiagnosticsToConsole("");
17534
17791
  printDiagnosticsToConsole("");
17535
17792
  }
17536
- const dir = path56.resolve(process.cwd(), argv.dir);
17793
+ const dir = path57.resolve(process.cwd(), argv.dir);
17537
17794
  try {
17538
17795
  if (await isConfigV2(dir)) {
17539
17796
  const result = await buildV2({ dir, jsonOnly });
@@ -17598,10 +17855,10 @@ var build_default = {
17598
17855
  };
17599
17856
 
17600
17857
  // src/cli/check/handler.ts
17601
- import path62 from "node:path";
17858
+ import path63 from "node:path";
17602
17859
 
17603
17860
  // src/cli/platform/tools/check-publish-readiness.ts
17604
- import path61 from "node:path";
17861
+ import path62 from "node:path";
17605
17862
 
17606
17863
  // src/cli/platform/helpers/dev-portal-backend.ts
17607
17864
  function deriveProvisioningState(backend) {
@@ -17686,7 +17943,7 @@ function formatDevPortalBackendResponse(projectId, backend) {
17686
17943
  init_gateway_limits();
17687
17944
  import { existsSync as existsSync8 } from "node:fs";
17688
17945
  import { access as access5, mkdir as mkdir13, readFile as readFile19, writeFile as writeFile16 } from "node:fs/promises";
17689
- import path57 from "node:path";
17946
+ import path58 from "node:path";
17690
17947
 
17691
17948
  // src/cli/make-config/header-pricing-nav.ts
17692
17949
  var HEADER_PRICING_PATH = "/pricing";
@@ -17808,7 +18065,7 @@ function isPlanFree(plan2) {
17808
18065
  return (plan2.priceAmount ?? 0) <= 0;
17809
18066
  }
17810
18067
  function manifestFilePath(siteDirectory) {
17811
- return path57.join(path57.resolve(siteDirectory), APITOGO_CONFIG_FILENAME2);
18068
+ return path58.join(path58.resolve(siteDirectory), APITOGO_CONFIG_FILENAME2);
17812
18069
  }
17813
18070
  var STORED_MANIFEST_KEYS = [
17814
18071
  "authentication",
@@ -18232,23 +18489,23 @@ function resolveBackendSourceDirectory(siteDirectory, sourceDirectory) {
18232
18489
  if (!trimmed) {
18233
18490
  return void 0;
18234
18491
  }
18235
- return path57.resolve(siteDirectory, trimmed);
18492
+ return path58.resolve(siteDirectory, trimmed);
18236
18493
  }
18237
18494
  function resolveBackendOpenApiPath(openApiPath, options) {
18238
18495
  const trimmed = openApiPath?.trim();
18239
18496
  if (!trimmed) {
18240
18497
  return void 0;
18241
18498
  }
18242
- if (path57.isAbsolute(trimmed)) {
18499
+ if (path58.isAbsolute(trimmed)) {
18243
18500
  return trimmed;
18244
18501
  }
18245
- const siteResolved = path57.resolve(options.siteDirectory, trimmed);
18502
+ const siteResolved = path58.resolve(options.siteDirectory, trimmed);
18246
18503
  if (existsSync8(siteResolved)) {
18247
18504
  return siteResolved;
18248
18505
  }
18249
18506
  const backendDir = options.backendSourceDirectory?.trim();
18250
18507
  if (backendDir) {
18251
- const backendResolved = path57.resolve(backendDir, trimmed);
18508
+ const backendResolved = path58.resolve(backendDir, trimmed);
18252
18509
  if (existsSync8(backendResolved)) {
18253
18510
  return backendResolved;
18254
18511
  }
@@ -18273,7 +18530,7 @@ async function readManifest2(siteDirectory) {
18273
18530
  }
18274
18531
  async function writeManifest2(siteDirectory, manifest) {
18275
18532
  const filePath = manifestFilePath(siteDirectory);
18276
- await mkdir13(path57.dirname(filePath), { recursive: true });
18533
+ await mkdir13(path58.dirname(filePath), { recursive: true });
18277
18534
  let config2 = {};
18278
18535
  if (await pathExists(filePath)) {
18279
18536
  const raw = await readFile19(filePath, "utf8");
@@ -18299,7 +18556,7 @@ import {
18299
18556
  unlink,
18300
18557
  writeFile as writeFile17
18301
18558
  } from "node:fs/promises";
18302
- import path58 from "node:path";
18559
+ import path59 from "node:path";
18303
18560
  var ENV_FILENAME = ".env";
18304
18561
  var COLLAPSED_UPPERCASE_ALIASES2 = {
18305
18562
  SECRETKEY: "secretKey",
@@ -18418,12 +18675,12 @@ async function readEnvFile(filePath) {
18418
18675
  }
18419
18676
  }
18420
18677
  async function loadSiteEnv(siteDirectory) {
18421
- const resolvedDirectory = path58.resolve(siteDirectory);
18678
+ const resolvedDirectory = path59.resolve(siteDirectory);
18422
18679
  const merged = { ...process.env };
18423
18680
  for (const fileName of [ENV_FILENAME, ".env.local"]) {
18424
18681
  Object.assign(
18425
18682
  merged,
18426
- await readEnvFile(path58.join(resolvedDirectory, fileName))
18683
+ await readEnvFile(path59.join(resolvedDirectory, fileName))
18427
18684
  );
18428
18685
  }
18429
18686
  return merged;
@@ -18460,7 +18717,7 @@ function buildAuthSecretsJson(secrets2, options) {
18460
18717
  return payload;
18461
18718
  }
18462
18719
  function envFilePath(siteDirectory) {
18463
- return path58.join(path58.resolve(siteDirectory), ENV_FILENAME);
18720
+ return path59.join(path59.resolve(siteDirectory), ENV_FILENAME);
18464
18721
  }
18465
18722
  function getSecretsStatus(siteDirectory, manifest, secrets2, envExists) {
18466
18723
  const providerSecretStatus = {};
@@ -18496,7 +18753,7 @@ function validateSecretsForPublish(manifest, secrets2, emailOptions) {
18496
18753
  }
18497
18754
  async function siteEnvFileHasEntries(siteDirectory) {
18498
18755
  for (const fileName of [ENV_FILENAME, ".env.local"]) {
18499
- const loaded = await readEnvFile(path58.join(siteDirectory, fileName));
18756
+ const loaded = await readEnvFile(path59.join(siteDirectory, fileName));
18500
18757
  if (Object.keys(loaded).length > 0) {
18501
18758
  return true;
18502
18759
  }
@@ -18504,7 +18761,7 @@ async function siteEnvFileHasEntries(siteDirectory) {
18504
18761
  return false;
18505
18762
  }
18506
18763
  async function resolvePublishConfig(siteDirectory, emailOptions) {
18507
- const resolvedDirectory = path58.resolve(siteDirectory);
18764
+ const resolvedDirectory = path59.resolve(siteDirectory);
18508
18765
  const manifestRaw = await readManifest2(resolvedDirectory);
18509
18766
  if (!manifestRaw) {
18510
18767
  throw new Error("apitogo.config.json not found in sourceDirectory.");
@@ -18536,11 +18793,11 @@ var SECRETS_COLLECTION_INSTRUCTION = "For go-live, plans must already be in apit
18536
18793
  init_gateway_limits();
18537
18794
  import { readFile as readFile21 } from "node:fs/promises";
18538
18795
  import { createRequire as createRequire2 } from "node:module";
18539
- import path59 from "node:path";
18796
+ import path60 from "node:path";
18540
18797
  var cachedFallbackTs;
18541
18798
  async function loadTypeScriptForSite(sourceDirectory) {
18542
18799
  const require2 = createRequire2(
18543
- path59.join(path59.resolve(sourceDirectory), "package.json")
18800
+ path60.join(path60.resolve(sourceDirectory), "package.json")
18544
18801
  );
18545
18802
  try {
18546
18803
  return require2("typescript");
@@ -18587,7 +18844,7 @@ async function referencedFileExists(sourceDirectory, reference) {
18587
18844
  return true;
18588
18845
  }
18589
18846
  return pathExists(
18590
- path59.resolve(sourceDirectory, reference.replace(/^\.\//, ""))
18847
+ path60.resolve(sourceDirectory, reference.replace(/^\.\//, ""))
18591
18848
  );
18592
18849
  }
18593
18850
  async function validateSiteConfigStructure(config2, sourceDirectory) {
@@ -18702,11 +18959,11 @@ var DEFAULT_TSX_CANDIDATES = [
18702
18959
  "landing/LandingPage.tsx"
18703
18960
  ];
18704
18961
  async function validateDevPortalSiteConfig(sourceDirectory, options) {
18705
- const resolvedDir = path59.resolve(sourceDirectory);
18962
+ const resolvedDir = path60.resolve(sourceDirectory);
18706
18963
  const issues = [];
18707
18964
  const checkedFiles = [];
18708
18965
  const skippedChecks = [];
18709
- const jsonPath = path59.join(resolvedDir, APITOGO_CONFIG_FILENAME2);
18966
+ const jsonPath = path60.join(resolvedDir, APITOGO_CONFIG_FILENAME2);
18710
18967
  if (await pathExists(jsonPath)) {
18711
18968
  checkedFiles.push(APITOGO_CONFIG_FILENAME2);
18712
18969
  let parsed;
@@ -18738,7 +18995,7 @@ async function validateDevPortalSiteConfig(sourceDirectory, options) {
18738
18995
  );
18739
18996
  const existingTsxFiles = [];
18740
18997
  for (const relativeFile of tsxFiles) {
18741
- if (await pathExists(path59.join(resolvedDir, relativeFile))) {
18998
+ if (await pathExists(path60.join(resolvedDir, relativeFile))) {
18742
18999
  existingTsxFiles.push(relativeFile);
18743
19000
  }
18744
19001
  }
@@ -18752,7 +19009,7 @@ async function validateDevPortalSiteConfig(sourceDirectory, options) {
18752
19009
  for (const relativeFile of existingTsxFiles) {
18753
19010
  checkedFiles.push(relativeFile);
18754
19011
  const content = await readFile21(
18755
- path59.join(resolvedDir, relativeFile),
19012
+ path60.join(resolvedDir, relativeFile),
18756
19013
  "utf8"
18757
19014
  );
18758
19015
  issues.push(
@@ -18774,7 +19031,7 @@ async function validateDevPortalSiteConfig(sourceDirectory, options) {
18774
19031
 
18775
19032
  // src/cli/platform/helpers/npm-build.ts
18776
19033
  import { access as access7, readFile as readFile22 } from "node:fs/promises";
18777
- import path60 from "node:path";
19034
+ import path61 from "node:path";
18778
19035
  var BUILD_LOG_TAIL_BYTES = 8192;
18779
19036
  var MIN_NODE_MAJOR = 20;
18780
19037
  var NpmBuildError = class extends Error {
@@ -18840,13 +19097,13 @@ function runCommand(command2, args, cwd, env) {
18840
19097
  }
18841
19098
  async function runNpmInstall(sourceDirectory, options) {
18842
19099
  assertNodeVersion(options?.nodeVersion);
18843
- const cwd = path60.resolve(sourceDirectory);
18844
- const packageJsonPath = path60.join(cwd, "package.json");
19100
+ const cwd = path61.resolve(sourceDirectory);
19101
+ const packageJsonPath = path61.join(cwd, "package.json");
18845
19102
  if (!await pathExists2(packageJsonPath)) {
18846
19103
  throw new Error(`package.json not found in sourceDirectory: ${cwd}`);
18847
19104
  }
18848
19105
  const startedAt = Date.now();
18849
- const lockPath = path60.join(cwd, "package-lock.json");
19106
+ const lockPath = path61.join(cwd, "package-lock.json");
18850
19107
  const installArgs = await pathExists2(lockPath) ? ["ci"] : ["install"];
18851
19108
  const install = await runCommand("npm", installArgs, cwd);
18852
19109
  const installLog = install.output;
@@ -18864,8 +19121,8 @@ async function runNpmInstall(sourceDirectory, options) {
18864
19121
  }
18865
19122
  async function runNpmBuild(sourceDirectory, options) {
18866
19123
  assertNodeVersion(options?.nodeVersion);
18867
- const cwd = path60.resolve(sourceDirectory);
18868
- const packageJsonPath = path60.join(cwd, "package.json");
19124
+ const cwd = path61.resolve(sourceDirectory);
19125
+ const packageJsonPath = path61.join(cwd, "package.json");
18869
19126
  if (!await pathExists2(packageJsonPath)) {
18870
19127
  throw new Error(`package.json not found in sourceDirectory: ${cwd}`);
18871
19128
  }
@@ -18882,8 +19139,8 @@ async function runNpmBuild(sourceDirectory, options) {
18882
19139
  build2.exitCode
18883
19140
  );
18884
19141
  }
18885
- const distPath = path60.join(cwd, "dist");
18886
- const indexPath = path60.join(distPath, "index.html");
19142
+ const distPath = path61.join(cwd, "dist");
19143
+ const indexPath = path61.join(distPath, "index.html");
18887
19144
  if (!await pathExists2(indexPath)) {
18888
19145
  throw new NpmBuildError(
18889
19146
  `Build completed but dist/index.html was not found at ${indexPath}`,
@@ -19267,7 +19524,7 @@ function validateManifestForPlatformPublish(manifest) {
19267
19524
 
19268
19525
  // src/cli/platform/tools/check-publish-readiness.ts
19269
19526
  async function checkPublishReadiness(client, _config, args) {
19270
- const sourceDirectory = path61.resolve(args.sourceDirectory);
19527
+ const sourceDirectory = path62.resolve(args.sourceDirectory);
19271
19528
  const manifest = await readManifest2(sourceDirectory) ?? createScaffoldManifest();
19272
19529
  const emailOptions = await resolveEmailOptionsForPublish(
19273
19530
  client,
@@ -19345,7 +19602,7 @@ async function checkPublishReadiness(client, _config, args) {
19345
19602
 
19346
19603
  // src/cli/check/handler.ts
19347
19604
  async function check(argv) {
19348
- const sourceDirectory = path62.resolve(process.cwd(), argv.dir);
19605
+ const sourceDirectory = path63.resolve(process.cwd(), argv.dir);
19349
19606
  if (await isConfigV2(sourceDirectory)) {
19350
19607
  await checkV2(sourceDirectory, argv);
19351
19608
  return;
@@ -19409,7 +19666,7 @@ var check_default = {
19409
19666
 
19410
19667
  // src/cli/make-config/handler.ts
19411
19668
  init_invariant();
19412
- import path64 from "node:path";
19669
+ import path65 from "node:path";
19413
19670
  import { stdin as input2 } from "node:process";
19414
19671
 
19415
19672
  // src/cli/make-config/interactive.ts
@@ -19419,7 +19676,7 @@ import { createInterface as createInterface3 } from "node:readline/promises";
19419
19676
  // src/cli/make-config/v2-edits.ts
19420
19677
  init_apitogo_config_core();
19421
19678
  import { access as access8, mkdir as mkdir15, readFile as readFile23, writeFile as writeFile18 } from "node:fs/promises";
19422
- import path63 from "node:path";
19679
+ import path64 from "node:path";
19423
19680
  import { glob as glob8 } from "glob";
19424
19681
  init_invariant();
19425
19682
  var ConfigEditError = class extends ApitogoError {
@@ -19432,10 +19689,10 @@ var exists4 = (target) => access8(target).then(
19432
19689
  () => true,
19433
19690
  () => false
19434
19691
  );
19435
- var toPosix2 = (value) => value.split(path63.sep).join("/");
19436
- var relativeToConfig = (project, target) => toPosix2(path63.relative(project.resolved.configDir, target)) || ".";
19692
+ var toPosix2 = (value) => value.split(path64.sep).join("/");
19693
+ var relativeToConfig = (project, target) => toPosix2(path64.relative(project.resolved.configDir, target)) || ".";
19437
19694
  async function openConfigProject(dir) {
19438
- const configPath = path63.join(dir, APITOGO_CONFIG_FILENAME);
19695
+ const configPath = path64.join(dir, APITOGO_CONFIG_FILENAME);
19439
19696
  if (!await exists4(configPath)) {
19440
19697
  throw new ConfigEditError(
19441
19698
  `There is no ${APITOGO_CONFIG_FILENAME} in ${dir}.`,
@@ -19500,7 +19757,7 @@ function setPortalName(project, name) {
19500
19757
  }
19501
19758
  async function addGatewaySpec(project, specPath) {
19502
19759
  const gateway = requireGateway(project);
19503
- const absolute = path63.resolve(project.dir, specPath);
19760
+ const absolute = path64.resolve(project.dir, specPath);
19504
19761
  if (!await exists4(absolute)) {
19505
19762
  throw new ConfigEditError(`The OpenAPI file ${specPath} does not exist.`);
19506
19763
  }
@@ -19544,7 +19801,7 @@ function parseBackendInput(input5) {
19544
19801
  }
19545
19802
  async function setGatewayOrigin(project, backend) {
19546
19803
  const gateway = requireGateway(project);
19547
- const source = path63.resolve(project.dir, backend.source);
19804
+ const source = path64.resolve(project.dir, backend.source);
19548
19805
  if (!await exists4(source)) {
19549
19806
  throw new ConfigEditError(
19550
19807
  `The API source folder ${backend.source} does not exist.`
@@ -19622,6 +19879,164 @@ export default function ${pageComponentName(slug)}() {
19622
19879
  }
19623
19880
  `;
19624
19881
  }
19882
+ var HEADER_PAGES_FOLDER = "header-pages";
19883
+ var RESERVED_HEADER_SLUGS = [
19884
+ "index",
19885
+ "api",
19886
+ "pricing",
19887
+ "account",
19888
+ "login",
19889
+ "signup"
19890
+ ];
19891
+ function buildTsxHeaderPage(slug, title, file, content) {
19892
+ const given = content?.trim() ? withoutLeadingH1(content) : "";
19893
+ const text2 = given ? `{${JSON.stringify(given)}}` : "Coming soon.";
19894
+ return `import { Callout, Heading, Typography } from "@lukoweb/apitogo/components";
19895
+
19896
+ /** The page's label in the header's top menu (portal.header.links in ${APITOGO_CONFIG_FILENAME}). */
19897
+ export const title = ${JSON.stringify(title)};
19898
+
19899
+ // This page is a React component: change it like any other one (add state, fetch data, use the portal's components).
19900
+ export default function ${pageComponentName(slug)}() {
19901
+ return (
19902
+ <section className="mx-auto max-w-screen-lg px-4 py-12 pt-(--padding-content-top)">
19903
+ <Typography>
19904
+ <Heading level={1}>{title}</Heading>
19905
+ <p>${text2}</p>
19906
+ {/* A note for you while you run the portal locally (\`apitogo run\`); the deployed portal does not show it. */}
19907
+ {import.meta.env.DEV && (
19908
+ <Callout type="info" title="Where this page lives">
19909
+ Edit <code>${file}</code>. The header's top menu links to it; the docs sidebar does not.
19910
+ </Callout>
19911
+ )}
19912
+ </Typography>
19913
+ </section>
19914
+ );
19915
+ }
19916
+ `;
19917
+ }
19918
+ var TSX_TITLE = /^export\s+const\s+title\s*(?::\s*string\s*)?=\s*.*$/m;
19919
+ async function writeHeaderPage(project, input5) {
19920
+ const portalConfig = requirePortal(project);
19921
+ const portal = project.resolved.portal;
19922
+ if (!portal?.docs.enabled || portal.docs.provider !== "apitogo") {
19923
+ throw new ConfigEditError(
19924
+ "This portal does not host pages.",
19925
+ 'Header pages are served with the docs pages: they need portal.docs.provider "apitogo" and portal.docs.enabled not false.'
19926
+ );
19927
+ }
19928
+ const slug = normalizeDocsSlug(input5.slug);
19929
+ if (!slug) throw new ConfigEditError("The page path must not be empty.");
19930
+ if (slug.includes("/")) {
19931
+ throw new ConfigEditError(
19932
+ `A header page has a plain name, not a path: "${input5.slug}".`,
19933
+ "Pass one word, for example --page-slug partners (the page is then at /partners)."
19934
+ );
19935
+ }
19936
+ if (RESERVED_HEADER_SLUGS.includes(slug)) {
19937
+ throw new ConfigEditError(
19938
+ `The portal uses /${slug} itself, so a header page cannot have it.`,
19939
+ "Pick another name."
19940
+ );
19941
+ }
19942
+ const relative = (file2) => toPosix2(path64.relative(project.dir, file2));
19943
+ const docsBase = path64.join(portal.dir, "pages", slug);
19944
+ for (const file2 of [`${docsBase}.md`, `${docsBase}.mdx`, `${docsBase}.tsx`]) {
19945
+ if (await exists4(file2)) {
19946
+ throw new ConfigEditError(
19947
+ `The docs page ${relative(file2)} already has the address /${slug}.`,
19948
+ "Pick another name for the header page."
19949
+ );
19950
+ }
19951
+ }
19952
+ const base = path64.join(portal.dir, HEADER_PAGES_FOLDER, slug);
19953
+ const existing = (await Promise.all(
19954
+ [`${base}.tsx`, `${base}.mdx`, `${base}.md`].map(
19955
+ async (file2) => await exists4(file2) ? file2 : null
19956
+ )
19957
+ )).find((file2) => file2 !== null);
19958
+ let file;
19959
+ let title;
19960
+ let action;
19961
+ if (input5.update) {
19962
+ if (!existing) {
19963
+ throw new ConfigEditError(
19964
+ `There is no header page at ${relative(`${base}.${input5.format ?? "tsx"}`)}.`,
19965
+ "Leave out --page-update to add it."
19966
+ );
19967
+ }
19968
+ const source = await readFile23(existing, "utf8");
19969
+ const react2 = existing.endsWith(".tsx");
19970
+ title = input5.title?.trim() || (react2 ? tsxPageLabel(source, existing) : titleOf(source)) || slug;
19971
+ file = existing;
19972
+ action = "updated";
19973
+ if (react2) {
19974
+ await writeFile18(
19975
+ file,
19976
+ input5.content !== void 0 ? buildTsxHeaderPage(slug, title, relative(file), input5.content) : TSX_TITLE.test(source) ? source.replace(
19977
+ TSX_TITLE,
19978
+ `export const title = ${JSON.stringify(title)};`
19979
+ ) : source,
19980
+ "utf8"
19981
+ );
19982
+ } else {
19983
+ const body = input5.content ?? source.replace(/^---\r?\n[\s\S]*?\r?\n---\r?\n?/, "").trim();
19984
+ await writeFile18(file, buildDocsPage(title, body), "utf8");
19985
+ }
19986
+ } else {
19987
+ if (existing) {
19988
+ throw new ConfigEditError(
19989
+ `A header page already exists at ${relative(existing)}.`,
19990
+ "Pass --page-update to change it."
19991
+ );
19992
+ }
19993
+ const given = input5.title?.trim();
19994
+ if (!given) {
19995
+ throw new ConfigEditError("A new page needs a title (--page-title).");
19996
+ }
19997
+ title = given;
19998
+ file = `${base}.${input5.format ?? "tsx"}`;
19999
+ action = "added";
20000
+ await mkdir15(path64.dirname(file), { recursive: true });
20001
+ await writeFile18(
20002
+ file,
20003
+ file.endsWith(".tsx") ? buildTsxHeaderPage(slug, title, relative(file), input5.content) : buildDocsPage(
20004
+ title,
20005
+ input5.content?.trim() ? input5.content : `{/* Write this page in Markdown. It is linked from the header's top menu (portal.header.links in ${APITOGO_CONFIG_FILENAME}), not from the docs sidebar. */}
20006
+
20007
+ Coming soon.`
20008
+ ),
20009
+ "utf8"
20010
+ );
20011
+ }
20012
+ const page = relativeToConfig(project, file);
20013
+ const target = path64.resolve(file);
20014
+ const links = portalConfig.header?.links ?? [];
20015
+ const known = links.some(
20016
+ (link) => "page" in link && path64.resolve(project.resolved.configDir, link.page) === target
20017
+ );
20018
+ const nextLinks = known ? links.map(
20019
+ (link) => "page" in link && path64.resolve(project.resolved.configDir, link.page) === target ? { label: title, page: link.page } : link
20020
+ ) : [...links, { label: title, page }];
20021
+ return {
20022
+ project: withConfig(project, {
20023
+ ...project.config,
20024
+ portal: {
20025
+ ...portalConfig,
20026
+ header: { ...portalConfig.header, links: nextLinks }
20027
+ }
20028
+ }),
20029
+ page: {
20030
+ action,
20031
+ slug,
20032
+ route: `/${slug}`,
20033
+ file: relative(file),
20034
+ format: path64.extname(file).slice(1),
20035
+ inPortal: true,
20036
+ header: true
20037
+ }
20038
+ };
20039
+ }
19625
20040
  async function writeDocsPage(project, input5) {
19626
20041
  requirePortal(project);
19627
20042
  if (/^[A-Za-z]:[\\/]/.test(input5.slug.trim())) {
@@ -19645,13 +20060,13 @@ async function writeDocsPage(project, input5) {
19645
20060
  "For example --page-order 1 puts the page first in the sidebar."
19646
20061
  );
19647
20062
  }
19648
- const base = path63.join(portal.dir, "pages", ...slug.split("/"));
20063
+ const base = path64.join(portal.dir, "pages", ...slug.split("/"));
19649
20064
  const existing = (await Promise.all(
19650
20065
  [`${base}.tsx`, `${base}.mdx`, `${base}.md`].map(
19651
20066
  async (file2) => await exists4(file2) ? file2 : null
19652
20067
  )
19653
20068
  )).find((file2) => file2 !== null);
19654
- const relative = (file2) => toPosix2(path63.relative(project.dir, file2));
20069
+ const relative = (file2) => toPosix2(path64.relative(project.dir, file2));
19655
20070
  const write = (file2, title, order = input5.order) => writeFile18(
19656
20071
  file2,
19657
20072
  file2.endsWith(".tsx") ? buildTsxDocsPage(slug, title, relative(file2), input5.content, order) : buildDocsPage(title, input5.content, order),
@@ -19685,17 +20100,17 @@ async function writeDocsPage(project, input5) {
19685
20100
  }
19686
20101
  file = `${base}.${input5.format ?? "tsx"}`;
19687
20102
  action = "added";
19688
- await mkdir15(path63.dirname(file), { recursive: true });
20103
+ await mkdir15(path64.dirname(file), { recursive: true });
19689
20104
  await write(file, title);
19690
20105
  }
19691
- const same = (match) => path63.resolve(match) === path63.resolve(file);
20106
+ const same = (match) => path64.resolve(match) === path64.resolve(file);
19692
20107
  const inPortal = file.endsWith(".tsx") ? (await findTsxPages(portal.docs.content)).some((page) => same(page.file)) : (await glob8(portal.docs.content, { absolute: true })).some(same);
19693
20108
  return {
19694
20109
  action,
19695
20110
  slug,
19696
20111
  route: `/${slug}`,
19697
20112
  file: relative(file),
19698
- format: path63.extname(file).slice(1),
20113
+ format: path64.extname(file).slice(1),
19699
20114
  inPortal
19700
20115
  };
19701
20116
  }
@@ -19774,7 +20189,7 @@ async function readPlanCatalog(project) {
19774
20189
  }
19775
20190
  var skuOf = (item) => isPlainObject3(item) && typeof item.sku === "string" ? item.sku.trim().toLowerCase() : void 0;
19776
20191
  async function writePlanCatalog(file, catalog, plans) {
19777
- await mkdir15(path63.dirname(file), { recursive: true });
20192
+ await mkdir15(path64.dirname(file), { recursive: true });
19778
20193
  await writeFile18(
19779
20194
  file,
19780
20195
  `${JSON.stringify({ ...catalog, plans }, null, 2)}
@@ -19797,7 +20212,7 @@ async function upsertPlan(project, plan2) {
19797
20212
  return {
19798
20213
  action: index === -1 ? "added" : "updated",
19799
20214
  sku: plan2.sku,
19800
- file: toPosix2(path63.relative(project.dir, file)),
20215
+ file: toPosix2(path64.relative(project.dir, file)),
19801
20216
  paid,
19802
20217
  needsBilling: paid && billing.provider !== "stripe"
19803
20218
  };
@@ -19887,12 +20302,12 @@ async function removePlan(project, rawSku) {
19887
20302
  if (next.length === catalog.plans.length) {
19888
20303
  const known = catalog.plans.map(skuOf).filter(Boolean).join(", ");
19889
20304
  throw new ConfigEditError(
19890
- `There is no plan "${sku}" in ${toPosix2(path63.relative(project.dir, file))}.`,
20305
+ `There is no plan "${sku}" in ${toPosix2(path64.relative(project.dir, file))}.`,
19891
20306
  known ? `The plans are: ${known}.` : "The plans file has no plans."
19892
20307
  );
19893
20308
  }
19894
20309
  await writePlanCatalog(file, catalog, next);
19895
- return { sku, file: toPosix2(path63.relative(project.dir, file)) };
20310
+ return { sku, file: toPosix2(path64.relative(project.dir, file)) };
19896
20311
  }
19897
20312
 
19898
20313
  // src/cli/make-config/wrap-up.ts
@@ -19915,6 +20330,12 @@ function printPageResult(page) {
19915
20330
  printSuccessToConsole(
19916
20331
  `Page ${page.action}: ${page.file} (route ${page.route})`
19917
20332
  );
20333
+ if (page.header) {
20334
+ printHintToConsole(
20335
+ `The header's top menu links to it (portal.header.links); the docs sidebar does not. Edit the file to change the content. Run \`apitogo deploy --only portal\` to publish it.`
20336
+ );
20337
+ return;
20338
+ }
19918
20339
  if (page.inPortal) {
19919
20340
  printHintToConsole(
19920
20341
  page.format === "tsx" ? "The page is a React component: edit it like any component. The sidebar is built from the pages folder, so the config does not change." : "The sidebar is built from the pages folder, so the config does not change. Edit the file to change the content."
@@ -20173,7 +20594,7 @@ async function applyConfigFlags(project, argv, changes) {
20173
20594
  return { project: next, openApi };
20174
20595
  }
20175
20596
  async function makeConfig(argv) {
20176
- const dir = path64.resolve(process.cwd(), argv.dir);
20597
+ const dir = path65.resolve(process.cwd(), argv.dir);
20177
20598
  const interactive = shouldRunInteractive(argv);
20178
20599
  const json = argv.json === true;
20179
20600
  const changes = [];
@@ -20216,7 +20637,32 @@ async function makeConfig(argv) {
20216
20637
  const applied = await applyConfigFlags(project, argv, changes);
20217
20638
  project = applied.project;
20218
20639
  openApi = applied.openApi;
20219
- if (argv.pageSlug !== void 0) {
20640
+ if (argv.pageHeader === true && argv.pageSlug === void 0) {
20641
+ throw new ApitogoError("--page-header needs --page-slug.", {
20642
+ developerHint: "Name the page, for example --page-slug partners --page-title Partners --page-header."
20643
+ });
20644
+ }
20645
+ if (argv.pageSlug !== void 0 && argv.pageHeader === true) {
20646
+ if (argv.pageOrder !== void 0) {
20647
+ throw new ApitogoError(
20648
+ "--page-order sets a place in the docs sidebar, which a header page is not in.",
20649
+ {
20650
+ developerHint: "Header links show in the order of portal.header.links in apitogo.config.json."
20651
+ }
20652
+ );
20653
+ }
20654
+ const written = await writeHeaderPage(project, {
20655
+ slug: argv.pageSlug,
20656
+ title: argv.pageTitle,
20657
+ content: argv.pageContent,
20658
+ update: argv.pageUpdate === true,
20659
+ format: argv.pageFormat
20660
+ });
20661
+ project = written.project;
20662
+ await saveConfigProject(project);
20663
+ page = written.page;
20664
+ changes.push(page.action === "added" ? "page_add" : "page_update");
20665
+ } else if (argv.pageSlug !== void 0) {
20220
20666
  page = await writeDocsPage(project, {
20221
20667
  slug: argv.pageSlug,
20222
20668
  title: argv.pageTitle,
@@ -20365,6 +20811,10 @@ function addConfigOptions(yargs2) {
20365
20811
  type: "boolean",
20366
20812
  default: false,
20367
20813
  describe: "Update an existing page instead of adding one"
20814
+ }).option("page-header", {
20815
+ type: "boolean",
20816
+ default: false,
20817
+ describe: "Make --page-slug a page of its own, linked from the header's top menu instead of the docs sidebar (<portal>/header-pages/<slug>.tsx; --page-format mdx for Markdown)"
20368
20818
  }).option("plan-sku", {
20369
20819
  type: "string",
20370
20820
  describe: "Add or change the plan with this SKU in the plans file; the --plan-* options set its fields"
@@ -20402,7 +20852,10 @@ function addConfigOptions(yargs2) {
20402
20852
  ).example(
20403
20853
  "$0 config --plan-sku enterprise --plan-price custom",
20404
20854
  "A contact-us plan"
20405
- ).example("$0 config --remove-plan enterprise", "Take a plan out").option("plan", {
20855
+ ).example("$0 config --remove-plan enterprise", "Take a plan out").example(
20856
+ "$0 config --page-slug partners --page-title Partners --page-header",
20857
+ "A page at /partners, linked from the header's top menu"
20858
+ ).option("plan", {
20406
20859
  type: "string",
20407
20860
  describe: "JSON plan added to the plans file (plans.file, default apitogo/plans.json), or replacing the one with the same sku"
20408
20861
  }).option("patch", {
@@ -20424,27 +20877,27 @@ var config_default = {
20424
20877
  init_apitogo_config_core();
20425
20878
  import { execFile } from "node:child_process";
20426
20879
  import { readFile as readFile24 } from "node:fs/promises";
20427
- import path66 from "node:path";
20880
+ import path67 from "node:path";
20428
20881
  import { promisify } from "node:util";
20429
20882
 
20430
20883
  // src/cli/create/dry-run.ts
20431
20884
  import { mkdir as mkdir16, mkdtemp, readdir as readdir4, rm as rm9 } from "node:fs/promises";
20432
20885
  import { tmpdir } from "node:os";
20433
- import path65 from "node:path";
20886
+ import path66 from "node:path";
20434
20887
  async function listFiles(root, dir = root) {
20435
20888
  const entries = await readdir4(dir, { withFileTypes: true });
20436
20889
  const nested = await Promise.all(
20437
20890
  entries.map(async (entry) => {
20438
- const full = path65.join(dir, entry.name);
20891
+ const full = path66.join(dir, entry.name);
20439
20892
  if (entry.isDirectory()) return listFiles(root, full);
20440
- return [path65.relative(root, full).split(path65.sep).join("/")];
20893
+ return [path66.relative(root, full).split(path66.sep).join("/")];
20441
20894
  })
20442
20895
  );
20443
20896
  return nested.flat();
20444
20897
  }
20445
20898
  async function previewCreate(input5) {
20446
- const scratch = await mkdtemp(path65.join(tmpdir(), "apitogo-create-"));
20447
- const dir = path65.join(scratch, "project");
20899
+ const scratch = await mkdtemp(path66.join(tmpdir(), "apitogo-create-"));
20900
+ const dir = path66.join(scratch, "project");
20448
20901
  try {
20449
20902
  await mkdir16(dir);
20450
20903
  await applyInitPlan(await planInit({ dir, name: input5.name }));
@@ -20477,9 +20930,9 @@ async function previewCreate(input5) {
20477
20930
  var execFileAsync = promisify(execFile);
20478
20931
  function nextStepHint(dir) {
20479
20932
  const linked = loadConfigV2(dir).resolved.project.id;
20480
- const cd = path66.resolve(dir) === path66.resolve(process.cwd());
20933
+ const cd = path67.resolve(dir) === path67.resolve(process.cwd());
20481
20934
  const steps = linked ? "apitogo deploy" : "apitogo project create, then apitogo deploy";
20482
- return cd ? `Next: ${steps}` : `Next: cd ${JSON.stringify(path66.relative(process.cwd(), dir))}, then ${steps}`;
20935
+ return cd ? `Next: ${steps}` : `Next: cd ${JSON.stringify(path67.relative(process.cwd(), dir))}, then ${steps}`;
20483
20936
  }
20484
20937
  function projectFolderName(name) {
20485
20938
  return name.trim().replace(/[<>:"/\\|?*\s]+/g, "-").replace(/^[-.]+|[-.]+$/g, "") || "apitogo-project";
@@ -20505,13 +20958,13 @@ async function gitHasUncommittedChanges(dir) {
20505
20958
  async function defaultName(dir) {
20506
20959
  try {
20507
20960
  const parsed = JSON.parse(
20508
- await readFile24(path66.join(dir, "package.json"), "utf8")
20961
+ await readFile24(path67.join(dir, "package.json"), "utf8")
20509
20962
  );
20510
20963
  if (typeof parsed.name === "string" && parsed.name.trim())
20511
20964
  return parsed.name.replace(/^@[^/]+\//, "");
20512
20965
  } catch {
20513
20966
  }
20514
- return path66.basename(dir);
20967
+ return path67.basename(dir);
20515
20968
  }
20516
20969
  function parseServicesFlag(value) {
20517
20970
  const names = (value ?? "").split(",").map((name) => name.trim()).filter(Boolean);
@@ -20537,7 +20990,7 @@ async function createProject(argv, deps = {}) {
20537
20990
  if (!json) printDiagnosticsToConsole(line2);
20538
20991
  };
20539
20992
  try {
20540
- const base = path66.resolve(process.cwd(), argv.dir ?? ".");
20993
+ const base = path67.resolve(process.cwd(), argv.dir ?? ".");
20541
20994
  let dir = base;
20542
20995
  let requested = parseServicesFlag(argv.services);
20543
20996
  let template = argv.template;
@@ -20560,8 +21013,8 @@ async function createProject(argv, deps = {}) {
20560
21013
  default: await defaultName(base)
20561
21014
  }) : await defaultName(base));
20562
21015
  const folder = projectFolderName(name);
20563
- if (argv.dir === void 0 && folder !== path66.basename(base))
20564
- dir = path66.join(base, folder);
21016
+ if (argv.dir === void 0 && folder !== path67.basename(base))
21017
+ dir = path67.join(base, folder);
20565
21018
  }
20566
21019
  if (await (deps.hasUncommittedChanges ?? gitHasUncommittedChanges)(dir))
20567
21020
  say(
@@ -20574,7 +21027,7 @@ async function createProject(argv, deps = {}) {
20574
21027
  if (argv.dryRun) {
20575
21028
  const services = requested.length > 0 ? requested : templateServices ? STARTER_SERVICES : [];
20576
21029
  const preview2 = services.length > 0 ? await previewCreate({ name, template, services }) : void 0;
20577
- const folder = path66.relative(process.cwd(), dir) || ".";
21030
+ const folder = path67.relative(process.cwd(), dir) || ".";
20578
21031
  if (json)
20579
21032
  console.log(
20580
21033
  JSON.stringify({
@@ -20607,7 +21060,7 @@ async function createProject(argv, deps = {}) {
20607
21060
  }
20608
21061
  await applyInitPlan(plan2);
20609
21062
  say(
20610
- dir === base ? `Created ${APITOGO_CONFIG_FILENAME}.` : `Created ${path66.relative(base, dir)}${path66.sep}${APITOGO_CONFIG_FILENAME}.`
21063
+ dir === base ? `Created ${APITOGO_CONFIG_FILENAME}.` : `Created ${path67.relative(base, dir)}${path67.sep}${APITOGO_CONFIG_FILENAME}.`
20611
21064
  );
20612
21065
  } else {
20613
21066
  say(`${APITOGO_CONFIG_FILENAME} exists: continuing from it.`);
@@ -20749,10 +21202,10 @@ var create_default = {
20749
21202
  };
20750
21203
 
20751
21204
  // src/cli/cmds/deploy.ts
20752
- import path85 from "node:path";
21205
+ import path86 from "node:path";
20753
21206
 
20754
21207
  // src/cli/apply/handler.ts
20755
- import path78 from "node:path";
21208
+ import path79 from "node:path";
20756
21209
 
20757
21210
  // src/cli/platform/helpers/gateway-plan-validation.ts
20758
21211
  init_gateway_limits();
@@ -20971,11 +21424,11 @@ init_gateway_limits();
20971
21424
  // src/cli/platform/helpers/deploy-artifact-hash.ts
20972
21425
  import { createHash as createHash3 } from "node:crypto";
20973
21426
  import { createReadStream, promises as fs6 } from "node:fs";
20974
- import path68 from "node:path";
21427
+ import path69 from "node:path";
20975
21428
 
20976
21429
  // src/cli/platform/helpers/package-files.ts
20977
21430
  import { promises as fs5 } from "node:fs";
20978
- import path67 from "node:path";
21431
+ import path68 from "node:path";
20979
21432
  import ignore from "ignore";
20980
21433
  var DEFAULT_EXCLUDES = /* @__PURE__ */ new Set([
20981
21434
  "node_modules",
@@ -20989,7 +21442,7 @@ async function loadGitignore(rootDir, extraExcludes = []) {
20989
21442
  const ig = ignore();
20990
21443
  ig.add(["node_modules/", ".git/", "dist/", "bin/", "obj/"]);
20991
21444
  if (extraExcludes.length > 0) ig.add([...extraExcludes]);
20992
- const gitignorePath = path67.join(rootDir, ".gitignore");
21445
+ const gitignorePath = path68.join(rootDir, ".gitignore");
20993
21446
  try {
20994
21447
  const content = await fs5.readFile(gitignorePath, "utf8");
20995
21448
  ig.add(content);
@@ -20998,11 +21451,11 @@ async function loadGitignore(rootDir, extraExcludes = []) {
20998
21451
  return ig;
20999
21452
  }
21000
21453
  async function collectFiles(rootDir, relativeDir, ig, files) {
21001
- const absDir = path67.join(rootDir, relativeDir);
21454
+ const absDir = path68.join(rootDir, relativeDir);
21002
21455
  const entries = await fs5.readdir(absDir, { withFileTypes: true });
21003
21456
  for (const entry of entries) {
21004
- const rel2 = relativeDir ? path67.join(relativeDir, entry.name) : entry.name;
21005
- const relPosix = rel2.split(path67.sep).join("/");
21457
+ const rel2 = relativeDir ? path68.join(relativeDir, entry.name) : entry.name;
21458
+ const relPosix = rel2.split(path68.sep).join("/");
21006
21459
  if (DEFAULT_EXCLUDES.has(entry.name) && !relativeDir) {
21007
21460
  continue;
21008
21461
  }
@@ -21017,7 +21470,7 @@ async function collectFiles(rootDir, relativeDir, ig, files) {
21017
21470
  }
21018
21471
  }
21019
21472
  async function collectPackageableFiles(sourceDirectory, extraExcludes = []) {
21020
- const resolved = path67.resolve(sourceDirectory);
21473
+ const resolved = path68.resolve(sourceDirectory);
21021
21474
  const stat7 = await fs5.stat(resolved).catch(() => null);
21022
21475
  if (!stat7?.isDirectory()) {
21023
21476
  throw new Error(`sourceDirectory is not a directory: ${resolved}`);
@@ -21043,7 +21496,7 @@ async function sha256File(filePath) {
21043
21496
  return hash2.digest("hex");
21044
21497
  }
21045
21498
  async function hashDirectoryContents(rootDirectory, extraExcludes = []) {
21046
- const resolved = path68.resolve(rootDirectory);
21499
+ const resolved = path69.resolve(rootDirectory);
21047
21500
  const files = (await collectPackageableFiles(resolved, extraExcludes)).sort(
21048
21501
  (a, b) => a.localeCompare(b)
21049
21502
  );
@@ -21051,13 +21504,13 @@ async function hashDirectoryContents(rootDirectory, extraExcludes = []) {
21051
21504
  for (const rel2 of files) {
21052
21505
  hash2.update(rel2);
21053
21506
  hash2.update("\0");
21054
- hash2.update(await fs6.readFile(path68.join(resolved, rel2)));
21507
+ hash2.update(await fs6.readFile(path69.join(resolved, rel2)));
21055
21508
  hash2.update("\0");
21056
21509
  }
21057
21510
  return hash2.digest("hex");
21058
21511
  }
21059
21512
  async function hashDevPortalSiteSources(siteDirectory, options) {
21060
- const resolved = path68.resolve(siteDirectory);
21513
+ const resolved = path69.resolve(siteDirectory);
21061
21514
  const hash2 = createHash3("sha256");
21062
21515
  const candidates = [
21063
21516
  "apitogo.config.json",
@@ -21067,7 +21520,7 @@ async function hashDevPortalSiteSources(siteDirectory, options) {
21067
21520
  "zudoku.config.tsx"
21068
21521
  ];
21069
21522
  for (const name of candidates) {
21070
- const full = path68.join(resolved, name);
21523
+ const full = path69.join(resolved, name);
21071
21524
  try {
21072
21525
  hash2.update(name);
21073
21526
  hash2.update("\0");
@@ -21077,7 +21530,7 @@ async function hashDevPortalSiteSources(siteDirectory, options) {
21077
21530
  }
21078
21531
  }
21079
21532
  for (const dir of ["pages", "landing", "src"]) {
21080
- const full = path68.join(resolved, dir);
21533
+ const full = path69.join(resolved, dir);
21081
21534
  try {
21082
21535
  const st = await fs6.stat(full);
21083
21536
  if (!st.isDirectory()) continue;
@@ -21306,8 +21759,8 @@ function normalizeActionKey(actionKey2) {
21306
21759
  const trimmed = actionKey2.trim();
21307
21760
  if (trimmed.includes(":")) {
21308
21761
  const [method2, ...pathParts] = trimmed.split(":");
21309
- const path100 = pathParts.join(":").trim();
21310
- return `${method2?.toLowerCase()}:${path100.startsWith("/") ? path100 : `/${path100}`}`;
21762
+ const path101 = pathParts.join(":").trim();
21763
+ return `${method2?.toLowerCase()}:${path101.startsWith("/") ? path101 : `/${path101}`}`;
21311
21764
  }
21312
21765
  const spaceIndex = trimmed.indexOf(" ");
21313
21766
  if (spaceIndex <= 0) {
@@ -21316,8 +21769,8 @@ function normalizeActionKey(actionKey2) {
21316
21769
  );
21317
21770
  }
21318
21771
  const method = trimmed.slice(0, spaceIndex).trim().toLowerCase();
21319
- const path99 = trimmed.slice(spaceIndex + 1).trim();
21320
- return `${method}:${path99.startsWith("/") ? path99 : `/${path99}`}`;
21772
+ const path100 = trimmed.slice(spaceIndex + 1).trim();
21773
+ return `${method}:${path100.startsWith("/") ? path100 : `/${path100}`}`;
21321
21774
  }
21322
21775
  async function applyGatewayPublicRoutes(client, projectId, routes, options) {
21323
21776
  const applied = [];
@@ -21796,7 +22249,7 @@ async function upsertGatewayPlansFromManifest(client, config2, projectId, plans,
21796
22249
 
21797
22250
  // src/cli/platform/helpers/openapi-gateway-servers.ts
21798
22251
  import { mkdir as mkdir17, readFile as readFile25, writeFile as writeFile19 } from "node:fs/promises";
21799
- import path69 from "node:path";
22252
+ import path70 from "node:path";
21800
22253
  import YAML2 from "yaml";
21801
22254
  var GATEWAY_URL_ENV_KEY = "APITOGO_GATEWAY_URL";
21802
22255
  var PUBLISH_OPENAPI_RELATIVE = ".apitogo/openapi-publish.json";
@@ -21828,7 +22281,7 @@ async function resolveProjectGatewayUrl(client, projectId) {
21828
22281
  }
21829
22282
  async function readOpenApiDocument(filePath) {
21830
22283
  const content = await readFile25(filePath, "utf8");
21831
- const ext = path69.extname(filePath).toLowerCase();
22284
+ const ext = path70.extname(filePath).toLowerCase();
21832
22285
  const parsed = ext === ".json" ? JSON.parse(content) : YAML2.parse(content);
21833
22286
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
21834
22287
  throw new Error(`OpenAPI document must be an object: ${filePath}`);
@@ -21836,13 +22289,13 @@ async function readOpenApiDocument(filePath) {
21836
22289
  return parsed;
21837
22290
  }
21838
22291
  async function materializePublishOpenApiWithGateway(siteDirectory, sourceOpenApiPath, gatewayUrl) {
21839
- const site = path69.resolve(siteDirectory);
21840
- const source = path69.resolve(sourceOpenApiPath);
22292
+ const site = path70.resolve(siteDirectory);
22293
+ const source = path70.resolve(sourceOpenApiPath);
21841
22294
  const relativeInput = PUBLISH_OPENAPI_RELATIVE;
21842
- const outputPath = path69.join(site, ...relativeInput.split("/"));
22295
+ const outputPath = path70.join(site, ...relativeInput.split("/"));
21843
22296
  const doc = await readOpenApiDocument(source);
21844
22297
  const rewritten = withGatewayServers(doc, gatewayUrl);
21845
- await mkdir17(path69.dirname(outputPath), { recursive: true });
22298
+ await mkdir17(path70.dirname(outputPath), { recursive: true });
21846
22299
  await writeFile19(
21847
22300
  outputPath,
21848
22301
  `${JSON.stringify(rewritten, null, 2)}
@@ -21852,8 +22305,8 @@ async function materializePublishOpenApiWithGateway(siteDirectory, sourceOpenApi
21852
22305
  return { outputPath, relativeInput };
21853
22306
  }
21854
22307
  async function pointSiteApisAtPublishOpenApi(siteDirectory, relativeInput) {
21855
- const configPath = path69.join(
21856
- path69.resolve(siteDirectory),
22308
+ const configPath = path70.join(
22309
+ path70.resolve(siteDirectory),
21857
22310
  APITOGO_CONFIG_FILENAME2
21858
22311
  );
21859
22312
  const raw = await readFile25(configPath, "utf8");
@@ -21895,7 +22348,7 @@ async function pointSiteApisAtPublishOpenApi(siteDirectory, relativeInput) {
21895
22348
  };
21896
22349
  }
21897
22350
  async function prepareOpenApiServersForGatewayBuild(options) {
21898
- const siteDirectory = path69.resolve(options.siteDirectory);
22351
+ const siteDirectory = path70.resolve(options.siteDirectory);
21899
22352
  let openApiPath = resolveBackendOpenApiPath(
21900
22353
  options.manifest?.backend?.openApiPath,
21901
22354
  {
@@ -21909,7 +22362,7 @@ async function prepareOpenApiServersForGatewayBuild(options) {
21909
22362
  if (!openApiPath) {
21910
22363
  try {
21911
22364
  const raw = await readFile25(
21912
- path69.join(siteDirectory, APITOGO_CONFIG_FILENAME2),
22365
+ path70.join(siteDirectory, APITOGO_CONFIG_FILENAME2),
21913
22366
  "utf8"
21914
22367
  );
21915
22368
  const config2 = JSON.parse(raw);
@@ -21938,8 +22391,8 @@ function normalizeActionKeyFormat(actionKey2) {
21938
22391
  const trimmed = actionKey2.trim();
21939
22392
  if (trimmed.includes(":")) {
21940
22393
  const [method2, ...pathParts] = trimmed.split(":");
21941
- const path100 = pathParts.join(":").trim();
21942
- return `${method2.toLowerCase()}:${path100.startsWith("/") ? path100 : `/${path100}`}`;
22394
+ const path101 = pathParts.join(":").trim();
22395
+ return `${method2.toLowerCase()}:${path101.startsWith("/") ? path101 : `/${path101}`}`;
21943
22396
  }
21944
22397
  const spaceIndex = trimmed.indexOf(" ");
21945
22398
  if (spaceIndex <= 0) {
@@ -21948,8 +22401,8 @@ function normalizeActionKeyFormat(actionKey2) {
21948
22401
  );
21949
22402
  }
21950
22403
  const method = trimmed.slice(0, spaceIndex).trim().toLowerCase();
21951
- const path99 = trimmed.slice(spaceIndex + 1).trim();
21952
- return `${method}:${path99.startsWith("/") ? path99 : `/${path99}`}`;
22404
+ const path100 = trimmed.slice(spaceIndex + 1).trim();
22405
+ return `${method}:${path100.startsWith("/") ? path100 : `/${path100}`}`;
21953
22406
  }
21954
22407
  function normalizePublicRoute(route) {
21955
22408
  const trimmed = route.trim();
@@ -21983,10 +22436,10 @@ function collectManifestPublicActionKeys(gateway) {
21983
22436
 
21984
22437
  // src/cli/platform/helpers/publish-ledger.ts
21985
22438
  import { mkdir as mkdir18, readFile as readFile26, writeFile as writeFile20 } from "node:fs/promises";
21986
- import path70 from "node:path";
22439
+ import path71 from "node:path";
21987
22440
  var PUBLISH_LEDGER_RELATIVE_PATH = ".apitogo/publish-state.json";
21988
22441
  function publishLedgerPath(siteDirectory) {
21989
- return path70.join(path70.resolve(siteDirectory), PUBLISH_LEDGER_RELATIVE_PATH);
22442
+ return path71.join(path71.resolve(siteDirectory), PUBLISH_LEDGER_RELATIVE_PATH);
21990
22443
  }
21991
22444
  async function readPublishLedger(siteDirectory) {
21992
22445
  const filePath = publishLedgerPath(siteDirectory);
@@ -22003,7 +22456,7 @@ async function readPublishLedger(siteDirectory) {
22003
22456
  }
22004
22457
  async function writePublishLedger(siteDirectory, ledger) {
22005
22458
  const filePath = publishLedgerPath(siteDirectory);
22006
- await mkdir18(path70.dirname(filePath), { recursive: true });
22459
+ await mkdir18(path71.dirname(filePath), { recursive: true });
22007
22460
  const next = {
22008
22461
  ...ledger,
22009
22462
  version: 1,
@@ -22524,7 +22977,7 @@ async function createProjectWithWarmPoolRetry(client, body, options) {
22524
22977
  // src/cli/platform/helpers/backend-packaging.ts
22525
22978
  import { mkdtemp as mkdtemp2, readdir as readdir5, rm as rm10 } from "node:fs/promises";
22526
22979
  import { tmpdir as tmpdir2 } from "node:os";
22527
- import path71 from "node:path";
22980
+ import path72 from "node:path";
22528
22981
  async function directoryHasRuntimeConfig(dir) {
22529
22982
  if (!await pathExists(dir)) {
22530
22983
  return false;
@@ -22566,20 +23019,20 @@ function runDotnetPublish(projectDir, outputDir) {
22566
23019
  async function findCsproj(projectDir) {
22567
23020
  const entries = await readdir5(projectDir);
22568
23021
  const csproj = entries.find((name) => name.endsWith(".csproj"));
22569
- return csproj ? path71.join(projectDir, csproj) : null;
23022
+ return csproj ? path72.join(projectDir, csproj) : null;
22570
23023
  }
22571
23024
  async function prepareBackendPackagingDirectory(args) {
22572
- const sourceDirectory = path71.resolve(args.sourceDirectory);
23025
+ const sourceDirectory = path72.resolve(args.sourceDirectory);
22573
23026
  if (args.publishDirectory?.trim()) {
22574
23027
  return {
22575
- directory: path71.resolve(args.publishDirectory),
23028
+ directory: path72.resolve(args.publishDirectory),
22576
23029
  packagingMode: "published"
22577
23030
  };
22578
23031
  }
22579
23032
  if (await directoryHasRuntimeConfig(sourceDirectory)) {
22580
23033
  return { directory: sourceDirectory, packagingMode: "published" };
22581
23034
  }
22582
- const publishSubdir = path71.join(sourceDirectory, "publish");
23035
+ const publishSubdir = path72.join(sourceDirectory, "publish");
22583
23036
  if (await directoryHasRuntimeConfig(publishSubdir)) {
22584
23037
  return { directory: publishSubdir, packagingMode: "published" };
22585
23038
  }
@@ -22591,9 +23044,9 @@ async function prepareBackendPackagingDirectory(args) {
22591
23044
  return { directory: sourceDirectory, packagingMode: "source" };
22592
23045
  }
22593
23046
  const tempRoot = await mkdtemp2(
22594
- path71.join(tmpdir2(), "apitogo-dotnet-publish-")
23047
+ path72.join(tmpdir2(), "apitogo-dotnet-publish-")
22595
23048
  );
22596
- const outputDir = path71.join(tempRoot, "publish");
23049
+ const outputDir = path72.join(tempRoot, "publish");
22597
23050
  const result = await runDotnetPublish(sourceDirectory, outputDir);
22598
23051
  if (result.exitCode !== 0) {
22599
23052
  await rm10(tempRoot, { recursive: true, force: true });
@@ -22638,13 +23091,13 @@ function formatJobStatusResponse(options) {
22638
23091
 
22639
23092
  // src/cli/platform/helpers/openapi.ts
22640
23093
  import { promises as fs7 } from "node:fs";
22641
- import path72 from "node:path";
23094
+ import path73 from "node:path";
22642
23095
  import SwaggerParser from "@apidevtools/swagger-parser";
22643
23096
  import YAML3 from "yaml";
22644
23097
  async function validateOpenApiFile(filePath) {
22645
- const resolved = path72.resolve(filePath);
23098
+ const resolved = path73.resolve(filePath);
22646
23099
  const content = await fs7.readFile(resolved, "utf8");
22647
- const ext = path72.extname(resolved).toLowerCase();
23100
+ const ext = path73.extname(resolved).toLowerCase();
22648
23101
  let parsed;
22649
23102
  try {
22650
23103
  parsed = ext === ".json" ? JSON.parse(content) : YAML3.parse(content);
@@ -22688,12 +23141,12 @@ async function validateOpenApiFile(filePath) {
22688
23141
  // src/cli/platform/helpers/zip.ts
22689
23142
  import { createReadStream as createReadStream2, createWriteStream as createWriteStream2, promises as fs8 } from "node:fs";
22690
23143
  import { tmpdir as tmpdir3 } from "node:os";
22691
- import path73 from "node:path";
23144
+ import path74 from "node:path";
22692
23145
  import { ZipArchive } from "archiver";
22693
23146
  async function packageBackendDirectory(sourceDirectory, extraExcludes = []) {
22694
- const resolved = path73.resolve(sourceDirectory);
23147
+ const resolved = path74.resolve(sourceDirectory);
22695
23148
  const files = await collectPackageableFiles(resolved, extraExcludes);
22696
- const zipPath = path73.join(
23149
+ const zipPath = path74.join(
22697
23150
  tmpdir3(),
22698
23151
  `apitogo-backend-${Date.now()}-${Math.random().toString(36).slice(2)}.zip`
22699
23152
  );
@@ -22705,8 +23158,8 @@ async function packageBackendDirectory(sourceDirectory, extraExcludes = []) {
22705
23158
  output4.on("error", reject);
22706
23159
  archive.pipe(output4);
22707
23160
  for (const rel2 of files) {
22708
- archive.append(createReadStream2(path73.join(resolved, rel2)), {
22709
- name: rel2.split(path73.sep).join("/")
23161
+ archive.append(createReadStream2(path74.join(resolved, rel2)), {
23162
+ name: rel2.split(path74.sep).join("/")
22710
23163
  });
22711
23164
  }
22712
23165
  void archive.finalize();
@@ -24276,11 +24729,11 @@ async function resolveProjectIdForPublish(client, config2, args, wait, progress,
24276
24729
  }
24277
24730
 
24278
24731
  // src/cli/platform/tools/dev-portal-site-build.ts
24279
- import path77 from "node:path";
24732
+ import path78 from "node:path";
24280
24733
 
24281
24734
  // src/cli/platform/helpers/dev-portal-site-api-url.ts
24282
24735
  import { readdir as readdir6, readFile as readFile27, writeFile as writeFile21 } from "node:fs/promises";
24283
- import path74 from "node:path";
24736
+ import path75 from "node:path";
24284
24737
  var SITE_API_BASE_URL_ENV_KEY = "VITE_APITOGO_AUTHENTICATION_API_BASE_URL";
24285
24738
  var PLACEHOLDER_HOST_MARKERS = [
24286
24739
  "dp-example.azurecontainerapps.io",
@@ -24301,10 +24754,10 @@ function isPlaceholderDevPortalApiUrl(apiBaseUrl) {
24301
24754
  return PLACEHOLDER_HOST_MARKERS.some((marker) => normalized.includes(marker));
24302
24755
  }
24303
24756
  async function readSiteApiBaseUrl(sourceDirectory) {
24304
- const resolved = path74.resolve(sourceDirectory);
24757
+ const resolved = path75.resolve(sourceDirectory);
24305
24758
  for (const envFile of [".env", ".env.production"]) {
24306
24759
  try {
24307
- const content = await readFile27(path74.join(resolved, envFile), "utf8");
24760
+ const content = await readFile27(path75.join(resolved, envFile), "utf8");
24308
24761
  const lines = [...content.matchAll(ENV_LINE_GLOBAL_RE)];
24309
24762
  if (lines.length === 0) {
24310
24763
  continue;
@@ -24329,11 +24782,11 @@ async function readSiteApiBaseUrl(sourceDirectory) {
24329
24782
  return null;
24330
24783
  }
24331
24784
  async function patchSiteApiBaseUrl(sourceDirectory, apiBaseUrl) {
24332
- const resolved = path74.resolve(sourceDirectory);
24785
+ const resolved = path75.resolve(sourceDirectory);
24333
24786
  const line2 = `${SITE_API_BASE_URL_ENV_KEY}=${apiBaseUrl}`;
24334
24787
  let patchedExisting = false;
24335
24788
  for (const envFile of [".env.production", ".env"]) {
24336
- const envPath = path74.join(resolved, envFile);
24789
+ const envPath = path75.join(resolved, envFile);
24337
24790
  try {
24338
24791
  const content = await readFile27(envPath, "utf8");
24339
24792
  const withoutAssignments = content.replace(ENV_LINE_GLOBAL_RE, "").replace(/\n{3,}/g, "\n\n").trimEnd();
@@ -24347,7 +24800,7 @@ ${line2}
24347
24800
  }
24348
24801
  }
24349
24802
  if (!patchedExisting) {
24350
- await writeFile21(path74.join(resolved, ".env"), `${line2}
24803
+ await writeFile21(path75.join(resolved, ".env"), `${line2}
24351
24804
  `);
24352
24805
  }
24353
24806
  }
@@ -24355,7 +24808,7 @@ function tryProjectId(explicit, manifestProjectId, config2) {
24355
24808
  return explicit?.trim() || manifestProjectId?.trim() || config2.defaultProjectId?.trim() || void 0;
24356
24809
  }
24357
24810
  async function ensureLiveSiteApiBaseUrlForBuild(_client, config2, args, resolveFromProject) {
24358
- const sourceDirectory = path74.resolve(args.sourceDirectory);
24811
+ const sourceDirectory = path75.resolve(args.sourceDirectory);
24359
24812
  const explicit = args.apiBaseUrl?.trim();
24360
24813
  if (explicit && !isPlaceholderDevPortalApiUrl(explicit)) {
24361
24814
  await patchSiteApiBaseUrl(sourceDirectory, explicit);
@@ -24391,7 +24844,7 @@ async function* walkFiles(root) {
24391
24844
  return;
24392
24845
  }
24393
24846
  for (const entry of entries) {
24394
- const full = path74.join(root, entry.name);
24847
+ const full = path75.join(root, entry.name);
24395
24848
  if (entry.isDirectory()) {
24396
24849
  if (entry.name === "node_modules" || entry.name === ".git") {
24397
24850
  continue;
@@ -24403,8 +24856,8 @@ async function* walkFiles(root) {
24403
24856
  }
24404
24857
  }
24405
24858
  async function assertDeployableDistHasLiveApiBaseUrl(distDirectory, expectedApiBaseUrl) {
24406
- const dist = path74.resolve(distDirectory);
24407
- const siteRoot = path74.dirname(dist);
24859
+ const dist = path75.resolve(distDirectory);
24860
+ const siteRoot = path75.dirname(dist);
24408
24861
  const fromEnv = expectedApiBaseUrl?.trim() || await readSiteApiBaseUrl(siteRoot) || await readSiteApiBaseUrl(dist);
24409
24862
  if (!fromEnv || isPlaceholderDevPortalApiUrl(fromEnv)) {
24410
24863
  throw new Error(
@@ -24440,7 +24893,7 @@ async function assertDeployableDistHasLiveApiBaseUrl(distDirectory, expectedApiB
24440
24893
 
24441
24894
  // src/cli/platform/tools/dev-portal.ts
24442
24895
  import { promises as fs10 } from "node:fs";
24443
- import path76 from "node:path";
24896
+ import path77 from "node:path";
24444
24897
 
24445
24898
  // src/cli/platform/helpers/dev-portal-backend-job.ts
24446
24899
  var DEV_PORTAL_BACKEND_UPDATE_RESOURCE_TYPE = "DevPortalBackendUpdate";
@@ -24550,7 +25003,7 @@ async function snapshotBackendUpdateJobIds(client, projectId) {
24550
25003
  // src/cli/platform/helpers/tarball.ts
24551
25004
  import { createReadStream as createReadStream3, createWriteStream as createWriteStream3, promises as fs9 } from "node:fs";
24552
25005
  import { tmpdir as tmpdir4 } from "node:os";
24553
- import path75 from "node:path";
25006
+ import path76 from "node:path";
24554
25007
  import { TarArchive } from "archiver";
24555
25008
  var DEV_PORTAL_DEPLOY_ARCHIVE_ROOT = "dist";
24556
25009
  function normalizeArchiveRoot(archiveRoot) {
@@ -24561,10 +25014,10 @@ function normalizeArchiveRoot(archiveRoot) {
24561
25014
  return `${trimmed}/`;
24562
25015
  }
24563
25016
  async function packageDirectoryAsTarGz(sourceDirectory, options) {
24564
- const resolved = path75.resolve(sourceDirectory);
25017
+ const resolved = path76.resolve(sourceDirectory);
24565
25018
  const files = await collectPackageableFiles(resolved);
24566
25019
  const entryPrefix = options?.archiveRoot ? normalizeArchiveRoot(options.archiveRoot) : "";
24567
- const archivePath = path75.join(
25020
+ const archivePath = path76.join(
24568
25021
  tmpdir4(),
24569
25022
  `apitogo-docs-${Date.now()}-${Math.random().toString(36).slice(2)}.tgz`
24570
25023
  );
@@ -24576,8 +25029,8 @@ async function packageDirectoryAsTarGz(sourceDirectory, options) {
24576
25029
  output4.on("error", reject);
24577
25030
  archive.pipe(output4);
24578
25031
  for (const rel2 of files) {
24579
- const entryName = entryPrefix + rel2.split(path75.sep).join("/");
24580
- archive.append(createReadStream3(path75.join(resolved, rel2)), {
25032
+ const entryName = entryPrefix + rel2.split(path76.sep).join("/");
25033
+ archive.append(createReadStream3(path76.join(resolved, rel2)), {
24581
25034
  name: entryName
24582
25035
  });
24583
25036
  }
@@ -24819,7 +25272,7 @@ function validateDeployArchiveInput(args) {
24819
25272
  async function resolveDevPortalArchive(args) {
24820
25273
  validateDeployArchiveInput(args);
24821
25274
  if (args.archivePath?.trim()) {
24822
- const archivePath = path76.resolve(args.archivePath.trim());
25275
+ const archivePath = path77.resolve(args.archivePath.trim());
24823
25276
  const stat7 = await fs10.stat(archivePath).catch(() => null);
24824
25277
  if (!stat7?.isFile()) {
24825
25278
  throw new Error(`archivePath is not a file: ${archivePath}`);
@@ -24884,7 +25337,7 @@ async function handleDeployDevPortal(client, config2, args, progress) {
24884
25337
  // src/cli/platform/tools/dev-portal-site-build.ts
24885
25338
  async function handleBuildDevPortalSite(client, config2, args, progress) {
24886
25339
  const buildProgress = progress?.child("Build dev portal site");
24887
- const sourceDirectory = path77.resolve(args.sourceDirectory);
25340
+ const sourceDirectory = path78.resolve(args.sourceDirectory);
24888
25341
  await buildProgress?.onStep("Ensuring live portal API URL");
24889
25342
  const apiBaseUrl = await ensureLiveSiteApiBaseUrlForBuild(
24890
25343
  client,
@@ -26390,7 +26843,7 @@ function resultLooksFailed(payload) {
26390
26843
  return parsed.status === "failed" || parsed.status === "blocked";
26391
26844
  }
26392
26845
  async function apply(argv) {
26393
- const sourceDirectory = path78.resolve(process.cwd(), argv.dir);
26846
+ const sourceDirectory = path79.resolve(process.cwd(), argv.dir);
26394
26847
  let config2;
26395
26848
  try {
26396
26849
  config2 = loadConfig();
@@ -26460,7 +26913,7 @@ async function apply(argv) {
26460
26913
  if (canPromptInteractively && parsed?.status === "blocked" && parsed.reason === "project_not_found") {
26461
26914
  const resolution = await resolveProjectNotFoundInteractively(
26462
26915
  parsed,
26463
- path78.basename(sourceDirectory)
26916
+ path79.basename(sourceDirectory)
26464
26917
  );
26465
26918
  if (resolution) {
26466
26919
  effectiveProjectId = void 0;
@@ -26480,7 +26933,7 @@ async function apply(argv) {
26480
26933
  while (canPromptInteractively && parsed?.status === "blocked" && parsed.reason === "project_name_exists") {
26481
26934
  const resolution = await resolveProjectNameExistsInteractively(
26482
26935
  parsed,
26483
- effectiveProjectName ?? path78.basename(sourceDirectory)
26936
+ effectiveProjectName ?? path79.basename(sourceDirectory)
26484
26937
  );
26485
26938
  if (!resolution) {
26486
26939
  break;
@@ -26551,11 +27004,11 @@ async function apply(argv) {
26551
27004
  }
26552
27005
 
26553
27006
  // src/cli/deploy/command.ts
26554
- import path84 from "node:path";
27007
+ import path85 from "node:path";
26555
27008
  init_invariant();
26556
27009
 
26557
27010
  // src/cli/project/create-handler.ts
26558
- import path79 from "node:path";
27011
+ import path80 from "node:path";
26559
27012
  import open2 from "open";
26560
27013
  init_invariant();
26561
27014
 
@@ -26589,11 +27042,22 @@ function assertConfigChecksOut(output4) {
26589
27042
  ].join("\n")
26590
27043
  );
26591
27044
  }
26592
- var hostsAnything = (config2) => config2.portal && config2.portal.enabled !== false || config2.gateway && config2.gateway.enabled !== false;
27045
+ var gatewayOn = (config2) => config2.gateway !== void 0 && config2.gateway.enabled !== false;
27046
+ var hostsAnything = (config2) => config2.portal && config2.portal.enabled !== false || gatewayOn(config2);
26593
27047
  async function createProjectV2(options, deps) {
26594
27048
  const { config: config2, resolved } = loadConfigV2(options.dir);
26595
27049
  const linkedProjectId = resolved.project.id;
26596
- const modules = modulesForConfig(config2);
27050
+ const externalDocsOnly = portalOnlyLinksExternalDocs(resolved);
27051
+ const modules = modulesForConfig(config2).filter(
27052
+ (module) => module !== "dev-portal" || !externalDocsOnly
27053
+ );
27054
+ if (externalDocsOnly && !gatewayOn(config2)) {
27055
+ throw new ProjectCreateError(
27056
+ "NOTHING_TO_HOST",
27057
+ "No enabled service needs a project: the docs are on an external site and nothing feeds the portal console.",
27058
+ "Enable identity or plans for a console next to your docs (`apitogo add identity`), or a gateway (`apitogo add gateway`)."
27059
+ );
27060
+ }
26597
27061
  if (!hostsAnything(config2)) {
26598
27062
  throw new ProjectCreateError(
26599
27063
  "NOTHING_TO_HOST",
@@ -26869,7 +27333,7 @@ async function createLinkedProject(argv, dir, options = {}) {
26869
27333
  }
26870
27334
  }
26871
27335
  async function createProject2(argv) {
26872
- const dir = path79.resolve(process.cwd(), argv.dir ?? ".");
27336
+ const dir = path80.resolve(process.cwd(), argv.dir ?? ".");
26873
27337
  if (await isConfigV2(dir)) {
26874
27338
  await createLinkedProject(argv, dir);
26875
27339
  return;
@@ -26929,7 +27393,7 @@ async function createProject2(argv) {
26929
27393
  }
26930
27394
 
26931
27395
  // src/cli/secrets/handler.ts
26932
- import path80 from "node:path";
27396
+ import path81 from "node:path";
26933
27397
  init_invariant();
26934
27398
 
26935
27399
  // src/cli/secrets/input.ts
@@ -26994,7 +27458,7 @@ var SecretsError = class extends ApitogoError {
26994
27458
  async function resolveProjectId2(argv, deps) {
26995
27459
  const explicit = argv.projectId?.trim();
26996
27460
  if (explicit) return explicit;
26997
- const dir = path80.resolve(process.cwd(), argv.dir ?? ".");
27461
+ const dir = path81.resolve(process.cwd(), argv.dir ?? ".");
26998
27462
  if (await isConfigV2(dir)) {
26999
27463
  const id = loadConfigV2(dir).resolved.project.id;
27000
27464
  if (id) return id;
@@ -27079,7 +27543,7 @@ var KNOWN_SECRET_PLACES = {
27079
27543
  async function wireKnownSecret(argv, name) {
27080
27544
  const place = KNOWN_SECRET_PLACES[name.toUpperCase()];
27081
27545
  if (!place) return void 0;
27082
- const dir = path80.resolve(process.cwd(), argv.dir ?? ".");
27546
+ const dir = path81.resolve(process.cwd(), argv.dir ?? ".");
27083
27547
  if (!await isConfigV2(dir)) return void 0;
27084
27548
  const project = await openConfigProject(dir);
27085
27549
  const plans = project.resolved.plans;
@@ -27140,7 +27604,7 @@ async function secrets(argv) {
27140
27604
  }
27141
27605
 
27142
27606
  // src/cli/deploy/stripe-key.ts
27143
- import path81 from "node:path";
27607
+ import path82 from "node:path";
27144
27608
  init_invariant();
27145
27609
  var StripeKeyError = class extends ApitogoError {
27146
27610
  constructor(message, developerHint) {
@@ -27158,7 +27622,7 @@ async function ensureStripeKey(argv, deps) {
27158
27622
  if (!wantsPlans(argv.only)) return { status: "not-needed" };
27159
27623
  let resolved;
27160
27624
  try {
27161
- resolved = loadConfigV2(path81.resolve(process.cwd(), argv.dir)).resolved;
27625
+ resolved = loadConfigV2(path82.resolve(process.cwd(), argv.dir)).resolved;
27162
27626
  } catch {
27163
27627
  return { status: "not-needed" };
27164
27628
  }
@@ -28095,7 +28559,7 @@ init_invariant();
28095
28559
 
28096
28560
  // src/cli/portal/report.ts
28097
28561
  import { access as access9 } from "node:fs/promises";
28098
- import path82 from "node:path";
28562
+ import path83 from "node:path";
28099
28563
 
28100
28564
  // src/cli/deploy/identity.ts
28101
28565
  init_invariant();
@@ -28106,6 +28570,16 @@ var IdentityDeployError = class extends ApitogoError {
28106
28570
  this.name = "IdentityDeployError";
28107
28571
  }
28108
28572
  };
28573
+ var IDENTITY_NOT_ACTIVE = "IDENTITY_NOT_ACTIVE";
28574
+ async function identityLost(client, projectId) {
28575
+ if (!client.listProjectIdentityConnections) return false;
28576
+ try {
28577
+ await client.listProjectIdentityConnections(projectId);
28578
+ return false;
28579
+ } catch (error) {
28580
+ return error instanceof PlatformApiError && error.status === 409 && error.problem?.code === IDENTITY_NOT_ACTIVE;
28581
+ }
28582
+ }
28109
28583
  var IDENTITY_STEPS = ["request", "activate"];
28110
28584
  var IDENTITY_CONNECTIONS_STEP = "connections";
28111
28585
  var SECRET_PREFIX = "secret:";
@@ -28305,10 +28779,13 @@ async function deployIdentity(resolved, projectId, deps, options = {}) {
28305
28779
  return rejected2(error);
28306
28780
  }
28307
28781
  },
28308
- // An identity that was removed, or that failed, is not a result worth keeping.
28782
+ // An identity that was removed, or that failed, is not a result worth keeping. Neither is one the platform still
28783
+ // lists as Active while the identity service lost it (API-132): asking again makes it again.
28309
28784
  stillValid: async () => {
28310
28785
  const found = await client.getProjectIdentity(projectId);
28311
- return found !== null && found.status !== "Failed" && found.status !== "Released" && found.status !== "Releasing";
28786
+ if (found === null || found.status === "Failed" || found.status === "Released" || found.status === "Releasing")
28787
+ return false;
28788
+ return found.status !== "Active" || !await identityLost(client, projectId);
28312
28789
  }
28313
28790
  };
28314
28791
  const activateStep = {
@@ -28866,7 +29343,7 @@ async function planPortalService(resolved, ledger) {
28866
29343
  const hash2 = await portalBuildHash(resolved, prepared);
28867
29344
  const previous = stepOf(ledger, "portal", "build");
28868
29345
  const built = await exists5(
28869
- path82.join(resolved.state.buildDir, "index.html")
29346
+ path83.join(resolved.state.buildDir, "index.html")
28870
29347
  );
28871
29348
  if (previous?.status === "succeeded" && previous.hash === hash2 && built) {
28872
29349
  return {
@@ -29238,6 +29715,25 @@ async function statusV2(dir, only, options = {}) {
29238
29715
  const ledger = await readLedger(resolved.state);
29239
29716
  const manifest = await readManifest(resolved.state);
29240
29717
  const services = [];
29718
+ const manifestSummary = {
29719
+ present: manifest !== void 0,
29720
+ schemaVersion: manifest?.schemaVersion,
29721
+ generatedAt: manifest?.generatedAt
29722
+ };
29723
+ const projectId = resolved.project.id ?? manifest?.project.id;
29724
+ if (options.projectRemote && projectId) {
29725
+ try {
29726
+ await options.projectRemote(projectId);
29727
+ } catch (error) {
29728
+ if (error instanceof ProjectGoneError)
29729
+ return {
29730
+ configPath: resolved.configPath,
29731
+ manifest: manifestSummary,
29732
+ project: { state: "gone", id: projectId, message: error.message },
29733
+ services: []
29734
+ };
29735
+ }
29736
+ }
29241
29737
  for (const service of chooseServices(only)) {
29242
29738
  const section = sectionOf(resolved, service);
29243
29739
  if (!section) {
@@ -29252,7 +29748,7 @@ async function statusV2(dir, only, options = {}) {
29252
29748
  if (service === "portal") {
29253
29749
  const build2 = stepOf(ledger, "portal", "build");
29254
29750
  const built = await exists5(
29255
- path82.join(resolved.state.buildDir, "index.html")
29751
+ path83.join(resolved.state.buildDir, "index.html")
29256
29752
  );
29257
29753
  const state = build2?.status === "failed" ? "build-failed" : build2?.status === "succeeded" && built ? "built" : "not-built";
29258
29754
  services.push({
@@ -29263,7 +29759,7 @@ async function statusV2(dir, only, options = {}) {
29263
29759
  ...build2?.finishedAt ? [`last build: ${build2.finishedAt}`] : [],
29264
29760
  ...build2?.status === "failed" ? [`error: ${build2.error ?? "unknown"}`] : [],
29265
29761
  ...state === "built" ? [
29266
- `output: ${path82.relative(resolved.configDir, resolved.state.buildDir)}`
29762
+ `output: ${path83.relative(resolved.configDir, resolved.state.buildDir)}`
29267
29763
  ] : [],
29268
29764
  ...url2 ? [`url: ${url2}`] : ["url: not known (portal is not deployed yet)"],
29269
29765
  ...await hostingLine(resolved, manifest, options.hostingRemote)
@@ -29301,11 +29797,7 @@ async function statusV2(dir, only, options = {}) {
29301
29797
  }
29302
29798
  return {
29303
29799
  configPath: resolved.configPath,
29304
- manifest: {
29305
- present: manifest !== void 0,
29306
- schemaVersion: manifest?.schemaVersion,
29307
- generatedAt: manifest?.generatedAt
29308
- },
29800
+ manifest: manifestSummary,
29309
29801
  services
29310
29802
  };
29311
29803
  }
@@ -29319,6 +29811,8 @@ function formatPlan(output4, options = {}) {
29319
29811
  ]);
29320
29812
  }
29321
29813
  function formatStatus(output4) {
29814
+ if (output4.project?.state === "gone")
29815
+ return [`project: gone`, ` ${output4.project.message}`];
29322
29816
  return [
29323
29817
  output4.manifest.present ? `services.json: version ${output4.manifest.schemaVersion}${output4.manifest.generatedAt ? `, written ${output4.manifest.generatedAt}` : ""}` : "services.json: none yet",
29324
29818
  ...output4.services.flatMap((svc) => [
@@ -29330,7 +29824,7 @@ function formatStatus(output4) {
29330
29824
 
29331
29825
  // src/cli/deploy/static-web-config.ts
29332
29826
  import { access as access10, writeFile as writeFile22 } from "node:fs/promises";
29333
- import path83 from "node:path";
29827
+ import path84 from "node:path";
29334
29828
  var STATIC_WEB_CONFIG_FILE = "staticwebapp.config.json";
29335
29829
  var STATIC_WEB_CONFIG_VERSION = 3;
29336
29830
  var CLIENT_ONLY_ROUTES = [
@@ -29348,7 +29842,7 @@ var DEFAULT_STATIC_WEB_CONFIG = {
29348
29842
  }
29349
29843
  };
29350
29844
  async function ensureStaticWebConfig(buildDir) {
29351
- const file = path83.join(buildDir, STATIC_WEB_CONFIG_FILE);
29845
+ const file = path84.join(buildDir, STATIC_WEB_CONFIG_FILE);
29352
29846
  try {
29353
29847
  await access10(file);
29354
29848
  return false;
@@ -29471,6 +29965,15 @@ async function deployV2(options, deps) {
29471
29965
  results.push(await deployPlansService(options, deps, projectId, named));
29472
29966
  continue;
29473
29967
  }
29968
+ if (portalOnlyLinksExternalDocs(resolved)) {
29969
+ results.push({
29970
+ service: "portal",
29971
+ status: "skipped",
29972
+ reason: "docs are on an external site and no service feeds the console, so there is nothing to host",
29973
+ steps: []
29974
+ });
29975
+ continue;
29976
+ }
29474
29977
  results.push(await deployPortal(options, deps, projectId));
29475
29978
  }
29476
29979
  const gatewayAt = results.findIndex(
@@ -29703,7 +30206,7 @@ function formatDeploy(output4) {
29703
30206
  // src/cli/deploy/command.ts
29704
30207
  async function ensureLinkedProject(argv, deps) {
29705
30208
  if (argv.projectId?.trim() || deps.envProjectId?.trim()) return true;
29706
- const dir = path84.resolve(process.cwd(), argv.dir);
30209
+ const dir = path85.resolve(process.cwd(), argv.dir);
29707
30210
  let linked;
29708
30211
  try {
29709
30212
  linked = Boolean(loadConfigV2(dir).resolved.project.id);
@@ -29754,7 +30257,7 @@ async function deployProject(argv) {
29754
30257
  try {
29755
30258
  const config2 = loadConfig();
29756
30259
  const client = new PlatformApiClient(config2);
29757
- const dir = path84.resolve(process.cwd(), argv.dir);
30260
+ const dir = path85.resolve(process.cwd(), argv.dir);
29758
30261
  const deploy = (only) => deployV2(
29759
30262
  {
29760
30263
  dir,
@@ -29862,7 +30365,7 @@ var deploy_default = {
29862
30365
  }),
29863
30366
  handler: async (argv) => {
29864
30367
  await captureEvent({ argv, event: "apitogo deploy" });
29865
- if (!await isLegacyConfig(path85.resolve(process.cwd(), argv.dir))) {
30368
+ if (!await isLegacyConfig(path86.resolve(process.cwd(), argv.dir))) {
29866
30369
  await deployProject(argv);
29867
30370
  return;
29868
30371
  }
@@ -29872,7 +30375,7 @@ var deploy_default = {
29872
30375
 
29873
30376
  // src/cli/export/handler.ts
29874
30377
  import { mkdir as mkdir19, writeFile as writeFile23 } from "node:fs/promises";
29875
- import path86 from "node:path";
30378
+ import path87 from "node:path";
29876
30379
  init_invariant();
29877
30380
  var ExportError = class extends ApitogoError {
29878
30381
  constructor(message, developerHint) {
@@ -29886,7 +30389,7 @@ async function exportOpenApi(args, deps) {
29886
30389
  `Cannot export "${args.target}".`,
29887
30390
  "Only `apitogo export openapi` exists."
29888
30391
  );
29889
- const dir = path86.resolve(process.cwd(), args.dir ?? ".");
30392
+ const dir = path87.resolve(process.cwd(), args.dir ?? ".");
29890
30393
  if (!await isConfigV2(dir))
29891
30394
  throw new ExportError(
29892
30395
  "This command needs a config version 2 project.",
@@ -29911,8 +30414,8 @@ async function exportOpenApi(args, deps) {
29911
30414
  throw error;
29912
30415
  }
29913
30416
  if (!args.out) return { text: text2 };
29914
- const file = path86.resolve(process.cwd(), args.out);
29915
- await mkdir19(path86.dirname(file), { recursive: true });
30417
+ const file = path87.resolve(process.cwd(), args.out);
30418
+ await mkdir19(path87.dirname(file), { recursive: true });
29916
30419
  await writeFile23(file, text2);
29917
30420
  return { text: text2, file };
29918
30421
  }
@@ -30005,8 +30508,8 @@ import open3 from "open";
30005
30508
  import colors14 from "picocolors";
30006
30509
 
30007
30510
  // src/cli/platform/device-auth.ts
30008
- function url(apiUrl, path99) {
30009
- return `${apiUrl}${path99.startsWith("/") ? path99 : `/${path99}`}`;
30511
+ function url(apiUrl, path100) {
30512
+ return `${apiUrl}${path100.startsWith("/") ? path100 : `/${path100}`}`;
30010
30513
  }
30011
30514
  async function requestDeviceCode(apiUrl) {
30012
30515
  const response = await fetchWithRetry(
@@ -30188,7 +30691,7 @@ var logout_default = {
30188
30691
  };
30189
30692
 
30190
30693
  // src/cli/portal/commands.ts
30191
- import path87 from "node:path";
30694
+ import path88 from "node:path";
30192
30695
  init_invariant();
30193
30696
  function parseOnly(value) {
30194
30697
  if (value === void 0) return void 0;
@@ -30205,7 +30708,7 @@ function parseOnly(value) {
30205
30708
  async function run2(command2, argv, compute, format) {
30206
30709
  const json = argv.json === true;
30207
30710
  try {
30208
- const dir = path87.resolve(process.cwd(), argv.dir ?? ".");
30711
+ const dir = path88.resolve(process.cwd(), argv.dir ?? ".");
30209
30712
  if (!await isConfigV2(dir)) {
30210
30713
  throw new ServiceEditError(
30211
30714
  `${command2} works with a config version 2 project, and there is none in ${dir}.`,
@@ -30243,6 +30746,14 @@ var plan = (argv) => run2(
30243
30746
  }),
30244
30747
  (output4) => formatPlan(output4, { verbose: argv.verbose === true })
30245
30748
  );
30749
+ function projectRemote() {
30750
+ try {
30751
+ const client = new PlatformApiClient(loadConfig());
30752
+ return (projectId) => client.getProject(projectId);
30753
+ } catch {
30754
+ return void 0;
30755
+ }
30756
+ }
30246
30757
  function gatewayRemote() {
30247
30758
  try {
30248
30759
  const client = new PlatformApiClient(loadConfig());
@@ -30300,6 +30811,7 @@ var statusOfProject = (argv) => run2(
30300
30811
  "status",
30301
30812
  argv,
30302
30813
  (dir, only) => statusV2(dir, only, {
30814
+ projectRemote: projectRemote(),
30303
30815
  hostingRemote: hostingRemote(),
30304
30816
  gatewayRemote: gatewayRemote(),
30305
30817
  identityRemote: identityRemote(),
@@ -30421,7 +30933,7 @@ async function switchProject(options, deps) {
30421
30933
  try {
30422
30934
  project = await deps.getProject(projectId);
30423
30935
  } catch (error) {
30424
- if (error instanceof PlatformApiError && (error.status === 404 || error.status === 403))
30936
+ if (error instanceof ProjectGoneError || error instanceof PlatformApiError && (error.status === 404 || error.status === 403))
30425
30937
  throw new ProjectUseError(
30426
30938
  "PROJECT_NOT_FOUND",
30427
30939
  `No project ${projectId} is yours.`,
@@ -30567,14 +31079,14 @@ var remove_default = {
30567
31079
 
30568
31080
  // src/cli/dev/handler.ts
30569
31081
  init_joinUrl();
30570
- import path90 from "node:path";
31082
+ import path91 from "node:path";
30571
31083
 
30572
31084
  // src/vite/dev-server.ts
30573
31085
  init_logger();
30574
31086
  import fs11 from "node:fs/promises";
30575
31087
  import http from "node:http";
30576
31088
  import https from "node:https";
30577
- import path89 from "node:path";
31089
+ import path90 from "node:path";
30578
31090
  import { createHttpTerminator } from "http-terminator";
30579
31091
  import {
30580
31092
  createServer as createViteServer,
@@ -30619,7 +31131,7 @@ function withSsrGraphParts(config2, ssrConfig) {
30619
31131
  // src/vite/pagefind-dev-index.ts
30620
31132
  init_invariant();
30621
31133
  init_joinUrl();
30622
- import path88 from "node:path";
31134
+ import path89 from "node:path";
30623
31135
  import { createIndex as createIndex2 } from "pagefind";
30624
31136
  import { isRunnableDevEnvironment } from "vite";
30625
31137
  async function* buildPagefindDevIndex(vite, config2) {
@@ -30672,7 +31184,7 @@ async function* buildPagefindDevIndex(vite, config2) {
30672
31184
  path: urlPath
30673
31185
  };
30674
31186
  }
30675
- const outputPath = path88.join(vite.config.publicDir, "pagefind");
31187
+ const outputPath = path89.join(vite.config.publicDir, "pagefind");
30676
31188
  await pagefindIndex.writeFiles({ outputPath });
30677
31189
  yield { type: "complete", success: true, indexed };
30678
31190
  }
@@ -30691,9 +31203,9 @@ var DevServer = class {
30691
31203
  this.protocol = "https";
30692
31204
  const { dir } = this.options;
30693
31205
  const [key, cert, ca] = await Promise.all([
30694
- fs11.readFile(path89.resolve(dir, config2.https.key)),
30695
- fs11.readFile(path89.resolve(dir, config2.https.cert)),
30696
- config2.https.ca ? fs11.readFile(path89.resolve(dir, config2.https.ca)) : void 0
31206
+ fs11.readFile(path90.resolve(dir, config2.https.key)),
31207
+ fs11.readFile(path90.resolve(dir, config2.https.cert)),
31208
+ config2.https.ca ? fs11.readFile(path90.resolve(dir, config2.https.ca)) : void 0
30697
31209
  ]);
30698
31210
  return https.createServer({ key, cert, ca });
30699
31211
  }
@@ -30710,7 +31222,7 @@ var DevServer = class {
30710
31222
  );
30711
31223
  const server = await this.createNodeServer(config2);
30712
31224
  if (config2.search?.type === "pagefind" && viteConfig.publicDir !== false) {
30713
- const publicRoot = path89.resolve(
31225
+ const publicRoot = path90.resolve(
30714
31226
  this.options.dir,
30715
31227
  typeof viteConfig.publicDir === "string" ? viteConfig.publicDir : "public"
30716
31228
  );
@@ -30727,7 +31239,7 @@ var DevServer = class {
30727
31239
  // built-in transform middleware which would treat the path as a static asset.
30728
31240
  name: "apitogo:entry-client",
30729
31241
  configureServer(server2) {
30730
- const entryPath = path89.posix.join(
31242
+ const entryPath = path90.posix.join(
30731
31243
  server2.config.base,
30732
31244
  "/__z/entry.client.tsx"
30733
31245
  );
@@ -30895,7 +31407,7 @@ init_package_json();
30895
31407
  async function dev(argv) {
30896
31408
  const packageJson3 = getZudokuPackageJson();
30897
31409
  process.env.NODE_ENV = "development";
30898
- let dir = path90.resolve(process.cwd(), argv.dir);
31410
+ let dir = path91.resolve(process.cwd(), argv.dir);
30899
31411
  if (await isConfigV2(dir)) {
30900
31412
  const { root, prepared } = await preparePortalWorkspace(dir, "development");
30901
31413
  dir = root;
@@ -31030,12 +31542,12 @@ var secrets_default = {
31030
31542
  };
31031
31543
 
31032
31544
  // src/cli/cmds/status.ts
31033
- import path92 from "node:path";
31545
+ import path93 from "node:path";
31034
31546
 
31035
31547
  // src/cli/status/handler.ts
31036
- import path91 from "node:path";
31548
+ import path92 from "node:path";
31037
31549
  async function status(argv) {
31038
- const sourceDirectory = path91.resolve(process.cwd(), argv.dir);
31550
+ const sourceDirectory = path92.resolve(process.cwd(), argv.dir);
31039
31551
  let config2;
31040
31552
  try {
31041
31553
  config2 = loadConfig();
@@ -31161,7 +31673,7 @@ var status_default = {
31161
31673
  }),
31162
31674
  handler: async (argv) => {
31163
31675
  await captureEvent({ argv, event: "apitogo status" });
31164
- if (await isConfigV2(path92.resolve(process.cwd(), argv.dir))) {
31676
+ if (await isConfigV2(path93.resolve(process.cwd(), argv.dir))) {
31165
31677
  await statusOfProject(argv);
31166
31678
  return;
31167
31679
  }
@@ -31170,11 +31682,11 @@ var status_default = {
31170
31682
  };
31171
31683
 
31172
31684
  // src/cli/cmds/teardown.ts
31173
- import path95 from "node:path";
31685
+ import path96 from "node:path";
31174
31686
 
31175
31687
  // src/cli/teardown/command.ts
31176
31688
  init_invariant();
31177
- import path93 from "node:path";
31689
+ import path94 from "node:path";
31178
31690
  import { createInterface as createInterface7 } from "node:readline/promises";
31179
31691
 
31180
31692
  // src/cli/teardown/v2.ts
@@ -31535,7 +32047,7 @@ async function teardownProject(argv) {
31535
32047
  const progress = createConsoleProgressReporter();
31536
32048
  const result = await teardownV2(
31537
32049
  {
31538
- dir: path93.resolve(process.cwd(), argv.dir),
32050
+ dir: path94.resolve(process.cwd(), argv.dir),
31539
32051
  projectId: argv.projectId,
31540
32052
  only: Array.isArray(argv.only) ? argv.only.join(",") : argv.only,
31541
32053
  yes: argv.yes === true,
@@ -31594,7 +32106,7 @@ async function teardownProject(argv) {
31594
32106
  }
31595
32107
 
31596
32108
  // src/cli/teardown/handler.ts
31597
- import path94 from "node:path";
32109
+ import path95 from "node:path";
31598
32110
  function resultLooksFailed3(payload) {
31599
32111
  try {
31600
32112
  const parsed = JSON.parse(payload);
@@ -31610,7 +32122,7 @@ async function teardown(argv) {
31610
32122
  );
31611
32123
  return;
31612
32124
  }
31613
- const sourceDirectory = path94.resolve(process.cwd(), argv.dir);
32125
+ const sourceDirectory = path95.resolve(process.cwd(), argv.dir);
31614
32126
  let config2;
31615
32127
  try {
31616
32128
  config2 = loadConfig();
@@ -31688,7 +32200,7 @@ var teardown_default = {
31688
32200
  }),
31689
32201
  handler: async (argv) => {
31690
32202
  await captureEvent({ argv, event: "apitogo teardown" });
31691
- if (!await isLegacyConfig(path95.resolve(process.cwd(), argv.dir))) {
32203
+ if (!await isLegacyConfig(path96.resolve(process.cwd(), argv.dir))) {
31692
32204
  await teardownProject(argv);
31693
32205
  return;
31694
32206
  }
@@ -31698,7 +32210,7 @@ var teardown_default = {
31698
32210
 
31699
32211
  // src/cli/teardown/reset-command.ts
31700
32212
  init_invariant();
31701
- import path96 from "node:path";
32213
+ import path97 from "node:path";
31702
32214
 
31703
32215
  // src/cli/teardown/reset.ts
31704
32216
  init_invariant();
@@ -31821,7 +32333,7 @@ async function resetProject(argv) {
31821
32333
  const config2 = loadConfig();
31822
32334
  const result = await resetProjectV2(
31823
32335
  {
31824
- dir: path96.resolve(process.cwd(), argv.dir),
32336
+ dir: path97.resolve(process.cwd(), argv.dir),
31825
32337
  projectId: argv.projectId,
31826
32338
  yes: argv.yes === true
31827
32339
  },
@@ -31885,7 +32397,7 @@ var test_reset_default = {
31885
32397
  };
31886
32398
 
31887
32399
  // src/cli/test-sign-in/handler.ts
31888
- import path97 from "node:path";
32400
+ import path98 from "node:path";
31889
32401
  init_invariant();
31890
32402
  var TestSignInError = class extends ApitogoError {
31891
32403
  constructor(message, developerHint) {
@@ -31896,7 +32408,7 @@ var TestSignInError = class extends ApitogoError {
31896
32408
  async function resolveProjectId3(argv) {
31897
32409
  const explicit = argv.projectId?.trim();
31898
32410
  if (explicit) return explicit;
31899
- const dir = path97.resolve(process.cwd(), argv.dir ?? ".");
32411
+ const dir = path98.resolve(process.cwd(), argv.dir ?? ".");
31900
32412
  if (await isConfigV2(dir)) {
31901
32413
  const id = loadConfigV2(dir).resolved.project.id;
31902
32414
  if (id) return id;
@@ -32036,36 +32548,36 @@ function createApitogoCli(argv) {
32036
32548
  // src/cli/local-handoff.ts
32037
32549
  import { spawn as spawn2 } from "node:child_process";
32038
32550
  import { existsSync as existsSync9, realpathSync as realpathSync2 } from "node:fs";
32039
- import path98 from "node:path";
32551
+ import path99 from "node:path";
32040
32552
  var LOCAL_HANDOFF_ENV = "APITOGO_LOCAL_CLI";
32041
32553
  var PACKAGE_NAME = "@lukoweb/apitogo";
32042
32554
  var resolveProjectDir = (argv, cwd) => {
32043
32555
  for (let i = 0; i < argv.length; i++) {
32044
32556
  const arg = argv[i];
32045
32557
  const next = argv[i + 1];
32046
- if (arg === "--dir" && next) return path98.resolve(cwd, next);
32558
+ if (arg === "--dir" && next) return path99.resolve(cwd, next);
32047
32559
  if (arg?.startsWith("--dir=")) {
32048
- return path98.resolve(cwd, arg.slice("--dir=".length));
32560
+ return path99.resolve(cwd, arg.slice("--dir=".length));
32049
32561
  }
32050
32562
  }
32051
32563
  return cwd;
32052
32564
  };
32053
32565
  var findLocalCli = (projectDir, runningRoot) => {
32054
- let dir = path98.resolve(projectDir);
32566
+ let dir = path99.resolve(projectDir);
32055
32567
  let localPackageRoot;
32056
32568
  while (!localPackageRoot) {
32057
- const candidate = path98.join(dir, "node_modules", PACKAGE_NAME);
32058
- if (existsSync9(path98.join(candidate, "package.json"))) {
32569
+ const candidate = path99.join(dir, "node_modules", PACKAGE_NAME);
32570
+ if (existsSync9(path99.join(candidate, "package.json"))) {
32059
32571
  localPackageRoot = candidate;
32060
- } else if (dir === path98.dirname(dir)) {
32572
+ } else if (dir === path99.dirname(dir)) {
32061
32573
  return void 0;
32062
32574
  } else {
32063
- dir = path98.dirname(dir);
32575
+ dir = path99.dirname(dir);
32064
32576
  }
32065
32577
  }
32066
32578
  const localRoot = realpathSync2(localPackageRoot);
32067
32579
  if (localRoot === realpathSync2(runningRoot)) return void 0;
32068
- return path98.join(localRoot, "cli.js");
32580
+ return path99.join(localRoot, "cli.js");
32069
32581
  };
32070
32582
  var handoffToLocalCli = async (argv, runningRoot) => {
32071
32583
  if (process.env[LOCAL_HANDOFF_ENV] || process.env.ZUDOKU_ENV === "internal") {
@@ -32077,7 +32589,7 @@ var handoffToLocalCli = async (argv, runningRoot) => {
32077
32589
  );
32078
32590
  if (!localCli) return void 0;
32079
32591
  printDiagnosticsToConsole(
32080
- `Using the project's local ${PACKAGE_NAME} (${path98.dirname(localCli)})`
32592
+ `Using the project's local ${PACKAGE_NAME} (${path99.dirname(localCli)})`
32081
32593
  );
32082
32594
  const child = spawn2(process.execPath, [localCli, ...argv], {
32083
32595
  stdio: "inherit",