@igniter-js/cli 0.2.5 → 0.2.61

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/index.js CHANGED
@@ -5160,7 +5160,7 @@ var require_Command = __commonJS({
5160
5160
  }
5161
5161
  }
5162
5162
  initPromise() {
5163
- const promise = new Promise((resolve4, reject) => {
5163
+ const promise = new Promise((resolve5, reject) => {
5164
5164
  if (!this.transformed) {
5165
5165
  this.transformed = true;
5166
5166
  const transformer = _Command._transformer.argument[this.name];
@@ -5169,7 +5169,7 @@ var require_Command = __commonJS({
5169
5169
  }
5170
5170
  this.stringifyArguments();
5171
5171
  }
5172
- this.resolve = this._convertValue(resolve4);
5172
+ this.resolve = this._convertValue(resolve5);
5173
5173
  if (this.errorStack) {
5174
5174
  this.reject = (err) => {
5175
5175
  reject((0, utils_1.optimizeErrorStack)(err, this.errorStack.stack, __dirname));
@@ -5199,7 +5199,7 @@ var require_Command = __commonJS({
5199
5199
  /**
5200
5200
  * Convert the value from buffer to the target encoding.
5201
5201
  */
5202
- _convertValue(resolve4) {
5202
+ _convertValue(resolve5) {
5203
5203
  return (value) => {
5204
5204
  try {
5205
5205
  const existingTimer = this._commandTimeoutTimer;
@@ -5207,7 +5207,7 @@ var require_Command = __commonJS({
5207
5207
  clearTimeout(existingTimer);
5208
5208
  delete this._commandTimeoutTimer;
5209
5209
  }
5210
- resolve4(this.transformReply(value));
5210
+ resolve5(this.transformReply(value));
5211
5211
  this.isResolved = true;
5212
5212
  } catch (err) {
5213
5213
  this.reject(err);
@@ -5462,13 +5462,13 @@ var require_autoPipelining = __commonJS({
5462
5462
  if (client.isCluster && !client.slots.length) {
5463
5463
  if (client.status === "wait")
5464
5464
  client.connect().catch(lodash_1.noop);
5465
- return (0, standard_as_callback_1.default)(new Promise(function(resolve4, reject) {
5465
+ return (0, standard_as_callback_1.default)(new Promise(function(resolve5, reject) {
5466
5466
  client.delayUntilReady((err) => {
5467
5467
  if (err) {
5468
5468
  reject(err);
5469
5469
  return;
5470
5470
  }
5471
- executeWithAutoPipelining(client, functionName, commandName, args, null).then(resolve4, reject);
5471
+ executeWithAutoPipelining(client, functionName, commandName, args, null).then(resolve5, reject);
5472
5472
  });
5473
5473
  }), callback);
5474
5474
  }
@@ -5485,13 +5485,13 @@ var require_autoPipelining = __commonJS({
5485
5485
  pipeline[exports2.kExec] = true;
5486
5486
  setImmediate(executeAutoPipeline, client, slotKey);
5487
5487
  }
5488
- const autoPipelinePromise = new Promise(function(resolve4, reject) {
5488
+ const autoPipelinePromise = new Promise(function(resolve5, reject) {
5489
5489
  pipeline[exports2.kCallbacks].push(function(err, value) {
5490
5490
  if (err) {
5491
5491
  reject(err);
5492
5492
  return;
5493
5493
  }
5494
- resolve4(value);
5494
+ resolve5(value);
5495
5495
  });
5496
5496
  if (functionName === "call") {
5497
5497
  args.unshift(commandName);
@@ -5729,8 +5729,8 @@ var require_Pipeline = __commonJS({
5729
5729
  this[name] = redis[name];
5730
5730
  this[name + "Buffer"] = redis[name + "Buffer"];
5731
5731
  });
5732
- this.promise = new Promise((resolve4, reject) => {
5733
- this.resolve = resolve4;
5732
+ this.promise = new Promise((resolve5, reject) => {
5733
+ this.resolve = resolve5;
5734
5734
  this.reject = reject;
5735
5735
  });
5736
5736
  const _this = this;
@@ -6025,13 +6025,13 @@ var require_transaction = __commonJS({
6025
6025
  if (this.isCluster && !this.redis.slots.length) {
6026
6026
  if (this.redis.status === "wait")
6027
6027
  this.redis.connect().catch(utils_1.noop);
6028
- return (0, standard_as_callback_1.default)(new Promise((resolve4, reject) => {
6028
+ return (0, standard_as_callback_1.default)(new Promise((resolve5, reject) => {
6029
6029
  this.redis.delayUntilReady((err) => {
6030
6030
  if (err) {
6031
6031
  reject(err);
6032
6032
  return;
6033
6033
  }
6034
- this.exec(pipeline).then(resolve4, reject);
6034
+ this.exec(pipeline).then(resolve5, reject);
6035
6035
  });
6036
6036
  }), callback);
6037
6037
  }
@@ -7207,7 +7207,7 @@ var require_cluster = __commonJS({
7207
7207
  * Connect to a cluster
7208
7208
  */
7209
7209
  connect() {
7210
- return new Promise((resolve4, reject) => {
7210
+ return new Promise((resolve5, reject) => {
7211
7211
  if (this.status === "connecting" || this.status === "connect" || this.status === "ready") {
7212
7212
  reject(new Error("Redis is already connecting/connected"));
7213
7213
  return;
@@ -7231,7 +7231,7 @@ var require_cluster = __commonJS({
7231
7231
  this.retryAttempts = 0;
7232
7232
  this.executeOfflineCommands();
7233
7233
  this.resetNodesRefreshInterval();
7234
- resolve4();
7234
+ resolve5();
7235
7235
  };
7236
7236
  let closeListener = void 0;
7237
7237
  const refreshListener = () => {
@@ -7824,7 +7824,7 @@ var require_cluster = __commonJS({
7824
7824
  });
7825
7825
  }
7826
7826
  resolveSrv(hostname) {
7827
- return new Promise((resolve4, reject) => {
7827
+ return new Promise((resolve5, reject) => {
7828
7828
  this.options.resolveSrv(hostname, (err, records) => {
7829
7829
  if (err) {
7830
7830
  return reject(err);
@@ -7838,7 +7838,7 @@ var require_cluster = __commonJS({
7838
7838
  if (!group.records.length) {
7839
7839
  sortedKeys.shift();
7840
7840
  }
7841
- self.dnsLookup(record.name).then((host) => resolve4({
7841
+ self.dnsLookup(record.name).then((host) => resolve5({
7842
7842
  host,
7843
7843
  port: record.port
7844
7844
  }), tryFirstOne);
@@ -7848,14 +7848,14 @@ var require_cluster = __commonJS({
7848
7848
  });
7849
7849
  }
7850
7850
  dnsLookup(hostname) {
7851
- return new Promise((resolve4, reject) => {
7851
+ return new Promise((resolve5, reject) => {
7852
7852
  this.options.dnsLookup(hostname, (err, address) => {
7853
7853
  if (err) {
7854
7854
  debug("failed to resolve hostname %s to IP: %s", hostname, err.message);
7855
7855
  reject(err);
7856
7856
  } else {
7857
7857
  debug("resolved hostname %s to IP %s", hostname, address);
7858
- resolve4(address);
7858
+ resolve5(address);
7859
7859
  }
7860
7860
  });
7861
7861
  });
@@ -7973,7 +7973,7 @@ var require_StandaloneConnector = __commonJS({
7973
7973
  if (options.tls) {
7974
7974
  Object.assign(connectionOptions, options.tls);
7975
7975
  }
7976
- return new Promise((resolve4, reject) => {
7976
+ return new Promise((resolve5, reject) => {
7977
7977
  process.nextTick(() => {
7978
7978
  if (!this.connecting) {
7979
7979
  reject(new Error(utils_1.CONNECTION_CLOSED_ERROR_MSG));
@@ -7992,7 +7992,7 @@ var require_StandaloneConnector = __commonJS({
7992
7992
  this.stream.once("error", (err) => {
7993
7993
  this.firstError = err;
7994
7994
  });
7995
- resolve4(this.stream);
7995
+ resolve5(this.stream);
7996
7996
  });
7997
7997
  });
7998
7998
  }
@@ -8151,7 +8151,7 @@ var require_SentinelConnector = __commonJS({
8151
8151
  const error = new Error(errorMsg);
8152
8152
  if (typeof retryDelay === "number") {
8153
8153
  eventEmitter("error", error);
8154
- await new Promise((resolve4) => setTimeout(resolve4, retryDelay));
8154
+ await new Promise((resolve5) => setTimeout(resolve5, retryDelay));
8155
8155
  return connectToNext();
8156
8156
  } else {
8157
8157
  throw error;
@@ -9457,7 +9457,7 @@ var require_Redis = __commonJS({
9457
9457
  * be resolved when the connection status is ready.
9458
9458
  */
9459
9459
  connect(callback) {
9460
- const promise = new Promise((resolve4, reject) => {
9460
+ const promise = new Promise((resolve5, reject) => {
9461
9461
  if (this.status === "connecting" || this.status === "connect" || this.status === "ready") {
9462
9462
  reject(new Error("Redis is already connecting/connected"));
9463
9463
  return;
@@ -9536,7 +9536,7 @@ var require_Redis = __commonJS({
9536
9536
  }
9537
9537
  const connectionReadyHandler = function() {
9538
9538
  _this.removeListener("close", connectionCloseHandler);
9539
- resolve4();
9539
+ resolve5();
9540
9540
  };
9541
9541
  var connectionCloseHandler = function() {
9542
9542
  _this.removeListener("ready", connectionReadyHandler);
@@ -9630,10 +9630,10 @@ var require_Redis = __commonJS({
9630
9630
  monitor: true,
9631
9631
  lazyConnect: false
9632
9632
  });
9633
- return (0, standard_as_callback_1.default)(new Promise(function(resolve4, reject) {
9633
+ return (0, standard_as_callback_1.default)(new Promise(function(resolve5, reject) {
9634
9634
  monitorInstance.once("error", reject);
9635
9635
  monitorInstance.once("monitoring", function() {
9636
- resolve4(monitorInstance);
9636
+ resolve5(monitorInstance);
9637
9637
  });
9638
9638
  }), callback);
9639
9639
  }
@@ -11415,8 +11415,8 @@ ${ANSI_COLORS.bold}TOP OPERATIONS${ANSI_COLORS.reset}`);
11415
11415
  }, 5e3);
11416
11416
  this.startApiMonitoring();
11417
11417
  await Promise.all(
11418
- this.processes.map((proc) => new Promise((resolve4) => {
11419
- proc.on("exit", resolve4);
11418
+ this.processes.map((proc) => new Promise((resolve5) => {
11419
+ proc.on("exit", resolve5);
11420
11420
  }))
11421
11421
  );
11422
11422
  }
@@ -11446,52 +11446,258 @@ ${ANSI_COLORS.bold}TOP OPERATIONS${ANSI_COLORS.reset}`);
11446
11446
  }
11447
11447
  });
11448
11448
 
11449
- // src/adapters/build/generator.ts
11450
- function getFileSize(filePath) {
11451
- try {
11452
- const stats = fs6.statSync(filePath);
11453
- const bytes = stats.size;
11454
- if (bytes < 1024) return `${bytes}b`;
11455
- if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)}kb`;
11456
- return `${(bytes / (1024 * 1024)).toFixed(1)}mb`;
11457
- } catch {
11458
- return "0b";
11459
- }
11460
- }
11461
- function extractRouterSchema(router) {
11462
- const controllersSchema = {};
11449
+ // src/adapters/build/introspector.ts
11450
+ var introspector_exports = {};
11451
+ __export(introspector_exports, {
11452
+ introspectRouter: () => introspectRouter,
11453
+ loadRouter: () => loadRouter
11454
+ });
11455
+ function introspectRouter(router) {
11456
+ const logger6 = createChildLogger({ component: "router-introspector" });
11457
+ logger6.debug("Starting router introspection");
11458
+ const introspectedControllers = {};
11463
11459
  let totalActions = 0;
11464
11460
  for (const [controllerName, controller] of Object.entries(router.controllers)) {
11465
- const actionsSchema = {};
11461
+ logger6.debug(`Introspecting controller: ${controllerName}`);
11462
+ const introspectedActions = {};
11466
11463
  if (controller && controller.actions) {
11467
11464
  for (const [actionName, action] of Object.entries(controller.actions)) {
11468
- actionsSchema[actionName] = {
11465
+ logger6.debug(`Introspecting action: ${controllerName}.${actionName}`, { path: action?.path, method: action?.method });
11466
+ const bodySchemaOutput = action.bodySchema !== void 0 ? action.bodySchema : void 0;
11467
+ const querySchemaOutput = action.querySchema !== void 0 ? action.querySchema : void 0;
11468
+ introspectedActions[actionName] = {
11469
11469
  name: action?.name || actionName,
11470
11470
  description: action?.description || "",
11471
11471
  path: action?.path || "",
11472
- method: action?.method || "GET"
11472
+ method: action?.method || "GET",
11473
+ isStream: action?.stream || false,
11474
+ ...bodySchemaOutput !== void 0 ? { bodySchema: bodySchemaOutput } : {},
11475
+ ...querySchemaOutput !== void 0 ? { querySchema: querySchemaOutput } : {}
11473
11476
  };
11474
11477
  totalActions++;
11475
11478
  }
11479
+ } else {
11480
+ logger6.debug(`Controller ${controllerName} has no actions property or is invalid.`);
11476
11481
  }
11477
- controllersSchema[controllerName] = {
11482
+ introspectedControllers[controllerName] = {
11478
11483
  name: controller?.name || controllerName,
11479
11484
  description: controller?.description || "",
11480
11485
  path: controller?.path || "",
11481
- actions: actionsSchema
11486
+ actions: introspectedActions
11482
11487
  };
11483
11488
  }
11484
- const schema = {
11485
- controllers: controllersSchema
11489
+ const schemaResult = {
11490
+ controllers: introspectedControllers,
11491
+ docs: router?.config?.docs
11486
11492
  };
11487
11493
  return {
11488
- schema,
11494
+ schema: schemaResult,
11489
11495
  stats: {
11490
- controllers: Object.keys(controllersSchema).length,
11496
+ controllers: Object.keys(introspectedControllers).length,
11491
11497
  actions: totalActions
11492
11498
  }
11493
11499
  };
11494
11500
  }
11501
+ async function loadRouter(routerPath) {
11502
+ const logger6 = createChildLogger({ component: "router-loader" });
11503
+ const fullPath = path7.resolve(process.cwd(), routerPath);
11504
+ logger6.debug("Loading router", { path: routerPath });
11505
+ try {
11506
+ const module2 = await loadWithTypeScriptSupport(fullPath);
11507
+ if (module2) {
11508
+ if (module2?.config && module2?.controllers) {
11509
+ return module2;
11510
+ }
11511
+ const router = module2?.AppRouter || module2?.default?.AppRouter || module2?.default || module2?.router;
11512
+ if (router && typeof router === "object") {
11513
+ return router;
11514
+ } else {
11515
+ logger6.debug("Module loaded but no router found", {
11516
+ exports: Object.keys(module2 || {})
11517
+ });
11518
+ }
11519
+ }
11520
+ const fallbackSpinner = createDetachedSpinner("Trying fallback loading method...");
11521
+ fallbackSpinner.start();
11522
+ const fallbackModule = await loadRouterWithIndexResolution(fullPath);
11523
+ if (fallbackModule) {
11524
+ if (fallbackModule?.config && fallbackModule?.controllers) {
11525
+ fallbackSpinner.success(`Router loaded successfully - ${Object.keys(fallbackModule.controllers).length} controllers`);
11526
+ return fallbackModule;
11527
+ }
11528
+ const router = fallbackModule?.AppRouter || fallbackModule?.default?.AppRouter || fallbackModule?.default || fallbackModule?.router;
11529
+ if (router && typeof router === "object") {
11530
+ fallbackSpinner.success(`Router loaded successfully - ${Object.keys(router.controllers).length} controllers`);
11531
+ return router;
11532
+ }
11533
+ }
11534
+ fallbackSpinner.error("Could not load router");
11535
+ } catch (error) {
11536
+ logger6.error("Failed to load router", { path: routerPath }, error);
11537
+ }
11538
+ return null;
11539
+ }
11540
+ async function loadWithTypeScriptSupport(filePath) {
11541
+ const logger6 = createChildLogger({ component: "tsx-loader" });
11542
+ const jsPath = filePath.replace(/\.ts$/, ".js");
11543
+ if (fs6.existsSync(jsPath)) {
11544
+ try {
11545
+ logger6.debug("Using compiled JS version");
11546
+ delete require.cache[jsPath];
11547
+ return require(jsPath);
11548
+ } catch (error) {
11549
+ logger6.debug("Compiled JS loading failed, trying TypeScript...");
11550
+ }
11551
+ }
11552
+ if (filePath.endsWith(".ts")) {
11553
+ try {
11554
+ logger6.debug("Using TSX runtime loader");
11555
+ const { spawn: spawn2 } = require("child_process");
11556
+ const tsxCheckResult = await new Promise((resolve5) => {
11557
+ const checkChild = spawn2("npx", ["tsx", "--version"], {
11558
+ stdio: "pipe",
11559
+ cwd: process.cwd()
11560
+ });
11561
+ checkChild.on("close", (code) => resolve5(code === 0));
11562
+ checkChild.on("error", () => resolve5(false));
11563
+ setTimeout(() => {
11564
+ checkChild.kill();
11565
+ resolve5(false);
11566
+ }, 5e3);
11567
+ });
11568
+ if (!tsxCheckResult) {
11569
+ throw new Error("TSX not available");
11570
+ }
11571
+ return await new Promise((resolve5, reject) => {
11572
+ const tsxScript = `
11573
+ import { zodToJsonSchema } from 'zod-to-json-schema'; // Precisamos importar isso no script filho
11574
+
11575
+ async function loadRouter() {
11576
+ try {
11577
+ const module = await import('${(0, import_url.pathToFileURL)(filePath).href}');
11578
+ const router = module?.AppRouter || module?.default?.AppRouter || module?.default || module?.router;
11579
+
11580
+ if (router && typeof router === 'object') {
11581
+ const safeRouter = {
11582
+ config: {
11583
+ baseURL: router.config?.baseURL || '',
11584
+ basePATH: router.config?.basePATH || '',
11585
+ docs: router.config?.docs || undefined,
11586
+ },
11587
+ controllers: {}
11588
+ };
11589
+
11590
+ if (router.controllers && typeof router.controllers === 'object') {
11591
+ for (const [controllerName, controller] of Object.entries(router.controllers)) {
11592
+ if (controller && typeof controller === 'object' && (controller as any).actions) {
11593
+ const safeActions = {};
11594
+ for (const [actionName, action] of Object.entries((controller as any).actions)) {
11595
+ if (action && typeof action === 'object') {
11596
+ // MODIFICA\xC7\xC3O PRINCIPAL AQUI
11597
+ safeActions[actionName] = {
11598
+ name: (action as any).name,
11599
+ path: (action as any).path,
11600
+ method: (action as any).method,
11601
+ description: (action as any).description,
11602
+ // Convertemos o schema Zod para JSON Schema AQUI, antes do JSON.stringify
11603
+ bodySchema: (action as any).body ? zodToJsonSchema((action as any).body, { target: 'openApi3' }) : undefined,
11604
+ querySchema: (action as any).query ? zodToJsonSchema((action as any).query, { target: 'openApi3' }) : undefined,
11605
+ use: (action as any).use,
11606
+ stream: (action as any).stream,
11607
+ };
11608
+ }
11609
+ }
11610
+ safeRouter.controllers[controllerName] = {
11611
+ name: (controller as any).name || controllerName,
11612
+ path: (controller as any).path || '',
11613
+ description: (controller as any).description,
11614
+ actions: safeActions,
11615
+ };
11616
+ }
11617
+ }
11618
+ }
11619
+ console.log('__ROUTER_SUCCESS__' + JSON.stringify(safeRouter));
11620
+ process.exit(0);
11621
+ } else {
11622
+ console.log('__ROUTER_ERROR__No router found in module');
11623
+ process.exit(1);
11624
+ }
11625
+ } catch (error) {
11626
+ console.log('__ROUTER_ERROR__' + error.message);
11627
+ process.exit(1);
11628
+ }
11629
+ }
11630
+ loadRouter();
11631
+ `;
11632
+ const child = spawn2("npx", ["tsx", "-e", tsxScript], {
11633
+ stdio: "pipe",
11634
+ cwd: process.cwd(),
11635
+ env: { ...process.env, NODE_NO_WARNINGS: "1" }
11636
+ });
11637
+ let output = "";
11638
+ let errorOutput = "";
11639
+ child.stdout?.on("data", (data) => output += data.toString());
11640
+ child.stderr?.on("data", (data) => errorOutput += data.toString());
11641
+ child.on("close", (code) => {
11642
+ if (output.includes("__ROUTER_SUCCESS__")) {
11643
+ const resultLine = output.split("\n").find((line) => line.includes("__ROUTER_SUCCESS__"));
11644
+ if (resultLine) {
11645
+ try {
11646
+ const routerData = JSON.parse(resultLine.replace("__ROUTER_SUCCESS__", ""));
11647
+ resolve5(routerData);
11648
+ } catch (e) {
11649
+ reject(new Error("Failed to parse router data: " + e.message));
11650
+ }
11651
+ } else {
11652
+ reject(new Error("Router success marker found but no data"));
11653
+ }
11654
+ } else if (output.includes("__ROUTER_ERROR__")) {
11655
+ const errorLine = output.split("\n").find((line) => line.includes("__ROUTER_ERROR__"));
11656
+ const errorMsg = errorLine ? errorLine.replace("__ROUTER_ERROR__", "") : "Unknown error";
11657
+ reject(new Error("Router loading failed: " + errorMsg));
11658
+ } else {
11659
+ reject(new Error(`TSX execution failed with code ${code}: ${errorOutput || "No output"}`));
11660
+ }
11661
+ });
11662
+ child.on("error", (error) => reject(new Error("Failed to spawn TSX process: " + error.message)));
11663
+ setTimeout(() => {
11664
+ child.kill();
11665
+ reject(new Error("Timeout loading TypeScript file with TSX"));
11666
+ }, 3e4);
11667
+ });
11668
+ } catch (error) {
11669
+ logger6.debug("TSX runtime loader failed", {}, error);
11670
+ }
11671
+ }
11672
+ return null;
11673
+ }
11674
+ async function loadRouterWithIndexResolution(routerPath) {
11675
+ return null;
11676
+ }
11677
+ var fs6, path7, import_url;
11678
+ var init_introspector = __esm({
11679
+ "src/adapters/build/introspector.ts"() {
11680
+ "use strict";
11681
+ fs6 = __toESM(require("fs"));
11682
+ path7 = __toESM(require("path"));
11683
+ import_url = require("url");
11684
+ init_logger();
11685
+ init_spinner();
11686
+ }
11687
+ });
11688
+
11689
+ // src/adapters/build/generator.ts
11690
+ function getFileSize(filePath) {
11691
+ try {
11692
+ const stats = fs7.statSync(filePath);
11693
+ const bytes = stats.size;
11694
+ if (bytes < 1024) return `${bytes}b`;
11695
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)}kb`;
11696
+ return `${(bytes / (1024 * 1024)).toFixed(1)}mb`;
11697
+ } catch {
11698
+ return "0b";
11699
+ }
11700
+ }
11495
11701
  async function generateSchemaFromRouter(router, config) {
11496
11702
  const logger6 = createChildLogger({ component: "generator" });
11497
11703
  const startTime = performance.now();
@@ -11504,7 +11710,7 @@ async function generateSchemaFromRouter(router, config) {
11504
11710
  extractSpinner = createDetachedSpinner("Extracting router schema...");
11505
11711
  extractSpinner.start();
11506
11712
  }
11507
- const { schema, stats } = extractRouterSchema(router);
11713
+ const { schema, stats } = introspectRouter(router);
11508
11714
  if (isInteractiveMode2) {
11509
11715
  logger6.success(`Schema extracted - ${stats.controllers} controllers, ${stats.actions} actions`);
11510
11716
  } else if (extractSpinner) {
@@ -11519,7 +11725,7 @@ async function generateSchemaFromRouter(router, config) {
11519
11725
  }
11520
11726
  const outputDir = config.outputDir || "generated";
11521
11727
  await ensureDirectoryExists(outputDir);
11522
- const outputPath = path7.resolve(outputDir);
11728
+ const outputPath = path8.resolve(outputDir);
11523
11729
  if (isInteractiveMode2) {
11524
11730
  logger6.success(`Output directory ready ${outputPath}`);
11525
11731
  } else if (dirSpinner) {
@@ -11549,8 +11755,8 @@ async function generateSchemaFromRouter(router, config) {
11549
11755
  let totalSize = 0;
11550
11756
  files.forEach((file) => {
11551
11757
  const size = getFileSize(file.path);
11552
- totalSize += fs6.statSync(file.path).size;
11553
- logger6.info(`Generated ${file.name}`, { size, path: path7.relative(process.cwd(), file.path) });
11758
+ totalSize += fs7.statSync(file.path).size;
11759
+ logger6.info(`Generated ${file.name}`, { size, path: path8.relative(process.cwd(), file.path) });
11554
11760
  });
11555
11761
  const duration = ((performance.now() - startTime) / 1e3).toFixed(2);
11556
11762
  const totalSizeFormatted = totalSize < 1024 ? `${totalSize}b` : totalSize < 1024 * 1024 ? `${(totalSize / 1024).toFixed(1)}kb` : `${(totalSize / (1024 * 1024)).toFixed(1)}mb`;
@@ -11583,13 +11789,13 @@ export const AppRouterSchema = ${JSON.stringify(schema, null, 2)} as const
11583
11789
 
11584
11790
  export type AppRouterSchemaType = typeof AppRouterSchema
11585
11791
  `;
11586
- const filePath = path7.join(outputDir, "igniter.schema.ts");
11792
+ const filePath = path8.join(outputDir, "igniter.schema.ts");
11587
11793
  await writeFileWithHeader(filePath, content, config);
11588
11794
  return filePath;
11589
11795
  }
11590
11796
  async function generateClientFile(schema, outputDir, config) {
11591
- const filePath = path7.join(outputDir, "igniter.client.ts");
11592
- if (fs6.existsSync(filePath)) {
11797
+ const filePath = path8.join(outputDir, "igniter.client.ts");
11798
+ if (fs7.existsSync(filePath)) {
11593
11799
  const logger6 = createChildLogger({ component: "generator" });
11594
11800
  logger6.info("Skipping client file generation, already exists", { path: filePath });
11595
11801
  return filePath;
@@ -11649,7 +11855,7 @@ export const useQueryClient = useIgniterQueryClient<AppRouterType>;
11649
11855
  async function writeFileWithHeader(filePath, content, config) {
11650
11856
  const header = generateFileHeader(config);
11651
11857
  const fullContent = header + "\n\n" + content;
11652
- await fs6.promises.writeFile(filePath, fullContent, "utf8");
11858
+ await fs7.promises.writeFile(filePath, fullContent, "utf8");
11653
11859
  }
11654
11860
  function generateFileHeader(config) {
11655
11861
  const timestamp = (/* @__PURE__ */ new Date()).toISOString();
@@ -11673,19 +11879,146 @@ function generateFileHeader(config) {
11673
11879
  }
11674
11880
  async function ensureDirectoryExists(dirPath) {
11675
11881
  try {
11676
- await fs6.promises.access(dirPath);
11882
+ await fs7.promises.access(dirPath);
11677
11883
  } catch (error) {
11678
- await fs6.promises.mkdir(dirPath, { recursive: true });
11884
+ await fs7.promises.mkdir(dirPath, { recursive: true });
11679
11885
  }
11680
11886
  }
11681
- var fs6, path7;
11887
+ var fs7, path8;
11682
11888
  var init_generator = __esm({
11683
11889
  "src/adapters/build/generator.ts"() {
11684
11890
  "use strict";
11685
- fs6 = __toESM(require("fs"));
11686
- path7 = __toESM(require("path"));
11891
+ fs7 = __toESM(require("fs"));
11892
+ path8 = __toESM(require("path"));
11687
11893
  init_logger();
11688
11894
  init_spinner();
11895
+ init_introspector();
11896
+ }
11897
+ });
11898
+
11899
+ // src/adapters/docs/openapi-generator.ts
11900
+ var openapi_generator_exports = {};
11901
+ __export(openapi_generator_exports, {
11902
+ OpenAPIGenerator: () => OpenAPIGenerator
11903
+ });
11904
+ function toPascalCase2(str) {
11905
+ if (!str) return "";
11906
+ return str.charAt(0).toUpperCase() + str.slice(1);
11907
+ }
11908
+ var OpenAPIGenerator;
11909
+ var init_openapi_generator = __esm({
11910
+ "src/adapters/docs/openapi-generator.ts"() {
11911
+ "use strict";
11912
+ OpenAPIGenerator = class {
11913
+ constructor(config) {
11914
+ this.schemas = {};
11915
+ this.docsConfig = config || {};
11916
+ }
11917
+ generate(router) {
11918
+ const servers = this.docsConfig.servers && this.docsConfig.servers.length > 0 ? this.docsConfig.servers : [{ url: "http://localhost:3000/api/v1", description: "Default server" }];
11919
+ const spec = {
11920
+ openapi: "3.0.0",
11921
+ info: this.docsConfig.info || { title: "Igniter API", version: "1.0.0" },
11922
+ servers,
11923
+ tags: this.buildTags(router),
11924
+ paths: this.buildPaths(router),
11925
+ components: {
11926
+ schemas: this.schemas,
11927
+ securitySchemes: this.docsConfig.securitySchemes || {}
11928
+ }
11929
+ };
11930
+ return spec;
11931
+ }
11932
+ buildTags(router) {
11933
+ const tags = [];
11934
+ for (const [controllerKey, controller] of Object.entries(router.controllers)) {
11935
+ const tag = {
11936
+ name: controller.name || controllerKey,
11937
+ description: controller.description
11938
+ };
11939
+ tags.push(tag);
11940
+ }
11941
+ return tags;
11942
+ }
11943
+ buildPaths(router) {
11944
+ const paths = {};
11945
+ for (const [controllerKey, controller] of Object.entries(router.controllers)) {
11946
+ for (const [actionKey, action] of Object.entries(controller.actions)) {
11947
+ const actionName = action.name || actionKey;
11948
+ let path11 = `/${controller.path}/${action.path}`;
11949
+ path11 = path11.replace(/\/{2,}/g, "/");
11950
+ if (path11.length > 1 && path11.endsWith("/")) {
11951
+ path11 = path11.slice(0, -1);
11952
+ }
11953
+ if (!paths[path11]) {
11954
+ paths[path11] = {};
11955
+ }
11956
+ const operation = {
11957
+ summary: action.description || actionName,
11958
+ operationId: actionName,
11959
+ tags: [controller.name || controllerKey],
11960
+ parameters: [],
11961
+ requestBody: void 0,
11962
+ responses: {
11963
+ "200": {
11964
+ description: "Success",
11965
+ content: {
11966
+ "application/json": {
11967
+ schema: {}
11968
+ }
11969
+ }
11970
+ }
11971
+ }
11972
+ };
11973
+ const pathParams = action.path.match(/:([a-zA-Z0-9_]+)/g);
11974
+ if (pathParams) {
11975
+ for (const param of pathParams) {
11976
+ const paramName = param.substring(1);
11977
+ operation.parameters.push({
11978
+ name: paramName,
11979
+ in: "path",
11980
+ required: true,
11981
+ schema: { type: "string" }
11982
+ });
11983
+ }
11984
+ }
11985
+ if (action.querySchema) {
11986
+ const querySchemaName = `${toPascalCase2(controller.name || controllerKey)}${toPascalCase2(actionName)}Query`;
11987
+ const queryJsonSchema = action.querySchema;
11988
+ this.schemas[querySchemaName] = queryJsonSchema;
11989
+ const properties = this.schemas[querySchemaName].properties || {};
11990
+ const requiredProps = this.schemas[querySchemaName].required || [];
11991
+ for (const propName of Object.keys(properties)) {
11992
+ operation.parameters.push({
11993
+ name: propName,
11994
+ in: "query",
11995
+ required: requiredProps.includes(propName),
11996
+ schema: properties[propName]
11997
+ });
11998
+ }
11999
+ }
12000
+ if (action.bodySchema) {
12001
+ const bodySchemaName = `${toPascalCase2(controller.name || controllerKey)}${toPascalCase2(actionName)}Body`;
12002
+ const bodyJsonSchema = action.bodySchema;
12003
+ this.schemas[bodySchemaName] = bodyJsonSchema;
12004
+ operation.requestBody = {
12005
+ required: true,
12006
+ content: {
12007
+ "application/json": {
12008
+ schema: { $ref: `#/components/schemas/${bodySchemaName}` }
12009
+ }
12010
+ }
12011
+ };
12012
+ }
12013
+ if (action.isStream) {
12014
+ operation.description = (operation.description ? operation.description + "\n\n" : "") + "This endpoint supports Server-Sent Events (SSE) for real-time updates. It functions as a standard GET request initially, then maintains an open connection for streaming data.";
12015
+ }
12016
+ paths[path11][action.method.toLowerCase()] = operation;
12017
+ }
12018
+ }
12019
+ return paths;
12020
+ }
12021
+ };
11689
12022
  }
11690
12023
  });
11691
12024
 
@@ -11694,17 +12027,18 @@ var watcher_exports = {};
11694
12027
  __export(watcher_exports, {
11695
12028
  IgniterWatcher: () => IgniterWatcher
11696
12029
  });
11697
- var fs7, path8, import_url, import_chokidar, IgniterWatcher;
12030
+ var fs8, path9, import_chokidar, IgniterWatcher;
11698
12031
  var init_watcher = __esm({
11699
12032
  "src/adapters/build/watcher.ts"() {
11700
12033
  "use strict";
11701
- fs7 = __toESM(require("fs"));
11702
- path8 = __toESM(require("path"));
11703
- import_url = require("url");
12034
+ fs8 = __toESM(require("fs"));
12035
+ path9 = __toESM(require("path"));
12036
+ init_introspector();
11704
12037
  init_generator();
11705
12038
  init_logger();
11706
12039
  init_spinner();
11707
12040
  import_chokidar = __toESM(require("chokidar"));
12041
+ init_openapi_generator();
11708
12042
  IgniterWatcher = class {
11709
12043
  // Detect interactive mode
11710
12044
  constructor(config) {
@@ -11724,6 +12058,8 @@ var init_watcher = __esm({
11724
12058
  hotReload: true,
11725
12059
  controllerPatterns: ["**/*.controller.{ts,js}"],
11726
12060
  debug: false,
12061
+ generateDocs: false,
12062
+ docsOutputDir: "./src/docs",
11727
12063
  ...config
11728
12064
  };
11729
12065
  this.isInteractiveMode = !!(process.env.IGNITER_INTERACTIVE_MODE === "true" || process.argv.includes("--interactive"));
@@ -11785,380 +12121,6 @@ var init_watcher = __esm({
11785
12121
  process.stdout.write("\n");
11786
12122
  }
11787
12123
  }
11788
- /**
11789
- * Load router from file with simplified approach
11790
- */
11791
- async loadRouter(routerPath) {
11792
- const logger6 = createChildLogger({ component: "router-loader" });
11793
- const fullPath = path8.resolve(process.cwd(), routerPath);
11794
- logger6.debug("Loading router", { path: routerPath });
11795
- try {
11796
- const module2 = await this.loadWithTypeScriptSupport(fullPath);
11797
- if (module2) {
11798
- if (module2?.config && module2?.controllers) {
11799
- const controllerCount = Object.keys(module2.controllers || {}).length;
11800
- return module2;
11801
- }
11802
- const router = module2?.AppRouter || module2?.default?.AppRouter || module2?.default || module2?.router;
11803
- if (router && typeof router === "object") {
11804
- const controllerCount = Object.keys(router.controllers || {}).length;
11805
- return router;
11806
- } else {
11807
- logger6.debug("Available exports", {
11808
- exports: Object.keys(module2 || {})
11809
- });
11810
- }
11811
- } else {
11812
- }
11813
- const fallbackSpinner = createDetachedSpinner("Trying fallback loading method...");
11814
- fallbackSpinner.start();
11815
- const fallbackModule = await this.loadRouterWithIndexResolution(fullPath);
11816
- if (fallbackModule) {
11817
- if (fallbackModule?.config && fallbackModule?.controllers) {
11818
- const controllerCount = Object.keys(fallbackModule.controllers || {}).length;
11819
- fallbackSpinner.success(`Router loaded successfully - ${controllerCount} controllers`);
11820
- return fallbackModule;
11821
- }
11822
- const router = fallbackModule?.AppRouter || fallbackModule?.default?.AppRouter || fallbackModule?.default || fallbackModule?.router;
11823
- if (router && typeof router === "object") {
11824
- const controllerCount = Object.keys(router.controllers || {}).length;
11825
- fallbackSpinner.success(`Router loaded successfully - ${controllerCount} controllers`);
11826
- return router;
11827
- }
11828
- }
11829
- fallbackSpinner.error("Could not load router");
11830
- } catch (error) {
11831
- logger6.error("Failed to load router", { path: routerPath }, error);
11832
- }
11833
- return null;
11834
- }
11835
- /**
11836
- * Load TypeScript files using TSX runtime loader
11837
- * This is the NEW robust approach that replaces the problematic transpilation
11838
- */
11839
- async loadWithTypeScriptSupport(filePath) {
11840
- const logger6 = createChildLogger({ component: "tsx-loader" });
11841
- const jsPath = filePath.replace(/\.ts$/, ".js");
11842
- if (fs7.existsSync(jsPath)) {
11843
- try {
11844
- logger6.debug("Using compiled JS version");
11845
- delete require.cache[jsPath];
11846
- const module2 = require(jsPath);
11847
- return module2;
11848
- } catch (error) {
11849
- logger6.debug("Compiled JS loading failed, trying TypeScript...");
11850
- }
11851
- }
11852
- if (filePath.endsWith(".ts")) {
11853
- try {
11854
- logger6.debug("Using TSX runtime loader");
11855
- const { spawn: spawn2 } = require("child_process");
11856
- const tsxCheckResult = await new Promise((resolve4) => {
11857
- const checkChild = spawn2("npx", ["tsx", "--version"], {
11858
- stdio: "pipe",
11859
- cwd: process.cwd()
11860
- });
11861
- checkChild.on("close", (code) => {
11862
- resolve4(code === 0);
11863
- });
11864
- checkChild.on("error", () => {
11865
- resolve4(false);
11866
- });
11867
- setTimeout(() => {
11868
- checkChild.kill();
11869
- resolve4(false);
11870
- }, 5e3);
11871
- });
11872
- if (!tsxCheckResult) {
11873
- throw new Error("TSX not available");
11874
- }
11875
- const result = await new Promise((resolve4, reject) => {
11876
- const tsxScript = `
11877
- async function loadRouter() {
11878
- try {
11879
- const module = await import('${(0, import_url.pathToFileURL)(filePath).href}');
11880
- const router = module?.AppRouter || module?.default?.AppRouter || module?.default || module?.router;
11881
-
11882
- if (router && typeof router === 'object') {
11883
- // Extract safe metadata for CLI use
11884
- const safeRouter = {
11885
- config: {
11886
- baseURL: router.config?.baseURL || '',
11887
- basePATH: router.config?.basePATH || ''
11888
- },
11889
- controllers: {}
11890
- };
11891
-
11892
- // Extract controller metadata (no handlers/functions)
11893
- if (router.controllers && typeof router.controllers === 'object') {
11894
- for (const [controllerName, controller] of Object.entries(router.controllers)) {
11895
- if (controller && typeof controller === 'object' && (controller as any).actions) {
11896
- const safeActions: Record<string, any> = {};
11897
-
11898
- for (const [actionName, action] of Object.entries((controller as any).actions)) {
11899
- if (action && typeof action === 'object') {
11900
- // Extract only metadata, no functions
11901
- safeActions[actionName] = {
11902
- path: (action as any).path || '',
11903
- method: (action as any).method || 'GET',
11904
- description: (action as any).description,
11905
- // Keep type inference data if available
11906
- $Infer: (action as any).$Infer
11907
- };
11908
- }
11909
- }
11910
-
11911
- safeRouter.controllers[controllerName] = {
11912
- name: (controller as any).name || controllerName,
11913
- path: (controller as any).path || '',
11914
- actions: safeActions
11915
- };
11916
- }
11917
- }
11918
- }
11919
-
11920
- console.log('__ROUTER_SUCCESS__' + JSON.stringify(safeRouter));
11921
- process.exit(0); // Force exit after success
11922
- } else {
11923
- console.log('__ROUTER_ERROR__No router found in module');
11924
- process.exit(1); // Force exit after error
11925
- }
11926
- } catch (error) {
11927
- console.log('__ROUTER_ERROR__' + error.message);
11928
- process.exit(1); // Force exit after error
11929
- }
11930
- }
11931
-
11932
- loadRouter();
11933
- `;
11934
- const child = spawn2("npx", ["tsx", "-e", tsxScript], {
11935
- stdio: "pipe",
11936
- cwd: process.cwd(),
11937
- env: { ...process.env, NODE_NO_WARNINGS: "1" }
11938
- });
11939
- let output = "";
11940
- let errorOutput = "";
11941
- child.stdout?.on("data", (data) => {
11942
- output += data.toString();
11943
- });
11944
- child.stderr?.on("data", (data) => {
11945
- errorOutput += data.toString();
11946
- });
11947
- child.on("close", (code) => {
11948
- if (output.includes("__ROUTER_SUCCESS__")) {
11949
- const resultLine = output.split("\n").find((line) => line.includes("__ROUTER_SUCCESS__"));
11950
- if (resultLine) {
11951
- try {
11952
- const routerData = JSON.parse(resultLine.replace("__ROUTER_SUCCESS__", ""));
11953
- resolve4(routerData);
11954
- } catch (e) {
11955
- reject(new Error("Failed to parse router data: " + e.message));
11956
- }
11957
- } else {
11958
- reject(new Error("Router success marker found but no data"));
11959
- }
11960
- } else if (output.includes("__ROUTER_ERROR__")) {
11961
- const errorLine = output.split("\n").find((line) => line.includes("__ROUTER_ERROR__"));
11962
- const errorMsg = errorLine ? errorLine.replace("__ROUTER_ERROR__", "") : "Unknown error";
11963
- reject(new Error("Router loading failed: " + errorMsg));
11964
- } else {
11965
- reject(new Error(`TSX execution failed with code ${code}: ${errorOutput || "No output"}`));
11966
- }
11967
- });
11968
- child.on("error", (error) => {
11969
- reject(new Error("Failed to spawn TSX process: " + error.message));
11970
- });
11971
- setTimeout(() => {
11972
- child.kill();
11973
- reject(new Error("Timeout loading TypeScript file with TSX"));
11974
- }, 3e4);
11975
- });
11976
- return result;
11977
- } catch (error) {
11978
- logger6.debug("TSX runtime loader failed", {}, error);
11979
- }
11980
- }
11981
- return null;
11982
- }
11983
- /**
11984
- * Load router by resolving directory imports to index files
11985
- */
11986
- async loadRouterWithIndexResolution(routerPath) {
11987
- const logger6 = createChildLogger({ component: "index-resolver" });
11988
- try {
11989
- const routerContent = fs7.readFileSync(routerPath, "utf8");
11990
- const importRegex = /from\s+['\"]([^'\"]+)['\"]/g;
11991
- let resolvedContent = routerContent;
11992
- const matches = Array.from(routerContent.matchAll(importRegex));
11993
- for (const [fullMatch, importPath] of matches) {
11994
- if (!importPath.startsWith(".") && !importPath.startsWith("@")) {
11995
- continue;
11996
- }
11997
- const basePath = path8.dirname(routerPath);
11998
- let resolvedPath;
11999
- if (importPath.startsWith("@/")) {
12000
- resolvedPath = path8.resolve(process.cwd(), "src", importPath.substring(2));
12001
- } else if (importPath.startsWith("./")) {
12002
- resolvedPath = path8.resolve(basePath, importPath);
12003
- } else {
12004
- resolvedPath = path8.resolve(basePath, importPath);
12005
- }
12006
- let finalPath = importPath;
12007
- if (!importPath.match(/\\.(js|ts|tsx|jsx)$/)) {
12008
- const possibleFiles = [
12009
- resolvedPath + ".ts",
12010
- resolvedPath + ".tsx",
12011
- resolvedPath + ".js",
12012
- resolvedPath + ".jsx"
12013
- ];
12014
- let fileFound = false;
12015
- for (const filePath of possibleFiles) {
12016
- if (fs7.existsSync(filePath)) {
12017
- const ext = path8.extname(filePath);
12018
- finalPath = importPath + ext;
12019
- fileFound = true;
12020
- break;
12021
- }
12022
- }
12023
- if (!fileFound) {
12024
- const possibleIndexFiles = [
12025
- path8.join(resolvedPath, "index.ts"),
12026
- path8.join(resolvedPath, "index.tsx"),
12027
- path8.join(resolvedPath, "index.js"),
12028
- path8.join(resolvedPath, "index.jsx")
12029
- ];
12030
- for (const indexFile of possibleIndexFiles) {
12031
- if (fs7.existsSync(indexFile)) {
12032
- const ext = path8.extname(indexFile);
12033
- finalPath = importPath + "/index" + ext;
12034
- break;
12035
- }
12036
- }
12037
- }
12038
- }
12039
- const absolutePath = path8.resolve(basePath, finalPath);
12040
- if (fs7.existsSync(absolutePath)) {
12041
- const fileUrl = (0, import_url.pathToFileURL)(absolutePath).href;
12042
- resolvedContent = resolvedContent.replace(fullMatch, `from '${fileUrl}'`);
12043
- } else {
12044
- resolvedContent = resolvedContent.replace(fullMatch, `from '${finalPath}'`);
12045
- }
12046
- }
12047
- const tempFileName = `igniter-temp-${Date.now()}.ts`;
12048
- const tempFilePath = path8.join(process.cwd(), tempFileName);
12049
- try {
12050
- fs7.writeFileSync(tempFilePath, resolvedContent);
12051
- logger6.debug("Loading resolved module via TSX");
12052
- const { spawn: spawn2 } = require("child_process");
12053
- const result = await new Promise((resolve4, reject) => {
12054
- const tsxScript = `
12055
- async function loadResolvedRouter() {
12056
- try {
12057
- const module = await import('${(0, import_url.pathToFileURL)(tempFilePath).href}');
12058
- const router = module?.AppRouter || module?.default?.AppRouter || module?.default || module?.router;
12059
-
12060
- if (router && typeof router === 'object') {
12061
- // Extract safe metadata
12062
- const safeRouter = {
12063
- config: {
12064
- baseURL: router.config?.baseURL || '',
12065
- basePATH: router.config?.basePATH || ''
12066
- },
12067
- controllers: {}
12068
- };
12069
-
12070
- if (router.controllers && typeof router.controllers === 'object') {
12071
- for (const [controllerName, controller] of Object.entries(router.controllers)) {
12072
- if (controller && typeof controller === 'object' && (controller as any).actions) {
12073
- const safeActions: Record<string, any> = {};
12074
-
12075
- for (const [actionName, action] of Object.entries((controller as any).actions)) {
12076
- if (action && typeof action === 'object') {
12077
- safeActions[actionName] = {
12078
- path: (action as any).path || '',
12079
- method: (action as any).method || 'GET',
12080
- description: (action as any).description,
12081
- $Infer: (action as any).$Infer
12082
- };
12083
- }
12084
- }
12085
-
12086
- safeRouter.controllers[controllerName] = {
12087
- name: (controller as any).name || controllerName,
12088
- path: (controller as any).path || '',
12089
- actions: safeActions
12090
- };
12091
- }
12092
- }
12093
- }
12094
-
12095
- console.log('__ROUTER_SUCCESS__' + JSON.stringify(safeRouter));
12096
- process.exit(0); // Force exit after success
12097
- } else {
12098
- console.log('__ROUTER_ERROR__No router found in resolved module');
12099
- process.exit(1); // Force exit after error
12100
- }
12101
- } catch (error) {
12102
- console.log('__ROUTER_ERROR__' + error.message);
12103
- process.exit(1); // Force exit after error
12104
- }
12105
- }
12106
-
12107
- loadResolvedRouter();
12108
- `;
12109
- const child = spawn2("npx", ["tsx", "-e", tsxScript], {
12110
- stdio: "pipe",
12111
- cwd: process.cwd(),
12112
- env: { ...process.env, NODE_NO_WARNINGS: "1" }
12113
- });
12114
- let output = "";
12115
- let errorOutput = "";
12116
- child.stdout?.on("data", (data) => {
12117
- output += data.toString();
12118
- });
12119
- child.stderr?.on("data", (data) => {
12120
- errorOutput += data.toString();
12121
- });
12122
- child.on("close", (code) => {
12123
- if (output.includes("__ROUTER_SUCCESS__")) {
12124
- const resultLine = output.split("\n").find((line) => line.includes("__ROUTER_SUCCESS__"));
12125
- if (resultLine) {
12126
- try {
12127
- const routerData = JSON.parse(resultLine.replace("__ROUTER_SUCCESS__", ""));
12128
- resolve4(routerData);
12129
- } catch (e) {
12130
- reject(new Error("Failed to parse resolved router data: " + e.message));
12131
- }
12132
- } else {
12133
- reject(new Error("Router success marker found but no data"));
12134
- }
12135
- } else if (output.includes("__ROUTER_ERROR__")) {
12136
- const errorLine = output.split("\n").find((line) => line.includes("__ROUTER_ERROR__"));
12137
- const errorMsg = errorLine ? errorLine.replace("__ROUTER_ERROR__", "") : "Unknown error";
12138
- reject(new Error("Router loading failed: " + errorMsg));
12139
- } else {
12140
- reject(new Error(`TSX execution failed with code ${code}: ${errorOutput || "No output"}`));
12141
- }
12142
- });
12143
- child.on("error", (error) => {
12144
- reject(new Error("Failed to spawn TSX process for resolved module: " + error.message));
12145
- });
12146
- setTimeout(() => {
12147
- child.kill();
12148
- reject(new Error("Timeout loading resolved TypeScript file with TSX"));
12149
- }, 3e4);
12150
- });
12151
- return result;
12152
- } finally {
12153
- if (fs7.existsSync(tempFilePath)) {
12154
- fs7.unlinkSync(tempFilePath);
12155
- }
12156
- }
12157
- } catch (error) {
12158
- logger6.error("Index resolution failed", { path: routerPath }, error);
12159
- throw error;
12160
- }
12161
- }
12162
12124
  /**
12163
12125
  * Start watching controller files
12164
12126
  */
@@ -12173,7 +12135,7 @@ var init_watcher = __esm({
12173
12135
  persistent: true,
12174
12136
  ignoreInitial: false
12175
12137
  });
12176
- await new Promise((resolve4, reject) => {
12138
+ await new Promise((resolve5, reject) => {
12177
12139
  this.watcher.on("add", this.handleFileChange.bind(this));
12178
12140
  this.watcher.on("change", this.handleFileChange.bind(this));
12179
12141
  this.watcher.on("unlink", this.handleFileChange.bind(this));
@@ -12181,7 +12143,7 @@ var init_watcher = __esm({
12181
12143
  this.logger.success("File watcher is ready", {
12182
12144
  watching: this.config.controllerPatterns?.join(", ")
12183
12145
  });
12184
- resolve4();
12146
+ resolve5();
12185
12147
  });
12186
12148
  this.watcher.on("error", (error) => {
12187
12149
  this.logger.error("File watcher error", {}, error);
@@ -12254,8 +12216,8 @@ var init_watcher = __esm({
12254
12216
  ];
12255
12217
  let router = null;
12256
12218
  for (const routerPath of possibleRouterPaths) {
12257
- if (fs7.existsSync(routerPath)) {
12258
- router = await this.loadRouter(routerPath);
12219
+ if (fs8.existsSync(routerPath)) {
12220
+ router = await loadRouter(routerPath);
12259
12221
  if (router) {
12260
12222
  break;
12261
12223
  } else {
@@ -12270,20 +12232,43 @@ var init_watcher = __esm({
12270
12232
  return;
12271
12233
  }
12272
12234
  await generateSchemaFromRouter(router, this.config);
12235
+ if (this.config.generateDocs) {
12236
+ await this.generateOpenAPISpec(router);
12237
+ }
12273
12238
  } catch (error) {
12274
12239
  this.logger.error("Schema generation failed", {}, error);
12275
12240
  } finally {
12276
12241
  this.isGenerating = false;
12277
12242
  }
12278
12243
  }
12244
+ /**
12245
+ * Generate OpenAPI specification from router
12246
+ */
12247
+ async generateOpenAPISpec(router) {
12248
+ try {
12249
+ this.logger.info("Generating OpenAPI specification...");
12250
+ const introspected = introspectRouter(router);
12251
+ const generator = new OpenAPIGenerator(introspected.schema.docs || {});
12252
+ const openApiSpec = generator.generate(introspected.schema);
12253
+ const outputDir = path9.resolve(this.config.docsOutputDir);
12254
+ if (!fs8.existsSync(outputDir)) {
12255
+ fs8.mkdirSync(outputDir, { recursive: true });
12256
+ }
12257
+ const outputPath = path9.join(outputDir, "openapi.json");
12258
+ fs8.writeFileSync(outputPath, JSON.stringify(openApiSpec, null, 2), "utf8");
12259
+ this.logger.success(`OpenAPI specification updated at ${outputPath}`);
12260
+ } catch (error) {
12261
+ this.logger.error("Error generating OpenAPI specification:", error);
12262
+ }
12263
+ }
12279
12264
  };
12280
12265
  }
12281
12266
  });
12282
12267
 
12283
12268
  // src/index.ts
12284
12269
  var import_commander = require("commander");
12285
- var fs8 = __toESM(require("fs"));
12286
- var path9 = __toESM(require("path"));
12270
+ var fs9 = __toESM(require("fs"));
12271
+ var path10 = __toESM(require("path"));
12287
12272
 
12288
12273
  // src/adapters/framework/framework-detector.ts
12289
12274
  var fs2 = __toESM(require("fs"));
@@ -14467,13 +14452,13 @@ program.command("init").description("Create a new Igniter.js project with intera
14467
14452
  process.exit(1);
14468
14453
  }
14469
14454
  }
14470
- const targetDir = projectName === "." ? process.cwd() : path9.resolve(projectName);
14471
- const isExistingProject = await fs8.promises.stat(path9.join(targetDir, "package.json")).catch(() => null) !== null;
14455
+ const targetDir = projectName === "." ? process.cwd() : path10.resolve(projectName);
14456
+ const isExistingProject = await fs9.promises.stat(path10.join(targetDir, "package.json")).catch(() => null) !== null;
14472
14457
  if (!options.force) {
14473
14458
  try {
14474
- const stats = await fs8.promises.stat(targetDir);
14459
+ const stats = await fs9.promises.stat(targetDir);
14475
14460
  if (stats.isDirectory()) {
14476
- const files = await fs8.promises.readdir(targetDir);
14461
+ const files = await fs9.promises.readdir(targetDir);
14477
14462
  const nonEmptyFiles = files.filter((file) => !file.startsWith("."));
14478
14463
  if (nonEmptyFiles.length > 0 && !isExistingProject) {
14479
14464
  const shouldOverwrite = await confirmOverwrite(`Directory '${projectName}' is not empty. Continue?`);
@@ -14502,7 +14487,7 @@ program.command("init").description("Create a new Igniter.js project with intera
14502
14487
  process.exit(1);
14503
14488
  }
14504
14489
  });
14505
- program.command("dev").description("Start development mode with framework and Igniter (interactive dashboard by default)").option("--framework <type>", `Framework type (${getFrameworkList()}, generic)`).option("--output <dir>", "Output directory for generated client files", "src/").option("--debug", "Enable debug mode").option("--port <number>", "Port for the dev server", "3000").option("--cmd <command>", "Custom command to start dev server").option("--no-framework", "Disable framework dev server (Igniter only)").option("--no-interactive", "Disable interactive mode (use regular concurrent mode)").action(async (options) => {
14490
+ program.command("dev").description("Start development mode with framework and Igniter (interactive dashboard by default)").option("--framework <type>", `Framework type (${getFrameworkList()}, generic)`).option("--output <dir>", "Output directory for generated client files", "src/").option("--debug", "Enable debug mode").option("--port <number>", "Port for the dev server", "3000").option("--cmd <command>", "Custom command to start dev server").option("--no-framework", "Disable framework dev server (Igniter only)").option("--no-interactive", "Disable interactive mode (use regular concurrent mode)").option("--docs", "Enable automatic OpenAPI documentation generation").option("--docs-output <dir>", "Output directory for OpenAPI docs", "./src/docs").action(async (options) => {
14506
14491
  const detectedFramework = detectFramework();
14507
14492
  const framework = options.framework ? isFrameworkSupported(options.framework) ? options.framework : "generic" : detectedFramework;
14508
14493
  const useInteractive = options.interactive !== false;
@@ -14531,9 +14516,10 @@ program.command("dev").description("Start development mode with framework and Ig
14531
14516
  });
14532
14517
  }
14533
14518
  }
14519
+ const docsFlags = options.docs ? ` --docs --docs-output ${options.docsOutput}` : "";
14534
14520
  processes.push({
14535
14521
  name: "Igniter",
14536
- command: `igniter generate schema --watch --framework ${framework} --output ${options.output}${options.debug ? " --debug" : ""}`,
14522
+ command: `igniter generate schema --watch --framework ${framework} --output ${options.output}${options.debug ? " --debug" : ""}${docsFlags}`,
14537
14523
  color: "blue",
14538
14524
  cwd: process.cwd()
14539
14525
  });
@@ -14544,7 +14530,7 @@ program.command("dev").description("Start development mode with framework and Ig
14544
14530
  }
14545
14531
  });
14546
14532
  var generate = program.command("generate").description("Scaffold new features or generate client schema");
14547
- generate.command("schema").description("Generate client schema from your Igniter router (for CI/CD or manual builds)").option("--framework <type>", `Framework type (${getFrameworkList()}, generic)`).option("--output <dir>", "Output directory", "src/").option("--debug", "Enable debug mode").option("--watch", "Watch for changes and regenerate automatically").action(async (options) => {
14533
+ generate.command("schema").description("Generate client schema from your Igniter router (for CI/CD or manual builds)").option("--framework <type>", `Framework type (${getFrameworkList()}, generic)`).option("--output <dir>", "Output directory", "src/").option("--debug", "Enable debug mode").option("--watch", "Watch for changes and regenerate automatically").option("--docs", "Enable automatic OpenAPI documentation generation").option("--docs-output <dir>", "Output directory for OpenAPI docs", "./src/docs").action(async (options) => {
14548
14534
  const startTime = performance.now();
14549
14535
  const detectedFramework = detectFramework();
14550
14536
  const framework = options.framework ? isFrameworkSupported(options.framework) ? options.framework : "generic" : detectedFramework;
@@ -14557,7 +14543,9 @@ generate.command("schema").description("Generate client schema from your Igniter
14557
14543
  framework,
14558
14544
  outputDir: options.output,
14559
14545
  debug: options.debug,
14560
- controllerPatterns: ["**/*.controller.{ts,js}"]
14546
+ controllerPatterns: ["**/*.controller.{ts,js}"],
14547
+ generateDocs: options.docs,
14548
+ docsOutputDir: options.docsOutput
14561
14549
  });
14562
14550
  watcherSpinner.success("Generator loaded");
14563
14551
  if (options.watch) {
@@ -14570,6 +14558,69 @@ generate.command("schema").description("Generate client schema from your Igniter
14570
14558
  process.exit(0);
14571
14559
  }
14572
14560
  });
14561
+ generate.command("docs").description("Generate OpenAPI specification and/or interactive playground").option("--output <dir>", "Output directory for the OpenAPI spec", "./src").option("--ui", "Generate a self-contained HTML file with Scalar UI").action(async (options) => {
14562
+ const docsLogger = createChildLogger({ component: "generate-docs-command" });
14563
+ try {
14564
+ docsLogger.info("Starting OpenAPI documentation generation...");
14565
+ const { loadRouter: loadRouter2, introspectRouter: introspectRouter2 } = await Promise.resolve().then(() => (init_introspector(), introspector_exports));
14566
+ const { OpenAPIGenerator: OpenAPIGenerator2 } = await Promise.resolve().then(() => (init_openapi_generator(), openapi_generator_exports));
14567
+ const possibleRouterPaths = [
14568
+ "src/igniter.router.ts",
14569
+ "src/igniter.router.js",
14570
+ "src/router.ts",
14571
+ "src/router.js",
14572
+ "igniter.router.ts",
14573
+ "igniter.router.js",
14574
+ "router.ts",
14575
+ "router.js"
14576
+ ];
14577
+ let router = null;
14578
+ for (const routerPath of possibleRouterPaths) {
14579
+ if (fs9.existsSync(routerPath)) {
14580
+ router = await loadRouter2(routerPath);
14581
+ if (router) {
14582
+ docsLogger.info(`Found router at: ${routerPath}`);
14583
+ break;
14584
+ }
14585
+ }
14586
+ }
14587
+ if (!router) {
14588
+ docsLogger.error("No Igniter router found in your project. Please ensure you have a router file (e.g., src/igniter.router.ts).");
14589
+ process.exit(1);
14590
+ }
14591
+ const introspected = introspectRouter2(router);
14592
+ const generator = new OpenAPIGenerator2(introspected.schema.docs || {});
14593
+ const openApiSpec = generator.generate(introspected.schema);
14594
+ const outputDir = path10.resolve(options.output, "docs");
14595
+ if (!fs9.existsSync(outputDir)) {
14596
+ fs9.mkdirSync(outputDir, { recursive: true });
14597
+ }
14598
+ const outputPath = path10.join(outputDir, "openapi.json");
14599
+ fs9.writeFileSync(outputPath, JSON.stringify(openApiSpec, null, 2), "utf8");
14600
+ docsLogger.success(`OpenAPI specification generated at ${outputPath}`);
14601
+ if (options.ui) {
14602
+ const scalarHtml = `<!doctype html>
14603
+ <html lang="en">
14604
+ <head>
14605
+ <meta charset="utf-8" />
14606
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
14607
+ <title>API Reference</title>
14608
+ </head>
14609
+ <body>
14610
+ <script id="api-reference" data-url="./openapi.json"></script>
14611
+ <script src="https://cdn.jsdelivr.net/npm/@scalar/api-reference"></script>
14612
+ </body>
14613
+ </html>`;
14614
+ const uiOutputPath = path10.join(outputDir, "index.html");
14615
+ fs9.writeFileSync(uiOutputPath, scalarHtml, "utf8");
14616
+ docsLogger.success(`Scalar UI generated at ${uiOutputPath}`);
14617
+ }
14618
+ } catch (error) {
14619
+ docsLogger.error("Failed to generate OpenAPI documentation:");
14620
+ console.error(error);
14621
+ process.exit(1);
14622
+ }
14623
+ });
14573
14624
  generate.command("feature").description("Scaffold a new feature module").argument("<name>", "The name of the feature (e.g., 'user', 'products')").option("--schema <value>", "Generate from a schema provider (e.g., 'prisma:User')").action(async (name, options) => {
14574
14625
  await handleGenerateFeature(name, options);
14575
14626
  });