@lukoweb/apitogo 0.1.77 → 0.1.78

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 = (path43) => stat(path43).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.78",
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, path43 = []) => {
6406
+ const transformed = transform(specification, path43);
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 = [...path43, 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 path43 = ref.split("/").slice(1);
6452
6441
  let current = schema2;
6453
- for (const segment of path41) {
6442
+ for (const segment of path43) {
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, path43) => {
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], `${path43}/${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, path43);
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, path43);
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], `${path43}/${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, path43) => {
6550
6539
  if (sub.example !== void 0) {
6551
- if (isSchemaPath(path41 ?? [])) {
6540
+ if (isSchemaPath(path43 ?? [])) {
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, path43) => {
6565
6554
  if (schema3.type === "object" && schema3.properties !== void 0) {
6566
- const parentPath = path41?.slice(0, -1);
6555
+ const parentPath = path43?.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" && path43?.[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, path43) => {
6572
+ if (path43?.includes("content") && path43.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, path43) => {
6610
6599
  if (schema3.type === "string" && schema3.format === "byte") {
6611
- const parentPath = path41?.slice(0, -1);
6600
+ const parentPath = path43?.slice(0, -1);
6612
6601
  const contentMediaType = parentPath?.find(
6613
- (_, index) => path41?.[index - 1] === "content"
6602
+ (_, index) => path43?.[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(path43) {
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) => path43[index] === segment);
6639
6628
  }
6640
- return path41.includes(location);
6641
- }) || path41.includes("schema") || path41.some((segment) => segment.endsWith("Schema"));
6629
+ return path43.includes(location);
6630
+ }) || path43.includes("schema") || path43.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(), path43 = [], 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 = path43.find((p) => !OPENAPI_PROPS.has(p)) || path43[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
+ [...path43, 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
+ (path43) => HttpMethods.map((k) => path43?.[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
+ ([path43, 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: path43,
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(([path43, value]) => ({
7197
+ path: path43,
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 = (path43) => path43.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, path43, filePath) => {
7394
+ const parts = getSegmentsFromPath(path43);
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 === path43 ? 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: ${path43}`
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 = (path43) => this.processedSchemas[path43]?.at(0);
7648
+ getSchemasForPath = (path43) => this.processedSchemas[path43];
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 = (path43) => {
10050
+ const segments = path43.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 = (path43) => path43.includes("*") || /^\d+$/.test(path43);
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,1320 @@ 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
10535
-
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 {
10656
- }
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
- }
10665
- }
10519
+ // src/cli/make-config/handler.ts
10520
+ import { constants as constants2 } from "node:fs";
10521
+ import { access as access3, readFile as readFile5, writeFile as writeFile7 } from "node:fs/promises";
10522
+ import path34 from "node:path";
10523
+ import { glob as glob5 } from "glob";
10666
10524
 
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);
10685
- }
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
+ var ask = async (rl, question, defaultValue) => {
10529
+ const suffix = defaultValue ? ` [${defaultValue}]` : "";
10530
+ const answer = (await rl.question(`${question}${suffix}: `)).trim();
10531
+ return answer || defaultValue || "";
10686
10532
  };
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
- }
10699
- }
10700
- if (cmdIdx === -1) {
10701
- return false;
10533
+ var askYesNo = async (rl, question, defaultYes) => {
10534
+ const hint = defaultYes ? "Y/n" : "y/N";
10535
+ const answer = (await rl.question(`${question} (${hint}): `)).trim().toLowerCase();
10536
+ if (!answer) {
10537
+ return defaultYes;
10702
10538
  }
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";
10707
- }
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;
10715
- }
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
10733
- },
10734
- ["dist"]
10735
- );
10736
- return archivePath;
10539
+ return answer === "y" || answer === "yes";
10737
10540
  };
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";
10541
+ async function promptMakeConfigInteractive(defaults) {
10542
+ const rl = createInterface({ input, output });
10743
10543
  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}...`
10544
+ const title = await ask(rl, "Site title", defaults.title);
10545
+ const logoAlt = await ask(rl, "Logo alt text", defaults.logoAlt);
10546
+ const authEnabled = await askYesNo(
10547
+ rl,
10548
+ "Enable authentication",
10549
+ defaults.authEnabled
10550
+ );
10551
+ let apiBaseUrl = defaults.apiBaseUrl ?? "";
10552
+ if (authEnabled) {
10553
+ apiBaseUrl = await ask(
10554
+ rl,
10555
+ "Auth API base URL (optional; secrets stay in .env)",
10556
+ apiBaseUrl
10754
10557
  );
10755
10558
  }
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)
10559
+ const sans = await ask(rl, "Sans font", defaults.sans ?? "IBM Plex Sans");
10560
+ const mono = await ask(
10561
+ rl,
10562
+ "Mono font",
10563
+ defaults.mono ?? "JetBrains Mono"
10762
10564
  );
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
10773
- },
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))}`
10565
+ const lightPrimary = await ask(
10566
+ rl,
10567
+ "Light primary color",
10568
+ defaults.lightPrimary ?? "#E11D2A"
10569
+ );
10570
+ const darkPrimary = await ask(
10571
+ rl,
10572
+ "Dark primary color",
10573
+ defaults.darkPrimary ?? "#F23645"
10574
+ );
10575
+ let openApiPath;
10576
+ if (defaults.discoveredOpenApi) {
10577
+ const useDiscovered = await askYesNo(
10578
+ rl,
10579
+ `Wire OpenAPI at ${defaults.discoveredOpenApi}`,
10580
+ true
10780
10581
  );
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)
10582
+ openApiPath = useDiscovered ? defaults.discoveredOpenApi : null;
10583
+ } else {
10584
+ const entered = await ask(
10585
+ rl,
10586
+ "OpenAPI file path (leave empty to skip)",
10587
+ ""
10791
10588
  );
10589
+ openApiPath = entered || null;
10792
10590
  }
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);
10591
+ const authentication = {
10592
+ enabled: authEnabled,
10593
+ type: "dev-portal"
10594
+ };
10595
+ if (apiBaseUrl) {
10596
+ authentication.apiBaseUrl = apiBaseUrl;
10799
10597
  }
10598
+ return {
10599
+ title,
10600
+ site: {
10601
+ title,
10602
+ logo: { alt: logoAlt }
10603
+ },
10604
+ theme: {
10605
+ fonts: { sans, mono },
10606
+ light: { primary: lightPrimary },
10607
+ dark: { primary: darkPrimary }
10608
+ },
10609
+ authentication,
10610
+ openApiPath
10611
+ };
10612
+ } finally {
10613
+ rl.close();
10800
10614
  }
10801
10615
  }
10802
10616
 
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();
10617
+ // src/cli/make-config/json-config.ts
10618
+ var APITOGO_JSON_FILENAME = "apitogo.config.json";
10619
+ var DOCUMENTATION_NAV_LABEL = "Documentation";
10620
+ var API_REFERENCE_PATH = "/api";
10621
+ var API_REFERENCE_LABEL = "API Reference";
10622
+ function isPlainObject2(value) {
10623
+ return value !== null && typeof value === "object" && !Array.isArray(value);
10624
+ }
10625
+ function deepMerge(target, source) {
10626
+ const result = { ...target };
10627
+ for (const [key, value] of Object.entries(source)) {
10628
+ const existing = result[key];
10629
+ if (isPlainObject2(existing) && isPlainObject2(value)) {
10630
+ result[key] = deepMerge(existing, value);
10631
+ } else {
10632
+ result[key] = value;
10838
10633
  }
10839
- await captureEvent({ argv, event: "apitogo deploy" });
10840
- await deploy(argv);
10841
- }
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;
10876
10634
  }
10877
- return port;
10635
+ return result;
10878
10636
  }
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");
10637
+ function parseJsonObjectFlag(raw, flagName) {
10638
+ if (raw === void 0 || raw.trim() === "") {
10639
+ return void 0;
10896
10640
  }
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++;
10641
+ let parsed;
10642
+ try {
10643
+ parsed = JSON.parse(raw);
10644
+ } catch {
10645
+ throw new Error(`--${flagName} must be a JSON object.`);
10646
+ }
10647
+ if (!isPlainObject2(parsed)) {
10648
+ throw new Error(`--${flagName} must be a JSON object.`);
10649
+ }
10650
+ return parsed;
10651
+ }
10652
+ function titleFromDirectoryName(dirName) {
10653
+ const titled = dirName.split(/[-_\s]+/).filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join(" ");
10654
+ return titled || "Developer Portal";
10655
+ }
10656
+ function createDefaultApitogoJsonConfig(options) {
10657
+ const filesGlob = `/${options.pagesDir}/**/*.{md,mdx}`;
10658
+ return {
10659
+ site: {
10660
+ title: options.title,
10661
+ logo: {
10662
+ src: { light: "/logo-light.png", dark: "/logo-dark.png" },
10663
+ alt: "APIToGo",
10664
+ width: "130px"
10665
+ }
10666
+ },
10667
+ authentication: {
10668
+ enabled: false,
10669
+ type: "dev-portal",
10670
+ redirectToAfterSignIn: "/account/dashboard",
10671
+ redirectToAfterSignUp: "/account/dashboard"
10672
+ },
10673
+ metadata: {
10674
+ favicon: "/Icon.png"
10675
+ },
10676
+ theme: {
10677
+ fonts: {
10678
+ sans: "IBM Plex Sans",
10679
+ mono: "JetBrains Mono"
10680
+ },
10681
+ light: {
10682
+ background: "#F5F6F9",
10683
+ foreground: "#0E1016",
10684
+ card: "#FFFFFF",
10685
+ cardForeground: "#0E1016",
10686
+ primary: "#E11D2A",
10687
+ primaryForeground: "#ffffff",
10688
+ secondary: "#FAFBFC",
10689
+ secondaryForeground: "#0E1016",
10690
+ muted: "#FAFBFC",
10691
+ mutedForeground: "#565E70",
10692
+ accent: "rgba(225, 29, 42, 0.08)",
10693
+ accentForeground: "#E11D2A",
10694
+ border: "#E6E8EF",
10695
+ input: "#D6DAE3",
10696
+ ring: "#E11D2A"
10697
+ },
10698
+ dark: {
10699
+ background: "#0B0D12",
10700
+ foreground: "#EDEFF5",
10701
+ card: "#14171F",
10702
+ cardForeground: "#EDEFF5",
10703
+ primary: "#F23645",
10704
+ primaryForeground: "#ffffff",
10705
+ secondary: "#191D27",
10706
+ secondaryForeground: "#EDEFF5",
10707
+ muted: "#191D27",
10708
+ mutedForeground: "#A6AEBF",
10709
+ accent: "rgba(242, 54, 69, 0.13)",
10710
+ accentForeground: "#F23645",
10711
+ border: "#252B37",
10712
+ input: "#333A48",
10713
+ ring: "#F23645"
10714
+ }
10715
+ },
10716
+ modules: {
10717
+ docs: {
10718
+ files: filesGlob
10719
+ },
10720
+ landing: {
10721
+ enabled: true,
10722
+ path: "/",
10723
+ layout: "landing",
10724
+ page: "./landing/LandingPage.tsx"
10725
+ },
10726
+ userPanel: {
10727
+ enabled: true,
10728
+ dashboard: { enabled: true },
10729
+ userManagement: { enabled: true },
10730
+ plans: {
10731
+ enabled: false,
10732
+ page: "./user-panel/AccountPlansPage.tsx"
10733
+ }
10928
10734
  }
10929
- } catch (error) {
10930
- console.error(`Failed to index ${urlPath}:`, error);
10931
10735
  }
10932
- yield {
10933
- type: "progress",
10934
- total: paths.length,
10935
- current: indexed,
10936
- path: urlPath
10937
- };
10938
- }
10939
- const outputPath = path35.join(vite.config.publicDir, "pagefind");
10940
- await pagefindIndex.writeFiles({ outputPath });
10941
- yield { type: "complete", success: true, indexed };
10736
+ };
10942
10737
  }
10943
-
10944
- // src/vite/dev-server.ts
10945
- var DEFAULT_DEV_PORT = 3e3;
10946
- var DevServer = class {
10947
- constructor(options) {
10948
- this.options = options;
10738
+ function applyConfigPatches(config2, patches) {
10739
+ let next = { ...config2 };
10740
+ if (patches.site) {
10741
+ next.site = deepMerge(
10742
+ isPlainObject2(next.site) ? next.site : {},
10743
+ patches.site
10744
+ );
10949
10745
  }
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 });
10746
+ if (patches.theme) {
10747
+ next.theme = deepMerge(
10748
+ isPlainObject2(next.theme) ? next.theme : {},
10749
+ patches.theme
10750
+ );
10963
10751
  }
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
10752
+ if (patches.authentication) {
10753
+ next.authentication = deepMerge(
10754
+ isPlainObject2(next.authentication) ? next.authentication : {},
10755
+ patches.authentication
10974
10756
  );
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);
10757
+ }
10758
+ if (patches.title?.trim()) {
10759
+ const site = isPlainObject2(next.site) ? next.site : {};
10760
+ next.site = { ...site, title: patches.title.trim() };
10761
+ }
10762
+ return next;
10763
+ }
10764
+ var docSortRank = (docPath) => {
10765
+ const lastSegment = docPath.split("/").at(-1) ?? docPath;
10766
+ if (lastSegment === "introduction") return 0;
10767
+ if (lastSegment === "example") return 1;
10768
+ if (lastSegment === "getting-started") return 2;
10769
+ if (lastSegment === "index") return 3;
10770
+ return 4;
10771
+ };
10772
+ var sortDocPaths = (docPaths) => [...docPaths].sort((a, b) => {
10773
+ const rankDiff = docSortRank(a) - docSortRank(b);
10774
+ if (rankDiff !== 0) return rankDiff;
10775
+ return a.localeCompare(b);
10776
+ });
10777
+ var createTreeNode = () => ({ docs: [], children: /* @__PURE__ */ new Map() });
10778
+ var titleCase = (value) => value.split(/[-_\s]+/).filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join(" ");
10779
+ var buildTree = (routePaths) => {
10780
+ const root = createTreeNode();
10781
+ for (const routePath of routePaths) {
10782
+ const segments = routePath.replace(/^\//, "").split("/").filter(Boolean);
10783
+ const directorySegments = segments.slice(0, -1);
10784
+ let current = root;
10785
+ for (const segment of directorySegments) {
10786
+ let child = current.children.get(segment);
10787
+ if (!child) {
10788
+ child = createTreeNode();
10789
+ current.children.set(segment, child);
10790
+ }
10791
+ current = child;
10982
10792
  }
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);
10793
+ current.docs.push(routePath);
10794
+ }
10795
+ return root;
10796
+ };
10797
+ var treeToNavigationItems = (node) => {
10798
+ const docs = sortDocPaths(node.docs);
10799
+ const categories = [...node.children.keys()].sort((a, b) => a.localeCompare(b)).map((directoryName) => {
10800
+ const childNode = node.children.get(directoryName);
10801
+ if (!childNode) {
10802
+ throw new Error(`Missing child node for directory '${directoryName}'.`);
11068
10803
  }
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);
11137
- });
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();
10804
+ return {
10805
+ type: "category",
10806
+ label: titleCase(directoryName),
10807
+ items: treeToNavigationItems(childNode)
10808
+ };
10809
+ });
10810
+ return [...docs, ...categories];
10811
+ };
10812
+ function buildDocumentationItems(routePaths) {
10813
+ return treeToNavigationItems(buildTree(sortDocPaths(routePaths)));
10814
+ }
10815
+ function navItemKey(item) {
10816
+ if (typeof item === "string") {
10817
+ return `path:${item}`;
10818
+ }
10819
+ if (typeof item.to === "string") {
10820
+ return `to:${item.to}`;
10821
+ }
10822
+ if (typeof item.path === "string") {
10823
+ return `path:${item.path}`;
10824
+ }
10825
+ if (item.type === "category" && typeof item.label === "string") {
10826
+ return `category:${item.label}`;
10827
+ }
10828
+ return JSON.stringify(item);
10829
+ }
10830
+ function mergeNavItems(existing, incoming) {
10831
+ const seen = new Set(existing.map(navItemKey));
10832
+ const merged = [...existing];
10833
+ for (const item of incoming) {
10834
+ const key = navItemKey(item);
10835
+ if (seen.has(key)) {
10836
+ continue;
11146
10837
  }
11147
- return { vite };
10838
+ seen.add(key);
10839
+ merged.push(item);
11148
10840
  }
11149
- async stop() {
11150
- await this.terminator?.terminate();
10841
+ return merged;
10842
+ }
10843
+ function isDocumentationCategory(item) {
10844
+ return typeof item === "object" && item.type === "category" && item.label === DOCUMENTATION_NAV_LABEL;
10845
+ }
10846
+ function isApiReferenceLink(item) {
10847
+ return typeof item === "object" && item.type === "link" && item.to === API_REFERENCE_PATH;
10848
+ }
10849
+ function mergeDocumentationNavigation(navigation, docRoutePaths, options) {
10850
+ const existing = Array.isArray(navigation) ? navigation : [];
10851
+ const generated = buildDocumentationItems(docRoutePaths);
10852
+ const rest = existing.filter((item) => !isDocumentationCategory(item));
10853
+ const currentDocs = existing.find(isDocumentationCategory);
10854
+ const currentItems = Array.isArray(currentDocs?.items) ? currentDocs.items : [];
10855
+ const items = options.force ? generated : mergeNavItems(currentItems, generated);
10856
+ const documentation = {
10857
+ type: "category",
10858
+ label: DOCUMENTATION_NAV_LABEL,
10859
+ items
10860
+ };
10861
+ if (generated.length === 0) {
10862
+ return existing;
11151
10863
  }
11152
- };
11153
-
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
11170
- );
11171
- printDiagnosticsToConsole("Ctrl+C to exit");
11172
- printDiagnosticsToConsole("");
11173
- printDiagnosticsToConsole(
11174
- `\u{1F680} Running APIToGo v${packageJson2.version}: ${url}`
11175
- );
11176
- 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
- }
10864
+ if (currentDocs) {
10865
+ const index = existing.findIndex(isDocumentationCategory);
10866
+ const next = [...existing];
10867
+ next[index] = documentation;
10868
+ return next;
10869
+ }
10870
+ return [documentation, ...rest];
10871
+ }
10872
+ function applyOpenApiToJsonConfig(config2, input2) {
10873
+ const next = { ...config2 };
10874
+ next.apis = [
10875
+ {
10876
+ type: "file",
10877
+ input: input2,
10878
+ path: "api"
11189
10879
  }
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();
10880
+ ];
10881
+ const navigation = Array.isArray(next.navigation) ? [...next.navigation] : [];
10882
+ if (!navigation.some(isApiReferenceLink)) {
10883
+ navigation.push({
10884
+ type: "link",
10885
+ to: API_REFERENCE_PATH,
10886
+ label: API_REFERENCE_LABEL
11203
10887
  });
11204
- process.on("exit", exit);
11205
- });
10888
+ }
10889
+ next.navigation = navigation;
10890
+ return next;
10891
+ }
10892
+ function openapiInputForConfig(specRelativePosix) {
10893
+ if (specRelativePosix.startsWith("./")) {
10894
+ return specRelativePosix;
10895
+ }
10896
+ return `./${specRelativePosix}`;
11206
10897
  }
10898
+ function toRoutePath(relativeFile, pagesDir) {
10899
+ const relativeNoExt = relativeFile.replace(/\.(mdx?|tsx|jsx)$/i, "");
10900
+ const escaped = pagesDir.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
10901
+ const withoutPagesPrefix = relativeNoExt.replace(
10902
+ new RegExp(`^${escaped}/?`),
10903
+ ""
10904
+ );
10905
+ const normalized = withoutPagesPrefix.replace(/\/index$/, "");
10906
+ return `/${normalized || "index"}`;
10907
+ }
10908
+ function pageComponentName(slug) {
10909
+ const pascal = slug.replace(/^\/+/, "").split(/[^a-zA-Z0-9]+/).filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
10910
+ const name = /^[A-Za-z]/.test(pascal) ? pascal : `Page${pascal}`;
10911
+ return name.endsWith("Page") ? name : `${name}Page`;
10912
+ }
10913
+ var THIN_WRAPPER_SOURCE = `import { devPortalBillingPlugin } from "@lukoweb/apitogo-plugin-dev-portal-billing";
10914
+ import { buildApitogoConfig } from "@lukoweb/apitogo/build-config";
10915
+ import siteConfig from "./apitogo.config.json";
11207
10916
 
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);
10917
+ export default buildApitogoConfig({
10918
+ ...siteConfig,
10919
+ plugins: [devPortalBillingPlugin()],
10920
+ });
10921
+ `;
10922
+ function isThinConfigWrapper(source) {
10923
+ return /export\s+default\s+buildApitogoConfig\s*\(/.test(source);
10924
+ }
10925
+ function buildThinWrapperWithCustomPages(tsxRoutePaths) {
10926
+ if (tsxRoutePaths.length === 0) {
10927
+ return THIN_WRAPPER_SOURCE;
11238
10928
  }
11239
- };
10929
+ const imports = tsxRoutePaths.map((routePath) => {
10930
+ const slug = routePath.replace(/^\//, "");
10931
+ const name = pageComponentName(slug);
10932
+ return `import ${name} from "./pages/${slug}";`;
10933
+ }).join("\n");
10934
+ const customPages = tsxRoutePaths.map((routePath) => {
10935
+ const slug = routePath.replace(/^\//, "");
10936
+ const name = pageComponentName(slug);
10937
+ return ` {
10938
+ type: "custom-page",
10939
+ path: ${JSON.stringify(routePath)},
10940
+ element: <${name} />,
10941
+ },`;
10942
+ }).join("\n");
10943
+ return `${imports}
10944
+ import { devPortalBillingPlugin } from "@lukoweb/apitogo-plugin-dev-portal-billing";
10945
+ import { buildApitogoConfig } from "@lukoweb/apitogo/build-config";
10946
+ import siteConfig from "./apitogo.config.json";
11240
10947
 
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";
10948
+ const config = buildApitogoConfig({
10949
+ ...siteConfig,
10950
+ plugins: [devPortalBillingPlugin()],
10951
+ });
11246
10952
 
11247
- // 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
- var ask = async (rl, question, defaultValue) => {
11251
- const suffix = defaultValue ? ` [${defaultValue}]` : "";
11252
- const answer = (await rl.question(`${question}${suffix}: `)).trim();
11253
- return answer || defaultValue || "";
11254
- };
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;
11260
- }
11261
- return answer === "y" || answer === "yes";
10953
+ export default {
10954
+ ...config,
10955
+ navigation: [
10956
+ {
10957
+ type: "category",
10958
+ label: ${JSON.stringify(DOCUMENTATION_NAV_LABEL)},
10959
+ items: [
10960
+ ${customPages}
10961
+ ...(((config.navigation ?? []).find(
10962
+ (item) =>
10963
+ typeof item === "object" &&
10964
+ item !== null &&
10965
+ "label" in item &&
10966
+ item.label === ${JSON.stringify(DOCUMENTATION_NAV_LABEL)},
10967
+ ) as { items?: unknown[] } | undefined)?.items ?? []),
10968
+ ],
10969
+ },
10970
+ ...(config.navigation ?? []).filter(
10971
+ (item) =>
10972
+ !(
10973
+ typeof item === "object" &&
10974
+ item !== null &&
10975
+ "label" in item &&
10976
+ item.label === ${JSON.stringify(DOCUMENTATION_NAV_LABEL)}
10977
+ ),
10978
+ ),
10979
+ ],
11262
10980
  };
11263
- async function promptMakeConfigInteractive(defaults) {
11264
- const rl = createInterface({ input, output });
11265
- try {
11266
- 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(
11276
- rl,
11277
- "Auth API base URL (optional; secrets stay in .env)",
11278
- apiBaseUrl
11279
- );
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(
11300
- rl,
11301
- `Wire OpenAPI at ${defaults.discoveredOpenApi}`,
11302
- true
11303
- );
11304
- openApiPath = useDiscovered ? defaults.discoveredOpenApi : null;
11305
- } else {
11306
- const entered = await ask(
11307
- rl,
11308
- "OpenAPI file path (leave empty to skip)",
11309
- ""
11310
- );
11311
- openApiPath = entered || null;
11312
- }
11313
- const authentication = {
11314
- enabled: authEnabled,
11315
- type: "dev-portal"
11316
- };
11317
- if (apiBaseUrl) {
11318
- authentication.apiBaseUrl = apiBaseUrl;
11319
- }
11320
- return {
11321
- title,
11322
- site: {
11323
- title,
11324
- logo: { alt: logoAlt }
11325
- },
11326
- theme: {
11327
- fonts: { sans, mono },
11328
- light: { primary: lightPrimary },
11329
- dark: { primary: darkPrimary }
11330
- },
11331
- authentication,
11332
- openApiPath
11333
- };
11334
- } finally {
11335
- rl.close();
11336
- }
10981
+ `;
11337
10982
  }
11338
10983
 
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;
10984
+ // src/cli/make-config/scaffold-project.ts
10985
+ init_package_json();
10986
+ import { constants } from "node:fs";
10987
+ import { access as access2, mkdir as mkdir6, readFile as readFile4, writeFile as writeFile6 } from "node:fs/promises";
10988
+ import path33 from "node:path";
10989
+ import { glob as glob4 } from "glob";
10990
+ var OPENAPI_GLOBS = [
10991
+ "apis/openapi.json",
10992
+ "apis/openapi.yaml",
10993
+ "apis/openapi.yml",
10994
+ "openapi.json",
10995
+ "openapi.yaml",
10996
+ "openapi.yml"
10997
+ ];
10998
+ var toPosix = (p) => p.split(path33.sep).join("/");
10999
+ var sanitizePackageName = (dir) => {
11000
+ const base = path33.basename(path33.resolve(dir));
11001
+ const s = base.toLowerCase().replace(/[^a-z0-9-_.]/g, "-").replace(/^-+|-+$/g, "");
11002
+ return s || "apitogo-site";
11003
+ };
11004
+ var apitogoVersionRange = () => {
11005
+ const v = getZudokuPackageJson()?.version;
11006
+ return v ? `^${v}` : "latest";
11007
+ };
11008
+ async function findOpenApiSpecPath(dir) {
11009
+ for (const rel of OPENAPI_GLOBS) {
11010
+ const full = path33.join(dir, rel);
11011
+ try {
11012
+ await access2(full, constants.R_OK);
11013
+ return toPosix(rel);
11014
+ } catch {
11355
11015
  }
11356
11016
  }
11357
- return result;
11017
+ const extra = await glob4("apis/**/*.{json,yaml,yml}", {
11018
+ cwd: dir,
11019
+ posix: true,
11020
+ ignore: ["**/node_modules/**"]
11021
+ });
11022
+ return extra[0] ? toPosix(extra[0]) : null;
11358
11023
  }
11359
- function parseJsonObjectFlag(raw, flagName) {
11360
- if (raw === void 0 || raw.trim() === "") {
11361
- return void 0;
11024
+ async function ensurePackageJson(dir) {
11025
+ const pkgPath = path33.join(dir, "package.json");
11026
+ const apitogoVer = apitogoVersionRange();
11027
+ const baseDeps = {
11028
+ react: ">=19.0.0",
11029
+ "react-dom": ">=19.0.0",
11030
+ "@lukoweb/apitogo": apitogoVer,
11031
+ "@lukoweb/apitogo-plugin-dev-portal-billing": apitogoVer,
11032
+ "@lukoweb/apitogo-module-landing": apitogoVer,
11033
+ "@lukoweb/apitogo-module-user-panel": apitogoVer,
11034
+ "lucide-react": "0.577.0"
11035
+ };
11036
+ const baseScripts = {
11037
+ dev: "apitogo dev",
11038
+ build: "apitogo build",
11039
+ preview: "apitogo build --preview"
11040
+ };
11041
+ try {
11042
+ await access2(pkgPath, constants.R_OK);
11043
+ } catch (err) {
11044
+ const code = err.code;
11045
+ if (code !== "ENOENT") throw err;
11046
+ const pkg2 = {
11047
+ name: sanitizePackageName(dir),
11048
+ version: "0.1.0",
11049
+ private: true,
11050
+ type: "module",
11051
+ scripts: baseScripts,
11052
+ dependencies: baseDeps,
11053
+ devDependencies: {
11054
+ "@types/node": "^22",
11055
+ "@types/react": "^19",
11056
+ "@types/react-dom": "^19",
11057
+ typescript: "^5"
11058
+ }
11059
+ };
11060
+ await writeFile6(pkgPath, `${JSON.stringify(pkg2, null, 2)}
11061
+ `, "utf8");
11062
+ return true;
11362
11063
  }
11363
- let parsed;
11064
+ const raw = await readFile4(pkgPath, "utf8");
11065
+ let pkg;
11364
11066
  try {
11365
- parsed = JSON.parse(raw);
11067
+ pkg = JSON.parse(raw);
11366
11068
  } catch {
11367
- throw new Error(`--${flagName} must be a JSON object.`);
11069
+ throw new Error(
11070
+ "package.json exists but is not valid JSON; fix or remove it and run again."
11071
+ );
11368
11072
  }
11369
- if (!isPlainObject2(parsed)) {
11370
- throw new Error(`--${flagName} must be a JSON object.`);
11073
+ pkg.scripts = {
11074
+ ...typeof pkg.scripts === "object" && pkg.scripts !== null ? pkg.scripts : {}
11075
+ };
11076
+ const scripts = pkg.scripts;
11077
+ for (const [k, v] of Object.entries(baseScripts)) {
11078
+ if (!scripts[k]) scripts[k] = v;
11371
11079
  }
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";
11080
+ pkg.dependencies = {
11081
+ ...typeof pkg.dependencies === "object" && pkg.dependencies !== null ? pkg.dependencies : {}
11082
+ };
11083
+ const deps = pkg.dependencies;
11084
+ for (const [k, v] of Object.entries(baseDeps)) {
11085
+ if (!deps[k]) deps[k] = v;
11086
+ }
11087
+ await writeFile6(pkgPath, `${JSON.stringify(pkg, null, 2)}
11088
+ `, "utf8");
11089
+ return false;
11377
11090
  }
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"
11091
+ async function ensureTsconfigJson(dir) {
11092
+ const tsPath = path33.join(dir, "tsconfig.json");
11093
+ try {
11094
+ await access2(tsPath, constants.R_OK);
11095
+ return false;
11096
+ } catch {
11097
+ const tsconfig = {
11098
+ compilerOptions: {
11099
+ target: "ES2022",
11100
+ lib: ["ESNext", "DOM", "DOM.Iterable", "WebWorker"],
11101
+ module: "ESNext",
11102
+ moduleResolution: "Bundler",
11103
+ useDefineForClassFields: true,
11104
+ skipLibCheck: true,
11105
+ skipDefaultLibCheck: true,
11106
+ resolveJsonModule: true,
11107
+ isolatedModules: true,
11108
+ useUnknownInCatchVariables: false,
11109
+ jsx: "react-jsx"
11447
11110
  },
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
- };
11111
+ include: [
11112
+ "*.config.ts",
11113
+ "*.config.tsx",
11114
+ "*.config.jsx",
11115
+ "*.config.js",
11116
+ "*.config.mjs",
11117
+ "pages",
11118
+ "landing",
11119
+ "user-panel"
11120
+ ]
11121
+ };
11122
+ await writeFile6(tsPath, `${JSON.stringify(tsconfig, null, 2)}
11123
+ `, "utf8");
11124
+ return true;
11125
+ }
11459
11126
  }
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
- );
11127
+ var LANDING_PAGE_SOURCE = `import {
11128
+ FeaturesSection,
11129
+ HeroSection,
11130
+ LandingShell,
11131
+ } from "@lukoweb/apitogo-module-landing";
11132
+
11133
+ export default function LandingPage() {
11134
+ return (
11135
+ <LandingShell
11136
+ title="Your API, ready for developers"
11137
+ description="Documentation, API reference, and a developer portal \u2014 all in one place."
11138
+ >
11139
+ <HeroSection
11140
+ hero={{
11141
+ title: "Your API, ready for developers",
11142
+ description:
11143
+ "Ship docs, interactive API reference, and a developer portal without building from scratch.",
11144
+ }}
11145
+ primaryAction={{ label: "Get started", href: "/introduction" }}
11146
+ secondaryAction={{ label: "API Reference", href: "/api" }}
11147
+ />
11148
+ <FeaturesSection
11149
+ features={[
11150
+ {
11151
+ title: "Documentation",
11152
+ description: "Guides and references for every integration path.",
11153
+ icon: "book-open",
11154
+ },
11155
+ {
11156
+ title: "API Reference",
11157
+ description: "Interactive OpenAPI explorer with code samples.",
11158
+ icon: "code",
11159
+ },
11160
+ {
11161
+ title: "Developer Portal",
11162
+ description: "Auth, API keys, and subscription billing built in.",
11163
+ icon: "shield-check",
11164
+ },
11165
+ ]}
11166
+ />
11167
+ </LandingShell>
11168
+ );
11169
+ }
11170
+ `;
11171
+ var ACCOUNT_PLANS_PAGE_SOURCE = `export { AccountPlansPage as default } from "@lukoweb/apitogo-plugin-dev-portal-billing";
11172
+ `;
11173
+ var fileExists2 = async (filePath) => {
11174
+ try {
11175
+ await access2(filePath, constants.F_OK);
11176
+ return true;
11177
+ } catch {
11178
+ return false;
11467
11179
  }
11468
- if (patches.theme) {
11469
- next.theme = deepMerge(
11470
- isPlainObject2(next.theme) ? next.theme : {},
11471
- patches.theme
11472
- );
11180
+ };
11181
+ async function ensurePortalStubFiles(dir) {
11182
+ const landingPath = path33.join(dir, "landing", "LandingPage.tsx");
11183
+ if (!await fileExists2(landingPath)) {
11184
+ await mkdir6(path33.join(dir, "landing"), { recursive: true });
11185
+ await writeFile6(landingPath, LANDING_PAGE_SOURCE, "utf8");
11473
11186
  }
11474
- if (patches.authentication) {
11475
- next.authentication = deepMerge(
11476
- isPlainObject2(next.authentication) ? next.authentication : {},
11477
- patches.authentication
11478
- );
11187
+ const plansPath = path33.join(dir, "user-panel", "AccountPlansPage.tsx");
11188
+ if (!await fileExists2(plansPath)) {
11189
+ await mkdir6(path33.join(dir, "user-panel"), { recursive: true });
11190
+ await writeFile6(plansPath, ACCOUNT_PLANS_PAGE_SOURCE, "utf8");
11479
11191
  }
11480
- if (patches.title?.trim()) {
11481
- const site = isPlainObject2(next.site) ? next.site : {};
11482
- next.site = { ...site, title: patches.title.trim() };
11192
+ }
11193
+ async function ensureThinConfigTsx(dir) {
11194
+ const tsxPath = path33.join(dir, "apitogo.config.tsx");
11195
+ if (await fileExists2(tsxPath)) {
11196
+ return false;
11483
11197
  }
11484
- return next;
11198
+ await writeFile6(tsxPath, THIN_WRAPPER_SOURCE, "utf8");
11199
+ return true;
11485
11200
  }
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;
11201
+
11202
+ // src/cli/make-config/handler.ts
11203
+ var toPosixPath2 = (value) => value.split(path34.sep).join(path34.posix.sep);
11204
+ var fileExists3 = async (filePath) => {
11205
+ try {
11206
+ await access3(filePath, constants2.F_OK);
11207
+ return true;
11208
+ } catch {
11209
+ return false;
11210
+ }
11493
11211
  };
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);
11212
+ var readJsonConfig = async (filePath) => {
11213
+ const raw = await readFile5(filePath, "utf8");
11214
+ const parsed = JSON.parse(raw);
11215
+ if (!isPlainObject2(parsed)) {
11216
+ throw new Error(`${APITOGO_JSON_FILENAME} must be a JSON object.`);
11516
11217
  }
11517
- return root;
11218
+ return parsed;
11518
11219
  };
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}'.`);
11220
+ async function makeConfig(argv) {
11221
+ const dir = path34.resolve(process.cwd(), argv.dir);
11222
+ const pagesDir = toPosixPath2(
11223
+ argv.pagesDir.replace(/^[./]+/, "").replace(/\/+$/, "")
11224
+ );
11225
+ try {
11226
+ const discoveredOpenApi = await findOpenApiSpecPath(dir);
11227
+ const createdPackageJson = await ensurePackageJson(dir);
11228
+ const createdTsconfig = await ensureTsconfigJson(dir);
11229
+ if (createdPackageJson) {
11230
+ printDiagnosticsToConsole(
11231
+ "Created package.json with dev, build, and preview scripts."
11232
+ );
11525
11233
  }
11526
- return {
11527
- type: "category",
11528
- label: titleCase(directoryName),
11529
- items: treeToNavigationItems(childNode)
11234
+ if (createdTsconfig) {
11235
+ printDiagnosticsToConsole("Created tsconfig.json.");
11236
+ }
11237
+ await ensurePortalStubFiles(dir);
11238
+ const jsonPath = path34.join(dir, APITOGO_JSON_FILENAME);
11239
+ let createdJson = false;
11240
+ let config2;
11241
+ if (await fileExists3(jsonPath)) {
11242
+ config2 = await readJsonConfig(jsonPath);
11243
+ } else {
11244
+ config2 = createDefaultApitogoJsonConfig({
11245
+ title: titleFromDirectoryName(path34.basename(dir)),
11246
+ pagesDir
11247
+ });
11248
+ createdJson = true;
11249
+ printDiagnosticsToConsole(`Created ${APITOGO_JSON_FILENAME}`);
11250
+ }
11251
+ const createdTsx = await ensureThinConfigTsx(dir);
11252
+ if (createdTsx) {
11253
+ printDiagnosticsToConsole("Created apitogo.config.tsx");
11254
+ }
11255
+ const flagPatches = {
11256
+ site: parseJsonObjectFlag(argv.site, "site"),
11257
+ theme: parseJsonObjectFlag(argv.theme, "theme"),
11258
+ authentication: parseJsonObjectFlag(argv.authentication, "authentication"),
11259
+ title: argv.title
11530
11260
  };
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}`;
11540
- }
11541
- if (typeof item.to === "string") {
11542
- return `to:${item.to}`;
11543
- }
11544
- if (typeof item.path === "string") {
11545
- return `path:${item.path}`;
11546
- }
11547
- if (item.type === "category" && typeof item.label === "string") {
11548
- return `category:${item.label}`;
11549
- }
11550
- return JSON.stringify(item);
11551
- }
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;
11261
+ let openApiOverride;
11262
+ if (argv.interactive) {
11263
+ const site = isPlainObject2(config2.site) ? config2.site : {};
11264
+ const logo = isPlainObject2(site.logo) ? site.logo : {};
11265
+ const theme = isPlainObject2(config2.theme) ? config2.theme : {};
11266
+ const fonts = isPlainObject2(theme.fonts) ? theme.fonts : {};
11267
+ const light = isPlainObject2(theme.light) ? theme.light : {};
11268
+ const dark = isPlainObject2(theme.dark) ? theme.dark : {};
11269
+ const authentication = isPlainObject2(config2.authentication) ? config2.authentication : {};
11270
+ const prompted = await promptMakeConfigInteractive({
11271
+ title: flagPatches.title ?? (typeof site.title === "string" ? site.title : titleFromDirectoryName(path34.basename(dir))),
11272
+ logoAlt: typeof logo.alt === "string" ? logo.alt : "APIToGo",
11273
+ authEnabled: authentication.enabled === true,
11274
+ apiBaseUrl: typeof authentication.apiBaseUrl === "string" ? authentication.apiBaseUrl : void 0,
11275
+ sans: typeof fonts.sans === "string" ? fonts.sans : void 0,
11276
+ mono: typeof fonts.mono === "string" ? fonts.mono : void 0,
11277
+ lightPrimary: typeof light.primary === "string" ? light.primary : void 0,
11278
+ darkPrimary: typeof dark.primary === "string" ? dark.primary : void 0,
11279
+ discoveredOpenApi
11280
+ });
11281
+ openApiOverride = prompted.openApiPath;
11282
+ config2 = applyConfigPatches(config2, prompted);
11559
11283
  }
11560
- seen.add(key);
11561
- merged.push(item);
11562
- }
11563
- return merged;
11564
- }
11565
- function isDocumentationCategory(item) {
11566
- return typeof item === "object" && item.type === "category" && item.label === DOCUMENTATION_NAV_LABEL;
11567
- }
11568
- function isApiReferenceLink(item) {
11569
- return typeof item === "object" && item.type === "link" && item.to === API_REFERENCE_PATH;
11570
- }
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;
11585
- }
11586
- if (currentDocs) {
11587
- const index = existing.findIndex(isDocumentationCategory);
11588
- const next = [...existing];
11589
- next[index] = documentation;
11590
- return next;
11591
- }
11592
- return [documentation, ...rest];
11593
- }
11594
- function applyOpenApiToJsonConfig(config2, input2) {
11595
- const next = { ...config2 };
11596
- next.apis = [
11597
- {
11598
- type: "file",
11599
- input: input2,
11600
- path: "api"
11284
+ config2 = applyConfigPatches(config2, flagPatches);
11285
+ const pageFiles = await glob5(`${pagesDir}/**/*.{md,mdx,tsx,jsx}`, {
11286
+ cwd: dir,
11287
+ ignore: ["**/node_modules/**", "**/.git/**", "**/dist/**"],
11288
+ posix: true
11289
+ });
11290
+ const markdownFiles = pageFiles.filter((file) => /\.mdx?$/i.test(file));
11291
+ const tsxFiles = pageFiles.filter((file) => /\.(tsx|jsx)$/i.test(file));
11292
+ const markdownRoutes = markdownFiles.map(
11293
+ (file) => toRoutePath(file, pagesDir)
11294
+ );
11295
+ const tsxRoutes = tsxFiles.map((file) => toRoutePath(file, pagesDir));
11296
+ if (markdownRoutes.length === 0 && tsxRoutes.length === 0) {
11297
+ printDiagnosticsToConsole(
11298
+ `No docs pages under '${pagesDir}/' \u2014 skipping documentation nav scan.`
11299
+ );
11300
+ }
11301
+ config2.navigation = mergeDocumentationNavigation(
11302
+ config2.navigation,
11303
+ markdownRoutes,
11304
+ { force: argv.force === true }
11305
+ );
11306
+ const openApiRel = openApiOverride === void 0 ? discoveredOpenApi : openApiOverride;
11307
+ if (openApiRel) {
11308
+ config2 = applyOpenApiToJsonConfig(
11309
+ config2,
11310
+ openapiInputForConfig(openApiRel)
11311
+ );
11312
+ }
11313
+ await writeFile7(jsonPath, `${JSON.stringify(config2, null, 2)}
11314
+ `, "utf8");
11315
+ const tsxPath = path34.join(dir, "apitogo.config.tsx");
11316
+ if (await fileExists3(tsxPath) && tsxRoutes.length > 0) {
11317
+ const tsxSource = await readFile5(tsxPath, "utf8");
11318
+ if (isThinConfigWrapper(tsxSource)) {
11319
+ await writeFile7(
11320
+ tsxPath,
11321
+ buildThinWrapperWithCustomPages(tsxRoutes),
11322
+ "utf8"
11323
+ );
11324
+ }
11325
+ }
11326
+ const action = createdJson ? "Initialized" : "Updated";
11327
+ printResultToConsole(
11328
+ `${action} ${APITOGO_JSON_FILENAME} (site/theme/auth/navigation/OpenAPI as applicable; redirects unchanged).`
11329
+ );
11330
+ if (createdPackageJson) {
11331
+ printDiagnosticsToConsole(
11332
+ "Next: run npm install (or pnpm install / yarn) in this directory, then npm run dev."
11333
+ );
11601
11334
  }
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
11609
- });
11610
- }
11611
- next.navigation = navigation;
11612
- return next;
11613
- }
11614
- function openapiInputForConfig(specRelativePosix) {
11615
- if (specRelativePosix.startsWith("./")) {
11616
- return specRelativePosix;
11335
+ } catch (error) {
11336
+ await printCriticalFailureToConsoleAndExit(
11337
+ error instanceof Error ? error.message : String(error)
11338
+ );
11617
11339
  }
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
11340
  }
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
11341
 
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;
11342
+ // src/cli/cmds/config.ts
11343
+ var config_default = {
11344
+ desc: "Create or update apitogo.config.json from pages, OpenAPI, and optional site/theme/auth flags",
11345
+ command: "config",
11346
+ aliases: ["make-config"],
11347
+ builder: (yargs2) => yargs2.option("dir", {
11348
+ type: "string",
11349
+ describe: "The directory containing your project",
11350
+ default: ".",
11351
+ normalize: true,
11352
+ hidden: true
11353
+ }).option("pages-dir", {
11354
+ type: "string",
11355
+ describe: "Directory to scan for md/mdx/tsx pages",
11356
+ default: "pages"
11357
+ }).option("interactive", {
11358
+ alias: "i",
11359
+ type: "boolean",
11360
+ describe: "Prompt for site, theme, authentication, and OpenAPI",
11361
+ default: false
11362
+ }).option("site", {
11363
+ type: "string",
11364
+ describe: "JSON object merged into site"
11365
+ }).option("theme", {
11366
+ type: "string",
11367
+ describe: "JSON object merged into theme"
11368
+ }).option("authentication", {
11369
+ type: "string",
11370
+ describe: "JSON object merged into authentication"
11371
+ }).option("title", {
11372
+ type: "string",
11373
+ describe: "Set site.title"
11374
+ }).option("force", {
11375
+ type: "boolean",
11376
+ describe: "Replace Documentation nav items from disk (keeps API Reference)",
11377
+ default: false
11378
+ }),
11379
+ handler: async (argv) => {
11380
+ await captureEvent({ argv, event: "apitogo config" });
11381
+ await makeConfig(argv);
11650
11382
  }
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";
11669
-
11670
- const config = buildApitogoConfig({
11671
- ...siteConfig,
11672
- plugins: [devPortalBillingPlugin()],
11673
- });
11674
-
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
- ],
11702
11383
  };
11703
- `;
11704
- }
11705
11384
 
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";
11710
- 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";
11385
+ // src/cli/cmds/configure-github-workflow.ts
11386
+ var REMOVED = "apitogo configure-github-workflow was removed. Multi-env SWA deploy is deprecated; use `apitogo apply`.";
11387
+ var configure_github_workflow_default = {
11388
+ desc: "Removed \u2014 multi-env SWA deploy is deprecated",
11389
+ command: "configure-github-workflow",
11390
+ builder: (yargs2) => yargs2,
11391
+ handler: async (argv) => {
11392
+ await captureEvent({
11393
+ argv,
11394
+ event: "apitogo configure-github-workflow removed"
11395
+ });
11396
+ await printCriticalFailureToConsoleAndExit(REMOVED);
11397
+ }
11729
11398
  };
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
- }
11399
+
11400
+ // src/cli/cmds/deploy.ts
11401
+ var REMOVED2 = "apitogo deploy was removed. Use `apitogo apply` (optionally `--only portal-frontend`).";
11402
+ var deploy_default = {
11403
+ desc: "Removed \u2014 use apitogo apply",
11404
+ command: "deploy",
11405
+ builder: (yargs2) => yargs2,
11406
+ handler: async (argv) => {
11407
+ await captureEvent({ argv, event: "apitogo deploy removed" });
11408
+ await printCriticalFailureToConsoleAndExit(REMOVED2);
11738
11409
  }
11739
- const extra = await glob4("apis/**/*.{json,yaml,yml}", {
11740
- cwd: dir,
11741
- posix: true,
11742
- ignore: ["**/node_modules/**"]
11410
+ };
11411
+
11412
+ // src/cli/dev/handler.ts
11413
+ init_joinUrl();
11414
+ import path37 from "node:path";
11415
+
11416
+ // src/vite/dev-server.ts
11417
+ init_logger();
11418
+ import fs4 from "node:fs/promises";
11419
+ import http from "node:http";
11420
+ import https from "node:https";
11421
+ import path36 from "node:path";
11422
+ import { createHttpTerminator } from "http-terminator";
11423
+ import {
11424
+ createServer as createViteServer,
11425
+ isRunnableDevEnvironment as isRunnableDevEnvironment2,
11426
+ mergeConfig as mergeConfig4
11427
+ } from "vite";
11428
+
11429
+ // src/cli/common/utils/ports.ts
11430
+ import net from "node:net";
11431
+ var isPortAvailable = (port) => new Promise((resolve) => {
11432
+ const server = net.createServer();
11433
+ server.unref();
11434
+ server.once("error", () => resolve(false));
11435
+ server.listen(port, () => {
11436
+ server.close();
11437
+ resolve(true);
11743
11438
  });
11744
- return extra[0] ? toPosix(extra[0]) : null;
11439
+ });
11440
+ async function findAvailablePort(startPort) {
11441
+ let port = startPort;
11442
+ for (; port < startPort + 1e3; port++) {
11443
+ if (await isPortAvailable(port)) break;
11444
+ }
11445
+ return port;
11745
11446
  }
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"
11447
+
11448
+ // src/vite/dev-server.ts
11449
+ init_loader();
11450
+
11451
+ // src/vite/pagefind-dev-index.ts
11452
+ init_invariant();
11453
+ init_joinUrl();
11454
+ import path35 from "node:path";
11455
+ import { createIndex as createIndex2 } from "pagefind";
11456
+ import { isRunnableDevEnvironment } from "vite";
11457
+ async function* buildPagefindDevIndex(vite, config2) {
11458
+ const { index, errors } = await createIndex2();
11459
+ invariant(index, `Failed to create pagefind index: ${errors.join(", ")}`);
11460
+ const pagefindIndex = index;
11461
+ const ssrEnvironment = vite.environments.ssr;
11462
+ if (!isRunnableDevEnvironment(ssrEnvironment)) {
11463
+ throw new Error("SSR environment is not runnable");
11464
+ }
11465
+ const serverModule = await ssrEnvironment.runner.import(
11466
+ getAppServerEntryPath()
11467
+ );
11468
+ const routes = serverModule.getRoutesByConfig(config2);
11469
+ const paths = routesToPaths(routes);
11470
+ const { basePath } = config2;
11471
+ const template = getDevHtml({
11472
+ jsEntry: "/__z/entry.client.tsx",
11473
+ dir: config2.site?.dir
11474
+ });
11475
+ const transformedTemplate = await vite.transformIndexHtml("/", template);
11476
+ let indexed = 0;
11477
+ for (const urlPath of paths) {
11478
+ try {
11479
+ const pathname = joinUrl(basePath, urlPath);
11480
+ const url = joinUrl("http://localhost", pathname);
11481
+ const request = new Request(url);
11482
+ const response = await serverModule.handleRequest({
11483
+ template: transformedTemplate,
11484
+ request,
11485
+ routes,
11486
+ basePath,
11487
+ bypassProtection: true
11488
+ });
11489
+ if (response.status < 400 && response.body) {
11490
+ const content = await response.text();
11491
+ await pagefindIndex.addHTMLFile({
11492
+ url: urlPath,
11493
+ content
11494
+ });
11495
+ indexed++;
11780
11496
  }
11497
+ } catch (error) {
11498
+ console.error(`Failed to index ${urlPath}:`, error);
11499
+ }
11500
+ yield {
11501
+ type: "progress",
11502
+ total: paths.length,
11503
+ current: indexed,
11504
+ path: urlPath
11781
11505
  };
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
11506
  }
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;
11507
+ const outputPath = path35.join(vite.config.publicDir, "pagefind");
11508
+ await pagefindIndex.writeFiles({ outputPath });
11509
+ yield { type: "complete", success: true, indexed };
11510
+ }
11511
+
11512
+ // src/vite/dev-server.ts
11513
+ var DEFAULT_DEV_PORT = 3e3;
11514
+ var DevServer = class {
11515
+ constructor(options) {
11516
+ this.options = options;
11801
11517
  }
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;
11518
+ terminator;
11519
+ resolvedPort = 0;
11520
+ protocol = "http";
11521
+ async createNodeServer(config2) {
11522
+ if (!config2.https) return http.createServer();
11523
+ this.protocol = "https";
11524
+ const { dir } = this.options;
11525
+ const [key, cert, ca] = await Promise.all([
11526
+ fs4.readFile(path36.resolve(dir, config2.https.key)),
11527
+ fs4.readFile(path36.resolve(dir, config2.https.cert)),
11528
+ config2.https.ca ? fs4.readFile(path36.resolve(dir, config2.https.ca)) : void 0
11529
+ ]);
11530
+ return https.createServer({ key, cert, ca });
11808
11531
  }
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"
11532
+ async start() {
11533
+ const configEnv = {
11534
+ mode: "development",
11535
+ command: "serve",
11536
+ isSsrBuild: this.options.ssr
11537
+ };
11538
+ const viteConfig = await getViteConfig(this.options.dir, configEnv);
11539
+ const { config: config2 } = await loadZudokuConfig(configEnv, this.options.dir);
11540
+ this.resolvedPort = await findAvailablePort(
11541
+ this.options.argPort ?? config2.port ?? DEFAULT_DEV_PORT
11542
+ );
11543
+ const server = await this.createNodeServer(config2);
11544
+ if (config2.search?.type === "pagefind" && viteConfig.publicDir !== false) {
11545
+ const publicRoot = path36.resolve(
11546
+ this.options.dir,
11547
+ typeof viteConfig.publicDir === "string" ? viteConfig.publicDir : "public"
11548
+ );
11549
+ await ensurePagefindDevStub(publicRoot);
11550
+ }
11551
+ const mergedViteConfig = mergeConfig4(viteConfig, {
11552
+ server: {
11553
+ hmr: { server }
11832
11554
  },
11833
- include: [
11834
- "*.config.ts",
11835
- "*.config.tsx",
11836
- "*.config.jsx",
11837
- "*.config.js",
11838
- "*.config.mjs",
11839
- "pages",
11840
- "landing",
11841
- "user-panel"
11555
+ plugins: [
11556
+ {
11557
+ // Serves the client entry via Vite's client transform pipeline.
11558
+ // Must be registered via configureServer so it runs before Vite's
11559
+ // built-in transform middleware which would treat the path as a static asset.
11560
+ name: "apitogo:entry-client",
11561
+ configureServer(server2) {
11562
+ const entryPath = path36.posix.join(
11563
+ server2.config.base,
11564
+ "/__z/entry.client.tsx"
11565
+ );
11566
+ server2.middlewares.use(entryPath, async (_req, res) => {
11567
+ const transformed = await server2.environments.client.transformRequest(
11568
+ getAppClientEntryPath()
11569
+ );
11570
+ if (!transformed) {
11571
+ res.writeHead(500);
11572
+ res.end("Error transforming client entry");
11573
+ return;
11574
+ }
11575
+ res.writeHead(200, { "Content-Type": "text/javascript" });
11576
+ res.end(transformed.code);
11577
+ });
11578
+ }
11579
+ }
11842
11580
  ]
11843
- };
11844
- await writeFile7(tsPath, `${JSON.stringify(tsconfig, null, 2)}
11845
- `, "utf8");
11846
- return true;
11581
+ });
11582
+ const vite = await createViteServer(mergedViteConfig);
11583
+ const graphql = createGraphQLServer({ graphqlEndpoint: "/__z/graphql" });
11584
+ vite.middlewares.use((req, res, next) => {
11585
+ if (req.method === "GET" && req.originalUrl === "/" && config2.basePath && config2.basePath !== "/") {
11586
+ res.writeHead(307, { Location: config2.basePath });
11587
+ res.end();
11588
+ return;
11589
+ }
11590
+ next();
11591
+ });
11592
+ vite.middlewares.use(graphql.graphqlEndpoint, graphql);
11593
+ vite.middlewares.use("/__z/pagefind-reindex", async (_req, res) => {
11594
+ res.writeHead(200, {
11595
+ "Content-Type": "text/event-stream",
11596
+ "Cache-Control": "no-cache",
11597
+ Connection: "keep-alive"
11598
+ });
11599
+ const { config: currentConfig } = await loadZudokuConfig(
11600
+ configEnv,
11601
+ this.options.dir
11602
+ );
11603
+ const sendEvent = (data) => res.write(`data: ${JSON.stringify(data)}
11604
+
11605
+ `);
11606
+ if (currentConfig.search?.type !== "pagefind") {
11607
+ sendEvent({
11608
+ type: "complete",
11609
+ success: false,
11610
+ indexed: 0,
11611
+ error: "Pagefind search is not enabled"
11612
+ });
11613
+ res.end();
11614
+ return;
11615
+ }
11616
+ try {
11617
+ for await (const event of buildPagefindDevIndex(vite, currentConfig)) {
11618
+ sendEvent(event);
11619
+ }
11620
+ } catch (error) {
11621
+ const message = error instanceof Error ? error.message : "Unknown error";
11622
+ sendEvent({
11623
+ type: "complete",
11624
+ success: false,
11625
+ indexed: 0,
11626
+ error: message
11627
+ });
11628
+ }
11629
+ res.end();
11630
+ });
11631
+ printDiagnosticsToConsole(
11632
+ `Server-side rendering ${this.options.ssr ? "enabled" : "disabled"}`
11633
+ );
11634
+ if (config2.search?.type === "pagefind" && vite.config.publicDir) {
11635
+ await ensurePagefindDevStub(vite.config.publicDir);
11636
+ }
11637
+ vite.middlewares.use(async (req, res) => {
11638
+ const url = req.originalUrl ?? req.url ?? "/";
11639
+ const ssrEnvironment = vite.environments.ssr;
11640
+ if (!isRunnableDevEnvironment2(ssrEnvironment)) {
11641
+ res.writeHead(500);
11642
+ res.end("SSR environment not available");
11643
+ return;
11644
+ }
11645
+ try {
11646
+ const { config: currentConfig } = await loadZudokuConfig(
11647
+ configEnv,
11648
+ this.options.dir
11649
+ );
11650
+ const rawHtml = getDevHtml({
11651
+ jsEntry: "/__z/entry.client.tsx",
11652
+ dir: currentConfig.site?.dir
11653
+ });
11654
+ const template = await vite.transformIndexHtml(url, rawHtml);
11655
+ if (this.options.ssr) {
11656
+ const entryServer = await ssrEnvironment.runner.import(
11657
+ getAppServerEntryPath()
11658
+ );
11659
+ const hasBody = req.method !== "GET" && req.method !== "HEAD";
11660
+ const request = new Request(
11661
+ `${this.protocol}://${req.headers.host}${url}`,
11662
+ {
11663
+ method: req.method,
11664
+ headers: req.headers,
11665
+ body: hasBody ? req : void 0,
11666
+ // Required by Node when body is a readable stream
11667
+ // @ts-expect-error Missing type definition
11668
+ duplex: hasBody ? "half" : void 0
11669
+ }
11670
+ );
11671
+ const response = await entryServer.handleRequest({
11672
+ template,
11673
+ request,
11674
+ routes: entryServer.getRoutesByConfig(currentConfig),
11675
+ basePath: currentConfig.basePath
11676
+ });
11677
+ for (const [key, value] of response.headers) {
11678
+ res.appendHeader(key, value);
11679
+ }
11680
+ res.writeHead(response.status);
11681
+ for await (const chunk of response.body ?? []) {
11682
+ res.write(chunk);
11683
+ }
11684
+ res.end();
11685
+ } else {
11686
+ res.writeHead(200, { "Content-Type": "text/html" });
11687
+ res.end(template);
11688
+ }
11689
+ } catch (e) {
11690
+ logger.error(e);
11691
+ const html = `<!DOCTYPE html><html><body><script type="module">
11692
+ import { ErrorOverlay } from '/@vite/client';
11693
+ document.body.appendChild(new ErrorOverlay(${JSON.stringify({
11694
+ message: e instanceof Error ? e.message : String(e),
11695
+ stack: e instanceof Error ? e.stack : ""
11696
+ })}));
11697
+ </script></body></html>`;
11698
+ res.writeHead(500, { "Content-Type": "text/html" });
11699
+ res.end(html);
11700
+ }
11701
+ });
11702
+ server.on("request", vite.middlewares);
11703
+ await new Promise((resolve) => {
11704
+ server.listen(this.resolvedPort, resolve);
11705
+ });
11706
+ this.terminator = createHttpTerminator({ server });
11707
+ if (this.options.open || process.env.ZUDOKU_OPEN_BROWSER) {
11708
+ const url = `${this.protocol}://localhost:${this.resolvedPort}`;
11709
+ vite.resolvedUrls = {
11710
+ local: [`${url}${vite.config.base || "/"}`],
11711
+ network: []
11712
+ };
11713
+ vite.openBrowser();
11714
+ }
11715
+ return { vite };
11847
11716
  }
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;
11717
+ async stop() {
11718
+ await this.terminator?.terminate();
11901
11719
  }
11902
11720
  };
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
11721
 
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(
11722
+ // src/cli/dev/handler.ts
11723
+ init_package_json();
11724
+ async function dev(argv) {
11725
+ const packageJson2 = getZudokuPackageJson();
11726
+ process.env.NODE_ENV = "development";
11727
+ const dir = path37.resolve(process.cwd(), argv.dir);
11728
+ const server = new DevServer({
11936
11729
  dir,
11937
- ".github",
11938
- "workflows",
11939
- "apitogo-deploy.yml"
11730
+ argPort: argv.port,
11731
+ ssr: argv.ssr,
11732
+ open: argv.open
11733
+ });
11734
+ const { vite } = await server.start();
11735
+ const url = joinUrl(
11736
+ `${server.protocol}://localhost:${server.resolvedPort}`,
11737
+ vite.config.base
11940
11738
  );
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(/\/+$/, "")
11739
+ printDiagnosticsToConsole("Ctrl+C to exit");
11740
+ printDiagnosticsToConsole("");
11741
+ printDiagnosticsToConsole(
11742
+ `\u{1F680} Running APIToGo v${packageJson2.version}: ${url}`
11960
11743
  );
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.");
11972
- }
11973
- 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");
11990
- }
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);
11744
+ printDiagnosticsToConsole("");
11745
+ let hasExited = false;
11746
+ return new Promise((resolve) => {
11747
+ function exit() {
11748
+ if (!hasExited) {
11749
+ hasExited = true;
11750
+ server.stop().then(() => {
11751
+ resolve();
11752
+ }).catch((e) => {
11753
+ console.error("Error stopping server", e);
11754
+ resolve();
11755
+ });
11756
+ }
12019
11757
  }
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
11758
+ process.on("SIGTERM", exit);
11759
+ process.on("SIGINT", exit);
11760
+ process.on("uncaughtException", (e) => {
11761
+ console.error("Uncaught exception", e);
11762
+ void exit();
11763
+ });
11764
+ process.on("unhandledRejection", (e) => {
11765
+ if (e instanceof DOMException && e.name === "AbortError") {
11766
+ console.log(`[Abort] ${e.message}`);
11767
+ return;
11768
+ }
11769
+ console.error("Unhandled rejection", e);
11770
+ void exit();
11771
+ });
11772
+ process.on("exit", exit);
11773
+ });
11774
+ }
11775
+
11776
+ // src/cli/cmds/dev.ts
11777
+ var dev_default = {
11778
+ desc: "Runs locally",
11779
+ command: "dev",
11780
+ builder: (yargs2) => {
11781
+ return yargs2.option("dir", {
11782
+ type: "string",
11783
+ describe: "The directory containing your project",
11784
+ default: ".",
11785
+ normalize: true,
11786
+ hidden: true
11787
+ }).option("port", {
11788
+ type: "number",
11789
+ describe: "The port to run the local server on"
11790
+ }).option("ssr", {
11791
+ type: "boolean",
11792
+ describe: "Enable server-side rendering",
11793
+ default: true
11794
+ }).option("open", {
11795
+ type: "boolean",
11796
+ describe: "Automatically open the browser",
11797
+ default: false
12025
11798
  });
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
- );
11799
+ },
11800
+ handler: async (argv) => {
11801
+ await captureEvent({
11802
+ argv,
11803
+ event: "apitogo dev"
11804
+ });
11805
+ await dev(argv);
11806
+ }
11807
+ };
11808
+
11809
+ // src/cli/init/handler.ts
11810
+ import path38 from "node:path";
11811
+ async function init2(argv) {
11812
+ const dir = path38.resolve(process.cwd(), argv.dir ?? ".");
11813
+ try {
11814
+ const createdPackageJson = await ensurePackageJson(dir);
11815
+ const createdTsconfig = await ensureTsconfigJson(dir);
11816
+ await ensurePortalStubFiles(dir);
11817
+ await ensureThinConfigTsx(dir);
11818
+ if (createdPackageJson) {
11819
+ printDiagnosticsToConsole("Created package.json");
12048
11820
  }
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
- }
11821
+ if (createdTsconfig) {
11822
+ printDiagnosticsToConsole("Created tsconfig.json");
12061
11823
  }
12062
- const action = createdJson ? "Initialized" : "Updated";
11824
+ await makeConfig({
11825
+ dir,
11826
+ pagesDir: "pages",
11827
+ title: argv.name,
11828
+ force: false
11829
+ });
12063
11830
  printResultToConsole(
12064
- `${action} ${APITOGO_JSON_FILENAME} (site/theme/auth/navigation/OpenAPI as applicable; redirects unchanged).`
11831
+ `Portal scaffolded in ${dir}. Next: apitogo config -i, apitogo dev, then apitogo apply.`
12065
11832
  );
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
11833
  } catch (error) {
12078
11834
  await printCriticalFailureToConsoleAndExit(
12079
11835
  error instanceof Error ? error.message : String(error)
@@ -12081,155 +11837,246 @@ async function makeConfig(argv) {
12081
11837
  }
12082
11838
  }
12083
11839
 
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", {
11840
+ // src/cli/cmds/init.ts
11841
+ var init_default = {
11842
+ desc: "Scaffold a local APItoGo developer portal (no platform calls)",
11843
+ command: "init [dir]",
11844
+ builder: (yargs2) => yargs2.positional("dir", {
12089
11845
  type: "string",
12090
- describe: "The directory containing your project",
11846
+ describe: "Directory to scaffold into",
12091
11847
  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", {
11848
+ normalize: true
11849
+ }).option("name", {
12113
11850
  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
11851
+ describe: "Package / site name"
12119
11852
  }),
12120
11853
  handler: async (argv) => {
12121
- await captureEvent({ argv, event: "apitogo make-config" });
12122
- await makeConfig(argv);
11854
+ await captureEvent({ argv, event: "apitogo init" });
11855
+ await init2(argv);
12123
11856
  }
12124
11857
  };
12125
11858
 
12126
11859
  // src/cli/cmds/preview.ts
12127
- var previewCommand = {
12128
- desc: "Preview production build",
11860
+ var REMOVED3 = "apitogo preview was removed. Use `apitogo build --preview` or `apitogo dev`.";
11861
+ var preview_default = {
11862
+ desc: "Removed \u2014 use apitogo build --preview",
12129
11863
  command: "preview",
11864
+ builder: (yargs2) => yargs2,
11865
+ handler: async (argv) => {
11866
+ await captureEvent({ argv, event: "apitogo preview removed" });
11867
+ await printCriticalFailureToConsoleAndExit(REMOVED3);
11868
+ }
11869
+ };
11870
+
11871
+ // src/cli/cmds/remove.ts
11872
+ var REMOVED4 = "apitogo remove was removed. Use `apitogo teardown --yes` to delete the platform project.";
11873
+ var remove_default = {
11874
+ desc: "Removed \u2014 use apitogo teardown",
11875
+ command: "remove",
11876
+ builder: (yargs2) => yargs2,
11877
+ handler: async (argv) => {
11878
+ await captureEvent({ argv, event: "apitogo remove removed" });
11879
+ await printCriticalFailureToConsoleAndExit(REMOVED4);
11880
+ }
11881
+ };
11882
+
11883
+ // src/cli/status/handler.ts
11884
+ import path40 from "node:path";
11885
+
11886
+ // src/cli/common/mcp-cli.ts
11887
+ import { access as access4 } from "node:fs/promises";
11888
+ import { spawn as spawn2 } from "node:child_process";
11889
+ import path39 from "node:path";
11890
+ async function pathExists2(candidate) {
11891
+ try {
11892
+ await access4(candidate);
11893
+ return true;
11894
+ } catch {
11895
+ return false;
11896
+ }
11897
+ }
11898
+ async function resolveApitogoMcpCliCommand(binary) {
11899
+ const binName = `apitogo-${binary}`;
11900
+ const mcpPath = process.env.APITOGO_MCP_PATH?.trim();
11901
+ if (mcpPath) {
11902
+ const scriptJs = path39.join(mcpPath, "dist", "cli", `${binary}.js`);
11903
+ if (await pathExists2(scriptJs)) {
11904
+ return {
11905
+ command: process.execPath,
11906
+ argsPrefix: [scriptJs],
11907
+ note: `Using local ${binName} from APITOGO_MCP_PATH=${mcpPath}`
11908
+ };
11909
+ }
11910
+ throw new Error(
11911
+ `APITOGO_MCP_PATH is set (${mcpPath}) but dist/cli/${binary}.js was not found. Run npm run build in the MCP checkout.`
11912
+ );
11913
+ }
11914
+ return {
11915
+ command: "npx",
11916
+ argsPrefix: ["--yes", "--package=@lukoweb/apitogo-mcp", binName],
11917
+ note: "Using npx @lukoweb/apitogo-mcp (set APITOGO_MCP_PATH for a local checkout)"
11918
+ };
11919
+ }
11920
+ async function runApitogoMcpCli(binary, childArgs, options = {}) {
11921
+ let resolved;
11922
+ try {
11923
+ resolved = await resolveApitogoMcpCliCommand(binary);
11924
+ } catch (error) {
11925
+ await printCriticalFailureToConsoleAndExit(
11926
+ error instanceof Error ? error.message : String(error)
11927
+ );
11928
+ return;
11929
+ }
11930
+ if (resolved.note && !options.quiet) {
11931
+ printDiagnosticsToConsole(resolved.note);
11932
+ }
11933
+ const exitCode = await new Promise((resolvePromise) => {
11934
+ const child = spawn2(
11935
+ resolved.command,
11936
+ [...resolved.argsPrefix, ...childArgs],
11937
+ {
11938
+ stdio: "inherit",
11939
+ env: process.env,
11940
+ shell: process.platform === "win32"
11941
+ }
11942
+ );
11943
+ child.on("error", (error) => {
11944
+ void printCriticalFailureToConsoleAndExit(
11945
+ `Failed to start apitogo-${binary}: ${error.message}`
11946
+ );
11947
+ resolvePromise(1);
11948
+ });
11949
+ child.on("close", (code) => {
11950
+ resolvePromise(code ?? 1);
11951
+ });
11952
+ });
11953
+ if (exitCode !== 0) {
11954
+ process.exit(exitCode);
11955
+ }
11956
+ }
11957
+
11958
+ // src/cli/status/handler.ts
11959
+ async function status(argv) {
11960
+ const dir = path40.resolve(process.cwd(), argv.dir);
11961
+ const args = [dir];
11962
+ if (argv.projectId?.trim()) {
11963
+ args.push("--project-id", argv.projectId.trim());
11964
+ }
11965
+ if (argv.skipLive) {
11966
+ args.push("--skip-live");
11967
+ }
11968
+ if (argv.json) {
11969
+ args.push("--json");
11970
+ }
11971
+ await runApitogoMcpCli("status", args, { quiet: Boolean(argv.json) });
11972
+ }
11973
+
11974
+ // src/cli/cmds/status.ts
11975
+ var status_default = {
11976
+ desc: "Show account, project, and live readiness for the linked APItoGo project",
11977
+ command: "status",
12130
11978
  builder: (yargs2) => yargs2.option("dir", {
12131
11979
  type: "string",
12132
- describe: "The directory containing your project",
11980
+ describe: "Site directory containing apitogo.config.json",
12133
11981
  default: ".",
12134
- normalize: true,
12135
- hidden: true
12136
- }).option("port", {
12137
- type: "number",
12138
- describe: "The port to run the local server on"
11982
+ normalize: true
11983
+ }).option("project-id", {
11984
+ type: "string",
11985
+ describe: "Override project GUID"
11986
+ }).option("skip-live", {
11987
+ type: "boolean",
11988
+ describe: "Skip live health checks",
11989
+ default: false
11990
+ }).option("json", {
11991
+ type: "boolean",
11992
+ describe: "Print raw JSON only",
11993
+ default: false
12139
11994
  }),
12140
11995
  handler: async (argv) => {
12141
- process.env.NODE_ENV = "production";
12142
- await captureEvent({ argv, event: "apitogo preview" });
12143
- await preview(argv);
11996
+ await captureEvent({ argv, event: "apitogo status" });
11997
+ await status(argv);
12144
11998
  }
12145
11999
  };
12146
- var preview_default = previewCommand;
12147
12000
 
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
- }
12001
+ // src/cli/teardown/handler.ts
12002
+ import path41 from "node:path";
12003
+ async function teardown(argv) {
12004
+ if (!argv.yes) {
12005
+ await printCriticalFailureToConsoleAndExit(
12006
+ "Refusing to teardown without --yes."
12007
+ );
12008
+ return;
12009
+ }
12010
+ const dir = path41.resolve(process.cwd(), argv.dir);
12011
+ const args = [dir, "--yes"];
12012
+ if (argv.projectId?.trim()) {
12013
+ args.push("--project-id", argv.projectId.trim());
12198
12014
  }
12015
+ if (argv.wait === false) {
12016
+ args.push("--no-wait");
12017
+ }
12018
+ if (argv.json) {
12019
+ args.push("--json");
12020
+ }
12021
+ await runApitogoMcpCli("teardown", args, { quiet: Boolean(argv.json) });
12199
12022
  }
12200
12023
 
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", {
12024
+ // src/cli/cmds/teardown.ts
12025
+ var teardown_default = {
12026
+ desc: "Delete the linked APItoGo project",
12027
+ command: "teardown",
12028
+ builder: (yargs2) => yargs2.option("dir", {
12206
12029
  type: "string",
12207
- describe: "Deployment token sent to the remove endpoint"
12208
- }).option("env", {
12030
+ describe: "Site directory containing apitogo.config.json",
12031
+ default: ".",
12032
+ normalize: true
12033
+ }).option("project-id", {
12209
12034
  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", {
12035
+ describe: "Override project GUID"
12036
+ }).option("yes", {
12037
+ alias: "y",
12038
+ type: "boolean",
12039
+ describe: "Confirm teardown (required)",
12040
+ default: false
12041
+ }).option("wait", {
12042
+ type: "boolean",
12043
+ describe: "Wait for deletion to finish",
12044
+ default: true
12045
+ }).option("json", {
12213
12046
  type: "boolean",
12214
- describe: "JSON-only output on stdout; adds ?jsonOnly=true to the remove request",
12047
+ describe: "Print raw JSON only",
12215
12048
  default: false
12216
12049
  }),
12217
12050
  handler: async (argv) => {
12218
- if (argv.jsonOnly) {
12219
- applyJsonOnlyDeployQuietMode();
12220
- }
12221
- await captureEvent({ argv, event: "apitogo remove" });
12222
- await remove(argv);
12051
+ await captureEvent({ argv, event: "apitogo teardown" });
12052
+ await teardown(argv);
12223
12053
  }
12224
12054
  };
12225
12055
 
12226
12056
  // src/cli/common/outdated.ts
12227
12057
  import { existsSync as existsSync3, mkdirSync } from "node:fs";
12228
- import { readFile as readFile7, writeFile as writeFile9 } from "node:fs/promises";
12058
+ import { readFile as readFile6, writeFile as writeFile8 } from "node:fs/promises";
12229
12059
  import { join } from "node:path";
12230
12060
  import colors10 from "picocolors";
12231
12061
  import { gt } from "semver";
12232
12062
 
12063
+ // src/cli/common/json-only-deploy.ts
12064
+ function isDeployJsonOnlyArgv() {
12065
+ const args = process.argv;
12066
+ let cmdIdx = -1;
12067
+ for (const cmd of ["deploy", "remove"]) {
12068
+ const i = args.indexOf(cmd);
12069
+ if (i !== -1) {
12070
+ cmdIdx = i;
12071
+ break;
12072
+ }
12073
+ }
12074
+ if (cmdIdx === -1) {
12075
+ return false;
12076
+ }
12077
+ return args.slice(cmdIdx).some((a) => a === "--json-only" || a.startsWith("--json-only="));
12078
+ }
12079
+
12233
12080
  // src/cli/common/utils/box.ts
12234
12081
  import colors9 from "picocolors";
12235
12082
  import stripAnsi from "strip-ansi";
@@ -12275,12 +12122,12 @@ function box(message, {
12275
12122
 
12276
12123
  // src/cli/common/xdg/lib.ts
12277
12124
  import { homedir } from "node:os";
12278
- import path40 from "node:path";
12125
+ import path42 from "node:path";
12279
12126
  function defineDirectoryWithFallback(xdgName, fallback) {
12280
12127
  if (process.env[xdgName]) {
12281
12128
  return process.env[xdgName];
12282
12129
  } else {
12283
- return path40.join(homedir(), fallback);
12130
+ return path42.join(homedir(), fallback);
12284
12131
  }
12285
12132
  }
12286
12133
  var XDG_CONFIG_HOME = defineDirectoryWithFallback(
@@ -12295,15 +12142,15 @@ var XDG_STATE_HOME = defineDirectoryWithFallback(
12295
12142
  "XDG_DATA_HOME",
12296
12143
  ".local/state"
12297
12144
  );
12298
- var ZUDOKU_XDG_CONFIG_HOME = path40.join(
12145
+ var ZUDOKU_XDG_CONFIG_HOME = path42.join(
12299
12146
  XDG_CONFIG_HOME,
12300
12147
  CLI_XDG_FOLDER_NAME
12301
12148
  );
12302
- var ZUDOKU_XDG_DATA_HOME = path40.join(
12149
+ var ZUDOKU_XDG_DATA_HOME = path42.join(
12303
12150
  XDG_DATA_HOME,
12304
12151
  CLI_XDG_FOLDER_NAME
12305
12152
  );
12306
- var ZUDOKU_XDG_STATE_HOME = path40.join(
12153
+ var ZUDOKU_XDG_STATE_HOME = path42.join(
12307
12154
  XDG_STATE_HOME,
12308
12155
  CLI_XDG_FOLDER_NAME
12309
12156
  );
@@ -12355,7 +12202,7 @@ async function getVersionCheckInfo() {
12355
12202
  let versionCheckInfo;
12356
12203
  if (existsSync3(versionCheckPath)) {
12357
12204
  try {
12358
- versionCheckInfo = await readFile7(versionCheckPath, "utf-8").then(
12205
+ versionCheckInfo = await readFile6(versionCheckPath, "utf-8").then(
12359
12206
  JSON.parse
12360
12207
  );
12361
12208
  } catch {
@@ -12380,7 +12227,7 @@ async function getVersionCheckInfo() {
12380
12227
  lastCheck: Date.now(),
12381
12228
  latestVersion
12382
12229
  };
12383
- await writeFile9(
12230
+ await writeFile8(
12384
12231
  versionCheckPath,
12385
12232
  JSON.stringify(versionCheckInfo),
12386
12233
  "utf-8"
@@ -12467,7 +12314,7 @@ var cli = yargs(hideBin(process.argv)).option("zuplo", {
12467
12314
  process.env.ZUPLO = "1";
12468
12315
  printDiagnosticsToConsole("Running in Zuplo mode");
12469
12316
  }
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();
12317
+ }).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
12318
  try {
12472
12319
  void warnIfOutdatedVersion(packageJson?.version);
12473
12320
  await cli.argv;