@lukoweb/apitogo 0.2.47 → 0.2.49

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
@@ -837,7 +837,7 @@ import { stat as stat4 } from "node:fs/promises";
837
837
  var fileExists;
838
838
  var init_file_exists = __esm({
839
839
  "src/config/file-exists.ts"() {
840
- fileExists = (path95) => stat4(path95).then(() => true).catch(() => false);
840
+ fileExists = (path96) => stat4(path96).then(() => true).catch(() => false);
841
841
  }
842
842
  });
843
843
 
@@ -5836,6 +5836,36 @@ import yargs from "yargs/yargs";
5836
5836
  // src/cli/services/handler.ts
5837
5837
  import path52 from "node:path";
5838
5838
 
5839
+ // src/config/v2/billing.ts
5840
+ import { readFile as readFile2 } from "node:fs/promises";
5841
+ var DEFAULT_STRIPE_KEY_SECRET = "STRIPE_API_KEY";
5842
+ function isPaidPlan(plan2) {
5843
+ return plan2.pricingModel !== "custom" && typeof plan2.priceAmount === "number" && plan2.priceAmount > 0;
5844
+ }
5845
+ async function paidPlanSkus(resolved) {
5846
+ const file = resolved.plans?.file;
5847
+ if (!file) return [];
5848
+ try {
5849
+ const raw = JSON.parse(await readFile2(file, "utf8"));
5850
+ if (!Array.isArray(raw.plans)) return [];
5851
+ return raw.plans.filter(
5852
+ (plan2) => typeof plan2 === "object" && plan2 !== null && isPaidPlan(plan2)
5853
+ ).map((plan2) => String(plan2.sku ?? "?"));
5854
+ } catch {
5855
+ return [];
5856
+ }
5857
+ }
5858
+ async function billingNeeded(resolved) {
5859
+ const plans = resolved.plans;
5860
+ if (!plans?.enabled || plans.billing.provider !== "stripe") return false;
5861
+ return (await paidPlanSkus(resolved)).length > 0;
5862
+ }
5863
+ function stripeKeySecretName(resolved) {
5864
+ const ref = resolved.plans?.billing.apiKey;
5865
+ const match = ref ? /^secret:(.+)$/.exec(ref) : null;
5866
+ return match?.[1] ?? DEFAULT_STRIPE_KEY_SECRET;
5867
+ }
5868
+
5839
5869
  // src/config/v2/errors.ts
5840
5870
  init_invariant();
5841
5871
  function formatConfigIssue(issue4) {
@@ -6043,8 +6073,8 @@ var ConfigV2Schema = z.strictObject({
6043
6073
  var REMOVED_KEYS = {
6044
6074
  "plans.provider": "plans.provider was removed. Only plans.billing.provider (stripe or none) is selectable."
6045
6075
  };
6046
- function pathToString(path95) {
6047
- return path95.reduce(
6076
+ function pathToString(path96) {
6077
+ return path96.reduce(
6048
6078
  (acc, segment) => typeof segment === "number" ? `${acc}[${segment}]` : acc === "" ? String(segment) : `${acc}.${String(segment)}`,
6049
6079
  ""
6050
6080
  );
@@ -6064,9 +6094,9 @@ function step(schema2, segment) {
6064
6094
  }
6065
6095
  return void 0;
6066
6096
  }
6067
- function knownKeysAt(path95) {
6097
+ function knownKeysAt(path96) {
6068
6098
  let current = ConfigV2Schema;
6069
- for (const segment of path95) {
6099
+ for (const segment of path96) {
6070
6100
  if (!current) return [];
6071
6101
  current = step(current, segment);
6072
6102
  }
@@ -6102,7 +6132,7 @@ function unknownKeyHint(fullPath, key, known) {
6102
6132
  return known.length > 0 ? `Allowed keys: ${known.join(", ")}.` : void 0;
6103
6133
  }
6104
6134
  function mapIssue(issue4) {
6105
- const path95 = pathToString(issue4.path);
6135
+ const path96 = pathToString(issue4.path);
6106
6136
  switch (issue4.code) {
6107
6137
  case "unrecognized_keys":
6108
6138
  return issue4.keys.map((key) => {
@@ -6118,20 +6148,20 @@ function mapIssue(issue4) {
6118
6148
  return [
6119
6149
  {
6120
6150
  code: "CONFIG_INVALID_VALUE",
6121
- path: path95,
6151
+ path: path96,
6122
6152
  message: `must be one of: ${issue4.values.map((v) => JSON.stringify(v)).join(", ")}.`
6123
6153
  }
6124
6154
  ];
6125
6155
  case "custom": {
6126
6156
  const code = typeof issue4.params?.code === "string" ? issue4.params.code : "CONFIG_INVALID_VALUE";
6127
- return [{ code, path: path95, message: issue4.message ?? "is invalid." }];
6157
+ return [{ code, path: path96, message: issue4.message ?? "is invalid." }];
6128
6158
  }
6129
6159
  case "invalid_type":
6130
6160
  return [
6131
- /received undefined/.test(issue4.message) ? { code: "CONFIG_MISSING_FIELD", path: path95, message: "is required." } : { code: "CONFIG_INVALID_VALUE", path: path95, message: issue4.message }
6161
+ /received undefined/.test(issue4.message) ? { code: "CONFIG_MISSING_FIELD", path: path96, message: "is required." } : { code: "CONFIG_INVALID_VALUE", path: path96, message: issue4.message }
6132
6162
  ];
6133
6163
  default:
6134
- return [{ code: "CONFIG_INVALID_VALUE", path: path95, message: issue4.message }];
6164
+ return [{ code: "CONFIG_INVALID_VALUE", path: path96, message: issue4.message }];
6135
6165
  }
6136
6166
  }
6137
6167
  function isRecord(value) {
@@ -6495,7 +6525,8 @@ function resolveConfigV2(config2, configPath) {
6495
6525
  file: plans.file ? path7.resolve(configDir, plans.file) : path7.join(root, "plans.json"),
6496
6526
  billing: {
6497
6527
  provider: plans.billing?.provider ?? "stripe",
6498
- apiKey: plans.billing?.apiKey,
6528
+ // Stripe is the only provider. Its key lives in the STRIPE_API_KEY secret unless the config names another.
6529
+ apiKey: plans.billing?.apiKey ?? ((plans.billing?.provider ?? "stripe") === "stripe" ? "secret:STRIPE_API_KEY" : void 0),
6499
6530
  webhookSecret: plans.billing?.webhookSecret,
6500
6531
  successUrl: plans.billing?.successUrl,
6501
6532
  cancelUrl: plans.billing?.cancelUrl
@@ -6575,7 +6606,7 @@ function loadConfigV2(pathOrDir) {
6575
6606
  }
6576
6607
 
6577
6608
  // src/config/v2/rules.ts
6578
- import { readdir as readdir2, readFile as readFile2, stat as stat2 } from "node:fs/promises";
6609
+ import { readdir as readdir2, readFile as readFile3, stat as stat2 } from "node:fs/promises";
6579
6610
  import path9 from "node:path";
6580
6611
  import { glob } from "glob";
6581
6612
  import { parse as parseYaml } from "yaml";
@@ -6727,7 +6758,7 @@ async function portalRules(config2, resolved, out) {
6727
6758
  }
6728
6759
  if (await isFile(portal.theme)) {
6729
6760
  try {
6730
- JSON.parse(await readFile2(portal.theme, "utf8"));
6761
+ JSON.parse(await readFile3(portal.theme, "utf8"));
6731
6762
  } catch (error) {
6732
6763
  out.error({
6733
6764
  code: "PORTAL_THEME_INVALID",
@@ -6924,7 +6955,7 @@ async function specRule(spec, index, resolved, out) {
6924
6955
  }
6925
6956
  let document;
6926
6957
  try {
6927
- const text = await readFile2(spec, "utf8");
6958
+ const text = await readFile3(spec, "utf8");
6928
6959
  document = spec.toLowerCase().endsWith(".json") ? JSON.parse(text) : parseYaml(text);
6929
6960
  } catch (error) {
6930
6961
  out.error({
@@ -7066,7 +7097,7 @@ function identityRules(config2, resolved, out) {
7066
7097
  }
7067
7098
  }
7068
7099
  }
7069
- async function plansRules(config2, resolved, out) {
7100
+ async function plansRules(resolved, out, paid) {
7070
7101
  const plans = resolved.plans;
7071
7102
  if (!plans?.enabled) return;
7072
7103
  if (!await isFile(plans.file)) {
@@ -7078,7 +7109,7 @@ async function plansRules(config2, resolved, out) {
7078
7109
  });
7079
7110
  } else {
7080
7111
  try {
7081
- JSON.parse(await readFile2(plans.file, "utf8"));
7112
+ JSON.parse(await readFile3(plans.file, "utf8"));
7082
7113
  } catch (error) {
7083
7114
  out.error({
7084
7115
  code: "PLANS_FILE_INVALID",
@@ -7087,15 +7118,7 @@ async function plansRules(config2, resolved, out) {
7087
7118
  });
7088
7119
  }
7089
7120
  }
7090
- if (plans.billing.provider === "stripe") {
7091
- if (!config2.plans?.billing?.apiKey) {
7092
- out.error({
7093
- code: "PLANS_BILLING_INCOMPLETE",
7094
- path: "plans.billing.apiKey",
7095
- message: "plans.billing.apiKey is required when billing.provider is stripe.",
7096
- hint: 'Set it to a secret reference, for example "secret:STRIPE_API_KEY".'
7097
- });
7098
- }
7121
+ if (paid) {
7099
7122
  const headless = !(resolved.portal?.enabled === true);
7100
7123
  for (const field of ["successUrl", "cancelUrl"]) {
7101
7124
  if (headless && !plans.billing[field]) {
@@ -7109,8 +7132,12 @@ async function plansRules(config2, resolved, out) {
7109
7132
  }
7110
7133
  }
7111
7134
  }
7112
- function secretRules(config2, context, out, skipped) {
7113
- const refs = collectSecretRefs(config2);
7135
+ function secretRules(config2, resolved, context, out, skipped, paid) {
7136
+ const stripeKeyPath = "plans.billing.apiKey";
7137
+ const named = collectSecretRefs(config2).filter(
7138
+ (ref) => paid || !ref.path.startsWith("plans.billing")
7139
+ );
7140
+ const refs = paid && !named.some((ref) => ref.path === stripeKeyPath) ? [...named, { name: stripeKeySecretName(resolved), path: stripeKeyPath }] : named;
7114
7141
  if (refs.length === 0) return;
7115
7142
  if (!context.knownSecrets) {
7116
7143
  skipped.push(
@@ -7119,7 +7146,14 @@ function secretRules(config2, context, out, skipped) {
7119
7146
  return;
7120
7147
  }
7121
7148
  for (const ref of refs) {
7122
- if (!context.knownSecrets.has(ref.name)) {
7149
+ if (ref.path === stripeKeyPath && !context.knownSecrets.has(ref.name)) {
7150
+ out.warn({
7151
+ code: "SECRET_NOT_SET",
7152
+ path: ref.path,
7153
+ message: `a plan has a price, and the Stripe key (secret ${ref.name}) is not set yet.`,
7154
+ hint: `apitogo deploy asks for it. Or run: apitogo secrets set ${ref.name}`
7155
+ });
7156
+ } else if (!context.knownSecrets.has(ref.name)) {
7123
7157
  out.error({
7124
7158
  code: "SECRET_NOT_SET",
7125
7159
  path: ref.path,
@@ -7139,12 +7173,13 @@ async function checkConfigV2(config2, resolved, context = {}) {
7139
7173
  error: (issue4) => errors.push(issue4),
7140
7174
  warn: (issue4) => warnings.push(issue4)
7141
7175
  };
7176
+ const paid = await billingNeeded(resolved);
7142
7177
  dependencyRules(config2, resolved, out);
7143
- secretRules(config2, context, out, skipped);
7178
+ secretRules(config2, resolved, context, out, skipped, paid);
7144
7179
  await portalRules(config2, resolved, out);
7145
7180
  await gatewayRules(resolved, out);
7146
7181
  identityRules(config2, resolved, out);
7147
- await plansRules(config2, resolved, out);
7182
+ await plansRules(resolved, out, paid);
7148
7183
  return { errors, warnings, skipped };
7149
7184
  }
7150
7185
 
@@ -7190,7 +7225,7 @@ function portalProfile(resolved) {
7190
7225
  }
7191
7226
 
7192
7227
  // src/config/v2/manifest.ts
7193
- import { mkdir as mkdir3, readFile as readFile3, rename, rm, writeFile as writeFile4 } from "node:fs/promises";
7228
+ import { mkdir as mkdir3, readFile as readFile4, rename, rm, writeFile as writeFile4 } from "node:fs/promises";
7194
7229
  import path10 from "node:path";
7195
7230
  import { z as z3 } from "zod";
7196
7231
  var SUPPORTED_MANIFEST_SCHEMA = 1;
@@ -7240,7 +7275,7 @@ var ServiceManifestSchema = z3.looseObject({
7240
7275
  }).optional()
7241
7276
  }).default({})
7242
7277
  });
7243
- var issue = (code, path95, message, hint) => ({ code, path: path95, message, hint });
7278
+ var issue = (code, path96, message, hint) => ({ code, path: path96, message, hint });
7244
7279
  function parseManifest(raw, file = "services.json") {
7245
7280
  const version = typeof raw === "object" && raw !== null && "schemaVersion" in raw ? raw.schemaVersion : void 0;
7246
7281
  if (typeof version === "number" && version > SUPPORTED_MANIFEST_SCHEMA) {
@@ -7273,7 +7308,7 @@ async function readManifest(state) {
7273
7308
  const file = manifestPathFor(state);
7274
7309
  let text;
7275
7310
  try {
7276
- text = await readFile3(file, "utf8");
7311
+ text = await readFile4(file, "utf8");
7277
7312
  } catch (error) {
7278
7313
  if (error.code === "ENOENT") return void 0;
7279
7314
  throw error;
@@ -7488,7 +7523,7 @@ async function removeServicesFromManifest(state, names) {
7488
7523
  }
7489
7524
 
7490
7525
  // src/config/v2/gateway-reference.ts
7491
- import { readFile as readFile4 } from "node:fs/promises";
7526
+ import { readFile as readFile5 } from "node:fs/promises";
7492
7527
  import { parse } from "yaml";
7493
7528
  var DEFAULT_KEY_HEADER = "Ocp-Apim-Subscription-Key";
7494
7529
  var METHODS = [
@@ -7502,8 +7537,8 @@ var METHODS = [
7502
7537
  "trace"
7503
7538
  ];
7504
7539
  var isObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
7505
- function actionKey(method, path95) {
7506
- const trimmed = path95.trim();
7540
+ function actionKey(method, path96) {
7541
+ const trimmed = path96.trim();
7507
7542
  const withSlash = trimmed.startsWith("/") ? trimmed : `/${trimmed}`;
7508
7543
  const normalized = withSlash.length > 1 ? withSlash.replace(/\/+$/, "") || "/" : withSlash;
7509
7544
  return `${method.trim().toLowerCase()}:${normalized}`;
@@ -7540,8 +7575,8 @@ var ReferenceSpecError = class extends Error {
7540
7575
  };
7541
7576
  function operationsOf(paths) {
7542
7577
  return Object.entries(paths).flatMap(
7543
- ([path95, item]) => isObject(item) ? METHODS.filter((method) => isObject(item[method])).map(
7544
- (method) => [path95, item, method]
7578
+ ([path96, item]) => isObject(item) ? METHODS.filter((method) => isObject(item[method])).map(
7579
+ (method) => [path96, item, method]
7545
7580
  ) : []
7546
7581
  );
7547
7582
  }
@@ -7556,14 +7591,14 @@ function buildReferenceSpec(specText, options) {
7556
7591
  throw new ReferenceSpecError("The OpenAPI file could not be read.");
7557
7592
  const doc = structuredClone(root);
7558
7593
  const paths = isObject(doc.paths) ? doc.paths : {};
7559
- for (const [path95, item, method] of operationsOf(paths))
7560
- if (options.deniedOperations.has(actionKey(method, path95)))
7594
+ for (const [path96, item, method] of operationsOf(paths))
7595
+ if (options.deniedOperations.has(actionKey(method, path96)))
7561
7596
  delete item[method];
7562
- for (const [path95, item] of Object.entries(paths))
7597
+ for (const [path96, item] of Object.entries(paths))
7563
7598
  if (!isObject(item) || !METHODS.some((method) => method in item))
7564
- delete paths[path95];
7565
- for (const [path95, item, method] of operationsOf(paths))
7566
- if (options.publicOperations.has(actionKey(method, path95)))
7599
+ delete paths[path96];
7600
+ for (const [path96, item, method] of operationsOf(paths))
7601
+ if (options.publicOperations.has(actionKey(method, path96)))
7567
7602
  item[method].security = [];
7568
7603
  doc.servers = [{ url: options.gatewayBaseUrl.replace(/\/+$/, "") }];
7569
7604
  for (const item of Object.values(paths)) {
@@ -7626,7 +7661,7 @@ async function renderReferenceSpec(gateway, gatewayBaseUrl, keyHeaderName) {
7626
7661
  throw new ReferenceSpecError("gateway.specs lists no OpenAPI file.");
7627
7662
  let text;
7628
7663
  try {
7629
- text = await readFile4(file, "utf8");
7664
+ text = await readFile5(file, "utf8");
7630
7665
  } catch {
7631
7666
  throw new ReferenceSpecError(`Cannot read the OpenAPI file ${file}.`);
7632
7667
  }
@@ -7697,7 +7732,7 @@ function stableJson(value) {
7697
7732
  }
7698
7733
 
7699
7734
  // src/config/v2/wrapper.ts
7700
- import { mkdir as mkdir4, readFile as readFile6, rename as rename2, rm as rm2, writeFile as writeFile5 } from "node:fs/promises";
7735
+ import { mkdir as mkdir4, readFile as readFile7, rename as rename2, rm as rm2, writeFile as writeFile5 } from "node:fs/promises";
7701
7736
  import path12 from "node:path";
7702
7737
 
7703
7738
  // src/config/v2/docs-navigation.ts
@@ -7759,7 +7794,7 @@ import { glob as glob2 } from "glob";
7759
7794
  import globParent from "glob-parent";
7760
7795
 
7761
7796
  // src/lib/util/readFrontmatter.ts
7762
- import { readFile as readFile5 } from "node:fs/promises";
7797
+ import { readFile as readFile6 } from "node:fs/promises";
7763
7798
  import matter from "gray-matter";
7764
7799
  import { parse as parse2, stringify } from "yaml";
7765
7800
  var yaml = {
@@ -7767,7 +7802,7 @@ var yaml = {
7767
7802
  stringify: (obj) => stringify(obj)
7768
7803
  };
7769
7804
  var readFrontmatter = async (filePath) => {
7770
- const content = await readFile5(filePath, "utf-8");
7805
+ const content = await readFile6(filePath, "utf-8");
7771
7806
  const normalizedContent = content.replace(/\r\n/g, "\n");
7772
7807
  return matter(normalizedContent, { engines: { yaml } });
7773
7808
  };
@@ -8103,7 +8138,7 @@ function renderWrapper(plan2) {
8103
8138
  async function readDemoPlans(file) {
8104
8139
  if (!file) return void 0;
8105
8140
  try {
8106
- const parsed = JSON.parse(await readFile6(file, "utf8"));
8141
+ const parsed = JSON.parse(await readFile7(file, "utf8"));
8107
8142
  return Array.isArray(parsed.plans) ? parsed.plans : void 0;
8108
8143
  } catch {
8109
8144
  return void 0;
@@ -8113,7 +8148,7 @@ async function readTheme(resolved) {
8113
8148
  const file = resolved.portal?.theme;
8114
8149
  if (!file) return void 0;
8115
8150
  try {
8116
- return JSON.parse(await readFile6(file, "utf8"));
8151
+ return JSON.parse(await readFile7(file, "utf8"));
8117
8152
  } catch {
8118
8153
  return void 0;
8119
8154
  }
@@ -8249,7 +8284,7 @@ async function preparePortal(resolved, manifest, mode, options = {}) {
8249
8284
 
8250
8285
  // src/config/v2/workspace.ts
8251
8286
  import { createHash as createHash2 } from "node:crypto";
8252
- import { access as access2, lstat, readFile as readFile7, rm as rm3, writeFile as writeFile6 } from "node:fs/promises";
8287
+ import { access as access2, lstat, readFile as readFile8, rm as rm3, writeFile as writeFile6 } from "node:fs/promises";
8253
8288
  import path13 from "node:path";
8254
8289
  import { glob as glob3 } from "glob";
8255
8290
 
@@ -8285,7 +8320,7 @@ async function relink(target, link) {
8285
8320
  var GENERATED_PACKAGE_JSON = '{\n "private": true,\n "type": "module"\n}\n';
8286
8321
  async function writeGeneratedPackageJson(root) {
8287
8322
  const file = path13.join(root, "package.json");
8288
- const current = await readFile7(file, "utf8").catch(() => void 0);
8323
+ const current = await readFile8(file, "utf8").catch(() => void 0);
8289
8324
  if (current !== GENERATED_PACKAGE_JSON)
8290
8325
  await writeFile6(file, GENERATED_PACKAGE_JSON, "utf8");
8291
8326
  }
@@ -8304,7 +8339,7 @@ async function materializeWorkspace(resolved, prepared) {
8304
8339
  return root;
8305
8340
  }
8306
8341
  async function configFingerprint(configPath) {
8307
- const text = await readFile7(configPath, "utf8");
8342
+ const text = await readFile8(configPath, "utf8");
8308
8343
  try {
8309
8344
  const raw = JSON.parse(text);
8310
8345
  if (raw.project) {
@@ -8353,7 +8388,7 @@ async function portalArtifactHash(resolved, manifestText, cliVersion, wrapper =
8353
8388
  for (const file of files) {
8354
8389
  hash2.update(`file:${path13.relative(resolved.configDir, file)}
8355
8390
  `);
8356
- hash2.update(await readFile7(file));
8391
+ hash2.update(await readFile8(file));
8357
8392
  }
8358
8393
  return hash2.digest("hex");
8359
8394
  }
@@ -9163,13 +9198,6 @@ async function buildPlans(context) {
9163
9198
  const { options, dir, resolved } = context;
9164
9199
  const o = options.plans ?? {};
9165
9200
  const template = o.template ?? (options.template === "blank" ? "none" : isFilled(options.template) ? "starter" : "free");
9166
- const billing = o.billing ?? (template === "tiered" ? "stripe" : "none");
9167
- if (template === "tiered" && billing === "none") {
9168
- throw new ServiceEditError(
9169
- "The tiered template has paid plans, which need a billing provider.",
9170
- "Use --billing stripe, or --template free."
9171
- );
9172
- }
9173
9201
  const file = o.file ? insideProject(dir, o.file, "Plans file") : void 0;
9174
9202
  const target = file ? path14.join(dir, file) : resolved.plans?.file ?? path14.join(resolved.root, "plans.json");
9175
9203
  await addFile(
@@ -9178,20 +9206,14 @@ async function buildPlans(context) {
9178
9206
  `${JSON.stringify({ version: 1, plans: PLAN_TEMPLATES[template] }, null, 2)}
9179
9207
  `
9180
9208
  );
9181
- if (billing === "stripe") {
9209
+ if (PLAN_TEMPLATES[template].some((plan2) => isPaidPlan(plan2))) {
9182
9210
  context.notes.push(
9183
- "Set the billing key: apitogo secrets set STRIPE_API_KEY"
9211
+ "A plan has a price, so payments go through Stripe: apitogo deploy asks for your Stripe secret key."
9184
9212
  );
9185
9213
  }
9186
9214
  return {
9187
9215
  ...options.disabled ? { enabled: false } : {},
9188
- ...file ? { file } : {},
9189
- ...billing === "stripe" ? {
9190
- billing: {
9191
- provider: "stripe",
9192
- apiKey: "secret:STRIPE_API_KEY"
9193
- }
9194
- } : { billing: { provider: "none" } }
9216
+ ...file ? { file } : {}
9195
9217
  };
9196
9218
  }
9197
9219
  function reparse(configPath, raw) {
@@ -9383,7 +9405,7 @@ async function clearProjectId(dir, projectId) {
9383
9405
 
9384
9406
  // src/config/v2/ledger.ts
9385
9407
  init_invariant();
9386
- import { mkdir as mkdir6, readFile as readFile8, rename as rename4, rm as rm5, writeFile as writeFile8 } from "node:fs/promises";
9408
+ import { mkdir as mkdir6, readFile as readFile9, rename as rename4, rm as rm5, writeFile as writeFile8 } from "node:fs/promises";
9387
9409
  import path15 from "node:path";
9388
9410
  var LEDGER_VERSION = 1;
9389
9411
  var LedgerError = class extends ApitogoError {
@@ -9400,7 +9422,7 @@ var emptyLedger = () => ({
9400
9422
  async function readLedger(state) {
9401
9423
  let text;
9402
9424
  try {
9403
- text = await readFile8(state.file, "utf8");
9425
+ text = await readFile9(state.file, "utf8");
9404
9426
  } catch (error) {
9405
9427
  if (error.code === "ENOENT")
9406
9428
  return emptyLedger();
@@ -9535,7 +9557,7 @@ init_invariant();
9535
9557
 
9536
9558
  // src/cli/check/v2.ts
9537
9559
  init_apitogo_config_core();
9538
- import { readFile as readFile15 } from "node:fs/promises";
9560
+ import { readFile as readFile16 } from "node:fs/promises";
9539
9561
  import path49 from "node:path";
9540
9562
 
9541
9563
  // src/cli/platform/client/platform-api.ts
@@ -9649,8 +9671,8 @@ var PlatformApiClient = class {
9649
9671
  constructor(config2) {
9650
9672
  this.config = config2;
9651
9673
  }
9652
- url(path95) {
9653
- return `${this.config.apiUrl}${path95.startsWith("/") ? path95 : `/${path95}`}`;
9674
+ url(path96) {
9675
+ return `${this.config.apiUrl}${path96.startsWith("/") ? path96 : `/${path96}`}`;
9654
9676
  }
9655
9677
  headers(extra) {
9656
9678
  return {
@@ -9660,9 +9682,9 @@ var PlatformApiClient = class {
9660
9682
  ...extra
9661
9683
  };
9662
9684
  }
9663
- async request(method, path95, init3) {
9685
+ async request(method, path96, init3) {
9664
9686
  const response = await fetchWithRetry(
9665
- this.url(path95),
9687
+ this.url(path96),
9666
9688
  {
9667
9689
  method,
9668
9690
  ...init3,
@@ -10108,15 +10130,15 @@ var PlatformApiClient = class {
10108
10130
  }
10109
10131
  );
10110
10132
  }
10111
- deleteDevPortalPage(projectId, path95) {
10112
- const query = new URLSearchParams({ path: path95 });
10133
+ deleteDevPortalPage(projectId, path96) {
10134
+ const query = new URLSearchParams({ path: path96 });
10113
10135
  return this.request(
10114
10136
  "DELETE",
10115
10137
  `/api/v1/projects/${projectId}/dev-portal/pages/files?${query.toString()}`
10116
10138
  );
10117
10139
  }
10118
- renameDevPortalPage(projectId, path95, newName) {
10119
- const query = new URLSearchParams({ path: path95 });
10140
+ renameDevPortalPage(projectId, path96, newName) {
10141
+ const query = new URLSearchParams({ path: path96 });
10120
10142
  return this.request(
10121
10143
  "POST",
10122
10144
  `/api/v1/projects/${projectId}/dev-portal/pages/files/rename?${query.toString()}`,
@@ -10232,8 +10254,8 @@ var PlatformApiClient = class {
10232
10254
  * the project-less endpoint is used; `planTier` overrides the tier used for paid-plan checks.
10233
10255
  */
10234
10256
  validateGatewayPlans(plans, options = {}) {
10235
- const path95 = options.projectId ? `/api/v1/projects/${options.projectId}/gateway/plans/validate` : "/api/v1/gateway/plans/validate";
10236
- return this.request("POST", path95, {
10257
+ const path96 = options.projectId ? `/api/v1/projects/${options.projectId}/gateway/plans/validate` : "/api/v1/gateway/plans/validate";
10258
+ return this.request("POST", path96, {
10237
10259
  headers: { "Content-Type": "application/json" },
10238
10260
  body: JSON.stringify({ plans, planTier: options.planTier })
10239
10261
  });
@@ -10358,8 +10380,8 @@ var PlatformApiClient = class {
10358
10380
  }
10359
10381
  };
10360
10382
  async function readFileBytes(filePath) {
10361
- const { readFile: readFile26 } = await import("node:fs/promises");
10362
- const buffer = await readFile26(filePath);
10383
+ const { readFile: readFile27 } = await import("node:fs/promises");
10384
+ const buffer = await readFile27(filePath);
10363
10385
  const arrayBuffer = new ArrayBuffer(buffer.byteLength);
10364
10386
  new Uint8Array(arrayBuffer).set(buffer);
10365
10387
  return arrayBuffer;
@@ -10456,7 +10478,7 @@ function resolveProjectId(explicit, config2) {
10456
10478
  import {
10457
10479
  access as access4,
10458
10480
  mkdir as mkdir12,
10459
- readFile as readFile12,
10481
+ readFile as readFile13,
10460
10482
  rename as rename6,
10461
10483
  rm as rm8,
10462
10484
  writeFile as writeFile15
@@ -10464,7 +10486,7 @@ import {
10464
10486
  import path48 from "node:path";
10465
10487
 
10466
10488
  // src/vite/build.ts
10467
- import { mkdir as mkdir11, readFile as readFile10, rename as rename5, rm as rm7, writeFile as writeFile13 } from "node:fs/promises";
10489
+ import { mkdir as mkdir11, readFile as readFile11, rename as rename5, rm as rm7, writeFile as writeFile13 } from "node:fs/promises";
10468
10490
  import path46 from "node:path";
10469
10491
  import { build as esbuild } from "esbuild";
10470
10492
  import { build as viteBuild, mergeConfig as mergeConfig3 } from "vite";
@@ -10612,7 +10634,7 @@ import {
10612
10634
  // package.json
10613
10635
  var package_default = {
10614
10636
  name: "@lukoweb/apitogo",
10615
- version: "0.2.47",
10637
+ version: "0.2.49",
10616
10638
  type: "module",
10617
10639
  sideEffects: [
10618
10640
  "**/*.css",
@@ -11397,14 +11419,14 @@ var flattenAllOf = (schema2) => {
11397
11419
  };
11398
11420
 
11399
11421
  // src/lib/util/traverse.ts
11400
- var traverse = (specification, transform, path95 = []) => {
11401
- const transformed = transform(specification, path95);
11422
+ var traverse = (specification, transform, path96 = []) => {
11423
+ const transformed = transform(specification, path96);
11402
11424
  if (typeof transformed !== "object" || transformed === null) {
11403
11425
  return transformed;
11404
11426
  }
11405
11427
  const result = Array.isArray(transformed) ? [] : {};
11406
11428
  for (const [key, value] of Object.entries(transformed)) {
11407
- const currentPath = [...path95, key];
11429
+ const currentPath = [...path96, key];
11408
11430
  if (Array.isArray(value)) {
11409
11431
  result[key] = value.map(
11410
11432
  (item, index) => typeof item === "object" && item != null ? traverse(item, transform, [...currentPath, index.toString()]) : item
@@ -11432,9 +11454,9 @@ var resolveLocalRef = (schema2, ref) => {
11432
11454
  if (schemaCache?.has(ref)) {
11433
11455
  return schemaCache.get(ref);
11434
11456
  }
11435
- const path95 = ref.split("/").slice(1);
11457
+ const path96 = ref.split("/").slice(1);
11436
11458
  let current = schema2;
11437
- for (const segment of path95) {
11459
+ for (const segment of path96) {
11438
11460
  if (!current || typeof current !== "object") {
11439
11461
  current = null;
11440
11462
  }
@@ -11453,7 +11475,7 @@ var dereference = async (schema2, resolvers = []) => {
11453
11475
  }
11454
11476
  const cloned = structuredClone(schema2);
11455
11477
  const visited = /* @__PURE__ */ new Set();
11456
- const resolve = async (current, path95) => {
11478
+ const resolve = async (current, path96) => {
11457
11479
  if (isIndexableObject(current)) {
11458
11480
  if (visited.has(current)) {
11459
11481
  return CIRCULAR_REF;
@@ -11461,7 +11483,7 @@ var dereference = async (schema2, resolvers = []) => {
11461
11483
  visited.add(current);
11462
11484
  if (Array.isArray(current)) {
11463
11485
  for (let index = 0; index < current.length; index++) {
11464
- current[index] = await resolve(current[index], `${path95}/${index}`);
11486
+ current[index] = await resolve(current[index], `${path96}/${index}`);
11465
11487
  }
11466
11488
  } else {
11467
11489
  if ("$ref" in current && typeof current.$ref === "string") {
@@ -11472,13 +11494,13 @@ var dereference = async (schema2, resolvers = []) => {
11472
11494
  for (const resolver of resolvers) {
11473
11495
  const resolved = await resolver($ref);
11474
11496
  if (resolved) {
11475
- result2 = await resolve(resolved, path95);
11497
+ result2 = await resolve(resolved, path96);
11476
11498
  break;
11477
11499
  }
11478
11500
  }
11479
11501
  if (result2 === void 0) {
11480
11502
  const resolved = await resolveLocalRef(cloned, $ref);
11481
- result2 = await resolve(resolved, path95);
11503
+ result2 = await resolve(resolved, path96);
11482
11504
  }
11483
11505
  if (hasSiblings) {
11484
11506
  if (result2 === CIRCULAR_REF) {
@@ -11491,7 +11513,7 @@ var dereference = async (schema2, resolvers = []) => {
11491
11513
  return result2;
11492
11514
  }
11493
11515
  for (const key in current) {
11494
- current[key] = await resolve(current[key], `${path95}/${key}`);
11516
+ current[key] = await resolve(current[key], `${path96}/${key}`);
11495
11517
  }
11496
11518
  }
11497
11519
  visited.delete(current);
@@ -11530,9 +11552,9 @@ var upgradeSchema = (schema2) => {
11530
11552
  }
11531
11553
  return sub;
11532
11554
  });
11533
- schema2 = traverse(schema2, (sub, path95) => {
11555
+ schema2 = traverse(schema2, (sub, path96) => {
11534
11556
  if (sub.example !== void 0) {
11535
- if (isSchemaPath(path95 ?? [])) {
11557
+ if (isSchemaPath(path96 ?? [])) {
11536
11558
  sub.examples = [sub.example];
11537
11559
  } else {
11538
11560
  sub.examples = {
@@ -11545,11 +11567,11 @@ var upgradeSchema = (schema2) => {
11545
11567
  }
11546
11568
  return sub;
11547
11569
  });
11548
- schema2 = traverse(schema2, (schema3, path95) => {
11570
+ schema2 = traverse(schema2, (schema3, path96) => {
11549
11571
  if (schema3.type === "object" && schema3.properties !== void 0) {
11550
- const parentPath = path95?.slice(0, -1);
11572
+ const parentPath = path96?.slice(0, -1);
11551
11573
  const isMultipart = parentPath?.some((segment, index) => {
11552
- return segment === "content" && path95?.[index + 1] === "multipart/form-data";
11574
+ return segment === "content" && path96?.[index + 1] === "multipart/form-data";
11553
11575
  });
11554
11576
  if (isMultipart) {
11555
11577
  const entries = Object.entries(schema3.properties);
@@ -11563,8 +11585,8 @@ var upgradeSchema = (schema2) => {
11563
11585
  }
11564
11586
  return schema3;
11565
11587
  });
11566
- schema2 = traverse(schema2, (schema3, path95) => {
11567
- if (path95?.includes("content") && path95.includes("application/octet-stream")) {
11588
+ schema2 = traverse(schema2, (schema3, path96) => {
11589
+ if (path96?.includes("content") && path96.includes("application/octet-stream")) {
11568
11590
  return {};
11569
11591
  }
11570
11592
  if (schema3.type === "string" && schema3.format === "binary") {
@@ -11590,11 +11612,11 @@ var upgradeSchema = (schema2) => {
11590
11612
  }
11591
11613
  return sub;
11592
11614
  });
11593
- schema2 = traverse(schema2, (schema3, path95) => {
11615
+ schema2 = traverse(schema2, (schema3, path96) => {
11594
11616
  if (schema3.type === "string" && schema3.format === "byte") {
11595
- const parentPath = path95?.slice(0, -1);
11617
+ const parentPath = path96?.slice(0, -1);
11596
11618
  const contentMediaType = parentPath?.find(
11597
- (_, index) => path95?.[index - 1] === "content"
11619
+ (_, index) => path96?.[index - 1] === "content"
11598
11620
  );
11599
11621
  return {
11600
11622
  type: "string",
@@ -11606,7 +11628,7 @@ var upgradeSchema = (schema2) => {
11606
11628
  });
11607
11629
  return schema2;
11608
11630
  };
11609
- function isSchemaPath(path95) {
11631
+ function isSchemaPath(path96) {
11610
11632
  const schemaLocations = [
11611
11633
  ["components", "schemas"],
11612
11634
  "properties",
@@ -11619,10 +11641,10 @@ function isSchemaPath(path95) {
11619
11641
  ];
11620
11642
  return schemaLocations.some((location) => {
11621
11643
  if (Array.isArray(location)) {
11622
- return location.every((segment, index) => path95[index] === segment);
11644
+ return location.every((segment, index) => path96[index] === segment);
11623
11645
  }
11624
- return path95.includes(location);
11625
- }) || path95.includes("schema") || path95.some((segment) => segment.endsWith("Schema"));
11646
+ return path96.includes(location);
11647
+ }) || path96.includes("schema") || path96.some((segment) => segment.endsWith("Schema"));
11626
11648
  }
11627
11649
 
11628
11650
  // src/lib/oas/parser/index.ts
@@ -11704,13 +11726,13 @@ var OPENAPI_PROPS = /* @__PURE__ */ new Set([
11704
11726
  "anyOf",
11705
11727
  "oneOf"
11706
11728
  ]);
11707
- var handleCircularRefs = (obj, currentPath = /* @__PURE__ */ new WeakSet(), refs = /* @__PURE__ */ new WeakMap(), path95 = [], currentRefPaths = /* @__PURE__ */ new Set()) => {
11729
+ var handleCircularRefs = (obj, currentPath = /* @__PURE__ */ new WeakSet(), refs = /* @__PURE__ */ new WeakMap(), path96 = [], currentRefPaths = /* @__PURE__ */ new Set()) => {
11708
11730
  if (obj === null || typeof obj !== "object") return obj;
11709
11731
  const refPath = obj.__$ref;
11710
11732
  const isCircular = currentPath.has(obj) || typeof refPath === "string" && currentRefPaths.has(refPath);
11711
11733
  if (isCircular) {
11712
11734
  if (typeof refPath === "string") return SCHEMA_REF_PREFIX + refPath;
11713
- const circularProp = path95.find((p) => !OPENAPI_PROPS.has(p)) || path95[0];
11735
+ const circularProp = path96.find((p) => !OPENAPI_PROPS.has(p)) || path96[0];
11714
11736
  return [CIRCULAR_REF, circularProp].filter(Boolean).join(":");
11715
11737
  }
11716
11738
  if (refs.has(obj)) return refs.get(obj);
@@ -11720,7 +11742,7 @@ var handleCircularRefs = (obj, currentPath = /* @__PURE__ */ new WeakSet(), refs
11720
11742
  value,
11721
11743
  currentPath,
11722
11744
  refs,
11723
- [...path95, key],
11745
+ [...path96, key],
11724
11746
  currentRefPaths
11725
11747
  );
11726
11748
  const result = Array.isArray(obj) ? obj.map((item, i) => recurse(item, i.toString())) : Object.fromEntries(
@@ -11755,7 +11777,7 @@ var resolveExtensions = (obj) => Object.fromEntries(
11755
11777
  var getAllTags = (schema2) => {
11756
11778
  const rootTags = schema2.tags ?? [];
11757
11779
  const operations = Object.values(schema2.paths ?? {}).flatMap(
11758
- (path95) => HttpMethods.map((k) => path95?.[k]).filter((op) => op != null)
11780
+ (path96) => HttpMethods.map((k) => path96?.[k]).filter((op) => op != null)
11759
11781
  );
11760
11782
  const operationTags = new Set(operations.flatMap((op) => op.tags ?? []));
11761
11783
  const hasUntaggedOperations = operations.some(
@@ -11810,7 +11832,7 @@ var getAllSlugs = (ops, schemaTags = []) => {
11810
11832
  var getOperationSlugKey = (op) => [op.path, op.method, op.operationId, op.summary].filter(Boolean).join("-");
11811
11833
  var getAllOperations = (paths) => {
11812
11834
  const operations = Object.entries(paths ?? {}).flatMap(
11813
- ([path95, value]) => HttpMethods.flatMap((method) => {
11835
+ ([path96, value]) => HttpMethods.flatMap((method) => {
11814
11836
  if (!value?.[method]) return [];
11815
11837
  const operation = value[method];
11816
11838
  const pathParameters = value.parameters ?? [];
@@ -11830,7 +11852,7 @@ var getAllOperations = (paths) => {
11830
11852
  return {
11831
11853
  ...operation,
11832
11854
  method,
11833
- path: path95,
11855
+ path: path96,
11834
11856
  parameters,
11835
11857
  servers,
11836
11858
  tags: operation.tags ?? []
@@ -12188,8 +12210,8 @@ var Schema = builder.objectRef("Schema").implement({
12188
12210
  }),
12189
12211
  paths: t.field({
12190
12212
  type: [PathItem],
12191
- resolve: (root) => Object.entries(root.paths ?? {}).map(([path95, value]) => ({
12192
- path: path95,
12213
+ resolve: (root) => Object.entries(root.paths ?? {}).map(([path96, value]) => ({
12214
+ path: path96,
12193
12215
  // biome-ignore lint/style/noNonNullAssertion: value is guaranteed to be defined
12194
12216
  methods: Object.keys(value)
12195
12217
  }))
@@ -12354,7 +12376,7 @@ init_joinUrl();
12354
12376
 
12355
12377
  // src/vite/api/schema-codegen.ts
12356
12378
  var unescapeJsonPointer = (uri) => decodeURIComponent(uri.replace(/~1/g, "/").replace(/~0/g, "~"));
12357
- var getSegmentsFromPath = (path95) => path95.split("/").slice(1).map(unescapeJsonPointer);
12379
+ var getSegmentsFromPath = (path96) => path96.split("/").slice(1).map(unescapeJsonPointer);
12358
12380
  var createLocalRefMap = (obj) => {
12359
12381
  const refMap = /* @__PURE__ */ new Map();
12360
12382
  const siblingsMap = /* @__PURE__ */ new Map();
@@ -12385,16 +12407,16 @@ var replaceMarkers = (code, mergedRefs) => code.replace(/"__refMap:(.*?)"/g, '__
12385
12407
  /"__refMap\+Siblings:(.*?)"/g,
12386
12408
  (_, key) => mergedRefs.get(key) ?? `__refMap["${key}"]`
12387
12409
  );
12388
- var lookup = (schema2, path95, filePath) => {
12389
- const parts = getSegmentsFromPath(path95);
12410
+ var lookup = (schema2, path96, filePath) => {
12411
+ const parts = getSegmentsFromPath(path96);
12390
12412
  let val = schema2;
12391
12413
  for (const part of parts) {
12392
12414
  while (val.$ref?.startsWith("#/")) {
12393
- val = val.$ref === path95 ? val : lookup(schema2, val.$ref, filePath);
12415
+ val = val.$ref === path96 ? val : lookup(schema2, val.$ref, filePath);
12394
12416
  }
12395
12417
  if (val[part] === void 0) {
12396
12418
  throw new Error(
12397
- `Error in ${filePath ?? "code generation"}: Could not find path segment ${part} in path: ${path95}`
12419
+ `Error in ${filePath ?? "code generation"}: Could not find path segment ${part} in path: ${path96}`
12398
12420
  );
12399
12421
  }
12400
12422
  val = val[part];
@@ -12639,8 +12661,8 @@ var SchemaManager = class {
12639
12661
  }
12640
12662
  }
12641
12663
  };
12642
- getLatestSchema = (path95) => this.processedSchemas[path95]?.at(0);
12643
- getSchemasForPath = (path95) => this.processedSchemas[path95];
12664
+ getLatestSchema = (path96) => this.processedSchemas[path96]?.at(0);
12665
+ getSchemasForPath = (path96) => this.processedSchemas[path96];
12644
12666
  getSchemaImports = () => Object.values(this.processedSchemas).flat().filter((s) => s.importKey);
12645
12667
  getUrlToFilePathMap = () => {
12646
12668
  const map = /* @__PURE__ */ new Map();
@@ -15008,7 +15030,7 @@ async function writeOutput(dir, {
15008
15030
  // src/vite/prerender/prerender.ts
15009
15031
  init_logger();
15010
15032
  init_file_exists();
15011
- import { readFile as readFile9, rm as rm6 } from "node:fs/promises";
15033
+ import { readFile as readFile10, rm as rm6 } from "node:fs/promises";
15012
15034
  import os from "node:os";
15013
15035
  import path45 from "node:path";
15014
15036
  import { pathToFileURL } from "node:url";
@@ -15100,14 +15122,14 @@ async function generateSitemap({
15100
15122
 
15101
15123
  // src/vite/prerender/utils.ts
15102
15124
  init_joinUrl();
15103
- var resolveRoutePath = (path95) => {
15104
- const segments = path95.split("/");
15125
+ var resolveRoutePath = (path96) => {
15126
+ const segments = path96.split("/");
15105
15127
  if (segments.some((s) => s.startsWith(":") && !s.endsWith("?"))) {
15106
15128
  return void 0;
15107
15129
  }
15108
15130
  return segments.filter((s) => !s.startsWith(":")).join("/") || void 0;
15109
15131
  };
15110
- var isSkipped = (path95) => path95.includes("*") || /^\d+$/.test(path95);
15132
+ var isSkipped = (path96) => path96.includes("*") || /^\d+$/.test(path96);
15111
15133
  var resolveRoutes = (routes, parentPath = "") => routes.flatMap((route) => {
15112
15134
  if (route.path && isSkipped(route.path)) return [];
15113
15135
  const routePath = route.path ? resolveRoutePath(route.path) : void 0;
@@ -15284,7 +15306,7 @@ var prerender = async ({
15284
15306
  );
15285
15307
  let markdownFileInfos = [];
15286
15308
  if (await fileExists(markdownInfoPath)) {
15287
- const markdownInfoContent = await readFile9(markdownInfoPath, "utf-8");
15309
+ const markdownInfoContent = await readFile10(markdownInfoPath, "utf-8");
15288
15310
  markdownFileInfos = JSON.parse(markdownInfoContent);
15289
15311
  }
15290
15312
  if (llmsConfig.llmsTxt || llmsConfig.llmsTxtFull) {
@@ -15435,7 +15457,7 @@ var bundleSSREntry = async (options) => {
15435
15457
  const { dir, adapter, serverOutDir, serverConfigFilename, html, basePath } = options;
15436
15458
  const tempEntryPath = path46.join(dir, "__ssr-entry.ts");
15437
15459
  const packageRoot = getZudokuRootDir();
15438
- const templateContent = await readFile10(
15460
+ const templateContent = await readFile11(
15439
15461
  path46.join(packageRoot, "src/vite/ssr-templates", `${adapter}.ts`),
15440
15462
  "utf-8"
15441
15463
  );
@@ -15466,7 +15488,7 @@ init_package_json();
15466
15488
 
15467
15489
  // src/cli/init/v2-packages.ts
15468
15490
  import { existsSync as existsSync5 } from "node:fs";
15469
- import { readFile as readFile11, writeFile as writeFile14 } from "node:fs/promises";
15491
+ import { readFile as readFile12, writeFile as writeFile14 } from "node:fs/promises";
15470
15492
  import path47 from "node:path";
15471
15493
  var CORE_PACKAGES = [
15472
15494
  "@lukoweb/apitogo",
@@ -15613,17 +15635,17 @@ async function buildV2(options) {
15613
15635
  }
15614
15636
 
15615
15637
  // src/cli/check/gateway-online.ts
15616
- import { readFile as readFile13 } from "node:fs/promises";
15638
+ import { readFile as readFile14 } from "node:fs/promises";
15617
15639
  var SPEC_SEND_LIMIT = 5 * 1024 * 1024 + 64 * 1024;
15618
- var issue2 = (code, path95, message, hint) => ({ code, path: path95, message, hint });
15640
+ var issue2 = (code, path96, message, hint) => ({ code, path: path96, message, hint });
15619
15641
  function capabilityIssues(gateway, answer) {
15620
15642
  const can = answer.capabilities;
15621
15643
  const plan2 = `your ${answer.tier} plan`;
15622
15644
  const found = [];
15623
- const unsupported = (path95, what, hint) => found.push(
15645
+ const unsupported = (path96, what, hint) => found.push(
15624
15646
  issue2(
15625
15647
  "GATEWAY_CAPABILITY_UNSUPPORTED",
15626
- path95,
15648
+ path96,
15627
15649
  `${what} is not available on ${plan2}.`,
15628
15650
  hint
15629
15651
  )
@@ -15677,7 +15699,7 @@ async function onlineGatewayRules(gateway, projectId, api, derivedCors = []) {
15677
15699
  const specFile = gateway.specs[0];
15678
15700
  if (specFile) {
15679
15701
  try {
15680
- const text = await readFile13(specFile, "utf8");
15702
+ const text = await readFile14(specFile, "utf8");
15681
15703
  if (text.length <= SPEC_SEND_LIMIT) spec = text;
15682
15704
  } catch {
15683
15705
  }
@@ -15704,9 +15726,9 @@ async function onlineGatewayRules(gateway, projectId, api, derivedCors = []) {
15704
15726
  }
15705
15727
 
15706
15728
  // src/cli/deploy/plans-file.ts
15707
- init_invariant();
15708
- import { readFile as readFile14 } from "node:fs/promises";
15729
+ import { readFile as readFile15 } from "node:fs/promises";
15709
15730
  import { z as z14 } from "zod";
15731
+ init_invariant();
15710
15732
  var PlansFileError = class extends ApitogoError {
15711
15733
  constructor(code, message, developerHint) {
15712
15734
  super(message, { developerHint, title: "Cannot read the plans file" });
@@ -15749,7 +15771,7 @@ var issuesOf = (error) => error.issues.map(
15749
15771
  async function readPlansFile(file) {
15750
15772
  let text;
15751
15773
  try {
15752
- text = await readFile14(file, "utf8");
15774
+ text = await readFile15(file, "utf8");
15753
15775
  } catch {
15754
15776
  throw new PlansFileError(
15755
15777
  "PLANS_FILE_UNREADABLE",
@@ -15790,12 +15812,12 @@ async function readPlansFile(file) {
15790
15812
  var BILLING_PERIOD = { month: "monthly", year: "annual" };
15791
15813
  function toPlatformPlans(plans, options = {}) {
15792
15814
  return plans.map((plan2, index) => {
15793
- const paid = plan2.pricingModel !== "custom" && (plan2.priceAmount ?? 0) > 0;
15815
+ const paid = isPaidPlan(plan2);
15794
15816
  if (paid && !options.billing)
15795
15817
  throw new PlansFileError(
15796
15818
  "PLANS_PAID_UNSUPPORTED",
15797
- `The plan "${plan2.sku}" has a price, and plans.billing.provider is not stripe, so nobody could pay for it.`,
15798
- "Set plans.billing.provider to stripe (with plans.billing.apiKey), or set priceAmount to 0 (or pricingModel to custom for a contact-us plan)."
15819
+ `The plan "${plan2.sku}" has a price, but plans.billing.provider is "none", so nobody could pay for it.`,
15820
+ "Remove plans.billing.provider (Stripe is the provider; deploy asks for its key), or set priceAmount to 0 (or pricingModel to custom for a contact-us plan)."
15799
15821
  );
15800
15822
  const limits = plan2.limits ? {
15801
15823
  ...plan2.limits.rateLimit ? {
@@ -15827,9 +15849,9 @@ function toPlatformPlans(plans, options = {}) {
15827
15849
  }
15828
15850
 
15829
15851
  // src/cli/check/services-online.ts
15830
- var issue3 = (code, path95, message, hint) => ({
15852
+ var issue3 = (code, path96, message, hint) => ({
15831
15853
  code,
15832
- path: path95,
15854
+ path: path96,
15833
15855
  message,
15834
15856
  hint
15835
15857
  });
@@ -15940,7 +15962,7 @@ async function onlinePlansRules(resolved, projectId, api) {
15940
15962
  async function isConfigV2(dir) {
15941
15963
  try {
15942
15964
  return /"version"\s*:\s*2\b/.test(
15943
- await readFile15(path49.join(dir, APITOGO_CONFIG_FILENAME), "utf8")
15965
+ await readFile16(path49.join(dir, APITOGO_CONFIG_FILENAME), "utf8")
15944
15966
  );
15945
15967
  } catch {
15946
15968
  return false;
@@ -16502,8 +16524,7 @@ function addOptionsFromArguments(service, argv) {
16502
16524
  } : void 0,
16503
16525
  plans: service === "plans" ? {
16504
16526
  file: argv.file,
16505
- template: argv.template === "free" || argv.template === "tiered" ? argv.template : void 0,
16506
- billing: argv.billing
16527
+ template: argv.template === "free" || argv.template === "tiered" ? argv.template : void 0
16507
16528
  } : void 0
16508
16529
  };
16509
16530
  }
@@ -16810,10 +16831,6 @@ var add_default = {
16810
16831
  "none"
16811
16832
  ],
16812
16833
  describe: "Content to create: blank (empty shell), starter (filled example) or full (starter plus a Node.js backend in server/). Plans also accept free, tiered and none; none is blank"
16813
- }).option("billing", {
16814
- type: "string",
16815
- choices: ["stripe", "none"],
16816
- describe: "plans: billing provider"
16817
16834
  }),
16818
16835
  handler: async (argv) => {
16819
16836
  await captureEvent({ argv, event: "apitogo add" });
@@ -17026,7 +17043,7 @@ function formatDevPortalBackendResponse(projectId, backend) {
17026
17043
  // src/cli/platform/helpers/dev-portal-manifest.ts
17027
17044
  init_gateway_limits();
17028
17045
  import { existsSync as existsSync7 } from "node:fs";
17029
- import { access as access5, mkdir as mkdir13, readFile as readFile16, writeFile as writeFile16 } from "node:fs/promises";
17046
+ import { access as access5, mkdir as mkdir13, readFile as readFile17, writeFile as writeFile16 } from "node:fs/promises";
17030
17047
  import path55 from "node:path";
17031
17048
 
17032
17049
  // src/cli/make-config/header-pricing-nav.ts
@@ -17606,7 +17623,7 @@ function mergePublishPrerequisitesWithPlatformValidation(prerequisites, manifest
17606
17623
  async function readManifest2(siteDirectory) {
17607
17624
  const filePath = manifestFilePath(siteDirectory);
17608
17625
  if (await pathExists(filePath)) {
17609
- const raw = await readFile16(filePath, "utf8");
17626
+ const raw = await readFile17(filePath, "utf8");
17610
17627
  const config2 = JSON.parse(raw);
17611
17628
  return extractManifestFromConfig(config2);
17612
17629
  }
@@ -17617,7 +17634,7 @@ async function writeManifest2(siteDirectory, manifest) {
17617
17634
  await mkdir13(path55.dirname(filePath), { recursive: true });
17618
17635
  let config2 = {};
17619
17636
  if (await pathExists(filePath)) {
17620
- const raw = await readFile16(filePath, "utf8");
17637
+ const raw = await readFile17(filePath, "utf8");
17621
17638
  config2 = JSON.parse(raw);
17622
17639
  }
17623
17640
  const {
@@ -17636,7 +17653,7 @@ import {
17636
17653
  access as access6,
17637
17654
  appendFile,
17638
17655
  mkdir as mkdir14,
17639
- readFile as readFile17,
17656
+ readFile as readFile18,
17640
17657
  unlink,
17641
17658
  writeFile as writeFile17
17642
17659
  } from "node:fs/promises";
@@ -17742,7 +17759,7 @@ function applyEnvOverrides2(config2, env) {
17742
17759
  }
17743
17760
  async function readEnvFile(filePath) {
17744
17761
  try {
17745
- const raw = await readFile17(filePath, "utf8");
17762
+ const raw = await readFile18(filePath, "utf8");
17746
17763
  const env = {};
17747
17764
  for (const line2 of raw.split("\n")) {
17748
17765
  const trimmed = line2.trim();
@@ -17875,7 +17892,7 @@ var SECRETS_COLLECTION_INSTRUCTION = "For go-live, plans must already be in apit
17875
17892
 
17876
17893
  // src/cli/platform/helpers/dev-portal-site-validation.ts
17877
17894
  init_gateway_limits();
17878
- import { readFile as readFile18 } from "node:fs/promises";
17895
+ import { readFile as readFile19 } from "node:fs/promises";
17879
17896
  import { createRequire as createRequire2 } from "node:module";
17880
17897
  import path57 from "node:path";
17881
17898
  var cachedFallbackTs;
@@ -18053,7 +18070,7 @@ async function validateDevPortalSiteConfig(sourceDirectory, options) {
18053
18070
  let parsed;
18054
18071
  let parseFailed = false;
18055
18072
  try {
18056
- parsed = JSON.parse(await readFile18(jsonPath, "utf8"));
18073
+ parsed = JSON.parse(await readFile19(jsonPath, "utf8"));
18057
18074
  } catch (error) {
18058
18075
  parseFailed = true;
18059
18076
  issues.push({
@@ -18092,7 +18109,7 @@ async function validateDevPortalSiteConfig(sourceDirectory, options) {
18092
18109
  } else {
18093
18110
  for (const relativeFile of existingTsxFiles) {
18094
18111
  checkedFiles.push(relativeFile);
18095
- const content = await readFile18(
18112
+ const content = await readFile19(
18096
18113
  path57.join(resolvedDir, relativeFile),
18097
18114
  "utf8"
18098
18115
  );
@@ -18115,7 +18132,7 @@ async function validateDevPortalSiteConfig(sourceDirectory, options) {
18115
18132
 
18116
18133
  // src/cli/platform/helpers/npm-build.ts
18117
18134
  import { spawn as spawn2 } from "node:child_process";
18118
- import { access as access7, readFile as readFile19 } from "node:fs/promises";
18135
+ import { access as access7, readFile as readFile20 } from "node:fs/promises";
18119
18136
  import path58 from "node:path";
18120
18137
  var BUILD_LOG_TAIL_BYTES = 8192;
18121
18138
  var MIN_NODE_MAJOR = 20;
@@ -18761,7 +18778,7 @@ import { createInterface as createInterface3 } from "node:readline/promises";
18761
18778
 
18762
18779
  // src/cli/make-config/v2-edits.ts
18763
18780
  init_apitogo_config_core();
18764
- import { access as access8, mkdir as mkdir15, readFile as readFile20, writeFile as writeFile18 } from "node:fs/promises";
18781
+ import { access as access8, mkdir as mkdir15, readFile as readFile21, writeFile as writeFile18 } from "node:fs/promises";
18765
18782
  import path61 from "node:path";
18766
18783
  import { glob as glob7 } from "glob";
18767
18784
  init_invariant();
@@ -18957,7 +18974,7 @@ async function writeDocsPage(project, input5) {
18957
18974
  "Leave out --page-update to add it."
18958
18975
  );
18959
18976
  }
18960
- const title = input5.title?.trim() || titleOf(await readFile20(existing, "utf8"));
18977
+ const title = input5.title?.trim() || titleOf(await readFile21(existing, "utf8"));
18961
18978
  file = existing;
18962
18979
  action = "updated";
18963
18980
  await writeFile18(file, buildDocsPage(title ?? slug, input5.content), "utf8");
@@ -19046,7 +19063,7 @@ async function upsertPlan(project, plan2) {
19046
19063
  if (await exists4(plans.file)) {
19047
19064
  let raw;
19048
19065
  try {
19049
- raw = JSON.parse(await readFile20(plans.file, "utf8"));
19066
+ raw = JSON.parse(await readFile21(plans.file, "utf8"));
19050
19067
  } catch (error) {
19051
19068
  throw new ConfigEditError(
19052
19069
  `${relativeToConfig(project, plans.file)} is not valid JSON (${error.message}).`
@@ -19073,7 +19090,7 @@ async function upsertPlan(project, plan2) {
19073
19090
  `,
19074
19091
  "utf8"
19075
19092
  );
19076
- const paid = plan2.pricingModel !== "custom" && (plan2.priceAmount ?? 0) > 0;
19093
+ const paid = isPaidPlan(plan2);
19077
19094
  return {
19078
19095
  action: index === -1 ? "added" : "updated",
19079
19096
  sku: plan2.sku,
@@ -19117,7 +19134,12 @@ function printPlanResult(plan2) {
19117
19134
  printSuccessToConsole(`Plan "${plan2.sku}" ${plan2.action} in ${plan2.file}.`);
19118
19135
  if (plan2.needsBilling) {
19119
19136
  printWarningToConsole(
19120
- "This plan has a price and plans.billing.provider is not stripe, so deploy will refuse it. Set plans.billing.provider to stripe, or make the plan free or custom."
19137
+ 'This plan has a price, but plans.billing.provider is "none", so deploy will refuse it. Remove plans.billing.provider (Stripe is the provider), or make the plan free or custom.'
19138
+ );
19139
+ }
19140
+ if (plan2.paid && !plan2.needsBilling) {
19141
+ printHintToConsole(
19142
+ "Payments go through Stripe: apitogo deploy asks for your Stripe secret key if the project has none yet."
19121
19143
  );
19122
19144
  }
19123
19145
  if (plan2.paid) printPaidPlanPlatformWarning();
@@ -19512,7 +19534,7 @@ var config_default = {
19512
19534
  // src/cli/create/handler.ts
19513
19535
  init_apitogo_config_core();
19514
19536
  import { execFile } from "node:child_process";
19515
- import { readFile as readFile21 } from "node:fs/promises";
19537
+ import { readFile as readFile22 } from "node:fs/promises";
19516
19538
  import path63 from "node:path";
19517
19539
  import { promisify } from "node:util";
19518
19540
  var execFileAsync = promisify(execFile);
@@ -19546,7 +19568,7 @@ async function gitHasUncommittedChanges(dir) {
19546
19568
  async function defaultName(dir) {
19547
19569
  try {
19548
19570
  const parsed = JSON.parse(
19549
- await readFile21(path63.join(dir, "package.json"), "utf8")
19571
+ await readFile22(path63.join(dir, "package.json"), "utf8")
19550
19572
  );
19551
19573
  if (typeof parsed.name === "string" && parsed.name.trim())
19552
19574
  return parsed.name.replace(/^@[^/]+\//, "");
@@ -19772,7 +19794,7 @@ var create_default = {
19772
19794
  };
19773
19795
 
19774
19796
  // src/cli/cmds/deploy.ts
19775
- import path80 from "node:path";
19797
+ import path81 from "node:path";
19776
19798
 
19777
19799
  // src/cli/apply/handler.ts
19778
19800
  import path75 from "node:path";
@@ -20319,8 +20341,8 @@ function normalizeActionKey(actionKey2) {
20319
20341
  const trimmed = actionKey2.trim();
20320
20342
  if (trimmed.includes(":")) {
20321
20343
  const [method2, ...pathParts] = trimmed.split(":");
20322
- const path96 = pathParts.join(":").trim();
20323
- return `${method2?.toLowerCase()}:${path96.startsWith("/") ? path96 : `/${path96}`}`;
20344
+ const path97 = pathParts.join(":").trim();
20345
+ return `${method2?.toLowerCase()}:${path97.startsWith("/") ? path97 : `/${path97}`}`;
20324
20346
  }
20325
20347
  const spaceIndex = trimmed.indexOf(" ");
20326
20348
  if (spaceIndex <= 0) {
@@ -20329,8 +20351,8 @@ function normalizeActionKey(actionKey2) {
20329
20351
  );
20330
20352
  }
20331
20353
  const method = trimmed.slice(0, spaceIndex).trim().toLowerCase();
20332
- const path95 = trimmed.slice(spaceIndex + 1).trim();
20333
- return `${method}:${path95.startsWith("/") ? path95 : `/${path95}`}`;
20354
+ const path96 = trimmed.slice(spaceIndex + 1).trim();
20355
+ return `${method}:${path96.startsWith("/") ? path96 : `/${path96}`}`;
20334
20356
  }
20335
20357
  async function applyGatewayPublicRoutes(client, projectId, routes, options) {
20336
20358
  const applied = [];
@@ -20808,7 +20830,7 @@ async function upsertGatewayPlansFromManifest(client, config2, projectId, plans,
20808
20830
  }
20809
20831
 
20810
20832
  // src/cli/platform/helpers/openapi-gateway-servers.ts
20811
- import { mkdir as mkdir16, readFile as readFile22, writeFile as writeFile19 } from "node:fs/promises";
20833
+ import { mkdir as mkdir16, readFile as readFile23, writeFile as writeFile19 } from "node:fs/promises";
20812
20834
  import path66 from "node:path";
20813
20835
  import YAML from "yaml";
20814
20836
  var GATEWAY_URL_ENV_KEY = "APITOGO_GATEWAY_URL";
@@ -20840,7 +20862,7 @@ async function resolveProjectGatewayUrl(client, projectId) {
20840
20862
  return null;
20841
20863
  }
20842
20864
  async function readOpenApiDocument(filePath) {
20843
- const content = await readFile22(filePath, "utf8");
20865
+ const content = await readFile23(filePath, "utf8");
20844
20866
  const ext = path66.extname(filePath).toLowerCase();
20845
20867
  const parsed = ext === ".json" ? JSON.parse(content) : YAML.parse(content);
20846
20868
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
@@ -20869,7 +20891,7 @@ async function pointSiteApisAtPublishOpenApi(siteDirectory, relativeInput) {
20869
20891
  path66.resolve(siteDirectory),
20870
20892
  APITOGO_CONFIG_FILENAME2
20871
20893
  );
20872
- const raw = await readFile22(configPath, "utf8");
20894
+ const raw = await readFile23(configPath, "utf8");
20873
20895
  const config2 = JSON.parse(raw);
20874
20896
  const previousApis = config2.apis;
20875
20897
  const previousBackend = config2.backend;
@@ -20886,7 +20908,7 @@ async function pointSiteApisAtPublishOpenApi(siteDirectory, relativeInput) {
20886
20908
  `, "utf8");
20887
20909
  return {
20888
20910
  restore: async () => {
20889
- const currentRaw = await readFile22(configPath, "utf8");
20911
+ const currentRaw = await readFile23(configPath, "utf8");
20890
20912
  const current = JSON.parse(currentRaw);
20891
20913
  if (previousApis === void 0) {
20892
20914
  delete current.apis;
@@ -20921,7 +20943,7 @@ async function prepareOpenApiServersForGatewayBuild(options) {
20921
20943
  );
20922
20944
  if (!openApiPath) {
20923
20945
  try {
20924
- const raw = await readFile22(
20946
+ const raw = await readFile23(
20925
20947
  path66.join(siteDirectory, APITOGO_CONFIG_FILENAME2),
20926
20948
  "utf8"
20927
20949
  );
@@ -20951,8 +20973,8 @@ function normalizeActionKeyFormat(actionKey2) {
20951
20973
  const trimmed = actionKey2.trim();
20952
20974
  if (trimmed.includes(":")) {
20953
20975
  const [method2, ...pathParts] = trimmed.split(":");
20954
- const path96 = pathParts.join(":").trim();
20955
- return `${method2.toLowerCase()}:${path96.startsWith("/") ? path96 : `/${path96}`}`;
20976
+ const path97 = pathParts.join(":").trim();
20977
+ return `${method2.toLowerCase()}:${path97.startsWith("/") ? path97 : `/${path97}`}`;
20956
20978
  }
20957
20979
  const spaceIndex = trimmed.indexOf(" ");
20958
20980
  if (spaceIndex <= 0) {
@@ -20961,8 +20983,8 @@ function normalizeActionKeyFormat(actionKey2) {
20961
20983
  );
20962
20984
  }
20963
20985
  const method = trimmed.slice(0, spaceIndex).trim().toLowerCase();
20964
- const path95 = trimmed.slice(spaceIndex + 1).trim();
20965
- return `${method}:${path95.startsWith("/") ? path95 : `/${path95}`}`;
20986
+ const path96 = trimmed.slice(spaceIndex + 1).trim();
20987
+ return `${method}:${path96.startsWith("/") ? path96 : `/${path96}`}`;
20966
20988
  }
20967
20989
  function normalizePublicRoute(route) {
20968
20990
  const trimmed = route.trim();
@@ -20995,7 +21017,7 @@ function collectManifestPublicActionKeys(gateway) {
20995
21017
  }
20996
21018
 
20997
21019
  // src/cli/platform/helpers/publish-ledger.ts
20998
- import { mkdir as mkdir17, readFile as readFile23, writeFile as writeFile20 } from "node:fs/promises";
21020
+ import { mkdir as mkdir17, readFile as readFile24, writeFile as writeFile20 } from "node:fs/promises";
20999
21021
  import path67 from "node:path";
21000
21022
  var PUBLISH_LEDGER_RELATIVE_PATH = ".apitogo/publish-state.json";
21001
21023
  function publishLedgerPath(siteDirectory) {
@@ -21004,7 +21026,7 @@ function publishLedgerPath(siteDirectory) {
21004
21026
  async function readPublishLedger(siteDirectory) {
21005
21027
  const filePath = publishLedgerPath(siteDirectory);
21006
21028
  try {
21007
- const raw = await readFile23(filePath, "utf8");
21029
+ const raw = await readFile24(filePath, "utf8");
21008
21030
  const parsed = JSON.parse(raw);
21009
21031
  if (parsed?.version !== 1 || !Array.isArray(parsed.steps)) {
21010
21032
  return null;
@@ -23275,7 +23297,7 @@ async function resolveProjectIdForPublish(client, config2, args, wait, progress,
23275
23297
  import path74 from "node:path";
23276
23298
 
23277
23299
  // src/cli/platform/helpers/dev-portal-site-api-url.ts
23278
- import { readdir as readdir5, readFile as readFile24, writeFile as writeFile21 } from "node:fs/promises";
23300
+ import { readdir as readdir5, readFile as readFile25, writeFile as writeFile21 } from "node:fs/promises";
23279
23301
  import path71 from "node:path";
23280
23302
  var SITE_API_BASE_URL_ENV_KEY = "VITE_APITOGO_AUTHENTICATION_API_BASE_URL";
23281
23303
  var PLACEHOLDER_HOST_MARKERS = [
@@ -23300,7 +23322,7 @@ async function readSiteApiBaseUrl(sourceDirectory) {
23300
23322
  const resolved = path71.resolve(sourceDirectory);
23301
23323
  for (const envFile of [".env", ".env.production"]) {
23302
23324
  try {
23303
- const content = await readFile24(path71.join(resolved, envFile), "utf8");
23325
+ const content = await readFile25(path71.join(resolved, envFile), "utf8");
23304
23326
  const lines = [...content.matchAll(ENV_LINE_GLOBAL_RE)];
23305
23327
  if (lines.length === 0) {
23306
23328
  continue;
@@ -23331,7 +23353,7 @@ async function patchSiteApiBaseUrl(sourceDirectory, apiBaseUrl) {
23331
23353
  for (const envFile of [".env.production", ".env"]) {
23332
23354
  const envPath = path71.join(resolved, envFile);
23333
23355
  try {
23334
- const content = await readFile24(envPath, "utf8");
23356
+ const content = await readFile25(envPath, "utf8");
23335
23357
  const withoutAssignments = content.replace(ENV_LINE_GLOBAL_RE, "").replace(/\n{3,}/g, "\n\n").trimEnd();
23336
23358
  const patched = withoutAssignments ? `${withoutAssignments}
23337
23359
  ${line2}
@@ -23411,7 +23433,7 @@ async function assertDeployableDistHasLiveApiBaseUrl(distDirectory, expectedApiB
23411
23433
  let found = false;
23412
23434
  let foundPlaceholder = false;
23413
23435
  for await (const file of walkFiles(dist)) {
23414
- const content = await readFile24(file, "utf8");
23436
+ const content = await readFile25(file, "utf8");
23415
23437
  if (content.includes(needle)) {
23416
23438
  found = true;
23417
23439
  }
@@ -25547,7 +25569,7 @@ async function apply(argv) {
25547
25569
  }
25548
25570
 
25549
25571
  // src/cli/deploy/command.ts
25550
- import path79 from "node:path";
25572
+ import path80 from "node:path";
25551
25573
  init_invariant();
25552
25574
 
25553
25575
  // src/cli/project/create-handler.ts
@@ -25914,17 +25936,129 @@ async function createProject2(argv) {
25914
25936
  }
25915
25937
  }
25916
25938
 
25939
+ // src/cli/secrets/input.ts
25940
+ import { createInterface as createInterface6 } from "node:readline/promises";
25941
+ var CTRL_C = "";
25942
+ var CTRL_D = "";
25943
+ function readHidden(prompt, input5 = process.stdin, output4 = process.stdout) {
25944
+ return new Promise((resolve, reject) => {
25945
+ if (!input5.isTTY || !input5.setRawMode) {
25946
+ reject(new Error("A hidden prompt needs an interactive terminal."));
25947
+ return;
25948
+ }
25949
+ let value = "";
25950
+ output4.write(prompt);
25951
+ input5.setRawMode(true);
25952
+ input5.resume();
25953
+ const finish = (action) => {
25954
+ input5.removeListener("data", onData);
25955
+ input5.setRawMode?.(false);
25956
+ input5.pause();
25957
+ output4.write("\n");
25958
+ action();
25959
+ };
25960
+ const onData = (chunk) => {
25961
+ for (const char of chunk.toString("utf8")) {
25962
+ if (char === CTRL_C)
25963
+ return finish(() => reject(new Error("Cancelled.")));
25964
+ if (char === "\r" || char === "\n" || char === CTRL_D)
25965
+ return finish(() => resolve(value));
25966
+ if (char === "\x7F" || char === "\b") value = value.slice(0, -1);
25967
+ else value += char;
25968
+ }
25969
+ };
25970
+ input5.on("data", onData);
25971
+ });
25972
+ }
25973
+ async function readStdin(input5 = process.stdin) {
25974
+ const chunks = [];
25975
+ for await (const chunk of input5) {
25976
+ chunks.push(typeof chunk === "string" ? Buffer.from(chunk) : chunk);
25977
+ }
25978
+ return Buffer.concat(chunks).toString("utf8").replace(/\r?\n$/, "");
25979
+ }
25980
+ async function confirm(question) {
25981
+ const rl = createInterface6({ input: process.stdin, output: process.stdout });
25982
+ try {
25983
+ const answer = (await rl.question(question)).trim().toLowerCase();
25984
+ return answer === "y" || answer === "yes";
25985
+ } finally {
25986
+ rl.close();
25987
+ }
25988
+ }
25989
+
25990
+ // src/cli/deploy/stripe-key.ts
25991
+ import path77 from "node:path";
25992
+ init_invariant();
25993
+ var StripeKeyError = class extends ApitogoError {
25994
+ constructor(message, developerHint) {
25995
+ super(message, { developerHint, title: "Stripe key needed" });
25996
+ this.name = "StripeKeyError";
25997
+ }
25998
+ };
25999
+ var STRIPE_KEY = /^(sk|rk)_(test|live)_[A-Za-z0-9]+$/;
26000
+ var wantsPlans = (only) => {
26001
+ if (only === void 0) return true;
26002
+ const names = (Array.isArray(only) ? only : only.split(",")).map((name) => name.trim()).filter(Boolean);
26003
+ return names.length === 0 || names.includes("plans");
26004
+ };
26005
+ async function ensureStripeKey(argv, deps) {
26006
+ if (!wantsPlans(argv.only)) return { status: "not-needed" };
26007
+ let resolved;
26008
+ try {
26009
+ resolved = loadConfigV2(path77.resolve(process.cwd(), argv.dir)).resolved;
26010
+ } catch {
26011
+ return { status: "not-needed" };
26012
+ }
26013
+ if (!resolved.plans?.enabled || resolved.plans.billing.provider !== "stripe")
26014
+ return { status: "not-needed" };
26015
+ const paid = await paidPlanSkus(resolved);
26016
+ if (paid.length === 0) return { status: "not-needed" };
26017
+ const projectId = argv.projectId?.trim() || resolved.project.id || deps.envProjectId?.trim();
26018
+ if (!projectId) return { status: "not-needed" };
26019
+ const name = stripeKeySecretName(resolved);
26020
+ const client = deps.client();
26021
+ const { secrets: secrets2 } = await client.listSecrets(projectId);
26022
+ if (secrets2.some((secret) => secret.name === name))
26023
+ return { status: "present", name };
26024
+ const reason = `The plan${paid.length > 1 ? "s" : ""} ${paid.join(", ")} ${paid.length > 1 ? "have" : "has"} a price, so payments go through Stripe, and this project has no Stripe key yet.`;
26025
+ if (!deps.interactive) {
26026
+ throw new StripeKeyError(
26027
+ reason,
26028
+ `Store it with \`apitogo secrets set ${name}\`, then deploy again. Or make the plans free.`
26029
+ );
26030
+ }
26031
+ deps.print(reason);
26032
+ deps.print(
26033
+ "Paste your Stripe secret key (starts with sk_ or rk_; use an sk_test_ key to try it out). It is stored as a project secret, not in your files."
26034
+ );
26035
+ const key = (await deps.askHidden("Stripe secret key (hidden): ")).trim();
26036
+ if (!key)
26037
+ throw new StripeKeyError(
26038
+ "No key was entered. Nothing was deployed.",
26039
+ `Run \`apitogo secrets set ${name}\` when you have it, then deploy again.`
26040
+ );
26041
+ if (!STRIPE_KEY.test(key))
26042
+ throw new StripeKeyError(
26043
+ "That is not a Stripe secret key. Nothing was deployed.",
26044
+ "A Stripe secret key starts with sk_test_, sk_live_, rk_test_ or rk_live_. Find it in Stripe under Developers > API keys."
26045
+ );
26046
+ await client.setSecret(projectId, name, key);
26047
+ deps.print(`Stripe key saved as the project secret ${name}.`);
26048
+ return { status: "saved", name };
26049
+ }
26050
+
25917
26051
  // src/cli/deploy/v2.ts
25918
26052
  import { rm as rm10 } from "node:fs/promises";
25919
26053
  init_invariant();
25920
26054
 
25921
26055
  // src/cli/portal/report.ts
25922
26056
  import { access as access9 } from "node:fs/promises";
25923
- import path77 from "node:path";
26057
+ import path78 from "node:path";
25924
26058
 
25925
26059
  // src/cli/deploy/gateway.ts
25926
26060
  import { createHash as createHash4 } from "node:crypto";
25927
- import { readFile as readFile25 } from "node:fs/promises";
26061
+ import { readFile as readFile26 } from "node:fs/promises";
25928
26062
  init_invariant();
25929
26063
  var GatewayDeployError = class extends ApitogoError {
25930
26064
  constructor(code, message, developerHint) {
@@ -25962,7 +26096,7 @@ async function readSpec(gateway) {
25962
26096
  "Add one to gateway.specs in the config."
25963
26097
  );
25964
26098
  try {
25965
- return await readFile25(file);
26099
+ return await readFile26(file);
25966
26100
  } catch {
25967
26101
  throw new GatewayDeployError(
25968
26102
  "GATEWAY_SPEC_UNREADABLE",
@@ -26722,12 +26856,13 @@ async function plansInputs(resolved, projectId) {
26722
26856
  const plans = resolved.plans;
26723
26857
  if (!plans) throw new Error("plans is not in the config.");
26724
26858
  const billing = plans.billing;
26725
- const wanted = toPlatformPlans(await readPlansFile(plans.file), {
26859
+ const filePlans = await readPlansFile(plans.file);
26860
+ const wanted = toPlatformPlans(filePlans, {
26726
26861
  billing: billing.provider === "stripe"
26727
26862
  });
26728
26863
  const manifest = await readManifest(resolved.state);
26729
26864
  const ledger = await readLedger(resolved.state);
26730
- const billingOn = billing.provider === "stripe";
26865
+ const billingOn = billing.provider === "stripe" && filePlans.some((plan2) => isPaidPlan(plan2));
26731
26866
  const billingStep = billingOn || stepOf(ledger, "plans", "billing") !== void 0;
26732
26867
  const identity = manifest?.services.identity;
26733
26868
  const ownIdentity = Boolean(
@@ -26739,11 +26874,12 @@ async function plansInputs(resolved, projectId) {
26739
26874
  ownIdentity,
26740
26875
  catalogHash: stableJson({ projectId, plans: wanted }),
26741
26876
  backendHash: stableJson({ projectId }),
26877
+ billingOn,
26742
26878
  billingStep,
26743
26879
  // Only the references to the project's secrets: a rotated value is applied with --force.
26744
26880
  billingHash: stableJson({
26745
26881
  projectId,
26746
- provider: billing.provider,
26882
+ provider: billingOn ? "stripe" : "none",
26747
26883
  apiKey: billingOn ? billing.apiKey : void 0,
26748
26884
  webhookSecret: billingOn ? billing.webhookSecret : void 0,
26749
26885
  successUrl: billingOn ? billing.successUrl : void 0,
@@ -26809,6 +26945,7 @@ async function deployPlans(resolved, projectId, deps, options = {}) {
26809
26945
  wanted,
26810
26946
  manifest,
26811
26947
  ownIdentity,
26948
+ billingOn,
26812
26949
  billingStep,
26813
26950
  billingHash,
26814
26951
  catalogHash,
@@ -26968,7 +27105,7 @@ async function deployPlans(resolved, projectId, deps, options = {}) {
26968
27105
  name: "billing",
26969
27106
  hash: () => billingHash,
26970
27107
  run: async () => {
26971
- const body = plans.billing.provider === "stripe" ? {
27108
+ const body = billingOn ? {
26972
27109
  provider: "stripe",
26973
27110
  apiKey: plans.billing.apiKey,
26974
27111
  webhookSecret: plans.billing.webhookSecret,
@@ -26985,8 +27122,7 @@ async function deployPlans(resolved, projectId, deps, options = {}) {
26985
27122
  code: "PLANS_BILLING",
26986
27123
  text: "the billing settings"
26987
27124
  });
26988
- if (plans.billing.provider === "stripe")
26989
- billingNote = billingNotes(queued);
27125
+ if (billingOn) billingNote = billingNotes(queued);
26990
27126
  return { ...done, mode: queued.mode };
26991
27127
  }
26992
27128
  };
@@ -27029,9 +27165,9 @@ async function deployPlans(resolved, projectId, deps, options = {}) {
27029
27165
  projectId,
27030
27166
  projectName: resolved.project.name,
27031
27167
  apiBaseUrl,
27032
- billing: plans.billing.provider,
27168
+ billing: billingOn ? "stripe" : "none",
27033
27169
  catalogRevision: catalog && !catalog.skipped ? previous + 1 : Math.max(previous, 1),
27034
- capabilities: plans.billing.provider === "stripe" ? { ...PLANS_CAPABILITIES, checkout: true } : PLANS_CAPABILITIES
27170
+ capabilities: billingOn ? { ...PLANS_CAPABILITIES, checkout: true } : PLANS_CAPABILITIES
27035
27171
  })
27036
27172
  );
27037
27173
  }
@@ -27190,7 +27326,7 @@ async function planPlansService(resolved, ledger, plansRemote2) {
27190
27326
  ),
27191
27327
  inputs.ownIdentity ? "sign-in: the project's identity will be applied to the plans backend" : "sign-in: no identity of the platform deployed, so nothing is applied to the plans backend",
27192
27328
  ...inputs.billingStep ? [
27193
- resolved.plans?.billing.provider === "stripe" ? "billing: the Stripe key and webhook secret of the project's secrets will be applied to the plans backend" : "billing: switched off, so the stored Stripe settings will be removed from the plans backend"
27329
+ inputs.billingOn ? "billing: a plan has a price, so the Stripe key and webhook secret of the project's secrets will be applied to the plans backend" : "billing: no plan has a price, so the stored Stripe settings will be removed from the plans backend"
27194
27330
  ] : [],
27195
27331
  ...steps.map(
27196
27332
  (name) => `step ${name}: ${stepState(stepOf(ledger, "plans", name), hashes[name])}`
@@ -27384,7 +27520,7 @@ async function planPortalService(resolved, ledger) {
27384
27520
  const hash2 = await portalBuildHash(resolved, prepared);
27385
27521
  const previous = stepOf(ledger, "portal", "build");
27386
27522
  const built = await exists5(
27387
- path77.join(resolved.state.buildDir, "index.html")
27523
+ path78.join(resolved.state.buildDir, "index.html")
27388
27524
  );
27389
27525
  if (previous?.status === "succeeded" && previous.hash === hash2 && built) {
27390
27526
  return {
@@ -27743,7 +27879,7 @@ async function statusV2(dir, only, options = {}) {
27743
27879
  if (service === "portal") {
27744
27880
  const build2 = stepOf(ledger, "portal", "build");
27745
27881
  const built = await exists5(
27746
- path77.join(resolved.state.buildDir, "index.html")
27882
+ path78.join(resolved.state.buildDir, "index.html")
27747
27883
  );
27748
27884
  const state = build2?.status === "failed" ? "build-failed" : build2?.status === "succeeded" && built ? "built" : "not-built";
27749
27885
  services.push({
@@ -27754,7 +27890,7 @@ async function statusV2(dir, only, options = {}) {
27754
27890
  ...build2?.finishedAt ? [`last build: ${build2.finishedAt}`] : [],
27755
27891
  ...build2?.status === "failed" ? [`error: ${build2.error ?? "unknown"}`] : [],
27756
27892
  ...state === "built" ? [
27757
- `output: ${path77.relative(resolved.configDir, resolved.state.buildDir)}`
27893
+ `output: ${path78.relative(resolved.configDir, resolved.state.buildDir)}`
27758
27894
  ] : [],
27759
27895
  ...url2 ? [`url: ${url2}`] : ["url: not known (portal is not deployed yet)"],
27760
27896
  ...await hostingLine(resolved, manifest, options.hostingRemote)
@@ -27821,7 +27957,7 @@ function formatStatus(output4) {
27821
27957
 
27822
27958
  // src/cli/deploy/static-web-config.ts
27823
27959
  import { access as access10, writeFile as writeFile22 } from "node:fs/promises";
27824
- import path78 from "node:path";
27960
+ import path79 from "node:path";
27825
27961
  var STATIC_WEB_CONFIG_FILE = "staticwebapp.config.json";
27826
27962
  var STATIC_WEB_CONFIG_VERSION = 3;
27827
27963
  var CLIENT_ONLY_ROUTES = [
@@ -27839,7 +27975,7 @@ var DEFAULT_STATIC_WEB_CONFIG = {
27839
27975
  }
27840
27976
  };
27841
27977
  async function ensureStaticWebConfig(buildDir) {
27842
- const file = path78.join(buildDir, STATIC_WEB_CONFIG_FILE);
27978
+ const file = path79.join(buildDir, STATIC_WEB_CONFIG_FILE);
27843
27979
  try {
27844
27980
  await access10(file);
27845
27981
  return false;
@@ -28190,7 +28326,7 @@ function formatDeploy(output4) {
28190
28326
  // src/cli/deploy/command.ts
28191
28327
  async function ensureLinkedProject(argv, deps) {
28192
28328
  if (argv.projectId?.trim() || deps.envProjectId?.trim()) return true;
28193
- const dir = path79.resolve(process.cwd(), argv.dir);
28329
+ const dir = path80.resolve(process.cwd(), argv.dir);
28194
28330
  let linked;
28195
28331
  try {
28196
28332
  linked = Boolean(loadConfigV2(dir).resolved.project.id);
@@ -28220,6 +28356,18 @@ async function deployProject(argv) {
28220
28356
  createProject: (dir) => createLinkedProject({ dir }, dir, { continuingDeploy: true })
28221
28357
  });
28222
28358
  if (!ready) return;
28359
+ try {
28360
+ await ensureStripeKey(argv, {
28361
+ client: () => new PlatformApiClient(loadConfig()),
28362
+ envProjectId: process.env.APITOGO_DEFAULT_PROJECT_ID,
28363
+ interactive,
28364
+ askHidden: (prompt) => readHidden(prompt),
28365
+ print: printDiagnosticsToConsole
28366
+ });
28367
+ } catch (error) {
28368
+ await reportFailure(error, json);
28369
+ return;
28370
+ }
28223
28371
  const quiet = json || argv.verbose !== true;
28224
28372
  if (quiet) applyJsonOnlyDeployQuietMode();
28225
28373
  if (!json && quiet)
@@ -28230,7 +28378,7 @@ async function deployProject(argv) {
28230
28378
  const config2 = loadConfig();
28231
28379
  const output4 = await deployV2(
28232
28380
  {
28233
- dir: path79.resolve(process.cwd(), argv.dir),
28381
+ dir: path80.resolve(process.cwd(), argv.dir),
28234
28382
  only: Array.isArray(argv.only) ? argv.only.join(",") : argv.only,
28235
28383
  projectId: argv.projectId,
28236
28384
  env: argv.env,
@@ -28250,23 +28398,26 @@ async function deployProject(argv) {
28250
28398
  for (const line2 of formatDeploy(output4)) printDiagnosticsToConsole(line2);
28251
28399
  printResultToConsole("Deploy finished.");
28252
28400
  } catch (error) {
28253
- const message = error instanceof Error ? error.message : String(error);
28254
- const hint = error instanceof ApitogoError ? error.developerHint : void 0;
28255
- if (json) {
28256
- console.log(
28257
- JSON.stringify({
28258
- status: "failed",
28259
- error: message,
28260
- hint: hint ?? null
28261
- })
28262
- );
28263
- process.exitCode = 1;
28264
- return;
28265
- }
28266
- await printCriticalFailureToConsoleAndExit(
28267
- hint ? `${message} ${hint}` : message
28401
+ await reportFailure(error, json);
28402
+ }
28403
+ }
28404
+ async function reportFailure(error, json) {
28405
+ const message = error instanceof Error ? error.message : String(error);
28406
+ const hint = error instanceof ApitogoError ? error.developerHint : void 0;
28407
+ if (json) {
28408
+ console.log(
28409
+ JSON.stringify({
28410
+ status: "failed",
28411
+ error: message,
28412
+ hint: hint ?? null
28413
+ })
28268
28414
  );
28415
+ process.exitCode = 1;
28416
+ return;
28269
28417
  }
28418
+ await printCriticalFailureToConsoleAndExit(
28419
+ hint ? `${message} ${hint}` : message
28420
+ );
28270
28421
  }
28271
28422
 
28272
28423
  // src/cli/cmds/deploy.ts
@@ -28321,7 +28472,7 @@ var deploy_default = {
28321
28472
  }),
28322
28473
  handler: async (argv) => {
28323
28474
  await captureEvent({ argv, event: "apitogo deploy" });
28324
- if (await isConfigV2(path80.resolve(process.cwd(), argv.dir))) {
28475
+ if (await isConfigV2(path81.resolve(process.cwd(), argv.dir))) {
28325
28476
  await deployProject(argv);
28326
28477
  return;
28327
28478
  }
@@ -28331,7 +28482,7 @@ var deploy_default = {
28331
28482
 
28332
28483
  // src/cli/export/handler.ts
28333
28484
  import { mkdir as mkdir18, writeFile as writeFile23 } from "node:fs/promises";
28334
- import path81 from "node:path";
28485
+ import path82 from "node:path";
28335
28486
  init_invariant();
28336
28487
  var ExportError = class extends ApitogoError {
28337
28488
  constructor(message, developerHint) {
@@ -28345,7 +28496,7 @@ async function exportOpenApi(args, deps) {
28345
28496
  `Cannot export "${args.target}".`,
28346
28497
  "Only `apitogo export openapi` exists."
28347
28498
  );
28348
- const dir = path81.resolve(process.cwd(), args.dir ?? ".");
28499
+ const dir = path82.resolve(process.cwd(), args.dir ?? ".");
28349
28500
  if (!await isConfigV2(dir))
28350
28501
  throw new ExportError(
28351
28502
  "This command needs a config version 2 project.",
@@ -28370,8 +28521,8 @@ async function exportOpenApi(args, deps) {
28370
28521
  throw error;
28371
28522
  }
28372
28523
  if (!args.out) return { text };
28373
- const file = path81.resolve(process.cwd(), args.out);
28374
- await mkdir18(path81.dirname(file), { recursive: true });
28524
+ const file = path82.resolve(process.cwd(), args.out);
28525
+ await mkdir18(path82.dirname(file), { recursive: true });
28375
28526
  await writeFile23(file, text);
28376
28527
  return { text, file };
28377
28528
  }
@@ -28464,8 +28615,8 @@ import open3 from "open";
28464
28615
  import colors14 from "picocolors";
28465
28616
 
28466
28617
  // src/cli/platform/device-auth.ts
28467
- function url(apiUrl, path95) {
28468
- return `${apiUrl}${path95.startsWith("/") ? path95 : `/${path95}`}`;
28618
+ function url(apiUrl, path96) {
28619
+ return `${apiUrl}${path96.startsWith("/") ? path96 : `/${path96}`}`;
28469
28620
  }
28470
28621
  async function requestDeviceCode(apiUrl) {
28471
28622
  const response = await fetchWithRetry(
@@ -28647,7 +28798,7 @@ var logout_default = {
28647
28798
  };
28648
28799
 
28649
28800
  // src/cli/portal/commands.ts
28650
- import path82 from "node:path";
28801
+ import path83 from "node:path";
28651
28802
  init_invariant();
28652
28803
  function parseOnly(value) {
28653
28804
  if (value === void 0) return void 0;
@@ -28664,7 +28815,7 @@ function parseOnly(value) {
28664
28815
  async function run2(command2, argv, compute, format) {
28665
28816
  const json = argv.json === true;
28666
28817
  try {
28667
- const dir = path82.resolve(process.cwd(), argv.dir ?? ".");
28818
+ const dir = path83.resolve(process.cwd(), argv.dir ?? ".");
28668
28819
  if (!await isConfigV2(dir)) {
28669
28820
  throw new ServiceEditError(
28670
28821
  `${command2} works with a config version 2 project, and there is none in ${dir}.`,
@@ -28880,14 +29031,14 @@ var remove_default = {
28880
29031
 
28881
29032
  // src/cli/dev/handler.ts
28882
29033
  init_joinUrl();
28883
- import path85 from "node:path";
29034
+ import path86 from "node:path";
28884
29035
 
28885
29036
  // src/vite/dev-server.ts
28886
29037
  init_logger();
28887
29038
  import fs11 from "node:fs/promises";
28888
29039
  import http from "node:http";
28889
29040
  import https from "node:https";
28890
- import path84 from "node:path";
29041
+ import path85 from "node:path";
28891
29042
  import { createHttpTerminator } from "http-terminator";
28892
29043
  import {
28893
29044
  createServer as createViteServer,
@@ -28920,7 +29071,7 @@ init_loader();
28920
29071
  // src/vite/pagefind-dev-index.ts
28921
29072
  init_invariant();
28922
29073
  init_joinUrl();
28923
- import path83 from "node:path";
29074
+ import path84 from "node:path";
28924
29075
  import { createIndex as createIndex2 } from "pagefind";
28925
29076
  import { isRunnableDevEnvironment } from "vite";
28926
29077
  async function* buildPagefindDevIndex(vite, config2) {
@@ -28973,7 +29124,7 @@ async function* buildPagefindDevIndex(vite, config2) {
28973
29124
  path: urlPath
28974
29125
  };
28975
29126
  }
28976
- const outputPath = path83.join(vite.config.publicDir, "pagefind");
29127
+ const outputPath = path84.join(vite.config.publicDir, "pagefind");
28977
29128
  await pagefindIndex.writeFiles({ outputPath });
28978
29129
  yield { type: "complete", success: true, indexed };
28979
29130
  }
@@ -28992,9 +29143,9 @@ var DevServer = class {
28992
29143
  this.protocol = "https";
28993
29144
  const { dir } = this.options;
28994
29145
  const [key, cert, ca] = await Promise.all([
28995
- fs11.readFile(path84.resolve(dir, config2.https.key)),
28996
- fs11.readFile(path84.resolve(dir, config2.https.cert)),
28997
- config2.https.ca ? fs11.readFile(path84.resolve(dir, config2.https.ca)) : void 0
29146
+ fs11.readFile(path85.resolve(dir, config2.https.key)),
29147
+ fs11.readFile(path85.resolve(dir, config2.https.cert)),
29148
+ config2.https.ca ? fs11.readFile(path85.resolve(dir, config2.https.ca)) : void 0
28998
29149
  ]);
28999
29150
  return https.createServer({ key, cert, ca });
29000
29151
  }
@@ -29011,7 +29162,7 @@ var DevServer = class {
29011
29162
  );
29012
29163
  const server = await this.createNodeServer(config2);
29013
29164
  if (config2.search?.type === "pagefind" && viteConfig.publicDir !== false) {
29014
- const publicRoot = path84.resolve(
29165
+ const publicRoot = path85.resolve(
29015
29166
  this.options.dir,
29016
29167
  typeof viteConfig.publicDir === "string" ? viteConfig.publicDir : "public"
29017
29168
  );
@@ -29028,7 +29179,7 @@ var DevServer = class {
29028
29179
  // built-in transform middleware which would treat the path as a static asset.
29029
29180
  name: "apitogo:entry-client",
29030
29181
  configureServer(server2) {
29031
- const entryPath = path84.posix.join(
29182
+ const entryPath = path85.posix.join(
29032
29183
  server2.config.base,
29033
29184
  "/__z/entry.client.tsx"
29034
29185
  );
@@ -29193,7 +29344,7 @@ init_package_json();
29193
29344
  async function dev(argv) {
29194
29345
  const packageJson3 = getZudokuPackageJson();
29195
29346
  process.env.NODE_ENV = "development";
29196
- let dir = path85.resolve(process.cwd(), argv.dir);
29347
+ let dir = path86.resolve(process.cwd(), argv.dir);
29197
29348
  if (await isConfigV2(dir)) {
29198
29349
  const { root, prepared } = await preparePortalWorkspace(dir, "development");
29199
29350
  dir = root;
@@ -29292,61 +29443,8 @@ var run_default = {
29292
29443
  };
29293
29444
 
29294
29445
  // src/cli/secrets/handler.ts
29295
- import path86 from "node:path";
29446
+ import path87 from "node:path";
29296
29447
  init_invariant();
29297
-
29298
- // src/cli/secrets/input.ts
29299
- import { createInterface as createInterface6 } from "node:readline/promises";
29300
- var CTRL_C = "";
29301
- var CTRL_D = "";
29302
- function readHidden(prompt, input5 = process.stdin, output4 = process.stdout) {
29303
- return new Promise((resolve, reject) => {
29304
- if (!input5.isTTY || !input5.setRawMode) {
29305
- reject(new Error("A hidden prompt needs an interactive terminal."));
29306
- return;
29307
- }
29308
- let value = "";
29309
- output4.write(prompt);
29310
- input5.setRawMode(true);
29311
- input5.resume();
29312
- const finish = (action) => {
29313
- input5.removeListener("data", onData);
29314
- input5.setRawMode?.(false);
29315
- input5.pause();
29316
- output4.write("\n");
29317
- action();
29318
- };
29319
- const onData = (chunk) => {
29320
- for (const char of chunk.toString("utf8")) {
29321
- if (char === CTRL_C)
29322
- return finish(() => reject(new Error("Cancelled.")));
29323
- if (char === "\r" || char === "\n" || char === CTRL_D)
29324
- return finish(() => resolve(value));
29325
- if (char === "\x7F" || char === "\b") value = value.slice(0, -1);
29326
- else value += char;
29327
- }
29328
- };
29329
- input5.on("data", onData);
29330
- });
29331
- }
29332
- async function readStdin(input5 = process.stdin) {
29333
- const chunks = [];
29334
- for await (const chunk of input5) {
29335
- chunks.push(typeof chunk === "string" ? Buffer.from(chunk) : chunk);
29336
- }
29337
- return Buffer.concat(chunks).toString("utf8").replace(/\r?\n$/, "");
29338
- }
29339
- async function confirm(question) {
29340
- const rl = createInterface6({ input: process.stdin, output: process.stdout });
29341
- try {
29342
- const answer = (await rl.question(question)).trim().toLowerCase();
29343
- return answer === "y" || answer === "yes";
29344
- } finally {
29345
- rl.close();
29346
- }
29347
- }
29348
-
29349
- // src/cli/secrets/handler.ts
29350
29448
  var SECRET_NAME = /^[A-Za-z_][A-Za-z0-9_]{0,127}$/;
29351
29449
  var SecretsError = class extends ApitogoError {
29352
29450
  constructor(message, developerHint) {
@@ -29357,7 +29455,7 @@ var SecretsError = class extends ApitogoError {
29357
29455
  async function resolveProjectId2(argv, deps) {
29358
29456
  const explicit = argv.projectId?.trim();
29359
29457
  if (explicit) return explicit;
29360
- const dir = path86.resolve(process.cwd(), argv.dir ?? ".");
29458
+ const dir = path87.resolve(process.cwd(), argv.dir ?? ".");
29361
29459
  if (await isConfigV2(dir)) {
29362
29460
  const id = loadConfigV2(dir).resolved.project.id;
29363
29461
  if (id) return id;
@@ -29517,12 +29615,12 @@ var secrets_default = {
29517
29615
  };
29518
29616
 
29519
29617
  // src/cli/cmds/status.ts
29520
- import path88 from "node:path";
29618
+ import path89 from "node:path";
29521
29619
 
29522
29620
  // src/cli/status/handler.ts
29523
- import path87 from "node:path";
29621
+ import path88 from "node:path";
29524
29622
  async function status(argv) {
29525
- const sourceDirectory = path87.resolve(process.cwd(), argv.dir);
29623
+ const sourceDirectory = path88.resolve(process.cwd(), argv.dir);
29526
29624
  let config2;
29527
29625
  try {
29528
29626
  config2 = loadConfig();
@@ -29620,7 +29718,7 @@ var status_default = {
29620
29718
  }),
29621
29719
  handler: async (argv) => {
29622
29720
  await captureEvent({ argv, event: "apitogo status" });
29623
- if (await isConfigV2(path88.resolve(process.cwd(), argv.dir))) {
29721
+ if (await isConfigV2(path89.resolve(process.cwd(), argv.dir))) {
29624
29722
  await statusOfProject(argv);
29625
29723
  return;
29626
29724
  }
@@ -29629,11 +29727,11 @@ var status_default = {
29629
29727
  };
29630
29728
 
29631
29729
  // src/cli/cmds/teardown.ts
29632
- import path91 from "node:path";
29730
+ import path92 from "node:path";
29633
29731
 
29634
29732
  // src/cli/teardown/command.ts
29635
29733
  init_invariant();
29636
- import path89 from "node:path";
29734
+ import path90 from "node:path";
29637
29735
  import { createInterface as createInterface7 } from "node:readline/promises";
29638
29736
 
29639
29737
  // src/cli/teardown/v2.ts
@@ -29994,7 +30092,7 @@ async function teardownProject(argv) {
29994
30092
  const progress = createConsoleProgressReporter();
29995
30093
  const result = await teardownV2(
29996
30094
  {
29997
- dir: path89.resolve(process.cwd(), argv.dir),
30095
+ dir: path90.resolve(process.cwd(), argv.dir),
29998
30096
  projectId: argv.projectId,
29999
30097
  only: Array.isArray(argv.only) ? argv.only.join(",") : argv.only,
30000
30098
  yes: argv.yes === true,
@@ -30053,7 +30151,7 @@ async function teardownProject(argv) {
30053
30151
  }
30054
30152
 
30055
30153
  // src/cli/teardown/handler.ts
30056
- import path90 from "node:path";
30154
+ import path91 from "node:path";
30057
30155
  function resultLooksFailed3(payload) {
30058
30156
  try {
30059
30157
  const parsed = JSON.parse(payload);
@@ -30069,7 +30167,7 @@ async function teardown(argv) {
30069
30167
  );
30070
30168
  return;
30071
30169
  }
30072
- const sourceDirectory = path90.resolve(process.cwd(), argv.dir);
30170
+ const sourceDirectory = path91.resolve(process.cwd(), argv.dir);
30073
30171
  let config2;
30074
30172
  try {
30075
30173
  config2 = loadConfig();
@@ -30147,7 +30245,7 @@ var teardown_default = {
30147
30245
  }),
30148
30246
  handler: async (argv) => {
30149
30247
  await captureEvent({ argv, event: "apitogo teardown" });
30150
- if (await isConfigV2(path91.resolve(process.cwd(), argv.dir))) {
30248
+ if (await isConfigV2(path92.resolve(process.cwd(), argv.dir))) {
30151
30249
  await teardownProject(argv);
30152
30250
  return;
30153
30251
  }
@@ -30157,7 +30255,7 @@ var teardown_default = {
30157
30255
 
30158
30256
  // src/cli/teardown/reset-command.ts
30159
30257
  init_invariant();
30160
- import path92 from "node:path";
30258
+ import path93 from "node:path";
30161
30259
 
30162
30260
  // src/cli/teardown/reset.ts
30163
30261
  init_invariant();
@@ -30280,7 +30378,7 @@ async function resetProject(argv) {
30280
30378
  const config2 = loadConfig();
30281
30379
  const result = await resetProjectV2(
30282
30380
  {
30283
- dir: path92.resolve(process.cwd(), argv.dir),
30381
+ dir: path93.resolve(process.cwd(), argv.dir),
30284
30382
  projectId: argv.projectId,
30285
30383
  yes: argv.yes === true
30286
30384
  },
@@ -30344,7 +30442,7 @@ var test_reset_default = {
30344
30442
  };
30345
30443
 
30346
30444
  // src/cli/test-sign-in/handler.ts
30347
- import path93 from "node:path";
30445
+ import path94 from "node:path";
30348
30446
  init_invariant();
30349
30447
  var TestSignInError = class extends ApitogoError {
30350
30448
  constructor(message, developerHint) {
@@ -30355,7 +30453,7 @@ var TestSignInError = class extends ApitogoError {
30355
30453
  async function resolveProjectId3(argv) {
30356
30454
  const explicit = argv.projectId?.trim();
30357
30455
  if (explicit) return explicit;
30358
- const dir = path93.resolve(process.cwd(), argv.dir ?? ".");
30456
+ const dir = path94.resolve(process.cwd(), argv.dir ?? ".");
30359
30457
  if (await isConfigV2(dir)) {
30360
30458
  const id = loadConfigV2(dir).resolved.project.id;
30361
30459
  if (id) return id;
@@ -30495,36 +30593,36 @@ function createApitogoCli(argv) {
30495
30593
  // src/cli/local-handoff.ts
30496
30594
  import { spawn as spawn4 } from "node:child_process";
30497
30595
  import { existsSync as existsSync8, realpathSync as realpathSync2 } from "node:fs";
30498
- import path94 from "node:path";
30596
+ import path95 from "node:path";
30499
30597
  var LOCAL_HANDOFF_ENV = "APITOGO_LOCAL_CLI";
30500
30598
  var PACKAGE_NAME = "@lukoweb/apitogo";
30501
30599
  var resolveProjectDir = (argv, cwd) => {
30502
30600
  for (let i = 0; i < argv.length; i++) {
30503
30601
  const arg = argv[i];
30504
30602
  const next = argv[i + 1];
30505
- if (arg === "--dir" && next) return path94.resolve(cwd, next);
30603
+ if (arg === "--dir" && next) return path95.resolve(cwd, next);
30506
30604
  if (arg?.startsWith("--dir=")) {
30507
- return path94.resolve(cwd, arg.slice("--dir=".length));
30605
+ return path95.resolve(cwd, arg.slice("--dir=".length));
30508
30606
  }
30509
30607
  }
30510
30608
  return cwd;
30511
30609
  };
30512
30610
  var findLocalCli = (projectDir, runningRoot) => {
30513
- let dir = path94.resolve(projectDir);
30611
+ let dir = path95.resolve(projectDir);
30514
30612
  let localPackageRoot;
30515
30613
  while (!localPackageRoot) {
30516
- const candidate = path94.join(dir, "node_modules", PACKAGE_NAME);
30517
- if (existsSync8(path94.join(candidate, "package.json"))) {
30614
+ const candidate = path95.join(dir, "node_modules", PACKAGE_NAME);
30615
+ if (existsSync8(path95.join(candidate, "package.json"))) {
30518
30616
  localPackageRoot = candidate;
30519
- } else if (dir === path94.dirname(dir)) {
30617
+ } else if (dir === path95.dirname(dir)) {
30520
30618
  return void 0;
30521
30619
  } else {
30522
- dir = path94.dirname(dir);
30620
+ dir = path95.dirname(dir);
30523
30621
  }
30524
30622
  }
30525
30623
  const localRoot = realpathSync2(localPackageRoot);
30526
30624
  if (localRoot === realpathSync2(runningRoot)) return void 0;
30527
- return path94.join(localRoot, "cli.js");
30625
+ return path95.join(localRoot, "cli.js");
30528
30626
  };
30529
30627
  var handoffToLocalCli = async (argv, runningRoot) => {
30530
30628
  if (process.env[LOCAL_HANDOFF_ENV] || process.env.ZUDOKU_ENV === "internal") {
@@ -30536,7 +30634,7 @@ var handoffToLocalCli = async (argv, runningRoot) => {
30536
30634
  );
30537
30635
  if (!localCli) return void 0;
30538
30636
  printDiagnosticsToConsole(
30539
- `Using the project's local ${PACKAGE_NAME} (${path94.dirname(localCli)})`
30637
+ `Using the project's local ${PACKAGE_NAME} (${path95.dirname(localCli)})`
30540
30638
  );
30541
30639
  const child = spawn4(process.execPath, [localCli, ...argv], {
30542
30640
  stdio: "inherit",