@lukoweb/apitogo 0.1.118 → 0.1.120

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
@@ -5067,12 +5067,12 @@ async function loadZudokuConfigWithMeta(rootDir) {
5067
5067
  };
5068
5068
  return configWithMetadata;
5069
5069
  }
5070
- function findOutputPathOfServerConfig(output3) {
5071
- if (Array.isArray(output3)) {
5070
+ function findOutputPathOfServerConfig(output4) {
5071
+ if (Array.isArray(output4)) {
5072
5072
  throw new Error("Expected a single output, but got an array");
5073
5073
  }
5074
- if ("output" in output3) {
5075
- const result = output3.output.find(
5074
+ if ("output" in output4) {
5075
+ const result = output4.output.find(
5076
5076
  (o) => "isEntry" in o && o.isEntry && serverConfigOutputFiles.includes(o.fileName)
5077
5077
  );
5078
5078
  if (result) {
@@ -5802,7 +5802,7 @@ import {
5802
5802
  // package.json
5803
5803
  var package_default = {
5804
5804
  name: "@lukoweb/apitogo",
5805
- version: "0.1.118",
5805
+ version: "0.1.120",
5806
5806
  type: "module",
5807
5807
  sideEffects: [
5808
5808
  "**/*.css",
@@ -7633,13 +7633,13 @@ var generateCode = (schema2, filePath) => {
7633
7633
 
7634
7634
  // src/vite/api/SchemaManager.ts
7635
7635
  var FALLBACK_VERSION = "default";
7636
- var parseInputString = (input4) => {
7637
- const idx = input4.indexOf("?");
7636
+ var parseInputString = (input5) => {
7637
+ const idx = input5.indexOf("?");
7638
7638
  if (idx === -1)
7639
- return { filePath: input4, params: {} };
7639
+ return { filePath: input5, params: {} };
7640
7640
  return {
7641
- filePath: input4.slice(0, idx),
7642
- params: Object.fromEntries(new URLSearchParams(input4.slice(idx)))
7641
+ filePath: input5.slice(0, idx),
7642
+ params: Object.fromEntries(new URLSearchParams(input5.slice(idx)))
7643
7643
  };
7644
7644
  };
7645
7645
  var canonicalParams = (params) => Object.entries(params).sort(([a], [b]) => a.localeCompare(b)).map(([k, v]) => `${k}=${v}`).join("&");
@@ -7675,8 +7675,8 @@ var SchemaManager = class {
7675
7675
  ...processors
7676
7676
  ];
7677
7677
  }
7678
- getPathForFile = (input4, params) => {
7679
- const filePath = path11.resolve(this.config.__meta.rootDir, input4);
7678
+ getPathForFile = (input5, params) => {
7679
+ const filePath = path11.resolve(this.config.__meta.rootDir, input5);
7680
7680
  const apis = ensureArray(this.config.apis ?? []);
7681
7681
  for (const apiConfig of apis) {
7682
7682
  if (!apiConfig || apiConfig.type !== "file" || !apiConfig.path) continue;
@@ -7686,12 +7686,12 @@ var SchemaManager = class {
7686
7686
  if (match) return apiConfig.path;
7687
7687
  }
7688
7688
  };
7689
- processSchema = async (input4) => {
7690
- const filePath = path11.resolve(this.config.__meta.rootDir, input4.input);
7691
- const params = input4.params;
7692
- const configuredPath = this.getPathForFile(input4.input, params);
7689
+ processSchema = async (input5) => {
7690
+ const filePath = path11.resolve(this.config.__meta.rootDir, input5.input);
7691
+ const params = input5.params;
7692
+ const configuredPath = this.getPathForFile(input5.input, params);
7693
7693
  if (!configuredPath) {
7694
- console.warn(`No path found for file ${input4.input}`);
7694
+ console.warn(`No path found for file ${input5.input}`);
7695
7695
  return;
7696
7696
  }
7697
7697
  const parser = new $RefParser2();
@@ -7798,20 +7798,20 @@ var SchemaManager = class {
7798
7798
  if (apiConfig.type !== "file" || !apiConfig.path) continue;
7799
7799
  const inputs = normalizeInputs(apiConfig.input);
7800
7800
  if (inputs.length === 0) throw new Error("No schema found");
7801
- this.processedSchemas[apiConfig.path] = inputs.map((input4) => ({
7801
+ this.processedSchemas[apiConfig.path] = inputs.map((input5) => ({
7802
7802
  schema: {},
7803
7803
  version: "",
7804
- path: input4.path ?? "",
7805
- label: input4.label,
7806
- inputPath: path11.resolve(this.config.__meta.rootDir, input4.input),
7807
- params: input4.params,
7804
+ path: input5.path ?? "",
7805
+ label: input5.label,
7806
+ inputPath: path11.resolve(this.config.__meta.rootDir, input5.input),
7807
+ params: input5.params,
7808
7808
  importKey: "",
7809
7809
  downloadUrl: "",
7810
7810
  processedJsonPath: "",
7811
7811
  processedTime: 0
7812
7812
  }));
7813
7813
  const results = await Promise.allSettled(
7814
- inputs.map((input4) => this.processSchema(input4))
7814
+ inputs.map((input5) => this.processSchema(input5))
7815
7815
  );
7816
7816
  const errors = results.flatMap(
7817
7817
  (r) => r.status === "rejected" ? r.reason : []
@@ -7890,7 +7890,7 @@ import { readFile as readFile2 } from "node:fs/promises";
7890
7890
  import matter from "gray-matter";
7891
7891
  import { parse, stringify as stringify2 } from "yaml";
7892
7892
  var yaml = {
7893
- parse: (input4) => parse(input4) ?? {},
7893
+ parse: (input5) => parse(input5) ?? {},
7894
7894
  stringify: (obj) => stringify2(obj)
7895
7895
  };
7896
7896
  var readFrontmatter = async (filePath) => {
@@ -10163,25 +10163,25 @@ function generateOutput({
10163
10163
  });
10164
10164
  }
10165
10165
  }
10166
- const output3 = {
10166
+ const output4 = {
10167
10167
  version: 3,
10168
10168
  framework: {
10169
10169
  version: pkgJson.version
10170
10170
  },
10171
10171
  routes
10172
10172
  };
10173
- return output3;
10173
+ return output4;
10174
10174
  }
10175
10175
  async function writeOutput(dir, {
10176
10176
  config: config2,
10177
10177
  redirects,
10178
10178
  rewrites
10179
10179
  }) {
10180
- const output3 = generateOutput({ config: config2, redirects, rewrites });
10180
+ const output4 = generateOutput({ config: config2, redirects, rewrites });
10181
10181
  const outputDir = process.env.VERCEL ? path27.join(dir, ".vercel/output") : path27.join(dir, "dist/.output");
10182
10182
  await mkdir3(outputDir, { recursive: true });
10183
10183
  const outputFile = path27.join(outputDir, "config.json");
10184
- await writeFile4(outputFile, JSON.stringify(output3, null, 2), "utf-8");
10184
+ await writeFile4(outputFile, JSON.stringify(output4, null, 2), "utf-8");
10185
10185
  if (process.env.VERCEL) {
10186
10186
  console.log("Wrote Vercel output to", outputDir);
10187
10187
  }
@@ -10202,12 +10202,12 @@ init_invariant();
10202
10202
  init_joinUrl();
10203
10203
 
10204
10204
  // src/vite/reporter.ts
10205
- function writeLine(output3) {
10205
+ function writeLine(output4) {
10206
10206
  clearLine();
10207
- if (output3.length < process.stdout.columns) {
10208
- process.stdout.write(output3);
10207
+ if (output4.length < process.stdout.columns) {
10208
+ process.stdout.write(output4);
10209
10209
  } else {
10210
- process.stdout.write(output3.substring(0, process.stdout.columns - 1));
10210
+ process.stdout.write(output4.substring(0, process.stdout.columns - 1));
10211
10211
  }
10212
10212
  }
10213
10213
  function isTTY() {
@@ -10877,7 +10877,7 @@ function isNetworkError(error) {
10877
10877
  const message = error.message.toLowerCase();
10878
10878
  return message.includes("fetch failed") || message.includes("network") || message.includes("econnreset") || message.includes("etimedout") || message.includes("socket hang up") || error.name === "AbortError" || error.name === "TypeError";
10879
10879
  }
10880
- async function fetchWithRetry(input4, init4, options) {
10880
+ async function fetchWithRetry(input5, init4, options) {
10881
10881
  const maxRetries = options?.maxRetries ?? 3;
10882
10882
  const baseDelayMs = options?.baseDelayMs ?? 1e3;
10883
10883
  const maxDelayMs = options?.maxDelayMs ?? 15e3;
@@ -10887,7 +10887,7 @@ async function fetchWithRetry(input4, init4, options) {
10887
10887
  let attempt = 0;
10888
10888
  while (true) {
10889
10889
  try {
10890
- const response = await fetch(input4, init4);
10890
+ const response = await fetch(input5, init4);
10891
10891
  const retriableStatus = isRetriableHttpStatus(response.status);
10892
10892
  const mayRetryStatus = retriableStatus && (isSafeMethod || response.status === 429 || retryMutatingOnServerError);
10893
10893
  if (mayRetryStatus && attempt < maxRetries) {
@@ -11432,9 +11432,7 @@ function loadConfig() {
11432
11432
  apiSecret = stored?.apiSecret;
11433
11433
  }
11434
11434
  if (!apiKeyId || !apiSecret) {
11435
- throw new Error(
11436
- "No credentials found. Run `apitogo login`, or set APITOGO_API_KEY_ID and APITOGO_API_SECRET for CI."
11437
- );
11435
+ throw new Error("No credentials found. Run `apitogo login`.");
11438
11436
  }
11439
11437
  const defaultProjectId = process.env.APITOGO_DEFAULT_PROJECT_ID?.trim() || void 0;
11440
11438
  const devPortalDeployUrl = process.env.APITOGO_DEV_PORTAL_DEPLOY_URL?.trim() || process.env.APITOGO_DOC_DEPLOY_URL?.trim() || DEFAULT_DEV_PORTAL_DEPLOY_URL;
@@ -11558,6 +11556,7 @@ function formatDevPortalBackendResponse(projectId, backend) {
11558
11556
  }
11559
11557
 
11560
11558
  // src/cli/platform/helpers/dev-portal-manifest.ts
11559
+ init_gateway_limits();
11561
11560
  import { existsSync as existsSync4 } from "node:fs";
11562
11561
  import { access, mkdir as mkdir6, readFile as readFile6, writeFile as writeFile7 } from "node:fs/promises";
11563
11562
  import path36 from "node:path";
@@ -11613,7 +11612,6 @@ function readLandingFromConfig(config2) {
11613
11612
  }
11614
11613
 
11615
11614
  // src/cli/platform/helpers/dev-portal-manifest.ts
11616
- init_gateway_limits();
11617
11615
  var APITOGO_CONFIG_FILENAME2 = "apitogo.config.json";
11618
11616
  var PLACEHOLDER_DEV_PORTAL_API_URL = "https://dp-example.azurecontainerapps.io";
11619
11617
  function isPlanFree(plan) {
@@ -11810,7 +11808,9 @@ function applyManifestToConfig(config2, manifest) {
11810
11808
  for (const key of STORED_MANIFEST_KEYS) {
11811
11809
  const value = manifest[key];
11812
11810
  if (value !== void 0) {
11813
- next[key] = key === "gateway" ? gatewayForConfig(value) : value;
11811
+ next[key] = key === "gateway" ? gatewayForConfig(
11812
+ value
11813
+ ) : value;
11814
11814
  } else {
11815
11815
  delete next[key];
11816
11816
  }
@@ -11824,7 +11824,9 @@ function applyManifestToConfig(config2, manifest) {
11824
11824
  delete next.plans;
11825
11825
  delete next.pricing;
11826
11826
  if (next.authentication && typeof next.authentication === "object") {
11827
- const authentication = { ...next.authentication };
11827
+ const authentication = {
11828
+ ...next.authentication
11829
+ };
11828
11830
  delete authentication.gateway;
11829
11831
  next.authentication = authentication;
11830
11832
  }
@@ -11863,7 +11865,7 @@ var DEFAULT_PLANS = [
11863
11865
  displayName: "Free",
11864
11866
  priceAmount: 0,
11865
11867
  limits: {
11866
- rateLimit: { calls: 10, renewalPeriodSeconds: 60 }
11868
+ rateLimit: { calls: 3, renewalPeriodSeconds: 60 }
11867
11869
  },
11868
11870
  includedActionKeys: []
11869
11871
  },
@@ -12034,7 +12036,9 @@ function resolveManifestNextStep(manifest, secrets) {
12034
12036
  if (!secrets) {
12035
12037
  return "Continue shaping the site in the running local preview. For publish, save auth secrets (OIDC provider secrets, and Stripe if paid plans) in .env using APITOGO_CONFIG_AUTHENTICATION__* keys \u2014 never in chat. Native auth email is always platform-managed.";
12036
12038
  }
12037
- const secretFields = prerequisites.missingFields.filter((field) => field !== "plans");
12039
+ const secretFields = prerequisites.missingFields.filter(
12040
+ (field) => field !== "plans"
12041
+ );
12038
12042
  if (secretFields.length > 0) {
12039
12043
  return `Publish prerequisites incomplete in .env: ${secretFields.join(", ")}.`;
12040
12044
  }
@@ -12055,7 +12059,10 @@ function applyScaffoldManifestPolicy(manifest, options) {
12055
12059
  const result = {
12056
12060
  ...manifest,
12057
12061
  siteName: title,
12058
- branding: { ...manifest.branding, title: manifest.branding?.title ?? title }
12062
+ branding: {
12063
+ ...manifest.branding,
12064
+ title: manifest.branding?.title ?? title
12065
+ }
12059
12066
  };
12060
12067
  if (options.includeDefaultPlans) {
12061
12068
  result.plans = mergePlansBySku2(result.plans, DEFAULT_PLANS);
@@ -12096,7 +12103,10 @@ function resolveBackendOpenApiPath(openApiPath, options) {
12096
12103
  return siteResolved;
12097
12104
  }
12098
12105
  function toSiteRelativeOpenApiInput(siteDirectory, openApiAbsolutePath) {
12099
- const relative = path36.relative(path36.resolve(siteDirectory), path36.resolve(openApiAbsolutePath));
12106
+ const relative = path36.relative(
12107
+ path36.resolve(siteDirectory),
12108
+ path36.resolve(openApiAbsolutePath)
12109
+ );
12100
12110
  const posix = relative.split(path36.sep).join("/");
12101
12111
  if (!posix || posix === ".") {
12102
12112
  return "./";
@@ -12154,7 +12164,11 @@ async function writeManifest(siteDirectory, manifest) {
12154
12164
  const raw = await readFile6(filePath, "utf8");
12155
12165
  config2 = JSON.parse(raw);
12156
12166
  }
12157
- const { oidc: _oidc, stripe: _stripe, ...safeManifest } = manifest;
12167
+ const {
12168
+ oidc: _oidc,
12169
+ stripe: _stripe,
12170
+ ...safeManifest
12171
+ } = manifest;
12158
12172
  config2 = applyManifestToConfig(config2, safeManifest);
12159
12173
  await writeFile7(filePath, `${JSON.stringify(config2, null, 2)}
12160
12174
  `, "utf8");
@@ -12763,15 +12777,15 @@ function runCommand(command2, args, cwd, env) {
12763
12777
  env: env ? { ...process.env, ...env } : process.env,
12764
12778
  shell: process.platform === "win32"
12765
12779
  });
12766
- let output3 = "";
12780
+ let output4 = "";
12767
12781
  child.stdout.on("data", (chunk) => {
12768
- output3 += chunk.toString();
12782
+ output4 += chunk.toString();
12769
12783
  });
12770
12784
  child.stderr.on("data", (chunk) => {
12771
- output3 += chunk.toString();
12785
+ output4 += chunk.toString();
12772
12786
  });
12773
12787
  child.on("error", reject);
12774
- child.on("close", (exitCode) => resolve({ exitCode, output: output3 }));
12788
+ child.on("close", (exitCode) => resolve({ exitCode, output: output4 }));
12775
12789
  });
12776
12790
  }
12777
12791
  async function runNpmInstall(sourceDirectory, options) {
@@ -14428,15 +14442,15 @@ function runCommand2(command2, args, cwd) {
14428
14442
  env: process.env,
14429
14443
  shell: process.platform === "win32"
14430
14444
  });
14431
- let output3 = "";
14445
+ let output4 = "";
14432
14446
  child.stdout.on("data", (chunk) => {
14433
- output3 += chunk.toString();
14447
+ output4 += chunk.toString();
14434
14448
  });
14435
14449
  child.stderr.on("data", (chunk) => {
14436
- output3 += chunk.toString();
14450
+ output4 += chunk.toString();
14437
14451
  });
14438
14452
  child.on("error", reject);
14439
- child.on("close", (exitCode) => resolve({ exitCode, output: output3 }));
14453
+ child.on("close", (exitCode) => resolve({ exitCode, output: output4 }));
14440
14454
  });
14441
14455
  }
14442
14456
  function buildCreateApitogoCliArgs(invocationArgs, projectName, options) {
@@ -14622,10 +14636,10 @@ function patchApisConfig(content, config2) {
14622
14636
  const asArray = match[0].includes("apis: [");
14623
14637
  return content.replace(APIS_CONFIG_PATTERN, renderApisConfig(config2, asArray));
14624
14638
  }
14625
- function patchApitogoConfigOpenApiToFile(content, input4 = "./openapi.json") {
14639
+ function patchApitogoConfigOpenApiToFile(content, input5 = "./openapi.json") {
14626
14640
  return patchApisConfig(content, {
14627
14641
  type: "file",
14628
- input: input4,
14642
+ input: input5,
14629
14643
  path: "api"
14630
14644
  });
14631
14645
  }
@@ -14837,7 +14851,7 @@ async function discoverOpenApiByMode(siteDirectory, mode) {
14837
14851
  async function wireDevPortalOpenApi(sourceDirectory, openApiPath) {
14838
14852
  const resolvedSource = path47.resolve(sourceDirectory);
14839
14853
  const resolvedOpenApi = path47.resolve(openApiPath);
14840
- const input4 = toSiteRelativeOpenApiInput(resolvedSource, resolvedOpenApi);
14854
+ const input5 = toSiteRelativeOpenApiInput(resolvedSource, resolvedOpenApi);
14841
14855
  const validation = await validateOpenApiFile(resolvedOpenApi);
14842
14856
  if (!validation.valid) {
14843
14857
  return {
@@ -14851,7 +14865,7 @@ async function wireDevPortalOpenApi(sourceDirectory, openApiPath) {
14851
14865
  const raw = await readFile12(jsonConfigPath, "utf8");
14852
14866
  const siteConfig = JSON.parse(raw);
14853
14867
  const updated = applyOpenApiToSiteConfig(siteConfig, {
14854
- input: input4,
14868
+ input: input5,
14855
14869
  apiPath: "api"
14856
14870
  });
14857
14871
  await writeFile11(
@@ -14864,7 +14878,7 @@ async function wireDevPortalOpenApi(sourceDirectory, openApiPath) {
14864
14878
  const tsxConfigPath = path47.join(resolvedSource, "apitogo.config.tsx");
14865
14879
  const configContent = await readFile12(tsxConfigPath, "utf8");
14866
14880
  try {
14867
- const patched = patchApitogoConfigOpenApiToFile(configContent, input4);
14881
+ const patched = patchApitogoConfigOpenApiToFile(configContent, input5);
14868
14882
  await writeFile11(tsxConfigPath, patched);
14869
14883
  } catch (error) {
14870
14884
  return {
@@ -14877,13 +14891,13 @@ async function wireDevPortalOpenApi(sourceDirectory, openApiPath) {
14877
14891
  const manifest = await readManifest(resolvedSource) ?? createScaffoldManifest();
14878
14892
  manifest.backend = {
14879
14893
  ...manifest.backend,
14880
- openApiPath: input4
14894
+ openApiPath: input5
14881
14895
  };
14882
14896
  await writeManifest(resolvedSource, manifest);
14883
14897
  return {
14884
14898
  status: "succeeded",
14885
14899
  openApiPath: resolvedOpenApi,
14886
- openApiInput: input4,
14900
+ openApiInput: input5,
14887
14901
  openApiTitle: validation.title ?? null,
14888
14902
  openApiVersion: validation.version
14889
14903
  };
@@ -15133,7 +15147,7 @@ function createDefaultApitogoJsonConfig(options) {
15133
15147
  priceAmount: 0,
15134
15148
  billingPeriod: "month",
15135
15149
  limits: {
15136
- rateLimit: { calls: 10, renewalPeriodSeconds: 1 },
15150
+ rateLimit: { calls: 3, renewalPeriodSeconds: 60 },
15137
15151
  quota: { calls: 1e3, renewalPeriodDays: 30 }
15138
15152
  }
15139
15153
  },
@@ -15304,12 +15318,12 @@ function mergeDocumentationNavigation(navigation, docRoutePaths, options) {
15304
15318
  }
15305
15319
  return [documentation, ...rest];
15306
15320
  }
15307
- function applyOpenApiToJsonConfig(config2, input4) {
15321
+ function applyOpenApiToJsonConfig(config2, input5) {
15308
15322
  const next = { ...config2 };
15309
15323
  next.apis = [
15310
15324
  {
15311
15325
  type: "file",
15312
- input: input4,
15326
+ input: input5,
15313
15327
  path: "api"
15314
15328
  }
15315
15329
  ];
@@ -15352,15 +15366,15 @@ function buildLimits(rateLimitCalls, monthlyQuota) {
15352
15366
  quota: { calls: monthlyQuota, renewalPeriodDays: 30 }
15353
15367
  };
15354
15368
  }
15355
- function buildPlanItemFromWizard(input4) {
15356
- const sku = input4.sku ?? slugifyPlanSku(input4.displayName);
15369
+ function buildPlanItemFromWizard(input5) {
15370
+ const sku = input5.sku ?? slugifyPlanSku(input5.displayName);
15357
15371
  const item = {
15358
15372
  sku,
15359
- displayName: input4.displayName,
15360
- priceAmount: input4.priceAmount,
15361
- limits: buildLimits(input4.rateLimitCalls, input4.monthlyQuota)
15373
+ displayName: input5.displayName,
15374
+ priceAmount: input5.priceAmount,
15375
+ limits: buildLimits(input5.rateLimitCalls, input5.monthlyQuota)
15362
15376
  };
15363
- if (input4.priceAmount > 0) {
15377
+ if (input5.priceAmount > 0) {
15364
15378
  item.priceCurrency = "usd";
15365
15379
  item.billingPeriod = "month";
15366
15380
  }
@@ -16658,138 +16672,394 @@ async function reportPollProgress(progress, stepLabel, value) {
16658
16672
  // src/cli/platform/tools/dev-portal-publish.ts
16659
16673
  init_gateway_limits();
16660
16674
 
16661
- // src/cli/platform/helpers/gateway-plan-sync.ts
16662
- init_gateway_limits();
16675
+ // src/cli/platform/helpers/deploy-artifact-hash.ts
16676
+ import { createHash as createHash2 } from "node:crypto";
16677
+ import { createReadStream, promises as fs8 } from "node:fs";
16678
+ import path54 from "node:path";
16663
16679
 
16664
- // src/cli/platform/tools/gateway.ts
16665
- var DEFAULT_BASELINE_LIMITS_JSON = JSON.stringify({
16666
- rateLimit: { calls: 100, renewalPeriodSeconds: 60 },
16667
- quota: { calls: 1e4, renewalPeriodDays: 1 }
16668
- });
16669
- var DEFAULT_ENTRA_WAIT_TIMEOUT_MS = 12e4;
16670
- var DEFAULT_ENTRA_POLL_INTERVAL_MS = 5e3;
16671
- function sameStringList(a, b) {
16672
- const left = [...a ?? []].map((value) => value.trim()).filter(Boolean).sort();
16673
- const right = [...b ?? []].map((value) => value.trim()).filter(Boolean).sort();
16674
- if (left.length !== right.length) return false;
16675
- return left.every((value, index) => value === right[index]);
16676
- }
16677
- function isPlatformManagedAuthMode(authMode) {
16678
- return authMode.trim().toLowerCase() === "platformmanagedentra";
16679
- }
16680
- function arraysEquivalentForAuth(existing, desired) {
16681
- if (existing.authMode.toLowerCase() !== desired.authMode.toLowerCase()) {
16682
- return false;
16683
- }
16684
- if (isPlatformManagedAuthMode(desired.authMode)) {
16685
- return true;
16686
- }
16687
- return (existing.oidcMetadataUrl ?? "").trim() === (desired.oidcMetadataUrl ?? "").trim() && sameStringList(existing.oidcAllowedIssuers, desired.oidcAllowedIssuers ?? void 0) && sameStringList(existing.oidcAudiences, desired.oidcAudiences ?? void 0);
16688
- }
16689
- async function ensureGatewayAuthConfigured(client, projectId, desired) {
16690
- let existing = null;
16680
+ // src/cli/platform/helpers/package-files.ts
16681
+ import { promises as fs7 } from "node:fs";
16682
+ import path53 from "node:path";
16683
+ import ignore from "ignore";
16684
+ var DEFAULT_EXCLUDES = /* @__PURE__ */ new Set([
16685
+ "node_modules",
16686
+ ".git",
16687
+ ".DS_Store",
16688
+ "dist",
16689
+ "bin",
16690
+ "obj"
16691
+ ]);
16692
+ async function loadGitignore(rootDir) {
16693
+ const ig = ignore();
16694
+ ig.add(["node_modules/", ".git/", "dist/", "bin/", "obj/"]);
16695
+ const gitignorePath = path53.join(rootDir, ".gitignore");
16691
16696
  try {
16692
- existing = await client.getGatewayAuth(projectId);
16693
- } catch (error) {
16694
- if (!isNotFoundError(error)) {
16695
- throw error;
16696
- }
16697
- }
16698
- if (existing && arraysEquivalentForAuth(existing, desired) && (!isPlatformManagedAuthMode(desired.authMode) || existing.platformEntraProvisionStatus?.toLowerCase() === "ready")) {
16699
- return { auth: existing, created: false, updated: false, skipped: true };
16697
+ const content = await fs7.readFile(gitignorePath, "utf8");
16698
+ ig.add(content);
16699
+ } catch {
16700
16700
  }
16701
- const auth = await client.updateGatewayAuth(projectId, desired);
16702
- return {
16703
- auth,
16704
- created: existing === null,
16705
- updated: existing !== null,
16706
- skipped: false
16707
- };
16701
+ return ig;
16708
16702
  }
16709
- async function waitForGatewayEntraProvision(client, projectId, options) {
16710
- const timeoutMs = options?.timeoutMs ?? DEFAULT_ENTRA_WAIT_TIMEOUT_MS;
16711
- const pollIntervalMs = options?.pollIntervalMs ?? DEFAULT_ENTRA_POLL_INTERVAL_MS;
16712
- const started = Date.now();
16713
- while (true) {
16714
- const auth = await client.getGatewayAuth(projectId);
16715
- if (!isPlatformManagedAuthMode(auth.authMode)) {
16716
- return { auth, status: "not_applicable" };
16717
- }
16718
- const status2 = auth.platformEntraProvisionStatus?.toLowerCase() ?? "";
16719
- if (status2 === "ready") {
16720
- return { auth, status: "ready" };
16703
+ async function collectFiles(rootDir, relativeDir, ig, files) {
16704
+ const absDir = path53.join(rootDir, relativeDir);
16705
+ const entries = await fs7.readdir(absDir, { withFileTypes: true });
16706
+ for (const entry of entries) {
16707
+ const rel = relativeDir ? path53.join(relativeDir, entry.name) : entry.name;
16708
+ const relPosix = rel.split(path53.sep).join("/");
16709
+ if (DEFAULT_EXCLUDES.has(entry.name) && !relativeDir) {
16710
+ continue;
16721
16711
  }
16722
- if (status2 === "failed") {
16723
- return { auth, status: "failed" };
16712
+ if (ig.ignores(relPosix)) {
16713
+ continue;
16724
16714
  }
16725
- if (Date.now() - started >= timeoutMs) {
16726
- return { auth, status: "timeout" };
16715
+ if (entry.isDirectory()) {
16716
+ await collectFiles(rootDir, rel, ig, files);
16717
+ } else if (entry.isFile()) {
16718
+ files.push(rel);
16727
16719
  }
16728
- await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
16729
16720
  }
16730
16721
  }
16731
- function normalizeActionKey(actionKey) {
16732
- const trimmed = actionKey.trim();
16733
- if (trimmed.includes(":")) {
16734
- const [method2, ...pathParts] = trimmed.split(":");
16735
- const path73 = pathParts.join(":").trim();
16736
- return `${method2.toLowerCase()}:${path73.startsWith("/") ? path73 : `/${path73}`}`;
16722
+ async function collectPackageableFiles(sourceDirectory) {
16723
+ const resolved = path53.resolve(sourceDirectory);
16724
+ const stat4 = await fs7.stat(resolved).catch(() => null);
16725
+ if (!stat4?.isDirectory()) {
16726
+ throw new Error(`sourceDirectory is not a directory: ${resolved}`);
16737
16727
  }
16738
- const spaceIndex = trimmed.indexOf(" ");
16739
- if (spaceIndex <= 0) {
16740
- throw new Error(
16741
- "actionKey must be in format 'get:/path' or 'GET /path' (method plus path)."
16742
- );
16728
+ const ig = await loadGitignore(resolved);
16729
+ const files = [];
16730
+ await collectFiles(resolved, "", ig, files);
16731
+ if (files.length === 0) {
16732
+ throw new Error(`No files to package in ${resolved}`);
16743
16733
  }
16744
- const method = trimmed.slice(0, spaceIndex).trim().toLowerCase();
16745
- const path72 = trimmed.slice(spaceIndex + 1).trim();
16746
- return `${method}:${path72.startsWith("/") ? path72 : `/${path72}`}`;
16734
+ return files.sort();
16747
16735
  }
16748
- async function applyGatewayPublicRoutes(client, projectId, routes, options) {
16749
- const applied = [];
16750
- const unresolved = [];
16751
- const warnings = [];
16752
- if (!routes.length) {
16753
- return { applied, unresolved, warnings };
16736
+
16737
+ // src/cli/platform/helpers/deploy-artifact-hash.ts
16738
+ async function sha256File(filePath) {
16739
+ const hash2 = createHash2("sha256");
16740
+ await new Promise((resolve, reject) => {
16741
+ const stream = createReadStream(filePath);
16742
+ stream.on("data", (chunk) => hash2.update(chunk));
16743
+ stream.on("error", reject);
16744
+ stream.on("end", () => resolve());
16745
+ });
16746
+ return hash2.digest("hex");
16747
+ }
16748
+ async function hashDirectoryContents(rootDirectory) {
16749
+ const resolved = path54.resolve(rootDirectory);
16750
+ const files = (await collectPackageableFiles(resolved)).sort((a, b) => a.localeCompare(b));
16751
+ const hash2 = createHash2("sha256");
16752
+ for (const rel of files) {
16753
+ hash2.update(rel);
16754
+ hash2.update("\0");
16755
+ hash2.update(await fs8.readFile(path54.join(resolved, rel)));
16756
+ hash2.update("\0");
16754
16757
  }
16755
- let knownKeys = null;
16756
- if (options?.knownActions) {
16757
- knownKeys = new Set(options.knownActions.map((action) => action.actionKey.toLowerCase()));
16758
- } else {
16758
+ return hash2.digest("hex");
16759
+ }
16760
+ async function hashDevPortalSiteSources(siteDirectory, options) {
16761
+ const resolved = path54.resolve(siteDirectory);
16762
+ const hash2 = createHash2("sha256");
16763
+ const candidates = [
16764
+ "apitogo.config.json",
16765
+ "apitogo.config.ts",
16766
+ "package.json",
16767
+ "zudoku.config.ts",
16768
+ "zudoku.config.tsx"
16769
+ ];
16770
+ for (const name of candidates) {
16771
+ const full = path54.join(resolved, name);
16759
16772
  try {
16760
- const listed = await listAllGatewayActions(client, projectId);
16761
- if (listed.length > 0) {
16762
- knownKeys = new Set(listed.map((action) => action.actionKey.toLowerCase()));
16763
- }
16773
+ hash2.update(name);
16774
+ hash2.update("\0");
16775
+ hash2.update(await fs8.readFile(full));
16776
+ hash2.update("\0");
16764
16777
  } catch {
16765
- knownKeys = null;
16766
16778
  }
16767
16779
  }
16768
- for (const route of routes) {
16769
- let actionKey;
16780
+ for (const dir of ["pages", "landing", "src"]) {
16781
+ const full = path54.join(resolved, dir);
16770
16782
  try {
16771
- const trimmed = route.trim();
16772
- actionKey = trimmed.startsWith("/") ? normalizeActionKey(`GET ${trimmed}`) : normalizeActionKey(trimmed);
16773
- } catch (error) {
16774
- unresolved.push({
16775
- route,
16776
- error: error instanceof Error ? error.message : String(error)
16777
- });
16778
- continue;
16779
- }
16780
- if (knownKeys && !knownKeys.has(actionKey.toLowerCase())) {
16781
- const message = `Action '${actionKey}' not found in gateway actions.`;
16782
- unresolved.push({ route, actionKey, error: message });
16783
- warnings.push(message);
16784
- continue;
16783
+ const st = await fs8.stat(full);
16784
+ if (!st.isDirectory()) continue;
16785
+ hash2.update(dir);
16786
+ hash2.update("\0");
16787
+ hash2.update(await hashDirectoryContents(full));
16788
+ hash2.update("\0");
16789
+ } catch {
16785
16790
  }
16791
+ }
16792
+ const gatewayUrl = options?.gatewayUrl?.trim().replace(/\/+$/, "");
16793
+ if (gatewayUrl) {
16794
+ hash2.update("gatewayUrl");
16795
+ hash2.update("\0");
16796
+ hash2.update(gatewayUrl);
16797
+ hash2.update("\0");
16798
+ }
16799
+ return hash2.digest("hex");
16800
+ }
16801
+
16802
+ // src/cli/platform/helpers/dev-portal-plan-sync.ts
16803
+ var PLANS_TIMEOUT_MS = 3e4;
16804
+ var DEFAULT_RETRIES = 5;
16805
+ var DEFAULT_RETRY_DELAY_MS = 5e3;
16806
+ async function syncDevPortalPlanCatalog(options) {
16807
+ const fetchImpl = options.fetchImpl ?? fetch;
16808
+ const retries = Math.max(1, options.retries ?? DEFAULT_RETRIES);
16809
+ const retryDelayMs = options.retryDelayMs ?? DEFAULT_RETRY_DELAY_MS;
16810
+ const base = options.backendApiBaseUrl.replace(/\/$/, "");
16811
+ const url2 = `${base}/api/v1/plans?refresh=true&force=true`;
16812
+ let lastError;
16813
+ for (let attempt = 1; attempt <= retries; attempt++) {
16814
+ const controller = new AbortController();
16815
+ const timeout = setTimeout(() => controller.abort(), PLANS_TIMEOUT_MS);
16786
16816
  try {
16787
- const action = await client.setGatewayActionPublic(projectId, actionKey, true);
16788
- applied.push(action);
16817
+ const response = await fetchImpl(url2, {
16818
+ method: "GET",
16819
+ headers: { Accept: "application/json" },
16820
+ signal: controller.signal
16821
+ });
16822
+ if (!response.ok) {
16823
+ throw new Error(`GET /api/v1/plans?refresh=true&force=true returned ${response.status}.`);
16824
+ }
16825
+ const catalog = await response.json();
16826
+ if (options.expectedPlanCount !== void 0 && catalog.plans.length < options.expectedPlanCount) {
16827
+ throw new Error(
16828
+ `Plan catalog has ${catalog.plans.length} plan(s); expected at least ${options.expectedPlanCount}.`
16829
+ );
16830
+ }
16831
+ return catalog;
16789
16832
  } catch (error) {
16790
- const message = error instanceof Error ? error.message : String(error);
16791
- unresolved.push({ route, actionKey, error: message });
16792
- warnings.push(`Failed to set public for '${actionKey}': ${message}`);
16833
+ lastError = error instanceof Error && error.name === "AbortError" ? new Error("GET /api/v1/plans?refresh=true&force=true timed out.") : error instanceof Error ? error : new Error(String(error));
16834
+ if (attempt < retries) {
16835
+ await new Promise((resolve) => setTimeout(resolve, retryDelayMs));
16836
+ }
16837
+ } finally {
16838
+ clearTimeout(timeout);
16839
+ }
16840
+ }
16841
+ throw lastError ?? new Error("Dev portal plan catalog sync failed.");
16842
+ }
16843
+
16844
+ // src/cli/platform/helpers/dev-portal-settings-kinds.ts
16845
+ function isHttpUrl(value) {
16846
+ return /^https?:\/\//i.test(value.trim());
16847
+ }
16848
+ function resolveLogoSettings(logoUrl) {
16849
+ const trimmed = logoUrl?.trim();
16850
+ if (!trimmed) {
16851
+ return { logoKind: "RepoRelative" };
16852
+ }
16853
+ if (isHttpUrl(trimmed)) {
16854
+ return { logoKind: "AbsoluteUrl", logoValue: trimmed };
16855
+ }
16856
+ return { logoKind: "RepoRelative", logoValue: trimmed };
16857
+ }
16858
+ function resolveOpenApiSettings(openApiPath) {
16859
+ const trimmed = openApiPath?.trim();
16860
+ if (!trimmed) {
16861
+ return { openApiSourceKind: "RepoRelative" };
16862
+ }
16863
+ if (isHttpUrl(trimmed)) {
16864
+ return { openApiSourceKind: "ExternalUrl", openApiValue: trimmed };
16865
+ }
16866
+ return { openApiSourceKind: "RepoRelative", openApiValue: trimmed };
16867
+ }
16868
+
16869
+ // src/cli/platform/helpers/dev-portal-urls.ts
16870
+ function normalizeHostname(hostname) {
16871
+ return hostname.trim().replace(/^https?:\/\//, "").replace(/\/+$/, "");
16872
+ }
16873
+ function resolveSwaHostname(project) {
16874
+ const siteUrl = project.siteUrl?.trim();
16875
+ if (siteUrl) {
16876
+ try {
16877
+ return normalizeHostname(new URL(siteUrl).hostname);
16878
+ } catch {
16879
+ return normalizeHostname(siteUrl);
16880
+ }
16881
+ }
16882
+ const custom = project.userCustomDomain?.trim();
16883
+ if (custom) {
16884
+ return normalizeHostname(custom);
16885
+ }
16886
+ const defaultHost = project.azureStaticWebDefaultHostname?.trim();
16887
+ if (defaultHost) {
16888
+ return normalizeHostname(defaultHost);
16889
+ }
16890
+ return null;
16891
+ }
16892
+ function resolveSwaHostnameWithFallback(project, platformSiteUrl) {
16893
+ return resolveSwaHostname(project) ?? (platformSiteUrl?.trim() ? normalizeHostname(platformSiteUrl) : null);
16894
+ }
16895
+ function resolveSwaUrl(project) {
16896
+ const hostname = resolveSwaHostname(project);
16897
+ if (!hostname) {
16898
+ return null;
16899
+ }
16900
+ return `https://${hostname}`;
16901
+ }
16902
+ function resolveDevPortalUrls(project, backendFqdn, providerIds = [], platformSiteUrl) {
16903
+ const hostname = resolveSwaHostnameWithFallback(project, platformSiteUrl);
16904
+ const swaUrl = hostname ? `https://${hostname}` : null;
16905
+ if (!swaUrl) {
16906
+ throw new Error(
16907
+ "Static Web App hostname is not available. Ensure the dev-portal module is provisioned."
16908
+ );
16909
+ }
16910
+ const backendApiBaseUrl = toApiBaseUrl(backendFqdn);
16911
+ if (!backendApiBaseUrl) {
16912
+ throw new Error("Dev portal backend FQDN is missing.");
16913
+ }
16914
+ const swaOrigin = new URL(swaUrl).origin;
16915
+ const backendOrigin = new URL(backendApiBaseUrl).origin;
16916
+ const oidcRedirectUris = providerIds.map((providerId) => ({
16917
+ providerId,
16918
+ redirectUri: `${backendOrigin}/signin-oidc/${providerId}`
16919
+ }));
16920
+ return {
16921
+ swaUrl,
16922
+ swaOrigin,
16923
+ stripeSuccessUrl: `${swaOrigin}/account/subscriptions`,
16924
+ stripeCancelUrl: `${swaOrigin}/pricing`,
16925
+ oidcRedirectUris,
16926
+ stripeWebhookUrl: `${backendOrigin}/api/v1/webhooks/stripe`,
16927
+ backendApiBaseUrl
16928
+ };
16929
+ }
16930
+
16931
+ // src/cli/platform/helpers/gateway-plan-sync.ts
16932
+ init_gateway_limits();
16933
+
16934
+ // src/cli/platform/tools/gateway.ts
16935
+ var DEFAULT_BASELINE_LIMITS_JSON = JSON.stringify({
16936
+ rateLimit: { calls: 100, renewalPeriodSeconds: 60 },
16937
+ quota: { calls: 1e4, renewalPeriodDays: 1 }
16938
+ });
16939
+ var DEFAULT_ENTRA_WAIT_TIMEOUT_MS = 12e4;
16940
+ var DEFAULT_ENTRA_POLL_INTERVAL_MS = 5e3;
16941
+ function sameStringList(a, b) {
16942
+ const left = [...a ?? []].map((value) => value.trim()).filter(Boolean).sort();
16943
+ const right = [...b ?? []].map((value) => value.trim()).filter(Boolean).sort();
16944
+ if (left.length !== right.length) return false;
16945
+ return left.every((value, index) => value === right[index]);
16946
+ }
16947
+ function isPlatformManagedAuthMode(authMode) {
16948
+ return authMode.trim().toLowerCase() === "platformmanagedentra";
16949
+ }
16950
+ function arraysEquivalentForAuth(existing, desired) {
16951
+ if (existing.authMode.toLowerCase() !== desired.authMode.toLowerCase()) {
16952
+ return false;
16953
+ }
16954
+ if (isPlatformManagedAuthMode(desired.authMode)) {
16955
+ return true;
16956
+ }
16957
+ return (existing.oidcMetadataUrl ?? "").trim() === (desired.oidcMetadataUrl ?? "").trim() && sameStringList(existing.oidcAllowedIssuers, desired.oidcAllowedIssuers ?? void 0) && sameStringList(existing.oidcAudiences, desired.oidcAudiences ?? void 0);
16958
+ }
16959
+ async function ensureGatewayAuthConfigured(client, projectId, desired) {
16960
+ let existing = null;
16961
+ try {
16962
+ existing = await client.getGatewayAuth(projectId);
16963
+ } catch (error) {
16964
+ if (!isNotFoundError(error)) {
16965
+ throw error;
16966
+ }
16967
+ }
16968
+ if (existing && arraysEquivalentForAuth(existing, desired) && (!isPlatformManagedAuthMode(desired.authMode) || existing.platformEntraProvisionStatus?.toLowerCase() === "ready")) {
16969
+ return { auth: existing, created: false, updated: false, skipped: true };
16970
+ }
16971
+ const auth = await client.updateGatewayAuth(projectId, desired);
16972
+ return {
16973
+ auth,
16974
+ created: existing === null,
16975
+ updated: existing !== null,
16976
+ skipped: false
16977
+ };
16978
+ }
16979
+ async function waitForGatewayEntraProvision(client, projectId, options) {
16980
+ const timeoutMs = options?.timeoutMs ?? DEFAULT_ENTRA_WAIT_TIMEOUT_MS;
16981
+ const pollIntervalMs = options?.pollIntervalMs ?? DEFAULT_ENTRA_POLL_INTERVAL_MS;
16982
+ const started = Date.now();
16983
+ while (true) {
16984
+ const auth = await client.getGatewayAuth(projectId);
16985
+ if (!isPlatformManagedAuthMode(auth.authMode)) {
16986
+ return { auth, status: "not_applicable" };
16987
+ }
16988
+ const status2 = auth.platformEntraProvisionStatus?.toLowerCase() ?? "";
16989
+ if (status2 === "ready") {
16990
+ return { auth, status: "ready" };
16991
+ }
16992
+ if (status2 === "failed") {
16993
+ return { auth, status: "failed" };
16994
+ }
16995
+ if (Date.now() - started >= timeoutMs) {
16996
+ return { auth, status: "timeout" };
16997
+ }
16998
+ await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
16999
+ }
17000
+ }
17001
+ function normalizeActionKey(actionKey) {
17002
+ const trimmed = actionKey.trim();
17003
+ if (trimmed.includes(":")) {
17004
+ const [method2, ...pathParts] = trimmed.split(":");
17005
+ const path73 = pathParts.join(":").trim();
17006
+ return `${method2.toLowerCase()}:${path73.startsWith("/") ? path73 : `/${path73}`}`;
17007
+ }
17008
+ const spaceIndex = trimmed.indexOf(" ");
17009
+ if (spaceIndex <= 0) {
17010
+ throw new Error(
17011
+ "actionKey must be in format 'get:/path' or 'GET /path' (method plus path)."
17012
+ );
17013
+ }
17014
+ const method = trimmed.slice(0, spaceIndex).trim().toLowerCase();
17015
+ const path72 = trimmed.slice(spaceIndex + 1).trim();
17016
+ return `${method}:${path72.startsWith("/") ? path72 : `/${path72}`}`;
17017
+ }
17018
+ async function applyGatewayPublicRoutes(client, projectId, routes, options) {
17019
+ const applied = [];
17020
+ const unresolved = [];
17021
+ const warnings = [];
17022
+ if (!routes.length) {
17023
+ return { applied, unresolved, warnings };
17024
+ }
17025
+ let knownKeys = null;
17026
+ if (options?.knownActions) {
17027
+ knownKeys = new Set(options.knownActions.map((action) => action.actionKey.toLowerCase()));
17028
+ } else {
17029
+ try {
17030
+ const listed = await listAllGatewayActions(client, projectId);
17031
+ if (listed.length > 0) {
17032
+ knownKeys = new Set(listed.map((action) => action.actionKey.toLowerCase()));
17033
+ }
17034
+ } catch {
17035
+ knownKeys = null;
17036
+ }
17037
+ }
17038
+ for (const route of routes) {
17039
+ let actionKey;
17040
+ try {
17041
+ const trimmed = route.trim();
17042
+ actionKey = trimmed.startsWith("/") ? normalizeActionKey(`GET ${trimmed}`) : normalizeActionKey(trimmed);
17043
+ } catch (error) {
17044
+ unresolved.push({
17045
+ route,
17046
+ error: error instanceof Error ? error.message : String(error)
17047
+ });
17048
+ continue;
17049
+ }
17050
+ if (knownKeys && !knownKeys.has(actionKey.toLowerCase())) {
17051
+ const message = `Action '${actionKey}' not found in gateway actions.`;
17052
+ unresolved.push({ route, actionKey, error: message });
17053
+ warnings.push(message);
17054
+ continue;
17055
+ }
17056
+ try {
17057
+ const action = await client.setGatewayActionPublic(projectId, actionKey, true);
17058
+ applied.push(action);
17059
+ } catch (error) {
17060
+ const message = error instanceof Error ? error.message : String(error);
17061
+ unresolved.push({ route, actionKey, error: message });
17062
+ warnings.push(`Failed to set public for '${actionKey}': ${message}`);
16793
17063
  }
16794
17064
  }
16795
17065
  return { applied, unresolved, warnings };
@@ -17039,13 +17309,13 @@ async function resolveActionKeysOrAllProjectActions(manifestPlan, existingPlan,
17039
17309
  var GATEWAY_PLAN_SORT_TEMP_OFFSET = 1e3;
17040
17310
  function buildGatewayPlanSortTargetsFromIds(manifestPlans, planIdsBySku, skuById) {
17041
17311
  const targets = [];
17042
- for (const [index, input4] of manifestPlans.entries()) {
17043
- const key = input4.sku.toLowerCase();
17312
+ for (const [index, input5] of manifestPlans.entries()) {
17313
+ const key = input5.sku.toLowerCase();
17044
17314
  const planId = planIdsBySku.get(key);
17045
17315
  if (!planId) {
17046
- throw new Error(`Gateway plan '${input4.sku}' was not found; upsert plans before reordering.`);
17316
+ throw new Error(`Gateway plan '${input5.sku}' was not found; upsert plans before reordering.`);
17047
17317
  }
17048
- const sku = skuById?.get(key)?.sku ?? input4.sku;
17318
+ const sku = skuById?.get(key)?.sku ?? input5.sku;
17049
17319
  targets.push({ planId, sku, sortOrder: index });
17050
17320
  }
17051
17321
  return targets;
@@ -17091,12 +17361,12 @@ async function upsertGatewayPlansFromManifest(client, config2, projectId, plans,
17091
17361
  return cachedProjectActionKeys;
17092
17362
  };
17093
17363
  for (const [index, inputRaw] of plans.entries()) {
17094
- const input4 = normalizePlanForGatewayApi(inputRaw);
17095
- const found = bySku.get(input4.sku.toLowerCase());
17364
+ const input5 = normalizePlanForGatewayApi(inputRaw);
17365
+ const found = bySku.get(input5.sku.toLowerCase());
17096
17366
  if (found) {
17097
17367
  if (options?.syncActionsOnly) {
17098
17368
  const actionKeys = await resolveActionKeysOrAllProjectActions(
17099
- input4,
17369
+ input5,
17100
17370
  found,
17101
17371
  projectActionKeys
17102
17372
  );
@@ -17109,29 +17379,29 @@ async function upsertGatewayPlansFromManifest(client, config2, projectId, plans,
17109
17379
  includedActionKeys: actionKeys
17110
17380
  });
17111
17381
  results.push(JSON.parse(patchedJson2));
17112
- planIdsBySku.set(input4.sku.toLowerCase(), found.id);
17382
+ planIdsBySku.set(input5.sku.toLowerCase(), found.id);
17113
17383
  continue;
17114
17384
  }
17115
17385
  const patchArgs = {
17116
17386
  projectId,
17117
17387
  planId: found.id,
17118
- displayName: input4.displayName,
17388
+ displayName: input5.displayName,
17119
17389
  isActive: true,
17120
- limitsJson: planLimitsForPlatform(input4),
17121
- priceAmount: input4.priceAmount,
17122
- priceCurrency: input4.priceCurrency,
17123
- billingPeriod: input4.billingPeriod ? normalizeBillingPeriod2(input4.billingPeriod) : void 0
17390
+ limitsJson: planLimitsForPlatform(input5),
17391
+ priceAmount: input5.priceAmount,
17392
+ priceCurrency: input5.priceCurrency,
17393
+ billingPeriod: input5.billingPeriod ? normalizeBillingPeriod2(input5.billingPeriod) : void 0
17124
17394
  };
17125
17395
  if (!options?.deferActionKeys) {
17126
17396
  patchArgs.includedActionKeys = await resolveActionKeysOrAllProjectActions(
17127
- input4,
17397
+ input5,
17128
17398
  found,
17129
17399
  projectActionKeys
17130
17400
  );
17131
17401
  }
17132
17402
  const patchedJson = await handlePatchGatewayPlan(client, config2, patchArgs);
17133
17403
  results.push(JSON.parse(patchedJson));
17134
- planIdsBySku.set(input4.sku.toLowerCase(), found.id);
17404
+ planIdsBySku.set(input5.sku.toLowerCase(), found.id);
17135
17405
  continue;
17136
17406
  }
17137
17407
  if (options?.syncActionsOnly) {
@@ -17139,19 +17409,19 @@ async function upsertGatewayPlansFromManifest(client, config2, projectId, plans,
17139
17409
  }
17140
17410
  const createdJson = await handleCreateGatewayPlan(client, config2, {
17141
17411
  projectId,
17142
- sku: input4.sku,
17143
- displayName: input4.displayName,
17144
- priceAmount: input4.priceAmount,
17145
- priceCurrency: input4.priceCurrency ?? "usd",
17146
- billingPeriod: normalizeBillingPeriod2(input4.billingPeriod),
17147
- limitsJson: planLimitsForPlatform(input4),
17148
- includedActionKeys: options?.deferActionKeys ? [] : await resolveActionKeysOrAllProjectActions(input4, void 0, projectActionKeys),
17412
+ sku: input5.sku,
17413
+ displayName: input5.displayName,
17414
+ priceAmount: input5.priceAmount,
17415
+ priceCurrency: input5.priceCurrency ?? "usd",
17416
+ billingPeriod: normalizeBillingPeriod2(input5.billingPeriod),
17417
+ limitsJson: planLimitsForPlatform(input5),
17418
+ includedActionKeys: options?.deferActionKeys ? [] : await resolveActionKeysOrAllProjectActions(input5, void 0, projectActionKeys),
17149
17419
  skipDefaultActions: true,
17150
17420
  sortOrder: index
17151
17421
  });
17152
17422
  const created = JSON.parse(createdJson);
17153
17423
  if (created.plan?.id) {
17154
- planIdsBySku.set(input4.sku.toLowerCase(), created.plan.id);
17424
+ planIdsBySku.set(input5.sku.toLowerCase(), created.plan.id);
17155
17425
  }
17156
17426
  results.push(created);
17157
17427
  }
@@ -17163,358 +17433,240 @@ async function upsertGatewayPlansFromManifest(client, config2, projectId, plans,
17163
17433
  return results;
17164
17434
  }
17165
17435
 
17166
- // src/cli/platform/helpers/dev-portal-plan-sync.ts
17167
- var PLANS_TIMEOUT_MS = 3e4;
17168
- var DEFAULT_RETRIES = 5;
17169
- var DEFAULT_RETRY_DELAY_MS = 5e3;
17170
- async function syncDevPortalPlanCatalog(options) {
17171
- const fetchImpl = options.fetchImpl ?? fetch;
17172
- const retries = Math.max(1, options.retries ?? DEFAULT_RETRIES);
17173
- const retryDelayMs = options.retryDelayMs ?? DEFAULT_RETRY_DELAY_MS;
17174
- const base = options.backendApiBaseUrl.replace(/\/$/, "");
17175
- const url2 = `${base}/api/v1/plans?refresh=true&force=true`;
17176
- let lastError;
17177
- for (let attempt = 1; attempt <= retries; attempt++) {
17178
- const controller = new AbortController();
17179
- const timeout = setTimeout(() => controller.abort(), PLANS_TIMEOUT_MS);
17180
- try {
17181
- const response = await fetchImpl(url2, {
17182
- method: "GET",
17183
- headers: { Accept: "application/json" },
17184
- signal: controller.signal
17185
- });
17186
- if (!response.ok) {
17187
- throw new Error(`GET /api/v1/plans?refresh=true&force=true returned ${response.status}.`);
17188
- }
17189
- const catalog = await response.json();
17190
- if (options.expectedPlanCount !== void 0 && catalog.plans.length < options.expectedPlanCount) {
17191
- throw new Error(
17192
- `Plan catalog has ${catalog.plans.length} plan(s); expected at least ${options.expectedPlanCount}.`
17193
- );
17194
- }
17195
- return catalog;
17196
- } catch (error) {
17197
- lastError = error instanceof Error && error.name === "AbortError" ? new Error("GET /api/v1/plans?refresh=true&force=true timed out.") : error instanceof Error ? error : new Error(String(error));
17198
- if (attempt < retries) {
17199
- await new Promise((resolve) => setTimeout(resolve, retryDelayMs));
17200
- }
17201
- } finally {
17202
- clearTimeout(timeout);
17203
- }
17204
- }
17205
- throw lastError ?? new Error("Dev portal plan catalog sync failed.");
17206
- }
17207
-
17208
- // src/cli/platform/helpers/dev-portal-urls.ts
17209
- function normalizeHostname(hostname) {
17210
- return hostname.trim().replace(/^https?:\/\//, "").replace(/\/+$/, "");
17211
- }
17212
- function resolveSwaHostname(project) {
17213
- const siteUrl = project.siteUrl?.trim();
17214
- if (siteUrl) {
17215
- try {
17216
- return normalizeHostname(new URL(siteUrl).hostname);
17217
- } catch {
17218
- return normalizeHostname(siteUrl);
17219
- }
17220
- }
17221
- const custom = project.userCustomDomain?.trim();
17222
- if (custom) {
17223
- return normalizeHostname(custom);
17224
- }
17225
- const defaultHost = project.azureStaticWebDefaultHostname?.trim();
17226
- if (defaultHost) {
17227
- return normalizeHostname(defaultHost);
17228
- }
17229
- return null;
17230
- }
17231
- function resolveSwaHostnameWithFallback(project, platformSiteUrl) {
17232
- return resolveSwaHostname(project) ?? (platformSiteUrl?.trim() ? normalizeHostname(platformSiteUrl) : null);
17233
- }
17234
- function resolveSwaUrl(project) {
17235
- const hostname = resolveSwaHostname(project);
17236
- if (!hostname) {
17237
- return null;
17238
- }
17239
- return `https://${hostname}`;
17436
+ // src/cli/platform/helpers/openapi-gateway-servers.ts
17437
+ import { mkdir as mkdir11, readFile as readFile15, writeFile as writeFile14 } from "node:fs/promises";
17438
+ import path55 from "node:path";
17439
+ import YAML3 from "yaml";
17440
+ var GATEWAY_URL_ENV_KEY = "APITOGO_GATEWAY_URL";
17441
+ var PUBLISH_OPENAPI_RELATIVE = ".apitogo/openapi-publish.json";
17442
+ function normalizeGatewayUrl(gatewayUrl) {
17443
+ return gatewayUrl.trim().replace(/\/+$/, "");
17240
17444
  }
17241
- function resolveDevPortalUrls(project, backendFqdn, providerIds = [], platformSiteUrl) {
17242
- const hostname = resolveSwaHostnameWithFallback(project, platformSiteUrl);
17243
- const swaUrl = hostname ? `https://${hostname}` : null;
17244
- if (!swaUrl) {
17245
- throw new Error(
17246
- "Static Web App hostname is not available. Ensure the dev-portal module is provisioned."
17247
- );
17248
- }
17249
- const backendApiBaseUrl = toApiBaseUrl(backendFqdn);
17250
- if (!backendApiBaseUrl) {
17251
- throw new Error("Dev portal backend FQDN is missing.");
17252
- }
17253
- const swaOrigin = new URL(swaUrl).origin;
17254
- const backendOrigin = new URL(backendApiBaseUrl).origin;
17255
- const oidcRedirectUris = providerIds.map((providerId) => ({
17256
- providerId,
17257
- redirectUri: `${backendOrigin}/signin-oidc/${providerId}`
17258
- }));
17445
+ function withGatewayServers(doc, gatewayUrl) {
17446
+ const url2 = normalizeGatewayUrl(gatewayUrl);
17259
17447
  return {
17260
- swaUrl,
17261
- swaOrigin,
17262
- stripeSuccessUrl: `${swaOrigin}/account/subscriptions`,
17263
- stripeCancelUrl: `${swaOrigin}/pricing`,
17264
- oidcRedirectUris,
17265
- stripeWebhookUrl: `${backendOrigin}/api/v1/webhooks/stripe`,
17266
- backendApiBaseUrl
17448
+ ...doc,
17449
+ servers: [{ url: url2, description: "API gateway" }]
17267
17450
  };
17268
17451
  }
17269
-
17270
- // src/cli/platform/helpers/status.ts
17271
- function normalizeInfrastructureStatus(platformStatus) {
17272
- switch (platformStatus.toLowerCase()) {
17273
- case "completed":
17274
- case "completed_with_warnings":
17275
- return "succeeded";
17276
- case "in_progress":
17277
- return "running";
17278
- case "failed":
17279
- return "failed";
17280
- case "pending":
17281
- case "none":
17282
- default:
17283
- return "pending";
17452
+ async function resolveProjectGatewayUrl(client, projectId) {
17453
+ const project = await client.getProject(projectId);
17454
+ const fromProject = project.gatewayUrl?.trim();
17455
+ if (fromProject) {
17456
+ return normalizeGatewayUrl(fromProject);
17284
17457
  }
17285
- }
17286
- function normalizeDeploymentStatus(platformStatus) {
17287
- switch (platformStatus.toLowerCase()) {
17288
- case "healthy":
17289
- return "succeeded";
17290
- case "failed":
17291
- case "cancelled":
17292
- return "failed";
17293
- case "queued":
17294
- case "validating":
17295
- case "building":
17296
- case "deploying":
17297
- return "running";
17298
- default:
17299
- return "pending";
17458
+ try {
17459
+ const summary = await client.getBackendSummary(projectId);
17460
+ const fromBackend = summary.backendUrl?.trim();
17461
+ if (fromBackend) {
17462
+ return normalizeGatewayUrl(fromBackend);
17463
+ }
17464
+ } catch {
17300
17465
  }
17466
+ return null;
17301
17467
  }
17302
- function isTerminalInfrastructureStatus(platformStatus) {
17303
- const s = platformStatus.toLowerCase();
17304
- return s === "completed" || s === "completed_with_warnings" || s === "failed";
17305
- }
17306
- function isTerminalDeploymentStatus(platformStatus) {
17307
- const s = platformStatus.toLowerCase();
17308
- return s === "healthy" || s === "failed" || s === "cancelled";
17468
+ async function readOpenApiDocument(filePath) {
17469
+ const content = await readFile15(filePath, "utf8");
17470
+ const ext = path55.extname(filePath).toLowerCase();
17471
+ const parsed = ext === ".json" ? JSON.parse(content) : YAML3.parse(content);
17472
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
17473
+ throw new Error(`OpenAPI document must be an object: ${filePath}`);
17474
+ }
17475
+ return parsed;
17309
17476
  }
17310
- var DEFAULT_POLL_INTERVAL_MS = 5e3;
17311
- var DEFAULT_POLL_TIMEOUT_MS = 15 * 60 * 1e3;
17312
-
17313
- // src/cli/platform/helpers/poll.ts
17314
- function sleep2(ms) {
17315
- return new Promise((resolve) => setTimeout(resolve, ms));
17477
+ async function materializePublishOpenApiWithGateway(siteDirectory, sourceOpenApiPath, gatewayUrl) {
17478
+ const site = path55.resolve(siteDirectory);
17479
+ const source = path55.resolve(sourceOpenApiPath);
17480
+ const relativeInput = PUBLISH_OPENAPI_RELATIVE;
17481
+ const outputPath = path55.join(site, ...relativeInput.split("/"));
17482
+ const doc = await readOpenApiDocument(source);
17483
+ const rewritten = withGatewayServers(doc, gatewayUrl);
17484
+ await mkdir11(path55.dirname(outputPath), { recursive: true });
17485
+ await writeFile14(outputPath, `${JSON.stringify(rewritten, null, 2)}
17486
+ `, "utf8");
17487
+ return { outputPath, relativeInput };
17316
17488
  }
17317
- async function pollUntilTerminal(options) {
17318
- const timeoutMs = options.timeoutMs ?? DEFAULT_POLL_TIMEOUT_MS;
17319
- const intervalMs = options.intervalMs ?? DEFAULT_POLL_INTERVAL_MS;
17320
- const deadline = Date.now() + timeoutMs;
17321
- const stepLabel = options.stepLabel ?? "Job";
17322
- let last;
17323
- while (Date.now() <= deadline) {
17324
- if (options.progress?.isCancelled()) {
17325
- if (!last) {
17326
- throw new Error("Operation cancelled before first poll result.");
17489
+ async function pointSiteApisAtPublishOpenApi(siteDirectory, relativeInput) {
17490
+ const configPath = path55.join(path55.resolve(siteDirectory), APITOGO_CONFIG_FILENAME2);
17491
+ const raw = await readFile15(configPath, "utf8");
17492
+ const config2 = JSON.parse(raw);
17493
+ const previousApis = config2.apis;
17494
+ const previousBackend = config2.backend;
17495
+ const apis = Array.isArray(config2.apis) ? [...config2.apis] : [];
17496
+ const first = apis[0] && typeof apis[0] === "object" ? { ...apis[0] } : { type: "file", path: "api" };
17497
+ first.type = first.type ?? "file";
17498
+ first.input = relativeInput;
17499
+ first.path = first.path ?? "api";
17500
+ config2.apis = [first, ...apis.slice(1)];
17501
+ const backend = config2.backend && typeof config2.backend === "object" ? { ...config2.backend } : {};
17502
+ backend.openApiPath = relativeInput;
17503
+ config2.backend = backend;
17504
+ await writeFile14(configPath, `${JSON.stringify(config2, null, 2)}
17505
+ `, "utf8");
17506
+ return {
17507
+ restore: async () => {
17508
+ const currentRaw = await readFile15(configPath, "utf8");
17509
+ const current = JSON.parse(currentRaw);
17510
+ if (previousApis === void 0) {
17511
+ delete current.apis;
17512
+ } else {
17513
+ current.apis = previousApis;
17327
17514
  }
17328
- return {
17329
- status: options.getStatus(last),
17330
- value: last,
17331
- timedOut: true,
17332
- cancelled: true
17333
- };
17334
- }
17335
- try {
17336
- last = await options.poll();
17337
- } catch (error) {
17338
- const handled = options.onPollError?.(error, last);
17339
- if (handled) {
17340
- return handled;
17515
+ if (previousBackend === void 0) {
17516
+ delete current.backend;
17517
+ } else {
17518
+ current.backend = previousBackend;
17341
17519
  }
17342
- throw error;
17343
- }
17344
- options.onPoll?.(last);
17345
- if (options.progress && isPollableJobValue(last)) {
17346
- await reportPollProgress(options.progress, stepLabel, last);
17347
- }
17348
- if (options.isTerminal(last)) {
17349
- return {
17350
- status: options.getStatus(last),
17351
- value: last,
17352
- timedOut: false
17353
- };
17520
+ await writeFile14(configPath, `${JSON.stringify(current, null, 2)}
17521
+ `, "utf8");
17354
17522
  }
17355
- if (Date.now() + intervalMs > deadline) {
17356
- break;
17523
+ };
17524
+ }
17525
+ async function prepareOpenApiServersForGatewayBuild(options) {
17526
+ const siteDirectory = path55.resolve(options.siteDirectory);
17527
+ let openApiPath = resolveBackendOpenApiPath(options.manifest?.backend?.openApiPath, {
17528
+ siteDirectory,
17529
+ backendSourceDirectory: options.manifest?.backend?.sourceDirectory
17530
+ });
17531
+ if (!openApiPath) {
17532
+ try {
17533
+ const raw = await readFile15(path55.join(siteDirectory, APITOGO_CONFIG_FILENAME2), "utf8");
17534
+ const config2 = JSON.parse(raw);
17535
+ const input5 = Array.isArray(config2.apis) ? config2.apis[0]?.input?.trim() : void 0;
17536
+ openApiPath = resolveBackendOpenApiPath(input5, { siteDirectory });
17537
+ } catch {
17357
17538
  }
17358
- await sleep2(intervalMs);
17359
17539
  }
17360
- if (!last) {
17361
- throw new Error("Polling produced no results.");
17540
+ if (!openApiPath) {
17541
+ return null;
17362
17542
  }
17363
- return {
17364
- status: options.getStatus(last),
17365
- value: last,
17366
- timedOut: true
17367
- };
17368
- }
17369
- function isPollableJobValue(value) {
17370
- return typeof value === "object" && value !== null && "status" in value;
17543
+ const { outputPath, relativeInput } = await materializePublishOpenApiWithGateway(
17544
+ siteDirectory,
17545
+ openApiPath,
17546
+ options.gatewayUrl
17547
+ );
17548
+ const { restore } = await pointSiteApisAtPublishOpenApi(siteDirectory, relativeInput);
17549
+ return { relativeInput, outputPath, restore };
17371
17550
  }
17372
17551
 
17373
- // src/cli/platform/helpers/warm-pool.ts
17374
- var WARM_POOL_CAPACITY_TEMPORARILY_UNAVAILABLE = "WARM_POOL_CAPACITY_TEMPORARILY_UNAVAILABLE";
17375
- var COLD_MODULE_INFRA_RESOURCE_TYPES = /* @__PURE__ */ new Set([
17376
- "AzureProjectInfrastructure",
17377
- "DevPortalInfrastructure",
17378
- "BackendInfrastructure"
17379
- ]);
17380
- var WARM_POOL_CREATE_RETRY_BUDGET_MS = 10 * 60 * 1e3;
17381
- function sleep3(ms) {
17382
- return new Promise((resolve) => setTimeout(resolve, ms));
17383
- }
17384
- function warmPoolRetryDelayMs() {
17385
- return 6e4 + Math.floor(Math.random() * 30001);
17386
- }
17387
- function isWarmPoolCapacityUnavailableError(error) {
17388
- if (!(error instanceof PlatformApiError)) {
17389
- return false;
17552
+ // src/cli/platform/helpers/public-routes.ts
17553
+ function normalizeActionKeyFormat(actionKey) {
17554
+ const trimmed = actionKey.trim();
17555
+ if (trimmed.includes(":")) {
17556
+ const [method2, ...pathParts] = trimmed.split(":");
17557
+ const path73 = pathParts.join(":").trim();
17558
+ return `${method2.toLowerCase()}:${path73.startsWith("/") ? path73 : `/${path73}`}`;
17390
17559
  }
17391
- if (error.status !== 503) {
17392
- return false;
17560
+ const spaceIndex = trimmed.indexOf(" ");
17561
+ if (spaceIndex <= 0) {
17562
+ throw new Error(
17563
+ "actionKey must be in format 'get:/path' or 'GET /path' (method plus path)."
17564
+ );
17393
17565
  }
17394
- const code = error.problem?.code;
17395
- return code === WARM_POOL_CAPACITY_TEMPORARILY_UNAVAILABLE;
17396
- }
17397
- function isWarmPoolInfrastructureReady(project) {
17398
- return project.infrastructureStatus?.toLowerCase() === "ready";
17399
- }
17400
- function isWarmPoolInfrastructureFailed(project) {
17401
- return project.infrastructureStatus?.toLowerCase() === "failed";
17566
+ const method = trimmed.slice(0, spaceIndex).trim().toLowerCase();
17567
+ const path72 = trimmed.slice(spaceIndex + 1).trim();
17568
+ return `${method}:${path72.startsWith("/") ? path72 : `/${path72}`}`;
17402
17569
  }
17403
- function isWarmPoolInfrastructureTerminal(project) {
17404
- const status2 = project.infrastructureStatus?.toLowerCase() ?? "";
17405
- return status2 === "ready" || status2 === "failed" || status2 === "deleting";
17570
+ function normalizePublicRoute(route) {
17571
+ const trimmed = route.trim();
17572
+ if (!trimmed) {
17573
+ throw new Error("public route must be a non-empty string");
17574
+ }
17575
+ if (trimmed.startsWith("/")) {
17576
+ return normalizeActionKeyFormat(`GET ${trimmed}`);
17577
+ }
17578
+ return normalizeActionKeyFormat(trimmed);
17406
17579
  }
17407
- function shouldSkipColdModuleInfrastructureWait(project, infraStatus) {
17408
- if (isWarmPoolInfrastructureReady(project)) {
17409
- return true;
17580
+ function collectManifestPublicActionKeys(gateway) {
17581
+ if (!gateway) {
17582
+ return [];
17410
17583
  }
17411
- const resourceType = infraStatus?.resourceType;
17412
- if (resourceType === "WarmPoolBinding") {
17413
- return true;
17584
+ const keys = /* @__PURE__ */ new Set();
17585
+ for (const route of gateway.publicRoutes ?? []) {
17586
+ try {
17587
+ keys.add(normalizePublicRoute(route));
17588
+ } catch {
17589
+ }
17414
17590
  }
17415
- if (resourceType && COLD_MODULE_INFRA_RESOURCE_TYPES.has(resourceType)) {
17416
- return false;
17591
+ if (gateway.publicHealthActionKey?.trim()) {
17592
+ try {
17593
+ keys.add(normalizePublicRoute(gateway.publicHealthActionKey));
17594
+ } catch {
17595
+ }
17417
17596
  }
17418
- return false;
17597
+ return [...keys];
17419
17598
  }
17420
- function resolvePortalBackendFqdn(project) {
17421
- const portalApiUrl = project.portalApiUrl?.trim();
17422
- if (!portalApiUrl) {
17423
- return null;
17424
- }
17599
+
17600
+ // src/cli/platform/helpers/publish-ledger.ts
17601
+ import { mkdir as mkdir12, readFile as readFile16, writeFile as writeFile15 } from "node:fs/promises";
17602
+ import path56 from "node:path";
17603
+ var PUBLISH_LEDGER_RELATIVE_PATH = ".apitogo/publish-state.json";
17604
+ function publishLedgerPath(siteDirectory) {
17605
+ return path56.join(path56.resolve(siteDirectory), PUBLISH_LEDGER_RELATIVE_PATH);
17606
+ }
17607
+ async function readPublishLedger(siteDirectory) {
17608
+ const filePath = publishLedgerPath(siteDirectory);
17425
17609
  try {
17426
- return new URL(portalApiUrl).hostname;
17610
+ const raw = await readFile16(filePath, "utf8");
17611
+ const parsed = JSON.parse(raw);
17612
+ if (parsed?.version !== 1 || !Array.isArray(parsed.steps)) {
17613
+ return null;
17614
+ }
17615
+ return parsed;
17427
17616
  } catch {
17428
17617
  return null;
17429
17618
  }
17430
17619
  }
17431
- var RESERVED_ENDPOINT_URL_AGENT_INSTRUCTION = "Do not share siteUrl, portalApiUrl, gatewayUrl, or any warm-pool hostnames with the user yet \u2014 those addresses are reserved infrastructure and show Azure's default page until the site or backend is actually deployed. Share live URLs only after publish_dev_portal, deploy_backend, or verify_dev_portal_live succeeds.";
17432
- function omitReservedEndpointUrls(project) {
17433
- const {
17434
- siteUrl: _siteUrl,
17435
- portalApiUrl: _portalApiUrl,
17436
- gatewayUrl: _gatewayUrl,
17437
- ...rest
17438
- } = project;
17439
- return rest;
17440
- }
17441
- async function waitForWarmPoolBinding(client, projectId, options) {
17442
- const poll = await pollUntilTerminal({
17443
- poll: () => client.getProject(projectId),
17444
- isTerminal: (project2) => isWarmPoolInfrastructureTerminal(project2),
17445
- getStatus: (project2) => {
17446
- if (isWarmPoolInfrastructureFailed(project2)) {
17447
- return "failed";
17448
- }
17449
- if (isWarmPoolInfrastructureReady(project2)) {
17450
- return "succeeded";
17451
- }
17452
- return "running";
17453
- },
17454
- timeoutMs: options?.timeoutMs ?? DEFAULT_POLL_TIMEOUT_MS,
17455
- intervalMs: options?.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS,
17456
- stepLabel: "Warm pool binding",
17457
- progress: options?.progress
17458
- });
17459
- const project = poll.value;
17460
- let status2;
17461
- if (poll.timedOut) {
17462
- status2 = "running";
17463
- } else if (isWarmPoolInfrastructureFailed(project)) {
17464
- status2 = "failed";
17465
- } else if (isWarmPoolInfrastructureReady(project)) {
17466
- status2 = "succeeded";
17467
- } else {
17468
- status2 = "running";
17469
- }
17470
- return {
17471
- status: status2,
17472
- timedOut: poll.timedOut,
17473
- project
17620
+ async function writePublishLedger(siteDirectory, ledger) {
17621
+ const filePath = publishLedgerPath(siteDirectory);
17622
+ await mkdir12(path56.dirname(filePath), { recursive: true });
17623
+ const next = {
17624
+ ...ledger,
17625
+ version: 1,
17626
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
17474
17627
  };
17628
+ await writeFile15(filePath, `${JSON.stringify(next, null, 2)}
17629
+ `, "utf8");
17630
+ return filePath;
17475
17631
  }
17476
- async function createProjectWithWarmPoolRetry(client, body, options) {
17477
- const maxRetryMs = options?.maxRetryMs ?? WARM_POOL_CREATE_RETRY_BUDGET_MS;
17478
- const startedAt = Date.now();
17479
- while (true) {
17480
- try {
17481
- return await client.createProject(body);
17482
- } catch (error) {
17483
- if (!isWarmPoolCapacityUnavailableError(error)) {
17484
- throw error;
17485
- }
17486
- if (Date.now() - startedAt >= maxRetryMs) {
17487
- throw error;
17488
- }
17489
- await sleep3(warmPoolRetryDelayMs());
17632
+ function resolveResumeStepFromLedger(ledger, orderedSteps) {
17633
+ if (!ledger?.steps?.length) {
17634
+ return void 0;
17635
+ }
17636
+ const byStep = new Map(ledger.steps.map((step) => [step.step, step]));
17637
+ for (const step of orderedSteps) {
17638
+ const record = byStep.get(step);
17639
+ if (!record || record.status !== "succeeded" && record.status !== "skipped") {
17640
+ return step;
17490
17641
  }
17491
17642
  }
17643
+ return void 0;
17492
17644
  }
17493
-
17494
- // src/cli/platform/helpers/dev-portal-settings-kinds.ts
17495
- function isHttpUrl(value) {
17496
- return /^https?:\/\//i.test(value.trim());
17497
- }
17498
- function resolveLogoSettings(logoUrl) {
17499
- const trimmed = logoUrl?.trim();
17500
- if (!trimmed) {
17501
- return { logoKind: "RepoRelative" };
17502
- }
17503
- if (isHttpUrl(trimmed)) {
17504
- return { logoKind: "AbsoluteUrl", logoValue: trimmed };
17505
- }
17506
- return { logoKind: "RepoRelative", logoValue: trimmed };
17645
+ function createEmptyPublishLedger(projectId) {
17646
+ return {
17647
+ version: 1,
17648
+ projectId,
17649
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
17650
+ steps: []
17651
+ };
17507
17652
  }
17508
- function resolveOpenApiSettings(openApiPath) {
17509
- const trimmed = openApiPath?.trim();
17510
- if (!trimmed) {
17511
- return { openApiSourceKind: "RepoRelative" };
17653
+ var StepTimer = class {
17654
+ startedAt;
17655
+ startedAtIso;
17656
+ constructor() {
17657
+ this.startedAt = Date.now();
17658
+ this.startedAtIso = new Date(this.startedAt).toISOString();
17512
17659
  }
17513
- if (isHttpUrl(trimmed)) {
17514
- return { openApiSourceKind: "ExternalUrl", openApiValue: trimmed };
17660
+ finish(detail) {
17661
+ const finishedAt = Date.now();
17662
+ return {
17663
+ startedAt: this.startedAtIso,
17664
+ finishedAt: new Date(finishedAt).toISOString(),
17665
+ durationMs: finishedAt - this.startedAt,
17666
+ detail
17667
+ };
17515
17668
  }
17516
- return { openApiSourceKind: "RepoRelative", openApiValue: trimmed };
17517
- }
17669
+ };
17518
17670
 
17519
17671
  // src/cli/platform/helpers/validate-dev-portal-readiness.ts
17520
17672
  import { lookup as lookup2 } from "node:dns/promises";
@@ -17758,337 +17910,247 @@ async function validateDevPortalReadiness(client, projectId, options) {
17758
17910
  };
17759
17911
  }
17760
17912
 
17761
- // src/cli/platform/helpers/publish-ledger.ts
17762
- import { mkdir as mkdir11, readFile as readFile15, writeFile as writeFile14 } from "node:fs/promises";
17763
- import path53 from "node:path";
17764
- var PUBLISH_LEDGER_RELATIVE_PATH = ".apitogo/publish-state.json";
17765
- function publishLedgerPath(siteDirectory) {
17766
- return path53.join(path53.resolve(siteDirectory), PUBLISH_LEDGER_RELATIVE_PATH);
17767
- }
17768
- async function readPublishLedger(siteDirectory) {
17769
- const filePath = publishLedgerPath(siteDirectory);
17770
- try {
17771
- const raw = await readFile15(filePath, "utf8");
17772
- const parsed = JSON.parse(raw);
17773
- if (parsed?.version !== 1 || !Array.isArray(parsed.steps)) {
17774
- return null;
17913
+ // src/cli/platform/helpers/wait-for-gateway-actions.ts
17914
+ async function waitForGatewayActionsRegistered(client, projectId, options) {
17915
+ const minActionCount = options?.minActionCount ?? 1;
17916
+ const timeoutMs = options?.timeoutMs ?? 18e4;
17917
+ const pollIntervalMs = options?.pollIntervalMs ?? 5e3;
17918
+ const started = Date.now();
17919
+ while (true) {
17920
+ const page = await client.listGatewayActions(projectId, 1, 1);
17921
+ const actionCount = page.totalCount;
17922
+ if (actionCount >= minActionCount) {
17923
+ return { actionCount, timedOut: false };
17775
17924
  }
17776
- return parsed;
17777
- } catch {
17778
- return null;
17925
+ if (Date.now() - started >= timeoutMs) {
17926
+ return { actionCount, timedOut: true };
17927
+ }
17928
+ await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
17779
17929
  }
17780
17930
  }
17781
- async function writePublishLedger(siteDirectory, ledger) {
17782
- const filePath = publishLedgerPath(siteDirectory);
17783
- await mkdir11(path53.dirname(filePath), { recursive: true });
17784
- const next = {
17785
- ...ledger,
17786
- version: 1,
17787
- updatedAt: (/* @__PURE__ */ new Date()).toISOString()
17788
- };
17789
- await writeFile14(filePath, `${JSON.stringify(next, null, 2)}
17790
- `, "utf8");
17791
- return filePath;
17931
+
17932
+ // src/cli/platform/helpers/status.ts
17933
+ function normalizeInfrastructureStatus(platformStatus) {
17934
+ switch (platformStatus.toLowerCase()) {
17935
+ case "completed":
17936
+ case "completed_with_warnings":
17937
+ return "succeeded";
17938
+ case "in_progress":
17939
+ return "running";
17940
+ case "failed":
17941
+ return "failed";
17942
+ case "pending":
17943
+ case "none":
17944
+ default:
17945
+ return "pending";
17946
+ }
17792
17947
  }
17793
- function resolveResumeStepFromLedger(ledger, orderedSteps) {
17794
- if (!ledger?.steps?.length) {
17795
- return void 0;
17948
+ function normalizeDeploymentStatus(platformStatus) {
17949
+ switch (platformStatus.toLowerCase()) {
17950
+ case "healthy":
17951
+ return "succeeded";
17952
+ case "failed":
17953
+ case "cancelled":
17954
+ return "failed";
17955
+ case "queued":
17956
+ case "validating":
17957
+ case "building":
17958
+ case "deploying":
17959
+ return "running";
17960
+ default:
17961
+ return "pending";
17796
17962
  }
17797
- const byStep = new Map(ledger.steps.map((step) => [step.step, step]));
17798
- for (const step of orderedSteps) {
17799
- const record = byStep.get(step);
17800
- if (!record || record.status !== "succeeded" && record.status !== "skipped") {
17801
- return step;
17963
+ }
17964
+ function isTerminalInfrastructureStatus(platformStatus) {
17965
+ const s = platformStatus.toLowerCase();
17966
+ return s === "completed" || s === "completed_with_warnings" || s === "failed";
17967
+ }
17968
+ function isTerminalDeploymentStatus(platformStatus) {
17969
+ const s = platformStatus.toLowerCase();
17970
+ return s === "healthy" || s === "failed" || s === "cancelled";
17971
+ }
17972
+ var DEFAULT_POLL_INTERVAL_MS = 5e3;
17973
+ var DEFAULT_POLL_TIMEOUT_MS = 15 * 60 * 1e3;
17974
+
17975
+ // src/cli/platform/helpers/poll.ts
17976
+ function sleep2(ms) {
17977
+ return new Promise((resolve) => setTimeout(resolve, ms));
17978
+ }
17979
+ async function pollUntilTerminal(options) {
17980
+ const timeoutMs = options.timeoutMs ?? DEFAULT_POLL_TIMEOUT_MS;
17981
+ const intervalMs = options.intervalMs ?? DEFAULT_POLL_INTERVAL_MS;
17982
+ const deadline = Date.now() + timeoutMs;
17983
+ const stepLabel = options.stepLabel ?? "Job";
17984
+ let last;
17985
+ while (Date.now() <= deadline) {
17986
+ if (options.progress?.isCancelled()) {
17987
+ if (!last) {
17988
+ throw new Error("Operation cancelled before first poll result.");
17989
+ }
17990
+ return {
17991
+ status: options.getStatus(last),
17992
+ value: last,
17993
+ timedOut: true,
17994
+ cancelled: true
17995
+ };
17996
+ }
17997
+ try {
17998
+ last = await options.poll();
17999
+ } catch (error) {
18000
+ const handled = options.onPollError?.(error, last);
18001
+ if (handled) {
18002
+ return handled;
18003
+ }
18004
+ throw error;
18005
+ }
18006
+ options.onPoll?.(last);
18007
+ if (options.progress && isPollableJobValue(last)) {
18008
+ await reportPollProgress(options.progress, stepLabel, last);
18009
+ }
18010
+ if (options.isTerminal(last)) {
18011
+ return {
18012
+ status: options.getStatus(last),
18013
+ value: last,
18014
+ timedOut: false
18015
+ };
17802
18016
  }
18017
+ if (Date.now() + intervalMs > deadline) {
18018
+ break;
18019
+ }
18020
+ await sleep2(intervalMs);
18021
+ }
18022
+ if (!last) {
18023
+ throw new Error("Polling produced no results.");
17803
18024
  }
17804
- return void 0;
17805
- }
17806
- function createEmptyPublishLedger(projectId) {
17807
18025
  return {
17808
- version: 1,
17809
- projectId,
17810
- updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
17811
- steps: []
18026
+ status: options.getStatus(last),
18027
+ value: last,
18028
+ timedOut: true
17812
18029
  };
17813
18030
  }
17814
- var StepTimer = class {
17815
- startedAt;
17816
- startedAtIso;
17817
- constructor() {
17818
- this.startedAt = Date.now();
17819
- this.startedAtIso = new Date(this.startedAt).toISOString();
17820
- }
17821
- finish(detail) {
17822
- const finishedAt = Date.now();
17823
- return {
17824
- startedAt: this.startedAtIso,
17825
- finishedAt: new Date(finishedAt).toISOString(),
17826
- durationMs: finishedAt - this.startedAt,
17827
- detail
17828
- };
17829
- }
17830
- };
17831
-
17832
- // src/cli/platform/helpers/wait-for-gateway-actions.ts
17833
- async function waitForGatewayActionsRegistered(client, projectId, options) {
17834
- const minActionCount = options?.minActionCount ?? 1;
17835
- const timeoutMs = options?.timeoutMs ?? 18e4;
17836
- const pollIntervalMs = options?.pollIntervalMs ?? 5e3;
17837
- const started = Date.now();
17838
- while (true) {
17839
- const page = await client.listGatewayActions(projectId, 1, 1);
17840
- const actionCount = page.totalCount;
17841
- if (actionCount >= minActionCount) {
17842
- return { actionCount, timedOut: false };
17843
- }
17844
- if (Date.now() - started >= timeoutMs) {
17845
- return { actionCount, timedOut: true };
17846
- }
17847
- await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
17848
- }
18031
+ function isPollableJobValue(value) {
18032
+ return typeof value === "object" && value !== null && "status" in value;
17849
18033
  }
17850
18034
 
17851
- // src/cli/platform/helpers/deploy-artifact-hash.ts
17852
- import { createHash as createHash2 } from "node:crypto";
17853
- import { createReadStream, promises as fs8 } from "node:fs";
17854
- import path55 from "node:path";
17855
-
17856
- // src/cli/platform/helpers/package-files.ts
17857
- import { promises as fs7 } from "node:fs";
17858
- import path54 from "node:path";
17859
- import ignore from "ignore";
17860
- var DEFAULT_EXCLUDES = /* @__PURE__ */ new Set([
17861
- "node_modules",
17862
- ".git",
17863
- ".DS_Store",
17864
- "dist",
17865
- "bin",
17866
- "obj"
18035
+ // src/cli/platform/helpers/warm-pool.ts
18036
+ var WARM_POOL_CAPACITY_TEMPORARILY_UNAVAILABLE = "WARM_POOL_CAPACITY_TEMPORARILY_UNAVAILABLE";
18037
+ var COLD_MODULE_INFRA_RESOURCE_TYPES = /* @__PURE__ */ new Set([
18038
+ "AzureProjectInfrastructure",
18039
+ "DevPortalInfrastructure",
18040
+ "BackendInfrastructure"
17867
18041
  ]);
17868
- async function loadGitignore(rootDir) {
17869
- const ig = ignore();
17870
- ig.add(["node_modules/", ".git/", "dist/", "bin/", "obj/"]);
17871
- const gitignorePath = path54.join(rootDir, ".gitignore");
17872
- try {
17873
- const content = await fs7.readFile(gitignorePath, "utf8");
17874
- ig.add(content);
17875
- } catch {
17876
- }
17877
- return ig;
18042
+ var WARM_POOL_CREATE_RETRY_BUDGET_MS = 10 * 60 * 1e3;
18043
+ function sleep3(ms) {
18044
+ return new Promise((resolve) => setTimeout(resolve, ms));
17878
18045
  }
17879
- async function collectFiles(rootDir, relativeDir, ig, files) {
17880
- const absDir = path54.join(rootDir, relativeDir);
17881
- const entries = await fs7.readdir(absDir, { withFileTypes: true });
17882
- for (const entry of entries) {
17883
- const rel = relativeDir ? path54.join(relativeDir, entry.name) : entry.name;
17884
- const relPosix = rel.split(path54.sep).join("/");
17885
- if (DEFAULT_EXCLUDES.has(entry.name) && !relativeDir) {
17886
- continue;
17887
- }
17888
- if (ig.ignores(relPosix)) {
17889
- continue;
17890
- }
17891
- if (entry.isDirectory()) {
17892
- await collectFiles(rootDir, rel, ig, files);
17893
- } else if (entry.isFile()) {
17894
- files.push(rel);
17895
- }
17896
- }
18046
+ function warmPoolRetryDelayMs() {
18047
+ return 6e4 + Math.floor(Math.random() * 30001);
17897
18048
  }
17898
- async function collectPackageableFiles(sourceDirectory) {
17899
- const resolved = path54.resolve(sourceDirectory);
17900
- const stat4 = await fs7.stat(resolved).catch(() => null);
17901
- if (!stat4?.isDirectory()) {
17902
- throw new Error(`sourceDirectory is not a directory: ${resolved}`);
18049
+ function isWarmPoolCapacityUnavailableError(error) {
18050
+ if (!(error instanceof PlatformApiError)) {
18051
+ return false;
17903
18052
  }
17904
- const ig = await loadGitignore(resolved);
17905
- const files = [];
17906
- await collectFiles(resolved, "", ig, files);
17907
- if (files.length === 0) {
17908
- throw new Error(`No files to package in ${resolved}`);
18053
+ if (error.status !== 503) {
18054
+ return false;
17909
18055
  }
17910
- return files.sort();
18056
+ const code = error.problem?.code;
18057
+ return code === WARM_POOL_CAPACITY_TEMPORARILY_UNAVAILABLE;
17911
18058
  }
17912
-
17913
- // src/cli/platform/helpers/deploy-artifact-hash.ts
17914
- async function sha256File(filePath) {
17915
- const hash2 = createHash2("sha256");
17916
- await new Promise((resolve, reject) => {
17917
- const stream = createReadStream(filePath);
17918
- stream.on("data", (chunk) => hash2.update(chunk));
17919
- stream.on("error", reject);
17920
- stream.on("end", () => resolve());
17921
- });
17922
- return hash2.digest("hex");
18059
+ function isWarmPoolInfrastructureReady(project) {
18060
+ return project.infrastructureStatus?.toLowerCase() === "ready";
17923
18061
  }
17924
- async function hashDirectoryContents(rootDirectory) {
17925
- const resolved = path55.resolve(rootDirectory);
17926
- const files = (await collectPackageableFiles(resolved)).sort((a, b) => a.localeCompare(b));
17927
- const hash2 = createHash2("sha256");
17928
- for (const rel of files) {
17929
- hash2.update(rel);
17930
- hash2.update("\0");
17931
- hash2.update(await fs8.readFile(path55.join(resolved, rel)));
17932
- hash2.update("\0");
17933
- }
17934
- return hash2.digest("hex");
18062
+ function isWarmPoolInfrastructureFailed(project) {
18063
+ return project.infrastructureStatus?.toLowerCase() === "failed";
17935
18064
  }
17936
- async function hashDevPortalSiteSources(siteDirectory, options) {
17937
- const resolved = path55.resolve(siteDirectory);
17938
- const hash2 = createHash2("sha256");
17939
- const candidates = [
17940
- "apitogo.config.json",
17941
- "apitogo.config.ts",
17942
- "package.json",
17943
- "zudoku.config.ts",
17944
- "zudoku.config.tsx"
17945
- ];
17946
- for (const name of candidates) {
17947
- const full = path55.join(resolved, name);
17948
- try {
17949
- hash2.update(name);
17950
- hash2.update("\0");
17951
- hash2.update(await fs8.readFile(full));
17952
- hash2.update("\0");
17953
- } catch {
17954
- }
18065
+ function isWarmPoolInfrastructureTerminal(project) {
18066
+ const status2 = project.infrastructureStatus?.toLowerCase() ?? "";
18067
+ return status2 === "ready" || status2 === "failed" || status2 === "deleting";
18068
+ }
18069
+ function shouldSkipColdModuleInfrastructureWait(project, infraStatus) {
18070
+ if (isWarmPoolInfrastructureReady(project)) {
18071
+ return true;
17955
18072
  }
17956
- for (const dir of ["pages", "landing", "src"]) {
17957
- const full = path55.join(resolved, dir);
17958
- try {
17959
- const st = await fs8.stat(full);
17960
- if (!st.isDirectory()) continue;
17961
- hash2.update(dir);
17962
- hash2.update("\0");
17963
- hash2.update(await hashDirectoryContents(full));
17964
- hash2.update("\0");
17965
- } catch {
17966
- }
18073
+ const resourceType = infraStatus?.resourceType;
18074
+ if (resourceType === "WarmPoolBinding") {
18075
+ return true;
17967
18076
  }
17968
- const gatewayUrl = options?.gatewayUrl?.trim().replace(/\/+$/, "");
17969
- if (gatewayUrl) {
17970
- hash2.update("gatewayUrl");
17971
- hash2.update("\0");
17972
- hash2.update(gatewayUrl);
17973
- hash2.update("\0");
18077
+ if (resourceType && COLD_MODULE_INFRA_RESOURCE_TYPES.has(resourceType)) {
18078
+ return false;
17974
18079
  }
17975
- return hash2.digest("hex");
17976
- }
17977
-
17978
- // src/cli/platform/helpers/openapi-gateway-servers.ts
17979
- import { mkdir as mkdir12, readFile as readFile16, writeFile as writeFile15 } from "node:fs/promises";
17980
- import path56 from "node:path";
17981
- import YAML3 from "yaml";
17982
- var GATEWAY_URL_ENV_KEY = "APITOGO_GATEWAY_URL";
17983
- var PUBLISH_OPENAPI_RELATIVE = ".apitogo/openapi-publish.json";
17984
- function normalizeGatewayUrl(gatewayUrl) {
17985
- return gatewayUrl.trim().replace(/\/+$/, "");
17986
- }
17987
- function withGatewayServers(doc, gatewayUrl) {
17988
- const url2 = normalizeGatewayUrl(gatewayUrl);
17989
- return {
17990
- ...doc,
17991
- servers: [{ url: url2, description: "API gateway" }]
17992
- };
18080
+ return false;
17993
18081
  }
17994
- async function resolveProjectGatewayUrl(client, projectId) {
17995
- const project = await client.getProject(projectId);
17996
- const fromProject = project.gatewayUrl?.trim();
17997
- if (fromProject) {
17998
- return normalizeGatewayUrl(fromProject);
18082
+ function resolvePortalBackendFqdn(project) {
18083
+ const portalApiUrl = project.portalApiUrl?.trim();
18084
+ if (!portalApiUrl) {
18085
+ return null;
17999
18086
  }
18000
18087
  try {
18001
- const summary = await client.getBackendSummary(projectId);
18002
- const fromBackend = summary.backendUrl?.trim();
18003
- if (fromBackend) {
18004
- return normalizeGatewayUrl(fromBackend);
18005
- }
18088
+ return new URL(portalApiUrl).hostname;
18006
18089
  } catch {
18090
+ return null;
18007
18091
  }
18008
- return null;
18009
- }
18010
- async function readOpenApiDocument(filePath) {
18011
- const content = await readFile16(filePath, "utf8");
18012
- const ext = path56.extname(filePath).toLowerCase();
18013
- const parsed = ext === ".json" ? JSON.parse(content) : YAML3.parse(content);
18014
- if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
18015
- throw new Error(`OpenAPI document must be an object: ${filePath}`);
18016
- }
18017
- return parsed;
18018
18092
  }
18019
- async function materializePublishOpenApiWithGateway(siteDirectory, sourceOpenApiPath, gatewayUrl) {
18020
- const site = path56.resolve(siteDirectory);
18021
- const source = path56.resolve(sourceOpenApiPath);
18022
- const relativeInput = PUBLISH_OPENAPI_RELATIVE;
18023
- const outputPath = path56.join(site, ...relativeInput.split("/"));
18024
- const doc = await readOpenApiDocument(source);
18025
- const rewritten = withGatewayServers(doc, gatewayUrl);
18026
- await mkdir12(path56.dirname(outputPath), { recursive: true });
18027
- await writeFile15(outputPath, `${JSON.stringify(rewritten, null, 2)}
18028
- `, "utf8");
18029
- return { outputPath, relativeInput };
18093
+ var RESERVED_ENDPOINT_URL_AGENT_INSTRUCTION = "Do not share siteUrl, portalApiUrl, gatewayUrl, or any warm-pool hostnames with the user yet \u2014 those addresses are reserved infrastructure and show Azure's default page until the site or backend is actually deployed. Share live URLs only after publish_dev_portal, deploy_backend, or verify_dev_portal_live succeeds.";
18094
+ function omitReservedEndpointUrls(project) {
18095
+ const {
18096
+ siteUrl: _siteUrl,
18097
+ portalApiUrl: _portalApiUrl,
18098
+ gatewayUrl: _gatewayUrl,
18099
+ ...rest
18100
+ } = project;
18101
+ return rest;
18030
18102
  }
18031
- async function pointSiteApisAtPublishOpenApi(siteDirectory, relativeInput) {
18032
- const configPath = path56.join(path56.resolve(siteDirectory), APITOGO_CONFIG_FILENAME2);
18033
- const raw = await readFile16(configPath, "utf8");
18034
- const config2 = JSON.parse(raw);
18035
- const previousApis = config2.apis;
18036
- const previousBackend = config2.backend;
18037
- const apis = Array.isArray(config2.apis) ? [...config2.apis] : [];
18038
- const first = apis[0] && typeof apis[0] === "object" ? { ...apis[0] } : { type: "file", path: "api" };
18039
- first.type = first.type ?? "file";
18040
- first.input = relativeInput;
18041
- first.path = first.path ?? "api";
18042
- config2.apis = [first, ...apis.slice(1)];
18043
- const backend = config2.backend && typeof config2.backend === "object" ? { ...config2.backend } : {};
18044
- backend.openApiPath = relativeInput;
18045
- config2.backend = backend;
18046
- await writeFile15(configPath, `${JSON.stringify(config2, null, 2)}
18047
- `, "utf8");
18048
- return {
18049
- restore: async () => {
18050
- const currentRaw = await readFile16(configPath, "utf8");
18051
- const current = JSON.parse(currentRaw);
18052
- if (previousApis === void 0) {
18053
- delete current.apis;
18054
- } else {
18055
- current.apis = previousApis;
18103
+ async function waitForWarmPoolBinding(client, projectId, options) {
18104
+ const poll = await pollUntilTerminal({
18105
+ poll: () => client.getProject(projectId),
18106
+ isTerminal: (project2) => isWarmPoolInfrastructureTerminal(project2),
18107
+ getStatus: (project2) => {
18108
+ if (isWarmPoolInfrastructureFailed(project2)) {
18109
+ return "failed";
18056
18110
  }
18057
- if (previousBackend === void 0) {
18058
- delete current.backend;
18059
- } else {
18060
- current.backend = previousBackend;
18111
+ if (isWarmPoolInfrastructureReady(project2)) {
18112
+ return "succeeded";
18061
18113
  }
18062
- await writeFile15(configPath, `${JSON.stringify(current, null, 2)}
18063
- `, "utf8");
18064
- }
18114
+ return "running";
18115
+ },
18116
+ timeoutMs: options?.timeoutMs ?? DEFAULT_POLL_TIMEOUT_MS,
18117
+ intervalMs: options?.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS,
18118
+ stepLabel: "Warm pool binding",
18119
+ progress: options?.progress
18120
+ });
18121
+ const project = poll.value;
18122
+ let status2;
18123
+ if (poll.timedOut) {
18124
+ status2 = "running";
18125
+ } else if (isWarmPoolInfrastructureFailed(project)) {
18126
+ status2 = "failed";
18127
+ } else if (isWarmPoolInfrastructureReady(project)) {
18128
+ status2 = "succeeded";
18129
+ } else {
18130
+ status2 = "running";
18131
+ }
18132
+ return {
18133
+ status: status2,
18134
+ timedOut: poll.timedOut,
18135
+ project
18065
18136
  };
18066
18137
  }
18067
- async function prepareOpenApiServersForGatewayBuild(options) {
18068
- const siteDirectory = path56.resolve(options.siteDirectory);
18069
- let openApiPath = resolveBackendOpenApiPath(options.manifest?.backend?.openApiPath, {
18070
- siteDirectory,
18071
- backendSourceDirectory: options.manifest?.backend?.sourceDirectory
18072
- });
18073
- if (!openApiPath) {
18138
+ async function createProjectWithWarmPoolRetry(client, body, options) {
18139
+ const maxRetryMs = options?.maxRetryMs ?? WARM_POOL_CREATE_RETRY_BUDGET_MS;
18140
+ const startedAt = Date.now();
18141
+ while (true) {
18074
18142
  try {
18075
- const raw = await readFile16(path56.join(siteDirectory, APITOGO_CONFIG_FILENAME2), "utf8");
18076
- const config2 = JSON.parse(raw);
18077
- const input4 = Array.isArray(config2.apis) ? config2.apis[0]?.input?.trim() : void 0;
18078
- openApiPath = resolveBackendOpenApiPath(input4, { siteDirectory });
18079
- } catch {
18143
+ return await client.createProject(body);
18144
+ } catch (error) {
18145
+ if (!isWarmPoolCapacityUnavailableError(error)) {
18146
+ throw error;
18147
+ }
18148
+ if (Date.now() - startedAt >= maxRetryMs) {
18149
+ throw error;
18150
+ }
18151
+ await sleep3(warmPoolRetryDelayMs());
18080
18152
  }
18081
18153
  }
18082
- if (!openApiPath) {
18083
- return null;
18084
- }
18085
- const { outputPath, relativeInput } = await materializePublishOpenApiWithGateway(
18086
- siteDirectory,
18087
- openApiPath,
18088
- options.gatewayUrl
18089
- );
18090
- const { restore } = await pointSiteApisAtPublishOpenApi(siteDirectory, relativeInput);
18091
- return { relativeInput, outputPath, restore };
18092
18154
  }
18093
18155
 
18094
18156
  // src/cli/platform/helpers/backend-packaging.ts
@@ -18124,15 +18186,15 @@ function runDotnetPublish(projectDir, outputDir) {
18124
18186
  shell: process.platform === "win32"
18125
18187
  }
18126
18188
  );
18127
- let output3 = "";
18189
+ let output4 = "";
18128
18190
  child.stdout.on("data", (chunk) => {
18129
- output3 += chunk.toString();
18191
+ output4 += chunk.toString();
18130
18192
  });
18131
18193
  child.stderr.on("data", (chunk) => {
18132
- output3 += chunk.toString();
18194
+ output4 += chunk.toString();
18133
18195
  });
18134
18196
  child.on("error", reject);
18135
- child.on("close", (exitCode) => resolve({ exitCode, output: output3 }));
18197
+ child.on("close", (exitCode) => resolve({ exitCode, output: output4 }));
18136
18198
  });
18137
18199
  }
18138
18200
  async function findCsproj(projectDir) {
@@ -18219,12 +18281,12 @@ async function packageBackendDirectory(sourceDirectory) {
18219
18281
  `apitogo-backend-${Date.now()}-${Math.random().toString(36).slice(2)}.zip`
18220
18282
  );
18221
18283
  await new Promise((resolve, reject) => {
18222
- const output3 = createWriteStream2(zipPath);
18284
+ const output4 = createWriteStream2(zipPath);
18223
18285
  const archive = new ZipArchive({ zlib: { level: 6 } });
18224
- output3.on("close", () => resolve());
18286
+ output4.on("close", () => resolve());
18225
18287
  archive.on("error", reject);
18226
- output3.on("error", reject);
18227
- archive.pipe(output3);
18288
+ output4.on("error", reject);
18289
+ archive.pipe(output4);
18228
18290
  for (const rel of files) {
18229
18291
  archive.append(createReadStream2(path58.join(resolved, rel)), { name: rel.split(path58.sep).join("/") });
18230
18292
  }
@@ -19506,7 +19568,9 @@ function normalizeModuleType2(type) {
19506
19568
  var READY_MODULE_STATUSES2 = /* @__PURE__ */ new Set(["provisioned", "configured", "started"]);
19507
19569
  function isModuleProvisioned(project, moduleType) {
19508
19570
  const normalized = normalizeModuleType2(moduleType);
19509
- const module = project.modules.find((m) => normalizeModuleType2(m.type) === normalized);
19571
+ const module = project.modules.find(
19572
+ (m) => normalizeModuleType2(m.type) === normalized
19573
+ );
19510
19574
  if (!module?.status) {
19511
19575
  return false;
19512
19576
  }
@@ -19528,10 +19592,14 @@ async function ensureModuleProvisioned(client, config2, projectId, moduleType, w
19528
19592
  }
19529
19593
  await progress?.onStep(`Activating module ${moduleType}`);
19530
19594
  try {
19531
- await handleActivateModule(client, { ...config2, defaultProjectId: projectId }, {
19532
- projectId,
19533
- moduleType
19534
- });
19595
+ await handleActivateModule(
19596
+ client,
19597
+ { ...config2, defaultProjectId: projectId },
19598
+ {
19599
+ projectId,
19600
+ moduleType
19601
+ }
19602
+ );
19535
19603
  } catch (error) {
19536
19604
  if (isAlreadyProvisionedError(error)) {
19537
19605
  return;
@@ -19567,7 +19635,9 @@ async function ensureModuleProvisioned(client, config2, projectId, moduleType, w
19567
19635
  }
19568
19636
  async function ensureModulesProvisioned(client, config2, projectId, moduleTypes, wait, timeoutMs, pollIntervalMs, progress) {
19569
19637
  const project = await client.getProject(projectId);
19570
- const pending = moduleTypes.filter((moduleType) => !isModuleProvisioned(project, moduleType));
19638
+ const pending = moduleTypes.filter(
19639
+ (moduleType) => !isModuleProvisioned(project, moduleType)
19640
+ );
19571
19641
  if (pending.length === 0) {
19572
19642
  return;
19573
19643
  }
@@ -19580,228 +19650,100 @@ async function ensureModulesProvisioned(client, config2, projectId, moduleTypes,
19580
19650
  wait,
19581
19651
  timeoutMs,
19582
19652
  pollIntervalMs,
19583
- progress
19584
- );
19585
- return;
19586
- }
19587
- await progress?.onStep(`Activating modules: ${pending.join(", ")}`);
19588
- await Promise.all(
19589
- pending.map(
19590
- (moduleType) => ensureModuleProvisioned(
19591
- client,
19592
- config2,
19593
- projectId,
19594
- moduleType,
19595
- wait,
19596
- timeoutMs,
19597
- pollIntervalMs,
19598
- progress?.child(moduleType)
19599
- )
19600
- )
19601
- );
19602
- }
19603
- async function resolveProjectIdForPublish(client, config2, args, wait, progress, timeoutMs, pollIntervalMs) {
19604
- const completedSteps = [];
19605
- if (args.projectId) {
19606
- const projectId = resolveProjectId(args.projectId, config2);
19607
- const project = await client.getProject(projectId);
19608
- if (!isPlatformPlanUnselected(project.planTier)) {
19609
- await ensureModulesProvisioned(
19610
- client,
19611
- config2,
19612
- projectId,
19613
- ["apihub", "dev-portal"],
19614
- wait,
19615
- timeoutMs,
19616
- pollIntervalMs,
19617
- progress
19618
- );
19619
- }
19620
- completedSteps.push({ step: "resolve_project", status: "succeeded", detail: { projectId } });
19621
- return { projectId, completedSteps };
19622
- }
19623
- if (!args.projectName?.trim()) {
19624
- throw new Error("projectName is required when projectId is not provided.");
19625
- }
19626
- await progress?.onStep("Creating project and provisioning modules");
19627
- const bootstrapJson = await handleBootstrapProject(
19628
- client,
19629
- config2,
19630
- {
19631
- name: args.projectName.trim(),
19632
- modules: ["apihub", "dev-portal"],
19633
- selectFreePlan: false,
19634
- configureGatewayBaseline: false,
19635
- wait,
19636
- timeoutMs,
19637
- pollIntervalMs
19638
- },
19639
- progress
19640
- );
19641
- const bootstrap = parseHandlerResult(bootstrapJson, "bootstrap_project");
19642
- completedSteps.push({ step: "bootstrap_project", status: "succeeded", detail: bootstrap });
19643
- return { projectId: bootstrap.projectId, completedSteps };
19644
- }
19645
-
19646
- // src/cli/platform/tools/dev-portal.ts
19647
- import { promises as fs11 } from "node:fs";
19648
- import path61 from "node:path";
19649
-
19650
- // src/cli/platform/helpers/tarball.ts
19651
- import { createReadStream as createReadStream3, createWriteStream as createWriteStream3, promises as fs10 } from "node:fs";
19652
- import { tmpdir as tmpdir3 } from "node:os";
19653
- import path59 from "node:path";
19654
- import { TarArchive } from "archiver";
19655
- var DEV_PORTAL_DEPLOY_ARCHIVE_ROOT = "dist";
19656
- function normalizeArchiveRoot(archiveRoot) {
19657
- const trimmed = archiveRoot.trim().replace(/^\/+|\/+$/g, "");
19658
- if (!trimmed) {
19659
- throw new Error("archiveRoot must not be empty.");
19660
- }
19661
- return `${trimmed}/`;
19662
- }
19663
- async function packageDirectoryAsTarGz(sourceDirectory, options) {
19664
- const resolved = path59.resolve(sourceDirectory);
19665
- const files = await collectPackageableFiles(resolved);
19666
- const entryPrefix = options?.archiveRoot ? normalizeArchiveRoot(options.archiveRoot) : "";
19667
- const archivePath = path59.join(
19668
- tmpdir3(),
19669
- `apitogo-docs-${Date.now()}-${Math.random().toString(36).slice(2)}.tgz`
19670
- );
19671
- await new Promise((resolve, reject) => {
19672
- const output3 = createWriteStream3(archivePath);
19673
- const archive = new TarArchive({ gzip: true, gzipOptions: { level: 6 } });
19674
- output3.on("close", () => resolve());
19675
- archive.on("error", reject);
19676
- output3.on("error", reject);
19677
- archive.pipe(output3);
19678
- for (const rel of files) {
19679
- const entryName = entryPrefix + rel.split(path59.sep).join("/");
19680
- archive.append(createReadStream3(path59.join(resolved, rel)), { name: entryName });
19681
- }
19682
- void archive.finalize();
19683
- });
19684
- const { size } = await fs10.stat(archivePath);
19685
- return { archivePath, byteSize: size };
19686
- }
19687
- async function cleanupArchive(archivePath) {
19688
- await fs10.unlink(archivePath).catch(() => void 0);
19689
- }
19690
- function isTarGzPath(filePath) {
19691
- const normalized = filePath.toLowerCase();
19692
- return normalized.endsWith(".tgz") || normalized.endsWith(".tar.gz");
19693
- }
19694
-
19695
- // src/cli/platform/helpers/dev-portal-backend-job.ts
19696
- var DEV_PORTAL_BACKEND_UPDATE_RESOURCE_TYPE = "DevPortalBackendUpdate";
19697
- var ACTIVE_JOB_STATUSES = /* @__PURE__ */ new Set(["Pending", "InProgress"]);
19698
- var RECENT_COMPLETED_WINDOW_MS = 3e4;
19699
- var BACKEND_UPDATE_JOB_APPEARANCE_TIMEOUT_MS = 3e4;
19700
- function isBackendUpdateJob(job) {
19701
- return job.resourceType === DEV_PORTAL_BACKEND_UPDATE_RESOURCE_TYPE;
19702
- }
19703
- function isActiveBackendUpdateJob(job) {
19704
- return isBackendUpdateJob(job) && ACTIVE_JOB_STATUSES.has(job.status);
19705
- }
19706
- function isRecentlyCompletedBackendUpdateJob(job) {
19707
- if (!isBackendUpdateJob(job) || job.status !== "Completed" || !job.completedAt) {
19708
- return false;
19709
- }
19710
- const completedAtMs = Date.parse(job.completedAt);
19711
- if (Number.isNaN(completedAtMs)) {
19712
- return false;
19713
- }
19714
- return Date.now() - completedAtMs <= RECENT_COMPLETED_WINDOW_MS;
19715
- }
19716
- function sortJobsNewestFirst(jobs) {
19717
- return [...jobs].sort(
19718
- (a, b) => Date.parse(b.createdAt) - Date.parse(a.createdAt)
19719
- );
19720
- }
19721
- function findBackendUpdateJob(jobs, options) {
19722
- const sorted = sortJobsNewestFirst(jobs);
19723
- if (options?.jobId) {
19724
- return sorted.find((job) => job.id === options.jobId) ?? null;
19725
- }
19726
- const active = sorted.find(isActiveBackendUpdateJob);
19727
- if (active) {
19728
- return active;
19729
- }
19730
- const baseline = options?.baselineJobIds;
19731
- if (baseline) {
19732
- const newlyCompleted = sorted.find(
19733
- (job) => isBackendUpdateJob(job) && !baseline.has(job.id) && (ACTIVE_JOB_STATUSES.has(job.status) || job.status === "Completed")
19734
- );
19735
- if (newlyCompleted) {
19736
- return newlyCompleted;
19737
- }
19738
- }
19739
- const recentlyCompleted = sorted.find(isRecentlyCompletedBackendUpdateJob);
19740
- if (recentlyCompleted) {
19741
- return recentlyCompleted;
19653
+ progress
19654
+ );
19655
+ return;
19742
19656
  }
19743
- return null;
19657
+ await progress?.onStep(`Activating modules: ${pending.join(", ")}`);
19658
+ await Promise.all(
19659
+ pending.map(
19660
+ (moduleType) => ensureModuleProvisioned(
19661
+ client,
19662
+ config2,
19663
+ projectId,
19664
+ moduleType,
19665
+ wait,
19666
+ timeoutMs,
19667
+ pollIntervalMs,
19668
+ progress?.child(moduleType)
19669
+ )
19670
+ )
19671
+ );
19744
19672
  }
19745
- async function discoverBackendUpdateJob(options) {
19746
- if (options.jobId) {
19747
- const result = await options.client.listProvisioningJobs(options.projectId);
19748
- const job = findBackendUpdateJob(result.jobs, { jobId: options.jobId });
19749
- if (!job) {
19750
- throw new Error(`DevPortalBackendUpdate job not found: ${options.jobId}`);
19673
+ async function resolveProjectIdForPublish(client, config2, args, wait, progress, timeoutMs, pollIntervalMs) {
19674
+ const completedSteps = [];
19675
+ if (args.projectId) {
19676
+ const projectId = resolveProjectId(args.projectId, config2);
19677
+ let project;
19678
+ try {
19679
+ project = await client.getProject(projectId);
19680
+ } catch (error) {
19681
+ if (isNotFoundError(error)) {
19682
+ return {
19683
+ blocked: true,
19684
+ payload: {
19685
+ status: "blocked",
19686
+ reason: "project_not_found",
19687
+ projectId,
19688
+ userMessage: `Project ${projectId} was not found. It may have been deleted, or it belongs to a different account than the one you're currently logged in as.`,
19689
+ nextStep: "create_or_fix_project"
19690
+ }
19691
+ };
19692
+ }
19693
+ throw error;
19751
19694
  }
19752
- return { job, timedOut: false };
19753
- }
19754
- const pollOnce = async () => {
19755
- const result = await options.client.listProvisioningJobs(options.projectId);
19756
- return findBackendUpdateJob(result.jobs, {
19757
- baselineJobIds: options.baselineJobIds
19695
+ if (!isPlatformPlanUnselected(project.planTier)) {
19696
+ await ensureModulesProvisioned(
19697
+ client,
19698
+ config2,
19699
+ projectId,
19700
+ ["apihub", "dev-portal"],
19701
+ wait,
19702
+ timeoutMs,
19703
+ pollIntervalMs,
19704
+ progress
19705
+ );
19706
+ }
19707
+ completedSteps.push({
19708
+ step: "resolve_project",
19709
+ status: "succeeded",
19710
+ detail: { projectId }
19758
19711
  });
19759
- };
19760
- const first = await pollOnce();
19761
- if (first) {
19762
- return { job: first, timedOut: false };
19712
+ return { blocked: false, projectId, completedSteps };
19763
19713
  }
19764
- if (!options.baselineJobIds) {
19765
- return { job: null, timedOut: false };
19714
+ if (!args.projectName?.trim()) {
19715
+ throw new Error("projectName is required when projectId is not provided.");
19766
19716
  }
19767
- const appearanceTimeoutMs = Math.min(
19768
- options.timeoutMs ?? BACKEND_UPDATE_JOB_APPEARANCE_TIMEOUT_MS,
19769
- BACKEND_UPDATE_JOB_APPEARANCE_TIMEOUT_MS
19717
+ await progress?.onStep("Creating project and provisioning modules");
19718
+ const bootstrapJson = await handleBootstrapProject(
19719
+ client,
19720
+ config2,
19721
+ {
19722
+ name: args.projectName.trim(),
19723
+ modules: ["apihub", "dev-portal"],
19724
+ selectFreePlan: false,
19725
+ configureGatewayBaseline: false,
19726
+ wait,
19727
+ timeoutMs,
19728
+ pollIntervalMs
19729
+ },
19730
+ progress
19770
19731
  );
19771
- const pollIntervalMs = options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
19772
- const deadline = Date.now() + appearanceTimeoutMs;
19773
- while (Date.now() <= deadline) {
19774
- const job = await pollOnce();
19775
- if (job) {
19776
- return { job, timedOut: false };
19777
- }
19778
- if (Date.now() + pollIntervalMs > deadline) {
19779
- break;
19780
- }
19781
- await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
19782
- }
19783
- return { job: null, timedOut: false };
19784
- }
19785
- async function pollBackendUpdateJobStatus(client, projectId, jobId, options) {
19786
- return pollUntilTerminal({
19787
- poll: () => client.getDevPortalDeployStatus(projectId, jobId),
19788
- isTerminal: (value) => isTerminalInfrastructureStatus(value.status),
19789
- getStatus: (value) => normalizeInfrastructureStatus(value.status),
19790
- timeoutMs: options?.timeoutMs ?? DEFAULT_POLL_TIMEOUT_MS,
19791
- intervalMs: options?.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS,
19792
- stepLabel: "Dev portal backend update"
19732
+ const bootstrap = parseHandlerResult(bootstrapJson, "bootstrap_project");
19733
+ completedSteps.push({
19734
+ step: "bootstrap_project",
19735
+ status: "succeeded",
19736
+ detail: bootstrap
19793
19737
  });
19794
- }
19795
- async function snapshotBackendUpdateJobIds(client, projectId) {
19796
- const result = await client.listProvisioningJobs(projectId);
19797
- return new Set(
19798
- result.jobs.filter(isBackendUpdateJob).map((job) => job.id)
19799
- );
19738
+ return { blocked: false, projectId: bootstrap.projectId, completedSteps };
19800
19739
  }
19801
19740
 
19741
+ // src/cli/platform/tools/dev-portal-site-build.ts
19742
+ import path62 from "node:path";
19743
+
19802
19744
  // src/cli/platform/helpers/dev-portal-site-api-url.ts
19803
19745
  import { readdir as readdir2, readFile as readFile17, writeFile as writeFile16 } from "node:fs/promises";
19804
- import path60 from "node:path";
19746
+ import path59 from "node:path";
19805
19747
  var SITE_API_BASE_URL_ENV_KEY = "VITE_APITOGO_AUTHENTICATION_API_BASE_URL";
19806
19748
  var PLACEHOLDER_HOST_MARKERS = [
19807
19749
  "dp-example.azurecontainerapps.io",
@@ -19819,10 +19761,10 @@ function isPlaceholderDevPortalApiUrl(apiBaseUrl) {
19819
19761
  return PLACEHOLDER_HOST_MARKERS.some((marker) => normalized.includes(marker));
19820
19762
  }
19821
19763
  async function readSiteApiBaseUrl(sourceDirectory) {
19822
- const resolved = path60.resolve(sourceDirectory);
19764
+ const resolved = path59.resolve(sourceDirectory);
19823
19765
  for (const envFile of [".env", ".env.production"]) {
19824
19766
  try {
19825
- const content = await readFile17(path60.join(resolved, envFile), "utf8");
19767
+ const content = await readFile17(path59.join(resolved, envFile), "utf8");
19826
19768
  const lines = [...content.matchAll(ENV_LINE_GLOBAL_RE)];
19827
19769
  if (lines.length === 0) {
19828
19770
  continue;
@@ -19847,11 +19789,11 @@ async function readSiteApiBaseUrl(sourceDirectory) {
19847
19789
  return null;
19848
19790
  }
19849
19791
  async function patchSiteApiBaseUrl(sourceDirectory, apiBaseUrl) {
19850
- const resolved = path60.resolve(sourceDirectory);
19792
+ const resolved = path59.resolve(sourceDirectory);
19851
19793
  const line = `${SITE_API_BASE_URL_ENV_KEY}=${apiBaseUrl}`;
19852
19794
  let patchedExisting = false;
19853
19795
  for (const envFile of [".env.production", ".env"]) {
19854
- const envPath = path60.join(resolved, envFile);
19796
+ const envPath = path59.join(resolved, envFile);
19855
19797
  try {
19856
19798
  const content = await readFile17(envPath, "utf8");
19857
19799
  const withoutAssignments = content.replace(ENV_LINE_GLOBAL_RE, "").replace(/\n{3,}/g, "\n\n").trimEnd();
@@ -19865,7 +19807,7 @@ ${line}
19865
19807
  }
19866
19808
  }
19867
19809
  if (!patchedExisting) {
19868
- await writeFile16(path60.join(resolved, ".env"), `${line}
19810
+ await writeFile16(path59.join(resolved, ".env"), `${line}
19869
19811
  `);
19870
19812
  }
19871
19813
  }
@@ -19873,7 +19815,7 @@ function tryProjectId(explicit, manifestProjectId, config2) {
19873
19815
  return explicit?.trim() || manifestProjectId?.trim() || config2.defaultProjectId?.trim() || void 0;
19874
19816
  }
19875
19817
  async function ensureLiveSiteApiBaseUrlForBuild(_client, config2, args, resolveFromProject) {
19876
- const sourceDirectory = path60.resolve(args.sourceDirectory);
19818
+ const sourceDirectory = path59.resolve(args.sourceDirectory);
19877
19819
  const explicit = args.apiBaseUrl?.trim();
19878
19820
  if (explicit && !isPlaceholderDevPortalApiUrl(explicit)) {
19879
19821
  await patchSiteApiBaseUrl(sourceDirectory, explicit);
@@ -19909,7 +19851,7 @@ async function* walkFiles(root) {
19909
19851
  return;
19910
19852
  }
19911
19853
  for (const entry of entries) {
19912
- const full = path60.join(root, entry.name);
19854
+ const full = path59.join(root, entry.name);
19913
19855
  if (entry.isDirectory()) {
19914
19856
  if (entry.name === "node_modules" || entry.name === ".git") {
19915
19857
  continue;
@@ -19921,39 +19863,195 @@ async function* walkFiles(root) {
19921
19863
  }
19922
19864
  }
19923
19865
  async function assertDeployableDistHasLiveApiBaseUrl(distDirectory, expectedApiBaseUrl) {
19924
- const dist = path60.resolve(distDirectory);
19925
- const siteRoot = path60.dirname(dist);
19866
+ const dist = path59.resolve(distDirectory);
19867
+ const siteRoot = path59.dirname(dist);
19926
19868
  const fromEnv = expectedApiBaseUrl?.trim() || await readSiteApiBaseUrl(siteRoot) || await readSiteApiBaseUrl(dist);
19927
19869
  if (!fromEnv || isPlaceholderDevPortalApiUrl(fromEnv)) {
19928
19870
  throw new Error(
19929
19871
  `Refusing to deploy: no live ${SITE_API_BASE_URL_ENV_KEY}. Run build_dev_portal_site first (it patches .env automatically) or set the live portal URL.`
19930
19872
  );
19931
19873
  }
19932
- const needle = fromEnv.replace(/\/+$/, "");
19933
- let found = false;
19934
- let foundPlaceholder = false;
19935
- for await (const file of walkFiles(dist)) {
19936
- const content = await readFile17(file, "utf8");
19937
- if (content.includes(needle)) {
19938
- found = true;
19939
- }
19940
- if (PLACEHOLDER_HOST_MARKERS.some((marker) => content.includes(marker))) {
19941
- foundPlaceholder = true;
19874
+ const needle = fromEnv.replace(/\/+$/, "");
19875
+ let found = false;
19876
+ let foundPlaceholder = false;
19877
+ for await (const file of walkFiles(dist)) {
19878
+ const content = await readFile17(file, "utf8");
19879
+ if (content.includes(needle)) {
19880
+ found = true;
19881
+ }
19882
+ if (PLACEHOLDER_HOST_MARKERS.some((marker) => content.includes(marker))) {
19883
+ foundPlaceholder = true;
19884
+ }
19885
+ if (found && foundPlaceholder) {
19886
+ break;
19887
+ }
19888
+ }
19889
+ if (foundPlaceholder && !found) {
19890
+ throw new Error(
19891
+ "Refusing to deploy: built site still contains a placeholder portal API URL. Rebuild with build_dev_portal_site."
19892
+ );
19893
+ }
19894
+ if (!found) {
19895
+ throw new Error(
19896
+ `Refusing to deploy: dist was not built with ${SITE_API_BASE_URL_ENV_KEY}=${needle}. Run build_dev_portal_site so the live URL is baked into the bundle.`
19897
+ );
19898
+ }
19899
+ }
19900
+
19901
+ // src/cli/platform/tools/dev-portal.ts
19902
+ import { promises as fs11 } from "node:fs";
19903
+ import path61 from "node:path";
19904
+
19905
+ // src/cli/platform/helpers/tarball.ts
19906
+ import { createReadStream as createReadStream3, createWriteStream as createWriteStream3, promises as fs10 } from "node:fs";
19907
+ import { tmpdir as tmpdir3 } from "node:os";
19908
+ import path60 from "node:path";
19909
+ import { TarArchive } from "archiver";
19910
+ var DEV_PORTAL_DEPLOY_ARCHIVE_ROOT = "dist";
19911
+ function normalizeArchiveRoot(archiveRoot) {
19912
+ const trimmed = archiveRoot.trim().replace(/^\/+|\/+$/g, "");
19913
+ if (!trimmed) {
19914
+ throw new Error("archiveRoot must not be empty.");
19915
+ }
19916
+ return `${trimmed}/`;
19917
+ }
19918
+ async function packageDirectoryAsTarGz(sourceDirectory, options) {
19919
+ const resolved = path60.resolve(sourceDirectory);
19920
+ const files = await collectPackageableFiles(resolved);
19921
+ const entryPrefix = options?.archiveRoot ? normalizeArchiveRoot(options.archiveRoot) : "";
19922
+ const archivePath = path60.join(
19923
+ tmpdir3(),
19924
+ `apitogo-docs-${Date.now()}-${Math.random().toString(36).slice(2)}.tgz`
19925
+ );
19926
+ await new Promise((resolve, reject) => {
19927
+ const output4 = createWriteStream3(archivePath);
19928
+ const archive = new TarArchive({ gzip: true, gzipOptions: { level: 6 } });
19929
+ output4.on("close", () => resolve());
19930
+ archive.on("error", reject);
19931
+ output4.on("error", reject);
19932
+ archive.pipe(output4);
19933
+ for (const rel of files) {
19934
+ const entryName = entryPrefix + rel.split(path60.sep).join("/");
19935
+ archive.append(createReadStream3(path60.join(resolved, rel)), { name: entryName });
19936
+ }
19937
+ void archive.finalize();
19938
+ });
19939
+ const { size } = await fs10.stat(archivePath);
19940
+ return { archivePath, byteSize: size };
19941
+ }
19942
+ async function cleanupArchive(archivePath) {
19943
+ await fs10.unlink(archivePath).catch(() => void 0);
19944
+ }
19945
+ function isTarGzPath(filePath) {
19946
+ const normalized = filePath.toLowerCase();
19947
+ return normalized.endsWith(".tgz") || normalized.endsWith(".tar.gz");
19948
+ }
19949
+
19950
+ // src/cli/platform/helpers/dev-portal-backend-job.ts
19951
+ var DEV_PORTAL_BACKEND_UPDATE_RESOURCE_TYPE = "DevPortalBackendUpdate";
19952
+ var ACTIVE_JOB_STATUSES = /* @__PURE__ */ new Set(["Pending", "InProgress"]);
19953
+ var RECENT_COMPLETED_WINDOW_MS = 3e4;
19954
+ var BACKEND_UPDATE_JOB_APPEARANCE_TIMEOUT_MS = 3e4;
19955
+ function isBackendUpdateJob(job) {
19956
+ return job.resourceType === DEV_PORTAL_BACKEND_UPDATE_RESOURCE_TYPE;
19957
+ }
19958
+ function isActiveBackendUpdateJob(job) {
19959
+ return isBackendUpdateJob(job) && ACTIVE_JOB_STATUSES.has(job.status);
19960
+ }
19961
+ function isRecentlyCompletedBackendUpdateJob(job) {
19962
+ if (!isBackendUpdateJob(job) || job.status !== "Completed" || !job.completedAt) {
19963
+ return false;
19964
+ }
19965
+ const completedAtMs = Date.parse(job.completedAt);
19966
+ if (Number.isNaN(completedAtMs)) {
19967
+ return false;
19968
+ }
19969
+ return Date.now() - completedAtMs <= RECENT_COMPLETED_WINDOW_MS;
19970
+ }
19971
+ function sortJobsNewestFirst(jobs) {
19972
+ return [...jobs].sort(
19973
+ (a, b) => Date.parse(b.createdAt) - Date.parse(a.createdAt)
19974
+ );
19975
+ }
19976
+ function findBackendUpdateJob(jobs, options) {
19977
+ const sorted = sortJobsNewestFirst(jobs);
19978
+ if (options?.jobId) {
19979
+ return sorted.find((job) => job.id === options.jobId) ?? null;
19980
+ }
19981
+ const active = sorted.find(isActiveBackendUpdateJob);
19982
+ if (active) {
19983
+ return active;
19984
+ }
19985
+ const baseline = options?.baselineJobIds;
19986
+ if (baseline) {
19987
+ const newlyCompleted = sorted.find(
19988
+ (job) => isBackendUpdateJob(job) && !baseline.has(job.id) && (ACTIVE_JOB_STATUSES.has(job.status) || job.status === "Completed")
19989
+ );
19990
+ if (newlyCompleted) {
19991
+ return newlyCompleted;
19992
+ }
19993
+ }
19994
+ const recentlyCompleted = sorted.find(isRecentlyCompletedBackendUpdateJob);
19995
+ if (recentlyCompleted) {
19996
+ return recentlyCompleted;
19997
+ }
19998
+ return null;
19999
+ }
20000
+ async function discoverBackendUpdateJob(options) {
20001
+ if (options.jobId) {
20002
+ const result = await options.client.listProvisioningJobs(options.projectId);
20003
+ const job = findBackendUpdateJob(result.jobs, { jobId: options.jobId });
20004
+ if (!job) {
20005
+ throw new Error(`DevPortalBackendUpdate job not found: ${options.jobId}`);
20006
+ }
20007
+ return { job, timedOut: false };
20008
+ }
20009
+ const pollOnce = async () => {
20010
+ const result = await options.client.listProvisioningJobs(options.projectId);
20011
+ return findBackendUpdateJob(result.jobs, {
20012
+ baselineJobIds: options.baselineJobIds
20013
+ });
20014
+ };
20015
+ const first = await pollOnce();
20016
+ if (first) {
20017
+ return { job: first, timedOut: false };
20018
+ }
20019
+ if (!options.baselineJobIds) {
20020
+ return { job: null, timedOut: false };
20021
+ }
20022
+ const appearanceTimeoutMs = Math.min(
20023
+ options.timeoutMs ?? BACKEND_UPDATE_JOB_APPEARANCE_TIMEOUT_MS,
20024
+ BACKEND_UPDATE_JOB_APPEARANCE_TIMEOUT_MS
20025
+ );
20026
+ const pollIntervalMs = options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
20027
+ const deadline = Date.now() + appearanceTimeoutMs;
20028
+ while (Date.now() <= deadline) {
20029
+ const job = await pollOnce();
20030
+ if (job) {
20031
+ return { job, timedOut: false };
19942
20032
  }
19943
- if (found && foundPlaceholder) {
20033
+ if (Date.now() + pollIntervalMs > deadline) {
19944
20034
  break;
19945
20035
  }
20036
+ await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
19946
20037
  }
19947
- if (foundPlaceholder && !found) {
19948
- throw new Error(
19949
- "Refusing to deploy: built site still contains a placeholder portal API URL. Rebuild with build_dev_portal_site."
19950
- );
19951
- }
19952
- if (!found) {
19953
- throw new Error(
19954
- `Refusing to deploy: dist was not built with ${SITE_API_BASE_URL_ENV_KEY}=${needle}. Run build_dev_portal_site so the live URL is baked into the bundle.`
19955
- );
19956
- }
20038
+ return { job: null, timedOut: false };
20039
+ }
20040
+ async function pollBackendUpdateJobStatus(client, projectId, jobId, options) {
20041
+ return pollUntilTerminal({
20042
+ poll: () => client.getDevPortalDeployStatus(projectId, jobId),
20043
+ isTerminal: (value) => isTerminalInfrastructureStatus(value.status),
20044
+ getStatus: (value) => normalizeInfrastructureStatus(value.status),
20045
+ timeoutMs: options?.timeoutMs ?? DEFAULT_POLL_TIMEOUT_MS,
20046
+ intervalMs: options?.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS,
20047
+ stepLabel: "Dev portal backend update"
20048
+ });
20049
+ }
20050
+ async function snapshotBackendUpdateJobIds(client, projectId) {
20051
+ const result = await client.listProvisioningJobs(projectId);
20052
+ return new Set(
20053
+ result.jobs.filter(isBackendUpdateJob).map((job) => job.id)
20054
+ );
19957
20055
  }
19958
20056
 
19959
20057
  // src/cli/platform/tools/dev-portal.ts
@@ -20245,7 +20343,6 @@ async function handleDeployDevPortal(client, config2, args, progress) {
20245
20343
  }
20246
20344
 
20247
20345
  // src/cli/platform/tools/dev-portal-site-build.ts
20248
- import path62 from "node:path";
20249
20346
  async function handleBuildDevPortalSite(client, config2, args, progress) {
20250
20347
  const buildProgress = progress?.child("Build dev portal site");
20251
20348
  const sourceDirectory = path62.resolve(args.sourceDirectory);
@@ -20325,54 +20422,6 @@ async function handleBuildDevPortalSite(client, config2, args, progress) {
20325
20422
  }
20326
20423
  }
20327
20424
 
20328
- // src/cli/platform/helpers/public-routes.ts
20329
- function normalizeActionKeyFormat(actionKey) {
20330
- const trimmed = actionKey.trim();
20331
- if (trimmed.includes(":")) {
20332
- const [method2, ...pathParts] = trimmed.split(":");
20333
- const path73 = pathParts.join(":").trim();
20334
- return `${method2.toLowerCase()}:${path73.startsWith("/") ? path73 : `/${path73}`}`;
20335
- }
20336
- const spaceIndex = trimmed.indexOf(" ");
20337
- if (spaceIndex <= 0) {
20338
- throw new Error(
20339
- "actionKey must be in format 'get:/path' or 'GET /path' (method plus path)."
20340
- );
20341
- }
20342
- const method = trimmed.slice(0, spaceIndex).trim().toLowerCase();
20343
- const path72 = trimmed.slice(spaceIndex + 1).trim();
20344
- return `${method}:${path72.startsWith("/") ? path72 : `/${path72}`}`;
20345
- }
20346
- function normalizePublicRoute(route) {
20347
- const trimmed = route.trim();
20348
- if (!trimmed) {
20349
- throw new Error("public route must be a non-empty string");
20350
- }
20351
- if (trimmed.startsWith("/")) {
20352
- return normalizeActionKeyFormat(`GET ${trimmed}`);
20353
- }
20354
- return normalizeActionKeyFormat(trimmed);
20355
- }
20356
- function collectManifestPublicActionKeys(gateway) {
20357
- if (!gateway) {
20358
- return [];
20359
- }
20360
- const keys = /* @__PURE__ */ new Set();
20361
- for (const route of gateway.publicRoutes ?? []) {
20362
- try {
20363
- keys.add(normalizePublicRoute(route));
20364
- } catch {
20365
- }
20366
- }
20367
- if (gateway.publicHealthActionKey?.trim()) {
20368
- try {
20369
- keys.add(normalizePublicRoute(gateway.publicHealthActionKey));
20370
- } catch {
20371
- }
20372
- }
20373
- return [...keys];
20374
- }
20375
-
20376
20425
  // src/cli/platform/tools/dev-portal-publish.ts
20377
20426
  var PUBLISH_STEPS = [
20378
20427
  "resolve_project",
@@ -20403,10 +20452,7 @@ var PUBLISH_STEPS_BY_RESOURCE = {
20403
20452
  "update_dev_portal_settings",
20404
20453
  "sync_dev_portal_plan_catalog"
20405
20454
  ],
20406
- "portal-frontend": [
20407
- "build_dev_portal_site",
20408
- "deploy_dev_portal"
20409
- ]
20455
+ "portal-frontend": ["build_dev_portal_site", "deploy_dev_portal"]
20410
20456
  };
20411
20457
  function parseApplyResources(raw) {
20412
20458
  if (raw === void 0) {
@@ -20450,7 +20496,9 @@ function shouldRunPublishStep(step, resumeFromStep, completedSteps) {
20450
20496
  return true;
20451
20497
  }
20452
20498
  if (stepIndex < resumeIndex) {
20453
- return completedSteps.some((record) => record.step === step && record.status === "succeeded");
20499
+ return completedSteps.some(
20500
+ (record) => record.step === step && record.status === "succeeded"
20501
+ );
20454
20502
  }
20455
20503
  return true;
20456
20504
  }
@@ -20483,10 +20531,16 @@ async function shouldSkipBackendRedeploy(client, projectId, manifest, ledger, ex
20483
20531
  const remoteSha = summary.artifactContentSha256?.toLowerCase() ?? null;
20484
20532
  const ledgerSha = ledger.backendArtifactHash?.toLowerCase() ?? null;
20485
20533
  if (remoteSha && ledgerSha && remoteSha === ledgerSha) {
20486
- return { skip: true, reason: "healthy backend matches ledger artifact hash" };
20534
+ return {
20535
+ skip: true,
20536
+ reason: "healthy backend matches ledger artifact hash"
20537
+ };
20487
20538
  }
20488
20539
  if (manifest.backend?.deployed && remoteSha && ledgerSha && remoteSha === ledgerSha) {
20489
- return { skip: true, reason: "backend marked deployed with matching hash" };
20540
+ return {
20541
+ skip: true,
20542
+ reason: "backend marked deployed with matching hash"
20543
+ };
20490
20544
  }
20491
20545
  } catch {
20492
20546
  }
@@ -20510,7 +20564,9 @@ async function runApplyFromSiteDirectory(client, config2, args, progress) {
20510
20564
  const wait = args.wait !== false;
20511
20565
  const resources = resolveApplyResources(args.only);
20512
20566
  const forceBackendRedeploy = Boolean(args.force && resources.has("apihub"));
20513
- const forceSiteRedeploy = Boolean(args.force && resources.has("portal-frontend"));
20567
+ const forceSiteRedeploy = Boolean(
20568
+ args.force && resources.has("portal-frontend")
20569
+ );
20514
20570
  const includeApihub = resources.has("apihub");
20515
20571
  const includePortalBackend = resources.has("portal-backend");
20516
20572
  const includePortalFrontend = resources.has("portal-frontend");
@@ -20541,28 +20597,43 @@ async function runApplyFromSiteDirectory(client, config2, args, progress) {
20541
20597
  version: 1,
20542
20598
  projectId: projectId || ledger.projectId,
20543
20599
  updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
20544
- lastSuccessfulStep: [...timedSteps].reverse().find((step) => step.status === "succeeded" || step.status === "skipped")?.step ?? ledger.lastSuccessfulStep,
20600
+ lastSuccessfulStep: [...timedSteps].reverse().find(
20601
+ (step) => step.status === "succeeded" || step.status === "skipped"
20602
+ )?.step ?? ledger.lastSuccessfulStep,
20545
20603
  steps: timedSteps.length > 0 ? timedSteps : ledger.steps
20546
20604
  };
20547
20605
  await writePublishLedger(args.sourceDirectory, ledger);
20548
20606
  };
20549
20607
  try {
20550
- const { manifest: loadedManifest, secrets } = await resolvePublishConfig(args.sourceDirectory);
20608
+ const { manifest: loadedManifest, secrets } = await resolvePublishConfig(
20609
+ args.sourceDirectory
20610
+ );
20551
20611
  const projectIdHint = args.projectId ?? loadedManifest.projectId;
20552
- const emailOptions = await resolveEmailOptionsForPublish(client, projectIdHint);
20553
- let manifest = validateManifestForPublish(loadedManifest, secrets, emailOptions);
20612
+ const emailOptions = await resolveEmailOptionsForPublish(
20613
+ client,
20614
+ projectIdHint
20615
+ );
20616
+ const manifest = validateManifestForPublish(
20617
+ loadedManifest,
20618
+ secrets,
20619
+ emailOptions
20620
+ );
20554
20621
  await writeManifest(args.sourceDirectory, manifest);
20555
20622
  const stripeRequired = manifestRequiresStripe(manifest.plans);
20556
20623
  const ledgerResume = resolveResumeStepFromLedger(ledger, PUBLISH_STEPS);
20557
20624
  const effectiveResumeFromStep = args.resumeFromStep ?? ledgerResume;
20558
20625
  let publicRouteWarnings = [];
20559
- if (shouldRunPublishStep("resolve_project", effectiveResumeFromStep, completedSteps)) {
20626
+ if (args.createNewProject || shouldRunPublishStep(
20627
+ "resolve_project",
20628
+ effectiveResumeFromStep,
20629
+ completedSteps
20630
+ )) {
20560
20631
  const timer = new StepTimer();
20561
20632
  const resolved = await resolveProjectIdForPublish(
20562
20633
  client,
20563
20634
  config2,
20564
20635
  {
20565
- projectId: args.projectId ?? manifest.projectId,
20636
+ projectId: args.createNewProject ? void 0 : args.projectId ?? manifest.projectId,
20566
20637
  projectName: args.projectName ?? resolveProjectDisplayName(manifest)
20567
20638
  },
20568
20639
  wait,
@@ -20570,13 +20641,48 @@ async function runApplyFromSiteDirectory(client, config2, args, progress) {
20570
20641
  args.timeoutMs,
20571
20642
  args.pollIntervalMs
20572
20643
  );
20644
+ if (resolved.blocked) {
20645
+ return JSON.stringify(
20646
+ {
20647
+ ...resolved.payload,
20648
+ completedSteps,
20649
+ sourceDirectory: args.sourceDirectory
20650
+ },
20651
+ null,
20652
+ 2
20653
+ );
20654
+ }
20573
20655
  projectId = resolved.projectId;
20574
20656
  for (const step of resolved.completedSteps) {
20575
20657
  completedSteps.push(step);
20576
20658
  }
20577
20659
  recordStep("resolve_project", "succeeded", timer.finish({ projectId }));
20578
20660
  } else {
20579
- projectId = resolveProjectId(args.projectId ?? manifest.projectId, config2);
20661
+ const candidateProjectId = resolveProjectId(
20662
+ args.projectId ?? manifest.projectId,
20663
+ config2
20664
+ );
20665
+ try {
20666
+ await client.getProject(candidateProjectId);
20667
+ } catch (error) {
20668
+ if (isNotFoundError(error)) {
20669
+ return JSON.stringify(
20670
+ {
20671
+ status: "blocked",
20672
+ reason: "project_not_found",
20673
+ projectId: candidateProjectId,
20674
+ userMessage: `Project ${candidateProjectId} was not found. It may have been deleted, or it belongs to a different account than the one you're currently logged in as.`,
20675
+ nextStep: "create_or_fix_project",
20676
+ completedSteps,
20677
+ sourceDirectory: args.sourceDirectory
20678
+ },
20679
+ null,
20680
+ 2
20681
+ );
20682
+ }
20683
+ throw error;
20684
+ }
20685
+ projectId = candidateProjectId;
20580
20686
  recordStep("resolve_project", "skipped", new StepTimer().finish());
20581
20687
  }
20582
20688
  manifest.projectId = projectId;
@@ -20602,7 +20708,11 @@ async function runApplyFromSiteDirectory(client, config2, args, progress) {
20602
20708
  if (ensured.status !== "succeeded") {
20603
20709
  return ensureJson;
20604
20710
  }
20605
- completedSteps.push({ step: "ensure_platform_plan", status: "succeeded", detail: ensured });
20711
+ completedSteps.push({
20712
+ step: "ensure_platform_plan",
20713
+ status: "succeeded",
20714
+ detail: ensured
20715
+ });
20606
20716
  }
20607
20717
  const planGate = await assertPlatformPlanReadyForPublish(client, projectId);
20608
20718
  if (planGate.blocked) {
@@ -20671,7 +20781,12 @@ async function runApplyFromSiteDirectory(client, config2, args, progress) {
20671
20781
  detail: { reason: "resource not selected" }
20672
20782
  });
20673
20783
  }
20674
- if (shouldRunResourceStep("upsert_gateway_plans", resources, effectiveResumeFromStep, completedSteps)) {
20784
+ if (shouldRunResourceStep(
20785
+ "upsert_gateway_plans",
20786
+ resources,
20787
+ effectiveResumeFromStep,
20788
+ completedSteps
20789
+ )) {
20675
20790
  await publishProgress?.onStep("Creating gateway plans");
20676
20791
  const timer = new StepTimer();
20677
20792
  const planResults = await upsertGatewayPlansFromManifest(
@@ -20681,7 +20796,11 @@ async function runApplyFromSiteDirectory(client, config2, args, progress) {
20681
20796
  manifest.plans,
20682
20797
  { deferActionKeys: true }
20683
20798
  );
20684
- recordStep("upsert_gateway_plans", "succeeded", timer.finish(planResults));
20799
+ recordStep(
20800
+ "upsert_gateway_plans",
20801
+ "succeeded",
20802
+ timer.finish(planResults)
20803
+ );
20685
20804
  } else {
20686
20805
  recordStep("upsert_gateway_plans", "skipped", new StepTimer().finish());
20687
20806
  }
@@ -20698,7 +20817,9 @@ async function runApplyFromSiteDirectory(client, config2, args, progress) {
20698
20817
  const project = await client.getProject(projectId);
20699
20818
  await publishProgress?.onStep("Loading backend FQDN");
20700
20819
  let backendFqdn = resolvePortalBackendFqdn(project);
20701
- const backendJson = await handleGetDevPortalBackend(client, config2, { projectId });
20820
+ const backendJson = await handleGetDevPortalBackend(client, config2, {
20821
+ projectId
20822
+ });
20702
20823
  const backendResult = JSON.parse(backendJson);
20703
20824
  backendFqdn = backendFqdn ?? backendResult.backend?.fqdn?.trim() ?? null;
20704
20825
  const platformSiteUrl = backendResult.backend?.platformSiteUrl?.trim() ?? null;
@@ -20714,7 +20835,12 @@ async function runApplyFromSiteDirectory(client, config2, args, progress) {
20714
20835
  );
20715
20836
  const urls = backendFqdn ? resolveDevPortalUrls(project, backendFqdn, providerIds, platformSiteUrl) : null;
20716
20837
  const backendApiBaseUrl = backendFqdn ? `https://${backendFqdn}` : "";
20717
- if (shouldRunResourceStep("update_dev_portal_settings", resources, effectiveResumeFromStep, completedSteps)) {
20838
+ if (shouldRunResourceStep(
20839
+ "update_dev_portal_settings",
20840
+ resources,
20841
+ effectiveResumeFromStep,
20842
+ completedSteps
20843
+ )) {
20718
20844
  await publishProgress?.onStep("Updating dev portal settings");
20719
20845
  const timer = new StepTimer();
20720
20846
  const openApiPath = manifest.backend?.openApiPath;
@@ -20733,7 +20859,9 @@ async function runApplyFromSiteDirectory(client, config2, args, progress) {
20733
20859
  openApiSourceKind: openApi.openApiSourceKind,
20734
20860
  openApiValue: openApi.openApiValue,
20735
20861
  authConfigJson: JSON.stringify(buildAuthConfigJson(manifest)),
20736
- authSecretsJson: JSON.stringify(buildAuthSecretsJson(secrets, emailOptions)),
20862
+ authSecretsJson: JSON.stringify(
20863
+ buildAuthSecretsJson(secrets, emailOptions)
20864
+ ),
20737
20865
  stripeSecretKey: stripeRequired ? secrets.stripe.secretKey : void 0,
20738
20866
  stripeWebhookSecret: stripeRequired ? secrets.stripe.webhookSecret : void 0,
20739
20867
  stripeSuccessUrl: stripeRequired ? urls.stripeSuccessUrl : void 0,
@@ -20747,9 +20875,18 @@ async function runApplyFromSiteDirectory(client, config2, args, progress) {
20747
20875
  parseHandlerResult(settingsJson, "update_dev_portal_settings");
20748
20876
  recordStep("update_dev_portal_settings", "succeeded", timer.finish());
20749
20877
  } else {
20750
- recordStep("update_dev_portal_settings", "skipped", new StepTimer().finish());
20878
+ recordStep(
20879
+ "update_dev_portal_settings",
20880
+ "skipped",
20881
+ new StepTimer().finish()
20882
+ );
20751
20883
  }
20752
- if (shouldRunResourceStep("configure_gateway_baseline", resources, effectiveResumeFromStep, completedSteps)) {
20884
+ if (shouldRunResourceStep(
20885
+ "configure_gateway_baseline",
20886
+ resources,
20887
+ effectiveResumeFromStep,
20888
+ completedSteps
20889
+ )) {
20753
20890
  await publishProgress?.onStep("Configuring gateway baseline");
20754
20891
  const timer = new StepTimer();
20755
20892
  const gateway = manifest.gateway ?? {};
@@ -20783,14 +20920,21 @@ async function runApplyFromSiteDirectory(client, config2, args, progress) {
20783
20920
  { ...config2, defaultProjectId: projectId },
20784
20921
  baselineArgs
20785
20922
  );
20786
- const baseline = parseHandlerResult(baselineJson, "configure_gateway_baseline");
20923
+ const baseline = parseHandlerResult(
20924
+ baselineJson,
20925
+ "configure_gateway_baseline"
20926
+ );
20787
20927
  recordStep(
20788
20928
  "configure_gateway_baseline",
20789
20929
  baseline.status === "pending" ? "pending" : "succeeded",
20790
20930
  timer.finish(baseline)
20791
20931
  );
20792
20932
  } else {
20793
- recordStep("configure_gateway_baseline", "skipped", new StepTimer().finish());
20933
+ recordStep(
20934
+ "configure_gateway_baseline",
20935
+ "skipped",
20936
+ new StepTimer().finish()
20937
+ );
20794
20938
  }
20795
20939
  const skipBackendDecision = await shouldSkipBackendRedeploy(
20796
20940
  client,
@@ -20806,13 +20950,21 @@ async function runApplyFromSiteDirectory(client, config2, args, progress) {
20806
20950
  const hasBackendSource = Boolean(backendSourceDirectory);
20807
20951
  let backendDeploySucceeded = false;
20808
20952
  let backendDeployFailed = false;
20809
- if (shouldRunResourceStep("deploy_backend", resources, effectiveResumeFromStep, completedSteps) && backendConfig && backendSourceDirectory && !skipBackend) {
20953
+ if (shouldRunResourceStep(
20954
+ "deploy_backend",
20955
+ resources,
20956
+ effectiveResumeFromStep,
20957
+ completedSteps
20958
+ ) && backendConfig && backendSourceDirectory && !skipBackend) {
20810
20959
  await publishProgress?.onStep("Deploying API backend");
20811
20960
  const timer = new StepTimer();
20812
- const resolvedOpenApiPath = resolveBackendOpenApiPath(backendConfig.openApiPath, {
20813
- siteDirectory: args.sourceDirectory,
20814
- backendSourceDirectory
20815
- });
20961
+ const resolvedOpenApiPath = resolveBackendOpenApiPath(
20962
+ backendConfig.openApiPath,
20963
+ {
20964
+ siteDirectory: args.sourceDirectory,
20965
+ backendSourceDirectory
20966
+ }
20967
+ );
20816
20968
  try {
20817
20969
  const deployJson = await handleDeployBackend(
20818
20970
  client,
@@ -20874,14 +21026,25 @@ async function runApplyFromSiteDirectory(client, config2, args, progress) {
20874
21026
  }
20875
21027
  }
20876
21028
  const shouldSyncPlanActions = hasBackendSource && !backendDeployFailed && (backendDeploySucceeded || manifest.backend?.deployed === true);
20877
- if (shouldRunResourceStep("sync_gateway_plan_actions", resources, effectiveResumeFromStep, completedSteps) && shouldSyncPlanActions) {
20878
- await publishProgress?.onStep("Waiting for OpenAPI actions then syncing plan membership");
21029
+ if (shouldRunResourceStep(
21030
+ "sync_gateway_plan_actions",
21031
+ resources,
21032
+ effectiveResumeFromStep,
21033
+ completedSteps
21034
+ ) && shouldSyncPlanActions) {
21035
+ await publishProgress?.onStep(
21036
+ "Waiting for OpenAPI actions then syncing plan membership"
21037
+ );
20879
21038
  const timer = new StepTimer();
20880
- const actionWait = await waitForGatewayActionsRegistered(client, projectId, {
20881
- minActionCount: 1,
20882
- timeoutMs: args.timeoutMs ?? 18e4,
20883
- pollIntervalMs: args.pollIntervalMs ?? 5e3
20884
- });
21039
+ const actionWait = await waitForGatewayActionsRegistered(
21040
+ client,
21041
+ projectId,
21042
+ {
21043
+ minActionCount: 1,
21044
+ timeoutMs: args.timeoutMs ?? 18e4,
21045
+ pollIntervalMs: args.pollIntervalMs ?? 5e3
21046
+ }
21047
+ );
20885
21048
  if (actionWait.timedOut && actionWait.actionCount < 1) {
20886
21049
  throw new BootstrapStepError(
20887
21050
  "sync_gateway_plan_actions",
@@ -20899,9 +21062,17 @@ async function runApplyFromSiteDirectory(client, config2, args, progress) {
20899
21062
  recordStep(
20900
21063
  "sync_gateway_plan_actions",
20901
21064
  "succeeded",
20902
- timer.finish({ actionCount: actionWait.actionCount, results: actionSyncResults })
21065
+ timer.finish({
21066
+ actionCount: actionWait.actionCount,
21067
+ results: actionSyncResults
21068
+ })
20903
21069
  );
20904
- } else if (shouldRunResourceStep("sync_gateway_plan_actions", resources, effectiveResumeFromStep, completedSteps)) {
21070
+ } else if (shouldRunResourceStep(
21071
+ "sync_gateway_plan_actions",
21072
+ resources,
21073
+ effectiveResumeFromStep,
21074
+ completedSteps
21075
+ )) {
20905
21076
  recordStep(
20906
21077
  "sync_gateway_plan_actions",
20907
21078
  "skipped",
@@ -20910,15 +21081,26 @@ async function runApplyFromSiteDirectory(client, config2, args, progress) {
20910
21081
  })
20911
21082
  );
20912
21083
  } else {
20913
- recordStep("sync_gateway_plan_actions", "skipped", new StepTimer().finish());
21084
+ recordStep(
21085
+ "sync_gateway_plan_actions",
21086
+ "skipped",
21087
+ new StepTimer().finish()
21088
+ );
20914
21089
  }
20915
21090
  const publicActionKeys = collectManifestPublicActionKeys(manifest.gateway);
20916
- if (shouldRunResourceStep("apply_public_routes", resources, effectiveResumeFromStep, completedSteps)) {
21091
+ if (shouldRunResourceStep(
21092
+ "apply_public_routes",
21093
+ resources,
21094
+ effectiveResumeFromStep,
21095
+ completedSteps
21096
+ )) {
20917
21097
  if (publicActionKeys.length === 0) {
20918
21098
  recordStep(
20919
21099
  "apply_public_routes",
20920
21100
  "skipped",
20921
- new StepTimer().finish({ reason: "no publicRoutes or publicHealthActionKey in manifest" })
21101
+ new StepTimer().finish({
21102
+ reason: "no publicRoutes or publicHealthActionKey in manifest"
21103
+ })
20922
21104
  );
20923
21105
  } else if (!shouldSyncPlanActions && !(manifest.backend?.deployed === true)) {
20924
21106
  recordStep(
@@ -20935,12 +21117,20 @@ async function runApplyFromSiteDirectory(client, config2, args, progress) {
20935
21117
  } else {
20936
21118
  await publishProgress?.onStep("Applying public gateway routes");
20937
21119
  const timer = new StepTimer();
20938
- const actionWait = await waitForGatewayActionsRegistered(client, projectId, {
20939
- minActionCount: 1,
20940
- timeoutMs: args.timeoutMs ?? 12e4,
20941
- pollIntervalMs: args.pollIntervalMs ?? 5e3
20942
- });
20943
- const result = await applyGatewayPublicRoutes(client, projectId, publicActionKeys);
21120
+ const actionWait = await waitForGatewayActionsRegistered(
21121
+ client,
21122
+ projectId,
21123
+ {
21124
+ minActionCount: 1,
21125
+ timeoutMs: args.timeoutMs ?? 12e4,
21126
+ pollIntervalMs: args.pollIntervalMs ?? 5e3
21127
+ }
21128
+ );
21129
+ const result = await applyGatewayPublicRoutes(
21130
+ client,
21131
+ projectId,
21132
+ publicActionKeys
21133
+ );
20944
21134
  publicRouteWarnings = result.warnings;
20945
21135
  recordStep(
20946
21136
  "apply_public_routes",
@@ -20956,7 +21146,12 @@ async function runApplyFromSiteDirectory(client, config2, args, progress) {
20956
21146
  } else {
20957
21147
  recordStep("apply_public_routes", "skipped", new StepTimer().finish());
20958
21148
  }
20959
- if (shouldRunResourceStep("sync_dev_portal_plan_catalog", resources, effectiveResumeFromStep, completedSteps)) {
21149
+ if (shouldRunResourceStep(
21150
+ "sync_dev_portal_plan_catalog",
21151
+ resources,
21152
+ effectiveResumeFromStep,
21153
+ completedSteps
21154
+ )) {
20960
21155
  await publishProgress?.onStep("Syncing dev portal plan catalog");
20961
21156
  const timer = new StepTimer();
20962
21157
  const expectedPlanCount = manifest.plans.length;
@@ -20974,29 +21169,52 @@ async function runApplyFromSiteDirectory(client, config2, args, progress) {
20974
21169
  })
20975
21170
  );
20976
21171
  } else {
20977
- recordStep("sync_dev_portal_plan_catalog", "skipped", new StepTimer().finish());
21172
+ recordStep(
21173
+ "sync_dev_portal_plan_catalog",
21174
+ "skipped",
21175
+ new StepTimer().finish()
21176
+ );
20978
21177
  }
20979
- let gatewayUrl = await resolveProjectGatewayUrl(client, projectId);
20980
- const siteSourceHash = await hashDevPortalSiteSources(args.sourceDirectory, {
20981
- gatewayUrl
20982
- });
21178
+ let gatewayUrl = await resolveProjectGatewayUrl(
21179
+ client,
21180
+ projectId
21181
+ );
21182
+ const siteSourceHash = await hashDevPortalSiteSources(
21183
+ args.sourceDirectory,
21184
+ {
21185
+ gatewayUrl
21186
+ }
21187
+ );
20983
21188
  const skipSiteDeploy = !forceSiteRedeploy && Boolean(ledger.siteArtifactHash) && ledger.siteArtifactHash === siteSourceHash;
20984
- if (shouldRunResourceStep("build_dev_portal_site", resources, effectiveResumeFromStep, completedSteps)) {
21189
+ if (shouldRunResourceStep(
21190
+ "build_dev_portal_site",
21191
+ resources,
21192
+ effectiveResumeFromStep,
21193
+ completedSteps
21194
+ )) {
20985
21195
  if (skipSiteDeploy) {
20986
21196
  recordStep(
20987
21197
  "build_dev_portal_site",
20988
21198
  "skipped",
20989
- new StepTimer().finish({ reason: "unchanged site artifact hash", siteSourceHash })
21199
+ new StepTimer().finish({
21200
+ reason: "unchanged site artifact hash",
21201
+ siteSourceHash
21202
+ })
20990
21203
  );
20991
21204
  recordStep(
20992
21205
  "deploy_dev_portal",
20993
21206
  "skipped",
20994
- new StepTimer().finish({ reason: "unchanged site artifact hash", siteSourceHash })
21207
+ new StepTimer().finish({
21208
+ reason: "unchanged site artifact hash",
21209
+ siteSourceHash
21210
+ })
20995
21211
  );
20996
21212
  } else {
20997
21213
  await publishProgress?.onStep("Validating site config before build");
20998
21214
  const timer = new StepTimer();
20999
- const preBuildValidation = await validateDevPortalSiteConfig(args.sourceDirectory);
21215
+ const preBuildValidation = await validateDevPortalSiteConfig(
21216
+ args.sourceDirectory
21217
+ );
21000
21218
  if (!preBuildValidation.valid) {
21001
21219
  throw new BootstrapStepError(
21002
21220
  "build_dev_portal_site",
@@ -21017,12 +21235,18 @@ async function runApplyFromSiteDirectory(client, config2, args, progress) {
21017
21235
  },
21018
21236
  publishProgress
21019
21237
  );
21020
- const buildResult = parseHandlerResult(
21021
- buildJson,
21022
- "build_dev_portal_site"
21238
+ const buildResult = parseHandlerResult(buildJson, "build_dev_portal_site");
21239
+ recordStep(
21240
+ "build_dev_portal_site",
21241
+ "succeeded",
21242
+ timer.finish(buildResult)
21023
21243
  );
21024
- recordStep("build_dev_portal_site", "succeeded", timer.finish(buildResult));
21025
- if (shouldRunResourceStep("deploy_dev_portal", resources, effectiveResumeFromStep, completedSteps)) {
21244
+ if (shouldRunResourceStep(
21245
+ "deploy_dev_portal",
21246
+ resources,
21247
+ effectiveResumeFromStep,
21248
+ completedSteps
21249
+ )) {
21026
21250
  await publishProgress?.onStep("Deploying dev portal site");
21027
21251
  const deployTimer = new StepTimer();
21028
21252
  const deployPortalJson = await handleDeployDevPortal(
@@ -21036,10 +21260,19 @@ async function runApplyFromSiteDirectory(client, config2, args, progress) {
21036
21260
  );
21037
21261
  parseHandlerResult(deployPortalJson, "deploy_dev_portal");
21038
21262
  ledger.siteArtifactHash = siteSourceHash;
21039
- recordStep("deploy_dev_portal", "succeeded", deployTimer.finish({ siteSourceHash }));
21263
+ recordStep(
21264
+ "deploy_dev_portal",
21265
+ "succeeded",
21266
+ deployTimer.finish({ siteSourceHash })
21267
+ );
21040
21268
  }
21041
21269
  }
21042
- } else if (shouldRunResourceStep("deploy_dev_portal", resources, effectiveResumeFromStep, completedSteps)) {
21270
+ } else if (shouldRunResourceStep(
21271
+ "deploy_dev_portal",
21272
+ resources,
21273
+ effectiveResumeFromStep,
21274
+ completedSteps
21275
+ )) {
21043
21276
  recordStep("build_dev_portal_site", "skipped", new StepTimer().finish());
21044
21277
  recordStep("deploy_dev_portal", "skipped", new StepTimer().finish());
21045
21278
  }
@@ -21064,12 +21297,7 @@ async function runApplyFromSiteDirectory(client, config2, args, progress) {
21064
21297
  }
21065
21298
  };
21066
21299
  gatewayUrl = refreshedProject.gatewayUrl?.trim().replace(/\/+$/, "") || gatewayUrl || await resolveProjectGatewayUrl(client, projectId);
21067
- const swaUrl = backendFqdn ? resolveDevPortalUrls(
21068
- refreshedProject,
21069
- backendFqdn,
21070
- [],
21071
- platformSiteUrl
21072
- ).swaUrl : null;
21300
+ const swaUrl = backendFqdn ? resolveDevPortalUrls(refreshedProject, backendFqdn, [], platformSiteUrl).swaUrl : null;
21073
21301
  const manualSteps = stripeRequired ? postValidation.manualSteps : {
21074
21302
  oidcRedirectUri: postValidation.manualSteps.oidcRedirectUri
21075
21303
  };
@@ -21157,7 +21385,9 @@ async function handleVerifyDevPortalLive(client, config2, args) {
21157
21385
  }
21158
21386
  projectId = resolveProjectId(projectId, config2);
21159
21387
  if (!backendFqdn) {
21160
- const backendJson = await handleGetDevPortalBackend(client, config2, { projectId });
21388
+ const backendJson = await handleGetDevPortalBackend(client, config2, {
21389
+ projectId
21390
+ });
21161
21391
  const backendResult = JSON.parse(backendJson);
21162
21392
  backendFqdn = backendResult.backend?.fqdn?.trim();
21163
21393
  }
@@ -21173,7 +21403,9 @@ async function handleVerifyDevPortalLive(client, config2, args) {
21173
21403
  postDeploy: true
21174
21404
  });
21175
21405
  for (let attempt = 1; attempt < retries && !validation.passed; attempt++) {
21176
- const plansCheck = validation.checks.find((check2) => check2.name === "plans_endpoint");
21406
+ const plansCheck = validation.checks.find(
21407
+ (check2) => check2.name === "plans_endpoint"
21408
+ );
21177
21409
  if (plansCheck?.passed) {
21178
21410
  break;
21179
21411
  }
@@ -21207,62 +21439,6 @@ async function handleVerifyDevPortalLive(client, config2, args) {
21207
21439
  );
21208
21440
  }
21209
21441
 
21210
- // src/cli/apply/human-output.ts
21211
- function formatNonSuccessResultForHuman(parsed) {
21212
- const status2 = parsed.status;
21213
- if (status2 === "blocked" && parsed.reason === "platform_plan_unselected") {
21214
- const lines = [
21215
- "Deploy is blocked: this project doesn't have an API ToGo platform hosting plan yet."
21216
- ];
21217
- if (typeof parsed.freeProjectsQuotaHint === "string") {
21218
- lines.push(parsed.freeProjectsQuotaHint);
21219
- }
21220
- lines.push(
21221
- "Pick a plan and re-run:",
21222
- " apitogo deploy --platform-plan free",
21223
- " apitogo deploy --platform-plan starter",
21224
- " apitogo deploy --platform-plan professional",
21225
- "Run `apitogo deploy --json` to see full plan details (pricing, limits) as data."
21226
- );
21227
- return lines;
21228
- }
21229
- if (status2 === "blocked" && parsed.reason === "paid_gateway_plans_require_starter") {
21230
- const lines = [];
21231
- lines.push(
21232
- typeof parsed.userMessage === "string" ? parsed.userMessage : "Deploy is blocked: your Free platform plan can't host paid API plans."
21233
- );
21234
- lines.push(
21235
- "Choose one:",
21236
- " apitogo deploy --platform-plan starter # or professional, to keep the paid plans",
21237
- " Edit apitogo.config.json to remove those plans or set their price to 0, then re-run apitogo deploy"
21238
- );
21239
- return lines;
21240
- }
21241
- if (status2 === "blocked") {
21242
- return [
21243
- typeof parsed.userMessage === "string" ? parsed.userMessage : `Deploy is blocked${typeof parsed.reason === "string" ? `: ${parsed.reason}` : "."}`
21244
- ];
21245
- }
21246
- if (status2 === "failed") {
21247
- const lines = [
21248
- `Deploy failed${typeof parsed.failedStep === "string" ? ` at step "${parsed.failedStep}"` : ""}.`
21249
- ];
21250
- if (typeof parsed.error === "string") {
21251
- lines.push(parsed.error);
21252
- }
21253
- if (typeof parsed.recoveryHint === "string") {
21254
- lines.push(parsed.recoveryHint);
21255
- }
21256
- if (typeof parsed.failedStep === "string") {
21257
- lines.push(
21258
- `To retry from there: apitogo deploy --resume-from ${parsed.failedStep}`
21259
- );
21260
- }
21261
- return lines;
21262
- }
21263
- return [];
21264
- }
21265
-
21266
21442
  // src/cli/apply/platform-plan-prompt.ts
21267
21443
  import { stdin as input3, stdout as output2 } from "node:process";
21268
21444
  import { createInterface as createInterface2 } from "node:readline/promises";
@@ -21385,6 +21561,33 @@ ${ensured.checkoutUrl}`
21385
21561
  return true;
21386
21562
  }
21387
21563
 
21564
+ // src/cli/apply/project-not-found-prompt.ts
21565
+ import { stdin as input4, stdout as output3 } from "node:process";
21566
+ import { createInterface as createInterface3 } from "node:readline/promises";
21567
+ async function resolveProjectNotFoundInteractively(payload, defaultProjectName) {
21568
+ const projectId = typeof payload.projectId === "string" ? payload.projectId : "";
21569
+ const message = typeof payload.userMessage === "string" ? payload.userMessage : `Project ${projectId} was not found.`;
21570
+ printWarningToConsole(message);
21571
+ printDiagnosticsToConsole(
21572
+ "This usually means the project was deleted, or it belongs to a different account than the one you're logged in as."
21573
+ );
21574
+ printDiagnosticsToConsole(
21575
+ "If that's unexpected, cancel here and run `apitogo login` to check your account."
21576
+ );
21577
+ printDiagnosticsToConsole("");
21578
+ const rl = createInterface3({ input: input4, output: output3 });
21579
+ try {
21580
+ const createAnswer = (await rl.question("Create a new project instead? [Y/n] ")).trim().toLowerCase();
21581
+ if (createAnswer === "n" || createAnswer === "no") {
21582
+ return null;
21583
+ }
21584
+ const nameAnswer = (await rl.question(`Project name [${defaultProjectName}]: `)).trim();
21585
+ return { projectName: nameAnswer || defaultProjectName };
21586
+ } finally {
21587
+ rl.close();
21588
+ }
21589
+ }
21590
+
21388
21591
  // src/cli/apply/handler.ts
21389
21592
  function normalizeOnly(only) {
21390
21593
  if (!only) return [];
@@ -21429,18 +21632,22 @@ async function apply(argv) {
21429
21632
  }
21430
21633
  const platformPlan = argv.platformPlan?.trim();
21431
21634
  const progress = createConsoleProgressReporter();
21635
+ let effectiveProjectId = argv.projectId?.trim();
21636
+ let effectiveProjectName = argv.projectName?.trim();
21637
+ let createNewProject = false;
21432
21638
  const runOnce = (plan) => runApplyFromSiteDirectory(
21433
21639
  client,
21434
21640
  config2,
21435
21641
  {
21436
21642
  sourceDirectory,
21437
- projectId: argv.projectId?.trim(),
21438
- projectName: argv.projectName?.trim(),
21643
+ projectId: effectiveProjectId,
21644
+ projectName: effectiveProjectName,
21439
21645
  platformPlan: plan,
21440
21646
  only,
21441
21647
  force: argv.force,
21442
21648
  resumeFromStep: argv.resumeFrom?.trim(),
21443
- wait: argv.wait !== false
21649
+ wait: argv.wait !== false,
21650
+ createNewProject
21444
21651
  },
21445
21652
  progress
21446
21653
  );
@@ -21455,6 +21662,26 @@ async function apply(argv) {
21455
21662
  }
21456
21663
  let parsed = safeParseResult(result);
21457
21664
  const canPromptInteractively = !argv.json && Boolean(process.stdin.isTTY) && Boolean(process.stdout.isTTY);
21665
+ if (canPromptInteractively && parsed?.status === "blocked" && parsed.reason === "project_not_found") {
21666
+ const resolution = await resolveProjectNotFoundInteractively(
21667
+ parsed,
21668
+ path63.basename(sourceDirectory)
21669
+ );
21670
+ if (resolution) {
21671
+ effectiveProjectId = void 0;
21672
+ effectiveProjectName = resolution.projectName;
21673
+ createNewProject = true;
21674
+ try {
21675
+ result = await runOnce(platformPlan);
21676
+ } catch (error) {
21677
+ await printCriticalFailureToConsoleAndExit(
21678
+ error instanceof Error ? error.message : String(error)
21679
+ );
21680
+ return;
21681
+ }
21682
+ parsed = safeParseResult(result);
21683
+ }
21684
+ }
21458
21685
  if (canPromptInteractively && parsed?.status === "blocked" && parsed.reason === "platform_plan_unselected") {
21459
21686
  const resolved = await resolvePlatformPlanInteractively(
21460
21687
  client,
@@ -21476,7 +21703,7 @@ async function apply(argv) {
21476
21703
  if (argv.json) {
21477
21704
  console.log(result);
21478
21705
  } else {
21479
- const humanLines = parsed ? formatNonSuccessResultForHuman(parsed) : [];
21706
+ const humanLines = parsed ? formatResultForHuman(parsed) : [];
21480
21707
  if (humanLines.length > 0) {
21481
21708
  for (const line of humanLines) {
21482
21709
  console.log(line);
@@ -21869,10 +22096,10 @@ function siteRelative(siteDirectory, filePath) {
21869
22096
  function formatOpenApiDiscoveryLines(siteDirectory, openApiDiscovery) {
21870
22097
  const status2 = String(openApiDiscovery.status ?? "unknown");
21871
22098
  if (status2 === "wired") {
21872
- const input4 = typeof openApiDiscovery.openApiInput === "string" ? openApiDiscovery.openApiInput : typeof openApiDiscovery.openApiPath === "string" ? siteRelative(siteDirectory, openApiDiscovery.openApiPath) : "OpenAPI";
22099
+ const input5 = typeof openApiDiscovery.openApiInput === "string" ? openApiDiscovery.openApiInput : typeof openApiDiscovery.openApiPath === "string" ? siteRelative(siteDirectory, openApiDiscovery.openApiPath) : "OpenAPI";
21873
22100
  const title = typeof openApiDiscovery.openApiTitle === "string" ? ` (${openApiDiscovery.openApiTitle})` : "";
21874
22101
  const apiPath = typeof openApiDiscovery.apiReferencePath === "string" ? openApiDiscovery.apiReferencePath : "/api";
21875
- return [`OpenAPI: wired ${input4}${title} \u2192 ${apiPath}`];
22102
+ return [`OpenAPI: wired ${input5}${title} \u2192 ${apiPath}`];
21876
22103
  }
21877
22104
  if (status2 === "skipped") {
21878
22105
  return ["OpenAPI: search skipped"];
@@ -21924,23 +22151,23 @@ async function pathExists6(filePath) {
21924
22151
  }
21925
22152
  }
21926
22153
  async function wireOpenApiIntoJsonConfig(siteDirectory, openApiPath) {
21927
- const input4 = toSiteRelativeOpenApiInput(siteDirectory, openApiPath);
22154
+ const input5 = toSiteRelativeOpenApiInput(siteDirectory, openApiPath);
21928
22155
  const jsonConfigPath = path65.join(siteDirectory, "apitogo.config.json");
21929
22156
  const raw = await readFile19(jsonConfigPath, "utf8");
21930
22157
  const siteConfig = JSON.parse(raw);
21931
22158
  const updated = applyOpenApiToSiteConfig(siteConfig, {
21932
- input: input4,
22159
+ input: input5,
21933
22160
  apiPath: "api"
21934
22161
  });
21935
22162
  const backend = updated.backend && typeof updated.backend === "object" && !Array.isArray(updated.backend) ? { ...updated.backend } : {};
21936
- updated.backend = { ...backend, openApiPath: input4 };
22163
+ updated.backend = { ...backend, openApiPath: input5 };
21937
22164
  await writeFile18(
21938
22165
  jsonConfigPath,
21939
22166
  `${JSON.stringify(updated, null, 2)}
21940
22167
  `,
21941
22168
  "utf8"
21942
22169
  );
21943
- return { input: input4 };
22170
+ return { input: input5 };
21944
22171
  }
21945
22172
  async function discoverAndWireOpenApiLite(siteDirectory, mode) {
21946
22173
  if (mode === "none") {
@@ -21992,7 +22219,7 @@ async function discoverAndWireOpenApiLite(siteDirectory, mode) {
21992
22219
  };
21993
22220
  }
21994
22221
  try {
21995
- const { input: input4 } = await wireOpenApiIntoJsonConfig(
22222
+ const { input: input5 } = await wireOpenApiIntoJsonConfig(
21996
22223
  siteDirectory,
21997
22224
  selected.path
21998
22225
  );
@@ -22000,7 +22227,7 @@ async function discoverAndWireOpenApiLite(siteDirectory, mode) {
22000
22227
  openApiDiscovery: {
22001
22228
  status: "wired",
22002
22229
  openApiPath: selected.path,
22003
- openApiInput: input4,
22230
+ openApiInput: input5,
22004
22231
  openApiTitle: selected.title ?? null,
22005
22232
  openApiVersion: selected.version ?? null,
22006
22233
  apiReferencePath: "/api",