@lukoweb/apitogo 0.2.2 → 0.2.10

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
@@ -836,7 +836,7 @@ import { stat as stat4 } from "node:fs/promises";
836
836
  var fileExists;
837
837
  var init_file_exists = __esm({
838
838
  "src/config/file-exists.ts"() {
839
- fileExists = (path95) => stat4(path95).then(() => true).catch(() => false);
839
+ fileExists = (path100) => stat4(path100).then(() => true).catch(() => false);
840
840
  }
841
841
  });
842
842
 
@@ -5809,24 +5809,24 @@ init_package_json();
5809
5809
  import yargs from "yargs/yargs";
5810
5810
 
5811
5811
  // src/cli/services/handler.ts
5812
- import path48 from "node:path";
5812
+ import path49 from "node:path";
5813
5813
  import { createInterface as createInterface2 } from "node:readline/promises";
5814
5814
 
5815
5815
  // src/config/v2/errors.ts
5816
5816
  init_invariant();
5817
- function formatConfigIssue(issue2) {
5818
- const hint = issue2.hint ? ` ${issue2.hint}` : "";
5819
- const rest = issue2.message.slice(issue2.path.length);
5820
- const startsWithPath = issue2.path !== "" && issue2.message.startsWith(issue2.path) && (rest === "" || /^[\s:]/.test(rest));
5821
- const text = startsWithPath ? issue2.message : `${issue2.path === "" ? "(root)" : issue2.path}: ${issue2.message}`;
5822
- return `${text}${hint} [${issue2.code}]`;
5817
+ function formatConfigIssue(issue3) {
5818
+ const hint = issue3.hint ? ` ${issue3.hint}` : "";
5819
+ const rest = issue3.message.slice(issue3.path.length);
5820
+ const startsWithPath = issue3.path !== "" && issue3.message.startsWith(issue3.path) && (rest === "" || /^[\s:]/.test(rest));
5821
+ const text = startsWithPath ? issue3.message : `${issue3.path === "" ? "(root)" : issue3.path}: ${issue3.message}`;
5822
+ return `${text}${hint} [${issue3.code}]`;
5823
5823
  }
5824
5824
  var ConfigError = class extends ApitogoError {
5825
5825
  issues;
5826
5826
  constructor(file, issues) {
5827
5827
  super(
5828
5828
  `${file} has ${issues.length} problem${issues.length === 1 ? "" : "s"}:
5829
- ${issues.map((issue2) => ` - ${formatConfigIssue(issue2)}`).join("\n")}`,
5829
+ ${issues.map((issue3) => ` - ${formatConfigIssue(issue3)}`).join("\n")}`,
5830
5830
  { title: "Invalid apitogo config" }
5831
5831
  );
5832
5832
  this.name = "ConfigError";
@@ -6055,13 +6055,24 @@ var GatewayOriginSchema = z.strictObject({
6055
6055
  runtime: z.enum(["nodejs", "python", "dotnet"]).optional(),
6056
6056
  runtimeVersion: nonEmpty.optional(),
6057
6057
  exclude: pathList.optional(),
6058
- url: httpsUrl.optional()
6058
+ url: httpsUrl.optional(),
6059
+ /** Path the platform probes to check the origin, for example `/health`. */
6060
+ healthPath: z.string().regex(/^\/(?!\/)/, { error: 'must start with a single "/"' }).optional(),
6061
+ timeoutSeconds: z.int().min(1).max(120).optional(),
6062
+ /** What the gateway sends to the origin as credential. The value is always a secret reference. */
6063
+ auth: z.strictObject({
6064
+ type: z.enum(["none", "header", "mtls"]),
6065
+ name: nonEmpty.optional(),
6066
+ value: secretRef.optional()
6067
+ }).optional()
6059
6068
  });
6060
6069
  var GatewaySchema = z.strictObject({
6061
6070
  enabled,
6062
6071
  provider: nonEmpty.optional(),
6063
6072
  origin: GatewayOriginSchema,
6064
6073
  specs: z.array(path4).min(1, { error: "needs at least one OpenAPI file" }),
6074
+ /** Who may read the OpenAPI file the platform publishes for this gateway. Default: public. */
6075
+ export: z.strictObject({ access: z.enum(["public", "authenticated"]).optional() }).optional(),
6065
6076
  auth: z.strictObject({
6066
6077
  mode: z.enum(["identity", "oidc"]),
6067
6078
  metadataUrl: httpsUrl.optional(),
@@ -6069,15 +6080,27 @@ var GatewaySchema = z.strictObject({
6069
6080
  audiences: z.array(nonEmpty).optional()
6070
6081
  }).optional(),
6071
6082
  policies: z.strictObject({
6083
+ access: z.strictObject({
6084
+ jwt: z.boolean().optional(),
6085
+ apiKey: z.boolean().optional(),
6086
+ apiKeyHeader: nonEmpty.optional(),
6087
+ bindKeyToToken: z.boolean().optional()
6088
+ }).optional(),
6072
6089
  rateLimit: z.strictObject({
6073
6090
  calls: z.int().positive(),
6074
- periodSeconds: z.int().positive()
6091
+ periodSeconds: z.int().positive(),
6092
+ scope: z.enum(["key", "entitlement"]).optional()
6075
6093
  }).optional(),
6076
6094
  quota: z.strictObject({
6077
6095
  calls: z.int().positive(),
6078
- periodDays: z.int().min(1)
6096
+ periodDays: z.int().min(1),
6097
+ scope: z.enum(["key", "entitlement"]).optional()
6079
6098
  }).optional(),
6080
- cors: z.array(z.url()).optional()
6099
+ cors: z.array(z.url()).optional(),
6100
+ operations: z.strictObject({
6101
+ public: z.array(nonEmpty).optional(),
6102
+ deny: z.array(nonEmpty).optional()
6103
+ }).optional()
6081
6104
  }).optional()
6082
6105
  });
6083
6106
  var connectionId = z.string().regex(/^[a-z0-9-]{1,32}$/, {
@@ -6166,8 +6189,8 @@ var ConfigV2Schema = z.strictObject({
6166
6189
  var REMOVED_KEYS = {
6167
6190
  "plans.provider": "plans.provider was removed. Only plans.billing.provider (stripe or none) is selectable."
6168
6191
  };
6169
- function pathToString(path95) {
6170
- return path95.reduce(
6192
+ function pathToString(path100) {
6193
+ return path100.reduce(
6171
6194
  (acc, segment) => typeof segment === "number" ? `${acc}[${segment}]` : acc === "" ? String(segment) : `${acc}.${String(segment)}`,
6172
6195
  ""
6173
6196
  );
@@ -6187,9 +6210,9 @@ function step(schema2, segment) {
6187
6210
  }
6188
6211
  return void 0;
6189
6212
  }
6190
- function knownKeysAt(path95) {
6213
+ function knownKeysAt(path100) {
6191
6214
  let current = ConfigV2Schema;
6192
- for (const segment of path95) {
6215
+ for (const segment of path100) {
6193
6216
  if (!current) return [];
6194
6217
  current = step(current, segment);
6195
6218
  }
@@ -6224,37 +6247,37 @@ function unknownKeyHint(fullPath, key, known) {
6224
6247
  if (closest) return `Did you mean "${closest.candidate}"?`;
6225
6248
  return known.length > 0 ? `Allowed keys: ${known.join(", ")}.` : void 0;
6226
6249
  }
6227
- function mapIssue(issue2) {
6228
- const path95 = pathToString(issue2.path);
6229
- switch (issue2.code) {
6250
+ function mapIssue(issue3) {
6251
+ const path100 = pathToString(issue3.path);
6252
+ switch (issue3.code) {
6230
6253
  case "unrecognized_keys":
6231
- return issue2.keys.map((key) => {
6232
- const fullPath = pathToString([...issue2.path, key]);
6254
+ return issue3.keys.map((key) => {
6255
+ const fullPath = pathToString([...issue3.path, key]);
6233
6256
  return {
6234
6257
  code: "CONFIG_UNKNOWN_KEY",
6235
6258
  path: fullPath,
6236
6259
  message: `Unknown key "${key}".`,
6237
- hint: unknownKeyHint(fullPath, key, knownKeysAt(issue2.path))
6260
+ hint: unknownKeyHint(fullPath, key, knownKeysAt(issue3.path))
6238
6261
  };
6239
6262
  });
6240
6263
  case "invalid_value":
6241
6264
  return [
6242
6265
  {
6243
6266
  code: "CONFIG_INVALID_VALUE",
6244
- path: path95,
6245
- message: `must be one of: ${issue2.values.map((v) => JSON.stringify(v)).join(", ")}.`
6267
+ path: path100,
6268
+ message: `must be one of: ${issue3.values.map((v) => JSON.stringify(v)).join(", ")}.`
6246
6269
  }
6247
6270
  ];
6248
6271
  case "custom": {
6249
- const code = typeof issue2.params?.code === "string" ? issue2.params.code : "CONFIG_INVALID_VALUE";
6250
- return [{ code, path: path95, message: issue2.message ?? "is invalid." }];
6272
+ const code = typeof issue3.params?.code === "string" ? issue3.params.code : "CONFIG_INVALID_VALUE";
6273
+ return [{ code, path: path100, message: issue3.message ?? "is invalid." }];
6251
6274
  }
6252
6275
  case "invalid_type":
6253
6276
  return [
6254
- /received undefined/.test(issue2.message) ? { code: "CONFIG_MISSING_FIELD", path: path95, message: "is required." } : { code: "CONFIG_INVALID_VALUE", path: path95, message: issue2.message }
6277
+ /received undefined/.test(issue3.message) ? { code: "CONFIG_MISSING_FIELD", path: path100, message: "is required." } : { code: "CONFIG_INVALID_VALUE", path: path100, message: issue3.message }
6255
6278
  ];
6256
6279
  default:
6257
- return [{ code: "CONFIG_INVALID_VALUE", path: path95, message: issue2.message }];
6280
+ return [{ code: "CONFIG_INVALID_VALUE", path: path100, message: issue3.message }];
6258
6281
  }
6259
6282
  }
6260
6283
  function isRecord(value) {
@@ -6564,6 +6587,7 @@ function resolveConfigV2(config2, configPath) {
6564
6587
  },
6565
6588
  specs: gateway.specs.map((spec) => path7.resolve(configDir, spec)),
6566
6589
  auth: gateway.auth,
6590
+ export: gateway.export,
6567
6591
  policies: gateway.policies ?? {}
6568
6592
  };
6569
6593
  }
@@ -6703,7 +6727,7 @@ function dependencyRules(config2, resolved, out) {
6703
6727
  function checkDependencies(config2, resolved) {
6704
6728
  const issues = [];
6705
6729
  dependencyRules(config2, resolved, {
6706
- error: (issue2) => void issues.push(issue2),
6730
+ error: (issue3) => void issues.push(issue3),
6707
6731
  warn: () => void 0
6708
6732
  });
6709
6733
  return issues;
@@ -6801,10 +6825,55 @@ async function docsContentRule(config2, patterns, resolved, out) {
6801
6825
  function isIpLiteral(hostname) {
6802
6826
  return /^\d{1,3}(\.\d{1,3}){3}$/.test(hostname) || hostname.startsWith("[");
6803
6827
  }
6828
+ function originAuthRules(origin, out) {
6829
+ const auth = origin.auth;
6830
+ if (!auth) return;
6831
+ const base = "gateway.origin.auth";
6832
+ if (auth.type === "header") {
6833
+ if (!auth.name)
6834
+ out.error({
6835
+ code: "GATEWAY_ORIGIN_INCOMPLETE",
6836
+ path: `${base}.name`,
6837
+ message: "origin.auth.type is header, so origin.auth.name is required.",
6838
+ hint: 'For example: "X-Origin-Key".'
6839
+ });
6840
+ if (!auth.value)
6841
+ out.error({
6842
+ code: "GATEWAY_ORIGIN_INCOMPLETE",
6843
+ path: `${base}.value`,
6844
+ message: "origin.auth.type is header, so origin.auth.value is required.",
6845
+ hint: 'A secret reference, for example "secret:ORIGIN_KEY". Store it with `apitogo secrets set ORIGIN_KEY`.'
6846
+ });
6847
+ } else if (auth.type === "none" && (auth.name || auth.value)) {
6848
+ out.error({
6849
+ code: "GATEWAY_ORIGIN_INVALID",
6850
+ path: base,
6851
+ message: "origin.auth.type is none, but a name or value is set.",
6852
+ hint: 'Remove them, or set the type to "header".'
6853
+ });
6854
+ } else if (auth.type === "mtls") {
6855
+ out.error({
6856
+ code: "GATEWAY_CAPABILITY_UNSUPPORTED",
6857
+ path: `${base}.type`,
6858
+ message: "Client certificates (mtls) for the origin are not available yet.",
6859
+ hint: 'Use type "header" with a secret.'
6860
+ });
6861
+ }
6862
+ }
6804
6863
  async function gatewayRules(resolved, out) {
6805
6864
  const gateway = resolved.gateway;
6806
6865
  if (!gateway?.enabled) return;
6807
6866
  const origin = gateway.origin;
6867
+ (gateway.policies.cors ?? []).forEach((site, index) => {
6868
+ const { pathname, search, hash: hash2 } = new URL(site);
6869
+ if (pathname === "/" && !search && !hash2 && !site.endsWith("/")) return;
6870
+ out.error({
6871
+ code: "GATEWAY_CORS_ORIGIN_INVALID",
6872
+ path: `gateway.policies.cors[${index}]`,
6873
+ message: `"${site}" is not a browser origin: it has a path, a query or a trailing slash.`,
6874
+ hint: `Use only scheme and host, for example "${new URL(site).origin}".`
6875
+ });
6876
+ });
6808
6877
  if (origin.mode === "managed") {
6809
6878
  for (const field of ["source", "runtime", "runtimeVersion"]) {
6810
6879
  if (!origin[field]) {
@@ -6881,6 +6950,15 @@ async function gatewayRules(resolved, out) {
6881
6950
  }
6882
6951
  }
6883
6952
  }
6953
+ originAuthRules(origin, out);
6954
+ if (gateway.specs.length > 1) {
6955
+ out.warn({
6956
+ code: "GATEWAY_SPEC_INVALID",
6957
+ path: "gateway.specs",
6958
+ message: "gateway.specs lists several files, but the platform takes one OpenAPI file per gateway, so deploy will refuse it.",
6959
+ hint: "Merge them into one file."
6960
+ });
6961
+ }
6884
6962
  for (const [index, spec] of gateway.specs.entries())
6885
6963
  await specRule(spec, index, resolved, out);
6886
6964
  authRule(resolved, out);
@@ -7119,8 +7197,8 @@ async function checkConfigV2(config2, resolved, context = {}) {
7119
7197
  "online rules were not run (provider capabilities, origin reachability, OIDC discovery, tier limits)"
7120
7198
  ];
7121
7199
  const out = {
7122
- error: (issue2) => errors.push(issue2),
7123
- warn: (issue2) => warnings.push(issue2)
7200
+ error: (issue3) => errors.push(issue3),
7201
+ warn: (issue3) => warnings.push(issue3)
7124
7202
  };
7125
7203
  dependencyRules(config2, resolved, out);
7126
7204
  secretRules(config2, context, out, skipped);
@@ -7223,7 +7301,7 @@ var ServiceManifestSchema = z3.looseObject({
7223
7301
  }).optional()
7224
7302
  }).default({})
7225
7303
  });
7226
- var issue = (code, path95, message, hint) => ({ code, path: path95, message, hint });
7304
+ var issue = (code, path100, message, hint) => ({ code, path: path100, message, hint });
7227
7305
  function parseManifest(raw, file = "services.json") {
7228
7306
  const version = typeof raw === "object" && raw !== null && "schemaVersion" in raw ? raw.schemaVersion : void 0;
7229
7307
  if (typeof version === "number" && version > SUPPORTED_MANIFEST_SCHEMA) {
@@ -7351,6 +7429,34 @@ function withPortalEntry(existing, info) {
7351
7429
  }
7352
7430
  };
7353
7431
  }
7432
+ function withGatewayEntry(existing, info) {
7433
+ const base = existing ?? parseManifest({
7434
+ schemaVersion: SUPPORTED_MANIFEST_SCHEMA,
7435
+ project: { name: info.projectName }
7436
+ });
7437
+ const baseUrl = info.baseUrl.replace(/\/+$/, "");
7438
+ return {
7439
+ ...base,
7440
+ schemaVersion: SUPPORTED_MANIFEST_SCHEMA,
7441
+ generatedAt: (info.now ?? /* @__PURE__ */ new Date()).toISOString(),
7442
+ preview: void 0,
7443
+ project: { ...base.project, id: info.projectId, name: info.projectName },
7444
+ services: {
7445
+ ...base.services,
7446
+ gateway: {
7447
+ ...base.services.gateway,
7448
+ provider: info.provider,
7449
+ status: "ready",
7450
+ baseUrl,
7451
+ audience: baseUrl,
7452
+ capabilities: info.capabilities,
7453
+ ...info.openapiUrl ? { openapiUrl: info.openapiUrl } : {},
7454
+ ...info.openapiHash ? { openapiHash: info.openapiHash } : {},
7455
+ ...info.keyHeaderName ? { keyHeaderName: info.keyHeaderName } : {}
7456
+ }
7457
+ }
7458
+ };
7459
+ }
7354
7460
  async function writeManifest(state, manifest) {
7355
7461
  const file = manifestPathFor(state);
7356
7462
  await mkdir3(path10.dirname(file), { recursive: true });
@@ -7368,6 +7474,78 @@ async function writeManifest(state, manifest) {
7368
7474
  throw error;
7369
7475
  }
7370
7476
  }
7477
+ async function removeServicesFromManifest(state, names) {
7478
+ const existing = await readManifest(state);
7479
+ if (!existing) return;
7480
+ const services = Object.fromEntries(
7481
+ Object.entries(existing.services).filter(
7482
+ ([name]) => !names.includes(name)
7483
+ )
7484
+ );
7485
+ if (Object.keys(services).length === 0) {
7486
+ await rm(manifestPathFor(state), { force: true });
7487
+ return;
7488
+ }
7489
+ await writeManifest(state, { ...existing, services });
7490
+ }
7491
+
7492
+ // src/config/v2/gateway-request.ts
7493
+ function derivedCorsSites(sites) {
7494
+ const originOf = (url2) => {
7495
+ if (!url2) return void 0;
7496
+ try {
7497
+ const { protocol, origin } = new URL(url2);
7498
+ return protocol === "https:" ? origin : void 0;
7499
+ } catch {
7500
+ return void 0;
7501
+ }
7502
+ };
7503
+ const found = [
7504
+ { origin: originOf(sites.docsUrl), source: "docs" },
7505
+ { origin: originOf(sites.portalUrl), source: "portal" }
7506
+ ].flatMap((site) => site.origin ? [{ ...site, origin: site.origin }] : []);
7507
+ const seen = /* @__PURE__ */ new Set();
7508
+ return found.filter((site) => !seen.has(site.origin) && seen.add(site.origin)).sort((a, b) => a.origin < b.origin ? -1 : a.origin > b.origin ? 1 : 0);
7509
+ }
7510
+ var defined = (value) => Object.fromEntries(
7511
+ Object.entries(value).filter(([, item]) => item !== void 0)
7512
+ );
7513
+ function gatewayConfigBody(gateway, derivedCors = []) {
7514
+ const { origin, auth, policies } = gateway;
7515
+ const body = {
7516
+ provider: gateway.provider,
7517
+ origin: defined({
7518
+ mode: origin.mode,
7519
+ url: origin.url,
7520
+ healthPath: origin.healthPath,
7521
+ timeoutSeconds: origin.timeoutSeconds,
7522
+ auth: origin.auth ? defined({ ...origin.auth }) : void 0
7523
+ })
7524
+ };
7525
+ if (auth) body.auth = defined({ ...auth });
7526
+ if (gateway.export) body.export = defined({ ...gateway.export });
7527
+ if (derivedCors.length > 0) body.corsDerived = derivedCors;
7528
+ if (Object.keys(policies).length > 0) {
7529
+ body.policies = defined({
7530
+ access: policies.access ? defined({ ...policies.access }) : void 0,
7531
+ rateLimit: policies.rateLimit ? defined({ ...policies.rateLimit }) : void 0,
7532
+ quota: policies.quota ? defined({ ...policies.quota }) : void 0,
7533
+ cors: policies.cors,
7534
+ operations: policies.operations ? defined({ ...policies.operations }) : void 0
7535
+ });
7536
+ }
7537
+ return body;
7538
+ }
7539
+ function stableJson(value) {
7540
+ return JSON.stringify(
7541
+ value,
7542
+ (_key, item) => item && typeof item === "object" && !Array.isArray(item) ? Object.fromEntries(
7543
+ Object.entries(item).sort(
7544
+ ([a], [b]) => a < b ? -1 : a > b ? 1 : 0
7545
+ )
7546
+ ) : item
7547
+ );
7548
+ }
7371
7549
 
7372
7550
  // src/config/v2/wrapper.ts
7373
7551
  import { mkdir as mkdir4, readFile as readFile4, rename as rename2, rm as rm2, writeFile as writeFile5 } from "node:fs/promises";
@@ -8030,12 +8208,12 @@ async function planAddService(service, options) {
8030
8208
  )
8031
8209
  );
8032
8210
  const added = dependencyProblems(next, resolved.configPath).filter(
8033
- (issue2) => !before.has(issue2.code + issue2.path)
8211
+ (issue3) => !before.has(issue3.code + issue3.path)
8034
8212
  );
8035
8213
  if (added.length > 0) {
8036
8214
  throw new ServiceEditError(
8037
- added.map((issue2) => issue2.message).join(" "),
8038
- added.map((issue2) => issue2.hint).filter(Boolean).join(" ") || "Add the missing service first."
8215
+ added.map((issue3) => issue3.message).join(" "),
8216
+ added.map((issue3) => issue3.hint).filter(Boolean).join(" ") || "Add the missing service first."
8039
8217
  );
8040
8218
  }
8041
8219
  }
@@ -8064,11 +8242,11 @@ async function planRemoveService(service, options) {
8064
8242
  dependencyProblems(config2, resolved.configPath).map((i) => i.code + i.path)
8065
8243
  );
8066
8244
  const caused = dependencyProblems(next, resolved.configPath).filter(
8067
- (issue2) => !before.has(issue2.code + issue2.path)
8245
+ (issue3) => !before.has(issue3.code + issue3.path)
8068
8246
  );
8069
8247
  if (caused.length > 0) {
8070
8248
  throw new ServiceEditError(
8071
- `${service} cannot be removed while something depends on it: ${caused.map((issue2) => issue2.message).join(" ")}`,
8249
+ `${service} cannot be removed while something depends on it: ${caused.map((issue3) => issue3.message).join(" ")}`,
8072
8250
  "Remove or change the dependent service first."
8073
8251
  );
8074
8252
  }
@@ -8139,6 +8317,18 @@ async function setProjectId(dir, projectId) {
8139
8317
  await writeConfig(resolved.configPath, next);
8140
8318
  return true;
8141
8319
  }
8320
+ async function clearProjectId(dir, projectId) {
8321
+ const { config: config2, resolved } = loadConfigV2(dir);
8322
+ if (config2.project?.id !== projectId) return false;
8323
+ const { id: _id, ...project } = config2.project;
8324
+ const { project: _old, ...rest } = config2;
8325
+ const next = reparse(resolved.configPath, {
8326
+ ...rest,
8327
+ ...Object.keys(project).length > 0 ? { project } : {}
8328
+ });
8329
+ await writeConfig(resolved.configPath, next);
8330
+ return true;
8331
+ }
8142
8332
 
8143
8333
  // src/config/v2/ledger.ts
8144
8334
  init_invariant();
@@ -8273,13 +8463,24 @@ async function runPipeline(state, service, steps, options = {}) {
8273
8463
  }
8274
8464
  return outcomes;
8275
8465
  }
8466
+ async function clearServices(state, services) {
8467
+ const ledger = await readLedger(state);
8468
+ const remaining = Object.fromEntries(
8469
+ Object.entries(ledger.services).filter(
8470
+ ([name]) => !services.includes(name)
8471
+ )
8472
+ );
8473
+ if (Object.keys(remaining).length === Object.keys(ledger.services).length)
8474
+ return;
8475
+ await writeLedger(state, { ...ledger, services: remaining });
8476
+ }
8276
8477
 
8277
8478
  // src/cli/services/handler.ts
8278
8479
  init_invariant();
8279
8480
 
8280
8481
  // src/cli/check/v2.ts
8281
8482
  init_apitogo_config_core();
8282
- import { readFile as readFile11 } from "node:fs/promises";
8483
+ import { readFile as readFile12 } from "node:fs/promises";
8283
8484
  import path46 from "node:path";
8284
8485
 
8285
8486
  // src/cli/platform/client/platform-api.ts
@@ -8393,8 +8594,8 @@ var PlatformApiClient = class {
8393
8594
  constructor(config2) {
8394
8595
  this.config = config2;
8395
8596
  }
8396
- url(path95) {
8397
- return `${this.config.apiUrl}${path95.startsWith("/") ? path95 : `/${path95}`}`;
8597
+ url(path100) {
8598
+ return `${this.config.apiUrl}${path100.startsWith("/") ? path100 : `/${path100}`}`;
8398
8599
  }
8399
8600
  headers(extra) {
8400
8601
  return {
@@ -8404,9 +8605,9 @@ var PlatformApiClient = class {
8404
8605
  ...extra
8405
8606
  };
8406
8607
  }
8407
- async request(method, path95, init4) {
8608
+ async request(method, path100, init4) {
8408
8609
  const response = await fetchWithRetry(
8409
- this.url(path95),
8610
+ this.url(path100),
8410
8611
  {
8411
8612
  method,
8412
8613
  ...init4,
@@ -8621,6 +8822,92 @@ var PlatformApiClient = class {
8621
8822
  }
8622
8823
  return await response.json();
8623
8824
  }
8825
+ /** Uploads the OpenAPI file of the gateway. The same file twice changes nothing. */
8826
+ async putGatewaySpec(projectId, content) {
8827
+ return this.request(
8828
+ "PUT",
8829
+ `/api/v1/projects/${projectId}/gateway/spec`,
8830
+ {
8831
+ headers: { "Content-Type": "application/json" },
8832
+ body: content
8833
+ }
8834
+ );
8835
+ }
8836
+ /** Sends the wanted gateway state. The platform checks it all at once and answers with a ticket to poll. */
8837
+ putGatewayConfig(projectId, body) {
8838
+ return this.request(
8839
+ "PUT",
8840
+ `/api/v1/projects/${projectId}/gateway/config`,
8841
+ {
8842
+ headers: { "Content-Type": "application/json" },
8843
+ body: JSON.stringify(body)
8844
+ }
8845
+ );
8846
+ }
8847
+ getGatewayReconciliation(projectId, reconciliationId) {
8848
+ return this.request(
8849
+ "GET",
8850
+ `/api/v1/projects/${projectId}/gateway/reconciliations/${reconciliationId}`
8851
+ );
8852
+ }
8853
+ getGatewayStatus(projectId) {
8854
+ return this.request(
8855
+ "GET",
8856
+ `/api/v1/projects/${projectId}/gateway/status`
8857
+ );
8858
+ }
8859
+ /** The OpenAPI file as the gateway serves it (servers, security and denied operations already applied). */
8860
+ async getGatewayOpenApi(projectId) {
8861
+ const response = await fetchWithRetry(
8862
+ this.url(`/api/v1/projects/${projectId}/gateway/openapi.json`),
8863
+ { method: "GET", headers: this.headers() },
8864
+ {
8865
+ method: "GET",
8866
+ retryMutatingOnServerError: false,
8867
+ maxRetries: MAX_RETRIES
8868
+ }
8869
+ );
8870
+ if (!response.ok) throw await parseErrorResponse(response);
8871
+ return response.text();
8872
+ }
8873
+ getGatewayCapabilities(projectId) {
8874
+ return this.request(
8875
+ "GET",
8876
+ `/api/v1/projects/${projectId}/gateway/capabilities`
8877
+ );
8878
+ }
8879
+ /**
8880
+ * The online rules of `check`: origin address, OIDC discovery, plan capabilities, secrets and the OpenAPI text.
8881
+ * Changes nothing on the platform and always answers with the list of problems.
8882
+ */
8883
+ checkGateway(projectId, body) {
8884
+ return this.request(
8885
+ "POST",
8886
+ `/api/v1/projects/${projectId}/gateway/check`,
8887
+ {
8888
+ headers: { "Content-Type": "application/json" },
8889
+ body: JSON.stringify(body)
8890
+ }
8891
+ );
8892
+ }
8893
+ /** The dry run of a deploy: what the gateway would change. The platform changes nothing. */
8894
+ planGateway(projectId, body) {
8895
+ return this.request(
8896
+ "POST",
8897
+ `/api/v1/projects/${projectId}/gateway/plan`,
8898
+ {
8899
+ headers: { "Content-Type": "application/json" },
8900
+ body: JSON.stringify(body)
8901
+ }
8902
+ );
8903
+ }
8904
+ /** Asks the platform to remove the gateway. Answers with a ticket; the platform revokes keys and frees the unit. */
8905
+ deleteGateway(projectId) {
8906
+ return this.request(
8907
+ "DELETE",
8908
+ `/api/v1/projects/${projectId}/gateway`
8909
+ );
8910
+ }
8624
8911
  getDevPortalBackend(projectId) {
8625
8912
  return this.request(
8626
8913
  "GET",
@@ -8683,15 +8970,15 @@ var PlatformApiClient = class {
8683
8970
  }
8684
8971
  );
8685
8972
  }
8686
- deleteDevPortalPage(projectId, path95) {
8687
- const query = new URLSearchParams({ path: path95 });
8973
+ deleteDevPortalPage(projectId, path100) {
8974
+ const query = new URLSearchParams({ path: path100 });
8688
8975
  return this.request(
8689
8976
  "DELETE",
8690
8977
  `/api/v1/projects/${projectId}/dev-portal/pages/files?${query.toString()}`
8691
8978
  );
8692
8979
  }
8693
- renameDevPortalPage(projectId, path95, newName) {
8694
- const query = new URLSearchParams({ path: path95 });
8980
+ renameDevPortalPage(projectId, path100, newName) {
8981
+ const query = new URLSearchParams({ path: path100 });
8695
8982
  return this.request(
8696
8983
  "POST",
8697
8984
  `/api/v1/projects/${projectId}/dev-portal/pages/files/rename?${query.toString()}`,
@@ -8807,8 +9094,8 @@ var PlatformApiClient = class {
8807
9094
  * the project-less endpoint is used; `planTier` overrides the tier used for paid-plan checks.
8808
9095
  */
8809
9096
  validateGatewayPlans(plans, options = {}) {
8810
- const path95 = options.projectId ? `/api/v1/projects/${options.projectId}/gateway/plans/validate` : "/api/v1/gateway/plans/validate";
8811
- return this.request("POST", path95, {
9097
+ const path100 = options.projectId ? `/api/v1/projects/${options.projectId}/gateway/plans/validate` : "/api/v1/gateway/plans/validate";
9098
+ return this.request("POST", path100, {
8812
9099
  headers: { "Content-Type": "application/json" },
8813
9100
  body: JSON.stringify({ plans, planTier: options.planTier })
8814
9101
  });
@@ -8909,8 +9196,8 @@ var PlatformApiClient = class {
8909
9196
  }
8910
9197
  };
8911
9198
  async function readFileBytes(filePath) {
8912
- const { readFile: readFile27 } = await import("node:fs/promises");
8913
- const buffer = await readFile27(filePath);
9199
+ const { readFile: readFile29 } = await import("node:fs/promises");
9200
+ const buffer = await readFile29(filePath);
8914
9201
  const arrayBuffer = new ArrayBuffer(buffer.byteLength);
8915
9202
  new Uint8Array(arrayBuffer).set(buffer);
8916
9203
  return arrayBuffer;
@@ -9164,7 +9451,7 @@ import {
9164
9451
  // package.json
9165
9452
  var package_default = {
9166
9453
  name: "@lukoweb/apitogo",
9167
- version: "0.2.2",
9454
+ version: "0.2.10",
9168
9455
  type: "module",
9169
9456
  sideEffects: [
9170
9457
  "**/*.css",
@@ -9946,14 +10233,14 @@ var flattenAllOf = (schema2) => {
9946
10233
  };
9947
10234
 
9948
10235
  // src/lib/util/traverse.ts
9949
- var traverse = (specification, transform, path95 = []) => {
9950
- const transformed = transform(specification, path95);
10236
+ var traverse = (specification, transform, path100 = []) => {
10237
+ const transformed = transform(specification, path100);
9951
10238
  if (typeof transformed !== "object" || transformed === null) {
9952
10239
  return transformed;
9953
10240
  }
9954
10241
  const result = Array.isArray(transformed) ? [] : {};
9955
10242
  for (const [key, value] of Object.entries(transformed)) {
9956
- const currentPath = [...path95, key];
10243
+ const currentPath = [...path100, key];
9957
10244
  if (Array.isArray(value)) {
9958
10245
  result[key] = value.map(
9959
10246
  (item, index) => typeof item === "object" && item != null ? traverse(item, transform, [...currentPath, index.toString()]) : item
@@ -9981,9 +10268,9 @@ var resolveLocalRef = (schema2, ref) => {
9981
10268
  if (schemaCache?.has(ref)) {
9982
10269
  return schemaCache.get(ref);
9983
10270
  }
9984
- const path95 = ref.split("/").slice(1);
10271
+ const path100 = ref.split("/").slice(1);
9985
10272
  let current = schema2;
9986
- for (const segment of path95) {
10273
+ for (const segment of path100) {
9987
10274
  if (!current || typeof current !== "object") {
9988
10275
  current = null;
9989
10276
  }
@@ -10002,7 +10289,7 @@ var dereference = async (schema2, resolvers = []) => {
10002
10289
  }
10003
10290
  const cloned = structuredClone(schema2);
10004
10291
  const visited = /* @__PURE__ */ new Set();
10005
- const resolve = async (current, path95) => {
10292
+ const resolve = async (current, path100) => {
10006
10293
  if (isIndexableObject(current)) {
10007
10294
  if (visited.has(current)) {
10008
10295
  return CIRCULAR_REF;
@@ -10010,7 +10297,7 @@ var dereference = async (schema2, resolvers = []) => {
10010
10297
  visited.add(current);
10011
10298
  if (Array.isArray(current)) {
10012
10299
  for (let index = 0; index < current.length; index++) {
10013
- current[index] = await resolve(current[index], `${path95}/${index}`);
10300
+ current[index] = await resolve(current[index], `${path100}/${index}`);
10014
10301
  }
10015
10302
  } else {
10016
10303
  if ("$ref" in current && typeof current.$ref === "string") {
@@ -10021,13 +10308,13 @@ var dereference = async (schema2, resolvers = []) => {
10021
10308
  for (const resolver of resolvers) {
10022
10309
  const resolved = await resolver($ref);
10023
10310
  if (resolved) {
10024
- result2 = await resolve(resolved, path95);
10311
+ result2 = await resolve(resolved, path100);
10025
10312
  break;
10026
10313
  }
10027
10314
  }
10028
10315
  if (result2 === void 0) {
10029
10316
  const resolved = await resolveLocalRef(cloned, $ref);
10030
- result2 = await resolve(resolved, path95);
10317
+ result2 = await resolve(resolved, path100);
10031
10318
  }
10032
10319
  if (hasSiblings) {
10033
10320
  if (result2 === CIRCULAR_REF) {
@@ -10040,7 +10327,7 @@ var dereference = async (schema2, resolvers = []) => {
10040
10327
  return result2;
10041
10328
  }
10042
10329
  for (const key in current) {
10043
- current[key] = await resolve(current[key], `${path95}/${key}`);
10330
+ current[key] = await resolve(current[key], `${path100}/${key}`);
10044
10331
  }
10045
10332
  }
10046
10333
  visited.delete(current);
@@ -10079,9 +10366,9 @@ var upgradeSchema = (schema2) => {
10079
10366
  }
10080
10367
  return sub;
10081
10368
  });
10082
- schema2 = traverse(schema2, (sub, path95) => {
10369
+ schema2 = traverse(schema2, (sub, path100) => {
10083
10370
  if (sub.example !== void 0) {
10084
- if (isSchemaPath(path95 ?? [])) {
10371
+ if (isSchemaPath(path100 ?? [])) {
10085
10372
  sub.examples = [sub.example];
10086
10373
  } else {
10087
10374
  sub.examples = {
@@ -10094,11 +10381,11 @@ var upgradeSchema = (schema2) => {
10094
10381
  }
10095
10382
  return sub;
10096
10383
  });
10097
- schema2 = traverse(schema2, (schema3, path95) => {
10384
+ schema2 = traverse(schema2, (schema3, path100) => {
10098
10385
  if (schema3.type === "object" && schema3.properties !== void 0) {
10099
- const parentPath = path95?.slice(0, -1);
10386
+ const parentPath = path100?.slice(0, -1);
10100
10387
  const isMultipart = parentPath?.some((segment, index) => {
10101
- return segment === "content" && path95?.[index + 1] === "multipart/form-data";
10388
+ return segment === "content" && path100?.[index + 1] === "multipart/form-data";
10102
10389
  });
10103
10390
  if (isMultipart) {
10104
10391
  const entries = Object.entries(schema3.properties);
@@ -10112,8 +10399,8 @@ var upgradeSchema = (schema2) => {
10112
10399
  }
10113
10400
  return schema3;
10114
10401
  });
10115
- schema2 = traverse(schema2, (schema3, path95) => {
10116
- if (path95?.includes("content") && path95.includes("application/octet-stream")) {
10402
+ schema2 = traverse(schema2, (schema3, path100) => {
10403
+ if (path100?.includes("content") && path100.includes("application/octet-stream")) {
10117
10404
  return {};
10118
10405
  }
10119
10406
  if (schema3.type === "string" && schema3.format === "binary") {
@@ -10139,11 +10426,11 @@ var upgradeSchema = (schema2) => {
10139
10426
  }
10140
10427
  return sub;
10141
10428
  });
10142
- schema2 = traverse(schema2, (schema3, path95) => {
10429
+ schema2 = traverse(schema2, (schema3, path100) => {
10143
10430
  if (schema3.type === "string" && schema3.format === "byte") {
10144
- const parentPath = path95?.slice(0, -1);
10431
+ const parentPath = path100?.slice(0, -1);
10145
10432
  const contentMediaType = parentPath?.find(
10146
- (_, index) => path95?.[index - 1] === "content"
10433
+ (_, index) => path100?.[index - 1] === "content"
10147
10434
  );
10148
10435
  return {
10149
10436
  type: "string",
@@ -10155,7 +10442,7 @@ var upgradeSchema = (schema2) => {
10155
10442
  });
10156
10443
  return schema2;
10157
10444
  };
10158
- function isSchemaPath(path95) {
10445
+ function isSchemaPath(path100) {
10159
10446
  const schemaLocations = [
10160
10447
  ["components", "schemas"],
10161
10448
  "properties",
@@ -10168,10 +10455,10 @@ function isSchemaPath(path95) {
10168
10455
  ];
10169
10456
  return schemaLocations.some((location) => {
10170
10457
  if (Array.isArray(location)) {
10171
- return location.every((segment, index) => path95[index] === segment);
10458
+ return location.every((segment, index) => path100[index] === segment);
10172
10459
  }
10173
- return path95.includes(location);
10174
- }) || path95.includes("schema") || path95.some((segment) => segment.endsWith("Schema"));
10460
+ return path100.includes(location);
10461
+ }) || path100.includes("schema") || path100.some((segment) => segment.endsWith("Schema"));
10175
10462
  }
10176
10463
 
10177
10464
  // src/lib/oas/parser/index.ts
@@ -10253,13 +10540,13 @@ var OPENAPI_PROPS = /* @__PURE__ */ new Set([
10253
10540
  "anyOf",
10254
10541
  "oneOf"
10255
10542
  ]);
10256
- var handleCircularRefs = (obj, currentPath = /* @__PURE__ */ new WeakSet(), refs = /* @__PURE__ */ new WeakMap(), path95 = [], currentRefPaths = /* @__PURE__ */ new Set()) => {
10543
+ var handleCircularRefs = (obj, currentPath = /* @__PURE__ */ new WeakSet(), refs = /* @__PURE__ */ new WeakMap(), path100 = [], currentRefPaths = /* @__PURE__ */ new Set()) => {
10257
10544
  if (obj === null || typeof obj !== "object") return obj;
10258
10545
  const refPath = obj.__$ref;
10259
10546
  const isCircular = currentPath.has(obj) || typeof refPath === "string" && currentRefPaths.has(refPath);
10260
10547
  if (isCircular) {
10261
10548
  if (typeof refPath === "string") return SCHEMA_REF_PREFIX + refPath;
10262
- const circularProp = path95.find((p) => !OPENAPI_PROPS.has(p)) || path95[0];
10549
+ const circularProp = path100.find((p) => !OPENAPI_PROPS.has(p)) || path100[0];
10263
10550
  return [CIRCULAR_REF, circularProp].filter(Boolean).join(":");
10264
10551
  }
10265
10552
  if (refs.has(obj)) return refs.get(obj);
@@ -10269,7 +10556,7 @@ var handleCircularRefs = (obj, currentPath = /* @__PURE__ */ new WeakSet(), refs
10269
10556
  value,
10270
10557
  currentPath,
10271
10558
  refs,
10272
- [...path95, key],
10559
+ [...path100, key],
10273
10560
  currentRefPaths
10274
10561
  );
10275
10562
  const result = Array.isArray(obj) ? obj.map((item, i) => recurse(item, i.toString())) : Object.fromEntries(
@@ -10304,7 +10591,7 @@ var resolveExtensions = (obj) => Object.fromEntries(
10304
10591
  var getAllTags = (schema2) => {
10305
10592
  const rootTags = schema2.tags ?? [];
10306
10593
  const operations = Object.values(schema2.paths ?? {}).flatMap(
10307
- (path95) => HttpMethods.map((k) => path95?.[k]).filter((op) => op != null)
10594
+ (path100) => HttpMethods.map((k) => path100?.[k]).filter((op) => op != null)
10308
10595
  );
10309
10596
  const operationTags = new Set(operations.flatMap((op) => op.tags ?? []));
10310
10597
  const hasUntaggedOperations = operations.some(
@@ -10359,7 +10646,7 @@ var getAllSlugs = (ops, schemaTags = []) => {
10359
10646
  var getOperationSlugKey = (op) => [op.path, op.method, op.operationId, op.summary].filter(Boolean).join("-");
10360
10647
  var getAllOperations = (paths) => {
10361
10648
  const operations = Object.entries(paths ?? {}).flatMap(
10362
- ([path95, value]) => HttpMethods.flatMap((method) => {
10649
+ ([path100, value]) => HttpMethods.flatMap((method) => {
10363
10650
  if (!value?.[method]) return [];
10364
10651
  const operation = value[method];
10365
10652
  const pathParameters = value.parameters ?? [];
@@ -10379,7 +10666,7 @@ var getAllOperations = (paths) => {
10379
10666
  return {
10380
10667
  ...operation,
10381
10668
  method,
10382
- path: path95,
10669
+ path: path100,
10383
10670
  parameters,
10384
10671
  servers,
10385
10672
  tags: operation.tags ?? []
@@ -10737,8 +11024,8 @@ var Schema = builder.objectRef("Schema").implement({
10737
11024
  }),
10738
11025
  paths: t.field({
10739
11026
  type: [PathItem],
10740
- resolve: (root) => Object.entries(root.paths ?? {}).map(([path95, value]) => ({
10741
- path: path95,
11027
+ resolve: (root) => Object.entries(root.paths ?? {}).map(([path100, value]) => ({
11028
+ path: path100,
10742
11029
  // biome-ignore lint/style/noNonNullAssertion: value is guaranteed to be defined
10743
11030
  methods: Object.keys(value)
10744
11031
  }))
@@ -10903,7 +11190,7 @@ init_joinUrl();
10903
11190
 
10904
11191
  // src/vite/api/schema-codegen.ts
10905
11192
  var unescapeJsonPointer = (uri) => decodeURIComponent(uri.replace(/~1/g, "/").replace(/~0/g, "~"));
10906
- var getSegmentsFromPath = (path95) => path95.split("/").slice(1).map(unescapeJsonPointer);
11193
+ var getSegmentsFromPath = (path100) => path100.split("/").slice(1).map(unescapeJsonPointer);
10907
11194
  var createLocalRefMap = (obj) => {
10908
11195
  const refMap = /* @__PURE__ */ new Map();
10909
11196
  const siblingsMap = /* @__PURE__ */ new Map();
@@ -10934,16 +11221,16 @@ var replaceMarkers = (code, mergedRefs) => code.replace(/"__refMap:(.*?)"/g, '__
10934
11221
  /"__refMap\+Siblings:(.*?)"/g,
10935
11222
  (_, key) => mergedRefs.get(key) ?? `__refMap["${key}"]`
10936
11223
  );
10937
- var lookup = (schema2, path95, filePath) => {
10938
- const parts = getSegmentsFromPath(path95);
11224
+ var lookup = (schema2, path100, filePath) => {
11225
+ const parts = getSegmentsFromPath(path100);
10939
11226
  let val = schema2;
10940
11227
  for (const part of parts) {
10941
11228
  while (val.$ref?.startsWith("#/")) {
10942
- val = val.$ref === path95 ? val : lookup(schema2, val.$ref, filePath);
11229
+ val = val.$ref === path100 ? val : lookup(schema2, val.$ref, filePath);
10943
11230
  }
10944
11231
  if (val[part] === void 0) {
10945
11232
  throw new Error(
10946
- `Error in ${filePath ?? "code generation"}: Could not find path segment ${part} in path: ${path95}`
11233
+ `Error in ${filePath ?? "code generation"}: Could not find path segment ${part} in path: ${path100}`
10947
11234
  );
10948
11235
  }
10949
11236
  val = val[part];
@@ -11188,8 +11475,8 @@ var SchemaManager = class {
11188
11475
  }
11189
11476
  }
11190
11477
  };
11191
- getLatestSchema = (path95) => this.processedSchemas[path95]?.at(0);
11192
- getSchemasForPath = (path95) => this.processedSchemas[path95];
11478
+ getLatestSchema = (path100) => this.processedSchemas[path100]?.at(0);
11479
+ getSchemasForPath = (path100) => this.processedSchemas[path100];
11193
11480
  getSchemaImports = () => Object.values(this.processedSchemas).flat().filter((s) => s.importKey);
11194
11481
  getUrlToFilePathMap = () => {
11195
11482
  const map = /* @__PURE__ */ new Map();
@@ -13625,14 +13912,14 @@ async function generateSitemap({
13625
13912
 
13626
13913
  // src/vite/prerender/utils.ts
13627
13914
  init_joinUrl();
13628
- var resolveRoutePath = (path95) => {
13629
- const segments = path95.split("/");
13915
+ var resolveRoutePath = (path100) => {
13916
+ const segments = path100.split("/");
13630
13917
  if (segments.some((s) => s.startsWith(":") && !s.endsWith("?"))) {
13631
13918
  return void 0;
13632
13919
  }
13633
13920
  return segments.filter((s) => !s.startsWith(":")).join("/") || void 0;
13634
13921
  };
13635
- var isSkipped = (path95) => path95.includes("*") || /^\d+$/.test(path95);
13922
+ var isSkipped = (path100) => path100.includes("*") || /^\d+$/.test(path100);
13636
13923
  var resolveRoutes = (routes, parentPath = "") => routes.flatMap((route) => {
13637
13924
  if (route.path && isSkipped(route.path)) return [];
13638
13925
  const routePath = route.path ? resolveRoutePath(route.path) : void 0;
@@ -14081,11 +14368,94 @@ async function buildV2(options) {
14081
14368
  return { skipped, buildDir, hash: hash2 };
14082
14369
  }
14083
14370
 
14371
+ // src/cli/check/gateway-online.ts
14372
+ import { readFile as readFile11 } from "node:fs/promises";
14373
+ var SPEC_SEND_LIMIT = 5 * 1024 * 1024 + 64 * 1024;
14374
+ var issue2 = (code, path100, message, hint) => ({ code, path: path100, message, hint });
14375
+ function capabilityIssues(gateway, answer) {
14376
+ const can = answer.capabilities;
14377
+ const plan2 = `your ${answer.tier} plan`;
14378
+ const found = [];
14379
+ const unsupported = (path100, what, hint) => found.push(
14380
+ issue2(
14381
+ "GATEWAY_CAPABILITY_UNSUPPORTED",
14382
+ path100,
14383
+ `${what} is not available on ${plan2}.`,
14384
+ hint
14385
+ )
14386
+ );
14387
+ if (gateway.origin.mode === "managed" && can.managedOrigin !== true)
14388
+ unsupported(
14389
+ "gateway.origin.mode",
14390
+ "A managed origin",
14391
+ "Use a proxy origin, or change your plan."
14392
+ );
14393
+ if (gateway.origin.mode === "proxy" && can.proxyOrigin !== true)
14394
+ unsupported(
14395
+ "gateway.origin.mode",
14396
+ "A proxy origin",
14397
+ "Use a managed origin, or change your plan."
14398
+ );
14399
+ if (gateway.origin.auth?.type === "mtls" && can.originMtls !== true)
14400
+ unsupported(
14401
+ "gateway.origin.auth.type",
14402
+ "Client certificates (mtls) for the origin",
14403
+ 'Use type "header" with a secret.'
14404
+ );
14405
+ if (gateway.policies.access?.bindKeyToToken === true && can.bindKeyToToken !== true)
14406
+ unsupported(
14407
+ "gateway.policies.access.bindKeyToToken",
14408
+ "Binding a key to a token",
14409
+ "Remove bindKeyToToken."
14410
+ );
14411
+ for (const [name, scope] of [
14412
+ ["rateLimit", gateway.policies.rateLimit?.scope],
14413
+ ["quota", gateway.policies.quota?.scope]
14414
+ ])
14415
+ if (scope === "entitlement" && can.quotaPerEntitlement !== true)
14416
+ unsupported(
14417
+ `gateway.policies.${name}.scope`,
14418
+ "Counting per entitlement",
14419
+ 'Use scope "key".'
14420
+ );
14421
+ return found;
14422
+ }
14423
+ async function onlineGatewayRules(gateway, projectId, api, derivedCors = []) {
14424
+ let spec;
14425
+ const specFile = gateway.specs[0];
14426
+ if (specFile) {
14427
+ try {
14428
+ const text = await readFile11(specFile, "utf8");
14429
+ if (text.length <= SPEC_SEND_LIMIT) spec = text;
14430
+ } catch {
14431
+ }
14432
+ }
14433
+ try {
14434
+ const capabilities = await api.capabilities(projectId);
14435
+ const result = await api.check(projectId, {
14436
+ config: gatewayConfigBody(gateway, derivedCors),
14437
+ ...spec === void 0 ? {} : { spec }
14438
+ });
14439
+ const fromPlatform = result.issues.map(
14440
+ (item) => issue2(item.code, item.field, item.message, item.hint)
14441
+ );
14442
+ return {
14443
+ issues: [...capabilityIssues(gateway, capabilities), ...fromPlatform]
14444
+ };
14445
+ } catch (error) {
14446
+ const reason = error instanceof Error ? error.message : String(error);
14447
+ return {
14448
+ issues: [],
14449
+ skipped: `the gateway's online rules were not run: the platform could not be asked (${reason})`
14450
+ };
14451
+ }
14452
+ }
14453
+
14084
14454
  // src/cli/check/v2.ts
14085
14455
  async function isConfigV2(dir) {
14086
14456
  try {
14087
14457
  return /"version"\s*:\s*2\b/.test(
14088
- await readFile11(path46.join(dir, APITOGO_CONFIG_FILENAME), "utf8")
14458
+ await readFile12(path46.join(dir, APITOGO_CONFIG_FILENAME), "utf8")
14089
14459
  );
14090
14460
  } catch {
14091
14461
  return false;
@@ -14104,6 +14474,46 @@ async function fetchSecretNames(config2, configuredProjectId, args, deps) {
14104
14474
  };
14105
14475
  }
14106
14476
  }
14477
+ async function addGatewayOnlineRules(report, resolved, args, deps) {
14478
+ const gateway = resolved.gateway;
14479
+ if (args.offline || !deps.gateway || !gateway?.enabled) return;
14480
+ const projectId = resolved.project.id || deps.envProjectId?.trim();
14481
+ if (!projectId) {
14482
+ report.skipped.push(
14483
+ "the gateway's online rules were not run: this folder is not linked to a project (run `apitogo project create`)"
14484
+ );
14485
+ return;
14486
+ }
14487
+ const manifest = await readManifest(resolved.state);
14488
+ const result = await onlineGatewayRules(
14489
+ gateway,
14490
+ projectId,
14491
+ deps.gateway,
14492
+ derivedCorsSites({
14493
+ portalUrl: manifest?.services.portal?.url,
14494
+ docsUrl: resolved.portal?.docs.provider === "external" ? resolved.portal.docs.url : void 0
14495
+ })
14496
+ );
14497
+ if (result.skipped) {
14498
+ report.skipped.push(result.skipped);
14499
+ return;
14500
+ }
14501
+ const seen = new Set(
14502
+ [...report.errors, ...report.warnings].map((i) => `${i.code}|${i.path}`)
14503
+ );
14504
+ for (const item of result.issues) {
14505
+ const key = `${item.code}|${item.path}`;
14506
+ if (seen.has(key)) continue;
14507
+ seen.add(key);
14508
+ report.errors.push(item);
14509
+ }
14510
+ report.skipped = report.skipped.filter(
14511
+ (note) => !note.startsWith("online rules were not run")
14512
+ );
14513
+ report.skipped.unshift(
14514
+ "online rules ran for the gateway only; the other services have none yet"
14515
+ );
14516
+ }
14107
14517
  async function validatePortalBuild(dir) {
14108
14518
  try {
14109
14519
  await buildV2({ dir, force: true, jsonOnly: true });
@@ -14113,7 +14523,7 @@ async function validatePortalBuild(dir) {
14113
14523
  return {
14114
14524
  code: error.issues[0]?.code ?? "PORTAL_BUILD_FAILED",
14115
14525
  path: error.issues[0]?.path ?? "portal",
14116
- message: error.issues.map((issue2) => issue2.message).join(" "),
14526
+ message: error.issues.map((issue3) => issue3.message).join(" "),
14117
14527
  hint: error.issues[0]?.hint
14118
14528
  };
14119
14529
  }
@@ -14142,6 +14552,7 @@ async function runCheckV2(dir, args = {}, deps = {}) {
14142
14552
  knownSecrets: online.names
14143
14553
  });
14144
14554
  if (online.failure) report.skipped.push(online.failure);
14555
+ await addGatewayOnlineRules(report, resolved, args, deps);
14145
14556
  } catch (error) {
14146
14557
  if (!(error instanceof ConfigError)) throw error;
14147
14558
  report = {
@@ -14168,11 +14579,11 @@ async function runCheckV2(dir, args = {}, deps = {}) {
14168
14579
  ...report
14169
14580
  };
14170
14581
  }
14171
- var line = (label, issue2) => `${label}: ${formatConfigIssue(issue2)}`;
14582
+ var line = (label, issue3) => `${label}: ${formatConfigIssue(issue3)}`;
14172
14583
  function formatCheckV2(output4) {
14173
14584
  const lines = [
14174
- ...output4.errors.map((issue2) => line("error", issue2)),
14175
- ...output4.warnings.map((issue2) => line("warning", issue2)),
14585
+ ...output4.errors.map((issue3) => line("error", issue3)),
14586
+ ...output4.warnings.map((issue3) => line("warning", issue3)),
14176
14587
  ...output4.skipped.map((note) => `not checked: ${note}`)
14177
14588
  ];
14178
14589
  const errors = `${output4.errors.length} error${output4.errors.length === 1 ? "" : "s"}`;
@@ -14189,7 +14600,11 @@ function platformDependencies(args) {
14189
14600
  const client = new PlatformApiClient(config2);
14190
14601
  return {
14191
14602
  envProjectId: config2.defaultProjectId,
14192
- listSecrets: async (projectId) => (await client.listSecrets(projectId)).secrets.map((s) => s.name)
14603
+ listSecrets: async (projectId) => (await client.listSecrets(projectId)).secrets.map((s) => s.name),
14604
+ gateway: {
14605
+ check: (projectId, body) => client.checkGateway(projectId, body),
14606
+ capabilities: (projectId) => client.getGatewayCapabilities(projectId)
14607
+ }
14193
14608
  };
14194
14609
  } catch {
14195
14610
  return {};
@@ -14205,6 +14620,9 @@ async function checkV2(dir, args, deps = platformDependencies(args)) {
14205
14620
  if (output4.status === "failed") process.exitCode = 1;
14206
14621
  }
14207
14622
 
14623
+ // src/cli/services/handler.ts
14624
+ init_package_json();
14625
+
14208
14626
  // src/cli/init/v2-handler.ts
14209
14627
  import path47 from "node:path";
14210
14628
  import { createInterface } from "node:readline/promises";
@@ -14339,7 +14757,7 @@ async function initV2(argv) {
14339
14757
  `Created ${path47.basename(plan2.configPath)} (config version 2, ${plan2.layout} layout) in ${dir}.`
14340
14758
  );
14341
14759
  printHintToConsole(
14342
- "Config version 2 is a preview: check, run and build read it; deploy still reads the version 1 config."
14760
+ "Config version 2 is a preview: add, remove, check, run, build, plan, status, deploy and secrets read it."
14343
14761
  );
14344
14762
  } catch (error) {
14345
14763
  const message = error instanceof Error ? error.message : String(error);
@@ -14361,6 +14779,81 @@ async function initV2(argv) {
14361
14779
  }
14362
14780
  }
14363
14781
 
14782
+ // src/cli/services/packages.ts
14783
+ import { spawn } from "node:child_process";
14784
+ import { existsSync as existsSync5 } from "node:fs";
14785
+ import path48 from "node:path";
14786
+ var USER_PANEL_PACKAGE = "@lukoweb/apitogo-module-user-panel";
14787
+ var BILLING_PLUGIN_PACKAGE = "@lukoweb/apitogo-plugin-dev-portal-billing";
14788
+ function packagesForConfig(config2) {
14789
+ const portal = config2.portal;
14790
+ if (!portal || portal.enabled === false || portal.console?.enabled === false)
14791
+ return [];
14792
+ const identity = config2.identity && config2.identity.enabled !== false;
14793
+ const plans = config2.plans && config2.plans.enabled !== false;
14794
+ return [
14795
+ ...identity || plans ? [USER_PANEL_PACKAGE] : [],
14796
+ ...plans ? [BILLING_PLUGIN_PACKAGE] : []
14797
+ ];
14798
+ }
14799
+ function isInstalled(dir, name) {
14800
+ for (let current = path48.resolve(dir); ; current = path48.dirname(current)) {
14801
+ if (existsSync5(path48.join(current, "node_modules", name, "package.json")))
14802
+ return true;
14803
+ if (path48.dirname(current) === current) return false;
14804
+ }
14805
+ }
14806
+ function missingPackages(dir, names) {
14807
+ return names.filter((name) => !isInstalled(dir, name));
14808
+ }
14809
+ function detectPackageManager(dir) {
14810
+ if (existsSync5(path48.join(dir, "pnpm-lock.yaml"))) return "pnpm";
14811
+ if (existsSync5(path48.join(dir, "yarn.lock"))) return "yarn";
14812
+ return "npm";
14813
+ }
14814
+ function installCommand(manager, names, version) {
14815
+ const specs = names.map((name) => `${name}@${version}`);
14816
+ return manager === "npm" ? { command: "npm", args: ["install", ...specs] } : { command: manager, args: ["add", ...specs] };
14817
+ }
14818
+ var runInstaller = (dir, { command: command2, args }) => new Promise((resolve, reject) => {
14819
+ const child = spawn(command2, args, {
14820
+ cwd: dir,
14821
+ stdio: "inherit",
14822
+ shell: process.platform === "win32"
14823
+ });
14824
+ child.on("error", reject);
14825
+ child.on(
14826
+ "exit",
14827
+ (code) => code === 0 ? resolve() : reject(
14828
+ new Error(
14829
+ `${command2} ${args.join(" ")} failed with exit code ${code}.`
14830
+ )
14831
+ )
14832
+ );
14833
+ });
14834
+ async function installMissingPackages(dir, config2, version, options) {
14835
+ const missing = missingPackages(dir, packagesForConfig(config2));
14836
+ if (missing.length === 0) return void 0;
14837
+ const command2 = installCommand(detectPackageManager(dir), missing, version);
14838
+ const shown = `${command2.command} ${command2.args.join(" ")}`;
14839
+ if (!options.install)
14840
+ return {
14841
+ status: "skipped",
14842
+ packages: missing,
14843
+ reason: "--no-install was given",
14844
+ command: shown
14845
+ };
14846
+ if (!existsSync5(path48.join(dir, "package.json")))
14847
+ return {
14848
+ status: "skipped",
14849
+ packages: missing,
14850
+ reason: "there is no package.json here",
14851
+ command: shown
14852
+ };
14853
+ await (options.installer ?? runInstaller)(dir, command2);
14854
+ return { status: "installed", packages: missing, command: shown };
14855
+ }
14856
+
14364
14857
  // src/cli/services/handler.ts
14365
14858
  var list = (value) => {
14366
14859
  const items = (Array.isArray(value) ? value : value === void 0 ? [] : [value]).filter(
@@ -14369,7 +14862,7 @@ var list = (value) => {
14369
14862
  return items.length > 0 ? items : void 0;
14370
14863
  };
14371
14864
  function addOptionsFromArguments(service, argv) {
14372
- const dir = path48.resolve(process.cwd(), argv.dir ?? ".");
14865
+ const dir = path49.resolve(process.cwd(), argv.dir ?? ".");
14373
14866
  const issuers = list(argv.issuer);
14374
14867
  return {
14375
14868
  dir,
@@ -14428,12 +14921,12 @@ function describe(edit, dir) {
14428
14921
  return {
14429
14922
  service: edit.service,
14430
14923
  action: edit.action,
14431
- willCreate: edit.files.map((file) => path48.relative(dir, file.path)),
14432
- keep: edit.keep.map((file) => path48.relative(dir, file)),
14924
+ willCreate: edit.files.map((file) => path49.relative(dir, file.path)),
14925
+ keep: edit.keep.map((file) => path49.relative(dir, file)),
14433
14926
  notes: edit.notes
14434
14927
  };
14435
14928
  }
14436
- async function run(action, argv, plan2) {
14929
+ async function run(action, argv, plan2, deps = {}) {
14437
14930
  const json = argv.json === true;
14438
14931
  try {
14439
14932
  if (!isServiceName(argv.service)) {
@@ -14442,7 +14935,7 @@ async function run(action, argv, plan2) {
14442
14935
  `Choose one of ${SERVICES.join(", ")}.`
14443
14936
  );
14444
14937
  }
14445
- const dir = path48.resolve(process.cwd(), argv.dir ?? ".");
14938
+ const dir = path49.resolve(process.cwd(), argv.dir ?? ".");
14446
14939
  if (!await isConfigV2(dir)) {
14447
14940
  throw new ServiceEditError(
14448
14941
  `${action} works with a config version 2 project, and there is none in ${dir}.`,
@@ -14450,9 +14943,13 @@ async function run(action, argv, plan2) {
14450
14943
  );
14451
14944
  }
14452
14945
  const edit = await plan2(argv.service);
14453
- if (!json)
14946
+ const needed = action === "add" ? missingPackages(dir, packagesForConfig(edit.config)) : [];
14947
+ if (!json) {
14454
14948
  for (const line2 of formatServiceEdit(edit))
14455
14949
  printDiagnosticsToConsole(line2);
14950
+ if (needed.length > 0 && argv.install !== false)
14951
+ printDiagnosticsToConsole(`Will install: ${needed.join(", ")}`);
14952
+ }
14456
14953
  if (argv.dryRun) {
14457
14954
  if (json)
14458
14955
  console.log(
@@ -14478,13 +14975,15 @@ async function run(action, argv, plan2) {
14478
14975
  }
14479
14976
  }
14480
14977
  const { created } = await applyServiceEdit(edit);
14978
+ const install = action === "add" ? await installPackages(dir, edit, argv, deps, json) : void 0;
14481
14979
  if (json) {
14482
14980
  console.log(
14483
14981
  JSON.stringify(
14484
14982
  {
14485
14983
  status: action === "add" ? "added" : "removed",
14486
14984
  ...describe(edit, dir),
14487
- created: created.map((file) => path48.relative(dir, file))
14985
+ created: created.map((file) => path49.relative(dir, file)),
14986
+ ...install ? { packages: install } : {}
14488
14987
  },
14489
14988
  null,
14490
14989
  2
@@ -14493,7 +14992,7 @@ async function run(action, argv, plan2) {
14493
14992
  return;
14494
14993
  }
14495
14994
  printResultToConsole(
14496
- `${action === "add" ? "Added" : "Removed"} ${edit.service} in ${path48.basename(edit.configPath)}.`
14995
+ `${action === "add" ? "Added" : "Removed"} ${edit.service} in ${path49.basename(edit.configPath)}.`
14497
14996
  );
14498
14997
  } catch (error) {
14499
14998
  const message = error instanceof Error ? error.message : String(error);
@@ -14514,16 +15013,44 @@ async function run(action, argv, plan2) {
14514
15013
  );
14515
15014
  }
14516
15015
  }
14517
- var addService = (argv) => run(
15016
+ async function installPackages(dir, edit, argv, deps, json) {
15017
+ try {
15018
+ const outcome = await installMissingPackages(
15019
+ dir,
15020
+ edit.config,
15021
+ deps.version ?? getZudokuPackageJson().version,
15022
+ { install: argv.install !== false, installer: deps.installer }
15023
+ );
15024
+ if (outcome?.status === "skipped" && !json)
15025
+ printDiagnosticsToConsole(
15026
+ `Not installed (${outcome.reason}). Run: ${outcome.command}`
15027
+ );
15028
+ if (outcome?.status === "installed" && !json)
15029
+ printDiagnosticsToConsole(`Installed: ${outcome.packages.join(", ")}`);
15030
+ return outcome;
15031
+ } catch (error) {
15032
+ const reason = error instanceof Error ? error.message : String(error);
15033
+ if (!json)
15034
+ printDiagnosticsToConsole(
15035
+ `The packages could not be installed (${reason}). The service was added; install them yourself before you build.`
15036
+ );
15037
+ return {
15038
+ status: "failed",
15039
+ reason
15040
+ };
15041
+ }
15042
+ }
15043
+ var addService = (argv, deps = {}) => run(
14518
15044
  "add",
14519
15045
  argv,
14520
- (service) => planAddService(service, addOptionsFromArguments(service, argv))
15046
+ (service) => planAddService(service, addOptionsFromArguments(service, argv)),
15047
+ deps
14521
15048
  );
14522
15049
  var removeService = (argv) => run(
14523
15050
  "remove",
14524
15051
  argv,
14525
15052
  (service) => planRemoveService(service, {
14526
- dir: path48.resolve(process.cwd(), argv.dir ?? ".")
15053
+ dir: path49.resolve(process.cwd(), argv.dir ?? ".")
14527
15054
  })
14528
15055
  );
14529
15056
 
@@ -14558,6 +15085,10 @@ var add_default = {
14558
15085
  type: "boolean",
14559
15086
  default: false,
14560
15087
  describe: "Print a JSON result on stdout"
15088
+ }).option("install", {
15089
+ type: "boolean",
15090
+ default: true,
15091
+ describe: "Install the plugin packages the service needs (use --no-install to skip)"
14561
15092
  }).option("name", { type: "string", describe: "portal: display name" }).option("docs", {
14562
15093
  type: "string",
14563
15094
  array: true,
@@ -14667,16 +15198,16 @@ var add_default = {
14667
15198
  };
14668
15199
 
14669
15200
  // src/cli/build/handler.ts
14670
- import path50 from "node:path";
15201
+ import path51 from "node:path";
14671
15202
  init_logger();
14672
15203
  init_package_json();
14673
15204
 
14674
15205
  // src/cli/preview/handler.ts
14675
- import path49 from "node:path";
15206
+ import path50 from "node:path";
14676
15207
  import { preview as vitePreview } from "vite";
14677
15208
  var DEFAULT_PREVIEW_PORT = 4e3;
14678
15209
  async function preview(argv) {
14679
- const dir = path49.resolve(process.cwd(), argv.dir);
15210
+ const dir = path50.resolve(process.cwd(), argv.dir);
14680
15211
  const viteConfig = await getViteConfig(dir, {
14681
15212
  command: "serve",
14682
15213
  mode: "production",
@@ -14719,7 +15250,7 @@ async function build(argv) {
14719
15250
  printDiagnosticsToConsole("");
14720
15251
  printDiagnosticsToConsole("");
14721
15252
  }
14722
- const dir = path50.resolve(process.cwd(), argv.dir);
15253
+ const dir = path51.resolve(process.cwd(), argv.dir);
14723
15254
  try {
14724
15255
  if (await isConfigV2(dir)) {
14725
15256
  const result = await buildV2({ dir, jsonOnly });
@@ -14784,10 +15315,10 @@ var build_default = {
14784
15315
  };
14785
15316
 
14786
15317
  // src/cli/check/handler.ts
14787
- import path57 from "node:path";
15318
+ import path58 from "node:path";
14788
15319
 
14789
15320
  // src/cli/platform/tools/check-publish-readiness.ts
14790
- import path56 from "node:path";
15321
+ import path57 from "node:path";
14791
15322
 
14792
15323
  // src/cli/platform/helpers/dev-portal-backend.ts
14793
15324
  function deriveProvisioningState(backend) {
@@ -14870,12 +15401,12 @@ function formatDevPortalBackendResponse(projectId, backend) {
14870
15401
 
14871
15402
  // src/cli/platform/helpers/dev-portal-manifest.ts
14872
15403
  init_gateway_limits();
14873
- import { existsSync as existsSync5 } from "node:fs";
14874
- import { access as access5, mkdir as mkdir13, readFile as readFile12, writeFile as writeFile14 } from "node:fs/promises";
14875
- import path52 from "node:path";
15404
+ 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";
15406
+ import path53 from "node:path";
14876
15407
 
14877
15408
  // src/cli/make-config/json-config.ts
14878
- import path51 from "node:path";
15409
+ import path52 from "node:path";
14879
15410
 
14880
15411
  // src/cli/make-config/branding-assets.ts
14881
15412
  var LOGO_LIGHT = '<svg xmlns="http://www.w3.org/2000/svg" width="201.57" height="72.66" viewBox="-0.09 -46.99 201.57 72.66">\n <path d="M13.5 0.39Q8.03 0.39 4.76 -2.09Q1.48 -4.57 1.41 -9.61H8.03Q8.14 -7.48 9.6 -6.41Q11.05 -5.33 13.42 -5.33Q15.57 -5.33 16.8 -6.2Q18.03 -7.07 18.03 -8.46Q18.03 -9.71 16.87 -10.44Q15.7 -11.17 13.32 -11.72L10.23 -12.44Q6.46 -13.3 4.32 -15.25Q2.17 -17.21 2.17 -20.49Q2.17 -23.18 3.62 -25.21Q5.08 -27.23 7.64 -28.36Q10.2 -29.49 13.5 -29.49Q16.88 -29.49 19.36 -28.35Q21.84 -27.21 23.2 -25.17Q24.57 -23.12 24.61 -20.43H17.99Q17.85 -22.01 16.7 -22.89Q15.55 -23.77 13.48 -23.77Q11.41 -23.77 10.35 -22.97Q9.3 -22.17 9.3 -20.96Q9.3 -19.61 10.47 -18.88Q11.64 -18.14 13.59 -17.71L16.11 -17.13Q20.33 -16.19 22.67 -14.06Q25 -11.93 25 -8.44Q25 -4.3 21.95 -1.95Q18.91 0.39 13.5 0.39Z M32.97 0.39Q29.82 0.39 27.8 -1.21Q25.78 -2.81 25.78 -6.05Q25.78 -8.48 26.89 -9.9Q28 -11.33 29.85 -12.01Q31.7 -12.7 33.9 -12.89Q36.68 -13.12 37.88 -13.47Q39.08 -13.81 39.08 -14.82V-14.9Q39.08 -16.04 38.29 -16.65Q37.5 -17.27 36.11 -17.27Q34.68 -17.27 33.77 -16.66Q32.85 -16.05 32.58 -15.02L26.4 -15.82Q27.05 -18.61 29.56 -20.36Q32.07 -22.11 36.17 -22.11Q38.88 -22.11 41.08 -21.27Q43.28 -20.43 44.58 -18.8Q45.88 -17.17 45.88 -14.82V0H39.47V-3.07H39.29Q37.48 0.39 32.97 0.39ZM35.04 -4.12Q36.75 -4.12 37.95 -5.14Q39.14 -6.15 39.14 -7.77V-9.86Q38.55 -9.55 37.44 -9.34Q36.33 -9.12 35.37 -8.96Q32.22 -8.5 32.22 -6.37Q32.22 -5.27 33.01 -4.7Q33.81 -4.12 35.04 -4.12Z M48.59 0V-21.84H54.86L55.17 -17.5Q56.07 -19.96 57.78 -21.04Q59.49 -22.13 61.46 -22.13Q63.53 -22.13 64.91 -20.94Q66.28 -19.75 67.08 -17.07Q68.08 -19.77 70.02 -20.95Q71.97 -22.13 74.17 -22.13Q77.24 -22.13 79.2 -20.16Q81.17 -18.18 81.17 -14.73V0H74.37V-13.18Q74.37 -14.84 73.5 -15.7Q72.63 -16.56 71.28 -16.56Q69.84 -16.56 69 -15.62Q68.16 -14.67 68.16 -13.11V0H61.61V-13.28Q61.61 -14.79 60.77 -15.67Q59.92 -16.56 58.57 -16.56Q57.18 -16.56 56.29 -15.62Q55.4 -14.69 55.4 -12.97V0Z M83.88 8.16V-21.84H90.62V-18.09H90.85Q91.26 -19.06 92.04 -19.99Q92.82 -20.92 94.05 -21.51Q95.28 -22.11 97.02 -22.11Q99.33 -22.11 101.31 -20.9Q103.29 -19.69 104.51 -17.21Q105.73 -14.73 105.73 -10.9Q105.73 -7.19 104.55 -4.7Q103.37 -2.21 101.39 -0.95Q99.4 0.31 96.98 0.31Q94.48 0.31 92.99 -0.87Q91.49 -2.05 90.85 -3.54H90.69V8.16ZM94.66 -5Q96.63 -5 97.68 -6.62Q98.72 -8.24 98.72 -10.9Q98.72 -13.55 97.68 -15.17Q96.63 -16.78 94.66 -16.78Q92.71 -16.78 91.62 -15.2Q90.54 -13.61 90.54 -10.9Q90.54 -8.22 91.62 -6.61Q92.71 -5 94.66 -5Z M114.4 -29.1V0H107.59V-29.1Z M127.29 0.41Q122.15 0.41 119.21 -2.59Q116.27 -5.59 116.27 -10.84Q116.27 -14.2 117.59 -16.74Q118.91 -19.28 121.33 -20.69Q123.75 -22.11 127.04 -22.11Q130.1 -22.11 132.47 -20.83Q134.85 -19.55 136.21 -17.06Q137.56 -14.57 137.56 -10.98V-9.26H122.97Q123.07 -6.89 124.27 -5.71Q125.47 -4.53 127.41 -4.53Q128.77 -4.53 129.77 -5.11Q130.77 -5.68 131.18 -6.78L137.25 -5.78Q136.43 -2.95 133.84 -1.27Q131.25 0.41 127.29 0.41ZM123.01 -13.22H131.1Q130.9 -15.08 129.89 -16.12Q128.87 -17.17 127.09 -17.17Q125.3 -17.17 124.25 -16.1Q123.21 -15.04 123.01 -13.22Z" fill="#202633"/>\n <path d="M138.48 0 148.19 -29.1H157.43L167.43 0H159.69L157.72 -6.19H148.03L146.14 0ZM149.65 -11.46H156.04L155.65 -12.71Q154.89 -15.21 154.16 -17.95Q153.44 -20.68 152.74 -23.61Q152.07 -20.66 151.42 -17.93Q150.77 -15.2 150.04 -12.71Z M168.81 0V-29.1H180.72Q184 -29.1 186.39 -27.83Q188.77 -26.56 190.05 -24.3Q191.33 -22.03 191.33 -19.04Q191.33 -16.04 190.02 -13.8Q188.71 -11.56 186.29 -10.32Q183.87 -9.08 180.53 -9.08H175.7V0ZM175.7 -14.53H179.28Q181.7 -14.53 182.94 -15.77Q184.18 -17.01 184.18 -19.04Q184.18 -21.09 182.93 -22.3Q181.68 -23.52 179.26 -23.52H175.7Z M199.98 -29.1V0H193.08V-29.1Z" fill="#008A7D"/>\n</svg>\n';
@@ -15345,9 +15876,9 @@ function openapiInputForConfig(specRelativePosix) {
15345
15876
  return `./${specRelativePosix}`;
15346
15877
  }
15347
15878
  function toRelativeOpenApiInput(projectDir, inputPath) {
15348
- const resolved = path51.isAbsolute(inputPath) ? inputPath : path51.resolve(projectDir, inputPath);
15349
- const relative = path51.relative(projectDir, resolved);
15350
- const posix = relative.split(path51.sep).join(path51.posix.sep);
15879
+ const resolved = path52.isAbsolute(inputPath) ? inputPath : path52.resolve(projectDir, inputPath);
15880
+ const relative = path52.relative(projectDir, resolved);
15881
+ const posix = relative.split(path52.sep).join(path52.posix.sep);
15351
15882
  if (!posix) {
15352
15883
  throw new Error("OpenAPI path must point to a file.");
15353
15884
  }
@@ -15627,7 +16158,7 @@ function isPlanFree(plan2) {
15627
16158
  return (plan2.priceAmount ?? 0) <= 0;
15628
16159
  }
15629
16160
  function manifestFilePath(siteDirectory) {
15630
- return path52.join(path52.resolve(siteDirectory), APITOGO_CONFIG_FILENAME2);
16161
+ return path53.join(path53.resolve(siteDirectory), APITOGO_CONFIG_FILENAME2);
15631
16162
  }
15632
16163
  var STORED_MANIFEST_KEYS = [
15633
16164
  "authentication",
@@ -16100,39 +16631,39 @@ function resolveBackendSourceDirectory(siteDirectory, sourceDirectory) {
16100
16631
  if (!trimmed) {
16101
16632
  return void 0;
16102
16633
  }
16103
- return path52.resolve(siteDirectory, trimmed);
16634
+ return path53.resolve(siteDirectory, trimmed);
16104
16635
  }
16105
16636
  function resolveBackendOpenApiPath(openApiPath, options) {
16106
16637
  const trimmed = openApiPath?.trim();
16107
16638
  if (!trimmed) {
16108
16639
  return void 0;
16109
16640
  }
16110
- if (path52.isAbsolute(trimmed)) {
16641
+ if (path53.isAbsolute(trimmed)) {
16111
16642
  return trimmed;
16112
16643
  }
16113
- const siteResolved = path52.resolve(options.siteDirectory, trimmed);
16114
- if (existsSync5(siteResolved)) {
16644
+ const siteResolved = path53.resolve(options.siteDirectory, trimmed);
16645
+ if (existsSync6(siteResolved)) {
16115
16646
  return siteResolved;
16116
16647
  }
16117
16648
  const backendDir = options.backendSourceDirectory?.trim();
16118
16649
  if (backendDir) {
16119
- const backendResolved = path52.resolve(backendDir, trimmed);
16120
- if (existsSync5(backendResolved)) {
16650
+ const backendResolved = path53.resolve(backendDir, trimmed);
16651
+ if (existsSync6(backendResolved)) {
16121
16652
  return backendResolved;
16122
16653
  }
16123
16654
  }
16124
16655
  return siteResolved;
16125
16656
  }
16126
16657
  function toSiteRelativeOpenApiInput(siteDirectory, openApiAbsolutePath) {
16127
- const relative = path52.relative(
16128
- path52.resolve(siteDirectory),
16129
- path52.resolve(openApiAbsolutePath)
16658
+ const relative = path53.relative(
16659
+ path53.resolve(siteDirectory),
16660
+ path53.resolve(openApiAbsolutePath)
16130
16661
  );
16131
- const posix = relative.split(path52.sep).join("/");
16662
+ const posix = relative.split(path53.sep).join("/");
16132
16663
  if (!posix || posix === ".") {
16133
16664
  return "./";
16134
16665
  }
16135
- if (path52.isAbsolute(posix) || posix.startsWith("..")) {
16666
+ if (path53.isAbsolute(posix) || posix.startsWith("..")) {
16136
16667
  return posix;
16137
16668
  }
16138
16669
  return posix.startsWith("./") ? posix : `./${posix}`;
@@ -16171,7 +16702,7 @@ function applyOpenApiToSiteConfig(config2, options) {
16171
16702
  async function readManifest2(siteDirectory) {
16172
16703
  const filePath = manifestFilePath(siteDirectory);
16173
16704
  if (await pathExists(filePath)) {
16174
- const raw = await readFile12(filePath, "utf8");
16705
+ const raw = await readFile13(filePath, "utf8");
16175
16706
  const config2 = JSON.parse(raw);
16176
16707
  return extractManifestFromConfig(config2);
16177
16708
  }
@@ -16179,10 +16710,10 @@ async function readManifest2(siteDirectory) {
16179
16710
  }
16180
16711
  async function writeManifest2(siteDirectory, manifest) {
16181
16712
  const filePath = manifestFilePath(siteDirectory);
16182
- await mkdir13(path52.dirname(filePath), { recursive: true });
16713
+ await mkdir13(path53.dirname(filePath), { recursive: true });
16183
16714
  let config2 = {};
16184
16715
  if (await pathExists(filePath)) {
16185
- const raw = await readFile12(filePath, "utf8");
16716
+ const raw = await readFile13(filePath, "utf8");
16186
16717
  config2 = JSON.parse(raw);
16187
16718
  }
16188
16719
  const {
@@ -16201,11 +16732,11 @@ import {
16201
16732
  access as access6,
16202
16733
  appendFile,
16203
16734
  mkdir as mkdir14,
16204
- readFile as readFile13,
16735
+ readFile as readFile14,
16205
16736
  unlink,
16206
16737
  writeFile as writeFile15
16207
16738
  } from "node:fs/promises";
16208
- import path53 from "node:path";
16739
+ import path54 from "node:path";
16209
16740
  var ENV_FILENAME = ".env";
16210
16741
  var COLLAPSED_UPPERCASE_ALIASES2 = {
16211
16742
  SECRETKEY: "secretKey",
@@ -16307,7 +16838,7 @@ function applyEnvOverrides2(config2, env) {
16307
16838
  }
16308
16839
  async function readEnvFile(filePath) {
16309
16840
  try {
16310
- const raw = await readFile13(filePath, "utf8");
16841
+ const raw = await readFile14(filePath, "utf8");
16311
16842
  const env = {};
16312
16843
  for (const line2 of raw.split("\n")) {
16313
16844
  const trimmed = line2.trim();
@@ -16324,12 +16855,12 @@ async function readEnvFile(filePath) {
16324
16855
  }
16325
16856
  }
16326
16857
  async function loadSiteEnv(siteDirectory) {
16327
- const resolvedDirectory = path53.resolve(siteDirectory);
16858
+ const resolvedDirectory = path54.resolve(siteDirectory);
16328
16859
  const merged = { ...process.env };
16329
16860
  for (const fileName of [ENV_FILENAME, ".env.local"]) {
16330
16861
  Object.assign(
16331
16862
  merged,
16332
- await readEnvFile(path53.join(resolvedDirectory, fileName))
16863
+ await readEnvFile(path54.join(resolvedDirectory, fileName))
16333
16864
  );
16334
16865
  }
16335
16866
  return merged;
@@ -16366,7 +16897,7 @@ function buildAuthSecretsJson(secrets2, options) {
16366
16897
  return payload;
16367
16898
  }
16368
16899
  function envFilePath(siteDirectory) {
16369
- return path53.join(path53.resolve(siteDirectory), ENV_FILENAME);
16900
+ return path54.join(path54.resolve(siteDirectory), ENV_FILENAME);
16370
16901
  }
16371
16902
  function createEnvContent() {
16372
16903
  return [
@@ -16430,7 +16961,7 @@ function validateSecretsForPublish(manifest, secrets2, emailOptions) {
16430
16961
  }
16431
16962
  async function siteEnvFileHasEntries(siteDirectory) {
16432
16963
  for (const fileName of [ENV_FILENAME, ".env.local"]) {
16433
- const loaded = await readEnvFile(path53.join(siteDirectory, fileName));
16964
+ const loaded = await readEnvFile(path54.join(siteDirectory, fileName));
16434
16965
  if (Object.keys(loaded).length > 0) {
16435
16966
  return true;
16436
16967
  }
@@ -16438,7 +16969,7 @@ async function siteEnvFileHasEntries(siteDirectory) {
16438
16969
  return false;
16439
16970
  }
16440
16971
  async function resolvePublishConfig(siteDirectory, emailOptions) {
16441
- const resolvedDirectory = path53.resolve(siteDirectory);
16972
+ const resolvedDirectory = path54.resolve(siteDirectory);
16442
16973
  const manifestRaw = await readManifest2(resolvedDirectory);
16443
16974
  if (!manifestRaw) {
16444
16975
  throw new Error("apitogo.config.json not found in sourceDirectory.");
@@ -16471,7 +17002,7 @@ function envFileHasActiveAssignments(content) {
16471
17002
  });
16472
17003
  }
16473
17004
  async function writeScaffoldEnv(siteDirectory) {
16474
- const resolvedDirectory = path53.resolve(siteDirectory);
17005
+ const resolvedDirectory = path54.resolve(siteDirectory);
16475
17006
  const filesWritten = [];
16476
17007
  const envPath = envFilePath(resolvedDirectory);
16477
17008
  try {
@@ -16482,9 +17013,9 @@ async function writeScaffoldEnv(siteDirectory) {
16482
17013
  filesWritten.push(ENV_FILENAME);
16483
17014
  }
16484
17015
  for (const legacyName of [".env.example"]) {
16485
- const legacyPath = path53.join(resolvedDirectory, legacyName);
17016
+ const legacyPath = path54.join(resolvedDirectory, legacyName);
16486
17017
  try {
16487
- const legacy = await readFile13(legacyPath, "utf8");
17018
+ const legacy = await readFile14(legacyPath, "utf8");
16488
17019
  if (!envFileHasActiveAssignments(legacy)) {
16489
17020
  await unlink(legacyPath);
16490
17021
  filesWritten.push(`removed:${legacyName}`);
@@ -16492,10 +17023,10 @@ async function writeScaffoldEnv(siteDirectory) {
16492
17023
  } catch {
16493
17024
  }
16494
17025
  }
16495
- const gitignorePath = path53.join(resolvedDirectory, ".gitignore");
17026
+ const gitignorePath = path54.join(resolvedDirectory, ".gitignore");
16496
17027
  let gitignore = "";
16497
17028
  try {
16498
- gitignore = await readFile13(gitignorePath, "utf8");
17029
+ gitignore = await readFile14(gitignorePath, "utf8");
16499
17030
  } catch {
16500
17031
  gitignore = "";
16501
17032
  }
@@ -16527,13 +17058,13 @@ var SECRETS_COLLECTION_INSTRUCTION = "For go-live, plans must already be in apit
16527
17058
 
16528
17059
  // src/cli/platform/helpers/dev-portal-site-validation.ts
16529
17060
  init_gateway_limits();
16530
- import { readFile as readFile14 } from "node:fs/promises";
17061
+ import { readFile as readFile15 } from "node:fs/promises";
16531
17062
  import { createRequire as createRequire3 } from "node:module";
16532
- import path54 from "node:path";
17063
+ import path55 from "node:path";
16533
17064
  var cachedFallbackTs;
16534
17065
  async function loadTypeScriptForSite(sourceDirectory) {
16535
17066
  const require2 = createRequire3(
16536
- path54.join(path54.resolve(sourceDirectory), "package.json")
17067
+ path55.join(path55.resolve(sourceDirectory), "package.json")
16537
17068
  );
16538
17069
  try {
16539
17070
  return require2("typescript");
@@ -16580,7 +17111,7 @@ async function referencedFileExists(sourceDirectory, reference) {
16580
17111
  return true;
16581
17112
  }
16582
17113
  return pathExists(
16583
- path54.resolve(sourceDirectory, reference.replace(/^\.\//, ""))
17114
+ path55.resolve(sourceDirectory, reference.replace(/^\.\//, ""))
16584
17115
  );
16585
17116
  }
16586
17117
  async function validateSiteConfigStructure(config2, sourceDirectory) {
@@ -16695,17 +17226,17 @@ var DEFAULT_TSX_CANDIDATES = [
16695
17226
  "landing/LandingPage.tsx"
16696
17227
  ];
16697
17228
  async function validateDevPortalSiteConfig(sourceDirectory, options) {
16698
- const resolvedDir = path54.resolve(sourceDirectory);
17229
+ const resolvedDir = path55.resolve(sourceDirectory);
16699
17230
  const issues = [];
16700
17231
  const checkedFiles = [];
16701
17232
  const skippedChecks = [];
16702
- const jsonPath = path54.join(resolvedDir, APITOGO_CONFIG_FILENAME2);
17233
+ const jsonPath = path55.join(resolvedDir, APITOGO_CONFIG_FILENAME2);
16703
17234
  if (await pathExists(jsonPath)) {
16704
17235
  checkedFiles.push(APITOGO_CONFIG_FILENAME2);
16705
17236
  let parsed;
16706
17237
  let parseFailed = false;
16707
17238
  try {
16708
- parsed = JSON.parse(await readFile14(jsonPath, "utf8"));
17239
+ parsed = JSON.parse(await readFile15(jsonPath, "utf8"));
16709
17240
  } catch (error) {
16710
17241
  parseFailed = true;
16711
17242
  issues.push({
@@ -16731,7 +17262,7 @@ async function validateDevPortalSiteConfig(sourceDirectory, options) {
16731
17262
  );
16732
17263
  const existingTsxFiles = [];
16733
17264
  for (const relativeFile of tsxFiles) {
16734
- if (await pathExists(path54.join(resolvedDir, relativeFile))) {
17265
+ if (await pathExists(path55.join(resolvedDir, relativeFile))) {
16735
17266
  existingTsxFiles.push(relativeFile);
16736
17267
  }
16737
17268
  }
@@ -16744,8 +17275,8 @@ async function validateDevPortalSiteConfig(sourceDirectory, options) {
16744
17275
  } else {
16745
17276
  for (const relativeFile of existingTsxFiles) {
16746
17277
  checkedFiles.push(relativeFile);
16747
- const content = await readFile14(
16748
- path54.join(resolvedDir, relativeFile),
17278
+ const content = await readFile15(
17279
+ path55.join(resolvedDir, relativeFile),
16749
17280
  "utf8"
16750
17281
  );
16751
17282
  issues.push(
@@ -16765,7 +17296,7 @@ async function validateDevPortalSiteConfig(sourceDirectory, options) {
16765
17296
  };
16766
17297
  }
16767
17298
  function formatSiteValidationErrors(result) {
16768
- return result.issues.map((issue2) => `${issue2.file}: ${issue2.message}`).join("; ");
17299
+ return result.issues.map((issue3) => `${issue3.file}: ${issue3.message}`).join("; ");
16769
17300
  }
16770
17301
  function buildSiteValidationResponseFields(result) {
16771
17302
  if (result.valid) {
@@ -16781,9 +17312,9 @@ function buildSiteValidationResponseFields(result) {
16781
17312
  }
16782
17313
 
16783
17314
  // src/cli/platform/helpers/npm-build.ts
16784
- import { spawn } from "node:child_process";
16785
- import { access as access7, readFile as readFile15 } from "node:fs/promises";
16786
- import path55 from "node:path";
17315
+ import { spawn as spawn2 } from "node:child_process";
17316
+ import { access as access7, readFile as readFile16 } from "node:fs/promises";
17317
+ import path56 from "node:path";
16787
17318
  var BUILD_LOG_TAIL_BYTES = 8192;
16788
17319
  var MIN_NODE_MAJOR = 20;
16789
17320
  var NpmBuildError = class extends Error {
@@ -16832,7 +17363,7 @@ async function pathExists2(filePath) {
16832
17363
  }
16833
17364
  function runCommand(command2, args, cwd, env) {
16834
17365
  return new Promise((resolve, reject) => {
16835
- const child = spawn(command2, args, {
17366
+ const child = spawn2(command2, args, {
16836
17367
  cwd,
16837
17368
  env: env ? { ...process.env, ...env } : process.env,
16838
17369
  shell: process.platform === "win32"
@@ -16850,13 +17381,13 @@ function runCommand(command2, args, cwd, env) {
16850
17381
  }
16851
17382
  async function runNpmInstall(sourceDirectory, options) {
16852
17383
  assertNodeVersion(options?.nodeVersion);
16853
- const cwd = path55.resolve(sourceDirectory);
16854
- const packageJsonPath = path55.join(cwd, "package.json");
17384
+ const cwd = path56.resolve(sourceDirectory);
17385
+ const packageJsonPath = path56.join(cwd, "package.json");
16855
17386
  if (!await pathExists2(packageJsonPath)) {
16856
17387
  throw new Error(`package.json not found in sourceDirectory: ${cwd}`);
16857
17388
  }
16858
17389
  const startedAt = Date.now();
16859
- const lockPath = path55.join(cwd, "package-lock.json");
17390
+ const lockPath = path56.join(cwd, "package-lock.json");
16860
17391
  const installArgs = await pathExists2(lockPath) ? ["ci"] : ["install"];
16861
17392
  const install = await runCommand("npm", installArgs, cwd);
16862
17393
  const installLog = install.output;
@@ -16874,8 +17405,8 @@ async function runNpmInstall(sourceDirectory, options) {
16874
17405
  }
16875
17406
  async function runNpmBuild(sourceDirectory, options) {
16876
17407
  assertNodeVersion(options?.nodeVersion);
16877
- const cwd = path55.resolve(sourceDirectory);
16878
- const packageJsonPath = path55.join(cwd, "package.json");
17408
+ const cwd = path56.resolve(sourceDirectory);
17409
+ const packageJsonPath = path56.join(cwd, "package.json");
16879
17410
  if (!await pathExists2(packageJsonPath)) {
16880
17411
  throw new Error(`package.json not found in sourceDirectory: ${cwd}`);
16881
17412
  }
@@ -16892,8 +17423,8 @@ async function runNpmBuild(sourceDirectory, options) {
16892
17423
  build2.exitCode
16893
17424
  );
16894
17425
  }
16895
- const distPath = path55.join(cwd, "dist");
16896
- const indexPath = path55.join(distPath, "index.html");
17426
+ const distPath = path56.join(cwd, "dist");
17427
+ const indexPath = path56.join(distPath, "index.html");
16897
17428
  if (!await pathExists2(indexPath)) {
16898
17429
  throw new NpmBuildError(
16899
17430
  `Build completed but dist/index.html was not found at ${indexPath}`,
@@ -17277,7 +17808,7 @@ function validateManifestForPlatformPublish(manifest) {
17277
17808
 
17278
17809
  // src/cli/platform/tools/check-publish-readiness.ts
17279
17810
  async function checkPublishReadiness(client, _config, args) {
17280
- const sourceDirectory = path56.resolve(args.sourceDirectory);
17811
+ const sourceDirectory = path57.resolve(args.sourceDirectory);
17281
17812
  const manifest = await readManifest2(sourceDirectory) ?? createScaffoldManifest();
17282
17813
  const emailOptions = await resolveEmailOptionsForPublish(
17283
17814
  client,
@@ -17297,7 +17828,7 @@ async function checkPublishReadiness(client, _config, args) {
17297
17828
  const mergedPrerequisites = mergePublishPrerequisitesWithPlatformValidation(
17298
17829
  publishPrerequisites,
17299
17830
  platformValidation.issues.map(
17300
- (issue2) => `${issue2.field}: ${issue2.message}`
17831
+ (issue3) => `${issue3.field}: ${issue3.message}`
17301
17832
  )
17302
17833
  );
17303
17834
  const siteValidation = await validateDevPortalSiteConfig(sourceDirectory);
@@ -17355,7 +17886,7 @@ async function checkPublishReadiness(client, _config, args) {
17355
17886
 
17356
17887
  // src/cli/check/handler.ts
17357
17888
  async function check(argv) {
17358
- const sourceDirectory = path57.resolve(process.cwd(), argv.dir);
17889
+ const sourceDirectory = path58.resolve(process.cwd(), argv.dir);
17359
17890
  if (await isConfigV2(sourceDirectory)) {
17360
17891
  await checkV2(sourceDirectory, argv);
17361
17892
  return;
@@ -17404,7 +17935,7 @@ var check_default = {
17404
17935
  default: false
17405
17936
  }).option("offline", {
17406
17937
  type: "boolean",
17407
- describe: "Config version 2: do not ask the platform anything (secret names are then not checked)",
17938
+ describe: "Config version 2: do not ask the platform anything (secret names and the gateway's online rules are then not checked)",
17408
17939
  default: false
17409
17940
  }).option("json", {
17410
17941
  type: "boolean",
@@ -17419,14 +17950,14 @@ var check_default = {
17419
17950
 
17420
17951
  // src/cli/make-config/handler.ts
17421
17952
  import { constants as constants2 } from "node:fs";
17422
- import { access as access12, mkdir as mkdir17, readFile as readFile20, writeFile as writeFile20 } from "node:fs/promises";
17423
- import path67 from "node:path";
17953
+ import { access as access12, mkdir as mkdir17, readFile as readFile21, writeFile as writeFile20 } from "node:fs/promises";
17954
+ import path68 from "node:path";
17424
17955
  import { stdin as input2 } from "node:process";
17425
17956
  import { glob as glob7 } from "glob";
17426
17957
 
17427
17958
  // src/cli/platform/helpers/openapi-action-keys.ts
17428
- import { existsSync as existsSync6, readFileSync as readFileSync4 } from "node:fs";
17429
- import path58 from "node:path";
17959
+ import { existsSync as existsSync7, readFileSync as readFileSync4 } from "node:fs";
17960
+ import path59 from "node:path";
17430
17961
  import YAML from "yaml";
17431
17962
  var HTTP_METHODS = [
17432
17963
  "get",
@@ -17495,7 +18026,7 @@ function extractGatewayActionKeysFromOpenApiDocument(document) {
17495
18026
  function extractGatewayActionKeysFromOpenApiFile(filePath) {
17496
18027
  try {
17497
18028
  const raw = readFileSync4(filePath, "utf8");
17498
- const ext = path58.extname(filePath).toLowerCase();
18029
+ const ext = path59.extname(filePath).toLowerCase();
17499
18030
  const parsed = ext === ".json" ? JSON.parse(raw) : YAML.parse(raw);
17500
18031
  return extractGatewayActionKeysFromOpenApiDocument(parsed);
17501
18032
  } catch {
@@ -17516,8 +18047,8 @@ function resolveConfigOpenApiFilePath(siteDirectory, config2) {
17516
18047
  }
17517
18048
  }
17518
18049
  for (const candidate of candidates) {
17519
- const resolved = path58.isAbsolute(candidate) ? candidate : path58.resolve(siteDirectory, candidate);
17520
- if (existsSync6(resolved)) {
18050
+ const resolved = path59.isAbsolute(candidate) ? candidate : path59.resolve(siteDirectory, candidate);
18051
+ if (existsSync7(resolved)) {
17521
18052
  return resolved;
17522
18053
  }
17523
18054
  }
@@ -17531,8 +18062,8 @@ function loadOpenApiActionKeysForSite(siteDirectory, config2) {
17531
18062
  return extractGatewayActionKeysFromOpenApiFile(filePath);
17532
18063
  }
17533
18064
  function loadOpenApiActionKeysFromSiteDirectory(siteDirectory) {
17534
- const configPath = path58.join(siteDirectory, "apitogo.config.json");
17535
- if (!existsSync6(configPath)) {
18065
+ const configPath = path59.join(siteDirectory, "apitogo.config.json");
18066
+ if (!existsSync7(configPath)) {
17536
18067
  return [];
17537
18068
  }
17538
18069
  try {
@@ -17559,8 +18090,8 @@ function withDefaultIncludedActionKeys(plan2, actionKeys) {
17559
18090
  }
17560
18091
 
17561
18092
  // src/cli/scaffold/dev-portal-config-pages.ts
17562
- import { access as access8, readFile as readFile16, rename as rename7, rm as rm9, writeFile as writeFile16 } from "node:fs/promises";
17563
- import path59 from "node:path";
18093
+ import { access as access8, readFile as readFile17, rename as rename7, rm as rm9, writeFile as writeFile16 } from "node:fs/promises";
18094
+ import path60 from "node:path";
17564
18095
  async function writeConfigAtomically(filePath, content) {
17565
18096
  const temporaryPath = `${filePath}.${process.pid}.${Date.now()}.tmp`;
17566
18097
  try {
@@ -17573,7 +18104,7 @@ async function writeConfigAtomically(filePath, content) {
17573
18104
  }
17574
18105
  async function resolveApitogoConfigPath(sourceDirectory) {
17575
18106
  for (const filename of ["apitogo.config.tsx", "apitogo.config.jsx"]) {
17576
- const configPath = path59.join(sourceDirectory, filename);
18107
+ const configPath = path60.join(sourceDirectory, filename);
17577
18108
  try {
17578
18109
  await access8(configPath);
17579
18110
  return configPath;
@@ -18324,7 +18855,7 @@ async function wireNewDevPortalPageInConfig(sourceDirectory, slug, label) {
18324
18855
  if (!configPath) {
18325
18856
  return { updated: false };
18326
18857
  }
18327
- const original = await readFile16(configPath, "utf8");
18858
+ const original = await readFile17(configPath, "utf8");
18328
18859
  const patched = patchApitogoConfigForNewPage(original, slug, label, {
18329
18860
  typescript: configPath.endsWith(".tsx")
18330
18861
  });
@@ -18336,18 +18867,18 @@ async function wireNewDevPortalPageInConfig(sourceDirectory, slug, label) {
18336
18867
  }
18337
18868
 
18338
18869
  // src/cli/scaffold/wire-openapi.ts
18339
- import { access as access10, readFile as readFile18, writeFile as writeFile18 } from "node:fs/promises";
18340
- import path64 from "node:path";
18870
+ import { access as access10, readFile as readFile19, writeFile as writeFile18 } from "node:fs/promises";
18871
+ import path65 from "node:path";
18341
18872
 
18342
18873
  // src/cli/platform/helpers/openapi.ts
18343
18874
  import { promises as fs6 } from "node:fs";
18344
- import path61 from "node:path";
18875
+ import path62 from "node:path";
18345
18876
  import SwaggerParser from "@apidevtools/swagger-parser";
18346
18877
  import YAML2 from "yaml";
18347
18878
 
18348
18879
  // src/cli/scaffold/discover-openapi-files.ts
18349
18880
  import { promises as fs5 } from "node:fs";
18350
- import path60 from "node:path";
18881
+ import path61 from "node:path";
18351
18882
  var OPENAPI_BASENAMES = [
18352
18883
  "openapi.yaml",
18353
18884
  "openapi.yml",
@@ -18369,13 +18900,13 @@ var SKIP_OPENAPI_SEARCH_DIRS = /* @__PURE__ */ new Set([
18369
18900
  ".apitogo"
18370
18901
  ]);
18371
18902
  async function collectFromDir(dir, searchedDirectories, candidates) {
18372
- const resolved = path60.resolve(dir);
18903
+ const resolved = path61.resolve(dir);
18373
18904
  if (searchedDirectories.includes(resolved)) {
18374
18905
  return;
18375
18906
  }
18376
18907
  searchedDirectories.push(resolved);
18377
18908
  for (const name of OPENAPI_BASENAMES) {
18378
- const candidate = path60.join(resolved, name);
18909
+ const candidate = path61.join(resolved, name);
18379
18910
  try {
18380
18911
  await fs5.access(candidate);
18381
18912
  candidates.push(candidate);
@@ -18390,8 +18921,8 @@ async function collectSiteOpenApiFiles(siteDirectory) {
18390
18921
  return { candidates: [...new Set(candidates)], searchedDirectories };
18391
18922
  }
18392
18923
  async function collectNearbyOpenApiFiles(siteDirectory) {
18393
- const site = path60.resolve(siteDirectory);
18394
- const parent = path60.dirname(site);
18924
+ const site = path61.resolve(siteDirectory);
18925
+ const parent = path61.dirname(site);
18395
18926
  const searchedDirectories = [];
18396
18927
  const candidates = [];
18397
18928
  if (parent && parent !== site) {
@@ -18402,8 +18933,8 @@ async function collectNearbyOpenApiFiles(siteDirectory) {
18402
18933
  if (!entry.isDirectory() || SKIP_OPENAPI_SEARCH_DIRS.has(entry.name)) {
18403
18934
  continue;
18404
18935
  }
18405
- const sibling = path60.join(parent, entry.name);
18406
- if (path60.resolve(sibling) === site) {
18936
+ const sibling = path61.join(parent, entry.name);
18937
+ if (path61.resolve(sibling) === site) {
18407
18938
  continue;
18408
18939
  }
18409
18940
  await collectFromDir(sibling, searchedDirectories, candidates);
@@ -18431,7 +18962,7 @@ function parseOpenApiObject(doc) {
18431
18962
  };
18432
18963
  }
18433
18964
  function liteParseOpenApi(content, filePath) {
18434
- const ext = path60.extname(filePath).toLowerCase();
18965
+ const ext = path61.extname(filePath).toLowerCase();
18435
18966
  if (ext === ".json") {
18436
18967
  try {
18437
18968
  const parsed = JSON.parse(content);
@@ -18496,9 +19027,9 @@ async function discoverNearbyOpenApi(siteDirectory) {
18496
19027
  };
18497
19028
  }
18498
19029
  async function validateOpenApiFile(filePath) {
18499
- const resolved = path61.resolve(filePath);
19030
+ const resolved = path62.resolve(filePath);
18500
19031
  const content = await fs6.readFile(resolved, "utf8");
18501
- const ext = path61.extname(resolved).toLowerCase();
19032
+ const ext = path62.extname(resolved).toLowerCase();
18502
19033
  let parsed;
18503
19034
  try {
18504
19035
  parsed = ext === ".json" ? JSON.parse(content) : YAML2.parse(content);
@@ -18540,8 +19071,8 @@ async function validateOpenApiFile(filePath) {
18540
19071
  }
18541
19072
 
18542
19073
  // src/cli/scaffold/scaffold-dev-portal-site.ts
18543
- import { mkdir as mkdir15, readFile as readFile17, writeFile as writeFile17 } from "node:fs/promises";
18544
- import path63 from "node:path";
19074
+ import { mkdir as mkdir15, readFile as readFile18, writeFile as writeFile17 } from "node:fs/promises";
19075
+ import path64 from "node:path";
18545
19076
 
18546
19077
  // src/cli/scaffold/apitogo-npm-packages.ts
18547
19078
  var DEFAULT_CREATE_APITOGO_PACKAGE2 = "@lukoweb/create-apitogo@latest";
@@ -18627,9 +19158,9 @@ var DOC_TOOL_LOCAL_PACKAGE_PATHS = {
18627
19158
  };
18628
19159
 
18629
19160
  // src/cli/scaffold/create-apitogo-cli.ts
18630
- import { spawn as spawn2 } from "node:child_process";
19161
+ import { spawn as spawn3 } from "node:child_process";
18631
19162
  import { access as access9, rm as rm10 } from "node:fs/promises";
18632
- import path62 from "node:path";
19163
+ import path63 from "node:path";
18633
19164
  var SKIP_NEARBY_DISCOVERY_ENV = "APITOGO_SKIP_NEARBY_DISCOVERY";
18634
19165
  var CreateApitogoCliError = class extends Error {
18635
19166
  exitCode;
@@ -18646,9 +19177,9 @@ function resolveDocToolPath(explicit) {
18646
19177
  return value || void 0;
18647
19178
  }
18648
19179
  function splitOutputDirectory(outputDirectory) {
18649
- const resolvedOutputDirectory = path62.resolve(outputDirectory);
18650
- const projectName = path62.basename(resolvedOutputDirectory);
18651
- const parentDirectory = path62.dirname(resolvedOutputDirectory);
19180
+ const resolvedOutputDirectory = path63.resolve(outputDirectory);
19181
+ const projectName = path63.basename(resolvedOutputDirectory);
19182
+ const parentDirectory = path63.dirname(resolvedOutputDirectory);
18652
19183
  if (!projectName || projectName === "." || projectName === "..") {
18653
19184
  throw new Error(
18654
19185
  "outputDirectory must be a named folder (e.g. ./dev-portal-site), not the current directory."
@@ -18674,7 +19205,7 @@ async function directoryHasEntries(dir) {
18674
19205
  }
18675
19206
  }
18676
19207
  function resolveLocalCreateApitogoEntry(docToolPath) {
18677
- return path62.resolve(docToolPath, "packages/create-zudoku/dist/index.js");
19208
+ return path63.resolve(docToolPath, "packages/create-zudoku/dist/index.js");
18678
19209
  }
18679
19210
  async function resolveCreateApitogoInvocation(options) {
18680
19211
  const docToolPath = resolveDocToolPath(options.docToolPath);
@@ -18701,7 +19232,7 @@ async function resolveCreateApitogoInvocation(options) {
18701
19232
  }
18702
19233
  function runCommand2(command2, args, cwd) {
18703
19234
  return new Promise((resolve, reject) => {
18704
- const child = spawn2(command2, args, {
19235
+ const child = spawn3(command2, args, {
18705
19236
  cwd,
18706
19237
  env: { ...process.env, [SKIP_NEARBY_DISCOVERY_ENV]: "1" },
18707
19238
  shell: process.platform === "win32"
@@ -18756,7 +19287,7 @@ async function runCreateApitogoDevPortal(options) {
18756
19287
  result.exitCode
18757
19288
  );
18758
19289
  }
18759
- if (!await pathExists3(path62.join(resolvedOutputDirectory, "package.json"))) {
19290
+ if (!await pathExists3(path63.join(resolvedOutputDirectory, "package.json"))) {
18760
19291
  throw new CreateApitogoCliError(
18761
19292
  `create-apitogo completed but package.json was not found at ${resolvedOutputDirectory}`,
18762
19293
  result.output,
@@ -18815,7 +19346,7 @@ function ensureRequiredApitogoModules(pkg, options) {
18815
19346
  ]
18816
19347
  ]) {
18817
19348
  if (options.docToolPath) {
18818
- deps[moduleName] = `file:${path63.join(options.docToolPath, localPath)}`;
19349
+ deps[moduleName] = `file:${path64.join(options.docToolPath, localPath)}`;
18819
19350
  continue;
18820
19351
  }
18821
19352
  deps[moduleName] = `npm:${moduleName}@${moduleAliasVersion}`;
@@ -18846,7 +19377,7 @@ function rewritePackageJson(raw, options) {
18846
19377
  DOC_TOOL_LOCAL_PACKAGE_PATHS
18847
19378
  )) {
18848
19379
  if (dep in deps) {
18849
- deps[dep] = `file:${path63.join(options.docToolPath, relativePath)}`;
19380
+ deps[dep] = `file:${path64.join(options.docToolPath, relativePath)}`;
18850
19381
  linkedWorkspacePackage = true;
18851
19382
  }
18852
19383
  }
@@ -18946,23 +19477,23 @@ function patchDashboardPageConfig(content) {
18946
19477
  );
18947
19478
  }
18948
19479
  async function ensureAccountPlansPage(outputDirectory) {
18949
- const pagePath = path63.join(outputDirectory, ACCOUNT_PLANS_PAGE_PATH);
19480
+ const pagePath = path64.join(outputDirectory, ACCOUNT_PLANS_PAGE_PATH);
18950
19481
  try {
18951
- await readFile17(pagePath, "utf8");
19482
+ await readFile18(pagePath, "utf8");
18952
19483
  return void 0;
18953
19484
  } catch {
18954
- await mkdir15(path63.dirname(pagePath), { recursive: true });
19485
+ await mkdir15(path64.dirname(pagePath), { recursive: true });
18955
19486
  await writeFile17(pagePath, ACCOUNT_PLANS_PAGE);
18956
19487
  return ACCOUNT_PLANS_PAGE_PATH;
18957
19488
  }
18958
19489
  }
18959
19490
  async function ensureDashboardPage(outputDirectory) {
18960
- const pagePath = path63.join(outputDirectory, DASHBOARD_PAGE_PATH);
19491
+ const pagePath = path64.join(outputDirectory, DASHBOARD_PAGE_PATH);
18961
19492
  try {
18962
- await readFile17(pagePath, "utf8");
19493
+ await readFile18(pagePath, "utf8");
18963
19494
  return void 0;
18964
19495
  } catch {
18965
- await mkdir15(path63.dirname(pagePath), { recursive: true });
19496
+ await mkdir15(path64.dirname(pagePath), { recursive: true });
18966
19497
  await writeFile17(pagePath, DASHBOARD_PAGE);
18967
19498
  return DASHBOARD_PAGE_PATH;
18968
19499
  }
@@ -19019,9 +19550,9 @@ function patchApitogoConfig(content, options) {
19019
19550
  async function patchScaffoldedSite(outputDirectory, options) {
19020
19551
  const title = options.branding?.title ?? options.siteName;
19021
19552
  const filesWritten = [];
19022
- const packageJsonPath = path63.join(outputDirectory, "package.json");
19553
+ const packageJsonPath = path64.join(outputDirectory, "package.json");
19023
19554
  if (options.patchPackageJson !== false) {
19024
- const packageJson3 = await readFile17(packageJsonPath, "utf8");
19555
+ const packageJson3 = await readFile18(packageJsonPath, "utf8");
19025
19556
  await writeFile17(
19026
19557
  packageJsonPath,
19027
19558
  rewritePackageJson(packageJson3, {
@@ -19032,8 +19563,8 @@ async function patchScaffoldedSite(outputDirectory, options) {
19032
19563
  );
19033
19564
  filesWritten.push("package.json");
19034
19565
  }
19035
- const configPath = path63.join(outputDirectory, "apitogo.config.tsx");
19036
- const configContent = await readFile17(configPath, "utf8");
19566
+ const configPath = path64.join(outputDirectory, "apitogo.config.tsx");
19567
+ const configContent = await readFile18(configPath, "utf8");
19037
19568
  await writeFile17(
19038
19569
  configPath,
19039
19570
  patchDashboardPageConfig(
@@ -19127,8 +19658,8 @@ async function discoverOpenApiByMode(siteDirectory, mode) {
19127
19658
  return discoverNearbyOpenApi(siteDirectory);
19128
19659
  }
19129
19660
  async function wireDevPortalOpenApi(sourceDirectory, openApiPath) {
19130
- const resolvedSource = path64.resolve(sourceDirectory);
19131
- const resolvedOpenApi = path64.resolve(openApiPath);
19661
+ const resolvedSource = path65.resolve(sourceDirectory);
19662
+ const resolvedOpenApi = path65.resolve(openApiPath);
19132
19663
  const input5 = toSiteRelativeOpenApiInput(resolvedSource, resolvedOpenApi);
19133
19664
  const validation = await validateOpenApiFile(resolvedOpenApi);
19134
19665
  if (!validation.valid) {
@@ -19140,7 +19671,7 @@ async function wireDevPortalOpenApi(sourceDirectory, openApiPath) {
19140
19671
  }
19141
19672
  const jsonConfigPath = manifestFilePath(resolvedSource);
19142
19673
  if (await pathExists4(jsonConfigPath)) {
19143
- const raw = await readFile18(jsonConfigPath, "utf8");
19674
+ const raw = await readFile19(jsonConfigPath, "utf8");
19144
19675
  const siteConfig = JSON.parse(raw);
19145
19676
  const updated = applyOpenApiToSiteConfig(siteConfig, {
19146
19677
  input: input5,
@@ -19153,8 +19684,8 @@ async function wireDevPortalOpenApi(sourceDirectory, openApiPath) {
19153
19684
  "utf8"
19154
19685
  );
19155
19686
  } else {
19156
- const tsxConfigPath = path64.join(resolvedSource, "apitogo.config.tsx");
19157
- const configContent = await readFile18(tsxConfigPath, "utf8");
19687
+ const tsxConfigPath = path65.join(resolvedSource, "apitogo.config.tsx");
19688
+ const configContent = await readFile19(tsxConfigPath, "utf8");
19158
19689
  try {
19159
19690
  const patched = patchApitogoConfigOpenApiToFile(configContent, input5);
19160
19691
  await writeFile18(tsxConfigPath, patched);
@@ -19239,7 +19770,7 @@ async function discoverAndWireOpenApi(siteDirectory, mode) {
19239
19770
  }
19240
19771
 
19241
19772
  // src/cli/make-config/interactive.ts
19242
- import path65 from "node:path";
19773
+ import path66 from "node:path";
19243
19774
  import { stdin as input, stdout as output } from "node:process";
19244
19775
  import { createInterface as createInterface3 } from "node:readline/promises";
19245
19776
 
@@ -19415,7 +19946,7 @@ async function promptMakeConfigInteractive(options) {
19415
19946
  continue;
19416
19947
  }
19417
19948
  const routePath = `/${slug}`;
19418
- const relativePath = path65.posix.join(options.pagesDir, `${slug}.tsx`);
19949
+ const relativePath = path66.posix.join(options.pagesDir, `${slug}.tsx`);
19419
19950
  const page = {
19420
19951
  slug,
19421
19952
  title: pageTitle.trim(),
@@ -19510,8 +20041,8 @@ export default function ${componentName}() {
19510
20041
  // src/cli/make-config/scaffold-project.ts
19511
20042
  init_package_json();
19512
20043
  import { constants } from "node:fs";
19513
- import { access as access11, mkdir as mkdir16, readFile as readFile19, writeFile as writeFile19 } from "node:fs/promises";
19514
- import path66 from "node:path";
20044
+ import { access as access11, mkdir as mkdir16, readFile as readFile20, writeFile as writeFile19 } from "node:fs/promises";
20045
+ import path67 from "node:path";
19515
20046
  import { glob as glob6 } from "glob";
19516
20047
 
19517
20048
  // src/cli/make-config/favicon-png.ts
@@ -19526,9 +20057,9 @@ var OPENAPI_GLOBS = [
19526
20057
  "openapi.yaml",
19527
20058
  "openapi.yml"
19528
20059
  ];
19529
- var toPosix2 = (p) => p.split(path66.sep).join("/");
20060
+ var toPosix2 = (p) => p.split(path67.sep).join("/");
19530
20061
  var sanitizePackageName = (dir) => {
19531
- const base = path66.basename(path66.resolve(dir));
20062
+ const base = path67.basename(path67.resolve(dir));
19532
20063
  const s = base.toLowerCase().replace(/[^a-z0-9-_.]/g, "-").replace(/^-+|-+$/g, "");
19533
20064
  return s || "apitogo-site";
19534
20065
  };
@@ -19538,7 +20069,7 @@ var apitogoVersionRange = () => {
19538
20069
  };
19539
20070
  async function findOpenApiSpecPath(dir) {
19540
20071
  for (const rel2 of OPENAPI_GLOBS) {
19541
- const full = path66.join(dir, rel2);
20072
+ const full = path67.join(dir, rel2);
19542
20073
  try {
19543
20074
  await access11(full, constants.R_OK);
19544
20075
  return toPosix2(rel2);
@@ -19553,7 +20084,7 @@ async function findOpenApiSpecPath(dir) {
19553
20084
  return extra[0] ? toPosix2(extra[0]) : null;
19554
20085
  }
19555
20086
  async function ensurePackageJson(dir) {
19556
- const pkgPath = path66.join(dir, "package.json");
20087
+ const pkgPath = path67.join(dir, "package.json");
19557
20088
  const apitogoVer = apitogoVersionRange();
19558
20089
  const baseDeps = {
19559
20090
  react: ">=19.0.0",
@@ -19592,7 +20123,7 @@ async function ensurePackageJson(dir) {
19592
20123
  `, "utf8");
19593
20124
  return true;
19594
20125
  }
19595
- const raw = await readFile19(pkgPath, "utf8");
20126
+ const raw = await readFile20(pkgPath, "utf8");
19596
20127
  let pkg;
19597
20128
  try {
19598
20129
  pkg = JSON.parse(raw);
@@ -19620,7 +20151,7 @@ async function ensurePackageJson(dir) {
19620
20151
  return false;
19621
20152
  }
19622
20153
  async function ensureTsconfigJson(dir) {
19623
- const tsPath = path66.join(dir, "tsconfig.json");
20154
+ const tsPath = path67.join(dir, "tsconfig.json");
19624
20155
  try {
19625
20156
  await access11(tsPath, constants.R_OK);
19626
20157
  return false;
@@ -19669,40 +20200,40 @@ var fileExists2 = async (filePath) => {
19669
20200
  }
19670
20201
  };
19671
20202
  async function ensurePortalStubFiles(dir) {
19672
- const landingPath = path66.join(dir, "landing", "LandingPage.tsx");
20203
+ const landingPath = path67.join(dir, "landing", "LandingPage.tsx");
19673
20204
  if (!await fileExists2(landingPath)) {
19674
- await mkdir16(path66.join(dir, "landing"), { recursive: true });
20205
+ await mkdir16(path67.join(dir, "landing"), { recursive: true });
19675
20206
  await writeFile19(landingPath, LANDING_PAGE_SOURCE, "utf8");
19676
20207
  }
19677
- const plansPath = path66.join(dir, "user-panel", "AccountPlansPage.tsx");
20208
+ const plansPath = path67.join(dir, "user-panel", "AccountPlansPage.tsx");
19678
20209
  if (!await fileExists2(plansPath)) {
19679
- await mkdir16(path66.join(dir, "user-panel"), { recursive: true });
20210
+ await mkdir16(path67.join(dir, "user-panel"), { recursive: true });
19680
20211
  await writeFile19(plansPath, ACCOUNT_PLANS_PAGE_SOURCE, "utf8");
19681
20212
  }
19682
- const dashboardPath = path66.join(dir, "user-panel", "DashboardPage.tsx");
20213
+ const dashboardPath = path67.join(dir, "user-panel", "DashboardPage.tsx");
19683
20214
  if (!await fileExists2(dashboardPath)) {
19684
- await mkdir16(path66.join(dir, "user-panel"), { recursive: true });
20215
+ await mkdir16(path67.join(dir, "user-panel"), { recursive: true });
19685
20216
  await writeFile19(dashboardPath, DASHBOARD_PAGE_SOURCE, "utf8");
19686
20217
  }
19687
20218
  await ensurePublicBrandingFiles(dir);
19688
20219
  }
19689
20220
  async function ensurePublicBrandingFiles(dir) {
19690
- const destDir = path66.join(dir, "public");
20221
+ const destDir = path67.join(dir, "public");
19691
20222
  await mkdir16(destDir, { recursive: true });
19692
20223
  for (const { name, content } of BRANDING_ASSETS) {
19693
- const dest = path66.join(destDir, name);
20224
+ const dest = path67.join(destDir, name);
19694
20225
  if (await fileExists2(dest)) {
19695
20226
  continue;
19696
20227
  }
19697
20228
  await writeFile19(dest, content, "utf8");
19698
20229
  }
19699
- const faviconDest = path66.join(destDir, "Icon.png");
20230
+ const faviconDest = path67.join(destDir, "Icon.png");
19700
20231
  if (!await fileExists2(faviconDest)) {
19701
20232
  await writeFile19(faviconDest, Buffer.from(FAVICON_PNG_BASE64, "base64"));
19702
20233
  }
19703
20234
  }
19704
20235
  async function ensureThinConfigTsx(dir) {
19705
- const tsxPath = path66.join(dir, "apitogo.config.tsx");
20236
+ const tsxPath = path67.join(dir, "apitogo.config.tsx");
19706
20237
  if (await fileExists2(tsxPath)) {
19707
20238
  return false;
19708
20239
  }
@@ -19711,7 +20242,7 @@ async function ensureThinConfigTsx(dir) {
19711
20242
  }
19712
20243
 
19713
20244
  // src/cli/make-config/handler.ts
19714
- var toPosixPath2 = (value) => value.split(path67.sep).join(path67.posix.sep);
20245
+ var toPosixPath2 = (value) => value.split(path68.sep).join(path68.posix.sep);
19715
20246
  var fileExists3 = async (filePath) => {
19716
20247
  try {
19717
20248
  await access12(filePath, constants2.F_OK);
@@ -19721,7 +20252,7 @@ var fileExists3 = async (filePath) => {
19721
20252
  }
19722
20253
  };
19723
20254
  var readJsonConfig = async (filePath) => {
19724
- const raw = await readFile20(filePath, "utf8");
20255
+ const raw = await readFile21(filePath, "utf8");
19725
20256
  const parsed = JSON.parse(raw);
19726
20257
  if (!isPlainObject3(parsed)) {
19727
20258
  throw new Error(`${APITOGO_JSON_FILENAME} must be a JSON object.`);
@@ -19769,9 +20300,9 @@ async function persistConfig(config2, ctx) {
19769
20300
  };
19770
20301
  await writeFile20(ctx.jsonPath, `${JSON.stringify(next, null, 2)}
19771
20302
  `, "utf8");
19772
- const tsxPath = path67.join(ctx.dir, "apitogo.config.tsx");
20303
+ const tsxPath = path68.join(ctx.dir, "apitogo.config.tsx");
19773
20304
  if (await fileExists3(tsxPath) && tsxRoutes.length > 0) {
19774
- const tsxSource = await readFile20(tsxPath, "utf8");
20305
+ const tsxSource = await readFile21(tsxPath, "utf8");
19775
20306
  if (isManagedConfigWrapper(tsxSource)) {
19776
20307
  await writeFile20(
19777
20308
  tsxPath,
@@ -19796,7 +20327,7 @@ function mergeBackendFlag(config2, backend) {
19796
20327
  return next;
19797
20328
  }
19798
20329
  async function makeConfig(argv) {
19799
- const dir = path67.resolve(process.cwd(), argv.dir);
20330
+ const dir = path68.resolve(process.cwd(), argv.dir);
19800
20331
  const pagesDir = toPosixPath2(
19801
20332
  argv.pagesDir.replace(/^[./]+/, "").replace(/\/+$/, "")
19802
20333
  );
@@ -19816,14 +20347,14 @@ async function makeConfig(argv) {
19816
20347
  printSuccessToConsole("Created tsconfig.json.");
19817
20348
  }
19818
20349
  await ensurePortalStubFiles(dir);
19819
- const jsonPath = path67.join(dir, APITOGO_JSON_FILENAME);
20350
+ const jsonPath = path68.join(dir, APITOGO_JSON_FILENAME);
19820
20351
  let createdJson = false;
19821
20352
  let config2;
19822
20353
  if (await fileExists3(jsonPath)) {
19823
20354
  config2 = await readJsonConfig(jsonPath);
19824
20355
  } else {
19825
20356
  config2 = createDefaultApitogoJsonConfig({
19826
- title: titleFromDirectoryName(path67.basename(dir)),
20357
+ title: titleFromDirectoryName(path68.basename(dir)),
19827
20358
  pagesDir
19828
20359
  });
19829
20360
  createdJson = true;
@@ -19875,8 +20406,8 @@ async function makeConfig(argv) {
19875
20406
  return;
19876
20407
  }
19877
20408
  if (action2.kind === "page") {
19878
- const pagePath = path67.join(dir, action2.page.relativePath);
19879
- await mkdir17(path67.dirname(pagePath), { recursive: true });
20409
+ const pagePath = path68.join(dir, action2.page.relativePath);
20410
+ await mkdir17(path68.dirname(pagePath), { recursive: true });
19880
20411
  await writeFile20(
19881
20412
  pagePath,
19882
20413
  buildStarterDocsPageTsx(action2.page.slug, action2.page.title),
@@ -19943,7 +20474,7 @@ async function makeConfig(argv) {
19943
20474
  if (argv.openapi) {
19944
20475
  const wire = await wireDevPortalOpenApi(
19945
20476
  dir,
19946
- path67.resolve(dir, argv.openapi)
20477
+ path68.resolve(dir, argv.openapi)
19947
20478
  );
19948
20479
  if (wire.status !== "succeeded") {
19949
20480
  throw new Error(
@@ -19972,7 +20503,7 @@ async function makeConfig(argv) {
19972
20503
  const slug = normalizePageSlug2(argv.pageSlug);
19973
20504
  const pageTemplate = normalizePageTemplate(argv.pageTemplate);
19974
20505
  const relativePagePath = pageComponentRelativePath(slug);
19975
- const filePath = path67.join(dir, relativePagePath);
20506
+ const filePath = path68.join(dir, relativePagePath);
19976
20507
  const isUpdate = argv.pageUpdate === true;
19977
20508
  if (isUpdate) {
19978
20509
  if (!await fileExists3(filePath)) {
@@ -20005,7 +20536,7 @@ async function makeConfig(argv) {
20005
20536
  argv.pageContent,
20006
20537
  pageTemplate
20007
20538
  );
20008
- await mkdir17(path67.dirname(filePath), { recursive: true });
20539
+ await mkdir17(path68.dirname(filePath), { recursive: true });
20009
20540
  await writeFile20(filePath, tsx, "utf8");
20010
20541
  const configResult = await wireNewDevPortalPageInConfig(
20011
20542
  dir,
@@ -20018,7 +20549,7 @@ async function makeConfig(argv) {
20018
20549
  pagePath: relativePagePath,
20019
20550
  pageTemplate,
20020
20551
  configUpdated: configResult.updated,
20021
- configPath: configResult.configPath ? path67.relative(dir, configResult.configPath) : null
20552
+ configPath: configResult.configPath ? path68.relative(dir, configResult.configPath) : null
20022
20553
  };
20023
20554
  changes.push("page_add");
20024
20555
  }
@@ -20143,10 +20674,10 @@ var config_default = {
20143
20674
  };
20144
20675
 
20145
20676
  // src/cli/cmds/deploy.ts
20146
- import path81 from "node:path";
20677
+ import path82 from "node:path";
20147
20678
 
20148
20679
  // src/cli/apply/handler.ts
20149
- import path78 from "node:path";
20680
+ import path79 from "node:path";
20150
20681
 
20151
20682
  // src/cli/platform/helpers/gateway-plan-validation.ts
20152
20683
  init_gateway_limits();
@@ -20167,15 +20698,15 @@ function buildGatewayPlansValidationRequest(plans) {
20167
20698
  };
20168
20699
  });
20169
20700
  }
20170
- function toConfigIssue(issue2) {
20701
+ function toConfigIssue(issue3) {
20171
20702
  return {
20172
- ...issue2,
20173
- field: issue2.field.replace(/\.limitsJson$/, ".limits")
20703
+ ...issue3,
20704
+ field: issue3.field.replace(/\.limitsJson$/, ".limits")
20174
20705
  };
20175
20706
  }
20176
- function formatGatewayPlanIssue(issue2) {
20177
- const sku = issue2.sku ? ` (${issue2.sku})` : "";
20178
- return `${issue2.field}${sku}: ${issue2.message}`;
20707
+ function formatGatewayPlanIssue(issue3) {
20708
+ const sku = issue3.sku ? ` (${issue3.sku})` : "";
20709
+ return `${issue3.field}${sku}: ${issue3.message}`;
20179
20710
  }
20180
20711
  async function checkGatewayPlansOnPlatform(client, args) {
20181
20712
  let result;
@@ -20198,10 +20729,10 @@ async function checkGatewayPlansOnPlatform(client, args) {
20198
20729
  async function assertGatewayPlansValidForPublish(client, args) {
20199
20730
  const localErrors = args.localErrors ?? [];
20200
20731
  const check2 = await checkGatewayPlansOnPlatform(client, args);
20201
- const localFields = new Set(localErrors.map((issue2) => issue2.field));
20732
+ const localFields = new Set(localErrors.map((issue3) => issue3.field));
20202
20733
  const errors = [
20203
20734
  ...localErrors,
20204
- ...(check2?.errors ?? []).filter((issue2) => !localFields.has(issue2.field))
20735
+ ...(check2?.errors ?? []).filter((issue3) => !localFields.has(issue3.field))
20205
20736
  ];
20206
20737
  if (errors.length === 0) {
20207
20738
  return { blocked: false };
@@ -20341,11 +20872,11 @@ init_gateway_limits();
20341
20872
  // src/cli/platform/helpers/deploy-artifact-hash.ts
20342
20873
  import { createHash as createHash3 } from "node:crypto";
20343
20874
  import { createReadStream, promises as fs8 } from "node:fs";
20344
- import path69 from "node:path";
20875
+ import path70 from "node:path";
20345
20876
 
20346
20877
  // src/cli/platform/helpers/package-files.ts
20347
20878
  import { promises as fs7 } from "node:fs";
20348
- import path68 from "node:path";
20879
+ import path69 from "node:path";
20349
20880
  import ignore from "ignore";
20350
20881
  var DEFAULT_EXCLUDES = /* @__PURE__ */ new Set([
20351
20882
  "node_modules",
@@ -20358,7 +20889,7 @@ var DEFAULT_EXCLUDES = /* @__PURE__ */ new Set([
20358
20889
  async function loadGitignore(rootDir) {
20359
20890
  const ig = ignore();
20360
20891
  ig.add(["node_modules/", ".git/", "dist/", "bin/", "obj/"]);
20361
- const gitignorePath = path68.join(rootDir, ".gitignore");
20892
+ const gitignorePath = path69.join(rootDir, ".gitignore");
20362
20893
  try {
20363
20894
  const content = await fs7.readFile(gitignorePath, "utf8");
20364
20895
  ig.add(content);
@@ -20367,11 +20898,11 @@ async function loadGitignore(rootDir) {
20367
20898
  return ig;
20368
20899
  }
20369
20900
  async function collectFiles(rootDir, relativeDir, ig, files) {
20370
- const absDir = path68.join(rootDir, relativeDir);
20901
+ const absDir = path69.join(rootDir, relativeDir);
20371
20902
  const entries = await fs7.readdir(absDir, { withFileTypes: true });
20372
20903
  for (const entry of entries) {
20373
- const rel2 = relativeDir ? path68.join(relativeDir, entry.name) : entry.name;
20374
- const relPosix = rel2.split(path68.sep).join("/");
20904
+ const rel2 = relativeDir ? path69.join(relativeDir, entry.name) : entry.name;
20905
+ const relPosix = rel2.split(path69.sep).join("/");
20375
20906
  if (DEFAULT_EXCLUDES.has(entry.name) && !relativeDir) {
20376
20907
  continue;
20377
20908
  }
@@ -20386,7 +20917,7 @@ async function collectFiles(rootDir, relativeDir, ig, files) {
20386
20917
  }
20387
20918
  }
20388
20919
  async function collectPackageableFiles(sourceDirectory) {
20389
- const resolved = path68.resolve(sourceDirectory);
20920
+ const resolved = path69.resolve(sourceDirectory);
20390
20921
  const stat7 = await fs7.stat(resolved).catch(() => null);
20391
20922
  if (!stat7?.isDirectory()) {
20392
20923
  throw new Error(`sourceDirectory is not a directory: ${resolved}`);
@@ -20412,7 +20943,7 @@ async function sha256File(filePath) {
20412
20943
  return hash2.digest("hex");
20413
20944
  }
20414
20945
  async function hashDirectoryContents(rootDirectory) {
20415
- const resolved = path69.resolve(rootDirectory);
20946
+ const resolved = path70.resolve(rootDirectory);
20416
20947
  const files = (await collectPackageableFiles(resolved)).sort(
20417
20948
  (a, b) => a.localeCompare(b)
20418
20949
  );
@@ -20420,13 +20951,13 @@ async function hashDirectoryContents(rootDirectory) {
20420
20951
  for (const rel2 of files) {
20421
20952
  hash2.update(rel2);
20422
20953
  hash2.update("\0");
20423
- hash2.update(await fs8.readFile(path69.join(resolved, rel2)));
20954
+ hash2.update(await fs8.readFile(path70.join(resolved, rel2)));
20424
20955
  hash2.update("\0");
20425
20956
  }
20426
20957
  return hash2.digest("hex");
20427
20958
  }
20428
20959
  async function hashDevPortalSiteSources(siteDirectory, options) {
20429
- const resolved = path69.resolve(siteDirectory);
20960
+ const resolved = path70.resolve(siteDirectory);
20430
20961
  const hash2 = createHash3("sha256");
20431
20962
  const candidates = [
20432
20963
  "apitogo.config.json",
@@ -20436,7 +20967,7 @@ async function hashDevPortalSiteSources(siteDirectory, options) {
20436
20967
  "zudoku.config.tsx"
20437
20968
  ];
20438
20969
  for (const name of candidates) {
20439
- const full = path69.join(resolved, name);
20970
+ const full = path70.join(resolved, name);
20440
20971
  try {
20441
20972
  hash2.update(name);
20442
20973
  hash2.update("\0");
@@ -20446,7 +20977,7 @@ async function hashDevPortalSiteSources(siteDirectory, options) {
20446
20977
  }
20447
20978
  }
20448
20979
  for (const dir of ["pages", "landing", "src"]) {
20449
- const full = path69.join(resolved, dir);
20980
+ const full = path70.join(resolved, dir);
20450
20981
  try {
20451
20982
  const st = await fs8.stat(full);
20452
20983
  if (!st.isDirectory()) continue;
@@ -20675,8 +21206,8 @@ function normalizeActionKey(actionKey) {
20675
21206
  const trimmed = actionKey.trim();
20676
21207
  if (trimmed.includes(":")) {
20677
21208
  const [method2, ...pathParts] = trimmed.split(":");
20678
- const path96 = pathParts.join(":").trim();
20679
- return `${method2?.toLowerCase()}:${path96.startsWith("/") ? path96 : `/${path96}`}`;
21209
+ const path101 = pathParts.join(":").trim();
21210
+ return `${method2?.toLowerCase()}:${path101.startsWith("/") ? path101 : `/${path101}`}`;
20680
21211
  }
20681
21212
  const spaceIndex = trimmed.indexOf(" ");
20682
21213
  if (spaceIndex <= 0) {
@@ -20685,8 +21216,8 @@ function normalizeActionKey(actionKey) {
20685
21216
  );
20686
21217
  }
20687
21218
  const method = trimmed.slice(0, spaceIndex).trim().toLowerCase();
20688
- const path95 = trimmed.slice(spaceIndex + 1).trim();
20689
- return `${method}:${path95.startsWith("/") ? path95 : `/${path95}`}`;
21219
+ const path100 = trimmed.slice(spaceIndex + 1).trim();
21220
+ return `${method}:${path100.startsWith("/") ? path100 : `/${path100}`}`;
20690
21221
  }
20691
21222
  async function applyGatewayPublicRoutes(client, projectId, routes, options) {
20692
21223
  const applied = [];
@@ -21164,8 +21695,8 @@ async function upsertGatewayPlansFromManifest(client, config2, projectId, plans,
21164
21695
  }
21165
21696
 
21166
21697
  // src/cli/platform/helpers/openapi-gateway-servers.ts
21167
- import { mkdir as mkdir18, readFile as readFile21, writeFile as writeFile21 } from "node:fs/promises";
21168
- import path70 from "node:path";
21698
+ import { mkdir as mkdir18, readFile as readFile22, writeFile as writeFile21 } from "node:fs/promises";
21699
+ import path71 from "node:path";
21169
21700
  import YAML3 from "yaml";
21170
21701
  var GATEWAY_URL_ENV_KEY = "APITOGO_GATEWAY_URL";
21171
21702
  var PUBLISH_OPENAPI_RELATIVE = ".apitogo/openapi-publish.json";
@@ -21196,8 +21727,8 @@ async function resolveProjectGatewayUrl(client, projectId) {
21196
21727
  return null;
21197
21728
  }
21198
21729
  async function readOpenApiDocument(filePath) {
21199
- const content = await readFile21(filePath, "utf8");
21200
- const ext = path70.extname(filePath).toLowerCase();
21730
+ const content = await readFile22(filePath, "utf8");
21731
+ const ext = path71.extname(filePath).toLowerCase();
21201
21732
  const parsed = ext === ".json" ? JSON.parse(content) : YAML3.parse(content);
21202
21733
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
21203
21734
  throw new Error(`OpenAPI document must be an object: ${filePath}`);
@@ -21205,13 +21736,13 @@ async function readOpenApiDocument(filePath) {
21205
21736
  return parsed;
21206
21737
  }
21207
21738
  async function materializePublishOpenApiWithGateway(siteDirectory, sourceOpenApiPath, gatewayUrl) {
21208
- const site = path70.resolve(siteDirectory);
21209
- const source = path70.resolve(sourceOpenApiPath);
21739
+ const site = path71.resolve(siteDirectory);
21740
+ const source = path71.resolve(sourceOpenApiPath);
21210
21741
  const relativeInput = PUBLISH_OPENAPI_RELATIVE;
21211
- const outputPath = path70.join(site, ...relativeInput.split("/"));
21742
+ const outputPath = path71.join(site, ...relativeInput.split("/"));
21212
21743
  const doc = await readOpenApiDocument(source);
21213
21744
  const rewritten = withGatewayServers(doc, gatewayUrl);
21214
- await mkdir18(path70.dirname(outputPath), { recursive: true });
21745
+ await mkdir18(path71.dirname(outputPath), { recursive: true });
21215
21746
  await writeFile21(
21216
21747
  outputPath,
21217
21748
  `${JSON.stringify(rewritten, null, 2)}
@@ -21221,11 +21752,11 @@ async function materializePublishOpenApiWithGateway(siteDirectory, sourceOpenApi
21221
21752
  return { outputPath, relativeInput };
21222
21753
  }
21223
21754
  async function pointSiteApisAtPublishOpenApi(siteDirectory, relativeInput) {
21224
- const configPath = path70.join(
21225
- path70.resolve(siteDirectory),
21755
+ const configPath = path71.join(
21756
+ path71.resolve(siteDirectory),
21226
21757
  APITOGO_CONFIG_FILENAME2
21227
21758
  );
21228
- const raw = await readFile21(configPath, "utf8");
21759
+ const raw = await readFile22(configPath, "utf8");
21229
21760
  const config2 = JSON.parse(raw);
21230
21761
  const previousApis = config2.apis;
21231
21762
  const previousBackend = config2.backend;
@@ -21242,7 +21773,7 @@ async function pointSiteApisAtPublishOpenApi(siteDirectory, relativeInput) {
21242
21773
  `, "utf8");
21243
21774
  return {
21244
21775
  restore: async () => {
21245
- const currentRaw = await readFile21(configPath, "utf8");
21776
+ const currentRaw = await readFile22(configPath, "utf8");
21246
21777
  const current = JSON.parse(currentRaw);
21247
21778
  if (previousApis === void 0) {
21248
21779
  delete current.apis;
@@ -21264,7 +21795,7 @@ async function pointSiteApisAtPublishOpenApi(siteDirectory, relativeInput) {
21264
21795
  };
21265
21796
  }
21266
21797
  async function prepareOpenApiServersForGatewayBuild(options) {
21267
- const siteDirectory = path70.resolve(options.siteDirectory);
21798
+ const siteDirectory = path71.resolve(options.siteDirectory);
21268
21799
  let openApiPath = resolveBackendOpenApiPath(
21269
21800
  options.manifest?.backend?.openApiPath,
21270
21801
  {
@@ -21277,8 +21808,8 @@ async function prepareOpenApiServersForGatewayBuild(options) {
21277
21808
  );
21278
21809
  if (!openApiPath) {
21279
21810
  try {
21280
- const raw = await readFile21(
21281
- path70.join(siteDirectory, APITOGO_CONFIG_FILENAME2),
21811
+ const raw = await readFile22(
21812
+ path71.join(siteDirectory, APITOGO_CONFIG_FILENAME2),
21282
21813
  "utf8"
21283
21814
  );
21284
21815
  const config2 = JSON.parse(raw);
@@ -21307,8 +21838,8 @@ function normalizeActionKeyFormat(actionKey) {
21307
21838
  const trimmed = actionKey.trim();
21308
21839
  if (trimmed.includes(":")) {
21309
21840
  const [method2, ...pathParts] = trimmed.split(":");
21310
- const path96 = pathParts.join(":").trim();
21311
- return `${method2.toLowerCase()}:${path96.startsWith("/") ? path96 : `/${path96}`}`;
21841
+ const path101 = pathParts.join(":").trim();
21842
+ return `${method2.toLowerCase()}:${path101.startsWith("/") ? path101 : `/${path101}`}`;
21312
21843
  }
21313
21844
  const spaceIndex = trimmed.indexOf(" ");
21314
21845
  if (spaceIndex <= 0) {
@@ -21317,8 +21848,8 @@ function normalizeActionKeyFormat(actionKey) {
21317
21848
  );
21318
21849
  }
21319
21850
  const method = trimmed.slice(0, spaceIndex).trim().toLowerCase();
21320
- const path95 = trimmed.slice(spaceIndex + 1).trim();
21321
- return `${method}:${path95.startsWith("/") ? path95 : `/${path95}`}`;
21851
+ const path100 = trimmed.slice(spaceIndex + 1).trim();
21852
+ return `${method}:${path100.startsWith("/") ? path100 : `/${path100}`}`;
21322
21853
  }
21323
21854
  function normalizePublicRoute(route) {
21324
21855
  const trimmed = route.trim();
@@ -21351,16 +21882,16 @@ function collectManifestPublicActionKeys(gateway) {
21351
21882
  }
21352
21883
 
21353
21884
  // src/cli/platform/helpers/publish-ledger.ts
21354
- import { mkdir as mkdir19, readFile as readFile22, writeFile as writeFile22 } from "node:fs/promises";
21355
- import path71 from "node:path";
21885
+ import { mkdir as mkdir19, readFile as readFile23, writeFile as writeFile22 } from "node:fs/promises";
21886
+ import path72 from "node:path";
21356
21887
  var PUBLISH_LEDGER_RELATIVE_PATH = ".apitogo/publish-state.json";
21357
21888
  function publishLedgerPath(siteDirectory) {
21358
- return path71.join(path71.resolve(siteDirectory), PUBLISH_LEDGER_RELATIVE_PATH);
21889
+ return path72.join(path72.resolve(siteDirectory), PUBLISH_LEDGER_RELATIVE_PATH);
21359
21890
  }
21360
21891
  async function readPublishLedger(siteDirectory) {
21361
21892
  const filePath = publishLedgerPath(siteDirectory);
21362
21893
  try {
21363
- const raw = await readFile22(filePath, "utf8");
21894
+ const raw = await readFile23(filePath, "utf8");
21364
21895
  const parsed = JSON.parse(raw);
21365
21896
  if (parsed?.version !== 1 || !Array.isArray(parsed.steps)) {
21366
21897
  return null;
@@ -21372,7 +21903,7 @@ async function readPublishLedger(siteDirectory) {
21372
21903
  }
21373
21904
  async function writePublishLedger(siteDirectory, ledger) {
21374
21905
  const filePath = publishLedgerPath(siteDirectory);
21375
- await mkdir19(path71.dirname(filePath), { recursive: true });
21906
+ await mkdir19(path72.dirname(filePath), { recursive: true });
21376
21907
  const next = {
21377
21908
  ...ledger,
21378
21909
  version: 1,
@@ -21891,10 +22422,10 @@ async function createProjectWithWarmPoolRetry(client, body, options) {
21891
22422
  }
21892
22423
 
21893
22424
  // src/cli/platform/helpers/backend-packaging.ts
21894
- import { spawn as spawn3 } from "node:child_process";
22425
+ import { spawn as spawn4 } from "node:child_process";
21895
22426
  import { mkdtemp, readdir as readdir2, rm as rm11 } from "node:fs/promises";
21896
22427
  import { tmpdir } from "node:os";
21897
- import path72 from "node:path";
22428
+ import path73 from "node:path";
21898
22429
  async function directoryHasRuntimeConfig(dir) {
21899
22430
  if (!await pathExists(dir)) {
21900
22431
  return false;
@@ -21904,7 +22435,7 @@ async function directoryHasRuntimeConfig(dir) {
21904
22435
  }
21905
22436
  function runDotnetPublish(projectDir, outputDir) {
21906
22437
  return new Promise((resolve, reject) => {
21907
- const child = spawn3(
22438
+ const child = spawn4(
21908
22439
  "dotnet",
21909
22440
  [
21910
22441
  "publish",
@@ -21937,20 +22468,20 @@ function runDotnetPublish(projectDir, outputDir) {
21937
22468
  async function findCsproj(projectDir) {
21938
22469
  const entries = await readdir2(projectDir);
21939
22470
  const csproj = entries.find((name) => name.endsWith(".csproj"));
21940
- return csproj ? path72.join(projectDir, csproj) : null;
22471
+ return csproj ? path73.join(projectDir, csproj) : null;
21941
22472
  }
21942
22473
  async function prepareBackendPackagingDirectory(args) {
21943
- const sourceDirectory = path72.resolve(args.sourceDirectory);
22474
+ const sourceDirectory = path73.resolve(args.sourceDirectory);
21944
22475
  if (args.publishDirectory?.trim()) {
21945
22476
  return {
21946
- directory: path72.resolve(args.publishDirectory),
22477
+ directory: path73.resolve(args.publishDirectory),
21947
22478
  packagingMode: "published"
21948
22479
  };
21949
22480
  }
21950
22481
  if (await directoryHasRuntimeConfig(sourceDirectory)) {
21951
22482
  return { directory: sourceDirectory, packagingMode: "published" };
21952
22483
  }
21953
- const publishSubdir = path72.join(sourceDirectory, "publish");
22484
+ const publishSubdir = path73.join(sourceDirectory, "publish");
21954
22485
  if (await directoryHasRuntimeConfig(publishSubdir)) {
21955
22486
  return { directory: publishSubdir, packagingMode: "published" };
21956
22487
  }
@@ -21962,9 +22493,9 @@ async function prepareBackendPackagingDirectory(args) {
21962
22493
  return { directory: sourceDirectory, packagingMode: "source" };
21963
22494
  }
21964
22495
  const tempRoot = await mkdtemp(
21965
- path72.join(tmpdir(), "apitogo-dotnet-publish-")
22496
+ path73.join(tmpdir(), "apitogo-dotnet-publish-")
21966
22497
  );
21967
- const outputDir = path72.join(tempRoot, "publish");
22498
+ const outputDir = path73.join(tempRoot, "publish");
21968
22499
  const result = await runDotnetPublish(sourceDirectory, outputDir);
21969
22500
  if (result.exitCode !== 0) {
21970
22501
  await rm11(tempRoot, { recursive: true, force: true });
@@ -22010,12 +22541,12 @@ function formatJobStatusResponse(options) {
22010
22541
  // src/cli/platform/helpers/zip.ts
22011
22542
  import { createReadStream as createReadStream2, createWriteStream as createWriteStream2, promises as fs9 } from "node:fs";
22012
22543
  import { tmpdir as tmpdir2 } from "node:os";
22013
- import path73 from "node:path";
22544
+ import path74 from "node:path";
22014
22545
  import { ZipArchive } from "archiver";
22015
22546
  async function packageBackendDirectory(sourceDirectory) {
22016
- const resolved = path73.resolve(sourceDirectory);
22547
+ const resolved = path74.resolve(sourceDirectory);
22017
22548
  const files = await collectPackageableFiles(resolved);
22018
- const zipPath = path73.join(
22549
+ const zipPath = path74.join(
22019
22550
  tmpdir2(),
22020
22551
  `apitogo-backend-${Date.now()}-${Math.random().toString(36).slice(2)}.zip`
22021
22552
  );
@@ -22027,8 +22558,8 @@ async function packageBackendDirectory(sourceDirectory) {
22027
22558
  output4.on("error", reject);
22028
22559
  archive.pipe(output4);
22029
22560
  for (const rel2 of files) {
22030
- archive.append(createReadStream2(path73.join(resolved, rel2)), {
22031
- name: rel2.split(path73.sep).join("/")
22561
+ archive.append(createReadStream2(path74.join(resolved, rel2)), {
22562
+ name: rel2.split(path74.sep).join("/")
22032
22563
  });
22033
22564
  }
22034
22565
  void archive.finalize();
@@ -23529,11 +24060,11 @@ async function resolveProjectIdForPublish(client, config2, args, wait, progress,
23529
24060
  }
23530
24061
 
23531
24062
  // src/cli/platform/tools/dev-portal-site-build.ts
23532
- import path77 from "node:path";
24063
+ import path78 from "node:path";
23533
24064
 
23534
24065
  // src/cli/platform/helpers/dev-portal-site-api-url.ts
23535
- import { readdir as readdir3, readFile as readFile23, writeFile as writeFile23 } from "node:fs/promises";
23536
- import path74 from "node:path";
24066
+ import { readdir as readdir3, readFile as readFile24, writeFile as writeFile23 } from "node:fs/promises";
24067
+ import path75 from "node:path";
23537
24068
  var SITE_API_BASE_URL_ENV_KEY = "VITE_APITOGO_AUTHENTICATION_API_BASE_URL";
23538
24069
  var PLACEHOLDER_HOST_MARKERS = [
23539
24070
  "dp-example.azurecontainerapps.io",
@@ -23554,10 +24085,10 @@ function isPlaceholderDevPortalApiUrl(apiBaseUrl) {
23554
24085
  return PLACEHOLDER_HOST_MARKERS.some((marker) => normalized.includes(marker));
23555
24086
  }
23556
24087
  async function readSiteApiBaseUrl(sourceDirectory) {
23557
- const resolved = path74.resolve(sourceDirectory);
24088
+ const resolved = path75.resolve(sourceDirectory);
23558
24089
  for (const envFile of [".env", ".env.production"]) {
23559
24090
  try {
23560
- const content = await readFile23(path74.join(resolved, envFile), "utf8");
24091
+ const content = await readFile24(path75.join(resolved, envFile), "utf8");
23561
24092
  const lines = [...content.matchAll(ENV_LINE_GLOBAL_RE)];
23562
24093
  if (lines.length === 0) {
23563
24094
  continue;
@@ -23582,13 +24113,13 @@ async function readSiteApiBaseUrl(sourceDirectory) {
23582
24113
  return null;
23583
24114
  }
23584
24115
  async function patchSiteApiBaseUrl(sourceDirectory, apiBaseUrl) {
23585
- const resolved = path74.resolve(sourceDirectory);
24116
+ const resolved = path75.resolve(sourceDirectory);
23586
24117
  const line2 = `${SITE_API_BASE_URL_ENV_KEY}=${apiBaseUrl}`;
23587
24118
  let patchedExisting = false;
23588
24119
  for (const envFile of [".env.production", ".env"]) {
23589
- const envPath = path74.join(resolved, envFile);
24120
+ const envPath = path75.join(resolved, envFile);
23590
24121
  try {
23591
- const content = await readFile23(envPath, "utf8");
24122
+ const content = await readFile24(envPath, "utf8");
23592
24123
  const withoutAssignments = content.replace(ENV_LINE_GLOBAL_RE, "").replace(/\n{3,}/g, "\n\n").trimEnd();
23593
24124
  const patched = withoutAssignments ? `${withoutAssignments}
23594
24125
  ${line2}
@@ -23600,7 +24131,7 @@ ${line2}
23600
24131
  }
23601
24132
  }
23602
24133
  if (!patchedExisting) {
23603
- await writeFile23(path74.join(resolved, ".env"), `${line2}
24134
+ await writeFile23(path75.join(resolved, ".env"), `${line2}
23604
24135
  `);
23605
24136
  }
23606
24137
  }
@@ -23608,7 +24139,7 @@ function tryProjectId(explicit, manifestProjectId, config2) {
23608
24139
  return explicit?.trim() || manifestProjectId?.trim() || config2.defaultProjectId?.trim() || void 0;
23609
24140
  }
23610
24141
  async function ensureLiveSiteApiBaseUrlForBuild(_client, config2, args, resolveFromProject) {
23611
- const sourceDirectory = path74.resolve(args.sourceDirectory);
24142
+ const sourceDirectory = path75.resolve(args.sourceDirectory);
23612
24143
  const explicit = args.apiBaseUrl?.trim();
23613
24144
  if (explicit && !isPlaceholderDevPortalApiUrl(explicit)) {
23614
24145
  await patchSiteApiBaseUrl(sourceDirectory, explicit);
@@ -23644,7 +24175,7 @@ async function* walkFiles(root) {
23644
24175
  return;
23645
24176
  }
23646
24177
  for (const entry of entries) {
23647
- const full = path74.join(root, entry.name);
24178
+ const full = path75.join(root, entry.name);
23648
24179
  if (entry.isDirectory()) {
23649
24180
  if (entry.name === "node_modules" || entry.name === ".git") {
23650
24181
  continue;
@@ -23656,8 +24187,8 @@ async function* walkFiles(root) {
23656
24187
  }
23657
24188
  }
23658
24189
  async function assertDeployableDistHasLiveApiBaseUrl(distDirectory, expectedApiBaseUrl) {
23659
- const dist = path74.resolve(distDirectory);
23660
- const siteRoot = path74.dirname(dist);
24190
+ const dist = path75.resolve(distDirectory);
24191
+ const siteRoot = path75.dirname(dist);
23661
24192
  const fromEnv = expectedApiBaseUrl?.trim() || await readSiteApiBaseUrl(siteRoot) || await readSiteApiBaseUrl(dist);
23662
24193
  if (!fromEnv || isPlaceholderDevPortalApiUrl(fromEnv)) {
23663
24194
  throw new Error(
@@ -23668,7 +24199,7 @@ async function assertDeployableDistHasLiveApiBaseUrl(distDirectory, expectedApiB
23668
24199
  let found = false;
23669
24200
  let foundPlaceholder = false;
23670
24201
  for await (const file of walkFiles(dist)) {
23671
- const content = await readFile23(file, "utf8");
24202
+ const content = await readFile24(file, "utf8");
23672
24203
  if (content.includes(needle)) {
23673
24204
  found = true;
23674
24205
  }
@@ -23693,7 +24224,7 @@ async function assertDeployableDistHasLiveApiBaseUrl(distDirectory, expectedApiB
23693
24224
 
23694
24225
  // src/cli/platform/tools/dev-portal.ts
23695
24226
  import { promises as fs11 } from "node:fs";
23696
- import path76 from "node:path";
24227
+ import path77 from "node:path";
23697
24228
 
23698
24229
  // src/cli/platform/helpers/dev-portal-backend-job.ts
23699
24230
  var DEV_PORTAL_BACKEND_UPDATE_RESOURCE_TYPE = "DevPortalBackendUpdate";
@@ -23803,7 +24334,7 @@ async function snapshotBackendUpdateJobIds(client, projectId) {
23803
24334
  // src/cli/platform/helpers/tarball.ts
23804
24335
  import { createReadStream as createReadStream3, createWriteStream as createWriteStream3, promises as fs10 } from "node:fs";
23805
24336
  import { tmpdir as tmpdir3 } from "node:os";
23806
- import path75 from "node:path";
24337
+ import path76 from "node:path";
23807
24338
  import { TarArchive } from "archiver";
23808
24339
  var DEV_PORTAL_DEPLOY_ARCHIVE_ROOT = "dist";
23809
24340
  function normalizeArchiveRoot(archiveRoot) {
@@ -23814,10 +24345,10 @@ function normalizeArchiveRoot(archiveRoot) {
23814
24345
  return `${trimmed}/`;
23815
24346
  }
23816
24347
  async function packageDirectoryAsTarGz(sourceDirectory, options) {
23817
- const resolved = path75.resolve(sourceDirectory);
24348
+ const resolved = path76.resolve(sourceDirectory);
23818
24349
  const files = await collectPackageableFiles(resolved);
23819
24350
  const entryPrefix = options?.archiveRoot ? normalizeArchiveRoot(options.archiveRoot) : "";
23820
- const archivePath = path75.join(
24351
+ const archivePath = path76.join(
23821
24352
  tmpdir3(),
23822
24353
  `apitogo-docs-${Date.now()}-${Math.random().toString(36).slice(2)}.tgz`
23823
24354
  );
@@ -23829,8 +24360,8 @@ async function packageDirectoryAsTarGz(sourceDirectory, options) {
23829
24360
  output4.on("error", reject);
23830
24361
  archive.pipe(output4);
23831
24362
  for (const rel2 of files) {
23832
- const entryName = entryPrefix + rel2.split(path75.sep).join("/");
23833
- archive.append(createReadStream3(path75.join(resolved, rel2)), {
24363
+ const entryName = entryPrefix + rel2.split(path76.sep).join("/");
24364
+ archive.append(createReadStream3(path76.join(resolved, rel2)), {
23834
24365
  name: entryName
23835
24366
  });
23836
24367
  }
@@ -24072,7 +24603,7 @@ function validateDeployArchiveInput(args) {
24072
24603
  async function resolveDevPortalArchive(args) {
24073
24604
  validateDeployArchiveInput(args);
24074
24605
  if (args.archivePath?.trim()) {
24075
- const archivePath = path76.resolve(args.archivePath.trim());
24606
+ const archivePath = path77.resolve(args.archivePath.trim());
24076
24607
  const stat7 = await fs11.stat(archivePath).catch(() => null);
24077
24608
  if (!stat7?.isFile()) {
24078
24609
  throw new Error(`archivePath is not a file: ${archivePath}`);
@@ -24137,7 +24668,7 @@ async function handleDeployDevPortal(client, config2, args, progress) {
24137
24668
  // src/cli/platform/tools/dev-portal-site-build.ts
24138
24669
  async function handleBuildDevPortalSite(client, config2, args, progress) {
24139
24670
  const buildProgress = progress?.child("Build dev portal site");
24140
- const sourceDirectory = path77.resolve(args.sourceDirectory);
24671
+ const sourceDirectory = path78.resolve(args.sourceDirectory);
24141
24672
  await buildProgress?.onStep("Ensuring live portal API URL");
24142
24673
  const apiBaseUrl = await ensureLiveSiteApiBaseUrlForBuild(
24143
24674
  client,
@@ -24353,7 +24884,7 @@ function throwOnManifestIssues(issues) {
24353
24884
  return;
24354
24885
  }
24355
24886
  throw new Error(
24356
- `apitogo.config.json is not platform-compatible: ${issues.map((issue2) => `${issue2.field}: ${issue2.message}`).join("; ")}`
24887
+ `apitogo.config.json is not platform-compatible: ${issues.map((issue3) => `${issue3.field}: ${issue3.message}`).join("; ")}`
24357
24888
  );
24358
24889
  }
24359
24890
  async function runApplyFromSiteDirectory(client, config2, args, progress) {
@@ -25371,14 +25902,14 @@ function formatBlockedOrFailedResultForHuman(parsed) {
25371
25902
  const lines = [
25372
25903
  typeof parsed.userMessage === "string" ? parsed.userMessage : "Deploy is blocked: apitogo.config.json has errors."
25373
25904
  ];
25374
- for (const issue2 of Array.isArray(parsed.errors) ? parsed.errors : []) {
25375
- lines.push(` - ${formatGatewayPlanIssue(issue2)}`);
25905
+ for (const issue3 of Array.isArray(parsed.errors) ? parsed.errors : []) {
25906
+ lines.push(` - ${formatGatewayPlanIssue(issue3)}`);
25376
25907
  }
25377
25908
  const warnings = Array.isArray(parsed.warnings) ? parsed.warnings : [];
25378
25909
  if (warnings.length > 0) {
25379
25910
  lines.push("", "Warnings:");
25380
- for (const issue2 of warnings) {
25381
- lines.push(` - ${formatGatewayPlanIssue(issue2)}`);
25911
+ for (const issue3 of warnings) {
25912
+ lines.push(` - ${formatGatewayPlanIssue(issue3)}`);
25382
25913
  }
25383
25914
  }
25384
25915
  lines.push(
@@ -25633,7 +26164,7 @@ function resultLooksFailed(payload) {
25633
26164
  return parsed.status === "failed" || parsed.status === "blocked";
25634
26165
  }
25635
26166
  async function apply(argv) {
25636
- const sourceDirectory = path78.resolve(process.cwd(), argv.dir);
26167
+ const sourceDirectory = path79.resolve(process.cwd(), argv.dir);
25637
26168
  let config2;
25638
26169
  try {
25639
26170
  config2 = loadConfig();
@@ -25703,7 +26234,7 @@ async function apply(argv) {
25703
26234
  if (canPromptInteractively && parsed?.status === "blocked" && parsed.reason === "project_not_found") {
25704
26235
  const resolution = await resolveProjectNotFoundInteractively(
25705
26236
  parsed,
25706
- path78.basename(sourceDirectory)
26237
+ path79.basename(sourceDirectory)
25707
26238
  );
25708
26239
  if (resolution) {
25709
26240
  effectiveProjectId = void 0;
@@ -25723,7 +26254,7 @@ async function apply(argv) {
25723
26254
  while (canPromptInteractively && parsed?.status === "blocked" && parsed.reason === "project_name_exists") {
25724
26255
  const resolution = await resolveProjectNameExistsInteractively(
25725
26256
  parsed,
25726
- effectiveProjectName ?? path78.basename(sourceDirectory)
26257
+ effectiveProjectName ?? path79.basename(sourceDirectory)
25727
26258
  );
25728
26259
  if (!resolution) {
25729
26260
  break;
@@ -25795,7 +26326,7 @@ async function apply(argv) {
25795
26326
 
25796
26327
  // src/cli/deploy/command.ts
25797
26328
  init_invariant();
25798
- import path80 from "node:path";
26329
+ import path81 from "node:path";
25799
26330
 
25800
26331
  // src/cli/deploy/v2.ts
25801
26332
  import { rm as rm12 } from "node:fs/promises";
@@ -25803,7 +26334,245 @@ init_invariant();
25803
26334
 
25804
26335
  // src/cli/portal/report.ts
25805
26336
  import { access as access13 } from "node:fs/promises";
25806
- import path79 from "node:path";
26337
+ import path80 from "node:path";
26338
+
26339
+ // src/cli/deploy/gateway.ts
26340
+ import { createHash as createHash4 } from "node:crypto";
26341
+ import { readFile as readFile25 } from "node:fs/promises";
26342
+ init_invariant();
26343
+ var GatewayDeployError = class extends ApitogoError {
26344
+ constructor(code, message, developerHint) {
26345
+ super(message, { developerHint, title: "Cannot deploy the gateway" });
26346
+ this.code = code;
26347
+ this.name = "GatewayDeployError";
26348
+ }
26349
+ };
26350
+ var sha256 = (text) => createHash4("sha256").update(text).digest("hex");
26351
+ function gatewayNotDeployableReason(gateway) {
26352
+ if (gateway.origin.mode === "managed")
26353
+ return "a managed origin cannot be deployed from this CLI version yet; use origin.mode proxy";
26354
+ if (gateway.specs.length > 1)
26355
+ return "the platform takes one OpenAPI file per gateway, but the config lists several";
26356
+ return void 0;
26357
+ }
26358
+ function assertDeployable(gateway) {
26359
+ if (gateway.origin.mode === "managed")
26360
+ throw new GatewayDeployError(
26361
+ "GATEWAY_MANAGED_ORIGIN_UNSUPPORTED",
26362
+ "A managed origin cannot be deployed from this CLI version yet.",
26363
+ 'Set gateway.origin.mode to "proxy" and point gateway.origin.url at your API.'
26364
+ );
26365
+ if (gateway.specs.length > 1)
26366
+ throw new GatewayDeployError(
26367
+ "GATEWAY_MULTIPLE_SPECS",
26368
+ "The platform takes one OpenAPI file per gateway, but gateway.specs lists several.",
26369
+ "Merge them into one file, or list only one."
26370
+ );
26371
+ }
26372
+ async function readSpec(gateway) {
26373
+ const file = gateway.specs[0];
26374
+ if (!file)
26375
+ throw new GatewayDeployError(
26376
+ "GATEWAY_SPEC_UNREADABLE",
26377
+ "gateway.specs lists no OpenAPI file.",
26378
+ "Add one to gateway.specs in the config."
26379
+ );
26380
+ try {
26381
+ return await readFile25(file);
26382
+ } catch {
26383
+ throw new GatewayDeployError(
26384
+ "GATEWAY_SPEC_UNREADABLE",
26385
+ `Cannot read the OpenAPI file ${file}.`,
26386
+ "Check gateway.specs in the config."
26387
+ );
26388
+ }
26389
+ }
26390
+ async function gatewayDeployInputs(resolved, projectId) {
26391
+ const gateway = resolved.gateway;
26392
+ if (!gateway) throw new Error("gateway is not configured");
26393
+ const spec = await readSpec(gateway);
26394
+ const manifest = await readManifest(resolved.state);
26395
+ const body = gatewayConfigBody(
26396
+ gateway,
26397
+ derivedCorsSites({
26398
+ portalUrl: manifest?.services.portal?.url,
26399
+ docsUrl: resolved.portal?.docs.provider === "external" ? resolved.portal.docs.url : void 0
26400
+ })
26401
+ );
26402
+ return {
26403
+ gateway,
26404
+ spec,
26405
+ body,
26406
+ specHash: sha256(`${projectId}|${sha256(spec)}`),
26407
+ configHash: sha256(`${projectId}|${stableJson(body)}`)
26408
+ };
26409
+ }
26410
+ var defaultSleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
26411
+ function describePlatformProblem(error) {
26412
+ if (!(error instanceof PlatformApiError) || !error.problem) return [];
26413
+ const errors = error.problem.errors;
26414
+ if (Array.isArray(errors) && errors.length > 0) {
26415
+ return errors.map((item) => {
26416
+ const entry = item;
26417
+ return `${String(entry.field ?? "gateway")}: ${String(entry.message ?? "")} ${String(entry.hint ?? "")} [${String(entry.code ?? "")}]`.replace(
26418
+ /\s+/g,
26419
+ " "
26420
+ );
26421
+ });
26422
+ }
26423
+ const { code, field, hint, detail } = error.problem;
26424
+ return code ? [
26425
+ `${String(field ?? "gateway")}: ${String(detail ?? "")} ${String(hint ?? "")} [${String(code)}]`.replace(
26426
+ /\s+/g,
26427
+ " "
26428
+ )
26429
+ ] : [];
26430
+ }
26431
+ function rejected(what, error) {
26432
+ if (error instanceof PlatformApiError && error.status === 400) {
26433
+ const lines = describePlatformProblem(error);
26434
+ throw new GatewayDeployError(
26435
+ "GATEWAY_REJECTED",
26436
+ `The platform refused the ${what}:
26437
+ ${lines.join("\n ") || error.message}`,
26438
+ "Fix the listed problems in apitogo.config.json and run deploy again."
26439
+ );
26440
+ }
26441
+ throw error;
26442
+ }
26443
+ async function deployGateway(resolved, projectId, deps, options = {}) {
26444
+ const { gateway, spec, body, specHash, configHash } = await gatewayDeployInputs(resolved, projectId);
26445
+ assertDeployable(gateway);
26446
+ const client = deps.client;
26447
+ const sleep5 = deps.sleep ?? defaultSleep;
26448
+ const now = deps.now ?? Date.now;
26449
+ const timeoutMs = deps.reconcileTimeoutMs ?? 18e4;
26450
+ const intervalMs = deps.pollIntervalMs ?? 2e3;
26451
+ const specStep = {
26452
+ name: "spec",
26453
+ hash: () => specHash,
26454
+ run: async () => {
26455
+ try {
26456
+ const uploaded = await client.putGatewaySpec(projectId, spec);
26457
+ return {
26458
+ specSha256: uploaded.sha256,
26459
+ operationCount: String(uploaded.operationCount)
26460
+ };
26461
+ } catch (error) {
26462
+ return rejected("OpenAPI file", error);
26463
+ }
26464
+ }
26465
+ };
26466
+ const ticketOf = async () => stepOf(await readLedger(resolved.state), "gateway", "config")?.output?.reconciliationId;
26467
+ const configStep = {
26468
+ name: "config",
26469
+ hash: () => configHash,
26470
+ run: async () => {
26471
+ try {
26472
+ const accepted = await client.putGatewayConfig(projectId, body);
26473
+ return {
26474
+ reconciliationId: accepted.reconciliationId,
26475
+ desiredHash: accepted.desiredHash
26476
+ };
26477
+ } catch (error) {
26478
+ return rejected("gateway config", error);
26479
+ }
26480
+ },
26481
+ // A reconciliation the platform gave up on is not a result worth keeping.
26482
+ stillValid: async () => {
26483
+ const ticket = await ticketOf();
26484
+ if (!ticket) return false;
26485
+ try {
26486
+ const state = await client.getGatewayReconciliation(projectId, ticket);
26487
+ return state.state !== "Failed" && state.state !== "Superseded";
26488
+ } catch {
26489
+ return false;
26490
+ }
26491
+ }
26492
+ };
26493
+ const reconcileStep = {
26494
+ name: "reconcile",
26495
+ hash: () => configHash,
26496
+ run: async () => {
26497
+ const ticket = await ticketOf();
26498
+ if (!ticket)
26499
+ throw new GatewayDeployError(
26500
+ "GATEWAY_RECONCILE_FAILED",
26501
+ "There is no reconciliation to wait for.",
26502
+ "Run deploy again."
26503
+ );
26504
+ const deadline = now() + timeoutMs;
26505
+ for (; ; ) {
26506
+ const state = await client.getGatewayReconciliation(projectId, ticket);
26507
+ if (state.state === "Succeeded") break;
26508
+ if (state.state === "Failed" || state.state === "Superseded")
26509
+ throw new GatewayDeployError(
26510
+ "GATEWAY_RECONCILE_FAILED",
26511
+ `The platform could not apply the gateway config: ${[state.errorCode, state.errorMessage].filter(Boolean).join(" ") || state.state}.`,
26512
+ "Fix the cause and run deploy again; the config is sent again."
26513
+ );
26514
+ if (now() >= deadline)
26515
+ throw new GatewayDeployError(
26516
+ "GATEWAY_RECONCILE_TIMEOUT",
26517
+ `The platform did not finish within ${Math.round(timeoutMs / 1e3)} seconds (state: ${state.state}).`,
26518
+ "Run deploy again to keep waiting. If it stays Pending, the platform worker that applies gateway configs may not be running yet."
26519
+ );
26520
+ await sleep5(intervalMs);
26521
+ }
26522
+ const status2 = await client.getGatewayStatus(projectId);
26523
+ return {
26524
+ baseUrl: status2.baseUrl,
26525
+ operationCount: String(status2.operationCount),
26526
+ ...status2.specSha256 ? { specSha256: status2.specSha256 } : {}
26527
+ };
26528
+ }
26529
+ };
26530
+ const outcomes = await runPipeline(
26531
+ resolved.state,
26532
+ "gateway",
26533
+ [specStep, configStep, reconcileStep],
26534
+ { force: options.force }
26535
+ );
26536
+ const reconcile = outcomes.find((outcome) => outcome.step === "reconcile");
26537
+ const ran = reconcile !== void 0 && !reconcile.skipped;
26538
+ const known = (await readManifest(resolved.state))?.services.gateway;
26539
+ let baseUrl = reconcile?.output?.baseUrl ?? known?.baseUrl;
26540
+ if (ran || known === void 0) {
26541
+ const status2 = await client.getGatewayStatus(projectId);
26542
+ baseUrl = status2.baseUrl || baseUrl;
26543
+ if (baseUrl) {
26544
+ const capabilities = await client.getGatewayCapabilities(projectId);
26545
+ const previous = known?.baseUrl?.replace(/\/+$/, "");
26546
+ if (baseUrl.replace(/\/+$/, "") !== previous || ran) {
26547
+ await writeManifest(
26548
+ resolved.state,
26549
+ withGatewayEntry(await readManifest(resolved.state), {
26550
+ projectId,
26551
+ projectName: resolved.project.name,
26552
+ provider: status2.provider,
26553
+ baseUrl,
26554
+ capabilities: capabilities.capabilities,
26555
+ openapiUrl: status2.openApiPath ?? void 0,
26556
+ openapiHash: status2.openApiHash ?? status2.specSha256 ?? void 0
26557
+ })
26558
+ );
26559
+ }
26560
+ }
26561
+ }
26562
+ const config2 = outcomes.find((outcome) => outcome.step === "config");
26563
+ return {
26564
+ status: ran ? "deployed" : "unchanged",
26565
+ steps: outcomes.map((outcome) => ({
26566
+ step: outcome.step,
26567
+ skipped: outcome.skipped
26568
+ })),
26569
+ baseUrl,
26570
+ operationCount: reconcile?.output?.operationCount ? Number(reconcile.output.operationCount) : void 0,
26571
+ reconciliationId: config2?.output?.reconciliationId
26572
+ };
26573
+ }
26574
+
26575
+ // src/cli/portal/report.ts
25807
26576
  var DEPLOY_ORDER = [
25808
26577
  "identity",
25809
26578
  "gateway",
@@ -25844,7 +26613,7 @@ async function planPortalService(resolved, ledger) {
25844
26613
  const hash2 = await portalBuildHash(resolved, prepared);
25845
26614
  const previous = stepOf(ledger, "portal", "build");
25846
26615
  const built = await exists4(
25847
- path79.join(resolved.state.buildDir, "index.html")
26616
+ path80.join(resolved.state.buildDir, "index.html")
25848
26617
  );
25849
26618
  if (previous?.status === "succeeded" && previous.hash === hash2 && built) {
25850
26619
  return {
@@ -25874,7 +26643,148 @@ async function planPortalService(resolved, ledger) {
25874
26643
  };
25875
26644
  }
25876
26645
  }
25877
- async function planV2(dir, only) {
26646
+ function describePolicies(gateway) {
26647
+ const policies = gateway.policies;
26648
+ return [
26649
+ policies.access ? `access: ${[
26650
+ policies.access.jwt === void 0 ? void 0 : `jwt ${policies.access.jwt ? "on" : "off"}`,
26651
+ policies.access.apiKey === void 0 ? void 0 : `api key ${policies.access.apiKey ? "on" : "off"}`
26652
+ ].filter(Boolean).join(", ")}` : void 0,
26653
+ policies.rateLimit ? `rate limit: ${policies.rateLimit.calls} per ${policies.rateLimit.periodSeconds} s` : void 0,
26654
+ policies.quota ? `quota: ${policies.quota.calls} per ${policies.quota.periodDays} day(s)` : void 0,
26655
+ policies.cors ? `cors: ${policies.cors.length} origin(s)` : void 0,
26656
+ policies.operations?.public ? `public operations: ${policies.operations.public.length}` : void 0
26657
+ ].filter((line2) => line2 !== void 0);
26658
+ }
26659
+ function dryRunLines(dryRun) {
26660
+ const changes = dryRun.changes;
26661
+ if (!changes) return [];
26662
+ const list2 = (label, items) => items.length > 0 ? [` ${label}: ${items.join(", ")}`] : [];
26663
+ if (changes.wouldCreate)
26664
+ return [
26665
+ "platform dry run: the gateway does not exist yet; everything would be created",
26666
+ ...list2("operations", changes.operationsAdded),
26667
+ ...list2("public", changes.nowPublic),
26668
+ ...list2("denied", changes.nowDenied)
26669
+ ];
26670
+ if (!changes.hasChanges)
26671
+ return ["platform dry run: the gateway already matches this config"];
26672
+ return [
26673
+ "platform dry run: the gateway would change",
26674
+ ...list2("operations added", changes.operationsAdded),
26675
+ ...list2("operations removed", changes.operationsRemoved),
26676
+ ...list2("now public", changes.nowPublic),
26677
+ ...list2("now protected", changes.nowProtected),
26678
+ ...list2("now denied", changes.nowDenied),
26679
+ ...changes.originChanged ? [" origin address changes"] : [],
26680
+ ...changes.policyChanges.map((c) => ` ${c}`),
26681
+ ...dryRun.activeKeys > 0 ? [
26682
+ ` active keys: ${dryRun.activeKeys} (removed or denied operations stop working for them)`
26683
+ ] : []
26684
+ ];
26685
+ }
26686
+ async function planGatewayService(resolved, ledger, dryRun) {
26687
+ const base = { service: "gateway", issues: [] };
26688
+ const gateway = resolved.gateway;
26689
+ if (!gateway) throw new Error("gateway is not configured");
26690
+ const why = gatewayNotDeployableReason(gateway);
26691
+ if (why)
26692
+ return {
26693
+ ...base,
26694
+ action: "not-deployable-yet",
26695
+ reason: `configured, but ${why}`,
26696
+ details: []
26697
+ };
26698
+ const details = [
26699
+ `origin: proxy ${gateway.origin.url ?? "(no url set)"}`,
26700
+ ...gateway.auth ? [`auth: ${gateway.auth.mode}`] : ["auth: none"],
26701
+ ...describePolicies(gateway)
26702
+ ];
26703
+ const projectId = resolved.project.id;
26704
+ const notAsked = !dryRun ? "platform dry run: not asked, you are not logged in" : !projectId ? "platform dry run: not asked, this folder is not linked to a project" : void 0;
26705
+ let inputs;
26706
+ try {
26707
+ inputs = await gatewayDeployInputs(resolved, resolved.project.id ?? "");
26708
+ } catch (error) {
26709
+ return {
26710
+ ...base,
26711
+ action: "blocked",
26712
+ reason: "cannot deploy until this is fixed",
26713
+ details,
26714
+ issues: [
26715
+ {
26716
+ code: "GATEWAY_SPEC_UNREADABLE",
26717
+ path: "gateway.specs",
26718
+ message: error instanceof Error ? error.message : String(error)
26719
+ }
26720
+ ]
26721
+ };
26722
+ }
26723
+ const issues = [];
26724
+ if (notAsked) {
26725
+ details.push(notAsked);
26726
+ } else if (dryRun && projectId) {
26727
+ try {
26728
+ const answer = await dryRun(projectId, {
26729
+ config: inputs.body,
26730
+ spec: inputs.spec.toString("utf8")
26731
+ });
26732
+ if (!answer.ok)
26733
+ issues.push(
26734
+ ...answer.issues.map((item) => ({
26735
+ code: item.code,
26736
+ path: item.field,
26737
+ message: item.message,
26738
+ hint: item.hint
26739
+ }))
26740
+ );
26741
+ else details.push(...dryRunLines(answer));
26742
+ if (answer.ok && !answer.comparedWithGateway)
26743
+ details.push(
26744
+ " (the platform has no gateway unit for this project yet, so nothing could be compared)"
26745
+ );
26746
+ } catch (error) {
26747
+ details.push(
26748
+ `platform dry run: not available (${error instanceof Error ? error.message : String(error)})`
26749
+ );
26750
+ }
26751
+ }
26752
+ const step2 = (name) => stepOf(ledger, "gateway", name);
26753
+ const hashes = {
26754
+ spec: inputs.specHash,
26755
+ config: inputs.configHash,
26756
+ reconcile: inputs.configHash
26757
+ };
26758
+ const state = (name) => {
26759
+ const record = step2(name);
26760
+ if (!record) return "not done yet";
26761
+ if (record.status === "failed")
26762
+ return `failed last time (${record.error ?? "no message"})`;
26763
+ if (record.status === "running") return "did not finish last time";
26764
+ return record.hash === hashes[name] ? "done, unchanged" : "inputs changed";
26765
+ };
26766
+ const lines = [
26767
+ `step spec (upload the OpenAPI file): ${state("spec")}`,
26768
+ `step config (send the wanted state): ${state("config")}`,
26769
+ `step reconcile (wait until the platform applied it): ${state("reconcile")}`
26770
+ ];
26771
+ const allDone = step2("spec")?.status === "succeeded" && step2("spec")?.hash === hashes.spec && step2("config")?.status === "succeeded" && step2("config")?.hash === hashes.config && step2("reconcile")?.status === "succeeded" && step2("reconcile")?.hash === hashes.reconcile;
26772
+ if (issues.length > 0)
26773
+ return {
26774
+ ...base,
26775
+ action: "blocked",
26776
+ reason: "the platform refuses this config; fix these first",
26777
+ details: [...details, ...lines],
26778
+ issues
26779
+ };
26780
+ return {
26781
+ ...base,
26782
+ action: allDone ? "skip" : "deploy",
26783
+ reason: allDone ? "deploy skipped: nothing it depends on has changed" : "will deploy: the wanted state or the OpenAPI file is not applied yet",
26784
+ details: [...details, ...lines]
26785
+ };
26786
+ }
26787
+ async function planV2(dir, only, options = {}) {
25878
26788
  const { resolved } = loadConfigV2(dir);
25879
26789
  const ledger = await readLedger(resolved.state);
25880
26790
  const services = [];
@@ -25898,6 +26808,10 @@ async function planV2(dir, only) {
25898
26808
  });
25899
26809
  } else if (service === "portal") {
25900
26810
  services.push(await planPortalService(resolved, ledger));
26811
+ } else if (service === "gateway") {
26812
+ services.push(
26813
+ await planGatewayService(resolved, ledger, options.gatewayDryRun)
26814
+ );
25901
26815
  } else {
25902
26816
  services.push({
25903
26817
  service,
@@ -25913,7 +26827,80 @@ async function planV2(dir, only) {
25913
26827
  function entryStatus(manifest, service) {
25914
26828
  return manifest?.services[service];
25915
26829
  }
25916
- async function statusV2(dir, only) {
26830
+ function originHealthLines(origin) {
26831
+ if (!origin.lastProbeAtUtc) return ["origin check: not checked yet"];
26832
+ return [
26833
+ `origin check: ${origin.lastProbeAtUtc}${origin.latencyMs != null ? `, ${origin.latencyMs} ms` : ""}`,
26834
+ ...origin.lastErrorCode ? [`origin problem: ${origin.lastErrorCode}`] : [],
26835
+ ...origin.healthChangedAtUtc ? [`origin health changed: ${origin.healthChangedAtUtc}`] : []
26836
+ ];
26837
+ }
26838
+ function corsLines(platform) {
26839
+ const sites = platform.cors ?? [];
26840
+ return [
26841
+ ...sites.length > 0 ? [
26842
+ "browser origins (CORS):",
26843
+ ...sites.map(
26844
+ (site) => ` ${site.origin} (${site.source})${site.applied ? "" : ", not on the real gateway yet"}`
26845
+ )
26846
+ ] : [],
26847
+ ...platform.openApiPath ? [
26848
+ `openapi: ${platform.openApiPath} (${platform.exportAccess ?? "public"}${platform.openApiHash ? `, ${platform.openApiHash.slice(0, 12)}` : ""})`
26849
+ ] : []
26850
+ ];
26851
+ }
26852
+ async function gatewayStatus(resolved, ledger, manifest, remote) {
26853
+ const steps = ["spec", "config", "reconcile"].map((name) => ({
26854
+ name,
26855
+ record: stepOf(ledger, "gateway", name)
26856
+ }));
26857
+ const failed = steps.find((step2) => step2.record?.status === "failed");
26858
+ const local = [
26859
+ ...steps.filter((step2) => step2.record).map((step2) => `step ${step2.name}: ${step2.record?.status}`),
26860
+ ...failed ? [`error: ${failed.record?.error ?? "unknown"}`] : []
26861
+ ];
26862
+ const entry = manifest?.services.gateway;
26863
+ const projectId = resolved.project.id ?? manifest?.project.id;
26864
+ let state = entry ? "ready" : "not-deployed";
26865
+ const details = [];
26866
+ if (entry?.baseUrl) details.push(`base url: ${entry.baseUrl}`);
26867
+ if (entry?.provider) details.push(`provider: ${entry.provider}`);
26868
+ if (remote && projectId) {
26869
+ try {
26870
+ const platform = await remote(projectId);
26871
+ details.push(
26872
+ `platform state: ${platform.status}`,
26873
+ platform.inSync ? "drift: none (the real gateway matches the config)" : `drift: the real gateway does not match the config yet (wanted ${platform.desiredHash?.slice(0, 12) ?? "none"}, real ${platform.observedHash?.slice(0, 12) ?? "none"})`,
26874
+ ...platform.origin ? [
26875
+ `origin: ${platform.origin.mode}${platform.origin.url ? ` ${platform.origin.url}` : ""}, health ${platform.origin.health}`,
26876
+ ...originHealthLines(platform.origin)
26877
+ ] : [],
26878
+ `operations: ${platform.operationCount}`,
26879
+ ...corsLines(platform)
26880
+ );
26881
+ const latest = platform.latestReconciliation;
26882
+ if (latest)
26883
+ details.push(
26884
+ `last reconciliation: ${latest.state}${latest.errorCode ? ` (${latest.errorCode})` : ""}`
26885
+ );
26886
+ if (platform.baseUrl && !entry?.baseUrl)
26887
+ details.push(`base url: ${platform.baseUrl}`);
26888
+ if (!platform.inSync && ["Active", "Reconciling", "Failed"].includes(platform.status))
26889
+ state = "drift";
26890
+ } catch (error) {
26891
+ details.push(
26892
+ error instanceof PlatformApiError && error.status === 404 ? "platform: no gateway for this project yet" : `platform: not reachable (${error instanceof Error ? error.message : String(error)}); showing local records only`
26893
+ );
26894
+ }
26895
+ } else if (!projectId) {
26896
+ details.push("platform: not asked, this folder is not linked to a project");
26897
+ } else if (!remote) {
26898
+ details.push("platform: not asked, you are not logged in");
26899
+ }
26900
+ if (failed) state = "deploy-failed";
26901
+ return { service: "gateway", state, details: [...details, ...local] };
26902
+ }
26903
+ async function statusV2(dir, only, options = {}) {
25917
26904
  const { resolved } = loadConfigV2(dir);
25918
26905
  const ledger = await readLedger(resolved.state);
25919
26906
  const manifest = await readManifest(resolved.state);
@@ -25933,7 +26920,7 @@ async function statusV2(dir, only) {
25933
26920
  if (service === "portal") {
25934
26921
  const build2 = stepOf(ledger, "portal", "build");
25935
26922
  const built = await exists4(
25936
- path79.join(resolved.state.buildDir, "index.html")
26923
+ path80.join(resolved.state.buildDir, "index.html")
25937
26924
  );
25938
26925
  const state = build2?.status === "failed" ? "build-failed" : build2?.status === "succeeded" && built ? "built" : "not-built";
25939
26926
  services.push({
@@ -25944,13 +26931,19 @@ async function statusV2(dir, only) {
25944
26931
  ...build2?.finishedAt ? [`last build: ${build2.finishedAt}`] : [],
25945
26932
  ...build2?.status === "failed" ? [`error: ${build2.error ?? "unknown"}`] : [],
25946
26933
  ...state === "built" ? [
25947
- `output: ${path79.relative(resolved.configDir, resolved.state.buildDir)}`
26934
+ `output: ${path80.relative(resolved.configDir, resolved.state.buildDir)}`
25948
26935
  ] : [],
25949
26936
  ...url2 ? [`url: ${url2}`] : ["url: not known (portal is not deployed yet)"]
25950
26937
  ]
25951
26938
  });
25952
26939
  continue;
25953
26940
  }
26941
+ if (service === "gateway") {
26942
+ services.push(
26943
+ await gatewayStatus(resolved, ledger, manifest, options.gatewayRemote)
26944
+ );
26945
+ continue;
26946
+ }
25954
26947
  services.push({
25955
26948
  service,
25956
26949
  state: entry ? "ready" : "not-deployed",
@@ -25975,7 +26968,7 @@ function formatPlan(output4) {
25975
26968
  `${svc.service}: ${svc.reason}`,
25976
26969
  ...svc.details.map((line2) => ` ${line2}`),
25977
26970
  ...svc.issues.map(
25978
- (issue2) => ` problem: ${issue2.message}${issue2.hint ? ` ${issue2.hint}` : ""} [${issue2.code}]`
26971
+ (issue3) => ` problem: ${issue3.message}${issue3.hint ? ` ${issue3.hint}` : ""} [${issue3.code}]`
25979
26972
  )
25980
26973
  ]);
25981
26974
  }
@@ -26042,7 +27035,7 @@ async function deployV2(options, deps) {
26042
27035
  throw new DeployError(
26043
27036
  "PROJECT_REQUIRED",
26044
27037
  "This folder is not linked to a project yet.",
26045
- "Pass --project-id <id>, or set APITOGO_DEFAULT_PROJECT_ID. The id is then saved as project.id in apitogo.config.json."
27038
+ "Run `apitogo project create` to make a platform project and link this folder. Or pass --project-id <id> (saved as project.id), or set APITOGO_DEFAULT_PROJECT_ID."
26046
27039
  );
26047
27040
  }
26048
27041
  const results = [];
@@ -26068,12 +27061,16 @@ async function deployV2(options, deps) {
26068
27061
  });
26069
27062
  continue;
26070
27063
  }
27064
+ if (service === "gateway") {
27065
+ results.push(await deployGatewayService(options, deps, projectId, named));
27066
+ continue;
27067
+ }
26071
27068
  if (service !== "portal") {
26072
27069
  if (named)
26073
27070
  throw new DeployError(
26074
27071
  "SERVICE_NOT_DEPLOYABLE",
26075
27072
  `Deploying ${service} is not available in this CLI version yet.`,
26076
- "Only the portal can be deployed for now."
27073
+ "Only the portal and the gateway can be deployed for now."
26077
27074
  );
26078
27075
  results.push({
26079
27076
  service,
@@ -26094,6 +27091,36 @@ async function deployV2(options, deps) {
26094
27091
  if (!resolved.project.id) await setProjectId(options.dir, projectId);
26095
27092
  return { projectId, services: results };
26096
27093
  }
27094
+ async function deployGatewayService(options, deps, projectId, named) {
27095
+ const { resolved } = loadConfigV2(options.dir);
27096
+ const gateway = resolved.gateway;
27097
+ const why = gateway ? gatewayNotDeployableReason(gateway) : void 0;
27098
+ if (why) {
27099
+ if (named)
27100
+ throw new DeployError(
27101
+ "SERVICE_NOT_DEPLOYABLE",
27102
+ `The gateway cannot be deployed: ${why}.`,
27103
+ "Change gateway.origin / gateway.specs in the config, or deploy another service."
27104
+ );
27105
+ return { service: "gateway", status: "skipped", reason: why, steps: [] };
27106
+ }
27107
+ const client = deps.client;
27108
+ if (!client.putGatewaySpec || !client.putGatewayConfig || !client.getGatewayReconciliation || !client.getGatewayStatus || !client.getGatewayCapabilities)
27109
+ throw new Error("The platform client has no gateway calls.");
27110
+ const result = await deployGateway(
27111
+ resolved,
27112
+ projectId,
27113
+ { ...deps.gateway, client },
27114
+ { force: options.force }
27115
+ );
27116
+ return {
27117
+ service: "gateway",
27118
+ status: result.status,
27119
+ steps: result.steps,
27120
+ url: result.baseUrl,
27121
+ operationCount: result.operationCount
27122
+ };
27123
+ }
26097
27124
  async function deployPortal(options, deps, projectId) {
26098
27125
  const { resolved, root, prepared } = await preparePortalWorkspace(
26099
27126
  options.dir,
@@ -26154,7 +27181,8 @@ async function deployPortal(options, deps, projectId) {
26154
27181
  function formatDeploy(output4) {
26155
27182
  return output4.services.flatMap((result) => {
26156
27183
  const url2 = result.url ? ` (${result.url})` : "";
26157
- const head = result.status === "deployed" ? `${result.service}: deployed${result.environment ? ` to ${result.environment}` : ""}${url2}` : result.status === "unchanged" ? `${result.service}: unchanged, nothing to publish${url2}` : `${result.service}: skipped, ${result.reason}`;
27184
+ const ops = result.operationCount !== void 0 ? `, ${result.operationCount} operation(s)` : "";
27185
+ const head = result.status === "deployed" ? `${result.service}: deployed${result.environment ? ` to ${result.environment}` : ""}${url2}${ops}` : result.status === "unchanged" ? `${result.service}: unchanged, nothing to publish${url2}` : `${result.service}: skipped, ${result.reason}`;
26158
27186
  return [
26159
27187
  head,
26160
27188
  ...result.steps.map(
@@ -26171,7 +27199,7 @@ async function deployProject(argv) {
26171
27199
  const config2 = loadConfig();
26172
27200
  const output4 = await deployV2(
26173
27201
  {
26174
- dir: path80.resolve(process.cwd(), argv.dir),
27202
+ dir: path81.resolve(process.cwd(), argv.dir),
26175
27203
  only: Array.isArray(argv.only) ? argv.only.join(",") : argv.only,
26176
27204
  projectId: argv.projectId,
26177
27205
  env: argv.env,
@@ -26252,7 +27280,7 @@ var deploy_default = {
26252
27280
  }),
26253
27281
  handler: async (argv) => {
26254
27282
  await captureEvent({ argv, event: "apitogo deploy" });
26255
- if (await isConfigV2(path81.resolve(process.cwd(), argv.dir))) {
27283
+ if (await isConfigV2(path82.resolve(process.cwd(), argv.dir))) {
26256
27284
  await deployProject(argv);
26257
27285
  return;
26258
27286
  }
@@ -26260,16 +27288,108 @@ var deploy_default = {
26260
27288
  }
26261
27289
  };
26262
27290
 
27291
+ // src/cli/export/handler.ts
27292
+ import { mkdir as mkdir20, writeFile as writeFile24 } from "node:fs/promises";
27293
+ import path83 from "node:path";
27294
+ init_invariant();
27295
+ var ExportError = class extends ApitogoError {
27296
+ constructor(message, developerHint) {
27297
+ super(message, { developerHint, title: "Cannot export OpenAPI" });
27298
+ this.name = "ExportError";
27299
+ }
27300
+ };
27301
+ async function exportOpenApi(args, deps) {
27302
+ if (args.target !== "openapi")
27303
+ throw new ExportError(
27304
+ `Cannot export "${args.target}".`,
27305
+ "Only `apitogo export openapi` exists."
27306
+ );
27307
+ const dir = path83.resolve(process.cwd(), args.dir ?? ".");
27308
+ if (!await isConfigV2(dir))
27309
+ throw new ExportError(
27310
+ "This command needs a config version 2 project.",
27311
+ "Run it in a folder with a version 2 apitogo.config.json."
27312
+ );
27313
+ const { resolved } = loadConfigV2(dir);
27314
+ const projectId = args.projectId?.trim() || resolved.project.id || deps.envProjectId?.trim();
27315
+ if (!projectId)
27316
+ throw new ExportError(
27317
+ "This folder is not linked to a project.",
27318
+ "Run `apitogo project create`, or pass --project-id."
27319
+ );
27320
+ let text;
27321
+ try {
27322
+ text = await deps.client.getGatewayOpenApi(projectId);
27323
+ } catch (error) {
27324
+ if (error instanceof PlatformApiError && error.status === 404)
27325
+ throw new ExportError(
27326
+ "The platform has no OpenAPI file for this project yet.",
27327
+ "Deploy the gateway first with `apitogo deploy --only gateway`."
27328
+ );
27329
+ throw error;
27330
+ }
27331
+ if (!args.out) return { text };
27332
+ const file = path83.resolve(process.cwd(), args.out);
27333
+ await mkdir20(path83.dirname(file), { recursive: true });
27334
+ await writeFile24(file, text);
27335
+ return { text, file };
27336
+ }
27337
+ async function exportCommand(args) {
27338
+ try {
27339
+ const config2 = loadConfig();
27340
+ const result = await exportOpenApi(args, {
27341
+ client: new PlatformApiClient(config2),
27342
+ envProjectId: config2.defaultProjectId
27343
+ });
27344
+ if (result.file) printDiagnosticsToConsole(`Wrote ${result.file}.`);
27345
+ else printResultToConsole(result.text);
27346
+ } catch (error) {
27347
+ const message = error instanceof Error ? error.message : String(error);
27348
+ const hint = error instanceof ApitogoError ? error.developerHint : void 0;
27349
+ await printCriticalFailureToConsoleAndExit(
27350
+ hint ? `${message} ${hint}` : message
27351
+ );
27352
+ }
27353
+ }
27354
+
27355
+ // src/cli/cmds/export.ts
27356
+ var export_default = {
27357
+ desc: "Print or save the OpenAPI file the platform publishes for your gateway (config version 2)",
27358
+ command: "export <target>",
27359
+ builder: (yargs2) => yargs2.positional("target", {
27360
+ type: "string",
27361
+ choices: ["openapi"],
27362
+ describe: "What to export"
27363
+ }).option("out", {
27364
+ type: "string",
27365
+ describe: "Write to this file instead of printing",
27366
+ normalize: true
27367
+ }).option("dir", {
27368
+ type: "string",
27369
+ describe: "Project directory",
27370
+ default: ".",
27371
+ normalize: true,
27372
+ hidden: true
27373
+ }).option("project-id", {
27374
+ type: "string",
27375
+ describe: "Project id (default: project.id in apitogo.config.json)"
27376
+ }),
27377
+ handler: async (argv) => {
27378
+ await captureEvent({ argv: { dir: argv.dir }, event: "apitogo export" });
27379
+ await exportCommand(argv);
27380
+ }
27381
+ };
27382
+
26263
27383
  // src/cli/init/handler.ts
26264
- import path85 from "node:path";
27384
+ import path87 from "node:path";
26265
27385
 
26266
27386
  // src/cli/scaffold/run-scaffold-discovery.ts
26267
- import { access as access15, readFile as readFile26, writeFile as writeFile25 } from "node:fs/promises";
26268
- import path84 from "node:path";
27387
+ import { access as access15, readFile as readFile28, writeFile as writeFile26 } from "node:fs/promises";
27388
+ import path86 from "node:path";
26269
27389
 
26270
27390
  // src/cli/scaffold/detect-backend.ts
26271
- import { readdir as readdir4, readFile as readFile24 } from "node:fs/promises";
26272
- import path82 from "node:path";
27391
+ import { readdir as readdir4, readFile as readFile26 } from "node:fs/promises";
27392
+ import path84 from "node:path";
26273
27393
  var DEFAULT_RUNTIME_VERSION = {
26274
27394
  dotnet: "DOTNET_10",
26275
27395
  nodejs: "NODE_22_LTS",
@@ -26286,7 +27406,7 @@ async function listFiles(directory) {
26286
27406
  }
26287
27407
  async function readText(filePath) {
26288
27408
  try {
26289
- return await readFile24(filePath, "utf8");
27409
+ return await readFile26(filePath, "utf8");
26290
27410
  } catch {
26291
27411
  return void 0;
26292
27412
  }
@@ -26315,15 +27435,15 @@ function isRunnableNodePackage(pkg) {
26315
27435
  return typeof scripts.start === "string" || typeof pkg.main === "string";
26316
27436
  }
26317
27437
  async function detectParentBackend(siteDirectory) {
26318
- const site = path82.resolve(siteDirectory);
26319
- const directory = path82.dirname(site);
27438
+ const site = path84.resolve(siteDirectory);
27439
+ const directory = path84.dirname(site);
26320
27440
  if (directory === site) {
26321
27441
  return void 0;
26322
27442
  }
26323
27443
  const files = await listFiles(directory);
26324
27444
  const toResult = (runtime, runtimeVersion, marker) => ({
26325
27445
  directory,
26326
- sourceDirectory: path82.relative(site, directory).split(path82.sep).join("/") || ".",
27446
+ sourceDirectory: path84.relative(site, directory).split(path84.sep).join("/") || ".",
26327
27447
  runtime,
26328
27448
  runtimeVersion,
26329
27449
  marker
@@ -26331,7 +27451,7 @@ async function detectParentBackend(siteDirectory) {
26331
27451
  const csproj = files.find((f) => f.endsWith(".csproj"));
26332
27452
  const solution = files.find((f) => /\.slnx?$/.test(f));
26333
27453
  if (csproj || solution) {
26334
- const content = csproj ? await readText(path82.join(directory, csproj)) : void 0;
27454
+ const content = csproj ? await readText(path84.join(directory, csproj)) : void 0;
26335
27455
  return toResult(
26336
27456
  "dotnet",
26337
27457
  dotnetVersionFromCsproj(content),
@@ -26339,7 +27459,7 @@ async function detectParentBackend(siteDirectory) {
26339
27459
  );
26340
27460
  }
26341
27461
  if (files.includes("package.json")) {
26342
- const raw = await readText(path82.join(directory, "package.json"));
27462
+ const raw = await readText(path84.join(directory, "package.json"));
26343
27463
  try {
26344
27464
  const pkg = JSON.parse(raw ?? "");
26345
27465
  if (isRunnableNodePackage(pkg)) {
@@ -26354,7 +27474,7 @@ async function detectParentBackend(siteDirectory) {
26354
27474
  }
26355
27475
  const pythonMarker = PYTHON_MARKERS.find((m) => files.includes(m));
26356
27476
  if (pythonMarker) {
26357
- const versionSource = await readText(path82.join(directory, ".python-version")) ?? (pythonMarker === "pyproject.toml" ? (await readText(path82.join(directory, pythonMarker)))?.match(
27477
+ const versionSource = await readText(path84.join(directory, ".python-version")) ?? (pythonMarker === "pyproject.toml" ? (await readText(path84.join(directory, pythonMarker)))?.match(
26358
27478
  /requires-python\s*=\s*"([^"]+)"/
26359
27479
  )?.[1] : void 0);
26360
27480
  return toResult(
@@ -26370,14 +27490,14 @@ async function detectParentBackend(siteDirectory) {
26370
27490
  import {
26371
27491
  access as access14,
26372
27492
  lstat as lstat2,
26373
- mkdir as mkdir20,
27493
+ mkdir as mkdir21,
26374
27494
  readdir as readdir5,
26375
- readFile as readFile25,
27495
+ readFile as readFile27,
26376
27496
  readlink,
26377
27497
  symlink as symlink2,
26378
- writeFile as writeFile24
27498
+ writeFile as writeFile25
26379
27499
  } from "node:fs/promises";
26380
- import path83 from "node:path";
27500
+ import path85 from "node:path";
26381
27501
  var DOCS_DIR_WHITELIST = [
26382
27502
  "docs",
26383
27503
  "documents",
@@ -26414,14 +27534,14 @@ async function isDirectory2(filePath) {
26414
27534
  }
26415
27535
  }
26416
27536
  function toPosix3(value) {
26417
- return value.split(path83.sep).join(path83.posix.sep);
27537
+ return value.split(path85.sep).join(path85.posix.sep);
26418
27538
  }
26419
27539
  function isWhitelistedDirName(name) {
26420
27540
  return DOCS_DIR_WHITELIST.includes(name.toLowerCase());
26421
27541
  }
26422
27542
  function isInside2(parent, child) {
26423
- const relative = path83.relative(parent, child);
26424
- return relative === "" || !relative.startsWith("..") && !path83.isAbsolute(relative);
27543
+ const relative = path85.relative(parent, child);
27544
+ return relative === "" || !relative.startsWith("..") && !path85.isAbsolute(relative);
26425
27545
  }
26426
27546
  async function collectMarkdownFiles(dir, excludeDir) {
26427
27547
  const files = [];
@@ -26435,9 +27555,9 @@ async function collectMarkdownFiles(dir, excludeDir) {
26435
27555
  if (entry.name.startsWith(".")) {
26436
27556
  continue;
26437
27557
  }
26438
- const full = path83.join(dir, entry.name);
27558
+ const full = path85.join(dir, entry.name);
26439
27559
  if (entry.isDirectory()) {
26440
- if (SKIP_SEARCH_DIRS.has(entry.name) || entry.isSymbolicLink() || excludeDir && path83.resolve(full) === excludeDir) {
27560
+ if (SKIP_SEARCH_DIRS.has(entry.name) || entry.isSymbolicLink() || excludeDir && path85.resolve(full) === excludeDir) {
26441
27561
  continue;
26442
27562
  }
26443
27563
  files.push(...await collectMarkdownFiles(full, excludeDir));
@@ -26450,11 +27570,11 @@ async function collectMarkdownFiles(dir, excludeDir) {
26450
27570
  return files;
26451
27571
  }
26452
27572
  async function collectSearchRoots(siteDirectory) {
26453
- const site = path83.resolve(siteDirectory);
26454
- const parent = path83.dirname(site);
27573
+ const site = path85.resolve(siteDirectory);
27574
+ const parent = path85.dirname(site);
26455
27575
  const roots = [];
26456
27576
  const add = (dir) => {
26457
- const resolved = path83.resolve(dir);
27577
+ const resolved = path85.resolve(dir);
26458
27578
  if (!roots.includes(resolved)) {
26459
27579
  roots.push(resolved);
26460
27580
  }
@@ -26467,7 +27587,7 @@ async function collectSearchRoots(siteDirectory) {
26467
27587
  if (!entry.isDirectory() || SKIP_SEARCH_DIRS.has(entry.name)) {
26468
27588
  continue;
26469
27589
  }
26470
- add(path83.join(parent, entry.name));
27590
+ add(path85.join(parent, entry.name));
26471
27591
  }
26472
27592
  } catch {
26473
27593
  }
@@ -26476,13 +27596,13 @@ async function collectSearchRoots(siteDirectory) {
26476
27596
  return roots;
26477
27597
  }
26478
27598
  async function discoverNearbyDocsDirectories(siteDirectory) {
26479
- const site = path83.resolve(siteDirectory);
26480
- const sitePages = path83.join(site, PAGES_DIR);
27599
+ const site = path85.resolve(siteDirectory);
27600
+ const sitePages = path85.join(site, PAGES_DIR);
26481
27601
  const searchedDirectories = await collectSearchRoots(site);
26482
27602
  const directories = [];
26483
27603
  const seen = /* @__PURE__ */ new Set();
26484
27604
  const consider = async (dir) => {
26485
- const resolved = path83.resolve(dir);
27605
+ const resolved = path85.resolve(dir);
26486
27606
  if (resolved === site || seen.has(resolved) || isInside2(sitePages, resolved)) {
26487
27607
  return;
26488
27608
  }
@@ -26499,7 +27619,7 @@ async function discoverNearbyDocsDirectories(siteDirectory) {
26499
27619
  directories.push({ sourceDirectory: resolved, files });
26500
27620
  };
26501
27621
  for (const root of searchedDirectories) {
26502
- if (isWhitelistedDirName(path83.basename(root))) {
27622
+ if (isWhitelistedDirName(path85.basename(root))) {
26503
27623
  await consider(root);
26504
27624
  }
26505
27625
  try {
@@ -26511,7 +27631,7 @@ async function discoverNearbyDocsDirectories(siteDirectory) {
26511
27631
  if (!isWhitelistedDirName(entry.name)) {
26512
27632
  continue;
26513
27633
  }
26514
- await consider(path83.join(root, entry.name));
27634
+ await consider(path85.join(root, entry.name));
26515
27635
  }
26516
27636
  } catch {
26517
27637
  }
@@ -26530,8 +27650,8 @@ function slugifyDirName(name) {
26530
27650
  return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "") || "docs";
26531
27651
  }
26532
27652
  async function allocateLinkName(pagesDir, sourceDirectory, used) {
26533
- const base = slugifyDirName(path83.basename(sourceDirectory));
26534
- const parent = slugifyDirName(path83.basename(path83.dirname(sourceDirectory)));
27653
+ const base = slugifyDirName(path85.basename(sourceDirectory));
27654
+ const parent = slugifyDirName(path85.basename(path85.dirname(sourceDirectory)));
26535
27655
  const candidates = [base, `${parent}-${base}`];
26536
27656
  let index = 2;
26537
27657
  while (true) {
@@ -26539,7 +27659,7 @@ async function allocateLinkName(pagesDir, sourceDirectory, used) {
26539
27659
  if (used.has(name)) {
26540
27660
  continue;
26541
27661
  }
26542
- const target = path83.join(pagesDir, name);
27662
+ const target = path85.join(pagesDir, name);
26543
27663
  if (await pathExists5(target)) {
26544
27664
  const stat7 = await lstat2(target);
26545
27665
  if (!stat7.isDirectory() || !await isManagedLinkDir(target)) {
@@ -26570,8 +27690,8 @@ async function alreadyLinked(linkPath, sourcePath) {
26570
27690
  return false;
26571
27691
  }
26572
27692
  const current = await readlink(linkPath);
26573
- const resolved = path83.resolve(path83.dirname(linkPath), current);
26574
- return resolved === path83.resolve(sourcePath);
27693
+ const resolved = path85.resolve(path85.dirname(linkPath), current);
27694
+ return resolved === path85.resolve(sourcePath);
26575
27695
  } catch {
26576
27696
  return false;
26577
27697
  }
@@ -26583,8 +27703,8 @@ async function linkMarkdownFile(sourcePath, linkPath) {
26583
27703
  if (await pathExists5(linkPath)) {
26584
27704
  return "skipped";
26585
27705
  }
26586
- await mkdir20(path83.dirname(linkPath), { recursive: true });
26587
- const relativeTarget = path83.relative(path83.dirname(linkPath), sourcePath);
27706
+ await mkdir21(path85.dirname(linkPath), { recursive: true });
27707
+ const relativeTarget = path85.relative(path85.dirname(linkPath), sourcePath);
26588
27708
  await symlink2(
26589
27709
  relativeTarget,
26590
27710
  linkPath,
@@ -26599,11 +27719,11 @@ async function applyDocsNavigation(siteDirectory, routes) {
26599
27719
  if (routes.length === 0) {
26600
27720
  return;
26601
27721
  }
26602
- const configPath = path83.join(siteDirectory, CONFIG_FILENAME);
27722
+ const configPath = path85.join(siteDirectory, CONFIG_FILENAME);
26603
27723
  if (!await pathExists5(configPath)) {
26604
27724
  return;
26605
27725
  }
26606
- const raw = await readFile25(configPath, "utf8");
27726
+ const raw = await readFile27(configPath, "utf8");
26607
27727
  const parsed = JSON.parse(raw);
26608
27728
  if (!isPlainObject5(parsed)) {
26609
27729
  return;
@@ -26612,12 +27732,12 @@ async function applyDocsNavigation(siteDirectory, routes) {
26612
27732
  force: false
26613
27733
  });
26614
27734
  const updated = ensureHeaderDocsNavigation(parsed, routes);
26615
- await writeFile24(configPath, `${JSON.stringify(updated, null, 2)}
27735
+ await writeFile25(configPath, `${JSON.stringify(updated, null, 2)}
26616
27736
  `, "utf8");
26617
27737
  }
26618
27738
  async function discoverAndWireDocs(siteDirectory) {
26619
- const site = path83.resolve(siteDirectory);
26620
- const pagesDir = path83.join(site, PAGES_DIR);
27739
+ const site = path85.resolve(siteDirectory);
27740
+ const pagesDir = path85.join(site, PAGES_DIR);
26621
27741
  const { directories, searchedDirectories } = await discoverNearbyDocsDirectories(site);
26622
27742
  if (directories.length === 0) {
26623
27743
  return {
@@ -26629,7 +27749,7 @@ async function discoverAndWireDocs(siteDirectory) {
26629
27749
  skippedCount: 0
26630
27750
  };
26631
27751
  }
26632
- await mkdir20(pagesDir, { recursive: true });
27752
+ await mkdir21(pagesDir, { recursive: true });
26633
27753
  const usedNames = /* @__PURE__ */ new Set();
26634
27754
  const sources = [];
26635
27755
  const routes = [];
@@ -26639,15 +27759,15 @@ async function discoverAndWireDocs(siteDirectory) {
26639
27759
  directory.sourceDirectory,
26640
27760
  usedNames
26641
27761
  );
26642
- const linkDirectory = path83.join(pagesDir, linkName);
27762
+ const linkDirectory = path85.join(pagesDir, linkName);
26643
27763
  const linked = [];
26644
27764
  const skipped = [];
26645
27765
  for (const sourcePath of directory.files) {
26646
27766
  const relativeSource = toPosix3(
26647
- path83.relative(directory.sourceDirectory, sourcePath)
27767
+ path85.relative(directory.sourceDirectory, sourcePath)
26648
27768
  );
26649
- const linkPath = path83.join(linkDirectory, relativeSource);
26650
- const relativeLink = toPosix3(path83.relative(site, linkPath));
27769
+ const linkPath = path85.join(linkDirectory, relativeSource);
27770
+ const relativeLink = toPosix3(path85.relative(site, linkPath));
26651
27771
  const route = toRoutePath(relativeLink, PAGES_DIR);
26652
27772
  if (routes.includes(route)) {
26653
27773
  skipped.push({
@@ -26695,11 +27815,11 @@ async function discoverAndWireDocs(siteDirectory) {
26695
27815
  };
26696
27816
  }
26697
27817
  function siteRelative(siteDirectory, filePath) {
26698
- const relative = toPosix3(path83.relative(siteDirectory, filePath));
27818
+ const relative = toPosix3(path85.relative(siteDirectory, filePath));
26699
27819
  if (!relative) {
26700
27820
  return ".";
26701
27821
  }
26702
- if (path83.isAbsolute(relative)) {
27822
+ if (path85.isAbsolute(relative)) {
26703
27823
  return toPosix3(filePath);
26704
27824
  }
26705
27825
  if (relative.startsWith("..")) {
@@ -26787,8 +27907,8 @@ async function pathExists6(filePath) {
26787
27907
  }
26788
27908
  async function wireOpenApiIntoJsonConfig(siteDirectory, openApiPath) {
26789
27909
  const input5 = toSiteRelativeOpenApiInput(siteDirectory, openApiPath);
26790
- const jsonConfigPath = path84.join(siteDirectory, "apitogo.config.json");
26791
- const raw = await readFile26(jsonConfigPath, "utf8");
27910
+ const jsonConfigPath = path86.join(siteDirectory, "apitogo.config.json");
27911
+ const raw = await readFile28(jsonConfigPath, "utf8");
26792
27912
  const siteConfig = JSON.parse(raw);
26793
27913
  const updated = applyOpenApiToSiteConfig(siteConfig, {
26794
27914
  input: input5,
@@ -26796,7 +27916,7 @@ async function wireOpenApiIntoJsonConfig(siteDirectory, openApiPath) {
26796
27916
  });
26797
27917
  const backend = updated.backend && typeof updated.backend === "object" && !Array.isArray(updated.backend) ? { ...updated.backend } : {};
26798
27918
  updated.backend = { ...backend, openApiPath: input5 };
26799
- await writeFile25(
27919
+ await writeFile26(
26800
27920
  jsonConfigPath,
26801
27921
  `${JSON.stringify(updated, null, 2)}
26802
27922
  `,
@@ -26815,7 +27935,7 @@ async function discoverAndWireOpenApiLite(siteDirectory, mode) {
26815
27935
  const invalidCandidates = [];
26816
27936
  let selected;
26817
27937
  for (const candidate of search.candidates) {
26818
- const content = await readFile26(candidate, "utf8");
27938
+ const content = await readFile28(candidate, "utf8");
26819
27939
  const parsed = liteParseOpenApi(content, candidate);
26820
27940
  if (parsed.valid) {
26821
27941
  selected = {
@@ -26839,7 +27959,7 @@ async function discoverAndWireOpenApiLite(siteDirectory, mode) {
26839
27959
  wired: false
26840
27960
  };
26841
27961
  }
26842
- const jsonConfigPath = path84.join(siteDirectory, "apitogo.config.json");
27962
+ const jsonConfigPath = path86.join(siteDirectory, "apitogo.config.json");
26843
27963
  if (!await pathExists6(jsonConfigPath)) {
26844
27964
  return {
26845
27965
  openApiDiscovery: {
@@ -26889,13 +28009,13 @@ async function discoverAndWireBackend(siteDirectory, mode) {
26889
28009
  if (mode !== "nearby") {
26890
28010
  return { status: "skipped", mode };
26891
28011
  }
26892
- const jsonConfigPath = path84.join(siteDirectory, "apitogo.config.json");
28012
+ const jsonConfigPath = path86.join(siteDirectory, "apitogo.config.json");
26893
28013
  if (!await pathExists6(jsonConfigPath)) {
26894
28014
  return { status: "not_found" };
26895
28015
  }
26896
28016
  try {
26897
28017
  const siteConfig = JSON.parse(
26898
- await readFile26(jsonConfigPath, "utf8")
28018
+ await readFile28(jsonConfigPath, "utf8")
26899
28019
  );
26900
28020
  const existing = siteConfig.backend && typeof siteConfig.backend === "object" && !Array.isArray(siteConfig.backend) ? siteConfig.backend : {};
26901
28021
  if (typeof existing.sourceDirectory === "string" && existing.sourceDirectory.trim()) {
@@ -26916,7 +28036,7 @@ async function discoverAndWireBackend(siteDirectory, mode) {
26916
28036
  runtime: detected.runtime,
26917
28037
  runtimeVersion: detected.runtimeVersion
26918
28038
  };
26919
- await writeFile25(
28039
+ await writeFile26(
26920
28040
  jsonConfigPath,
26921
28041
  `${JSON.stringify(siteConfig, null, 2)}
26922
28042
  `,
@@ -26956,8 +28076,8 @@ function slugifyName2(name) {
26956
28076
  return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "") || "dev-portal-site";
26957
28077
  }
26958
28078
  function resolveInitOutputDirectory(dirArg, name) {
26959
- const resolved = path85.resolve(process.cwd(), dirArg);
26960
- const base = path85.basename(resolved);
28079
+ const resolved = path87.resolve(process.cwd(), dirArg);
28080
+ const base = path87.basename(resolved);
26961
28081
  const isDot = dirArg === "." || dirArg === "./" || base === "." || resolved === process.cwd();
26962
28082
  if (isDot) {
26963
28083
  const fromName = name?.trim();
@@ -26967,7 +28087,7 @@ function resolveInitOutputDirectory(dirArg, name) {
26967
28087
  );
26968
28088
  }
26969
28089
  return {
26970
- outputDirectory: path85.resolve(process.cwd(), slugifyName2(fromName)),
28090
+ outputDirectory: path87.resolve(process.cwd(), slugifyName2(fromName)),
26971
28091
  siteName: fromName
26972
28092
  };
26973
28093
  }
@@ -27111,7 +28231,7 @@ var init_default = {
27111
28231
  }).option("v2", {
27112
28232
  type: "boolean",
27113
28233
  default: false,
27114
- describe: "Preview: create a config version 2 project (standalone or embedded layout). check, run and build read it; deploy still reads the version 1 config"
28234
+ describe: "Preview: create a config version 2 project (standalone or embedded layout). add, remove, check, run, build, plan, status, deploy and secrets read it"
27115
28235
  }).option("layout", {
27116
28236
  type: "string",
27117
28237
  choices: ["standalone", "embedded"],
@@ -27150,8 +28270,8 @@ import open2 from "open";
27150
28270
  import colors14 from "picocolors";
27151
28271
 
27152
28272
  // src/cli/platform/device-auth.ts
27153
- function url(apiUrl, path95) {
27154
- return `${apiUrl}${path95.startsWith("/") ? path95 : `/${path95}`}`;
28273
+ function url(apiUrl, path100) {
28274
+ return `${apiUrl}${path100.startsWith("/") ? path100 : `/${path100}`}`;
27155
28275
  }
27156
28276
  async function requestDeviceCode(apiUrl) {
27157
28277
  const response = await fetchWithRetry(
@@ -27333,7 +28453,7 @@ var logout_default = {
27333
28453
  };
27334
28454
 
27335
28455
  // src/cli/portal/commands.ts
27336
- import path86 from "node:path";
28456
+ import path88 from "node:path";
27337
28457
  init_invariant();
27338
28458
  function parseOnly(value) {
27339
28459
  if (value === void 0) return void 0;
@@ -27350,7 +28470,7 @@ function parseOnly(value) {
27350
28470
  async function run2(command2, argv, compute, format) {
27351
28471
  const json = argv.json === true;
27352
28472
  try {
27353
- const dir = path86.resolve(process.cwd(), argv.dir ?? ".");
28473
+ const dir = path88.resolve(process.cwd(), argv.dir ?? ".");
27354
28474
  if (!await isConfigV2(dir)) {
27355
28475
  throw new ServiceEditError(
27356
28476
  `${command2} works with a config version 2 project, and there is none in ${dir}.`,
@@ -27379,8 +28499,34 @@ async function run2(command2, argv, compute, format) {
27379
28499
  );
27380
28500
  }
27381
28501
  }
27382
- var plan = (argv) => run2("plan", argv, planV2, formatPlan);
27383
- var statusOfProject = (argv) => run2("status", argv, statusV2, formatStatus);
28502
+ var plan = (argv) => run2(
28503
+ "plan",
28504
+ argv,
28505
+ (dir, only) => planV2(dir, only, { gatewayDryRun: gatewayDryRun() }),
28506
+ formatPlan
28507
+ );
28508
+ function gatewayRemote() {
28509
+ try {
28510
+ const client = new PlatformApiClient(loadConfig());
28511
+ return (projectId) => client.getGatewayStatus(projectId);
28512
+ } catch {
28513
+ return void 0;
28514
+ }
28515
+ }
28516
+ function gatewayDryRun() {
28517
+ try {
28518
+ const client = new PlatformApiClient(loadConfig());
28519
+ return (projectId, body) => client.planGateway(projectId, body);
28520
+ } catch {
28521
+ return void 0;
28522
+ }
28523
+ }
28524
+ var statusOfProject = (argv) => run2(
28525
+ "status",
28526
+ argv,
28527
+ (dir, only) => statusV2(dir, only, { gatewayRemote: gatewayRemote() }),
28528
+ formatStatus
28529
+ );
27384
28530
 
27385
28531
  // src/cli/cmds/plan.ts
27386
28532
  var plan_default = {
@@ -27406,6 +28552,88 @@ var plan_default = {
27406
28552
  }
27407
28553
  };
27408
28554
 
28555
+ // src/cli/project/create-handler.ts
28556
+ init_invariant();
28557
+ import path89 from "node:path";
28558
+
28559
+ // src/cli/project/v2.ts
28560
+ init_invariant();
28561
+ var ProjectCreateError = class extends ApitogoError {
28562
+ constructor(code, message, developerHint) {
28563
+ super(message, { developerHint, title: "Cannot create the project" });
28564
+ this.code = code;
28565
+ this.name = "ProjectCreateError";
28566
+ }
28567
+ };
28568
+ function modulesForConfig(config2) {
28569
+ return [
28570
+ ...config2.portal && config2.portal.enabled !== false ? ["dev-portal"] : [],
28571
+ ...config2.gateway && config2.gateway.enabled !== false ? ["apihub"] : []
28572
+ ];
28573
+ }
28574
+ async function createProjectV2(options, deps) {
28575
+ const { config: config2, resolved } = loadConfigV2(options.dir);
28576
+ if (resolved.project.id) {
28577
+ throw new ProjectCreateError(
28578
+ "ALREADY_LINKED",
28579
+ `This folder is already linked to project ${resolved.project.id}.`,
28580
+ "Run `apitogo deploy`. To start over, run `apitogo teardown` first."
28581
+ );
28582
+ }
28583
+ const modules = modulesForConfig(config2);
28584
+ if (modules.length === 0) {
28585
+ throw new ProjectCreateError(
28586
+ "NOTHING_TO_HOST",
28587
+ "No enabled service needs hosting.",
28588
+ "Enable the portal or the gateway first (`apitogo add portal`)."
28589
+ );
28590
+ }
28591
+ const name = options.name?.trim() || resolved.project.name;
28592
+ const raw = await deps.bootstrap({
28593
+ name,
28594
+ description: options.description,
28595
+ modules,
28596
+ wait: options.wait !== false
28597
+ });
28598
+ const outcome = parse3(raw);
28599
+ if (outcome.requiresPlatformPlanSelection) {
28600
+ throw new ProjectCreateError(
28601
+ "PLAN_NOT_SELECTED",
28602
+ `A project named "${name}" already exists and has no plan yet.`,
28603
+ "Pick another name with --name, or choose a plan for that project in the web app."
28604
+ );
28605
+ }
28606
+ if (outcome.status === "failed" || outcome.status === "blocked" || !outcome.projectId) {
28607
+ throw new ProjectCreateError(
28608
+ "CREATE_FAILED",
28609
+ `The platform could not create the project: ${outcome.message ?? outcome.status ?? "no answer"}.`,
28610
+ "Nothing was written to this folder. Try again, or check `apitogo status`."
28611
+ );
28612
+ }
28613
+ if (outcome.status === "running" || outcome.status === "pending") {
28614
+ await setProjectId(options.dir, outcome.projectId);
28615
+ throw new ProjectCreateError(
28616
+ "CREATE_FAILED",
28617
+ "The project was created but hosting is still being prepared.",
28618
+ `The folder is linked (project.id ${outcome.projectId}). Wait a few minutes, then run \`apitogo deploy\`.`
28619
+ );
28620
+ }
28621
+ await setProjectId(options.dir, outcome.projectId);
28622
+ return {
28623
+ projectId: outcome.projectId,
28624
+ projectName: name,
28625
+ modules,
28626
+ resumed: outcome.resumed === true
28627
+ };
28628
+ }
28629
+ function parse3(raw) {
28630
+ try {
28631
+ return JSON.parse(raw);
28632
+ } catch {
28633
+ return { status: "failed", message: "unreadable answer from the platform" };
28634
+ }
28635
+ }
28636
+
27409
28637
  // src/cli/project/create-handler.ts
27410
28638
  function resultLooksFailed2(payload) {
27411
28639
  try {
@@ -27415,7 +28643,71 @@ function resultLooksFailed2(payload) {
27415
28643
  return true;
27416
28644
  }
27417
28645
  }
28646
+ async function createLinkedProject(argv, dir) {
28647
+ const json = argv.json === true;
28648
+ try {
28649
+ const config2 = loadConfig();
28650
+ const client = new PlatformApiClient(config2);
28651
+ const progress = createConsoleProgressReporter();
28652
+ const result = await createProjectV2(
28653
+ {
28654
+ dir,
28655
+ name: argv.name,
28656
+ description: argv.description,
28657
+ wait: argv.wait !== false
28658
+ },
28659
+ {
28660
+ bootstrap: (args) => handleBootstrapProject(
28661
+ client,
28662
+ config2,
28663
+ {
28664
+ name: args.name,
28665
+ description: args.description,
28666
+ modules: args.modules,
28667
+ selectFreePlan: true,
28668
+ wait: args.wait,
28669
+ configureGatewayBaseline: false
28670
+ },
28671
+ json ? void 0 : progress
28672
+ )
28673
+ }
28674
+ );
28675
+ progress.stop();
28676
+ if (json) {
28677
+ console.log(JSON.stringify({ status: "succeeded", ...result }, null, 2));
28678
+ return;
28679
+ }
28680
+ printDiagnosticsToConsole(
28681
+ `Project ${result.projectName} (${result.projectId}) is ready on the free plan with: ${result.modules.join(", ")}.`
28682
+ );
28683
+ printResultToConsole(
28684
+ "The folder is linked. Run `apitogo deploy` to publish."
28685
+ );
28686
+ } catch (error) {
28687
+ const message = error instanceof Error ? error.message : String(error);
28688
+ const hint = error instanceof ApitogoError ? error.developerHint : void 0;
28689
+ if (json) {
28690
+ console.log(
28691
+ JSON.stringify({
28692
+ status: "failed",
28693
+ error: message,
28694
+ hint: hint ?? null
28695
+ })
28696
+ );
28697
+ process.exitCode = 1;
28698
+ return;
28699
+ }
28700
+ await printCriticalFailureToConsoleAndExit(
28701
+ hint ? `${message} ${hint}` : message
28702
+ );
28703
+ }
28704
+ }
27418
28705
  async function createProject(argv) {
28706
+ const dir = path89.resolve(process.cwd(), argv.dir ?? ".");
28707
+ if (await isConfigV2(dir)) {
28708
+ await createLinkedProject(argv, dir);
28709
+ return;
28710
+ }
27419
28711
  const name = argv.name?.trim();
27420
28712
  if (!name) {
27421
28713
  await printCriticalFailureToConsoleAndExit(
@@ -27466,12 +28758,17 @@ async function createProject(argv) {
27466
28758
 
27467
28759
  // src/cli/cmds/project-create.ts
27468
28760
  var project_create_default = {
27469
- desc: "Create an API ToGo platform project (plan unselected)",
28761
+ desc: "Create an API ToGo platform project. In a config version 2 folder: free plan, hosting for the enabled services, and project.id written into the config",
27470
28762
  command: "project create",
27471
28763
  builder: (yargs2) => yargs2.option("name", {
27472
28764
  type: "string",
27473
- describe: "Project display name",
27474
- demandOption: true
28765
+ describe: "Project display name (required for version 1; version 2 defaults to project.name)"
28766
+ }).option("dir", {
28767
+ type: "string",
28768
+ describe: "Project directory",
28769
+ default: ".",
28770
+ normalize: true,
28771
+ hidden: true
27475
28772
  }).option("description", {
27476
28773
  type: "string",
27477
28774
  describe: "Optional project description"
@@ -27530,14 +28827,14 @@ var remove_default = {
27530
28827
 
27531
28828
  // src/cli/dev/handler.ts
27532
28829
  init_joinUrl();
27533
- import path89 from "node:path";
28830
+ import path92 from "node:path";
27534
28831
 
27535
28832
  // src/vite/dev-server.ts
27536
28833
  init_logger();
27537
28834
  import fs12 from "node:fs/promises";
27538
28835
  import http from "node:http";
27539
28836
  import https from "node:https";
27540
- import path88 from "node:path";
28837
+ import path91 from "node:path";
27541
28838
  import { createHttpTerminator } from "http-terminator";
27542
28839
  import {
27543
28840
  createServer as createViteServer,
@@ -27570,7 +28867,7 @@ init_loader();
27570
28867
  // src/vite/pagefind-dev-index.ts
27571
28868
  init_invariant();
27572
28869
  init_joinUrl();
27573
- import path87 from "node:path";
28870
+ import path90 from "node:path";
27574
28871
  import { createIndex as createIndex2 } from "pagefind";
27575
28872
  import { isRunnableDevEnvironment } from "vite";
27576
28873
  async function* buildPagefindDevIndex(vite, config2) {
@@ -27623,7 +28920,7 @@ async function* buildPagefindDevIndex(vite, config2) {
27623
28920
  path: urlPath
27624
28921
  };
27625
28922
  }
27626
- const outputPath = path87.join(vite.config.publicDir, "pagefind");
28923
+ const outputPath = path90.join(vite.config.publicDir, "pagefind");
27627
28924
  await pagefindIndex.writeFiles({ outputPath });
27628
28925
  yield { type: "complete", success: true, indexed };
27629
28926
  }
@@ -27642,9 +28939,9 @@ var DevServer = class {
27642
28939
  this.protocol = "https";
27643
28940
  const { dir } = this.options;
27644
28941
  const [key, cert, ca] = await Promise.all([
27645
- fs12.readFile(path88.resolve(dir, config2.https.key)),
27646
- fs12.readFile(path88.resolve(dir, config2.https.cert)),
27647
- config2.https.ca ? fs12.readFile(path88.resolve(dir, config2.https.ca)) : void 0
28942
+ fs12.readFile(path91.resolve(dir, config2.https.key)),
28943
+ fs12.readFile(path91.resolve(dir, config2.https.cert)),
28944
+ config2.https.ca ? fs12.readFile(path91.resolve(dir, config2.https.ca)) : void 0
27648
28945
  ]);
27649
28946
  return https.createServer({ key, cert, ca });
27650
28947
  }
@@ -27661,7 +28958,7 @@ var DevServer = class {
27661
28958
  );
27662
28959
  const server = await this.createNodeServer(config2);
27663
28960
  if (config2.search?.type === "pagefind" && viteConfig.publicDir !== false) {
27664
- const publicRoot = path88.resolve(
28961
+ const publicRoot = path91.resolve(
27665
28962
  this.options.dir,
27666
28963
  typeof viteConfig.publicDir === "string" ? viteConfig.publicDir : "public"
27667
28964
  );
@@ -27678,7 +28975,7 @@ var DevServer = class {
27678
28975
  // built-in transform middleware which would treat the path as a static asset.
27679
28976
  name: "apitogo:entry-client",
27680
28977
  configureServer(server2) {
27681
- const entryPath = path88.posix.join(
28978
+ const entryPath = path91.posix.join(
27682
28979
  server2.config.base,
27683
28980
  "/__z/entry.client.tsx"
27684
28981
  );
@@ -27843,7 +29140,7 @@ init_package_json();
27843
29140
  async function dev(argv) {
27844
29141
  const packageJson3 = getZudokuPackageJson();
27845
29142
  process.env.NODE_ENV = "development";
27846
- let dir = path89.resolve(process.cwd(), argv.dir);
29143
+ let dir = path92.resolve(process.cwd(), argv.dir);
27847
29144
  if (await isConfigV2(dir)) {
27848
29145
  const { root, prepared } = await preparePortalWorkspace(dir, "development");
27849
29146
  dir = root;
@@ -27942,7 +29239,7 @@ var run_default = {
27942
29239
  };
27943
29240
 
27944
29241
  // src/cli/secrets/handler.ts
27945
- import path90 from "node:path";
29242
+ import path93 from "node:path";
27946
29243
  init_invariant();
27947
29244
 
27948
29245
  // src/cli/secrets/input.ts
@@ -28007,7 +29304,7 @@ var SecretsError = class extends ApitogoError {
28007
29304
  async function resolveProjectId2(argv, deps) {
28008
29305
  const explicit = argv.projectId?.trim();
28009
29306
  if (explicit) return explicit;
28010
- const dir = path90.resolve(process.cwd(), argv.dir ?? ".");
29307
+ const dir = path93.resolve(process.cwd(), argv.dir ?? ".");
28011
29308
  if (await isConfigV2(dir)) {
28012
29309
  const id = loadConfigV2(dir).resolved.project.id;
28013
29310
  if (id) return id;
@@ -28167,12 +29464,12 @@ var secrets_default = {
28167
29464
  };
28168
29465
 
28169
29466
  // src/cli/cmds/status.ts
28170
- import path92 from "node:path";
29467
+ import path95 from "node:path";
28171
29468
 
28172
29469
  // src/cli/status/handler.ts
28173
- import path91 from "node:path";
29470
+ import path94 from "node:path";
28174
29471
  async function status(argv) {
28175
- const sourceDirectory = path91.resolve(process.cwd(), argv.dir);
29472
+ const sourceDirectory = path94.resolve(process.cwd(), argv.dir);
28176
29473
  let config2;
28177
29474
  try {
28178
29475
  config2 = loadConfig();
@@ -28270,7 +29567,7 @@ var status_default = {
28270
29567
  }),
28271
29568
  handler: async (argv) => {
28272
29569
  await captureEvent({ argv, event: "apitogo status" });
28273
- if (await isConfigV2(path92.resolve(process.cwd(), argv.dir))) {
29570
+ if (await isConfigV2(path95.resolve(process.cwd(), argv.dir))) {
28274
29571
  await statusOfProject(argv);
28275
29572
  return;
28276
29573
  }
@@ -28278,8 +29575,287 @@ var status_default = {
28278
29575
  }
28279
29576
  };
28280
29577
 
29578
+ // src/cli/cmds/teardown.ts
29579
+ import path98 from "node:path";
29580
+
29581
+ // src/cli/teardown/command.ts
29582
+ init_invariant();
29583
+ import path96 from "node:path";
29584
+ import { createInterface as createInterface7 } from "node:readline/promises";
29585
+
29586
+ // src/cli/teardown/v2.ts
29587
+ init_invariant();
29588
+ var TeardownError = class extends ApitogoError {
29589
+ constructor(code, message, developerHint) {
29590
+ super(message, { developerHint, title: "Cannot tear down" });
29591
+ this.code = code;
29592
+ this.name = "TeardownError";
29593
+ }
29594
+ };
29595
+ async function teardownV2(options, deps) {
29596
+ if (options.only) {
29597
+ if (options.only.trim() === "gateway")
29598
+ return teardownGateway(options, deps);
29599
+ throw new TeardownError(
29600
+ "PARTIAL_TEARDOWN_UNSUPPORTED",
29601
+ "The platform cannot release the hosting of one service on its own: it is released by deleting the project.",
29602
+ "Run `apitogo teardown` without --only to delete the whole project, or `apitogo teardown --only gateway` to remove just the gateway. Your local files are kept."
29603
+ );
29604
+ }
29605
+ const { resolved } = loadConfigV2(options.dir);
29606
+ const projectId = options.projectId?.trim() || resolved.project.id || deps.envProjectId?.trim() || "";
29607
+ if (!projectId) {
29608
+ throw new TeardownError(
29609
+ "PROJECT_REQUIRED",
29610
+ "This folder is not linked to a project, so there is nothing to tear down.",
29611
+ "Pass --project-id <id> to name the project."
29612
+ );
29613
+ }
29614
+ const project = await deps.client.getProject(projectId);
29615
+ const name = project.name;
29616
+ if (options.yes !== true) {
29617
+ if (!deps.isTerminal) {
29618
+ throw new TeardownError(
29619
+ "CONFIRMATION_REQUIRED",
29620
+ "This is not an interactive terminal.",
29621
+ "Pass --yes to delete the project without asking."
29622
+ );
29623
+ }
29624
+ const typed = await deps.askName(
29625
+ `This deletes the project "${name}" (${projectId}) and its hosted site from the platform. Your local files are kept.
29626
+ Type the project name to confirm: `
29627
+ );
29628
+ if (typed.trim() !== name) {
29629
+ throw new TeardownError(
29630
+ "CANCELLED",
29631
+ "The name did not match. Nothing was deleted."
29632
+ );
29633
+ }
29634
+ }
29635
+ const raw = await deps.deleteProject(projectId, options.wait !== false);
29636
+ const outcome = parseOutcome(raw);
29637
+ if (outcome.status === "failed" || outcome.status === "blocked") {
29638
+ throw new TeardownError(
29639
+ "DELETE_FAILED",
29640
+ `The platform could not delete the project: ${outcome.message ?? outcome.status}.`,
29641
+ "Nothing local was changed. Try again, or contact support with the project id."
29642
+ );
29643
+ }
29644
+ if (outcome.status !== "succeeded") {
29645
+ return {
29646
+ projectId,
29647
+ projectName: name,
29648
+ status: "pending",
29649
+ cleared: [],
29650
+ message: "Deletion is queued and runs in the background. Local records are kept until it finishes: run `apitogo teardown` again later to clear them."
29651
+ };
29652
+ }
29653
+ const linked = await isLinkedTo(options.dir, projectId);
29654
+ const cleared = linked ? await clearLocalRecords(options.dir, projectId) : [];
29655
+ return {
29656
+ projectId,
29657
+ projectName: name,
29658
+ status: "removed",
29659
+ cleared,
29660
+ message: linked ? "The project was deleted. Your local files are kept." : "The project was deleted. This folder is linked to a different project, so its local records were left as they are."
29661
+ };
29662
+ }
29663
+ function parseOutcome(raw) {
29664
+ try {
29665
+ const parsed = JSON.parse(raw);
29666
+ return { status: parsed.status ?? "unknown", message: parsed.message };
29667
+ } catch {
29668
+ return { status: "failed", message: "unreadable answer from the platform" };
29669
+ }
29670
+ }
29671
+ async function isLinkedTo(dir, projectId) {
29672
+ const { resolved } = loadConfigV2(dir);
29673
+ if (resolved.project.id === projectId) return true;
29674
+ return (await readManifest(resolved.state))?.project.id === projectId;
29675
+ }
29676
+ async function clearLocalRecords(dir, projectId) {
29677
+ const { resolved } = loadConfigV2(dir);
29678
+ const cleared = [];
29679
+ const ledger = await readLedger(resolved.state);
29680
+ const recorded = DEPLOY_ORDER.filter((service) => ledger.services[service]);
29681
+ if (recorded.length > 0) {
29682
+ await clearServices(resolved.state, recorded);
29683
+ cleared.push("state.json entries");
29684
+ }
29685
+ await removeServicesFromManifest(resolved.state, [...DEPLOY_ORDER]);
29686
+ cleared.push("services.json");
29687
+ if (await clearProjectId(dir, projectId))
29688
+ cleared.push("project.id in the config");
29689
+ return cleared;
29690
+ }
29691
+ async function teardownGateway(options, deps) {
29692
+ const { resolved } = loadConfigV2(options.dir);
29693
+ const projectId = options.projectId?.trim() || resolved.project.id || deps.envProjectId?.trim() || "";
29694
+ if (!projectId) {
29695
+ throw new TeardownError(
29696
+ "PROJECT_REQUIRED",
29697
+ "This folder is not linked to a project, so there is no gateway to remove.",
29698
+ "Pass --project-id <id> to name the project."
29699
+ );
29700
+ }
29701
+ if (!deps.client.deleteGateway || !deps.client.getGatewayReconciliation)
29702
+ throw new Error("The platform client has no gateway calls.");
29703
+ const project = await deps.client.getProject(projectId);
29704
+ const name = project.name;
29705
+ if (options.yes !== true) {
29706
+ if (!deps.isTerminal) {
29707
+ throw new TeardownError(
29708
+ "CONFIRMATION_REQUIRED",
29709
+ "This is not an interactive terminal.",
29710
+ "Pass --yes to remove the gateway without asking."
29711
+ );
29712
+ }
29713
+ const typed = await deps.askName(
29714
+ `This removes the gateway of project "${name}" (${projectId}). Its API keys are revoked and stop working, and the gateway address is released. The project and the portal stay.
29715
+ Type the project name to confirm: `
29716
+ );
29717
+ if (typed.trim() !== name) {
29718
+ throw new TeardownError(
29719
+ "CANCELLED",
29720
+ "The name did not match. Nothing was removed."
29721
+ );
29722
+ }
29723
+ }
29724
+ let ticket;
29725
+ try {
29726
+ ticket = await deps.client.deleteGateway(projectId);
29727
+ } catch (error) {
29728
+ if (error instanceof PlatformApiError && error.status === 404)
29729
+ throw new TeardownError(
29730
+ "GATEWAY_NOT_DEPLOYED",
29731
+ "The platform has no gateway for this project.",
29732
+ "There is nothing to remove. Clear stale local records by deleting the gateway entries in the state folder."
29733
+ );
29734
+ throw error;
29735
+ }
29736
+ const sleep5 = deps.gateway?.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
29737
+ const deadline = Date.now() + (options.wait === false ? 0 : deps.gateway?.timeoutMs ?? 12e4);
29738
+ for (; ; ) {
29739
+ const state = await deps.client.getGatewayReconciliation(
29740
+ projectId,
29741
+ ticket.reconciliationId
29742
+ );
29743
+ if (state.state === "Succeeded") break;
29744
+ if (state.state === "Failed" || state.state === "Superseded") {
29745
+ throw new TeardownError(
29746
+ "DELETE_FAILED",
29747
+ `The platform could not remove the gateway: ${[state.errorCode, state.errorMessage].filter(Boolean).join(" ") || state.state}.`,
29748
+ "Nothing local was changed. Try again, or contact support with the project id."
29749
+ );
29750
+ }
29751
+ if (Date.now() >= deadline) {
29752
+ return {
29753
+ projectId,
29754
+ projectName: name,
29755
+ status: "pending",
29756
+ cleared: [],
29757
+ message: "The gateway is queued for removal and the platform finishes it in the background. Local records are kept until then: run `apitogo teardown --only gateway` again later to clear them."
29758
+ };
29759
+ }
29760
+ await sleep5(deps.gateway?.pollIntervalMs ?? 2e3);
29761
+ }
29762
+ const cleared = [];
29763
+ const ledger = await readLedger(resolved.state);
29764
+ if (ledger.services.gateway) {
29765
+ await clearServices(resolved.state, ["gateway"]);
29766
+ cleared.push("state.json entries of the gateway");
29767
+ }
29768
+ const manifest = await readManifest(resolved.state);
29769
+ if (manifest?.services.gateway) {
29770
+ await removeServicesFromManifest(resolved.state, ["gateway"]);
29771
+ cleared.push("the gateway entry of services.json");
29772
+ }
29773
+ return {
29774
+ projectId,
29775
+ projectName: name,
29776
+ status: "removed",
29777
+ cleared,
29778
+ message: "The gateway was removed. The project and your local files are kept."
29779
+ };
29780
+ }
29781
+
29782
+ // src/cli/teardown/command.ts
29783
+ async function ask3(prompt) {
29784
+ const rl = createInterface7({ input: process.stdin, output: process.stdout });
29785
+ try {
29786
+ return await rl.question(prompt);
29787
+ } finally {
29788
+ rl.close();
29789
+ }
29790
+ }
29791
+ async function teardownProject(argv) {
29792
+ const json = argv.json === true;
29793
+ try {
29794
+ const config2 = loadConfig();
29795
+ const client = new PlatformApiClient(config2);
29796
+ const progress = createConsoleProgressReporter();
29797
+ const result = await teardownV2(
29798
+ {
29799
+ dir: path96.resolve(process.cwd(), argv.dir),
29800
+ projectId: argv.projectId,
29801
+ only: Array.isArray(argv.only) ? argv.only.join(",") : argv.only,
29802
+ yes: argv.yes === true,
29803
+ wait: argv.wait !== false
29804
+ },
29805
+ {
29806
+ client,
29807
+ envProjectId: config2.defaultProjectId,
29808
+ isTerminal: process.stdin.isTTY === true && !json,
29809
+ askName: ask3,
29810
+ deleteProject: (projectId, wait) => handleDeleteProject(
29811
+ client,
29812
+ config2,
29813
+ { projectId, wait },
29814
+ json ? void 0 : progress
29815
+ )
29816
+ }
29817
+ );
29818
+ progress.stop();
29819
+ if (json) {
29820
+ console.log(
29821
+ JSON.stringify(
29822
+ { ...result, outcome: result.status, status: "succeeded" },
29823
+ null,
29824
+ 2
29825
+ )
29826
+ );
29827
+ return;
29828
+ }
29829
+ printDiagnosticsToConsole(
29830
+ `${result.status === "removed" ? "Deleted" : "Queued deletion of"} project ${result.projectName} (${result.projectId}).`
29831
+ );
29832
+ if (result.cleared.length > 0)
29833
+ printDiagnosticsToConsole(
29834
+ `Cleared locally: ${result.cleared.join(", ")}.`
29835
+ );
29836
+ printResultToConsole(result.message);
29837
+ } catch (error) {
29838
+ const message = error instanceof Error ? error.message : String(error);
29839
+ const hint = error instanceof ApitogoError ? error.developerHint : void 0;
29840
+ if (json) {
29841
+ console.log(
29842
+ JSON.stringify({
29843
+ status: "failed",
29844
+ error: message,
29845
+ hint: hint ?? null
29846
+ })
29847
+ );
29848
+ process.exitCode = 1;
29849
+ return;
29850
+ }
29851
+ await printCriticalFailureToConsoleAndExit(
29852
+ hint ? `${message} ${hint}` : message
29853
+ );
29854
+ }
29855
+ }
29856
+
28281
29857
  // src/cli/teardown/handler.ts
28282
- import path93 from "node:path";
29858
+ import path97 from "node:path";
28283
29859
  function resultLooksFailed3(payload) {
28284
29860
  try {
28285
29861
  const parsed = JSON.parse(payload);
@@ -28295,7 +29871,7 @@ async function teardown(argv) {
28295
29871
  );
28296
29872
  return;
28297
29873
  }
28298
- const sourceDirectory = path93.resolve(process.cwd(), argv.dir);
29874
+ const sourceDirectory = path97.resolve(process.cwd(), argv.dir);
28299
29875
  let config2;
28300
29876
  try {
28301
29877
  config2 = loadConfig();
@@ -28344,7 +29920,7 @@ async function teardown(argv) {
28344
29920
 
28345
29921
  // src/cli/cmds/teardown.ts
28346
29922
  var teardown_default = {
28347
- desc: "Delete the linked API ToGo project",
29923
+ desc: "Delete the linked API ToGo project. Config version 2 asks you to type the project name; local files are kept",
28348
29924
  command: "teardown",
28349
29925
  builder: (yargs2) => yargs2.option("dir", {
28350
29926
  type: "string",
@@ -28354,10 +29930,13 @@ var teardown_default = {
28354
29930
  }).option("project-id", {
28355
29931
  type: "string",
28356
29932
  describe: "Override project GUID"
29933
+ }).option("only", {
29934
+ type: "string",
29935
+ describe: "Config version 2: only `gateway` is supported (removes the gateway, keeps the project); other hosting is released only by deleting the whole project"
28357
29936
  }).option("yes", {
28358
29937
  alias: "y",
28359
29938
  type: "boolean",
28360
- describe: "Confirm teardown (required)",
29939
+ describe: "Confirm without asking (version 1: required)",
28361
29940
  default: false
28362
29941
  }).option("wait", {
28363
29942
  type: "boolean",
@@ -28370,6 +29949,10 @@ var teardown_default = {
28370
29949
  }),
28371
29950
  handler: async (argv) => {
28372
29951
  await captureEvent({ argv, event: "apitogo teardown" });
29952
+ if (await isConfigV2(path98.resolve(process.cwd(), argv.dir))) {
29953
+ await teardownProject(argv);
29954
+ return;
29955
+ }
28373
29956
  await teardown(argv);
28374
29957
  }
28375
29958
  };
@@ -28438,42 +30021,42 @@ To fix, run:
28438
30021
  var packageJson = getZudokuPackageJson();
28439
30022
  var command = (module) => module;
28440
30023
  function createApitogoCli(argv) {
28441
- return yargs(argv).scriptName("apitogo").middleware(warnPackageVersionMismatch).command(command(init_default)).command(command(add_default)).command(command(remove_default)).command(command(config_default)).command(command(run_default)).command(command(build_default)).command(command(check_default)).command(command(plan_default)).command(command(secrets_default)).command(command(project_create_default)).command(command(deploy_default)).command(command(status_default)).command(command(teardown_default)).command(command(login_default)).command(command(logout_default)).demandCommand().strictCommands().version(packageJson?.version).fail(false).help();
30024
+ return yargs(argv).scriptName("apitogo").middleware(warnPackageVersionMismatch).command(command(init_default)).command(command(add_default)).command(command(remove_default)).command(command(config_default)).command(command(run_default)).command(command(build_default)).command(command(check_default)).command(command(plan_default)).command(command(secrets_default)).command(command(project_create_default)).command(command(deploy_default)).command(command(export_default)).command(command(status_default)).command(command(teardown_default)).command(command(login_default)).command(command(logout_default)).demandCommand().strictCommands().version(packageJson?.version).fail(false).help();
28442
30025
  }
28443
30026
 
28444
30027
  // src/cli/local-handoff.ts
28445
- import { spawn as spawn4 } from "node:child_process";
28446
- import { existsSync as existsSync7, realpathSync } from "node:fs";
28447
- import path94 from "node:path";
30028
+ import { spawn as spawn5 } from "node:child_process";
30029
+ import { existsSync as existsSync8, realpathSync } from "node:fs";
30030
+ import path99 from "node:path";
28448
30031
  var LOCAL_HANDOFF_ENV = "APITOGO_LOCAL_CLI";
28449
30032
  var PACKAGE_NAME = "@lukoweb/apitogo";
28450
30033
  var resolveProjectDir = (argv, cwd) => {
28451
30034
  for (let i = 0; i < argv.length; i++) {
28452
30035
  const arg = argv[i];
28453
30036
  const next = argv[i + 1];
28454
- if (arg === "--dir" && next) return path94.resolve(cwd, next);
30037
+ if (arg === "--dir" && next) return path99.resolve(cwd, next);
28455
30038
  if (arg?.startsWith("--dir=")) {
28456
- return path94.resolve(cwd, arg.slice("--dir=".length));
30039
+ return path99.resolve(cwd, arg.slice("--dir=".length));
28457
30040
  }
28458
30041
  }
28459
30042
  return cwd;
28460
30043
  };
28461
30044
  var findLocalCli = (projectDir, runningRoot) => {
28462
- let dir = path94.resolve(projectDir);
30045
+ let dir = path99.resolve(projectDir);
28463
30046
  let localPackageRoot;
28464
30047
  while (!localPackageRoot) {
28465
- const candidate = path94.join(dir, "node_modules", PACKAGE_NAME);
28466
- if (existsSync7(path94.join(candidate, "package.json"))) {
30048
+ const candidate = path99.join(dir, "node_modules", PACKAGE_NAME);
30049
+ if (existsSync8(path99.join(candidate, "package.json"))) {
28467
30050
  localPackageRoot = candidate;
28468
- } else if (dir === path94.dirname(dir)) {
30051
+ } else if (dir === path99.dirname(dir)) {
28469
30052
  return void 0;
28470
30053
  } else {
28471
- dir = path94.dirname(dir);
30054
+ dir = path99.dirname(dir);
28472
30055
  }
28473
30056
  }
28474
30057
  const localRoot = realpathSync(localPackageRoot);
28475
30058
  if (localRoot === realpathSync(runningRoot)) return void 0;
28476
- return path94.join(localRoot, "cli.js");
30059
+ return path99.join(localRoot, "cli.js");
28477
30060
  };
28478
30061
  var handoffToLocalCli = async (argv, runningRoot) => {
28479
30062
  if (process.env[LOCAL_HANDOFF_ENV] || process.env.ZUDOKU_ENV === "internal") {
@@ -28485,9 +30068,9 @@ var handoffToLocalCli = async (argv, runningRoot) => {
28485
30068
  );
28486
30069
  if (!localCli) return void 0;
28487
30070
  printDiagnosticsToConsole(
28488
- `Using the project's local ${PACKAGE_NAME} (${path94.dirname(localCli)})`
30071
+ `Using the project's local ${PACKAGE_NAME} (${path99.dirname(localCli)})`
28489
30072
  );
28490
- const child = spawn4(process.execPath, [localCli, ...argv], {
30073
+ const child = spawn5(process.execPath, [localCli, ...argv], {
28491
30074
  stdio: "inherit",
28492
30075
  env: { ...process.env, [LOCAL_HANDOFF_ENV]: "1" }
28493
30076
  });