@lukoweb/apitogo 0.2.10 → 0.2.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli/cli.js CHANGED
@@ -7489,6 +7489,157 @@ async function removeServicesFromManifest(state, names) {
7489
7489
  await writeManifest(state, { ...existing, services });
7490
7490
  }
7491
7491
 
7492
+ // src/config/v2/gateway-reference.ts
7493
+ import { readFile as readFile4 } from "node:fs/promises";
7494
+ import { parse } from "yaml";
7495
+ var DEFAULT_KEY_HEADER = "Ocp-Apim-Subscription-Key";
7496
+ var METHODS = [
7497
+ "get",
7498
+ "put",
7499
+ "post",
7500
+ "delete",
7501
+ "options",
7502
+ "head",
7503
+ "patch",
7504
+ "trace"
7505
+ ];
7506
+ var isObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
7507
+ function actionKey(method, path100) {
7508
+ const trimmed = path100.trim();
7509
+ const withSlash = trimmed.startsWith("/") ? trimmed : `/${trimmed}`;
7510
+ const normalized = withSlash.length > 1 ? withSlash.replace(/\/+$/, "") || "/" : withSlash;
7511
+ return `${method.trim().toLowerCase()}:${normalized}`;
7512
+ }
7513
+ function configActionKey(entry) {
7514
+ const match = /^\s*([A-Za-z]+)\s+(\S+)\s*$/.exec(entry);
7515
+ return match?.[1] && match[2] ? actionKey(match[1], match[2]) : void 0;
7516
+ }
7517
+ function referenceOptions(gateway, gatewayBaseUrl, keyHeaderName) {
7518
+ const access16 = gateway.policies.access;
7519
+ const jwt = access16?.jwt ?? gateway.auth !== void 0;
7520
+ const keys = (list2) => new Set(
7521
+ (list2 ?? []).flatMap((entry) => {
7522
+ const key = configActionKey(entry);
7523
+ return key ? [key] : [];
7524
+ })
7525
+ );
7526
+ return {
7527
+ gatewayBaseUrl,
7528
+ originUrl: gateway.origin.url,
7529
+ jwtRequired: jwt && gateway.auth !== void 0,
7530
+ oidcMetadataUrl: jwt ? gateway.auth?.metadataUrl : void 0,
7531
+ apiKeyRequired: access16?.apiKey ?? true,
7532
+ apiKeyHeader: keyHeaderName ?? access16?.apiKeyHeader ?? DEFAULT_KEY_HEADER,
7533
+ publicOperations: keys(gateway.policies.operations?.public),
7534
+ deniedOperations: keys(gateway.policies.operations?.deny)
7535
+ };
7536
+ }
7537
+ var ReferenceSpecError = class extends Error {
7538
+ constructor(message) {
7539
+ super(message);
7540
+ this.name = "ReferenceSpecError";
7541
+ }
7542
+ };
7543
+ function operationsOf(paths) {
7544
+ return Object.entries(paths).flatMap(
7545
+ ([path100, item]) => isObject(item) ? METHODS.filter((method) => isObject(item[method])).map(
7546
+ (method) => [path100, item, method]
7547
+ ) : []
7548
+ );
7549
+ }
7550
+ function buildReferenceSpec(specText, options) {
7551
+ let root;
7552
+ try {
7553
+ root = parse(specText);
7554
+ } catch {
7555
+ throw new ReferenceSpecError("The OpenAPI file could not be read.");
7556
+ }
7557
+ if (!isObject(root) || typeof root.openapi !== "string")
7558
+ throw new ReferenceSpecError("The OpenAPI file could not be read.");
7559
+ const doc = structuredClone(root);
7560
+ const paths = isObject(doc.paths) ? doc.paths : {};
7561
+ for (const [path100, item, method] of operationsOf(paths))
7562
+ if (options.deniedOperations.has(actionKey(method, path100)))
7563
+ delete item[method];
7564
+ for (const [path100, item] of Object.entries(paths))
7565
+ if (!isObject(item) || !METHODS.some((method) => method in item))
7566
+ delete paths[path100];
7567
+ for (const [path100, item, method] of operationsOf(paths))
7568
+ if (options.publicOperations.has(actionKey(method, path100)))
7569
+ item[method].security = [];
7570
+ doc.servers = [{ url: options.gatewayBaseUrl.replace(/\/+$/, "") }];
7571
+ for (const item of Object.values(paths)) {
7572
+ if (!isObject(item)) continue;
7573
+ delete item.servers;
7574
+ for (const method of METHODS) {
7575
+ const operation = item[method];
7576
+ if (isObject(operation)) delete operation.servers;
7577
+ }
7578
+ }
7579
+ const schemes = {};
7580
+ const requirement = {};
7581
+ if (options.jwtRequired) {
7582
+ schemes.bearerAuth = {
7583
+ type: "http",
7584
+ scheme: "bearer",
7585
+ bearerFormat: "JWT"
7586
+ };
7587
+ requirement.bearerAuth = [];
7588
+ }
7589
+ if (options.oidcMetadataUrl)
7590
+ schemes.openIdAuth = {
7591
+ type: "openIdConnect",
7592
+ openIdConnectUrl: options.oidcMetadataUrl
7593
+ };
7594
+ if (options.apiKeyRequired) {
7595
+ schemes.apiKeyAuth = {
7596
+ type: "apiKey",
7597
+ in: "header",
7598
+ name: options.apiKeyHeader
7599
+ };
7600
+ requirement.apiKeyAuth = [];
7601
+ }
7602
+ const components = isObject(doc.components) ? doc.components : {};
7603
+ if (Object.keys(schemes).length > 0) components.securitySchemes = schemes;
7604
+ else delete components.securitySchemes;
7605
+ if (Object.keys(components).length > 0) doc.components = components;
7606
+ else delete doc.components;
7607
+ doc.security = Object.keys(requirement).length > 0 ? [requirement] : [];
7608
+ for (const [, item, method] of operationsOf(paths)) {
7609
+ const operation = item[method];
7610
+ if (Array.isArray(operation.security) && operation.security.length > 0)
7611
+ delete operation.security;
7612
+ }
7613
+ return JSON.parse(hideOrigin(JSON.stringify(doc), options));
7614
+ }
7615
+ function hideOrigin(text, options) {
7616
+ const origin = options.originUrl?.trim().replace(/\/+$/, "");
7617
+ if (!origin) return text;
7618
+ const gateway = options.gatewayBaseUrl.trim().replace(/\/+$/, "");
7619
+ const pattern = new RegExp(
7620
+ origin.replace(/[.*+?^${}()|[\]\\/]/g, "\\$&"),
7621
+ "gi"
7622
+ );
7623
+ return text.replace(pattern, () => gateway);
7624
+ }
7625
+ async function renderReferenceSpec(gateway, gatewayBaseUrl, keyHeaderName) {
7626
+ const file = gateway.specs[0];
7627
+ if (!file)
7628
+ throw new ReferenceSpecError("gateway.specs lists no OpenAPI file.");
7629
+ let text;
7630
+ try {
7631
+ text = await readFile4(file, "utf8");
7632
+ } catch {
7633
+ throw new ReferenceSpecError(`Cannot read the OpenAPI file ${file}.`);
7634
+ }
7635
+ const spec = buildReferenceSpec(
7636
+ text,
7637
+ referenceOptions(gateway, gatewayBaseUrl, keyHeaderName)
7638
+ );
7639
+ return `${JSON.stringify(spec, null, 2)}
7640
+ `;
7641
+ }
7642
+
7492
7643
  // src/config/v2/gateway-request.ts
7493
7644
  function derivedCorsSites(sites) {
7494
7645
  const originOf = (url2) => {
@@ -7548,7 +7699,7 @@ function stableJson(value) {
7548
7699
  }
7549
7700
 
7550
7701
  // src/config/v2/wrapper.ts
7551
- import { mkdir as mkdir4, readFile as readFile4, rename as rename2, rm as rm2, writeFile as writeFile5 } from "node:fs/promises";
7702
+ import { mkdir as mkdir4, readFile as readFile5, rename as rename2, rm as rm2, writeFile as writeFile5 } from "node:fs/promises";
7552
7703
  import path11 from "node:path";
7553
7704
  var WRAPPER_FILE = "apitogo.config.tsx";
7554
7705
  var CONTENT_LINK = "content";
@@ -7613,16 +7764,30 @@ var DEFAULT_SCOPES = ["openid", "profile", "email"];
7613
7764
  function planPortal(profile, manifest) {
7614
7765
  const skipped = [];
7615
7766
  const skip = (piece, reason) => void skipped.push({ piece, reason });
7616
- const { identity, plans } = manifest.services;
7767
+ const { identity, plans, gateway } = manifest.services;
7617
7768
  const plan2 = {
7618
7769
  docs: profile.modules.docs,
7619
- apiReference: profile.pipelines.openapi,
7770
+ apiReference: false,
7620
7771
  userPanel: false,
7621
7772
  signup: false,
7622
7773
  skipped,
7623
7774
  preview: manifest.preview === true
7624
7775
  };
7625
7776
  if (!profile.enabled) return plan2;
7777
+ if (profile.pipelines.openapi) {
7778
+ if (!gateway) skip("api reference", "services.json has no gateway entry");
7779
+ else if (!serviceReady(gateway))
7780
+ skip("api reference", `gateway is ${gateway.status}, not ready`);
7781
+ else if (!gateway.baseUrl)
7782
+ skip("api reference", "the gateway entry has no base URL yet");
7783
+ else {
7784
+ plan2.apiReference = true;
7785
+ plan2.gatewayReference = {
7786
+ baseUrl: gateway.baseUrl,
7787
+ keyHeaderName: gateway.keyHeaderName
7788
+ };
7789
+ }
7790
+ }
7626
7791
  if (!profile.console.enabled) {
7627
7792
  skip("console", "portal.console.enabled is false");
7628
7793
  return plan2;
@@ -7663,6 +7828,8 @@ function planPortal(profile, manifest) {
7663
7828
  return plan2;
7664
7829
  }
7665
7830
  var SITE_CONFIG_FILE = "apitogo.config.json";
7831
+ var REFERENCE_SPEC_FILE = "openapi/gateway.json";
7832
+ var API_REFERENCE_PATH = "api";
7666
7833
  function buildSiteConfig(resolved, plan2, theme) {
7667
7834
  const portal = resolved.portal;
7668
7835
  return {
@@ -7681,6 +7848,22 @@ function buildSiteConfig(resolved, plan2, theme) {
7681
7848
  )
7682
7849
  }
7683
7850
  } : {},
7851
+ ...plan2.apiReference ? {
7852
+ apis: [
7853
+ {
7854
+ type: "file",
7855
+ input: `./${REFERENCE_SPEC_FILE}`,
7856
+ path: API_REFERENCE_PATH
7857
+ }
7858
+ ],
7859
+ navigation: [
7860
+ {
7861
+ type: "link",
7862
+ to: `/${API_REFERENCE_PATH}`,
7863
+ label: "API Reference"
7864
+ }
7865
+ ]
7866
+ } : {},
7684
7867
  ...theme !== void 0 ? { theme } : {},
7685
7868
  ...plan2.auth ? { authentication: { type: "openid", ...plan2.auth } } : {},
7686
7869
  ...plan2.preview ? { preview: true } : {}
@@ -7714,7 +7897,7 @@ async function readTheme(resolved) {
7714
7897
  const file = resolved.portal?.theme;
7715
7898
  if (!file) return void 0;
7716
7899
  try {
7717
- return JSON.parse(await readFile4(file, "utf8"));
7900
+ return JSON.parse(await readFile5(file, "utf8"));
7718
7901
  } catch {
7719
7902
  return void 0;
7720
7903
  }
@@ -7754,6 +7937,24 @@ async function preparePortal(resolved, manifest, mode, options = {}) {
7754
7937
  effective = { ...effective, preview: void 0, services: {} };
7755
7938
  }
7756
7939
  const plan2 = planPortal(profile, effective);
7940
+ let referenceSpec;
7941
+ if (plan2.apiReference && plan2.gatewayReference && resolved.gateway) {
7942
+ try {
7943
+ referenceSpec = await renderReferenceSpec(
7944
+ resolved.gateway,
7945
+ plan2.gatewayReference.baseUrl,
7946
+ plan2.gatewayReference.keyHeaderName
7947
+ );
7948
+ } catch (error) {
7949
+ if (!(error instanceof ReferenceSpecError)) throw error;
7950
+ plan2.apiReference = false;
7951
+ plan2.gatewayReference = void 0;
7952
+ plan2.skipped.push({
7953
+ piece: "api reference",
7954
+ reason: `${error.message} The API page is left out.`
7955
+ });
7956
+ }
7957
+ }
7757
7958
  const siteConfig = buildSiteConfig(resolved, plan2, await readTheme(resolved));
7758
7959
  const wrapper = renderWrapper(plan2);
7759
7960
  const wrapperPath = path11.join(resolved.state.generatedDir, WRAPPER_FILE);
@@ -7764,6 +7965,16 @@ async function preparePortal(resolved, manifest, mode, options = {}) {
7764
7965
  `${JSON.stringify(siteConfig, null, 2)}
7765
7966
  `
7766
7967
  );
7968
+ const referenceFile = path11.join(
7969
+ path11.dirname(wrapperPath),
7970
+ REFERENCE_SPEC_FILE
7971
+ );
7972
+ if (referenceSpec !== void 0) {
7973
+ await mkdir4(path11.dirname(referenceFile), { recursive: true });
7974
+ await writeFile5(referenceFile, referenceSpec);
7975
+ } else {
7976
+ await rm2(referenceFile, { force: true });
7977
+ }
7767
7978
  const temporary = `${wrapperPath}.${process.pid}.tmp`;
7768
7979
  try {
7769
7980
  await writeFile5(temporary, wrapper, "utf8");
@@ -7778,6 +7989,7 @@ async function preparePortal(resolved, manifest, mode, options = {}) {
7778
7989
  plan: plan2,
7779
7990
  wrapper,
7780
7991
  siteConfig,
7992
+ referenceSpec,
7781
7993
  // Without a console there are no console links to offer, so a docs-only portal ships no manifest.
7782
7994
  publishedManifest: manifestSource === "file" && profile.console.enabled ? publishedManifest(effective, plan2) : void 0,
7783
7995
  wrapperPath,
@@ -7787,7 +7999,7 @@ async function preparePortal(resolved, manifest, mode, options = {}) {
7787
7999
 
7788
8000
  // src/config/v2/workspace.ts
7789
8001
  import { createHash as createHash2 } from "node:crypto";
7790
- import { access as access2, lstat, readFile as readFile5, rm as rm3, symlink } from "node:fs/promises";
8002
+ import { access as access2, lstat, readFile as readFile6, rm as rm3, symlink } from "node:fs/promises";
7791
8003
  import path12 from "node:path";
7792
8004
  import { glob as glob2 } from "glob";
7793
8005
  var exists2 = (file) => access2(file).then(
@@ -7816,7 +8028,7 @@ async function materializeWorkspace(resolved, prepared) {
7816
8028
  return root;
7817
8029
  }
7818
8030
  async function configFingerprint(configPath) {
7819
- const text = await readFile5(configPath, "utf8");
8031
+ const text = await readFile6(configPath, "utf8");
7820
8032
  try {
7821
8033
  const raw = JSON.parse(text);
7822
8034
  if (raw.project) {
@@ -7865,7 +8077,7 @@ async function portalArtifactHash(resolved, manifestText, cliVersion, wrapper =
7865
8077
  for (const file of files) {
7866
8078
  hash2.update(`file:${path12.relative(resolved.configDir, file)}
7867
8079
  `);
7868
- hash2.update(await readFile5(file));
8080
+ hash2.update(await readFile6(file));
7869
8081
  }
7870
8082
  return hash2.digest("hex");
7871
8083
  }
@@ -8332,7 +8544,7 @@ async function clearProjectId(dir, projectId) {
8332
8544
 
8333
8545
  // src/config/v2/ledger.ts
8334
8546
  init_invariant();
8335
- import { mkdir as mkdir6, readFile as readFile6, rename as rename4, rm as rm5, writeFile as writeFile7 } from "node:fs/promises";
8547
+ import { mkdir as mkdir6, readFile as readFile7, rename as rename4, rm as rm5, writeFile as writeFile7 } from "node:fs/promises";
8336
8548
  import path14 from "node:path";
8337
8549
  var LEDGER_VERSION = 1;
8338
8550
  var LedgerError = class extends ApitogoError {
@@ -8349,7 +8561,7 @@ var emptyLedger = () => ({
8349
8561
  async function readLedger(state) {
8350
8562
  let text;
8351
8563
  try {
8352
- text = await readFile6(state.file, "utf8");
8564
+ text = await readFile7(state.file, "utf8");
8353
8565
  } catch (error) {
8354
8566
  if (error.code === "ENOENT")
8355
8567
  return emptyLedger();
@@ -8480,7 +8692,7 @@ init_invariant();
8480
8692
 
8481
8693
  // src/cli/check/v2.ts
8482
8694
  init_apitogo_config_core();
8483
- import { readFile as readFile12 } from "node:fs/promises";
8695
+ import { readFile as readFile13 } from "node:fs/promises";
8484
8696
  import path46 from "node:path";
8485
8697
 
8486
8698
  // src/cli/platform/client/platform-api.ts
@@ -9164,8 +9376,8 @@ var PlatformApiClient = class {
9164
9376
  `/api/v1/projects/${projectId}/gateway/actions?${query.toString()}`
9165
9377
  );
9166
9378
  }
9167
- setGatewayActionPublic(projectId, actionKey, isPublic) {
9168
- const encodedKey = encodeURIComponent(actionKey);
9379
+ setGatewayActionPublic(projectId, actionKey2, isPublic) {
9380
+ const encodedKey = encodeURIComponent(actionKey2);
9169
9381
  return this.request(
9170
9382
  "PATCH",
9171
9383
  `/api/v1/projects/${projectId}/gateway/actions/${encodedKey}`,
@@ -9196,8 +9408,8 @@ var PlatformApiClient = class {
9196
9408
  }
9197
9409
  };
9198
9410
  async function readFileBytes(filePath) {
9199
- const { readFile: readFile29 } = await import("node:fs/promises");
9200
- const buffer = await readFile29(filePath);
9411
+ const { readFile: readFile30 } = await import("node:fs/promises");
9412
+ const buffer = await readFile30(filePath);
9201
9413
  const arrayBuffer = new ArrayBuffer(buffer.byteLength);
9202
9414
  new Uint8Array(arrayBuffer).set(buffer);
9203
9415
  return arrayBuffer;
@@ -9294,7 +9506,7 @@ function resolveProjectId(explicit, config2) {
9294
9506
  import {
9295
9507
  access as access4,
9296
9508
  mkdir as mkdir12,
9297
- readFile as readFile10,
9509
+ readFile as readFile11,
9298
9510
  rename as rename6,
9299
9511
  rm as rm8,
9300
9512
  writeFile as writeFile13
@@ -9303,7 +9515,7 @@ import { createRequire as createRequire2 } from "node:module";
9303
9515
  import path45 from "node:path";
9304
9516
 
9305
9517
  // src/vite/build.ts
9306
- import { mkdir as mkdir11, readFile as readFile9, rename as rename5, rm as rm7, writeFile as writeFile12 } from "node:fs/promises";
9518
+ import { mkdir as mkdir11, readFile as readFile10, rename as rename5, rm as rm7, writeFile as writeFile12 } from "node:fs/promises";
9307
9519
  import path44 from "node:path";
9308
9520
  import { build as esbuild } from "esbuild";
9309
9521
  import { build as viteBuild, mergeConfig as mergeConfig3 } from "vite";
@@ -9451,7 +9663,7 @@ import {
9451
9663
  // package.json
9452
9664
  var package_default = {
9453
9665
  name: "@lukoweb/apitogo",
9454
- version: "0.2.10",
9666
+ version: "0.2.13",
9455
9667
  type: "module",
9456
9668
  sideEffects: [
9457
9669
  "**/*.css",
@@ -11538,15 +11750,15 @@ import { mdxFromMarkdown } from "mdast-util-mdx";
11538
11750
  import { mdxjs } from "micromark-extension-mdxjs";
11539
11751
 
11540
11752
  // src/lib/util/readFrontmatter.ts
11541
- import { readFile as readFile7 } from "node:fs/promises";
11753
+ import { readFile as readFile8 } from "node:fs/promises";
11542
11754
  import matter from "gray-matter";
11543
- import { parse, stringify as stringify2 } from "yaml";
11755
+ import { parse as parse2, stringify as stringify2 } from "yaml";
11544
11756
  var yaml = {
11545
- parse: (input5) => parse(input5) ?? {},
11757
+ parse: (input5) => parse2(input5) ?? {},
11546
11758
  stringify: (obj) => stringify2(obj)
11547
11759
  };
11548
11760
  var readFrontmatter = async (filePath) => {
11549
- const content = await readFile7(filePath, "utf-8");
11761
+ const content = await readFile8(filePath, "utf-8");
11550
11762
  const normalizedContent = content.replace(/\r\n/g, "\n");
11551
11763
  return matter(normalizedContent, { engines: { yaml } });
11552
11764
  };
@@ -12857,7 +13069,7 @@ var remarkInjectFilepath = (rootDir) => (tree, vfile) => {
12857
13069
  import { spawnSync } from "node:child_process";
12858
13070
  import { stat as stat6 } from "node:fs/promises";
12859
13071
  import { visit as visit4 } from "unist-util-visit";
12860
- import { parse as parse2, stringify as stringify4 } from "yaml";
13072
+ import { parse as parse3, stringify as stringify4 } from "yaml";
12861
13073
  var isGitAvailable;
12862
13074
  var hasWarnedShallowClone = false;
12863
13075
  var checkGitAvailable = () => {
@@ -12920,7 +13132,7 @@ var remarkLastModified = () => {
12920
13132
  let hasYaml = false;
12921
13133
  visit4(tree, "yaml", (node) => {
12922
13134
  hasYaml = true;
12923
- const data = parse2(node.value) ?? {};
13135
+ const data = parse3(node.value) ?? {};
12924
13136
  if (!data.lastModifiedTime) {
12925
13137
  data.lastModifiedTime = lastModifiedISO;
12926
13138
  node.value = stringify4(data).trim();
@@ -13820,7 +14032,7 @@ async function writeOutput(dir, {
13820
14032
  // src/vite/prerender/prerender.ts
13821
14033
  init_logger();
13822
14034
  init_file_exists();
13823
- import { readFile as readFile8, rm as rm6 } from "node:fs/promises";
14035
+ import { readFile as readFile9, rm as rm6 } from "node:fs/promises";
13824
14036
  import os from "node:os";
13825
14037
  import path43 from "node:path";
13826
14038
  import { pathToFileURL } from "node:url";
@@ -14096,7 +14308,7 @@ var prerender = async ({
14096
14308
  );
14097
14309
  let markdownFileInfos = [];
14098
14310
  if (await fileExists(markdownInfoPath)) {
14099
- const markdownInfoContent = await readFile8(markdownInfoPath, "utf-8");
14311
+ const markdownInfoContent = await readFile9(markdownInfoPath, "utf-8");
14100
14312
  markdownFileInfos = JSON.parse(markdownInfoContent);
14101
14313
  }
14102
14314
  if (llmsConfig.llmsTxt || llmsConfig.llmsTxtFull) {
@@ -14247,7 +14459,7 @@ var bundleSSREntry = async (options) => {
14247
14459
  const { dir, adapter, serverOutDir, serverConfigFilename, html, basePath } = options;
14248
14460
  const tempEntryPath = path44.join(dir, "__ssr-entry.ts");
14249
14461
  const packageRoot = getZudokuRootDir();
14250
- const templateContent = await readFile9(
14462
+ const templateContent = await readFile10(
14251
14463
  path44.join(packageRoot, "src/vite/ssr-templates", `${adapter}.ts`),
14252
14464
  "utf-8"
14253
14465
  );
@@ -14314,7 +14526,7 @@ async function portalBuildHash(resolved, prepared) {
14314
14526
  resolved,
14315
14527
  void 0,
14316
14528
  getZudokuPackageJson().version,
14317
- prepared.wrapper + JSON.stringify(prepared.siteConfig) + JSON.stringify(prepared.publishedManifest ?? null)
14529
+ prepared.wrapper + JSON.stringify(prepared.siteConfig) + JSON.stringify(prepared.publishedManifest ?? null) + (prepared.referenceSpec ?? "")
14318
14530
  );
14319
14531
  }
14320
14532
  function portalBuildStep(context, options = {}, build2 = runBuild) {
@@ -14369,7 +14581,7 @@ async function buildV2(options) {
14369
14581
  }
14370
14582
 
14371
14583
  // src/cli/check/gateway-online.ts
14372
- import { readFile as readFile11 } from "node:fs/promises";
14584
+ import { readFile as readFile12 } from "node:fs/promises";
14373
14585
  var SPEC_SEND_LIMIT = 5 * 1024 * 1024 + 64 * 1024;
14374
14586
  var issue2 = (code, path100, message, hint) => ({ code, path: path100, message, hint });
14375
14587
  function capabilityIssues(gateway, answer) {
@@ -14425,7 +14637,7 @@ async function onlineGatewayRules(gateway, projectId, api, derivedCors = []) {
14425
14637
  const specFile = gateway.specs[0];
14426
14638
  if (specFile) {
14427
14639
  try {
14428
- const text = await readFile11(specFile, "utf8");
14640
+ const text = await readFile12(specFile, "utf8");
14429
14641
  if (text.length <= SPEC_SEND_LIMIT) spec = text;
14430
14642
  } catch {
14431
14643
  }
@@ -14455,7 +14667,7 @@ async function onlineGatewayRules(gateway, projectId, api, derivedCors = []) {
14455
14667
  async function isConfigV2(dir) {
14456
14668
  try {
14457
14669
  return /"version"\s*:\s*2\b/.test(
14458
- await readFile12(path46.join(dir, APITOGO_CONFIG_FILENAME), "utf8")
14670
+ await readFile13(path46.join(dir, APITOGO_CONFIG_FILENAME), "utf8")
14459
14671
  );
14460
14672
  } catch {
14461
14673
  return false;
@@ -14514,6 +14726,19 @@ async function addGatewayOnlineRules(report, resolved, args, deps) {
14514
14726
  "online rules ran for the gateway only; the other services have none yet"
14515
14727
  );
14516
14728
  }
14729
+ async function addApiReferenceWarning(report, resolved) {
14730
+ const portal = resolved.portal;
14731
+ if (!portal?.enabled || !portal.docs.enabled || portal.docs.provider !== "apitogo" || resolved.gateway?.enabled !== true)
14732
+ return;
14733
+ const entry = (await readManifest(resolved.state))?.services.gateway;
14734
+ if (entry?.baseUrl && (entry.status ?? "ready") === "ready") return;
14735
+ report.warnings.push({
14736
+ code: "PORTAL_API_REFERENCE_SKIPPED",
14737
+ path: "portal.docs",
14738
+ message: "the portal's API page is left out of the build because the gateway has no address yet.",
14739
+ hint: "Run `apitogo deploy --only gateway`, then build the portal again."
14740
+ });
14741
+ }
14517
14742
  async function validatePortalBuild(dir) {
14518
14743
  try {
14519
14744
  await buildV2({ dir, force: true, jsonOnly: true });
@@ -14553,6 +14778,7 @@ async function runCheckV2(dir, args = {}, deps = {}) {
14553
14778
  });
14554
14779
  if (online.failure) report.skipped.push(online.failure);
14555
14780
  await addGatewayOnlineRules(report, resolved, args, deps);
14781
+ await addApiReferenceWarning(report, resolved);
14556
14782
  } catch (error) {
14557
14783
  if (!(error instanceof ConfigError)) throw error;
14558
14784
  report = {
@@ -15402,7 +15628,7 @@ function formatDevPortalBackendResponse(projectId, backend) {
15402
15628
  // src/cli/platform/helpers/dev-portal-manifest.ts
15403
15629
  init_gateway_limits();
15404
15630
  import { existsSync as existsSync6 } from "node:fs";
15405
- import { access as access5, mkdir as mkdir13, readFile as readFile13, writeFile as writeFile14 } from "node:fs/promises";
15631
+ import { access as access5, mkdir as mkdir13, readFile as readFile14, writeFile as writeFile14 } from "node:fs/promises";
15406
15632
  import path53 from "node:path";
15407
15633
 
15408
15634
  // src/cli/make-config/json-config.ts
@@ -15455,7 +15681,7 @@ function ensureHeaderPricingNavigation(config2) {
15455
15681
  // src/cli/make-config/json-config.ts
15456
15682
  var APITOGO_JSON_FILENAME = "apitogo.config.json";
15457
15683
  var DOCUMENTATION_NAV_LABEL = "Documentation";
15458
- var API_REFERENCE_PATH = "/api";
15684
+ var API_REFERENCE_PATH2 = "/api";
15459
15685
  var API_REFERENCE_LABEL = "API Reference";
15460
15686
  function isPlainObject3(value) {
15461
15687
  return value !== null && typeof value === "object" && !Array.isArray(value);
@@ -15816,7 +16042,7 @@ function isDocumentationCategory(item) {
15816
16042
  return typeof item === "object" && item.type === "category" && item.label === DOCUMENTATION_NAV_LABEL;
15817
16043
  }
15818
16044
  function isApiReferenceLink(item) {
15819
- return typeof item === "object" && item.type === "link" && item.to === API_REFERENCE_PATH;
16045
+ return typeof item === "object" && item.type === "link" && item.to === API_REFERENCE_PATH2;
15820
16046
  }
15821
16047
  function mergeDocumentationNavigation(navigation, docRoutePaths, options) {
15822
16048
  const existing = Array.isArray(navigation) ? navigation : [];
@@ -15858,14 +16084,14 @@ function applyOpenApiToJsonConfig(config2, input5) {
15858
16084
  if (!navigation.some(isApiReferenceLink)) {
15859
16085
  navigation.push({
15860
16086
  type: "link",
15861
- to: API_REFERENCE_PATH,
16087
+ to: API_REFERENCE_PATH2,
15862
16088
  label: API_REFERENCE_LABEL
15863
16089
  });
15864
16090
  }
15865
16091
  next.navigation = navigation;
15866
16092
  return ensureHeaderApiReferenceNavigation(
15867
16093
  next,
15868
- API_REFERENCE_PATH,
16094
+ API_REFERENCE_PATH2,
15869
16095
  API_REFERENCE_LABEL
15870
16096
  );
15871
16097
  }
@@ -16702,7 +16928,7 @@ function applyOpenApiToSiteConfig(config2, options) {
16702
16928
  async function readManifest2(siteDirectory) {
16703
16929
  const filePath = manifestFilePath(siteDirectory);
16704
16930
  if (await pathExists(filePath)) {
16705
- const raw = await readFile13(filePath, "utf8");
16931
+ const raw = await readFile14(filePath, "utf8");
16706
16932
  const config2 = JSON.parse(raw);
16707
16933
  return extractManifestFromConfig(config2);
16708
16934
  }
@@ -16713,7 +16939,7 @@ async function writeManifest2(siteDirectory, manifest) {
16713
16939
  await mkdir13(path53.dirname(filePath), { recursive: true });
16714
16940
  let config2 = {};
16715
16941
  if (await pathExists(filePath)) {
16716
- const raw = await readFile13(filePath, "utf8");
16942
+ const raw = await readFile14(filePath, "utf8");
16717
16943
  config2 = JSON.parse(raw);
16718
16944
  }
16719
16945
  const {
@@ -16732,7 +16958,7 @@ import {
16732
16958
  access as access6,
16733
16959
  appendFile,
16734
16960
  mkdir as mkdir14,
16735
- readFile as readFile14,
16961
+ readFile as readFile15,
16736
16962
  unlink,
16737
16963
  writeFile as writeFile15
16738
16964
  } from "node:fs/promises";
@@ -16838,7 +17064,7 @@ function applyEnvOverrides2(config2, env) {
16838
17064
  }
16839
17065
  async function readEnvFile(filePath) {
16840
17066
  try {
16841
- const raw = await readFile14(filePath, "utf8");
17067
+ const raw = await readFile15(filePath, "utf8");
16842
17068
  const env = {};
16843
17069
  for (const line2 of raw.split("\n")) {
16844
17070
  const trimmed = line2.trim();
@@ -17015,7 +17241,7 @@ async function writeScaffoldEnv(siteDirectory) {
17015
17241
  for (const legacyName of [".env.example"]) {
17016
17242
  const legacyPath = path54.join(resolvedDirectory, legacyName);
17017
17243
  try {
17018
- const legacy = await readFile14(legacyPath, "utf8");
17244
+ const legacy = await readFile15(legacyPath, "utf8");
17019
17245
  if (!envFileHasActiveAssignments(legacy)) {
17020
17246
  await unlink(legacyPath);
17021
17247
  filesWritten.push(`removed:${legacyName}`);
@@ -17026,7 +17252,7 @@ async function writeScaffoldEnv(siteDirectory) {
17026
17252
  const gitignorePath = path54.join(resolvedDirectory, ".gitignore");
17027
17253
  let gitignore = "";
17028
17254
  try {
17029
- gitignore = await readFile14(gitignorePath, "utf8");
17255
+ gitignore = await readFile15(gitignorePath, "utf8");
17030
17256
  } catch {
17031
17257
  gitignore = "";
17032
17258
  }
@@ -17058,7 +17284,7 @@ var SECRETS_COLLECTION_INSTRUCTION = "For go-live, plans must already be in apit
17058
17284
 
17059
17285
  // src/cli/platform/helpers/dev-portal-site-validation.ts
17060
17286
  init_gateway_limits();
17061
- import { readFile as readFile15 } from "node:fs/promises";
17287
+ import { readFile as readFile16 } from "node:fs/promises";
17062
17288
  import { createRequire as createRequire3 } from "node:module";
17063
17289
  import path55 from "node:path";
17064
17290
  var cachedFallbackTs;
@@ -17236,7 +17462,7 @@ async function validateDevPortalSiteConfig(sourceDirectory, options) {
17236
17462
  let parsed;
17237
17463
  let parseFailed = false;
17238
17464
  try {
17239
- parsed = JSON.parse(await readFile15(jsonPath, "utf8"));
17465
+ parsed = JSON.parse(await readFile16(jsonPath, "utf8"));
17240
17466
  } catch (error) {
17241
17467
  parseFailed = true;
17242
17468
  issues.push({
@@ -17275,7 +17501,7 @@ async function validateDevPortalSiteConfig(sourceDirectory, options) {
17275
17501
  } else {
17276
17502
  for (const relativeFile of existingTsxFiles) {
17277
17503
  checkedFiles.push(relativeFile);
17278
- const content = await readFile15(
17504
+ const content = await readFile16(
17279
17505
  path55.join(resolvedDir, relativeFile),
17280
17506
  "utf8"
17281
17507
  );
@@ -17313,7 +17539,7 @@ function buildSiteValidationResponseFields(result) {
17313
17539
 
17314
17540
  // src/cli/platform/helpers/npm-build.ts
17315
17541
  import { spawn as spawn2 } from "node:child_process";
17316
- import { access as access7, readFile as readFile16 } from "node:fs/promises";
17542
+ import { access as access7, readFile as readFile17 } from "node:fs/promises";
17317
17543
  import path56 from "node:path";
17318
17544
  var BUILD_LOG_TAIL_BYTES = 8192;
17319
17545
  var MIN_NODE_MAJOR = 20;
@@ -17950,7 +18176,7 @@ var check_default = {
17950
18176
 
17951
18177
  // src/cli/make-config/handler.ts
17952
18178
  import { constants as constants2 } from "node:fs";
17953
- import { access as access12, mkdir as mkdir17, readFile as readFile21, writeFile as writeFile20 } from "node:fs/promises";
18179
+ import { access as access12, mkdir as mkdir17, readFile as readFile22, writeFile as writeFile20 } from "node:fs/promises";
17954
18180
  import path68 from "node:path";
17955
18181
  import { stdin as input2 } from "node:process";
17956
18182
  import { glob as glob7 } from "glob";
@@ -18090,7 +18316,7 @@ function withDefaultIncludedActionKeys(plan2, actionKeys) {
18090
18316
  }
18091
18317
 
18092
18318
  // src/cli/scaffold/dev-portal-config-pages.ts
18093
- import { access as access8, readFile as readFile17, rename as rename7, rm as rm9, writeFile as writeFile16 } from "node:fs/promises";
18319
+ import { access as access8, readFile as readFile18, rename as rename7, rm as rm9, writeFile as writeFile16 } from "node:fs/promises";
18094
18320
  import path60 from "node:path";
18095
18321
  async function writeConfigAtomically(filePath, content) {
18096
18322
  const temporaryPath = `${filePath}.${process.pid}.${Date.now()}.tmp`;
@@ -18855,7 +19081,7 @@ async function wireNewDevPortalPageInConfig(sourceDirectory, slug, label) {
18855
19081
  if (!configPath) {
18856
19082
  return { updated: false };
18857
19083
  }
18858
- const original = await readFile17(configPath, "utf8");
19084
+ const original = await readFile18(configPath, "utf8");
18859
19085
  const patched = patchApitogoConfigForNewPage(original, slug, label, {
18860
19086
  typescript: configPath.endsWith(".tsx")
18861
19087
  });
@@ -18867,7 +19093,7 @@ async function wireNewDevPortalPageInConfig(sourceDirectory, slug, label) {
18867
19093
  }
18868
19094
 
18869
19095
  // src/cli/scaffold/wire-openapi.ts
18870
- import { access as access10, readFile as readFile19, writeFile as writeFile18 } from "node:fs/promises";
19096
+ import { access as access10, readFile as readFile20, writeFile as writeFile18 } from "node:fs/promises";
18871
19097
  import path65 from "node:path";
18872
19098
 
18873
19099
  // src/cli/platform/helpers/openapi.ts
@@ -19071,7 +19297,7 @@ async function validateOpenApiFile(filePath) {
19071
19297
  }
19072
19298
 
19073
19299
  // src/cli/scaffold/scaffold-dev-portal-site.ts
19074
- import { mkdir as mkdir15, readFile as readFile18, writeFile as writeFile17 } from "node:fs/promises";
19300
+ import { mkdir as mkdir15, readFile as readFile19, writeFile as writeFile17 } from "node:fs/promises";
19075
19301
  import path64 from "node:path";
19076
19302
 
19077
19303
  // src/cli/scaffold/apitogo-npm-packages.ts
@@ -19479,7 +19705,7 @@ function patchDashboardPageConfig(content) {
19479
19705
  async function ensureAccountPlansPage(outputDirectory) {
19480
19706
  const pagePath = path64.join(outputDirectory, ACCOUNT_PLANS_PAGE_PATH);
19481
19707
  try {
19482
- await readFile18(pagePath, "utf8");
19708
+ await readFile19(pagePath, "utf8");
19483
19709
  return void 0;
19484
19710
  } catch {
19485
19711
  await mkdir15(path64.dirname(pagePath), { recursive: true });
@@ -19490,7 +19716,7 @@ async function ensureAccountPlansPage(outputDirectory) {
19490
19716
  async function ensureDashboardPage(outputDirectory) {
19491
19717
  const pagePath = path64.join(outputDirectory, DASHBOARD_PAGE_PATH);
19492
19718
  try {
19493
- await readFile18(pagePath, "utf8");
19719
+ await readFile19(pagePath, "utf8");
19494
19720
  return void 0;
19495
19721
  } catch {
19496
19722
  await mkdir15(path64.dirname(pagePath), { recursive: true });
@@ -19552,7 +19778,7 @@ async function patchScaffoldedSite(outputDirectory, options) {
19552
19778
  const filesWritten = [];
19553
19779
  const packageJsonPath = path64.join(outputDirectory, "package.json");
19554
19780
  if (options.patchPackageJson !== false) {
19555
- const packageJson3 = await readFile18(packageJsonPath, "utf8");
19781
+ const packageJson3 = await readFile19(packageJsonPath, "utf8");
19556
19782
  await writeFile17(
19557
19783
  packageJsonPath,
19558
19784
  rewritePackageJson(packageJson3, {
@@ -19564,7 +19790,7 @@ async function patchScaffoldedSite(outputDirectory, options) {
19564
19790
  filesWritten.push("package.json");
19565
19791
  }
19566
19792
  const configPath = path64.join(outputDirectory, "apitogo.config.tsx");
19567
- const configContent = await readFile18(configPath, "utf8");
19793
+ const configContent = await readFile19(configPath, "utf8");
19568
19794
  await writeFile17(
19569
19795
  configPath,
19570
19796
  patchDashboardPageConfig(
@@ -19671,7 +19897,7 @@ async function wireDevPortalOpenApi(sourceDirectory, openApiPath) {
19671
19897
  }
19672
19898
  const jsonConfigPath = manifestFilePath(resolvedSource);
19673
19899
  if (await pathExists4(jsonConfigPath)) {
19674
- const raw = await readFile19(jsonConfigPath, "utf8");
19900
+ const raw = await readFile20(jsonConfigPath, "utf8");
19675
19901
  const siteConfig = JSON.parse(raw);
19676
19902
  const updated = applyOpenApiToSiteConfig(siteConfig, {
19677
19903
  input: input5,
@@ -19685,7 +19911,7 @@ async function wireDevPortalOpenApi(sourceDirectory, openApiPath) {
19685
19911
  );
19686
19912
  } else {
19687
19913
  const tsxConfigPath = path65.join(resolvedSource, "apitogo.config.tsx");
19688
- const configContent = await readFile19(tsxConfigPath, "utf8");
19914
+ const configContent = await readFile20(tsxConfigPath, "utf8");
19689
19915
  try {
19690
19916
  const patched = patchApitogoConfigOpenApiToFile(configContent, input5);
19691
19917
  await writeFile18(tsxConfigPath, patched);
@@ -20041,7 +20267,7 @@ export default function ${componentName}() {
20041
20267
  // src/cli/make-config/scaffold-project.ts
20042
20268
  init_package_json();
20043
20269
  import { constants } from "node:fs";
20044
- import { access as access11, mkdir as mkdir16, readFile as readFile20, writeFile as writeFile19 } from "node:fs/promises";
20270
+ import { access as access11, mkdir as mkdir16, readFile as readFile21, writeFile as writeFile19 } from "node:fs/promises";
20045
20271
  import path67 from "node:path";
20046
20272
  import { glob as glob6 } from "glob";
20047
20273
 
@@ -20123,7 +20349,7 @@ async function ensurePackageJson(dir) {
20123
20349
  `, "utf8");
20124
20350
  return true;
20125
20351
  }
20126
- const raw = await readFile20(pkgPath, "utf8");
20352
+ const raw = await readFile21(pkgPath, "utf8");
20127
20353
  let pkg;
20128
20354
  try {
20129
20355
  pkg = JSON.parse(raw);
@@ -20252,7 +20478,7 @@ var fileExists3 = async (filePath) => {
20252
20478
  }
20253
20479
  };
20254
20480
  var readJsonConfig = async (filePath) => {
20255
- const raw = await readFile21(filePath, "utf8");
20481
+ const raw = await readFile22(filePath, "utf8");
20256
20482
  const parsed = JSON.parse(raw);
20257
20483
  if (!isPlainObject3(parsed)) {
20258
20484
  throw new Error(`${APITOGO_JSON_FILENAME} must be a JSON object.`);
@@ -20302,7 +20528,7 @@ async function persistConfig(config2, ctx) {
20302
20528
  `, "utf8");
20303
20529
  const tsxPath = path68.join(ctx.dir, "apitogo.config.tsx");
20304
20530
  if (await fileExists3(tsxPath) && tsxRoutes.length > 0) {
20305
- const tsxSource = await readFile21(tsxPath, "utf8");
20531
+ const tsxSource = await readFile22(tsxPath, "utf8");
20306
20532
  if (isManagedConfigWrapper(tsxSource)) {
20307
20533
  await writeFile20(
20308
20534
  tsxPath,
@@ -21202,8 +21428,8 @@ async function waitForGatewayEntraProvision(client, projectId, options) {
21202
21428
  await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
21203
21429
  }
21204
21430
  }
21205
- function normalizeActionKey(actionKey) {
21206
- const trimmed = actionKey.trim();
21431
+ function normalizeActionKey(actionKey2) {
21432
+ const trimmed = actionKey2.trim();
21207
21433
  if (trimmed.includes(":")) {
21208
21434
  const [method2, ...pathParts] = trimmed.split(":");
21209
21435
  const path101 = pathParts.join(":").trim();
@@ -21244,10 +21470,10 @@ async function applyGatewayPublicRoutes(client, projectId, routes, options) {
21244
21470
  }
21245
21471
  }
21246
21472
  for (const route of routes) {
21247
- let actionKey;
21473
+ let actionKey2;
21248
21474
  try {
21249
21475
  const trimmed = route.trim();
21250
- actionKey = trimmed.startsWith("/") ? normalizeActionKey(`GET ${trimmed}`) : normalizeActionKey(trimmed);
21476
+ actionKey2 = trimmed.startsWith("/") ? normalizeActionKey(`GET ${trimmed}`) : normalizeActionKey(trimmed);
21251
21477
  } catch (error) {
21252
21478
  unresolved.push({
21253
21479
  route,
@@ -21255,23 +21481,23 @@ async function applyGatewayPublicRoutes(client, projectId, routes, options) {
21255
21481
  });
21256
21482
  continue;
21257
21483
  }
21258
- if (knownKeys && !knownKeys.has(actionKey.toLowerCase())) {
21259
- const message = `Action '${actionKey}' not found in gateway actions.`;
21260
- unresolved.push({ route, actionKey, error: message });
21484
+ if (knownKeys && !knownKeys.has(actionKey2.toLowerCase())) {
21485
+ const message = `Action '${actionKey2}' not found in gateway actions.`;
21486
+ unresolved.push({ route, actionKey: actionKey2, error: message });
21261
21487
  warnings.push(message);
21262
21488
  continue;
21263
21489
  }
21264
21490
  try {
21265
21491
  const action = await client.setGatewayActionPublic(
21266
21492
  projectId,
21267
- actionKey,
21493
+ actionKey2,
21268
21494
  true
21269
21495
  );
21270
21496
  applied.push(action);
21271
21497
  } catch (error) {
21272
21498
  const message = error instanceof Error ? error.message : String(error);
21273
- unresolved.push({ route, actionKey, error: message });
21274
- warnings.push(`Failed to set public for '${actionKey}': ${message}`);
21499
+ unresolved.push({ route, actionKey: actionKey2, error: message });
21500
+ warnings.push(`Failed to set public for '${actionKey2}': ${message}`);
21275
21501
  }
21276
21502
  }
21277
21503
  return { applied, unresolved, warnings };
@@ -21695,7 +21921,7 @@ async function upsertGatewayPlansFromManifest(client, config2, projectId, plans,
21695
21921
  }
21696
21922
 
21697
21923
  // src/cli/platform/helpers/openapi-gateway-servers.ts
21698
- import { mkdir as mkdir18, readFile as readFile22, writeFile as writeFile21 } from "node:fs/promises";
21924
+ import { mkdir as mkdir18, readFile as readFile23, writeFile as writeFile21 } from "node:fs/promises";
21699
21925
  import path71 from "node:path";
21700
21926
  import YAML3 from "yaml";
21701
21927
  var GATEWAY_URL_ENV_KEY = "APITOGO_GATEWAY_URL";
@@ -21727,7 +21953,7 @@ async function resolveProjectGatewayUrl(client, projectId) {
21727
21953
  return null;
21728
21954
  }
21729
21955
  async function readOpenApiDocument(filePath) {
21730
- const content = await readFile22(filePath, "utf8");
21956
+ const content = await readFile23(filePath, "utf8");
21731
21957
  const ext = path71.extname(filePath).toLowerCase();
21732
21958
  const parsed = ext === ".json" ? JSON.parse(content) : YAML3.parse(content);
21733
21959
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
@@ -21756,7 +21982,7 @@ async function pointSiteApisAtPublishOpenApi(siteDirectory, relativeInput) {
21756
21982
  path71.resolve(siteDirectory),
21757
21983
  APITOGO_CONFIG_FILENAME2
21758
21984
  );
21759
- const raw = await readFile22(configPath, "utf8");
21985
+ const raw = await readFile23(configPath, "utf8");
21760
21986
  const config2 = JSON.parse(raw);
21761
21987
  const previousApis = config2.apis;
21762
21988
  const previousBackend = config2.backend;
@@ -21773,7 +21999,7 @@ async function pointSiteApisAtPublishOpenApi(siteDirectory, relativeInput) {
21773
21999
  `, "utf8");
21774
22000
  return {
21775
22001
  restore: async () => {
21776
- const currentRaw = await readFile22(configPath, "utf8");
22002
+ const currentRaw = await readFile23(configPath, "utf8");
21777
22003
  const current = JSON.parse(currentRaw);
21778
22004
  if (previousApis === void 0) {
21779
22005
  delete current.apis;
@@ -21808,7 +22034,7 @@ async function prepareOpenApiServersForGatewayBuild(options) {
21808
22034
  );
21809
22035
  if (!openApiPath) {
21810
22036
  try {
21811
- const raw = await readFile22(
22037
+ const raw = await readFile23(
21812
22038
  path71.join(siteDirectory, APITOGO_CONFIG_FILENAME2),
21813
22039
  "utf8"
21814
22040
  );
@@ -21834,8 +22060,8 @@ async function prepareOpenApiServersForGatewayBuild(options) {
21834
22060
  }
21835
22061
 
21836
22062
  // src/cli/platform/helpers/public-routes.ts
21837
- function normalizeActionKeyFormat(actionKey) {
21838
- const trimmed = actionKey.trim();
22063
+ function normalizeActionKeyFormat(actionKey2) {
22064
+ const trimmed = actionKey2.trim();
21839
22065
  if (trimmed.includes(":")) {
21840
22066
  const [method2, ...pathParts] = trimmed.split(":");
21841
22067
  const path101 = pathParts.join(":").trim();
@@ -21882,7 +22108,7 @@ function collectManifestPublicActionKeys(gateway) {
21882
22108
  }
21883
22109
 
21884
22110
  // src/cli/platform/helpers/publish-ledger.ts
21885
- import { mkdir as mkdir19, readFile as readFile23, writeFile as writeFile22 } from "node:fs/promises";
22111
+ import { mkdir as mkdir19, readFile as readFile24, writeFile as writeFile22 } from "node:fs/promises";
21886
22112
  import path72 from "node:path";
21887
22113
  var PUBLISH_LEDGER_RELATIVE_PATH = ".apitogo/publish-state.json";
21888
22114
  function publishLedgerPath(siteDirectory) {
@@ -21891,7 +22117,7 @@ function publishLedgerPath(siteDirectory) {
21891
22117
  async function readPublishLedger(siteDirectory) {
21892
22118
  const filePath = publishLedgerPath(siteDirectory);
21893
22119
  try {
21894
- const raw = await readFile23(filePath, "utf8");
22120
+ const raw = await readFile24(filePath, "utf8");
21895
22121
  const parsed = JSON.parse(raw);
21896
22122
  if (parsed?.version !== 1 || !Array.isArray(parsed.steps)) {
21897
22123
  return null;
@@ -24063,7 +24289,7 @@ async function resolveProjectIdForPublish(client, config2, args, wait, progress,
24063
24289
  import path78 from "node:path";
24064
24290
 
24065
24291
  // src/cli/platform/helpers/dev-portal-site-api-url.ts
24066
- import { readdir as readdir3, readFile as readFile24, writeFile as writeFile23 } from "node:fs/promises";
24292
+ import { readdir as readdir3, readFile as readFile25, writeFile as writeFile23 } from "node:fs/promises";
24067
24293
  import path75 from "node:path";
24068
24294
  var SITE_API_BASE_URL_ENV_KEY = "VITE_APITOGO_AUTHENTICATION_API_BASE_URL";
24069
24295
  var PLACEHOLDER_HOST_MARKERS = [
@@ -24088,7 +24314,7 @@ async function readSiteApiBaseUrl(sourceDirectory) {
24088
24314
  const resolved = path75.resolve(sourceDirectory);
24089
24315
  for (const envFile of [".env", ".env.production"]) {
24090
24316
  try {
24091
- const content = await readFile24(path75.join(resolved, envFile), "utf8");
24317
+ const content = await readFile25(path75.join(resolved, envFile), "utf8");
24092
24318
  const lines = [...content.matchAll(ENV_LINE_GLOBAL_RE)];
24093
24319
  if (lines.length === 0) {
24094
24320
  continue;
@@ -24119,7 +24345,7 @@ async function patchSiteApiBaseUrl(sourceDirectory, apiBaseUrl) {
24119
24345
  for (const envFile of [".env.production", ".env"]) {
24120
24346
  const envPath = path75.join(resolved, envFile);
24121
24347
  try {
24122
- const content = await readFile24(envPath, "utf8");
24348
+ const content = await readFile25(envPath, "utf8");
24123
24349
  const withoutAssignments = content.replace(ENV_LINE_GLOBAL_RE, "").replace(/\n{3,}/g, "\n\n").trimEnd();
24124
24350
  const patched = withoutAssignments ? `${withoutAssignments}
24125
24351
  ${line2}
@@ -24199,7 +24425,7 @@ async function assertDeployableDistHasLiveApiBaseUrl(distDirectory, expectedApiB
24199
24425
  let found = false;
24200
24426
  let foundPlaceholder = false;
24201
24427
  for await (const file of walkFiles(dist)) {
24202
- const content = await readFile24(file, "utf8");
24428
+ const content = await readFile25(file, "utf8");
24203
24429
  if (content.includes(needle)) {
24204
24430
  found = true;
24205
24431
  }
@@ -26338,7 +26564,7 @@ import path80 from "node:path";
26338
26564
 
26339
26565
  // src/cli/deploy/gateway.ts
26340
26566
  import { createHash as createHash4 } from "node:crypto";
26341
- import { readFile as readFile25 } from "node:fs/promises";
26567
+ import { readFile as readFile26 } from "node:fs/promises";
26342
26568
  init_invariant();
26343
26569
  var GatewayDeployError = class extends ApitogoError {
26344
26570
  constructor(code, message, developerHint) {
@@ -26378,7 +26604,7 @@ async function readSpec(gateway) {
26378
26604
  "Add one to gateway.specs in the config."
26379
26605
  );
26380
26606
  try {
26381
- return await readFile25(file);
26607
+ return await readFile26(file);
26382
26608
  } catch {
26383
26609
  throw new GatewayDeployError(
26384
26610
  "GATEWAY_SPEC_UNREADABLE",
@@ -27384,11 +27610,11 @@ var export_default = {
27384
27610
  import path87 from "node:path";
27385
27611
 
27386
27612
  // src/cli/scaffold/run-scaffold-discovery.ts
27387
- import { access as access15, readFile as readFile28, writeFile as writeFile26 } from "node:fs/promises";
27613
+ import { access as access15, readFile as readFile29, writeFile as writeFile26 } from "node:fs/promises";
27388
27614
  import path86 from "node:path";
27389
27615
 
27390
27616
  // src/cli/scaffold/detect-backend.ts
27391
- import { readdir as readdir4, readFile as readFile26 } from "node:fs/promises";
27617
+ import { readdir as readdir4, readFile as readFile27 } from "node:fs/promises";
27392
27618
  import path84 from "node:path";
27393
27619
  var DEFAULT_RUNTIME_VERSION = {
27394
27620
  dotnet: "DOTNET_10",
@@ -27406,7 +27632,7 @@ async function listFiles(directory) {
27406
27632
  }
27407
27633
  async function readText(filePath) {
27408
27634
  try {
27409
- return await readFile26(filePath, "utf8");
27635
+ return await readFile27(filePath, "utf8");
27410
27636
  } catch {
27411
27637
  return void 0;
27412
27638
  }
@@ -27492,7 +27718,7 @@ import {
27492
27718
  lstat as lstat2,
27493
27719
  mkdir as mkdir21,
27494
27720
  readdir as readdir5,
27495
- readFile as readFile27,
27721
+ readFile as readFile28,
27496
27722
  readlink,
27497
27723
  symlink as symlink2,
27498
27724
  writeFile as writeFile25
@@ -27723,7 +27949,7 @@ async function applyDocsNavigation(siteDirectory, routes) {
27723
27949
  if (!await pathExists5(configPath)) {
27724
27950
  return;
27725
27951
  }
27726
- const raw = await readFile27(configPath, "utf8");
27952
+ const raw = await readFile28(configPath, "utf8");
27727
27953
  const parsed = JSON.parse(raw);
27728
27954
  if (!isPlainObject5(parsed)) {
27729
27955
  return;
@@ -27908,7 +28134,7 @@ async function pathExists6(filePath) {
27908
28134
  async function wireOpenApiIntoJsonConfig(siteDirectory, openApiPath) {
27909
28135
  const input5 = toSiteRelativeOpenApiInput(siteDirectory, openApiPath);
27910
28136
  const jsonConfigPath = path86.join(siteDirectory, "apitogo.config.json");
27911
- const raw = await readFile28(jsonConfigPath, "utf8");
28137
+ const raw = await readFile29(jsonConfigPath, "utf8");
27912
28138
  const siteConfig = JSON.parse(raw);
27913
28139
  const updated = applyOpenApiToSiteConfig(siteConfig, {
27914
28140
  input: input5,
@@ -27935,7 +28161,7 @@ async function discoverAndWireOpenApiLite(siteDirectory, mode) {
27935
28161
  const invalidCandidates = [];
27936
28162
  let selected;
27937
28163
  for (const candidate of search.candidates) {
27938
- const content = await readFile28(candidate, "utf8");
28164
+ const content = await readFile29(candidate, "utf8");
27939
28165
  const parsed = liteParseOpenApi(content, candidate);
27940
28166
  if (parsed.valid) {
27941
28167
  selected = {
@@ -28015,7 +28241,7 @@ async function discoverAndWireBackend(siteDirectory, mode) {
28015
28241
  }
28016
28242
  try {
28017
28243
  const siteConfig = JSON.parse(
28018
- await readFile28(jsonConfigPath, "utf8")
28244
+ await readFile29(jsonConfigPath, "utf8")
28019
28245
  );
28020
28246
  const existing = siteConfig.backend && typeof siteConfig.backend === "object" && !Array.isArray(siteConfig.backend) ? siteConfig.backend : {};
28021
28247
  if (typeof existing.sourceDirectory === "string" && existing.sourceDirectory.trim()) {
@@ -28566,11 +28792,9 @@ var ProjectCreateError = class extends ApitogoError {
28566
28792
  }
28567
28793
  };
28568
28794
  function modulesForConfig(config2) {
28569
- return [
28570
- ...config2.portal && config2.portal.enabled !== false ? ["dev-portal"] : [],
28571
- ...config2.gateway && config2.gateway.enabled !== false ? ["apihub"] : []
28572
- ];
28795
+ return config2.portal && config2.portal.enabled !== false ? ["dev-portal"] : [];
28573
28796
  }
28797
+ var hostsAnything = (config2) => config2.portal && config2.portal.enabled !== false || config2.gateway && config2.gateway.enabled !== false;
28574
28798
  async function createProjectV2(options, deps) {
28575
28799
  const { config: config2, resolved } = loadConfigV2(options.dir);
28576
28800
  if (resolved.project.id) {
@@ -28581,10 +28805,10 @@ async function createProjectV2(options, deps) {
28581
28805
  );
28582
28806
  }
28583
28807
  const modules = modulesForConfig(config2);
28584
- if (modules.length === 0) {
28808
+ if (!hostsAnything(config2)) {
28585
28809
  throw new ProjectCreateError(
28586
28810
  "NOTHING_TO_HOST",
28587
- "No enabled service needs hosting.",
28811
+ "No enabled service needs a project.",
28588
28812
  "Enable the portal or the gateway first (`apitogo add portal`)."
28589
28813
  );
28590
28814
  }
@@ -28595,7 +28819,7 @@ async function createProjectV2(options, deps) {
28595
28819
  modules,
28596
28820
  wait: options.wait !== false
28597
28821
  });
28598
- const outcome = parse3(raw);
28822
+ const outcome = parse4(raw);
28599
28823
  if (outcome.requiresPlatformPlanSelection) {
28600
28824
  throw new ProjectCreateError(
28601
28825
  "PLAN_NOT_SELECTED",
@@ -28626,7 +28850,7 @@ async function createProjectV2(options, deps) {
28626
28850
  resumed: outcome.resumed === true
28627
28851
  };
28628
28852
  }
28629
- function parse3(raw) {
28853
+ function parse4(raw) {
28630
28854
  try {
28631
28855
  return JSON.parse(raw);
28632
28856
  } catch {
@@ -28678,7 +28902,7 @@ async function createLinkedProject(argv, dir) {
28678
28902
  return;
28679
28903
  }
28680
28904
  printDiagnosticsToConsole(
28681
- `Project ${result.projectName} (${result.projectId}) is ready on the free plan with: ${result.modules.join(", ")}.`
28905
+ `Project ${result.projectName} (${result.projectId}) is ready on the free plan${result.modules.length > 0 ? ` with: ${result.modules.join(", ")}` : " (the gateway is created by `apitogo deploy`)"}.`
28682
28906
  );
28683
28907
  printResultToConsole(
28684
28908
  "The folder is linked. Run `apitogo deploy` to publish."
@@ -29774,6 +29998,7 @@ Type the project name to confirm: `
29774
29998
  projectId,
29775
29999
  projectName: name,
29776
30000
  status: "removed",
30001
+ scope: "gateway",
29777
30002
  cleared,
29778
30003
  message: "The gateway was removed. The project and your local files are kept."
29779
30004
  };
@@ -29827,7 +30052,7 @@ async function teardownProject(argv) {
29827
30052
  return;
29828
30053
  }
29829
30054
  printDiagnosticsToConsole(
29830
- `${result.status === "removed" ? "Deleted" : "Queued deletion of"} project ${result.projectName} (${result.projectId}).`
30055
+ result.scope === "gateway" ? `Removed the gateway of project ${result.projectName} (${result.projectId}).` : `${result.status === "removed" ? "Deleted" : "Queued deletion of"} project ${result.projectName} (${result.projectId}).`
29831
30056
  );
29832
30057
  if (result.cleared.length > 0)
29833
30058
  printDiagnosticsToConsole(