@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 +1482 -1255
- package/dist/declarations/cli/platform/helpers/dev-portal-manifest.d.ts +1 -1
- package/dist/declarations/cli/platform/tools/dev-portal-bootstrap-shared.d.ts +10 -5
- package/dist/declarations/cli/platform/tools/dev-portal-publish.d.ts +2 -1
- package/package.json +1 -1
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(
|
|
5071
|
-
if (Array.isArray(
|
|
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
|
|
5075
|
-
const result =
|
|
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.
|
|
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 = (
|
|
7637
|
-
const idx =
|
|
7636
|
+
var parseInputString = (input5) => {
|
|
7637
|
+
const idx = input5.indexOf("?");
|
|
7638
7638
|
if (idx === -1)
|
|
7639
|
-
return { filePath:
|
|
7639
|
+
return { filePath: input5, params: {} };
|
|
7640
7640
|
return {
|
|
7641
|
-
filePath:
|
|
7642
|
-
params: Object.fromEntries(new URLSearchParams(
|
|
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 = (
|
|
7679
|
-
const filePath = path11.resolve(this.config.__meta.rootDir,
|
|
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 (
|
|
7690
|
-
const filePath = path11.resolve(this.config.__meta.rootDir,
|
|
7691
|
-
const params =
|
|
7692
|
-
const configuredPath = this.getPathForFile(
|
|
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 ${
|
|
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((
|
|
7801
|
+
this.processedSchemas[apiConfig.path] = inputs.map((input5) => ({
|
|
7802
7802
|
schema: {},
|
|
7803
7803
|
version: "",
|
|
7804
|
-
path:
|
|
7805
|
-
label:
|
|
7806
|
-
inputPath: path11.resolve(this.config.__meta.rootDir,
|
|
7807
|
-
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((
|
|
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: (
|
|
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
|
|
10166
|
+
const output4 = {
|
|
10167
10167
|
version: 3,
|
|
10168
10168
|
framework: {
|
|
10169
10169
|
version: pkgJson.version
|
|
10170
10170
|
},
|
|
10171
10171
|
routes
|
|
10172
10172
|
};
|
|
10173
|
-
return
|
|
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
|
|
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(
|
|
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(
|
|
10205
|
+
function writeLine(output4) {
|
|
10206
10206
|
clearLine();
|
|
10207
|
-
if (
|
|
10208
|
-
process.stdout.write(
|
|
10207
|
+
if (output4.length < process.stdout.columns) {
|
|
10208
|
+
process.stdout.write(output4);
|
|
10209
10209
|
} else {
|
|
10210
|
-
process.stdout.write(
|
|
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(
|
|
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(
|
|
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(
|
|
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 = {
|
|
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:
|
|
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(
|
|
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: {
|
|
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(
|
|
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 {
|
|
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
|
|
12780
|
+
let output4 = "";
|
|
12767
12781
|
child.stdout.on("data", (chunk) => {
|
|
12768
|
-
|
|
12782
|
+
output4 += chunk.toString();
|
|
12769
12783
|
});
|
|
12770
12784
|
child.stderr.on("data", (chunk) => {
|
|
12771
|
-
|
|
12785
|
+
output4 += chunk.toString();
|
|
12772
12786
|
});
|
|
12773
12787
|
child.on("error", reject);
|
|
12774
|
-
child.on("close", (exitCode) => resolve({ exitCode, output:
|
|
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
|
|
14445
|
+
let output4 = "";
|
|
14432
14446
|
child.stdout.on("data", (chunk) => {
|
|
14433
|
-
|
|
14447
|
+
output4 += chunk.toString();
|
|
14434
14448
|
});
|
|
14435
14449
|
child.stderr.on("data", (chunk) => {
|
|
14436
|
-
|
|
14450
|
+
output4 += chunk.toString();
|
|
14437
14451
|
});
|
|
14438
14452
|
child.on("error", reject);
|
|
14439
|
-
child.on("close", (exitCode) => resolve({ exitCode, output:
|
|
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,
|
|
14639
|
+
function patchApitogoConfigOpenApiToFile(content, input5 = "./openapi.json") {
|
|
14626
14640
|
return patchApisConfig(content, {
|
|
14627
14641
|
type: "file",
|
|
14628
|
-
input:
|
|
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
|
|
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:
|
|
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,
|
|
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:
|
|
14894
|
+
openApiPath: input5
|
|
14881
14895
|
};
|
|
14882
14896
|
await writeManifest(resolvedSource, manifest);
|
|
14883
14897
|
return {
|
|
14884
14898
|
status: "succeeded",
|
|
14885
14899
|
openApiPath: resolvedOpenApi,
|
|
14886
|
-
openApiInput:
|
|
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:
|
|
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,
|
|
15321
|
+
function applyOpenApiToJsonConfig(config2, input5) {
|
|
15308
15322
|
const next = { ...config2 };
|
|
15309
15323
|
next.apis = [
|
|
15310
15324
|
{
|
|
15311
15325
|
type: "file",
|
|
15312
|
-
input:
|
|
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(
|
|
15356
|
-
const sku =
|
|
15369
|
+
function buildPlanItemFromWizard(input5) {
|
|
15370
|
+
const sku = input5.sku ?? slugifyPlanSku(input5.displayName);
|
|
15357
15371
|
const item = {
|
|
15358
15372
|
sku,
|
|
15359
|
-
displayName:
|
|
15360
|
-
priceAmount:
|
|
15361
|
-
limits: buildLimits(
|
|
15373
|
+
displayName: input5.displayName,
|
|
15374
|
+
priceAmount: input5.priceAmount,
|
|
15375
|
+
limits: buildLimits(input5.rateLimitCalls, input5.monthlyQuota)
|
|
15362
15376
|
};
|
|
15363
|
-
if (
|
|
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/
|
|
16662
|
-
|
|
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/
|
|
16665
|
-
|
|
16666
|
-
|
|
16667
|
-
|
|
16668
|
-
|
|
16669
|
-
|
|
16670
|
-
|
|
16671
|
-
|
|
16672
|
-
|
|
16673
|
-
|
|
16674
|
-
|
|
16675
|
-
|
|
16676
|
-
|
|
16677
|
-
|
|
16678
|
-
|
|
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
|
-
|
|
16693
|
-
|
|
16694
|
-
|
|
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
|
-
|
|
16702
|
-
return {
|
|
16703
|
-
auth,
|
|
16704
|
-
created: existing === null,
|
|
16705
|
-
updated: existing !== null,
|
|
16706
|
-
skipped: false
|
|
16707
|
-
};
|
|
16701
|
+
return ig;
|
|
16708
16702
|
}
|
|
16709
|
-
async function
|
|
16710
|
-
const
|
|
16711
|
-
const
|
|
16712
|
-
const
|
|
16713
|
-
|
|
16714
|
-
const
|
|
16715
|
-
if (
|
|
16716
|
-
|
|
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 (
|
|
16723
|
-
|
|
16712
|
+
if (ig.ignores(relPosix)) {
|
|
16713
|
+
continue;
|
|
16724
16714
|
}
|
|
16725
|
-
if (
|
|
16726
|
-
|
|
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
|
|
16732
|
-
const
|
|
16733
|
-
|
|
16734
|
-
|
|
16735
|
-
|
|
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
|
|
16739
|
-
|
|
16740
|
-
|
|
16741
|
-
|
|
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
|
-
|
|
16745
|
-
const path72 = trimmed.slice(spaceIndex + 1).trim();
|
|
16746
|
-
return `${method}:${path72.startsWith("/") ? path72 : `/${path72}`}`;
|
|
16734
|
+
return files.sort();
|
|
16747
16735
|
}
|
|
16748
|
-
|
|
16749
|
-
|
|
16750
|
-
|
|
16751
|
-
const
|
|
16752
|
-
|
|
16753
|
-
|
|
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
|
-
|
|
16756
|
-
|
|
16757
|
-
|
|
16758
|
-
|
|
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
|
-
|
|
16761
|
-
|
|
16762
|
-
|
|
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
|
|
16769
|
-
|
|
16780
|
+
for (const dir of ["pages", "landing", "src"]) {
|
|
16781
|
+
const full = path54.join(resolved, dir);
|
|
16770
16782
|
try {
|
|
16771
|
-
const
|
|
16772
|
-
|
|
16773
|
-
|
|
16774
|
-
|
|
16775
|
-
|
|
16776
|
-
|
|
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
|
|
16788
|
-
|
|
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
|
-
|
|
16791
|
-
|
|
16792
|
-
|
|
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,
|
|
17043
|
-
const key =
|
|
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 '${
|
|
17316
|
+
throw new Error(`Gateway plan '${input5.sku}' was not found; upsert plans before reordering.`);
|
|
17047
17317
|
}
|
|
17048
|
-
const sku = skuById?.get(key)?.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
|
|
17095
|
-
const found = bySku.get(
|
|
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
|
-
|
|
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(
|
|
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:
|
|
17388
|
+
displayName: input5.displayName,
|
|
17119
17389
|
isActive: true,
|
|
17120
|
-
limitsJson: planLimitsForPlatform(
|
|
17121
|
-
priceAmount:
|
|
17122
|
-
priceCurrency:
|
|
17123
|
-
billingPeriod:
|
|
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
|
-
|
|
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(
|
|
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:
|
|
17143
|
-
displayName:
|
|
17144
|
-
priceAmount:
|
|
17145
|
-
priceCurrency:
|
|
17146
|
-
billingPeriod: normalizeBillingPeriod2(
|
|
17147
|
-
limitsJson: planLimitsForPlatform(
|
|
17148
|
-
includedActionKeys: options?.deferActionKeys ? [] : await resolveActionKeysOrAllProjectActions(
|
|
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(
|
|
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/
|
|
17167
|
-
|
|
17168
|
-
|
|
17169
|
-
|
|
17170
|
-
|
|
17171
|
-
|
|
17172
|
-
|
|
17173
|
-
|
|
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
|
|
17242
|
-
const
|
|
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
|
-
|
|
17261
|
-
|
|
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
|
-
|
|
17271
|
-
|
|
17272
|
-
|
|
17273
|
-
|
|
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
|
-
|
|
17287
|
-
|
|
17288
|
-
|
|
17289
|
-
return
|
|
17290
|
-
|
|
17291
|
-
|
|
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
|
|
17303
|
-
const
|
|
17304
|
-
|
|
17305
|
-
|
|
17306
|
-
|
|
17307
|
-
|
|
17308
|
-
|
|
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
|
-
|
|
17311
|
-
|
|
17312
|
-
|
|
17313
|
-
|
|
17314
|
-
|
|
17315
|
-
|
|
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
|
|
17318
|
-
const
|
|
17319
|
-
const
|
|
17320
|
-
const
|
|
17321
|
-
const
|
|
17322
|
-
|
|
17323
|
-
|
|
17324
|
-
|
|
17325
|
-
|
|
17326
|
-
|
|
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
|
-
|
|
17329
|
-
|
|
17330
|
-
|
|
17331
|
-
|
|
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
|
-
|
|
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
|
-
|
|
17356
|
-
|
|
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 (!
|
|
17361
|
-
|
|
17540
|
+
if (!openApiPath) {
|
|
17541
|
+
return null;
|
|
17362
17542
|
}
|
|
17363
|
-
|
|
17364
|
-
|
|
17365
|
-
|
|
17366
|
-
|
|
17367
|
-
|
|
17368
|
-
}
|
|
17369
|
-
|
|
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/
|
|
17374
|
-
|
|
17375
|
-
|
|
17376
|
-
"
|
|
17377
|
-
|
|
17378
|
-
|
|
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
|
-
|
|
17392
|
-
|
|
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
|
|
17395
|
-
|
|
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
|
|
17404
|
-
const
|
|
17405
|
-
|
|
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
|
|
17408
|
-
if (
|
|
17409
|
-
return
|
|
17580
|
+
function collectManifestPublicActionKeys(gateway) {
|
|
17581
|
+
if (!gateway) {
|
|
17582
|
+
return [];
|
|
17410
17583
|
}
|
|
17411
|
-
const
|
|
17412
|
-
|
|
17413
|
-
|
|
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 (
|
|
17416
|
-
|
|
17591
|
+
if (gateway.publicHealthActionKey?.trim()) {
|
|
17592
|
+
try {
|
|
17593
|
+
keys.add(normalizePublicRoute(gateway.publicHealthActionKey));
|
|
17594
|
+
} catch {
|
|
17595
|
+
}
|
|
17417
17596
|
}
|
|
17418
|
-
return
|
|
17597
|
+
return [...keys];
|
|
17419
17598
|
}
|
|
17420
|
-
|
|
17421
|
-
|
|
17422
|
-
|
|
17423
|
-
|
|
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
|
-
|
|
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
|
-
|
|
17432
|
-
|
|
17433
|
-
|
|
17434
|
-
|
|
17435
|
-
|
|
17436
|
-
|
|
17437
|
-
|
|
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
|
-
|
|
17477
|
-
|
|
17478
|
-
|
|
17479
|
-
|
|
17480
|
-
|
|
17481
|
-
|
|
17482
|
-
|
|
17483
|
-
|
|
17484
|
-
|
|
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
|
-
|
|
17495
|
-
|
|
17496
|
-
|
|
17497
|
-
|
|
17498
|
-
|
|
17499
|
-
|
|
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
|
-
|
|
17509
|
-
|
|
17510
|
-
|
|
17511
|
-
|
|
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
|
-
|
|
17514
|
-
|
|
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
|
-
|
|
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/
|
|
17762
|
-
|
|
17763
|
-
|
|
17764
|
-
|
|
17765
|
-
|
|
17766
|
-
|
|
17767
|
-
|
|
17768
|
-
|
|
17769
|
-
|
|
17770
|
-
|
|
17771
|
-
|
|
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
|
-
|
|
17777
|
-
|
|
17778
|
-
|
|
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
|
-
|
|
17782
|
-
|
|
17783
|
-
|
|
17784
|
-
|
|
17785
|
-
|
|
17786
|
-
|
|
17787
|
-
|
|
17788
|
-
|
|
17789
|
-
|
|
17790
|
-
|
|
17791
|
-
|
|
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
|
|
17794
|
-
|
|
17795
|
-
|
|
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
|
-
|
|
17798
|
-
|
|
17799
|
-
|
|
17800
|
-
|
|
17801
|
-
|
|
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
|
-
|
|
17809
|
-
|
|
17810
|
-
|
|
17811
|
-
steps: []
|
|
18026
|
+
status: options.getStatus(last),
|
|
18027
|
+
value: last,
|
|
18028
|
+
timedOut: true
|
|
17812
18029
|
};
|
|
17813
18030
|
}
|
|
17814
|
-
|
|
17815
|
-
|
|
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/
|
|
17852
|
-
|
|
17853
|
-
|
|
17854
|
-
|
|
17855
|
-
|
|
17856
|
-
|
|
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
|
-
|
|
17869
|
-
|
|
17870
|
-
|
|
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
|
-
|
|
17880
|
-
|
|
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
|
-
|
|
17899
|
-
|
|
17900
|
-
|
|
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
|
-
|
|
17905
|
-
|
|
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
|
-
|
|
18056
|
+
const code = error.problem?.code;
|
|
18057
|
+
return code === WARM_POOL_CAPACITY_TEMPORARILY_UNAVAILABLE;
|
|
17911
18058
|
}
|
|
17912
|
-
|
|
17913
|
-
|
|
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
|
-
|
|
17925
|
-
|
|
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
|
-
|
|
17937
|
-
const
|
|
17938
|
-
|
|
17939
|
-
|
|
17940
|
-
|
|
17941
|
-
|
|
17942
|
-
|
|
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
|
-
|
|
17957
|
-
|
|
17958
|
-
|
|
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
|
-
|
|
17969
|
-
|
|
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
|
|
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
|
-
|
|
17995
|
-
const
|
|
17996
|
-
|
|
17997
|
-
|
|
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
|
-
|
|
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
|
-
|
|
18020
|
-
|
|
18021
|
-
const
|
|
18022
|
-
|
|
18023
|
-
|
|
18024
|
-
|
|
18025
|
-
|
|
18026
|
-
|
|
18027
|
-
|
|
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
|
|
18032
|
-
const
|
|
18033
|
-
|
|
18034
|
-
|
|
18035
|
-
|
|
18036
|
-
|
|
18037
|
-
|
|
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 (
|
|
18058
|
-
|
|
18059
|
-
} else {
|
|
18060
|
-
current.backend = previousBackend;
|
|
18111
|
+
if (isWarmPoolInfrastructureReady(project2)) {
|
|
18112
|
+
return "succeeded";
|
|
18061
18113
|
}
|
|
18062
|
-
|
|
18063
|
-
|
|
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
|
|
18068
|
-
const
|
|
18069
|
-
|
|
18070
|
-
|
|
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
|
-
|
|
18076
|
-
|
|
18077
|
-
|
|
18078
|
-
|
|
18079
|
-
|
|
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
|
|
18189
|
+
let output4 = "";
|
|
18128
18190
|
child.stdout.on("data", (chunk) => {
|
|
18129
|
-
|
|
18191
|
+
output4 += chunk.toString();
|
|
18130
18192
|
});
|
|
18131
18193
|
child.stderr.on("data", (chunk) => {
|
|
18132
|
-
|
|
18194
|
+
output4 += chunk.toString();
|
|
18133
18195
|
});
|
|
18134
18196
|
child.on("error", reject);
|
|
18135
|
-
child.on("close", (exitCode) => resolve({ exitCode, output:
|
|
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
|
|
18284
|
+
const output4 = createWriteStream2(zipPath);
|
|
18223
18285
|
const archive = new ZipArchive({ zlib: { level: 6 } });
|
|
18224
|
-
|
|
18286
|
+
output4.on("close", () => resolve());
|
|
18225
18287
|
archive.on("error", reject);
|
|
18226
|
-
|
|
18227
|
-
archive.pipe(
|
|
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(
|
|
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(
|
|
19532
|
-
|
|
19533
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
|
19746
|
-
|
|
19747
|
-
|
|
19748
|
-
const
|
|
19749
|
-
|
|
19750
|
-
|
|
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
|
-
|
|
19753
|
-
|
|
19754
|
-
|
|
19755
|
-
|
|
19756
|
-
|
|
19757
|
-
|
|
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 (!
|
|
19765
|
-
|
|
19714
|
+
if (!args.projectName?.trim()) {
|
|
19715
|
+
throw new Error("projectName is required when projectId is not provided.");
|
|
19766
19716
|
}
|
|
19767
|
-
|
|
19768
|
-
|
|
19769
|
-
|
|
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
|
|
19772
|
-
|
|
19773
|
-
|
|
19774
|
-
|
|
19775
|
-
|
|
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
|
|
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 =
|
|
19764
|
+
const resolved = path59.resolve(sourceDirectory);
|
|
19823
19765
|
for (const envFile of [".env", ".env.production"]) {
|
|
19824
19766
|
try {
|
|
19825
|
-
const content = await readFile17(
|
|
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 =
|
|
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 =
|
|
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(
|
|
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 =
|
|
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 =
|
|
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 =
|
|
19925
|
-
const siteRoot =
|
|
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 (
|
|
20033
|
+
if (Date.now() + pollIntervalMs > deadline) {
|
|
19944
20034
|
break;
|
|
19945
20035
|
}
|
|
20036
|
+
await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
|
|
19946
20037
|
}
|
|
19947
|
-
|
|
19948
|
-
|
|
19949
|
-
|
|
19950
|
-
|
|
19951
|
-
|
|
19952
|
-
|
|
19953
|
-
|
|
19954
|
-
|
|
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(
|
|
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 {
|
|
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 {
|
|
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(
|
|
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(
|
|
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(
|
|
20608
|
+
const { manifest: loadedManifest, secrets } = await resolvePublishConfig(
|
|
20609
|
+
args.sourceDirectory
|
|
20610
|
+
);
|
|
20551
20611
|
const projectIdHint = args.projectId ?? loadedManifest.projectId;
|
|
20552
|
-
const emailOptions = await resolveEmailOptionsForPublish(
|
|
20553
|
-
|
|
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(
|
|
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
|
-
|
|
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({
|
|
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(
|
|
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(
|
|
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, {
|
|
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(
|
|
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(
|
|
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(
|
|
20878
|
+
recordStep(
|
|
20879
|
+
"update_dev_portal_settings",
|
|
20880
|
+
"skipped",
|
|
20881
|
+
new StepTimer().finish()
|
|
20882
|
+
);
|
|
20751
20883
|
}
|
|
20752
|
-
if (shouldRunResourceStep(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
20813
|
-
|
|
20814
|
-
|
|
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(
|
|
20878
|
-
|
|
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(
|
|
20881
|
-
|
|
20882
|
-
|
|
20883
|
-
|
|
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({
|
|
21065
|
+
timer.finish({
|
|
21066
|
+
actionCount: actionWait.actionCount,
|
|
21067
|
+
results: actionSyncResults
|
|
21068
|
+
})
|
|
20903
21069
|
);
|
|
20904
|
-
} else if (shouldRunResourceStep(
|
|
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(
|
|
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(
|
|
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({
|
|
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(
|
|
20939
|
-
|
|
20940
|
-
|
|
20941
|
-
|
|
20942
|
-
|
|
20943
|
-
|
|
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(
|
|
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(
|
|
21172
|
+
recordStep(
|
|
21173
|
+
"sync_dev_portal_plan_catalog",
|
|
21174
|
+
"skipped",
|
|
21175
|
+
new StepTimer().finish()
|
|
21176
|
+
);
|
|
20978
21177
|
}
|
|
20979
|
-
let gatewayUrl = await resolveProjectGatewayUrl(
|
|
20980
|
-
|
|
20981
|
-
|
|
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(
|
|
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({
|
|
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({
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
21025
|
-
|
|
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(
|
|
21263
|
+
recordStep(
|
|
21264
|
+
"deploy_dev_portal",
|
|
21265
|
+
"succeeded",
|
|
21266
|
+
deployTimer.finish({ siteSourceHash })
|
|
21267
|
+
);
|
|
21040
21268
|
}
|
|
21041
21269
|
}
|
|
21042
|
-
} else if (shouldRunResourceStep(
|
|
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, {
|
|
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(
|
|
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:
|
|
21438
|
-
projectName:
|
|
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 ?
|
|
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
|
|
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 ${
|
|
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
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
22230
|
+
openApiInput: input5,
|
|
22004
22231
|
openApiTitle: selected.title ?? null,
|
|
22005
22232
|
openApiVersion: selected.version ?? null,
|
|
22006
22233
|
apiReferencePath: "/api",
|