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