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