@lukoweb/apitogo 0.2.29 → 0.2.31
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 +359 -96
- package/dist/declarations/cli/platform/client/platform-api.d.ts +16 -1
- package/dist/declarations/cli/platform/helpers/platform-plan.d.ts +1 -1
- package/docs/cli/commands.md +132 -75
- package/docs/cli/go-live.md +135 -123
- package/docs/cli/walkthrough.md +40 -1
- package/docs/guides/customer-account.md +23 -2
- package/package.json +1 -1
package/dist/cli/cli.js
CHANGED
|
@@ -7092,7 +7092,7 @@ function identityRules(config2, resolved, out) {
|
|
|
7092
7092
|
);
|
|
7093
7093
|
}
|
|
7094
7094
|
}
|
|
7095
|
-
for (const [list2,
|
|
7095
|
+
for (const [list2, label2] of [
|
|
7096
7096
|
[identity.connections, "connections"],
|
|
7097
7097
|
[identity.clients, "clients"]
|
|
7098
7098
|
]) {
|
|
@@ -7101,8 +7101,8 @@ function identityRules(config2, resolved, out) {
|
|
|
7101
7101
|
if (seen.has(item.id)) {
|
|
7102
7102
|
out.error({
|
|
7103
7103
|
code: "IDENTITY_DUPLICATE_ID",
|
|
7104
|
-
path: `identity.${
|
|
7105
|
-
message: `the id "${item.id}" is used more than once in identity.${
|
|
7104
|
+
path: `identity.${label2}[${index}].id`,
|
|
7105
|
+
message: `the id "${item.id}" is used more than once in identity.${label2}.`
|
|
7106
7106
|
});
|
|
7107
7107
|
}
|
|
7108
7108
|
seen.add(item.id);
|
|
@@ -9133,7 +9133,9 @@ var PlatformApiClient = class {
|
|
|
9133
9133
|
headers: { "Content-Type": "application/json" },
|
|
9134
9134
|
body: JSON.stringify({
|
|
9135
9135
|
planSlug: body.planSlug,
|
|
9136
|
-
currency: body.currency ?? null
|
|
9136
|
+
currency: body.currency ?? null,
|
|
9137
|
+
// Only a platform admin may skip the payment; the platform refuses it for anyone else.
|
|
9138
|
+
...body.skipPayment ? { skipPayment: true } : {}
|
|
9137
9139
|
})
|
|
9138
9140
|
}
|
|
9139
9141
|
);
|
|
@@ -9431,6 +9433,20 @@ var PlatformApiClient = class {
|
|
|
9431
9433
|
);
|
|
9432
9434
|
return result ?? null;
|
|
9433
9435
|
}
|
|
9436
|
+
/**
|
|
9437
|
+
* Gives the plans backend the tenant's own Stripe settings. `apiKey` and `webhookSecret` are `secret:NAME`
|
|
9438
|
+
* references to project secrets, never values. Queues a job that updates the running backend.
|
|
9439
|
+
*/
|
|
9440
|
+
putDevPortalBilling(projectId, body) {
|
|
9441
|
+
return this.request(
|
|
9442
|
+
"PUT",
|
|
9443
|
+
`/api/v1/projects/${projectId}/dev-portal/billing`,
|
|
9444
|
+
{
|
|
9445
|
+
headers: { "Content-Type": "application/json" },
|
|
9446
|
+
body: JSON.stringify(body)
|
|
9447
|
+
}
|
|
9448
|
+
);
|
|
9449
|
+
}
|
|
9434
9450
|
/** Applies the project's identity to its plans backend as the sign-in provider. Queues a job. */
|
|
9435
9451
|
linkDevPortalIdentity(projectId) {
|
|
9436
9452
|
return this.request(
|
|
@@ -9980,7 +9996,7 @@ import {
|
|
|
9980
9996
|
// package.json
|
|
9981
9997
|
var package_default = {
|
|
9982
9998
|
name: "@lukoweb/apitogo",
|
|
9983
|
-
version: "0.2.
|
|
9999
|
+
version: "0.2.31",
|
|
9984
10000
|
type: "module",
|
|
9985
10001
|
sideEffects: [
|
|
9986
10002
|
"**/*.css",
|
|
@@ -12090,8 +12106,8 @@ var extractRichH1 = (content) => {
|
|
|
12090
12106
|
if (!h1) return void 0;
|
|
12091
12107
|
const children = h1.children;
|
|
12092
12108
|
const hasJsx = children.some(isMdxJsxElement);
|
|
12093
|
-
const
|
|
12094
|
-
return hasJsx ? { label, rich: children } : { label };
|
|
12109
|
+
const label2 = mdastToString(h1).trim();
|
|
12110
|
+
return hasJsx ? { label: label2, rich: children } : { label: label2 };
|
|
12095
12111
|
} catch {
|
|
12096
12112
|
return void 0;
|
|
12097
12113
|
}
|
|
@@ -12152,12 +12168,12 @@ var NavigationResolver = class {
|
|
|
12152
12168
|
return void 0;
|
|
12153
12169
|
}
|
|
12154
12170
|
const richH1 = extractRichH1(content);
|
|
12155
|
-
const
|
|
12171
|
+
const label2 = data.navigation_label ?? data.sidebar_label ?? data.title ?? richH1?.label ?? extractTitleFromContent(content) ?? filePath;
|
|
12156
12172
|
const icon = data.navigation_icon ?? data.sidebar_icon;
|
|
12157
12173
|
const doc = {
|
|
12158
12174
|
type: "doc",
|
|
12159
12175
|
file: filePath,
|
|
12160
|
-
label,
|
|
12176
|
+
label: label2,
|
|
12161
12177
|
icon,
|
|
12162
12178
|
display: data.navigation_display,
|
|
12163
12179
|
categoryLabel,
|
|
@@ -12543,8 +12559,8 @@ var viteApiPlugin = async () => {
|
|
|
12543
12559
|
return acc;
|
|
12544
12560
|
}, /* @__PURE__ */ new Map());
|
|
12545
12561
|
const categoryList = Array.from(categories.entries()).map(
|
|
12546
|
-
([
|
|
12547
|
-
label,
|
|
12562
|
+
([label2, tags]) => ({
|
|
12563
|
+
label: label2,
|
|
12548
12564
|
tags: Array.from(tags)
|
|
12549
12565
|
})
|
|
12550
12566
|
);
|
|
@@ -15137,14 +15153,14 @@ async function readPlansFile(file) {
|
|
|
15137
15153
|
return parsed.data.plans;
|
|
15138
15154
|
}
|
|
15139
15155
|
var BILLING_PERIOD = { month: "monthly", year: "annual" };
|
|
15140
|
-
function toPlatformPlans(plans) {
|
|
15156
|
+
function toPlatformPlans(plans, options = {}) {
|
|
15141
15157
|
return plans.map((plan2, index) => {
|
|
15142
15158
|
const paid = plan2.pricingModel !== "custom" && (plan2.priceAmount ?? 0) > 0;
|
|
15143
|
-
if (paid)
|
|
15159
|
+
if (paid && !options.billing)
|
|
15144
15160
|
throw new PlansFileError(
|
|
15145
15161
|
"PLANS_PAID_UNSUPPORTED",
|
|
15146
|
-
`The plan "${plan2.sku}" has a price, and
|
|
15147
|
-
"
|
|
15162
|
+
`The plan "${plan2.sku}" has a price, and plans.billing.provider is not stripe, so nobody could pay for it.`,
|
|
15163
|
+
"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
15164
|
);
|
|
15149
15165
|
const limits = plan2.limits ? {
|
|
15150
15166
|
...plan2.limits.rateLimit ? {
|
|
@@ -15233,7 +15249,11 @@ async function plansFileProblem(resolved) {
|
|
|
15233
15249
|
const plans = resolved.plans;
|
|
15234
15250
|
if (!plans?.enabled) return { wanted: [] };
|
|
15235
15251
|
try {
|
|
15236
|
-
return {
|
|
15252
|
+
return {
|
|
15253
|
+
wanted: toPlatformPlans(await readPlansFile(plans.file), {
|
|
15254
|
+
billing: plans.billing.provider === "stripe"
|
|
15255
|
+
})
|
|
15256
|
+
};
|
|
15237
15257
|
} catch (error) {
|
|
15238
15258
|
if (!(error instanceof PlansFileError)) throw error;
|
|
15239
15259
|
return {
|
|
@@ -15483,7 +15503,7 @@ async function runCheckV2(dir, args = {}, deps = {}) {
|
|
|
15483
15503
|
...report
|
|
15484
15504
|
};
|
|
15485
15505
|
}
|
|
15486
|
-
var line = (
|
|
15506
|
+
var line = (label2, issue4) => `${label2}: ${formatConfigIssue(issue4)}`;
|
|
15487
15507
|
function formatCheckV2(output4) {
|
|
15488
15508
|
const lines = [
|
|
15489
15509
|
...output4.errors.map((issue4) => line("error", issue4)),
|
|
@@ -17126,11 +17146,11 @@ function buildThinWrapperWithCustomPages(tsxRoutePaths) {
|
|
|
17126
17146
|
const customPages = tsxRoutePaths.map((routePath) => {
|
|
17127
17147
|
const slug = routePath.replace(/^\//, "");
|
|
17128
17148
|
const name = pageComponentName(slug);
|
|
17129
|
-
const
|
|
17149
|
+
const label2 = labelFromRoutePath(routePath);
|
|
17130
17150
|
return ` {
|
|
17131
17151
|
type: "custom-page",
|
|
17132
17152
|
path: ${JSON.stringify(routePath)},
|
|
17133
|
-
label: ${JSON.stringify(
|
|
17153
|
+
label: ${JSON.stringify(label2)},
|
|
17134
17154
|
element: <${name} />,
|
|
17135
17155
|
},`;
|
|
17136
17156
|
}).join("\n");
|
|
@@ -17213,7 +17233,7 @@ function ensureHeaderDocsNavigation(config2, docRoutes2) {
|
|
|
17213
17233
|
}
|
|
17214
17234
|
return { ...config2, header: { ...header, navigation } };
|
|
17215
17235
|
}
|
|
17216
|
-
function ensureHeaderApiReferenceNavigation(config2, to,
|
|
17236
|
+
function ensureHeaderApiReferenceNavigation(config2, to, label2 = HEADER_API_REFERENCE_LABEL) {
|
|
17217
17237
|
const current = readHeaderNavigation(config2);
|
|
17218
17238
|
if (!current) return config2;
|
|
17219
17239
|
const { header, navigation } = current;
|
|
@@ -17221,7 +17241,7 @@ function ensureHeaderApiReferenceNavigation(config2, to, label = HEADER_API_REFE
|
|
|
17221
17241
|
return config2;
|
|
17222
17242
|
}
|
|
17223
17243
|
const docsIndex = navigation.findIndex(isDocsItem);
|
|
17224
|
-
const item = { label, to };
|
|
17244
|
+
const item = { label: label2, to };
|
|
17225
17245
|
if (docsIndex >= 0) {
|
|
17226
17246
|
navigation.splice(docsIndex + 1, 0, item);
|
|
17227
17247
|
} else {
|
|
@@ -19555,10 +19575,10 @@ ${importStatement}${content.slice(lastImportEnd)}`;
|
|
|
19555
19575
|
return `${importStatement}
|
|
19556
19576
|
${content}`;
|
|
19557
19577
|
}
|
|
19558
|
-
function formatCustomPageNavigationEntry(slug,
|
|
19578
|
+
function formatCustomPageNavigationEntry(slug, label2, indent) {
|
|
19559
19579
|
const componentName = pageComponentName2(slug);
|
|
19560
19580
|
const inner = `${indent} `;
|
|
19561
|
-
const labelLine =
|
|
19581
|
+
const labelLine = label2?.trim() ? `${inner}label: ${JSON.stringify(label2.trim())},
|
|
19562
19582
|
` : "";
|
|
19563
19583
|
return `${indent}{
|
|
19564
19584
|
${inner}type: "custom-page",
|
|
@@ -19566,21 +19586,21 @@ ${inner}path: "/${normalizePageSlug2(slug)}",
|
|
|
19566
19586
|
` + labelLine + `${inner}element: <${componentName} />,
|
|
19567
19587
|
${indent}},`;
|
|
19568
19588
|
}
|
|
19569
|
-
function formatDocumentationCategoryWithPage(slug,
|
|
19589
|
+
function formatDocumentationCategoryWithPage(slug, label2, indent) {
|
|
19570
19590
|
const inner = `${indent} `;
|
|
19571
19591
|
const itemIndent = `${inner} `;
|
|
19572
19592
|
return `${indent}{
|
|
19573
19593
|
${inner}type: "category",
|
|
19574
19594
|
${inner}label: ${JSON.stringify(DOCUMENTATION_NAV_LABEL2)},
|
|
19575
19595
|
${inner}items: [
|
|
19576
|
-
${formatCustomPageNavigationEntry(slug,
|
|
19596
|
+
${formatCustomPageNavigationEntry(slug, label2, itemIndent)}
|
|
19577
19597
|
${inner}],
|
|
19578
19598
|
${indent}},`;
|
|
19579
19599
|
}
|
|
19580
|
-
function findMatchingArrayClose(content,
|
|
19600
|
+
function findMatchingArrayClose(content, open4) {
|
|
19581
19601
|
let depth = 0;
|
|
19582
19602
|
let inString = null;
|
|
19583
|
-
for (let i =
|
|
19603
|
+
for (let i = open4; i < content.length; i++) {
|
|
19584
19604
|
const char = content[i];
|
|
19585
19605
|
if (inString) {
|
|
19586
19606
|
if (char === "\\") {
|
|
@@ -19619,8 +19639,8 @@ function findDocumentationCategoryItemsRange(content, from, to) {
|
|
|
19619
19639
|
if (!itemsMatch) {
|
|
19620
19640
|
return null;
|
|
19621
19641
|
}
|
|
19622
|
-
const
|
|
19623
|
-
const close3 = findMatchingArrayClose(content,
|
|
19642
|
+
const open4 = afterLabel + itemsMatch.index + itemsMatch[0].length - 1;
|
|
19643
|
+
const close3 = findMatchingArrayClose(content, open4);
|
|
19624
19644
|
if (close3 === null || close3 > to) {
|
|
19625
19645
|
return null;
|
|
19626
19646
|
}
|
|
@@ -19631,7 +19651,7 @@ function findDocumentationCategoryItemsRange(content, from, to) {
|
|
|
19631
19651
|
const indentMatch = /^[ \t]*/.exec(content.slice(lineStart));
|
|
19632
19652
|
const itemsIndent = indentMatch?.[0] ?? "";
|
|
19633
19653
|
const entryIndent = `${itemsIndent} `;
|
|
19634
|
-
return { open:
|
|
19654
|
+
return { open: open4, close: close3, indent: entryIndent };
|
|
19635
19655
|
}
|
|
19636
19656
|
function appendEntryToArrayRange(content, range, entry) {
|
|
19637
19657
|
const before = content.slice(0, range.close);
|
|
@@ -19664,7 +19684,7 @@ ${normalizedEntry}
|
|
|
19664
19684
|
${range.indent}${trimmedInner}
|
|
19665
19685
|
${closeIndent}${content.slice(range.close)}`;
|
|
19666
19686
|
}
|
|
19667
|
-
function appendCustomPageToDirectBuildExport(content, slug,
|
|
19687
|
+
function appendCustomPageToDirectBuildExport(content, slug, label2) {
|
|
19668
19688
|
const exportPrefix = "export default ";
|
|
19669
19689
|
const callPrefix = "buildApitogoConfig(";
|
|
19670
19690
|
const exportIndex = content.indexOf(`${exportPrefix}${callPrefix}`);
|
|
@@ -19672,11 +19692,11 @@ function appendCustomPageToDirectBuildExport(content, slug, label) {
|
|
|
19672
19692
|
return null;
|
|
19673
19693
|
}
|
|
19674
19694
|
const callStart = exportIndex + exportPrefix.length;
|
|
19675
|
-
const
|
|
19695
|
+
const open4 = callStart + callPrefix.length - 1;
|
|
19676
19696
|
let depth = 0;
|
|
19677
19697
|
let inString = null;
|
|
19678
19698
|
let close3 = -1;
|
|
19679
|
-
for (let i =
|
|
19699
|
+
for (let i = open4; i < content.length; i++) {
|
|
19680
19700
|
const char = content[i];
|
|
19681
19701
|
if (inString) {
|
|
19682
19702
|
if (char === "\\") {
|
|
@@ -19711,7 +19731,7 @@ function appendCustomPageToDirectBuildExport(content, slug, label) {
|
|
|
19711
19731
|
replaceEnd += 1;
|
|
19712
19732
|
}
|
|
19713
19733
|
const buildCall = content.slice(callStart, close3 + 1);
|
|
19714
|
-
const category = formatDocumentationCategoryWithPage(slug,
|
|
19734
|
+
const category = formatDocumentationCategoryWithPage(slug, label2, " ");
|
|
19715
19735
|
const replacement = `const config = ${buildCall};
|
|
19716
19736
|
|
|
19717
19737
|
export default {
|
|
@@ -19737,10 +19757,10 @@ function findNavigationArrayRange(content) {
|
|
|
19737
19757
|
return null;
|
|
19738
19758
|
}
|
|
19739
19759
|
const indent = match[1] ?? "";
|
|
19740
|
-
const
|
|
19760
|
+
const open4 = match.index + match[0].length - 1;
|
|
19741
19761
|
let depth = 0;
|
|
19742
19762
|
let inString = null;
|
|
19743
|
-
for (let i =
|
|
19763
|
+
for (let i = open4; i < content.length; i++) {
|
|
19744
19764
|
const char = content[i];
|
|
19745
19765
|
if (inString) {
|
|
19746
19766
|
if (char === "\\") {
|
|
@@ -19759,13 +19779,13 @@ function findNavigationArrayRange(content) {
|
|
|
19759
19779
|
} else if (char === "]") {
|
|
19760
19780
|
depth -= 1;
|
|
19761
19781
|
if (depth === 0) {
|
|
19762
|
-
return { open:
|
|
19782
|
+
return { open: open4, close: i, indent };
|
|
19763
19783
|
}
|
|
19764
19784
|
}
|
|
19765
19785
|
}
|
|
19766
19786
|
return null;
|
|
19767
19787
|
}
|
|
19768
|
-
function appendCustomPageToNavigation(content, slug,
|
|
19788
|
+
function appendCustomPageToNavigation(content, slug, label2, _options) {
|
|
19769
19789
|
const pagePath = `"/${normalizePageSlug2(slug)}"`;
|
|
19770
19790
|
if (content.includes(`path: ${pagePath}`)) {
|
|
19771
19791
|
return content;
|
|
@@ -19773,7 +19793,7 @@ function appendCustomPageToNavigation(content, slug, label, _options) {
|
|
|
19773
19793
|
const rewrittenBuildExport = appendCustomPageToDirectBuildExport(
|
|
19774
19794
|
content,
|
|
19775
19795
|
slug,
|
|
19776
|
-
|
|
19796
|
+
label2
|
|
19777
19797
|
);
|
|
19778
19798
|
if (rewrittenBuildExport) {
|
|
19779
19799
|
return rewrittenBuildExport;
|
|
@@ -19790,14 +19810,14 @@ function appendCustomPageToNavigation(content, slug, label, _options) {
|
|
|
19790
19810
|
if (docsItems) {
|
|
19791
19811
|
const entry = formatCustomPageNavigationEntry(
|
|
19792
19812
|
slug,
|
|
19793
|
-
|
|
19813
|
+
label2,
|
|
19794
19814
|
docsItems.indent
|
|
19795
19815
|
);
|
|
19796
19816
|
return appendEntryToArrayRange(content, docsItems, entry);
|
|
19797
19817
|
}
|
|
19798
19818
|
const category = formatDocumentationCategoryWithPage(
|
|
19799
19819
|
slug,
|
|
19800
|
-
|
|
19820
|
+
label2,
|
|
19801
19821
|
`${range.indent} `
|
|
19802
19822
|
);
|
|
19803
19823
|
return prependEntryToArrayRange(
|
|
@@ -19806,11 +19826,11 @@ function appendCustomPageToNavigation(content, slug, label, _options) {
|
|
|
19806
19826
|
category
|
|
19807
19827
|
);
|
|
19808
19828
|
}
|
|
19809
|
-
function patchApitogoConfigForNewPage(content, slug,
|
|
19829
|
+
function patchApitogoConfigForNewPage(content, slug, label2, options) {
|
|
19810
19830
|
const withNavigation = appendCustomPageToNavigation(
|
|
19811
19831
|
content,
|
|
19812
19832
|
slug,
|
|
19813
|
-
|
|
19833
|
+
label2,
|
|
19814
19834
|
options
|
|
19815
19835
|
);
|
|
19816
19836
|
if (withNavigation === content) {
|
|
@@ -19818,13 +19838,13 @@ function patchApitogoConfigForNewPage(content, slug, label, options) {
|
|
|
19818
19838
|
}
|
|
19819
19839
|
return ensurePageImport(withNavigation, pageComponentName2(slug), slug);
|
|
19820
19840
|
}
|
|
19821
|
-
async function wireNewDevPortalPageInConfig(sourceDirectory, slug,
|
|
19841
|
+
async function wireNewDevPortalPageInConfig(sourceDirectory, slug, label2) {
|
|
19822
19842
|
const configPath = await resolveApitogoConfigPath(sourceDirectory);
|
|
19823
19843
|
if (!configPath) {
|
|
19824
19844
|
return { updated: false };
|
|
19825
19845
|
}
|
|
19826
19846
|
const original = await readFile20(configPath, "utf8");
|
|
19827
|
-
const patched = patchApitogoConfigForNewPage(original, slug,
|
|
19847
|
+
const patched = patchApitogoConfigForNewPage(original, slug, label2, {
|
|
19828
19848
|
typescript: configPath.endsWith(".tsx")
|
|
19829
19849
|
});
|
|
19830
19850
|
if (patched === original) {
|
|
@@ -21805,8 +21825,8 @@ function createScopedConsoleProgressReporter(stream, stepLabel, shared) {
|
|
|
21805
21825
|
}
|
|
21806
21826
|
},
|
|
21807
21827
|
isCancelled: () => false,
|
|
21808
|
-
child(
|
|
21809
|
-
return createScopedConsoleProgressReporter(stream,
|
|
21828
|
+
child(label2) {
|
|
21829
|
+
return createScopedConsoleProgressReporter(stream, label2, shared);
|
|
21810
21830
|
}
|
|
21811
21831
|
};
|
|
21812
21832
|
const baseOnNewMessages = reporter.onNewMessages.bind(reporter);
|
|
@@ -27904,15 +27924,41 @@ var PlansDeployError = class extends ApitogoError {
|
|
|
27904
27924
|
this.name = "PlansDeployError";
|
|
27905
27925
|
}
|
|
27906
27926
|
};
|
|
27907
|
-
var PLANS_STEPS = [
|
|
27927
|
+
var PLANS_STEPS = [
|
|
27928
|
+
"catalog",
|
|
27929
|
+
"backend",
|
|
27930
|
+
"billing",
|
|
27931
|
+
"identity-link"
|
|
27932
|
+
];
|
|
27908
27933
|
var defaultSleep3 = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
27909
27934
|
var BACKEND_MODULE = "dev-portal";
|
|
27910
27935
|
var PLANS_CAPABILITIES = { catalog: true, keys: true, usage: true };
|
|
27936
|
+
var STRIPE_WEBHOOK_EVENTS2 = [
|
|
27937
|
+
"checkout.session.completed",
|
|
27938
|
+
"customer.subscription.updated",
|
|
27939
|
+
"customer.subscription.deleted",
|
|
27940
|
+
"invoice.payment_failed"
|
|
27941
|
+
];
|
|
27942
|
+
function billingNotes(billing) {
|
|
27943
|
+
const notes = [
|
|
27944
|
+
billing.mode === "live" ? "Stripe: LIVE mode. Customers pay with real money." : "Stripe: test mode. Use Stripe's test cards; no real money moves."
|
|
27945
|
+
];
|
|
27946
|
+
notes.push(
|
|
27947
|
+
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.`
|
|
27948
|
+
);
|
|
27949
|
+
return notes;
|
|
27950
|
+
}
|
|
27911
27951
|
async function plansInputs(resolved, projectId) {
|
|
27912
27952
|
const plans = resolved.plans;
|
|
27913
27953
|
if (!plans) throw new Error("plans is not in the config.");
|
|
27914
|
-
const
|
|
27954
|
+
const billing = plans.billing;
|
|
27955
|
+
const wanted = toPlatformPlans(await readPlansFile(plans.file), {
|
|
27956
|
+
billing: billing.provider === "stripe"
|
|
27957
|
+
});
|
|
27915
27958
|
const manifest = await readManifest(resolved.state);
|
|
27959
|
+
const ledger = await readLedger(resolved.state);
|
|
27960
|
+
const billingOn = billing.provider === "stripe";
|
|
27961
|
+
const billingStep = billingOn || stepOf(ledger, "plans", "billing") !== void 0;
|
|
27916
27962
|
const identity = manifest?.services.identity;
|
|
27917
27963
|
const ownIdentity = Boolean(
|
|
27918
27964
|
identity?.provider === "apitogo" && identity.issuer
|
|
@@ -27923,6 +27969,16 @@ async function plansInputs(resolved, projectId) {
|
|
|
27923
27969
|
ownIdentity,
|
|
27924
27970
|
catalogHash: stableJson({ projectId, plans: wanted }),
|
|
27925
27971
|
backendHash: stableJson({ projectId }),
|
|
27972
|
+
billingStep,
|
|
27973
|
+
// Only the references to the project's secrets: a rotated value is applied with --force.
|
|
27974
|
+
billingHash: stableJson({
|
|
27975
|
+
projectId,
|
|
27976
|
+
provider: billing.provider,
|
|
27977
|
+
apiKey: billingOn ? billing.apiKey : void 0,
|
|
27978
|
+
webhookSecret: billingOn ? billing.webhookSecret : void 0,
|
|
27979
|
+
successUrl: billingOn ? billing.successUrl : void 0,
|
|
27980
|
+
cancelUrl: billingOn ? billing.cancelUrl : void 0
|
|
27981
|
+
}),
|
|
27926
27982
|
linkHash: stableJson({
|
|
27927
27983
|
projectId,
|
|
27928
27984
|
issuer: identity?.issuer,
|
|
@@ -27959,6 +28015,17 @@ function refused(error) {
|
|
|
27959
28015
|
}
|
|
27960
28016
|
throw error;
|
|
27961
28017
|
}
|
|
28018
|
+
function billingRefused(error) {
|
|
28019
|
+
if (error instanceof PlatformApiError && error.status === 400) {
|
|
28020
|
+
const lines = describePlatformProblem(error);
|
|
28021
|
+
throw new PlansDeployError(
|
|
28022
|
+
"PLANS_BILLING_REJECTED",
|
|
28023
|
+
`The platform refused the billing settings: ${lines.join(" ") || error.message}`,
|
|
28024
|
+
"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_."
|
|
28025
|
+
);
|
|
28026
|
+
}
|
|
28027
|
+
throw error;
|
|
28028
|
+
}
|
|
27962
28029
|
async function deployPlans(resolved, projectId, deps, options = {}) {
|
|
27963
28030
|
const plans = resolved.plans;
|
|
27964
28031
|
if (!plans) throw new Error("plans is not in the config.");
|
|
@@ -27968,7 +28035,16 @@ async function deployPlans(resolved, projectId, deps, options = {}) {
|
|
|
27968
28035
|
const intervalMs = deps.pollIntervalMs ?? 2e3;
|
|
27969
28036
|
const backendTimeoutMs = deps.backendTimeoutMs ?? 9e5;
|
|
27970
28037
|
const linkTimeoutMs = deps.linkTimeoutMs ?? 6e5;
|
|
27971
|
-
const {
|
|
28038
|
+
const {
|
|
28039
|
+
wanted,
|
|
28040
|
+
manifest,
|
|
28041
|
+
ownIdentity,
|
|
28042
|
+
billingStep,
|
|
28043
|
+
billingHash,
|
|
28044
|
+
catalogHash,
|
|
28045
|
+
backendHash,
|
|
28046
|
+
linkHash
|
|
28047
|
+
} = await plansInputs(resolved, projectId);
|
|
27972
28048
|
const catalogStep = {
|
|
27973
28049
|
name: "catalog",
|
|
27974
28050
|
hash: () => catalogHash,
|
|
@@ -28055,35 +28131,73 @@ async function deployPlans(resolved, projectId, deps, options = {}) {
|
|
|
28055
28131
|
},
|
|
28056
28132
|
stillValid: async () => Boolean(await backendOf())
|
|
28057
28133
|
};
|
|
28134
|
+
const waitForBackendUpdate = async (jobId, what) => {
|
|
28135
|
+
const deadline = now() + linkTimeoutMs;
|
|
28136
|
+
for (; ; ) {
|
|
28137
|
+
const job = (await client.listProvisioningJobs(projectId)).jobs.find(
|
|
28138
|
+
(item) => item.id === jobId
|
|
28139
|
+
);
|
|
28140
|
+
const status2 = job?.status.toLowerCase();
|
|
28141
|
+
if (status2?.startsWith("completed")) return { jobId };
|
|
28142
|
+
if (status2 === "failed")
|
|
28143
|
+
throw new PlansDeployError(
|
|
28144
|
+
`${what.code}_FAILED`,
|
|
28145
|
+
`Applying ${what.text} to the plans backend failed: ${job?.errorMessage ?? "no reason given"}.`,
|
|
28146
|
+
"Run deploy again; it is applied again."
|
|
28147
|
+
);
|
|
28148
|
+
if (now() >= deadline)
|
|
28149
|
+
throw new PlansDeployError(
|
|
28150
|
+
`${what.code}_TIMEOUT`,
|
|
28151
|
+
`Applying ${what.text} did not finish within ${Math.round(linkTimeoutMs / 1e3)} seconds (state: ${status2 ?? "unknown"}).`,
|
|
28152
|
+
"Run deploy again to keep waiting."
|
|
28153
|
+
);
|
|
28154
|
+
await sleep5(intervalMs);
|
|
28155
|
+
}
|
|
28156
|
+
};
|
|
28157
|
+
let billingNote;
|
|
28158
|
+
const billingStepDef = {
|
|
28159
|
+
name: "billing",
|
|
28160
|
+
hash: () => billingHash,
|
|
28161
|
+
run: async () => {
|
|
28162
|
+
const body = plans.billing.provider === "stripe" ? {
|
|
28163
|
+
provider: "stripe",
|
|
28164
|
+
apiKey: plans.billing.apiKey,
|
|
28165
|
+
webhookSecret: plans.billing.webhookSecret,
|
|
28166
|
+
successUrl: plans.billing.successUrl,
|
|
28167
|
+
cancelUrl: plans.billing.cancelUrl
|
|
28168
|
+
} : { provider: "none" };
|
|
28169
|
+
let queued;
|
|
28170
|
+
try {
|
|
28171
|
+
queued = await client.putDevPortalBilling(projectId, body);
|
|
28172
|
+
} catch (error) {
|
|
28173
|
+
billingRefused(error);
|
|
28174
|
+
}
|
|
28175
|
+
const done = await waitForBackendUpdate(queued.jobId, {
|
|
28176
|
+
code: "PLANS_BILLING",
|
|
28177
|
+
text: "the billing settings"
|
|
28178
|
+
});
|
|
28179
|
+
if (plans.billing.provider === "stripe")
|
|
28180
|
+
billingNote = billingNotes(queued);
|
|
28181
|
+
return { ...done, mode: queued.mode };
|
|
28182
|
+
}
|
|
28183
|
+
};
|
|
28058
28184
|
const linkStep = {
|
|
28059
28185
|
name: "identity-link",
|
|
28060
28186
|
hash: () => linkHash,
|
|
28061
28187
|
run: async () => {
|
|
28062
28188
|
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
|
-
}
|
|
28189
|
+
return waitForBackendUpdate(queued.jobId, {
|
|
28190
|
+
code: "PLANS_LINK",
|
|
28191
|
+
text: "the identity"
|
|
28192
|
+
});
|
|
28084
28193
|
}
|
|
28085
28194
|
};
|
|
28086
|
-
const steps = [
|
|
28195
|
+
const steps = [
|
|
28196
|
+
catalogStep,
|
|
28197
|
+
backendStep,
|
|
28198
|
+
...billingStep ? [billingStepDef] : [],
|
|
28199
|
+
...ownIdentity ? [linkStep] : []
|
|
28200
|
+
];
|
|
28087
28201
|
const outcomes = await runPipeline(
|
|
28088
28202
|
resolved.state,
|
|
28089
28203
|
"plans",
|
|
@@ -28108,7 +28222,7 @@ async function deployPlans(resolved, projectId, deps, options = {}) {
|
|
|
28108
28222
|
apiBaseUrl,
|
|
28109
28223
|
billing: plans.billing.provider,
|
|
28110
28224
|
catalogRevision: catalog && !catalog.skipped ? previous + 1 : Math.max(previous, 1),
|
|
28111
|
-
capabilities: PLANS_CAPABILITIES
|
|
28225
|
+
capabilities: plans.billing.provider === "stripe" ? { ...PLANS_CAPABILITIES, checkout: true } : PLANS_CAPABILITIES
|
|
28112
28226
|
})
|
|
28113
28227
|
);
|
|
28114
28228
|
}
|
|
@@ -28119,7 +28233,8 @@ async function deployPlans(resolved, projectId, deps, options = {}) {
|
|
|
28119
28233
|
step: outcome.step,
|
|
28120
28234
|
skipped: outcome.skipped
|
|
28121
28235
|
})),
|
|
28122
|
-
apiBaseUrl
|
|
28236
|
+
apiBaseUrl,
|
|
28237
|
+
...billingNote ? { notes: billingNote } : {}
|
|
28123
28238
|
};
|
|
28124
28239
|
}
|
|
28125
28240
|
|
|
@@ -28238,14 +28353,18 @@ async function planPlansService(resolved, ledger) {
|
|
|
28238
28353
|
const hashes = {
|
|
28239
28354
|
catalog: projectId ? inputs.catalogHash : void 0,
|
|
28240
28355
|
backend: projectId ? inputs.backendHash : void 0,
|
|
28356
|
+
billing: projectId ? inputs.billingHash : void 0,
|
|
28241
28357
|
"identity-link": projectId ? inputs.linkHash : void 0
|
|
28242
28358
|
};
|
|
28243
28359
|
const steps = PLANS_STEPS.filter(
|
|
28244
|
-
(name) => name !== "identity-link" || inputs.ownIdentity
|
|
28360
|
+
(name) => (name !== "identity-link" || inputs.ownIdentity) && (name !== "billing" || inputs.billingStep)
|
|
28245
28361
|
);
|
|
28246
28362
|
const details = [
|
|
28247
28363
|
...inputs.wanted.length === 0 ? ["plans: none in the file"] : inputs.wanted.map((plan2) => `plan ${plan2.sku}: ${plan2.displayName}`),
|
|
28248
28364
|
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",
|
|
28365
|
+
...inputs.billingStep ? [
|
|
28366
|
+
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"
|
|
28367
|
+
] : [],
|
|
28249
28368
|
...steps.map(
|
|
28250
28369
|
(name) => `step ${name}: ${stepState(stepOf(ledger, "plans", name), hashes[name])}`
|
|
28251
28370
|
)
|
|
@@ -28486,7 +28605,7 @@ function describePolicies(gateway) {
|
|
|
28486
28605
|
function dryRunLines(dryRun) {
|
|
28487
28606
|
const changes = dryRun.changes;
|
|
28488
28607
|
if (!changes) return [];
|
|
28489
|
-
const list2 = (
|
|
28608
|
+
const list2 = (label2, items) => items.length > 0 ? [` ${label2}: ${items.join(", ")}`] : [];
|
|
28490
28609
|
if (changes.wouldCreate)
|
|
28491
28610
|
return [
|
|
28492
28611
|
"platform dry run: the gateway does not exist yet; everything would be created",
|
|
@@ -29074,7 +29193,8 @@ async function deployPlansService(options, deps, projectId, named) {
|
|
|
29074
29193
|
service: "plans",
|
|
29075
29194
|
status: result.status,
|
|
29076
29195
|
steps: result.steps,
|
|
29077
|
-
url: result.apiBaseUrl
|
|
29196
|
+
url: result.apiBaseUrl,
|
|
29197
|
+
...result.notes ? { notes: result.notes } : {}
|
|
29078
29198
|
};
|
|
29079
29199
|
}
|
|
29080
29200
|
async function deployPortal(options, deps, projectId) {
|
|
@@ -29208,10 +29328,10 @@ var deploy_default = {
|
|
|
29208
29328
|
describe: "Override project GUID"
|
|
29209
29329
|
}).option("project-name", {
|
|
29210
29330
|
type: "string",
|
|
29211
|
-
describe: "
|
|
29331
|
+
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
29332
|
}).option("platform-plan", {
|
|
29213
29333
|
type: "string",
|
|
29214
|
-
describe: "
|
|
29334
|
+
describe: "Version 1: platform hosting tier. Version 2 ignores it: a project made by `apitogo project create` is on the Free plan",
|
|
29215
29335
|
choices: ["free", "starter", "professional"]
|
|
29216
29336
|
}).option("only", {
|
|
29217
29337
|
type: "string",
|
|
@@ -30546,6 +30666,7 @@ var plan_default = {
|
|
|
30546
30666
|
// src/cli/project/create-handler.ts
|
|
30547
30667
|
init_invariant();
|
|
30548
30668
|
import path93 from "node:path";
|
|
30669
|
+
import open3 from "open";
|
|
30549
30670
|
|
|
30550
30671
|
// src/cli/project/v2.ts
|
|
30551
30672
|
init_invariant();
|
|
@@ -30572,15 +30693,52 @@ async function createProjectV2(options, deps) {
|
|
|
30572
30693
|
);
|
|
30573
30694
|
}
|
|
30574
30695
|
const name = options.name?.trim() || resolved.project.name;
|
|
30696
|
+
const plan2 = options.plan ?? "free";
|
|
30697
|
+
const onProjectResolved = async (projectId) => {
|
|
30698
|
+
await setProjectId(options.dir, projectId);
|
|
30699
|
+
};
|
|
30700
|
+
let continueWith = linkedProjectId;
|
|
30701
|
+
if (plan2 !== "free") {
|
|
30702
|
+
const first = parse4(
|
|
30703
|
+
await deps.bootstrap({
|
|
30704
|
+
name,
|
|
30705
|
+
description: options.description,
|
|
30706
|
+
modules,
|
|
30707
|
+
wait: false,
|
|
30708
|
+
projectId: linkedProjectId,
|
|
30709
|
+
onProjectResolved,
|
|
30710
|
+
paidPlan: true
|
|
30711
|
+
})
|
|
30712
|
+
);
|
|
30713
|
+
assertCreated(first);
|
|
30714
|
+
const paid = await settlePaidPlan(
|
|
30715
|
+
first.projectId,
|
|
30716
|
+
plan2,
|
|
30717
|
+
options,
|
|
30718
|
+
deps
|
|
30719
|
+
);
|
|
30720
|
+
if (paid.status === "awaiting_checkout") {
|
|
30721
|
+
await setProjectId(options.dir, first.projectId);
|
|
30722
|
+
return {
|
|
30723
|
+
projectId: first.projectId,
|
|
30724
|
+
projectName: name,
|
|
30725
|
+
modules,
|
|
30726
|
+
resumed: first.resumed === true,
|
|
30727
|
+
plan: plan2,
|
|
30728
|
+
status: "awaiting_checkout",
|
|
30729
|
+
checkoutUrl: paid.url
|
|
30730
|
+
};
|
|
30731
|
+
}
|
|
30732
|
+
continueWith = first.projectId;
|
|
30733
|
+
}
|
|
30575
30734
|
const raw = await deps.bootstrap({
|
|
30576
30735
|
name,
|
|
30577
30736
|
description: options.description,
|
|
30578
30737
|
modules,
|
|
30579
30738
|
wait: options.wait !== false,
|
|
30580
|
-
projectId:
|
|
30581
|
-
onProjectResolved
|
|
30582
|
-
|
|
30583
|
-
}
|
|
30739
|
+
projectId: continueWith,
|
|
30740
|
+
onProjectResolved,
|
|
30741
|
+
...plan2 !== "free" ? { paidPlan: true } : {}
|
|
30584
30742
|
});
|
|
30585
30743
|
const outcome = parse4(raw);
|
|
30586
30744
|
if (outcome.requiresPlatformPlanSelection) {
|
|
@@ -30590,13 +30748,7 @@ async function createProjectV2(options, deps) {
|
|
|
30590
30748
|
"Pick another name with --name, or choose a plan for that project in the web app."
|
|
30591
30749
|
);
|
|
30592
30750
|
}
|
|
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
|
-
}
|
|
30751
|
+
assertCreated(outcome);
|
|
30600
30752
|
if (outcome.status === "running" || outcome.status === "pending") {
|
|
30601
30753
|
await setProjectId(options.dir, outcome.projectId);
|
|
30602
30754
|
throw new ProjectCreateError(
|
|
@@ -30610,9 +30762,82 @@ async function createProjectV2(options, deps) {
|
|
|
30610
30762
|
projectId: outcome.projectId,
|
|
30611
30763
|
projectName: name,
|
|
30612
30764
|
modules,
|
|
30613
|
-
resumed: outcome.resumed === true
|
|
30765
|
+
resumed: outcome.resumed === true,
|
|
30766
|
+
...plan2 !== "free" ? { plan: plan2 } : {}
|
|
30614
30767
|
};
|
|
30615
30768
|
}
|
|
30769
|
+
function assertCreated(outcome) {
|
|
30770
|
+
if (outcome.status === "failed" || outcome.status === "blocked" || !outcome.projectId) {
|
|
30771
|
+
throw new ProjectCreateError(
|
|
30772
|
+
"CREATE_FAILED",
|
|
30773
|
+
`The platform could not create the project: ${outcome.message ?? outcome.status ?? "no answer"}.`,
|
|
30774
|
+
"Nothing was written to this folder. Try again, or check `apitogo status`."
|
|
30775
|
+
);
|
|
30776
|
+
}
|
|
30777
|
+
}
|
|
30778
|
+
var defaultSleep4 = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
30779
|
+
var label = (plan2) => plan2.charAt(0).toUpperCase() + plan2.slice(1);
|
|
30780
|
+
async function settlePaidPlan(projectId, plan2, options, deps) {
|
|
30781
|
+
const api = deps.platform;
|
|
30782
|
+
if (!api) throw new Error("The platform client has no plan calls.");
|
|
30783
|
+
const sleep5 = api.sleep ?? defaultSleep4;
|
|
30784
|
+
const now = api.now ?? Date.now;
|
|
30785
|
+
const subscription = await api.getSubscription(projectId);
|
|
30786
|
+
const tier = subscription.planTier.toLowerCase();
|
|
30787
|
+
const onThisPlan = tier === plan2;
|
|
30788
|
+
if (onThisPlan && isPaidPlanActive(subscription)) return { status: "paid" };
|
|
30789
|
+
if (!isPlatformPlanUnselected(subscription.planTier) && !onThisPlan) {
|
|
30790
|
+
throw new ProjectCreateError(
|
|
30791
|
+
"PLAN_CHANGE_UNSUPPORTED",
|
|
30792
|
+
`This project is already on the ${label(tier)} plan, so it cannot be created on ${label(plan2)}.`,
|
|
30793
|
+
"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."
|
|
30794
|
+
);
|
|
30795
|
+
}
|
|
30796
|
+
if (!onThisPlan) {
|
|
30797
|
+
let session;
|
|
30798
|
+
try {
|
|
30799
|
+
session = await api.createCheckoutSession(projectId, {
|
|
30800
|
+
planSlug: plan2,
|
|
30801
|
+
...options.skipPayment ? { skipPayment: true } : {}
|
|
30802
|
+
});
|
|
30803
|
+
} catch (error) {
|
|
30804
|
+
if (options.skipPayment && error instanceof PlatformApiError && error.status === 403)
|
|
30805
|
+
throw new ProjectCreateError(
|
|
30806
|
+
"SKIP_PAYMENT_REFUSED",
|
|
30807
|
+
"Only a platform admin may skip the payment.",
|
|
30808
|
+
"Run it without --skip-payment to pay through the platform's checkout."
|
|
30809
|
+
);
|
|
30810
|
+
throw error;
|
|
30811
|
+
}
|
|
30812
|
+
if (session.granted) return { status: "paid" };
|
|
30813
|
+
if (!session.url)
|
|
30814
|
+
throw new ProjectCreateError(
|
|
30815
|
+
"CREATE_FAILED",
|
|
30816
|
+
"The platform gave no checkout link.",
|
|
30817
|
+
"Run the command again, or choose the plan in the web app."
|
|
30818
|
+
);
|
|
30819
|
+
api.say?.(`Pay for the ${label(plan2)} plan here: ${session.url}`);
|
|
30820
|
+
if (options.waitForPayment === false)
|
|
30821
|
+
return { status: "awaiting_checkout", url: session.url };
|
|
30822
|
+
if (options.open !== false)
|
|
30823
|
+
await api.openUrl?.(session.url).catch(() => {
|
|
30824
|
+
});
|
|
30825
|
+
}
|
|
30826
|
+
const timeoutMs = options.paymentTimeoutMs ?? 9e5;
|
|
30827
|
+
const deadline = now() + timeoutMs;
|
|
30828
|
+
for (; ; ) {
|
|
30829
|
+
const current = await api.getSubscription(projectId);
|
|
30830
|
+
if (current.planTier.toLowerCase() === plan2 && isPaidPlanActive(current))
|
|
30831
|
+
return { status: "paid" };
|
|
30832
|
+
if (now() >= deadline)
|
|
30833
|
+
throw new ProjectCreateError(
|
|
30834
|
+
"CHECKOUT_PENDING",
|
|
30835
|
+
`The payment for the ${label(plan2)} plan is not confirmed yet.`,
|
|
30836
|
+
`Finish paying with the link above, then run \`apitogo project create --plan ${plan2}\` again; it continues the same project (project.id ${projectId}).`
|
|
30837
|
+
);
|
|
30838
|
+
await sleep5(api.pollIntervalMs ?? 3e3);
|
|
30839
|
+
}
|
|
30840
|
+
}
|
|
30616
30841
|
function parse4(raw) {
|
|
30617
30842
|
try {
|
|
30618
30843
|
return JSON.parse(raw);
|
|
@@ -30641,9 +30866,22 @@ async function createLinkedProject(argv, dir) {
|
|
|
30641
30866
|
dir,
|
|
30642
30867
|
name: argv.name,
|
|
30643
30868
|
description: argv.description,
|
|
30644
|
-
wait: argv.wait !== false
|
|
30869
|
+
wait: argv.wait !== false,
|
|
30870
|
+
plan: argv.plan ?? "free",
|
|
30871
|
+
skipPayment: argv.skipPayment === true,
|
|
30872
|
+
open: argv.open !== false,
|
|
30873
|
+
// In JSON mode the link is returned and a later run continues; otherwise the command waits for the payment.
|
|
30874
|
+
waitForPayment: !json
|
|
30645
30875
|
},
|
|
30646
30876
|
{
|
|
30877
|
+
platform: {
|
|
30878
|
+
getSubscription: (projectId) => client.getSubscription(projectId),
|
|
30879
|
+
createCheckoutSession: (projectId, body) => client.createCheckoutSession(projectId, body),
|
|
30880
|
+
openUrl: async (url2) => {
|
|
30881
|
+
await open3(url2);
|
|
30882
|
+
},
|
|
30883
|
+
...json ? {} : { say: (message) => printDiagnosticsToConsole(message) }
|
|
30884
|
+
},
|
|
30647
30885
|
bootstrap: (args) => handleBootstrapProject(
|
|
30648
30886
|
client,
|
|
30649
30887
|
config2,
|
|
@@ -30651,7 +30889,7 @@ async function createLinkedProject(argv, dir) {
|
|
|
30651
30889
|
name: args.name,
|
|
30652
30890
|
description: args.description,
|
|
30653
30891
|
modules: args.modules,
|
|
30654
|
-
selectFreePlan: true,
|
|
30892
|
+
selectFreePlan: args.paidPlan !== true,
|
|
30655
30893
|
wait: args.wait,
|
|
30656
30894
|
configureGatewayBaseline: false,
|
|
30657
30895
|
projectId: args.projectId,
|
|
@@ -30666,8 +30904,14 @@ async function createLinkedProject(argv, dir) {
|
|
|
30666
30904
|
console.log(JSON.stringify({ status: "succeeded", ...result }, null, 2));
|
|
30667
30905
|
return;
|
|
30668
30906
|
}
|
|
30907
|
+
if (result.status === "awaiting_checkout") {
|
|
30908
|
+
printResultToConsole(
|
|
30909
|
+
`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.`
|
|
30910
|
+
);
|
|
30911
|
+
return;
|
|
30912
|
+
}
|
|
30669
30913
|
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`)"}.`
|
|
30914
|
+
`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
30915
|
);
|
|
30672
30916
|
printResultToConsole(
|
|
30673
30917
|
"The folder is linked. Run `apitogo deploy` to publish."
|
|
@@ -30697,6 +30941,12 @@ async function createProject(argv) {
|
|
|
30697
30941
|
await createLinkedProject(argv, dir);
|
|
30698
30942
|
return;
|
|
30699
30943
|
}
|
|
30944
|
+
if (argv.plan && argv.plan !== "free") {
|
|
30945
|
+
await printCriticalFailureToConsoleAndExit(
|
|
30946
|
+
"--plan is for config version 2 folders. For a version 1 project use `apitogo deploy --platform-plan`."
|
|
30947
|
+
);
|
|
30948
|
+
return;
|
|
30949
|
+
}
|
|
30700
30950
|
const name = argv.name?.trim();
|
|
30701
30951
|
if (!name) {
|
|
30702
30952
|
await printCriticalFailureToConsoleAndExit(
|
|
@@ -30763,11 +31013,24 @@ var project_create_default = {
|
|
|
30763
31013
|
describe: "Optional project description"
|
|
30764
31014
|
}).option("modules", {
|
|
30765
31015
|
type: "string",
|
|
30766
|
-
describe: "
|
|
31016
|
+
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
31017
|
default: "apihub"
|
|
31018
|
+
}).option("plan", {
|
|
31019
|
+
type: "string",
|
|
31020
|
+
choices: ["free", "starter", "professional"],
|
|
31021
|
+
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"
|
|
31022
|
+
}).option("open", {
|
|
31023
|
+
type: "boolean",
|
|
31024
|
+
describe: "Open the checkout page in the browser for a paid plan (use --no-open to only print the link)",
|
|
31025
|
+
default: true
|
|
31026
|
+
}).option("skip-payment", {
|
|
31027
|
+
type: "boolean",
|
|
31028
|
+
describe: "Platform admins only: give the paid plan without payment. The platform refuses it for anyone else",
|
|
31029
|
+
default: false,
|
|
31030
|
+
hidden: true
|
|
30768
31031
|
}).option("wait", {
|
|
30769
31032
|
type: "boolean",
|
|
30770
|
-
describe: "Wait
|
|
31033
|
+
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
31034
|
default: true
|
|
30772
31035
|
}).option("json", {
|
|
30773
31036
|
type: "boolean",
|