@lukoweb/apitogo 0.1.126 → 0.1.129
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 +59 -15
- package/dist/declarations/cli/platform/client/platform-api.d.ts +6 -0
- package/dist/declarations/cli/platform/helpers/dev-portal-landing.d.ts +1 -0
- package/dist/declarations/cli/platform/helpers/dev-portal-manifest.d.ts +2 -0
- package/dist/declarations/cli/platform/helpers/publish-preflight.d.ts +2 -0
- package/dist/declarations/cli/platform/tools/gateway.d.ts +6 -0
- package/dist/declarations/config/local-manifest.d.ts +20 -0
- package/dist/declarations/config/validators/ModulesSchema.d.ts +20 -0
- package/dist/declarations/config/validators/ZudokuConfig.d.ts +5 -0
- package/dist/flat-config.d.ts +32 -24
- package/docs/configuration/plans.md +26 -0
- package/docs/customization/landing-page.md +159 -0
- package/package.json +155 -10
- package/src/config/apitogo-config-core.ts +2 -0
- package/src/config/local-manifest.ts +17 -0
- package/src/config/validators/ModulesSchema.ts +11 -0
- package/LICENSE.md +0 -18
package/dist/cli/cli.js
CHANGED
|
@@ -736,6 +736,8 @@ var init_apitogo_config_core = __esm({
|
|
|
736
736
|
DEMOMODE: "demoMode",
|
|
737
737
|
DISPLAYNAME: "displayName",
|
|
738
738
|
PRICEAMOUNT: "priceAmount",
|
|
739
|
+
PRICINGMODEL: "pricingModel",
|
|
740
|
+
PRICELABEL: "priceLabel",
|
|
739
741
|
PRICECURRENCY: "priceCurrency",
|
|
740
742
|
BILLINGPERIOD: "billingPeriod",
|
|
741
743
|
LIMITSJSON: "limitsJson",
|
|
@@ -1020,6 +1022,10 @@ var init_ModulesSchema = __esm({
|
|
|
1020
1022
|
sku: z3.string(),
|
|
1021
1023
|
displayName: z3.string(),
|
|
1022
1024
|
priceAmount: z3.number().default(0),
|
|
1025
|
+
pricingModel: z3.enum(["fixed", "custom"]).optional().describe(
|
|
1026
|
+
'"custom" shows a contact-sales form instead of checkout; priceAmount must be 0.'
|
|
1027
|
+
),
|
|
1028
|
+
priceLabel: z3.string().max(64).optional().describe('Text shown instead of the price, e.g. "Custom".'),
|
|
1023
1029
|
priceCurrency: z3.string().trim().toLowerCase().regex(
|
|
1024
1030
|
/^[a-z]{3}$/,
|
|
1025
1031
|
"priceCurrency must be a 3-letter ISO 4217 code (lowercase)"
|
|
@@ -1127,6 +1133,8 @@ function plansToPreviewCatalog(plans) {
|
|
|
1127
1133
|
sku: plan.sku,
|
|
1128
1134
|
displayName: plan.displayName,
|
|
1129
1135
|
priceAmount: plan.priceAmount ?? 0,
|
|
1136
|
+
pricingModel: plan.pricingModel ?? "fixed",
|
|
1137
|
+
...plan.priceLabel ? { priceLabel: plan.priceLabel } : {},
|
|
1130
1138
|
priceCurrency: plan.priceCurrency ?? "usd",
|
|
1131
1139
|
billingPeriod: normalizeBillingPeriod(plan.billingPeriod),
|
|
1132
1140
|
limits: coerceGatewayLimits(plan) ?? null,
|
|
@@ -1175,13 +1183,14 @@ async function readEffectiveManifest(rootDir, _env) {
|
|
|
1175
1183
|
return null;
|
|
1176
1184
|
}
|
|
1177
1185
|
}
|
|
1178
|
-
var BillingPeriodSchema, GatewayLimitsSchema, PriceCurrencySchema, ALLOWED_RUNTIME_VERSIONS, ManifestPlanInputSchema, DevPortalLocalManifestSchema;
|
|
1186
|
+
var BillingPeriodSchema, PlanPricingModelSchema, GatewayLimitsSchema, PriceCurrencySchema, ALLOWED_RUNTIME_VERSIONS, ManifestPlanInputSchema, DevPortalLocalManifestSchema;
|
|
1179
1187
|
var init_local_manifest = __esm({
|
|
1180
1188
|
"src/config/local-manifest.ts"() {
|
|
1181
1189
|
init_apitogo_config_loader();
|
|
1182
1190
|
init_gateway_limits();
|
|
1183
1191
|
init_landing_manifest();
|
|
1184
1192
|
BillingPeriodSchema = z4.enum(["month", "year", "monthly", "annual"]);
|
|
1193
|
+
PlanPricingModelSchema = z4.enum(["fixed", "custom"]);
|
|
1185
1194
|
GatewayLimitsSchema = z4.object({
|
|
1186
1195
|
rateLimit: z4.object({
|
|
1187
1196
|
calls: z4.number(),
|
|
@@ -1212,6 +1221,9 @@ var init_local_manifest = __esm({
|
|
|
1212
1221
|
sku: z4.string().min(1),
|
|
1213
1222
|
displayName: z4.string().min(1),
|
|
1214
1223
|
priceAmount: z4.number().default(0),
|
|
1224
|
+
/** `custom` plans show a contact-sales form instead of checkout. */
|
|
1225
|
+
pricingModel: PlanPricingModelSchema.optional(),
|
|
1226
|
+
priceLabel: z4.string().max(64).optional(),
|
|
1215
1227
|
priceCurrency: PriceCurrencySchema.optional(),
|
|
1216
1228
|
billingPeriod: BillingPeriodSchema.optional(),
|
|
1217
1229
|
limits: GatewayLimitsSchema.optional(),
|
|
@@ -5797,7 +5809,7 @@ import {
|
|
|
5797
5809
|
// package.json
|
|
5798
5810
|
var package_default = {
|
|
5799
5811
|
name: "@lukoweb/apitogo",
|
|
5800
|
-
version: "0.1.
|
|
5812
|
+
version: "0.1.129",
|
|
5801
5813
|
type: "module",
|
|
5802
5814
|
sideEffects: [
|
|
5803
5815
|
"**/*.css",
|
|
@@ -13134,7 +13146,25 @@ function validateManifestForPlatformPublish(manifest) {
|
|
|
13134
13146
|
if (plan.priceAmount !== void 0 && plan.priceAmount < 0) {
|
|
13135
13147
|
issues.push({
|
|
13136
13148
|
field: `plans[${index}].priceAmount`,
|
|
13137
|
-
message:
|
|
13149
|
+
message: 'priceAmount must be >= 0. For negotiated pricing use pricingModel: "custom".'
|
|
13150
|
+
});
|
|
13151
|
+
}
|
|
13152
|
+
if (plan.pricingModel !== void 0 && plan.pricingModel !== "fixed" && plan.pricingModel !== "custom") {
|
|
13153
|
+
issues.push({
|
|
13154
|
+
field: `plans[${index}].pricingModel`,
|
|
13155
|
+
message: "pricingModel must be 'fixed' or 'custom'."
|
|
13156
|
+
});
|
|
13157
|
+
}
|
|
13158
|
+
if (plan.pricingModel === "custom" && (plan.priceAmount ?? 0) !== 0) {
|
|
13159
|
+
issues.push({
|
|
13160
|
+
field: `plans[${index}].priceAmount`,
|
|
13161
|
+
message: "priceAmount must be 0 (or omitted) for custom-priced plans."
|
|
13162
|
+
});
|
|
13163
|
+
}
|
|
13164
|
+
if (plan.priceLabel !== void 0 && plan.priceLabel.length > 64) {
|
|
13165
|
+
issues.push({
|
|
13166
|
+
field: `plans[${index}].priceLabel`,
|
|
13167
|
+
message: "priceLabel must be at most 64 characters."
|
|
13138
13168
|
});
|
|
13139
13169
|
}
|
|
13140
13170
|
const currencyResult = tryNormalizePriceCurrency(plan.priceCurrency);
|
|
@@ -15582,6 +15612,11 @@ function buildPlanItemFromWizard(input5) {
|
|
|
15582
15612
|
priceAmount: input5.priceAmount,
|
|
15583
15613
|
limits: buildLimits(input5.rateLimitCalls, input5.monthlyQuota)
|
|
15584
15614
|
};
|
|
15615
|
+
if (input5.pricingModel === "custom") {
|
|
15616
|
+
item.priceAmount = 0;
|
|
15617
|
+
item.pricingModel = "custom";
|
|
15618
|
+
return item;
|
|
15619
|
+
}
|
|
15585
15620
|
if (input5.priceAmount > 0) {
|
|
15586
15621
|
item.priceCurrency = "usd";
|
|
15587
15622
|
item.billingPeriod = "month";
|
|
@@ -15946,11 +15981,13 @@ async function promptMakeConfigInteractive(options) {
|
|
|
15946
15981
|
printDiagnosticsToConsole("Plan title is required.");
|
|
15947
15982
|
continue;
|
|
15948
15983
|
}
|
|
15949
|
-
const
|
|
15984
|
+
const pricingAnswer = (await ask(
|
|
15950
15985
|
rl,
|
|
15951
|
-
"
|
|
15952
|
-
|
|
15953
|
-
);
|
|
15986
|
+
"Pricing: fixed price or custom (contact sales)? (fixed/custom)",
|
|
15987
|
+
"fixed"
|
|
15988
|
+
)).toLowerCase();
|
|
15989
|
+
const pricingModel = pricingAnswer.startsWith("c") ? "custom" : "fixed";
|
|
15990
|
+
const priceAmount = pricingModel === "custom" ? 0 : await askNonNegativeNumber(rl, "Price (USD/month, 0 for free)", 0);
|
|
15954
15991
|
const rateLimitCalls = await askNonNegativeNumber(
|
|
15955
15992
|
rl,
|
|
15956
15993
|
"Rate limit (calls per minute)",
|
|
@@ -15964,6 +16001,7 @@ async function promptMakeConfigInteractive(options) {
|
|
|
15964
16001
|
const planItem = buildPlanItemFromWizard({
|
|
15965
16002
|
displayName: displayName.trim(),
|
|
15966
16003
|
priceAmount,
|
|
16004
|
+
pricingModel,
|
|
15967
16005
|
rateLimitCalls,
|
|
15968
16006
|
monthlyQuota
|
|
15969
16007
|
});
|
|
@@ -16167,7 +16205,7 @@ async function ensureTsconfigJson(dir) {
|
|
|
16167
16205
|
return true;
|
|
16168
16206
|
}
|
|
16169
16207
|
}
|
|
16170
|
-
var LANDING_PAGE_SOURCE = 'import {\n ApiShowcaseSection,\n FeaturesSection,\n HeroSection,\n LandingShell,\n PlansPreviewSection,\n PricingCtaSection,\n StepsSection,\n} from "@lukoweb/apitogo-module-landing";\n\nexport default function LandingPage() {\n return (\n <LandingShell\n title="Test against a real API before you build one"\n description="Sample API gives you realistic endpoints, authentication, rate limits, and webhooks \u2014 a fully working backend for prototypes, demos, and QA."\n >\n <HeroSection\n hero={{\n badge: "SANDBOX API",\n title: "Test against a real API before you build one",\n description:\n "Sample API gives you realistic endpoints, authentication, rate limits, and webhooks \u2014 a fully working backend for prototypes, demos, and QA. No setup, no infrastructure.",\n }}\n primaryAction={{ label: "Start for free", href: "/signup" }}\n secondaryAction={{ label: "Read the docs", href: "/introduction" }}\n codeExamples={[\n {\n filename: "Create a key",\n language: "bash",\n code: `$ curl -X POST https://api.sampleapi.dev/v1/keys\n# sk_live_9f2c7a1e...`,\n },\n {\n filename: "Call an endpoint",\n language: "bash",\n code: `$ curl https://api.sampleapi.dev/v1/users/42 \\\\\n -H "Authorization: Bearer sk_live_9f2c..."\n# { "id": 42, "name": "Priya Shah", "plan": "pro" }`,\n },\n ]}\n />\n <FeaturesSection\n section={{\n label: "Everything a real API has",\n title: "Built for testing, not just browsing",\n description:\n "Realistic data, live auth, rate limits, and webhooks \u2014 so you can ship with confidence.",\n }}\n features={[\n {\n title: "Realistic mock data",\n description:\n "Users, orders, products, and posts with relationships and pagination, not lorem ipsum.",\n icon: "boxes",\n },\n {\n title: "Real auth flow",\n description:\n "Every request checks a live API key, so you can test error handling and expiry.",\n icon: "shield-check",\n },\n {\n title: "Rate limiting",\n description:\n "Per-plan request limits and 429 responses, so you can build and test backoff logic.",\n icon: "gauge",\n },\n {\n title: "Webhooks",\n description:\n "Trigger test events and inspect delivered payloads and retries.",\n icon: "webhook",\n },\n {\n title: "OpenAPI & docs",\n description:\n "A full OpenAPI spec and reference docs for every endpoint.",\n icon: "book-open",\n },\n {\n title: "Usage analytics",\n description:\n "Track requests, latency, and errors per key from your dashboard.",\n icon: "activity",\n },\n ]}\n />\n <StepsSection\n steps={[\n {\n step: "01",\n title: "Create an account",\n description:\n "Sign up and land straight in your dashboard \u2014 no approval queue.",\n },\n {\n step: "02",\n title: "Generate a key",\n description:\n "Every key is scoped and rate-limited to your plan from the moment it\'s created.",\n },\n {\n step: "03",\n title: "Call any endpoint",\n description:\n "Users, orders, products, posts, and webhooks \u2014 all documented, all live.",\n },\n ]}\n />\n <ApiShowcaseSection\n apiShowcase={{\n title: "Sample endpoints",\n subtitle:\n "A slice of what\'s available \u2014 see the full reference in the docs.",\n browseAllHref: "/api",\n browseAllLabel: "Browse all APIs",\n layout: "list",\n items: [\n {\n name: "List paginated users",\n path: "/v1/users",\n method: "GET",\n summary: "List paginated users",\n },\n {\n name: "Create a mock order",\n path: "/v1/orders",\n method: "POST",\n summary: "Create a mock order",\n },\n {\n name: "Fetch one product",\n path: "/v1/products/{id}",\n method: "GET",\n summary: "Fetch one product",\n },\n {\n name: "List posts with comments",\n path: "/v1/posts",\n method: "GET",\n summary: "List posts with comments",\n },\n {\n name: "Fire a test webhook event",\n path: "/v1/webhooks/test",\n method: "POST",\n summary: "Fire a test webhook event",\n },\n {\n name: "Delete a mock order",\n path: "/v1/orders/{id}",\n method: "DELETE",\n summary: "Delete a mock order",\n },\n ],\n }}\n />\n <PlansPreviewSection\n plans={[\n {\n name: "Free",\n price: "$0",\n unit: "per month",\n blurb: "1,000 requests/mo.",\n },\n {\n name: "Developer",\n price: "$19",\n unit: "per month",\n blurb: "50,000 requests/mo, webhooks included.",\n featured: true,\n },\n {\n name: "Team",\n price: "$79",\n unit: "per month",\n blurb: "500,000 requests/mo, 5 team seats.",\n },\n {\n name: "Enterprise",\n price: "$299",\n unit: "per month",\n blurb: "Unlimited requests, SSO, SLA.",\n },\n ]}\n />\n <PricingCtaSection\n variant="banner"\n pricingCta={{\n title: "Start testing in minutes",\n description:\n "Free tier includes 1,000 requests a month \u2014 no card required.",\n primaryAction: { label: "Start for free", href: "/signup" },\n }}\n />\n </LandingShell>\n );\n}\n';
|
|
16208
|
+
var LANDING_PAGE_SOURCE = 'import {\n ApiShowcaseSection,\n FeaturesSection,\n HeroSection,\n LandingShell,\n PlansPreviewSection,\n PricingCtaSection,\n StepsSection,\n} from "@lukoweb/apitogo-module-landing";\n\nexport default function LandingPage() {\n return (\n <LandingShell\n title="Test against a real API before you build one"\n description="Sample API gives you realistic endpoints, authentication, rate limits, and webhooks \u2014 a fully working backend for prototypes, demos, and QA."\n >\n <HeroSection\n hero={{\n badge: "SANDBOX API",\n title: "Test against a real API before you build one",\n description:\n "Sample API gives you realistic endpoints, authentication, rate limits, and webhooks \u2014 a fully working backend for prototypes, demos, and QA. No setup, no infrastructure.",\n }}\n primaryAction={{ label: "Start for free", href: "/signup" }}\n secondaryAction={{ label: "Read the docs", href: "/introduction" }}\n codeExamples={[\n {\n filename: "Create a key",\n language: "bash",\n code: `$ curl -X POST https://api.sampleapi.dev/v1/keys\n# sk_live_9f2c7a1e...`,\n },\n {\n filename: "Call an endpoint",\n language: "bash",\n code: `$ curl https://api.sampleapi.dev/v1/users/42 \\\\\n -H "Authorization: Bearer sk_live_9f2c..."\n# { "id": 42, "name": "Priya Shah", "plan": "pro" }`,\n },\n ]}\n />\n <FeaturesSection\n section={{\n label: "Everything a real API has",\n title: "Built for testing, not just browsing",\n description:\n "Realistic data, live auth, rate limits, and webhooks \u2014 so you can ship with confidence.",\n }}\n features={[\n {\n title: "Realistic mock data",\n description:\n "Users, orders, products, and posts with relationships and pagination, not lorem ipsum.",\n icon: "boxes",\n },\n {\n title: "Real auth flow",\n description:\n "Every request checks a live API key, so you can test error handling and expiry.",\n icon: "shield-check",\n },\n {\n title: "Rate limiting",\n description:\n "Per-plan request limits and 429 responses, so you can build and test backoff logic.",\n icon: "gauge",\n },\n {\n title: "Webhooks",\n description:\n "Trigger test events and inspect delivered payloads and retries.",\n icon: "webhook",\n },\n {\n title: "OpenAPI & docs",\n description:\n "A full OpenAPI spec and reference docs for every endpoint.",\n icon: "book-open",\n },\n {\n title: "Usage analytics",\n description:\n "Track requests, latency, and errors per key from your dashboard.",\n icon: "activity",\n },\n ]}\n />\n <StepsSection\n steps={[\n {\n step: "01",\n title: "Create an account",\n description:\n "Sign up and land straight in your dashboard \u2014 no approval queue.",\n },\n {\n step: "02",\n title: "Generate a key",\n description:\n "Every key is scoped and rate-limited to your plan from the moment it\'s created.",\n },\n {\n step: "03",\n title: "Call any endpoint",\n description:\n "Users, orders, products, posts, and webhooks \u2014 all documented, all live.",\n },\n ]}\n />\n {/* Static sample list. To show your real API operations from the dev portal\n backend instead, see https://docs.apitogo.com/docs/customization/landing-page#live-apis */}\n <ApiShowcaseSection\n apiShowcase={{\n title: "Sample endpoints",\n subtitle:\n "A slice of what\'s available \u2014 see the full reference in the docs.",\n browseAllHref: "/api",\n browseAllLabel: "Browse all APIs",\n layout: "list",\n items: [\n {\n name: "List paginated users",\n path: "/v1/users",\n method: "GET",\n summary: "List paginated users",\n },\n {\n name: "Create a mock order",\n path: "/v1/orders",\n method: "POST",\n summary: "Create a mock order",\n },\n {\n name: "Fetch one product",\n path: "/v1/products/{id}",\n method: "GET",\n summary: "Fetch one product",\n },\n {\n name: "List posts with comments",\n path: "/v1/posts",\n method: "GET",\n summary: "List posts with comments",\n },\n {\n name: "Fire a test webhook event",\n path: "/v1/webhooks/test",\n method: "POST",\n summary: "Fire a test webhook event",\n },\n {\n name: "Delete a mock order",\n path: "/v1/orders/{id}",\n method: "DELETE",\n summary: "Delete a mock order",\n },\n ],\n }}\n />\n <PlansPreviewSection\n plans={[\n {\n name: "Free",\n price: "$0",\n unit: "per month",\n blurb: "1,000 requests/mo.",\n },\n {\n name: "Developer",\n price: "$19",\n unit: "per month",\n blurb: "50,000 requests/mo, webhooks included.",\n featured: true,\n },\n {\n name: "Team",\n price: "$79",\n unit: "per month",\n blurb: "500,000 requests/mo, 5 team seats.",\n },\n {\n name: "Enterprise",\n price: "$299",\n unit: "per month",\n blurb: "Unlimited requests, SSO, SLA.",\n },\n ]}\n />\n <PricingCtaSection\n variant="banner"\n pricingCta={{\n title: "Start testing in minutes",\n description:\n "Free tier includes 1,000 requests a month \u2014 no card required.",\n primaryAction: { label: "Start for free", href: "/signup" },\n }}\n />\n </LandingShell>\n );\n}\n';
|
|
16171
16209
|
var ACCOUNT_PLANS_PAGE_SOURCE = `export { AccountPlansPage as default } from "@lukoweb/apitogo-plugin-dev-portal-billing";
|
|
16172
16210
|
`;
|
|
16173
16211
|
var DASHBOARD_PAGE_SOURCE = `export { OverviewDashboardPage as default } from "@lukoweb/apitogo-plugin-dev-portal-billing";
|
|
@@ -17203,6 +17241,8 @@ async function handleCreateGatewayPlan(client, config2, args) {
|
|
|
17203
17241
|
limitsJson: args.limitsJson ?? null,
|
|
17204
17242
|
includedActionKeys,
|
|
17205
17243
|
priceAmount: args.priceAmount,
|
|
17244
|
+
pricingModel: args.pricingModel,
|
|
17245
|
+
priceLabel: args.priceLabel,
|
|
17206
17246
|
priceCurrency: args.priceCurrency,
|
|
17207
17247
|
billingPeriod: args.billingPeriod,
|
|
17208
17248
|
sortOrder: args.sortOrder
|
|
@@ -17229,6 +17269,8 @@ function buildUpdateGatewayPlanBody(existing, patch) {
|
|
|
17229
17269
|
limitsJson: patch.limitsJson !== void 0 ? patch.limitsJson : existing.limitsJson ?? null,
|
|
17230
17270
|
includedActionKeys: patch.includedActionKeys !== void 0 ? patch.includedActionKeys : existingActionKeys(existing),
|
|
17231
17271
|
priceAmount: patch.priceAmount ?? existing.priceAmount,
|
|
17272
|
+
pricingModel: patch.pricingModel ?? existing.pricingModel,
|
|
17273
|
+
priceLabel: patch.priceLabel ?? existing.priceLabel ?? void 0,
|
|
17232
17274
|
priceCurrency: patch.priceCurrency ?? existing.priceCurrency,
|
|
17233
17275
|
billingPeriod: patch.billingPeriod ?? existing.billingPeriod,
|
|
17234
17276
|
sortOrder: patch.sortOrder ?? existing.sortOrder
|
|
@@ -17509,6 +17551,9 @@ async function upsertGatewayPlansFromManifest(client, config2, projectId, plans,
|
|
|
17509
17551
|
isActive: true,
|
|
17510
17552
|
limitsJson: planLimitsForPlatform(input5),
|
|
17511
17553
|
priceAmount: input5.priceAmount,
|
|
17554
|
+
// Config is the source of truth: dropping pricingModel/priceLabel reverts to fixed / no label.
|
|
17555
|
+
pricingModel: input5.pricingModel ?? "fixed",
|
|
17556
|
+
priceLabel: input5.priceLabel ?? "",
|
|
17512
17557
|
priceCurrency: input5.priceCurrency,
|
|
17513
17558
|
billingPeriod: input5.billingPeriod ? normalizeBillingPeriod2(input5.billingPeriod) : void 0
|
|
17514
17559
|
};
|
|
@@ -17536,6 +17581,8 @@ async function upsertGatewayPlansFromManifest(client, config2, projectId, plans,
|
|
|
17536
17581
|
sku: input5.sku,
|
|
17537
17582
|
displayName: input5.displayName,
|
|
17538
17583
|
priceAmount: input5.priceAmount,
|
|
17584
|
+
pricingModel: input5.pricingModel,
|
|
17585
|
+
priceLabel: input5.priceLabel,
|
|
17539
17586
|
priceCurrency: input5.priceCurrency ?? "usd",
|
|
17540
17587
|
billingPeriod: normalizeBillingPeriod2(input5.billingPeriod),
|
|
17541
17588
|
limitsJson: planLimitsForPlatform(input5),
|
|
@@ -18226,17 +18273,14 @@ function isWarmPoolInfrastructureTerminal(project) {
|
|
|
18226
18273
|
return status2 === "ready" || status2 === "failed" || status2 === "deleting";
|
|
18227
18274
|
}
|
|
18228
18275
|
function shouldSkipColdModuleInfrastructureWait(project, infraStatus) {
|
|
18229
|
-
if (isWarmPoolInfrastructureReady(project)) {
|
|
18230
|
-
return true;
|
|
18231
|
-
}
|
|
18232
18276
|
const resourceType = infraStatus?.resourceType;
|
|
18233
|
-
if (resourceType === "WarmPoolBinding") {
|
|
18234
|
-
return true;
|
|
18235
|
-
}
|
|
18236
18277
|
if (resourceType && COLD_MODULE_INFRA_RESOURCE_TYPES.has(resourceType)) {
|
|
18237
18278
|
return false;
|
|
18238
18279
|
}
|
|
18239
|
-
|
|
18280
|
+
if (resourceType === "WarmPoolBinding") {
|
|
18281
|
+
return true;
|
|
18282
|
+
}
|
|
18283
|
+
return isWarmPoolInfrastructureReady(project);
|
|
18240
18284
|
}
|
|
18241
18285
|
function resolvePortalBackendFqdn(project) {
|
|
18242
18286
|
const portalApiUrl = project.portalApiUrl?.trim();
|
|
@@ -106,6 +106,8 @@ export interface GatewayPlanDto {
|
|
|
106
106
|
displayName: string;
|
|
107
107
|
isActive: boolean;
|
|
108
108
|
priceAmount?: number;
|
|
109
|
+
pricingModel?: "fixed" | "custom";
|
|
110
|
+
priceLabel?: string | null;
|
|
109
111
|
priceCurrency?: string;
|
|
110
112
|
billingPeriod?: string;
|
|
111
113
|
limitsJson?: string | null;
|
|
@@ -120,6 +122,8 @@ export interface CreateGatewayPlanRequest {
|
|
|
120
122
|
limitsJson?: string | null;
|
|
121
123
|
includedActionKeys: string[];
|
|
122
124
|
priceAmount?: number;
|
|
125
|
+
pricingModel?: "fixed" | "custom";
|
|
126
|
+
priceLabel?: string;
|
|
123
127
|
priceCurrency?: string;
|
|
124
128
|
billingPeriod?: string;
|
|
125
129
|
sortOrder?: number;
|
|
@@ -325,6 +329,8 @@ export interface UpdateGatewayPlanRequest {
|
|
|
325
329
|
limitsJson?: string | null;
|
|
326
330
|
includedActionKeys: string[];
|
|
327
331
|
priceAmount?: number;
|
|
332
|
+
pricingModel?: "fixed" | "custom";
|
|
333
|
+
priceLabel?: string;
|
|
328
334
|
priceCurrency?: string;
|
|
329
335
|
billingPeriod?: string;
|
|
330
336
|
sortOrder?: number;
|
|
@@ -7,6 +7,8 @@ export interface ManifestPlanInput {
|
|
|
7
7
|
sku: string;
|
|
8
8
|
displayName: string;
|
|
9
9
|
priceAmount?: number;
|
|
10
|
+
pricingModel?: "fixed" | "custom";
|
|
11
|
+
priceLabel?: string;
|
|
10
12
|
priceCurrency?: string;
|
|
11
13
|
billingPeriod?: "month" | "year" | "monthly" | "annual";
|
|
12
14
|
limits?: GatewayLimits;
|
|
@@ -42,6 +42,8 @@ export declare function handleCreateGatewayPlan(client: PlatformApiClient, confi
|
|
|
42
42
|
limitsJson?: string;
|
|
43
43
|
includedActionKeys?: string[];
|
|
44
44
|
priceAmount?: number;
|
|
45
|
+
pricingModel?: "fixed" | "custom";
|
|
46
|
+
priceLabel?: string;
|
|
45
47
|
priceCurrency?: string;
|
|
46
48
|
billingPeriod?: string;
|
|
47
49
|
sortOrder?: number;
|
|
@@ -59,6 +61,8 @@ export declare function handleUpdateGatewayPlan(client: PlatformApiClient, confi
|
|
|
59
61
|
limitsJson?: string;
|
|
60
62
|
includedActionKeys?: string[];
|
|
61
63
|
priceAmount?: number;
|
|
64
|
+
pricingModel?: "fixed" | "custom";
|
|
65
|
+
priceLabel?: string;
|
|
62
66
|
priceCurrency?: string;
|
|
63
67
|
billingPeriod?: string;
|
|
64
68
|
sortOrder?: number;
|
|
@@ -71,6 +75,8 @@ export declare function handlePatchGatewayPlan(client: PlatformApiClient, config
|
|
|
71
75
|
limitsJson?: string | null;
|
|
72
76
|
includedActionKeys?: string[];
|
|
73
77
|
priceAmount?: number;
|
|
78
|
+
pricingModel?: "fixed" | "custom";
|
|
79
|
+
priceLabel?: string;
|
|
74
80
|
priceCurrency?: string;
|
|
75
81
|
billingPeriod?: string;
|
|
76
82
|
sortOrder?: number;
|
|
@@ -9,11 +9,21 @@ export declare const APITOGO_MANIFEST_ENV_FILES: {
|
|
|
9
9
|
};
|
|
10
10
|
export { APITOGO_CONFIG_FILENAME };
|
|
11
11
|
export type ApitogoManifestEnv = keyof typeof APITOGO_MANIFEST_ENV_FILES;
|
|
12
|
+
export declare const PlanPricingModelSchema: z.ZodEnum<{
|
|
13
|
+
fixed: "fixed";
|
|
14
|
+
custom: "custom";
|
|
15
|
+
}>;
|
|
16
|
+
export type PlanPricingModel = z.infer<typeof PlanPricingModelSchema>;
|
|
12
17
|
export declare const ALLOWED_RUNTIME_VERSIONS: readonly ["DOTNET_10", "DOTNET_9", "DOTNET_8", "NODE_24_LTS", "NODE_22_LTS", "PYTHON_314", "PYTHON_313", "PYTHON_312", "PYTHON_311", "PYTHON_310"];
|
|
13
18
|
export declare const ManifestPlanInputSchema: z.ZodObject<{
|
|
14
19
|
sku: z.ZodString;
|
|
15
20
|
displayName: z.ZodString;
|
|
16
21
|
priceAmount: z.ZodDefault<z.ZodNumber>;
|
|
22
|
+
pricingModel: z.ZodOptional<z.ZodEnum<{
|
|
23
|
+
fixed: "fixed";
|
|
24
|
+
custom: "custom";
|
|
25
|
+
}>>;
|
|
26
|
+
priceLabel: z.ZodOptional<z.ZodString>;
|
|
17
27
|
priceCurrency: z.ZodOptional<z.ZodString>;
|
|
18
28
|
billingPeriod: z.ZodOptional<z.ZodEnum<{
|
|
19
29
|
month: "month";
|
|
@@ -34,6 +44,9 @@ export declare const ManifestPlanInputSchema: z.ZodObject<{
|
|
|
34
44
|
limitsJson: z.ZodOptional<z.ZodString>;
|
|
35
45
|
includedActionKeys: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
36
46
|
}, z.core.$strip>;
|
|
47
|
+
export declare function isPlanCustomPriced(plan: {
|
|
48
|
+
pricingModel?: string | null;
|
|
49
|
+
}): boolean;
|
|
37
50
|
export declare function isPlanFree(plan: {
|
|
38
51
|
priceAmount?: number;
|
|
39
52
|
}): boolean;
|
|
@@ -68,6 +81,11 @@ export declare const DevPortalLocalManifestSchema: z.ZodObject<{
|
|
|
68
81
|
sku: z.ZodString;
|
|
69
82
|
displayName: z.ZodString;
|
|
70
83
|
priceAmount: z.ZodDefault<z.ZodNumber>;
|
|
84
|
+
pricingModel: z.ZodOptional<z.ZodEnum<{
|
|
85
|
+
fixed: "fixed";
|
|
86
|
+
custom: "custom";
|
|
87
|
+
}>>;
|
|
88
|
+
priceLabel: z.ZodOptional<z.ZodString>;
|
|
71
89
|
priceCurrency: z.ZodOptional<z.ZodString>;
|
|
72
90
|
billingPeriod: z.ZodOptional<z.ZodEnum<{
|
|
73
91
|
month: "month";
|
|
@@ -230,6 +248,8 @@ export type DevPortalPreviewPlan = {
|
|
|
230
248
|
sku: string;
|
|
231
249
|
displayName: string;
|
|
232
250
|
priceAmount: number;
|
|
251
|
+
pricingModel: PlanPricingModel;
|
|
252
|
+
priceLabel?: string;
|
|
233
253
|
priceCurrency: string;
|
|
234
254
|
billingPeriod: string;
|
|
235
255
|
limits: GatewayLimits | null;
|
|
@@ -388,6 +388,11 @@ declare const PlanCatalogItemSchema: z.ZodObject<{
|
|
|
388
388
|
sku: z.ZodString;
|
|
389
389
|
displayName: z.ZodString;
|
|
390
390
|
priceAmount: z.ZodDefault<z.ZodNumber>;
|
|
391
|
+
pricingModel: z.ZodOptional<z.ZodEnum<{
|
|
392
|
+
fixed: "fixed";
|
|
393
|
+
custom: "custom";
|
|
394
|
+
}>>;
|
|
395
|
+
priceLabel: z.ZodOptional<z.ZodString>;
|
|
391
396
|
priceCurrency: z.ZodOptional<z.ZodString>;
|
|
392
397
|
billingPeriod: z.ZodOptional<z.ZodString>;
|
|
393
398
|
limits: z.ZodOptional<z.ZodObject<{
|
|
@@ -421,6 +426,11 @@ export declare const PlansModuleSchema: z.ZodObject<{
|
|
|
421
426
|
sku: z.ZodString;
|
|
422
427
|
displayName: z.ZodString;
|
|
423
428
|
priceAmount: z.ZodDefault<z.ZodNumber>;
|
|
429
|
+
pricingModel: z.ZodOptional<z.ZodEnum<{
|
|
430
|
+
fixed: "fixed";
|
|
431
|
+
custom: "custom";
|
|
432
|
+
}>>;
|
|
433
|
+
priceLabel: z.ZodOptional<z.ZodString>;
|
|
424
434
|
priceCurrency: z.ZodOptional<z.ZodString>;
|
|
425
435
|
billingPeriod: z.ZodOptional<z.ZodString>;
|
|
426
436
|
limits: z.ZodOptional<z.ZodObject<{
|
|
@@ -507,6 +517,11 @@ export declare const UserPanelModuleSchema: z.ZodObject<{
|
|
|
507
517
|
sku: z.ZodString;
|
|
508
518
|
displayName: z.ZodString;
|
|
509
519
|
priceAmount: z.ZodDefault<z.ZodNumber>;
|
|
520
|
+
pricingModel: z.ZodOptional<z.ZodEnum<{
|
|
521
|
+
fixed: "fixed";
|
|
522
|
+
custom: "custom";
|
|
523
|
+
}>>;
|
|
524
|
+
priceLabel: z.ZodOptional<z.ZodString>;
|
|
510
525
|
priceCurrency: z.ZodOptional<z.ZodString>;
|
|
511
526
|
billingPeriod: z.ZodOptional<z.ZodString>;
|
|
512
527
|
limits: z.ZodOptional<z.ZodObject<{
|
|
@@ -705,6 +720,11 @@ export declare const ModulesSchema: z.ZodOptional<z.ZodObject<{
|
|
|
705
720
|
sku: z.ZodString;
|
|
706
721
|
displayName: z.ZodString;
|
|
707
722
|
priceAmount: z.ZodDefault<z.ZodNumber>;
|
|
723
|
+
pricingModel: z.ZodOptional<z.ZodEnum<{
|
|
724
|
+
fixed: "fixed";
|
|
725
|
+
custom: "custom";
|
|
726
|
+
}>>;
|
|
727
|
+
priceLabel: z.ZodOptional<z.ZodString>;
|
|
708
728
|
priceCurrency: z.ZodOptional<z.ZodString>;
|
|
709
729
|
billingPeriod: z.ZodOptional<z.ZodString>;
|
|
710
730
|
limits: z.ZodOptional<z.ZodObject<{
|
|
@@ -7039,6 +7039,11 @@ export declare const ZudokuConfig: z.ZodObject<{
|
|
|
7039
7039
|
sku: z.ZodString;
|
|
7040
7040
|
displayName: z.ZodString;
|
|
7041
7041
|
priceAmount: z.ZodDefault<z.ZodNumber>;
|
|
7042
|
+
pricingModel: z.ZodOptional<z.ZodEnum<{
|
|
7043
|
+
fixed: "fixed";
|
|
7044
|
+
custom: "custom";
|
|
7045
|
+
}>>;
|
|
7046
|
+
priceLabel: z.ZodOptional<z.ZodString>;
|
|
7042
7047
|
priceCurrency: z.ZodOptional<z.ZodString>;
|
|
7043
7048
|
billingPeriod: z.ZodOptional<z.ZodString>;
|
|
7044
7049
|
limits: z.ZodOptional<z.ZodObject<{
|
package/dist/flat-config.d.ts
CHANGED
|
@@ -68,6 +68,14 @@ export type _Schema37 = {
|
|
|
68
68
|
sku: string
|
|
69
69
|
displayName: string
|
|
70
70
|
priceAmount?: number
|
|
71
|
+
/**
|
|
72
|
+
* "custom" shows a contact-sales form instead of checkout; priceAmount must be 0.
|
|
73
|
+
*/
|
|
74
|
+
pricingModel?: ("fixed" | "custom")
|
|
75
|
+
/**
|
|
76
|
+
* Text shown instead of the price, e.g. "Custom".
|
|
77
|
+
*/
|
|
78
|
+
priceLabel?: string
|
|
71
79
|
priceCurrency?: string
|
|
72
80
|
billingPeriod?: string
|
|
73
81
|
limits?: {
|
|
@@ -86,31 +94,31 @@ export type _Schema37 = {
|
|
|
86
94
|
limitsJson?: string
|
|
87
95
|
includedActionKeys?: string[]
|
|
88
96
|
}[]
|
|
89
|
-
export type
|
|
97
|
+
export type _Schema40 = ({
|
|
90
98
|
type: "url"
|
|
91
|
-
input: (string |
|
|
92
|
-
server?:
|
|
93
|
-
path?:
|
|
94
|
-
categories?:
|
|
95
|
-
options?:
|
|
99
|
+
input: (string | _Schema41[])
|
|
100
|
+
server?: _Schema42
|
|
101
|
+
path?: _Schema43
|
|
102
|
+
categories?: _Schema44
|
|
103
|
+
options?: _Schema46
|
|
96
104
|
} | {
|
|
97
105
|
type: "file"
|
|
98
|
-
input: (string | (string |
|
|
99
|
-
server?:
|
|
100
|
-
path?:
|
|
101
|
-
categories?:
|
|
102
|
-
options?:
|
|
106
|
+
input: (string | (string | _Schema41)[])
|
|
107
|
+
server?: _Schema42
|
|
108
|
+
path?: _Schema43
|
|
109
|
+
categories?: _Schema44
|
|
110
|
+
options?: _Schema46
|
|
103
111
|
} | {
|
|
104
112
|
type: "raw"
|
|
105
113
|
input: string
|
|
106
|
-
server?:
|
|
107
|
-
path?:
|
|
108
|
-
categories?:
|
|
109
|
-
options?:
|
|
114
|
+
server?: _Schema42
|
|
115
|
+
path?: _Schema43
|
|
116
|
+
categories?: _Schema44
|
|
117
|
+
options?: _Schema46
|
|
110
118
|
})
|
|
111
|
-
export type
|
|
112
|
-
export type
|
|
113
|
-
export type
|
|
119
|
+
export type _Schema42 = string
|
|
120
|
+
export type _Schema43 = string
|
|
121
|
+
export type _Schema44 = {
|
|
114
122
|
label: string
|
|
115
123
|
tags: string[]
|
|
116
124
|
}[]
|
|
@@ -412,8 +420,8 @@ export interface FlatZudokuConfig {
|
|
|
412
420
|
landing?: _Schema16
|
|
413
421
|
userPanel?: _Schema25
|
|
414
422
|
}
|
|
415
|
-
apis?: (
|
|
416
|
-
catalogs?: (
|
|
423
|
+
apis?: (_Schema40 | _Schema40[])
|
|
424
|
+
catalogs?: (_Schema47 | _Schema47[])
|
|
417
425
|
apiKeys?: {
|
|
418
426
|
enabled: boolean
|
|
419
427
|
getConsumers?: unknown
|
|
@@ -441,7 +449,7 @@ export interface FlatZudokuConfig {
|
|
|
441
449
|
}
|
|
442
450
|
enableStatusPages?: boolean
|
|
443
451
|
defaults?: {
|
|
444
|
-
apis:
|
|
452
|
+
apis: _Schema46
|
|
445
453
|
examplesLanguage?: string
|
|
446
454
|
}
|
|
447
455
|
__pluginDirs?: string[]
|
|
@@ -731,12 +739,12 @@ export interface _Schema36 {
|
|
|
731
739
|
description?: string
|
|
732
740
|
}[]
|
|
733
741
|
}
|
|
734
|
-
export interface
|
|
742
|
+
export interface _Schema41 {
|
|
735
743
|
path: string
|
|
736
744
|
input: string
|
|
737
745
|
label?: string
|
|
738
746
|
}
|
|
739
|
-
export interface
|
|
747
|
+
export interface _Schema46 {
|
|
740
748
|
examplesLanguage?: string
|
|
741
749
|
supportedLanguages?: {
|
|
742
750
|
value: string
|
|
@@ -753,7 +761,7 @@ export interface _Schema44 {
|
|
|
753
761
|
transformExamples?: unknown
|
|
754
762
|
generateCodeSnippet?: unknown
|
|
755
763
|
}
|
|
756
|
-
export interface
|
|
764
|
+
export interface _Schema47 {
|
|
757
765
|
path: string
|
|
758
766
|
label: string
|
|
759
767
|
items?: string[]
|
|
@@ -67,6 +67,10 @@ npx apitogo config
|
|
|
67
67
|
- **`sku`** — stable identifier (used in checkout URLs and gateway sync).
|
|
68
68
|
- **`displayName`** — label on `/pricing`.
|
|
69
69
|
- **`priceAmount`** — `0` for free tiers; greater than zero for paid plans (USD by default).
|
|
70
|
+
- **`pricingModel`** — `fixed` (default) or `custom`. See
|
|
71
|
+
[Custom pricing](#custom-pricing-contact-sales).
|
|
72
|
+
- **`priceLabel`** — optional text shown instead of the price, such as `"Let's talk"` (max 64
|
|
73
|
+
characters).
|
|
70
74
|
- **`limits`** — rate limit and quota policies (see below). Encoded `limitsJson` strings are still
|
|
71
75
|
accepted on load.
|
|
72
76
|
|
|
@@ -90,6 +94,28 @@ OpenAPI is deployed, you can scope a plan to specific operations with `includedA
|
|
|
90
94
|
example `get:/orders/{id}`). Edit those fields in `apitogo.config.json` — see
|
|
91
95
|
[Manifest](./manifest.md).
|
|
92
96
|
|
|
97
|
+
## Custom pricing (contact sales)
|
|
98
|
+
|
|
99
|
+
Set `pricingModel: "custom"` for plans with negotiated prices, such as an Enterprise tier. Leave
|
|
100
|
+
`priceAmount` at `0` or omit it.
|
|
101
|
+
|
|
102
|
+
```json
|
|
103
|
+
{
|
|
104
|
+
"sku": "enterprise",
|
|
105
|
+
"displayName": "Enterprise",
|
|
106
|
+
"pricingModel": "custom",
|
|
107
|
+
"priceLabel": "Let's talk"
|
|
108
|
+
}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
On `/pricing`, custom plans show `priceLabel` (or **Custom**) and a **Contact sales** button. The
|
|
112
|
+
button opens a public `/contact-sales` form instead of Stripe checkout or registration. Visitors
|
|
113
|
+
don't need an account.
|
|
114
|
+
|
|
115
|
+
Each submission is emailed to the project's **sales contact email**, or to the project owner's email
|
|
116
|
+
when none is set. Custom plans can't be subscribed to through checkout. Custom plans are available
|
|
117
|
+
on every platform plan, including Free.
|
|
118
|
+
|
|
93
119
|
## Publish
|
|
94
120
|
|
|
95
121
|
Gateway plans are applied when you run:
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
---
|
|
2
|
+
sidebar_icon: house
|
|
3
|
+
title: Landing Page
|
|
4
|
+
description:
|
|
5
|
+
Customize your dev portal home page and show your real API operations in the API showcase, loaded
|
|
6
|
+
live from your dev portal backend.
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
A scaffolded dev portal home page lives in `landing/LandingPage.tsx`. It composes section components
|
|
10
|
+
from `@lukoweb/apitogo-module-landing` inside a `LandingShell`:
|
|
11
|
+
|
|
12
|
+
| Component | Purpose |
|
|
13
|
+
| --------------------- | --------------------------------------------- |
|
|
14
|
+
| `HeroSection` | Headline, description, CTAs and code examples |
|
|
15
|
+
| `TrustSection` | Logos or social proof |
|
|
16
|
+
| `FeaturesSection` | Feature grid |
|
|
17
|
+
| `StepsSection` | Numbered "how it works" steps |
|
|
18
|
+
| `ApiShowcaseSection` | Grid or method + path list of your API |
|
|
19
|
+
| `PlansPreviewSection` | Short plan comparison linking to `/pricing` |
|
|
20
|
+
| `PricingCtaSection` | Closing call to action |
|
|
21
|
+
|
|
22
|
+
Edit the file directly to add, remove or reorder sections. The site footer is configured separately
|
|
23
|
+
in `apitogo.config.json` under `modules.landing.content.footer` (see
|
|
24
|
+
[Footer](/docs/configuration/footer)).
|
|
25
|
+
|
|
26
|
+
## API showcase
|
|
27
|
+
|
|
28
|
+
`ApiShowcaseSection` renders the items you pass it. The scaffold ships with a **static sample
|
|
29
|
+
list**:
|
|
30
|
+
|
|
31
|
+
```tsx
|
|
32
|
+
<ApiShowcaseSection
|
|
33
|
+
apiShowcase={{
|
|
34
|
+
title: "Sample endpoints",
|
|
35
|
+
layout: "list", // or "grid"
|
|
36
|
+
browseAllHref: "/api",
|
|
37
|
+
items: [
|
|
38
|
+
{ name: "List users", path: "/v1/users", method: "GET", summary: "List paginated users" },
|
|
39
|
+
],
|
|
40
|
+
}}
|
|
41
|
+
/>
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Each item accepts `name`, `path`, and optionally `method`, `summary`, `icon` and `latency`.
|
|
45
|
+
|
|
46
|
+
## Live APIs
|
|
47
|
+
|
|
48
|
+
Instead of maintaining the list by hand, you can load your API's real operations. Your dev portal
|
|
49
|
+
backend syncs the **gateway actions** of your project (every operation discovered from the OpenAPI
|
|
50
|
+
spec you deployed) and serves them publicly at `GET /api/v1/actions`. The `useGatewayActions()` hook
|
|
51
|
+
fetches that list; you decide what to show and pass the result to `ApiShowcaseSection`.
|
|
52
|
+
|
|
53
|
+
```tsx
|
|
54
|
+
import {
|
|
55
|
+
ApiShowcaseSection,
|
|
56
|
+
LandingShell,
|
|
57
|
+
toApiShowcaseItems,
|
|
58
|
+
useGatewayActions,
|
|
59
|
+
} from "@lukoweb/apitogo-module-landing";
|
|
60
|
+
|
|
61
|
+
const staticItems = [
|
|
62
|
+
{ name: "List users", path: "/v1/users", method: "GET", summary: "List paginated users" },
|
|
63
|
+
];
|
|
64
|
+
|
|
65
|
+
// Optional friendly labels, keyed by action key (`method:path`, lower-case method).
|
|
66
|
+
const labels: Record<string, string> = {
|
|
67
|
+
"get:/v1/users": "List paginated users",
|
|
68
|
+
"post:/v1/orders": "Create an order",
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
export default function LandingPage() {
|
|
72
|
+
const { data } = useGatewayActions({ inPlan: true });
|
|
73
|
+
|
|
74
|
+
const liveItems = toApiShowcaseItems(
|
|
75
|
+
(data?.actions ?? []).filter((action) => action.path.startsWith("/v1/")).slice(0, 6),
|
|
76
|
+
labels,
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
return (
|
|
80
|
+
<LandingShell title="My API">
|
|
81
|
+
<ApiShowcaseSection
|
|
82
|
+
apiShowcase={{
|
|
83
|
+
title: "Our APIs",
|
|
84
|
+
layout: "list",
|
|
85
|
+
browseAllHref: "/api",
|
|
86
|
+
items: liveItems.length > 0 ? liveItems : staticItems,
|
|
87
|
+
}}
|
|
88
|
+
/>
|
|
89
|
+
</LandingShell>
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### Filters
|
|
95
|
+
|
|
96
|
+
`useGatewayActions()` accepts server-side filters. All filters are combined:
|
|
97
|
+
|
|
98
|
+
| Option | Type | Description |
|
|
99
|
+
| ------------ | --------- | ------------------------------------------------------ |
|
|
100
|
+
| `inPlan` | `boolean` | Only actions included in at least one active plan |
|
|
101
|
+
| `plan` | `string` | Only actions included in the plan with this SKU |
|
|
102
|
+
| `isPublic` | `boolean` | Only actions callable without an API key (or only not) |
|
|
103
|
+
| `method` | `string` | HTTP method, e.g. `GET` |
|
|
104
|
+
| `pathPrefix` | `string` | Path prefix, e.g. `/v1/orders` |
|
|
105
|
+
| `limit` | `number` | Maximum number of actions (server cap: 500) |
|
|
106
|
+
| `enabled` | `boolean` | Set to `false` to skip the request |
|
|
107
|
+
|
|
108
|
+
Anything else — sorting, renaming, grouping, picking a hand-curated subset — is up to you in plain
|
|
109
|
+
TypeScript before you build the items.
|
|
110
|
+
|
|
111
|
+
Each action has this shape:
|
|
112
|
+
|
|
113
|
+
```ts
|
|
114
|
+
type GatewayAction = {
|
|
115
|
+
actionKey: string; // e.g. "get:/orders/{id}"
|
|
116
|
+
method: string; // e.g. "GET"
|
|
117
|
+
path: string; // e.g. "/orders/{id}"
|
|
118
|
+
isPublic: boolean; // callable without an API key
|
|
119
|
+
};
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Actions only carry the method and path. Use the `summaries` argument of `toApiShowcaseItems()` (or
|
|
123
|
+
your own mapping) to add human-friendly names.
|
|
124
|
+
|
|
125
|
+
### Always keep a fallback
|
|
126
|
+
|
|
127
|
+
The hook runs in the browser only, so the prerendered HTML never contains a stale list. `data` is
|
|
128
|
+
`undefined`:
|
|
129
|
+
|
|
130
|
+
- while the request is loading,
|
|
131
|
+
- if the request fails,
|
|
132
|
+
- in preview and demo mode, or when no dev portal backend URL is configured (for example in
|
|
133
|
+
`npm run dev` before your portal is provisioned).
|
|
134
|
+
|
|
135
|
+
`ApiShowcaseSection` renders nothing for an empty list, so pass a static fallback as shown above.
|
|
136
|
+
|
|
137
|
+
### What becomes public
|
|
138
|
+
|
|
139
|
+
`GET /api/v1/actions` is anonymous: **every operation in the OpenAPI spec deployed to your gateway
|
|
140
|
+
is listable**, including ones you may not document in the portal. Use `inPlan: true` to limit the
|
|
141
|
+
list to operations your plans actually sell, and remove internal operations from the spec you deploy
|
|
142
|
+
to the gateway if they must not be discoverable.
|
|
143
|
+
|
|
144
|
+
### Freshness and limits
|
|
145
|
+
|
|
146
|
+
- The backend syncs actions at startup and on the plan sync interval (15 minutes by default). A new
|
|
147
|
+
release shows up after the next sync.
|
|
148
|
+
- Responses are cacheable for 5 minutes (`Cache-Control: public, max-age=300`).
|
|
149
|
+
- The endpoint is rate limited per client IP (60 requests per minute).
|
|
150
|
+
|
|
151
|
+
### Troubleshooting
|
|
152
|
+
|
|
153
|
+
- **The list is always the fallback:** check that `authentication.apiBaseUrl` (or
|
|
154
|
+
`VITE_APITOGO_AUTHENTICATION_API_BASE_URL`) points at your dev portal backend and is not the
|
|
155
|
+
placeholder URL.
|
|
156
|
+
- **CORS errors:** the backend only allows the portal's configured frontend URL. Serving the site
|
|
157
|
+
from another domain requires adding it to the backend's allowed origins.
|
|
158
|
+
- **Empty list on a new portal:** the portal is not activated yet, or the gateway has no release.
|
|
159
|
+
Deploy your backend, then wait for the next sync.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lukoweb/apitogo",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.129",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"sideEffects": [
|
|
6
6
|
"**/*.css",
|
|
@@ -167,6 +167,16 @@
|
|
|
167
167
|
"default": "./src/cli/platform/apply/index.ts"
|
|
168
168
|
}
|
|
169
169
|
},
|
|
170
|
+
"scripts": {
|
|
171
|
+
"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",
|
|
172
|
+
"postbuild": "tsx scripts/check-external-deps.ts",
|
|
173
|
+
"typecheck": "tsc --project tsconfig.app.json --noEmit",
|
|
174
|
+
"generate:types": "tsx scripts/generate-types.js && tsx scripts/generate-flat-config.js",
|
|
175
|
+
"build:standalone": "vite build --mode standalone --config vite.standalone.config.ts --log-level=error",
|
|
176
|
+
"prepack": "npm run build && tsx scripts/generate-publish-exports.ts",
|
|
177
|
+
"clean": "rm -rf dist",
|
|
178
|
+
"codegen": "graphql-codegen --config ./src/codegen.ts"
|
|
179
|
+
},
|
|
170
180
|
"dependencies": {
|
|
171
181
|
"@apidevtools/json-schema-ref-parser": "15.3.1",
|
|
172
182
|
"@apidevtools/swagger-parser": "^12.1.0",
|
|
@@ -346,13 +356,148 @@
|
|
|
346
356
|
"optional": true
|
|
347
357
|
}
|
|
348
358
|
},
|
|
349
|
-
"
|
|
350
|
-
"
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
359
|
+
"publishConfig": {
|
|
360
|
+
"exports": {
|
|
361
|
+
".": {
|
|
362
|
+
"types": "./dist/declarations/index.d.ts",
|
|
363
|
+
"default": "./src/index.ts"
|
|
364
|
+
},
|
|
365
|
+
"./package.json": "./package.json",
|
|
366
|
+
"./ui/*": {
|
|
367
|
+
"types": "./dist/declarations/lib/ui/*.d.ts",
|
|
368
|
+
"default": "./src/lib/ui/*.tsx"
|
|
369
|
+
},
|
|
370
|
+
"./ui/*.js": {
|
|
371
|
+
"types": "./dist/declarations/lib/ui/*.d.ts",
|
|
372
|
+
"default": "./src/lib/ui/*.tsx"
|
|
373
|
+
},
|
|
374
|
+
"./client": {
|
|
375
|
+
"types": "./client.d.ts"
|
|
376
|
+
},
|
|
377
|
+
"./auth/clerk": {
|
|
378
|
+
"types": "./dist/declarations/lib/authentication/providers/clerk.d.ts",
|
|
379
|
+
"default": "./src/lib/authentication/providers/clerk.tsx"
|
|
380
|
+
},
|
|
381
|
+
"./auth/auth0": {
|
|
382
|
+
"types": "./dist/declarations/lib/authentication/providers/auth0.d.ts",
|
|
383
|
+
"default": "./src/lib/authentication/providers/auth0.tsx"
|
|
384
|
+
},
|
|
385
|
+
"./auth/openid": {
|
|
386
|
+
"types": "./dist/declarations/lib/authentication/providers/openid.d.ts",
|
|
387
|
+
"default": "./src/lib/authentication/providers/openid.tsx"
|
|
388
|
+
},
|
|
389
|
+
"./auth/supabase": {
|
|
390
|
+
"types": "./dist/declarations/lib/authentication/providers/supabase.d.ts",
|
|
391
|
+
"default": "./src/lib/authentication/providers/supabase.tsx"
|
|
392
|
+
},
|
|
393
|
+
"./auth/azureb2c": {
|
|
394
|
+
"types": "./dist/declarations/lib/authentication/providers/azureb2c.d.ts",
|
|
395
|
+
"default": "./src/lib/authentication/providers/azureb2c.tsx"
|
|
396
|
+
},
|
|
397
|
+
"./auth/firebase": {
|
|
398
|
+
"types": "./dist/declarations/lib/authentication/providers/firebase.d.ts",
|
|
399
|
+
"default": "./src/lib/authentication/providers/firebase.tsx"
|
|
400
|
+
},
|
|
401
|
+
"./auth/dev-portal": {
|
|
402
|
+
"types": "./dist/declarations/lib/authentication/providers/dev-portal.d.ts",
|
|
403
|
+
"default": "./src/lib/authentication/providers/dev-portal.tsx"
|
|
404
|
+
},
|
|
405
|
+
"./plugins": {
|
|
406
|
+
"types": "./dist/declarations/lib/core/plugins.d.ts",
|
|
407
|
+
"default": "./src/lib/core/plugins.ts"
|
|
408
|
+
},
|
|
409
|
+
"./plugins/api-keys": {
|
|
410
|
+
"types": "./dist/declarations/lib/plugins/api-keys/index.d.ts",
|
|
411
|
+
"default": "./src/lib/plugins/api-keys/index.tsx"
|
|
412
|
+
},
|
|
413
|
+
"./plugins/markdown": {
|
|
414
|
+
"types": "./dist/declarations/lib/plugins/markdown/index.d.ts",
|
|
415
|
+
"default": "./src/lib/plugins/markdown/index.tsx"
|
|
416
|
+
},
|
|
417
|
+
"./plugins/openapi": {
|
|
418
|
+
"types": "./dist/declarations/lib/plugins/openapi/index.d.ts",
|
|
419
|
+
"default": "./src/lib/plugins/openapi/index.tsx"
|
|
420
|
+
},
|
|
421
|
+
"./plugins/custom-pages": {
|
|
422
|
+
"types": "./dist/declarations/lib/plugins/custom-pages/index.d.ts",
|
|
423
|
+
"default": "./src/lib/plugins/custom-pages/index.tsx"
|
|
424
|
+
},
|
|
425
|
+
"./plugins/search-inkeep": {
|
|
426
|
+
"types": "./dist/declarations/lib/plugins/search-inkeep/index.d.ts",
|
|
427
|
+
"default": "./src/lib/plugins/search-inkeep/index.tsx"
|
|
428
|
+
},
|
|
429
|
+
"./plugins/search-pagefind": {
|
|
430
|
+
"types": "./dist/declarations/lib/plugins/search-pagefind/index.d.ts",
|
|
431
|
+
"default": "./src/lib/plugins/search-pagefind/index.tsx"
|
|
432
|
+
},
|
|
433
|
+
"./plugins/api-catalog": {
|
|
434
|
+
"types": "./dist/declarations/lib/plugins/api-catalog/index.d.ts",
|
|
435
|
+
"default": "./src/lib/plugins/api-catalog/index.tsx"
|
|
436
|
+
},
|
|
437
|
+
"./modules/landing": "@lukoweb/apitogo-module-landing",
|
|
438
|
+
"./modules/user-panel": "@lukoweb/apitogo-module-user-panel",
|
|
439
|
+
"./components": {
|
|
440
|
+
"types": "./dist/declarations/lib/components/index.d.ts",
|
|
441
|
+
"default": "./src/lib/components/index.ts"
|
|
442
|
+
},
|
|
443
|
+
"./mermaid": {
|
|
444
|
+
"types": "./dist/declarations/lib/components/Mermaid.d.ts",
|
|
445
|
+
"default": "./src/lib/components/Mermaid.tsx"
|
|
446
|
+
},
|
|
447
|
+
"./router": {
|
|
448
|
+
"types": "./dist/declarations/lib/core/router.d.ts",
|
|
449
|
+
"default": "./src/lib/core/router.ts"
|
|
450
|
+
},
|
|
451
|
+
"./react-query": {
|
|
452
|
+
"types": "./dist/declarations/lib/core/react-query.d.ts",
|
|
453
|
+
"default": "./src/lib/core/react-query.ts"
|
|
454
|
+
},
|
|
455
|
+
"./icons": {
|
|
456
|
+
"types": "./dist/declarations/lib/icons.d.ts",
|
|
457
|
+
"default": "./src/lib/icons.ts"
|
|
458
|
+
},
|
|
459
|
+
"./vite": {
|
|
460
|
+
"types": "./dist/declarations/vite/index.d.ts",
|
|
461
|
+
"default": "./src/vite/index.ts"
|
|
462
|
+
},
|
|
463
|
+
"./app/*": {
|
|
464
|
+
"types": "./dist/declarations/app/*.d.ts",
|
|
465
|
+
"default": "./src/app/*"
|
|
466
|
+
},
|
|
467
|
+
"./hooks": {
|
|
468
|
+
"types": "./dist/declarations/lib/hooks/index.d.ts",
|
|
469
|
+
"default": "./src/lib/hooks/index.ts"
|
|
470
|
+
},
|
|
471
|
+
"./main.css": "./src/app/main.css",
|
|
472
|
+
"./processors/*": {
|
|
473
|
+
"types": "./dist/declarations/lib/plugins/openapi/processors/*.d.ts",
|
|
474
|
+
"default": "./src/lib/plugins/openapi/processors/*.ts"
|
|
475
|
+
},
|
|
476
|
+
"./with-zuplo": {
|
|
477
|
+
"types": "./dist/declarations/zuplo/with-zuplo.d.ts",
|
|
478
|
+
"default": "./src/zuplo/with-zuplo.ts"
|
|
479
|
+
},
|
|
480
|
+
"./testing": {
|
|
481
|
+
"types": "./dist/declarations/lib/testing/index.d.ts",
|
|
482
|
+
"default": "./src/lib/testing/index.tsx"
|
|
483
|
+
},
|
|
484
|
+
"./local-manifest": {
|
|
485
|
+
"types": "./dist/declarations/config/local-manifest.d.ts",
|
|
486
|
+
"default": "./src/config/local-manifest.ts"
|
|
487
|
+
},
|
|
488
|
+
"./build-config": {
|
|
489
|
+
"types": "./dist/declarations/config/build-apitogo-config.d.ts",
|
|
490
|
+
"default": "./src/config/build-apitogo-config.ts"
|
|
491
|
+
},
|
|
492
|
+
"./config-loader": {
|
|
493
|
+
"types": "./dist/declarations/config/apitogo-config-loader.d.ts",
|
|
494
|
+
"default": "./src/config/apitogo-config-loader.ts"
|
|
495
|
+
},
|
|
496
|
+
"./apply": {
|
|
497
|
+
"types": "./dist/declarations/cli/platform/apply/index.d.ts",
|
|
498
|
+
"default": "./src/cli/platform/apply/index.ts"
|
|
499
|
+
}
|
|
500
|
+
},
|
|
501
|
+
"access": "public"
|
|
357
502
|
}
|
|
358
|
-
}
|
|
503
|
+
}
|
|
@@ -106,6 +106,8 @@ const COLLAPSED_UPPERCASE_ALIASES: Record<string, string> = {
|
|
|
106
106
|
DEMOMODE: "demoMode",
|
|
107
107
|
DISPLAYNAME: "displayName",
|
|
108
108
|
PRICEAMOUNT: "priceAmount",
|
|
109
|
+
PRICINGMODEL: "pricingModel",
|
|
110
|
+
PRICELABEL: "priceLabel",
|
|
109
111
|
PRICECURRENCY: "priceCurrency",
|
|
110
112
|
BILLINGPERIOD: "billingPeriod",
|
|
111
113
|
LIMITSJSON: "limitsJson",
|
|
@@ -36,6 +36,10 @@ export type ApitogoManifestEnv = keyof typeof APITOGO_MANIFEST_ENV_FILES;
|
|
|
36
36
|
|
|
37
37
|
const BillingPeriodSchema = z.enum(["month", "year", "monthly", "annual"]);
|
|
38
38
|
|
|
39
|
+
export const PlanPricingModelSchema = z.enum(["fixed", "custom"]);
|
|
40
|
+
|
|
41
|
+
export type PlanPricingModel = z.infer<typeof PlanPricingModelSchema>;
|
|
42
|
+
|
|
39
43
|
const GatewayLimitsSchema = z
|
|
40
44
|
.object({
|
|
41
45
|
rateLimit: z
|
|
@@ -81,6 +85,9 @@ export const ManifestPlanInputSchema = z.object({
|
|
|
81
85
|
sku: z.string().min(1),
|
|
82
86
|
displayName: z.string().min(1),
|
|
83
87
|
priceAmount: z.number().default(0),
|
|
88
|
+
/** `custom` plans show a contact-sales form instead of checkout. */
|
|
89
|
+
pricingModel: PlanPricingModelSchema.optional(),
|
|
90
|
+
priceLabel: z.string().max(64).optional(),
|
|
84
91
|
priceCurrency: PriceCurrencySchema.optional(),
|
|
85
92
|
billingPeriod: BillingPeriodSchema.optional(),
|
|
86
93
|
limits: GatewayLimitsSchema.optional(),
|
|
@@ -89,6 +96,12 @@ export const ManifestPlanInputSchema = z.object({
|
|
|
89
96
|
includedActionKeys: z.array(z.string()).optional(),
|
|
90
97
|
});
|
|
91
98
|
|
|
99
|
+
export function isPlanCustomPriced(plan: {
|
|
100
|
+
pricingModel?: string | null;
|
|
101
|
+
}): boolean {
|
|
102
|
+
return plan.pricingModel === "custom";
|
|
103
|
+
}
|
|
104
|
+
|
|
92
105
|
/** Free plans have zero price; paid plans have priceAmount greater than zero. */
|
|
93
106
|
export function isPlanFree(plan: { priceAmount?: number }): boolean {
|
|
94
107
|
return (plan.priceAmount ?? 0) <= 0;
|
|
@@ -198,6 +211,8 @@ export type DevPortalPreviewPlan = {
|
|
|
198
211
|
sku: string;
|
|
199
212
|
displayName: string;
|
|
200
213
|
priceAmount: number;
|
|
214
|
+
pricingModel: PlanPricingModel;
|
|
215
|
+
priceLabel?: string;
|
|
201
216
|
priceCurrency: string;
|
|
202
217
|
billingPeriod: string;
|
|
203
218
|
limits: GatewayLimits | null;
|
|
@@ -272,6 +287,8 @@ export function plansToPreviewCatalog(
|
|
|
272
287
|
sku: plan.sku,
|
|
273
288
|
displayName: plan.displayName,
|
|
274
289
|
priceAmount: plan.priceAmount ?? 0,
|
|
290
|
+
pricingModel: plan.pricingModel ?? "fixed",
|
|
291
|
+
...(plan.priceLabel ? { priceLabel: plan.priceLabel } : {}),
|
|
275
292
|
priceCurrency: plan.priceCurrency ?? "usd",
|
|
276
293
|
billingPeriod: normalizeBillingPeriod(plan.billingPeriod),
|
|
277
294
|
limits: coerceGatewayLimits(plan) ?? null,
|
|
@@ -264,6 +264,17 @@ const PlanCatalogItemSchema = z.object({
|
|
|
264
264
|
sku: z.string(),
|
|
265
265
|
displayName: z.string(),
|
|
266
266
|
priceAmount: z.number().default(0),
|
|
267
|
+
pricingModel: z
|
|
268
|
+
.enum(["fixed", "custom"])
|
|
269
|
+
.optional()
|
|
270
|
+
.describe(
|
|
271
|
+
'"custom" shows a contact-sales form instead of checkout; priceAmount must be 0.',
|
|
272
|
+
),
|
|
273
|
+
priceLabel: z
|
|
274
|
+
.string()
|
|
275
|
+
.max(64)
|
|
276
|
+
.optional()
|
|
277
|
+
.describe('Text shown instead of the price, e.g. "Custom".'),
|
|
267
278
|
priceCurrency: z
|
|
268
279
|
.string()
|
|
269
280
|
.trim()
|
package/LICENSE.md
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
The MIT License (MIT)
|
|
2
|
-
|
|
3
|
-
Copyright (c) Zuplo, Inc.
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
|
|
6
|
-
associated documentation files (the "Software"), to deal in the Software without restriction,
|
|
7
|
-
including without limitation the rights to use, copy, modify, merge, publish, distribute,
|
|
8
|
-
sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
|
|
9
|
-
furnished to do so, subject to the following conditions:
|
|
10
|
-
|
|
11
|
-
The above copyright notice and this permission notice shall be included in all copies or substantial
|
|
12
|
-
portions of the Software.
|
|
13
|
-
|
|
14
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
|
|
15
|
-
NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
16
|
-
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES
|
|
17
|
-
OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
|
18
|
-
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|