@lukoweb/apitogo 0.1.127 → 0.1.130

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
@@ -836,7 +836,7 @@ import { stat } from "node:fs/promises";
836
836
  var fileExists;
837
837
  var init_file_exists = __esm({
838
838
  "src/config/file-exists.ts"() {
839
- fileExists = (path72) => stat(path72).then(() => true).catch(() => false);
839
+ fileExists = (path73) => stat(path73).then(() => true).catch(() => false);
840
840
  }
841
841
  });
842
842
 
@@ -5809,7 +5809,7 @@ import {
5809
5809
  // package.json
5810
5810
  var package_default = {
5811
5811
  name: "@lukoweb/apitogo",
5812
- version: "0.1.127",
5812
+ version: "0.1.130",
5813
5813
  type: "module",
5814
5814
  sideEffects: [
5815
5815
  "**/*.css",
@@ -6588,14 +6588,14 @@ var flattenAllOf = (schema2) => {
6588
6588
  };
6589
6589
 
6590
6590
  // src/lib/util/traverse.ts
6591
- var traverse = (specification, transform, path72 = []) => {
6592
- const transformed = transform(specification, path72);
6591
+ var traverse = (specification, transform, path73 = []) => {
6592
+ const transformed = transform(specification, path73);
6593
6593
  if (typeof transformed !== "object" || transformed === null) {
6594
6594
  return transformed;
6595
6595
  }
6596
6596
  const result = Array.isArray(transformed) ? [] : {};
6597
6597
  for (const [key, value] of Object.entries(transformed)) {
6598
- const currentPath = [...path72, key];
6598
+ const currentPath = [...path73, key];
6599
6599
  if (Array.isArray(value)) {
6600
6600
  result[key] = value.map(
6601
6601
  (item, index) => typeof item === "object" && item != null ? traverse(item, transform, [...currentPath, index.toString()]) : item
@@ -6623,9 +6623,9 @@ var resolveLocalRef = (schema2, ref) => {
6623
6623
  if (schemaCache?.has(ref)) {
6624
6624
  return schemaCache.get(ref);
6625
6625
  }
6626
- const path72 = ref.split("/").slice(1);
6626
+ const path73 = ref.split("/").slice(1);
6627
6627
  let current = schema2;
6628
- for (const segment of path72) {
6628
+ for (const segment of path73) {
6629
6629
  if (!current || typeof current !== "object") {
6630
6630
  current = null;
6631
6631
  }
@@ -6644,7 +6644,7 @@ var dereference = async (schema2, resolvers = []) => {
6644
6644
  }
6645
6645
  const cloned = structuredClone(schema2);
6646
6646
  const visited = /* @__PURE__ */ new Set();
6647
- const resolve = async (current, path72) => {
6647
+ const resolve = async (current, path73) => {
6648
6648
  if (isIndexableObject(current)) {
6649
6649
  if (visited.has(current)) {
6650
6650
  return CIRCULAR_REF;
@@ -6652,7 +6652,7 @@ var dereference = async (schema2, resolvers = []) => {
6652
6652
  visited.add(current);
6653
6653
  if (Array.isArray(current)) {
6654
6654
  for (let index = 0; index < current.length; index++) {
6655
- current[index] = await resolve(current[index], `${path72}/${index}`);
6655
+ current[index] = await resolve(current[index], `${path73}/${index}`);
6656
6656
  }
6657
6657
  } else {
6658
6658
  if ("$ref" in current && typeof current.$ref === "string") {
@@ -6663,13 +6663,13 @@ var dereference = async (schema2, resolvers = []) => {
6663
6663
  for (const resolver of resolvers) {
6664
6664
  const resolved = await resolver($ref);
6665
6665
  if (resolved) {
6666
- result2 = await resolve(resolved, path72);
6666
+ result2 = await resolve(resolved, path73);
6667
6667
  break;
6668
6668
  }
6669
6669
  }
6670
6670
  if (result2 === void 0) {
6671
6671
  const resolved = await resolveLocalRef(cloned, $ref);
6672
- result2 = await resolve(resolved, path72);
6672
+ result2 = await resolve(resolved, path73);
6673
6673
  }
6674
6674
  if (hasSiblings) {
6675
6675
  if (result2 === CIRCULAR_REF) {
@@ -6682,7 +6682,7 @@ var dereference = async (schema2, resolvers = []) => {
6682
6682
  return result2;
6683
6683
  }
6684
6684
  for (const key in current) {
6685
- current[key] = await resolve(current[key], `${path72}/${key}`);
6685
+ current[key] = await resolve(current[key], `${path73}/${key}`);
6686
6686
  }
6687
6687
  }
6688
6688
  visited.delete(current);
@@ -6721,9 +6721,9 @@ var upgradeSchema = (schema2) => {
6721
6721
  }
6722
6722
  return sub;
6723
6723
  });
6724
- schema2 = traverse(schema2, (sub, path72) => {
6724
+ schema2 = traverse(schema2, (sub, path73) => {
6725
6725
  if (sub.example !== void 0) {
6726
- if (isSchemaPath(path72 ?? [])) {
6726
+ if (isSchemaPath(path73 ?? [])) {
6727
6727
  sub.examples = [sub.example];
6728
6728
  } else {
6729
6729
  sub.examples = {
@@ -6736,11 +6736,11 @@ var upgradeSchema = (schema2) => {
6736
6736
  }
6737
6737
  return sub;
6738
6738
  });
6739
- schema2 = traverse(schema2, (schema3, path72) => {
6739
+ schema2 = traverse(schema2, (schema3, path73) => {
6740
6740
  if (schema3.type === "object" && schema3.properties !== void 0) {
6741
- const parentPath = path72?.slice(0, -1);
6741
+ const parentPath = path73?.slice(0, -1);
6742
6742
  const isMultipart = parentPath?.some((segment, index) => {
6743
- return segment === "content" && path72?.[index + 1] === "multipart/form-data";
6743
+ return segment === "content" && path73?.[index + 1] === "multipart/form-data";
6744
6744
  });
6745
6745
  if (isMultipart) {
6746
6746
  const entries = Object.entries(schema3.properties);
@@ -6754,8 +6754,8 @@ var upgradeSchema = (schema2) => {
6754
6754
  }
6755
6755
  return schema3;
6756
6756
  });
6757
- schema2 = traverse(schema2, (schema3, path72) => {
6758
- if (path72?.includes("content") && path72.includes("application/octet-stream")) {
6757
+ schema2 = traverse(schema2, (schema3, path73) => {
6758
+ if (path73?.includes("content") && path73.includes("application/octet-stream")) {
6759
6759
  return {};
6760
6760
  }
6761
6761
  if (schema3.type === "string" && schema3.format === "binary") {
@@ -6781,11 +6781,11 @@ var upgradeSchema = (schema2) => {
6781
6781
  }
6782
6782
  return sub;
6783
6783
  });
6784
- schema2 = traverse(schema2, (schema3, path72) => {
6784
+ schema2 = traverse(schema2, (schema3, path73) => {
6785
6785
  if (schema3.type === "string" && schema3.format === "byte") {
6786
- const parentPath = path72?.slice(0, -1);
6786
+ const parentPath = path73?.slice(0, -1);
6787
6787
  const contentMediaType = parentPath?.find(
6788
- (_, index) => path72?.[index - 1] === "content"
6788
+ (_, index) => path73?.[index - 1] === "content"
6789
6789
  );
6790
6790
  return {
6791
6791
  type: "string",
@@ -6797,7 +6797,7 @@ var upgradeSchema = (schema2) => {
6797
6797
  });
6798
6798
  return schema2;
6799
6799
  };
6800
- function isSchemaPath(path72) {
6800
+ function isSchemaPath(path73) {
6801
6801
  const schemaLocations = [
6802
6802
  ["components", "schemas"],
6803
6803
  "properties",
@@ -6810,10 +6810,10 @@ function isSchemaPath(path72) {
6810
6810
  ];
6811
6811
  return schemaLocations.some((location) => {
6812
6812
  if (Array.isArray(location)) {
6813
- return location.every((segment, index) => path72[index] === segment);
6813
+ return location.every((segment, index) => path73[index] === segment);
6814
6814
  }
6815
- return path72.includes(location);
6816
- }) || path72.includes("schema") || path72.some((segment) => segment.endsWith("Schema"));
6815
+ return path73.includes(location);
6816
+ }) || path73.includes("schema") || path73.some((segment) => segment.endsWith("Schema"));
6817
6817
  }
6818
6818
 
6819
6819
  // src/lib/oas/parser/index.ts
@@ -6895,13 +6895,13 @@ var OPENAPI_PROPS = /* @__PURE__ */ new Set([
6895
6895
  "anyOf",
6896
6896
  "oneOf"
6897
6897
  ]);
6898
- var handleCircularRefs = (obj, currentPath = /* @__PURE__ */ new WeakSet(), refs = /* @__PURE__ */ new WeakMap(), path72 = [], currentRefPaths = /* @__PURE__ */ new Set()) => {
6898
+ var handleCircularRefs = (obj, currentPath = /* @__PURE__ */ new WeakSet(), refs = /* @__PURE__ */ new WeakMap(), path73 = [], currentRefPaths = /* @__PURE__ */ new Set()) => {
6899
6899
  if (obj === null || typeof obj !== "object") return obj;
6900
6900
  const refPath = obj.__$ref;
6901
6901
  const isCircular = currentPath.has(obj) || typeof refPath === "string" && currentRefPaths.has(refPath);
6902
6902
  if (isCircular) {
6903
6903
  if (typeof refPath === "string") return SCHEMA_REF_PREFIX + refPath;
6904
- const circularProp = path72.find((p) => !OPENAPI_PROPS.has(p)) || path72[0];
6904
+ const circularProp = path73.find((p) => !OPENAPI_PROPS.has(p)) || path73[0];
6905
6905
  return [CIRCULAR_REF, circularProp].filter(Boolean).join(":");
6906
6906
  }
6907
6907
  if (refs.has(obj)) return refs.get(obj);
@@ -6911,7 +6911,7 @@ var handleCircularRefs = (obj, currentPath = /* @__PURE__ */ new WeakSet(), refs
6911
6911
  value,
6912
6912
  currentPath,
6913
6913
  refs,
6914
- [...path72, key],
6914
+ [...path73, key],
6915
6915
  currentRefPaths
6916
6916
  );
6917
6917
  const result = Array.isArray(obj) ? obj.map((item, i) => recurse(item, i.toString())) : Object.fromEntries(
@@ -6946,7 +6946,7 @@ var resolveExtensions = (obj) => Object.fromEntries(
6946
6946
  var getAllTags = (schema2) => {
6947
6947
  const rootTags = schema2.tags ?? [];
6948
6948
  const operations = Object.values(schema2.paths ?? {}).flatMap(
6949
- (path72) => HttpMethods.map((k) => path72?.[k]).filter((op) => op != null)
6949
+ (path73) => HttpMethods.map((k) => path73?.[k]).filter((op) => op != null)
6950
6950
  );
6951
6951
  const operationTags = new Set(operations.flatMap((op) => op.tags ?? []));
6952
6952
  const hasUntaggedOperations = operations.some(
@@ -7001,7 +7001,7 @@ var getAllSlugs = (ops, schemaTags = []) => {
7001
7001
  var getOperationSlugKey = (op) => [op.path, op.method, op.operationId, op.summary].filter(Boolean).join("-");
7002
7002
  var getAllOperations = (paths) => {
7003
7003
  const operations = Object.entries(paths ?? {}).flatMap(
7004
- ([path72, value]) => HttpMethods.flatMap((method) => {
7004
+ ([path73, value]) => HttpMethods.flatMap((method) => {
7005
7005
  if (!value?.[method]) return [];
7006
7006
  const operation = value[method];
7007
7007
  const pathParameters = value.parameters ?? [];
@@ -7021,7 +7021,7 @@ var getAllOperations = (paths) => {
7021
7021
  return {
7022
7022
  ...operation,
7023
7023
  method,
7024
- path: path72,
7024
+ path: path73,
7025
7025
  parameters,
7026
7026
  servers,
7027
7027
  tags: operation.tags ?? []
@@ -7379,8 +7379,8 @@ var Schema = builder.objectRef("Schema").implement({
7379
7379
  }),
7380
7380
  paths: t.field({
7381
7381
  type: [PathItem],
7382
- resolve: (root) => Object.entries(root.paths ?? {}).map(([path72, value]) => ({
7383
- path: path72,
7382
+ resolve: (root) => Object.entries(root.paths ?? {}).map(([path73, value]) => ({
7383
+ path: path73,
7384
7384
  // biome-ignore lint/style/noNonNullAssertion: value is guaranteed to be defined
7385
7385
  methods: Object.keys(value)
7386
7386
  }))
@@ -7545,7 +7545,7 @@ init_joinUrl();
7545
7545
 
7546
7546
  // src/vite/api/schema-codegen.ts
7547
7547
  var unescapeJsonPointer = (uri) => decodeURIComponent(uri.replace(/~1/g, "/").replace(/~0/g, "~"));
7548
- var getSegmentsFromPath = (path72) => path72.split("/").slice(1).map(unescapeJsonPointer);
7548
+ var getSegmentsFromPath = (path73) => path73.split("/").slice(1).map(unescapeJsonPointer);
7549
7549
  var createLocalRefMap = (obj) => {
7550
7550
  const refMap = /* @__PURE__ */ new Map();
7551
7551
  const siblingsMap = /* @__PURE__ */ new Map();
@@ -7576,16 +7576,16 @@ var replaceMarkers = (code, mergedRefs) => code.replace(/"__refMap:(.*?)"/g, '__
7576
7576
  /"__refMap\+Siblings:(.*?)"/g,
7577
7577
  (_, key) => mergedRefs.get(key) ?? `__refMap["${key}"]`
7578
7578
  );
7579
- var lookup = (schema2, path72, filePath) => {
7580
- const parts = getSegmentsFromPath(path72);
7579
+ var lookup = (schema2, path73, filePath) => {
7580
+ const parts = getSegmentsFromPath(path73);
7581
7581
  let val = schema2;
7582
7582
  for (const part of parts) {
7583
7583
  while (val.$ref?.startsWith("#/")) {
7584
- val = val.$ref === path72 ? val : lookup(schema2, val.$ref, filePath);
7584
+ val = val.$ref === path73 ? val : lookup(schema2, val.$ref, filePath);
7585
7585
  }
7586
7586
  if (val[part] === void 0) {
7587
7587
  throw new Error(
7588
- `Error in ${filePath ?? "code generation"}: Could not find path segment ${part} in path: ${path72}`
7588
+ `Error in ${filePath ?? "code generation"}: Could not find path segment ${part} in path: ${path73}`
7589
7589
  );
7590
7590
  }
7591
7591
  val = val[part];
@@ -7830,8 +7830,8 @@ var SchemaManager = class {
7830
7830
  }
7831
7831
  }
7832
7832
  };
7833
- getLatestSchema = (path72) => this.processedSchemas[path72]?.at(0);
7834
- getSchemasForPath = (path72) => this.processedSchemas[path72];
7833
+ getLatestSchema = (path73) => this.processedSchemas[path73]?.at(0);
7834
+ getSchemasForPath = (path73) => this.processedSchemas[path73];
7835
7835
  getSchemaImports = () => Object.values(this.processedSchemas).flat().filter((s) => s.importKey);
7836
7836
  getUrlToFilePathMap = () => {
7837
7837
  const map = /* @__PURE__ */ new Map();
@@ -10269,14 +10269,14 @@ async function generateSitemap({
10269
10269
 
10270
10270
  // src/vite/prerender/utils.ts
10271
10271
  init_joinUrl();
10272
- var resolveRoutePath = (path72) => {
10273
- const segments = path72.split("/");
10272
+ var resolveRoutePath = (path73) => {
10273
+ const segments = path73.split("/");
10274
10274
  if (segments.some((s) => s.startsWith(":") && !s.endsWith("?"))) {
10275
10275
  return void 0;
10276
10276
  }
10277
10277
  return segments.filter((s) => !s.startsWith(":")).join("/") || void 0;
10278
10278
  };
10279
- var isSkipped = (path72) => path72.includes("*") || /^\d+$/.test(path72);
10279
+ var isSkipped = (path73) => path73.includes("*") || /^\d+$/.test(path73);
10280
10280
  var resolveRoutes = (routes, parentPath = "") => routes.flatMap((route) => {
10281
10281
  if (route.path && isSkipped(route.path)) return [];
10282
10282
  const routePath = route.path ? resolveRoutePath(route.path) : void 0;
@@ -10908,8 +10908,8 @@ var PlatformApiClient = class {
10908
10908
  constructor(config2) {
10909
10909
  this.config = config2;
10910
10910
  }
10911
- url(path72) {
10912
- return `${this.config.apiUrl}${path72.startsWith("/") ? path72 : `/${path72}`}`;
10911
+ url(path73) {
10912
+ return `${this.config.apiUrl}${path73.startsWith("/") ? path73 : `/${path73}`}`;
10913
10913
  }
10914
10914
  headers(extra) {
10915
10915
  return {
@@ -10919,9 +10919,9 @@ var PlatformApiClient = class {
10919
10919
  ...extra
10920
10920
  };
10921
10921
  }
10922
- async request(method, path72, init4) {
10922
+ async request(method, path73, init4) {
10923
10923
  const response = await fetchWithRetry(
10924
- this.url(path72),
10924
+ this.url(path73),
10925
10925
  {
10926
10926
  method,
10927
10927
  ...init4,
@@ -11133,15 +11133,15 @@ var PlatformApiClient = class {
11133
11133
  }
11134
11134
  );
11135
11135
  }
11136
- deleteDevPortalPage(projectId, path72) {
11137
- const query = new URLSearchParams({ path: path72 });
11136
+ deleteDevPortalPage(projectId, path73) {
11137
+ const query = new URLSearchParams({ path: path73 });
11138
11138
  return this.request(
11139
11139
  "DELETE",
11140
11140
  `/api/v1/projects/${projectId}/dev-portal/pages/files?${query.toString()}`
11141
11141
  );
11142
11142
  }
11143
- renameDevPortalPage(projectId, path72, newName) {
11144
- const query = new URLSearchParams({ path: path72 });
11143
+ renameDevPortalPage(projectId, path73, newName) {
11144
+ const query = new URLSearchParams({ path: path73 });
11145
11145
  return this.request(
11146
11146
  "POST",
11147
11147
  `/api/v1/projects/${projectId}/dev-portal/pages/files/rename?${query.toString()}`,
@@ -16205,7 +16205,7 @@ async function ensureTsconfigJson(dir) {
16205
16205
  return true;
16206
16206
  }
16207
16207
  }
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 <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';
16209
16209
  var ACCOUNT_PLANS_PAGE_SOURCE = `export { AccountPlansPage as default } from "@lukoweb/apitogo-plugin-dev-portal-billing";
16210
16210
  `;
16211
16211
  var DASHBOARD_PAGE_SOURCE = `export { OverviewDashboardPage as default } from "@lukoweb/apitogo-plugin-dev-portal-billing";
@@ -17129,8 +17129,8 @@ function normalizeActionKey(actionKey) {
17129
17129
  const trimmed = actionKey.trim();
17130
17130
  if (trimmed.includes(":")) {
17131
17131
  const [method2, ...pathParts] = trimmed.split(":");
17132
- const path73 = pathParts.join(":").trim();
17133
- return `${method2?.toLowerCase()}:${path73.startsWith("/") ? path73 : `/${path73}`}`;
17132
+ const path74 = pathParts.join(":").trim();
17133
+ return `${method2?.toLowerCase()}:${path74.startsWith("/") ? path74 : `/${path74}`}`;
17134
17134
  }
17135
17135
  const spaceIndex = trimmed.indexOf(" ");
17136
17136
  if (spaceIndex <= 0) {
@@ -17139,8 +17139,8 @@ function normalizeActionKey(actionKey) {
17139
17139
  );
17140
17140
  }
17141
17141
  const method = trimmed.slice(0, spaceIndex).trim().toLowerCase();
17142
- const path72 = trimmed.slice(spaceIndex + 1).trim();
17143
- return `${method}:${path72.startsWith("/") ? path72 : `/${path72}`}`;
17142
+ const path73 = trimmed.slice(spaceIndex + 1).trim();
17143
+ return `${method}:${path73.startsWith("/") ? path73 : `/${path73}`}`;
17144
17144
  }
17145
17145
  async function applyGatewayPublicRoutes(client, projectId, routes, options) {
17146
17146
  const applied = [];
@@ -17758,8 +17758,8 @@ function normalizeActionKeyFormat(actionKey) {
17758
17758
  const trimmed = actionKey.trim();
17759
17759
  if (trimmed.includes(":")) {
17760
17760
  const [method2, ...pathParts] = trimmed.split(":");
17761
- const path73 = pathParts.join(":").trim();
17762
- return `${method2.toLowerCase()}:${path73.startsWith("/") ? path73 : `/${path73}`}`;
17761
+ const path74 = pathParts.join(":").trim();
17762
+ return `${method2.toLowerCase()}:${path74.startsWith("/") ? path74 : `/${path74}`}`;
17763
17763
  }
17764
17764
  const spaceIndex = trimmed.indexOf(" ");
17765
17765
  if (spaceIndex <= 0) {
@@ -17768,8 +17768,8 @@ function normalizeActionKeyFormat(actionKey) {
17768
17768
  );
17769
17769
  }
17770
17770
  const method = trimmed.slice(0, spaceIndex).trim().toLowerCase();
17771
- const path72 = trimmed.slice(spaceIndex + 1).trim();
17772
- return `${method}:${path72.startsWith("/") ? path72 : `/${path72}`}`;
17771
+ const path73 = trimmed.slice(spaceIndex + 1).trim();
17772
+ return `${method}:${path73.startsWith("/") ? path73 : `/${path73}`}`;
17773
17773
  }
17774
17774
  function normalizePublicRoute(route) {
17775
17775
  const trimmed = route.trim();
@@ -18273,17 +18273,14 @@ function isWarmPoolInfrastructureTerminal(project) {
18273
18273
  return status2 === "ready" || status2 === "failed" || status2 === "deleting";
18274
18274
  }
18275
18275
  function shouldSkipColdModuleInfrastructureWait(project, infraStatus) {
18276
- if (isWarmPoolInfrastructureReady(project)) {
18277
- return true;
18278
- }
18279
18276
  const resourceType = infraStatus?.resourceType;
18280
- if (resourceType === "WarmPoolBinding") {
18281
- return true;
18282
- }
18283
18277
  if (resourceType && COLD_MODULE_INFRA_RESOURCE_TYPES.has(resourceType)) {
18284
18278
  return false;
18285
18279
  }
18286
- return false;
18280
+ if (resourceType === "WarmPoolBinding") {
18281
+ return true;
18282
+ }
18283
+ return isWarmPoolInfrastructureReady(project);
18287
18284
  }
18288
18285
  function resolvePortalBackendFqdn(project) {
18289
18286
  const portalApiUrl = project.portalApiUrl?.trim();
@@ -22812,8 +22809,8 @@ import readline from "node:readline/promises";
22812
22809
  import open2 from "open";
22813
22810
 
22814
22811
  // src/cli/platform/device-auth.ts
22815
- function url(apiUrl, path72) {
22816
- return `${apiUrl}${path72.startsWith("/") ? path72 : `/${path72}`}`;
22812
+ function url(apiUrl, path73) {
22813
+ return `${apiUrl}${path73.startsWith("/") ? path73 : `/${path73}`}`;
22817
22814
  }
22818
22815
  async function requestDeviceCode(apiUrl) {
22819
22816
  const response = await fetchWithRetry(
@@ -23730,8 +23727,79 @@ function createApitogoCli(argv) {
23730
23727
  }).middleware(warnPackageVersionMismatch).command(command(init_default)).command(command(config_default)).command(command(run_default)).command(command(build_default)).command(command(check_default)).command(command(project_create_default)).command(command(deploy_default)).command(command(status_default)).command(command(teardown_default)).command(command(login_default)).command(command(logout_default)).demandCommand().strictCommands().version(packageJson?.version).fail(false).help();
23731
23728
  }
23732
23729
 
23730
+ // src/cli/local-handoff.ts
23731
+ import { spawn as spawn4 } from "node:child_process";
23732
+ import { existsSync as existsSync6, realpathSync } from "node:fs";
23733
+ import path72 from "node:path";
23734
+ var LOCAL_HANDOFF_ENV = "APITOGO_LOCAL_CLI";
23735
+ var PACKAGE_NAME = "@lukoweb/apitogo";
23736
+ var resolveProjectDir = (argv, cwd) => {
23737
+ for (let i = 0; i < argv.length; i++) {
23738
+ const arg = argv[i];
23739
+ const next = argv[i + 1];
23740
+ if (arg === "--dir" && next) return path72.resolve(cwd, next);
23741
+ if (arg?.startsWith("--dir=")) {
23742
+ return path72.resolve(cwd, arg.slice("--dir=".length));
23743
+ }
23744
+ }
23745
+ return cwd;
23746
+ };
23747
+ var findLocalCli = (projectDir, runningRoot) => {
23748
+ let dir = path72.resolve(projectDir);
23749
+ let localPackageRoot;
23750
+ while (!localPackageRoot) {
23751
+ const candidate = path72.join(dir, "node_modules", PACKAGE_NAME);
23752
+ if (existsSync6(path72.join(candidate, "package.json"))) {
23753
+ localPackageRoot = candidate;
23754
+ } else if (dir === path72.dirname(dir)) {
23755
+ return void 0;
23756
+ } else {
23757
+ dir = path72.dirname(dir);
23758
+ }
23759
+ }
23760
+ const localRoot = realpathSync(localPackageRoot);
23761
+ if (localRoot === realpathSync(runningRoot)) return void 0;
23762
+ return path72.join(localRoot, "cli.js");
23763
+ };
23764
+ var handoffToLocalCli = async (argv, runningRoot) => {
23765
+ if (process.env[LOCAL_HANDOFF_ENV] || process.env.ZUDOKU_ENV === "internal") {
23766
+ return void 0;
23767
+ }
23768
+ const localCli = findLocalCli(
23769
+ resolveProjectDir(argv, process.cwd()),
23770
+ runningRoot
23771
+ );
23772
+ if (!localCli) return void 0;
23773
+ printDiagnosticsToConsole(
23774
+ `Using the project's local ${PACKAGE_NAME} (${path72.dirname(localCli)})`
23775
+ );
23776
+ const child = spawn4(process.execPath, [localCli, ...argv], {
23777
+ stdio: "inherit",
23778
+ env: { ...process.env, [LOCAL_HANDOFF_ENV]: "1" }
23779
+ });
23780
+ process.on("SIGINT", () => {
23781
+ });
23782
+ process.on("SIGTERM", () => child.kill("SIGTERM"));
23783
+ return new Promise((resolve) => {
23784
+ child.on("error", (err) => {
23785
+ printDiagnosticsToConsole(
23786
+ `Failed to start local ${PACKAGE_NAME}: ${err.message}`
23787
+ );
23788
+ resolve(1);
23789
+ });
23790
+ child.on("exit", (code, signal) => resolve(code ?? (signal ? 1 : 0)));
23791
+ });
23792
+ };
23793
+
23733
23794
  // src/cli/cli.ts
23734
23795
  process.env.ZUDOKU_ENV = process.env.ZUDOKU_INTERNAL_DEV ? "internal" : "module";
23796
+ var handoffExitCode = await handoffToLocalCli(
23797
+ hideBin(process.argv),
23798
+ getZudokuRootDir()
23799
+ );
23800
+ if (handoffExitCode !== void 0) {
23801
+ process.exit(handoffExitCode);
23802
+ }
23735
23803
  var packageJson2 = getZudokuPackageJson();
23736
23804
  if (SENTRY_DSN) {
23737
23805
  Sentry2.init({
@@ -34,6 +34,7 @@ export type LandingTrustInput = {
34
34
  export type LandingApiShowcaseItemInput = {
35
35
  name: string;
36
36
  path: string;
37
+ method?: string;
37
38
  icon?: string;
38
39
  latency?: string;
39
40
  summary?: string;
@@ -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.127",
3
+ "version": "0.1.130",
4
4
  "type": "module",
5
5
  "sideEffects": [
6
6
  "**/*.css",