@lukoweb/apitogo 0.1.75 → 0.1.77

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
@@ -4940,12 +4940,12 @@ async function loadZudokuConfigWithMeta(rootDir) {
4940
4940
  };
4941
4941
  return configWithMetadata;
4942
4942
  }
4943
- function findOutputPathOfServerConfig(output) {
4944
- if (Array.isArray(output)) {
4943
+ function findOutputPathOfServerConfig(output2) {
4944
+ if (Array.isArray(output2)) {
4945
4945
  throw new Error("Expected a single output, but got an array");
4946
4946
  }
4947
- if ("output" in output) {
4948
- const result = output.output.find(
4947
+ if ("output" in output2) {
4948
+ const result = output2.output.find(
4949
4949
  (o) => "isEntry" in o && o.isEntry && serverConfigOutputFiles.includes(o.fileName)
4950
4950
  );
4951
4951
  if (result) {
@@ -5644,7 +5644,7 @@ import {
5644
5644
  // package.json
5645
5645
  var package_default = {
5646
5646
  name: "@lukoweb/apitogo",
5647
- version: "0.1.75",
5647
+ version: "0.1.77",
5648
5648
  type: "module",
5649
5649
  sideEffects: [
5650
5650
  "**/*.css",
@@ -7465,13 +7465,13 @@ var generateCode = (schema2, filePath) => {
7465
7465
 
7466
7466
  // src/vite/api/SchemaManager.ts
7467
7467
  var FALLBACK_VERSION = "default";
7468
- var parseInputString = (input) => {
7469
- const idx = input.indexOf("?");
7468
+ var parseInputString = (input2) => {
7469
+ const idx = input2.indexOf("?");
7470
7470
  if (idx === -1)
7471
- return { filePath: input, params: {} };
7471
+ return { filePath: input2, params: {} };
7472
7472
  return {
7473
- filePath: input.slice(0, idx),
7474
- params: Object.fromEntries(new URLSearchParams(input.slice(idx)))
7473
+ filePath: input2.slice(0, idx),
7474
+ params: Object.fromEntries(new URLSearchParams(input2.slice(idx)))
7475
7475
  };
7476
7476
  };
7477
7477
  var canonicalParams = (params) => Object.entries(params).sort(([a], [b]) => a.localeCompare(b)).map(([k, v]) => `${k}=${v}`).join("&");
@@ -7507,8 +7507,8 @@ var SchemaManager = class {
7507
7507
  ...processors
7508
7508
  ];
7509
7509
  }
7510
- getPathForFile = (input, params) => {
7511
- const filePath = path11.resolve(this.config.__meta.rootDir, input);
7510
+ getPathForFile = (input2, params) => {
7511
+ const filePath = path11.resolve(this.config.__meta.rootDir, input2);
7512
7512
  const apis = ensureArray(this.config.apis ?? []);
7513
7513
  for (const apiConfig of apis) {
7514
7514
  if (!apiConfig || apiConfig.type !== "file" || !apiConfig.path) continue;
@@ -7518,12 +7518,12 @@ var SchemaManager = class {
7518
7518
  if (match) return apiConfig.path;
7519
7519
  }
7520
7520
  };
7521
- processSchema = async (input) => {
7522
- const filePath = path11.resolve(this.config.__meta.rootDir, input.input);
7523
- const params = input.params;
7524
- const configuredPath = this.getPathForFile(input.input, params);
7521
+ processSchema = async (input2) => {
7522
+ const filePath = path11.resolve(this.config.__meta.rootDir, input2.input);
7523
+ const params = input2.params;
7524
+ const configuredPath = this.getPathForFile(input2.input, params);
7525
7525
  if (!configuredPath) {
7526
- console.warn(`No path found for file ${input.input}`);
7526
+ console.warn(`No path found for file ${input2.input}`);
7527
7527
  return;
7528
7528
  }
7529
7529
  const parser = new $RefParser2();
@@ -7630,20 +7630,20 @@ var SchemaManager = class {
7630
7630
  if (apiConfig.type !== "file" || !apiConfig.path) continue;
7631
7631
  const inputs = normalizeInputs(apiConfig.input);
7632
7632
  if (inputs.length === 0) throw new Error("No schema found");
7633
- this.processedSchemas[apiConfig.path] = inputs.map((input) => ({
7633
+ this.processedSchemas[apiConfig.path] = inputs.map((input2) => ({
7634
7634
  schema: {},
7635
7635
  version: "",
7636
- path: input.path ?? "",
7637
- label: input.label,
7638
- inputPath: path11.resolve(this.config.__meta.rootDir, input.input),
7639
- params: input.params,
7636
+ path: input2.path ?? "",
7637
+ label: input2.label,
7638
+ inputPath: path11.resolve(this.config.__meta.rootDir, input2.input),
7639
+ params: input2.params,
7640
7640
  importKey: "",
7641
7641
  downloadUrl: "",
7642
7642
  processedJsonPath: "",
7643
7643
  processedTime: 0
7644
7644
  }));
7645
7645
  const results = await Promise.allSettled(
7646
- inputs.map((input) => this.processSchema(input))
7646
+ inputs.map((input2) => this.processSchema(input2))
7647
7647
  );
7648
7648
  const errors = results.flatMap(
7649
7649
  (r) => r.status === "rejected" ? r.reason : []
@@ -7722,7 +7722,7 @@ import { readFile } from "node:fs/promises";
7722
7722
  import matter from "gray-matter";
7723
7723
  import { parse, stringify as stringify2 } from "yaml";
7724
7724
  var yaml = {
7725
- parse: (input) => parse(input) ?? {},
7725
+ parse: (input2) => parse(input2) ?? {},
7726
7726
  stringify: (obj) => stringify2(obj)
7727
7727
  };
7728
7728
  var readFrontmatter = async (filePath) => {
@@ -9938,25 +9938,25 @@ function generateOutput({
9938
9938
  });
9939
9939
  }
9940
9940
  }
9941
- const output = {
9941
+ const output2 = {
9942
9942
  version: 3,
9943
9943
  framework: {
9944
9944
  version: pkgJson.version
9945
9945
  },
9946
9946
  routes
9947
9947
  };
9948
- return output;
9948
+ return output2;
9949
9949
  }
9950
9950
  async function writeOutput(dir, {
9951
9951
  config: config2,
9952
9952
  redirects,
9953
9953
  rewrites
9954
9954
  }) {
9955
- const output = generateOutput({ config: config2, redirects, rewrites });
9955
+ const output2 = generateOutput({ config: config2, redirects, rewrites });
9956
9956
  const outputDir = process.env.VERCEL ? path26.join(dir, ".vercel/output") : path26.join(dir, "dist/.output");
9957
9957
  await mkdir3(outputDir, { recursive: true });
9958
9958
  const outputFile = path26.join(outputDir, "config.json");
9959
- await writeFile3(outputFile, JSON.stringify(output, null, 2), "utf-8");
9959
+ await writeFile3(outputFile, JSON.stringify(output2, null, 2), "utf-8");
9960
9960
  if (process.env.VERCEL) {
9961
9961
  console.log("Wrote Vercel output to", outputDir);
9962
9962
  }
@@ -9977,12 +9977,12 @@ init_invariant();
9977
9977
  init_joinUrl();
9978
9978
 
9979
9979
  // src/vite/reporter.ts
9980
- function writeLine(output) {
9980
+ function writeLine(output2) {
9981
9981
  clearLine();
9982
- if (output.length < process.stdout.columns) {
9983
- process.stdout.write(output);
9982
+ if (output2.length < process.stdout.columns) {
9983
+ process.stdout.write(output2);
9984
9984
  } else {
9985
- process.stdout.write(output.substring(0, process.stdout.columns - 1));
9985
+ process.stdout.write(output2.substring(0, process.stdout.columns - 1));
9986
9986
  }
9987
9987
  }
9988
9988
  function isTTY() {
@@ -11240,14 +11240,473 @@ var dev_default = {
11240
11240
 
11241
11241
  // src/cli/make-config/handler.ts
11242
11242
  import { constants as constants3 } from "node:fs";
11243
- import { access as access5, mkdir as mkdir7, readFile as readFile6, writeFile as writeFile8 } from "node:fs/promises";
11243
+ import { access as access5, mkdir as mkdir8, readFile as readFile6, writeFile as writeFile8 } from "node:fs/promises";
11244
11244
  import path39 from "node:path";
11245
11245
  import { glob as glob5 } from "glob";
11246
11246
 
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";
11262
+ };
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
+ }
11337
+ }
11338
+
11339
+ // src/cli/make-config/json-config.ts
11340
+ var APITOGO_JSON_FILENAME = "apitogo.config.json";
11341
+ var DOCUMENTATION_NAV_LABEL = "Documentation";
11342
+ var API_REFERENCE_PATH = "/api";
11343
+ var API_REFERENCE_LABEL = "API Reference";
11344
+ function isPlainObject2(value) {
11345
+ return value !== null && typeof value === "object" && !Array.isArray(value);
11346
+ }
11347
+ function deepMerge(target, source) {
11348
+ const result = { ...target };
11349
+ for (const [key, value] of Object.entries(source)) {
11350
+ const existing = result[key];
11351
+ if (isPlainObject2(existing) && isPlainObject2(value)) {
11352
+ result[key] = deepMerge(existing, value);
11353
+ } else {
11354
+ result[key] = value;
11355
+ }
11356
+ }
11357
+ return result;
11358
+ }
11359
+ function parseJsonObjectFlag(raw, flagName) {
11360
+ if (raw === void 0 || raw.trim() === "") {
11361
+ return void 0;
11362
+ }
11363
+ let parsed;
11364
+ try {
11365
+ parsed = JSON.parse(raw);
11366
+ } catch {
11367
+ throw new Error(`--${flagName} must be a JSON object.`);
11368
+ }
11369
+ if (!isPlainObject2(parsed)) {
11370
+ throw new Error(`--${flagName} must be a JSON object.`);
11371
+ }
11372
+ return parsed;
11373
+ }
11374
+ function titleFromDirectoryName(dirName) {
11375
+ const titled = dirName.split(/[-_\s]+/).filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join(" ");
11376
+ return titled || "Developer Portal";
11377
+ }
11378
+ function createDefaultApitogoJsonConfig(options) {
11379
+ const filesGlob = `/${options.pagesDir}/**/*.{md,mdx}`;
11380
+ return {
11381
+ site: {
11382
+ title: options.title,
11383
+ logo: {
11384
+ src: { light: "/logo-light.png", dark: "/logo-dark.png" },
11385
+ alt: "APIToGo",
11386
+ width: "130px"
11387
+ }
11388
+ },
11389
+ authentication: {
11390
+ enabled: false,
11391
+ type: "dev-portal",
11392
+ redirectToAfterSignIn: "/account/dashboard",
11393
+ redirectToAfterSignUp: "/account/dashboard"
11394
+ },
11395
+ metadata: {
11396
+ favicon: "/Icon.png"
11397
+ },
11398
+ theme: {
11399
+ fonts: {
11400
+ sans: "IBM Plex Sans",
11401
+ mono: "JetBrains Mono"
11402
+ },
11403
+ light: {
11404
+ background: "#F5F6F9",
11405
+ foreground: "#0E1016",
11406
+ card: "#FFFFFF",
11407
+ cardForeground: "#0E1016",
11408
+ primary: "#E11D2A",
11409
+ primaryForeground: "#ffffff",
11410
+ secondary: "#FAFBFC",
11411
+ secondaryForeground: "#0E1016",
11412
+ muted: "#FAFBFC",
11413
+ mutedForeground: "#565E70",
11414
+ accent: "rgba(225, 29, 42, 0.08)",
11415
+ accentForeground: "#E11D2A",
11416
+ border: "#E6E8EF",
11417
+ input: "#D6DAE3",
11418
+ ring: "#E11D2A"
11419
+ },
11420
+ dark: {
11421
+ background: "#0B0D12",
11422
+ foreground: "#EDEFF5",
11423
+ card: "#14171F",
11424
+ cardForeground: "#EDEFF5",
11425
+ primary: "#F23645",
11426
+ primaryForeground: "#ffffff",
11427
+ secondary: "#191D27",
11428
+ secondaryForeground: "#EDEFF5",
11429
+ muted: "#191D27",
11430
+ mutedForeground: "#A6AEBF",
11431
+ accent: "rgba(242, 54, 69, 0.13)",
11432
+ accentForeground: "#F23645",
11433
+ border: "#252B37",
11434
+ input: "#333A48",
11435
+ ring: "#F23645"
11436
+ }
11437
+ },
11438
+ modules: {
11439
+ docs: {
11440
+ files: filesGlob
11441
+ },
11442
+ landing: {
11443
+ enabled: true,
11444
+ path: "/",
11445
+ layout: "landing",
11446
+ page: "./landing/LandingPage.tsx"
11447
+ },
11448
+ userPanel: {
11449
+ enabled: true,
11450
+ dashboard: { enabled: true },
11451
+ userManagement: { enabled: true },
11452
+ plans: {
11453
+ enabled: false,
11454
+ page: "./user-panel/AccountPlansPage.tsx"
11455
+ }
11456
+ }
11457
+ }
11458
+ };
11459
+ }
11460
+ function applyConfigPatches(config2, patches) {
11461
+ let next = { ...config2 };
11462
+ if (patches.site) {
11463
+ next.site = deepMerge(
11464
+ isPlainObject2(next.site) ? next.site : {},
11465
+ patches.site
11466
+ );
11467
+ }
11468
+ if (patches.theme) {
11469
+ next.theme = deepMerge(
11470
+ isPlainObject2(next.theme) ? next.theme : {},
11471
+ patches.theme
11472
+ );
11473
+ }
11474
+ if (patches.authentication) {
11475
+ next.authentication = deepMerge(
11476
+ isPlainObject2(next.authentication) ? next.authentication : {},
11477
+ patches.authentication
11478
+ );
11479
+ }
11480
+ if (patches.title?.trim()) {
11481
+ const site = isPlainObject2(next.site) ? next.site : {};
11482
+ next.site = { ...site, title: patches.title.trim() };
11483
+ }
11484
+ return next;
11485
+ }
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;
11493
+ };
11494
+ var sortDocPaths = (docPaths) => [...docPaths].sort((a, b) => {
11495
+ const rankDiff = docSortRank(a) - docSortRank(b);
11496
+ if (rankDiff !== 0) return rankDiff;
11497
+ return a.localeCompare(b);
11498
+ });
11499
+ var createTreeNode = () => ({ docs: [], children: /* @__PURE__ */ new Map() });
11500
+ var titleCase = (value) => value.split(/[-_\s]+/).filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join(" ");
11501
+ var buildTree = (routePaths) => {
11502
+ const root = createTreeNode();
11503
+ for (const routePath of routePaths) {
11504
+ const segments = routePath.replace(/^\//, "").split("/").filter(Boolean);
11505
+ const directorySegments = segments.slice(0, -1);
11506
+ let current = root;
11507
+ for (const segment of directorySegments) {
11508
+ let child = current.children.get(segment);
11509
+ if (!child) {
11510
+ child = createTreeNode();
11511
+ current.children.set(segment, child);
11512
+ }
11513
+ current = child;
11514
+ }
11515
+ current.docs.push(routePath);
11516
+ }
11517
+ return root;
11518
+ };
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}'.`);
11525
+ }
11526
+ return {
11527
+ type: "category",
11528
+ label: titleCase(directoryName),
11529
+ items: treeToNavigationItems(childNode)
11530
+ };
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;
11559
+ }
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"
11601
+ }
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;
11617
+ }
11618
+ return `./${specRelativePosix}`;
11619
+ }
11620
+ function toRoutePath(relativeFile, pagesDir) {
11621
+ const relativeNoExt = relativeFile.replace(/\.(mdx?|tsx|jsx)$/i, "");
11622
+ const escaped = pagesDir.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
11623
+ const withoutPagesPrefix = relativeNoExt.replace(
11624
+ new RegExp(`^${escaped}/?`),
11625
+ ""
11626
+ );
11627
+ const normalized = withoutPagesPrefix.replace(/\/index$/, "");
11628
+ return `/${normalized || "index"}`;
11629
+ }
11630
+ function pageComponentName(slug) {
11631
+ const pascal = slug.replace(/^\/+/, "").split(/[^a-zA-Z0-9]+/).filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
11632
+ const name = /^[A-Za-z]/.test(pascal) ? pascal : `Page${pascal}`;
11633
+ return name.endsWith("Page") ? name : `${name}Page`;
11634
+ }
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
+
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;
11650
+ }
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
+ };
11703
+ `;
11704
+ }
11705
+
11247
11706
  // src/cli/make-config/scaffold-project.ts
11248
11707
  init_package_json();
11249
11708
  import { constants as constants2 } from "node:fs";
11250
- import { access as access4, readFile as readFile5, writeFile as writeFile7 } from "node:fs/promises";
11709
+ import { access as access4, mkdir as mkdir7, readFile as readFile5, writeFile as writeFile7 } from "node:fs/promises";
11251
11710
  import path38 from "node:path";
11252
11711
  import { glob as glob4 } from "glob";
11253
11712
  var OPENAPI_GLOBS = [
@@ -11258,56 +11717,6 @@ var OPENAPI_GLOBS = [
11258
11717
  "openapi.yaml",
11259
11718
  "openapi.yml"
11260
11719
  ];
11261
- var findMatchingCurly = (source, startIndex) => {
11262
- let depth = 0;
11263
- let inSingleQuote = false;
11264
- let inDoubleQuote = false;
11265
- let inTemplate = false;
11266
- let inLineComment = false;
11267
- let inBlockComment = false;
11268
- for (let index = startIndex; index < source.length; index++) {
11269
- const char = source[index];
11270
- const next = source[index + 1];
11271
- const prev = source[index - 1];
11272
- if (inLineComment) {
11273
- if (char === "\n") inLineComment = false;
11274
- continue;
11275
- }
11276
- if (inBlockComment) {
11277
- if (prev === "*" && char === "/") inBlockComment = false;
11278
- continue;
11279
- }
11280
- if (!inSingleQuote && !inDoubleQuote && !inTemplate) {
11281
- if (char === "/" && next === "/") {
11282
- inLineComment = true;
11283
- index++;
11284
- continue;
11285
- }
11286
- if (char === "/" && next === "*") {
11287
- inBlockComment = true;
11288
- index++;
11289
- continue;
11290
- }
11291
- }
11292
- if (!inDoubleQuote && !inTemplate && char === "'" && prev !== "\\") {
11293
- inSingleQuote = !inSingleQuote;
11294
- continue;
11295
- }
11296
- if (!inSingleQuote && !inTemplate && char === '"' && prev !== "\\") {
11297
- inDoubleQuote = !inDoubleQuote;
11298
- continue;
11299
- }
11300
- if (!inSingleQuote && !inDoubleQuote && char === "`" && prev !== "\\") {
11301
- inTemplate = !inTemplate;
11302
- continue;
11303
- }
11304
- if (inSingleQuote || inDoubleQuote || inTemplate) continue;
11305
- if (char === "{") depth++;
11306
- if (char === "}") depth--;
11307
- if (depth === 0) return index;
11308
- }
11309
- throw new Error("Could not find matching closing brace for object.");
11310
- };
11311
11720
  var toPosix = (p) => p.split(path38.sep).join("/");
11312
11721
  var sanitizePackageName = (dir) => {
11313
11722
  const base = path38.basename(path38.resolve(dir));
@@ -11340,7 +11749,11 @@ async function ensurePackageJson(dir) {
11340
11749
  const baseDeps = {
11341
11750
  react: ">=19.0.0",
11342
11751
  "react-dom": ">=19.0.0",
11343
- "@lukoweb/apitogo": apitogoVer
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"
11344
11757
  };
11345
11758
  const baseScripts = {
11346
11759
  dev: "apitogo dev",
@@ -11423,7 +11836,9 @@ async function ensureTsconfigJson(dir) {
11423
11836
  "*.config.jsx",
11424
11837
  "*.config.js",
11425
11838
  "*.config.mjs",
11426
- "pages"
11839
+ "pages",
11840
+ "landing",
11841
+ "user-panel"
11427
11842
  ]
11428
11843
  };
11429
11844
  await writeFile7(tsPath, `${JSON.stringify(tsconfig, null, 2)}
@@ -11431,298 +11846,90 @@ async function ensureTsconfigJson(dir) {
11431
11846
  return true;
11432
11847
  }
11433
11848
  }
11434
- function insertApisIfMissing(source, openapiInput) {
11435
- if (/\bapis\s*:/.test(source)) return source;
11436
- const apisBlock = [
11437
- " apis: {",
11438
- ' type: "file",',
11439
- ` input: "${openapiInput.replace(/"/g, '\\"')}",`,
11440
- ' path: "api",',
11441
- " },"
11442
- ].join("\n");
11443
- const docsMatch = source.match(/\bdocs\s*:\s*\{/);
11444
- if (!docsMatch || docsMatch.index === void 0) {
11445
- const m = source.match(/(const\s+config\s*:\s*ApitogoConfig\s*=\s*\{)/);
11446
- if (!m || m.index === void 0) {
11447
- return source;
11448
- }
11449
- const open = m.index + m[0].length;
11450
- return `${source.slice(0, open)}
11451
- ${apisBlock}
11452
- ${source.slice(open)}`;
11453
- }
11454
- const openBrace = source.indexOf("{", docsMatch.index);
11455
- const closeBrace = findMatchingCurly(source, openBrace);
11456
- let p = closeBrace + 1;
11457
- while (p < source.length && /\s/.test(source[p])) p++;
11458
- if (source[p] === ",") p++;
11459
- while (p < source.length && /\s/.test(source[p])) p++;
11460
- return `${source.slice(0, p)}${apisBlock}
11461
- ${source.slice(p)}`;
11462
- }
11463
- function openapiInputForConfig(specRelativePosix) {
11464
- if (specRelativePosix.startsWith("./")) return specRelativePosix;
11465
- return `./${specRelativePosix}`;
11466
- }
11467
-
11468
- // src/cli/make-config/handler.ts
11469
- var CONFIG_FILENAMES = [
11470
- "apitogo.config.tsx",
11471
- "apitogo.config.ts",
11472
- "apitogo.config.jsx",
11473
- "apitogo.config.js",
11474
- "apitogo.config.mjs",
11475
- "zudoku.config.tsx",
11476
- "zudoku.config.ts",
11477
- "zudoku.config.jsx",
11478
- "zudoku.config.js",
11479
- "zudoku.config.mjs"
11480
- ];
11481
- var DEFAULT_NEW_CONFIG_FILENAME = "apitogo.config.tsx";
11482
- var createTreeNode = () => ({ docs: [], children: /* @__PURE__ */ new Map() });
11483
- var toPosixPath2 = (value) => value.split(path39.sep).join(path39.posix.sep);
11484
- var toRoutePath = (relativeFile, pagesDir) => {
11485
- const relativeNoExt = relativeFile.replace(/\.mdx?$/, "");
11486
- const withoutPagesPrefix = relativeNoExt.replace(
11487
- new RegExp(`^${pagesDir.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}/?`),
11488
- ""
11489
- );
11490
- const normalized = withoutPagesPrefix.replace(/\/index$/, "");
11491
- return `/${normalized || "index"}`;
11492
- };
11493
- var titleCase = (value) => value.split(/[-_\s]+/).filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join(" ");
11494
- var docSortRank = (docPath) => {
11495
- const lastSegment = docPath.split("/").at(-1) ?? docPath;
11496
- if (lastSegment === "introduction") return 0;
11497
- if (lastSegment === "example") return 1;
11498
- if (lastSegment === "getting-started") return 2;
11499
- if (lastSegment === "index") return 3;
11500
- return 4;
11501
- };
11502
- var sortDocPaths = (docPaths) => [...docPaths].sort((a, b) => {
11503
- const rankDiff = docSortRank(a) - docSortRank(b);
11504
- if (rankDiff !== 0) return rankDiff;
11505
- return a.localeCompare(b);
11506
- });
11507
- var sortDirectoryNames = (names) => [...names].sort((a, b) => a.localeCompare(b));
11508
- var buildTree = (routePaths) => {
11509
- const root = createTreeNode();
11510
- for (const routePath of routePaths) {
11511
- const segments = routePath.replace(/^\//, "").split("/").filter(Boolean);
11512
- const directorySegments = segments.slice(0, -1);
11513
- let current = root;
11514
- for (const segment of directorySegments) {
11515
- let child = current.children.get(segment);
11516
- if (!child) {
11517
- child = createTreeNode();
11518
- current.children.set(segment, child);
11519
- }
11520
- current = child;
11521
- }
11522
- current.docs.push(routePath);
11523
- }
11524
- return root;
11525
- };
11526
- var treeToNavigationItems = (node) => {
11527
- const docs = sortDocPaths(node.docs);
11528
- const categories = sortDirectoryNames([...node.children.keys()]).map(
11529
- (directoryName) => {
11530
- const childNode = node.children.get(directoryName);
11531
- if (!childNode) {
11532
- throw new Error(`Missing child node for directory '${directoryName}'.`);
11533
- }
11534
- return {
11535
- type: "category",
11536
- label: titleCase(directoryName),
11537
- items: treeToNavigationItems(childNode)
11538
- };
11539
- }
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>
11540
11890
  );
11541
- return [...docs, ...categories];
11542
- };
11543
- var formatNavigationItem = (item, indentLevel) => {
11544
- const indent = " ".repeat(indentLevel);
11545
- if (typeof item === "string") {
11546
- return `${indent}"${item}"`;
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;
11547
11901
  }
11548
- const items = item.items.map(
11549
- (subItem) => formatNavigationItem(subItem, indentLevel + 2)
11550
- );
11551
- return [
11552
- `${indent}{`,
11553
- `${indent} type: "category",`,
11554
- `${indent} label: "${item.label}",`,
11555
- `${indent} items: [`,
11556
- items.join(",\n"),
11557
- `${indent} ],`,
11558
- `${indent}}`
11559
- ].join("\n");
11560
11902
  };
11561
- var buildNavigationCode = (items) => {
11562
- const docsItems = items.map((item) => formatNavigationItem(item, 3));
11563
- const documentationItems = [
11564
- ...docsItems,
11565
- [
11566
- " {",
11567
- ' type: "category",',
11568
- ' label: "Useful Links",',
11569
- " collapsible: false,",
11570
- ' icon: "link",',
11571
- " items: [",
11572
- " {",
11573
- ' type: "link",',
11574
- ' icon: "book",',
11575
- ' label: "APIToGo",',
11576
- ' to: "https://apitogo.com",',
11577
- " },",
11578
- " ],",
11579
- " },"
11580
- ].join("\n")
11581
- ];
11582
- return [
11583
- "[",
11584
- " {",
11585
- ' type: "category",',
11586
- ' label: "Documentation",',
11587
- " items: [",
11588
- documentationItems.join(",\n"),
11589
- " ],",
11590
- " },",
11591
- " {",
11592
- ' type: "link",',
11593
- ' to: "/api",',
11594
- ' label: "API Reference",',
11595
- " },",
11596
- "]"
11597
- ].join("\n");
11598
- };
11599
- var buildRedirectsCode = (firstDocPath) => `[{ from: "/", to: "${firstDocPath}" }]`;
11600
- var findMatchingBracket = (source, startIndex) => {
11601
- let depth = 0;
11602
- let inSingleQuote = false;
11603
- let inDoubleQuote = false;
11604
- let inTemplate = false;
11605
- let inLineComment = false;
11606
- let inBlockComment = false;
11607
- for (let index = startIndex; index < source.length; index++) {
11608
- const char = source[index];
11609
- const next = source[index + 1];
11610
- const prev = source[index - 1];
11611
- if (inLineComment) {
11612
- if (char === "\n") inLineComment = false;
11613
- continue;
11614
- }
11615
- if (inBlockComment) {
11616
- if (prev === "*" && char === "/") inBlockComment = false;
11617
- continue;
11618
- }
11619
- if (!inSingleQuote && !inDoubleQuote && !inTemplate) {
11620
- if (char === "/" && next === "/") {
11621
- inLineComment = true;
11622
- index++;
11623
- continue;
11624
- }
11625
- if (char === "/" && next === "*") {
11626
- inBlockComment = true;
11627
- index++;
11628
- continue;
11629
- }
11630
- }
11631
- if (!inDoubleQuote && !inTemplate && char === "'" && prev !== "\\") {
11632
- inSingleQuote = !inSingleQuote;
11633
- continue;
11634
- }
11635
- if (!inSingleQuote && !inTemplate && char === '"' && prev !== "\\") {
11636
- inDoubleQuote = !inDoubleQuote;
11637
- continue;
11638
- }
11639
- if (!inSingleQuote && !inDoubleQuote && char === "`" && prev !== "\\") {
11640
- inTemplate = !inTemplate;
11641
- continue;
11642
- }
11643
- if (inSingleQuote || inDoubleQuote || inTemplate) continue;
11644
- if (char === "[") depth++;
11645
- if (char === "]") depth--;
11646
- if (depth === 0) return index;
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");
11647
11908
  }
11648
- throw new Error("Could not find matching closing bracket.");
11649
- };
11650
- var findPropertyArrayBounds = (source, propertyName) => {
11651
- const re = new RegExp(`\\b${propertyName}\\s*:`);
11652
- const match = source.match(re);
11653
- if (!match || match.index === void 0) {
11654
- throw new Error(`Could not find '${propertyName}' in config file.`);
11655
- }
11656
- const arrayStart = source.indexOf("[", match.index);
11657
- if (arrayStart === -1) {
11658
- throw new Error(
11659
- `Could not find array value for '${propertyName}' (expected [...]).`
11660
- );
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");
11661
11913
  }
11662
- const arrayEnd = findMatchingBracket(source, arrayStart);
11663
- return { arrayStart, arrayEnd };
11664
- };
11665
- var replaceArrayProperty = (source, propertyName, replacement) => {
11666
- const { arrayStart, arrayEnd } = findPropertyArrayBounds(
11667
- source,
11668
- propertyName
11669
- );
11670
- return `${source.slice(0, arrayStart)}${replacement}${source.slice(arrayEnd + 1)}`;
11671
- };
11672
- var insertNavigationAndRedirectsKeys = (source) => {
11673
- const m = source.match(/(const\s+config\s*:\s*ApitogoConfig\s*=\s*\{)/);
11674
- if (!m || m.index === void 0) {
11675
- throw new Error(
11676
- "Could not find 'const config: ApitogoConfig = {' \u2014 add navigation and redirects arrays manually."
11677
- );
11914
+ }
11915
+ async function ensureThinConfigTsx(dir) {
11916
+ const tsxPath = path38.join(dir, "apitogo.config.tsx");
11917
+ if (await fileExists2(tsxPath)) {
11918
+ return false;
11678
11919
  }
11679
- const insertAt = m.index + m[0].length;
11680
- return `${source.slice(0, insertAt)}
11681
- navigation: [],
11682
- redirects: [],${source.slice(insertAt)}`;
11683
- };
11684
- var insertRedirectsAfterNavigation = (source) => {
11685
- if (/\bredirects\s*:/.test(source)) return source;
11686
- const { arrayEnd } = findPropertyArrayBounds(source, "navigation");
11687
- let i = arrayEnd + 1;
11688
- while (i < source.length && /\s/.test(source[i])) i++;
11689
- if (source[i] === ",") {
11690
- return `${source.slice(0, i + 1)}
11691
- redirects: [],${source.slice(i + 1)}`;
11692
- }
11693
- return `${source.slice(0, arrayEnd + 1)},
11694
- redirects: [],${source.slice(arrayEnd + 1)}`;
11695
- };
11696
- var ensureNavigationAndRedirectsForUpdate = (source) => {
11697
- const hasNav = /\bnavigation\s*:/.test(source);
11698
- const hasRedirects = /\bredirects\s*:/.test(source);
11699
- if (hasNav && hasRedirects) return source;
11700
- if (!hasNav && !hasRedirects) return insertNavigationAndRedirectsKeys(source);
11701
- if (hasNav && !hasRedirects) return insertRedirectsAfterNavigation(source);
11702
- throw new Error(
11703
- "Found 'redirects' but not 'navigation'. Add a navigation: [...] array manually."
11704
- );
11705
- };
11706
- var buildNewConfigContents = (pagesDir, openApiSpecRelative) => {
11707
- const filesGlob = `/${pagesDir}/**/*.{md,mdx}`;
11708
- const apisSection = openApiSpecRelative == null ? "" : `
11709
- apis: {
11710
- type: "file",
11711
- input: "${openapiInputForConfig(openApiSpecRelative)}",
11712
- path: "api",
11713
- },`;
11714
- return `import type { ApitogoConfig } from "@lukoweb/apitogo";
11715
-
11716
- const config: ApitogoConfig = {
11717
- docs: {
11718
- files: "${filesGlob}",
11719
- },${apisSection}
11720
- navigation: [],
11721
- redirects: [],
11722
- };
11920
+ await writeFile7(tsxPath, THIN_WRAPPER_SOURCE, "utf8");
11921
+ return true;
11922
+ }
11723
11923
 
11724
- export default config;
11725
- `;
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
+ }
11726
11933
  };
11727
11934
  var ensureApitogoDeployWorkflow = async (dir) => {
11728
11935
  const workflowPath = path39.join(
@@ -11731,25 +11938,20 @@ var ensureApitogoDeployWorkflow = async (dir) => {
11731
11938
  "workflows",
11732
11939
  "apitogo-deploy.yml"
11733
11940
  );
11734
- try {
11735
- await access5(workflowPath, constants3.F_OK);
11941
+ if (await fileExists3(workflowPath)) {
11736
11942
  return false;
11737
- } catch {
11738
11943
  }
11739
- await mkdir7(path39.join(dir, ".github", "workflows"), { recursive: true });
11944
+ await mkdir8(path39.join(dir, ".github", "workflows"), { recursive: true });
11740
11945
  await writeFile8(workflowPath, APITOGO_DEPLOY_WORKFLOW_YAML, "utf8");
11741
11946
  return true;
11742
11947
  };
11743
- var findExistingConfigPath = async (dir) => {
11744
- for (const filename of CONFIG_FILENAMES) {
11745
- const fullPath = path39.join(dir, filename);
11746
- try {
11747
- await readFile6(fullPath, "utf8");
11748
- return fullPath;
11749
- } catch {
11750
- }
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.`);
11751
11953
  }
11752
- return null;
11954
+ return parsed;
11753
11955
  };
11754
11956
  async function makeConfig(argv) {
11755
11957
  const dir = path39.resolve(process.cwd(), argv.dir);
@@ -11757,7 +11959,7 @@ async function makeConfig(argv) {
11757
11959
  argv.pagesDir.replace(/^[./]+/, "").replace(/\/+$/, "")
11758
11960
  );
11759
11961
  try {
11760
- const openApiSpecPath = await findOpenApiSpecPath(dir);
11962
+ const discoveredOpenApi = await findOpenApiSpecPath(dir);
11761
11963
  const createdPackageJson = await ensurePackageJson(dir);
11762
11964
  const createdTsconfig = await ensureTsconfigJson(dir);
11763
11965
  if (createdPackageJson) {
@@ -11768,51 +11970,98 @@ async function makeConfig(argv) {
11768
11970
  if (createdTsconfig) {
11769
11971
  printDiagnosticsToConsole("Created tsconfig.json.");
11770
11972
  }
11771
- let configPath = await findExistingConfigPath(dir);
11772
- let createdNew = false;
11773
- if (!configPath) {
11774
- configPath = path39.join(dir, DEFAULT_NEW_CONFIG_FILENAME);
11775
- await writeFile8(
11776
- configPath,
11777
- buildNewConfigContents(pagesDir, openApiSpecPath),
11778
- "utf8"
11779
- );
11780
- createdNew = true;
11781
- printDiagnosticsToConsole(`Created ${DEFAULT_NEW_CONFIG_FILENAME}`);
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);
11782
12019
  }
11783
- const pageFiles = await glob5(`${pagesDir}/**/*.{md,mdx}`, {
12020
+ config2 = applyConfigPatches(config2, flagPatches);
12021
+ const pageFiles = await glob5(`${pagesDir}/**/*.{md,mdx,tsx,jsx}`, {
11784
12022
  cwd: dir,
11785
12023
  ignore: ["**/node_modules/**", "**/.git/**", "**/dist/**"],
11786
12024
  posix: true
11787
12025
  });
11788
- const routePaths = sortDocPaths(
11789
- pageFiles.map((file) => toRoutePath(file, pagesDir))
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)
11790
12030
  );
11791
- if (routePaths.length === 0) {
11792
- throw new Error(`No .md or .mdx files found under '${pagesDir}'.`);
11793
- }
11794
- let originalConfig = await readFile6(configPath, "utf8");
11795
- if (!createdNew && openApiSpecPath) {
11796
- originalConfig = insertApisIfMissing(
11797
- originalConfig,
11798
- openapiInputForConfig(openApiSpecPath)
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.`
11799
12035
  );
11800
12036
  }
11801
- originalConfig = ensureNavigationAndRedirectsForUpdate(originalConfig);
11802
- const withNavigation = replaceArrayProperty(
11803
- originalConfig,
11804
- "navigation",
11805
- buildNavigationCode(treeToNavigationItems(buildTree(routePaths)))
12037
+ config2.navigation = mergeDocumentationNavigation(
12038
+ config2.navigation,
12039
+ markdownRoutes,
12040
+ { force: argv.force === true }
11806
12041
  );
11807
- const withRedirects = replaceArrayProperty(
11808
- withNavigation,
11809
- "redirects",
11810
- buildRedirectsCode(routePaths[0])
11811
- );
11812
- await writeFile8(configPath, withRedirects, "utf8");
11813
- const action = createdNew ? "Initialized" : "Updated";
12042
+ const openApiRel = openApiOverride === void 0 ? discoveredOpenApi : openApiOverride;
12043
+ if (openApiRel) {
12044
+ config2 = applyOpenApiToJsonConfig(
12045
+ config2,
12046
+ openapiInputForConfig(openApiRel)
12047
+ );
12048
+ }
12049
+ await writeFile8(jsonPath, `${JSON.stringify(config2, null, 2)}
12050
+ `, "utf8");
12051
+ const tsxPath = path39.join(dir, "apitogo.config.tsx");
12052
+ if (await fileExists3(tsxPath) && tsxRoutes.length > 0) {
12053
+ const tsxSource = await readFile6(tsxPath, "utf8");
12054
+ if (isThinConfigWrapper(tsxSource)) {
12055
+ await writeFile8(
12056
+ tsxPath,
12057
+ buildThinWrapperWithCustomPages(tsxRoutes),
12058
+ "utf8"
12059
+ );
12060
+ }
12061
+ }
12062
+ const action = createdJson ? "Initialized" : "Updated";
11814
12063
  printResultToConsole(
11815
- `${action} ${path39.basename(configPath)} from ${pagesDir}/ (navigation and redirects; other top-level settings preserved unless newly scaffolded).`
12064
+ `${action} ${APITOGO_JSON_FILENAME} (site/theme/auth/navigation/OpenAPI as applicable; redirects unchanged).`
11816
12065
  );
11817
12066
  const createdWorkflow = await ensureApitogoDeployWorkflow(dir);
11818
12067
  if (createdWorkflow) {
@@ -11834,7 +12083,7 @@ async function makeConfig(argv) {
11834
12083
 
11835
12084
  // src/cli/cmds/make-config.ts
11836
12085
  var make_config_default = {
11837
- desc: "Create or update config navigation from the pages folder (other settings preserved)",
12086
+ desc: "Create or update apitogo.config.json from pages, OpenAPI, and optional site/theme/auth flags",
11838
12087
  command: "make-config",
11839
12088
  builder: (yargs2) => yargs2.option("dir", {
11840
12089
  type: "string",
@@ -11844,8 +12093,29 @@ var make_config_default = {
11844
12093
  hidden: true
11845
12094
  }).option("pages-dir", {
11846
12095
  type: "string",
11847
- describe: "Directory to scan for md/mdx pages",
12096
+ describe: "Directory to scan for md/mdx/tsx pages",
11848
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", {
12113
+ 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
11849
12119
  }),
11850
12120
  handler: async (argv) => {
11851
12121
  await captureEvent({ argv, event: "apitogo make-config" });