@lukoweb/apitogo 0.1.119 → 0.1.121
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli/cli.js
CHANGED
|
@@ -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.121",
|
|
5806
5806
|
type: "module",
|
|
5807
5807
|
sideEffects: [
|
|
5808
5808
|
"**/*.css",
|
|
@@ -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");
|
|
@@ -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
|
},
|
|
@@ -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 };
|
|
@@ -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,77 +17910,6 @@ async function validateDevPortalReadiness(client, projectId, options) {
|
|
|
17758
17910
|
};
|
|
17759
17911
|
}
|
|
17760
17912
|
|
|
17761
|
-
// src/cli/platform/helpers/publish-ledger.ts
|
|
17762
|
-
import { mkdir as mkdir11, readFile as readFile15, writeFile as writeFile14 } from "node:fs/promises";
|
|
17763
|
-
import path53 from "node:path";
|
|
17764
|
-
var PUBLISH_LEDGER_RELATIVE_PATH = ".apitogo/publish-state.json";
|
|
17765
|
-
function publishLedgerPath(siteDirectory) {
|
|
17766
|
-
return path53.join(path53.resolve(siteDirectory), PUBLISH_LEDGER_RELATIVE_PATH);
|
|
17767
|
-
}
|
|
17768
|
-
async function readPublishLedger(siteDirectory) {
|
|
17769
|
-
const filePath = publishLedgerPath(siteDirectory);
|
|
17770
|
-
try {
|
|
17771
|
-
const raw = await readFile15(filePath, "utf8");
|
|
17772
|
-
const parsed = JSON.parse(raw);
|
|
17773
|
-
if (parsed?.version !== 1 || !Array.isArray(parsed.steps)) {
|
|
17774
|
-
return null;
|
|
17775
|
-
}
|
|
17776
|
-
return parsed;
|
|
17777
|
-
} catch {
|
|
17778
|
-
return null;
|
|
17779
|
-
}
|
|
17780
|
-
}
|
|
17781
|
-
async function writePublishLedger(siteDirectory, ledger) {
|
|
17782
|
-
const filePath = publishLedgerPath(siteDirectory);
|
|
17783
|
-
await mkdir11(path53.dirname(filePath), { recursive: true });
|
|
17784
|
-
const next = {
|
|
17785
|
-
...ledger,
|
|
17786
|
-
version: 1,
|
|
17787
|
-
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
17788
|
-
};
|
|
17789
|
-
await writeFile14(filePath, `${JSON.stringify(next, null, 2)}
|
|
17790
|
-
`, "utf8");
|
|
17791
|
-
return filePath;
|
|
17792
|
-
}
|
|
17793
|
-
function resolveResumeStepFromLedger(ledger, orderedSteps) {
|
|
17794
|
-
if (!ledger?.steps?.length) {
|
|
17795
|
-
return void 0;
|
|
17796
|
-
}
|
|
17797
|
-
const byStep = new Map(ledger.steps.map((step) => [step.step, step]));
|
|
17798
|
-
for (const step of orderedSteps) {
|
|
17799
|
-
const record = byStep.get(step);
|
|
17800
|
-
if (!record || record.status !== "succeeded" && record.status !== "skipped") {
|
|
17801
|
-
return step;
|
|
17802
|
-
}
|
|
17803
|
-
}
|
|
17804
|
-
return void 0;
|
|
17805
|
-
}
|
|
17806
|
-
function createEmptyPublishLedger(projectId) {
|
|
17807
|
-
return {
|
|
17808
|
-
version: 1,
|
|
17809
|
-
projectId,
|
|
17810
|
-
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
17811
|
-
steps: []
|
|
17812
|
-
};
|
|
17813
|
-
}
|
|
17814
|
-
var StepTimer = class {
|
|
17815
|
-
startedAt;
|
|
17816
|
-
startedAtIso;
|
|
17817
|
-
constructor() {
|
|
17818
|
-
this.startedAt = Date.now();
|
|
17819
|
-
this.startedAtIso = new Date(this.startedAt).toISOString();
|
|
17820
|
-
}
|
|
17821
|
-
finish(detail) {
|
|
17822
|
-
const finishedAt = Date.now();
|
|
17823
|
-
return {
|
|
17824
|
-
startedAt: this.startedAtIso,
|
|
17825
|
-
finishedAt: new Date(finishedAt).toISOString(),
|
|
17826
|
-
durationMs: finishedAt - this.startedAt,
|
|
17827
|
-
detail
|
|
17828
|
-
};
|
|
17829
|
-
}
|
|
17830
|
-
};
|
|
17831
|
-
|
|
17832
17913
|
// src/cli/platform/helpers/wait-for-gateway-actions.ts
|
|
17833
17914
|
async function waitForGatewayActionsRegistered(client, projectId, options) {
|
|
17834
17915
|
const minActionCount = options?.minActionCount ?? 1;
|
|
@@ -17844,251 +17925,232 @@ async function waitForGatewayActionsRegistered(client, projectId, options) {
|
|
|
17844
17925
|
if (Date.now() - started >= timeoutMs) {
|
|
17845
17926
|
return { actionCount, timedOut: true };
|
|
17846
17927
|
}
|
|
17847
|
-
await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
|
|
17848
|
-
}
|
|
17849
|
-
}
|
|
17850
|
-
|
|
17851
|
-
// src/cli/platform/helpers/deploy-artifact-hash.ts
|
|
17852
|
-
import { createHash as createHash2 } from "node:crypto";
|
|
17853
|
-
import { createReadStream, promises as fs8 } from "node:fs";
|
|
17854
|
-
import path55 from "node:path";
|
|
17855
|
-
|
|
17856
|
-
// src/cli/platform/helpers/package-files.ts
|
|
17857
|
-
import { promises as fs7 } from "node:fs";
|
|
17858
|
-
import path54 from "node:path";
|
|
17859
|
-
import ignore from "ignore";
|
|
17860
|
-
var DEFAULT_EXCLUDES = /* @__PURE__ */ new Set([
|
|
17861
|
-
"node_modules",
|
|
17862
|
-
".git",
|
|
17863
|
-
".DS_Store",
|
|
17864
|
-
"dist",
|
|
17865
|
-
"bin",
|
|
17866
|
-
"obj"
|
|
17867
|
-
]);
|
|
17868
|
-
async function loadGitignore(rootDir) {
|
|
17869
|
-
const ig = ignore();
|
|
17870
|
-
ig.add(["node_modules/", ".git/", "dist/", "bin/", "obj/"]);
|
|
17871
|
-
const gitignorePath = path54.join(rootDir, ".gitignore");
|
|
17872
|
-
try {
|
|
17873
|
-
const content = await fs7.readFile(gitignorePath, "utf8");
|
|
17874
|
-
ig.add(content);
|
|
17875
|
-
} catch {
|
|
17876
|
-
}
|
|
17877
|
-
return ig;
|
|
17878
|
-
}
|
|
17879
|
-
async function collectFiles(rootDir, relativeDir, ig, files) {
|
|
17880
|
-
const absDir = path54.join(rootDir, relativeDir);
|
|
17881
|
-
const entries = await fs7.readdir(absDir, { withFileTypes: true });
|
|
17882
|
-
for (const entry of entries) {
|
|
17883
|
-
const rel = relativeDir ? path54.join(relativeDir, entry.name) : entry.name;
|
|
17884
|
-
const relPosix = rel.split(path54.sep).join("/");
|
|
17885
|
-
if (DEFAULT_EXCLUDES.has(entry.name) && !relativeDir) {
|
|
17886
|
-
continue;
|
|
17887
|
-
}
|
|
17888
|
-
if (ig.ignores(relPosix)) {
|
|
17889
|
-
continue;
|
|
17890
|
-
}
|
|
17891
|
-
if (entry.isDirectory()) {
|
|
17892
|
-
await collectFiles(rootDir, rel, ig, files);
|
|
17893
|
-
} else if (entry.isFile()) {
|
|
17894
|
-
files.push(rel);
|
|
17895
|
-
}
|
|
17928
|
+
await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
|
|
17896
17929
|
}
|
|
17897
17930
|
}
|
|
17898
|
-
|
|
17899
|
-
|
|
17900
|
-
|
|
17901
|
-
|
|
17902
|
-
|
|
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";
|
|
17903
17946
|
}
|
|
17904
|
-
|
|
17905
|
-
|
|
17906
|
-
|
|
17907
|
-
|
|
17908
|
-
|
|
17947
|
+
}
|
|
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";
|
|
17909
17962
|
}
|
|
17910
|
-
return files.sort();
|
|
17911
17963
|
}
|
|
17912
|
-
|
|
17913
|
-
|
|
17914
|
-
|
|
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");
|
|
17964
|
+
function isTerminalInfrastructureStatus(platformStatus) {
|
|
17965
|
+
const s = platformStatus.toLowerCase();
|
|
17966
|
+
return s === "completed" || s === "completed_with_warnings" || s === "failed";
|
|
17923
17967
|
}
|
|
17924
|
-
|
|
17925
|
-
const
|
|
17926
|
-
|
|
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");
|
|
17968
|
+
function isTerminalDeploymentStatus(platformStatus) {
|
|
17969
|
+
const s = platformStatus.toLowerCase();
|
|
17970
|
+
return s === "healthy" || s === "failed" || s === "cancelled";
|
|
17935
17971
|
}
|
|
17936
|
-
|
|
17937
|
-
|
|
17938
|
-
|
|
17939
|
-
|
|
17940
|
-
|
|
17941
|
-
|
|
17942
|
-
|
|
17943
|
-
|
|
17944
|
-
|
|
17945
|
-
|
|
17946
|
-
|
|
17947
|
-
|
|
17948
|
-
|
|
17949
|
-
|
|
17950
|
-
|
|
17951
|
-
|
|
17952
|
-
|
|
17953
|
-
|
|
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
|
+
};
|
|
17954
17996
|
}
|
|
17955
|
-
}
|
|
17956
|
-
for (const dir of ["pages", "landing", "src"]) {
|
|
17957
|
-
const full = path55.join(resolved, dir);
|
|
17958
17997
|
try {
|
|
17959
|
-
|
|
17960
|
-
|
|
17961
|
-
|
|
17962
|
-
|
|
17963
|
-
|
|
17964
|
-
|
|
17965
|
-
|
|
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
|
+
};
|
|
18016
|
+
}
|
|
18017
|
+
if (Date.now() + intervalMs > deadline) {
|
|
18018
|
+
break;
|
|
17966
18019
|
}
|
|
18020
|
+
await sleep2(intervalMs);
|
|
17967
18021
|
}
|
|
17968
|
-
|
|
17969
|
-
|
|
17970
|
-
hash2.update("gatewayUrl");
|
|
17971
|
-
hash2.update("\0");
|
|
17972
|
-
hash2.update(gatewayUrl);
|
|
17973
|
-
hash2.update("\0");
|
|
18022
|
+
if (!last) {
|
|
18023
|
+
throw new Error("Polling produced no results.");
|
|
17974
18024
|
}
|
|
17975
|
-
return
|
|
18025
|
+
return {
|
|
18026
|
+
status: options.getStatus(last),
|
|
18027
|
+
value: last,
|
|
18028
|
+
timedOut: true
|
|
18029
|
+
};
|
|
18030
|
+
}
|
|
18031
|
+
function isPollableJobValue(value) {
|
|
18032
|
+
return typeof value === "object" && value !== null && "status" in value;
|
|
17976
18033
|
}
|
|
17977
18034
|
|
|
17978
|
-
// src/cli/platform/helpers/
|
|
17979
|
-
|
|
17980
|
-
|
|
17981
|
-
|
|
17982
|
-
|
|
17983
|
-
|
|
17984
|
-
|
|
17985
|
-
|
|
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"
|
|
18041
|
+
]);
|
|
18042
|
+
var WARM_POOL_CREATE_RETRY_BUDGET_MS = 10 * 60 * 1e3;
|
|
18043
|
+
function sleep3(ms) {
|
|
18044
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
17986
18045
|
}
|
|
17987
|
-
function
|
|
17988
|
-
|
|
17989
|
-
return {
|
|
17990
|
-
...doc,
|
|
17991
|
-
servers: [{ url: url2, description: "API gateway" }]
|
|
17992
|
-
};
|
|
18046
|
+
function warmPoolRetryDelayMs() {
|
|
18047
|
+
return 6e4 + Math.floor(Math.random() * 30001);
|
|
17993
18048
|
}
|
|
17994
|
-
|
|
17995
|
-
|
|
17996
|
-
|
|
17997
|
-
if (fromProject) {
|
|
17998
|
-
return normalizeGatewayUrl(fromProject);
|
|
18049
|
+
function isWarmPoolCapacityUnavailableError(error) {
|
|
18050
|
+
if (!(error instanceof PlatformApiError)) {
|
|
18051
|
+
return false;
|
|
17999
18052
|
}
|
|
18000
|
-
|
|
18001
|
-
|
|
18002
|
-
const fromBackend = summary.backendUrl?.trim();
|
|
18003
|
-
if (fromBackend) {
|
|
18004
|
-
return normalizeGatewayUrl(fromBackend);
|
|
18005
|
-
}
|
|
18006
|
-
} catch {
|
|
18053
|
+
if (error.status !== 503) {
|
|
18054
|
+
return false;
|
|
18007
18055
|
}
|
|
18008
|
-
|
|
18056
|
+
const code = error.problem?.code;
|
|
18057
|
+
return code === WARM_POOL_CAPACITY_TEMPORARILY_UNAVAILABLE;
|
|
18009
18058
|
}
|
|
18010
|
-
|
|
18011
|
-
|
|
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;
|
|
18059
|
+
function isWarmPoolInfrastructureReady(project) {
|
|
18060
|
+
return project.infrastructureStatus?.toLowerCase() === "ready";
|
|
18018
18061
|
}
|
|
18019
|
-
|
|
18020
|
-
|
|
18021
|
-
const source = path56.resolve(sourceOpenApiPath);
|
|
18022
|
-
const relativeInput = PUBLISH_OPENAPI_RELATIVE;
|
|
18023
|
-
const outputPath = path56.join(site, ...relativeInput.split("/"));
|
|
18024
|
-
const doc = await readOpenApiDocument(source);
|
|
18025
|
-
const rewritten = withGatewayServers(doc, gatewayUrl);
|
|
18026
|
-
await mkdir12(path56.dirname(outputPath), { recursive: true });
|
|
18027
|
-
await writeFile15(outputPath, `${JSON.stringify(rewritten, null, 2)}
|
|
18028
|
-
`, "utf8");
|
|
18029
|
-
return { outputPath, relativeInput };
|
|
18062
|
+
function isWarmPoolInfrastructureFailed(project) {
|
|
18063
|
+
return project.infrastructureStatus?.toLowerCase() === "failed";
|
|
18030
18064
|
}
|
|
18031
|
-
|
|
18032
|
-
const
|
|
18033
|
-
|
|
18034
|
-
|
|
18035
|
-
|
|
18036
|
-
|
|
18037
|
-
|
|
18038
|
-
|
|
18039
|
-
|
|
18040
|
-
|
|
18041
|
-
|
|
18042
|
-
|
|
18043
|
-
|
|
18044
|
-
|
|
18045
|
-
|
|
18046
|
-
|
|
18047
|
-
|
|
18048
|
-
|
|
18049
|
-
|
|
18050
|
-
|
|
18051
|
-
|
|
18052
|
-
|
|
18053
|
-
|
|
18054
|
-
|
|
18055
|
-
|
|
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;
|
|
18072
|
+
}
|
|
18073
|
+
const resourceType = infraStatus?.resourceType;
|
|
18074
|
+
if (resourceType === "WarmPoolBinding") {
|
|
18075
|
+
return true;
|
|
18076
|
+
}
|
|
18077
|
+
if (resourceType && COLD_MODULE_INFRA_RESOURCE_TYPES.has(resourceType)) {
|
|
18078
|
+
return false;
|
|
18079
|
+
}
|
|
18080
|
+
return false;
|
|
18081
|
+
}
|
|
18082
|
+
function resolvePortalBackendFqdn(project) {
|
|
18083
|
+
const portalApiUrl = project.portalApiUrl?.trim();
|
|
18084
|
+
if (!portalApiUrl) {
|
|
18085
|
+
return null;
|
|
18086
|
+
}
|
|
18087
|
+
try {
|
|
18088
|
+
return new URL(portalApiUrl).hostname;
|
|
18089
|
+
} catch {
|
|
18090
|
+
return null;
|
|
18091
|
+
}
|
|
18092
|
+
}
|
|
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;
|
|
18102
|
+
}
|
|
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
|
|
@@ -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
|
}
|
|
@@ -19597,228 +19667,83 @@ async function ensureModulesProvisioned(client, config2, projectId, moduleTypes,
|
|
|
19597
19667
|
pollIntervalMs,
|
|
19598
19668
|
progress?.child(moduleType)
|
|
19599
19669
|
)
|
|
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
|
-
let project;
|
|
19608
|
-
try {
|
|
19609
|
-
project = await client.getProject(projectId);
|
|
19610
|
-
} catch (error) {
|
|
19611
|
-
if (isNotFoundError(error)) {
|
|
19612
|
-
return {
|
|
19613
|
-
blocked: true,
|
|
19614
|
-
payload: {
|
|
19615
|
-
status: "blocked",
|
|
19616
|
-
reason: "project_not_found",
|
|
19617
|
-
projectId,
|
|
19618
|
-
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.`,
|
|
19619
|
-
nextStep: "create_or_fix_project"
|
|
19620
|
-
}
|
|
19621
|
-
};
|
|
19622
|
-
}
|
|
19623
|
-
throw error;
|
|
19624
|
-
}
|
|
19625
|
-
if (!isPlatformPlanUnselected(project.planTier)) {
|
|
19626
|
-
await ensureModulesProvisioned(
|
|
19627
|
-
client,
|
|
19628
|
-
config2,
|
|
19629
|
-
projectId,
|
|
19630
|
-
["apihub", "dev-portal"],
|
|
19631
|
-
wait,
|
|
19632
|
-
timeoutMs,
|
|
19633
|
-
pollIntervalMs,
|
|
19634
|
-
progress
|
|
19635
|
-
);
|
|
19636
|
-
}
|
|
19637
|
-
completedSteps.push({ step: "resolve_project", status: "succeeded", detail: { projectId } });
|
|
19638
|
-
return { blocked: false, projectId, completedSteps };
|
|
19639
|
-
}
|
|
19640
|
-
if (!args.projectName?.trim()) {
|
|
19641
|
-
throw new Error("projectName is required when projectId is not provided.");
|
|
19642
|
-
}
|
|
19643
|
-
await progress?.onStep("Creating project and provisioning modules");
|
|
19644
|
-
const bootstrapJson = await handleBootstrapProject(
|
|
19645
|
-
client,
|
|
19646
|
-
config2,
|
|
19647
|
-
{
|
|
19648
|
-
name: args.projectName.trim(),
|
|
19649
|
-
modules: ["apihub", "dev-portal"],
|
|
19650
|
-
selectFreePlan: false,
|
|
19651
|
-
configureGatewayBaseline: false,
|
|
19652
|
-
wait,
|
|
19653
|
-
timeoutMs,
|
|
19654
|
-
pollIntervalMs
|
|
19655
|
-
},
|
|
19656
|
-
progress
|
|
19657
|
-
);
|
|
19658
|
-
const bootstrap = parseHandlerResult(bootstrapJson, "bootstrap_project");
|
|
19659
|
-
completedSteps.push({ step: "bootstrap_project", status: "succeeded", detail: bootstrap });
|
|
19660
|
-
return { blocked: false, projectId: bootstrap.projectId, completedSteps };
|
|
19661
|
-
}
|
|
19662
|
-
|
|
19663
|
-
// src/cli/platform/tools/dev-portal.ts
|
|
19664
|
-
import { promises as fs11 } from "node:fs";
|
|
19665
|
-
import path61 from "node:path";
|
|
19666
|
-
|
|
19667
|
-
// src/cli/platform/helpers/tarball.ts
|
|
19668
|
-
import { createReadStream as createReadStream3, createWriteStream as createWriteStream3, promises as fs10 } from "node:fs";
|
|
19669
|
-
import { tmpdir as tmpdir3 } from "node:os";
|
|
19670
|
-
import path59 from "node:path";
|
|
19671
|
-
import { TarArchive } from "archiver";
|
|
19672
|
-
var DEV_PORTAL_DEPLOY_ARCHIVE_ROOT = "dist";
|
|
19673
|
-
function normalizeArchiveRoot(archiveRoot) {
|
|
19674
|
-
const trimmed = archiveRoot.trim().replace(/^\/+|\/+$/g, "");
|
|
19675
|
-
if (!trimmed) {
|
|
19676
|
-
throw new Error("archiveRoot must not be empty.");
|
|
19677
|
-
}
|
|
19678
|
-
return `${trimmed}/`;
|
|
19679
|
-
}
|
|
19680
|
-
async function packageDirectoryAsTarGz(sourceDirectory, options) {
|
|
19681
|
-
const resolved = path59.resolve(sourceDirectory);
|
|
19682
|
-
const files = await collectPackageableFiles(resolved);
|
|
19683
|
-
const entryPrefix = options?.archiveRoot ? normalizeArchiveRoot(options.archiveRoot) : "";
|
|
19684
|
-
const archivePath = path59.join(
|
|
19685
|
-
tmpdir3(),
|
|
19686
|
-
`apitogo-docs-${Date.now()}-${Math.random().toString(36).slice(2)}.tgz`
|
|
19687
|
-
);
|
|
19688
|
-
await new Promise((resolve, reject) => {
|
|
19689
|
-
const output4 = createWriteStream3(archivePath);
|
|
19690
|
-
const archive = new TarArchive({ gzip: true, gzipOptions: { level: 6 } });
|
|
19691
|
-
output4.on("close", () => resolve());
|
|
19692
|
-
archive.on("error", reject);
|
|
19693
|
-
output4.on("error", reject);
|
|
19694
|
-
archive.pipe(output4);
|
|
19695
|
-
for (const rel of files) {
|
|
19696
|
-
const entryName = entryPrefix + rel.split(path59.sep).join("/");
|
|
19697
|
-
archive.append(createReadStream3(path59.join(resolved, rel)), { name: entryName });
|
|
19698
|
-
}
|
|
19699
|
-
void archive.finalize();
|
|
19700
|
-
});
|
|
19701
|
-
const { size } = await fs10.stat(archivePath);
|
|
19702
|
-
return { archivePath, byteSize: size };
|
|
19703
|
-
}
|
|
19704
|
-
async function cleanupArchive(archivePath) {
|
|
19705
|
-
await fs10.unlink(archivePath).catch(() => void 0);
|
|
19706
|
-
}
|
|
19707
|
-
function isTarGzPath(filePath) {
|
|
19708
|
-
const normalized = filePath.toLowerCase();
|
|
19709
|
-
return normalized.endsWith(".tgz") || normalized.endsWith(".tar.gz");
|
|
19710
|
-
}
|
|
19711
|
-
|
|
19712
|
-
// src/cli/platform/helpers/dev-portal-backend-job.ts
|
|
19713
|
-
var DEV_PORTAL_BACKEND_UPDATE_RESOURCE_TYPE = "DevPortalBackendUpdate";
|
|
19714
|
-
var ACTIVE_JOB_STATUSES = /* @__PURE__ */ new Set(["Pending", "InProgress"]);
|
|
19715
|
-
var RECENT_COMPLETED_WINDOW_MS = 3e4;
|
|
19716
|
-
var BACKEND_UPDATE_JOB_APPEARANCE_TIMEOUT_MS = 3e4;
|
|
19717
|
-
function isBackendUpdateJob(job) {
|
|
19718
|
-
return job.resourceType === DEV_PORTAL_BACKEND_UPDATE_RESOURCE_TYPE;
|
|
19719
|
-
}
|
|
19720
|
-
function isActiveBackendUpdateJob(job) {
|
|
19721
|
-
return isBackendUpdateJob(job) && ACTIVE_JOB_STATUSES.has(job.status);
|
|
19722
|
-
}
|
|
19723
|
-
function isRecentlyCompletedBackendUpdateJob(job) {
|
|
19724
|
-
if (!isBackendUpdateJob(job) || job.status !== "Completed" || !job.completedAt) {
|
|
19725
|
-
return false;
|
|
19726
|
-
}
|
|
19727
|
-
const completedAtMs = Date.parse(job.completedAt);
|
|
19728
|
-
if (Number.isNaN(completedAtMs)) {
|
|
19729
|
-
return false;
|
|
19730
|
-
}
|
|
19731
|
-
return Date.now() - completedAtMs <= RECENT_COMPLETED_WINDOW_MS;
|
|
19732
|
-
}
|
|
19733
|
-
function sortJobsNewestFirst(jobs) {
|
|
19734
|
-
return [...jobs].sort(
|
|
19735
|
-
(a, b) => Date.parse(b.createdAt) - Date.parse(a.createdAt)
|
|
19736
|
-
);
|
|
19737
|
-
}
|
|
19738
|
-
function findBackendUpdateJob(jobs, options) {
|
|
19739
|
-
const sorted = sortJobsNewestFirst(jobs);
|
|
19740
|
-
if (options?.jobId) {
|
|
19741
|
-
return sorted.find((job) => job.id === options.jobId) ?? null;
|
|
19742
|
-
}
|
|
19743
|
-
const active = sorted.find(isActiveBackendUpdateJob);
|
|
19744
|
-
if (active) {
|
|
19745
|
-
return active;
|
|
19746
|
-
}
|
|
19747
|
-
const baseline = options?.baselineJobIds;
|
|
19748
|
-
if (baseline) {
|
|
19749
|
-
const newlyCompleted = sorted.find(
|
|
19750
|
-
(job) => isBackendUpdateJob(job) && !baseline.has(job.id) && (ACTIVE_JOB_STATUSES.has(job.status) || job.status === "Completed")
|
|
19751
|
-
);
|
|
19752
|
-
if (newlyCompleted) {
|
|
19753
|
-
return newlyCompleted;
|
|
19754
|
-
}
|
|
19755
|
-
}
|
|
19756
|
-
const recentlyCompleted = sorted.find(isRecentlyCompletedBackendUpdateJob);
|
|
19757
|
-
if (recentlyCompleted) {
|
|
19758
|
-
return recentlyCompleted;
|
|
19759
|
-
}
|
|
19760
|
-
return null;
|
|
19670
|
+
)
|
|
19671
|
+
);
|
|
19761
19672
|
}
|
|
19762
|
-
async function
|
|
19763
|
-
|
|
19764
|
-
|
|
19765
|
-
const
|
|
19766
|
-
|
|
19767
|
-
|
|
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;
|
|
19768
19694
|
}
|
|
19769
|
-
|
|
19770
|
-
|
|
19771
|
-
|
|
19772
|
-
|
|
19773
|
-
|
|
19774
|
-
|
|
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 }
|
|
19775
19711
|
});
|
|
19776
|
-
|
|
19777
|
-
const first = await pollOnce();
|
|
19778
|
-
if (first) {
|
|
19779
|
-
return { job: first, timedOut: false };
|
|
19712
|
+
return { blocked: false, projectId, completedSteps };
|
|
19780
19713
|
}
|
|
19781
|
-
if (!
|
|
19782
|
-
|
|
19714
|
+
if (!args.projectName?.trim()) {
|
|
19715
|
+
throw new Error("projectName is required when projectId is not provided.");
|
|
19783
19716
|
}
|
|
19784
|
-
|
|
19785
|
-
|
|
19786
|
-
|
|
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
|
|
19787
19731
|
);
|
|
19788
|
-
const
|
|
19789
|
-
|
|
19790
|
-
|
|
19791
|
-
|
|
19792
|
-
|
|
19793
|
-
return { job, timedOut: false };
|
|
19794
|
-
}
|
|
19795
|
-
if (Date.now() + pollIntervalMs > deadline) {
|
|
19796
|
-
break;
|
|
19797
|
-
}
|
|
19798
|
-
await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
|
|
19799
|
-
}
|
|
19800
|
-
return { job: null, timedOut: false };
|
|
19801
|
-
}
|
|
19802
|
-
async function pollBackendUpdateJobStatus(client, projectId, jobId, options) {
|
|
19803
|
-
return pollUntilTerminal({
|
|
19804
|
-
poll: () => client.getDevPortalDeployStatus(projectId, jobId),
|
|
19805
|
-
isTerminal: (value) => isTerminalInfrastructureStatus(value.status),
|
|
19806
|
-
getStatus: (value) => normalizeInfrastructureStatus(value.status),
|
|
19807
|
-
timeoutMs: options?.timeoutMs ?? DEFAULT_POLL_TIMEOUT_MS,
|
|
19808
|
-
intervalMs: options?.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS,
|
|
19809
|
-
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
|
|
19810
19737
|
});
|
|
19738
|
+
return { blocked: false, projectId: bootstrap.projectId, completedSteps };
|
|
19811
19739
|
}
|
|
19812
|
-
|
|
19813
|
-
|
|
19814
|
-
|
|
19815
|
-
result.jobs.filter(isBackendUpdateJob).map((job) => job.id)
|
|
19816
|
-
);
|
|
19817
|
-
}
|
|
19740
|
+
|
|
19741
|
+
// src/cli/platform/tools/dev-portal-site-build.ts
|
|
19742
|
+
import path62 from "node:path";
|
|
19818
19743
|
|
|
19819
19744
|
// src/cli/platform/helpers/dev-portal-site-api-url.ts
|
|
19820
19745
|
import { readdir as readdir2, readFile as readFile17, writeFile as writeFile16 } from "node:fs/promises";
|
|
19821
|
-
import
|
|
19746
|
+
import path59 from "node:path";
|
|
19822
19747
|
var SITE_API_BASE_URL_ENV_KEY = "VITE_APITOGO_AUTHENTICATION_API_BASE_URL";
|
|
19823
19748
|
var PLACEHOLDER_HOST_MARKERS = [
|
|
19824
19749
|
"dp-example.azurecontainerapps.io",
|
|
@@ -19836,10 +19761,10 @@ function isPlaceholderDevPortalApiUrl(apiBaseUrl) {
|
|
|
19836
19761
|
return PLACEHOLDER_HOST_MARKERS.some((marker) => normalized.includes(marker));
|
|
19837
19762
|
}
|
|
19838
19763
|
async function readSiteApiBaseUrl(sourceDirectory) {
|
|
19839
|
-
const resolved =
|
|
19764
|
+
const resolved = path59.resolve(sourceDirectory);
|
|
19840
19765
|
for (const envFile of [".env", ".env.production"]) {
|
|
19841
19766
|
try {
|
|
19842
|
-
const content = await readFile17(
|
|
19767
|
+
const content = await readFile17(path59.join(resolved, envFile), "utf8");
|
|
19843
19768
|
const lines = [...content.matchAll(ENV_LINE_GLOBAL_RE)];
|
|
19844
19769
|
if (lines.length === 0) {
|
|
19845
19770
|
continue;
|
|
@@ -19864,11 +19789,11 @@ async function readSiteApiBaseUrl(sourceDirectory) {
|
|
|
19864
19789
|
return null;
|
|
19865
19790
|
}
|
|
19866
19791
|
async function patchSiteApiBaseUrl(sourceDirectory, apiBaseUrl) {
|
|
19867
|
-
const resolved =
|
|
19792
|
+
const resolved = path59.resolve(sourceDirectory);
|
|
19868
19793
|
const line = `${SITE_API_BASE_URL_ENV_KEY}=${apiBaseUrl}`;
|
|
19869
19794
|
let patchedExisting = false;
|
|
19870
19795
|
for (const envFile of [".env.production", ".env"]) {
|
|
19871
|
-
const envPath =
|
|
19796
|
+
const envPath = path59.join(resolved, envFile);
|
|
19872
19797
|
try {
|
|
19873
19798
|
const content = await readFile17(envPath, "utf8");
|
|
19874
19799
|
const withoutAssignments = content.replace(ENV_LINE_GLOBAL_RE, "").replace(/\n{3,}/g, "\n\n").trimEnd();
|
|
@@ -19882,7 +19807,7 @@ ${line}
|
|
|
19882
19807
|
}
|
|
19883
19808
|
}
|
|
19884
19809
|
if (!patchedExisting) {
|
|
19885
|
-
await writeFile16(
|
|
19810
|
+
await writeFile16(path59.join(resolved, ".env"), `${line}
|
|
19886
19811
|
`);
|
|
19887
19812
|
}
|
|
19888
19813
|
}
|
|
@@ -19890,7 +19815,7 @@ function tryProjectId(explicit, manifestProjectId, config2) {
|
|
|
19890
19815
|
return explicit?.trim() || manifestProjectId?.trim() || config2.defaultProjectId?.trim() || void 0;
|
|
19891
19816
|
}
|
|
19892
19817
|
async function ensureLiveSiteApiBaseUrlForBuild(_client, config2, args, resolveFromProject) {
|
|
19893
|
-
const sourceDirectory =
|
|
19818
|
+
const sourceDirectory = path59.resolve(args.sourceDirectory);
|
|
19894
19819
|
const explicit = args.apiBaseUrl?.trim();
|
|
19895
19820
|
if (explicit && !isPlaceholderDevPortalApiUrl(explicit)) {
|
|
19896
19821
|
await patchSiteApiBaseUrl(sourceDirectory, explicit);
|
|
@@ -19914,63 +19839,219 @@ async function ensureLiveSiteApiBaseUrlForBuild(_client, config2, args, resolveF
|
|
|
19914
19839
|
`Resolved portal API URL is still a placeholder (${resolved}). Ensure the dev-portal backend is provisioned.`
|
|
19915
19840
|
);
|
|
19916
19841
|
}
|
|
19917
|
-
const normalized = resolved.replace(/\/+$/, "");
|
|
19918
|
-
await patchSiteApiBaseUrl(sourceDirectory, normalized);
|
|
19919
|
-
return normalized;
|
|
19920
|
-
}
|
|
19921
|
-
async function* walkFiles(root) {
|
|
19922
|
-
let entries;
|
|
19923
|
-
try {
|
|
19924
|
-
entries = await readdir2(root, { withFileTypes: true });
|
|
19925
|
-
} catch {
|
|
19926
|
-
return;
|
|
19842
|
+
const normalized = resolved.replace(/\/+$/, "");
|
|
19843
|
+
await patchSiteApiBaseUrl(sourceDirectory, normalized);
|
|
19844
|
+
return normalized;
|
|
19845
|
+
}
|
|
19846
|
+
async function* walkFiles(root) {
|
|
19847
|
+
let entries;
|
|
19848
|
+
try {
|
|
19849
|
+
entries = await readdir2(root, { withFileTypes: true });
|
|
19850
|
+
} catch {
|
|
19851
|
+
return;
|
|
19852
|
+
}
|
|
19853
|
+
for (const entry of entries) {
|
|
19854
|
+
const full = path59.join(root, entry.name);
|
|
19855
|
+
if (entry.isDirectory()) {
|
|
19856
|
+
if (entry.name === "node_modules" || entry.name === ".git") {
|
|
19857
|
+
continue;
|
|
19858
|
+
}
|
|
19859
|
+
yield* walkFiles(full);
|
|
19860
|
+
} else if (entry.isFile() && /\.(js|mjs|cjs|html|map)$/i.test(entry.name)) {
|
|
19861
|
+
yield full;
|
|
19862
|
+
}
|
|
19863
|
+
}
|
|
19864
|
+
}
|
|
19865
|
+
async function assertDeployableDistHasLiveApiBaseUrl(distDirectory, expectedApiBaseUrl) {
|
|
19866
|
+
const dist = path59.resolve(distDirectory);
|
|
19867
|
+
const siteRoot = path59.dirname(dist);
|
|
19868
|
+
const fromEnv = expectedApiBaseUrl?.trim() || await readSiteApiBaseUrl(siteRoot) || await readSiteApiBaseUrl(dist);
|
|
19869
|
+
if (!fromEnv || isPlaceholderDevPortalApiUrl(fromEnv)) {
|
|
19870
|
+
throw new Error(
|
|
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.`
|
|
19872
|
+
);
|
|
19873
|
+
}
|
|
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;
|
|
19927
19997
|
}
|
|
19928
|
-
|
|
19929
|
-
|
|
19930
|
-
|
|
19931
|
-
|
|
19932
|
-
|
|
19933
|
-
|
|
19934
|
-
|
|
19935
|
-
|
|
19936
|
-
yield full;
|
|
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}`);
|
|
19937
20006
|
}
|
|
20007
|
+
return { job, timedOut: false };
|
|
19938
20008
|
}
|
|
19939
|
-
|
|
19940
|
-
|
|
19941
|
-
|
|
19942
|
-
|
|
19943
|
-
|
|
19944
|
-
|
|
19945
|
-
|
|
19946
|
-
|
|
19947
|
-
|
|
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 };
|
|
19948
20018
|
}
|
|
19949
|
-
|
|
19950
|
-
|
|
19951
|
-
|
|
19952
|
-
|
|
19953
|
-
|
|
19954
|
-
|
|
19955
|
-
|
|
19956
|
-
|
|
19957
|
-
|
|
19958
|
-
|
|
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 };
|
|
19959
20032
|
}
|
|
19960
|
-
if (
|
|
20033
|
+
if (Date.now() + pollIntervalMs > deadline) {
|
|
19961
20034
|
break;
|
|
19962
20035
|
}
|
|
20036
|
+
await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
|
|
19963
20037
|
}
|
|
19964
|
-
|
|
19965
|
-
|
|
19966
|
-
|
|
19967
|
-
|
|
19968
|
-
|
|
19969
|
-
|
|
19970
|
-
|
|
19971
|
-
|
|
19972
|
-
|
|
19973
|
-
|
|
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
|
+
);
|
|
19974
20055
|
}
|
|
19975
20056
|
|
|
19976
20057
|
// src/cli/platform/tools/dev-portal.ts
|
|
@@ -20262,7 +20343,6 @@ async function handleDeployDevPortal(client, config2, args, progress) {
|
|
|
20262
20343
|
}
|
|
20263
20344
|
|
|
20264
20345
|
// src/cli/platform/tools/dev-portal-site-build.ts
|
|
20265
|
-
import path62 from "node:path";
|
|
20266
20346
|
async function handleBuildDevPortalSite(client, config2, args, progress) {
|
|
20267
20347
|
const buildProgress = progress?.child("Build dev portal site");
|
|
20268
20348
|
const sourceDirectory = path62.resolve(args.sourceDirectory);
|
|
@@ -20342,54 +20422,6 @@ async function handleBuildDevPortalSite(client, config2, args, progress) {
|
|
|
20342
20422
|
}
|
|
20343
20423
|
}
|
|
20344
20424
|
|
|
20345
|
-
// src/cli/platform/helpers/public-routes.ts
|
|
20346
|
-
function normalizeActionKeyFormat(actionKey) {
|
|
20347
|
-
const trimmed = actionKey.trim();
|
|
20348
|
-
if (trimmed.includes(":")) {
|
|
20349
|
-
const [method2, ...pathParts] = trimmed.split(":");
|
|
20350
|
-
const path73 = pathParts.join(":").trim();
|
|
20351
|
-
return `${method2.toLowerCase()}:${path73.startsWith("/") ? path73 : `/${path73}`}`;
|
|
20352
|
-
}
|
|
20353
|
-
const spaceIndex = trimmed.indexOf(" ");
|
|
20354
|
-
if (spaceIndex <= 0) {
|
|
20355
|
-
throw new Error(
|
|
20356
|
-
"actionKey must be in format 'get:/path' or 'GET /path' (method plus path)."
|
|
20357
|
-
);
|
|
20358
|
-
}
|
|
20359
|
-
const method = trimmed.slice(0, spaceIndex).trim().toLowerCase();
|
|
20360
|
-
const path72 = trimmed.slice(spaceIndex + 1).trim();
|
|
20361
|
-
return `${method}:${path72.startsWith("/") ? path72 : `/${path72}`}`;
|
|
20362
|
-
}
|
|
20363
|
-
function normalizePublicRoute(route) {
|
|
20364
|
-
const trimmed = route.trim();
|
|
20365
|
-
if (!trimmed) {
|
|
20366
|
-
throw new Error("public route must be a non-empty string");
|
|
20367
|
-
}
|
|
20368
|
-
if (trimmed.startsWith("/")) {
|
|
20369
|
-
return normalizeActionKeyFormat(`GET ${trimmed}`);
|
|
20370
|
-
}
|
|
20371
|
-
return normalizeActionKeyFormat(trimmed);
|
|
20372
|
-
}
|
|
20373
|
-
function collectManifestPublicActionKeys(gateway) {
|
|
20374
|
-
if (!gateway) {
|
|
20375
|
-
return [];
|
|
20376
|
-
}
|
|
20377
|
-
const keys = /* @__PURE__ */ new Set();
|
|
20378
|
-
for (const route of gateway.publicRoutes ?? []) {
|
|
20379
|
-
try {
|
|
20380
|
-
keys.add(normalizePublicRoute(route));
|
|
20381
|
-
} catch {
|
|
20382
|
-
}
|
|
20383
|
-
}
|
|
20384
|
-
if (gateway.publicHealthActionKey?.trim()) {
|
|
20385
|
-
try {
|
|
20386
|
-
keys.add(normalizePublicRoute(gateway.publicHealthActionKey));
|
|
20387
|
-
} catch {
|
|
20388
|
-
}
|
|
20389
|
-
}
|
|
20390
|
-
return [...keys];
|
|
20391
|
-
}
|
|
20392
|
-
|
|
20393
20425
|
// src/cli/platform/tools/dev-portal-publish.ts
|
|
20394
20426
|
var PUBLISH_STEPS = [
|
|
20395
20427
|
"resolve_project",
|
|
@@ -20420,10 +20452,7 @@ var PUBLISH_STEPS_BY_RESOURCE = {
|
|
|
20420
20452
|
"update_dev_portal_settings",
|
|
20421
20453
|
"sync_dev_portal_plan_catalog"
|
|
20422
20454
|
],
|
|
20423
|
-
"portal-frontend": [
|
|
20424
|
-
"build_dev_portal_site",
|
|
20425
|
-
"deploy_dev_portal"
|
|
20426
|
-
]
|
|
20455
|
+
"portal-frontend": ["build_dev_portal_site", "deploy_dev_portal"]
|
|
20427
20456
|
};
|
|
20428
20457
|
function parseApplyResources(raw) {
|
|
20429
20458
|
if (raw === void 0) {
|
|
@@ -20467,7 +20496,9 @@ function shouldRunPublishStep(step, resumeFromStep, completedSteps) {
|
|
|
20467
20496
|
return true;
|
|
20468
20497
|
}
|
|
20469
20498
|
if (stepIndex < resumeIndex) {
|
|
20470
|
-
return completedSteps.some(
|
|
20499
|
+
return completedSteps.some(
|
|
20500
|
+
(record) => record.step === step && record.status === "succeeded"
|
|
20501
|
+
);
|
|
20471
20502
|
}
|
|
20472
20503
|
return true;
|
|
20473
20504
|
}
|
|
@@ -20500,10 +20531,16 @@ async function shouldSkipBackendRedeploy(client, projectId, manifest, ledger, ex
|
|
|
20500
20531
|
const remoteSha = summary.artifactContentSha256?.toLowerCase() ?? null;
|
|
20501
20532
|
const ledgerSha = ledger.backendArtifactHash?.toLowerCase() ?? null;
|
|
20502
20533
|
if (remoteSha && ledgerSha && remoteSha === ledgerSha) {
|
|
20503
|
-
return {
|
|
20534
|
+
return {
|
|
20535
|
+
skip: true,
|
|
20536
|
+
reason: "healthy backend matches ledger artifact hash"
|
|
20537
|
+
};
|
|
20504
20538
|
}
|
|
20505
20539
|
if (manifest.backend?.deployed && remoteSha && ledgerSha && remoteSha === ledgerSha) {
|
|
20506
|
-
return {
|
|
20540
|
+
return {
|
|
20541
|
+
skip: true,
|
|
20542
|
+
reason: "backend marked deployed with matching hash"
|
|
20543
|
+
};
|
|
20507
20544
|
}
|
|
20508
20545
|
} catch {
|
|
20509
20546
|
}
|
|
@@ -20527,7 +20564,9 @@ async function runApplyFromSiteDirectory(client, config2, args, progress) {
|
|
|
20527
20564
|
const wait = args.wait !== false;
|
|
20528
20565
|
const resources = resolveApplyResources(args.only);
|
|
20529
20566
|
const forceBackendRedeploy = Boolean(args.force && resources.has("apihub"));
|
|
20530
|
-
const forceSiteRedeploy = Boolean(
|
|
20567
|
+
const forceSiteRedeploy = Boolean(
|
|
20568
|
+
args.force && resources.has("portal-frontend")
|
|
20569
|
+
);
|
|
20531
20570
|
const includeApihub = resources.has("apihub");
|
|
20532
20571
|
const includePortalBackend = resources.has("portal-backend");
|
|
20533
20572
|
const includePortalFrontend = resources.has("portal-frontend");
|
|
@@ -20558,22 +20597,37 @@ async function runApplyFromSiteDirectory(client, config2, args, progress) {
|
|
|
20558
20597
|
version: 1,
|
|
20559
20598
|
projectId: projectId || ledger.projectId,
|
|
20560
20599
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
20561
|
-
lastSuccessfulStep: [...timedSteps].reverse().find(
|
|
20600
|
+
lastSuccessfulStep: [...timedSteps].reverse().find(
|
|
20601
|
+
(step) => step.status === "succeeded" || step.status === "skipped"
|
|
20602
|
+
)?.step ?? ledger.lastSuccessfulStep,
|
|
20562
20603
|
steps: timedSteps.length > 0 ? timedSteps : ledger.steps
|
|
20563
20604
|
};
|
|
20564
20605
|
await writePublishLedger(args.sourceDirectory, ledger);
|
|
20565
20606
|
};
|
|
20566
20607
|
try {
|
|
20567
|
-
const { manifest: loadedManifest, secrets } = await resolvePublishConfig(
|
|
20608
|
+
const { manifest: loadedManifest, secrets } = await resolvePublishConfig(
|
|
20609
|
+
args.sourceDirectory
|
|
20610
|
+
);
|
|
20568
20611
|
const projectIdHint = args.projectId ?? loadedManifest.projectId;
|
|
20569
|
-
const emailOptions = await resolveEmailOptionsForPublish(
|
|
20570
|
-
|
|
20612
|
+
const emailOptions = await resolveEmailOptionsForPublish(
|
|
20613
|
+
client,
|
|
20614
|
+
projectIdHint
|
|
20615
|
+
);
|
|
20616
|
+
const manifest = validateManifestForPublish(
|
|
20617
|
+
loadedManifest,
|
|
20618
|
+
secrets,
|
|
20619
|
+
emailOptions
|
|
20620
|
+
);
|
|
20571
20621
|
await writeManifest(args.sourceDirectory, manifest);
|
|
20572
20622
|
const stripeRequired = manifestRequiresStripe(manifest.plans);
|
|
20573
20623
|
const ledgerResume = resolveResumeStepFromLedger(ledger, PUBLISH_STEPS);
|
|
20574
20624
|
const effectiveResumeFromStep = args.resumeFromStep ?? ledgerResume;
|
|
20575
20625
|
let publicRouteWarnings = [];
|
|
20576
|
-
if (args.createNewProject || shouldRunPublishStep(
|
|
20626
|
+
if (args.createNewProject || shouldRunPublishStep(
|
|
20627
|
+
"resolve_project",
|
|
20628
|
+
effectiveResumeFromStep,
|
|
20629
|
+
completedSteps
|
|
20630
|
+
)) {
|
|
20577
20631
|
const timer = new StepTimer();
|
|
20578
20632
|
const resolved = await resolveProjectIdForPublish(
|
|
20579
20633
|
client,
|
|
@@ -20654,7 +20708,11 @@ async function runApplyFromSiteDirectory(client, config2, args, progress) {
|
|
|
20654
20708
|
if (ensured.status !== "succeeded") {
|
|
20655
20709
|
return ensureJson;
|
|
20656
20710
|
}
|
|
20657
|
-
completedSteps.push({
|
|
20711
|
+
completedSteps.push({
|
|
20712
|
+
step: "ensure_platform_plan",
|
|
20713
|
+
status: "succeeded",
|
|
20714
|
+
detail: ensured
|
|
20715
|
+
});
|
|
20658
20716
|
}
|
|
20659
20717
|
const planGate = await assertPlatformPlanReadyForPublish(client, projectId);
|
|
20660
20718
|
if (planGate.blocked) {
|
|
@@ -20723,7 +20781,12 @@ async function runApplyFromSiteDirectory(client, config2, args, progress) {
|
|
|
20723
20781
|
detail: { reason: "resource not selected" }
|
|
20724
20782
|
});
|
|
20725
20783
|
}
|
|
20726
|
-
if (shouldRunResourceStep(
|
|
20784
|
+
if (shouldRunResourceStep(
|
|
20785
|
+
"upsert_gateway_plans",
|
|
20786
|
+
resources,
|
|
20787
|
+
effectiveResumeFromStep,
|
|
20788
|
+
completedSteps
|
|
20789
|
+
)) {
|
|
20727
20790
|
await publishProgress?.onStep("Creating gateway plans");
|
|
20728
20791
|
const timer = new StepTimer();
|
|
20729
20792
|
const planResults = await upsertGatewayPlansFromManifest(
|
|
@@ -20733,7 +20796,11 @@ async function runApplyFromSiteDirectory(client, config2, args, progress) {
|
|
|
20733
20796
|
manifest.plans,
|
|
20734
20797
|
{ deferActionKeys: true }
|
|
20735
20798
|
);
|
|
20736
|
-
recordStep(
|
|
20799
|
+
recordStep(
|
|
20800
|
+
"upsert_gateway_plans",
|
|
20801
|
+
"succeeded",
|
|
20802
|
+
timer.finish(planResults)
|
|
20803
|
+
);
|
|
20737
20804
|
} else {
|
|
20738
20805
|
recordStep("upsert_gateway_plans", "skipped", new StepTimer().finish());
|
|
20739
20806
|
}
|
|
@@ -20750,7 +20817,9 @@ async function runApplyFromSiteDirectory(client, config2, args, progress) {
|
|
|
20750
20817
|
const project = await client.getProject(projectId);
|
|
20751
20818
|
await publishProgress?.onStep("Loading backend FQDN");
|
|
20752
20819
|
let backendFqdn = resolvePortalBackendFqdn(project);
|
|
20753
|
-
const backendJson = await handleGetDevPortalBackend(client, config2, {
|
|
20820
|
+
const backendJson = await handleGetDevPortalBackend(client, config2, {
|
|
20821
|
+
projectId
|
|
20822
|
+
});
|
|
20754
20823
|
const backendResult = JSON.parse(backendJson);
|
|
20755
20824
|
backendFqdn = backendFqdn ?? backendResult.backend?.fqdn?.trim() ?? null;
|
|
20756
20825
|
const platformSiteUrl = backendResult.backend?.platformSiteUrl?.trim() ?? null;
|
|
@@ -20766,7 +20835,12 @@ async function runApplyFromSiteDirectory(client, config2, args, progress) {
|
|
|
20766
20835
|
);
|
|
20767
20836
|
const urls = backendFqdn ? resolveDevPortalUrls(project, backendFqdn, providerIds, platformSiteUrl) : null;
|
|
20768
20837
|
const backendApiBaseUrl = backendFqdn ? `https://${backendFqdn}` : "";
|
|
20769
|
-
if (shouldRunResourceStep(
|
|
20838
|
+
if (shouldRunResourceStep(
|
|
20839
|
+
"update_dev_portal_settings",
|
|
20840
|
+
resources,
|
|
20841
|
+
effectiveResumeFromStep,
|
|
20842
|
+
completedSteps
|
|
20843
|
+
)) {
|
|
20770
20844
|
await publishProgress?.onStep("Updating dev portal settings");
|
|
20771
20845
|
const timer = new StepTimer();
|
|
20772
20846
|
const openApiPath = manifest.backend?.openApiPath;
|
|
@@ -20785,7 +20859,9 @@ async function runApplyFromSiteDirectory(client, config2, args, progress) {
|
|
|
20785
20859
|
openApiSourceKind: openApi.openApiSourceKind,
|
|
20786
20860
|
openApiValue: openApi.openApiValue,
|
|
20787
20861
|
authConfigJson: JSON.stringify(buildAuthConfigJson(manifest)),
|
|
20788
|
-
authSecretsJson: JSON.stringify(
|
|
20862
|
+
authSecretsJson: JSON.stringify(
|
|
20863
|
+
buildAuthSecretsJson(secrets, emailOptions)
|
|
20864
|
+
),
|
|
20789
20865
|
stripeSecretKey: stripeRequired ? secrets.stripe.secretKey : void 0,
|
|
20790
20866
|
stripeWebhookSecret: stripeRequired ? secrets.stripe.webhookSecret : void 0,
|
|
20791
20867
|
stripeSuccessUrl: stripeRequired ? urls.stripeSuccessUrl : void 0,
|
|
@@ -20799,9 +20875,18 @@ async function runApplyFromSiteDirectory(client, config2, args, progress) {
|
|
|
20799
20875
|
parseHandlerResult(settingsJson, "update_dev_portal_settings");
|
|
20800
20876
|
recordStep("update_dev_portal_settings", "succeeded", timer.finish());
|
|
20801
20877
|
} else {
|
|
20802
|
-
recordStep(
|
|
20878
|
+
recordStep(
|
|
20879
|
+
"update_dev_portal_settings",
|
|
20880
|
+
"skipped",
|
|
20881
|
+
new StepTimer().finish()
|
|
20882
|
+
);
|
|
20803
20883
|
}
|
|
20804
|
-
if (shouldRunResourceStep(
|
|
20884
|
+
if (shouldRunResourceStep(
|
|
20885
|
+
"configure_gateway_baseline",
|
|
20886
|
+
resources,
|
|
20887
|
+
effectiveResumeFromStep,
|
|
20888
|
+
completedSteps
|
|
20889
|
+
)) {
|
|
20805
20890
|
await publishProgress?.onStep("Configuring gateway baseline");
|
|
20806
20891
|
const timer = new StepTimer();
|
|
20807
20892
|
const gateway = manifest.gateway ?? {};
|
|
@@ -20835,14 +20920,21 @@ async function runApplyFromSiteDirectory(client, config2, args, progress) {
|
|
|
20835
20920
|
{ ...config2, defaultProjectId: projectId },
|
|
20836
20921
|
baselineArgs
|
|
20837
20922
|
);
|
|
20838
|
-
const baseline = parseHandlerResult(
|
|
20923
|
+
const baseline = parseHandlerResult(
|
|
20924
|
+
baselineJson,
|
|
20925
|
+
"configure_gateway_baseline"
|
|
20926
|
+
);
|
|
20839
20927
|
recordStep(
|
|
20840
20928
|
"configure_gateway_baseline",
|
|
20841
20929
|
baseline.status === "pending" ? "pending" : "succeeded",
|
|
20842
20930
|
timer.finish(baseline)
|
|
20843
20931
|
);
|
|
20844
20932
|
} else {
|
|
20845
|
-
recordStep(
|
|
20933
|
+
recordStep(
|
|
20934
|
+
"configure_gateway_baseline",
|
|
20935
|
+
"skipped",
|
|
20936
|
+
new StepTimer().finish()
|
|
20937
|
+
);
|
|
20846
20938
|
}
|
|
20847
20939
|
const skipBackendDecision = await shouldSkipBackendRedeploy(
|
|
20848
20940
|
client,
|
|
@@ -20858,13 +20950,21 @@ async function runApplyFromSiteDirectory(client, config2, args, progress) {
|
|
|
20858
20950
|
const hasBackendSource = Boolean(backendSourceDirectory);
|
|
20859
20951
|
let backendDeploySucceeded = false;
|
|
20860
20952
|
let backendDeployFailed = false;
|
|
20861
|
-
if (shouldRunResourceStep(
|
|
20953
|
+
if (shouldRunResourceStep(
|
|
20954
|
+
"deploy_backend",
|
|
20955
|
+
resources,
|
|
20956
|
+
effectiveResumeFromStep,
|
|
20957
|
+
completedSteps
|
|
20958
|
+
) && backendConfig && backendSourceDirectory && !skipBackend) {
|
|
20862
20959
|
await publishProgress?.onStep("Deploying API backend");
|
|
20863
20960
|
const timer = new StepTimer();
|
|
20864
|
-
const resolvedOpenApiPath = resolveBackendOpenApiPath(
|
|
20865
|
-
|
|
20866
|
-
|
|
20867
|
-
|
|
20961
|
+
const resolvedOpenApiPath = resolveBackendOpenApiPath(
|
|
20962
|
+
backendConfig.openApiPath,
|
|
20963
|
+
{
|
|
20964
|
+
siteDirectory: args.sourceDirectory,
|
|
20965
|
+
backendSourceDirectory
|
|
20966
|
+
}
|
|
20967
|
+
);
|
|
20868
20968
|
try {
|
|
20869
20969
|
const deployJson = await handleDeployBackend(
|
|
20870
20970
|
client,
|
|
@@ -20926,14 +21026,25 @@ async function runApplyFromSiteDirectory(client, config2, args, progress) {
|
|
|
20926
21026
|
}
|
|
20927
21027
|
}
|
|
20928
21028
|
const shouldSyncPlanActions = hasBackendSource && !backendDeployFailed && (backendDeploySucceeded || manifest.backend?.deployed === true);
|
|
20929
|
-
if (shouldRunResourceStep(
|
|
20930
|
-
|
|
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
|
+
);
|
|
20931
21038
|
const timer = new StepTimer();
|
|
20932
|
-
const actionWait = await waitForGatewayActionsRegistered(
|
|
20933
|
-
|
|
20934
|
-
|
|
20935
|
-
|
|
20936
|
-
|
|
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
|
+
);
|
|
20937
21048
|
if (actionWait.timedOut && actionWait.actionCount < 1) {
|
|
20938
21049
|
throw new BootstrapStepError(
|
|
20939
21050
|
"sync_gateway_plan_actions",
|
|
@@ -20951,9 +21062,17 @@ async function runApplyFromSiteDirectory(client, config2, args, progress) {
|
|
|
20951
21062
|
recordStep(
|
|
20952
21063
|
"sync_gateway_plan_actions",
|
|
20953
21064
|
"succeeded",
|
|
20954
|
-
timer.finish({
|
|
21065
|
+
timer.finish({
|
|
21066
|
+
actionCount: actionWait.actionCount,
|
|
21067
|
+
results: actionSyncResults
|
|
21068
|
+
})
|
|
20955
21069
|
);
|
|
20956
|
-
} else if (shouldRunResourceStep(
|
|
21070
|
+
} else if (shouldRunResourceStep(
|
|
21071
|
+
"sync_gateway_plan_actions",
|
|
21072
|
+
resources,
|
|
21073
|
+
effectiveResumeFromStep,
|
|
21074
|
+
completedSteps
|
|
21075
|
+
)) {
|
|
20957
21076
|
recordStep(
|
|
20958
21077
|
"sync_gateway_plan_actions",
|
|
20959
21078
|
"skipped",
|
|
@@ -20962,15 +21081,26 @@ async function runApplyFromSiteDirectory(client, config2, args, progress) {
|
|
|
20962
21081
|
})
|
|
20963
21082
|
);
|
|
20964
21083
|
} else {
|
|
20965
|
-
recordStep(
|
|
21084
|
+
recordStep(
|
|
21085
|
+
"sync_gateway_plan_actions",
|
|
21086
|
+
"skipped",
|
|
21087
|
+
new StepTimer().finish()
|
|
21088
|
+
);
|
|
20966
21089
|
}
|
|
20967
21090
|
const publicActionKeys = collectManifestPublicActionKeys(manifest.gateway);
|
|
20968
|
-
if (shouldRunResourceStep(
|
|
21091
|
+
if (shouldRunResourceStep(
|
|
21092
|
+
"apply_public_routes",
|
|
21093
|
+
resources,
|
|
21094
|
+
effectiveResumeFromStep,
|
|
21095
|
+
completedSteps
|
|
21096
|
+
)) {
|
|
20969
21097
|
if (publicActionKeys.length === 0) {
|
|
20970
21098
|
recordStep(
|
|
20971
21099
|
"apply_public_routes",
|
|
20972
21100
|
"skipped",
|
|
20973
|
-
new StepTimer().finish({
|
|
21101
|
+
new StepTimer().finish({
|
|
21102
|
+
reason: "no publicRoutes or publicHealthActionKey in manifest"
|
|
21103
|
+
})
|
|
20974
21104
|
);
|
|
20975
21105
|
} else if (!shouldSyncPlanActions && !(manifest.backend?.deployed === true)) {
|
|
20976
21106
|
recordStep(
|
|
@@ -20987,12 +21117,20 @@ async function runApplyFromSiteDirectory(client, config2, args, progress) {
|
|
|
20987
21117
|
} else {
|
|
20988
21118
|
await publishProgress?.onStep("Applying public gateway routes");
|
|
20989
21119
|
const timer = new StepTimer();
|
|
20990
|
-
const actionWait = await waitForGatewayActionsRegistered(
|
|
20991
|
-
|
|
20992
|
-
|
|
20993
|
-
|
|
20994
|
-
|
|
20995
|
-
|
|
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
|
+
);
|
|
20996
21134
|
publicRouteWarnings = result.warnings;
|
|
20997
21135
|
recordStep(
|
|
20998
21136
|
"apply_public_routes",
|
|
@@ -21008,7 +21146,12 @@ async function runApplyFromSiteDirectory(client, config2, args, progress) {
|
|
|
21008
21146
|
} else {
|
|
21009
21147
|
recordStep("apply_public_routes", "skipped", new StepTimer().finish());
|
|
21010
21148
|
}
|
|
21011
|
-
if (shouldRunResourceStep(
|
|
21149
|
+
if (shouldRunResourceStep(
|
|
21150
|
+
"sync_dev_portal_plan_catalog",
|
|
21151
|
+
resources,
|
|
21152
|
+
effectiveResumeFromStep,
|
|
21153
|
+
completedSteps
|
|
21154
|
+
)) {
|
|
21012
21155
|
await publishProgress?.onStep("Syncing dev portal plan catalog");
|
|
21013
21156
|
const timer = new StepTimer();
|
|
21014
21157
|
const expectedPlanCount = manifest.plans.length;
|
|
@@ -21026,29 +21169,52 @@ async function runApplyFromSiteDirectory(client, config2, args, progress) {
|
|
|
21026
21169
|
})
|
|
21027
21170
|
);
|
|
21028
21171
|
} else {
|
|
21029
|
-
recordStep(
|
|
21172
|
+
recordStep(
|
|
21173
|
+
"sync_dev_portal_plan_catalog",
|
|
21174
|
+
"skipped",
|
|
21175
|
+
new StepTimer().finish()
|
|
21176
|
+
);
|
|
21030
21177
|
}
|
|
21031
|
-
let gatewayUrl = await resolveProjectGatewayUrl(
|
|
21032
|
-
|
|
21033
|
-
|
|
21034
|
-
|
|
21178
|
+
let gatewayUrl = await resolveProjectGatewayUrl(
|
|
21179
|
+
client,
|
|
21180
|
+
projectId
|
|
21181
|
+
);
|
|
21182
|
+
const siteSourceHash = await hashDevPortalSiteSources(
|
|
21183
|
+
args.sourceDirectory,
|
|
21184
|
+
{
|
|
21185
|
+
gatewayUrl
|
|
21186
|
+
}
|
|
21187
|
+
);
|
|
21035
21188
|
const skipSiteDeploy = !forceSiteRedeploy && Boolean(ledger.siteArtifactHash) && ledger.siteArtifactHash === siteSourceHash;
|
|
21036
|
-
if (shouldRunResourceStep(
|
|
21189
|
+
if (shouldRunResourceStep(
|
|
21190
|
+
"build_dev_portal_site",
|
|
21191
|
+
resources,
|
|
21192
|
+
effectiveResumeFromStep,
|
|
21193
|
+
completedSteps
|
|
21194
|
+
)) {
|
|
21037
21195
|
if (skipSiteDeploy) {
|
|
21038
21196
|
recordStep(
|
|
21039
21197
|
"build_dev_portal_site",
|
|
21040
21198
|
"skipped",
|
|
21041
|
-
new StepTimer().finish({
|
|
21199
|
+
new StepTimer().finish({
|
|
21200
|
+
reason: "unchanged site artifact hash",
|
|
21201
|
+
siteSourceHash
|
|
21202
|
+
})
|
|
21042
21203
|
);
|
|
21043
21204
|
recordStep(
|
|
21044
21205
|
"deploy_dev_portal",
|
|
21045
21206
|
"skipped",
|
|
21046
|
-
new StepTimer().finish({
|
|
21207
|
+
new StepTimer().finish({
|
|
21208
|
+
reason: "unchanged site artifact hash",
|
|
21209
|
+
siteSourceHash
|
|
21210
|
+
})
|
|
21047
21211
|
);
|
|
21048
21212
|
} else {
|
|
21049
21213
|
await publishProgress?.onStep("Validating site config before build");
|
|
21050
21214
|
const timer = new StepTimer();
|
|
21051
|
-
const preBuildValidation = await validateDevPortalSiteConfig(
|
|
21215
|
+
const preBuildValidation = await validateDevPortalSiteConfig(
|
|
21216
|
+
args.sourceDirectory
|
|
21217
|
+
);
|
|
21052
21218
|
if (!preBuildValidation.valid) {
|
|
21053
21219
|
throw new BootstrapStepError(
|
|
21054
21220
|
"build_dev_portal_site",
|
|
@@ -21069,12 +21235,18 @@ async function runApplyFromSiteDirectory(client, config2, args, progress) {
|
|
|
21069
21235
|
},
|
|
21070
21236
|
publishProgress
|
|
21071
21237
|
);
|
|
21072
|
-
const buildResult = parseHandlerResult(
|
|
21073
|
-
|
|
21074
|
-
"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)
|
|
21075
21243
|
);
|
|
21076
|
-
|
|
21077
|
-
|
|
21244
|
+
if (shouldRunResourceStep(
|
|
21245
|
+
"deploy_dev_portal",
|
|
21246
|
+
resources,
|
|
21247
|
+
effectiveResumeFromStep,
|
|
21248
|
+
completedSteps
|
|
21249
|
+
)) {
|
|
21078
21250
|
await publishProgress?.onStep("Deploying dev portal site");
|
|
21079
21251
|
const deployTimer = new StepTimer();
|
|
21080
21252
|
const deployPortalJson = await handleDeployDevPortal(
|
|
@@ -21088,10 +21260,19 @@ async function runApplyFromSiteDirectory(client, config2, args, progress) {
|
|
|
21088
21260
|
);
|
|
21089
21261
|
parseHandlerResult(deployPortalJson, "deploy_dev_portal");
|
|
21090
21262
|
ledger.siteArtifactHash = siteSourceHash;
|
|
21091
|
-
recordStep(
|
|
21263
|
+
recordStep(
|
|
21264
|
+
"deploy_dev_portal",
|
|
21265
|
+
"succeeded",
|
|
21266
|
+
deployTimer.finish({ siteSourceHash })
|
|
21267
|
+
);
|
|
21092
21268
|
}
|
|
21093
21269
|
}
|
|
21094
|
-
} else if (shouldRunResourceStep(
|
|
21270
|
+
} else if (shouldRunResourceStep(
|
|
21271
|
+
"deploy_dev_portal",
|
|
21272
|
+
resources,
|
|
21273
|
+
effectiveResumeFromStep,
|
|
21274
|
+
completedSteps
|
|
21275
|
+
)) {
|
|
21095
21276
|
recordStep("build_dev_portal_site", "skipped", new StepTimer().finish());
|
|
21096
21277
|
recordStep("deploy_dev_portal", "skipped", new StepTimer().finish());
|
|
21097
21278
|
}
|
|
@@ -21116,12 +21297,7 @@ async function runApplyFromSiteDirectory(client, config2, args, progress) {
|
|
|
21116
21297
|
}
|
|
21117
21298
|
};
|
|
21118
21299
|
gatewayUrl = refreshedProject.gatewayUrl?.trim().replace(/\/+$/, "") || gatewayUrl || await resolveProjectGatewayUrl(client, projectId);
|
|
21119
|
-
const swaUrl = backendFqdn ? resolveDevPortalUrls(
|
|
21120
|
-
refreshedProject,
|
|
21121
|
-
backendFqdn,
|
|
21122
|
-
[],
|
|
21123
|
-
platformSiteUrl
|
|
21124
|
-
).swaUrl : null;
|
|
21300
|
+
const swaUrl = backendFqdn ? resolveDevPortalUrls(refreshedProject, backendFqdn, [], platformSiteUrl).swaUrl : null;
|
|
21125
21301
|
const manualSteps = stripeRequired ? postValidation.manualSteps : {
|
|
21126
21302
|
oidcRedirectUri: postValidation.manualSteps.oidcRedirectUri
|
|
21127
21303
|
};
|
|
@@ -21209,7 +21385,9 @@ async function handleVerifyDevPortalLive(client, config2, args) {
|
|
|
21209
21385
|
}
|
|
21210
21386
|
projectId = resolveProjectId(projectId, config2);
|
|
21211
21387
|
if (!backendFqdn) {
|
|
21212
|
-
const backendJson = await handleGetDevPortalBackend(client, config2, {
|
|
21388
|
+
const backendJson = await handleGetDevPortalBackend(client, config2, {
|
|
21389
|
+
projectId
|
|
21390
|
+
});
|
|
21213
21391
|
const backendResult = JSON.parse(backendJson);
|
|
21214
21392
|
backendFqdn = backendResult.backend?.fqdn?.trim();
|
|
21215
21393
|
}
|
|
@@ -21225,7 +21403,9 @@ async function handleVerifyDevPortalLive(client, config2, args) {
|
|
|
21225
21403
|
postDeploy: true
|
|
21226
21404
|
});
|
|
21227
21405
|
for (let attempt = 1; attempt < retries && !validation.passed; attempt++) {
|
|
21228
|
-
const plansCheck = validation.checks.find(
|
|
21406
|
+
const plansCheck = validation.checks.find(
|
|
21407
|
+
(check2) => check2.name === "plans_endpoint"
|
|
21408
|
+
);
|
|
21229
21409
|
if (plansCheck?.passed) {
|
|
21230
21410
|
break;
|
|
21231
21411
|
}
|
|
@@ -21260,7 +21440,58 @@ async function handleVerifyDevPortalLive(client, config2, args) {
|
|
|
21260
21440
|
}
|
|
21261
21441
|
|
|
21262
21442
|
// src/cli/apply/human-output.ts
|
|
21263
|
-
function
|
|
21443
|
+
function formatSucceededResultForHuman(parsed) {
|
|
21444
|
+
const lines = [
|
|
21445
|
+
typeof parsed.message === "string" ? parsed.message : "Deploy succeeded."
|
|
21446
|
+
];
|
|
21447
|
+
const siteUrl = typeof parsed.siteUrl === "string" ? parsed.siteUrl : void 0;
|
|
21448
|
+
if (siteUrl) lines.push(` Site: ${siteUrl}`);
|
|
21449
|
+
const gatewayUrl = typeof parsed.gatewayUrl === "string" ? parsed.gatewayUrl : void 0;
|
|
21450
|
+
if (gatewayUrl) lines.push(` Gateway: ${gatewayUrl}`);
|
|
21451
|
+
const backendFqdn = typeof parsed.backendFqdn === "string" ? parsed.backendFqdn : void 0;
|
|
21452
|
+
if (backendFqdn) lines.push(` Backend: ${backendFqdn}`);
|
|
21453
|
+
if (Array.isArray(parsed.plans)) {
|
|
21454
|
+
lines.push(` Plans: ${parsed.plans.length}`);
|
|
21455
|
+
}
|
|
21456
|
+
const validation = parsed.validation;
|
|
21457
|
+
if (validation && validation.passed === false) {
|
|
21458
|
+
lines.push("", "Live checks did not all pass yet:");
|
|
21459
|
+
for (const warning of validation.warnings ?? []) {
|
|
21460
|
+
lines.push(` - ${warning}`);
|
|
21461
|
+
}
|
|
21462
|
+
lines.push(
|
|
21463
|
+
"Run `apitogo status` to re-check once the platform finishes setting up."
|
|
21464
|
+
);
|
|
21465
|
+
}
|
|
21466
|
+
const checklist = parsed.postPublishChecklist;
|
|
21467
|
+
if (Array.isArray(checklist) && checklist.length > 0) {
|
|
21468
|
+
lines.push("", "Post-publish checklist:");
|
|
21469
|
+
for (const item of checklist) {
|
|
21470
|
+
if (typeof item.label !== "string") continue;
|
|
21471
|
+
const marker = item.required === false ? "-" : "*";
|
|
21472
|
+
const url2 = typeof item.url === "string" && item.url ? ` (${item.url})` : "";
|
|
21473
|
+
lines.push(` ${marker} ${item.label}${url2}`);
|
|
21474
|
+
}
|
|
21475
|
+
}
|
|
21476
|
+
if (typeof parsed.userMessage === "string" && parsed.userMessage) {
|
|
21477
|
+
lines.push("", parsed.userMessage);
|
|
21478
|
+
}
|
|
21479
|
+
if (typeof parsed.recoveryHint === "string" && parsed.recoveryHint) {
|
|
21480
|
+
lines.push("", parsed.recoveryHint);
|
|
21481
|
+
}
|
|
21482
|
+
if (Array.isArray(parsed.warnings) && parsed.warnings.length > 0) {
|
|
21483
|
+
lines.push("", "Warnings:");
|
|
21484
|
+
for (const warning of parsed.warnings) {
|
|
21485
|
+
if (typeof warning === "string") lines.push(` - ${warning}`);
|
|
21486
|
+
}
|
|
21487
|
+
}
|
|
21488
|
+
lines.push(
|
|
21489
|
+
"",
|
|
21490
|
+
"Run `apitogo deploy --json` to see the full report (checks, timings, plan details) as data."
|
|
21491
|
+
);
|
|
21492
|
+
return lines;
|
|
21493
|
+
}
|
|
21494
|
+
function formatBlockedOrFailedResultForHuman(parsed) {
|
|
21264
21495
|
const status2 = parsed.status;
|
|
21265
21496
|
if (status2 === "blocked" && parsed.reason === "platform_plan_unselected") {
|
|
21266
21497
|
const lines = [
|
|
@@ -21328,6 +21559,12 @@ function formatNonSuccessResultForHuman(parsed) {
|
|
|
21328
21559
|
}
|
|
21329
21560
|
return [];
|
|
21330
21561
|
}
|
|
21562
|
+
function formatResultForHuman(parsed) {
|
|
21563
|
+
if (parsed.status === "succeeded") {
|
|
21564
|
+
return formatSucceededResultForHuman(parsed);
|
|
21565
|
+
}
|
|
21566
|
+
return formatBlockedOrFailedResultForHuman(parsed);
|
|
21567
|
+
}
|
|
21331
21568
|
|
|
21332
21569
|
// src/cli/apply/platform-plan-prompt.ts
|
|
21333
21570
|
import { stdin as input3, stdout as output2 } from "node:process";
|
|
@@ -21593,7 +21830,7 @@ async function apply(argv) {
|
|
|
21593
21830
|
if (argv.json) {
|
|
21594
21831
|
console.log(result);
|
|
21595
21832
|
} else {
|
|
21596
|
-
const humanLines = parsed ?
|
|
21833
|
+
const humanLines = parsed ? formatResultForHuman(parsed) : [];
|
|
21597
21834
|
if (humanLines.length > 0) {
|
|
21598
21835
|
for (const line of humanLines) {
|
|
21599
21836
|
console.log(line);
|