@lukoweb/apitogo 0.2.30 → 0.2.34
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 +794 -248
- package/dist/declarations/app/entry.server.d.ts +2 -2
- package/dist/declarations/app/main.d.ts +3 -1
- package/dist/declarations/cli/platform/client/platform-api.d.ts +16 -5
- package/dist/declarations/cli/platform/helpers/deploy-artifact-hash.d.ts +1 -1
- package/dist/declarations/cli/platform/helpers/package-files.d.ts +1 -1
- package/dist/declarations/cli/platform/helpers/platform-plan.d.ts +1 -1
- package/dist/declarations/cli/platform/helpers/zip.d.ts +1 -1
- package/dist/declarations/cli/platform/tools/infrastructure.d.ts +0 -4
- package/dist/declarations/config/effective-redirects.d.ts +6 -0
- package/dist/declarations/config/header-navigation-guard.d.ts +2 -0
- package/dist/declarations/config/resolve-modules.d.ts +1 -4
- package/dist/declarations/config/validators/HeaderNavigationSchema.d.ts +1 -2
- package/docs/cli/commands.md +151 -75
- package/docs/cli/go-live.md +135 -123
- package/docs/cli/walkthrough.md +40 -1
- package/docs/configuration/gateway.md +52 -0
- package/docs/guides/customer-account.md +33 -2
- package/docs/mcp/tools.md +101 -102
- package/package.json +2 -1
- package/src/app/demo.tsx +78 -77
- package/src/app/entry.client.tsx +115 -107
- package/src/app/entry.server.tsx +164 -160
- package/src/app/main.tsx +175 -167
- package/src/app/standalone.tsx +61 -60
- package/src/config/effective-redirects.ts +31 -0
- package/src/config/header-navigation-guard.ts +13 -0
- package/src/config/resolve-modules.ts +331 -354
- package/src/config/v2/home-redirect.ts +104 -98
- package/src/config/v2/index.ts +127 -126
- package/src/config/v2/ledger.ts +242 -235
- package/src/config/v2/resolve.ts +12 -4
- package/src/config/v2/rules.ts +716 -714
- package/src/config/v2/services.ts +799 -663
- package/src/config/v2/workspace.ts +17 -1
- package/src/config/validators/HeaderNavigationSchema.ts +42 -44
- package/src/lib/assets/powered-by-icon.png +0 -0
- package/src/lib/authentication/providers/dev-portal-auth-pages.tsx +667 -602
- package/src/lib/authentication/providers/dev-portal.tsx +476 -448
- package/src/lib/components/HeaderNavigation.tsx +176 -176
- package/src/lib/components/MobileTopNavigation.tsx +283 -283
- package/src/lib/components/navigation/PoweredByZudoku.tsx +42 -39
- package/src/vite/plugin-offroot-resolve.ts +68 -67
- package/src/vite/prerender/prerender.ts +242 -242
package/dist/cli/cli.js
CHANGED
|
@@ -5267,7 +5267,7 @@ var llms_exports = {};
|
|
|
5267
5267
|
__export(llms_exports, {
|
|
5268
5268
|
generateLlmsTxtFiles: () => generateLlmsTxtFiles
|
|
5269
5269
|
});
|
|
5270
|
-
import { writeFile as
|
|
5270
|
+
import { writeFile as writeFile12 } from "node:fs/promises";
|
|
5271
5271
|
import path44 from "node:path";
|
|
5272
5272
|
import colors10 from "picocolors";
|
|
5273
5273
|
async function generateLlmsTxtFiles({
|
|
@@ -5303,7 +5303,7 @@ async function generateLlmsTxtFiles({
|
|
|
5303
5303
|
}
|
|
5304
5304
|
}
|
|
5305
5305
|
const llmsTxt2 = llmsTxtParts.join("\n");
|
|
5306
|
-
await
|
|
5306
|
+
await writeFile12(path44.join(baseOutputDir, "llms.txt"), llmsTxt2, "utf-8");
|
|
5307
5307
|
if (process.env.APITOGO_JSON_ONLY !== "1") {
|
|
5308
5308
|
console.log(colors10.blue("\u2713 generated llms.txt"));
|
|
5309
5309
|
}
|
|
@@ -5330,7 +5330,7 @@ ${info.content}
|
|
|
5330
5330
|
`);
|
|
5331
5331
|
}
|
|
5332
5332
|
const llmsFull = llmsFullParts.join("\n");
|
|
5333
|
-
await
|
|
5333
|
+
await writeFile12(
|
|
5334
5334
|
path44.join(baseOutputDir, "llms-full.txt"),
|
|
5335
5335
|
llmsFull,
|
|
5336
5336
|
"utf-8"
|
|
@@ -5811,7 +5811,6 @@ import yargs from "yargs/yargs";
|
|
|
5811
5811
|
|
|
5812
5812
|
// src/cli/services/handler.ts
|
|
5813
5813
|
import path53 from "node:path";
|
|
5814
|
-
import { createInterface as createInterface2 } from "node:readline/promises";
|
|
5815
5814
|
|
|
5816
5815
|
// src/config/v2/errors.ts
|
|
5817
5816
|
init_invariant();
|
|
@@ -6540,8 +6539,11 @@ import path8 from "node:path";
|
|
|
6540
6539
|
|
|
6541
6540
|
// src/config/v2/resolve.ts
|
|
6542
6541
|
import path7 from "node:path";
|
|
6542
|
+
function toGlobPath(value) {
|
|
6543
|
+
return value.split(path7.sep).join("/");
|
|
6544
|
+
}
|
|
6543
6545
|
function fromConfig(configDir, value) {
|
|
6544
|
-
return value.startsWith("!") ? `!${path7.resolve(configDir, value.slice(1))}` : path7.resolve(configDir, value);
|
|
6546
|
+
return value.startsWith("!") ? `!${toGlobPath(path7.resolve(configDir, value.slice(1)))}` : toGlobPath(path7.resolve(configDir, value));
|
|
6545
6547
|
}
|
|
6546
6548
|
function resolveConfigV2(config2, configPath) {
|
|
6547
6549
|
const absoluteConfigPath = path7.resolve(configPath);
|
|
@@ -6569,7 +6571,7 @@ function resolveConfigV2(config2, configPath) {
|
|
|
6569
6571
|
docs: {
|
|
6570
6572
|
enabled: portal.docs?.enabled ?? true,
|
|
6571
6573
|
provider,
|
|
6572
|
-
content: provider === "external" ? [] : portal.docs?.content ? portal.docs.content.map((glob9) => fromConfig(configDir, glob9)) : [path7.join(dir, "pages", "**", "*.{md,mdx}")],
|
|
6574
|
+
content: provider === "external" ? [] : portal.docs?.content ? portal.docs.content.map((glob9) => fromConfig(configDir, glob9)) : [toGlobPath(path7.join(dir, "pages", "**", "*.{md,mdx}"))],
|
|
6573
6575
|
url: portal.docs?.url
|
|
6574
6576
|
},
|
|
6575
6577
|
console: { enabled: portal.console?.enabled ?? true },
|
|
@@ -6815,8 +6817,9 @@ async function portalRules(config2, resolved, out) {
|
|
|
6815
6817
|
}
|
|
6816
6818
|
}
|
|
6817
6819
|
async function docsContentRule(config2, patterns, resolved, out) {
|
|
6818
|
-
const
|
|
6819
|
-
const
|
|
6820
|
+
const globs = patterns.map(toGlobPath);
|
|
6821
|
+
const include = globs.filter((pattern) => !pattern.startsWith("!"));
|
|
6822
|
+
const ignore2 = globs.filter((pattern) => pattern.startsWith("!")).map((pattern) => pattern.slice(1));
|
|
6820
6823
|
const matches = include.length > 0 ? await glob(include, { nodir: true, ignore: ignore2, absolute: true }) : [];
|
|
6821
6824
|
if (matches.length > 0) return;
|
|
6822
6825
|
const shown = config2.portal?.docs?.content ?? [
|
|
@@ -7092,7 +7095,7 @@ function identityRules(config2, resolved, out) {
|
|
|
7092
7095
|
);
|
|
7093
7096
|
}
|
|
7094
7097
|
}
|
|
7095
|
-
for (const [list2,
|
|
7098
|
+
for (const [list2, label2] of [
|
|
7096
7099
|
[identity.connections, "connections"],
|
|
7097
7100
|
[identity.clients, "clients"]
|
|
7098
7101
|
]) {
|
|
@@ -7101,8 +7104,8 @@ function identityRules(config2, resolved, out) {
|
|
|
7101
7104
|
if (seen.has(item.id)) {
|
|
7102
7105
|
out.error({
|
|
7103
7106
|
code: "IDENTITY_DUPLICATE_ID",
|
|
7104
|
-
path: `identity.${
|
|
7105
|
-
message: `the id "${item.id}" is used more than once in identity.${
|
|
7107
|
+
path: `identity.${label2}[${index}].id`,
|
|
7108
|
+
message: `the id "${item.id}" is used more than once in identity.${label2}.`
|
|
7106
7109
|
});
|
|
7107
7110
|
}
|
|
7108
7111
|
seen.add(item.id);
|
|
@@ -7835,8 +7838,9 @@ var readFrontmatter = async (filePath) => {
|
|
|
7835
7838
|
// src/config/v2/home-redirect.ts
|
|
7836
7839
|
var IGNORED = ["**/node_modules/**", "**/dist/**", "**/.git/**"];
|
|
7837
7840
|
async function docRoutes(patterns, options = {}) {
|
|
7838
|
-
const
|
|
7839
|
-
const
|
|
7841
|
+
const globs = patterns.map(toGlobPath);
|
|
7842
|
+
const excluded = globs.filter((pattern) => pattern.startsWith("!")).map((pattern) => pattern.slice(1));
|
|
7843
|
+
const included = globs.filter((pattern) => !pattern.startsWith("!"));
|
|
7840
7844
|
const routes = await Promise.all(
|
|
7841
7845
|
included.map(async (pattern) => {
|
|
7842
7846
|
const files = await glob2(pattern, {
|
|
@@ -7852,7 +7856,7 @@ async function docRoutes(patterns, options = {}) {
|
|
|
7852
7856
|
}))
|
|
7853
7857
|
)).filter(({ draft }) => !draft).map(({ file }) => file);
|
|
7854
7858
|
return published.map(
|
|
7855
|
-
(file) => `/${path11.posix.relative(parent, file).replace(/\.mdx?$/, "")}`
|
|
7859
|
+
(file) => `/${path11.posix.relative(parent, file.replace(/^\/\/\?\//, "")).replace(/\.mdx?$/, "")}`
|
|
7856
7860
|
);
|
|
7857
7861
|
})
|
|
7858
7862
|
);
|
|
@@ -8223,7 +8227,7 @@ async function preparePortal(resolved, manifest, mode, options = {}) {
|
|
|
8223
8227
|
|
|
8224
8228
|
// src/config/v2/workspace.ts
|
|
8225
8229
|
import { createHash as createHash2 } from "node:crypto";
|
|
8226
|
-
import { access as access2, lstat, readFile as readFile7, rm as rm3 } from "node:fs/promises";
|
|
8230
|
+
import { access as access2, lstat, readFile as readFile7, rm as rm3, writeFile as writeFile6 } from "node:fs/promises";
|
|
8227
8231
|
import path13 from "node:path";
|
|
8228
8232
|
import { glob as glob3 } from "glob";
|
|
8229
8233
|
|
|
@@ -8256,9 +8260,17 @@ async function relink(target, link) {
|
|
|
8256
8260
|
await rm3(link, { recursive: true, force: true });
|
|
8257
8261
|
await linkDirectory(target, link);
|
|
8258
8262
|
}
|
|
8263
|
+
var GENERATED_PACKAGE_JSON = '{\n "private": true,\n "type": "module"\n}\n';
|
|
8264
|
+
async function writeGeneratedPackageJson(root) {
|
|
8265
|
+
const file = path13.join(root, "package.json");
|
|
8266
|
+
const current = await readFile7(file, "utf8").catch(() => void 0);
|
|
8267
|
+
if (current !== GENERATED_PACKAGE_JSON)
|
|
8268
|
+
await writeFile6(file, GENERATED_PACKAGE_JSON, "utf8");
|
|
8269
|
+
}
|
|
8259
8270
|
async function materializeWorkspace(resolved, prepared) {
|
|
8260
8271
|
const root = path13.dirname(prepared.wrapperPath);
|
|
8261
8272
|
await ensureStateDir(resolved.state);
|
|
8273
|
+
await writeGeneratedPackageJson(root);
|
|
8262
8274
|
await relink(resolved.configDir, path13.join(root, CONTENT_LINK));
|
|
8263
8275
|
const publicDir = resolved.portal && path13.join(resolved.portal.dir, "public");
|
|
8264
8276
|
const publicLink = path13.join(root, "public");
|
|
@@ -8326,15 +8338,22 @@ async function portalArtifactHash(resolved, manifestText, cliVersion, wrapper =
|
|
|
8326
8338
|
|
|
8327
8339
|
// src/config/v2/services.ts
|
|
8328
8340
|
init_invariant();
|
|
8329
|
-
import { access as access3, mkdir as mkdir5, rename as rename3, rm as rm4, stat as stat3, writeFile as
|
|
8341
|
+
import { access as access3, mkdir as mkdir5, rename as rename3, rm as rm4, stat as stat3, writeFile as writeFile7 } from "node:fs/promises";
|
|
8330
8342
|
import path14 from "node:path";
|
|
8331
8343
|
var SERVICES = ["portal", "gateway", "identity", "plans"];
|
|
8332
8344
|
var ServiceEditError = class extends ApitogoError {
|
|
8333
|
-
constructor(message, developerHint) {
|
|
8345
|
+
constructor(message, developerHint, missingServices = []) {
|
|
8334
8346
|
super(message, { developerHint, title: "Cannot change the services" });
|
|
8347
|
+
this.missingServices = missingServices;
|
|
8335
8348
|
this.name = "ServiceEditError";
|
|
8336
8349
|
}
|
|
8337
8350
|
};
|
|
8351
|
+
var httpsProblem = (value) => /^https:\/\/\S+$/i.test(value) ? void 0 : "The address must start with https://";
|
|
8352
|
+
var DEFAULT_RUNTIME_VERSION = {
|
|
8353
|
+
nodejs: "NODE_22_LTS",
|
|
8354
|
+
python: "PYTHON_312",
|
|
8355
|
+
dotnet: "DOTNET_8"
|
|
8356
|
+
};
|
|
8338
8357
|
function isServiceName(value) {
|
|
8339
8358
|
return SERVICES.includes(value);
|
|
8340
8359
|
}
|
|
@@ -8430,7 +8449,10 @@ async function buildPortal(context) {
|
|
|
8430
8449
|
if (!external && o.docsUrl) {
|
|
8431
8450
|
throw new ServiceEditError("--docs-url needs --docs-provider external.");
|
|
8432
8451
|
}
|
|
8433
|
-
|
|
8452
|
+
const docsUrl = o.docsUrl ?? (external && options.ask ? await options.ask.text("HTTPS address of your docs site", {
|
|
8453
|
+
validate: httpsProblem
|
|
8454
|
+
}) : void 0);
|
|
8455
|
+
if (external && !docsUrl) {
|
|
8434
8456
|
throw new ServiceEditError(
|
|
8435
8457
|
"--docs-provider external needs --docs-url <https url>."
|
|
8436
8458
|
);
|
|
@@ -8444,6 +8466,13 @@ async function buildPortal(context) {
|
|
|
8444
8466
|
const candidates = await detectExistingDocs(dir);
|
|
8445
8467
|
if (candidates.length === 1 || candidates.length > 1 && options.acceptAllDocs) {
|
|
8446
8468
|
content = candidates.map((candidate) => candidate.glob);
|
|
8469
|
+
} else if (candidates.length > 1 && options.ask) {
|
|
8470
|
+
const places = candidates.map((c) => c.path).join(", ");
|
|
8471
|
+
if (await options.ask.confirm(
|
|
8472
|
+
`Several places with documentation were found (${places}). Include all of them?`,
|
|
8473
|
+
true
|
|
8474
|
+
))
|
|
8475
|
+
content = candidates.map((candidate) => candidate.glob);
|
|
8447
8476
|
} else if (candidates.length > 1) {
|
|
8448
8477
|
throw new ServiceEditError(
|
|
8449
8478
|
`Several places with documentation were found (${candidates.map((c) => c.path).join(", ")}).`,
|
|
@@ -8462,7 +8491,7 @@ async function buildPortal(context) {
|
|
|
8462
8491
|
return {
|
|
8463
8492
|
...options.disabled ? { enabled: false } : {},
|
|
8464
8493
|
name,
|
|
8465
|
-
...external ? { docs: { provider: "external", url:
|
|
8494
|
+
...external ? { docs: { provider: "external", url: docsUrl } } : content.length > 0 ? { docs: { content } } : {},
|
|
8466
8495
|
...o.noConsole ? { console: { enabled: false } } : {},
|
|
8467
8496
|
...o.requireAuth ? { requireAuth: true } : {},
|
|
8468
8497
|
...o.search && !external ? { search: o.search } : {}
|
|
@@ -8471,7 +8500,20 @@ async function buildPortal(context) {
|
|
|
8471
8500
|
async function buildGateway(context) {
|
|
8472
8501
|
const { options, dir, resolved } = context;
|
|
8473
8502
|
const o = options.gateway ?? {};
|
|
8474
|
-
const
|
|
8503
|
+
const ask4 = options.ask;
|
|
8504
|
+
let mode = o.origin ?? (o.url ? "proxy" : o.source || o.runtime ? "managed" : void 0);
|
|
8505
|
+
if (!mode && ask4) {
|
|
8506
|
+
mode = await ask4.choose("How does the gateway reach your API?", [
|
|
8507
|
+
{
|
|
8508
|
+
value: "proxy",
|
|
8509
|
+
label: "My API runs somewhere else: the gateway forwards calls to its https address"
|
|
8510
|
+
},
|
|
8511
|
+
{
|
|
8512
|
+
value: "managed",
|
|
8513
|
+
label: "The platform runs my code (nodejs, python or dotnet)"
|
|
8514
|
+
}
|
|
8515
|
+
]);
|
|
8516
|
+
}
|
|
8475
8517
|
if (!mode) {
|
|
8476
8518
|
throw new ServiceEditError(
|
|
8477
8519
|
"The gateway needs an origin: say how it reaches your API.",
|
|
@@ -8480,11 +8522,25 @@ async function buildGateway(context) {
|
|
|
8480
8522
|
}
|
|
8481
8523
|
let origin;
|
|
8482
8524
|
if (mode === "proxy") {
|
|
8483
|
-
|
|
8525
|
+
const url2 = o.url ?? (ask4 ? await ask4.text("https address of your API", {
|
|
8526
|
+
validate: httpsProblem
|
|
8527
|
+
}) : void 0);
|
|
8528
|
+
if (!url2)
|
|
8484
8529
|
throw new ServiceEditError("--origin proxy needs --url <https url>.");
|
|
8485
|
-
origin = { mode, url:
|
|
8530
|
+
origin = { mode, url: url2 };
|
|
8486
8531
|
} else {
|
|
8487
|
-
|
|
8532
|
+
const source = o.source ?? (ask4 ? await ask4.text("Folder with your code, relative to the project", {
|
|
8533
|
+
default: "server"
|
|
8534
|
+
}) : void 0);
|
|
8535
|
+
const runtime = o.runtime ?? (ask4 ? await ask4.choose("Runtime of your code", [
|
|
8536
|
+
{ value: "nodejs", label: "Node.js" },
|
|
8537
|
+
{ value: "python", label: "Python" },
|
|
8538
|
+
{ value: "dotnet", label: ".NET" }
|
|
8539
|
+
]) : void 0);
|
|
8540
|
+
const runtimeVersion = o.runtimeVersion ?? (ask4 && runtime ? await ask4.text("Runtime version", {
|
|
8541
|
+
default: DEFAULT_RUNTIME_VERSION[runtime]
|
|
8542
|
+
}) : void 0);
|
|
8543
|
+
if (!source || !runtime || !runtimeVersion) {
|
|
8488
8544
|
throw new ServiceEditError(
|
|
8489
8545
|
"--origin managed needs --source, --runtime and --runtime-version.",
|
|
8490
8546
|
"For example: --source server --runtime nodejs --runtime-version NODE_22_LTS."
|
|
@@ -8492,16 +8548,29 @@ async function buildGateway(context) {
|
|
|
8492
8548
|
}
|
|
8493
8549
|
origin = {
|
|
8494
8550
|
mode,
|
|
8495
|
-
source: insideProject(dir,
|
|
8496
|
-
runtime
|
|
8497
|
-
runtimeVersion
|
|
8551
|
+
source: insideProject(dir, source, "Source folder"),
|
|
8552
|
+
runtime,
|
|
8553
|
+
runtimeVersion,
|
|
8498
8554
|
...o.exclude?.length ? { exclude: o.exclude } : {}
|
|
8499
8555
|
};
|
|
8500
8556
|
}
|
|
8501
8557
|
let specs;
|
|
8502
|
-
|
|
8558
|
+
let requestedSpecs = o.spec ?? [];
|
|
8559
|
+
let createSpec = o.createSpec === true;
|
|
8560
|
+
if (requestedSpecs.length === 0 && !createSpec && ask4) {
|
|
8561
|
+
const answer = await ask4.choose("OpenAPI file of your API", [
|
|
8562
|
+
{ value: "create", label: "Start from a small file the command creates" },
|
|
8563
|
+
{ value: "existing", label: "Use a file I already have" }
|
|
8564
|
+
]);
|
|
8565
|
+
if (answer === "create") createSpec = true;
|
|
8566
|
+
else
|
|
8567
|
+
requestedSpecs = [
|
|
8568
|
+
await ask4.text("Path of your OpenAPI file, relative to the project")
|
|
8569
|
+
];
|
|
8570
|
+
}
|
|
8571
|
+
if (requestedSpecs.length > 0) {
|
|
8503
8572
|
specs = [];
|
|
8504
|
-
for (const requested of
|
|
8573
|
+
for (const requested of requestedSpecs) {
|
|
8505
8574
|
const relative = insideProject(dir, requested, "OpenAPI file");
|
|
8506
8575
|
if (!await stat3(path14.join(dir, relative)).then(
|
|
8507
8576
|
(s) => s.isFile(),
|
|
@@ -8513,7 +8582,7 @@ async function buildGateway(context) {
|
|
|
8513
8582
|
}
|
|
8514
8583
|
specs.push(relative);
|
|
8515
8584
|
}
|
|
8516
|
-
} else if (
|
|
8585
|
+
} else if (createSpec) {
|
|
8517
8586
|
const target = path14.join(resolved.apisDir, "openapi.yaml");
|
|
8518
8587
|
await addFile(context, target, STARTER_SPEC(resolved.project.name));
|
|
8519
8588
|
specs = [rel(dir, target)];
|
|
@@ -8545,12 +8614,15 @@ async function buildGateway(context) {
|
|
|
8545
8614
|
...Object.keys(policies).length > 0 ? { policies } : {}
|
|
8546
8615
|
};
|
|
8547
8616
|
}
|
|
8548
|
-
function buildIdentity(context) {
|
|
8617
|
+
async function buildIdentity(context) {
|
|
8549
8618
|
const { options } = context;
|
|
8550
8619
|
const o = options.identity ?? {};
|
|
8551
8620
|
const provider = o.provider ?? "apitogo";
|
|
8552
8621
|
if (provider === "oidc") {
|
|
8553
|
-
|
|
8622
|
+
const issuer = o.issuer ?? (options.ask ? await options.ask.text("https address of your OIDC issuer", {
|
|
8623
|
+
validate: httpsProblem
|
|
8624
|
+
}) : void 0);
|
|
8625
|
+
if (!issuer)
|
|
8554
8626
|
throw new ServiceEditError("--provider oidc needs --issuer <https url>.");
|
|
8555
8627
|
if (o.connection || o.native === false) {
|
|
8556
8628
|
throw new ServiceEditError(
|
|
@@ -8560,7 +8632,7 @@ function buildIdentity(context) {
|
|
|
8560
8632
|
return {
|
|
8561
8633
|
...options.disabled ? { enabled: false } : {},
|
|
8562
8634
|
provider,
|
|
8563
|
-
issuer
|
|
8635
|
+
issuer,
|
|
8564
8636
|
...o.clientId ? { clientId: o.clientId } : {}
|
|
8565
8637
|
};
|
|
8566
8638
|
}
|
|
@@ -8634,6 +8706,15 @@ async function buildPlans(context) {
|
|
|
8634
8706
|
function reparse(configPath, raw) {
|
|
8635
8707
|
return parseConfigV2(JSON.parse(JSON.stringify(raw)), configPath);
|
|
8636
8708
|
}
|
|
8709
|
+
function missingServicesOf(issues) {
|
|
8710
|
+
const missing = /* @__PURE__ */ new Set();
|
|
8711
|
+
for (const { code } of issues) {
|
|
8712
|
+
if (code === "PLANS_GATEWAY_REQUIRED") missing.add("gateway");
|
|
8713
|
+
if (code === "PLANS_IDENTITY_REQUIRED" || code === "PORTAL_CAPABILITY_MISSING")
|
|
8714
|
+
missing.add("identity");
|
|
8715
|
+
}
|
|
8716
|
+
return [...missing];
|
|
8717
|
+
}
|
|
8637
8718
|
function dependencyProblems(config2, configPath) {
|
|
8638
8719
|
return checkDependencies(config2, resolveConfigV2(config2, configPath));
|
|
8639
8720
|
}
|
|
@@ -8655,7 +8736,7 @@ async function planAddService(service, options) {
|
|
|
8655
8736
|
keep: [],
|
|
8656
8737
|
notes: []
|
|
8657
8738
|
};
|
|
8658
|
-
const section = service === "portal" ? await buildPortal(context) : service === "gateway" ? await buildGateway(context) : service === "identity" ? buildIdentity(context) : await buildPlans(context);
|
|
8739
|
+
const section = service === "portal" ? await buildPortal(context) : service === "gateway" ? await buildGateway(context) : service === "identity" ? await buildIdentity(context) : await buildPlans(context);
|
|
8659
8740
|
const next = reparse(resolved.configPath, { ...config2, [service]: section });
|
|
8660
8741
|
if (!options.disabled) {
|
|
8661
8742
|
const before = new Set(
|
|
@@ -8669,7 +8750,8 @@ async function planAddService(service, options) {
|
|
|
8669
8750
|
if (added.length > 0) {
|
|
8670
8751
|
throw new ServiceEditError(
|
|
8671
8752
|
added.map((issue4) => issue4.message).join(" "),
|
|
8672
|
-
added.map((issue4) => issue4.hint).filter(Boolean).join(" ") || "Add the missing service first."
|
|
8753
|
+
added.map((issue4) => issue4.hint).filter(Boolean).join(" ") || "Add the missing service first.",
|
|
8754
|
+
missingServicesOf(added)
|
|
8673
8755
|
);
|
|
8674
8756
|
}
|
|
8675
8757
|
}
|
|
@@ -8727,7 +8809,7 @@ async function planRemoveService(service, options) {
|
|
|
8727
8809
|
async function writeConfig(configPath, config2) {
|
|
8728
8810
|
const temporary = `${configPath}.${process.pid}.tmp`;
|
|
8729
8811
|
try {
|
|
8730
|
-
await
|
|
8812
|
+
await writeFile7(temporary, `${JSON.stringify(config2, null, 2)}
|
|
8731
8813
|
`, "utf8");
|
|
8732
8814
|
await rename3(temporary, configPath);
|
|
8733
8815
|
} catch (error) {
|
|
@@ -8740,7 +8822,7 @@ async function applyServiceEdit(edit) {
|
|
|
8740
8822
|
for (const file of edit.files) {
|
|
8741
8823
|
await mkdir5(path14.dirname(file.path), { recursive: true });
|
|
8742
8824
|
try {
|
|
8743
|
-
await
|
|
8825
|
+
await writeFile7(file.path, file.content, { flag: "wx" });
|
|
8744
8826
|
created.push(file.path);
|
|
8745
8827
|
} catch (error) {
|
|
8746
8828
|
if (error.code !== "EEXIST") throw error;
|
|
@@ -8788,7 +8870,7 @@ async function clearProjectId(dir, projectId) {
|
|
|
8788
8870
|
|
|
8789
8871
|
// src/config/v2/ledger.ts
|
|
8790
8872
|
init_invariant();
|
|
8791
|
-
import { mkdir as mkdir6, readFile as readFile8, rename as rename4, rm as rm5, writeFile as
|
|
8873
|
+
import { mkdir as mkdir6, readFile as readFile8, rename as rename4, rm as rm5, writeFile as writeFile8 } from "node:fs/promises";
|
|
8792
8874
|
import path15 from "node:path";
|
|
8793
8875
|
var LEDGER_VERSION = 1;
|
|
8794
8876
|
var LedgerError = class extends ApitogoError {
|
|
@@ -8847,7 +8929,7 @@ async function writeLedger(state, ledger) {
|
|
|
8847
8929
|
await mkdir6(path15.dirname(state.file), { recursive: true });
|
|
8848
8930
|
const temporary = `${state.file}.${process.pid}.tmp`;
|
|
8849
8931
|
try {
|
|
8850
|
-
await
|
|
8932
|
+
await writeFile8(temporary, `${JSON.stringify(ledger, null, 2)}
|
|
8851
8933
|
`, "utf8");
|
|
8852
8934
|
await rename4(temporary, state.file);
|
|
8853
8935
|
} catch (error) {
|
|
@@ -8928,6 +9010,10 @@ async function clearServices(state, services) {
|
|
|
8928
9010
|
);
|
|
8929
9011
|
if (Object.keys(remaining).length === Object.keys(ledger.services).length)
|
|
8930
9012
|
return;
|
|
9013
|
+
if (Object.keys(remaining).length === 0) {
|
|
9014
|
+
await rm5(state.file, { force: true });
|
|
9015
|
+
return;
|
|
9016
|
+
}
|
|
8931
9017
|
await writeLedger(state, { ...ledger, services: remaining });
|
|
8932
9018
|
}
|
|
8933
9019
|
|
|
@@ -9133,7 +9219,9 @@ var PlatformApiClient = class {
|
|
|
9133
9219
|
headers: { "Content-Type": "application/json" },
|
|
9134
9220
|
body: JSON.stringify({
|
|
9135
9221
|
planSlug: body.planSlug,
|
|
9136
|
-
currency: body.currency ?? null
|
|
9222
|
+
currency: body.currency ?? null,
|
|
9223
|
+
// Only a platform admin may skip the payment; the platform refuses it for anyone else.
|
|
9224
|
+
...body.skipPayment ? { skipPayment: true } : {}
|
|
9137
9225
|
})
|
|
9138
9226
|
}
|
|
9139
9227
|
);
|
|
@@ -9162,13 +9250,6 @@ var PlatformApiClient = class {
|
|
|
9162
9250
|
`/api/v1/projects/${projectId}/infrastructure/provisioning-jobs`
|
|
9163
9251
|
);
|
|
9164
9252
|
}
|
|
9165
|
-
getDeploymentToken(projectId, regenerate = false) {
|
|
9166
|
-
const query = regenerate ? "?regenerate=true" : "";
|
|
9167
|
-
return this.request(
|
|
9168
|
-
"POST",
|
|
9169
|
-
`/api/v1/projects/${projectId}/infrastructure/deployment-token${query}`
|
|
9170
|
-
);
|
|
9171
|
-
}
|
|
9172
9253
|
/**
|
|
9173
9254
|
* Publishes a built portal (.tgz, root folder `dist`). The platform holds the deploy server key and the project's
|
|
9174
9255
|
* deployment token, so the CLI only needs its own login. `env` falls back to the platform default.
|
|
@@ -9431,6 +9512,20 @@ var PlatformApiClient = class {
|
|
|
9431
9512
|
);
|
|
9432
9513
|
return result ?? null;
|
|
9433
9514
|
}
|
|
9515
|
+
/**
|
|
9516
|
+
* Gives the plans backend the tenant's own Stripe settings. `apiKey` and `webhookSecret` are `secret:NAME`
|
|
9517
|
+
* references to project secrets, never values. Queues a job that updates the running backend.
|
|
9518
|
+
*/
|
|
9519
|
+
putDevPortalBilling(projectId, body) {
|
|
9520
|
+
return this.request(
|
|
9521
|
+
"PUT",
|
|
9522
|
+
`/api/v1/projects/${projectId}/dev-portal/billing`,
|
|
9523
|
+
{
|
|
9524
|
+
headers: { "Content-Type": "application/json" },
|
|
9525
|
+
body: JSON.stringify(body)
|
|
9526
|
+
}
|
|
9527
|
+
);
|
|
9528
|
+
}
|
|
9434
9529
|
/** Applies the project's identity to its plans backend as the sign-in provider. Queues a job. */
|
|
9435
9530
|
linkDevPortalIdentity(projectId) {
|
|
9436
9531
|
return this.request(
|
|
@@ -9827,12 +9922,12 @@ import {
|
|
|
9827
9922
|
readFile as readFile12,
|
|
9828
9923
|
rename as rename6,
|
|
9829
9924
|
rm as rm8,
|
|
9830
|
-
writeFile as
|
|
9925
|
+
writeFile as writeFile15
|
|
9831
9926
|
} from "node:fs/promises";
|
|
9832
9927
|
import path48 from "node:path";
|
|
9833
9928
|
|
|
9834
9929
|
// src/vite/build.ts
|
|
9835
|
-
import { mkdir as mkdir11, readFile as readFile10, rename as rename5, rm as rm7, writeFile as
|
|
9930
|
+
import { mkdir as mkdir11, readFile as readFile10, rename as rename5, rm as rm7, writeFile as writeFile13 } from "node:fs/promises";
|
|
9836
9931
|
import path46 from "node:path";
|
|
9837
9932
|
import { build as esbuild } from "esbuild";
|
|
9838
9933
|
import { build as viteBuild, mergeConfig as mergeConfig3 } from "vite";
|
|
@@ -9980,7 +10075,7 @@ import {
|
|
|
9980
10075
|
// package.json
|
|
9981
10076
|
var package_default = {
|
|
9982
10077
|
name: "@lukoweb/apitogo",
|
|
9983
|
-
version: "0.2.
|
|
10078
|
+
version: "0.2.34",
|
|
9984
10079
|
type: "module",
|
|
9985
10080
|
sideEffects: [
|
|
9986
10081
|
"**/*.css",
|
|
@@ -10058,6 +10153,7 @@ var package_default = {
|
|
|
10058
10153
|
build: "esbuild src/cli/cli.ts src/vite/prerender/worker.ts --outdir=dist/cli --entry-names=[name] --bundle --format=esm --packages=external --target=node20 --platform=node --log-level=error",
|
|
10059
10154
|
postbuild: "tsx scripts/check-external-deps.ts",
|
|
10060
10155
|
typecheck: "tsc --project tsconfig.app.json --noEmit",
|
|
10156
|
+
"test:e2e": "vitest run --config vitest.e2e.config.ts",
|
|
10061
10157
|
"generate:types": "tsx scripts/generate-types.js && tsx scripts/generate-flat-config.js",
|
|
10062
10158
|
"build:standalone": "vite build --mode standalone --config vite.standalone.config.ts --log-level=error",
|
|
10063
10159
|
prepack: "npm run build && tsx scripts/generate-publish-exports.ts",
|
|
@@ -12090,8 +12186,8 @@ var extractRichH1 = (content) => {
|
|
|
12090
12186
|
if (!h1) return void 0;
|
|
12091
12187
|
const children = h1.children;
|
|
12092
12188
|
const hasJsx = children.some(isMdxJsxElement);
|
|
12093
|
-
const
|
|
12094
|
-
return hasJsx ? { label, rich: children } : { label };
|
|
12189
|
+
const label2 = mdastToString(h1).trim();
|
|
12190
|
+
return hasJsx ? { label: label2, rich: children } : { label: label2 };
|
|
12095
12191
|
} catch {
|
|
12096
12192
|
return void 0;
|
|
12097
12193
|
}
|
|
@@ -12152,12 +12248,12 @@ var NavigationResolver = class {
|
|
|
12152
12248
|
return void 0;
|
|
12153
12249
|
}
|
|
12154
12250
|
const richH1 = extractRichH1(content);
|
|
12155
|
-
const
|
|
12251
|
+
const label2 = data.navigation_label ?? data.sidebar_label ?? data.title ?? richH1?.label ?? extractTitleFromContent(content) ?? filePath;
|
|
12156
12252
|
const icon = data.navigation_icon ?? data.sidebar_icon;
|
|
12157
12253
|
const doc = {
|
|
12158
12254
|
type: "doc",
|
|
12159
12255
|
file: filePath,
|
|
12160
|
-
label,
|
|
12256
|
+
label: label2,
|
|
12161
12257
|
icon,
|
|
12162
12258
|
display: data.navigation_display,
|
|
12163
12259
|
categoryLabel,
|
|
@@ -12229,13 +12325,13 @@ var NavigationResolver = class {
|
|
|
12229
12325
|
init_invariant();
|
|
12230
12326
|
|
|
12231
12327
|
// src/vite/debug.ts
|
|
12232
|
-
import { mkdir as mkdir7, writeFile as
|
|
12328
|
+
import { mkdir as mkdir7, writeFile as writeFile9 } from "node:fs/promises";
|
|
12233
12329
|
import path27 from "node:path";
|
|
12234
12330
|
async function writePluginDebugCode(rootDir, pluginName, code, extension = "js") {
|
|
12235
12331
|
if (process.env.ZUDOKU_BUILD_DEBUG) {
|
|
12236
12332
|
const debugDir = path27.join(rootDir, "dist", "debug");
|
|
12237
12333
|
await mkdir7(debugDir, { recursive: true });
|
|
12238
|
-
await
|
|
12334
|
+
await writeFile9(
|
|
12239
12335
|
path27.join(debugDir, `${pluginName}.${extension}`),
|
|
12240
12336
|
typeof code === "string" ? code : code.join("\n")
|
|
12241
12337
|
);
|
|
@@ -12543,8 +12639,8 @@ var viteApiPlugin = async () => {
|
|
|
12543
12639
|
return acc;
|
|
12544
12640
|
}, /* @__PURE__ */ new Map());
|
|
12545
12641
|
const categoryList = Array.from(categories.entries()).map(
|
|
12546
|
-
([
|
|
12547
|
-
label,
|
|
12642
|
+
([label2, tags]) => ({
|
|
12643
|
+
label: label2,
|
|
12548
12644
|
tags: Array.from(tags)
|
|
12549
12645
|
})
|
|
12550
12646
|
);
|
|
@@ -13092,7 +13188,7 @@ var plugin_local_manifest_default = viteLocalManifestPlugin;
|
|
|
13092
13188
|
init_loader();
|
|
13093
13189
|
init_ProtectedRoutesSchema();
|
|
13094
13190
|
init_joinUrl();
|
|
13095
|
-
import { mkdir as mkdir8, writeFile as
|
|
13191
|
+
import { mkdir as mkdir8, writeFile as writeFile10 } from "node:fs/promises";
|
|
13096
13192
|
import path34 from "node:path";
|
|
13097
13193
|
import { matchPath } from "react-router";
|
|
13098
13194
|
var processMarkdownFile = async (filePath) => {
|
|
@@ -13204,7 +13300,7 @@ var viteMarkdownExportPlugin = () => {
|
|
|
13204
13300
|
const segments = routePath === "/" ? ["index"] : routePath.split("/").filter(Boolean);
|
|
13205
13301
|
const outputPath = `${path34.join(distDir, ...segments)}.md`;
|
|
13206
13302
|
await mkdir8(path34.dirname(outputPath), { recursive: true });
|
|
13207
|
-
await
|
|
13303
|
+
await writeFile10(outputPath, finalMarkdown, "utf-8");
|
|
13208
13304
|
} catch (error) {
|
|
13209
13305
|
console.warn(`Failed to export markdown for ${routePath}:`, error);
|
|
13210
13306
|
}
|
|
@@ -13214,7 +13310,7 @@ var viteMarkdownExportPlugin = () => {
|
|
|
13214
13310
|
config2.__meta.rootDir,
|
|
13215
13311
|
"node_modules/.apitogo/markdown-info.json"
|
|
13216
13312
|
);
|
|
13217
|
-
await
|
|
13313
|
+
await writeFile10(
|
|
13218
13314
|
markdownInfoPath,
|
|
13219
13315
|
JSON.stringify(markdownFileInfos, null, 2),
|
|
13220
13316
|
"utf-8"
|
|
@@ -13842,7 +13938,7 @@ function isBareSpecifier(source) {
|
|
|
13842
13938
|
return !source.startsWith("\0") && !source.startsWith(".") && !source.startsWith("/") && !path38.isAbsolute(source);
|
|
13843
13939
|
}
|
|
13844
13940
|
function isInsideNodeModules(filePath) {
|
|
13845
|
-
return filePath.split(
|
|
13941
|
+
return filePath.split(/[\\/]/).includes("node_modules");
|
|
13846
13942
|
}
|
|
13847
13943
|
function shouldResolveFromSiteRoot(source, importer, rootDir) {
|
|
13848
13944
|
if (!importer || !isBareSpecifier(source)) {
|
|
@@ -14299,7 +14395,7 @@ ${cssLinks}
|
|
|
14299
14395
|
init_package_json();
|
|
14300
14396
|
init_joinUrl();
|
|
14301
14397
|
import assert from "node:assert";
|
|
14302
|
-
import { cp, mkdir as mkdir9, writeFile as
|
|
14398
|
+
import { cp, mkdir as mkdir9, writeFile as writeFile11 } from "node:fs/promises";
|
|
14303
14399
|
import path42 from "node:path";
|
|
14304
14400
|
var pkgJson = getZudokuPackageJson();
|
|
14305
14401
|
function generateOutput({
|
|
@@ -14360,7 +14456,7 @@ async function writeOutput(dir, {
|
|
|
14360
14456
|
const outputDir = process.env.VERCEL ? path42.join(dir, ".vercel/output") : path42.join(dir, "dist/.output");
|
|
14361
14457
|
await mkdir9(outputDir, { recursive: true });
|
|
14362
14458
|
const outputFile = path42.join(outputDir, "config.json");
|
|
14363
|
-
await
|
|
14459
|
+
await writeFile11(outputFile, JSON.stringify(output4, null, 2), "utf-8");
|
|
14364
14460
|
if (process.env.VERCEL) {
|
|
14365
14461
|
console.log("Wrote Vercel output to", outputDir);
|
|
14366
14462
|
}
|
|
@@ -14756,7 +14852,7 @@ var runPrerender = async (options) => {
|
|
|
14756
14852
|
});
|
|
14757
14853
|
const indexHtml = path46.join(clientOutDir, "index.html");
|
|
14758
14854
|
if (!workerResults.find((r) => r.outputPath === indexHtml)) {
|
|
14759
|
-
await
|
|
14855
|
+
await writeFile13(indexHtml, html, "utf-8");
|
|
14760
14856
|
}
|
|
14761
14857
|
const statusPages = workerResults.flatMap(
|
|
14762
14858
|
(r) => /400|404|500\.html$/.test(r.outputPath) ? r.outputPath : []
|
|
@@ -14781,7 +14877,7 @@ var runPrerender = async (options) => {
|
|
|
14781
14877
|
rewrites
|
|
14782
14878
|
});
|
|
14783
14879
|
if (ZuploEnv.isZuplo && issuer) {
|
|
14784
|
-
await
|
|
14880
|
+
await writeFile13(
|
|
14785
14881
|
path46.join(dir, DIST_DIR, ".output/zuplo.json"),
|
|
14786
14882
|
JSON.stringify({ issuer }, null, 2),
|
|
14787
14883
|
"utf-8"
|
|
@@ -14804,7 +14900,7 @@ var bundleSSREntry = async (options) => {
|
|
|
14804
14900
|
'"__BASE_PATH__"',
|
|
14805
14901
|
basePath ? JSON.stringify(basePath) : "undefined"
|
|
14806
14902
|
);
|
|
14807
|
-
await
|
|
14903
|
+
await writeFile13(tempEntryPath, entryContent, "utf-8");
|
|
14808
14904
|
try {
|
|
14809
14905
|
await esbuild({
|
|
14810
14906
|
entryPoints: [tempEntryPath],
|
|
@@ -14827,7 +14923,7 @@ init_package_json();
|
|
|
14827
14923
|
|
|
14828
14924
|
// src/cli/init/v2-packages.ts
|
|
14829
14925
|
import { existsSync as existsSync5 } from "node:fs";
|
|
14830
|
-
import { readFile as readFile11, writeFile as
|
|
14926
|
+
import { readFile as readFile11, writeFile as writeFile14 } from "node:fs/promises";
|
|
14831
14927
|
import path47 from "node:path";
|
|
14832
14928
|
var CORE_PACKAGES = [
|
|
14833
14929
|
"@lukoweb/apitogo",
|
|
@@ -14847,7 +14943,7 @@ function packageNameFrom(name) {
|
|
|
14847
14943
|
async function ensurePackageJson(dir, name) {
|
|
14848
14944
|
const file = path47.join(dir, "package.json");
|
|
14849
14945
|
if (existsSync5(file)) return false;
|
|
14850
|
-
await
|
|
14946
|
+
await writeFile14(
|
|
14851
14947
|
file,
|
|
14852
14948
|
`${JSON.stringify(
|
|
14853
14949
|
{
|
|
@@ -14940,7 +15036,7 @@ function portalBuildStep(context, options = {}, build2 = runBuild) {
|
|
|
14940
15036
|
await mkdir12(path48.dirname(buildDir), { recursive: true });
|
|
14941
15037
|
await rename6(path48.join(root, "dist"), buildDir);
|
|
14942
15038
|
if (prepared.publishedManifest) {
|
|
14943
|
-
await
|
|
15039
|
+
await writeFile15(
|
|
14944
15040
|
path48.join(buildDir, MANIFEST_FILE),
|
|
14945
15041
|
`${JSON.stringify(prepared.publishedManifest, null, 2)}
|
|
14946
15042
|
`
|
|
@@ -15137,14 +15233,14 @@ async function readPlansFile(file) {
|
|
|
15137
15233
|
return parsed.data.plans;
|
|
15138
15234
|
}
|
|
15139
15235
|
var BILLING_PERIOD = { month: "monthly", year: "annual" };
|
|
15140
|
-
function toPlatformPlans(plans) {
|
|
15236
|
+
function toPlatformPlans(plans, options = {}) {
|
|
15141
15237
|
return plans.map((plan2, index) => {
|
|
15142
15238
|
const paid = plan2.pricingModel !== "custom" && (plan2.priceAmount ?? 0) > 0;
|
|
15143
|
-
if (paid)
|
|
15239
|
+
if (paid && !options.billing)
|
|
15144
15240
|
throw new PlansFileError(
|
|
15145
15241
|
"PLANS_PAID_UNSUPPORTED",
|
|
15146
|
-
`The plan "${plan2.sku}" has a price, and
|
|
15147
|
-
"
|
|
15242
|
+
`The plan "${plan2.sku}" has a price, and plans.billing.provider is not stripe, so nobody could pay for it.`,
|
|
15243
|
+
"Set plans.billing.provider to stripe (with plans.billing.apiKey), or set priceAmount to 0 (or pricingModel to custom for a contact-us plan)."
|
|
15148
15244
|
);
|
|
15149
15245
|
const limits = plan2.limits ? {
|
|
15150
15246
|
...plan2.limits.rateLimit ? {
|
|
@@ -15233,7 +15329,11 @@ async function plansFileProblem(resolved) {
|
|
|
15233
15329
|
const plans = resolved.plans;
|
|
15234
15330
|
if (!plans?.enabled) return { wanted: [] };
|
|
15235
15331
|
try {
|
|
15236
|
-
return {
|
|
15332
|
+
return {
|
|
15333
|
+
wanted: toPlatformPlans(await readPlansFile(plans.file), {
|
|
15334
|
+
billing: plans.billing.provider === "stripe"
|
|
15335
|
+
})
|
|
15336
|
+
};
|
|
15237
15337
|
} catch (error) {
|
|
15238
15338
|
if (!(error instanceof PlansFileError)) throw error;
|
|
15239
15339
|
return {
|
|
@@ -15483,7 +15583,7 @@ async function runCheckV2(dir, args = {}, deps = {}) {
|
|
|
15483
15583
|
...report
|
|
15484
15584
|
};
|
|
15485
15585
|
}
|
|
15486
|
-
var line = (
|
|
15586
|
+
var line = (label2, issue4) => `${label2}: ${formatConfigIssue(issue4)}`;
|
|
15487
15587
|
function formatCheckV2(output4) {
|
|
15488
15588
|
const lines = [
|
|
15489
15589
|
...output4.errors.map((issue4) => line("error", issue4)),
|
|
@@ -15950,6 +16050,56 @@ async function installMissingPackages(dir, config2, version, options) {
|
|
|
15950
16050
|
return { status: "installed", packages: missing, command: shown };
|
|
15951
16051
|
}
|
|
15952
16052
|
|
|
16053
|
+
// src/cli/services/prompter.ts
|
|
16054
|
+
import { createInterface as createInterface2 } from "node:readline/promises";
|
|
16055
|
+
var terminalQuestion = async (prompt) => {
|
|
16056
|
+
const rl = createInterface2({ input: process.stdin, output: process.stdout });
|
|
16057
|
+
try {
|
|
16058
|
+
return await rl.question(prompt);
|
|
16059
|
+
} finally {
|
|
16060
|
+
rl.close();
|
|
16061
|
+
}
|
|
16062
|
+
};
|
|
16063
|
+
function createAsker(question = terminalQuestion, say = (line2) => console.log(line2)) {
|
|
16064
|
+
return {
|
|
16065
|
+
async text(prompt, options) {
|
|
16066
|
+
for (; ; ) {
|
|
16067
|
+
const suffix = options?.default ? ` [${options.default}]` : "";
|
|
16068
|
+
const answer = (await question(`${prompt}${suffix}: `)).trim();
|
|
16069
|
+
const value = answer || options?.default || "";
|
|
16070
|
+
if (!value) {
|
|
16071
|
+
say("A value is needed.");
|
|
16072
|
+
continue;
|
|
16073
|
+
}
|
|
16074
|
+
const problem = options?.validate?.(value);
|
|
16075
|
+
if (problem) {
|
|
16076
|
+
say(problem);
|
|
16077
|
+
continue;
|
|
16078
|
+
}
|
|
16079
|
+
return value;
|
|
16080
|
+
}
|
|
16081
|
+
},
|
|
16082
|
+
async choose(prompt, choices) {
|
|
16083
|
+
say(prompt);
|
|
16084
|
+
choices.forEach((choice, index) => {
|
|
16085
|
+
say(` ${index + 1}) ${choice.label}`);
|
|
16086
|
+
});
|
|
16087
|
+
for (; ; ) {
|
|
16088
|
+
const answer = (await question(`Choose 1-${choices.length} [1]: `)).trim();
|
|
16089
|
+
const index = Number(answer || "1");
|
|
16090
|
+
const chosen = Number.isInteger(index) && index >= 1 && index <= choices.length ? choices[index - 1] : choices.find((choice) => choice.value === answer);
|
|
16091
|
+
if (chosen) return chosen.value;
|
|
16092
|
+
say(`Pick a number from 1 to ${choices.length}.`);
|
|
16093
|
+
}
|
|
16094
|
+
},
|
|
16095
|
+
async confirm(prompt, defaultYes = false) {
|
|
16096
|
+
const answer = (await question(`${prompt} (${defaultYes ? "Y/n" : "y/N"}) `)).trim().toLowerCase();
|
|
16097
|
+
if (!answer) return defaultYes;
|
|
16098
|
+
return answer === "y" || answer === "yes";
|
|
16099
|
+
}
|
|
16100
|
+
};
|
|
16101
|
+
}
|
|
16102
|
+
|
|
15953
16103
|
// src/cli/services/handler.ts
|
|
15954
16104
|
var list = (value) => {
|
|
15955
16105
|
const items = (Array.isArray(value) ? value : value === void 0 ? [] : [value]).filter(
|
|
@@ -16004,15 +16154,6 @@ function addOptionsFromArguments(service, argv) {
|
|
|
16004
16154
|
};
|
|
16005
16155
|
}
|
|
16006
16156
|
var canPrompt2 = (json) => !json && process.stdin.isTTY === true && process.stdout.isTTY === true;
|
|
16007
|
-
async function confirm(question) {
|
|
16008
|
-
const rl = createInterface2({ input: process.stdin, output: process.stdout });
|
|
16009
|
-
try {
|
|
16010
|
-
const answer = (await rl.question(question)).trim().toLowerCase();
|
|
16011
|
-
return answer === "y" || answer === "yes";
|
|
16012
|
-
} finally {
|
|
16013
|
-
rl.close();
|
|
16014
|
-
}
|
|
16015
|
-
}
|
|
16016
16157
|
function describe(edit, dir) {
|
|
16017
16158
|
return {
|
|
16018
16159
|
service: edit.service,
|
|
@@ -16024,6 +16165,7 @@ function describe(edit, dir) {
|
|
|
16024
16165
|
}
|
|
16025
16166
|
async function run(action, argv, plan2, deps = {}) {
|
|
16026
16167
|
const json = argv.json === true;
|
|
16168
|
+
const asker = argv.yes === true ? void 0 : deps.asker ?? (canPrompt2(json) ? createAsker() : void 0);
|
|
16027
16169
|
try {
|
|
16028
16170
|
if (!isServiceName(argv.service)) {
|
|
16029
16171
|
throw new ServiceEditError(
|
|
@@ -16038,7 +16180,28 @@ async function run(action, argv, plan2, deps = {}) {
|
|
|
16038
16180
|
"Create one with `apitogo init`."
|
|
16039
16181
|
);
|
|
16040
16182
|
}
|
|
16041
|
-
const
|
|
16183
|
+
const service = argv.service;
|
|
16184
|
+
let edit;
|
|
16185
|
+
for (let attempt = 0; edit === void 0; attempt++) {
|
|
16186
|
+
try {
|
|
16187
|
+
edit = await plan2(service, asker);
|
|
16188
|
+
} catch (error) {
|
|
16189
|
+
const missing = action === "add" && error instanceof ServiceEditError ? error.missingServices : [];
|
|
16190
|
+
if (missing.length === 0 || !asker || argv.dryRun || attempt >= 3)
|
|
16191
|
+
throw error;
|
|
16192
|
+
for (const needed2 of missing) {
|
|
16193
|
+
if (!await asker.confirm(
|
|
16194
|
+
`${service} needs ${needed2}. Add ${needed2} now?`,
|
|
16195
|
+
true
|
|
16196
|
+
))
|
|
16197
|
+
throw error;
|
|
16198
|
+
await addService(
|
|
16199
|
+
{ service: needed2, dir: argv.dir, install: argv.install },
|
|
16200
|
+
deps
|
|
16201
|
+
);
|
|
16202
|
+
}
|
|
16203
|
+
}
|
|
16204
|
+
}
|
|
16042
16205
|
const needed = action === "add" ? missingPackages(dir, packagesForConfig(edit.config)) : [];
|
|
16043
16206
|
if (!json) {
|
|
16044
16207
|
for (const line2 of formatServiceEdit(edit))
|
|
@@ -16059,13 +16222,13 @@ async function run(action, argv, plan2, deps = {}) {
|
|
|
16059
16222
|
return;
|
|
16060
16223
|
}
|
|
16061
16224
|
if (argv.yes !== true) {
|
|
16062
|
-
if (!
|
|
16225
|
+
if (!asker) {
|
|
16063
16226
|
throw new ServiceEditError(
|
|
16064
16227
|
"This is not an interactive terminal.",
|
|
16065
16228
|
"Pass --yes to apply the change, or --dry-run to only see it."
|
|
16066
16229
|
);
|
|
16067
16230
|
}
|
|
16068
|
-
if (!await confirm("Apply this change?
|
|
16231
|
+
if (!await asker.confirm("Apply this change?")) {
|
|
16069
16232
|
printResultToConsole("Cancelled. Nothing was written.");
|
|
16070
16233
|
return;
|
|
16071
16234
|
}
|
|
@@ -16139,7 +16302,10 @@ async function installPackages(dir, edit, argv, deps, json) {
|
|
|
16139
16302
|
var addService = (argv, deps = {}) => run(
|
|
16140
16303
|
"add",
|
|
16141
16304
|
argv,
|
|
16142
|
-
(service) => planAddService(service,
|
|
16305
|
+
(service, ask4) => planAddService(service, {
|
|
16306
|
+
...addOptionsFromArguments(service, argv),
|
|
16307
|
+
ask: ask4
|
|
16308
|
+
}),
|
|
16143
16309
|
deps
|
|
16144
16310
|
);
|
|
16145
16311
|
var removeService = (argv) => run(
|
|
@@ -16498,7 +16664,7 @@ function formatDevPortalBackendResponse(projectId, backend) {
|
|
|
16498
16664
|
// src/cli/platform/helpers/dev-portal-manifest.ts
|
|
16499
16665
|
init_gateway_limits();
|
|
16500
16666
|
import { existsSync as existsSync7 } from "node:fs";
|
|
16501
|
-
import { access as access6, mkdir as mkdir13, readFile as readFile17, writeFile as
|
|
16667
|
+
import { access as access6, mkdir as mkdir13, readFile as readFile17, writeFile as writeFile16 } from "node:fs/promises";
|
|
16502
16668
|
import path57 from "node:path";
|
|
16503
16669
|
|
|
16504
16670
|
// src/cli/make-config/json-config.ts
|
|
@@ -17126,11 +17292,11 @@ function buildThinWrapperWithCustomPages(tsxRoutePaths) {
|
|
|
17126
17292
|
const customPages = tsxRoutePaths.map((routePath) => {
|
|
17127
17293
|
const slug = routePath.replace(/^\//, "");
|
|
17128
17294
|
const name = pageComponentName(slug);
|
|
17129
|
-
const
|
|
17295
|
+
const label2 = labelFromRoutePath(routePath);
|
|
17130
17296
|
return ` {
|
|
17131
17297
|
type: "custom-page",
|
|
17132
17298
|
path: ${JSON.stringify(routePath)},
|
|
17133
|
-
label: ${JSON.stringify(
|
|
17299
|
+
label: ${JSON.stringify(label2)},
|
|
17134
17300
|
element: <${name} />,
|
|
17135
17301
|
},`;
|
|
17136
17302
|
}).join("\n");
|
|
@@ -17213,7 +17379,7 @@ function ensureHeaderDocsNavigation(config2, docRoutes2) {
|
|
|
17213
17379
|
}
|
|
17214
17380
|
return { ...config2, header: { ...header, navigation } };
|
|
17215
17381
|
}
|
|
17216
|
-
function ensureHeaderApiReferenceNavigation(config2, to,
|
|
17382
|
+
function ensureHeaderApiReferenceNavigation(config2, to, label2 = HEADER_API_REFERENCE_LABEL) {
|
|
17217
17383
|
const current = readHeaderNavigation(config2);
|
|
17218
17384
|
if (!current) return config2;
|
|
17219
17385
|
const { header, navigation } = current;
|
|
@@ -17221,7 +17387,7 @@ function ensureHeaderApiReferenceNavigation(config2, to, label = HEADER_API_REFE
|
|
|
17221
17387
|
return config2;
|
|
17222
17388
|
}
|
|
17223
17389
|
const docsIndex = navigation.findIndex(isDocsItem);
|
|
17224
|
-
const item = { label, to };
|
|
17390
|
+
const item = { label: label2, to };
|
|
17225
17391
|
if (docsIndex >= 0) {
|
|
17226
17392
|
navigation.splice(docsIndex + 1, 0, item);
|
|
17227
17393
|
} else {
|
|
@@ -17818,7 +17984,7 @@ async function writeManifest2(siteDirectory, manifest) {
|
|
|
17818
17984
|
...safeManifest
|
|
17819
17985
|
} = manifest;
|
|
17820
17986
|
config2 = applyManifestToConfig(config2, safeManifest);
|
|
17821
|
-
await
|
|
17987
|
+
await writeFile16(filePath, `${JSON.stringify(config2, null, 2)}
|
|
17822
17988
|
`, "utf8");
|
|
17823
17989
|
return filePath;
|
|
17824
17990
|
}
|
|
@@ -17830,7 +17996,7 @@ import {
|
|
|
17830
17996
|
mkdir as mkdir14,
|
|
17831
17997
|
readFile as readFile18,
|
|
17832
17998
|
unlink,
|
|
17833
|
-
writeFile as
|
|
17999
|
+
writeFile as writeFile17
|
|
17834
18000
|
} from "node:fs/promises";
|
|
17835
18001
|
import path58 from "node:path";
|
|
17836
18002
|
var ENV_FILENAME = ".env";
|
|
@@ -18105,7 +18271,7 @@ async function writeScaffoldEnv(siteDirectory) {
|
|
|
18105
18271
|
await access7(envPath);
|
|
18106
18272
|
} catch {
|
|
18107
18273
|
await mkdir14(resolvedDirectory, { recursive: true });
|
|
18108
|
-
await
|
|
18274
|
+
await writeFile17(envPath, createEnvContent(), "utf8");
|
|
18109
18275
|
filesWritten.push(ENV_FILENAME);
|
|
18110
18276
|
}
|
|
18111
18277
|
for (const legacyName of [".env.example"]) {
|
|
@@ -18134,7 +18300,7 @@ async function writeScaffoldEnv(siteDirectory) {
|
|
|
18134
18300
|
if (!hasEntry) {
|
|
18135
18301
|
const suffix = gitignore.length === 0 ? "" : gitignore.endsWith("\n") ? "" : "\n";
|
|
18136
18302
|
if (gitignore.length === 0) {
|
|
18137
|
-
await
|
|
18303
|
+
await writeFile17(gitignorePath, `${entry}
|
|
18138
18304
|
`, "utf8");
|
|
18139
18305
|
} else {
|
|
18140
18306
|
await appendFile(gitignorePath, `${suffix}${entry}
|
|
@@ -18918,7 +19084,7 @@ var check_default = {
|
|
|
18918
19084
|
|
|
18919
19085
|
// src/cli/make-config/handler.ts
|
|
18920
19086
|
import { constants as constants2 } from "node:fs";
|
|
18921
|
-
import { access as access12, mkdir as mkdir17, readFile as readFile24, writeFile as
|
|
19087
|
+
import { access as access12, mkdir as mkdir17, readFile as readFile24, writeFile as writeFile22 } from "node:fs/promises";
|
|
18922
19088
|
import path71 from "node:path";
|
|
18923
19089
|
import { stdin as input2 } from "node:process";
|
|
18924
19090
|
import { glob as glob8 } from "glob";
|
|
@@ -19058,12 +19224,12 @@ function withDefaultIncludedActionKeys(plan2, actionKeys) {
|
|
|
19058
19224
|
}
|
|
19059
19225
|
|
|
19060
19226
|
// src/cli/scaffold/dev-portal-config-pages.ts
|
|
19061
|
-
import { access as access8, readFile as readFile20, rename as rename7, rm as rm9, writeFile as
|
|
19227
|
+
import { access as access8, readFile as readFile20, rename as rename7, rm as rm9, writeFile as writeFile18 } from "node:fs/promises";
|
|
19062
19228
|
import path63 from "node:path";
|
|
19063
19229
|
async function writeConfigAtomically(filePath, content) {
|
|
19064
19230
|
const temporaryPath = `${filePath}.${process.pid}.${Date.now()}.tmp`;
|
|
19065
19231
|
try {
|
|
19066
|
-
await
|
|
19232
|
+
await writeFile18(temporaryPath, content, "utf8");
|
|
19067
19233
|
await rename7(temporaryPath, filePath);
|
|
19068
19234
|
} catch (error) {
|
|
19069
19235
|
await rm9(temporaryPath, { force: true });
|
|
@@ -19555,10 +19721,10 @@ ${importStatement}${content.slice(lastImportEnd)}`;
|
|
|
19555
19721
|
return `${importStatement}
|
|
19556
19722
|
${content}`;
|
|
19557
19723
|
}
|
|
19558
|
-
function formatCustomPageNavigationEntry(slug,
|
|
19724
|
+
function formatCustomPageNavigationEntry(slug, label2, indent) {
|
|
19559
19725
|
const componentName = pageComponentName2(slug);
|
|
19560
19726
|
const inner = `${indent} `;
|
|
19561
|
-
const labelLine =
|
|
19727
|
+
const labelLine = label2?.trim() ? `${inner}label: ${JSON.stringify(label2.trim())},
|
|
19562
19728
|
` : "";
|
|
19563
19729
|
return `${indent}{
|
|
19564
19730
|
${inner}type: "custom-page",
|
|
@@ -19566,21 +19732,21 @@ ${inner}path: "/${normalizePageSlug2(slug)}",
|
|
|
19566
19732
|
` + labelLine + `${inner}element: <${componentName} />,
|
|
19567
19733
|
${indent}},`;
|
|
19568
19734
|
}
|
|
19569
|
-
function formatDocumentationCategoryWithPage(slug,
|
|
19735
|
+
function formatDocumentationCategoryWithPage(slug, label2, indent) {
|
|
19570
19736
|
const inner = `${indent} `;
|
|
19571
19737
|
const itemIndent = `${inner} `;
|
|
19572
19738
|
return `${indent}{
|
|
19573
19739
|
${inner}type: "category",
|
|
19574
19740
|
${inner}label: ${JSON.stringify(DOCUMENTATION_NAV_LABEL2)},
|
|
19575
19741
|
${inner}items: [
|
|
19576
|
-
${formatCustomPageNavigationEntry(slug,
|
|
19742
|
+
${formatCustomPageNavigationEntry(slug, label2, itemIndent)}
|
|
19577
19743
|
${inner}],
|
|
19578
19744
|
${indent}},`;
|
|
19579
19745
|
}
|
|
19580
|
-
function findMatchingArrayClose(content,
|
|
19746
|
+
function findMatchingArrayClose(content, open4) {
|
|
19581
19747
|
let depth = 0;
|
|
19582
19748
|
let inString = null;
|
|
19583
|
-
for (let i =
|
|
19749
|
+
for (let i = open4; i < content.length; i++) {
|
|
19584
19750
|
const char = content[i];
|
|
19585
19751
|
if (inString) {
|
|
19586
19752
|
if (char === "\\") {
|
|
@@ -19619,8 +19785,8 @@ function findDocumentationCategoryItemsRange(content, from, to) {
|
|
|
19619
19785
|
if (!itemsMatch) {
|
|
19620
19786
|
return null;
|
|
19621
19787
|
}
|
|
19622
|
-
const
|
|
19623
|
-
const close3 = findMatchingArrayClose(content,
|
|
19788
|
+
const open4 = afterLabel + itemsMatch.index + itemsMatch[0].length - 1;
|
|
19789
|
+
const close3 = findMatchingArrayClose(content, open4);
|
|
19624
19790
|
if (close3 === null || close3 > to) {
|
|
19625
19791
|
return null;
|
|
19626
19792
|
}
|
|
@@ -19631,7 +19797,7 @@ function findDocumentationCategoryItemsRange(content, from, to) {
|
|
|
19631
19797
|
const indentMatch = /^[ \t]*/.exec(content.slice(lineStart));
|
|
19632
19798
|
const itemsIndent = indentMatch?.[0] ?? "";
|
|
19633
19799
|
const entryIndent = `${itemsIndent} `;
|
|
19634
|
-
return { open:
|
|
19800
|
+
return { open: open4, close: close3, indent: entryIndent };
|
|
19635
19801
|
}
|
|
19636
19802
|
function appendEntryToArrayRange(content, range, entry) {
|
|
19637
19803
|
const before = content.slice(0, range.close);
|
|
@@ -19664,7 +19830,7 @@ ${normalizedEntry}
|
|
|
19664
19830
|
${range.indent}${trimmedInner}
|
|
19665
19831
|
${closeIndent}${content.slice(range.close)}`;
|
|
19666
19832
|
}
|
|
19667
|
-
function appendCustomPageToDirectBuildExport(content, slug,
|
|
19833
|
+
function appendCustomPageToDirectBuildExport(content, slug, label2) {
|
|
19668
19834
|
const exportPrefix = "export default ";
|
|
19669
19835
|
const callPrefix = "buildApitogoConfig(";
|
|
19670
19836
|
const exportIndex = content.indexOf(`${exportPrefix}${callPrefix}`);
|
|
@@ -19672,11 +19838,11 @@ function appendCustomPageToDirectBuildExport(content, slug, label) {
|
|
|
19672
19838
|
return null;
|
|
19673
19839
|
}
|
|
19674
19840
|
const callStart = exportIndex + exportPrefix.length;
|
|
19675
|
-
const
|
|
19841
|
+
const open4 = callStart + callPrefix.length - 1;
|
|
19676
19842
|
let depth = 0;
|
|
19677
19843
|
let inString = null;
|
|
19678
19844
|
let close3 = -1;
|
|
19679
|
-
for (let i =
|
|
19845
|
+
for (let i = open4; i < content.length; i++) {
|
|
19680
19846
|
const char = content[i];
|
|
19681
19847
|
if (inString) {
|
|
19682
19848
|
if (char === "\\") {
|
|
@@ -19711,7 +19877,7 @@ function appendCustomPageToDirectBuildExport(content, slug, label) {
|
|
|
19711
19877
|
replaceEnd += 1;
|
|
19712
19878
|
}
|
|
19713
19879
|
const buildCall = content.slice(callStart, close3 + 1);
|
|
19714
|
-
const category = formatDocumentationCategoryWithPage(slug,
|
|
19880
|
+
const category = formatDocumentationCategoryWithPage(slug, label2, " ");
|
|
19715
19881
|
const replacement = `const config = ${buildCall};
|
|
19716
19882
|
|
|
19717
19883
|
export default {
|
|
@@ -19737,10 +19903,10 @@ function findNavigationArrayRange(content) {
|
|
|
19737
19903
|
return null;
|
|
19738
19904
|
}
|
|
19739
19905
|
const indent = match[1] ?? "";
|
|
19740
|
-
const
|
|
19906
|
+
const open4 = match.index + match[0].length - 1;
|
|
19741
19907
|
let depth = 0;
|
|
19742
19908
|
let inString = null;
|
|
19743
|
-
for (let i =
|
|
19909
|
+
for (let i = open4; i < content.length; i++) {
|
|
19744
19910
|
const char = content[i];
|
|
19745
19911
|
if (inString) {
|
|
19746
19912
|
if (char === "\\") {
|
|
@@ -19759,13 +19925,13 @@ function findNavigationArrayRange(content) {
|
|
|
19759
19925
|
} else if (char === "]") {
|
|
19760
19926
|
depth -= 1;
|
|
19761
19927
|
if (depth === 0) {
|
|
19762
|
-
return { open:
|
|
19928
|
+
return { open: open4, close: i, indent };
|
|
19763
19929
|
}
|
|
19764
19930
|
}
|
|
19765
19931
|
}
|
|
19766
19932
|
return null;
|
|
19767
19933
|
}
|
|
19768
|
-
function appendCustomPageToNavigation(content, slug,
|
|
19934
|
+
function appendCustomPageToNavigation(content, slug, label2, _options) {
|
|
19769
19935
|
const pagePath = `"/${normalizePageSlug2(slug)}"`;
|
|
19770
19936
|
if (content.includes(`path: ${pagePath}`)) {
|
|
19771
19937
|
return content;
|
|
@@ -19773,7 +19939,7 @@ function appendCustomPageToNavigation(content, slug, label, _options) {
|
|
|
19773
19939
|
const rewrittenBuildExport = appendCustomPageToDirectBuildExport(
|
|
19774
19940
|
content,
|
|
19775
19941
|
slug,
|
|
19776
|
-
|
|
19942
|
+
label2
|
|
19777
19943
|
);
|
|
19778
19944
|
if (rewrittenBuildExport) {
|
|
19779
19945
|
return rewrittenBuildExport;
|
|
@@ -19790,14 +19956,14 @@ function appendCustomPageToNavigation(content, slug, label, _options) {
|
|
|
19790
19956
|
if (docsItems) {
|
|
19791
19957
|
const entry = formatCustomPageNavigationEntry(
|
|
19792
19958
|
slug,
|
|
19793
|
-
|
|
19959
|
+
label2,
|
|
19794
19960
|
docsItems.indent
|
|
19795
19961
|
);
|
|
19796
19962
|
return appendEntryToArrayRange(content, docsItems, entry);
|
|
19797
19963
|
}
|
|
19798
19964
|
const category = formatDocumentationCategoryWithPage(
|
|
19799
19965
|
slug,
|
|
19800
|
-
|
|
19966
|
+
label2,
|
|
19801
19967
|
`${range.indent} `
|
|
19802
19968
|
);
|
|
19803
19969
|
return prependEntryToArrayRange(
|
|
@@ -19806,11 +19972,11 @@ function appendCustomPageToNavigation(content, slug, label, _options) {
|
|
|
19806
19972
|
category
|
|
19807
19973
|
);
|
|
19808
19974
|
}
|
|
19809
|
-
function patchApitogoConfigForNewPage(content, slug,
|
|
19975
|
+
function patchApitogoConfigForNewPage(content, slug, label2, options) {
|
|
19810
19976
|
const withNavigation = appendCustomPageToNavigation(
|
|
19811
19977
|
content,
|
|
19812
19978
|
slug,
|
|
19813
|
-
|
|
19979
|
+
label2,
|
|
19814
19980
|
options
|
|
19815
19981
|
);
|
|
19816
19982
|
if (withNavigation === content) {
|
|
@@ -19818,13 +19984,13 @@ function patchApitogoConfigForNewPage(content, slug, label, options) {
|
|
|
19818
19984
|
}
|
|
19819
19985
|
return ensurePageImport(withNavigation, pageComponentName2(slug), slug);
|
|
19820
19986
|
}
|
|
19821
|
-
async function wireNewDevPortalPageInConfig(sourceDirectory, slug,
|
|
19987
|
+
async function wireNewDevPortalPageInConfig(sourceDirectory, slug, label2) {
|
|
19822
19988
|
const configPath = await resolveApitogoConfigPath(sourceDirectory);
|
|
19823
19989
|
if (!configPath) {
|
|
19824
19990
|
return { updated: false };
|
|
19825
19991
|
}
|
|
19826
19992
|
const original = await readFile20(configPath, "utf8");
|
|
19827
|
-
const patched = patchApitogoConfigForNewPage(original, slug,
|
|
19993
|
+
const patched = patchApitogoConfigForNewPage(original, slug, label2, {
|
|
19828
19994
|
typescript: configPath.endsWith(".tsx")
|
|
19829
19995
|
});
|
|
19830
19996
|
if (patched === original) {
|
|
@@ -19835,7 +20001,7 @@ async function wireNewDevPortalPageInConfig(sourceDirectory, slug, label) {
|
|
|
19835
20001
|
}
|
|
19836
20002
|
|
|
19837
20003
|
// src/cli/scaffold/wire-openapi.ts
|
|
19838
|
-
import { access as access10, readFile as readFile22, writeFile as
|
|
20004
|
+
import { access as access10, readFile as readFile22, writeFile as writeFile20 } from "node:fs/promises";
|
|
19839
20005
|
import path68 from "node:path";
|
|
19840
20006
|
|
|
19841
20007
|
// src/cli/platform/helpers/openapi.ts
|
|
@@ -20039,7 +20205,7 @@ async function validateOpenApiFile(filePath) {
|
|
|
20039
20205
|
}
|
|
20040
20206
|
|
|
20041
20207
|
// src/cli/scaffold/scaffold-dev-portal-site.ts
|
|
20042
|
-
import { mkdir as mkdir15, readFile as readFile21, writeFile as
|
|
20208
|
+
import { mkdir as mkdir15, readFile as readFile21, writeFile as writeFile19 } from "node:fs/promises";
|
|
20043
20209
|
import path67 from "node:path";
|
|
20044
20210
|
|
|
20045
20211
|
// src/cli/scaffold/apitogo-npm-packages.ts
|
|
@@ -20451,7 +20617,7 @@ async function ensureAccountPlansPage(outputDirectory) {
|
|
|
20451
20617
|
return void 0;
|
|
20452
20618
|
} catch {
|
|
20453
20619
|
await mkdir15(path67.dirname(pagePath), { recursive: true });
|
|
20454
|
-
await
|
|
20620
|
+
await writeFile19(pagePath, ACCOUNT_PLANS_PAGE);
|
|
20455
20621
|
return ACCOUNT_PLANS_PAGE_PATH;
|
|
20456
20622
|
}
|
|
20457
20623
|
}
|
|
@@ -20462,7 +20628,7 @@ async function ensureDashboardPage(outputDirectory) {
|
|
|
20462
20628
|
return void 0;
|
|
20463
20629
|
} catch {
|
|
20464
20630
|
await mkdir15(path67.dirname(pagePath), { recursive: true });
|
|
20465
|
-
await
|
|
20631
|
+
await writeFile19(pagePath, DASHBOARD_PAGE);
|
|
20466
20632
|
return DASHBOARD_PAGE_PATH;
|
|
20467
20633
|
}
|
|
20468
20634
|
}
|
|
@@ -20521,7 +20687,7 @@ async function patchScaffoldedSite(outputDirectory, options) {
|
|
|
20521
20687
|
const packageJsonPath = path67.join(outputDirectory, "package.json");
|
|
20522
20688
|
if (options.patchPackageJson !== false) {
|
|
20523
20689
|
const packageJson3 = await readFile21(packageJsonPath, "utf8");
|
|
20524
|
-
await
|
|
20690
|
+
await writeFile19(
|
|
20525
20691
|
packageJsonPath,
|
|
20526
20692
|
rewritePackageJson(packageJson3, {
|
|
20527
20693
|
siteName: options.siteName,
|
|
@@ -20533,7 +20699,7 @@ async function patchScaffoldedSite(outputDirectory, options) {
|
|
|
20533
20699
|
}
|
|
20534
20700
|
const configPath = path67.join(outputDirectory, "apitogo.config.tsx");
|
|
20535
20701
|
const configContent = await readFile21(configPath, "utf8");
|
|
20536
|
-
await
|
|
20702
|
+
await writeFile19(
|
|
20537
20703
|
configPath,
|
|
20538
20704
|
patchDashboardPageConfig(
|
|
20539
20705
|
patchAccountPlansPageConfig(
|
|
@@ -20645,7 +20811,7 @@ async function wireDevPortalOpenApi(sourceDirectory, openApiPath) {
|
|
|
20645
20811
|
input: input5,
|
|
20646
20812
|
apiPath: "api"
|
|
20647
20813
|
});
|
|
20648
|
-
await
|
|
20814
|
+
await writeFile20(
|
|
20649
20815
|
jsonConfigPath,
|
|
20650
20816
|
`${JSON.stringify(updated, null, 2)}
|
|
20651
20817
|
`,
|
|
@@ -20656,7 +20822,7 @@ async function wireDevPortalOpenApi(sourceDirectory, openApiPath) {
|
|
|
20656
20822
|
const configContent = await readFile22(tsxConfigPath, "utf8");
|
|
20657
20823
|
try {
|
|
20658
20824
|
const patched = patchApitogoConfigOpenApiToFile(configContent, input5);
|
|
20659
|
-
await
|
|
20825
|
+
await writeFile20(tsxConfigPath, patched);
|
|
20660
20826
|
} catch (error) {
|
|
20661
20827
|
return {
|
|
20662
20828
|
status: "failed",
|
|
@@ -21009,7 +21175,7 @@ export default function ${componentName}() {
|
|
|
21009
21175
|
// src/cli/make-config/scaffold-project.ts
|
|
21010
21176
|
init_package_json();
|
|
21011
21177
|
import { constants } from "node:fs";
|
|
21012
|
-
import { access as access11, mkdir as mkdir16, readFile as readFile23, writeFile as
|
|
21178
|
+
import { access as access11, mkdir as mkdir16, readFile as readFile23, writeFile as writeFile21 } from "node:fs/promises";
|
|
21013
21179
|
import path70 from "node:path";
|
|
21014
21180
|
import { glob as glob7 } from "glob";
|
|
21015
21181
|
|
|
@@ -21087,7 +21253,7 @@ async function ensurePackageJson2(dir) {
|
|
|
21087
21253
|
typescript: "^5"
|
|
21088
21254
|
}
|
|
21089
21255
|
};
|
|
21090
|
-
await
|
|
21256
|
+
await writeFile21(pkgPath, `${JSON.stringify(pkg2, null, 2)}
|
|
21091
21257
|
`, "utf8");
|
|
21092
21258
|
return true;
|
|
21093
21259
|
}
|
|
@@ -21114,7 +21280,7 @@ async function ensurePackageJson2(dir) {
|
|
|
21114
21280
|
for (const [k, v] of Object.entries(baseDeps)) {
|
|
21115
21281
|
if (!deps[k]) deps[k] = v;
|
|
21116
21282
|
}
|
|
21117
|
-
await
|
|
21283
|
+
await writeFile21(pkgPath, `${JSON.stringify(pkg, null, 2)}
|
|
21118
21284
|
`, "utf8");
|
|
21119
21285
|
return false;
|
|
21120
21286
|
}
|
|
@@ -21149,7 +21315,7 @@ async function ensureTsconfigJson(dir) {
|
|
|
21149
21315
|
"user-panel"
|
|
21150
21316
|
]
|
|
21151
21317
|
};
|
|
21152
|
-
await
|
|
21318
|
+
await writeFile21(tsPath, `${JSON.stringify(tsconfig, null, 2)}
|
|
21153
21319
|
`, "utf8");
|
|
21154
21320
|
return true;
|
|
21155
21321
|
}
|
|
@@ -21171,17 +21337,17 @@ async function ensurePortalStubFiles(dir) {
|
|
|
21171
21337
|
const landingPath = path70.join(dir, "landing", "LandingPage.tsx");
|
|
21172
21338
|
if (!await fileExists2(landingPath)) {
|
|
21173
21339
|
await mkdir16(path70.join(dir, "landing"), { recursive: true });
|
|
21174
|
-
await
|
|
21340
|
+
await writeFile21(landingPath, LANDING_PAGE_SOURCE, "utf8");
|
|
21175
21341
|
}
|
|
21176
21342
|
const plansPath = path70.join(dir, "user-panel", "AccountPlansPage.tsx");
|
|
21177
21343
|
if (!await fileExists2(plansPath)) {
|
|
21178
21344
|
await mkdir16(path70.join(dir, "user-panel"), { recursive: true });
|
|
21179
|
-
await
|
|
21345
|
+
await writeFile21(plansPath, ACCOUNT_PLANS_PAGE_SOURCE, "utf8");
|
|
21180
21346
|
}
|
|
21181
21347
|
const dashboardPath = path70.join(dir, "user-panel", "DashboardPage.tsx");
|
|
21182
21348
|
if (!await fileExists2(dashboardPath)) {
|
|
21183
21349
|
await mkdir16(path70.join(dir, "user-panel"), { recursive: true });
|
|
21184
|
-
await
|
|
21350
|
+
await writeFile21(dashboardPath, DASHBOARD_PAGE_SOURCE, "utf8");
|
|
21185
21351
|
}
|
|
21186
21352
|
await ensurePublicBrandingFiles(dir);
|
|
21187
21353
|
}
|
|
@@ -21193,11 +21359,11 @@ async function ensurePublicBrandingFiles(dir) {
|
|
|
21193
21359
|
if (await fileExists2(dest)) {
|
|
21194
21360
|
continue;
|
|
21195
21361
|
}
|
|
21196
|
-
await
|
|
21362
|
+
await writeFile21(dest, content, "utf8");
|
|
21197
21363
|
}
|
|
21198
21364
|
const faviconDest = path70.join(destDir, "Icon.png");
|
|
21199
21365
|
if (!await fileExists2(faviconDest)) {
|
|
21200
|
-
await
|
|
21366
|
+
await writeFile21(faviconDest, Buffer.from(FAVICON_PNG_BASE64, "base64"));
|
|
21201
21367
|
}
|
|
21202
21368
|
}
|
|
21203
21369
|
async function ensureThinConfigTsx(dir) {
|
|
@@ -21205,7 +21371,7 @@ async function ensureThinConfigTsx(dir) {
|
|
|
21205
21371
|
if (await fileExists2(tsxPath)) {
|
|
21206
21372
|
return false;
|
|
21207
21373
|
}
|
|
21208
|
-
await
|
|
21374
|
+
await writeFile21(tsxPath, THIN_WRAPPER_SOURCE, "utf8");
|
|
21209
21375
|
return true;
|
|
21210
21376
|
}
|
|
21211
21377
|
|
|
@@ -21266,13 +21432,13 @@ async function persistConfig(config2, ctx) {
|
|
|
21266
21432
|
force: ctx.force
|
|
21267
21433
|
})
|
|
21268
21434
|
};
|
|
21269
|
-
await
|
|
21435
|
+
await writeFile22(ctx.jsonPath, `${JSON.stringify(next, null, 2)}
|
|
21270
21436
|
`, "utf8");
|
|
21271
21437
|
const tsxPath = path71.join(ctx.dir, "apitogo.config.tsx");
|
|
21272
21438
|
if (await fileExists3(tsxPath) && tsxRoutes.length > 0) {
|
|
21273
21439
|
const tsxSource = await readFile24(tsxPath, "utf8");
|
|
21274
21440
|
if (isManagedConfigWrapper(tsxSource)) {
|
|
21275
|
-
await
|
|
21441
|
+
await writeFile22(
|
|
21276
21442
|
tsxPath,
|
|
21277
21443
|
buildThinWrapperWithCustomPages(tsxRoutes),
|
|
21278
21444
|
"utf8"
|
|
@@ -21376,7 +21542,7 @@ async function makeConfig(argv) {
|
|
|
21376
21542
|
if (action2.kind === "page") {
|
|
21377
21543
|
const pagePath = path71.join(dir, action2.page.relativePath);
|
|
21378
21544
|
await mkdir17(path71.dirname(pagePath), { recursive: true });
|
|
21379
|
-
await
|
|
21545
|
+
await writeFile22(
|
|
21380
21546
|
pagePath,
|
|
21381
21547
|
buildStarterDocsPageTsx(action2.page.slug, action2.page.title),
|
|
21382
21548
|
"utf8"
|
|
@@ -21485,7 +21651,7 @@ async function makeConfig(argv) {
|
|
|
21485
21651
|
argv.pageContent,
|
|
21486
21652
|
pageTemplate
|
|
21487
21653
|
);
|
|
21488
|
-
await
|
|
21654
|
+
await writeFile22(filePath, tsx, "utf8");
|
|
21489
21655
|
pageResult = {
|
|
21490
21656
|
action: "updated",
|
|
21491
21657
|
slug,
|
|
@@ -21505,7 +21671,7 @@ async function makeConfig(argv) {
|
|
|
21505
21671
|
pageTemplate
|
|
21506
21672
|
);
|
|
21507
21673
|
await mkdir17(path71.dirname(filePath), { recursive: true });
|
|
21508
|
-
await
|
|
21674
|
+
await writeFile22(filePath, tsx, "utf8");
|
|
21509
21675
|
const configResult = await wireNewDevPortalPageInConfig(
|
|
21510
21676
|
dir,
|
|
21511
21677
|
slug,
|
|
@@ -21805,8 +21971,8 @@ function createScopedConsoleProgressReporter(stream, stepLabel, shared) {
|
|
|
21805
21971
|
}
|
|
21806
21972
|
},
|
|
21807
21973
|
isCancelled: () => false,
|
|
21808
|
-
child(
|
|
21809
|
-
return createScopedConsoleProgressReporter(stream,
|
|
21974
|
+
child(label2) {
|
|
21975
|
+
return createScopedConsoleProgressReporter(stream, label2, shared);
|
|
21810
21976
|
}
|
|
21811
21977
|
};
|
|
21812
21978
|
const baseOnNewMessages = reporter.onNewMessages.bind(reporter);
|
|
@@ -21854,9 +22020,10 @@ var DEFAULT_EXCLUDES = /* @__PURE__ */ new Set([
|
|
|
21854
22020
|
"bin",
|
|
21855
22021
|
"obj"
|
|
21856
22022
|
]);
|
|
21857
|
-
async function loadGitignore(rootDir) {
|
|
22023
|
+
async function loadGitignore(rootDir, extraExcludes = []) {
|
|
21858
22024
|
const ig = ignore();
|
|
21859
22025
|
ig.add(["node_modules/", ".git/", "dist/", "bin/", "obj/"]);
|
|
22026
|
+
if (extraExcludes.length > 0) ig.add([...extraExcludes]);
|
|
21860
22027
|
const gitignorePath = path72.join(rootDir, ".gitignore");
|
|
21861
22028
|
try {
|
|
21862
22029
|
const content = await fs7.readFile(gitignorePath, "utf8");
|
|
@@ -21884,13 +22051,13 @@ async function collectFiles(rootDir, relativeDir, ig, files) {
|
|
|
21884
22051
|
}
|
|
21885
22052
|
}
|
|
21886
22053
|
}
|
|
21887
|
-
async function collectPackageableFiles(sourceDirectory) {
|
|
22054
|
+
async function collectPackageableFiles(sourceDirectory, extraExcludes = []) {
|
|
21888
22055
|
const resolved = path72.resolve(sourceDirectory);
|
|
21889
22056
|
const stat7 = await fs7.stat(resolved).catch(() => null);
|
|
21890
22057
|
if (!stat7?.isDirectory()) {
|
|
21891
22058
|
throw new Error(`sourceDirectory is not a directory: ${resolved}`);
|
|
21892
22059
|
}
|
|
21893
|
-
const ig = await loadGitignore(resolved);
|
|
22060
|
+
const ig = await loadGitignore(resolved, extraExcludes);
|
|
21894
22061
|
const files = [];
|
|
21895
22062
|
await collectFiles(resolved, "", ig, files);
|
|
21896
22063
|
if (files.length === 0) {
|
|
@@ -21910,9 +22077,9 @@ async function sha256File(filePath) {
|
|
|
21910
22077
|
});
|
|
21911
22078
|
return hash2.digest("hex");
|
|
21912
22079
|
}
|
|
21913
|
-
async function hashDirectoryContents(rootDirectory) {
|
|
22080
|
+
async function hashDirectoryContents(rootDirectory, extraExcludes = []) {
|
|
21914
22081
|
const resolved = path73.resolve(rootDirectory);
|
|
21915
|
-
const files = (await collectPackageableFiles(resolved)).sort(
|
|
22082
|
+
const files = (await collectPackageableFiles(resolved, extraExcludes)).sort(
|
|
21916
22083
|
(a, b) => a.localeCompare(b)
|
|
21917
22084
|
);
|
|
21918
22085
|
const hash2 = createHash3("sha256");
|
|
@@ -22663,7 +22830,7 @@ async function upsertGatewayPlansFromManifest(client, config2, projectId, plans,
|
|
|
22663
22830
|
}
|
|
22664
22831
|
|
|
22665
22832
|
// src/cli/platform/helpers/openapi-gateway-servers.ts
|
|
22666
|
-
import { mkdir as mkdir18, readFile as readFile25, writeFile as
|
|
22833
|
+
import { mkdir as mkdir18, readFile as readFile25, writeFile as writeFile23 } from "node:fs/promises";
|
|
22667
22834
|
import path74 from "node:path";
|
|
22668
22835
|
import YAML3 from "yaml";
|
|
22669
22836
|
var GATEWAY_URL_ENV_KEY = "APITOGO_GATEWAY_URL";
|
|
@@ -22711,7 +22878,7 @@ async function materializePublishOpenApiWithGateway(siteDirectory, sourceOpenApi
|
|
|
22711
22878
|
const doc = await readOpenApiDocument(source);
|
|
22712
22879
|
const rewritten = withGatewayServers(doc, gatewayUrl);
|
|
22713
22880
|
await mkdir18(path74.dirname(outputPath), { recursive: true });
|
|
22714
|
-
await
|
|
22881
|
+
await writeFile23(
|
|
22715
22882
|
outputPath,
|
|
22716
22883
|
`${JSON.stringify(rewritten, null, 2)}
|
|
22717
22884
|
`,
|
|
@@ -22737,7 +22904,7 @@ async function pointSiteApisAtPublishOpenApi(siteDirectory, relativeInput) {
|
|
|
22737
22904
|
const backend = config2.backend && typeof config2.backend === "object" ? { ...config2.backend } : {};
|
|
22738
22905
|
backend.openApiPath = relativeInput;
|
|
22739
22906
|
config2.backend = backend;
|
|
22740
|
-
await
|
|
22907
|
+
await writeFile23(configPath, `${JSON.stringify(config2, null, 2)}
|
|
22741
22908
|
`, "utf8");
|
|
22742
22909
|
return {
|
|
22743
22910
|
restore: async () => {
|
|
@@ -22753,7 +22920,7 @@ async function pointSiteApisAtPublishOpenApi(siteDirectory, relativeInput) {
|
|
|
22753
22920
|
} else {
|
|
22754
22921
|
current.backend = previousBackend;
|
|
22755
22922
|
}
|
|
22756
|
-
await
|
|
22923
|
+
await writeFile23(
|
|
22757
22924
|
configPath,
|
|
22758
22925
|
`${JSON.stringify(current, null, 2)}
|
|
22759
22926
|
`,
|
|
@@ -22850,7 +23017,7 @@ function collectManifestPublicActionKeys(gateway) {
|
|
|
22850
23017
|
}
|
|
22851
23018
|
|
|
22852
23019
|
// src/cli/platform/helpers/publish-ledger.ts
|
|
22853
|
-
import { mkdir as mkdir19, readFile as readFile26, writeFile as
|
|
23020
|
+
import { mkdir as mkdir19, readFile as readFile26, writeFile as writeFile24 } from "node:fs/promises";
|
|
22854
23021
|
import path75 from "node:path";
|
|
22855
23022
|
var PUBLISH_LEDGER_RELATIVE_PATH = ".apitogo/publish-state.json";
|
|
22856
23023
|
function publishLedgerPath(siteDirectory) {
|
|
@@ -22877,7 +23044,7 @@ async function writePublishLedger(siteDirectory, ledger) {
|
|
|
22877
23044
|
version: 1,
|
|
22878
23045
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
22879
23046
|
};
|
|
22880
|
-
await
|
|
23047
|
+
await writeFile24(filePath, `${JSON.stringify(next, null, 2)}
|
|
22881
23048
|
`, "utf8");
|
|
22882
23049
|
return filePath;
|
|
22883
23050
|
}
|
|
@@ -23511,9 +23678,9 @@ import { createReadStream as createReadStream2, createWriteStream as createWrite
|
|
|
23511
23678
|
import { tmpdir as tmpdir2 } from "node:os";
|
|
23512
23679
|
import path77 from "node:path";
|
|
23513
23680
|
import { ZipArchive } from "archiver";
|
|
23514
|
-
async function packageBackendDirectory(sourceDirectory) {
|
|
23681
|
+
async function packageBackendDirectory(sourceDirectory, extraExcludes = []) {
|
|
23515
23682
|
const resolved = path77.resolve(sourceDirectory);
|
|
23516
|
-
const files = await collectPackageableFiles(resolved);
|
|
23683
|
+
const files = await collectPackageableFiles(resolved, extraExcludes);
|
|
23517
23684
|
const zipPath = path77.join(
|
|
23518
23685
|
tmpdir2(),
|
|
23519
23686
|
`apitogo-backend-${Date.now()}-${Math.random().toString(36).slice(2)}.zip`
|
|
@@ -25038,7 +25205,7 @@ async function resolveProjectIdForPublish(client, config2, args, wait, progress,
|
|
|
25038
25205
|
import path81 from "node:path";
|
|
25039
25206
|
|
|
25040
25207
|
// src/cli/platform/helpers/dev-portal-site-api-url.ts
|
|
25041
|
-
import { readdir as readdir3, readFile as readFile27, writeFile as
|
|
25208
|
+
import { readdir as readdir3, readFile as readFile27, writeFile as writeFile25 } from "node:fs/promises";
|
|
25042
25209
|
import path78 from "node:path";
|
|
25043
25210
|
var SITE_API_BASE_URL_ENV_KEY = "VITE_APITOGO_AUTHENTICATION_API_BASE_URL";
|
|
25044
25211
|
var PLACEHOLDER_HOST_MARKERS = [
|
|
@@ -25100,13 +25267,13 @@ async function patchSiteApiBaseUrl(sourceDirectory, apiBaseUrl) {
|
|
|
25100
25267
|
${line2}
|
|
25101
25268
|
` : `${line2}
|
|
25102
25269
|
`;
|
|
25103
|
-
await
|
|
25270
|
+
await writeFile25(envPath, patched);
|
|
25104
25271
|
patchedExisting = true;
|
|
25105
25272
|
} catch {
|
|
25106
25273
|
}
|
|
25107
25274
|
}
|
|
25108
25275
|
if (!patchedExisting) {
|
|
25109
|
-
await
|
|
25276
|
+
await writeFile25(path78.join(resolved, ".env"), `${line2}
|
|
25110
25277
|
`);
|
|
25111
25278
|
}
|
|
25112
25279
|
}
|
|
@@ -27324,19 +27491,11 @@ var GatewayDeployError = class extends ApitogoError {
|
|
|
27324
27491
|
};
|
|
27325
27492
|
var sha256 = (text) => createHash4("sha256").update(text).digest("hex");
|
|
27326
27493
|
function gatewayNotDeployableReason(gateway) {
|
|
27327
|
-
if (gateway.origin.mode === "managed")
|
|
27328
|
-
return "a managed origin cannot be deployed from this CLI version yet; use origin.mode proxy";
|
|
27329
27494
|
if (gateway.specs.length > 1)
|
|
27330
27495
|
return "the platform takes one OpenAPI file per gateway, but the config lists several";
|
|
27331
27496
|
return void 0;
|
|
27332
27497
|
}
|
|
27333
27498
|
function assertDeployable(gateway) {
|
|
27334
|
-
if (gateway.origin.mode === "managed")
|
|
27335
|
-
throw new GatewayDeployError(
|
|
27336
|
-
"GATEWAY_MANAGED_ORIGIN_UNSUPPORTED",
|
|
27337
|
-
"A managed origin cannot be deployed from this CLI version yet.",
|
|
27338
|
-
'Set gateway.origin.mode to "proxy" and point gateway.origin.url at your API.'
|
|
27339
|
-
);
|
|
27340
27499
|
if (gateway.specs.length > 1)
|
|
27341
27500
|
throw new GatewayDeployError(
|
|
27342
27501
|
"GATEWAY_MULTIPLE_SPECS",
|
|
@@ -27383,6 +27542,37 @@ async function gatewayDeployInputs(resolved, projectId) {
|
|
|
27383
27542
|
};
|
|
27384
27543
|
}
|
|
27385
27544
|
var defaultSleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
27545
|
+
async function backendDeployInputs(gateway, projectId) {
|
|
27546
|
+
const { origin } = gateway;
|
|
27547
|
+
if (origin.mode !== "managed" || !origin.source || !origin.runtime || !origin.runtimeVersion)
|
|
27548
|
+
return void 0;
|
|
27549
|
+
const runtime = normalizeRuntime(origin.runtime);
|
|
27550
|
+
const runtimeVersion = normalizeRuntimeVersionSlug(
|
|
27551
|
+
runtime,
|
|
27552
|
+
origin.runtimeVersion
|
|
27553
|
+
);
|
|
27554
|
+
const exclude = origin.exclude ?? [];
|
|
27555
|
+
let tree;
|
|
27556
|
+
try {
|
|
27557
|
+
tree = await hashDirectoryContents(origin.source, exclude);
|
|
27558
|
+
} catch (error) {
|
|
27559
|
+
throw new GatewayDeployError(
|
|
27560
|
+
"GATEWAY_BACKEND_UNREADABLE",
|
|
27561
|
+
`Cannot read the backend source ${origin.source}: ${error instanceof Error ? error.message : String(error)}`,
|
|
27562
|
+
"Check gateway.origin.source in the config. It must be a folder with your API's code."
|
|
27563
|
+
);
|
|
27564
|
+
}
|
|
27565
|
+
return {
|
|
27566
|
+
source: origin.source,
|
|
27567
|
+
runtime,
|
|
27568
|
+
runtimeVersion,
|
|
27569
|
+
exclude,
|
|
27570
|
+
healthPath: origin.healthPath,
|
|
27571
|
+
hash: sha256(
|
|
27572
|
+
`${projectId}|${runtime}|${runtimeVersion}|${origin.healthPath ?? ""}|${stableJson(exclude)}|${tree}`
|
|
27573
|
+
)
|
|
27574
|
+
};
|
|
27575
|
+
}
|
|
27386
27576
|
function describePlatformProblem(error) {
|
|
27387
27577
|
if (!(error instanceof PlatformApiError) || !error.problem) return [];
|
|
27388
27578
|
const errors = error.problem.errors;
|
|
@@ -27418,11 +27608,73 @@ function rejected(what, error) {
|
|
|
27418
27608
|
async function deployGateway(resolved, projectId, deps, options = {}) {
|
|
27419
27609
|
const { gateway, spec, body, specHash, configHash } = await gatewayDeployInputs(resolved, projectId);
|
|
27420
27610
|
assertDeployable(gateway);
|
|
27611
|
+
const backend = await backendDeployInputs(gateway, projectId);
|
|
27421
27612
|
const client = deps.client;
|
|
27422
27613
|
const sleep5 = deps.sleep ?? defaultSleep;
|
|
27423
27614
|
const now = deps.now ?? Date.now;
|
|
27424
27615
|
const timeoutMs = deps.reconcileTimeoutMs ?? 18e4;
|
|
27616
|
+
const backendTimeoutMs = deps.backendTimeoutMs ?? 20 * 6e4;
|
|
27425
27617
|
const intervalMs = deps.pollIntervalMs ?? 2e3;
|
|
27618
|
+
const backendStep = {
|
|
27619
|
+
name: "backend",
|
|
27620
|
+
hash: () => backend?.hash ?? "",
|
|
27621
|
+
run: async () => {
|
|
27622
|
+
if (!backend) throw new Error("gateway.origin is not a managed origin");
|
|
27623
|
+
const prepared = await prepareBackendPackagingDirectory({
|
|
27624
|
+
sourceDirectory: backend.source,
|
|
27625
|
+
runtime: backend.runtime
|
|
27626
|
+
});
|
|
27627
|
+
let zipPath;
|
|
27628
|
+
try {
|
|
27629
|
+
const packaged = await packageBackendDirectory(
|
|
27630
|
+
prepared.directory,
|
|
27631
|
+
backend.exclude
|
|
27632
|
+
);
|
|
27633
|
+
zipPath = packaged.zipPath;
|
|
27634
|
+
let started;
|
|
27635
|
+
try {
|
|
27636
|
+
started = await client.deployBackend(projectId, {
|
|
27637
|
+
archivePath: packaged.zipPath,
|
|
27638
|
+
runtime: backend.runtime,
|
|
27639
|
+
runtimeVersion: backend.runtimeVersion,
|
|
27640
|
+
healthCheckPath: backend.healthPath
|
|
27641
|
+
});
|
|
27642
|
+
} catch (error) {
|
|
27643
|
+
return rejected("backend upload", error);
|
|
27644
|
+
}
|
|
27645
|
+
if (!started.reused) {
|
|
27646
|
+
const deadline = now() + backendTimeoutMs;
|
|
27647
|
+
for (; ; ) {
|
|
27648
|
+
const state = await client.getDeploymentStatus(
|
|
27649
|
+
projectId,
|
|
27650
|
+
started.deploymentId
|
|
27651
|
+
);
|
|
27652
|
+
if (state.status.toLowerCase() === "healthy") break;
|
|
27653
|
+
if (isTerminalDeploymentStatus(state.status))
|
|
27654
|
+
throw new GatewayDeployError(
|
|
27655
|
+
"GATEWAY_BACKEND_FAILED",
|
|
27656
|
+
`The platform could not deploy your backend (${state.status}): ${state.error ?? state.messages.at(-1)?.message ?? "no reason given"}.`,
|
|
27657
|
+
"Fix the cause and run deploy again; the backend is uploaded again."
|
|
27658
|
+
);
|
|
27659
|
+
if (now() >= deadline)
|
|
27660
|
+
throw new GatewayDeployError(
|
|
27661
|
+
"GATEWAY_BACKEND_TIMEOUT",
|
|
27662
|
+
`Your backend was not ready within ${Math.round(backendTimeoutMs / 6e4)} minutes (state: ${state.status}).`,
|
|
27663
|
+
"Run deploy again to keep waiting."
|
|
27664
|
+
);
|
|
27665
|
+
await sleep5(intervalMs);
|
|
27666
|
+
}
|
|
27667
|
+
}
|
|
27668
|
+
return {
|
|
27669
|
+
deploymentId: started.deploymentId,
|
|
27670
|
+
...started.artifactContentSha256 ? { artifactSha256: started.artifactContentSha256 } : {}
|
|
27671
|
+
};
|
|
27672
|
+
} finally {
|
|
27673
|
+
if (zipPath) await cleanupZip(zipPath);
|
|
27674
|
+
await prepared.cleanup?.();
|
|
27675
|
+
}
|
|
27676
|
+
}
|
|
27677
|
+
};
|
|
27426
27678
|
const specStep = {
|
|
27427
27679
|
name: "spec",
|
|
27428
27680
|
hash: () => specHash,
|
|
@@ -27502,12 +27754,30 @@ async function deployGateway(resolved, projectId, deps, options = {}) {
|
|
|
27502
27754
|
};
|
|
27503
27755
|
}
|
|
27504
27756
|
};
|
|
27505
|
-
|
|
27506
|
-
|
|
27507
|
-
|
|
27508
|
-
|
|
27509
|
-
|
|
27510
|
-
|
|
27757
|
+
let outcomes;
|
|
27758
|
+
if (backend) {
|
|
27759
|
+
const head = await runPipeline(
|
|
27760
|
+
resolved.state,
|
|
27761
|
+
"gateway",
|
|
27762
|
+
[specStep, configStep],
|
|
27763
|
+
{ force: options.force }
|
|
27764
|
+
);
|
|
27765
|
+
const middle = await runPipeline(resolved.state, "gateway", [backendStep], {
|
|
27766
|
+
force: options.force
|
|
27767
|
+
});
|
|
27768
|
+
const changed = [...head, ...middle].some((outcome) => !outcome.skipped);
|
|
27769
|
+
const tail = await runPipeline(resolved.state, "gateway", [reconcileStep], {
|
|
27770
|
+
force: options.force || changed
|
|
27771
|
+
});
|
|
27772
|
+
outcomes = [...head, ...middle, ...tail];
|
|
27773
|
+
} else {
|
|
27774
|
+
outcomes = await runPipeline(
|
|
27775
|
+
resolved.state,
|
|
27776
|
+
"gateway",
|
|
27777
|
+
[specStep, configStep, reconcileStep],
|
|
27778
|
+
{ force: options.force }
|
|
27779
|
+
);
|
|
27780
|
+
}
|
|
27511
27781
|
const reconcile = outcomes.find((outcome) => outcome.step === "reconcile");
|
|
27512
27782
|
const ran = reconcile !== void 0 && !reconcile.skipped;
|
|
27513
27783
|
const known = (await readManifest(resolved.state))?.services.gateway;
|
|
@@ -27904,15 +28174,41 @@ var PlansDeployError = class extends ApitogoError {
|
|
|
27904
28174
|
this.name = "PlansDeployError";
|
|
27905
28175
|
}
|
|
27906
28176
|
};
|
|
27907
|
-
var PLANS_STEPS = [
|
|
28177
|
+
var PLANS_STEPS = [
|
|
28178
|
+
"catalog",
|
|
28179
|
+
"backend",
|
|
28180
|
+
"billing",
|
|
28181
|
+
"identity-link"
|
|
28182
|
+
];
|
|
27908
28183
|
var defaultSleep3 = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
27909
28184
|
var BACKEND_MODULE = "dev-portal";
|
|
27910
28185
|
var PLANS_CAPABILITIES = { catalog: true, keys: true, usage: true };
|
|
28186
|
+
var STRIPE_WEBHOOK_EVENTS2 = [
|
|
28187
|
+
"checkout.session.completed",
|
|
28188
|
+
"customer.subscription.updated",
|
|
28189
|
+
"customer.subscription.deleted",
|
|
28190
|
+
"invoice.payment_failed"
|
|
28191
|
+
];
|
|
28192
|
+
function billingNotes(billing) {
|
|
28193
|
+
const notes = [
|
|
28194
|
+
billing.mode === "live" ? "Stripe: LIVE mode. Customers pay with real money." : "Stripe: test mode. Use Stripe's test cards; no real money moves."
|
|
28195
|
+
];
|
|
28196
|
+
notes.push(
|
|
28197
|
+
billing.hasWebhookSecret ? `Stripe webhook endpoint (add it in your Stripe account if it is not there yet): ${billing.webhookUrl}` : `A payment does not activate a plan until Stripe can confirm it. In your Stripe account add the webhook endpoint ${billing.webhookUrl} (events: ${STRIPE_WEBHOOK_EVENTS2.join(", ")}), store its signing secret with "apitogo secrets set STRIPE_WEBHOOK", set plans.billing.webhookSecret to "secret:STRIPE_WEBHOOK", and deploy again.`
|
|
28198
|
+
);
|
|
28199
|
+
return notes;
|
|
28200
|
+
}
|
|
27911
28201
|
async function plansInputs(resolved, projectId) {
|
|
27912
28202
|
const plans = resolved.plans;
|
|
27913
28203
|
if (!plans) throw new Error("plans is not in the config.");
|
|
27914
|
-
const
|
|
28204
|
+
const billing = plans.billing;
|
|
28205
|
+
const wanted = toPlatformPlans(await readPlansFile(plans.file), {
|
|
28206
|
+
billing: billing.provider === "stripe"
|
|
28207
|
+
});
|
|
27915
28208
|
const manifest = await readManifest(resolved.state);
|
|
28209
|
+
const ledger = await readLedger(resolved.state);
|
|
28210
|
+
const billingOn = billing.provider === "stripe";
|
|
28211
|
+
const billingStep = billingOn || stepOf(ledger, "plans", "billing") !== void 0;
|
|
27916
28212
|
const identity = manifest?.services.identity;
|
|
27917
28213
|
const ownIdentity = Boolean(
|
|
27918
28214
|
identity?.provider === "apitogo" && identity.issuer
|
|
@@ -27923,6 +28219,16 @@ async function plansInputs(resolved, projectId) {
|
|
|
27923
28219
|
ownIdentity,
|
|
27924
28220
|
catalogHash: stableJson({ projectId, plans: wanted }),
|
|
27925
28221
|
backendHash: stableJson({ projectId }),
|
|
28222
|
+
billingStep,
|
|
28223
|
+
// Only the references to the project's secrets: a rotated value is applied with --force.
|
|
28224
|
+
billingHash: stableJson({
|
|
28225
|
+
projectId,
|
|
28226
|
+
provider: billing.provider,
|
|
28227
|
+
apiKey: billingOn ? billing.apiKey : void 0,
|
|
28228
|
+
webhookSecret: billingOn ? billing.webhookSecret : void 0,
|
|
28229
|
+
successUrl: billingOn ? billing.successUrl : void 0,
|
|
28230
|
+
cancelUrl: billingOn ? billing.cancelUrl : void 0
|
|
28231
|
+
}),
|
|
27926
28232
|
linkHash: stableJson({
|
|
27927
28233
|
projectId,
|
|
27928
28234
|
issuer: identity?.issuer,
|
|
@@ -27959,6 +28265,17 @@ function refused(error) {
|
|
|
27959
28265
|
}
|
|
27960
28266
|
throw error;
|
|
27961
28267
|
}
|
|
28268
|
+
function billingRefused(error) {
|
|
28269
|
+
if (error instanceof PlatformApiError && error.status === 400) {
|
|
28270
|
+
const lines = describePlatformProblem(error);
|
|
28271
|
+
throw new PlansDeployError(
|
|
28272
|
+
"PLANS_BILLING_REJECTED",
|
|
28273
|
+
`The platform refused the billing settings: ${lines.join(" ") || error.message}`,
|
|
28274
|
+
"Check plans.billing in the config and the project's secrets (apitogo secrets list). The Stripe key starts with sk_ or rk_, the webhook secret with whsec_."
|
|
28275
|
+
);
|
|
28276
|
+
}
|
|
28277
|
+
throw error;
|
|
28278
|
+
}
|
|
27962
28279
|
async function deployPlans(resolved, projectId, deps, options = {}) {
|
|
27963
28280
|
const plans = resolved.plans;
|
|
27964
28281
|
if (!plans) throw new Error("plans is not in the config.");
|
|
@@ -27968,7 +28285,16 @@ async function deployPlans(resolved, projectId, deps, options = {}) {
|
|
|
27968
28285
|
const intervalMs = deps.pollIntervalMs ?? 2e3;
|
|
27969
28286
|
const backendTimeoutMs = deps.backendTimeoutMs ?? 9e5;
|
|
27970
28287
|
const linkTimeoutMs = deps.linkTimeoutMs ?? 6e5;
|
|
27971
|
-
const {
|
|
28288
|
+
const {
|
|
28289
|
+
wanted,
|
|
28290
|
+
manifest,
|
|
28291
|
+
ownIdentity,
|
|
28292
|
+
billingStep,
|
|
28293
|
+
billingHash,
|
|
28294
|
+
catalogHash,
|
|
28295
|
+
backendHash,
|
|
28296
|
+
linkHash
|
|
28297
|
+
} = await plansInputs(resolved, projectId);
|
|
27972
28298
|
const catalogStep = {
|
|
27973
28299
|
name: "catalog",
|
|
27974
28300
|
hash: () => catalogHash,
|
|
@@ -28055,35 +28381,73 @@ async function deployPlans(resolved, projectId, deps, options = {}) {
|
|
|
28055
28381
|
},
|
|
28056
28382
|
stillValid: async () => Boolean(await backendOf())
|
|
28057
28383
|
};
|
|
28384
|
+
const waitForBackendUpdate = async (jobId, what) => {
|
|
28385
|
+
const deadline = now() + linkTimeoutMs;
|
|
28386
|
+
for (; ; ) {
|
|
28387
|
+
const job = (await client.listProvisioningJobs(projectId)).jobs.find(
|
|
28388
|
+
(item) => item.id === jobId
|
|
28389
|
+
);
|
|
28390
|
+
const status2 = job?.status.toLowerCase();
|
|
28391
|
+
if (status2?.startsWith("completed")) return { jobId };
|
|
28392
|
+
if (status2 === "failed")
|
|
28393
|
+
throw new PlansDeployError(
|
|
28394
|
+
`${what.code}_FAILED`,
|
|
28395
|
+
`Applying ${what.text} to the plans backend failed: ${job?.errorMessage ?? "no reason given"}.`,
|
|
28396
|
+
"Run deploy again; it is applied again."
|
|
28397
|
+
);
|
|
28398
|
+
if (now() >= deadline)
|
|
28399
|
+
throw new PlansDeployError(
|
|
28400
|
+
`${what.code}_TIMEOUT`,
|
|
28401
|
+
`Applying ${what.text} did not finish within ${Math.round(linkTimeoutMs / 1e3)} seconds (state: ${status2 ?? "unknown"}).`,
|
|
28402
|
+
"Run deploy again to keep waiting."
|
|
28403
|
+
);
|
|
28404
|
+
await sleep5(intervalMs);
|
|
28405
|
+
}
|
|
28406
|
+
};
|
|
28407
|
+
let billingNote;
|
|
28408
|
+
const billingStepDef = {
|
|
28409
|
+
name: "billing",
|
|
28410
|
+
hash: () => billingHash,
|
|
28411
|
+
run: async () => {
|
|
28412
|
+
const body = plans.billing.provider === "stripe" ? {
|
|
28413
|
+
provider: "stripe",
|
|
28414
|
+
apiKey: plans.billing.apiKey,
|
|
28415
|
+
webhookSecret: plans.billing.webhookSecret,
|
|
28416
|
+
successUrl: plans.billing.successUrl,
|
|
28417
|
+
cancelUrl: plans.billing.cancelUrl
|
|
28418
|
+
} : { provider: "none" };
|
|
28419
|
+
let queued;
|
|
28420
|
+
try {
|
|
28421
|
+
queued = await client.putDevPortalBilling(projectId, body);
|
|
28422
|
+
} catch (error) {
|
|
28423
|
+
billingRefused(error);
|
|
28424
|
+
}
|
|
28425
|
+
const done = await waitForBackendUpdate(queued.jobId, {
|
|
28426
|
+
code: "PLANS_BILLING",
|
|
28427
|
+
text: "the billing settings"
|
|
28428
|
+
});
|
|
28429
|
+
if (plans.billing.provider === "stripe")
|
|
28430
|
+
billingNote = billingNotes(queued);
|
|
28431
|
+
return { ...done, mode: queued.mode };
|
|
28432
|
+
}
|
|
28433
|
+
};
|
|
28058
28434
|
const linkStep = {
|
|
28059
28435
|
name: "identity-link",
|
|
28060
28436
|
hash: () => linkHash,
|
|
28061
28437
|
run: async () => {
|
|
28062
28438
|
const queued = await client.linkDevPortalIdentity(projectId);
|
|
28063
|
-
|
|
28064
|
-
|
|
28065
|
-
|
|
28066
|
-
|
|
28067
|
-
);
|
|
28068
|
-
const status2 = job?.status.toLowerCase();
|
|
28069
|
-
if (status2?.startsWith("completed")) return { jobId: queued.jobId };
|
|
28070
|
-
if (status2 === "failed")
|
|
28071
|
-
throw new PlansDeployError(
|
|
28072
|
-
"PLANS_LINK_FAILED",
|
|
28073
|
-
`Applying the identity to the plans backend failed: ${job?.errorMessage ?? "no reason given"}.`,
|
|
28074
|
-
"Run deploy again; the link is applied again."
|
|
28075
|
-
);
|
|
28076
|
-
if (now() >= deadline)
|
|
28077
|
-
throw new PlansDeployError(
|
|
28078
|
-
"PLANS_LINK_TIMEOUT",
|
|
28079
|
-
`Applying the identity did not finish within ${Math.round(linkTimeoutMs / 1e3)} seconds (state: ${status2 ?? "unknown"}).`,
|
|
28080
|
-
"Run deploy again to keep waiting."
|
|
28081
|
-
);
|
|
28082
|
-
await sleep5(intervalMs);
|
|
28083
|
-
}
|
|
28439
|
+
return waitForBackendUpdate(queued.jobId, {
|
|
28440
|
+
code: "PLANS_LINK",
|
|
28441
|
+
text: "the identity"
|
|
28442
|
+
});
|
|
28084
28443
|
}
|
|
28085
28444
|
};
|
|
28086
|
-
const steps = [
|
|
28445
|
+
const steps = [
|
|
28446
|
+
catalogStep,
|
|
28447
|
+
backendStep,
|
|
28448
|
+
...billingStep ? [billingStepDef] : [],
|
|
28449
|
+
...ownIdentity ? [linkStep] : []
|
|
28450
|
+
];
|
|
28087
28451
|
const outcomes = await runPipeline(
|
|
28088
28452
|
resolved.state,
|
|
28089
28453
|
"plans",
|
|
@@ -28108,7 +28472,7 @@ async function deployPlans(resolved, projectId, deps, options = {}) {
|
|
|
28108
28472
|
apiBaseUrl,
|
|
28109
28473
|
billing: plans.billing.provider,
|
|
28110
28474
|
catalogRevision: catalog && !catalog.skipped ? previous + 1 : Math.max(previous, 1),
|
|
28111
|
-
capabilities: PLANS_CAPABILITIES
|
|
28475
|
+
capabilities: plans.billing.provider === "stripe" ? { ...PLANS_CAPABILITIES, checkout: true } : PLANS_CAPABILITIES
|
|
28112
28476
|
})
|
|
28113
28477
|
);
|
|
28114
28478
|
}
|
|
@@ -28119,7 +28483,8 @@ async function deployPlans(resolved, projectId, deps, options = {}) {
|
|
|
28119
28483
|
step: outcome.step,
|
|
28120
28484
|
skipped: outcome.skipped
|
|
28121
28485
|
})),
|
|
28122
|
-
apiBaseUrl
|
|
28486
|
+
apiBaseUrl,
|
|
28487
|
+
...billingNote ? { notes: billingNote } : {}
|
|
28123
28488
|
};
|
|
28124
28489
|
}
|
|
28125
28490
|
|
|
@@ -28238,14 +28603,18 @@ async function planPlansService(resolved, ledger) {
|
|
|
28238
28603
|
const hashes = {
|
|
28239
28604
|
catalog: projectId ? inputs.catalogHash : void 0,
|
|
28240
28605
|
backend: projectId ? inputs.backendHash : void 0,
|
|
28606
|
+
billing: projectId ? inputs.billingHash : void 0,
|
|
28241
28607
|
"identity-link": projectId ? inputs.linkHash : void 0
|
|
28242
28608
|
};
|
|
28243
28609
|
const steps = PLANS_STEPS.filter(
|
|
28244
|
-
(name) => name !== "identity-link" || inputs.ownIdentity
|
|
28610
|
+
(name) => (name !== "identity-link" || inputs.ownIdentity) && (name !== "billing" || inputs.billingStep)
|
|
28245
28611
|
);
|
|
28246
28612
|
const details = [
|
|
28247
28613
|
...inputs.wanted.length === 0 ? ["plans: none in the file"] : inputs.wanted.map((plan2) => `plan ${plan2.sku}: ${plan2.displayName}`),
|
|
28248
28614
|
inputs.ownIdentity ? "sign-in: the project's identity will be applied to the plans backend" : "sign-in: no identity of the platform deployed, so nothing is applied to the plans backend",
|
|
28615
|
+
...inputs.billingStep ? [
|
|
28616
|
+
resolved.plans?.billing.provider === "stripe" ? "billing: the Stripe key and webhook secret of the project's secrets will be applied to the plans backend" : "billing: switched off, so the stored Stripe settings will be removed from the plans backend"
|
|
28617
|
+
] : [],
|
|
28249
28618
|
...steps.map(
|
|
28250
28619
|
(name) => `step ${name}: ${stepState(stepOf(ledger, "plans", name), hashes[name])}`
|
|
28251
28620
|
)
|
|
@@ -28486,7 +28855,7 @@ function describePolicies(gateway) {
|
|
|
28486
28855
|
function dryRunLines(dryRun) {
|
|
28487
28856
|
const changes = dryRun.changes;
|
|
28488
28857
|
if (!changes) return [];
|
|
28489
|
-
const list2 = (
|
|
28858
|
+
const list2 = (label2, items) => items.length > 0 ? [` ${label2}: ${items.join(", ")}`] : [];
|
|
28490
28859
|
if (changes.wouldCreate)
|
|
28491
28860
|
return [
|
|
28492
28861
|
"platform dry run: the gateway does not exist yet; everything would be created",
|
|
@@ -28523,7 +28892,7 @@ async function planGatewayService(resolved, ledger, dryRun) {
|
|
|
28523
28892
|
details: []
|
|
28524
28893
|
};
|
|
28525
28894
|
const details = [
|
|
28526
|
-
`origin: proxy ${gateway.origin.url ?? "(no url set)"}`,
|
|
28895
|
+
gateway.origin.mode === "managed" ? `origin: managed (${gateway.origin.runtime ?? "no runtime"} from ${gateway.origin.source ?? "no source folder"})` : `origin: proxy ${gateway.origin.url ?? "(no url set)"}`,
|
|
28527
28896
|
...gateway.auth ? [`auth: ${gateway.auth.mode}`] : ["auth: none"],
|
|
28528
28897
|
...describePolicies(gateway)
|
|
28529
28898
|
];
|
|
@@ -28576,8 +28945,27 @@ async function planGatewayService(resolved, ledger, dryRun) {
|
|
|
28576
28945
|
);
|
|
28577
28946
|
}
|
|
28578
28947
|
}
|
|
28948
|
+
let backendHash;
|
|
28949
|
+
try {
|
|
28950
|
+
backendHash = (await backendDeployInputs(gateway, resolved.project.id ?? ""))?.hash;
|
|
28951
|
+
} catch (error) {
|
|
28952
|
+
return {
|
|
28953
|
+
...base,
|
|
28954
|
+
action: "blocked",
|
|
28955
|
+
reason: "cannot deploy until this is fixed",
|
|
28956
|
+
details,
|
|
28957
|
+
issues: [
|
|
28958
|
+
{
|
|
28959
|
+
code: "GATEWAY_BACKEND_UNREADABLE",
|
|
28960
|
+
path: "gateway.origin.source",
|
|
28961
|
+
message: error instanceof Error ? error.message : String(error)
|
|
28962
|
+
}
|
|
28963
|
+
]
|
|
28964
|
+
};
|
|
28965
|
+
}
|
|
28579
28966
|
const step2 = (name) => stepOf(ledger, "gateway", name);
|
|
28580
28967
|
const hashes = {
|
|
28968
|
+
backend: backendHash ?? "",
|
|
28581
28969
|
spec: inputs.specHash,
|
|
28582
28970
|
config: inputs.configHash,
|
|
28583
28971
|
reconcile: inputs.configHash
|
|
@@ -28593,9 +28981,10 @@ async function planGatewayService(resolved, ledger, dryRun) {
|
|
|
28593
28981
|
const lines = [
|
|
28594
28982
|
`step spec (upload the OpenAPI file): ${state("spec")}`,
|
|
28595
28983
|
`step config (send the wanted state): ${state("config")}`,
|
|
28984
|
+
...backendHash ? [`step backend (upload and start your code): ${state("backend")}`] : [],
|
|
28596
28985
|
`step reconcile (wait until the platform applied it): ${state("reconcile")}`
|
|
28597
28986
|
];
|
|
28598
|
-
const allDone = step2("spec")?.status === "succeeded" && step2("spec")?.hash === hashes.spec && step2("config")?.status === "succeeded" && step2("config")?.hash === hashes.config && step2("reconcile")?.status === "succeeded" && step2("reconcile")?.hash === hashes.reconcile;
|
|
28987
|
+
const allDone = (!backendHash || step2("backend")?.status === "succeeded" && step2("backend")?.hash === hashes.backend) && step2("spec")?.status === "succeeded" && step2("spec")?.hash === hashes.spec && step2("config")?.status === "succeeded" && step2("config")?.hash === hashes.config && step2("reconcile")?.status === "succeeded" && step2("reconcile")?.hash === hashes.reconcile;
|
|
28599
28988
|
if (issues.length > 0)
|
|
28600
28989
|
return {
|
|
28601
28990
|
...base,
|
|
@@ -28676,7 +29065,7 @@ function corsLines(platform) {
|
|
|
28676
29065
|
];
|
|
28677
29066
|
}
|
|
28678
29067
|
async function gatewayStatus(resolved, ledger, manifest, remote) {
|
|
28679
|
-
const steps = ["spec", "config", "reconcile"].map((name) => ({
|
|
29068
|
+
const steps = ["spec", "config", "backend", "reconcile"].map((name) => ({
|
|
28680
29069
|
name,
|
|
28681
29070
|
record: stepOf(ledger, "gateway", name)
|
|
28682
29071
|
}));
|
|
@@ -28694,6 +29083,14 @@ async function gatewayStatus(resolved, ledger, manifest, remote) {
|
|
|
28694
29083
|
if (remote && projectId) {
|
|
28695
29084
|
try {
|
|
28696
29085
|
const platform = await remote(projectId);
|
|
29086
|
+
if (platform.status === "Released") {
|
|
29087
|
+
details.length = 0;
|
|
29088
|
+
details.push(
|
|
29089
|
+
"platform state: Released (the gateway was removed; deploy makes a new one)"
|
|
29090
|
+
);
|
|
29091
|
+
state = "not-deployed";
|
|
29092
|
+
return { service: "gateway", state, details: [...details, ...local] };
|
|
29093
|
+
}
|
|
28697
29094
|
details.push(
|
|
28698
29095
|
`platform state: ${platform.status}`,
|
|
28699
29096
|
platform.inSync ? "drift: none (the real gateway matches the config)" : `drift: the real gateway does not match the config yet (wanted ${platform.desiredHash?.slice(0, 12) ?? "none"}, real ${platform.observedHash?.slice(0, 12) ?? "none"})`,
|
|
@@ -28831,7 +29228,7 @@ function formatStatus(output4) {
|
|
|
28831
29228
|
}
|
|
28832
29229
|
|
|
28833
29230
|
// src/cli/deploy/static-web-config.ts
|
|
28834
|
-
import { access as access14, writeFile as
|
|
29231
|
+
import { access as access14, writeFile as writeFile26 } from "node:fs/promises";
|
|
28835
29232
|
import path84 from "node:path";
|
|
28836
29233
|
var STATIC_WEB_CONFIG_FILE = "staticwebapp.config.json";
|
|
28837
29234
|
var STATIC_WEB_CONFIG_VERSION = 1;
|
|
@@ -28850,7 +29247,7 @@ async function ensureStaticWebConfig(buildDir) {
|
|
|
28850
29247
|
await access14(file);
|
|
28851
29248
|
return false;
|
|
28852
29249
|
} catch {
|
|
28853
|
-
await
|
|
29250
|
+
await writeFile26(
|
|
28854
29251
|
file,
|
|
28855
29252
|
`${JSON.stringify(DEFAULT_STATIC_WEB_CONFIG, null, 2)}
|
|
28856
29253
|
`,
|
|
@@ -28999,7 +29396,7 @@ async function deployGatewayService(options, deps, projectId, named) {
|
|
|
28999
29396
|
return { service: "gateway", status: "skipped", reason: why, steps: [] };
|
|
29000
29397
|
}
|
|
29001
29398
|
const client = deps.client;
|
|
29002
|
-
if (!client.putGatewaySpec || !client.putGatewayConfig || !client.getGatewayReconciliation || !client.getGatewayStatus || !client.getGatewayCapabilities)
|
|
29399
|
+
if (!client.putGatewaySpec || !client.putGatewayConfig || !client.getGatewayReconciliation || !client.getGatewayStatus || !client.getGatewayCapabilities || gateway?.origin.mode === "managed" && (!client.deployBackend || !client.getDeploymentStatus))
|
|
29003
29400
|
throw new Error("The platform client has no gateway calls.");
|
|
29004
29401
|
const result = await deployGateway(
|
|
29005
29402
|
resolved,
|
|
@@ -29074,7 +29471,8 @@ async function deployPlansService(options, deps, projectId, named) {
|
|
|
29074
29471
|
service: "plans",
|
|
29075
29472
|
status: result.status,
|
|
29076
29473
|
steps: result.steps,
|
|
29077
|
-
url: result.apiBaseUrl
|
|
29474
|
+
url: result.apiBaseUrl,
|
|
29475
|
+
...result.notes ? { notes: result.notes } : {}
|
|
29078
29476
|
};
|
|
29079
29477
|
}
|
|
29080
29478
|
async function deployPortal(options, deps, projectId) {
|
|
@@ -29208,10 +29606,10 @@ var deploy_default = {
|
|
|
29208
29606
|
describe: "Override project GUID"
|
|
29209
29607
|
}).option("project-name", {
|
|
29210
29608
|
type: "string",
|
|
29211
|
-
describe: "
|
|
29609
|
+
describe: "Version 1: display name when creating a project. Version 2 ignores it: the project is made by `apitogo project create` and named by project.name"
|
|
29212
29610
|
}).option("platform-plan", {
|
|
29213
29611
|
type: "string",
|
|
29214
|
-
describe: "
|
|
29612
|
+
describe: "Version 1: platform hosting tier. Version 2 ignores it: a project made by `apitogo project create` is on the Free plan",
|
|
29215
29613
|
choices: ["free", "starter", "professional"]
|
|
29216
29614
|
}).option("only", {
|
|
29217
29615
|
type: "string",
|
|
@@ -29246,7 +29644,7 @@ var deploy_default = {
|
|
|
29246
29644
|
};
|
|
29247
29645
|
|
|
29248
29646
|
// src/cli/export/handler.ts
|
|
29249
|
-
import { mkdir as mkdir20, writeFile as
|
|
29647
|
+
import { mkdir as mkdir20, writeFile as writeFile27 } from "node:fs/promises";
|
|
29250
29648
|
import path87 from "node:path";
|
|
29251
29649
|
init_invariant();
|
|
29252
29650
|
var ExportError = class extends ApitogoError {
|
|
@@ -29288,7 +29686,7 @@ async function exportOpenApi(args, deps) {
|
|
|
29288
29686
|
if (!args.out) return { text };
|
|
29289
29687
|
const file = path87.resolve(process.cwd(), args.out);
|
|
29290
29688
|
await mkdir20(path87.dirname(file), { recursive: true });
|
|
29291
|
-
await
|
|
29689
|
+
await writeFile27(file, text);
|
|
29292
29690
|
return { text, file };
|
|
29293
29691
|
}
|
|
29294
29692
|
async function exportCommand(args) {
|
|
@@ -29341,13 +29739,13 @@ var export_default = {
|
|
|
29341
29739
|
import path91 from "node:path";
|
|
29342
29740
|
|
|
29343
29741
|
// src/cli/scaffold/run-scaffold-discovery.ts
|
|
29344
|
-
import { access as access16, readFile as readFile31, writeFile as
|
|
29742
|
+
import { access as access16, readFile as readFile31, writeFile as writeFile29 } from "node:fs/promises";
|
|
29345
29743
|
import path90 from "node:path";
|
|
29346
29744
|
|
|
29347
29745
|
// src/cli/scaffold/detect-backend.ts
|
|
29348
29746
|
import { readdir as readdir4, readFile as readFile29 } from "node:fs/promises";
|
|
29349
29747
|
import path88 from "node:path";
|
|
29350
|
-
var
|
|
29748
|
+
var DEFAULT_RUNTIME_VERSION2 = {
|
|
29351
29749
|
dotnet: "DOTNET_10",
|
|
29352
29750
|
nodejs: "NODE_22_LTS",
|
|
29353
29751
|
python: "PYTHON_312"
|
|
@@ -29374,18 +29772,18 @@ function dotnetVersionFromCsproj(content) {
|
|
|
29374
29772
|
if (major === "8" || major === "9" || major === "10") {
|
|
29375
29773
|
return `DOTNET_${major}`;
|
|
29376
29774
|
}
|
|
29377
|
-
return
|
|
29775
|
+
return DEFAULT_RUNTIME_VERSION2.dotnet;
|
|
29378
29776
|
}
|
|
29379
29777
|
function nodeVersionFromEngines(engines) {
|
|
29380
29778
|
const range = engines && typeof engines === "object" && !Array.isArray(engines) ? engines.node : void 0;
|
|
29381
29779
|
if (typeof range === "string" && /(^|[^\d])24/.test(range)) {
|
|
29382
29780
|
return "NODE_24_LTS";
|
|
29383
29781
|
}
|
|
29384
|
-
return
|
|
29782
|
+
return DEFAULT_RUNTIME_VERSION2.nodejs;
|
|
29385
29783
|
}
|
|
29386
29784
|
function pythonVersionFromText(content) {
|
|
29387
29785
|
const match = content?.match(/3\.(1[0-4])/);
|
|
29388
|
-
return match ? `PYTHON_3${match[1]}` :
|
|
29786
|
+
return match ? `PYTHON_3${match[1]}` : DEFAULT_RUNTIME_VERSION2.python;
|
|
29389
29787
|
}
|
|
29390
29788
|
function isRunnableNodePackage(pkg) {
|
|
29391
29789
|
const scripts = pkg.scripts && typeof pkg.scripts === "object" ? pkg.scripts : {};
|
|
@@ -29452,7 +29850,7 @@ import {
|
|
|
29452
29850
|
readFile as readFile30,
|
|
29453
29851
|
readlink,
|
|
29454
29852
|
symlink as symlink2,
|
|
29455
|
-
writeFile as
|
|
29853
|
+
writeFile as writeFile28
|
|
29456
29854
|
} from "node:fs/promises";
|
|
29457
29855
|
import path89 from "node:path";
|
|
29458
29856
|
var DOCS_DIR_WHITELIST = [
|
|
@@ -29689,7 +30087,7 @@ async function applyDocsNavigation(siteDirectory, routes) {
|
|
|
29689
30087
|
force: false
|
|
29690
30088
|
});
|
|
29691
30089
|
const updated = ensureHeaderDocsNavigation(parsed, routes);
|
|
29692
|
-
await
|
|
30090
|
+
await writeFile28(configPath, `${JSON.stringify(updated, null, 2)}
|
|
29693
30091
|
`, "utf8");
|
|
29694
30092
|
}
|
|
29695
30093
|
async function discoverAndWireDocs(siteDirectory) {
|
|
@@ -29873,7 +30271,7 @@ async function wireOpenApiIntoJsonConfig(siteDirectory, openApiPath) {
|
|
|
29873
30271
|
});
|
|
29874
30272
|
const backend = updated.backend && typeof updated.backend === "object" && !Array.isArray(updated.backend) ? { ...updated.backend } : {};
|
|
29875
30273
|
updated.backend = { ...backend, openApiPath: input5 };
|
|
29876
|
-
await
|
|
30274
|
+
await writeFile29(
|
|
29877
30275
|
jsonConfigPath,
|
|
29878
30276
|
`${JSON.stringify(updated, null, 2)}
|
|
29879
30277
|
`,
|
|
@@ -29993,7 +30391,7 @@ async function discoverAndWireBackend(siteDirectory, mode) {
|
|
|
29993
30391
|
runtime: detected.runtime,
|
|
29994
30392
|
runtimeVersion: detected.runtimeVersion
|
|
29995
30393
|
};
|
|
29996
|
-
await
|
|
30394
|
+
await writeFile29(
|
|
29997
30395
|
jsonConfigPath,
|
|
29998
30396
|
`${JSON.stringify(siteConfig, null, 2)}
|
|
29999
30397
|
`,
|
|
@@ -30546,6 +30944,7 @@ var plan_default = {
|
|
|
30546
30944
|
// src/cli/project/create-handler.ts
|
|
30547
30945
|
init_invariant();
|
|
30548
30946
|
import path93 from "node:path";
|
|
30947
|
+
import open3 from "open";
|
|
30549
30948
|
|
|
30550
30949
|
// src/cli/project/v2.ts
|
|
30551
30950
|
init_invariant();
|
|
@@ -30557,7 +30956,12 @@ var ProjectCreateError = class extends ApitogoError {
|
|
|
30557
30956
|
}
|
|
30558
30957
|
};
|
|
30559
30958
|
function modulesForConfig(config2) {
|
|
30560
|
-
|
|
30959
|
+
const modules = [];
|
|
30960
|
+
if (config2.portal && config2.portal.enabled !== false)
|
|
30961
|
+
modules.push("dev-portal");
|
|
30962
|
+
if (config2.gateway && config2.gateway.enabled !== false && config2.gateway.origin?.mode === "managed")
|
|
30963
|
+
modules.push("apihub");
|
|
30964
|
+
return modules;
|
|
30561
30965
|
}
|
|
30562
30966
|
var hostsAnything = (config2) => config2.portal && config2.portal.enabled !== false || config2.gateway && config2.gateway.enabled !== false;
|
|
30563
30967
|
async function createProjectV2(options, deps) {
|
|
@@ -30572,15 +30976,52 @@ async function createProjectV2(options, deps) {
|
|
|
30572
30976
|
);
|
|
30573
30977
|
}
|
|
30574
30978
|
const name = options.name?.trim() || resolved.project.name;
|
|
30979
|
+
const plan2 = options.plan ?? "free";
|
|
30980
|
+
const onProjectResolved = async (projectId) => {
|
|
30981
|
+
await setProjectId(options.dir, projectId);
|
|
30982
|
+
};
|
|
30983
|
+
let continueWith = linkedProjectId;
|
|
30984
|
+
if (plan2 !== "free") {
|
|
30985
|
+
const first = parse4(
|
|
30986
|
+
await deps.bootstrap({
|
|
30987
|
+
name,
|
|
30988
|
+
description: options.description,
|
|
30989
|
+
modules,
|
|
30990
|
+
wait: false,
|
|
30991
|
+
projectId: linkedProjectId,
|
|
30992
|
+
onProjectResolved,
|
|
30993
|
+
paidPlan: true
|
|
30994
|
+
})
|
|
30995
|
+
);
|
|
30996
|
+
assertCreated(first);
|
|
30997
|
+
const paid = await settlePaidPlan(
|
|
30998
|
+
first.projectId,
|
|
30999
|
+
plan2,
|
|
31000
|
+
options,
|
|
31001
|
+
deps
|
|
31002
|
+
);
|
|
31003
|
+
if (paid.status === "awaiting_checkout") {
|
|
31004
|
+
await setProjectId(options.dir, first.projectId);
|
|
31005
|
+
return {
|
|
31006
|
+
projectId: first.projectId,
|
|
31007
|
+
projectName: name,
|
|
31008
|
+
modules,
|
|
31009
|
+
resumed: first.resumed === true,
|
|
31010
|
+
plan: plan2,
|
|
31011
|
+
status: "awaiting_checkout",
|
|
31012
|
+
checkoutUrl: paid.url
|
|
31013
|
+
};
|
|
31014
|
+
}
|
|
31015
|
+
continueWith = first.projectId;
|
|
31016
|
+
}
|
|
30575
31017
|
const raw = await deps.bootstrap({
|
|
30576
31018
|
name,
|
|
30577
31019
|
description: options.description,
|
|
30578
31020
|
modules,
|
|
30579
31021
|
wait: options.wait !== false,
|
|
30580
|
-
projectId:
|
|
30581
|
-
onProjectResolved
|
|
30582
|
-
|
|
30583
|
-
}
|
|
31022
|
+
projectId: continueWith,
|
|
31023
|
+
onProjectResolved,
|
|
31024
|
+
...plan2 !== "free" ? { paidPlan: true } : {}
|
|
30584
31025
|
});
|
|
30585
31026
|
const outcome = parse4(raw);
|
|
30586
31027
|
if (outcome.requiresPlatformPlanSelection) {
|
|
@@ -30590,13 +31031,7 @@ async function createProjectV2(options, deps) {
|
|
|
30590
31031
|
"Pick another name with --name, or choose a plan for that project in the web app."
|
|
30591
31032
|
);
|
|
30592
31033
|
}
|
|
30593
|
-
|
|
30594
|
-
throw new ProjectCreateError(
|
|
30595
|
-
"CREATE_FAILED",
|
|
30596
|
-
`The platform could not create the project: ${outcome.message ?? outcome.status ?? "no answer"}.`,
|
|
30597
|
-
"Nothing was written to this folder. Try again, or check `apitogo status`."
|
|
30598
|
-
);
|
|
30599
|
-
}
|
|
31034
|
+
assertCreated(outcome);
|
|
30600
31035
|
if (outcome.status === "running" || outcome.status === "pending") {
|
|
30601
31036
|
await setProjectId(options.dir, outcome.projectId);
|
|
30602
31037
|
throw new ProjectCreateError(
|
|
@@ -30610,9 +31045,82 @@ async function createProjectV2(options, deps) {
|
|
|
30610
31045
|
projectId: outcome.projectId,
|
|
30611
31046
|
projectName: name,
|
|
30612
31047
|
modules,
|
|
30613
|
-
resumed: outcome.resumed === true
|
|
31048
|
+
resumed: outcome.resumed === true,
|
|
31049
|
+
...plan2 !== "free" ? { plan: plan2 } : {}
|
|
30614
31050
|
};
|
|
30615
31051
|
}
|
|
31052
|
+
function assertCreated(outcome) {
|
|
31053
|
+
if (outcome.status === "failed" || outcome.status === "blocked" || !outcome.projectId) {
|
|
31054
|
+
throw new ProjectCreateError(
|
|
31055
|
+
"CREATE_FAILED",
|
|
31056
|
+
`The platform could not create the project: ${outcome.message ?? outcome.status ?? "no answer"}.`,
|
|
31057
|
+
"Nothing was written to this folder. Try again, or check `apitogo status`."
|
|
31058
|
+
);
|
|
31059
|
+
}
|
|
31060
|
+
}
|
|
31061
|
+
var defaultSleep4 = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
31062
|
+
var label = (plan2) => plan2.charAt(0).toUpperCase() + plan2.slice(1);
|
|
31063
|
+
async function settlePaidPlan(projectId, plan2, options, deps) {
|
|
31064
|
+
const api = deps.platform;
|
|
31065
|
+
if (!api) throw new Error("The platform client has no plan calls.");
|
|
31066
|
+
const sleep5 = api.sleep ?? defaultSleep4;
|
|
31067
|
+
const now = api.now ?? Date.now;
|
|
31068
|
+
const subscription = await api.getSubscription(projectId);
|
|
31069
|
+
const tier = subscription.planTier.toLowerCase();
|
|
31070
|
+
const onThisPlan = tier === plan2;
|
|
31071
|
+
if (onThisPlan && isPaidPlanActive(subscription)) return { status: "paid" };
|
|
31072
|
+
if (!isPlatformPlanUnselected(subscription.planTier) && !onThisPlan) {
|
|
31073
|
+
throw new ProjectCreateError(
|
|
31074
|
+
"PLAN_CHANGE_UNSUPPORTED",
|
|
31075
|
+
`This project is already on the ${label(tier)} plan, so it cannot be created on ${label(plan2)}.`,
|
|
31076
|
+
"Moving a project to another plan is not supported yet. Create a new project with --plan in a new folder, or change the plan in the web app."
|
|
31077
|
+
);
|
|
31078
|
+
}
|
|
31079
|
+
if (!onThisPlan) {
|
|
31080
|
+
let session;
|
|
31081
|
+
try {
|
|
31082
|
+
session = await api.createCheckoutSession(projectId, {
|
|
31083
|
+
planSlug: plan2,
|
|
31084
|
+
...options.skipPayment ? { skipPayment: true } : {}
|
|
31085
|
+
});
|
|
31086
|
+
} catch (error) {
|
|
31087
|
+
if (options.skipPayment && error instanceof PlatformApiError && error.status === 403)
|
|
31088
|
+
throw new ProjectCreateError(
|
|
31089
|
+
"SKIP_PAYMENT_REFUSED",
|
|
31090
|
+
"Only a platform admin may skip the payment.",
|
|
31091
|
+
"Run it without --skip-payment to pay through the platform's checkout."
|
|
31092
|
+
);
|
|
31093
|
+
throw error;
|
|
31094
|
+
}
|
|
31095
|
+
if (session.granted) return { status: "paid" };
|
|
31096
|
+
if (!session.url)
|
|
31097
|
+
throw new ProjectCreateError(
|
|
31098
|
+
"CREATE_FAILED",
|
|
31099
|
+
"The platform gave no checkout link.",
|
|
31100
|
+
"Run the command again, or choose the plan in the web app."
|
|
31101
|
+
);
|
|
31102
|
+
api.say?.(`Pay for the ${label(plan2)} plan here: ${session.url}`);
|
|
31103
|
+
if (options.waitForPayment === false)
|
|
31104
|
+
return { status: "awaiting_checkout", url: session.url };
|
|
31105
|
+
if (options.open !== false)
|
|
31106
|
+
await api.openUrl?.(session.url).catch(() => {
|
|
31107
|
+
});
|
|
31108
|
+
}
|
|
31109
|
+
const timeoutMs = options.paymentTimeoutMs ?? 9e5;
|
|
31110
|
+
const deadline = now() + timeoutMs;
|
|
31111
|
+
for (; ; ) {
|
|
31112
|
+
const current = await api.getSubscription(projectId);
|
|
31113
|
+
if (current.planTier.toLowerCase() === plan2 && isPaidPlanActive(current))
|
|
31114
|
+
return { status: "paid" };
|
|
31115
|
+
if (now() >= deadline)
|
|
31116
|
+
throw new ProjectCreateError(
|
|
31117
|
+
"CHECKOUT_PENDING",
|
|
31118
|
+
`The payment for the ${label(plan2)} plan is not confirmed yet.`,
|
|
31119
|
+
`Finish paying with the link above, then run \`apitogo project create --plan ${plan2}\` again; it continues the same project (project.id ${projectId}).`
|
|
31120
|
+
);
|
|
31121
|
+
await sleep5(api.pollIntervalMs ?? 3e3);
|
|
31122
|
+
}
|
|
31123
|
+
}
|
|
30616
31124
|
function parse4(raw) {
|
|
30617
31125
|
try {
|
|
30618
31126
|
return JSON.parse(raw);
|
|
@@ -30641,9 +31149,22 @@ async function createLinkedProject(argv, dir) {
|
|
|
30641
31149
|
dir,
|
|
30642
31150
|
name: argv.name,
|
|
30643
31151
|
description: argv.description,
|
|
30644
|
-
wait: argv.wait !== false
|
|
31152
|
+
wait: argv.wait !== false,
|
|
31153
|
+
plan: argv.plan ?? "free",
|
|
31154
|
+
skipPayment: argv.skipPayment === true,
|
|
31155
|
+
open: argv.open !== false,
|
|
31156
|
+
// In JSON mode the link is returned and a later run continues; otherwise the command waits for the payment.
|
|
31157
|
+
waitForPayment: !json
|
|
30645
31158
|
},
|
|
30646
31159
|
{
|
|
31160
|
+
platform: {
|
|
31161
|
+
getSubscription: (projectId) => client.getSubscription(projectId),
|
|
31162
|
+
createCheckoutSession: (projectId, body) => client.createCheckoutSession(projectId, body),
|
|
31163
|
+
openUrl: async (url2) => {
|
|
31164
|
+
await open3(url2);
|
|
31165
|
+
},
|
|
31166
|
+
...json ? {} : { say: (message) => printDiagnosticsToConsole(message) }
|
|
31167
|
+
},
|
|
30647
31168
|
bootstrap: (args) => handleBootstrapProject(
|
|
30648
31169
|
client,
|
|
30649
31170
|
config2,
|
|
@@ -30651,7 +31172,7 @@ async function createLinkedProject(argv, dir) {
|
|
|
30651
31172
|
name: args.name,
|
|
30652
31173
|
description: args.description,
|
|
30653
31174
|
modules: args.modules,
|
|
30654
|
-
selectFreePlan: true,
|
|
31175
|
+
selectFreePlan: args.paidPlan !== true,
|
|
30655
31176
|
wait: args.wait,
|
|
30656
31177
|
configureGatewayBaseline: false,
|
|
30657
31178
|
projectId: args.projectId,
|
|
@@ -30666,8 +31187,14 @@ async function createLinkedProject(argv, dir) {
|
|
|
30666
31187
|
console.log(JSON.stringify({ status: "succeeded", ...result }, null, 2));
|
|
30667
31188
|
return;
|
|
30668
31189
|
}
|
|
31190
|
+
if (result.status === "awaiting_checkout") {
|
|
31191
|
+
printResultToConsole(
|
|
31192
|
+
`Project ${result.projectName} (${result.projectId}) is created and linked. Pay for the ${result.plan} plan at ${result.checkoutUrl}, then run \`apitogo project create --plan ${result.plan}\` again to continue.`
|
|
31193
|
+
);
|
|
31194
|
+
return;
|
|
31195
|
+
}
|
|
30669
31196
|
printDiagnosticsToConsole(
|
|
30670
|
-
`Project ${result.projectName} (${result.projectId}) is ready on the free plan${result.modules.length > 0 ? ` with: ${result.modules.join(", ")}` : " (the gateway is created by `apitogo deploy`)"}.`
|
|
31197
|
+
`Project ${result.projectName} (${result.projectId}) is ready on the ${result.plan ?? "free"} plan${result.modules.length > 0 ? ` with: ${result.modules.join(", ")}` : " (the gateway is created by `apitogo deploy`)"}.`
|
|
30671
31198
|
);
|
|
30672
31199
|
printResultToConsole(
|
|
30673
31200
|
"The folder is linked. Run `apitogo deploy` to publish."
|
|
@@ -30697,6 +31224,12 @@ async function createProject(argv) {
|
|
|
30697
31224
|
await createLinkedProject(argv, dir);
|
|
30698
31225
|
return;
|
|
30699
31226
|
}
|
|
31227
|
+
if (argv.plan && argv.plan !== "free") {
|
|
31228
|
+
await printCriticalFailureToConsoleAndExit(
|
|
31229
|
+
"--plan is for config version 2 folders. For a version 1 project use `apitogo deploy --platform-plan`."
|
|
31230
|
+
);
|
|
31231
|
+
return;
|
|
31232
|
+
}
|
|
30700
31233
|
const name = argv.name?.trim();
|
|
30701
31234
|
if (!name) {
|
|
30702
31235
|
await printCriticalFailureToConsoleAndExit(
|
|
@@ -30763,11 +31296,24 @@ var project_create_default = {
|
|
|
30763
31296
|
describe: "Optional project description"
|
|
30764
31297
|
}).option("modules", {
|
|
30765
31298
|
type: "string",
|
|
30766
|
-
describe: "
|
|
31299
|
+
describe: "Version 1 only: comma-separated modules to activate (default: apihub). Version 2 takes them from the config (the portal needs dev-portal) and ignores this option",
|
|
30767
31300
|
default: "apihub"
|
|
31301
|
+
}).option("plan", {
|
|
31302
|
+
type: "string",
|
|
31303
|
+
choices: ["free", "starter", "professional"],
|
|
31304
|
+
describe: "Version 2: the platform plan of the new project (default: free). Starter and Professional are paid through the platform's own checkout, and a project needs one of them to sell paid plans"
|
|
31305
|
+
}).option("open", {
|
|
31306
|
+
type: "boolean",
|
|
31307
|
+
describe: "Open the checkout page in the browser for a paid plan (use --no-open to only print the link)",
|
|
31308
|
+
default: true
|
|
31309
|
+
}).option("skip-payment", {
|
|
31310
|
+
type: "boolean",
|
|
31311
|
+
describe: "Platform admins only: give the paid plan without payment. The platform refuses it for anyone else",
|
|
31312
|
+
default: false,
|
|
31313
|
+
hidden: true
|
|
30768
31314
|
}).option("wait", {
|
|
30769
31315
|
type: "boolean",
|
|
30770
|
-
describe: "Wait
|
|
31316
|
+
describe: "Wait until the hosting from the warm pool is ready before returning, once a platform plan is selected (always so in version 2). Use --no-wait to return sooner",
|
|
30771
31317
|
default: true
|
|
30772
31318
|
}).option("json", {
|
|
30773
31319
|
type: "boolean",
|
|
@@ -31272,7 +31818,7 @@ async function readStdin(input5 = process.stdin) {
|
|
|
31272
31818
|
}
|
|
31273
31819
|
return Buffer.concat(chunks).toString("utf8").replace(/\r?\n$/, "");
|
|
31274
31820
|
}
|
|
31275
|
-
async function
|
|
31821
|
+
async function confirm(question) {
|
|
31276
31822
|
const rl = createInterface6({ input: process.stdin, output: process.stdout });
|
|
31277
31823
|
try {
|
|
31278
31824
|
const answer = (await rl.question(question)).trim().toLowerCase();
|
|
@@ -31388,7 +31934,7 @@ async function secrets(argv) {
|
|
|
31388
31934
|
isTerminal: process.stdin.isTTY === true && !json,
|
|
31389
31935
|
askValue: (prompt) => readHidden(prompt),
|
|
31390
31936
|
readPiped: () => readStdin(),
|
|
31391
|
-
confirm
|
|
31937
|
+
confirm
|
|
31392
31938
|
});
|
|
31393
31939
|
if (json) {
|
|
31394
31940
|
console.log(JSON.stringify({ status: "succeeded", ...result }, null, 2));
|