@igniter-js/cli 0.2.4 → 0.2.6

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,63 +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
- config: {
11493
- baseURL: router.config?.baseURL || "",
11494
- basePATH: router.config?.basePATH || ""
11495
- },
11496
- controllers: controllersSchema,
11497
- processor: {},
11498
- handler: {},
11499
- $context: {},
11500
- $plugins: {},
11501
- $caller: {}
11497
+ const schemaResult = {
11498
+ controllers: introspectedControllers,
11499
+ docs: router?.config?.docs
11502
11500
  };
11503
11501
  return {
11504
- schema,
11502
+ schema: schemaResult,
11505
11503
  stats: {
11506
- controllers: Object.keys(controllersSchema).length,
11504
+ controllers: Object.keys(introspectedControllers).length,
11507
11505
  actions: totalActions
11508
11506
  }
11509
11507
  };
11510
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
+ }
11511
11707
  async function generateSchemaFromRouter(router, config) {
11512
11708
  const logger6 = createChildLogger({ component: "generator" });
11513
11709
  const startTime = performance.now();
@@ -11520,7 +11716,7 @@ async function generateSchemaFromRouter(router, config) {
11520
11716
  extractSpinner = createDetachedSpinner("Extracting router schema...");
11521
11717
  extractSpinner.start();
11522
11718
  }
11523
- const { schema, stats } = extractRouterSchema(router);
11719
+ const { schema, stats } = introspectRouter(router);
11524
11720
  if (isInteractiveMode2) {
11525
11721
  logger6.success(`Schema extracted - ${stats.controllers} controllers, ${stats.actions} actions`);
11526
11722
  } else if (extractSpinner) {
@@ -11535,7 +11731,7 @@ async function generateSchemaFromRouter(router, config) {
11535
11731
  }
11536
11732
  const outputDir = config.outputDir || "generated";
11537
11733
  await ensureDirectoryExists(outputDir);
11538
- const outputPath = path7.resolve(outputDir);
11734
+ const outputPath = path8.resolve(outputDir);
11539
11735
  if (isInteractiveMode2) {
11540
11736
  logger6.success(`Output directory ready ${outputPath}`);
11541
11737
  } else if (dirSpinner) {
@@ -11565,8 +11761,8 @@ async function generateSchemaFromRouter(router, config) {
11565
11761
  let totalSize = 0;
11566
11762
  files.forEach((file) => {
11567
11763
  const size = getFileSize(file.path);
11568
- totalSize += fs6.statSync(file.path).size;
11569
- 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) });
11570
11766
  });
11571
11767
  const duration = ((performance.now() - startTime) / 1e3).toFixed(2);
11572
11768
  const totalSizeFormatted = totalSize < 1024 ? `${totalSize}b` : totalSize < 1024 * 1024 ? `${(totalSize / 1024).toFixed(1)}kb` : `${(totalSize / (1024 * 1024)).toFixed(1)}mb`;
@@ -11599,33 +11795,45 @@ export const AppRouterSchema = ${JSON.stringify(schema, null, 2)} as const
11599
11795
 
11600
11796
  export type AppRouterSchemaType = typeof AppRouterSchema
11601
11797
  `;
11602
- const filePath = path7.join(outputDir, "igniter.schema.ts");
11798
+ const filePath = path8.join(outputDir, "igniter.schema.ts");
11603
11799
  await writeFileWithHeader(filePath, content, config);
11604
11800
  return filePath;
11605
11801
  }
11606
11802
  async function generateClientFile(schema, outputDir, config) {
11607
- const content = `import { createIgniterClient, useIgniterQueryClient } from '@igniter-js/core/client'
11803
+ const filePath = path8.join(outputDir, "igniter.client.ts");
11804
+ if (fs7.existsSync(filePath)) {
11805
+ const logger6 = createChildLogger({ component: "generator" });
11806
+ logger6.info("Skipping client file generation, already exists", { path: filePath });
11807
+ return filePath;
11808
+ }
11809
+ const content = `* eslint-disable */
11810
+ /* prettier-ignore */
11811
+
11812
+ import { createIgniterClient, useIgniterQueryClient } from '@igniter-js/core/client'
11608
11813
  import type { AppRouterType } from './igniter.router'
11609
11814
 
11610
11815
  /**
11611
- * Type-safe API client generated from your Igniter router
11612
- *
11613
- * Usage in Server Components:
11614
- * const users = await api.users.list.query()
11615
- *
11616
- * Usage in Client Components:
11617
- * const { data } = api.users.list.useQuery()
11618
- */
11816
+ * Type-safe API client generated from your Igniter router
11817
+ *
11818
+ * Usage in Server Components:
11819
+ * const users = await api.users.list.query()
11820
+ *
11821
+ * Usage in Client Components:
11822
+ * const { data } = api.users.list.useQuery()
11823
+ *
11824
+ * Note: Adjust environment variable prefixes (e.g., NEXT_PUBLIC_, BUN_PUBLIC_, DENO_PUBLIC_, REACT_APP_)
11825
+ * according to your project's framework/runtime (Next.js, Bun, Deno, React/Vite, etc.).
11826
+ */
11619
11827
  export const api = createIgniterClient<AppRouterType>({
11620
- baseURL: 'http://localhost:3000',
11621
- basePath: '/api/v1/',
11622
- router: () => {
11623
- if (typeof window === 'undefined') {
11624
- return require('./igniter.router').AppRouter
11625
- }
11828
+ baseURL: process.env.NEXT_PUBLIC_IGNITER_API_URL, // Adapt for your needs
11829
+ basePath: process.env.NEXT_PUBLIC_IGNITER_API_BASE_PATH,
11830
+ router: () => {
11831
+ if (typeof window === 'undefined') {
11832
+ return require('./igniter.router').AppRouter
11833
+ }
11626
11834
 
11627
- return require('./igniter.schema').AppRouterSchema
11628
- },
11835
+ return require('./igniter.schema').AppRouterSchema
11836
+ },
11629
11837
  })
11630
11838
 
11631
11839
  /**
@@ -11647,18 +11855,18 @@ export type ApiClient = typeof api
11647
11855
  */
11648
11856
  export const useQueryClient = useIgniterQueryClient<AppRouterType>;
11649
11857
  `;
11650
- const filePath = path7.join(outputDir, "igniter.client.ts");
11651
11858
  await writeFileWithHeader(filePath, content, config);
11652
11859
  return filePath;
11653
11860
  }
11654
11861
  async function writeFileWithHeader(filePath, content, config) {
11655
11862
  const header = generateFileHeader(config);
11656
11863
  const fullContent = header + "\n\n" + content;
11657
- await fs6.promises.writeFile(filePath, fullContent, "utf8");
11864
+ await fs7.promises.writeFile(filePath, fullContent, "utf8");
11658
11865
  }
11659
11866
  function generateFileHeader(config) {
11660
11867
  const timestamp = (/* @__PURE__ */ new Date()).toISOString();
11661
- return `/* eslint-disable @typescript-eslint/no-var-requires */
11868
+ return `/* eslint-disable */
11869
+ /* prettier-ignore */
11662
11870
 
11663
11871
  /**
11664
11872
  * Generated by @igniter-js/cli
@@ -11677,9 +11885,9 @@ function generateFileHeader(config) {
11677
11885
  }
11678
11886
  async function ensureDirectoryExists(dirPath) {
11679
11887
  try {
11680
- await fs6.promises.access(dirPath);
11888
+ await fs7.promises.access(dirPath);
11681
11889
  } catch (error) {
11682
- await fs6.promises.mkdir(dirPath, { recursive: true });
11890
+ await fs7.promises.mkdir(dirPath, { recursive: true });
11683
11891
  }
11684
11892
  }
11685
11893
  var init_generator = __esm({
@@ -11687,6 +11895,133 @@ var init_generator = __esm({
11687
11895
  "use strict";
11688
11896
  init_logger();
11689
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
+ };
11690
12025
  }
11691
12026
  });
11692
12027
 
@@ -11695,17 +12030,18 @@ var watcher_exports = {};
11695
12030
  __export(watcher_exports, {
11696
12031
  IgniterWatcher: () => IgniterWatcher
11697
12032
  });
11698
- import * as fs7 from "fs";
11699
- import * as path8 from "path";
11700
- import { pathToFileURL } from "url";
12033
+ import * as fs8 from "fs";
12034
+ import * as path9 from "path";
11701
12035
  import chokidar from "chokidar";
11702
12036
  var IgniterWatcher;
11703
12037
  var init_watcher = __esm({
11704
12038
  "src/adapters/build/watcher.ts"() {
11705
12039
  "use strict";
12040
+ init_introspector();
11706
12041
  init_generator();
11707
12042
  init_logger();
11708
12043
  init_spinner();
12044
+ init_openapi_generator();
11709
12045
  IgniterWatcher = class {
11710
12046
  // Detect interactive mode
11711
12047
  constructor(config) {
@@ -11725,6 +12061,8 @@ var init_watcher = __esm({
11725
12061
  hotReload: true,
11726
12062
  controllerPatterns: ["**/*.controller.{ts,js}"],
11727
12063
  debug: false,
12064
+ generateDocs: false,
12065
+ docsOutputDir: "./src/docs",
11728
12066
  ...config
11729
12067
  };
11730
12068
  this.isInteractiveMode = !!(process.env.IGNITER_INTERACTIVE_MODE === "true" || process.argv.includes("--interactive"));
@@ -11786,380 +12124,6 @@ var init_watcher = __esm({
11786
12124
  process.stdout.write("\n");
11787
12125
  }
11788
12126
  }
11789
- /**
11790
- * Load router from file with simplified approach
11791
- */
11792
- async loadRouter(routerPath) {
11793
- const logger6 = createChildLogger({ component: "router-loader" });
11794
- const fullPath = path8.resolve(process.cwd(), routerPath);
11795
- logger6.debug("Loading router", { path: routerPath });
11796
- try {
11797
- const module = await this.loadWithTypeScriptSupport(fullPath);
11798
- if (module) {
11799
- if (module?.config && module?.controllers) {
11800
- const controllerCount = Object.keys(module.controllers || {}).length;
11801
- return module;
11802
- }
11803
- const router = module?.AppRouter || module?.default?.AppRouter || module?.default || module?.router;
11804
- if (router && typeof router === "object") {
11805
- const controllerCount = Object.keys(router.controllers || {}).length;
11806
- return router;
11807
- } else {
11808
- logger6.debug("Available exports", {
11809
- exports: Object.keys(module || {})
11810
- });
11811
- }
11812
- } else {
11813
- }
11814
- const fallbackSpinner = createDetachedSpinner("Trying fallback loading method...");
11815
- fallbackSpinner.start();
11816
- const fallbackModule = await this.loadRouterWithIndexResolution(fullPath);
11817
- if (fallbackModule) {
11818
- if (fallbackModule?.config && fallbackModule?.controllers) {
11819
- const controllerCount = Object.keys(fallbackModule.controllers || {}).length;
11820
- fallbackSpinner.success(`Router loaded successfully - ${controllerCount} controllers`);
11821
- return fallbackModule;
11822
- }
11823
- const router = fallbackModule?.AppRouter || fallbackModule?.default?.AppRouter || fallbackModule?.default || fallbackModule?.router;
11824
- if (router && typeof router === "object") {
11825
- const controllerCount = Object.keys(router.controllers || {}).length;
11826
- fallbackSpinner.success(`Router loaded successfully - ${controllerCount} controllers`);
11827
- return router;
11828
- }
11829
- }
11830
- fallbackSpinner.error("Could not load router");
11831
- } catch (error) {
11832
- logger6.error("Failed to load router", { path: routerPath }, error);
11833
- }
11834
- return null;
11835
- }
11836
- /**
11837
- * Load TypeScript files using TSX runtime loader
11838
- * This is the NEW robust approach that replaces the problematic transpilation
11839
- */
11840
- async loadWithTypeScriptSupport(filePath) {
11841
- const logger6 = createChildLogger({ component: "tsx-loader" });
11842
- const jsPath = filePath.replace(/\.ts$/, ".js");
11843
- if (fs7.existsSync(jsPath)) {
11844
- try {
11845
- logger6.debug("Using compiled JS version");
11846
- delete __require.cache[jsPath];
11847
- const module = __require(jsPath);
11848
- return module;
11849
- } catch (error) {
11850
- logger6.debug("Compiled JS loading failed, trying TypeScript...");
11851
- }
11852
- }
11853
- if (filePath.endsWith(".ts")) {
11854
- try {
11855
- logger6.debug("Using TSX runtime loader");
11856
- const { spawn: spawn2 } = __require("child_process");
11857
- const tsxCheckResult = await new Promise((resolve4) => {
11858
- const checkChild = spawn2("npx", ["tsx", "--version"], {
11859
- stdio: "pipe",
11860
- cwd: process.cwd()
11861
- });
11862
- checkChild.on("close", (code) => {
11863
- resolve4(code === 0);
11864
- });
11865
- checkChild.on("error", () => {
11866
- resolve4(false);
11867
- });
11868
- setTimeout(() => {
11869
- checkChild.kill();
11870
- resolve4(false);
11871
- }, 5e3);
11872
- });
11873
- if (!tsxCheckResult) {
11874
- throw new Error("TSX not available");
11875
- }
11876
- const result = await new Promise((resolve4, reject) => {
11877
- const tsxScript = `
11878
- async function loadRouter() {
11879
- try {
11880
- const module = await import('${pathToFileURL(filePath).href}');
11881
- const router = module?.AppRouter || module?.default?.AppRouter || module?.default || module?.router;
11882
-
11883
- if (router && typeof router === 'object') {
11884
- // Extract safe metadata for CLI use
11885
- const safeRouter = {
11886
- config: {
11887
- baseURL: router.config?.baseURL || '',
11888
- basePATH: router.config?.basePATH || ''
11889
- },
11890
- controllers: {}
11891
- };
11892
-
11893
- // Extract controller metadata (no handlers/functions)
11894
- if (router.controllers && typeof router.controllers === 'object') {
11895
- for (const [controllerName, controller] of Object.entries(router.controllers)) {
11896
- if (controller && typeof controller === 'object' && (controller as any).actions) {
11897
- const safeActions: Record<string, any> = {};
11898
-
11899
- for (const [actionName, action] of Object.entries((controller as any).actions)) {
11900
- if (action && typeof action === 'object') {
11901
- // Extract only metadata, no functions
11902
- safeActions[actionName] = {
11903
- path: (action as any).path || '',
11904
- method: (action as any).method || 'GET',
11905
- description: (action as any).description,
11906
- // Keep type inference data if available
11907
- $Infer: (action as any).$Infer
11908
- };
11909
- }
11910
- }
11911
-
11912
- safeRouter.controllers[controllerName] = {
11913
- name: (controller as any).name || controllerName,
11914
- path: (controller as any).path || '',
11915
- actions: safeActions
11916
- };
11917
- }
11918
- }
11919
- }
11920
-
11921
- console.log('__ROUTER_SUCCESS__' + JSON.stringify(safeRouter));
11922
- process.exit(0); // Force exit after success
11923
- } else {
11924
- console.log('__ROUTER_ERROR__No router found in module');
11925
- process.exit(1); // Force exit after error
11926
- }
11927
- } catch (error) {
11928
- console.log('__ROUTER_ERROR__' + error.message);
11929
- process.exit(1); // Force exit after error
11930
- }
11931
- }
11932
-
11933
- loadRouter();
11934
- `;
11935
- const child = spawn2("npx", ["tsx", "-e", tsxScript], {
11936
- stdio: "pipe",
11937
- cwd: process.cwd(),
11938
- env: { ...process.env, NODE_NO_WARNINGS: "1" }
11939
- });
11940
- let output = "";
11941
- let errorOutput = "";
11942
- child.stdout?.on("data", (data) => {
11943
- output += data.toString();
11944
- });
11945
- child.stderr?.on("data", (data) => {
11946
- errorOutput += data.toString();
11947
- });
11948
- child.on("close", (code) => {
11949
- if (output.includes("__ROUTER_SUCCESS__")) {
11950
- const resultLine = output.split("\n").find((line) => line.includes("__ROUTER_SUCCESS__"));
11951
- if (resultLine) {
11952
- try {
11953
- const routerData = JSON.parse(resultLine.replace("__ROUTER_SUCCESS__", ""));
11954
- resolve4(routerData);
11955
- } catch (e) {
11956
- reject(new Error("Failed to parse router data: " + e.message));
11957
- }
11958
- } else {
11959
- reject(new Error("Router success marker found but no data"));
11960
- }
11961
- } else if (output.includes("__ROUTER_ERROR__")) {
11962
- const errorLine = output.split("\n").find((line) => line.includes("__ROUTER_ERROR__"));
11963
- const errorMsg = errorLine ? errorLine.replace("__ROUTER_ERROR__", "") : "Unknown error";
11964
- reject(new Error("Router loading failed: " + errorMsg));
11965
- } else {
11966
- reject(new Error(`TSX execution failed with code ${code}: ${errorOutput || "No output"}`));
11967
- }
11968
- });
11969
- child.on("error", (error) => {
11970
- reject(new Error("Failed to spawn TSX process: " + error.message));
11971
- });
11972
- setTimeout(() => {
11973
- child.kill();
11974
- reject(new Error("Timeout loading TypeScript file with TSX"));
11975
- }, 3e4);
11976
- });
11977
- return result;
11978
- } catch (error) {
11979
- logger6.debug("TSX runtime loader failed", {}, error);
11980
- }
11981
- }
11982
- return null;
11983
- }
11984
- /**
11985
- * Load router by resolving directory imports to index files
11986
- */
11987
- async loadRouterWithIndexResolution(routerPath) {
11988
- const logger6 = createChildLogger({ component: "index-resolver" });
11989
- try {
11990
- const routerContent = fs7.readFileSync(routerPath, "utf8");
11991
- const importRegex = /from\s+['\"]([^'\"]+)['\"]/g;
11992
- let resolvedContent = routerContent;
11993
- const matches = Array.from(routerContent.matchAll(importRegex));
11994
- for (const [fullMatch, importPath] of matches) {
11995
- if (!importPath.startsWith(".") && !importPath.startsWith("@")) {
11996
- continue;
11997
- }
11998
- const basePath = path8.dirname(routerPath);
11999
- let resolvedPath;
12000
- if (importPath.startsWith("@/")) {
12001
- resolvedPath = path8.resolve(process.cwd(), "src", importPath.substring(2));
12002
- } else if (importPath.startsWith("./")) {
12003
- resolvedPath = path8.resolve(basePath, importPath);
12004
- } else {
12005
- resolvedPath = path8.resolve(basePath, importPath);
12006
- }
12007
- let finalPath = importPath;
12008
- if (!importPath.match(/\\.(js|ts|tsx|jsx)$/)) {
12009
- const possibleFiles = [
12010
- resolvedPath + ".ts",
12011
- resolvedPath + ".tsx",
12012
- resolvedPath + ".js",
12013
- resolvedPath + ".jsx"
12014
- ];
12015
- let fileFound = false;
12016
- for (const filePath of possibleFiles) {
12017
- if (fs7.existsSync(filePath)) {
12018
- const ext = path8.extname(filePath);
12019
- finalPath = importPath + ext;
12020
- fileFound = true;
12021
- break;
12022
- }
12023
- }
12024
- if (!fileFound) {
12025
- const possibleIndexFiles = [
12026
- path8.join(resolvedPath, "index.ts"),
12027
- path8.join(resolvedPath, "index.tsx"),
12028
- path8.join(resolvedPath, "index.js"),
12029
- path8.join(resolvedPath, "index.jsx")
12030
- ];
12031
- for (const indexFile of possibleIndexFiles) {
12032
- if (fs7.existsSync(indexFile)) {
12033
- const ext = path8.extname(indexFile);
12034
- finalPath = importPath + "/index" + ext;
12035
- break;
12036
- }
12037
- }
12038
- }
12039
- }
12040
- const absolutePath = path8.resolve(basePath, finalPath);
12041
- if (fs7.existsSync(absolutePath)) {
12042
- const fileUrl = pathToFileURL(absolutePath).href;
12043
- resolvedContent = resolvedContent.replace(fullMatch, `from '${fileUrl}'`);
12044
- } else {
12045
- resolvedContent = resolvedContent.replace(fullMatch, `from '${finalPath}'`);
12046
- }
12047
- }
12048
- const tempFileName = `igniter-temp-${Date.now()}.ts`;
12049
- const tempFilePath = path8.join(process.cwd(), tempFileName);
12050
- try {
12051
- fs7.writeFileSync(tempFilePath, resolvedContent);
12052
- logger6.debug("Loading resolved module via TSX");
12053
- const { spawn: spawn2 } = __require("child_process");
12054
- const result = await new Promise((resolve4, reject) => {
12055
- const tsxScript = `
12056
- async function loadResolvedRouter() {
12057
- try {
12058
- const module = await import('${pathToFileURL(tempFilePath).href}');
12059
- const router = module?.AppRouter || module?.default?.AppRouter || module?.default || module?.router;
12060
-
12061
- if (router && typeof router === 'object') {
12062
- // Extract safe metadata
12063
- const safeRouter = {
12064
- config: {
12065
- baseURL: router.config?.baseURL || '',
12066
- basePATH: router.config?.basePATH || ''
12067
- },
12068
- controllers: {}
12069
- };
12070
-
12071
- if (router.controllers && typeof router.controllers === 'object') {
12072
- for (const [controllerName, controller] of Object.entries(router.controllers)) {
12073
- if (controller && typeof controller === 'object' && (controller as any).actions) {
12074
- const safeActions: Record<string, any> = {};
12075
-
12076
- for (const [actionName, action] of Object.entries((controller as any).actions)) {
12077
- if (action && typeof action === 'object') {
12078
- safeActions[actionName] = {
12079
- path: (action as any).path || '',
12080
- method: (action as any).method || 'GET',
12081
- description: (action as any).description,
12082
- $Infer: (action as any).$Infer
12083
- };
12084
- }
12085
- }
12086
-
12087
- safeRouter.controllers[controllerName] = {
12088
- name: (controller as any).name || controllerName,
12089
- path: (controller as any).path || '',
12090
- actions: safeActions
12091
- };
12092
- }
12093
- }
12094
- }
12095
-
12096
- console.log('__ROUTER_SUCCESS__' + JSON.stringify(safeRouter));
12097
- process.exit(0); // Force exit after success
12098
- } else {
12099
- console.log('__ROUTER_ERROR__No router found in resolved module');
12100
- process.exit(1); // Force exit after error
12101
- }
12102
- } catch (error) {
12103
- console.log('__ROUTER_ERROR__' + error.message);
12104
- process.exit(1); // Force exit after error
12105
- }
12106
- }
12107
-
12108
- loadResolvedRouter();
12109
- `;
12110
- const child = spawn2("npx", ["tsx", "-e", tsxScript], {
12111
- stdio: "pipe",
12112
- cwd: process.cwd(),
12113
- env: { ...process.env, NODE_NO_WARNINGS: "1" }
12114
- });
12115
- let output = "";
12116
- let errorOutput = "";
12117
- child.stdout?.on("data", (data) => {
12118
- output += data.toString();
12119
- });
12120
- child.stderr?.on("data", (data) => {
12121
- errorOutput += data.toString();
12122
- });
12123
- child.on("close", (code) => {
12124
- if (output.includes("__ROUTER_SUCCESS__")) {
12125
- const resultLine = output.split("\n").find((line) => line.includes("__ROUTER_SUCCESS__"));
12126
- if (resultLine) {
12127
- try {
12128
- const routerData = JSON.parse(resultLine.replace("__ROUTER_SUCCESS__", ""));
12129
- resolve4(routerData);
12130
- } catch (e) {
12131
- reject(new Error("Failed to parse resolved router data: " + e.message));
12132
- }
12133
- } else {
12134
- reject(new Error("Router success marker found but no data"));
12135
- }
12136
- } else if (output.includes("__ROUTER_ERROR__")) {
12137
- const errorLine = output.split("\n").find((line) => line.includes("__ROUTER_ERROR__"));
12138
- const errorMsg = errorLine ? errorLine.replace("__ROUTER_ERROR__", "") : "Unknown error";
12139
- reject(new Error("Router loading failed: " + errorMsg));
12140
- } else {
12141
- reject(new Error(`TSX execution failed with code ${code}: ${errorOutput || "No output"}`));
12142
- }
12143
- });
12144
- child.on("error", (error) => {
12145
- reject(new Error("Failed to spawn TSX process for resolved module: " + error.message));
12146
- });
12147
- setTimeout(() => {
12148
- child.kill();
12149
- reject(new Error("Timeout loading resolved TypeScript file with TSX"));
12150
- }, 3e4);
12151
- });
12152
- return result;
12153
- } finally {
12154
- if (fs7.existsSync(tempFilePath)) {
12155
- fs7.unlinkSync(tempFilePath);
12156
- }
12157
- }
12158
- } catch (error) {
12159
- logger6.error("Index resolution failed", { path: routerPath }, error);
12160
- throw error;
12161
- }
12162
- }
12163
12127
  /**
12164
12128
  * Start watching controller files
12165
12129
  */
@@ -12174,7 +12138,7 @@ var init_watcher = __esm({
12174
12138
  persistent: true,
12175
12139
  ignoreInitial: false
12176
12140
  });
12177
- await new Promise((resolve4, reject) => {
12141
+ await new Promise((resolve5, reject) => {
12178
12142
  this.watcher.on("add", this.handleFileChange.bind(this));
12179
12143
  this.watcher.on("change", this.handleFileChange.bind(this));
12180
12144
  this.watcher.on("unlink", this.handleFileChange.bind(this));
@@ -12182,7 +12146,7 @@ var init_watcher = __esm({
12182
12146
  this.logger.success("File watcher is ready", {
12183
12147
  watching: this.config.controllerPatterns?.join(", ")
12184
12148
  });
12185
- resolve4();
12149
+ resolve5();
12186
12150
  });
12187
12151
  this.watcher.on("error", (error) => {
12188
12152
  this.logger.error("File watcher error", {}, error);
@@ -12255,8 +12219,8 @@ var init_watcher = __esm({
12255
12219
  ];
12256
12220
  let router = null;
12257
12221
  for (const routerPath of possibleRouterPaths) {
12258
- if (fs7.existsSync(routerPath)) {
12259
- router = await this.loadRouter(routerPath);
12222
+ if (fs8.existsSync(routerPath)) {
12223
+ router = await loadRouter(routerPath);
12260
12224
  if (router) {
12261
12225
  break;
12262
12226
  } else {
@@ -12271,20 +12235,43 @@ var init_watcher = __esm({
12271
12235
  return;
12272
12236
  }
12273
12237
  await generateSchemaFromRouter(router, this.config);
12238
+ if (this.config.generateDocs) {
12239
+ await this.generateOpenAPISpec(router);
12240
+ }
12274
12241
  } catch (error) {
12275
12242
  this.logger.error("Schema generation failed", {}, error);
12276
12243
  } finally {
12277
12244
  this.isGenerating = false;
12278
12245
  }
12279
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
+ }
12280
12267
  };
12281
12268
  }
12282
12269
  });
12283
12270
 
12284
12271
  // src/index.ts
12285
12272
  import { Command } from "commander";
12286
- import * as fs8 from "fs";
12287
- import * as path9 from "path";
12273
+ import * as fs9 from "fs";
12274
+ import * as path10 from "path";
12288
12275
 
12289
12276
  // src/adapters/framework/framework-detector.ts
12290
12277
  import * as fs2 from "fs";
@@ -12736,7 +12723,7 @@ var IGNITER_FEATURES = {
12736
12723
  name: "Redis Store",
12737
12724
  description: "Caching, sessions, and pub/sub messaging",
12738
12725
  dependencies: [
12739
- { name: "@igniter-js/adapter-redis", version: "alpha" },
12726
+ { name: "@igniter-js/adapter-redis", version: "latest" },
12740
12727
  { name: "ioredis", version: "^5.6.1" }
12741
12728
  ],
12742
12729
  devDependencies: [
@@ -12764,8 +12751,8 @@ var IGNITER_FEATURES = {
12764
12751
  name: "BullMQ Jobs",
12765
12752
  description: "Background task processing and job queues",
12766
12753
  dependencies: [
12767
- { name: "@igniter-js/adapter-redis", version: "alpha" },
12768
- { name: "@igniter-js/adapter-bullmq", version: "alpha" },
12754
+ { name: "@igniter-js/adapter-redis", version: "latest" },
12755
+ { name: "@igniter-js/adapter-bullmq", version: "latest" },
12769
12756
  { name: "bullmq", version: "^4.0.0" },
12770
12757
  { name: "ioredis", version: "^5.6.1" }
12771
12758
  ],
@@ -12793,7 +12780,7 @@ var IGNITER_FEATURES = {
12793
12780
  name: "MCP Server",
12794
12781
  description: "Easy expose your API as a MCP server for AI assistants like Cursor, Claude, etc.",
12795
12782
  dependencies: [
12796
- { name: "@igniter-js/adapter-mcp", version: "alpha" },
12783
+ { name: "@igniter-js/adapter-mcp", version: "latest" },
12797
12784
  { name: "@vercel/mcp-adapter", version: "^0.2.0" },
12798
12785
  { name: "@modelcontextprotocol/sdk", version: "^1.10.2" },
12799
12786
  { name: "ioredis", version: "^5.6.1" }
@@ -12825,7 +12812,7 @@ var IGNITER_FEATURES = {
12825
12812
  name: "Enhanced Logging",
12826
12813
  description: "Advanced console logging with structured output",
12827
12814
  dependencies: [
12828
- { name: "@igniter-js/core", version: "alpha" }
12815
+ { name: "@igniter-js/core", version: "latest" }
12829
12816
  ],
12830
12817
  envVars: [
12831
12818
  { key: "IGNITER_LOG_LEVEL", value: "info", description: "Logging level (debug, info, warn, error)" }
@@ -12836,7 +12823,7 @@ var IGNITER_FEATURES = {
12836
12823
  name: "Telemetry",
12837
12824
  description: "Telemetry for tracking requests and errors",
12838
12825
  dependencies: [
12839
- { name: "@igniter-js/core", version: "alpha" }
12826
+ { name: "@igniter-js/core", version: "latest" }
12840
12827
  ],
12841
12828
  envVars: [
12842
12829
  { key: "IGNITER_TELEMETRY_ENABLE_TRACING", value: "true", description: "Enable telemetry tracing" },
@@ -14468,13 +14455,13 @@ program.command("init").description("Create a new Igniter.js project with intera
14468
14455
  process.exit(1);
14469
14456
  }
14470
14457
  }
14471
- const targetDir = projectName === "." ? process.cwd() : path9.resolve(projectName);
14472
- 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;
14473
14460
  if (!options.force) {
14474
14461
  try {
14475
- const stats = await fs8.promises.stat(targetDir);
14462
+ const stats = await fs9.promises.stat(targetDir);
14476
14463
  if (stats.isDirectory()) {
14477
- const files = await fs8.promises.readdir(targetDir);
14464
+ const files = await fs9.promises.readdir(targetDir);
14478
14465
  const nonEmptyFiles = files.filter((file) => !file.startsWith("."));
14479
14466
  if (nonEmptyFiles.length > 0 && !isExistingProject) {
14480
14467
  const shouldOverwrite = await confirmOverwrite(`Directory '${projectName}' is not empty. Continue?`);
@@ -14503,7 +14490,7 @@ program.command("init").description("Create a new Igniter.js project with intera
14503
14490
  process.exit(1);
14504
14491
  }
14505
14492
  });
14506
- 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) => {
14507
14494
  const detectedFramework = detectFramework();
14508
14495
  const framework = options.framework ? isFrameworkSupported(options.framework) ? options.framework : "generic" : detectedFramework;
14509
14496
  const useInteractive = options.interactive !== false;
@@ -14532,9 +14519,10 @@ program.command("dev").description("Start development mode with framework and Ig
14532
14519
  });
14533
14520
  }
14534
14521
  }
14522
+ const docsFlags = options.docs ? ` --docs --docs-output ${options.docsOutput}` : "";
14535
14523
  processes.push({
14536
14524
  name: "Igniter",
14537
- 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}`,
14538
14526
  color: "blue",
14539
14527
  cwd: process.cwd()
14540
14528
  });
@@ -14545,7 +14533,7 @@ program.command("dev").description("Start development mode with framework and Ig
14545
14533
  }
14546
14534
  });
14547
14535
  var generate = program.command("generate").description("Scaffold new features or generate client schema");
14548
- 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) => {
14549
14537
  const startTime = performance.now();
14550
14538
  const detectedFramework = detectFramework();
14551
14539
  const framework = options.framework ? isFrameworkSupported(options.framework) ? options.framework : "generic" : detectedFramework;
@@ -14558,7 +14546,9 @@ generate.command("schema").description("Generate client schema from your Igniter
14558
14546
  framework,
14559
14547
  outputDir: options.output,
14560
14548
  debug: options.debug,
14561
- controllerPatterns: ["**/*.controller.{ts,js}"]
14549
+ controllerPatterns: ["**/*.controller.{ts,js}"],
14550
+ generateDocs: options.docs,
14551
+ docsOutputDir: options.docsOutput
14562
14552
  });
14563
14553
  watcherSpinner.success("Generator loaded");
14564
14554
  if (options.watch) {
@@ -14571,6 +14561,69 @@ generate.command("schema").description("Generate client schema from your Igniter
14571
14561
  process.exit(0);
14572
14562
  }
14573
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
+ });
14574
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) => {
14575
14628
  await handleGenerateFeature(name, options);
14576
14629
  });