@lukoweb/apitogo 0.1.138 → 0.1.140
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli/cli.js
CHANGED
|
@@ -5809,7 +5809,7 @@ import {
|
|
|
5809
5809
|
// package.json
|
|
5810
5810
|
var package_default = {
|
|
5811
5811
|
name: "@lukoweb/apitogo",
|
|
5812
|
-
version: "0.1.
|
|
5812
|
+
version: "0.1.140",
|
|
5813
5813
|
type: "module",
|
|
5814
5814
|
sideEffects: [
|
|
5815
5815
|
"**/*.css",
|
|
@@ -16326,7 +16326,7 @@ async function ensureTsconfigJson(dir) {
|
|
|
16326
16326
|
return true;
|
|
16327
16327
|
}
|
|
16328
16328
|
}
|
|
16329
|
-
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
|
|
16329
|
+
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";\nimport { usePlansPreview } from "@lukoweb/apitogo-plugin-dev-portal-billing";\n\nexport default function LandingPage() {\n // Plans from modules.userPanel.plans.items (or the live dev portal backend).\n const plans = usePlansPreview();\n\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 plans={plans} />\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';
|
|
16330
16330
|
var ACCOUNT_PLANS_PAGE_SOURCE = `export { AccountPlansPage as default } from "@lukoweb/apitogo-plugin-dev-portal-billing";
|
|
16331
16331
|
`;
|
|
16332
16332
|
var DASHBOARD_PAGE_SOURCE = `export { OverviewDashboardPage as default } from "@lukoweb/apitogo-plugin-dev-portal-billing";
|
|
@@ -19048,6 +19048,20 @@ async function isBackendDeploymentHealthy(client, projectId) {
|
|
|
19048
19048
|
return summary.lastDeploymentStatus?.toLowerCase() === "healthy";
|
|
19049
19049
|
}
|
|
19050
19050
|
|
|
19051
|
+
// src/cli/platform/helpers/project-name.ts
|
|
19052
|
+
function normalizeProjectName(name) {
|
|
19053
|
+
return name.trim().split(/\s+/).filter(Boolean).join(" ").toLowerCase();
|
|
19054
|
+
}
|
|
19055
|
+
function findProjectByNormalizedName(projects, name) {
|
|
19056
|
+
const needle = normalizeProjectName(name);
|
|
19057
|
+
if (!needle) {
|
|
19058
|
+
return void 0;
|
|
19059
|
+
}
|
|
19060
|
+
return projects.find(
|
|
19061
|
+
(project) => normalizeProjectName(project.name) === needle
|
|
19062
|
+
);
|
|
19063
|
+
}
|
|
19064
|
+
|
|
19051
19065
|
// src/cli/platform/helpers/platform-plan.ts
|
|
19052
19066
|
var UNSELECTED_TIER = "unselected";
|
|
19053
19067
|
var FREE_TIER = "free";
|
|
@@ -19120,20 +19134,6 @@ function parseToolJsonResult(result) {
|
|
|
19120
19134
|
}
|
|
19121
19135
|
}
|
|
19122
19136
|
|
|
19123
|
-
// src/cli/platform/helpers/project-name.ts
|
|
19124
|
-
function normalizeProjectName(name) {
|
|
19125
|
-
return name.trim().split(/\s+/).filter(Boolean).join(" ").toLowerCase();
|
|
19126
|
-
}
|
|
19127
|
-
function findProjectByNormalizedName(projects, name) {
|
|
19128
|
-
const needle = normalizeProjectName(name);
|
|
19129
|
-
if (!needle) {
|
|
19130
|
-
return void 0;
|
|
19131
|
-
}
|
|
19132
|
-
return projects.find(
|
|
19133
|
-
(project) => normalizeProjectName(project.name) === needle
|
|
19134
|
-
);
|
|
19135
|
-
}
|
|
19136
|
-
|
|
19137
19137
|
// src/cli/platform/helpers/platform-plan-catalog.ts
|
|
19138
19138
|
var PLATFORM_PLAN_CATALOG = [
|
|
19139
19139
|
{
|
|
@@ -20155,6 +20155,27 @@ async function resolveProjectIdForPublish(client, config2, args, wait, progress,
|
|
|
20155
20155
|
if (!args.projectName?.trim()) {
|
|
20156
20156
|
throw new Error("projectName is required when projectId is not provided.");
|
|
20157
20157
|
}
|
|
20158
|
+
if (!args.reuseExistingByName) {
|
|
20159
|
+
const projectName = args.projectName.trim();
|
|
20160
|
+
const existing = findProjectByNormalizedName(
|
|
20161
|
+
await client.listProjects(),
|
|
20162
|
+
projectName
|
|
20163
|
+
);
|
|
20164
|
+
if (existing) {
|
|
20165
|
+
return {
|
|
20166
|
+
blocked: true,
|
|
20167
|
+
payload: {
|
|
20168
|
+
status: "blocked",
|
|
20169
|
+
reason: "project_name_exists",
|
|
20170
|
+
projectId: existing.id,
|
|
20171
|
+
projectName,
|
|
20172
|
+
userMessage: `The config has no projectId, but a project named "${projectName}" already exists on your account.`,
|
|
20173
|
+
nextStep: "reuse_or_rename_project",
|
|
20174
|
+
hint: "Re-run with a different --project-name to create a new project, or set projectId in the config to deploy to the existing one."
|
|
20175
|
+
}
|
|
20176
|
+
};
|
|
20177
|
+
}
|
|
20178
|
+
}
|
|
20158
20179
|
await progress?.onStep("Creating project and provisioning modules");
|
|
20159
20180
|
const bootstrapJson = await handleBootstrapProject(
|
|
20160
20181
|
client,
|
|
@@ -21108,7 +21129,8 @@ async function runApplyFromSiteDirectory(client, config2, args, progress) {
|
|
|
21108
21129
|
config2,
|
|
21109
21130
|
{
|
|
21110
21131
|
projectId: args.createNewProject ? void 0 : args.projectId ?? manifest.projectId,
|
|
21111
|
-
projectName: args.projectName ?? resolveProjectDisplayName(manifest)
|
|
21132
|
+
projectName: args.projectName ?? resolveProjectDisplayName(manifest),
|
|
21133
|
+
reuseExistingByName: args.reuseExistingProject
|
|
21112
21134
|
},
|
|
21113
21135
|
wait,
|
|
21114
21136
|
publishProgress,
|
|
@@ -21983,6 +22005,16 @@ function formatBlockedOrFailedResultForHuman(parsed) {
|
|
|
21983
22005
|
);
|
|
21984
22006
|
return lines;
|
|
21985
22007
|
}
|
|
22008
|
+
if (status2 === "blocked" && parsed.reason === "project_name_exists") {
|
|
22009
|
+
const projectId = typeof parsed.projectId === "string" ? parsed.projectId : void 0;
|
|
22010
|
+
return [
|
|
22011
|
+
typeof parsed.userMessage === "string" ? parsed.userMessage : "Deploy is blocked: a project with this name already exists.",
|
|
22012
|
+
"",
|
|
22013
|
+
"To fix this:",
|
|
22014
|
+
` Deploy to the existing project: set "projectId"${projectId ? ` to "${projectId}"` : ""} in apitogo.config.json`,
|
|
22015
|
+
" Or create a new project: re-run `apitogo deploy --project-name <new-name>`"
|
|
22016
|
+
];
|
|
22017
|
+
}
|
|
21986
22018
|
if (status2 === "blocked" && parsed.reason === "project_not_found") {
|
|
21987
22019
|
const projectId = typeof parsed.projectId === "string" ? parsed.projectId : void 0;
|
|
21988
22020
|
const lines = [
|
|
@@ -22226,6 +22258,34 @@ async function resolveProjectNotFoundInteractively(payload, defaultProjectName)
|
|
|
22226
22258
|
rl.close();
|
|
22227
22259
|
}
|
|
22228
22260
|
}
|
|
22261
|
+
async function resolveProjectNameExistsInteractively(payload, defaultProjectName) {
|
|
22262
|
+
const existingName = typeof payload.projectName === "string" ? payload.projectName : defaultProjectName;
|
|
22263
|
+
const projectId = typeof payload.projectId === "string" ? payload.projectId : "";
|
|
22264
|
+
printWarningToConsole(
|
|
22265
|
+
`This site has no projectId, but a project named "${existingName}"${projectId ? ` (${projectId})` : ""} already exists on your account.`
|
|
22266
|
+
);
|
|
22267
|
+
printDiagnosticsToConsole("");
|
|
22268
|
+
const rl = createInterface3({ input: input4, output: output3 });
|
|
22269
|
+
try {
|
|
22270
|
+
const choice = (await rl.question(
|
|
22271
|
+
"[c] Create a new project [u] Use the existing project [x] Cancel (default c): "
|
|
22272
|
+
)).trim().toLowerCase();
|
|
22273
|
+
if (choice === "x" || choice === "cancel") {
|
|
22274
|
+
return null;
|
|
22275
|
+
}
|
|
22276
|
+
if (choice === "u" || choice === "use") {
|
|
22277
|
+
return { action: "reuse" };
|
|
22278
|
+
}
|
|
22279
|
+
for (; ; ) {
|
|
22280
|
+
const nameAnswer = (await rl.question("New project name: ")).trim();
|
|
22281
|
+
if (nameAnswer) {
|
|
22282
|
+
return { action: "create", projectName: nameAnswer };
|
|
22283
|
+
}
|
|
22284
|
+
}
|
|
22285
|
+
} finally {
|
|
22286
|
+
rl.close();
|
|
22287
|
+
}
|
|
22288
|
+
}
|
|
22229
22289
|
|
|
22230
22290
|
// src/cli/apply/handler.ts
|
|
22231
22291
|
function normalizeOnly(only) {
|
|
@@ -22277,6 +22337,7 @@ async function apply(argv) {
|
|
|
22277
22337
|
let effectiveProjectId = argv.projectId?.trim();
|
|
22278
22338
|
let effectiveProjectName = argv.projectName?.trim();
|
|
22279
22339
|
let createNewProject = false;
|
|
22340
|
+
let reuseExistingProject = false;
|
|
22280
22341
|
const runOnce = (plan) => runApplyFromSiteDirectory(
|
|
22281
22342
|
client,
|
|
22282
22343
|
config2,
|
|
@@ -22289,7 +22350,8 @@ async function apply(argv) {
|
|
|
22289
22350
|
force: argv.force,
|
|
22290
22351
|
resumeFromStep: argv.resumeFrom?.trim(),
|
|
22291
22352
|
wait: argv.wait !== false,
|
|
22292
|
-
createNewProject
|
|
22353
|
+
createNewProject,
|
|
22354
|
+
reuseExistingProject
|
|
22293
22355
|
},
|
|
22294
22356
|
progress
|
|
22295
22357
|
);
|
|
@@ -22324,6 +22386,30 @@ async function apply(argv) {
|
|
|
22324
22386
|
parsed = safeParseResult(result);
|
|
22325
22387
|
}
|
|
22326
22388
|
}
|
|
22389
|
+
while (canPromptInteractively && parsed?.status === "blocked" && parsed.reason === "project_name_exists") {
|
|
22390
|
+
const resolution = await resolveProjectNameExistsInteractively(
|
|
22391
|
+
parsed,
|
|
22392
|
+
effectiveProjectName ?? path63.basename(sourceDirectory)
|
|
22393
|
+
);
|
|
22394
|
+
if (!resolution) {
|
|
22395
|
+
break;
|
|
22396
|
+
}
|
|
22397
|
+
if (resolution.action === "reuse") {
|
|
22398
|
+
reuseExistingProject = true;
|
|
22399
|
+
} else {
|
|
22400
|
+
effectiveProjectName = resolution.projectName;
|
|
22401
|
+
createNewProject = true;
|
|
22402
|
+
}
|
|
22403
|
+
try {
|
|
22404
|
+
result = await runOnce(platformPlan);
|
|
22405
|
+
} catch (error) {
|
|
22406
|
+
await printCriticalFailureToConsoleAndExit(
|
|
22407
|
+
error instanceof Error ? error.message : String(error)
|
|
22408
|
+
);
|
|
22409
|
+
return;
|
|
22410
|
+
}
|
|
22411
|
+
parsed = safeParseResult(result);
|
|
22412
|
+
}
|
|
22327
22413
|
if (canPromptInteractively && parsed?.status === "blocked" && parsed.reason === "platform_plan_unselected") {
|
|
22328
22414
|
const blockedProjectId = typeof parsed.projectId === "string" ? parsed.projectId : void 0;
|
|
22329
22415
|
const resolved = await resolvePlatformPlanInteractively(
|
|
@@ -27,4 +27,5 @@ export type ResolveProjectIdResult = {
|
|
|
27
27
|
export declare function resolveProjectIdForPublish(client: PlatformApiClient, config: ApitogoConfig, args: {
|
|
28
28
|
projectId?: string;
|
|
29
29
|
projectName?: string;
|
|
30
|
+
reuseExistingByName?: boolean;
|
|
30
31
|
}, wait: boolean, progress?: ProgressReporter, timeoutMs?: number, pollIntervalMs?: number): Promise<ResolveProjectIdResult>;
|
|
@@ -24,6 +24,7 @@ export interface PublishDevPortalArgs {
|
|
|
24
24
|
force?: boolean;
|
|
25
25
|
resumeFromStep?: PublishStepId;
|
|
26
26
|
createNewProject?: boolean;
|
|
27
|
+
reuseExistingProject?: boolean;
|
|
27
28
|
}
|
|
28
29
|
export type ApplyFromSiteDirectoryArgs = PublishDevPortalArgs;
|
|
29
30
|
export declare function validateManifestForPublish(manifest: DevPortalLocalManifest, secrets: DevPortalLocalSecrets, emailOptions?: {
|
|
@@ -157,3 +157,32 @@ to the gateway if they must not be discoverable.
|
|
|
157
157
|
from another domain requires adding it to the backend's allowed origins.
|
|
158
158
|
- **Empty list on a new portal:** the portal is not activated yet, or the gateway has no release.
|
|
159
159
|
Deploy your backend, then wait for the next sync.
|
|
160
|
+
|
|
161
|
+
## Plans preview
|
|
162
|
+
|
|
163
|
+
`PlansPreviewSection` shows the same plans as `/pricing`. The scaffold loads them with the
|
|
164
|
+
`usePlansPreview` hook from the billing plugin, so you only maintain plans in one place —
|
|
165
|
+
`modules.userPanel.plans.items` in `apitogo.config.json`:
|
|
166
|
+
|
|
167
|
+
```tsx
|
|
168
|
+
import { usePlansPreview } from "@lukoweb/apitogo-plugin-dev-portal-billing";
|
|
169
|
+
|
|
170
|
+
export default function LandingPage() {
|
|
171
|
+
const plans = usePlansPreview();
|
|
172
|
+
|
|
173
|
+
return (
|
|
174
|
+
<LandingShell title="…">
|
|
175
|
+
{/* … */}
|
|
176
|
+
<PlansPreviewSection plans={plans} />
|
|
177
|
+
</LandingShell>
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
In a live portal the plans come from your dev portal backend (`GET /api/v1/plans`); in local
|
|
183
|
+
preview, and while the live catalog loads, they come from `apitogo.config.json`. Each card shows the
|
|
184
|
+
plan's `displayName`, price and billing period, and its first two feature lines (`features`, or
|
|
185
|
+
lines derived from `limits`). The highlighted card matches the "Popular" plan on `/pricing`.
|
|
186
|
+
|
|
187
|
+
To write the cards by hand instead, pass a static array of `{ name, price, unit, blurb, featured }`
|
|
188
|
+
objects to `plans`.
|