@lukoweb/apitogo 0.1.77 → 0.1.79

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
@@ -744,7 +744,7 @@ import { stat } from "node:fs/promises";
744
744
  var fileExists;
745
745
  var init_file_exists = __esm({
746
746
  "src/config/file-exists.ts"() {
747
- fileExists = (path41) => stat(path41).then(() => true).catch(() => false);
747
+ fileExists = (path45) => stat(path45).then(() => true).catch(() => false);
748
748
  }
749
749
  });
750
750
 
@@ -5214,7 +5214,6 @@ var VERSION_CHECK_FILE = "version.json";
5214
5214
  var SENTRY_DSN = void 0;
5215
5215
  var MAX_WAIT_PENDING_TIME_MS = 1e3;
5216
5216
  var POST_HOG_CAPTURE_KEY = void 0;
5217
- var DEPLOY_SERVER_API_KEY = "qRVLTP22TFStdN6";
5218
5217
 
5219
5218
  // src/cli/common/machine-id/lib.ts
5220
5219
  import { execSync } from "node:child_process";
@@ -5338,13 +5337,6 @@ function printJsonOnlyErrorAndExit(message) {
5338
5337
  function printResultToConsole(message) {
5339
5338
  console.log(colors.bold(colors.green(message)));
5340
5339
  }
5341
- function textOrJson(text) {
5342
- try {
5343
- return JSON.parse(text);
5344
- } catch {
5345
- return text;
5346
- }
5347
- }
5348
5340
 
5349
5341
  // src/cli/apply/handler.ts
5350
5342
  async function pathExists(candidate) {
@@ -5376,6 +5368,11 @@ async function resolveApitogoApplyCommand() {
5376
5368
  note: "Using npx @lukoweb/apitogo-mcp (set APITOGO_MCP_PATH for a local checkout)"
5377
5369
  };
5378
5370
  }
5371
+ function normalizeOnly(only) {
5372
+ if (!only) return [];
5373
+ const values = Array.isArray(only) ? only : [only];
5374
+ return values.flatMap((value) => value.split(",")).map((value) => value.trim()).filter(Boolean);
5375
+ }
5379
5376
  function buildApplyArgs(argv) {
5380
5377
  const dir = path.resolve(process.cwd(), argv.dir);
5381
5378
  const args = [dir];
@@ -5388,14 +5385,11 @@ function buildApplyArgs(argv) {
5388
5385
  if (argv.platformPlan?.trim()) {
5389
5386
  args.push("--platform-plan", argv.platformPlan.trim());
5390
5387
  }
5391
- if (argv.skipBackend) {
5392
- args.push("--skip-backend");
5393
- }
5394
- if (argv.forceBackendRedeploy) {
5395
- args.push("--force-backend-redeploy");
5388
+ for (const resource of normalizeOnly(argv.only)) {
5389
+ args.push("--only", resource);
5396
5390
  }
5397
- if (argv.forceSiteRedeploy) {
5398
- args.push("--force-site-redeploy");
5391
+ if (argv.force) {
5392
+ args.push("--force");
5399
5393
  }
5400
5394
  if (argv.resumeFrom?.trim()) {
5401
5395
  args.push("--resume-from", argv.resumeFrom.trim());
@@ -5413,7 +5407,7 @@ async function apply(argv) {
5413
5407
  try {
5414
5408
  resolved = await resolveApitogoApplyCommand();
5415
5409
  } catch (error) {
5416
- printCriticalFailureToConsoleAndExit(
5410
+ await printCriticalFailureToConsoleAndExit(
5417
5411
  error instanceof Error ? error.message : String(error)
5418
5412
  );
5419
5413
  return;
@@ -5429,7 +5423,7 @@ async function apply(argv) {
5429
5423
  shell: process.platform === "win32"
5430
5424
  });
5431
5425
  child.on("error", (error) => {
5432
- printCriticalFailureToConsoleAndExit(
5426
+ void printCriticalFailureToConsoleAndExit(
5433
5427
  `Failed to start apitogo-apply: ${error.message}`
5434
5428
  );
5435
5429
  resolvePromise(1);
@@ -5445,7 +5439,7 @@ async function apply(argv) {
5445
5439
 
5446
5440
  // src/cli/cmds/apply.ts
5447
5441
  var apply_default = {
5448
- desc: "Apply apitogo.config.json to the APItoGo platform (gateway, portal, backend, site)",
5442
+ desc: "Apply apitogo.config.json to the APItoGo platform (apihub, portal backend, portal frontend)",
5449
5443
  command: "apply",
5450
5444
  builder: (yargs2) => yargs2.option("dir", {
5451
5445
  type: "string",
@@ -5462,17 +5456,12 @@ var apply_default = {
5462
5456
  type: "string",
5463
5457
  describe: "Platform hosting tier",
5464
5458
  choices: ["free", "starter", "professional"]
5465
- }).option("skip-backend", {
5466
- type: "boolean",
5467
- describe: "Skip backend redeploy when healthy",
5468
- default: false
5469
- }).option("force-backend-redeploy", {
5470
- type: "boolean",
5471
- describe: "Force backend package + deploy",
5472
- default: false
5473
- }).option("force-site-redeploy", {
5459
+ }).option("only", {
5460
+ type: "string",
5461
+ describe: "Limit to resources: apihub, portal-backend, portal-frontend (comma-separated, repeatable)"
5462
+ }).option("force", {
5474
5463
  type: "boolean",
5475
- describe: "Force site build + deploy",
5464
+ describe: "Force redeploy of selected resources even when healthy",
5476
5465
  default: false
5477
5466
  }).option("resume-from", {
5478
5467
  type: "string",
@@ -5644,7 +5633,7 @@ import {
5644
5633
  // package.json
5645
5634
  var package_default = {
5646
5635
  name: "@lukoweb/apitogo",
5647
- version: "0.1.77",
5636
+ version: "0.1.79",
5648
5637
  type: "module",
5649
5638
  sideEffects: [
5650
5639
  "**/*.css",
@@ -6413,14 +6402,14 @@ var flattenAllOf = (schema2) => {
6413
6402
  };
6414
6403
 
6415
6404
  // src/lib/util/traverse.ts
6416
- var traverse = (specification, transform, path41 = []) => {
6417
- const transformed = transform(specification, path41);
6405
+ var traverse = (specification, transform, path45 = []) => {
6406
+ const transformed = transform(specification, path45);
6418
6407
  if (typeof transformed !== "object" || transformed === null) {
6419
6408
  return transformed;
6420
6409
  }
6421
6410
  const result = Array.isArray(transformed) ? [] : {};
6422
6411
  for (const [key, value] of Object.entries(transformed)) {
6423
- const currentPath = [...path41, key];
6412
+ const currentPath = [...path45, key];
6424
6413
  if (Array.isArray(value)) {
6425
6414
  result[key] = value.map(
6426
6415
  (item, index) => typeof item === "object" && item != null ? traverse(item, transform, [...currentPath, index.toString()]) : item
@@ -6448,9 +6437,9 @@ var resolveLocalRef = (schema2, ref) => {
6448
6437
  if (schemaCache?.has(ref)) {
6449
6438
  return schemaCache.get(ref);
6450
6439
  }
6451
- const path41 = ref.split("/").slice(1);
6440
+ const path45 = ref.split("/").slice(1);
6452
6441
  let current = schema2;
6453
- for (const segment of path41) {
6442
+ for (const segment of path45) {
6454
6443
  if (!current || typeof current !== "object") {
6455
6444
  current = null;
6456
6445
  }
@@ -6469,7 +6458,7 @@ var dereference = async (schema2, resolvers = []) => {
6469
6458
  }
6470
6459
  const cloned = structuredClone(schema2);
6471
6460
  const visited = /* @__PURE__ */ new Set();
6472
- const resolve = async (current, path41) => {
6461
+ const resolve = async (current, path45) => {
6473
6462
  if (isIndexableObject(current)) {
6474
6463
  if (visited.has(current)) {
6475
6464
  return CIRCULAR_REF;
@@ -6477,7 +6466,7 @@ var dereference = async (schema2, resolvers = []) => {
6477
6466
  visited.add(current);
6478
6467
  if (Array.isArray(current)) {
6479
6468
  for (let index = 0; index < current.length; index++) {
6480
- current[index] = await resolve(current[index], `${path41}/${index}`);
6469
+ current[index] = await resolve(current[index], `${path45}/${index}`);
6481
6470
  }
6482
6471
  } else {
6483
6472
  if ("$ref" in current && typeof current.$ref === "string") {
@@ -6488,13 +6477,13 @@ var dereference = async (schema2, resolvers = []) => {
6488
6477
  for (const resolver of resolvers) {
6489
6478
  const resolved = await resolver($ref);
6490
6479
  if (resolved) {
6491
- result2 = await resolve(resolved, path41);
6480
+ result2 = await resolve(resolved, path45);
6492
6481
  break;
6493
6482
  }
6494
6483
  }
6495
6484
  if (result2 === void 0) {
6496
6485
  const resolved = await resolveLocalRef(cloned, $ref);
6497
- result2 = await resolve(resolved, path41);
6486
+ result2 = await resolve(resolved, path45);
6498
6487
  }
6499
6488
  if (hasSiblings) {
6500
6489
  if (result2 === CIRCULAR_REF) {
@@ -6507,7 +6496,7 @@ var dereference = async (schema2, resolvers = []) => {
6507
6496
  return result2;
6508
6497
  }
6509
6498
  for (const key in current) {
6510
- current[key] = await resolve(current[key], `${path41}/${key}`);
6499
+ current[key] = await resolve(current[key], `${path45}/${key}`);
6511
6500
  }
6512
6501
  }
6513
6502
  visited.delete(current);
@@ -6546,9 +6535,9 @@ var upgradeSchema = (schema2) => {
6546
6535
  }
6547
6536
  return sub;
6548
6537
  });
6549
- schema2 = traverse(schema2, (sub, path41) => {
6538
+ schema2 = traverse(schema2, (sub, path45) => {
6550
6539
  if (sub.example !== void 0) {
6551
- if (isSchemaPath(path41 ?? [])) {
6540
+ if (isSchemaPath(path45 ?? [])) {
6552
6541
  sub.examples = [sub.example];
6553
6542
  } else {
6554
6543
  sub.examples = {
@@ -6561,11 +6550,11 @@ var upgradeSchema = (schema2) => {
6561
6550
  }
6562
6551
  return sub;
6563
6552
  });
6564
- schema2 = traverse(schema2, (schema3, path41) => {
6553
+ schema2 = traverse(schema2, (schema3, path45) => {
6565
6554
  if (schema3.type === "object" && schema3.properties !== void 0) {
6566
- const parentPath = path41?.slice(0, -1);
6555
+ const parentPath = path45?.slice(0, -1);
6567
6556
  const isMultipart = parentPath?.some((segment, index) => {
6568
- return segment === "content" && path41?.[index + 1] === "multipart/form-data";
6557
+ return segment === "content" && path45?.[index + 1] === "multipart/form-data";
6569
6558
  });
6570
6559
  if (isMultipart) {
6571
6560
  const entries = Object.entries(schema3.properties);
@@ -6579,8 +6568,8 @@ var upgradeSchema = (schema2) => {
6579
6568
  }
6580
6569
  return schema3;
6581
6570
  });
6582
- schema2 = traverse(schema2, (schema3, path41) => {
6583
- if (path41?.includes("content") && path41.includes("application/octet-stream")) {
6571
+ schema2 = traverse(schema2, (schema3, path45) => {
6572
+ if (path45?.includes("content") && path45.includes("application/octet-stream")) {
6584
6573
  return {};
6585
6574
  }
6586
6575
  if (schema3.type === "string" && schema3.format === "binary") {
@@ -6606,11 +6595,11 @@ var upgradeSchema = (schema2) => {
6606
6595
  }
6607
6596
  return sub;
6608
6597
  });
6609
- schema2 = traverse(schema2, (schema3, path41) => {
6598
+ schema2 = traverse(schema2, (schema3, path45) => {
6610
6599
  if (schema3.type === "string" && schema3.format === "byte") {
6611
- const parentPath = path41?.slice(0, -1);
6600
+ const parentPath = path45?.slice(0, -1);
6612
6601
  const contentMediaType = parentPath?.find(
6613
- (_, index) => path41?.[index - 1] === "content"
6602
+ (_, index) => path45?.[index - 1] === "content"
6614
6603
  );
6615
6604
  return {
6616
6605
  type: "string",
@@ -6622,7 +6611,7 @@ var upgradeSchema = (schema2) => {
6622
6611
  });
6623
6612
  return schema2;
6624
6613
  };
6625
- function isSchemaPath(path41) {
6614
+ function isSchemaPath(path45) {
6626
6615
  const schemaLocations = [
6627
6616
  ["components", "schemas"],
6628
6617
  "properties",
@@ -6635,10 +6624,10 @@ function isSchemaPath(path41) {
6635
6624
  ];
6636
6625
  return schemaLocations.some((location) => {
6637
6626
  if (Array.isArray(location)) {
6638
- return location.every((segment, index) => path41[index] === segment);
6627
+ return location.every((segment, index) => path45[index] === segment);
6639
6628
  }
6640
- return path41.includes(location);
6641
- }) || path41.includes("schema") || path41.some((segment) => segment.endsWith("Schema"));
6629
+ return path45.includes(location);
6630
+ }) || path45.includes("schema") || path45.some((segment) => segment.endsWith("Schema"));
6642
6631
  }
6643
6632
 
6644
6633
  // src/lib/oas/parser/index.ts
@@ -6720,13 +6709,13 @@ var OPENAPI_PROPS = /* @__PURE__ */ new Set([
6720
6709
  "anyOf",
6721
6710
  "oneOf"
6722
6711
  ]);
6723
- var handleCircularRefs = (obj, currentPath = /* @__PURE__ */ new WeakSet(), refs = /* @__PURE__ */ new WeakMap(), path41 = [], currentRefPaths = /* @__PURE__ */ new Set()) => {
6712
+ var handleCircularRefs = (obj, currentPath = /* @__PURE__ */ new WeakSet(), refs = /* @__PURE__ */ new WeakMap(), path45 = [], currentRefPaths = /* @__PURE__ */ new Set()) => {
6724
6713
  if (obj === null || typeof obj !== "object") return obj;
6725
6714
  const refPath = obj.__$ref;
6726
6715
  const isCircular = currentPath.has(obj) || typeof refPath === "string" && currentRefPaths.has(refPath);
6727
6716
  if (isCircular) {
6728
6717
  if (typeof refPath === "string") return SCHEMA_REF_PREFIX + refPath;
6729
- const circularProp = path41.find((p) => !OPENAPI_PROPS.has(p)) || path41[0];
6718
+ const circularProp = path45.find((p) => !OPENAPI_PROPS.has(p)) || path45[0];
6730
6719
  return [CIRCULAR_REF, circularProp].filter(Boolean).join(":");
6731
6720
  }
6732
6721
  if (refs.has(obj)) return refs.get(obj);
@@ -6736,7 +6725,7 @@ var handleCircularRefs = (obj, currentPath = /* @__PURE__ */ new WeakSet(), refs
6736
6725
  value,
6737
6726
  currentPath,
6738
6727
  refs,
6739
- [...path41, key],
6728
+ [...path45, key],
6740
6729
  currentRefPaths
6741
6730
  );
6742
6731
  const result = Array.isArray(obj) ? obj.map((item, i) => recurse(item, i.toString())) : Object.fromEntries(
@@ -6771,7 +6760,7 @@ var resolveExtensions = (obj) => Object.fromEntries(
6771
6760
  var getAllTags = (schema2) => {
6772
6761
  const rootTags = schema2.tags ?? [];
6773
6762
  const operations = Object.values(schema2.paths ?? {}).flatMap(
6774
- (path41) => HttpMethods.map((k) => path41?.[k]).filter((op) => op != null)
6763
+ (path45) => HttpMethods.map((k) => path45?.[k]).filter((op) => op != null)
6775
6764
  );
6776
6765
  const operationTags = new Set(operations.flatMap((op) => op.tags ?? []));
6777
6766
  const hasUntaggedOperations = operations.some(
@@ -6826,7 +6815,7 @@ var getAllSlugs = (ops, schemaTags = []) => {
6826
6815
  var getOperationSlugKey = (op) => [op.path, op.method, op.operationId, op.summary].filter(Boolean).join("-");
6827
6816
  var getAllOperations = (paths) => {
6828
6817
  const operations = Object.entries(paths ?? {}).flatMap(
6829
- ([path41, value]) => HttpMethods.flatMap((method) => {
6818
+ ([path45, value]) => HttpMethods.flatMap((method) => {
6830
6819
  if (!value?.[method]) return [];
6831
6820
  const operation = value[method];
6832
6821
  const pathParameters = value.parameters ?? [];
@@ -6846,7 +6835,7 @@ var getAllOperations = (paths) => {
6846
6835
  return {
6847
6836
  ...operation,
6848
6837
  method,
6849
- path: path41,
6838
+ path: path45,
6850
6839
  parameters,
6851
6840
  servers,
6852
6841
  tags: operation.tags ?? []
@@ -7204,8 +7193,8 @@ var Schema = builder.objectRef("Schema").implement({
7204
7193
  }),
7205
7194
  paths: t.field({
7206
7195
  type: [PathItem],
7207
- resolve: (root) => Object.entries(root.paths ?? {}).map(([path41, value]) => ({
7208
- path: path41,
7196
+ resolve: (root) => Object.entries(root.paths ?? {}).map(([path45, value]) => ({
7197
+ path: path45,
7209
7198
  // biome-ignore lint/style/noNonNullAssertion: value is guaranteed to be defined
7210
7199
  methods: Object.keys(value)
7211
7200
  }))
@@ -7370,7 +7359,7 @@ init_joinUrl();
7370
7359
 
7371
7360
  // src/vite/api/schema-codegen.ts
7372
7361
  var unescapeJsonPointer = (uri) => decodeURIComponent(uri.replace(/~1/g, "/").replace(/~0/g, "~"));
7373
- var getSegmentsFromPath = (path41) => path41.split("/").slice(1).map(unescapeJsonPointer);
7362
+ var getSegmentsFromPath = (path45) => path45.split("/").slice(1).map(unescapeJsonPointer);
7374
7363
  var createLocalRefMap = (obj) => {
7375
7364
  const refMap = /* @__PURE__ */ new Map();
7376
7365
  const siblingsMap = /* @__PURE__ */ new Map();
@@ -7401,16 +7390,16 @@ var replaceMarkers = (code, mergedRefs) => code.replace(/"__refMap:(.*?)"/g, '__
7401
7390
  /"__refMap\+Siblings:(.*?)"/g,
7402
7391
  (_, key) => mergedRefs.get(key) ?? `__refMap["${key}"]`
7403
7392
  );
7404
- var lookup = (schema2, path41, filePath) => {
7405
- const parts = getSegmentsFromPath(path41);
7393
+ var lookup = (schema2, path45, filePath) => {
7394
+ const parts = getSegmentsFromPath(path45);
7406
7395
  let val = schema2;
7407
7396
  for (const part of parts) {
7408
7397
  while (val.$ref?.startsWith("#/")) {
7409
- val = val.$ref === path41 ? val : lookup(schema2, val.$ref, filePath);
7398
+ val = val.$ref === path45 ? val : lookup(schema2, val.$ref, filePath);
7410
7399
  }
7411
7400
  if (val[part] === void 0) {
7412
7401
  throw new Error(
7413
- `Error in ${filePath ?? "code generation"}: Could not find path segment ${part} in path: ${path41}`
7402
+ `Error in ${filePath ?? "code generation"}: Could not find path segment ${part} in path: ${path45}`
7414
7403
  );
7415
7404
  }
7416
7405
  val = val[part];
@@ -7655,8 +7644,8 @@ var SchemaManager = class {
7655
7644
  }
7656
7645
  }
7657
7646
  };
7658
- getLatestSchema = (path41) => this.processedSchemas[path41]?.at(0);
7659
- getSchemasForPath = (path41) => this.processedSchemas[path41];
7647
+ getLatestSchema = (path45) => this.processedSchemas[path45]?.at(0);
7648
+ getSchemasForPath = (path45) => this.processedSchemas[path45];
7660
7649
  getSchemaImports = () => Object.values(this.processedSchemas).flat().filter((s) => s.importKey);
7661
7650
  getUrlToFilePathMap = () => {
7662
7651
  const map = /* @__PURE__ */ new Map();
@@ -10057,14 +10046,14 @@ async function generateSitemap({
10057
10046
 
10058
10047
  // src/vite/prerender/utils.ts
10059
10048
  init_joinUrl();
10060
- var resolveRoutePath = (path41) => {
10061
- const segments = path41.split("/");
10049
+ var resolveRoutePath = (path45) => {
10050
+ const segments = path45.split("/");
10062
10051
  if (segments.some((s) => s.startsWith(":") && !s.endsWith("?"))) {
10063
10052
  return void 0;
10064
10053
  }
10065
10054
  return segments.filter((s) => !s.startsWith(":")).join("/") || void 0;
10066
10055
  };
10067
- var isSkipped = (path41) => path41.includes("*") || /^\d+$/.test(path41);
10056
+ var isSkipped = (path45) => path45.includes("*") || /^\d+$/.test(path45);
10068
10057
  var resolveRoutes = (routes, parentPath = "") => routes.flatMap((route) => {
10069
10058
  if (route.path && isSkipped(route.path)) return [];
10070
10059
  const routePath = route.path ? resolveRoutePath(route.path) : void 0;
@@ -10527,1553 +10516,1523 @@ var build_default = {
10527
10516
  }
10528
10517
  };
10529
10518
 
10530
- // src/cli/configure-github-workflow/handler.ts
10531
- import { constants } from "node:fs";
10532
- import { access as access2, mkdir as mkdir6, writeFile as writeFile6 } from "node:fs/promises";
10533
- import path33 from "node:path";
10534
- var APITOGO_DEPLOY_WORKFLOW_YAML = `name: Build and Deploy
10519
+ // src/cli/make-config/handler.ts
10520
+ import { constants as constants2 } from "node:fs";
10521
+ import { access as access3, mkdir as mkdir7, readFile as readFile5, writeFile as writeFile7 } from "node:fs/promises";
10522
+ import path36 from "node:path";
10523
+ import { glob as glob5 } from "glob";
10535
10524
 
10536
- on:
10537
- push:
10538
- branches:
10539
- - main
10540
- pull_request:
10541
- branches:
10542
- - main
10543
- types: [opened, synchronize, reopened, closed]
10544
- workflow_dispatch:
10545
-
10546
- jobs:
10547
- preview:
10548
- if: github.event_name == 'pull_request' && github.event.action != 'closed'
10549
- runs-on: ubuntu-latest
10550
-
10551
- environment:
10552
- name: preview
10553
- url: \${{ steps.deploy.outputs.url }}
10554
-
10555
- steps:
10556
- - name: Checkout repository
10557
- uses: actions/checkout@v4
10558
-
10559
- - name: Setup Node.js
10560
- uses: actions/setup-node@v4
10561
- with:
10562
- node-version: 20
10563
- cache: npm
10564
-
10565
- - name: Install dependencies
10566
- run: npm ci
10567
-
10568
- - name: Install APIToGo CLI
10569
- run: npm install -g @lukoweb/apitogo@latest
10570
-
10571
- - name: Check APIToGo version
10572
- run: apitogo --version
10573
-
10574
- - name: Deploy preview
10575
- id: deploy
10576
- env:
10577
- APITOGO_TOKEN: \${{ secrets.APITOGO_TOKEN }}
10578
- shell: bash
10579
- run: |
10580
- ENV_NAME=preview-pr-\${{ github.event.pull_request.number }}
10581
-
10582
- OUTPUT=$(apitogo deploy --deployment-token="$APITOGO_TOKEN" --env=$ENV_NAME --json-only)
10583
-
10584
- echo "$OUTPUT"
10585
-
10586
- URL=$(echo "$OUTPUT" | jq -r '.url')
10587
-
10588
- if [ -z "$URL" ] || [ "$URL" = "null" ]; then
10589
- echo "Failed to extract preview URL"
10590
- exit 1
10591
- fi
10592
-
10593
- echo "url=$URL" >> "$GITHUB_OUTPUT"
10594
-
10595
- cleanup-preview:
10596
- if: github.event_name == 'pull_request' && github.event.action == 'closed'
10597
- runs-on: ubuntu-latest
10598
-
10599
- steps:
10600
- - name: Install APIToGo CLI
10601
- run: npm install -g @lukoweb/apitogo@latest
10602
-
10603
- - name: Remove preview deployment
10604
- env:
10605
- APITOGO_TOKEN: \${{ secrets.APITOGO_TOKEN }}
10606
- shell: bash
10607
- run: |
10608
- ENV_NAME=preview-pr-\${{ github.event.pull_request.number }}
10609
-
10610
- apitogo remove --deployment-token="$APITOGO_TOKEN" --env=$ENV_NAME --json-only
10611
-
10612
- production:
10613
- if: github.event_name == 'push' && github.ref == 'refs/heads/main'
10614
- runs-on: ubuntu-latest
10615
-
10616
- environment:
10617
- name: production
10618
-
10619
- steps:
10620
- - name: Checkout repository
10621
- uses: actions/checkout@v4
10622
-
10623
- - name: Setup Node.js
10624
- uses: actions/setup-node@v4
10625
- with:
10626
- node-version: 20
10627
- cache: npm
10628
-
10629
- - name: Install dependencies
10630
- run: npm ci
10631
-
10632
- - name: Install APIToGo CLI
10633
- run: npm install -g @lukoweb/apitogo@latest
10634
-
10635
- - name: Check APIToGo version
10636
- run: apitogo --version
10637
-
10638
- - name: Deploy production
10639
- env:
10640
- APITOGO_TOKEN: \${{ secrets.APITOGO_TOKEN }}
10641
- run: apitogo deploy --deployment-token="$APITOGO_TOKEN" --json-only
10642
- `;
10643
- async function configureGithubWorkflow(argv) {
10644
- const dir = path33.resolve(process.cwd(), argv.dir);
10645
- const workflowDir = path33.join(dir, ".github", "workflows");
10646
- const workflowPath = path33.join(workflowDir, "apitogo-deploy.yml");
10647
- try {
10648
- try {
10649
- await access2(workflowPath, constants.F_OK);
10650
- if (!argv.force) {
10651
- await printCriticalFailureToConsoleAndExit(
10652
- `Workflow file already exists: ${workflowPath}. Pass --force to overwrite.`
10653
- );
10654
- }
10655
- } catch {
10525
+ // src/cli/make-config/interactive.ts
10526
+ import { createInterface } from "node:readline/promises";
10527
+ import { stdin as input, stdout as output } from "node:process";
10528
+ import path34 from "node:path";
10529
+
10530
+ // src/cli/make-config/json-config.ts
10531
+ import path33 from "node:path";
10532
+ var APITOGO_JSON_FILENAME = "apitogo.config.json";
10533
+ var DOCUMENTATION_NAV_LABEL = "Documentation";
10534
+ var API_REFERENCE_PATH = "/api";
10535
+ var API_REFERENCE_LABEL = "API Reference";
10536
+ function isPlainObject2(value) {
10537
+ return value !== null && typeof value === "object" && !Array.isArray(value);
10538
+ }
10539
+ function deepMerge(target, source) {
10540
+ const result = { ...target };
10541
+ for (const [key, value] of Object.entries(source)) {
10542
+ const existing = result[key];
10543
+ if (isPlainObject2(existing) && isPlainObject2(value)) {
10544
+ result[key] = deepMerge(existing, value);
10545
+ } else {
10546
+ result[key] = value;
10656
10547
  }
10657
- await mkdir6(workflowDir, { recursive: true });
10658
- await writeFile6(workflowPath, APITOGO_DEPLOY_WORKFLOW_YAML, "utf8");
10659
- printResultToConsole("Wrote .github/workflows/apitogo-deploy.yml");
10660
- } catch (error) {
10661
- await printCriticalFailureToConsoleAndExit(
10662
- error instanceof Error ? error.message : String(error)
10663
- );
10664
10548
  }
10549
+ return result;
10665
10550
  }
10666
-
10667
- // src/cli/cmds/configure-github-workflow.ts
10668
- var configure_github_workflow_default = {
10669
- desc: "Add a GitHub Actions workflow for APIToGo preview and production deploys",
10670
- command: "configure-github-workflow",
10671
- builder: (yargs2) => yargs2.option("dir", {
10672
- type: "string",
10673
- describe: "The directory containing your project",
10674
- default: ".",
10675
- normalize: true,
10676
- hidden: true
10677
- }).option("force", {
10678
- type: "boolean",
10679
- describe: "Overwrite .github/workflows/apitogo-deploy.yml if it already exists",
10680
- default: false
10681
- }),
10682
- handler: async (argv) => {
10683
- await captureEvent({ argv, event: "apitogo configure-github-workflow" });
10684
- await configureGithubWorkflow(argv);
10551
+ function parseJsonObjectFlag(raw, flagName) {
10552
+ if (raw === void 0 || raw.trim() === "") {
10553
+ return void 0;
10685
10554
  }
10686
- };
10687
-
10688
- // src/cli/common/json-only-deploy.ts
10689
- init_logger();
10690
- function isDeployJsonOnlyArgv() {
10691
- const args = process.argv;
10692
- let cmdIdx = -1;
10693
- for (const cmd of ["deploy", "remove"]) {
10694
- const i = args.indexOf(cmd);
10695
- if (i !== -1) {
10696
- cmdIdx = i;
10697
- break;
10698
- }
10555
+ let parsed;
10556
+ try {
10557
+ parsed = JSON.parse(raw);
10558
+ } catch {
10559
+ throw new Error(`--${flagName} must be a JSON object.`);
10699
10560
  }
10700
- if (cmdIdx === -1) {
10701
- return false;
10561
+ if (!isPlainObject2(parsed)) {
10562
+ throw new Error(`--${flagName} must be a JSON object.`);
10702
10563
  }
10703
- return args.slice(cmdIdx).some((a) => a === "--json-only" || a.startsWith("--json-only="));
10704
- }
10705
- function isJsonOnlyDeployEnv() {
10706
- return process.env.APITOGO_JSON_ONLY === "1";
10564
+ return parsed;
10707
10565
  }
10708
- function applyJsonOnlyDeployQuietMode() {
10709
- process.env.APITOGO_JSON_ONLY = "1";
10710
- const noop = () => {
10711
- };
10712
- logger.info = noop;
10713
- logger.warn = noop;
10714
- logger.warnOnce = noop;
10566
+ function titleFromDirectoryName(dirName) {
10567
+ const titled = dirName.split(/[-_\s]+/).filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join(" ");
10568
+ return titled || "Developer Portal";
10715
10569
  }
10716
-
10717
- // src/cli/deploy/handler.ts
10718
- import { access as access3, readFile as readFile4 } from "node:fs/promises";
10719
- import path34 from "node:path";
10720
- import { create as createTar } from "tar";
10721
- var DEPLOY_ENDPOINT = "https://deploy-server-dotnet2-b3fkcaaraydbckfe.canadacentral-01.azurewebsites.net/deploy";
10722
- var ARCHIVE_NAME = "dist.tgz";
10723
- var createDeploymentArchive = async (dir) => {
10724
- const distDir = path34.join(dir, "dist");
10725
- await access3(distDir);
10726
- const archivePath = path34.join(dir, ARCHIVE_NAME);
10727
- await createTar(
10728
- {
10729
- cwd: dir,
10730
- file: archivePath,
10731
- gzip: true,
10732
- portable: true
10570
+ function createDefaultApitogoJsonConfig(options) {
10571
+ const filesGlob = `/${options.pagesDir}/**/*.{md,mdx}`;
10572
+ return {
10573
+ site: {
10574
+ title: options.title,
10575
+ logo: {
10576
+ src: { light: "/logo-light.png", dark: "/logo-dark.png" },
10577
+ alt: "APIToGo",
10578
+ width: "130px"
10579
+ }
10733
10580
  },
10734
- ["dist"]
10735
- );
10736
- return archivePath;
10737
- };
10738
- async function deploy(argv) {
10739
- await build({ ...argv, preview: void 0 });
10740
- const dir = path34.resolve(process.cwd(), argv.dir);
10741
- const deploymentToken = argv.deploymentToken?.trim() || process.env.npm_config_deployment_token?.trim();
10742
- const env = argv.env?.trim() || process.env.npm_config_env?.trim() || "production";
10743
- try {
10744
- if (!deploymentToken) {
10745
- throw new Error("A deployment token is required.");
10746
- }
10747
- if (!isJsonOnlyDeployEnv()) {
10748
- printDiagnosticsToConsole("Creating deployment archive...");
10749
- }
10750
- const archivePath = await createDeploymentArchive(dir);
10751
- if (!isJsonOnlyDeployEnv()) {
10752
- printDiagnosticsToConsole(
10753
- `Uploading ${path34.basename(archivePath)} to ${env}...`
10754
- );
10755
- }
10756
- const archiveBuffer = await readFile4(archivePath);
10757
- const formData = new FormData();
10758
- formData.append(
10759
- "file",
10760
- new Blob([archiveBuffer], { type: "application/gzip" }),
10761
- path34.basename(archivePath)
10762
- );
10763
- formData.append("deploymentToken", deploymentToken);
10764
- formData.append("env", env);
10765
- const deployUrl = new URL(DEPLOY_ENDPOINT);
10766
- if (argv.jsonOnly) {
10767
- deployUrl.searchParams.set("jsonOnly", "true");
10768
- }
10769
- const response = await fetch(deployUrl, {
10770
- method: "POST",
10771
- headers: {
10772
- "x-api-key": DEPLOY_SERVER_API_KEY
10581
+ authentication: {
10582
+ enabled: true,
10583
+ type: "dev-portal",
10584
+ redirectToAfterSignIn: "/account/dashboard",
10585
+ redirectToAfterSignUp: "/account/dashboard"
10586
+ },
10587
+ metadata: {
10588
+ favicon: "/Icon.png"
10589
+ },
10590
+ theme: {
10591
+ fonts: {
10592
+ sans: "IBM Plex Sans",
10593
+ mono: "JetBrains Mono"
10773
10594
  },
10774
- body: formData
10775
- });
10776
- const responseText = await response.text();
10777
- if (!response.ok) {
10778
- throw new Error(
10779
- `Deploy failed with ${response.status}: ${JSON.stringify(textOrJson(responseText))}`
10780
- );
10781
- }
10782
- if (isJsonOnlyDeployEnv()) {
10783
- const parsed = responseText.trim() ? textOrJson(responseText) : {};
10784
- console.log(JSON.stringify(parsed));
10785
- return;
10786
- }
10787
- printResultToConsole(`Deployment uploaded to ${deployUrl.href}`);
10788
- if (responseText.trim()) {
10789
- printDiagnosticsToConsole(
10790
- JSON.stringify(textOrJson(responseText), null, 2)
10791
- );
10792
- }
10793
- } catch (error) {
10794
- const message = error instanceof Error ? error.message : String(error);
10795
- if (isJsonOnlyDeployEnv()) {
10796
- printJsonOnlyErrorAndExit(message);
10797
- } else {
10798
- await printCriticalFailureToConsoleAndExit(message);
10595
+ light: {
10596
+ background: "#F5F6F9",
10597
+ foreground: "#0E1016",
10598
+ card: "#FFFFFF",
10599
+ cardForeground: "#0E1016",
10600
+ primary: "#E11D2A",
10601
+ primaryForeground: "#ffffff",
10602
+ secondary: "#FAFBFC",
10603
+ secondaryForeground: "#0E1016",
10604
+ muted: "#FAFBFC",
10605
+ mutedForeground: "#565E70",
10606
+ accent: "rgba(225, 29, 42, 0.08)",
10607
+ accentForeground: "#E11D2A",
10608
+ border: "#E6E8EF",
10609
+ input: "#D6DAE3",
10610
+ ring: "#E11D2A"
10611
+ },
10612
+ dark: {
10613
+ background: "#0B0D12",
10614
+ foreground: "#EDEFF5",
10615
+ card: "#14171F",
10616
+ cardForeground: "#EDEFF5",
10617
+ primary: "#F23645",
10618
+ primaryForeground: "#ffffff",
10619
+ secondary: "#191D27",
10620
+ secondaryForeground: "#EDEFF5",
10621
+ muted: "#191D27",
10622
+ mutedForeground: "#A6AEBF",
10623
+ accent: "rgba(242, 54, 69, 0.13)",
10624
+ accentForeground: "#F23645",
10625
+ border: "#252B37",
10626
+ input: "#333A48",
10627
+ ring: "#F23645"
10628
+ }
10629
+ },
10630
+ modules: {
10631
+ docs: {
10632
+ files: filesGlob
10633
+ },
10634
+ landing: {
10635
+ enabled: true,
10636
+ path: "/",
10637
+ layout: "landing",
10638
+ page: "./landing/LandingPage.tsx"
10639
+ },
10640
+ userPanel: {
10641
+ enabled: true,
10642
+ dashboard: { enabled: true },
10643
+ userManagement: { enabled: true },
10644
+ plans: {
10645
+ enabled: false,
10646
+ page: "./user-panel/AccountPlansPage.tsx"
10647
+ }
10648
+ }
10799
10649
  }
10800
- }
10650
+ };
10801
10651
  }
10802
-
10803
- // src/cli/cmds/deploy.ts
10804
- var deploy_default = {
10805
- desc: "Build, archive, and deploy",
10806
- command: "deploy",
10807
- builder: (yargs2) => yargs2.option("deployment-token", {
10808
- type: "string",
10809
- describe: "Deployment token sent to the deploy endpoint"
10810
- }).option("env", {
10811
- type: "string",
10812
- describe: "Target environment name sent to the deploy API (e.g. production, preview, preview-a, preview-b)",
10813
- default: "production"
10814
- }).option("dir", {
10815
- type: "string",
10816
- describe: "The directory containing your project",
10817
- default: ".",
10818
- normalize: true,
10819
- hidden: true
10820
- }).option("experimental-ssr", {
10821
- type: "boolean",
10822
- describe: "Build for server-side rendering (experimental)",
10823
- default: false
10824
- }).option("adapter", {
10825
- type: "string",
10826
- describe: "SSR adapter (node, cloudflare, vercel)",
10827
- choices: ["node", "cloudflare", "vercel"],
10828
- default: "node"
10829
- }).option("json-only", {
10830
- type: "boolean",
10831
- describe: "JSON-only output on stdout; adds ?jsonOnly=true to the deploy request",
10832
- default: false
10833
- }),
10834
- handler: async (argv) => {
10835
- process.env.NODE_ENV = "production";
10836
- if (argv.jsonOnly) {
10837
- applyJsonOnlyDeployQuietMode();
10838
- }
10839
- await captureEvent({ argv, event: "apitogo deploy" });
10840
- await deploy(argv);
10652
+ function applyConfigPatches(config2, patches) {
10653
+ let next = { ...config2 };
10654
+ if (patches.site) {
10655
+ next.site = deepMerge(
10656
+ isPlainObject2(next.site) ? next.site : {},
10657
+ patches.site
10658
+ );
10841
10659
  }
10842
- };
10843
-
10844
- // src/cli/dev/handler.ts
10845
- init_joinUrl();
10846
- import path37 from "node:path";
10847
-
10848
- // src/vite/dev-server.ts
10849
- init_logger();
10850
- import fs4 from "node:fs/promises";
10851
- import http from "node:http";
10852
- import https from "node:https";
10853
- import path36 from "node:path";
10854
- import { createHttpTerminator } from "http-terminator";
10855
- import {
10856
- createServer as createViteServer,
10857
- isRunnableDevEnvironment as isRunnableDevEnvironment2,
10858
- mergeConfig as mergeConfig4
10859
- } from "vite";
10860
-
10861
- // src/cli/common/utils/ports.ts
10862
- import net from "node:net";
10863
- var isPortAvailable = (port) => new Promise((resolve) => {
10864
- const server = net.createServer();
10865
- server.unref();
10866
- server.once("error", () => resolve(false));
10867
- server.listen(port, () => {
10868
- server.close();
10869
- resolve(true);
10870
- });
10871
- });
10872
- async function findAvailablePort(startPort) {
10873
- let port = startPort;
10874
- for (; port < startPort + 1e3; port++) {
10875
- if (await isPortAvailable(port)) break;
10660
+ if (patches.theme) {
10661
+ next.theme = deepMerge(
10662
+ isPlainObject2(next.theme) ? next.theme : {},
10663
+ patches.theme
10664
+ );
10876
10665
  }
10877
- return port;
10878
- }
10879
-
10880
- // src/vite/dev-server.ts
10881
- init_loader();
10882
-
10883
- // src/vite/pagefind-dev-index.ts
10884
- init_invariant();
10885
- init_joinUrl();
10886
- import path35 from "node:path";
10887
- import { createIndex as createIndex2 } from "pagefind";
10888
- import { isRunnableDevEnvironment } from "vite";
10889
- async function* buildPagefindDevIndex(vite, config2) {
10890
- const { index, errors } = await createIndex2();
10891
- invariant(index, `Failed to create pagefind index: ${errors.join(", ")}`);
10892
- const pagefindIndex = index;
10893
- const ssrEnvironment = vite.environments.ssr;
10894
- if (!isRunnableDevEnvironment(ssrEnvironment)) {
10895
- throw new Error("SSR environment is not runnable");
10666
+ if (patches.authentication) {
10667
+ next.authentication = deepMerge(
10668
+ isPlainObject2(next.authentication) ? next.authentication : {},
10669
+ patches.authentication
10670
+ );
10896
10671
  }
10897
- const serverModule = await ssrEnvironment.runner.import(
10898
- getAppServerEntryPath()
10899
- );
10900
- const routes = serverModule.getRoutesByConfig(config2);
10901
- const paths = routesToPaths(routes);
10902
- const { basePath } = config2;
10903
- const template = getDevHtml({
10904
- jsEntry: "/__z/entry.client.tsx",
10905
- dir: config2.site?.dir
10906
- });
10907
- const transformedTemplate = await vite.transformIndexHtml("/", template);
10908
- let indexed = 0;
10909
- for (const urlPath of paths) {
10910
- try {
10911
- const pathname = joinUrl(basePath, urlPath);
10912
- const url = joinUrl("http://localhost", pathname);
10913
- const request = new Request(url);
10914
- const response = await serverModule.handleRequest({
10915
- template: transformedTemplate,
10916
- request,
10917
- routes,
10918
- basePath,
10919
- bypassProtection: true
10920
- });
10921
- if (response.status < 400 && response.body) {
10922
- const content = await response.text();
10923
- await pagefindIndex.addHTMLFile({
10924
- url: urlPath,
10925
- content
10926
- });
10927
- indexed++;
10672
+ if (patches.title?.trim()) {
10673
+ const site = isPlainObject2(next.site) ? next.site : {};
10674
+ next.site = { ...site, title: patches.title.trim() };
10675
+ }
10676
+ return next;
10677
+ }
10678
+ var docSortRank = (docPath) => {
10679
+ const lastSegment = docPath.split("/").at(-1) ?? docPath;
10680
+ if (lastSegment === "introduction") return 0;
10681
+ if (lastSegment === "example") return 1;
10682
+ if (lastSegment === "getting-started") return 2;
10683
+ if (lastSegment === "index") return 3;
10684
+ return 4;
10685
+ };
10686
+ var sortDocPaths = (docPaths) => [...docPaths].sort((a, b) => {
10687
+ const rankDiff = docSortRank(a) - docSortRank(b);
10688
+ if (rankDiff !== 0) return rankDiff;
10689
+ return a.localeCompare(b);
10690
+ });
10691
+ var createTreeNode = () => ({ docs: [], children: /* @__PURE__ */ new Map() });
10692
+ var titleCase = (value) => value.split(/[-_\s]+/).filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join(" ");
10693
+ var buildTree = (routePaths) => {
10694
+ const root = createTreeNode();
10695
+ for (const routePath of routePaths) {
10696
+ const segments = routePath.replace(/^\//, "").split("/").filter(Boolean);
10697
+ const directorySegments = segments.slice(0, -1);
10698
+ let current = root;
10699
+ for (const segment of directorySegments) {
10700
+ let child = current.children.get(segment);
10701
+ if (!child) {
10702
+ child = createTreeNode();
10703
+ current.children.set(segment, child);
10928
10704
  }
10929
- } catch (error) {
10930
- console.error(`Failed to index ${urlPath}:`, error);
10705
+ current = child;
10931
10706
  }
10932
- yield {
10933
- type: "progress",
10934
- total: paths.length,
10935
- current: indexed,
10936
- path: urlPath
10707
+ current.docs.push(routePath);
10708
+ }
10709
+ return root;
10710
+ };
10711
+ var treeToNavigationItems = (node) => {
10712
+ const docs = sortDocPaths(node.docs);
10713
+ const categories = [...node.children.keys()].sort((a, b) => a.localeCompare(b)).map((directoryName) => {
10714
+ const childNode = node.children.get(directoryName);
10715
+ if (!childNode) {
10716
+ throw new Error(`Missing child node for directory '${directoryName}'.`);
10717
+ }
10718
+ return {
10719
+ type: "category",
10720
+ label: titleCase(directoryName),
10721
+ items: treeToNavigationItems(childNode)
10937
10722
  };
10723
+ });
10724
+ return [...docs, ...categories];
10725
+ };
10726
+ function buildDocumentationItems(routePaths) {
10727
+ return treeToNavigationItems(buildTree(sortDocPaths(routePaths)));
10728
+ }
10729
+ function navItemKey(item) {
10730
+ if (typeof item === "string") {
10731
+ return `path:${item}`;
10938
10732
  }
10939
- const outputPath = path35.join(vite.config.publicDir, "pagefind");
10940
- await pagefindIndex.writeFiles({ outputPath });
10941
- yield { type: "complete", success: true, indexed };
10733
+ if (typeof item.to === "string") {
10734
+ return `to:${item.to}`;
10735
+ }
10736
+ if (typeof item.path === "string") {
10737
+ return `path:${item.path}`;
10738
+ }
10739
+ if (item.type === "category" && typeof item.label === "string") {
10740
+ return `category:${item.label}`;
10741
+ }
10742
+ return JSON.stringify(item);
10942
10743
  }
10943
-
10944
- // src/vite/dev-server.ts
10945
- var DEFAULT_DEV_PORT = 3e3;
10946
- var DevServer = class {
10947
- constructor(options) {
10948
- this.options = options;
10744
+ function mergeNavItems(existing, incoming) {
10745
+ const seen = new Set(existing.map(navItemKey));
10746
+ const merged = [...existing];
10747
+ for (const item of incoming) {
10748
+ const key = navItemKey(item);
10749
+ if (seen.has(key)) {
10750
+ continue;
10751
+ }
10752
+ seen.add(key);
10753
+ merged.push(item);
10949
10754
  }
10950
- terminator;
10951
- resolvedPort = 0;
10952
- protocol = "http";
10953
- async createNodeServer(config2) {
10954
- if (!config2.https) return http.createServer();
10955
- this.protocol = "https";
10956
- const { dir } = this.options;
10957
- const [key, cert, ca] = await Promise.all([
10958
- fs4.readFile(path36.resolve(dir, config2.https.key)),
10959
- fs4.readFile(path36.resolve(dir, config2.https.cert)),
10960
- config2.https.ca ? fs4.readFile(path36.resolve(dir, config2.https.ca)) : void 0
10961
- ]);
10962
- return https.createServer({ key, cert, ca });
10755
+ return merged;
10756
+ }
10757
+ function isDocumentationCategory(item) {
10758
+ return typeof item === "object" && item.type === "category" && item.label === DOCUMENTATION_NAV_LABEL;
10759
+ }
10760
+ function isApiReferenceLink(item) {
10761
+ return typeof item === "object" && item.type === "link" && item.to === API_REFERENCE_PATH;
10762
+ }
10763
+ function mergeDocumentationNavigation(navigation, docRoutePaths, options) {
10764
+ const existing = Array.isArray(navigation) ? navigation : [];
10765
+ const generated = buildDocumentationItems(docRoutePaths);
10766
+ const rest = existing.filter((item) => !isDocumentationCategory(item));
10767
+ const currentDocs = existing.find(isDocumentationCategory);
10768
+ const currentItems = Array.isArray(currentDocs?.items) ? currentDocs.items : [];
10769
+ const items = options.force ? generated : mergeNavItems(currentItems, generated);
10770
+ const documentation = {
10771
+ type: "category",
10772
+ label: DOCUMENTATION_NAV_LABEL,
10773
+ items
10774
+ };
10775
+ if (generated.length === 0) {
10776
+ return existing;
10963
10777
  }
10964
- async start() {
10965
- const configEnv = {
10966
- mode: "development",
10967
- command: "serve",
10968
- isSsrBuild: this.options.ssr
10969
- };
10970
- const viteConfig = await getViteConfig(this.options.dir, configEnv);
10971
- const { config: config2 } = await loadZudokuConfig(configEnv, this.options.dir);
10972
- this.resolvedPort = await findAvailablePort(
10973
- this.options.argPort ?? config2.port ?? DEFAULT_DEV_PORT
10974
- );
10975
- const server = await this.createNodeServer(config2);
10976
- if (config2.search?.type === "pagefind" && viteConfig.publicDir !== false) {
10977
- const publicRoot = path36.resolve(
10978
- this.options.dir,
10979
- typeof viteConfig.publicDir === "string" ? viteConfig.publicDir : "public"
10980
- );
10981
- await ensurePagefindDevStub(publicRoot);
10778
+ if (currentDocs) {
10779
+ const index = existing.findIndex(isDocumentationCategory);
10780
+ const next = [...existing];
10781
+ next[index] = documentation;
10782
+ return next;
10783
+ }
10784
+ return [documentation, ...rest];
10785
+ }
10786
+ function applyOpenApiToJsonConfig(config2, input2) {
10787
+ const next = { ...config2 };
10788
+ next.apis = [
10789
+ {
10790
+ type: "file",
10791
+ input: input2,
10792
+ path: "api"
10982
10793
  }
10983
- const mergedViteConfig = mergeConfig4(viteConfig, {
10984
- server: {
10985
- hmr: { server }
10986
- },
10987
- plugins: [
10988
- {
10989
- // Serves the client entry via Vite's client transform pipeline.
10990
- // Must be registered via configureServer so it runs before Vite's
10991
- // built-in transform middleware which would treat the path as a static asset.
10992
- name: "apitogo:entry-client",
10993
- configureServer(server2) {
10994
- const entryPath = path36.posix.join(
10995
- server2.config.base,
10996
- "/__z/entry.client.tsx"
10997
- );
10998
- server2.middlewares.use(entryPath, async (_req, res) => {
10999
- const transformed = await server2.environments.client.transformRequest(
11000
- getAppClientEntryPath()
11001
- );
11002
- if (!transformed) {
11003
- res.writeHead(500);
11004
- res.end("Error transforming client entry");
11005
- return;
11006
- }
11007
- res.writeHead(200, { "Content-Type": "text/javascript" });
11008
- res.end(transformed.code);
11009
- });
11010
- }
11011
- }
11012
- ]
11013
- });
11014
- const vite = await createViteServer(mergedViteConfig);
11015
- const graphql = createGraphQLServer({ graphqlEndpoint: "/__z/graphql" });
11016
- vite.middlewares.use((req, res, next) => {
11017
- if (req.method === "GET" && req.originalUrl === "/" && config2.basePath && config2.basePath !== "/") {
11018
- res.writeHead(307, { Location: config2.basePath });
11019
- res.end();
11020
- return;
11021
- }
11022
- next();
11023
- });
11024
- vite.middlewares.use(graphql.graphqlEndpoint, graphql);
11025
- vite.middlewares.use("/__z/pagefind-reindex", async (_req, res) => {
11026
- res.writeHead(200, {
11027
- "Content-Type": "text/event-stream",
11028
- "Cache-Control": "no-cache",
11029
- Connection: "keep-alive"
11030
- });
11031
- const { config: currentConfig } = await loadZudokuConfig(
11032
- configEnv,
11033
- this.options.dir
11034
- );
11035
- const sendEvent = (data) => res.write(`data: ${JSON.stringify(data)}
11036
-
11037
- `);
11038
- if (currentConfig.search?.type !== "pagefind") {
11039
- sendEvent({
11040
- type: "complete",
11041
- success: false,
11042
- indexed: 0,
11043
- error: "Pagefind search is not enabled"
11044
- });
11045
- res.end();
11046
- return;
11047
- }
11048
- try {
11049
- for await (const event of buildPagefindDevIndex(vite, currentConfig)) {
11050
- sendEvent(event);
11051
- }
11052
- } catch (error) {
11053
- const message = error instanceof Error ? error.message : "Unknown error";
11054
- sendEvent({
11055
- type: "complete",
11056
- success: false,
11057
- indexed: 0,
11058
- error: message
11059
- });
11060
- }
11061
- res.end();
11062
- });
11063
- printDiagnosticsToConsole(
11064
- `Server-side rendering ${this.options.ssr ? "enabled" : "disabled"}`
11065
- );
11066
- if (config2.search?.type === "pagefind" && vite.config.publicDir) {
11067
- await ensurePagefindDevStub(vite.config.publicDir);
11068
- }
11069
- vite.middlewares.use(async (req, res) => {
11070
- const url = req.originalUrl ?? req.url ?? "/";
11071
- const ssrEnvironment = vite.environments.ssr;
11072
- if (!isRunnableDevEnvironment2(ssrEnvironment)) {
11073
- res.writeHead(500);
11074
- res.end("SSR environment not available");
11075
- return;
11076
- }
11077
- try {
11078
- const { config: currentConfig } = await loadZudokuConfig(
11079
- configEnv,
11080
- this.options.dir
11081
- );
11082
- const rawHtml = getDevHtml({
11083
- jsEntry: "/__z/entry.client.tsx",
11084
- dir: currentConfig.site?.dir
11085
- });
11086
- const template = await vite.transformIndexHtml(url, rawHtml);
11087
- if (this.options.ssr) {
11088
- const entryServer = await ssrEnvironment.runner.import(
11089
- getAppServerEntryPath()
11090
- );
11091
- const hasBody = req.method !== "GET" && req.method !== "HEAD";
11092
- const request = new Request(
11093
- `${this.protocol}://${req.headers.host}${url}`,
11094
- {
11095
- method: req.method,
11096
- headers: req.headers,
11097
- body: hasBody ? req : void 0,
11098
- // Required by Node when body is a readable stream
11099
- // @ts-expect-error Missing type definition
11100
- duplex: hasBody ? "half" : void 0
11101
- }
11102
- );
11103
- const response = await entryServer.handleRequest({
11104
- template,
11105
- request,
11106
- routes: entryServer.getRoutesByConfig(currentConfig),
11107
- basePath: currentConfig.basePath
11108
- });
11109
- for (const [key, value] of response.headers) {
11110
- res.appendHeader(key, value);
11111
- }
11112
- res.writeHead(response.status);
11113
- for await (const chunk of response.body ?? []) {
11114
- res.write(chunk);
11115
- }
11116
- res.end();
11117
- } else {
11118
- res.writeHead(200, { "Content-Type": "text/html" });
11119
- res.end(template);
11120
- }
11121
- } catch (e) {
11122
- logger.error(e);
11123
- const html = `<!DOCTYPE html><html><body><script type="module">
11124
- import { ErrorOverlay } from '/@vite/client';
11125
- document.body.appendChild(new ErrorOverlay(${JSON.stringify({
11126
- message: e instanceof Error ? e.message : String(e),
11127
- stack: e instanceof Error ? e.stack : ""
11128
- })}));
11129
- </script></body></html>`;
11130
- res.writeHead(500, { "Content-Type": "text/html" });
11131
- res.end(html);
11132
- }
11133
- });
11134
- server.on("request", vite.middlewares);
11135
- await new Promise((resolve) => {
11136
- server.listen(this.resolvedPort, resolve);
10794
+ ];
10795
+ const navigation = Array.isArray(next.navigation) ? [...next.navigation] : [];
10796
+ if (!navigation.some(isApiReferenceLink)) {
10797
+ navigation.push({
10798
+ type: "link",
10799
+ to: API_REFERENCE_PATH,
10800
+ label: API_REFERENCE_LABEL
11137
10801
  });
11138
- this.terminator = createHttpTerminator({ server });
11139
- if (this.options.open || process.env.ZUDOKU_OPEN_BROWSER) {
11140
- const url = `${this.protocol}://localhost:${this.resolvedPort}`;
11141
- vite.resolvedUrls = {
11142
- local: [`${url}${vite.config.base || "/"}`],
11143
- network: []
11144
- };
11145
- vite.openBrowser();
10802
+ }
10803
+ next.navigation = navigation;
10804
+ return next;
10805
+ }
10806
+ function openapiInputForConfig(specRelativePosix) {
10807
+ if (specRelativePosix.startsWith("./")) {
10808
+ return specRelativePosix;
10809
+ }
10810
+ return `./${specRelativePosix}`;
10811
+ }
10812
+ function toRelativeOpenApiInput(projectDir, inputPath) {
10813
+ const resolved = path33.isAbsolute(inputPath) ? inputPath : path33.resolve(projectDir, inputPath);
10814
+ const relative = path33.relative(projectDir, resolved);
10815
+ const posix = relative.split(path33.sep).join(path33.posix.sep);
10816
+ if (!posix) {
10817
+ throw new Error("OpenAPI path must point to a file.");
10818
+ }
10819
+ return openapiInputForConfig(posix);
10820
+ }
10821
+ function normalizePageSlug(pathOrSlug) {
10822
+ return pathOrSlug.replace(/^\/+/, "").replace(/\/+$/, "").replace(/\.(mdx?|tsx|jsx)$/i, "");
10823
+ }
10824
+ function slugifyPlanSku(displayName) {
10825
+ const sku = displayName.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
10826
+ return sku || "plan";
10827
+ }
10828
+ function buildLimitsJson(rateLimitCalls, monthlyQuota) {
10829
+ return JSON.stringify({
10830
+ rateLimit: { calls: rateLimitCalls, renewalPeriodSeconds: 60 },
10831
+ quota: { calls: monthlyQuota, renewalPeriodDays: 30 }
10832
+ });
10833
+ }
10834
+ function buildPlanItemFromWizard(input2) {
10835
+ const sku = input2.sku ?? slugifyPlanSku(input2.displayName);
10836
+ const item = {
10837
+ sku,
10838
+ displayName: input2.displayName,
10839
+ priceAmount: input2.priceAmount,
10840
+ limitsJson: buildLimitsJson(input2.rateLimitCalls, input2.monthlyQuota)
10841
+ };
10842
+ if (input2.priceAmount > 0) {
10843
+ item.priceCurrency = "usd";
10844
+ item.billingPeriod = "month";
10845
+ }
10846
+ return item;
10847
+ }
10848
+ function applyPlansToJsonConfig(config2, planItems) {
10849
+ if (planItems.length === 0) {
10850
+ return config2;
10851
+ }
10852
+ const modules = isPlainObject2(config2.modules) ? { ...config2.modules } : {};
10853
+ const userPanel = isPlainObject2(modules.userPanel) ? { ...modules.userPanel } : {};
10854
+ const plans = isPlainObject2(userPanel.plans) ? { ...userPanel.plans } : {};
10855
+ const existing = Array.isArray(plans.items) ? [...plans.items] : [];
10856
+ for (const item of planItems) {
10857
+ const sku = item.sku;
10858
+ const index = typeof sku === "string" ? existing.findIndex((plan) => plan.sku === sku) : -1;
10859
+ if (index >= 0) {
10860
+ existing[index] = { ...existing[index], ...item };
10861
+ } else {
10862
+ existing.push(item);
11146
10863
  }
11147
- return { vite };
11148
10864
  }
11149
- async stop() {
11150
- await this.terminator?.terminate();
10865
+ userPanel.plans = {
10866
+ ...plans,
10867
+ enabled: true,
10868
+ items: existing
10869
+ };
10870
+ modules.userPanel = userPanel;
10871
+ return { ...config2, modules };
10872
+ }
10873
+ function toRoutePath(relativeFile, pagesDir) {
10874
+ const relativeNoExt = relativeFile.replace(/\.(mdx?|tsx|jsx)$/i, "");
10875
+ const escaped = pagesDir.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
10876
+ const withoutPagesPrefix = relativeNoExt.replace(
10877
+ new RegExp(`^${escaped}/?`),
10878
+ ""
10879
+ );
10880
+ const normalized = withoutPagesPrefix.replace(/\/index$/, "");
10881
+ return `/${normalized || "index"}`;
10882
+ }
10883
+ function pageComponentName(slug) {
10884
+ const pascal = slug.replace(/^\/+/, "").split(/[^a-zA-Z0-9]+/).filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
10885
+ const name = /^[A-Za-z]/.test(pascal) ? pascal : `Page${pascal}`;
10886
+ return name.endsWith("Page") ? name : `${name}Page`;
10887
+ }
10888
+ var THIN_WRAPPER_SOURCE = `import { devPortalBillingPlugin } from "@lukoweb/apitogo-plugin-dev-portal-billing";
10889
+ import { buildApitogoConfig } from "@lukoweb/apitogo/build-config";
10890
+ import siteConfig from "./apitogo.config.json";
10891
+
10892
+ export default buildApitogoConfig({
10893
+ ...siteConfig,
10894
+ plugins: [devPortalBillingPlugin()],
10895
+ });
10896
+ `;
10897
+ function isThinConfigWrapper(source) {
10898
+ return /export\s+default\s+buildApitogoConfig\s*\(/.test(source);
10899
+ }
10900
+ function buildThinWrapperWithCustomPages(tsxRoutePaths) {
10901
+ if (tsxRoutePaths.length === 0) {
10902
+ return THIN_WRAPPER_SOURCE;
11151
10903
  }
10904
+ const imports = tsxRoutePaths.map((routePath) => {
10905
+ const slug = routePath.replace(/^\//, "");
10906
+ const name = pageComponentName(slug);
10907
+ return `import ${name} from "./pages/${slug}";`;
10908
+ }).join("\n");
10909
+ const customPages = tsxRoutePaths.map((routePath) => {
10910
+ const slug = routePath.replace(/^\//, "");
10911
+ const name = pageComponentName(slug);
10912
+ return ` {
10913
+ type: "custom-page",
10914
+ path: ${JSON.stringify(routePath)},
10915
+ element: <${name} />,
10916
+ },`;
10917
+ }).join("\n");
10918
+ return `${imports}
10919
+ import { devPortalBillingPlugin } from "@lukoweb/apitogo-plugin-dev-portal-billing";
10920
+ import { buildApitogoConfig } from "@lukoweb/apitogo/build-config";
10921
+ import siteConfig from "./apitogo.config.json";
10922
+
10923
+ const config = buildApitogoConfig({
10924
+ ...siteConfig,
10925
+ plugins: [devPortalBillingPlugin()],
10926
+ });
10927
+
10928
+ export default {
10929
+ ...config,
10930
+ navigation: [
10931
+ {
10932
+ type: "category",
10933
+ label: ${JSON.stringify(DOCUMENTATION_NAV_LABEL)},
10934
+ items: [
10935
+ ${customPages}
10936
+ ...(((config.navigation ?? []).find(
10937
+ (item) =>
10938
+ typeof item === "object" &&
10939
+ item !== null &&
10940
+ "label" in item &&
10941
+ item.label === ${JSON.stringify(DOCUMENTATION_NAV_LABEL)},
10942
+ ) as { items?: unknown[] } | undefined)?.items ?? []),
10943
+ ],
10944
+ },
10945
+ ...(config.navigation ?? []).filter(
10946
+ (item) =>
10947
+ !(
10948
+ typeof item === "object" &&
10949
+ item !== null &&
10950
+ "label" in item &&
10951
+ item.label === ${JSON.stringify(DOCUMENTATION_NAV_LABEL)}
10952
+ ),
10953
+ ),
10954
+ ],
11152
10955
  };
10956
+ `;
10957
+ }
11153
10958
 
11154
- // src/cli/dev/handler.ts
11155
- init_package_json();
11156
- async function dev(argv) {
11157
- const packageJson2 = getZudokuPackageJson();
11158
- process.env.NODE_ENV = "development";
11159
- const dir = path37.resolve(process.cwd(), argv.dir);
11160
- const server = new DevServer({
11161
- dir,
11162
- argPort: argv.port,
11163
- ssr: argv.ssr,
11164
- open: argv.open
11165
- });
11166
- const { vite } = await server.start();
11167
- const url = joinUrl(
11168
- `${server.protocol}://localhost:${server.resolvedPort}`,
11169
- vite.config.base
10959
+ // src/cli/make-config/wrap-up.ts
10960
+ var DOCS = "https://docs.apitogo.com";
10961
+ function printPageAddedHint(relativePath, routePath) {
10962
+ printDiagnosticsToConsole(`Page created: ${relativePath} (route ${routePath})`);
10963
+ printDiagnosticsToConsole("Edit that file to change the content.");
10964
+ printDiagnosticsToConsole(
10965
+ `Predefined components: ${DOCS}/docs/guides/custom-pages and ${DOCS}/docs/components/callout`
11170
10966
  );
11171
- printDiagnosticsToConsole("Ctrl+C to exit");
10967
+ }
10968
+ function printPlanAddedHint(displayName) {
10969
+ printDiagnosticsToConsole(
10970
+ `Plan "${displayName}" added to modules.userPanel.plans.items in apitogo.config.json.`
10971
+ );
10972
+ printDiagnosticsToConsole(
10973
+ "Configure includedActionKeys, billing, and Stripe in the manifest before publish."
10974
+ );
10975
+ printDiagnosticsToConsole(`Docs: ${DOCS}/docs/configuration/plans`);
10976
+ }
10977
+ function printPaidPlanPlatformWarning() {
10978
+ printDiagnosticsToConsole(
10979
+ "Paid API plans require Starter or Professional platform hosting. Free cannot publish paid gateway plans or custom domains."
10980
+ );
10981
+ }
10982
+ function printInteractiveWrapUp(hasPaidPlans) {
11172
10983
  printDiagnosticsToConsole("");
10984
+ printDiagnosticsToConsole("Configuration saved. Next steps:");
10985
+ printDiagnosticsToConsole("- Preview locally: npm run dev");
11173
10986
  printDiagnosticsToConsole(
11174
- `\u{1F680} Running APIToGo v${packageJson2.version}: ${url}`
10987
+ "- Auth API URL is written automatically when you run apitogo apply"
11175
10988
  );
10989
+ if (hasPaidPlans) {
10990
+ printPaidPlanPlatformWarning();
10991
+ } else {
10992
+ printDiagnosticsToConsole(
10993
+ "Platform note: Free hosting does not support paid API plans or custom domains \u2014 use Starter or Professional for those."
10994
+ );
10995
+ }
11176
10996
  printDiagnosticsToConsole("");
11177
- let hasExited = false;
11178
- return new Promise((resolve) => {
11179
- function exit() {
11180
- if (!hasExited) {
11181
- hasExited = true;
11182
- server.stop().then(() => {
11183
- resolve();
11184
- }).catch((e) => {
11185
- console.error("Error stopping server", e);
11186
- resolve();
11187
- });
11188
- }
11189
- }
11190
- process.on("SIGTERM", exit);
11191
- process.on("SIGINT", exit);
11192
- process.on("uncaughtException", (e) => {
11193
- console.error("Uncaught exception", e);
11194
- void exit();
11195
- });
11196
- process.on("unhandledRejection", (e) => {
11197
- if (e instanceof DOMException && e.name === "AbortError") {
11198
- console.log(`[Abort] ${e.message}`);
11199
- return;
11200
- }
11201
- console.error("Unhandled rejection", e);
11202
- void exit();
11203
- });
11204
- process.on("exit", exit);
11205
- });
10997
+ printDiagnosticsToConsole("Configure more:");
10998
+ printDiagnosticsToConsole(`- Overview: ${DOCS}/docs/configuration/overview`);
10999
+ printDiagnosticsToConsole(`- Manifest: ${DOCS}/docs/configuration/manifest`);
11000
+ printDiagnosticsToConsole(`- Gateway plans: ${DOCS}/docs/configuration/plans`);
11001
+ printDiagnosticsToConsole(`- Custom pages: ${DOCS}/docs/guides/custom-pages`);
11002
+ printDiagnosticsToConsole(`- API reference: ${DOCS}/docs/configuration/api-reference`);
11003
+ printDiagnosticsToConsole(`- Theme: ${DOCS}/docs/customization/colors-theme`);
11004
+ printDiagnosticsToConsole(`- Fonts: ${DOCS}/docs/customization/fonts`);
11005
+ printDiagnosticsToConsole(`- CLI shape: ${DOCS}/docs/cli/shape`);
11006
+ printDiagnosticsToConsole(`- apitogo config: ${DOCS}/docs/configuration/make-config`);
11206
11007
  }
11207
11008
 
11208
- // src/cli/cmds/dev.ts
11209
- var dev_default = {
11210
- desc: "Runs locally",
11211
- command: "dev",
11212
- builder: (yargs2) => {
11213
- return yargs2.option("dir", {
11214
- type: "string",
11215
- describe: "The directory containing your project",
11216
- default: ".",
11217
- normalize: true,
11218
- hidden: true
11219
- }).option("port", {
11220
- type: "number",
11221
- describe: "The port to run the local server on"
11222
- }).option("ssr", {
11223
- type: "boolean",
11224
- describe: "Enable server-side rendering",
11225
- default: true
11226
- }).option("open", {
11227
- type: "boolean",
11228
- describe: "Automatically open the browser",
11229
- default: false
11230
- });
11231
- },
11232
- handler: async (argv) => {
11233
- await captureEvent({
11234
- argv,
11235
- event: "apitogo dev"
11236
- });
11237
- await dev(argv);
11238
- }
11239
- };
11240
-
11241
- // src/cli/make-config/handler.ts
11242
- import { constants as constants3 } from "node:fs";
11243
- import { access as access5, mkdir as mkdir8, readFile as readFile6, writeFile as writeFile8 } from "node:fs/promises";
11244
- import path39 from "node:path";
11245
- import { glob as glob5 } from "glob";
11246
-
11247
11009
  // src/cli/make-config/interactive.ts
11248
- import { createInterface } from "node:readline/promises";
11249
- import { stdin as input, stdout as output } from "node:process";
11250
11010
  var ask = async (rl, question, defaultValue) => {
11251
11011
  const suffix = defaultValue ? ` [${defaultValue}]` : "";
11252
11012
  const answer = (await rl.question(`${question}${suffix}: `)).trim();
11253
11013
  return answer || defaultValue || "";
11254
11014
  };
11255
- var askYesNo = async (rl, question, defaultYes) => {
11256
- const hint = defaultYes ? "Y/n" : "y/N";
11257
- const answer = (await rl.question(`${question} (${hint}): `)).trim().toLowerCase();
11258
- if (!answer) {
11259
- return defaultYes;
11015
+ var askNonNegativeNumber = async (rl, question, defaultValue) => {
11016
+ while (true) {
11017
+ const answer = await ask(rl, question, String(defaultValue));
11018
+ const parsed = Number.parseFloat(answer);
11019
+ if (!Number.isNaN(parsed) && parsed >= 0) {
11020
+ return parsed;
11021
+ }
11022
+ printDiagnosticsToConsole("Enter a non-negative number.");
11023
+ }
11024
+ };
11025
+ var parseLoopChoice = (raw) => {
11026
+ const value = raw.trim().toLowerCase();
11027
+ if (!value || value === "done") {
11028
+ return "done";
11029
+ }
11030
+ if (value === "openapi" || value === "page" || value === "plan") {
11031
+ return value;
11260
11032
  }
11261
- return answer === "y" || answer === "yes";
11033
+ return null;
11262
11034
  };
11263
11035
  async function promptMakeConfigInteractive(defaults) {
11264
11036
  const rl = createInterface({ input, output });
11037
+ const customPages = [];
11038
+ const planItems = [];
11039
+ let hasPaidPlans = false;
11040
+ let openApiPath;
11265
11041
  try {
11266
11042
  const title = await ask(rl, "Site title", defaults.title);
11267
- const logoAlt = await ask(rl, "Logo alt text", defaults.logoAlt);
11268
- const authEnabled = await askYesNo(
11269
- rl,
11270
- "Enable authentication",
11271
- defaults.authEnabled
11272
- );
11273
- let apiBaseUrl = defaults.apiBaseUrl ?? "";
11274
- if (authEnabled) {
11275
- apiBaseUrl = await ask(
11043
+ while (true) {
11044
+ const choiceRaw = await ask(
11276
11045
  rl,
11277
- "Auth API base URL (optional; secrets stay in .env)",
11278
- apiBaseUrl
11046
+ "Add OpenAPI, a custom page, or a plan? (openapi / page / plan / done)",
11047
+ "done"
11279
11048
  );
11280
- }
11281
- const sans = await ask(rl, "Sans font", defaults.sans ?? "IBM Plex Sans");
11282
- const mono = await ask(
11283
- rl,
11284
- "Mono font",
11285
- defaults.mono ?? "JetBrains Mono"
11286
- );
11287
- const lightPrimary = await ask(
11288
- rl,
11289
- "Light primary color",
11290
- defaults.lightPrimary ?? "#E11D2A"
11291
- );
11292
- const darkPrimary = await ask(
11293
- rl,
11294
- "Dark primary color",
11295
- defaults.darkPrimary ?? "#F23645"
11296
- );
11297
- let openApiPath;
11298
- if (defaults.discoveredOpenApi) {
11299
- const useDiscovered = await askYesNo(
11049
+ const choice = parseLoopChoice(choiceRaw);
11050
+ if (!choice) {
11051
+ printDiagnosticsToConsole("Choose openapi, page, plan, or done.");
11052
+ continue;
11053
+ }
11054
+ if (choice === "done") {
11055
+ break;
11056
+ }
11057
+ if (choice === "openapi") {
11058
+ const entered = await ask(
11059
+ rl,
11060
+ "OpenAPI file path (leave empty to skip)",
11061
+ defaults.discoveredOpenApi ?? ""
11062
+ );
11063
+ openApiPath = entered ? entered : null;
11064
+ if (openApiPath) {
11065
+ printDiagnosticsToConsole(
11066
+ `OpenAPI will be linked as a relative path in apitogo.config.json.`
11067
+ );
11068
+ }
11069
+ continue;
11070
+ }
11071
+ if (choice === "page") {
11072
+ const pageTitle = await ask(rl, "Page title");
11073
+ if (!pageTitle.trim()) {
11074
+ printDiagnosticsToConsole("Page title is required.");
11075
+ continue;
11076
+ }
11077
+ const address = await ask(
11078
+ rl,
11079
+ "Page URL path (e.g. /guides/getting-started)"
11080
+ );
11081
+ const slug = normalizePageSlug(address);
11082
+ if (!slug) {
11083
+ printDiagnosticsToConsole("Page URL path is required.");
11084
+ continue;
11085
+ }
11086
+ const routePath = `/${slug}`;
11087
+ const relativePath = path34.posix.join(defaults.pagesDir, `${slug}.tsx`);
11088
+ const page = {
11089
+ slug,
11090
+ title: pageTitle.trim(),
11091
+ routePath,
11092
+ relativePath
11093
+ };
11094
+ customPages.push(page);
11095
+ printPageAddedHint(relativePath, routePath);
11096
+ continue;
11097
+ }
11098
+ const displayName = await ask(rl, "Plan title");
11099
+ if (!displayName.trim()) {
11100
+ printDiagnosticsToConsole("Plan title is required.");
11101
+ continue;
11102
+ }
11103
+ const priceAmount = await askNonNegativeNumber(
11104
+ rl,
11105
+ "Price (USD/month, 0 for free)",
11106
+ 0
11107
+ );
11108
+ const rateLimitCalls = await askNonNegativeNumber(
11300
11109
  rl,
11301
- `Wire OpenAPI at ${defaults.discoveredOpenApi}`,
11302
- true
11110
+ "Rate limit (calls per minute)",
11111
+ 100
11303
11112
  );
11304
- openApiPath = useDiscovered ? defaults.discoveredOpenApi : null;
11305
- } else {
11306
- const entered = await ask(
11113
+ const monthlyQuota = await askNonNegativeNumber(
11307
11114
  rl,
11308
- "OpenAPI file path (leave empty to skip)",
11309
- ""
11115
+ "Monthly quota (calls per 30 days)",
11116
+ 5e4
11310
11117
  );
11311
- openApiPath = entered || null;
11312
- }
11313
- const authentication = {
11314
- enabled: authEnabled,
11315
- type: "dev-portal"
11316
- };
11317
- if (apiBaseUrl) {
11318
- authentication.apiBaseUrl = apiBaseUrl;
11118
+ const planItem = buildPlanItemFromWizard({
11119
+ displayName: displayName.trim(),
11120
+ priceAmount,
11121
+ rateLimitCalls,
11122
+ monthlyQuota
11123
+ });
11124
+ planItems.push(planItem);
11125
+ if (priceAmount > 0) {
11126
+ hasPaidPlans = true;
11127
+ printPaidPlanPlatformWarning();
11128
+ }
11129
+ printPlanAddedHint(displayName.trim());
11319
11130
  }
11320
11131
  return {
11321
11132
  title,
11322
- site: {
11323
- title,
11324
- logo: { alt: logoAlt }
11325
- },
11326
- theme: {
11327
- fonts: { sans, mono },
11328
- light: { primary: lightPrimary },
11329
- dark: { primary: darkPrimary }
11133
+ site: { title },
11134
+ authentication: {
11135
+ enabled: true,
11136
+ type: "dev-portal"
11330
11137
  },
11331
- authentication,
11332
- openApiPath
11138
+ openApiPath,
11139
+ customPages,
11140
+ planItems,
11141
+ hasPaidPlans
11333
11142
  };
11334
11143
  } finally {
11335
11144
  rl.close();
11336
11145
  }
11337
11146
  }
11338
11147
 
11339
- // src/cli/make-config/json-config.ts
11340
- var APITOGO_JSON_FILENAME = "apitogo.config.json";
11341
- var DOCUMENTATION_NAV_LABEL = "Documentation";
11342
- var API_REFERENCE_PATH = "/api";
11343
- var API_REFERENCE_LABEL = "API Reference";
11344
- function isPlainObject2(value) {
11345
- return value !== null && typeof value === "object" && !Array.isArray(value);
11346
- }
11347
- function deepMerge(target, source) {
11348
- const result = { ...target };
11349
- for (const [key, value] of Object.entries(source)) {
11350
- const existing = result[key];
11351
- if (isPlainObject2(existing) && isPlainObject2(value)) {
11352
- result[key] = deepMerge(existing, value);
11353
- } else {
11354
- result[key] = value;
11355
- }
11356
- }
11357
- return result;
11358
- }
11359
- function parseJsonObjectFlag(raw, flagName) {
11360
- if (raw === void 0 || raw.trim() === "") {
11361
- return void 0;
11362
- }
11363
- let parsed;
11364
- try {
11365
- parsed = JSON.parse(raw);
11366
- } catch {
11367
- throw new Error(`--${flagName} must be a JSON object.`);
11368
- }
11369
- if (!isPlainObject2(parsed)) {
11370
- throw new Error(`--${flagName} must be a JSON object.`);
11371
- }
11372
- return parsed;
11373
- }
11374
- function titleFromDirectoryName(dirName) {
11375
- const titled = dirName.split(/[-_\s]+/).filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join(" ");
11376
- return titled || "Developer Portal";
11377
- }
11378
- function createDefaultApitogoJsonConfig(options) {
11379
- const filesGlob = `/${options.pagesDir}/**/*.{md,mdx}`;
11380
- return {
11381
- site: {
11382
- title: options.title,
11383
- logo: {
11384
- src: { light: "/logo-light.png", dark: "/logo-dark.png" },
11385
- alt: "APIToGo",
11386
- width: "130px"
11387
- }
11388
- },
11389
- authentication: {
11390
- enabled: false,
11391
- type: "dev-portal",
11392
- redirectToAfterSignIn: "/account/dashboard",
11393
- redirectToAfterSignUp: "/account/dashboard"
11394
- },
11395
- metadata: {
11396
- favicon: "/Icon.png"
11397
- },
11398
- theme: {
11399
- fonts: {
11400
- sans: "IBM Plex Sans",
11401
- mono: "JetBrains Mono"
11402
- },
11403
- light: {
11404
- background: "#F5F6F9",
11405
- foreground: "#0E1016",
11406
- card: "#FFFFFF",
11407
- cardForeground: "#0E1016",
11408
- primary: "#E11D2A",
11409
- primaryForeground: "#ffffff",
11410
- secondary: "#FAFBFC",
11411
- secondaryForeground: "#0E1016",
11412
- muted: "#FAFBFC",
11413
- mutedForeground: "#565E70",
11414
- accent: "rgba(225, 29, 42, 0.08)",
11415
- accentForeground: "#E11D2A",
11416
- border: "#E6E8EF",
11417
- input: "#D6DAE3",
11418
- ring: "#E11D2A"
11419
- },
11420
- dark: {
11421
- background: "#0B0D12",
11422
- foreground: "#EDEFF5",
11423
- card: "#14171F",
11424
- cardForeground: "#EDEFF5",
11425
- primary: "#F23645",
11426
- primaryForeground: "#ffffff",
11427
- secondary: "#191D27",
11428
- secondaryForeground: "#EDEFF5",
11429
- muted: "#191D27",
11430
- mutedForeground: "#A6AEBF",
11431
- accent: "rgba(242, 54, 69, 0.13)",
11432
- accentForeground: "#F23645",
11433
- border: "#252B37",
11434
- input: "#333A48",
11435
- ring: "#F23645"
11436
- }
11437
- },
11438
- modules: {
11439
- docs: {
11440
- files: filesGlob
11441
- },
11442
- landing: {
11443
- enabled: true,
11444
- path: "/",
11445
- layout: "landing",
11446
- page: "./landing/LandingPage.tsx"
11447
- },
11448
- userPanel: {
11449
- enabled: true,
11450
- dashboard: { enabled: true },
11451
- userManagement: { enabled: true },
11452
- plans: {
11453
- enabled: false,
11454
- page: "./user-panel/AccountPlansPage.tsx"
11455
- }
11456
- }
11457
- }
11458
- };
11148
+ // src/cli/make-config/page-scaffold.ts
11149
+ function buildStarterDocsPageTsx(slug, title) {
11150
+ const componentName = pageComponentName(slug);
11151
+ const safeTitle = title.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
11152
+ return `import { Callout, Heading, Typography } from "@lukoweb/apitogo/components";
11153
+
11154
+ export default function ${componentName}() {
11155
+ return (
11156
+ <section className="mx-auto max-w-screen-xl px-4 py-12 pt-(--padding-content-top)">
11157
+ <Typography>
11158
+ <Heading level={1}>${safeTitle}</Heading>
11159
+ <p className="mt-4 text-muted-foreground">
11160
+ Edit this page in <code>pages/${slug}.tsx</code>.
11161
+ </p>
11162
+ <Callout type="info" title="Components">
11163
+ APIToGo ships predefined UI components for docs pages. See{" "}
11164
+ <a
11165
+ href="https://docs.apitogo.com/docs/guides/custom-pages"
11166
+ className="text-primary underline"
11167
+ >
11168
+ custom pages
11169
+ </a>{" "}
11170
+ and the{" "}
11171
+ <a
11172
+ href="https://docs.apitogo.com/docs/components/callout"
11173
+ className="text-primary underline"
11174
+ >
11175
+ component catalog
11176
+ </a>
11177
+ .
11178
+ </Callout>
11179
+ </Typography>
11180
+ </section>
11181
+ );
11459
11182
  }
11460
- function applyConfigPatches(config2, patches) {
11461
- let next = { ...config2 };
11462
- if (patches.site) {
11463
- next.site = deepMerge(
11464
- isPlainObject2(next.site) ? next.site : {},
11465
- patches.site
11466
- );
11467
- }
11468
- if (patches.theme) {
11469
- next.theme = deepMerge(
11470
- isPlainObject2(next.theme) ? next.theme : {},
11471
- patches.theme
11472
- );
11473
- }
11474
- if (patches.authentication) {
11475
- next.authentication = deepMerge(
11476
- isPlainObject2(next.authentication) ? next.authentication : {},
11477
- patches.authentication
11478
- );
11479
- }
11480
- if (patches.title?.trim()) {
11481
- const site = isPlainObject2(next.site) ? next.site : {};
11482
- next.site = { ...site, title: patches.title.trim() };
11483
- }
11484
- return next;
11183
+ `;
11485
11184
  }
11486
- var docSortRank = (docPath) => {
11487
- const lastSegment = docPath.split("/").at(-1) ?? docPath;
11488
- if (lastSegment === "introduction") return 0;
11489
- if (lastSegment === "example") return 1;
11490
- if (lastSegment === "getting-started") return 2;
11491
- if (lastSegment === "index") return 3;
11492
- return 4;
11185
+
11186
+ // src/cli/make-config/scaffold-project.ts
11187
+ init_package_json();
11188
+ import { constants } from "node:fs";
11189
+ import { access as access2, mkdir as mkdir6, readFile as readFile4, writeFile as writeFile6 } from "node:fs/promises";
11190
+ import path35 from "node:path";
11191
+ import { glob as glob4 } from "glob";
11192
+ var OPENAPI_GLOBS = [
11193
+ "apis/openapi.json",
11194
+ "apis/openapi.yaml",
11195
+ "apis/openapi.yml",
11196
+ "openapi.json",
11197
+ "openapi.yaml",
11198
+ "openapi.yml"
11199
+ ];
11200
+ var toPosix = (p) => p.split(path35.sep).join("/");
11201
+ var sanitizePackageName = (dir) => {
11202
+ const base = path35.basename(path35.resolve(dir));
11203
+ const s = base.toLowerCase().replace(/[^a-z0-9-_.]/g, "-").replace(/^-+|-+$/g, "");
11204
+ return s || "apitogo-site";
11493
11205
  };
11494
- var sortDocPaths = (docPaths) => [...docPaths].sort((a, b) => {
11495
- const rankDiff = docSortRank(a) - docSortRank(b);
11496
- if (rankDiff !== 0) return rankDiff;
11497
- return a.localeCompare(b);
11498
- });
11499
- var createTreeNode = () => ({ docs: [], children: /* @__PURE__ */ new Map() });
11500
- var titleCase = (value) => value.split(/[-_\s]+/).filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join(" ");
11501
- var buildTree = (routePaths) => {
11502
- const root = createTreeNode();
11503
- for (const routePath of routePaths) {
11504
- const segments = routePath.replace(/^\//, "").split("/").filter(Boolean);
11505
- const directorySegments = segments.slice(0, -1);
11506
- let current = root;
11507
- for (const segment of directorySegments) {
11508
- let child = current.children.get(segment);
11509
- if (!child) {
11510
- child = createTreeNode();
11511
- current.children.set(segment, child);
11512
- }
11513
- current = child;
11514
- }
11515
- current.docs.push(routePath);
11516
- }
11517
- return root;
11206
+ var apitogoVersionRange = () => {
11207
+ const v = getZudokuPackageJson()?.version;
11208
+ return v ? `^${v}` : "latest";
11518
11209
  };
11519
- var treeToNavigationItems = (node) => {
11520
- const docs = sortDocPaths(node.docs);
11521
- const categories = [...node.children.keys()].sort((a, b) => a.localeCompare(b)).map((directoryName) => {
11522
- const childNode = node.children.get(directoryName);
11523
- if (!childNode) {
11524
- throw new Error(`Missing child node for directory '${directoryName}'.`);
11210
+ async function findOpenApiSpecPath(dir) {
11211
+ for (const rel of OPENAPI_GLOBS) {
11212
+ const full = path35.join(dir, rel);
11213
+ try {
11214
+ await access2(full, constants.R_OK);
11215
+ return toPosix(rel);
11216
+ } catch {
11525
11217
  }
11526
- return {
11527
- type: "category",
11528
- label: titleCase(directoryName),
11529
- items: treeToNavigationItems(childNode)
11218
+ }
11219
+ const extra = await glob4("apis/**/*.{json,yaml,yml}", {
11220
+ cwd: dir,
11221
+ posix: true,
11222
+ ignore: ["**/node_modules/**"]
11223
+ });
11224
+ return extra[0] ? toPosix(extra[0]) : null;
11225
+ }
11226
+ async function ensurePackageJson(dir) {
11227
+ const pkgPath = path35.join(dir, "package.json");
11228
+ const apitogoVer = apitogoVersionRange();
11229
+ const baseDeps = {
11230
+ react: ">=19.0.0",
11231
+ "react-dom": ">=19.0.0",
11232
+ "@lukoweb/apitogo": apitogoVer,
11233
+ "@lukoweb/apitogo-plugin-dev-portal-billing": apitogoVer,
11234
+ "@lukoweb/apitogo-module-landing": apitogoVer,
11235
+ "@lukoweb/apitogo-module-user-panel": apitogoVer,
11236
+ "lucide-react": "0.577.0"
11237
+ };
11238
+ const baseScripts = {
11239
+ dev: "apitogo dev",
11240
+ build: "apitogo build",
11241
+ preview: "apitogo build --preview"
11242
+ };
11243
+ try {
11244
+ await access2(pkgPath, constants.R_OK);
11245
+ } catch (err) {
11246
+ const code = err.code;
11247
+ if (code !== "ENOENT") throw err;
11248
+ const pkg2 = {
11249
+ name: sanitizePackageName(dir),
11250
+ version: "0.1.0",
11251
+ private: true,
11252
+ type: "module",
11253
+ scripts: baseScripts,
11254
+ dependencies: baseDeps,
11255
+ devDependencies: {
11256
+ "@types/node": "^22",
11257
+ "@types/react": "^19",
11258
+ "@types/react-dom": "^19",
11259
+ typescript: "^5"
11260
+ }
11530
11261
  };
11531
- });
11532
- return [...docs, ...categories];
11533
- };
11534
- function buildDocumentationItems(routePaths) {
11535
- return treeToNavigationItems(buildTree(sortDocPaths(routePaths)));
11536
- }
11537
- function navItemKey(item) {
11538
- if (typeof item === "string") {
11539
- return `path:${item}`;
11262
+ await writeFile6(pkgPath, `${JSON.stringify(pkg2, null, 2)}
11263
+ `, "utf8");
11264
+ return true;
11540
11265
  }
11541
- if (typeof item.to === "string") {
11542
- return `to:${item.to}`;
11266
+ const raw = await readFile4(pkgPath, "utf8");
11267
+ let pkg;
11268
+ try {
11269
+ pkg = JSON.parse(raw);
11270
+ } catch {
11271
+ throw new Error(
11272
+ "package.json exists but is not valid JSON; fix or remove it and run again."
11273
+ );
11543
11274
  }
11544
- if (typeof item.path === "string") {
11545
- return `path:${item.path}`;
11275
+ pkg.scripts = {
11276
+ ...typeof pkg.scripts === "object" && pkg.scripts !== null ? pkg.scripts : {}
11277
+ };
11278
+ const scripts = pkg.scripts;
11279
+ for (const [k, v] of Object.entries(baseScripts)) {
11280
+ if (!scripts[k]) scripts[k] = v;
11546
11281
  }
11547
- if (item.type === "category" && typeof item.label === "string") {
11548
- return `category:${item.label}`;
11282
+ pkg.dependencies = {
11283
+ ...typeof pkg.dependencies === "object" && pkg.dependencies !== null ? pkg.dependencies : {}
11284
+ };
11285
+ const deps = pkg.dependencies;
11286
+ for (const [k, v] of Object.entries(baseDeps)) {
11287
+ if (!deps[k]) deps[k] = v;
11549
11288
  }
11550
- return JSON.stringify(item);
11289
+ await writeFile6(pkgPath, `${JSON.stringify(pkg, null, 2)}
11290
+ `, "utf8");
11291
+ return false;
11551
11292
  }
11552
- function mergeNavItems(existing, incoming) {
11553
- const seen = new Set(existing.map(navItemKey));
11554
- const merged = [...existing];
11555
- for (const item of incoming) {
11556
- const key = navItemKey(item);
11557
- if (seen.has(key)) {
11558
- continue;
11559
- }
11560
- seen.add(key);
11561
- merged.push(item);
11293
+ async function ensureTsconfigJson(dir) {
11294
+ const tsPath = path35.join(dir, "tsconfig.json");
11295
+ try {
11296
+ await access2(tsPath, constants.R_OK);
11297
+ return false;
11298
+ } catch {
11299
+ const tsconfig = {
11300
+ compilerOptions: {
11301
+ target: "ES2022",
11302
+ lib: ["ESNext", "DOM", "DOM.Iterable", "WebWorker"],
11303
+ module: "ESNext",
11304
+ moduleResolution: "Bundler",
11305
+ useDefineForClassFields: true,
11306
+ skipLibCheck: true,
11307
+ skipDefaultLibCheck: true,
11308
+ resolveJsonModule: true,
11309
+ isolatedModules: true,
11310
+ useUnknownInCatchVariables: false,
11311
+ jsx: "react-jsx"
11312
+ },
11313
+ include: [
11314
+ "*.config.ts",
11315
+ "*.config.tsx",
11316
+ "*.config.jsx",
11317
+ "*.config.js",
11318
+ "*.config.mjs",
11319
+ "pages",
11320
+ "landing",
11321
+ "user-panel"
11322
+ ]
11323
+ };
11324
+ await writeFile6(tsPath, `${JSON.stringify(tsconfig, null, 2)}
11325
+ `, "utf8");
11326
+ return true;
11562
11327
  }
11563
- return merged;
11564
- }
11565
- function isDocumentationCategory(item) {
11566
- return typeof item === "object" && item.type === "category" && item.label === DOCUMENTATION_NAV_LABEL;
11567
11328
  }
11568
- function isApiReferenceLink(item) {
11569
- return typeof item === "object" && item.type === "link" && item.to === API_REFERENCE_PATH;
11329
+ var LANDING_PAGE_SOURCE = `import {
11330
+ FeaturesSection,
11331
+ HeroSection,
11332
+ LandingShell,
11333
+ } from "@lukoweb/apitogo-module-landing";
11334
+
11335
+ export default function LandingPage() {
11336
+ return (
11337
+ <LandingShell
11338
+ title="Your API, ready for developers"
11339
+ description="Documentation, API reference, and a developer portal \u2014 all in one place."
11340
+ >
11341
+ <HeroSection
11342
+ hero={{
11343
+ title: "Your API, ready for developers",
11344
+ description:
11345
+ "Ship docs, interactive API reference, and a developer portal without building from scratch.",
11346
+ }}
11347
+ primaryAction={{ label: "Get started", href: "/introduction" }}
11348
+ secondaryAction={{ label: "API Reference", href: "/api" }}
11349
+ />
11350
+ <FeaturesSection
11351
+ features={[
11352
+ {
11353
+ title: "Documentation",
11354
+ description: "Guides and references for every integration path.",
11355
+ icon: "book-open",
11356
+ },
11357
+ {
11358
+ title: "API Reference",
11359
+ description: "Interactive OpenAPI explorer with code samples.",
11360
+ icon: "code",
11361
+ },
11362
+ {
11363
+ title: "Developer Portal",
11364
+ description: "Auth, API keys, and subscription billing built in.",
11365
+ icon: "shield-check",
11366
+ },
11367
+ ]}
11368
+ />
11369
+ </LandingShell>
11370
+ );
11570
11371
  }
11571
- function mergeDocumentationNavigation(navigation, docRoutePaths, options) {
11572
- const existing = Array.isArray(navigation) ? navigation : [];
11573
- const generated = buildDocumentationItems(docRoutePaths);
11574
- const rest = existing.filter((item) => !isDocumentationCategory(item));
11575
- const currentDocs = existing.find(isDocumentationCategory);
11576
- const currentItems = Array.isArray(currentDocs?.items) ? currentDocs.items : [];
11577
- const items = options.force ? generated : mergeNavItems(currentItems, generated);
11578
- const documentation = {
11579
- type: "category",
11580
- label: DOCUMENTATION_NAV_LABEL,
11581
- items
11582
- };
11583
- if (generated.length === 0) {
11584
- return existing;
11372
+ `;
11373
+ var ACCOUNT_PLANS_PAGE_SOURCE = `export { AccountPlansPage as default } from "@lukoweb/apitogo-plugin-dev-portal-billing";
11374
+ `;
11375
+ var fileExists2 = async (filePath) => {
11376
+ try {
11377
+ await access2(filePath, constants.F_OK);
11378
+ return true;
11379
+ } catch {
11380
+ return false;
11585
11381
  }
11586
- if (currentDocs) {
11587
- const index = existing.findIndex(isDocumentationCategory);
11588
- const next = [...existing];
11589
- next[index] = documentation;
11590
- return next;
11382
+ };
11383
+ async function ensurePortalStubFiles(dir) {
11384
+ const landingPath = path35.join(dir, "landing", "LandingPage.tsx");
11385
+ if (!await fileExists2(landingPath)) {
11386
+ await mkdir6(path35.join(dir, "landing"), { recursive: true });
11387
+ await writeFile6(landingPath, LANDING_PAGE_SOURCE, "utf8");
11388
+ }
11389
+ const plansPath = path35.join(dir, "user-panel", "AccountPlansPage.tsx");
11390
+ if (!await fileExists2(plansPath)) {
11391
+ await mkdir6(path35.join(dir, "user-panel"), { recursive: true });
11392
+ await writeFile6(plansPath, ACCOUNT_PLANS_PAGE_SOURCE, "utf8");
11591
11393
  }
11592
- return [documentation, ...rest];
11593
11394
  }
11594
- function applyOpenApiToJsonConfig(config2, input2) {
11595
- const next = { ...config2 };
11596
- next.apis = [
11597
- {
11598
- type: "file",
11599
- input: input2,
11600
- path: "api"
11395
+ async function ensureThinConfigTsx(dir) {
11396
+ const tsxPath = path35.join(dir, "apitogo.config.tsx");
11397
+ if (await fileExists2(tsxPath)) {
11398
+ return false;
11399
+ }
11400
+ await writeFile6(tsxPath, THIN_WRAPPER_SOURCE, "utf8");
11401
+ return true;
11402
+ }
11403
+
11404
+ // src/cli/make-config/handler.ts
11405
+ var toPosixPath2 = (value) => value.split(path36.sep).join(path36.posix.sep);
11406
+ var fileExists3 = async (filePath) => {
11407
+ try {
11408
+ await access3(filePath, constants2.F_OK);
11409
+ return true;
11410
+ } catch {
11411
+ return false;
11412
+ }
11413
+ };
11414
+ var readJsonConfig = async (filePath) => {
11415
+ const raw = await readFile5(filePath, "utf8");
11416
+ const parsed = JSON.parse(raw);
11417
+ if (!isPlainObject2(parsed)) {
11418
+ throw new Error(`${APITOGO_JSON_FILENAME} must be a JSON object.`);
11419
+ }
11420
+ return parsed;
11421
+ };
11422
+ async function makeConfig(argv) {
11423
+ const dir = path36.resolve(process.cwd(), argv.dir);
11424
+ const pagesDir = toPosixPath2(
11425
+ argv.pagesDir.replace(/^[./]+/, "").replace(/\/+$/, "")
11426
+ );
11427
+ try {
11428
+ const discoveredOpenApi = await findOpenApiSpecPath(dir);
11429
+ const createdPackageJson = await ensurePackageJson(dir);
11430
+ const createdTsconfig = await ensureTsconfigJson(dir);
11431
+ if (createdPackageJson) {
11432
+ printDiagnosticsToConsole(
11433
+ "Created package.json with dev, build, and preview scripts."
11434
+ );
11601
11435
  }
11602
- ];
11603
- const navigation = Array.isArray(next.navigation) ? [...next.navigation] : [];
11604
- if (!navigation.some(isApiReferenceLink)) {
11605
- navigation.push({
11606
- type: "link",
11607
- to: API_REFERENCE_PATH,
11608
- label: API_REFERENCE_LABEL
11436
+ if (createdTsconfig) {
11437
+ printDiagnosticsToConsole("Created tsconfig.json.");
11438
+ }
11439
+ await ensurePortalStubFiles(dir);
11440
+ const jsonPath = path36.join(dir, APITOGO_JSON_FILENAME);
11441
+ let createdJson = false;
11442
+ let config2;
11443
+ if (await fileExists3(jsonPath)) {
11444
+ config2 = await readJsonConfig(jsonPath);
11445
+ } else {
11446
+ config2 = createDefaultApitogoJsonConfig({
11447
+ title: titleFromDirectoryName(path36.basename(dir)),
11448
+ pagesDir
11449
+ });
11450
+ createdJson = true;
11451
+ printDiagnosticsToConsole(`Created ${APITOGO_JSON_FILENAME}`);
11452
+ }
11453
+ const createdTsx = await ensureThinConfigTsx(dir);
11454
+ if (createdTsx) {
11455
+ printDiagnosticsToConsole("Created apitogo.config.tsx");
11456
+ }
11457
+ const flagPatches = {
11458
+ site: parseJsonObjectFlag(argv.site, "site"),
11459
+ theme: parseJsonObjectFlag(argv.theme, "theme"),
11460
+ authentication: parseJsonObjectFlag(argv.authentication, "authentication"),
11461
+ title: argv.title
11462
+ };
11463
+ let openApiOverride;
11464
+ let interactiveHasPaidPlans = false;
11465
+ if (argv.interactive) {
11466
+ const site = isPlainObject2(config2.site) ? config2.site : {};
11467
+ const prompted = await promptMakeConfigInteractive({
11468
+ title: flagPatches.title ?? (typeof site.title === "string" ? site.title : titleFromDirectoryName(path36.basename(dir))),
11469
+ discoveredOpenApi,
11470
+ pagesDir
11471
+ });
11472
+ openApiOverride = prompted.openApiPath;
11473
+ interactiveHasPaidPlans = prompted.hasPaidPlans;
11474
+ config2 = applyConfigPatches(config2, prompted);
11475
+ config2 = applyPlansToJsonConfig(config2, prompted.planItems);
11476
+ for (const page of prompted.customPages) {
11477
+ const pagePath = path36.join(dir, page.relativePath);
11478
+ await mkdir7(path36.dirname(pagePath), { recursive: true });
11479
+ await writeFile7(
11480
+ pagePath,
11481
+ buildStarterDocsPageTsx(page.slug, page.title),
11482
+ "utf8"
11483
+ );
11484
+ }
11485
+ }
11486
+ config2 = applyConfigPatches(config2, flagPatches);
11487
+ const pageFiles = await glob5(`${pagesDir}/**/*.{md,mdx,tsx,jsx}`, {
11488
+ cwd: dir,
11489
+ ignore: ["**/node_modules/**", "**/.git/**", "**/dist/**"],
11490
+ posix: true
11609
11491
  });
11492
+ const markdownFiles = pageFiles.filter((file) => /\.mdx?$/i.test(file));
11493
+ const tsxFiles = pageFiles.filter((file) => /\.(tsx|jsx)$/i.test(file));
11494
+ const markdownRoutes = markdownFiles.map(
11495
+ (file) => toRoutePath(file, pagesDir)
11496
+ );
11497
+ const tsxRoutes = tsxFiles.map((file) => toRoutePath(file, pagesDir));
11498
+ if (markdownRoutes.length === 0 && tsxRoutes.length === 0) {
11499
+ printDiagnosticsToConsole(
11500
+ `No docs pages under '${pagesDir}/' \u2014 skipping documentation nav scan.`
11501
+ );
11502
+ }
11503
+ config2.navigation = mergeDocumentationNavigation(
11504
+ config2.navigation,
11505
+ markdownRoutes,
11506
+ { force: argv.force === true }
11507
+ );
11508
+ const openApiRel = openApiOverride === void 0 ? discoveredOpenApi : openApiOverride;
11509
+ if (openApiRel) {
11510
+ const openApiInput = argv.interactive ? toRelativeOpenApiInput(dir, openApiRel) : openapiInputForConfig(openApiRel);
11511
+ config2 = applyOpenApiToJsonConfig(config2, openApiInput);
11512
+ }
11513
+ await writeFile7(jsonPath, `${JSON.stringify(config2, null, 2)}
11514
+ `, "utf8");
11515
+ const tsxPath = path36.join(dir, "apitogo.config.tsx");
11516
+ if (await fileExists3(tsxPath) && tsxRoutes.length > 0) {
11517
+ const tsxSource = await readFile5(tsxPath, "utf8");
11518
+ if (isThinConfigWrapper(tsxSource)) {
11519
+ await writeFile7(
11520
+ tsxPath,
11521
+ buildThinWrapperWithCustomPages(tsxRoutes),
11522
+ "utf8"
11523
+ );
11524
+ }
11525
+ }
11526
+ const action = createdJson ? "Initialized" : "Updated";
11527
+ printResultToConsole(
11528
+ `${action} ${APITOGO_JSON_FILENAME} (site/auth/navigation/OpenAPI/plans as applicable; redirects unchanged).`
11529
+ );
11530
+ if (createdPackageJson) {
11531
+ printDiagnosticsToConsole(
11532
+ "Next: run npm install (or pnpm install / yarn) in this directory, then npm run dev."
11533
+ );
11534
+ }
11535
+ if (argv.interactive) {
11536
+ printInteractiveWrapUp(interactiveHasPaidPlans);
11537
+ }
11538
+ } catch (error) {
11539
+ await printCriticalFailureToConsoleAndExit(
11540
+ error instanceof Error ? error.message : String(error)
11541
+ );
11610
11542
  }
11611
- next.navigation = navigation;
11612
- return next;
11613
- }
11614
- function openapiInputForConfig(specRelativePosix) {
11615
- if (specRelativePosix.startsWith("./")) {
11616
- return specRelativePosix;
11617
- }
11618
- return `./${specRelativePosix}`;
11619
- }
11620
- function toRoutePath(relativeFile, pagesDir) {
11621
- const relativeNoExt = relativeFile.replace(/\.(mdx?|tsx|jsx)$/i, "");
11622
- const escaped = pagesDir.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
11623
- const withoutPagesPrefix = relativeNoExt.replace(
11624
- new RegExp(`^${escaped}/?`),
11625
- ""
11626
- );
11627
- const normalized = withoutPagesPrefix.replace(/\/index$/, "");
11628
- return `/${normalized || "index"}`;
11629
- }
11630
- function pageComponentName(slug) {
11631
- const pascal = slug.replace(/^\/+/, "").split(/[^a-zA-Z0-9]+/).filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
11632
- const name = /^[A-Za-z]/.test(pascal) ? pascal : `Page${pascal}`;
11633
- return name.endsWith("Page") ? name : `${name}Page`;
11634
11543
  }
11635
- var THIN_WRAPPER_SOURCE = `import { devPortalBillingPlugin } from "@lukoweb/apitogo-plugin-dev-portal-billing";
11636
- import { buildApitogoConfig } from "@lukoweb/apitogo/build-config";
11637
- import siteConfig from "./apitogo.config.json";
11638
11544
 
11639
- export default buildApitogoConfig({
11640
- ...siteConfig,
11641
- plugins: [devPortalBillingPlugin()],
11642
- });
11643
- `;
11644
- function isThinConfigWrapper(source) {
11645
- return /export\s+default\s+buildApitogoConfig\s*\(/.test(source);
11646
- }
11647
- function buildThinWrapperWithCustomPages(tsxRoutePaths) {
11648
- if (tsxRoutePaths.length === 0) {
11649
- return THIN_WRAPPER_SOURCE;
11545
+ // src/cli/cmds/config.ts
11546
+ var config_default = {
11547
+ desc: "Create or update apitogo.config.json from pages, OpenAPI, and optional site/theme/auth flags",
11548
+ command: "config",
11549
+ aliases: ["make-config"],
11550
+ builder: (yargs2) => yargs2.option("dir", {
11551
+ type: "string",
11552
+ describe: "The directory containing your project",
11553
+ default: ".",
11554
+ normalize: true,
11555
+ hidden: true
11556
+ }).option("pages-dir", {
11557
+ type: "string",
11558
+ describe: "Directory to scan for md/mdx/tsx pages",
11559
+ default: "pages"
11560
+ }).option("interactive", {
11561
+ alias: "i",
11562
+ type: "boolean",
11563
+ describe: "Interactive wizard: site title, OpenAPI path, custom pages, and gateway plans",
11564
+ default: false
11565
+ }).option("site", {
11566
+ type: "string",
11567
+ describe: "JSON object merged into site"
11568
+ }).option("theme", {
11569
+ type: "string",
11570
+ describe: "JSON object merged into theme"
11571
+ }).option("authentication", {
11572
+ type: "string",
11573
+ describe: "JSON object merged into authentication"
11574
+ }).option("title", {
11575
+ type: "string",
11576
+ describe: "Set site.title"
11577
+ }).option("force", {
11578
+ type: "boolean",
11579
+ describe: "Replace Documentation nav items from disk (keeps API Reference)",
11580
+ default: false
11581
+ }),
11582
+ handler: async (argv) => {
11583
+ await captureEvent({ argv, event: "apitogo config" });
11584
+ await makeConfig(argv);
11650
11585
  }
11651
- const imports = tsxRoutePaths.map((routePath) => {
11652
- const slug = routePath.replace(/^\//, "");
11653
- const name = pageComponentName(slug);
11654
- return `import ${name} from "./pages/${slug}";`;
11655
- }).join("\n");
11656
- const customPages = tsxRoutePaths.map((routePath) => {
11657
- const slug = routePath.replace(/^\//, "");
11658
- const name = pageComponentName(slug);
11659
- return ` {
11660
- type: "custom-page",
11661
- path: ${JSON.stringify(routePath)},
11662
- element: <${name} />,
11663
- },`;
11664
- }).join("\n");
11665
- return `${imports}
11666
- import { devPortalBillingPlugin } from "@lukoweb/apitogo-plugin-dev-portal-billing";
11667
- import { buildApitogoConfig } from "@lukoweb/apitogo/build-config";
11668
- import siteConfig from "./apitogo.config.json";
11586
+ };
11669
11587
 
11670
- const config = buildApitogoConfig({
11671
- ...siteConfig,
11672
- plugins: [devPortalBillingPlugin()],
11673
- });
11588
+ // src/cli/cmds/configure-github-workflow.ts
11589
+ var REMOVED = "apitogo configure-github-workflow was removed. Multi-env SWA deploy is deprecated; use `apitogo apply`.";
11590
+ var configure_github_workflow_default = {
11591
+ desc: "Removed \u2014 multi-env SWA deploy is deprecated",
11592
+ command: "configure-github-workflow",
11593
+ builder: (yargs2) => yargs2,
11594
+ handler: async (argv) => {
11595
+ await captureEvent({
11596
+ argv,
11597
+ event: "apitogo configure-github-workflow removed"
11598
+ });
11599
+ await printCriticalFailureToConsoleAndExit(REMOVED);
11600
+ }
11601
+ };
11674
11602
 
11675
- export default {
11676
- ...config,
11677
- navigation: [
11678
- {
11679
- type: "category",
11680
- label: ${JSON.stringify(DOCUMENTATION_NAV_LABEL)},
11681
- items: [
11682
- ${customPages}
11683
- ...(((config.navigation ?? []).find(
11684
- (item) =>
11685
- typeof item === "object" &&
11686
- item !== null &&
11687
- "label" in item &&
11688
- item.label === ${JSON.stringify(DOCUMENTATION_NAV_LABEL)},
11689
- ) as { items?: unknown[] } | undefined)?.items ?? []),
11690
- ],
11691
- },
11692
- ...(config.navigation ?? []).filter(
11693
- (item) =>
11694
- !(
11695
- typeof item === "object" &&
11696
- item !== null &&
11697
- "label" in item &&
11698
- item.label === ${JSON.stringify(DOCUMENTATION_NAV_LABEL)}
11699
- ),
11700
- ),
11701
- ],
11603
+ // src/cli/cmds/deploy.ts
11604
+ var REMOVED2 = "apitogo deploy was removed. Use `apitogo apply` (optionally `--only portal-frontend`).";
11605
+ var deploy_default = {
11606
+ desc: "Removed \u2014 use apitogo apply",
11607
+ command: "deploy",
11608
+ builder: (yargs2) => yargs2,
11609
+ handler: async (argv) => {
11610
+ await captureEvent({ argv, event: "apitogo deploy removed" });
11611
+ await printCriticalFailureToConsoleAndExit(REMOVED2);
11612
+ }
11702
11613
  };
11703
- `;
11704
- }
11705
11614
 
11706
- // src/cli/make-config/scaffold-project.ts
11707
- init_package_json();
11708
- import { constants as constants2 } from "node:fs";
11709
- import { access as access4, mkdir as mkdir7, readFile as readFile5, writeFile as writeFile7 } from "node:fs/promises";
11615
+ // src/cli/dev/handler.ts
11616
+ init_joinUrl();
11617
+ import path39 from "node:path";
11618
+
11619
+ // src/vite/dev-server.ts
11620
+ init_logger();
11621
+ import fs4 from "node:fs/promises";
11622
+ import http from "node:http";
11623
+ import https from "node:https";
11710
11624
  import path38 from "node:path";
11711
- import { glob as glob4 } from "glob";
11712
- var OPENAPI_GLOBS = [
11713
- "apis/openapi.json",
11714
- "apis/openapi.yaml",
11715
- "apis/openapi.yml",
11716
- "openapi.json",
11717
- "openapi.yaml",
11718
- "openapi.yml"
11719
- ];
11720
- var toPosix = (p) => p.split(path38.sep).join("/");
11721
- var sanitizePackageName = (dir) => {
11722
- const base = path38.basename(path38.resolve(dir));
11723
- const s = base.toLowerCase().replace(/[^a-z0-9-_.]/g, "-").replace(/^-+|-+$/g, "");
11724
- return s || "apitogo-site";
11725
- };
11726
- var apitogoVersionRange = () => {
11727
- const v = getZudokuPackageJson()?.version;
11728
- return v ? `^${v}` : "latest";
11729
- };
11730
- async function findOpenApiSpecPath(dir) {
11731
- for (const rel of OPENAPI_GLOBS) {
11732
- const full = path38.join(dir, rel);
11733
- try {
11734
- await access4(full, constants2.R_OK);
11735
- return toPosix(rel);
11736
- } catch {
11737
- }
11625
+ import { createHttpTerminator } from "http-terminator";
11626
+ import {
11627
+ createServer as createViteServer,
11628
+ isRunnableDevEnvironment as isRunnableDevEnvironment2,
11629
+ mergeConfig as mergeConfig4
11630
+ } from "vite";
11631
+
11632
+ // src/cli/common/utils/ports.ts
11633
+ import net from "node:net";
11634
+ var isPortAvailable = (port) => new Promise((resolve) => {
11635
+ const server = net.createServer();
11636
+ server.unref();
11637
+ server.once("error", () => resolve(false));
11638
+ server.listen(port, () => {
11639
+ server.close();
11640
+ resolve(true);
11641
+ });
11642
+ });
11643
+ async function findAvailablePort(startPort) {
11644
+ let port = startPort;
11645
+ for (; port < startPort + 1e3; port++) {
11646
+ if (await isPortAvailable(port)) break;
11647
+ }
11648
+ return port;
11649
+ }
11650
+
11651
+ // src/vite/dev-server.ts
11652
+ init_loader();
11653
+
11654
+ // src/vite/pagefind-dev-index.ts
11655
+ init_invariant();
11656
+ init_joinUrl();
11657
+ import path37 from "node:path";
11658
+ import { createIndex as createIndex2 } from "pagefind";
11659
+ import { isRunnableDevEnvironment } from "vite";
11660
+ async function* buildPagefindDevIndex(vite, config2) {
11661
+ const { index, errors } = await createIndex2();
11662
+ invariant(index, `Failed to create pagefind index: ${errors.join(", ")}`);
11663
+ const pagefindIndex = index;
11664
+ const ssrEnvironment = vite.environments.ssr;
11665
+ if (!isRunnableDevEnvironment(ssrEnvironment)) {
11666
+ throw new Error("SSR environment is not runnable");
11738
11667
  }
11739
- const extra = await glob4("apis/**/*.{json,yaml,yml}", {
11740
- cwd: dir,
11741
- posix: true,
11742
- ignore: ["**/node_modules/**"]
11668
+ const serverModule = await ssrEnvironment.runner.import(
11669
+ getAppServerEntryPath()
11670
+ );
11671
+ const routes = serverModule.getRoutesByConfig(config2);
11672
+ const paths = routesToPaths(routes);
11673
+ const { basePath } = config2;
11674
+ const template = getDevHtml({
11675
+ jsEntry: "/__z/entry.client.tsx",
11676
+ dir: config2.site?.dir
11743
11677
  });
11744
- return extra[0] ? toPosix(extra[0]) : null;
11745
- }
11746
- async function ensurePackageJson(dir) {
11747
- const pkgPath = path38.join(dir, "package.json");
11748
- const apitogoVer = apitogoVersionRange();
11749
- const baseDeps = {
11750
- react: ">=19.0.0",
11751
- "react-dom": ">=19.0.0",
11752
- "@lukoweb/apitogo": apitogoVer,
11753
- "@lukoweb/apitogo-plugin-dev-portal-billing": apitogoVer,
11754
- "@lukoweb/apitogo-module-landing": apitogoVer,
11755
- "@lukoweb/apitogo-module-user-panel": apitogoVer,
11756
- "lucide-react": "0.577.0"
11757
- };
11758
- const baseScripts = {
11759
- dev: "apitogo dev",
11760
- build: "apitogo build",
11761
- preview: "apitogo preview"
11762
- };
11763
- try {
11764
- await access4(pkgPath, constants2.R_OK);
11765
- } catch (err) {
11766
- const code = err.code;
11767
- if (code !== "ENOENT") throw err;
11768
- const pkg2 = {
11769
- name: sanitizePackageName(dir),
11770
- version: "0.1.0",
11771
- private: true,
11772
- type: "module",
11773
- scripts: baseScripts,
11774
- dependencies: baseDeps,
11775
- devDependencies: {
11776
- "@types/node": "^22",
11777
- "@types/react": "^19",
11778
- "@types/react-dom": "^19",
11779
- typescript: "^5"
11678
+ const transformedTemplate = await vite.transformIndexHtml("/", template);
11679
+ let indexed = 0;
11680
+ for (const urlPath of paths) {
11681
+ try {
11682
+ const pathname = joinUrl(basePath, urlPath);
11683
+ const url = joinUrl("http://localhost", pathname);
11684
+ const request = new Request(url);
11685
+ const response = await serverModule.handleRequest({
11686
+ template: transformedTemplate,
11687
+ request,
11688
+ routes,
11689
+ basePath,
11690
+ bypassProtection: true
11691
+ });
11692
+ if (response.status < 400 && response.body) {
11693
+ const content = await response.text();
11694
+ await pagefindIndex.addHTMLFile({
11695
+ url: urlPath,
11696
+ content
11697
+ });
11698
+ indexed++;
11780
11699
  }
11700
+ } catch (error) {
11701
+ console.error(`Failed to index ${urlPath}:`, error);
11702
+ }
11703
+ yield {
11704
+ type: "progress",
11705
+ total: paths.length,
11706
+ current: indexed,
11707
+ path: urlPath
11781
11708
  };
11782
- await writeFile7(pkgPath, `${JSON.stringify(pkg2, null, 2)}
11783
- `, "utf8");
11784
- return true;
11785
- }
11786
- const raw = await readFile5(pkgPath, "utf8");
11787
- let pkg;
11788
- try {
11789
- pkg = JSON.parse(raw);
11790
- } catch {
11791
- throw new Error(
11792
- "package.json exists but is not valid JSON; fix or remove it and run again."
11793
- );
11794
11709
  }
11795
- pkg.scripts = {
11796
- ...typeof pkg.scripts === "object" && pkg.scripts !== null ? pkg.scripts : {}
11797
- };
11798
- const scripts = pkg.scripts;
11799
- for (const [k, v] of Object.entries(baseScripts)) {
11800
- if (!scripts[k]) scripts[k] = v;
11710
+ const outputPath = path37.join(vite.config.publicDir, "pagefind");
11711
+ await pagefindIndex.writeFiles({ outputPath });
11712
+ yield { type: "complete", success: true, indexed };
11713
+ }
11714
+
11715
+ // src/vite/dev-server.ts
11716
+ var DEFAULT_DEV_PORT = 3e3;
11717
+ var DevServer = class {
11718
+ constructor(options) {
11719
+ this.options = options;
11801
11720
  }
11802
- pkg.dependencies = {
11803
- ...typeof pkg.dependencies === "object" && pkg.dependencies !== null ? pkg.dependencies : {}
11804
- };
11805
- const deps = pkg.dependencies;
11806
- for (const [k, v] of Object.entries(baseDeps)) {
11807
- if (!deps[k]) deps[k] = v;
11721
+ terminator;
11722
+ resolvedPort = 0;
11723
+ protocol = "http";
11724
+ async createNodeServer(config2) {
11725
+ if (!config2.https) return http.createServer();
11726
+ this.protocol = "https";
11727
+ const { dir } = this.options;
11728
+ const [key, cert, ca] = await Promise.all([
11729
+ fs4.readFile(path38.resolve(dir, config2.https.key)),
11730
+ fs4.readFile(path38.resolve(dir, config2.https.cert)),
11731
+ config2.https.ca ? fs4.readFile(path38.resolve(dir, config2.https.ca)) : void 0
11732
+ ]);
11733
+ return https.createServer({ key, cert, ca });
11808
11734
  }
11809
- await writeFile7(pkgPath, `${JSON.stringify(pkg, null, 2)}
11810
- `, "utf8");
11811
- return false;
11812
- }
11813
- async function ensureTsconfigJson(dir) {
11814
- const tsPath = path38.join(dir, "tsconfig.json");
11815
- try {
11816
- await access4(tsPath, constants2.R_OK);
11817
- return false;
11818
- } catch {
11819
- const tsconfig = {
11820
- compilerOptions: {
11821
- target: "ES2022",
11822
- lib: ["ESNext", "DOM", "DOM.Iterable", "WebWorker"],
11823
- module: "ESNext",
11824
- moduleResolution: "Bundler",
11825
- useDefineForClassFields: true,
11826
- skipLibCheck: true,
11827
- skipDefaultLibCheck: true,
11828
- resolveJsonModule: true,
11829
- isolatedModules: true,
11830
- useUnknownInCatchVariables: false,
11831
- jsx: "react-jsx"
11735
+ async start() {
11736
+ const configEnv = {
11737
+ mode: "development",
11738
+ command: "serve",
11739
+ isSsrBuild: this.options.ssr
11740
+ };
11741
+ const viteConfig = await getViteConfig(this.options.dir, configEnv);
11742
+ const { config: config2 } = await loadZudokuConfig(configEnv, this.options.dir);
11743
+ this.resolvedPort = await findAvailablePort(
11744
+ this.options.argPort ?? config2.port ?? DEFAULT_DEV_PORT
11745
+ );
11746
+ const server = await this.createNodeServer(config2);
11747
+ if (config2.search?.type === "pagefind" && viteConfig.publicDir !== false) {
11748
+ const publicRoot = path38.resolve(
11749
+ this.options.dir,
11750
+ typeof viteConfig.publicDir === "string" ? viteConfig.publicDir : "public"
11751
+ );
11752
+ await ensurePagefindDevStub(publicRoot);
11753
+ }
11754
+ const mergedViteConfig = mergeConfig4(viteConfig, {
11755
+ server: {
11756
+ hmr: { server }
11832
11757
  },
11833
- include: [
11834
- "*.config.ts",
11835
- "*.config.tsx",
11836
- "*.config.jsx",
11837
- "*.config.js",
11838
- "*.config.mjs",
11839
- "pages",
11840
- "landing",
11841
- "user-panel"
11758
+ plugins: [
11759
+ {
11760
+ // Serves the client entry via Vite's client transform pipeline.
11761
+ // Must be registered via configureServer so it runs before Vite's
11762
+ // built-in transform middleware which would treat the path as a static asset.
11763
+ name: "apitogo:entry-client",
11764
+ configureServer(server2) {
11765
+ const entryPath = path38.posix.join(
11766
+ server2.config.base,
11767
+ "/__z/entry.client.tsx"
11768
+ );
11769
+ server2.middlewares.use(entryPath, async (_req, res) => {
11770
+ const transformed = await server2.environments.client.transformRequest(
11771
+ getAppClientEntryPath()
11772
+ );
11773
+ if (!transformed) {
11774
+ res.writeHead(500);
11775
+ res.end("Error transforming client entry");
11776
+ return;
11777
+ }
11778
+ res.writeHead(200, { "Content-Type": "text/javascript" });
11779
+ res.end(transformed.code);
11780
+ });
11781
+ }
11782
+ }
11842
11783
  ]
11843
- };
11844
- await writeFile7(tsPath, `${JSON.stringify(tsconfig, null, 2)}
11845
- `, "utf8");
11846
- return true;
11784
+ });
11785
+ const vite = await createViteServer(mergedViteConfig);
11786
+ const graphql = createGraphQLServer({ graphqlEndpoint: "/__z/graphql" });
11787
+ vite.middlewares.use((req, res, next) => {
11788
+ if (req.method === "GET" && req.originalUrl === "/" && config2.basePath && config2.basePath !== "/") {
11789
+ res.writeHead(307, { Location: config2.basePath });
11790
+ res.end();
11791
+ return;
11792
+ }
11793
+ next();
11794
+ });
11795
+ vite.middlewares.use(graphql.graphqlEndpoint, graphql);
11796
+ vite.middlewares.use("/__z/pagefind-reindex", async (_req, res) => {
11797
+ res.writeHead(200, {
11798
+ "Content-Type": "text/event-stream",
11799
+ "Cache-Control": "no-cache",
11800
+ Connection: "keep-alive"
11801
+ });
11802
+ const { config: currentConfig } = await loadZudokuConfig(
11803
+ configEnv,
11804
+ this.options.dir
11805
+ );
11806
+ const sendEvent = (data) => res.write(`data: ${JSON.stringify(data)}
11807
+
11808
+ `);
11809
+ if (currentConfig.search?.type !== "pagefind") {
11810
+ sendEvent({
11811
+ type: "complete",
11812
+ success: false,
11813
+ indexed: 0,
11814
+ error: "Pagefind search is not enabled"
11815
+ });
11816
+ res.end();
11817
+ return;
11818
+ }
11819
+ try {
11820
+ for await (const event of buildPagefindDevIndex(vite, currentConfig)) {
11821
+ sendEvent(event);
11822
+ }
11823
+ } catch (error) {
11824
+ const message = error instanceof Error ? error.message : "Unknown error";
11825
+ sendEvent({
11826
+ type: "complete",
11827
+ success: false,
11828
+ indexed: 0,
11829
+ error: message
11830
+ });
11831
+ }
11832
+ res.end();
11833
+ });
11834
+ printDiagnosticsToConsole(
11835
+ `Server-side rendering ${this.options.ssr ? "enabled" : "disabled"}`
11836
+ );
11837
+ if (config2.search?.type === "pagefind" && vite.config.publicDir) {
11838
+ await ensurePagefindDevStub(vite.config.publicDir);
11839
+ }
11840
+ vite.middlewares.use(async (req, res) => {
11841
+ const url = req.originalUrl ?? req.url ?? "/";
11842
+ const ssrEnvironment = vite.environments.ssr;
11843
+ if (!isRunnableDevEnvironment2(ssrEnvironment)) {
11844
+ res.writeHead(500);
11845
+ res.end("SSR environment not available");
11846
+ return;
11847
+ }
11848
+ try {
11849
+ const { config: currentConfig } = await loadZudokuConfig(
11850
+ configEnv,
11851
+ this.options.dir
11852
+ );
11853
+ const rawHtml = getDevHtml({
11854
+ jsEntry: "/__z/entry.client.tsx",
11855
+ dir: currentConfig.site?.dir
11856
+ });
11857
+ const template = await vite.transformIndexHtml(url, rawHtml);
11858
+ if (this.options.ssr) {
11859
+ const entryServer = await ssrEnvironment.runner.import(
11860
+ getAppServerEntryPath()
11861
+ );
11862
+ const hasBody = req.method !== "GET" && req.method !== "HEAD";
11863
+ const request = new Request(
11864
+ `${this.protocol}://${req.headers.host}${url}`,
11865
+ {
11866
+ method: req.method,
11867
+ headers: req.headers,
11868
+ body: hasBody ? req : void 0,
11869
+ // Required by Node when body is a readable stream
11870
+ // @ts-expect-error Missing type definition
11871
+ duplex: hasBody ? "half" : void 0
11872
+ }
11873
+ );
11874
+ const response = await entryServer.handleRequest({
11875
+ template,
11876
+ request,
11877
+ routes: entryServer.getRoutesByConfig(currentConfig),
11878
+ basePath: currentConfig.basePath
11879
+ });
11880
+ for (const [key, value] of response.headers) {
11881
+ res.appendHeader(key, value);
11882
+ }
11883
+ res.writeHead(response.status);
11884
+ for await (const chunk of response.body ?? []) {
11885
+ res.write(chunk);
11886
+ }
11887
+ res.end();
11888
+ } else {
11889
+ res.writeHead(200, { "Content-Type": "text/html" });
11890
+ res.end(template);
11891
+ }
11892
+ } catch (e) {
11893
+ logger.error(e);
11894
+ const html = `<!DOCTYPE html><html><body><script type="module">
11895
+ import { ErrorOverlay } from '/@vite/client';
11896
+ document.body.appendChild(new ErrorOverlay(${JSON.stringify({
11897
+ message: e instanceof Error ? e.message : String(e),
11898
+ stack: e instanceof Error ? e.stack : ""
11899
+ })}));
11900
+ </script></body></html>`;
11901
+ res.writeHead(500, { "Content-Type": "text/html" });
11902
+ res.end(html);
11903
+ }
11904
+ });
11905
+ server.on("request", vite.middlewares);
11906
+ await new Promise((resolve) => {
11907
+ server.listen(this.resolvedPort, resolve);
11908
+ });
11909
+ this.terminator = createHttpTerminator({ server });
11910
+ if (this.options.open || process.env.ZUDOKU_OPEN_BROWSER) {
11911
+ const url = `${this.protocol}://localhost:${this.resolvedPort}`;
11912
+ vite.resolvedUrls = {
11913
+ local: [`${url}${vite.config.base || "/"}`],
11914
+ network: []
11915
+ };
11916
+ vite.openBrowser();
11917
+ }
11918
+ return { vite };
11847
11919
  }
11848
- }
11849
- var LANDING_PAGE_SOURCE = `import {
11850
- FeaturesSection,
11851
- HeroSection,
11852
- LandingShell,
11853
- } from "@lukoweb/apitogo-module-landing";
11854
-
11855
- export default function LandingPage() {
11856
- return (
11857
- <LandingShell
11858
- title="Your API, ready for developers"
11859
- description="Documentation, API reference, and a developer portal \u2014 all in one place."
11860
- >
11861
- <HeroSection
11862
- hero={{
11863
- title: "Your API, ready for developers",
11864
- description:
11865
- "Ship docs, interactive API reference, and a developer portal without building from scratch.",
11866
- }}
11867
- primaryAction={{ label: "Get started", href: "/introduction" }}
11868
- secondaryAction={{ label: "API Reference", href: "/api" }}
11869
- />
11870
- <FeaturesSection
11871
- features={[
11872
- {
11873
- title: "Documentation",
11874
- description: "Guides and references for every integration path.",
11875
- icon: "book-open",
11876
- },
11877
- {
11878
- title: "API Reference",
11879
- description: "Interactive OpenAPI explorer with code samples.",
11880
- icon: "code",
11881
- },
11882
- {
11883
- title: "Developer Portal",
11884
- description: "Auth, API keys, and subscription billing built in.",
11885
- icon: "shield-check",
11886
- },
11887
- ]}
11888
- />
11889
- </LandingShell>
11890
- );
11891
- }
11892
- `;
11893
- var ACCOUNT_PLANS_PAGE_SOURCE = `export { AccountPlansPage as default } from "@lukoweb/apitogo-plugin-dev-portal-billing";
11894
- `;
11895
- var fileExists2 = async (filePath) => {
11896
- try {
11897
- await access4(filePath, constants2.F_OK);
11898
- return true;
11899
- } catch {
11900
- return false;
11920
+ async stop() {
11921
+ await this.terminator?.terminate();
11901
11922
  }
11902
11923
  };
11903
- async function ensurePortalStubFiles(dir) {
11904
- const landingPath = path38.join(dir, "landing", "LandingPage.tsx");
11905
- if (!await fileExists2(landingPath)) {
11906
- await mkdir7(path38.join(dir, "landing"), { recursive: true });
11907
- await writeFile7(landingPath, LANDING_PAGE_SOURCE, "utf8");
11908
- }
11909
- const plansPath = path38.join(dir, "user-panel", "AccountPlansPage.tsx");
11910
- if (!await fileExists2(plansPath)) {
11911
- await mkdir7(path38.join(dir, "user-panel"), { recursive: true });
11912
- await writeFile7(plansPath, ACCOUNT_PLANS_PAGE_SOURCE, "utf8");
11913
- }
11914
- }
11915
- async function ensureThinConfigTsx(dir) {
11916
- const tsxPath = path38.join(dir, "apitogo.config.tsx");
11917
- if (await fileExists2(tsxPath)) {
11918
- return false;
11919
- }
11920
- await writeFile7(tsxPath, THIN_WRAPPER_SOURCE, "utf8");
11921
- return true;
11922
- }
11923
11924
 
11924
- // src/cli/make-config/handler.ts
11925
- var toPosixPath2 = (value) => value.split(path39.sep).join(path39.posix.sep);
11926
- var fileExists3 = async (filePath) => {
11927
- try {
11928
- await access5(filePath, constants3.F_OK);
11929
- return true;
11930
- } catch {
11931
- return false;
11932
- }
11933
- };
11934
- var ensureApitogoDeployWorkflow = async (dir) => {
11935
- const workflowPath = path39.join(
11925
+ // src/cli/dev/handler.ts
11926
+ init_package_json();
11927
+ async function dev(argv) {
11928
+ const packageJson2 = getZudokuPackageJson();
11929
+ process.env.NODE_ENV = "development";
11930
+ const dir = path39.resolve(process.cwd(), argv.dir);
11931
+ const server = new DevServer({
11936
11932
  dir,
11937
- ".github",
11938
- "workflows",
11939
- "apitogo-deploy.yml"
11933
+ argPort: argv.port,
11934
+ ssr: argv.ssr,
11935
+ open: argv.open
11936
+ });
11937
+ const { vite } = await server.start();
11938
+ const url = joinUrl(
11939
+ `${server.protocol}://localhost:${server.resolvedPort}`,
11940
+ vite.config.base
11940
11941
  );
11941
- if (await fileExists3(workflowPath)) {
11942
- return false;
11943
- }
11944
- await mkdir8(path39.join(dir, ".github", "workflows"), { recursive: true });
11945
- await writeFile8(workflowPath, APITOGO_DEPLOY_WORKFLOW_YAML, "utf8");
11946
- return true;
11947
- };
11948
- var readJsonConfig = async (filePath) => {
11949
- const raw = await readFile6(filePath, "utf8");
11950
- const parsed = JSON.parse(raw);
11951
- if (!isPlainObject2(parsed)) {
11952
- throw new Error(`${APITOGO_JSON_FILENAME} must be a JSON object.`);
11953
- }
11954
- return parsed;
11955
- };
11956
- async function makeConfig(argv) {
11957
- const dir = path39.resolve(process.cwd(), argv.dir);
11958
- const pagesDir = toPosixPath2(
11959
- argv.pagesDir.replace(/^[./]+/, "").replace(/\/+$/, "")
11942
+ printDiagnosticsToConsole("Ctrl+C to exit");
11943
+ printDiagnosticsToConsole("");
11944
+ printDiagnosticsToConsole(
11945
+ `\u{1F680} Running APIToGo v${packageJson2.version}: ${url}`
11960
11946
  );
11961
- try {
11962
- const discoveredOpenApi = await findOpenApiSpecPath(dir);
11963
- const createdPackageJson = await ensurePackageJson(dir);
11964
- const createdTsconfig = await ensureTsconfigJson(dir);
11965
- if (createdPackageJson) {
11966
- printDiagnosticsToConsole(
11967
- "Created package.json with dev, build, and preview scripts."
11968
- );
11969
- }
11970
- if (createdTsconfig) {
11971
- printDiagnosticsToConsole("Created tsconfig.json.");
11947
+ printDiagnosticsToConsole("");
11948
+ let hasExited = false;
11949
+ return new Promise((resolve) => {
11950
+ function exit() {
11951
+ if (!hasExited) {
11952
+ hasExited = true;
11953
+ server.stop().then(() => {
11954
+ resolve();
11955
+ }).catch((e) => {
11956
+ console.error("Error stopping server", e);
11957
+ resolve();
11958
+ });
11959
+ }
11972
11960
  }
11961
+ process.on("SIGTERM", exit);
11962
+ process.on("SIGINT", exit);
11963
+ process.on("uncaughtException", (e) => {
11964
+ console.error("Uncaught exception", e);
11965
+ void exit();
11966
+ });
11967
+ process.on("unhandledRejection", (e) => {
11968
+ if (e instanceof DOMException && e.name === "AbortError") {
11969
+ console.log(`[Abort] ${e.message}`);
11970
+ return;
11971
+ }
11972
+ console.error("Unhandled rejection", e);
11973
+ void exit();
11974
+ });
11975
+ process.on("exit", exit);
11976
+ });
11977
+ }
11978
+
11979
+ // src/cli/cmds/dev.ts
11980
+ var dev_default = {
11981
+ desc: "Runs locally",
11982
+ command: "dev",
11983
+ builder: (yargs2) => {
11984
+ return yargs2.option("dir", {
11985
+ type: "string",
11986
+ describe: "The directory containing your project",
11987
+ default: ".",
11988
+ normalize: true,
11989
+ hidden: true
11990
+ }).option("port", {
11991
+ type: "number",
11992
+ describe: "The port to run the local server on"
11993
+ }).option("ssr", {
11994
+ type: "boolean",
11995
+ describe: "Enable server-side rendering",
11996
+ default: true
11997
+ }).option("open", {
11998
+ type: "boolean",
11999
+ describe: "Automatically open the browser",
12000
+ default: false
12001
+ });
12002
+ },
12003
+ handler: async (argv) => {
12004
+ await captureEvent({
12005
+ argv,
12006
+ event: "apitogo dev"
12007
+ });
12008
+ await dev(argv);
12009
+ }
12010
+ };
12011
+
12012
+ // src/cli/init/handler.ts
12013
+ import path40 from "node:path";
12014
+ async function init2(argv) {
12015
+ const dir = path40.resolve(process.cwd(), argv.dir ?? ".");
12016
+ try {
12017
+ const createdPackageJson = await ensurePackageJson(dir);
12018
+ const createdTsconfig = await ensureTsconfigJson(dir);
11973
12019
  await ensurePortalStubFiles(dir);
11974
- const jsonPath = path39.join(dir, APITOGO_JSON_FILENAME);
11975
- let createdJson = false;
11976
- let config2;
11977
- if (await fileExists3(jsonPath)) {
11978
- config2 = await readJsonConfig(jsonPath);
11979
- } else {
11980
- config2 = createDefaultApitogoJsonConfig({
11981
- title: titleFromDirectoryName(path39.basename(dir)),
11982
- pagesDir
11983
- });
11984
- createdJson = true;
11985
- printDiagnosticsToConsole(`Created ${APITOGO_JSON_FILENAME}`);
11986
- }
11987
- const createdTsx = await ensureThinConfigTsx(dir);
11988
- if (createdTsx) {
11989
- printDiagnosticsToConsole("Created apitogo.config.tsx");
12020
+ await ensureThinConfigTsx(dir);
12021
+ if (createdPackageJson) {
12022
+ printDiagnosticsToConsole("Created package.json");
11990
12023
  }
11991
- const flagPatches = {
11992
- site: parseJsonObjectFlag(argv.site, "site"),
11993
- theme: parseJsonObjectFlag(argv.theme, "theme"),
11994
- authentication: parseJsonObjectFlag(argv.authentication, "authentication"),
11995
- title: argv.title
11996
- };
11997
- let openApiOverride;
11998
- if (argv.interactive) {
11999
- const site = isPlainObject2(config2.site) ? config2.site : {};
12000
- const logo = isPlainObject2(site.logo) ? site.logo : {};
12001
- const theme = isPlainObject2(config2.theme) ? config2.theme : {};
12002
- const fonts = isPlainObject2(theme.fonts) ? theme.fonts : {};
12003
- const light = isPlainObject2(theme.light) ? theme.light : {};
12004
- const dark = isPlainObject2(theme.dark) ? theme.dark : {};
12005
- const authentication = isPlainObject2(config2.authentication) ? config2.authentication : {};
12006
- const prompted = await promptMakeConfigInteractive({
12007
- title: flagPatches.title ?? (typeof site.title === "string" ? site.title : titleFromDirectoryName(path39.basename(dir))),
12008
- logoAlt: typeof logo.alt === "string" ? logo.alt : "APIToGo",
12009
- authEnabled: authentication.enabled === true,
12010
- apiBaseUrl: typeof authentication.apiBaseUrl === "string" ? authentication.apiBaseUrl : void 0,
12011
- sans: typeof fonts.sans === "string" ? fonts.sans : void 0,
12012
- mono: typeof fonts.mono === "string" ? fonts.mono : void 0,
12013
- lightPrimary: typeof light.primary === "string" ? light.primary : void 0,
12014
- darkPrimary: typeof dark.primary === "string" ? dark.primary : void 0,
12015
- discoveredOpenApi
12016
- });
12017
- openApiOverride = prompted.openApiPath;
12018
- config2 = applyConfigPatches(config2, prompted);
12024
+ if (createdTsconfig) {
12025
+ printDiagnosticsToConsole("Created tsconfig.json");
12019
12026
  }
12020
- config2 = applyConfigPatches(config2, flagPatches);
12021
- const pageFiles = await glob5(`${pagesDir}/**/*.{md,mdx,tsx,jsx}`, {
12022
- cwd: dir,
12023
- ignore: ["**/node_modules/**", "**/.git/**", "**/dist/**"],
12024
- posix: true
12027
+ await makeConfig({
12028
+ dir,
12029
+ pagesDir: "pages",
12030
+ title: argv.name,
12031
+ force: false
12025
12032
  });
12026
- const markdownFiles = pageFiles.filter((file) => /\.mdx?$/i.test(file));
12027
- const tsxFiles = pageFiles.filter((file) => /\.(tsx|jsx)$/i.test(file));
12028
- const markdownRoutes = markdownFiles.map(
12029
- (file) => toRoutePath(file, pagesDir)
12030
- );
12031
- const tsxRoutes = tsxFiles.map((file) => toRoutePath(file, pagesDir));
12032
- if (markdownRoutes.length === 0 && tsxRoutes.length === 0) {
12033
- printDiagnosticsToConsole(
12034
- `No docs pages under '${pagesDir}/' \u2014 skipping documentation nav scan.`
12035
- );
12036
- }
12037
- config2.navigation = mergeDocumentationNavigation(
12038
- config2.navigation,
12039
- markdownRoutes,
12040
- { force: argv.force === true }
12041
- );
12042
- const openApiRel = openApiOverride === void 0 ? discoveredOpenApi : openApiOverride;
12043
- if (openApiRel) {
12044
- config2 = applyOpenApiToJsonConfig(
12045
- config2,
12046
- openapiInputForConfig(openApiRel)
12047
- );
12048
- }
12049
- await writeFile8(jsonPath, `${JSON.stringify(config2, null, 2)}
12050
- `, "utf8");
12051
- const tsxPath = path39.join(dir, "apitogo.config.tsx");
12052
- if (await fileExists3(tsxPath) && tsxRoutes.length > 0) {
12053
- const tsxSource = await readFile6(tsxPath, "utf8");
12054
- if (isThinConfigWrapper(tsxSource)) {
12055
- await writeFile8(
12056
- tsxPath,
12057
- buildThinWrapperWithCustomPages(tsxRoutes),
12058
- "utf8"
12059
- );
12060
- }
12061
- }
12062
- const action = createdJson ? "Initialized" : "Updated";
12063
12033
  printResultToConsole(
12064
- `${action} ${APITOGO_JSON_FILENAME} (site/theme/auth/navigation/OpenAPI as applicable; redirects unchanged).`
12034
+ `Portal scaffolded in ${dir}. Next: apitogo config -i, apitogo dev, then apitogo apply.`
12065
12035
  );
12066
- const createdWorkflow = await ensureApitogoDeployWorkflow(dir);
12067
- if (createdWorkflow) {
12068
- printDiagnosticsToConsole(
12069
- "Created .github/workflows/apitogo-deploy.yml (preview + production; removes preview when a PR is closed)."
12070
- );
12071
- }
12072
- if (createdPackageJson) {
12073
- printDiagnosticsToConsole(
12074
- "Next: run npm install (or pnpm install / yarn) in this directory, then npm run dev."
12075
- );
12076
- }
12077
12036
  } catch (error) {
12078
12037
  await printCriticalFailureToConsoleAndExit(
12079
12038
  error instanceof Error ? error.message : String(error)
@@ -12081,155 +12040,246 @@ async function makeConfig(argv) {
12081
12040
  }
12082
12041
  }
12083
12042
 
12084
- // src/cli/cmds/make-config.ts
12085
- var make_config_default = {
12086
- desc: "Create or update apitogo.config.json from pages, OpenAPI, and optional site/theme/auth flags",
12087
- command: "make-config",
12088
- builder: (yargs2) => yargs2.option("dir", {
12043
+ // src/cli/cmds/init.ts
12044
+ var init_default = {
12045
+ desc: "Scaffold a local APItoGo developer portal (no platform calls)",
12046
+ command: "init [dir]",
12047
+ builder: (yargs2) => yargs2.positional("dir", {
12089
12048
  type: "string",
12090
- describe: "The directory containing your project",
12049
+ describe: "Directory to scaffold into",
12091
12050
  default: ".",
12092
- normalize: true,
12093
- hidden: true
12094
- }).option("pages-dir", {
12095
- type: "string",
12096
- describe: "Directory to scan for md/mdx/tsx pages",
12097
- default: "pages"
12098
- }).option("interactive", {
12099
- alias: "i",
12100
- type: "boolean",
12101
- describe: "Prompt for site, theme, authentication, and OpenAPI",
12102
- default: false
12103
- }).option("site", {
12104
- type: "string",
12105
- describe: "JSON object merged into site"
12106
- }).option("theme", {
12107
- type: "string",
12108
- describe: "JSON object merged into theme"
12109
- }).option("authentication", {
12110
- type: "string",
12111
- describe: "JSON object merged into authentication"
12112
- }).option("title", {
12051
+ normalize: true
12052
+ }).option("name", {
12113
12053
  type: "string",
12114
- describe: "Set site.title"
12115
- }).option("force", {
12116
- type: "boolean",
12117
- describe: "Replace Documentation nav items from disk (keeps API Reference)",
12118
- default: false
12054
+ describe: "Package / site name"
12119
12055
  }),
12120
12056
  handler: async (argv) => {
12121
- await captureEvent({ argv, event: "apitogo make-config" });
12122
- await makeConfig(argv);
12057
+ await captureEvent({ argv, event: "apitogo init" });
12058
+ await init2(argv);
12123
12059
  }
12124
12060
  };
12125
12061
 
12126
12062
  // src/cli/cmds/preview.ts
12127
- var previewCommand = {
12128
- desc: "Preview production build",
12063
+ var REMOVED3 = "apitogo preview was removed. Use `apitogo build --preview` or `apitogo dev`.";
12064
+ var preview_default = {
12065
+ desc: "Removed \u2014 use apitogo build --preview",
12129
12066
  command: "preview",
12067
+ builder: (yargs2) => yargs2,
12068
+ handler: async (argv) => {
12069
+ await captureEvent({ argv, event: "apitogo preview removed" });
12070
+ await printCriticalFailureToConsoleAndExit(REMOVED3);
12071
+ }
12072
+ };
12073
+
12074
+ // src/cli/cmds/remove.ts
12075
+ var REMOVED4 = "apitogo remove was removed. Use `apitogo teardown --yes` to delete the platform project.";
12076
+ var remove_default = {
12077
+ desc: "Removed \u2014 use apitogo teardown",
12078
+ command: "remove",
12079
+ builder: (yargs2) => yargs2,
12080
+ handler: async (argv) => {
12081
+ await captureEvent({ argv, event: "apitogo remove removed" });
12082
+ await printCriticalFailureToConsoleAndExit(REMOVED4);
12083
+ }
12084
+ };
12085
+
12086
+ // src/cli/status/handler.ts
12087
+ import path42 from "node:path";
12088
+
12089
+ // src/cli/common/mcp-cli.ts
12090
+ import { access as access4 } from "node:fs/promises";
12091
+ import { spawn as spawn2 } from "node:child_process";
12092
+ import path41 from "node:path";
12093
+ async function pathExists2(candidate) {
12094
+ try {
12095
+ await access4(candidate);
12096
+ return true;
12097
+ } catch {
12098
+ return false;
12099
+ }
12100
+ }
12101
+ async function resolveApitogoMcpCliCommand(binary) {
12102
+ const binName = `apitogo-${binary}`;
12103
+ const mcpPath = process.env.APITOGO_MCP_PATH?.trim();
12104
+ if (mcpPath) {
12105
+ const scriptJs = path41.join(mcpPath, "dist", "cli", `${binary}.js`);
12106
+ if (await pathExists2(scriptJs)) {
12107
+ return {
12108
+ command: process.execPath,
12109
+ argsPrefix: [scriptJs],
12110
+ note: `Using local ${binName} from APITOGO_MCP_PATH=${mcpPath}`
12111
+ };
12112
+ }
12113
+ throw new Error(
12114
+ `APITOGO_MCP_PATH is set (${mcpPath}) but dist/cli/${binary}.js was not found. Run npm run build in the MCP checkout.`
12115
+ );
12116
+ }
12117
+ return {
12118
+ command: "npx",
12119
+ argsPrefix: ["--yes", "--package=@lukoweb/apitogo-mcp", binName],
12120
+ note: "Using npx @lukoweb/apitogo-mcp (set APITOGO_MCP_PATH for a local checkout)"
12121
+ };
12122
+ }
12123
+ async function runApitogoMcpCli(binary, childArgs, options = {}) {
12124
+ let resolved;
12125
+ try {
12126
+ resolved = await resolveApitogoMcpCliCommand(binary);
12127
+ } catch (error) {
12128
+ await printCriticalFailureToConsoleAndExit(
12129
+ error instanceof Error ? error.message : String(error)
12130
+ );
12131
+ return;
12132
+ }
12133
+ if (resolved.note && !options.quiet) {
12134
+ printDiagnosticsToConsole(resolved.note);
12135
+ }
12136
+ const exitCode = await new Promise((resolvePromise) => {
12137
+ const child = spawn2(
12138
+ resolved.command,
12139
+ [...resolved.argsPrefix, ...childArgs],
12140
+ {
12141
+ stdio: "inherit",
12142
+ env: process.env,
12143
+ shell: process.platform === "win32"
12144
+ }
12145
+ );
12146
+ child.on("error", (error) => {
12147
+ void printCriticalFailureToConsoleAndExit(
12148
+ `Failed to start apitogo-${binary}: ${error.message}`
12149
+ );
12150
+ resolvePromise(1);
12151
+ });
12152
+ child.on("close", (code) => {
12153
+ resolvePromise(code ?? 1);
12154
+ });
12155
+ });
12156
+ if (exitCode !== 0) {
12157
+ process.exit(exitCode);
12158
+ }
12159
+ }
12160
+
12161
+ // src/cli/status/handler.ts
12162
+ async function status(argv) {
12163
+ const dir = path42.resolve(process.cwd(), argv.dir);
12164
+ const args = [dir];
12165
+ if (argv.projectId?.trim()) {
12166
+ args.push("--project-id", argv.projectId.trim());
12167
+ }
12168
+ if (argv.skipLive) {
12169
+ args.push("--skip-live");
12170
+ }
12171
+ if (argv.json) {
12172
+ args.push("--json");
12173
+ }
12174
+ await runApitogoMcpCli("status", args, { quiet: Boolean(argv.json) });
12175
+ }
12176
+
12177
+ // src/cli/cmds/status.ts
12178
+ var status_default = {
12179
+ desc: "Show account, project, and live readiness for the linked APItoGo project",
12180
+ command: "status",
12130
12181
  builder: (yargs2) => yargs2.option("dir", {
12131
12182
  type: "string",
12132
- describe: "The directory containing your project",
12183
+ describe: "Site directory containing apitogo.config.json",
12133
12184
  default: ".",
12134
- normalize: true,
12135
- hidden: true
12136
- }).option("port", {
12137
- type: "number",
12138
- describe: "The port to run the local server on"
12185
+ normalize: true
12186
+ }).option("project-id", {
12187
+ type: "string",
12188
+ describe: "Override project GUID"
12189
+ }).option("skip-live", {
12190
+ type: "boolean",
12191
+ describe: "Skip live health checks",
12192
+ default: false
12193
+ }).option("json", {
12194
+ type: "boolean",
12195
+ describe: "Print raw JSON only",
12196
+ default: false
12139
12197
  }),
12140
12198
  handler: async (argv) => {
12141
- process.env.NODE_ENV = "production";
12142
- await captureEvent({ argv, event: "apitogo preview" });
12143
- await preview(argv);
12199
+ await captureEvent({ argv, event: "apitogo status" });
12200
+ await status(argv);
12144
12201
  }
12145
12202
  };
12146
- var preview_default = previewCommand;
12147
12203
 
12148
- // src/cli/remove/handler.ts
12149
- var REMOVE_ENDPOINT = "https://deploy-server-dotnet2-b3fkcaaraydbckfe.canadacentral-01.azurewebsites.net/remove";
12150
- async function remove(argv) {
12151
- const deploymentToken = argv.deploymentToken?.trim() || process.env.npm_config_deployment_token?.trim();
12152
- const env = argv.env?.trim() || process.env.npm_config_env?.trim() || "production";
12153
- try {
12154
- if (!deploymentToken) {
12155
- throw new Error("A deployment token is required.");
12156
- }
12157
- if (!isJsonOnlyDeployEnv()) {
12158
- printDiagnosticsToConsole(`Removing deployment from ${env}...`);
12159
- }
12160
- const formData = new FormData();
12161
- formData.append("deploymentToken", deploymentToken);
12162
- formData.append("env", env);
12163
- const removeUrl = new URL(REMOVE_ENDPOINT);
12164
- if (argv.jsonOnly) {
12165
- removeUrl.searchParams.set("jsonOnly", "true");
12166
- }
12167
- const response = await fetch(removeUrl, {
12168
- method: "POST",
12169
- headers: {
12170
- "x-api-key": DEPLOY_SERVER_API_KEY
12171
- },
12172
- body: formData
12173
- });
12174
- const responseText = await response.text();
12175
- if (!response.ok) {
12176
- throw new Error(
12177
- `Remove failed with ${response.status}: ${JSON.stringify(textOrJson(responseText))}`
12178
- );
12179
- }
12180
- if (isJsonOnlyDeployEnv()) {
12181
- const parsed = responseText.trim() ? textOrJson(responseText) : {};
12182
- console.log(JSON.stringify(parsed));
12183
- return;
12184
- }
12185
- printResultToConsole(`Removal request sent to ${removeUrl.href}`);
12186
- if (responseText.trim()) {
12187
- printDiagnosticsToConsole(
12188
- JSON.stringify(textOrJson(responseText), null, 2)
12189
- );
12190
- }
12191
- } catch (error) {
12192
- const message = error instanceof Error ? error.message : String(error);
12193
- if (isJsonOnlyDeployEnv()) {
12194
- printJsonOnlyErrorAndExit(message);
12195
- } else {
12196
- await printCriticalFailureToConsoleAndExit(message);
12197
- }
12204
+ // src/cli/teardown/handler.ts
12205
+ import path43 from "node:path";
12206
+ async function teardown(argv) {
12207
+ if (!argv.yes) {
12208
+ await printCriticalFailureToConsoleAndExit(
12209
+ "Refusing to teardown without --yes."
12210
+ );
12211
+ return;
12212
+ }
12213
+ const dir = path43.resolve(process.cwd(), argv.dir);
12214
+ const args = [dir, "--yes"];
12215
+ if (argv.projectId?.trim()) {
12216
+ args.push("--project-id", argv.projectId.trim());
12198
12217
  }
12218
+ if (argv.wait === false) {
12219
+ args.push("--no-wait");
12220
+ }
12221
+ if (argv.json) {
12222
+ args.push("--json");
12223
+ }
12224
+ await runApitogoMcpCli("teardown", args, { quiet: Boolean(argv.json) });
12199
12225
  }
12200
12226
 
12201
- // src/cli/cmds/remove.ts
12202
- var remove_default = {
12203
- desc: "Remove a deployment from the target environment",
12204
- command: "remove",
12205
- builder: (yargs2) => yargs2.option("deployment-token", {
12227
+ // src/cli/cmds/teardown.ts
12228
+ var teardown_default = {
12229
+ desc: "Delete the linked APItoGo project",
12230
+ command: "teardown",
12231
+ builder: (yargs2) => yargs2.option("dir", {
12206
12232
  type: "string",
12207
- describe: "Deployment token sent to the remove endpoint"
12208
- }).option("env", {
12233
+ describe: "Site directory containing apitogo.config.json",
12234
+ default: ".",
12235
+ normalize: true
12236
+ }).option("project-id", {
12209
12237
  type: "string",
12210
- describe: "Target environment name sent to the remove API (e.g. production, preview, preview-a, preview-b)",
12211
- default: "production"
12212
- }).option("json-only", {
12238
+ describe: "Override project GUID"
12239
+ }).option("yes", {
12240
+ alias: "y",
12241
+ type: "boolean",
12242
+ describe: "Confirm teardown (required)",
12243
+ default: false
12244
+ }).option("wait", {
12245
+ type: "boolean",
12246
+ describe: "Wait for deletion to finish",
12247
+ default: true
12248
+ }).option("json", {
12213
12249
  type: "boolean",
12214
- describe: "JSON-only output on stdout; adds ?jsonOnly=true to the remove request",
12250
+ describe: "Print raw JSON only",
12215
12251
  default: false
12216
12252
  }),
12217
12253
  handler: async (argv) => {
12218
- if (argv.jsonOnly) {
12219
- applyJsonOnlyDeployQuietMode();
12220
- }
12221
- await captureEvent({ argv, event: "apitogo remove" });
12222
- await remove(argv);
12254
+ await captureEvent({ argv, event: "apitogo teardown" });
12255
+ await teardown(argv);
12223
12256
  }
12224
12257
  };
12225
12258
 
12226
12259
  // src/cli/common/outdated.ts
12227
12260
  import { existsSync as existsSync3, mkdirSync } from "node:fs";
12228
- import { readFile as readFile7, writeFile as writeFile9 } from "node:fs/promises";
12261
+ import { readFile as readFile6, writeFile as writeFile8 } from "node:fs/promises";
12229
12262
  import { join } from "node:path";
12230
12263
  import colors10 from "picocolors";
12231
12264
  import { gt } from "semver";
12232
12265
 
12266
+ // src/cli/common/json-only-deploy.ts
12267
+ function isDeployJsonOnlyArgv() {
12268
+ const args = process.argv;
12269
+ let cmdIdx = -1;
12270
+ for (const cmd of ["deploy", "remove"]) {
12271
+ const i = args.indexOf(cmd);
12272
+ if (i !== -1) {
12273
+ cmdIdx = i;
12274
+ break;
12275
+ }
12276
+ }
12277
+ if (cmdIdx === -1) {
12278
+ return false;
12279
+ }
12280
+ return args.slice(cmdIdx).some((a) => a === "--json-only" || a.startsWith("--json-only="));
12281
+ }
12282
+
12233
12283
  // src/cli/common/utils/box.ts
12234
12284
  import colors9 from "picocolors";
12235
12285
  import stripAnsi from "strip-ansi";
@@ -12275,12 +12325,12 @@ function box(message, {
12275
12325
 
12276
12326
  // src/cli/common/xdg/lib.ts
12277
12327
  import { homedir } from "node:os";
12278
- import path40 from "node:path";
12328
+ import path44 from "node:path";
12279
12329
  function defineDirectoryWithFallback(xdgName, fallback) {
12280
12330
  if (process.env[xdgName]) {
12281
12331
  return process.env[xdgName];
12282
12332
  } else {
12283
- return path40.join(homedir(), fallback);
12333
+ return path44.join(homedir(), fallback);
12284
12334
  }
12285
12335
  }
12286
12336
  var XDG_CONFIG_HOME = defineDirectoryWithFallback(
@@ -12295,15 +12345,15 @@ var XDG_STATE_HOME = defineDirectoryWithFallback(
12295
12345
  "XDG_DATA_HOME",
12296
12346
  ".local/state"
12297
12347
  );
12298
- var ZUDOKU_XDG_CONFIG_HOME = path40.join(
12348
+ var ZUDOKU_XDG_CONFIG_HOME = path44.join(
12299
12349
  XDG_CONFIG_HOME,
12300
12350
  CLI_XDG_FOLDER_NAME
12301
12351
  );
12302
- var ZUDOKU_XDG_DATA_HOME = path40.join(
12352
+ var ZUDOKU_XDG_DATA_HOME = path44.join(
12303
12353
  XDG_DATA_HOME,
12304
12354
  CLI_XDG_FOLDER_NAME
12305
12355
  );
12306
- var ZUDOKU_XDG_STATE_HOME = path40.join(
12356
+ var ZUDOKU_XDG_STATE_HOME = path44.join(
12307
12357
  XDG_STATE_HOME,
12308
12358
  CLI_XDG_FOLDER_NAME
12309
12359
  );
@@ -12355,7 +12405,7 @@ async function getVersionCheckInfo() {
12355
12405
  let versionCheckInfo;
12356
12406
  if (existsSync3(versionCheckPath)) {
12357
12407
  try {
12358
- versionCheckInfo = await readFile7(versionCheckPath, "utf-8").then(
12408
+ versionCheckInfo = await readFile6(versionCheckPath, "utf-8").then(
12359
12409
  JSON.parse
12360
12410
  );
12361
12411
  } catch {
@@ -12380,7 +12430,7 @@ async function getVersionCheckInfo() {
12380
12430
  lastCheck: Date.now(),
12381
12431
  latestVersion
12382
12432
  };
12383
- await writeFile9(
12433
+ await writeFile8(
12384
12434
  versionCheckPath,
12385
12435
  JSON.stringify(versionCheckInfo),
12386
12436
  "utf-8"
@@ -12467,7 +12517,7 @@ var cli = yargs(hideBin(process.argv)).option("zuplo", {
12467
12517
  process.env.ZUPLO = "1";
12468
12518
  printDiagnosticsToConsole("Running in Zuplo mode");
12469
12519
  }
12470
- }).middleware(warnPackageVersionMismatch).command(apply_default).command(build_default).command(configure_github_workflow_default).command(dev_default).command(deploy_default).command(remove_default).command(preview_default).command(make_config_default).demandCommand().strictCommands().version(packageJson?.version).fail(false).help();
12520
+ }).middleware(warnPackageVersionMismatch).command(init_default).command(config_default).command(dev_default).command(build_default).command(apply_default).command(status_default).command(teardown_default).command(deploy_default).command(remove_default).command(preview_default).command(configure_github_workflow_default).demandCommand().strictCommands().version(packageJson?.version).fail(false).help();
12471
12521
  try {
12472
12522
  void warnIfOutdatedVersion(packageJson?.version);
12473
12523
  await cli.argv;