@igniter-js/cli 0.2.0-alpha.0 → 0.2.1

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
@@ -299,13 +299,13 @@ var init_spinner = __esm({
299
299
  // src/adapters/logger.ts
300
300
  function getLogLevel() {
301
301
  if (process.env.DEBUG === "true") {
302
- return import_core2.IgniterLogLevel.DEBUG;
302
+ return import_core.IgniterLogLevel.DEBUG;
303
303
  }
304
304
  const envLevel = process.env.LOG_LEVEL?.toUpperCase();
305
- if (envLevel && envLevel in import_core2.IgniterLogLevel) {
306
- return import_core2.IgniterLogLevel[envLevel];
305
+ if (envLevel && envLevel in import_core.IgniterLogLevel) {
306
+ return import_core.IgniterLogLevel[envLevel];
307
307
  }
308
- return import_core2.IgniterLogLevel.INFO;
308
+ return import_core.IgniterLogLevel.INFO;
309
309
  }
310
310
  function isConcurrentMode() {
311
311
  return !!(process.env.FORCE_COLOR || // concurrently sets this
@@ -316,6 +316,7 @@ function isInteractiveMode() {
316
316
  }
317
317
  function createChildLogger(context, options = {}) {
318
318
  const childLogger = logger.child(context);
319
+ childLogger.spinner = (text, id) => cliSpinnerManager.createSpinner(text, id);
319
320
  if (options.level) {
320
321
  childLogger?.setLevel?.(options.level);
321
322
  }
@@ -336,12 +337,11 @@ function formatError(error) {
336
337
  }
337
338
  return { error };
338
339
  }
339
- var import_core, import_core2, logger, cliSpinnerManager;
340
+ var import_core, logger, cliSpinnerManager;
340
341
  var init_logger = __esm({
341
342
  "src/adapters/logger.ts"() {
342
343
  "use strict";
343
344
  import_core = require("@igniter-js/core");
344
- import_core2 = require("@igniter-js/core");
345
345
  init_spinner();
346
346
  logger = (0, import_core.createConsoleLogger)({
347
347
  level: getLogLevel(),
@@ -3513,7 +3513,7 @@ var require_lib = __commonJS({
3513
3513
  }
3514
3514
  return utf8;
3515
3515
  };
3516
- var generate = module2.exports = function generate2(str) {
3516
+ var generate2 = module2.exports = function generate3(str) {
3517
3517
  var char;
3518
3518
  var i = 0;
3519
3519
  var start = -1;
@@ -3539,9 +3539,9 @@ var require_lib = __commonJS({
3539
3539
  module2.exports.generateMulti = function generateMulti(keys) {
3540
3540
  var i = 1;
3541
3541
  var len = keys.length;
3542
- var base = generate(keys[0]);
3542
+ var base = generate2(keys[0]);
3543
3543
  while (i < len) {
3544
- if (generate(keys[i++]) !== base) return -1;
3544
+ if (generate2(keys[i++]) !== base) return -1;
3545
3545
  }
3546
3546
  return base;
3547
3547
  };
@@ -4270,19 +4270,21 @@ var require_supports_color = __commonJS({
4270
4270
  var tty = require("tty");
4271
4271
  var hasFlag = require_has_flag();
4272
4272
  var { env } = process;
4273
- var forceColor;
4273
+ var flagForceColor;
4274
4274
  if (hasFlag("no-color") || hasFlag("no-colors") || hasFlag("color=false") || hasFlag("color=never")) {
4275
- forceColor = 0;
4275
+ flagForceColor = 0;
4276
4276
  } else if (hasFlag("color") || hasFlag("colors") || hasFlag("color=true") || hasFlag("color=always")) {
4277
- forceColor = 1;
4277
+ flagForceColor = 1;
4278
4278
  }
4279
- if ("FORCE_COLOR" in env) {
4280
- if (env.FORCE_COLOR === "true") {
4281
- forceColor = 1;
4282
- } else if (env.FORCE_COLOR === "false") {
4283
- forceColor = 0;
4284
- } else {
4285
- forceColor = env.FORCE_COLOR.length === 0 ? 1 : Math.min(parseInt(env.FORCE_COLOR, 10), 3);
4279
+ function envForceColor() {
4280
+ if ("FORCE_COLOR" in env) {
4281
+ if (env.FORCE_COLOR === "true") {
4282
+ return 1;
4283
+ }
4284
+ if (env.FORCE_COLOR === "false") {
4285
+ return 0;
4286
+ }
4287
+ return env.FORCE_COLOR.length === 0 ? 1 : Math.min(Number.parseInt(env.FORCE_COLOR, 10), 3);
4286
4288
  }
4287
4289
  }
4288
4290
  function translateLevel(level) {
@@ -4296,15 +4298,22 @@ var require_supports_color = __commonJS({
4296
4298
  has16m: level >= 3
4297
4299
  };
4298
4300
  }
4299
- function supportsColor(haveStream, streamIsTTY) {
4301
+ function supportsColor(haveStream, { streamIsTTY, sniffFlags = true } = {}) {
4302
+ const noFlagForceColor = envForceColor();
4303
+ if (noFlagForceColor !== void 0) {
4304
+ flagForceColor = noFlagForceColor;
4305
+ }
4306
+ const forceColor = sniffFlags ? flagForceColor : noFlagForceColor;
4300
4307
  if (forceColor === 0) {
4301
4308
  return 0;
4302
4309
  }
4303
- if (hasFlag("color=16m") || hasFlag("color=full") || hasFlag("color=truecolor")) {
4304
- return 3;
4305
- }
4306
- if (hasFlag("color=256")) {
4307
- return 2;
4310
+ if (sniffFlags) {
4311
+ if (hasFlag("color=16m") || hasFlag("color=full") || hasFlag("color=truecolor")) {
4312
+ return 3;
4313
+ }
4314
+ if (hasFlag("color=256")) {
4315
+ return 2;
4316
+ }
4308
4317
  }
4309
4318
  if (haveStream && !streamIsTTY && forceColor === void 0) {
4310
4319
  return 0;
@@ -4321,7 +4330,7 @@ var require_supports_color = __commonJS({
4321
4330
  return 1;
4322
4331
  }
4323
4332
  if ("CI" in env) {
4324
- if (["TRAVIS", "CIRCLECI", "APPVEYOR", "GITLAB_CI", "GITHUB_ACTIONS", "BUILDKITE"].some((sign) => sign in env) || env.CI_NAME === "codeship") {
4333
+ if (["TRAVIS", "CIRCLECI", "APPVEYOR", "GITLAB_CI", "GITHUB_ACTIONS", "BUILDKITE", "DRONE"].some((sign) => sign in env) || env.CI_NAME === "codeship") {
4325
4334
  return 1;
4326
4335
  }
4327
4336
  return min;
@@ -4333,7 +4342,7 @@ var require_supports_color = __commonJS({
4333
4342
  return 3;
4334
4343
  }
4335
4344
  if ("TERM_PROGRAM" in env) {
4336
- const version = parseInt((env.TERM_PROGRAM_VERSION || "").split(".")[0], 10);
4345
+ const version = Number.parseInt((env.TERM_PROGRAM_VERSION || "").split(".")[0], 10);
4337
4346
  switch (env.TERM_PROGRAM) {
4338
4347
  case "iTerm.app":
4339
4348
  return version >= 3 ? 3 : 2;
@@ -4352,14 +4361,17 @@ var require_supports_color = __commonJS({
4352
4361
  }
4353
4362
  return min;
4354
4363
  }
4355
- function getSupportLevel(stream) {
4356
- const level = supportsColor(stream, stream && stream.isTTY);
4364
+ function getSupportLevel(stream, options = {}) {
4365
+ const level = supportsColor(stream, {
4366
+ streamIsTTY: stream && stream.isTTY,
4367
+ ...options
4368
+ });
4357
4369
  return translateLevel(level);
4358
4370
  }
4359
4371
  module2.exports = {
4360
4372
  supportsColor: getSupportLevel,
4361
- stdout: translateLevel(supportsColor(true, tty.isatty(1))),
4362
- stderr: translateLevel(supportsColor(true, tty.isatty(2)))
4373
+ stdout: getSupportLevel({ isTTY: tty.isatty(1) }),
4374
+ stderr: getSupportLevel({ isTTY: tty.isatty(2) })
4363
4375
  };
4364
4376
  }
4365
4377
  });
@@ -10055,11 +10067,11 @@ async function runInteractiveProcesses(configs) {
10055
10067
  await manager.start();
10056
10068
  }
10057
10069
  async function runConcurrentProcesses(options) {
10058
- const logger4 = createChildLogger({ component: "concurrent-processes" });
10070
+ const logger6 = createChildLogger({ component: "concurrent-processes" });
10059
10071
  if (options.interactive) {
10060
10072
  return runInteractiveProcesses(options.processes);
10061
10073
  }
10062
- logger4.info("Starting concurrent processes", {
10074
+ logger6.info("Starting concurrent processes", {
10063
10075
  processCount: options.processes.length,
10064
10076
  processes: options.processes.map((p) => ({ name: p.name, command: p.command }))
10065
10077
  });
@@ -10081,9 +10093,9 @@ async function runConcurrentProcesses(options) {
10081
10093
  }
10082
10094
  );
10083
10095
  await result;
10084
- logger4.success("All processes completed successfully");
10096
+ logger6.success("All processes completed successfully");
10085
10097
  } catch (error) {
10086
- logger4.error("Concurrent processes failed", { error: formatError(error) });
10098
+ logger6.error("Concurrent processes failed", { error: formatError(error) });
10087
10099
  throw error;
10088
10100
  }
10089
10101
  }
@@ -10113,8 +10125,8 @@ function createFrameworkDevProcess(framework, command, options = {}) {
10113
10125
  };
10114
10126
  }
10115
10127
  async function startIgniterWithFramework(options) {
10116
- const logger4 = createChildLogger({ component: "igniter-with-framework" });
10117
- logger4.info("Starting Igniter with framework", {
10128
+ const logger6 = createChildLogger({ component: "igniter-with-framework" });
10129
+ logger6.info("Starting Igniter with framework", {
10118
10130
  framework: options.framework,
10119
10131
  frameworkCommand: options.frameworkCommand,
10120
10132
  port: options.port,
@@ -11437,7 +11449,7 @@ ${ANSI_COLORS.bold}TOP OPERATIONS${ANSI_COLORS.reset}`);
11437
11449
  // src/adapters/build/generator.ts
11438
11450
  function getFileSize(filePath) {
11439
11451
  try {
11440
- const stats = fs4.statSync(filePath);
11452
+ const stats = fs6.statSync(filePath);
11441
11453
  const bytes = stats.size;
11442
11454
  if (bytes < 1024) return `${bytes}b`;
11443
11455
  if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)}kb`;
@@ -11447,7 +11459,6 @@ function getFileSize(filePath) {
11447
11459
  }
11448
11460
  }
11449
11461
  function extractRouterSchema(router) {
11450
- const logger4 = createChildLogger({ component: "schema-extractor" });
11451
11462
  const controllersSchema = {};
11452
11463
  let totalActions = 0;
11453
11464
  for (const [controllerName, controller] of Object.entries(router.controllers)) {
@@ -11455,17 +11466,17 @@ function extractRouterSchema(router) {
11455
11466
  if (controller && controller.actions) {
11456
11467
  for (const [actionName, action] of Object.entries(controller.actions)) {
11457
11468
  actionsSchema[actionName] = {
11469
+ name: action?.name || actionName,
11470
+ description: action?.description || "",
11458
11471
  path: action?.path || "",
11459
- method: action?.method || "GET",
11460
- description: action?.description,
11461
- // Keep type inference data
11462
- $Infer: action?.$Infer
11472
+ method: action?.method || "GET"
11463
11473
  };
11464
11474
  totalActions++;
11465
11475
  }
11466
11476
  }
11467
11477
  controllersSchema[controllerName] = {
11468
11478
  name: controller?.name || controllerName,
11479
+ description: controller?.description || "",
11469
11480
  path: controller?.path || "",
11470
11481
  actions: actionsSchema
11471
11482
  };
@@ -11475,7 +11486,12 @@ function extractRouterSchema(router) {
11475
11486
  baseURL: router.config?.baseURL || "",
11476
11487
  basePATH: router.config?.basePATH || ""
11477
11488
  },
11478
- controllers: controllersSchema
11489
+ controllers: controllersSchema,
11490
+ processor: {},
11491
+ handler: {},
11492
+ $context: {},
11493
+ $plugins: {},
11494
+ $caller: {}
11479
11495
  };
11480
11496
  return {
11481
11497
  schema,
@@ -11486,41 +11502,41 @@ function extractRouterSchema(router) {
11486
11502
  };
11487
11503
  }
11488
11504
  async function generateSchemaFromRouter(router, config) {
11489
- const logger4 = createChildLogger({ component: "generator" });
11505
+ const logger6 = createChildLogger({ component: "generator" });
11490
11506
  const startTime = performance.now();
11491
11507
  const isInteractiveMode2 = !!(process.env.IGNITER_INTERACTIVE_MODE === "true" || process.argv.includes("--interactive"));
11492
11508
  try {
11493
11509
  let extractSpinner = null;
11494
11510
  if (isInteractiveMode2) {
11495
- logger4.info("Extracting router schema...");
11511
+ logger6.info("Extracting router schema...");
11496
11512
  } else {
11497
11513
  extractSpinner = createDetachedSpinner("Extracting router schema...");
11498
11514
  extractSpinner.start();
11499
11515
  }
11500
11516
  const { schema, stats } = extractRouterSchema(router);
11501
11517
  if (isInteractiveMode2) {
11502
- logger4.success(`Schema extracted - ${stats.controllers} controllers, ${stats.actions} actions`);
11518
+ logger6.success(`Schema extracted - ${stats.controllers} controllers, ${stats.actions} actions`);
11503
11519
  } else if (extractSpinner) {
11504
11520
  extractSpinner.success(`Schema extracted - ${stats.controllers} controllers, ${stats.actions} actions`);
11505
11521
  }
11506
11522
  let dirSpinner = null;
11507
11523
  if (isInteractiveMode2) {
11508
- logger4.info("Preparing output directory...");
11524
+ logger6.info("Preparing output directory...");
11509
11525
  } else {
11510
11526
  dirSpinner = createDetachedSpinner("Preparing output directory...");
11511
11527
  dirSpinner.start();
11512
11528
  }
11513
11529
  const outputDir = config.outputDir || "generated";
11514
11530
  await ensureDirectoryExists(outputDir);
11515
- const outputPath = path5.resolve(outputDir);
11531
+ const outputPath = path7.resolve(outputDir);
11516
11532
  if (isInteractiveMode2) {
11517
- logger4.success(`Output directory ready ${outputPath}`);
11533
+ logger6.success(`Output directory ready ${outputPath}`);
11518
11534
  } else if (dirSpinner) {
11519
11535
  dirSpinner.success(`Output directory ready ${outputPath}`);
11520
11536
  }
11521
11537
  let filesSpinner = null;
11522
11538
  if (isInteractiveMode2) {
11523
- logger4.info("Generating client files...");
11539
+ logger6.info("Generating client files...");
11524
11540
  } else {
11525
11541
  filesSpinner = createDetachedSpinner("Generating client files...");
11526
11542
  filesSpinner.start();
@@ -11530,7 +11546,7 @@ async function generateSchemaFromRouter(router, config) {
11530
11546
  generateClientFile(schema, outputDir, config)
11531
11547
  ]);
11532
11548
  if (isInteractiveMode2) {
11533
- logger4.success("Files generated successfully");
11549
+ logger6.success("Files generated successfully");
11534
11550
  } else if (filesSpinner) {
11535
11551
  filesSpinner.success("Files generated successfully");
11536
11552
  }
@@ -11542,15 +11558,15 @@ async function generateSchemaFromRouter(router, config) {
11542
11558
  let totalSize = 0;
11543
11559
  files.forEach((file) => {
11544
11560
  const size = getFileSize(file.path);
11545
- totalSize += fs4.statSync(file.path).size;
11546
- logger4.info(`Generated ${file.name}`, { size, path: path5.relative(process.cwd(), file.path) });
11561
+ totalSize += fs6.statSync(file.path).size;
11562
+ logger6.info(`Generated ${file.name}`, { size, path: path7.relative(process.cwd(), file.path) });
11547
11563
  });
11548
11564
  const duration = ((performance.now() - startTime) / 1e3).toFixed(2);
11549
11565
  const totalSizeFormatted = totalSize < 1024 ? `${totalSize}b` : totalSize < 1024 * 1024 ? `${(totalSize / 1024).toFixed(1)}kb` : `${(totalSize / (1024 * 1024)).toFixed(1)}mb`;
11550
- logger4.separator();
11551
- logger4.success("Igniter.js development server is up and running");
11552
- logger4.info("Press Ctrl+C to stop");
11553
- logger4.info("Summary", {
11566
+ logger6.separator();
11567
+ logger6.success("Igniter.js development server is up and running");
11568
+ logger6.info("Press Ctrl+C to stop");
11569
+ logger6.info("Summary", {
11554
11570
  output: outputPath,
11555
11571
  files: files.length,
11556
11572
  totalSize: totalSizeFormatted,
@@ -11559,13 +11575,13 @@ async function generateSchemaFromRouter(router, config) {
11559
11575
  duration: `${duration}s`,
11560
11576
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
11561
11577
  });
11562
- logger4.groupEnd();
11578
+ logger6.groupEnd();
11563
11579
  } else {
11564
11580
  const duration = ((performance.now() - startTime) / 1e3).toFixed(2);
11565
- logger4.success(`Client generated successfully - ${stats.controllers} controllers, ${stats.actions} actions (${duration}s)`);
11581
+ logger6.success(`Client generated successfully - ${stats.controllers} controllers, ${stats.actions} actions (${duration}s)`);
11566
11582
  }
11567
11583
  } catch (error) {
11568
- logger4.error("Client generation failed", {}, error);
11584
+ logger6.error("Client generation failed", {}, error);
11569
11585
  throw error;
11570
11586
  }
11571
11587
  }
@@ -11576,75 +11592,77 @@ export const AppRouterSchema = ${JSON.stringify(schema, null, 2)} as const
11576
11592
 
11577
11593
  export type AppRouterSchemaType = typeof AppRouterSchema
11578
11594
  `;
11579
- const filePath = path5.join(outputDir, "igniter.schema.ts");
11595
+ const filePath = path7.join(outputDir, "igniter.schema.ts");
11580
11596
  await writeFileWithHeader(filePath, content, config);
11581
11597
  return filePath;
11582
11598
  }
11583
11599
  async function generateClientFile(schema, outputDir, config) {
11584
- const content = `// Generated by @igniter-js/cli - DO NOT EDIT
11585
-
11586
- import { isServer } from '@igniter-js/core'
11587
- import { createIgniterClient, useIgniterQueryClient } from '@igniter-js/core/client'
11588
- import { AppRouterSchema } from './igniter.schema'
11600
+ const content = `import { createIgniterClient, useIgniterQueryClient } from '@igniter-js/core/client'
11589
11601
  import type { AppRouterType } from './igniter.router'
11590
11602
 
11591
11603
  /**
11592
- * Type-safe API client generated from your Igniter router
11593
- *
11594
- * Usage in Server Components:
11595
- * const users = await api.users.list.query()
11596
- *
11597
- * Usage in Client Components:
11598
- * const { data } = api.users.list.useQuery()
11599
- */
11600
- export const api = createIgniterClient<AppRouterType>(() => {
11601
- if (isServer) {
11602
- return require('./igniter.router').AppRouter
11603
- }
11604
-
11605
- return AppRouterSchema
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
+ */
11612
+ 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
+ }
11619
+
11620
+ return require('./igniter.schema').AppRouterSchema
11621
+ },
11606
11622
  })
11607
11623
 
11608
11624
  /**
11609
- * Type-safe API client generated from your Igniter router
11610
- *
11611
- * Usage in Server Components:
11612
- * const users = await api.users.list.query()
11613
- *
11614
- * Usage in Client Components:
11615
- * const { data } = api.users.list.useQuery()
11616
- */
11625
+ * Type-safe API client generated from your Igniter router
11626
+ *
11627
+ * Usage in Server Components:
11628
+ * const users = await api.users.list.query()
11629
+ *
11630
+ * Usage in Client Components:
11631
+ * const { data } = api.users.list.useQuery()
11632
+ */
11617
11633
  export type ApiClient = typeof api
11618
11634
 
11619
11635
  /**
11620
- * Type-safe query client generated from your Igniter router
11621
- *
11622
- * Usage in Client Components:
11623
- * const { invalidate } = useQueryClient()
11624
- */
11636
+ * Type-safe query client generated from your Igniter router
11637
+ *
11638
+ * Usage in Client Components:
11639
+ * const { invalidate } = useQueryClient()
11640
+ */
11625
11641
  export const useQueryClient = useIgniterQueryClient<AppRouterType>;
11626
11642
  `;
11627
- const filePath = path5.join(outputDir, "igniter.client.ts");
11643
+ const filePath = path7.join(outputDir, "igniter.client.ts");
11628
11644
  await writeFileWithHeader(filePath, content, config);
11629
11645
  return filePath;
11630
11646
  }
11631
11647
  async function writeFileWithHeader(filePath, content, config) {
11632
11648
  const header = generateFileHeader(config);
11633
11649
  const fullContent = header + "\n\n" + content;
11634
- await fs4.promises.writeFile(filePath, fullContent, "utf8");
11650
+ await fs6.promises.writeFile(filePath, fullContent, "utf8");
11635
11651
  }
11636
11652
  function generateFileHeader(config) {
11637
11653
  const timestamp = (/* @__PURE__ */ new Date()).toISOString();
11638
- return `/**
11654
+ return `/* eslint-disable @typescript-eslint/no-var-requires */
11655
+
11656
+ /**
11639
11657
  * Generated by @igniter-js/cli
11640
- *
11658
+ *
11641
11659
  * WARNING: DO NOT EDIT THIS FILE MANUALLY
11642
- *
11660
+ *
11643
11661
  * This file was automatically generated from your Igniter router.
11644
11662
  * Any changes made to this file will be overwritten when the CLI regenerates it.
11645
- *
11663
+ *
11646
11664
  * To modify the client API, update your controller files instead.
11647
- *
11665
+ *
11648
11666
  * Generated: ${timestamp}
11649
11667
  * Framework: ${config.framework}
11650
11668
  * Output: ${config.outputDir}
@@ -11652,17 +11670,17 @@ function generateFileHeader(config) {
11652
11670
  }
11653
11671
  async function ensureDirectoryExists(dirPath) {
11654
11672
  try {
11655
- await fs4.promises.access(dirPath);
11673
+ await fs6.promises.access(dirPath);
11656
11674
  } catch (error) {
11657
- await fs4.promises.mkdir(dirPath, { recursive: true });
11675
+ await fs6.promises.mkdir(dirPath, { recursive: true });
11658
11676
  }
11659
11677
  }
11660
- var fs4, path5;
11678
+ var fs6, path7;
11661
11679
  var init_generator = __esm({
11662
11680
  "src/adapters/build/generator.ts"() {
11663
11681
  "use strict";
11664
- fs4 = __toESM(require("fs"));
11665
- path5 = __toESM(require("path"));
11682
+ fs6 = __toESM(require("fs"));
11683
+ path7 = __toESM(require("path"));
11666
11684
  init_logger();
11667
11685
  init_spinner();
11668
11686
  }
@@ -11673,16 +11691,17 @@ var watcher_exports = {};
11673
11691
  __export(watcher_exports, {
11674
11692
  IgniterWatcher: () => IgniterWatcher
11675
11693
  });
11676
- var fs5, path6, import_url, IgniterWatcher;
11694
+ var fs7, path8, import_url, import_chokidar, IgniterWatcher;
11677
11695
  var init_watcher = __esm({
11678
11696
  "src/adapters/build/watcher.ts"() {
11679
11697
  "use strict";
11680
- fs5 = __toESM(require("fs"));
11681
- path6 = __toESM(require("path"));
11698
+ fs7 = __toESM(require("fs"));
11699
+ path8 = __toESM(require("path"));
11682
11700
  import_url = require("url");
11683
11701
  init_generator();
11684
11702
  init_logger();
11685
11703
  init_spinner();
11704
+ import_chokidar = __toESM(require("chokidar"));
11686
11705
  IgniterWatcher = class {
11687
11706
  // Detect interactive mode
11688
11707
  constructor(config) {
@@ -11767,9 +11786,9 @@ var init_watcher = __esm({
11767
11786
  * Load router from file with simplified approach
11768
11787
  */
11769
11788
  async loadRouter(routerPath) {
11770
- const logger4 = createChildLogger({ component: "router-loader" });
11771
- const fullPath = path6.resolve(process.cwd(), routerPath);
11772
- logger4.debug("Loading router", { path: routerPath });
11789
+ const logger6 = createChildLogger({ component: "router-loader" });
11790
+ const fullPath = path8.resolve(process.cwd(), routerPath);
11791
+ logger6.debug("Loading router", { path: routerPath });
11773
11792
  try {
11774
11793
  const module2 = await this.loadWithTypeScriptSupport(fullPath);
11775
11794
  if (module2) {
@@ -11782,7 +11801,7 @@ var init_watcher = __esm({
11782
11801
  const controllerCount = Object.keys(router.controllers || {}).length;
11783
11802
  return router;
11784
11803
  } else {
11785
- logger4.debug("Available exports", {
11804
+ logger6.debug("Available exports", {
11786
11805
  exports: Object.keys(module2 || {})
11787
11806
  });
11788
11807
  }
@@ -11806,7 +11825,7 @@ var init_watcher = __esm({
11806
11825
  }
11807
11826
  fallbackSpinner.error("Could not load router");
11808
11827
  } catch (error) {
11809
- logger4.error("Failed to load router", { path: routerPath }, error);
11828
+ logger6.error("Failed to load router", { path: routerPath }, error);
11810
11829
  }
11811
11830
  return null;
11812
11831
  }
@@ -11815,24 +11834,24 @@ var init_watcher = __esm({
11815
11834
  * This is the NEW robust approach that replaces the problematic transpilation
11816
11835
  */
11817
11836
  async loadWithTypeScriptSupport(filePath) {
11818
- const logger4 = createChildLogger({ component: "tsx-loader" });
11837
+ const logger6 = createChildLogger({ component: "tsx-loader" });
11819
11838
  const jsPath = filePath.replace(/\.ts$/, ".js");
11820
- if (fs5.existsSync(jsPath)) {
11839
+ if (fs7.existsSync(jsPath)) {
11821
11840
  try {
11822
- logger4.debug("Using compiled JS version");
11841
+ logger6.debug("Using compiled JS version");
11823
11842
  delete require.cache[jsPath];
11824
11843
  const module2 = require(jsPath);
11825
11844
  return module2;
11826
11845
  } catch (error) {
11827
- logger4.debug("Compiled JS loading failed, trying TypeScript...");
11846
+ logger6.debug("Compiled JS loading failed, trying TypeScript...");
11828
11847
  }
11829
11848
  }
11830
11849
  if (filePath.endsWith(".ts")) {
11831
11850
  try {
11832
- logger4.debug("Using TSX runtime loader");
11833
- const { spawn: spawn3 } = require("child_process");
11851
+ logger6.debug("Using TSX runtime loader");
11852
+ const { spawn: spawn2 } = require("child_process");
11834
11853
  const tsxCheckResult = await new Promise((resolve4) => {
11835
- const checkChild = spawn3("npx", ["tsx", "--version"], {
11854
+ const checkChild = spawn2("npx", ["tsx", "--version"], {
11836
11855
  stdio: "pipe",
11837
11856
  cwd: process.cwd()
11838
11857
  });
@@ -11856,7 +11875,7 @@ var init_watcher = __esm({
11856
11875
  try {
11857
11876
  const module = await import('${(0, import_url.pathToFileURL)(filePath).href}');
11858
11877
  const router = module?.AppRouter || module?.default?.AppRouter || module?.default || module?.router;
11859
-
11878
+
11860
11879
  if (router && typeof router === 'object') {
11861
11880
  // Extract safe metadata for CLI use
11862
11881
  const safeRouter = {
@@ -11866,13 +11885,13 @@ var init_watcher = __esm({
11866
11885
  },
11867
11886
  controllers: {}
11868
11887
  };
11869
-
11888
+
11870
11889
  // Extract controller metadata (no handlers/functions)
11871
11890
  if (router.controllers && typeof router.controllers === 'object') {
11872
11891
  for (const [controllerName, controller] of Object.entries(router.controllers)) {
11873
11892
  if (controller && typeof controller === 'object' && (controller as any).actions) {
11874
11893
  const safeActions: Record<string, any> = {};
11875
-
11894
+
11876
11895
  for (const [actionName, action] of Object.entries((controller as any).actions)) {
11877
11896
  if (action && typeof action === 'object') {
11878
11897
  // Extract only metadata, no functions
@@ -11885,7 +11904,7 @@ var init_watcher = __esm({
11885
11904
  };
11886
11905
  }
11887
11906
  }
11888
-
11907
+
11889
11908
  safeRouter.controllers[controllerName] = {
11890
11909
  name: (controller as any).name || controllerName,
11891
11910
  path: (controller as any).path || '',
@@ -11894,7 +11913,7 @@ var init_watcher = __esm({
11894
11913
  }
11895
11914
  }
11896
11915
  }
11897
-
11916
+
11898
11917
  console.log('__ROUTER_SUCCESS__' + JSON.stringify(safeRouter));
11899
11918
  process.exit(0); // Force exit after success
11900
11919
  } else {
@@ -11906,10 +11925,10 @@ var init_watcher = __esm({
11906
11925
  process.exit(1); // Force exit after error
11907
11926
  }
11908
11927
  }
11909
-
11928
+
11910
11929
  loadRouter();
11911
11930
  `;
11912
- const child = spawn3("npx", ["tsx", "-e", tsxScript], {
11931
+ const child = spawn2("npx", ["tsx", "-e", tsxScript], {
11913
11932
  stdio: "pipe",
11914
11933
  cwd: process.cwd(),
11915
11934
  env: { ...process.env, NODE_NO_WARNINGS: "1" }
@@ -11953,7 +11972,7 @@ var init_watcher = __esm({
11953
11972
  });
11954
11973
  return result;
11955
11974
  } catch (error) {
11956
- logger4.debug("TSX runtime loader failed", {}, error);
11975
+ logger6.debug("TSX runtime loader failed", {}, error);
11957
11976
  }
11958
11977
  }
11959
11978
  return null;
@@ -11962,9 +11981,9 @@ var init_watcher = __esm({
11962
11981
  * Load router by resolving directory imports to index files
11963
11982
  */
11964
11983
  async loadRouterWithIndexResolution(routerPath) {
11965
- const logger4 = createChildLogger({ component: "index-resolver" });
11984
+ const logger6 = createChildLogger({ component: "index-resolver" });
11966
11985
  try {
11967
- const routerContent = fs5.readFileSync(routerPath, "utf8");
11986
+ const routerContent = fs7.readFileSync(routerPath, "utf8");
11968
11987
  const importRegex = /from\s+['\"]([^'\"]+)['\"]/g;
11969
11988
  let resolvedContent = routerContent;
11970
11989
  const matches = Array.from(routerContent.matchAll(importRegex));
@@ -11972,14 +11991,14 @@ var init_watcher = __esm({
11972
11991
  if (!importPath.startsWith(".") && !importPath.startsWith("@")) {
11973
11992
  continue;
11974
11993
  }
11975
- const basePath = path6.dirname(routerPath);
11994
+ const basePath = path8.dirname(routerPath);
11976
11995
  let resolvedPath;
11977
11996
  if (importPath.startsWith("@/")) {
11978
- resolvedPath = path6.resolve(process.cwd(), "src", importPath.substring(2));
11997
+ resolvedPath = path8.resolve(process.cwd(), "src", importPath.substring(2));
11979
11998
  } else if (importPath.startsWith("./")) {
11980
- resolvedPath = path6.resolve(basePath, importPath);
11999
+ resolvedPath = path8.resolve(basePath, importPath);
11981
12000
  } else {
11982
- resolvedPath = path6.resolve(basePath, importPath);
12001
+ resolvedPath = path8.resolve(basePath, importPath);
11983
12002
  }
11984
12003
  let finalPath = importPath;
11985
12004
  if (!importPath.match(/\\.(js|ts|tsx|jsx)$/)) {
@@ -11991,8 +12010,8 @@ var init_watcher = __esm({
11991
12010
  ];
11992
12011
  let fileFound = false;
11993
12012
  for (const filePath of possibleFiles) {
11994
- if (fs5.existsSync(filePath)) {
11995
- const ext = path6.extname(filePath);
12013
+ if (fs7.existsSync(filePath)) {
12014
+ const ext = path8.extname(filePath);
11996
12015
  finalPath = importPath + ext;
11997
12016
  fileFound = true;
11998
12017
  break;
@@ -12000,22 +12019,22 @@ var init_watcher = __esm({
12000
12019
  }
12001
12020
  if (!fileFound) {
12002
12021
  const possibleIndexFiles = [
12003
- path6.join(resolvedPath, "index.ts"),
12004
- path6.join(resolvedPath, "index.tsx"),
12005
- path6.join(resolvedPath, "index.js"),
12006
- path6.join(resolvedPath, "index.jsx")
12022
+ path8.join(resolvedPath, "index.ts"),
12023
+ path8.join(resolvedPath, "index.tsx"),
12024
+ path8.join(resolvedPath, "index.js"),
12025
+ path8.join(resolvedPath, "index.jsx")
12007
12026
  ];
12008
12027
  for (const indexFile of possibleIndexFiles) {
12009
- if (fs5.existsSync(indexFile)) {
12010
- const ext = path6.extname(indexFile);
12028
+ if (fs7.existsSync(indexFile)) {
12029
+ const ext = path8.extname(indexFile);
12011
12030
  finalPath = importPath + "/index" + ext;
12012
12031
  break;
12013
12032
  }
12014
12033
  }
12015
12034
  }
12016
12035
  }
12017
- const absolutePath = path6.resolve(basePath, finalPath);
12018
- if (fs5.existsSync(absolutePath)) {
12036
+ const absolutePath = path8.resolve(basePath, finalPath);
12037
+ if (fs7.existsSync(absolutePath)) {
12019
12038
  const fileUrl = (0, import_url.pathToFileURL)(absolutePath).href;
12020
12039
  resolvedContent = resolvedContent.replace(fullMatch, `from '${fileUrl}'`);
12021
12040
  } else {
@@ -12023,18 +12042,18 @@ var init_watcher = __esm({
12023
12042
  }
12024
12043
  }
12025
12044
  const tempFileName = `igniter-temp-${Date.now()}.ts`;
12026
- const tempFilePath = path6.join(process.cwd(), tempFileName);
12045
+ const tempFilePath = path8.join(process.cwd(), tempFileName);
12027
12046
  try {
12028
- fs5.writeFileSync(tempFilePath, resolvedContent);
12029
- logger4.debug("Loading resolved module via TSX");
12030
- const { spawn: spawn3 } = require("child_process");
12047
+ fs7.writeFileSync(tempFilePath, resolvedContent);
12048
+ logger6.debug("Loading resolved module via TSX");
12049
+ const { spawn: spawn2 } = require("child_process");
12031
12050
  const result = await new Promise((resolve4, reject) => {
12032
12051
  const tsxScript = `
12033
12052
  async function loadResolvedRouter() {
12034
12053
  try {
12035
12054
  const module = await import('${(0, import_url.pathToFileURL)(tempFilePath).href}');
12036
12055
  const router = module?.AppRouter || module?.default?.AppRouter || module?.default || module?.router;
12037
-
12056
+
12038
12057
  if (router && typeof router === 'object') {
12039
12058
  // Extract safe metadata
12040
12059
  const safeRouter = {
@@ -12044,12 +12063,12 @@ var init_watcher = __esm({
12044
12063
  },
12045
12064
  controllers: {}
12046
12065
  };
12047
-
12066
+
12048
12067
  if (router.controllers && typeof router.controllers === 'object') {
12049
12068
  for (const [controllerName, controller] of Object.entries(router.controllers)) {
12050
12069
  if (controller && typeof controller === 'object' && (controller as any).actions) {
12051
12070
  const safeActions: Record<string, any> = {};
12052
-
12071
+
12053
12072
  for (const [actionName, action] of Object.entries((controller as any).actions)) {
12054
12073
  if (action && typeof action === 'object') {
12055
12074
  safeActions[actionName] = {
@@ -12060,7 +12079,7 @@ var init_watcher = __esm({
12060
12079
  };
12061
12080
  }
12062
12081
  }
12063
-
12082
+
12064
12083
  safeRouter.controllers[controllerName] = {
12065
12084
  name: (controller as any).name || controllerName,
12066
12085
  path: (controller as any).path || '',
@@ -12069,7 +12088,7 @@ var init_watcher = __esm({
12069
12088
  }
12070
12089
  }
12071
12090
  }
12072
-
12091
+
12073
12092
  console.log('__ROUTER_SUCCESS__' + JSON.stringify(safeRouter));
12074
12093
  process.exit(0); // Force exit after success
12075
12094
  } else {
@@ -12081,10 +12100,10 @@ var init_watcher = __esm({
12081
12100
  process.exit(1); // Force exit after error
12082
12101
  }
12083
12102
  }
12084
-
12103
+
12085
12104
  loadResolvedRouter();
12086
12105
  `;
12087
- const child = spawn3("npx", ["tsx", "-e", tsxScript], {
12106
+ const child = spawn2("npx", ["tsx", "-e", tsxScript], {
12088
12107
  stdio: "pipe",
12089
12108
  cwd: process.cwd(),
12090
12109
  env: { ...process.env, NODE_NO_WARNINGS: "1" }
@@ -12128,12 +12147,12 @@ var init_watcher = __esm({
12128
12147
  });
12129
12148
  return result;
12130
12149
  } finally {
12131
- if (fs5.existsSync(tempFilePath)) {
12132
- fs5.unlinkSync(tempFilePath);
12150
+ if (fs7.existsSync(tempFilePath)) {
12151
+ fs7.unlinkSync(tempFilePath);
12133
12152
  }
12134
12153
  }
12135
12154
  } catch (error) {
12136
- logger4.error("Index resolution failed", { path: routerPath }, error);
12155
+ logger6.error("Index resolution failed", { path: routerPath }, error);
12137
12156
  throw error;
12138
12157
  }
12139
12158
  }
@@ -12142,12 +12161,11 @@ var init_watcher = __esm({
12142
12161
  */
12143
12162
  async start() {
12144
12163
  try {
12145
- const chokidar = await import("chokidar");
12146
12164
  this.logger.info("Starting file watcher", {
12147
12165
  output: this.config.outputDir,
12148
12166
  patterns: this.config.controllerPatterns?.join(", ")
12149
12167
  });
12150
- this.watcher = chokidar.watch(this.config.controllerPatterns, {
12168
+ this.watcher = import_chokidar.default.watch(this.config.controllerPatterns, {
12151
12169
  ignored: ["**/node_modules/**", "**/dist/**", "**/build/**", "**/.git/**"],
12152
12170
  persistent: true,
12153
12171
  ignoreInitial: false
@@ -12233,7 +12251,7 @@ var init_watcher = __esm({
12233
12251
  ];
12234
12252
  let router = null;
12235
12253
  for (const routerPath of possibleRouterPaths) {
12236
- if (fs5.existsSync(routerPath)) {
12254
+ if (fs7.existsSync(routerPath)) {
12237
12255
  router = await this.loadRouter(routerPath);
12238
12256
  if (router) {
12239
12257
  break;
@@ -12261,13 +12279,12 @@ var init_watcher = __esm({
12261
12279
 
12262
12280
  // src/index.ts
12263
12281
  var import_commander = require("commander");
12264
- var fs6 = __toESM(require("fs"));
12265
- var path7 = __toESM(require("path"));
12282
+ var fs8 = __toESM(require("fs"));
12283
+ var path9 = __toESM(require("path"));
12266
12284
 
12267
12285
  // src/adapters/framework/framework-detector.ts
12268
12286
  var fs2 = __toESM(require("fs"));
12269
12287
  var path2 = __toESM(require("path"));
12270
- var import_child_process2 = require("child_process");
12271
12288
  init_logger();
12272
12289
  init_concurrent_processes();
12273
12290
  var frameworkConfigs = {
@@ -12354,11 +12371,11 @@ var frameworkConfigs = {
12354
12371
  }
12355
12372
  };
12356
12373
  function detectFramework(cwd = process.cwd()) {
12357
- const logger4 = createChildLogger({ component: "framework-detector" });
12358
- logger4.debug("Starting framework detection", { cwd });
12374
+ const logger6 = createChildLogger({ component: "framework-detector" });
12375
+ logger6.debug("Starting framework detection", { cwd });
12359
12376
  for (const [frameworkName, config] of Object.entries(frameworkConfigs)) {
12360
12377
  if (frameworkName === "generic") continue;
12361
- logger4.debug("Checking config files", {
12378
+ logger6.debug("Checking config files", {
12362
12379
  framework: frameworkName,
12363
12380
  configFiles: config.configFiles
12364
12381
  });
@@ -12366,7 +12383,7 @@ function detectFramework(cwd = process.cwd()) {
12366
12383
  const configPath = path2.join(cwd, configFile);
12367
12384
  const exists = fs2.existsSync(configPath);
12368
12385
  if (exists) {
12369
- logger4.debug("Found framework config file", {
12386
+ logger6.debug("Found framework config file", {
12370
12387
  framework: frameworkName,
12371
12388
  configFile,
12372
12389
  path: configPath
@@ -12381,7 +12398,7 @@ function detectFramework(cwd = process.cwd()) {
12381
12398
  try {
12382
12399
  const packageJsonPath = path2.join(cwd, "package.json");
12383
12400
  if (fs2.existsSync(packageJsonPath)) {
12384
- logger4.debug("Reading package.json", { path: packageJsonPath });
12401
+ logger6.debug("Reading package.json", { path: packageJsonPath });
12385
12402
  const packageJson = JSON.parse(fs2.readFileSync(packageJsonPath, "utf8"));
12386
12403
  const allDeps = {
12387
12404
  ...packageJson.dependencies,
@@ -12389,14 +12406,14 @@ function detectFramework(cwd = process.cwd()) {
12389
12406
  };
12390
12407
  for (const [frameworkName, config] of Object.entries(frameworkConfigs)) {
12391
12408
  if (frameworkName === "generic") continue;
12392
- logger4.debug("Checking dependencies", {
12409
+ logger6.debug("Checking dependencies", {
12393
12410
  framework: frameworkName,
12394
12411
  dependencies: config.packageDependencies
12395
12412
  });
12396
12413
  const hasFrameworkDep = config.packageDependencies.some((dep) => {
12397
12414
  const exists = !!allDeps[dep];
12398
12415
  if (exists) {
12399
- logger4.debug("Found framework dependency", {
12416
+ logger6.debug("Found framework dependency", {
12400
12417
  framework: frameworkName,
12401
12418
  dependency: dep,
12402
12419
  version: allDeps[dep]
@@ -12405,134 +12422,35 @@ function detectFramework(cwd = process.cwd()) {
12405
12422
  return exists;
12406
12423
  });
12407
12424
  if (hasFrameworkDep) {
12408
- logger4.info("Framework detected via dependencies", { framework: frameworkName });
12425
+ logger6.info("Framework detected via dependencies", { framework: frameworkName });
12409
12426
  return frameworkName;
12410
12427
  }
12411
12428
  }
12412
12429
  }
12413
12430
  } catch (error) {
12414
- logger4.warn("Could not read package.json", { error: formatError(error) });
12431
+ logger6.warn("Could not read package.json", { error: formatError(error) });
12415
12432
  }
12416
- logger4.info("No specific framework detected, using generic");
12433
+ logger6.info("No specific framework detected, using generic");
12417
12434
  return "generic";
12418
12435
  }
12419
12436
  function detectPackageManager(cwd = process.cwd()) {
12420
- const logger4 = createChildLogger({ component: "package-manager-detector" });
12421
- logger4.debug("Detecting package manager", { cwd });
12437
+ const logger6 = createChildLogger({ component: "package-manager-detector" });
12438
+ logger6.debug("Detecting package manager", { cwd });
12422
12439
  if (fs2.existsSync(path2.join(cwd, "bun.lockb"))) {
12423
- logger4.info("Detected package manager", { manager: "bun" });
12440
+ logger6.info("Detected package manager", { manager: "bun" });
12424
12441
  return "bun";
12425
12442
  }
12426
12443
  if (fs2.existsSync(path2.join(cwd, "pnpm-lock.yaml"))) {
12427
- logger4.info("Detected package manager", { manager: "pnpm" });
12444
+ logger6.info("Detected package manager", { manager: "pnpm" });
12428
12445
  return "pnpm";
12429
12446
  }
12430
12447
  if (fs2.existsSync(path2.join(cwd, "yarn.lock"))) {
12431
- logger4.info("Detected package manager", { manager: "yarn" });
12448
+ logger6.info("Detected package manager", { manager: "yarn" });
12432
12449
  return "yarn";
12433
12450
  }
12434
- logger4.info("No lockfile found, using npm");
12451
+ logger6.info("No lockfile found, using npm");
12435
12452
  return "npm";
12436
12453
  }
12437
- function buildDevCommand(framework, packageManager, customCommand) {
12438
- const logger4 = createChildLogger({ component: "command-builder" });
12439
- if (customCommand) {
12440
- logger4.info("Using custom command", { command: customCommand });
12441
- return customCommand;
12442
- }
12443
- const config = frameworkConfigs[framework];
12444
- const baseCommand = config.devCommand;
12445
- const finalCommand = baseCommand.replace("npm run", `${packageManager} run`);
12446
- logger4.info("Built dev command", {
12447
- framework,
12448
- packageManager,
12449
- baseCommand,
12450
- finalCommand
12451
- });
12452
- return finalCommand;
12453
- }
12454
- function createServerLogPrefix() {
12455
- return "\u2502 ";
12456
- }
12457
- async function startDevServer(options = {}) {
12458
- const logger4 = createChildLogger({ component: "dev-server" });
12459
- const cwd = options.cwd || process.cwd();
12460
- const framework = options.framework || detectFramework(cwd);
12461
- const packageManager = detectPackageManager(cwd);
12462
- const config = frameworkConfigs[framework];
12463
- const command = buildDevCommand(framework, packageManager, options.command);
12464
- const port = options.port || config.defaultPort;
12465
- if (options.withIgniter) {
12466
- logger4.info("Starting dev server with Igniter watcher", {
12467
- framework,
12468
- packageManager,
12469
- command,
12470
- port,
12471
- igniterCommand: options.igniterCommand
12472
- });
12473
- try {
12474
- await startIgniterWithFramework({
12475
- framework,
12476
- frameworkCommand: command,
12477
- cwd,
12478
- port,
12479
- debug: options.debug,
12480
- igniterWatcherCommand: options.igniterCommand
12481
- });
12482
- return;
12483
- } catch (error) {
12484
- logger4.error("Failed to start concurrent processes", { error: formatError(error) });
12485
- throw error;
12486
- }
12487
- }
12488
- logger4.info("Starting dev server", {
12489
- framework,
12490
- packageManager,
12491
- command,
12492
- port
12493
- });
12494
- const [cmd, ...args] = command.split(" ");
12495
- const serverProcess = (0, import_child_process2.spawn)(cmd, args, {
12496
- stdio: ["inherit", "pipe", "pipe"],
12497
- cwd,
12498
- env: {
12499
- ...process.env,
12500
- PORT: port.toString(),
12501
- NODE_ENV: "development"
12502
- }
12503
- });
12504
- const logPrefix = createServerLogPrefix();
12505
- serverProcess.stdout?.on("data", (data) => {
12506
- const lines = data.toString().split("\n");
12507
- lines.forEach((line) => {
12508
- if (line.trim()) {
12509
- console.log(`${logPrefix}${line}`);
12510
- }
12511
- });
12512
- });
12513
- serverProcess.stderr?.on("data", (data) => {
12514
- const lines = data.toString().split("\n");
12515
- lines.forEach((line) => {
12516
- if (line.trim()) {
12517
- console.error(`${logPrefix}\x1B[31m${line}\x1B[0m`);
12518
- }
12519
- });
12520
- });
12521
- serverProcess.on("error", (error) => {
12522
- logger4.error("Dev server error", { error: formatError(error) });
12523
- });
12524
- serverProcess.on("exit", (code) => {
12525
- console.log(`
12526
- \u2514${"\u2500".repeat(60)}\u2518
12527
- `);
12528
- if (code === 0) {
12529
- logger4.success("Dev server exited successfully");
12530
- } else {
12531
- logger4.error("Dev server exited with error", { code });
12532
- }
12533
- });
12534
- return serverProcess;
12535
- }
12536
12454
  function isFrameworkSupported(framework) {
12537
12455
  return framework in frameworkConfigs;
12538
12456
  }
@@ -12569,7 +12487,7 @@ function showWelcome() {
12569
12487
  console.log(import_chalk2.default.dim("This process will configure your project with everything you need."));
12570
12488
  console.log();
12571
12489
  }
12572
- async function runSetupPrompts(targetDir) {
12490
+ async function runSetupPrompts(targetDir, isExistingProject = false) {
12573
12491
  showWelcome();
12574
12492
  const detectedFramework = detectFramework();
12575
12493
  const detectedPackageManager = detectPackageManager();
@@ -12577,7 +12495,7 @@ async function runSetupPrompts(targetDir) {
12577
12495
  try {
12578
12496
  const answers = await (0, import_prompts.default)([
12579
12497
  {
12580
- type: "text",
12498
+ type: isExistingProject ? null : "text",
12581
12499
  name: "projectName",
12582
12500
  message: import_chalk2.default.bold("\u2022 What will your project be called?"),
12583
12501
  initial: projectName,
@@ -12593,7 +12511,7 @@ async function runSetupPrompts(targetDir) {
12593
12511
  {
12594
12512
  type: "select",
12595
12513
  name: "framework",
12596
- message: import_chalk2.default.bold("\u2022 Which framework are you using?"),
12514
+ message: isExistingProject && detectedFramework !== "generic" ? `We detected ${import_chalk2.default.cyan(detectedFramework)}. Please confirm or select another.` : import_chalk2.default.bold("\u2022 Which framework are you using?"),
12597
12515
  choices: [
12598
12516
  {
12599
12517
  title: `${import_chalk2.default.green("Next.js")} ${detectedFramework === "nextjs" ? import_chalk2.default.dim("(detected)") : ""}`,
@@ -12660,6 +12578,13 @@ async function runSetupPrompts(targetDir) {
12660
12578
  value: "logging",
12661
12579
  selected: true
12662
12580
  // Default selected
12581
+ },
12582
+ {
12583
+ title: `${import_chalk2.default.yellow("Telemetry")}`,
12584
+ description: "Telemetry for tracking requests and errors",
12585
+ value: "telemetry",
12586
+ selected: true
12587
+ // Default selected
12663
12588
  }
12664
12589
  ],
12665
12590
  instructions: import_chalk2.default.dim("Use \u2191\u2193 to navigate, space to select, enter to confirm")
@@ -12684,10 +12609,6 @@ async function runSetupPrompts(targetDir) {
12684
12609
  {
12685
12610
  title: `${import_chalk2.default.green("SQLite + Prisma")} ${import_chalk2.default.dim("- Local development")}`,
12686
12611
  value: "sqlite"
12687
- },
12688
- {
12689
- title: `${import_chalk2.default.yellow("MongoDB + Mongoose")} ${import_chalk2.default.dim("- Document database")}`,
12690
- value: "mongodb"
12691
12612
  }
12692
12613
  ],
12693
12614
  initial: 0
@@ -12702,7 +12623,7 @@ async function runSetupPrompts(targetDir) {
12702
12623
  {
12703
12624
  type: "select",
12704
12625
  name: "packageManager",
12705
- message: import_chalk2.default.bold("\u2022 Which package manager?"),
12626
+ message: isExistingProject ? `We detected ${import_chalk2.default.cyan(detectedPackageManager)}. Please confirm or select another.` : import_chalk2.default.bold("\u2022 Which package manager?"),
12706
12627
  choices: [
12707
12628
  {
12708
12629
  title: `${import_chalk2.default.red("npm")} ${detectedPackageManager === "npm" ? import_chalk2.default.dim("(detected)") : ""}`,
@@ -12724,7 +12645,7 @@ async function runSetupPrompts(targetDir) {
12724
12645
  initial: getPackageManagerChoiceIndex(detectedPackageManager)
12725
12646
  },
12726
12647
  {
12727
- type: "confirm",
12648
+ type: isExistingProject ? null : "confirm",
12728
12649
  name: "initGit",
12729
12650
  message: import_chalk2.default.bold("\u2022 Initialize Git repository?"),
12730
12651
  initial: true
@@ -12745,15 +12666,16 @@ async function runSetupPrompts(targetDir) {
12745
12666
  store: answers.features.includes("store"),
12746
12667
  jobs: answers.features.includes("jobs"),
12747
12668
  mcp: answers.features.includes("mcp"),
12748
- logging: answers.features.includes("logging")
12669
+ logging: answers.features.includes("logging"),
12670
+ telemetry: answers.features.includes("telemetry")
12749
12671
  };
12750
12672
  const config = {
12751
- projectName: answers.projectName,
12673
+ projectName: answers.projectName || projectName,
12752
12674
  framework: answers.framework,
12753
12675
  features: featuresObj,
12754
12676
  database: { provider: answers.database },
12755
12677
  packageManager: answers.packageManager,
12756
- initGit: answers.initGit,
12678
+ initGit: answers.initGit === void 0 ? false : answers.initGit,
12757
12679
  installDependencies: answers.installDependencies,
12758
12680
  dockerCompose: answers.dockerCompose || false
12759
12681
  };
@@ -12796,11 +12718,11 @@ function getPackageManagerChoiceIndex(detected) {
12796
12718
  const managers = ["npm", "yarn", "pnpm", "bun"];
12797
12719
  return Math.max(0, managers.indexOf(detected));
12798
12720
  }
12799
- async function confirmOverwrite(targetPath) {
12721
+ async function confirmOverwrite(message) {
12800
12722
  const { overwrite } = await (0, import_prompts.default)({
12801
12723
  type: "confirm",
12802
12724
  name: "overwrite",
12803
- message: `Directory ${import_chalk2.default.cyan(targetPath)} already exists. Overwrite?`,
12725
+ message,
12804
12726
  initial: false
12805
12727
  });
12806
12728
  return overwrite;
@@ -12820,7 +12742,7 @@ var IGNITER_FEATURES = {
12820
12742
  name: "Redis Store",
12821
12743
  description: "Caching, sessions, and pub/sub messaging",
12822
12744
  dependencies: [
12823
- { name: "@igniter-js/adapter-redis", version: "*" },
12745
+ { name: "@igniter-js/adapter-redis", version: "alpha" },
12824
12746
  { name: "ioredis", version: "^5.6.1" }
12825
12747
  ],
12826
12748
  devDependencies: [
@@ -12848,8 +12770,9 @@ var IGNITER_FEATURES = {
12848
12770
  name: "BullMQ Jobs",
12849
12771
  description: "Background task processing and job queues",
12850
12772
  dependencies: [
12851
- { name: "@igniter-js/adapter-bullmq", version: "*" },
12852
- { name: "bullmq", version: "^5.0.0" },
12773
+ { name: "@igniter-js/adapter-redis", version: "alpha" },
12774
+ { name: "@igniter-js/adapter-bullmq", version: "alpha" },
12775
+ { name: "bullmq", version: "^4.0.0" },
12853
12776
  { name: "ioredis", version: "^5.6.1" }
12854
12777
  ],
12855
12778
  devDependencies: [
@@ -12868,21 +12791,39 @@ var IGNITER_FEATURES = {
12868
12791
  ],
12869
12792
  envVars: [
12870
12793
  { key: "REDIS_URL", value: "redis://localhost:6379", description: "Redis connection URL for jobs" },
12871
- { key: "QUEUE_PREFIX", value: "igniter", description: "Job queue prefix" }
12794
+ { key: "IGNITER_JOBS_QUEUE_PREFIX", value: "igniter", description: "Job queue prefix" }
12872
12795
  ]
12873
12796
  },
12874
12797
  mcp: {
12875
12798
  key: "mcp",
12876
12799
  name: "MCP Server",
12877
- description: "AI assistant integration with Model Context Protocol",
12800
+ description: "Easy expose your API as a MCP server for AI assistants like Cursor, Claude, etc.",
12878
12801
  dependencies: [
12879
- { name: "@igniter-js/adapter-mcp", version: "*" },
12802
+ { name: "@igniter-js/adapter-mcp", version: "alpha" },
12880
12803
  { name: "@vercel/mcp-adapter", version: "^0.2.0" },
12881
- { name: "@modelcontextprotocol/sdk", version: "^1.10.2" }
12804
+ { name: "@modelcontextprotocol/sdk", version: "^1.10.2" },
12805
+ { name: "ioredis", version: "^5.6.1" }
12806
+ ],
12807
+ devDependencies: [
12808
+ { name: "@types/ioredis", version: "^4.28.10" }
12809
+ ],
12810
+ dockerServices: [
12811
+ {
12812
+ name: "redis",
12813
+ image: "redis:7-alpine",
12814
+ ports: ["6379:6379"],
12815
+ environment: {
12816
+ REDIS_PASSWORD: ""
12817
+ },
12818
+ volumes: ["redis_data:/data"]
12819
+ }
12882
12820
  ],
12883
12821
  envVars: [
12884
- { key: "MCP_SERVER_URL", value: "http://localhost:3000/mcp", description: "MCP server endpoint" },
12885
- { key: "MCP_SESSION_TIMEOUT", value: "3600000", description: "MCP session timeout in ms" }
12822
+ { key: "IGNITER_MCP_SERVER_BASE_PATH", value: "/api/mcp", description: "MCP server base path" },
12823
+ { key: "IGNITER_MCP_SERVER_TIMEOUT", value: "3600000", description: "MCP session timeout in ms" },
12824
+ { key: "REDIS_URL", value: "redis://localhost:6379", description: "Redis connection URL" },
12825
+ { key: "REDIS_HOST", value: "localhost", description: "Redis host" },
12826
+ { key: "REDIS_PORT", value: "6379", description: "Redis port" }
12886
12827
  ]
12887
12828
  },
12888
12829
  logging: {
@@ -12890,11 +12831,24 @@ var IGNITER_FEATURES = {
12890
12831
  name: "Enhanced Logging",
12891
12832
  description: "Advanced console logging with structured output",
12892
12833
  dependencies: [
12893
- { name: "@igniter-js/core", version: "*" }
12834
+ { name: "@igniter-js/core", version: "alpha" }
12894
12835
  ],
12895
12836
  envVars: [
12896
- { key: "LOG_LEVEL", value: "info", description: "Logging level (debug, info, warn, error)" },
12897
- { key: "LOG_FORMAT", value: "pretty", description: "Log output format (pretty, json)" }
12837
+ { key: "IGNITER_LOG_LEVEL", value: "info", description: "Logging level (debug, info, warn, error)" }
12838
+ ]
12839
+ },
12840
+ telemetry: {
12841
+ key: "telemetry",
12842
+ name: "Telemetry",
12843
+ description: "Telemetry for tracking requests and errors",
12844
+ dependencies: [
12845
+ { name: "@igniter-js/core", version: "alpha" }
12846
+ ],
12847
+ envVars: [
12848
+ { key: "IGNITER_TELEMETRY_ENABLE_TRACING", value: "true", description: "Enable telemetry tracing" },
12849
+ { key: "IGNITER_TELEMETRY_ENABLE_METRICS", value: "true", description: "Enable telemetry metrics" },
12850
+ { key: "IGNITER_TELEMETRY_ENABLE_EVENTS", value: "true", description: "Enable telemetry metrics" },
12851
+ { key: "IGNITER_TELEMETRY_ENABLE_CLI_INTEGRATION", value: "true", description: "Enable telemetry metrics" }
12898
12852
  ]
12899
12853
  }
12900
12854
  };
@@ -13017,7 +12971,7 @@ function getEnvironmentVariables(enabledFeatures, databaseProvider, projectName)
13017
12971
  if (dbConfig?.envVars) {
13018
12972
  const dbEnvVars = dbConfig.envVars.map((envVar) => ({
13019
12973
  ...envVar,
13020
- value: envVar.value.replace("igniter_db", `${projectName.replace(/[^a-z0-9]/gi, "_")}_db`)
12974
+ value: envVar.value
13021
12975
  }));
13022
12976
  envVars.push(...dbEnvVars);
13023
12977
  }
@@ -13032,29 +12986,30 @@ function generateIgniterRouter(config) {
13032
12986
  const { features } = config;
13033
12987
  let imports = [`import { Igniter } from '@igniter-js/core'`];
13034
12988
  let serviceImports = [];
13035
- imports.push('import type { IgniterAppContext } from "./igniter.context"');
12989
+ imports.push('import { createIgniterAppContext } from "./igniter.context"');
13036
12990
  if (features.store) {
13037
12991
  serviceImports.push('import { store } from "@/services/store"');
13038
12992
  }
13039
12993
  if (features.jobs) {
13040
- serviceImports.push('import { jobs } from "@/services/jobs"');
12994
+ serviceImports.push('import { REGISTERED_JOBS } from "@/services/jobs"');
13041
12995
  }
13042
12996
  if (features.logging) {
13043
12997
  serviceImports.push('import { logger } from "@/services/logger"');
13044
12998
  }
13045
- if (config.database.provider !== "none") {
13046
- serviceImports.push('import { database } from "@/services/database"');
12999
+ if (features.telemetry) {
13000
+ serviceImports.push('import { telemetry } from "@/services/telemetry"');
13047
13001
  }
13048
13002
  const allImports = [...imports, ...serviceImports].join("\n");
13049
- let configChain = ["export const igniter = Igniter", " .context<IgniterAppContext>()"];
13003
+ let configChain = ["export const igniter = Igniter", " .context(createIgniterAppContext)"];
13050
13004
  if (features.store) configChain.push(" .store(store)");
13051
- if (features.jobs) configChain.push(" .jobs(jobs)");
13005
+ if (features.jobs) configChain.push(" .jobs(REGISTERED_JOBS)");
13052
13006
  if (features.logging) configChain.push(" .logger(logger)");
13007
+ if (features.telemetry) configChain.push(" .telemetry(telemetry)");
13053
13008
  configChain.push(" .create()");
13054
13009
  const content = `${allImports}
13055
13010
 
13056
13011
  /**
13057
- * @description Initialize the Igniter.js Router with enhanced features
13012
+ * @description Initialize the Igniter.js
13058
13013
  * @see https://github.com/felipebarcelospro/igniter-js
13059
13014
  */
13060
13015
  ${configChain.join("\n")}
@@ -13065,30 +13020,18 @@ ${configChain.join("\n")}
13065
13020
  };
13066
13021
  }
13067
13022
  function generateIgniterContext(config) {
13068
- const { features, database } = config;
13023
+ const { database } = config;
13069
13024
  let serviceImports = [];
13070
13025
  let contextProperties = [];
13071
- if (features.store) {
13072
- serviceImports.push('import { store } from "@/services/store"');
13073
- contextProperties.push(" store,");
13074
- }
13075
- if (features.jobs) {
13076
- serviceImports.push('import { jobs } from "@/services/jobs"');
13077
- contextProperties.push(" jobs,");
13078
- }
13079
- if (features.logging) {
13080
- serviceImports.push('import { logger } from "@/services/logger"');
13081
- contextProperties.push(" logger,");
13082
- }
13083
13026
  if (database.provider !== "none") {
13084
13027
  serviceImports.push('import { database } from "@/services/database"');
13085
- contextProperties.push(" database,");
13028
+ contextProperties.push(" // database,");
13086
13029
  }
13087
13030
  const allImports = serviceImports.join("\n");
13088
13031
  const content = `${allImports}
13089
13032
 
13090
13033
  /**
13091
- * @description Create the context of the application
13034
+ * @description Create the context of the Igniter.js application
13092
13035
  * @see https://github.com/felipebarcelospro/igniter-js
13093
13036
  */
13094
13037
  export const createIgniterAppContext = () => {
@@ -13098,8 +13041,7 @@ ${contextProperties.join("\n")}
13098
13041
  }
13099
13042
 
13100
13043
  /**
13101
- * @description The context of the application
13102
- * Enhanced with store, jobs, and logger from Igniter.js builder
13044
+ * @description The context of the Igniter.js application
13103
13045
  * @see https://github.com/felipebarcelospro/igniter-js
13104
13046
  */
13105
13047
  export type IgniterAppContext = Awaited<ReturnType<typeof createIgniterAppContext>>
@@ -13115,11 +13057,13 @@ function generateExampleController(config) {
13115
13057
  import { z } from 'zod'`;
13116
13058
  let exampleActions = ` // Health check action
13117
13059
  health: igniter.query({
13060
+ name: 'health',
13061
+ description: 'Health check',
13118
13062
  path: '/',
13119
13063
  handler: async ({ request, response, context }) => {
13120
13064
  ${features.logging ? "context.logger.info('Health check requested')" : ""}
13121
- return response.success({
13122
- status: 'ok',
13065
+ return response.success({
13066
+ status: 'ok',
13123
13067
  timestamp: new Date().toISOString(),
13124
13068
  features: {
13125
13069
  store: ${features.store},
@@ -13135,33 +13079,32 @@ import { z } from 'zod'`;
13135
13079
 
13136
13080
  // Cache demonstration action
13137
13081
  cacheDemo: igniter.query({
13138
- path: '/cache/:key',
13139
- params: z.object({
13140
- key: z.string()
13141
- }),
13082
+ name: 'cacheDemo',
13083
+ description: 'Demonstrate caching',
13084
+ path: '/cache/:key' as const,
13142
13085
  handler: async ({ request, response, context }) => {
13143
13086
  const { key } = request.params
13144
13087
  const cached = await context.store.get(key)
13145
-
13088
+
13146
13089
  if (cached) {
13147
- return response.success({
13148
- data: cached,
13149
- source: 'cache'
13090
+ return response.success({
13091
+ data: cached,
13092
+ source: 'cache'
13150
13093
  })
13151
13094
  }
13152
-
13095
+
13153
13096
  // Generate sample data
13154
- const data = {
13097
+ const data = {
13155
13098
  message: \`Hello from \${key}\`,
13156
13099
  timestamp: new Date().toISOString()
13157
13100
  }
13158
-
13101
+
13159
13102
  // Cache for 1 hour
13160
13103
  await context.store.set(key, data, { ttl: 3600 })
13161
-
13162
- return response.success({
13163
- data,
13164
- source: 'generated'
13104
+
13105
+ return response.success({
13106
+ data,
13107
+ source: 'generated'
13165
13108
  })
13166
13109
  }
13167
13110
  })`;
@@ -13171,25 +13114,28 @@ import { z } from 'zod'`;
13171
13114
 
13172
13115
  // Background job scheduling action
13173
13116
  scheduleJob: igniter.mutation({
13117
+ name: 'scheduleJob',
13118
+ description: 'Schedule a background job',
13174
13119
  path: '/schedule-job',
13120
+ method: 'POST',
13175
13121
  body: z.object({
13176
13122
  message: z.string(),
13177
13123
  delay: z.number().optional()
13178
13124
  }),
13179
13125
  handler: async ({ request, response, context }) => {
13180
13126
  const { message, delay = 0 } = request.body
13181
-
13127
+
13182
13128
  const jobId = await context.jobs.add('processMessage', {
13183
13129
  message,
13184
13130
  timestamp: new Date().toISOString()
13185
13131
  }, { delay })
13186
-
13132
+
13187
13133
  ${features.logging ? "context.logger.info('Job scheduled', { jobId, message })" : ""}
13188
-
13189
- return response.success({
13134
+
13135
+ return response.success({
13190
13136
  jobId,
13191
13137
  message: 'Job scheduled successfully',
13192
- delay
13138
+ delay
13193
13139
  })
13194
13140
  }
13195
13141
  })`;
@@ -13201,6 +13147,7 @@ import { z } from 'zod'`;
13201
13147
  * @see https://github.com/felipebarcelospro/igniter-js
13202
13148
  */
13203
13149
  export const exampleController = igniter.controller({
13150
+ name: 'example',
13204
13151
  path: '/example',
13205
13152
  actions: {
13206
13153
  ${exampleActions}
@@ -13221,14 +13168,12 @@ import { exampleController } from '@/features/example'
13221
13168
  * @see https://github.com/felipebarcelospro/igniter-js
13222
13169
  */
13223
13170
  export const AppRouter = igniter.router({
13224
- baseURL: process.env.NEXT_PUBLIC_IGNITER_APP_URL, // Default is http://localhost:3000
13225
- basePATH: process.env.NEXT_PUBLIC_IGNITER_APP_BASE_PATH, // Default is /api/v1
13226
13171
  controllers: {
13227
13172
  example: exampleController
13228
13173
  }
13229
13174
  })
13230
13175
 
13231
- export type AppRouter = typeof AppRouter
13176
+ export type AppRouterType = typeof AppRouter
13232
13177
  `;
13233
13178
  return {
13234
13179
  path: "src/igniter.router.ts",
@@ -13247,103 +13192,228 @@ export * from './example.interfaces'
13247
13192
  function generateServiceFiles(config) {
13248
13193
  const { features, database } = config;
13249
13194
  const files = [];
13195
+ files.push({
13196
+ path: "src/app/api/v1/[[...all]]/route.ts",
13197
+ content: `import { AppRouter } from '@/igniter.router'
13198
+ import { nextRouteHandlerAdapter } from '@igniter-js/core/adapters'
13199
+
13200
+ export const { GET, POST, PUT, DELETE } = nextRouteHandlerAdapter(AppRouter)
13201
+ `
13202
+ });
13203
+ if (features.store || features.jobs) {
13204
+ files.push({
13205
+ path: "src/services/redis.ts",
13206
+ content: `import { Redis } from 'ioredis'
13207
+
13208
+ /**
13209
+ * Redis client instance for caching, session storage, and pub/sub.
13210
+ *
13211
+ * @remarks
13212
+ * Used for caching, session management, and real-time messaging.
13213
+ *
13214
+ * @see https://github.com/luin/ioredis
13215
+ */
13216
+ export const redis = new Redis(process.env.REDIS_URL!, {
13217
+ maxRetriesPerRequest: null,
13218
+ })
13219
+ `
13220
+ });
13221
+ }
13250
13222
  if (features.store) {
13251
13223
  files.push({
13252
13224
  path: "src/services/store.ts",
13253
13225
  content: `import { createRedisStoreAdapter } from '@igniter-js/adapter-redis'
13254
- import { Redis } from 'ioredis'
13226
+ import { redis } from './redis'
13255
13227
 
13256
13228
  /**
13257
- * Redis client instance for caching and pub/sub
13258
- * @description Handles caching, session storage and real-time messaging
13259
- */
13260
- const redis = new Redis(process.env.REDIS_URL!)
13261
-
13262
- /**
13263
- * Store adapter for data persistence
13264
- * @description Provides a unified interface for data storage operations
13265
- */
13266
- export const store = createRedisStoreAdapter({ redis })
13229
+ * Store adapter for data persistence.
13230
+ *
13231
+ * @remarks
13232
+ * Provides a unified interface for data storage operations using Redis.
13233
+ *
13234
+ * @see https://github.com/felipebarcelospro/igniter-js/tree/main/packages/adapter-redis
13235
+ */
13236
+ export const store = createRedisStoreAdapter(redis)
13267
13237
  `
13268
13238
  });
13269
13239
  }
13270
13240
  if (features.jobs) {
13271
13241
  files.push({
13272
13242
  path: "src/services/jobs.ts",
13273
- content: `import { createBullMQAdapter } from '@igniter-js/adapter-bullmq'
13274
- import { Redis } from 'ioredis'
13243
+ content: `import { store } from './store'
13244
+ import { createBullMQAdapter } from '@igniter-js/adapter-bullmq'
13245
+ import { z } from 'zod'
13275
13246
 
13276
13247
  /**
13277
- * Redis connection for job queue
13278
- */
13279
- const redis = new Redis(process.env.REDIS_URL!)
13248
+ * Job queue adapter for background processing.
13249
+ *
13250
+ * @remarks
13251
+ * Handles asynchronous job processing with BullMQ.
13252
+ *
13253
+ * @see https://github.com/felipebarcelospro/igniter-js/tree/main/packages/adapter-bullmq
13254
+ */
13255
+ export const jobs = createBullMQAdapter({
13256
+ store,
13257
+ autoStartWorker: {
13258
+ concurrency: 1,
13259
+ queues: ['*']
13260
+ }
13261
+ })
13280
13262
 
13281
- /**
13282
- * Job queue adapter for background processing
13283
- * @description Handles asynchronous job processing with BullMQ
13284
- */
13285
- export const jobs = createBullMQAdapter({ redis })
13263
+ export const REGISTERED_JOBS = jobs.merge({
13264
+ system: jobs.router({
13265
+ jobs: {
13266
+ sampleJob: jobs.register({
13267
+ name: 'sampleJob',
13268
+ input: z.object({
13269
+ message: z.string()
13270
+ }),
13271
+ handler: async ({ input }) => {
13272
+ console.log(input.message)
13273
+ }
13274
+ })
13275
+ }
13276
+ })
13277
+ })
13286
13278
  `
13287
13279
  });
13288
13280
  }
13289
13281
  if (features.logging) {
13290
13282
  files.push({
13291
13283
  path: "src/services/logger.ts",
13292
- content: `import { createConsoleLogger } from '@igniter-js/core'
13284
+ content: `import { createConsoleLogger, IgniterLogLevel } from '@igniter-js/core'
13293
13285
 
13294
13286
  /**
13295
- * Logger instance for application logging
13296
- * @description Provides structured logging with configurable log levels
13297
- */
13298
- export const logger = createConsoleLogger({
13299
- level: process.env.LOG_LEVEL as any || 'info'
13287
+ * Logger instance for application logging.
13288
+ *
13289
+ * @remarks
13290
+ * Provides structured logging with configurable log levels.
13291
+ *
13292
+ * @see https://github.com/felipebarcelospro/igniter-js/tree/main/packages/core
13293
+ */
13294
+ export const logger = createConsoleLogger({
13295
+ level: IgniterLogLevel.INFO,
13296
+ showTimestamp: true,
13300
13297
  })
13301
13298
  `
13302
13299
  });
13303
13300
  }
13304
13301
  if (database.provider !== "none") {
13305
- if (database.provider === "mongodb") {
13306
- files.push({
13307
- path: "src/services/database.ts",
13308
- content: `import mongoose from 'mongoose'
13302
+ files.push({
13303
+ path: "src/services/database.ts",
13304
+ content: `import { PrismaClient } from '@prisma/client'
13309
13305
 
13310
13306
  /**
13311
- * MongoDB connection configuration
13312
- * @description Connects to MongoDB using Mongoose
13307
+ * Prisma client instance for database operations.
13308
+ *
13309
+ * @remarks
13310
+ * Provides type-safe database access with Prisma ORM.
13311
+ *
13312
+ * @see https://www.prisma.io/docs/concepts/components/prisma-client
13313
13313
  */
13314
- export const database = mongoose.connect(process.env.DATABASE_URL!)
13314
+ export const database = new PrismaClient()
13315
13315
  `
13316
- });
13317
- } else {
13318
- files.push({
13319
- path: "src/services/database.ts",
13320
- content: `import { PrismaClient } from '@prisma/client'
13316
+ });
13317
+ }
13318
+ if (features.telemetry) {
13319
+ files.push({
13320
+ path: "src/services/telemetry.ts",
13321
+ content: `import { createConsoleTelemetryAdapter } from '@igniter-js/core/adapters'
13322
+ import { store } from './store'
13323
+
13324
+ /**
13325
+ * Telemetry service for tracking requests and errors.
13326
+ *
13327
+ * @remarks
13328
+ * Provides telemetry tracking with configurable options.
13329
+ *
13330
+ * @see https://github.com/felipebarcelospro/igniter-js/tree/main/packages/core
13331
+ */
13332
+ export const telemetry = createConsoleTelemetryAdapter({
13333
+ serviceName: 'my-igniter-app',
13334
+ enableEvents: process.env.IGNITER_TELEMETRY_ENABLE_EVENTS === 'true',
13335
+ enableMetrics: process.env.IGNITER_TELEMETRY_ENABLE_METRICS === 'true',
13336
+ enableTracing: process.env.IGNITER_TELEMETRY_ENABLE_TRACING === 'true',
13337
+ }, {
13338
+ enableCliIntegration: process.env.IGNITER_TELEMETRY_ENABLE_CLI_INTEGRATION === 'true',
13339
+ store: store
13340
+ })
13341
+ `
13342
+ });
13343
+ }
13344
+ if (features.mcp) {
13345
+ files.push({
13346
+ path: "src/app/api/mcp/[transport].ts",
13347
+ content: `import { createMcpAdapter } from '@igniter-js/adapter-mcp'
13348
+ import { AppRouter } from '@/igniter.router'
13321
13349
 
13322
13350
  /**
13323
- * Prisma client instance for database operations
13324
- * @description Provides type-safe database access with Prisma ORM
13351
+ * MCP server instance for exposing API as a MCP server.
13352
+ *
13353
+ * @see https://github.com/felipebarcelospro/igniter-js/tree/main/packages/adapter-mcp
13325
13354
  */
13326
- export const database = new PrismaClient()
13355
+ export default createMcpAdapter(AppRouter, {
13356
+ serverInfo: {
13357
+ name: 'Igniter.js MCP Server',
13358
+ version: '1.0.0',
13359
+ },
13360
+ adapter: {
13361
+ redis: {
13362
+ url: process.env.REDIS_URL!,
13363
+ maxRetriesPerRequest: null,
13364
+ },
13365
+ basePath: process.env.IGNITER_MCP_SERVER_BASE_PATH || '/api/mcp',
13366
+ maxDuration: process.env.IGNITER_MCP_SERVER_TIMEOUT || 60,
13367
+ },
13368
+ })
13327
13369
  `
13328
- });
13329
- }
13370
+ });
13330
13371
  }
13331
13372
  return files;
13332
13373
  }
13333
13374
  function generateIgniterClient(config) {
13334
- const content = `import { createIgniterClient } from '@igniter-js/core/client'
13335
- import type { AppRouter } from '@/igniter.router'
13375
+ const content = `import { createIgniterClient, useIgniterQueryClient } from '@igniter-js/core/client'
13376
+ import type { AppRouterType } from './igniter.router'
13336
13377
 
13337
13378
  /**
13338
- * @description Igniter.js client for frontend usage
13339
- * @see https://github.com/felipebarcelospro/igniter-js
13340
- */
13341
- export const client = createIgniterClient<AppRouter>({
13342
- baseURL: process.env.NEXT_PUBLIC_IGNITER_APP_URL || 'http://localhost:3000',
13343
- basePATH: process.env.NEXT_PUBLIC_IGNITER_APP_BASE_PATH || '/api/v1'
13379
+ * Type-safe API client generated from your Igniter router
13380
+ *
13381
+ * Usage in Server Components:
13382
+ * const users = await api.users.list.query()
13383
+ *
13384
+ * Usage in Client Components:
13385
+ * const { data } = api.users.list.useQuery()
13386
+ */
13387
+ export const api = createIgniterClient<AppRouterType>({
13388
+ baseURL: 'http://localhost:3000',
13389
+ basePath: '/api/v1/',
13390
+ router: () => {
13391
+ if (typeof window === 'undefined') {
13392
+ return require('./igniter.router').AppRouter
13393
+ }
13394
+
13395
+ return require('./igniter.schema').AppRouterSchema
13396
+ },
13344
13397
  })
13345
13398
 
13346
- export type IgniterClient = typeof client
13399
+ /**
13400
+ * Type-safe API client generated from your Igniter router
13401
+ *
13402
+ * Usage in Server Components:
13403
+ * const users = await api.users.list.query()
13404
+ *
13405
+ * Usage in Client Components:
13406
+ * const { data } = api.users.list.useQuery()
13407
+ */
13408
+ export type ApiClient = typeof api
13409
+
13410
+ /**
13411
+ * Type-safe query client generated from your Igniter router
13412
+ *
13413
+ * Usage in Client Components:
13414
+ * const { invalidate } = useQueryClient()
13415
+ */
13416
+ export const useQueryClient = useIgniterQueryClient<AppRouterType>;
13347
13417
  `;
13348
13418
  return {
13349
13419
  path: "src/igniter.client.ts",
@@ -13384,117 +13454,6 @@ export interface ExampleJobPayload {
13384
13454
  content
13385
13455
  };
13386
13456
  }
13387
- function generatePackageJson(config, dependencies, devDependencies) {
13388
- const scripts = {
13389
- "dev": "next dev",
13390
- "build": "next build",
13391
- "start": "next start",
13392
- "lint": "next lint",
13393
- "type-check": "tsc --noEmit"
13394
- };
13395
- if (config.database.provider !== "none" && config.database.provider !== "mongodb") {
13396
- scripts["db:generate"] = "prisma generate";
13397
- scripts["db:push"] = "prisma db push";
13398
- scripts["db:studio"] = "prisma studio";
13399
- scripts["db:migrate"] = "prisma migrate dev";
13400
- }
13401
- switch (config.framework) {
13402
- case "vite":
13403
- scripts.dev = "vite";
13404
- scripts.build = "vite build";
13405
- scripts.start = "vite preview";
13406
- break;
13407
- case "nuxt":
13408
- scripts.dev = "nuxt dev";
13409
- scripts.build = "nuxt build";
13410
- scripts.start = "nuxt start";
13411
- break;
13412
- case "sveltekit":
13413
- scripts.dev = "vite dev";
13414
- scripts.build = "svelte-kit build";
13415
- scripts.start = "node build";
13416
- break;
13417
- case "remix":
13418
- scripts.dev = "remix dev";
13419
- scripts.build = "remix build";
13420
- scripts.start = "remix-serve build";
13421
- break;
13422
- case "astro":
13423
- scripts.dev = "astro dev";
13424
- scripts.build = "astro build";
13425
- scripts.start = "astro preview";
13426
- break;
13427
- case "express":
13428
- scripts.dev = "tsx watch src/server.ts";
13429
- scripts.build = "tsc";
13430
- scripts.start = "node dist/server.js";
13431
- break;
13432
- }
13433
- const deps = dependencies.reduce((acc, dep) => {
13434
- const [name, version] = dep.split("@");
13435
- acc[name] = version;
13436
- return acc;
13437
- }, {});
13438
- const devDeps = devDependencies.reduce((acc, dep) => {
13439
- const [name, version] = dep.split("@");
13440
- acc[name] = version;
13441
- return acc;
13442
- }, {});
13443
- const packageJson = {
13444
- name: config.projectName,
13445
- version: "0.1.0",
13446
- private: true,
13447
- scripts,
13448
- dependencies: deps,
13449
- devDependencies: devDeps
13450
- };
13451
- return {
13452
- path: "package.json",
13453
- content: JSON.stringify(packageJson, null, 2)
13454
- };
13455
- }
13456
- function generateTsConfig(framework) {
13457
- let compilerOptions = {
13458
- target: "ES2020",
13459
- lib: ["ES2020", "DOM"],
13460
- allowJs: true,
13461
- skipLibCheck: true,
13462
- strict: true,
13463
- forceConsistentCasingInFileNames: true,
13464
- noEmit: true,
13465
- esModuleInterop: true,
13466
- module: "esnext",
13467
- moduleResolution: "node",
13468
- resolveJsonModule: true,
13469
- isolatedModules: true,
13470
- jsx: "preserve",
13471
- incremental: true,
13472
- baseUrl: ".",
13473
- paths: {
13474
- "@/*": ["./src/*"]
13475
- }
13476
- };
13477
- switch (framework) {
13478
- case "nextjs":
13479
- compilerOptions.plugins = [{ name: "next" }];
13480
- break;
13481
- case "vite":
13482
- compilerOptions.types = ["vite/client"];
13483
- break;
13484
- case "nuxt":
13485
- compilerOptions.paths["~/*"] = ["./src/*"];
13486
- break;
13487
- }
13488
- const tsConfig = {
13489
- compilerOptions,
13490
- include: ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
13491
- exclude: ["node_modules"]
13492
- };
13493
- return {
13494
- path: "tsconfig.json",
13495
- content: JSON.stringify(tsConfig, null, 2)
13496
- };
13497
- }
13498
13457
  function generateEnvFile(config) {
13499
13458
  const envVars = getEnvironmentVariables(
13500
13459
  Object.entries(config.features).filter(([_, enabled]) => enabled).map(([key, _]) => key),
@@ -13571,171 +13530,19 @@ ${Object.keys(dockerCompose.volumes).map((volume) => ` ${volume}:`).join("\n")}
13571
13530
  `
13572
13531
  };
13573
13532
  }
13574
- function generateGitIgnore() {
13575
- const content = `# Dependencies
13576
- node_modules/
13577
- npm-debug.log*
13578
- yarn-debug.log*
13579
- yarn-error.log*
13580
- .npm
13581
- .yarn/
13582
-
13583
- # Environment
13584
- .env
13585
- .env.local
13586
- .env.development.local
13587
- .env.test.local
13588
- .env.production.local
13589
-
13590
- # Build outputs
13591
- dist/
13592
- build/
13593
- .next/
13594
- .nuxt/
13595
- .svelte-kit/
13596
-
13597
- # Database
13598
- *.db
13599
- *.sqlite
13600
- prisma/migrations/
13601
-
13602
- # IDE
13603
- .vscode/
13604
- .idea/
13605
- *.swp
13606
- *.swo
13607
- *~
13608
-
13609
- # OS
13610
- .DS_Store
13611
- Thumbs.db
13612
-
13613
- # Logs
13614
- logs
13615
- *.log
13616
-
13617
- # Coverage
13618
- coverage/
13619
- .nyc_output/
13620
-
13621
- # Cache
13622
- .cache/
13623
- .tmp/
13624
- .temp/
13625
- `;
13626
- return {
13627
- path: ".gitignore",
13628
- content
13629
- };
13630
- }
13631
- function generateReadme(config) {
13632
- const enabledFeatures = Object.entries(config.features).filter(([_, enabled]) => enabled).map(([key, _]) => `- **${key}**: Enabled`);
13633
- const content = `# ${config.projectName}
13634
-
13635
- A modern, type-safe API built with [Igniter.js](https://github.com/felipebarcelospro/igniter-js) and ${config.framework}.
13636
-
13637
- ## Features
13638
-
13639
- ${enabledFeatures.join("\n")}
13640
- ${config.database.provider !== "none" ? `- **Database**: ${config.database.provider}` : ""}
13641
- ${config.dockerCompose ? "- **Docker**: Compose setup included" : ""}
13642
-
13643
- ## Development
13644
-
13645
- ### Prerequisites
13646
-
13647
- - Node.js 18+
13648
- - ${config.packageManager}
13649
- ${config.dockerCompose ? "- Docker and Docker Compose" : ""}
13650
-
13651
- ### Getting Started
13652
-
13653
- 1. **Install dependencies:**
13654
- \`\`\`bash
13655
- ${config.packageManager} install
13656
- \`\`\`
13657
-
13658
- ${config.dockerCompose ? `2. **Start services with Docker:**
13659
- \`\`\`bash
13660
- docker-compose up -d
13661
- \`\`\`
13662
-
13663
- ` : ""}${config.database.provider !== "none" && config.database.provider !== "mongodb" ? `3. **Setup database:**
13664
- \`\`\`bash
13665
- ${config.packageManager} run db:push
13666
- \`\`\`
13667
-
13668
- ` : ""}4. **Start development server:**
13669
- \`\`\`bash
13670
- ${config.packageManager} run dev
13671
- \`\`\`
13672
-
13673
- Visit [http://localhost:3000](http://localhost:3000) to see your app!
13674
-
13675
- ## Project Structure
13676
-
13677
- \`\`\`
13678
- src/
13679
- \u251C\u2500\u2500 igniter.ts # Core initialization
13680
- \u251C\u2500\u2500 igniter.client.ts # Client implementation
13681
- \u251C\u2500\u2500 igniter.context.ts # Context management
13682
- \u251C\u2500\u2500 igniter.router.ts # Router configuration
13683
- \u251C\u2500\u2500 features/ # Application features
13684
- \u2502 \u2514\u2500\u2500 example/
13685
- \u2502 \u251C\u2500\u2500 controllers/ # Feature controllers
13686
- \u2502 \u251C\u2500\u2500 procedures/ # Feature procedures/middleware
13687
- \u2502 \u251C\u2500\u2500 example.interfaces.ts # Type definitions
13688
- \u2502 \u2514\u2500\u2500 index.ts # Feature exports
13689
- \u2514\u2500\u2500 services/ # Service layer
13690
- \`\`\`
13691
-
13692
- ## API Endpoints
13693
-
13694
- - \`GET /api/v1/example\` - Health check
13695
- ${config.features.store ? "- `GET /api/v1/example/cache/:key` - Cache example" : ""}
13696
- ${config.features.jobs ? "- `POST /api/v1/example/schedule-job` - Schedule background job" : ""}
13697
-
13698
- ## Learn More
13699
-
13700
- - [Igniter.js Documentation](https://github.com/felipebarcelospro/igniter-js)
13701
- - [${config.framework} Documentation](https://docs.${config.framework === "nextjs" ? "nextjs.org" : config.framework + ".dev"})
13702
- ${config.database.provider !== "none" && config.database.provider !== "mongodb" ? "- [Prisma Documentation](https://prisma.io/docs)" : ""}
13703
- ${config.database.provider === "mongodb" ? "- [Mongoose Documentation](https://mongoosejs.com/docs)" : ""}
13704
-
13705
- ## Contributing
13706
-
13707
- 1. Fork the repository
13708
- 2. Create your feature branch (\`git checkout -b feature/amazing-feature\`)
13709
- 3. Commit your changes (\`git commit -m 'Add some amazing feature'\`)
13710
- 4. Push to the branch (\`git push origin feature/amazing-feature\`)
13711
- 5. Open a Pull Request
13712
-
13713
- ## License
13714
-
13715
- This project is licensed under the MIT License.
13716
- `;
13717
- return {
13718
- path: "README.md",
13719
- content
13720
- };
13721
- }
13722
- function generateAllTemplates(config, dependencies, devDependencies) {
13533
+ function generateAllTemplates(config, isExistingProject) {
13723
13534
  const templates = [
13724
- // Core Igniter files
13535
+ // Core Igniter files - always generate
13725
13536
  generateIgniterRouter(config),
13726
13537
  generateIgniterContext(config),
13727
13538
  generateMainRouter(config),
13728
13539
  generateIgniterClient(config),
13729
- // Feature files
13540
+ // Feature files - always generate
13730
13541
  generateExampleController(config),
13731
13542
  generateFeatureIndex(config),
13732
13543
  generateExampleInterfaces(),
13733
- // Configuration files
13734
- generatePackageJson(config, dependencies, devDependencies),
13735
- generateTsConfig(config.framework),
13736
- generateEnvFile(config),
13737
- generateGitIgnore(),
13738
- generateReadme(config)
13544
+ // .env.example is safe to generate as it won't overwrite a user's .env file
13545
+ generateEnvFile(config)
13739
13546
  ];
13740
13547
  const serviceFiles = generateServiceFiles(config);
13741
13548
  templates.push(...serviceFiles);
@@ -13750,10 +13557,11 @@ function generateAllTemplates(config, dependencies, devDependencies) {
13750
13557
  init_logger();
13751
13558
  var logger2 = createChildLogger({ component: "project-generator" });
13752
13559
  var ProjectGenerator = class {
13753
- constructor(config, targetDir) {
13560
+ constructor(config, targetDir, isExistingProject) {
13754
13561
  this.spinner = (0, import_ora.default)();
13755
13562
  this.config = config;
13756
13563
  this.targetDir = import_path2.default.resolve(targetDir);
13564
+ this.isExistingProject = isExistingProject;
13757
13565
  }
13758
13566
  /**
13759
13567
  * Generate the complete project
@@ -13762,14 +13570,19 @@ var ProjectGenerator = class {
13762
13570
  try {
13763
13571
  logger2.info("Starting project generation", {
13764
13572
  project: this.config.projectName,
13765
- targetDir: this.targetDir
13573
+ targetDir: this.targetDir,
13574
+ isExisting: this.isExistingProject
13766
13575
  });
13767
- await this.createProjectStructure();
13576
+ if (!this.isExistingProject) {
13577
+ await this.createProjectStructure();
13578
+ } else {
13579
+ this.spinner.succeed(import_chalk3.default.dim("\u2713 Existing project detected, skipping structure creation."));
13580
+ }
13768
13581
  await this.generateFiles();
13769
13582
  if (this.config.installDependencies) {
13770
13583
  await this.installDependencies();
13771
13584
  }
13772
- if (this.config.initGit) {
13585
+ if (this.config.initGit && !this.isExistingProject) {
13773
13586
  await this.initializeGit();
13774
13587
  }
13775
13588
  await this.runPostSetupTasks();
@@ -13780,12 +13593,75 @@ var ProjectGenerator = class {
13780
13593
  throw error;
13781
13594
  }
13782
13595
  }
13596
+ async downloadTemplate() {
13597
+ const { framework } = this.config;
13598
+ const templateUrl = `https://github.com/felipebarcelospro/igniter-js.git`;
13599
+ const branch = "release/0.2.0-alpha.0";
13600
+ const tempDir = import_path2.default.join(this.targetDir, "__igniter_tmp__");
13601
+ const starterDir = import_path2.default.join(tempDir, "apps", `starter-${framework}`);
13602
+ const destDir = this.targetDir;
13603
+ let isValidStarter = false;
13604
+ this.spinner.start(`Baixando apenas o conte\xFAdo da pasta starter (${framework}) do branch ${branch}...`);
13605
+ try {
13606
+ await import_promises.default.rm(tempDir, { recursive: true, force: true }).catch(() => {
13607
+ });
13608
+ await (0, import_execa.execa)("git", [
13609
+ "clone",
13610
+ "--depth",
13611
+ "1",
13612
+ "--branch",
13613
+ branch,
13614
+ "--single-branch",
13615
+ templateUrl,
13616
+ tempDir
13617
+ ]);
13618
+ const stat = await import_promises.default.stat(starterDir).catch(() => null);
13619
+ if (!stat || !stat.isDirectory()) {
13620
+ throw new Error("Diret\xF3rio starter n\xE3o encontrado no template clonado.");
13621
+ }
13622
+ isValidStarter = true;
13623
+ const copyRecursive = async (src, dest) => {
13624
+ const entries = await import_promises.default.readdir(src, { withFileTypes: true });
13625
+ for (const entry of entries) {
13626
+ const srcPath = import_path2.default.join(src, entry.name);
13627
+ const destPath = import_path2.default.join(dest, entry.name);
13628
+ if (entry.isDirectory()) {
13629
+ await import_promises.default.mkdir(destPath, { recursive: true });
13630
+ await copyRecursive(srcPath, destPath);
13631
+ } else if (entry.isFile()) {
13632
+ await import_promises.default.copyFile(srcPath, destPath);
13633
+ }
13634
+ }
13635
+ };
13636
+ await copyRecursive(starterDir, destDir);
13637
+ await import_promises.default.rm(tempDir, { recursive: true, force: true }).catch(() => {
13638
+ });
13639
+ this.spinner.succeed(import_chalk3.default.dim("\u2713 Conte\xFAdo da pasta starter copiado com sucesso"));
13640
+ return { isStarter: isValidStarter, success: true };
13641
+ } catch (error) {
13642
+ console.error("Template download/copy failed", error);
13643
+ this.spinner.fail(import_chalk3.default.red("Falha ao baixar/copiar o template starter"));
13644
+ logger2.error("Template download/copy failed", { error });
13645
+ await import_promises.default.rm(tempDir, { recursive: true, force: true }).catch(() => {
13646
+ });
13647
+ return { isStarter: isValidStarter, success: false };
13648
+ }
13649
+ }
13783
13650
  /**
13784
13651
  * Create project directory structure based on README.md structure
13785
13652
  */
13786
13653
  async createProjectStructure() {
13787
13654
  this.spinner.start("Creating project structure...");
13788
13655
  try {
13656
+ const result = await this.downloadTemplate();
13657
+ if (result.isStarter) {
13658
+ if (result.success !== true) {
13659
+ throw new Error("Template download/copy failed");
13660
+ }
13661
+ if (result.success === true) {
13662
+ return;
13663
+ }
13664
+ }
13789
13665
  await import_promises.default.mkdir(this.targetDir, { recursive: true });
13790
13666
  const dirs = [
13791
13667
  "src",
@@ -13804,7 +13680,7 @@ var ProjectGenerator = class {
13804
13680
  "src/features/example/presentation/utils"
13805
13681
  );
13806
13682
  }
13807
- if (this.config.database.provider !== "none" && this.config.database.provider !== "mongodb") {
13683
+ if (this.config.database.provider !== "none") {
13808
13684
  dirs.push("prisma");
13809
13685
  }
13810
13686
  for (const dir of dirs) {
@@ -13824,45 +13700,101 @@ var ProjectGenerator = class {
13824
13700
  this.spinner.start("Generating project files...");
13825
13701
  try {
13826
13702
  const featureDeps = getFeatureDependencies(
13827
- Object.entries(this.config.features).filter(([_, enabled]) => enabled).map(([key, _]) => key)
13703
+ Object.entries(this.config.features).filter(([_, enabled]) => enabled).map(([key]) => key)
13828
13704
  );
13829
13705
  const dbConfig = DATABASE_CONFIGS[this.config.database.provider];
13830
- const allDependencies = [
13831
- "@igniter-js/core@*",
13832
- "zod@^3.22.0",
13833
- ...featureDeps.dependencies.map((dep) => `${dep.name}@${dep.version}`),
13834
- ...dbConfig.dependencies.map((dep) => `${dep.name}@${dep.version}`)
13706
+ const coreDependencies = [
13707
+ { name: "@igniter-js/core", version: "*" },
13708
+ { name: "zod", version: "3.25.48" }
13835
13709
  ];
13836
- const allDevDependencies = [
13837
- "typescript@^5.6.3",
13838
- "@types/node@^22.9.0",
13839
- "tsx@^4.7.0",
13840
- ...featureDeps.devDependencies.map((dep) => `${dep.name}@${dep.version}`),
13841
- ...(dbConfig.devDependencies || []).map((dep) => `${dep.name}@${dep.version}`)
13710
+ const coreDevDependencies = [
13711
+ { name: "typescript", version: "^5.6.3" },
13712
+ { name: "@types/node", version: "^22.9.0" },
13713
+ { name: "tsx", version: "^4.7.0" }
13842
13714
  ];
13843
- const templates = generateAllTemplates(this.config, allDependencies, allDevDependencies);
13715
+ if (this.isExistingProject) {
13716
+ const deps = [...coreDependencies, ...featureDeps.dependencies, ...dbConfig.dependencies];
13717
+ const devDeps = [...coreDevDependencies, ...featureDeps.devDependencies || [], ...dbConfig.devDependencies || []];
13718
+ await this.updatePackageJson(deps, devDeps);
13719
+ }
13720
+ const allTemplates = generateAllTemplates(
13721
+ this.config,
13722
+ this.isExistingProject,
13723
+ [...coreDependencies, ...featureDeps.dependencies, ...dbConfig.dependencies].map(
13724
+ (d) => `${d.name}@${d.version}`
13725
+ ),
13726
+ [
13727
+ ...coreDevDependencies,
13728
+ ...featureDeps.devDependencies || [],
13729
+ ...dbConfig.devDependencies || []
13730
+ ].map((d) => `${d.name}@${d.version}`)
13731
+ );
13844
13732
  let writtenCount = 0;
13845
- for (const template of templates) {
13733
+ for (const template of allTemplates) {
13846
13734
  const filePath = import_path2.default.join(this.targetDir, template.path);
13847
- const dir = import_path2.default.dirname(filePath);
13848
- await import_promises.default.mkdir(dir, { recursive: true });
13849
- await import_promises.default.writeFile(filePath, template.content, "utf8");
13850
- if (template.executable) {
13851
- await import_promises.default.chmod(filePath, "755");
13735
+ if (this.isExistingProject) {
13736
+ if (template.path === "package.json") continue;
13737
+ const fileExists = await import_promises.default.stat(filePath).catch(() => null);
13738
+ if (fileExists && [".gitignore", "README.md", "tsconfig.json"].includes(import_path2.default.basename(filePath))) {
13739
+ this.spinner.info(import_chalk3.default.dim(`Skipping existing file: ${template.path}`));
13740
+ continue;
13741
+ }
13852
13742
  }
13853
13743
  writtenCount++;
13854
- this.spinner.text = `Generating files... (${writtenCount}/${templates.length})`;
13744
+ this.spinner.text = `Generating files... (${writtenCount}/${allTemplates.length})`;
13855
13745
  }
13856
- if (this.config.database.provider !== "none" && this.config.database.provider !== "mongodb") {
13746
+ if (this.config.database.provider !== "none") {
13857
13747
  await this.generatePrismaSchema();
13858
13748
  }
13859
- this.spinner.succeed(import_chalk3.default.green(`\u2713 Generated ${templates.length} files`));
13860
- logger2.info("Project files generated successfully", { fileCount: templates.length });
13749
+ this.spinner.succeed(import_chalk3.default.green(`\u2713 Generated ${writtenCount} files`));
13750
+ logger2.info("Project files generated successfully", { fileCount: writtenCount });
13861
13751
  } catch (error) {
13862
13752
  this.spinner.fail(import_chalk3.default.red("\u2717 Failed to generate files"));
13863
13753
  throw error;
13864
13754
  }
13865
13755
  }
13756
+ /**
13757
+ * Updates an existing package.json file with new dependencies and scripts.
13758
+ */
13759
+ async updatePackageJson(dependencies, devDependencies) {
13760
+ const pkgJsonPath = import_path2.default.join(this.targetDir, "package.json");
13761
+ try {
13762
+ const pkgJsonContent = await import_promises.default.readFile(pkgJsonPath, "utf-8");
13763
+ const pkgJson = JSON.parse(pkgJsonContent);
13764
+ this.spinner.text = "Updating package.json...";
13765
+ pkgJson.dependencies = pkgJson.dependencies || {};
13766
+ for (const dep of dependencies) {
13767
+ if (!pkgJson.dependencies[dep.name]) {
13768
+ pkgJson.dependencies[dep.name] = dep.version;
13769
+ }
13770
+ }
13771
+ pkgJson.devDependencies = pkgJson.devDependencies || {};
13772
+ for (const dep of devDependencies) {
13773
+ if (!pkgJson.devDependencies[dep.name]) {
13774
+ pkgJson.devDependencies[dep.name] = dep.version;
13775
+ }
13776
+ }
13777
+ if (this.config.database.provider !== "none") {
13778
+ pkgJson.scripts = pkgJson.scripts || {};
13779
+ const newScripts = {
13780
+ "db:generate": "prisma generate",
13781
+ "db:push": "prisma db push",
13782
+ "db:studio": "prisma studio",
13783
+ "db:migrate": "prisma migrate dev"
13784
+ };
13785
+ for (const [name, command] of Object.entries(newScripts)) {
13786
+ if (!pkgJson.scripts[name]) {
13787
+ pkgJson.scripts[name] = command;
13788
+ }
13789
+ }
13790
+ }
13791
+ await import_promises.default.writeFile(pkgJsonPath, JSON.stringify(pkgJson, null, 2));
13792
+ this.spinner.succeed(import_chalk3.default.green("\u2713 package.json updated"));
13793
+ } catch (error) {
13794
+ this.spinner.warn(import_chalk3.default.yellow("Could not update package.json. Please add dependencies manually."));
13795
+ logger2.warn("Failed to update package.json", { error });
13796
+ }
13797
+ }
13866
13798
  /**
13867
13799
  * Generate Prisma schema file
13868
13800
  */
@@ -13881,32 +13813,9 @@ datasource db {
13881
13813
  provider = "${providerName}"
13882
13814
  url = ${datasourceUrl}
13883
13815
  }
13884
-
13885
- // Example model - customize as needed
13886
- model User {
13887
- id String @id @default(cuid())
13888
- email String @unique
13889
- name String?
13890
- createdAt DateTime @default(now())
13891
- updatedAt DateTime @updatedAt
13892
-
13893
- @@map("users")
13894
- }
13895
-
13896
- model Post {
13897
- id String @id @default(cuid())
13898
- title String
13899
- content String?
13900
- published Boolean @default(false)
13901
- authorId String
13902
- author User @relation(fields: [authorId], references: [id])
13903
- createdAt DateTime @default(now())
13904
- updatedAt DateTime @updatedAt
13905
-
13906
- @@map("posts")
13907
- }
13908
13816
  `;
13909
13817
  const schemaPath = import_path2.default.join(this.targetDir, "prisma", "schema.prisma");
13818
+ await import_promises.default.mkdir(import_path2.default.dirname(schemaPath), { recursive: true });
13910
13819
  await import_promises.default.writeFile(schemaPath, schema, "utf8");
13911
13820
  }
13912
13821
  /**
@@ -13969,7 +13878,7 @@ model Post {
13969
13878
  * Run post-setup tasks like Prisma generation
13970
13879
  */
13971
13880
  async runPostSetupTasks() {
13972
- if (this.config.database.provider !== "none" && this.config.database.provider !== "mongodb") {
13881
+ if (this.config.database.provider !== "none") {
13973
13882
  this.spinner.start("Generating Prisma client...");
13974
13883
  try {
13975
13884
  const { command, args } = this.getRunCommand("db:generate");
@@ -14004,17 +13913,23 @@ model Post {
14004
13913
  */
14005
13914
  showSuccessMessage() {
14006
13915
  console.log();
14007
- console.log(import_chalk3.default.green("\u2713 Success! Your Igniter.js project is ready!"));
13916
+ if (this.isExistingProject) {
13917
+ console.log(import_chalk3.default.green("\u2713 Success! Igniter.js has been added to your project!"));
13918
+ } else {
13919
+ console.log(import_chalk3.default.green("\u2713 Success! Your Igniter.js project is ready!"));
13920
+ }
14008
13921
  console.log();
14009
13922
  console.log(import_chalk3.default.bold("Next steps:"));
14010
- console.log(` ${import_chalk3.default.cyan("cd")} ${this.config.projectName}`);
13923
+ if (!this.isExistingProject) {
13924
+ console.log(` ${import_chalk3.default.cyan("cd")} ${this.config.projectName}`);
13925
+ }
14011
13926
  if (!this.config.installDependencies) {
14012
13927
  console.log(` ${import_chalk3.default.cyan(this.config.packageManager)} install`);
14013
13928
  }
14014
13929
  if (this.config.dockerCompose) {
14015
13930
  console.log(` ${import_chalk3.default.cyan("docker-compose")} up -d`);
14016
13931
  }
14017
- if (this.config.database.provider !== "none" && this.config.database.provider !== "mongodb") {
13932
+ if (this.config.database.provider !== "none") {
14018
13933
  console.log(` ${import_chalk3.default.cyan(this.config.packageManager)} run db:push`);
14019
13934
  }
14020
13935
  console.log(` ${import_chalk3.default.cyan(this.config.packageManager)} run dev`);
@@ -14022,15 +13937,19 @@ model Post {
14022
13937
  console.log(import_chalk3.default.bold("Helpful commands:"));
14023
13938
  console.log(` ${import_chalk3.default.dim("Start development:")} ${import_chalk3.default.cyan(`${this.config.packageManager} run dev`)}`);
14024
13939
  console.log(` ${import_chalk3.default.dim("Build for production:")} ${import_chalk3.default.cyan(`${this.config.packageManager} run build`)}`);
14025
- if (this.config.database.provider !== "none" && this.config.database.provider !== "mongodb") {
13940
+ if (this.config.database.provider !== "none") {
14026
13941
  console.log(` ${import_chalk3.default.dim("Database operations:")} ${import_chalk3.default.cyan(`${this.config.packageManager} run db:studio`)}`);
14027
13942
  }
13943
+ if (this.isExistingProject) {
13944
+ console.log();
13945
+ console.log(import_chalk3.default.yellow("Remember to integrate the Igniter router into your existing server setup!"));
13946
+ }
14028
13947
  console.log();
14029
13948
  console.log(import_chalk3.default.dim("Happy coding!"));
14030
13949
  }
14031
13950
  };
14032
- async function generateProject(config, targetDir) {
14033
- const generator = new ProjectGenerator(config, targetDir);
13951
+ async function generateProject(config, targetDir, isExistingProject) {
13952
+ const generator = new ProjectGenerator(config, targetDir, isExistingProject);
14034
13953
  await generator.generate();
14035
13954
  }
14036
13955
 
@@ -14104,6 +14023,7 @@ function showInitHelp() {
14104
14023
  console.log(" \u{1F504} BullMQ Jobs Background task processing");
14105
14024
  console.log(" \u{1F916} MCP Server AI assistant integration");
14106
14025
  console.log(" \u{1F4DD} Enhanced Logging Structured console output");
14026
+ console.log(" \u{1F4CA} Telemetry Tracking requests and errors");
14107
14027
  console.log();
14108
14028
  console.log(import_chalk4.default.bold("Supported Frameworks:"));
14109
14029
  console.log(" \u2022 Next.js \u2022 Express");
@@ -14123,192 +14043,520 @@ function showInitHelp() {
14123
14043
 
14124
14044
  // src/index.ts
14125
14045
  init_spinner();
14046
+
14047
+ // src/adapters/scaffold.ts
14048
+ var fs5 = __toESM(require("fs/promises"));
14049
+ var path6 = __toESM(require("path"));
14050
+ var import_chalk5 = __toESM(require("chalk"));
14051
+ init_logger();
14052
+
14053
+ // src/adapters/scaffold/providers/prisma.ts
14054
+ var import_prisma_ast = require("@mrleebo/prisma-ast");
14055
+ var fs4 = __toESM(require("fs/promises"));
14056
+ var path5 = __toESM(require("path"));
14057
+ init_logger();
14058
+ var logger4 = createChildLogger({ component: "prisma-provider" });
14059
+ function mapPrismaTypeToTsType(prismaType) {
14060
+ const cleanType = prismaType.replace("?", "");
14061
+ switch (cleanType) {
14062
+ case "String":
14063
+ case "Json":
14064
+ case "Bytes":
14065
+ case "Unsupported":
14066
+ return "string";
14067
+ case "BigInt":
14068
+ return "bigint";
14069
+ case "Int":
14070
+ case "Float":
14071
+ case "Decimal":
14072
+ return "number";
14073
+ case "Boolean":
14074
+ return "boolean";
14075
+ case "DateTime":
14076
+ return "Date";
14077
+ default:
14078
+ return "string";
14079
+ }
14080
+ }
14081
+ function hasAttribute(prop, attributeName) {
14082
+ if (prop.type !== "field" || !Array.isArray(prop.attributes)) {
14083
+ return false;
14084
+ }
14085
+ return prop.attributes.some((attr) => attr.name === attributeName);
14086
+ }
14087
+ var PrismaProvider = class {
14088
+ constructor(customPath) {
14089
+ this.schemaPath = customPath || path5.join(process.cwd(), "prisma", "schema.prisma");
14090
+ logger4.debug(`Prisma schema path set to: ${this.schemaPath}`);
14091
+ }
14092
+ /**
14093
+ * Reads and parses the Prisma schema file to extract the details of a specific model.
14094
+ *
14095
+ * @param modelName - The name of the model to retrieve (e.g., 'User').
14096
+ * @returns A promise that resolves to the standardized `ModelSchema` or null if not found.
14097
+ */
14098
+ async getModel(modelName) {
14099
+ try {
14100
+ const schemaContent = await fs4.readFile(this.schemaPath, "utf-8");
14101
+ const ast = (0, import_prisma_ast.getSchema)(schemaContent);
14102
+ const model = ast.list.find(
14103
+ (node) => node.type === "model" && node.name === modelName
14104
+ );
14105
+ if (!model) {
14106
+ logger4.warn(`Model '${modelName}' not found in schema.`);
14107
+ return null;
14108
+ }
14109
+ const fields = model.properties.filter((prop) => prop.type === "field").map((prop) => {
14110
+ const isRelation = !/^[A-Z]/.test(prop.fieldType.toString()) && typeof prop.fieldType !== "string";
14111
+ return {
14112
+ name: prop.name,
14113
+ type: mapPrismaTypeToTsType(prop.fieldType),
14114
+ isId: hasAttribute(prop, "id"),
14115
+ isRequired: !(prop.optional || hasAttribute(prop, "default")),
14116
+ isUnique: hasAttribute(prop, "unique"),
14117
+ isRelation,
14118
+ hasDefault: hasAttribute(prop, "default"),
14119
+ isAutoGenerated: this.isFieldAutoGenerated(prop)
14120
+ };
14121
+ });
14122
+ return {
14123
+ name: model.name,
14124
+ fields
14125
+ };
14126
+ } catch (error) {
14127
+ if (error.code === "ENOENT") {
14128
+ logger4.error(`Prisma schema file not found at: ${this.schemaPath}`);
14129
+ } else {
14130
+ logger4.error("Failed to parse Prisma schema", { error });
14131
+ }
14132
+ throw new Error(`Could not process Prisma schema. Make sure '${this.schemaPath}' exists and is valid.`);
14133
+ }
14134
+ }
14135
+ /**
14136
+ * Determines if a field's value is automatically managed by the database.
14137
+ *
14138
+ * @param prop - The schema property to inspect.
14139
+ * @returns True if the field is auto-generated.
14140
+ */
14141
+ isFieldAutoGenerated(prop) {
14142
+ if (hasAttribute(prop, "updatedAt")) {
14143
+ return true;
14144
+ }
14145
+ if (prop.type === "field" && Array.isArray(prop.attributes)) {
14146
+ const defaultAttr = prop.attributes.find((attr) => attr.name === "default");
14147
+ if (defaultAttr && Array.isArray(defaultAttr.args)) {
14148
+ const defaultValue = defaultAttr.args[0]?.value;
14149
+ if (typeof defaultValue === "string" && ["autoincrement()", "now()", "cuid()", "uuid()"].includes(defaultValue)) {
14150
+ return true;
14151
+ }
14152
+ }
14153
+ }
14154
+ return false;
14155
+ }
14156
+ };
14157
+
14158
+ // src/adapters/scaffold.ts
14159
+ var logger5 = createChildLogger({ component: "scaffold" });
14160
+ function toPascalCase(str) {
14161
+ return str.replace(/(^\w|-\w)/g, (g) => g.replace(/-/, "").toUpperCase());
14162
+ }
14163
+ function toCamelCase(str) {
14164
+ const pascal = toPascalCase(str);
14165
+ return pascal.charAt(0).toLowerCase() + pascal.slice(1);
14166
+ }
14167
+ async function writeFile2(filePath, content) {
14168
+ const dir = path6.dirname(filePath);
14169
+ await fs5.mkdir(dir, { recursive: true });
14170
+ await fs5.writeFile(filePath, content, "utf-8");
14171
+ }
14172
+ function getSchemaProvider(providerName) {
14173
+ if (providerName.toLowerCase() === "prisma") {
14174
+ return new PrismaProvider();
14175
+ }
14176
+ throw new Error(`Unsupported schema provider: ${providerName}`);
14177
+ }
14178
+ function generateCrudInterfacesTemplate(model, featureName) {
14179
+ const modelNamePascal = toPascalCase(model.name);
14180
+ const zodFields = model.fields.filter((field) => !field.isRelation).map((field) => {
14181
+ let zodType;
14182
+ switch (field.type) {
14183
+ case "string":
14184
+ case "bigint":
14185
+ zodType = "z.string()";
14186
+ break;
14187
+ case "number":
14188
+ zodType = "z.number()";
14189
+ break;
14190
+ case "boolean":
14191
+ zodType = "z.boolean()";
14192
+ break;
14193
+ case "Date":
14194
+ zodType = "z.date()";
14195
+ break;
14196
+ default:
14197
+ zodType = `z.any() // Type '${field.type}' not directly supported`;
14198
+ }
14199
+ if (!field.isRequired) {
14200
+ zodType += ".nullable()";
14201
+ }
14202
+ return ` ${field.name}: ${zodType},`;
14203
+ }).join("\n");
14204
+ const createInputOmissions = model.fields.filter((f) => f.isId || f.isAutoGenerated).map((f) => ` ${f.name}: true,`).join("\n");
14205
+ return `import { z } from 'zod';
14206
+
14207
+ // Generated from your '${model.name}' Prisma model
14208
+ export const ${modelNamePascal}Schema = z.object({
14209
+ ${zodFields}
14210
+ });
14211
+
14212
+ // Schema for creating a new ${model.name}.
14213
+ // Fields managed by the database (id, createdAt, etc.) are omitted.
14214
+ export const Create${modelNamePascal}InputSchema = ${modelNamePascal}Schema.omit({
14215
+ ${createInputOmissions}
14216
+ });
14217
+
14218
+ // Schema for updating a ${model.name}. All fields are optional.
14219
+ export const Update${modelNamePascal}InputSchema = Create${modelNamePascal}InputSchema.partial();
14220
+
14221
+ // Exporting types for convenience
14222
+ export type ${modelNamePascal} = z.infer<typeof ${modelNamePascal}Schema>;
14223
+ export type Create${modelNamePascal}Input = z.infer<typeof Create${modelNamePascal}InputSchema>;
14224
+ export type Update${modelNamePascal}Input = z.infer<typeof Update${modelNamePascal}InputSchema>;
14225
+ `;
14226
+ }
14227
+ function generateCrudProcedureTemplate(model, featureName) {
14228
+ const modelNameCamel = toCamelCase(model.name);
14229
+ const modelNamePascal = toPascalCase(model.name);
14230
+ const idField = model.fields.find((f) => f.isId);
14231
+ if (!idField) throw new Error(`Model ${model.name} has no ID field.`);
14232
+ return `import { igniter } from '@/igniter';
14233
+ import type { Create${modelNamePascal}Input, Update${modelNamePascal}Input } from '../${featureName}.interfaces';
14234
+
14235
+ export const ${modelNameCamel}Procedure = igniter.procedure({
14236
+ name: '${modelNameCamel}',
14237
+ handler: async (_, { context }) => {
14238
+ // This procedure acts as a repository, centralizing database access logic.
14239
+ return {
14240
+ ${modelNameCamel}Repository: {
14241
+ findAll: () => context.database.${modelNameCamel}.findMany(),
14242
+ findById: (id: ${idField.type}) => context.database.${modelNameCamel}.findUnique({ where: { id } }),
14243
+ create: (data: Create${modelNamePascal}Input) => context.database.${modelNameCamel}.create({ data }),
14244
+ update: (id: ${idField.type}, data: Update${modelNamePascal}Input) => context.database.${modelNameCamel}.update({ where: { id }, data }),
14245
+ delete: (id: ${idField.type}) => context.database.${modelNameCamel}.delete({ where: { id } }),
14246
+ }
14247
+ };
14248
+ }
14249
+ });
14250
+ `;
14251
+ }
14252
+ function generateCrudControllerTemplate(model, featureName) {
14253
+ const modelNameCamel = toCamelCase(model.name);
14254
+ const modelNamePascal = toPascalCase(model.name);
14255
+ const idField = model.fields.find((f) => f.isId);
14256
+ if (!idField) throw new Error(`Model ${model.name} has no ID field.`);
14257
+ let idZodType = "z.string()";
14258
+ if (idField.type === "number") idZodType = "z.coerce.number()";
14259
+ return `import { igniter } from '@/igniter';
14260
+ import { z } from 'zod';
14261
+ import { ${modelNameCamel}Procedure } from '../procedures/${featureName}.procedure'
14262
+ import { Create${modelNamePascal}InputSchema, Update${modelNamePascal}InputSchema } from '../${featureName}.interfaces'
14263
+
14264
+ export const ${modelNameCamel}Controller = igniter.controller({
14265
+ name: '${modelNamePascal}',
14266
+ description: 'Endpoints for ${modelNamePascal}s',
14267
+ path: '/${modelNameCamel}s', // e.g., /users
14268
+ actions: {
14269
+ list: igniter.query({
14270
+ name: 'list',
14271
+ description: 'List all ${modelNamePascal}s',
14272
+ path: '/',
14273
+ use: [${modelNameCamel}Procedure()],
14274
+ handler: async ({ context, response }) => {
14275
+ const records = await context.${modelNameCamel}Repository.findAll()
14276
+ return response.success(records)
14277
+ },
14278
+ }),
14279
+
14280
+ getById: igniter.query({
14281
+ name: 'getById',
14282
+ description: 'Get a ${modelNamePascal} by ID',
14283
+ path: '/:id' as const,
14284
+ use: [${modelNameCamel}Procedure()],
14285
+ handler: async ({ request, context, response }) => {
14286
+ const record = await context.${modelNameCamel}Repository.findById(request.params.id)
14287
+ if (!record) {
14288
+ return response.notFound('${modelNamePascal} not found')
14289
+ }
14290
+ return response.success(record)
14291
+ },
14292
+ }),
14293
+
14294
+ create: igniter.mutation({
14295
+ name: 'create',
14296
+ description: 'Create a new ${modelNamePascal}',
14297
+ path: '/',
14298
+ method: 'POST',
14299
+ body: Create${modelNamePascal}InputSchema,
14300
+ use: [${modelNameCamel}Procedure()],
14301
+ handler: async ({ request, context, response }) => {
14302
+ const newRecord = await context.${modelNameCamel}Repository.create(request.body)
14303
+ return response.created(newRecord)
14304
+ },
14305
+ }),
14306
+
14307
+ update: igniter.mutation({
14308
+ name: 'update',
14309
+ description: 'Update a ${modelNamePascal} by ID',
14310
+ path: '/:id' as const,
14311
+ method: 'PUT',
14312
+ body: Update${modelNamePascal}InputSchema,
14313
+ use: [${modelNameCamel}Procedure()],
14314
+ handler: async ({ request, context, response }) => {
14315
+ const updatedRecord = await context.${modelNameCamel}Repository.update(request.params.id, request.body)
14316
+ return response.success(updatedRecord)
14317
+ },
14318
+ }),
14319
+
14320
+ delete: igniter.mutation({
14321
+ name: 'delete',
14322
+ description: 'Delete a ${modelNamePascal} by ID',
14323
+ path: '/:id' as const,
14324
+ method: 'DELETE',
14325
+ use: [${modelNameCamel}Procedure()],
14326
+ handler: async ({ request, context, response }) => {
14327
+ await context.${modelNameCamel}Repository.delete(request.params.id)
14328
+ return response.noContent()
14329
+ },
14330
+ }),
14331
+ },
14332
+ })
14333
+ `;
14334
+ }
14335
+ function generateCrudIndexTemplate(featureName) {
14336
+ const procedureFileName = `${featureName}.procedure`;
14337
+ const controllerFileName = `${featureName}.controller`;
14338
+ const interfacesFileName = `${featureName}.interfaces`;
14339
+ return `export * from './controllers/${controllerFileName}'
14340
+ export * from './procedures/${procedureFileName}'
14341
+ export * from './${interfacesFileName}'
14342
+ `;
14343
+ }
14344
+ function generateEmptyControllerTemplate(featureName) {
14345
+ const controllerName = `${featureName.toLowerCase()}Controller`;
14346
+ return `import { igniter } from '@/igniter'
14347
+ import { z } from 'zod'
14348
+
14349
+ export const ${controllerName} = igniter.controller({
14350
+ name: '${featureName}',
14351
+ path: '/${featureName}',
14352
+ actions: {
14353
+ hello: igniter.query({
14354
+ path: '/hello',
14355
+ handler: async ({ response }) => {
14356
+ return response.success({ message: 'Hello from ${featureName}!' })
14357
+ },
14358
+ }),
14359
+ },
14360
+ })
14361
+ `;
14362
+ }
14363
+ function generateEmptyInterfacesTemplate(featureName) {
14364
+ return `// Zod schemas and TypeScript types for the ${featureName} feature.
14365
+ `;
14366
+ }
14367
+ function generateEmptyIndexTemplate(featureName) {
14368
+ return `export * from './controllers/${featureName}.controller'
14369
+ `;
14370
+ }
14371
+ async function scaffoldEmptyFeature(featureName, featureDir) {
14372
+ const spinner = logger5.spinner(`Creating empty feature '${featureName}'...`);
14373
+ spinner.start();
14374
+ try {
14375
+ await fs5.mkdir(path6.join(featureDir, "controllers"), { recursive: true });
14376
+ await fs5.mkdir(path6.join(featureDir, "procedures"), { recursive: true });
14377
+ await writeFile2(
14378
+ path6.join(featureDir, "controllers", `${featureName}.controller.ts`),
14379
+ generateEmptyControllerTemplate(featureName)
14380
+ );
14381
+ await writeFile2(
14382
+ path6.join(featureDir, `${featureName}.interfaces.ts`),
14383
+ generateEmptyInterfacesTemplate(featureName)
14384
+ );
14385
+ await writeFile2(
14386
+ path6.join(featureDir, "index.ts"),
14387
+ generateEmptyIndexTemplate(featureName)
14388
+ );
14389
+ spinner.success(`Scaffolded empty feature '${featureName}'`);
14390
+ } catch (error) {
14391
+ spinner.error(`Failed to create empty feature '${featureName}'`);
14392
+ throw error;
14393
+ }
14394
+ }
14395
+ async function scaffoldFeatureFromSchema(featureName, schemaString, featureDir) {
14396
+ const spinner = logger5.spinner(`Scaffolding feature '${featureName}' from schema...`);
14397
+ spinner.start();
14398
+ try {
14399
+ const [providerName, modelName] = schemaString.split(":");
14400
+ if (!providerName || !modelName) {
14401
+ throw new Error("Invalid schema format. Expected `provider:ModelName` (e.g., `prisma:User`).");
14402
+ }
14403
+ const provider = getSchemaProvider(providerName);
14404
+ const model = await provider.getModel(modelName);
14405
+ if (!model) {
14406
+ throw new Error(`Model '${modelName}' not found using provider '${providerName}'.`);
14407
+ }
14408
+ spinner.update("Generating files from model schema...");
14409
+ await fs5.mkdir(path6.join(featureDir, "controllers"), { recursive: true });
14410
+ await fs5.mkdir(path6.join(featureDir, "procedures"), { recursive: true });
14411
+ await writeFile2(
14412
+ path6.join(featureDir, `${featureName}.interfaces.ts`),
14413
+ generateCrudInterfacesTemplate(model, featureName)
14414
+ );
14415
+ await writeFile2(
14416
+ path6.join(featureDir, "procedures", `${featureName}.procedure.ts`),
14417
+ generateCrudProcedureTemplate(model, featureName)
14418
+ );
14419
+ await writeFile2(
14420
+ path6.join(featureDir, "controllers", `${featureName}.controller.ts`),
14421
+ generateCrudControllerTemplate(model, featureName)
14422
+ );
14423
+ await writeFile2(
14424
+ path6.join(featureDir, "index.ts"),
14425
+ generateCrudIndexTemplate(featureName)
14426
+ );
14427
+ spinner.success(`Successfully scaffolded feature '${featureName}' from '${modelName}' model.`);
14428
+ console.log(import_chalk5.default.cyan(`
14429
+ \u2705 Next step: Register the '${toCamelCase(featureName)}Controller' in 'src/igniter.router.ts'`));
14430
+ } catch (error) {
14431
+ spinner.error(`Failed to scaffold feature from schema`);
14432
+ throw error;
14433
+ }
14434
+ }
14435
+ async function handleGenerateFeature(featureName, options) {
14436
+ const normalizedName = featureName.toLowerCase();
14437
+ const featureDir = path6.join(process.cwd(), "src", "features", normalizedName);
14438
+ logger5.info(`Scaffolding feature: ${import_chalk5.default.cyan(normalizedName)}`);
14439
+ try {
14440
+ await fs5.access(featureDir);
14441
+ logger5.error(`Feature '${normalizedName}' already exists.`);
14442
+ console.error(import_chalk5.default.red(`\u2717 Feature '${normalizedName}' already exists at ${path6.relative(process.cwd(), featureDir)}`));
14443
+ return;
14444
+ } catch (error) {
14445
+ }
14446
+ if (options.schema) {
14447
+ await scaffoldFeatureFromSchema(normalizedName, options.schema, featureDir);
14448
+ } else {
14449
+ await scaffoldEmptyFeature(normalizedName, featureDir);
14450
+ }
14451
+ }
14452
+ async function handleGenerateController(name, feature) {
14453
+ logger5.warn(`'generate controller' is not yet fully implemented. Use 'generate feature --schema' instead.`);
14454
+ }
14455
+ async function handleGenerateProcedure(name, feature) {
14456
+ logger5.warn(`'generate procedure' is not yet fully implemented. Use 'generate feature --schema' instead.`);
14457
+ }
14458
+
14459
+ // src/index.ts
14126
14460
  var program = new import_commander.Command();
14127
14461
  program.name("igniter").description("CLI for Igniter.js type-safe client generation").version("1.0.0");
14128
14462
  program.command("init").description("Create a new Igniter.js project with interactive setup").argument("[project-name]", "Name of the project directory").option("--force", "Skip confirmation prompts and overwrite existing files").option("--pm, --package-manager <manager>", "Package manager to use (npm, yarn, pnpm, bun)").option("--template <template>", "Use a specific template (coming soon)").option("-f, --framework <framework>", "Target framework (nextjs, vite, etc.)").option("--no-git", "Skip git repository initialization").option("--no-install", "Skip automatic dependency installation").action(async (projectName, options) => {
14129
14463
  const initLogger = createChildLogger({ component: "init-command" });
14130
14464
  try {
14131
- if (options.help) {
14132
- showInitHelp();
14133
- return;
14134
- }
14135
14465
  if (!projectName) {
14136
- console.log("Welcome to Igniter.js!");
14137
- console.log();
14138
- console.log("To create a new project:");
14139
- console.log(" igniter init my-awesome-api");
14140
- console.log();
14141
- console.log("To initialize in current directory:");
14142
- console.log(" igniter init .");
14143
- console.log();
14144
- console.log("For more options:");
14145
- console.log(" igniter init --help");
14466
+ showInitHelp();
14146
14467
  return;
14147
14468
  }
14148
14469
  if (projectName !== ".") {
14149
14470
  const validation2 = validateProjectName(projectName);
14150
14471
  if (!validation2.valid) {
14151
- initLogger.error("Invalid project name", {
14152
- projectName,
14153
- reason: validation2.message
14154
- });
14472
+ initLogger.error("Invalid project name", { projectName, reason: validation2.message });
14155
14473
  console.error(`\u2717 ${validation2.message}`);
14156
14474
  process.exit(1);
14157
14475
  }
14158
14476
  }
14159
- initLogger.info("Starting init command", {
14160
- projectName,
14161
- options
14162
- });
14163
- const targetDir = projectName ? path7.resolve(projectName) : process.cwd();
14477
+ const targetDir = projectName === "." ? process.cwd() : path9.resolve(projectName);
14478
+ const isExistingProject = await fs8.promises.stat(path9.join(targetDir, "package.json")).catch(() => null) !== null;
14164
14479
  if (!options.force) {
14165
14480
  try {
14166
- const stats = await fs6.promises.stat(targetDir);
14481
+ const stats = await fs8.promises.stat(targetDir);
14167
14482
  if (stats.isDirectory()) {
14168
- const files = await fs6.promises.readdir(targetDir);
14169
- const nonEmptyFiles = files.filter(
14170
- (file) => !file.startsWith(".") && !["README.md", "LICENSE", "package.json"].includes(file.toUpperCase())
14171
- );
14172
- if (nonEmptyFiles.length > 0) {
14173
- const shouldOverwrite = await confirmOverwrite(targetDir);
14483
+ const files = await fs8.promises.readdir(targetDir);
14484
+ const nonEmptyFiles = files.filter((file) => !file.startsWith("."));
14485
+ if (nonEmptyFiles.length > 0 && !isExistingProject) {
14486
+ const shouldOverwrite = await confirmOverwrite(`Directory '${projectName}' is not empty. Continue?`);
14174
14487
  if (!shouldOverwrite) {
14175
- console.log("Setup cancelled");
14488
+ console.log("Setup cancelled.");
14176
14489
  process.exit(0);
14177
14490
  }
14178
14491
  }
14179
14492
  }
14180
14493
  } catch (error) {
14494
+ if (error.code !== "ENOENT") {
14495
+ throw error;
14496
+ }
14181
14497
  }
14182
14498
  }
14183
- const config = await runSetupPrompts(projectName);
14499
+ const config = await runSetupPrompts(targetDir, isExistingProject);
14184
14500
  const validation = validateConfig(config);
14185
14501
  if (!validation.isValid) {
14186
14502
  console.error(`\u2717 ${validation.message}`);
14187
14503
  process.exit(1);
14188
14504
  }
14189
- await generateProject(config, targetDir);
14190
- initLogger.info("Project generated successfully", {
14191
- project: config.projectName,
14192
- targetDir
14193
- });
14505
+ await generateProject(config, targetDir, isExistingProject);
14194
14506
  } catch (error) {
14195
14507
  initLogger.error("Init command failed", { error });
14196
14508
  console.error("\u2717 Failed to initialize project:", error instanceof Error ? error.message : String(error));
14197
14509
  process.exit(1);
14198
14510
  }
14199
14511
  });
14200
- program.command("dev").description("Start development mode with framework and Igniter (interactive dashboard by default)").option(
14201
- "--framework <type>",
14202
- `Framework type (${getFrameworkList()}, generic)`
14203
- ).option("--output <dir>", "Output directory", "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) => {
14512
+ 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) => {
14204
14513
  const detectedFramework = detectFramework();
14205
14514
  const framework = options.framework ? isFrameworkSupported(options.framework) ? options.framework : "generic" : detectedFramework;
14206
- const cmdLogger = createChildLogger({ command: "dev" });
14207
- logger.group("Igniter.js");
14208
14515
  const useInteractive = options.interactive !== false;
14209
- if (useInteractive) {
14210
- logger.info("Starting interactive development mode", {
14211
- framework,
14212
- output: options.output,
14213
- port: options.port,
14214
- withFramework: !options.noFramework
14215
- });
14216
- const { runInteractiveProcesses: runInteractiveProcesses2 } = await Promise.resolve().then(() => (init_concurrent_processes(), concurrent_processes_exports));
14217
- const processes = [];
14218
- if (!options.noFramework && framework !== "generic") {
14219
- const frameworkCommands = {
14220
- nextjs: "npm run dev --turbo",
14221
- vite: "npm run dev",
14222
- nuxt: "npm run dev",
14223
- sveltekit: "npm run dev",
14224
- remix: "npm run dev",
14225
- astro: "npm run dev",
14226
- express: "npm run dev",
14227
- "tanstack-start": "npm run dev"
14228
- };
14229
- const frameworkCommand = options.cmd || frameworkCommands[framework];
14230
- if (frameworkCommand) {
14231
- processes.push({
14232
- name: framework === "nextjs" ? "Next.js" : framework === "sveltekit" ? "SvelteKit" : framework === "tanstack-start" ? "TanStack Start" : framework.charAt(0).toUpperCase() + framework.slice(1),
14233
- command: frameworkCommand,
14234
- color: framework === "nextjs" ? "green" : framework === "vite" ? "yellow" : framework === "nuxt" ? "cyan" : framework === "sveltekit" ? "magenta" : framework === "remix" ? "red" : framework === "astro" ? "white" : framework === "express" ? "blue" : framework === "tanstack-start" ? "purple" : "gray",
14235
- cwd: process.cwd(),
14236
- env: {
14237
- PORT: options.port.toString(),
14238
- NODE_ENV: "development"
14239
- }
14240
- });
14241
- }
14516
+ logger.info(`Starting ${useInteractive ? "interactive" : "concurrent"} development mode`, { framework });
14517
+ const { runInteractiveProcesses: runInteractiveProcesses2, runConcurrentProcesses: runConcurrentProcesses2 } = await Promise.resolve().then(() => (init_concurrent_processes(), concurrent_processes_exports));
14518
+ const processes = [];
14519
+ if (!options.noFramework && framework !== "generic") {
14520
+ const frameworkCommands = {
14521
+ nextjs: "npm run dev",
14522
+ vite: "npm run dev",
14523
+ nuxt: "npm run dev",
14524
+ sveltekit: "npm run dev",
14525
+ remix: "npm run dev",
14526
+ astro: "npm run dev",
14527
+ express: "npm run dev",
14528
+ "tanstack-start": "npm run dev"
14529
+ };
14530
+ const frameworkCommand = options.cmd || frameworkCommands[framework];
14531
+ if (frameworkCommand) {
14532
+ processes.push({
14533
+ name: framework.charAt(0).toUpperCase() + framework.slice(1),
14534
+ command: frameworkCommand,
14535
+ color: "green",
14536
+ cwd: process.cwd(),
14537
+ env: { PORT: options.port.toString(), NODE_ENV: "development" }
14538
+ });
14242
14539
  }
14243
- processes.push({
14244
- name: "Igniter.js",
14245
- command: `igniter generate --framework ${framework} --output ${options.output}${options.debug ? " --debug" : ""}`,
14246
- color: "blue",
14247
- cwd: process.cwd()
14248
- });
14540
+ }
14541
+ processes.push({
14542
+ name: "Igniter",
14543
+ command: `igniter generate schema --watch --framework ${framework} --output ${options.output}${options.debug ? " --debug" : ""}`,
14544
+ color: "blue",
14545
+ cwd: process.cwd()
14546
+ });
14547
+ if (useInteractive) {
14249
14548
  await runInteractiveProcesses2(processes);
14250
14549
  } else {
14251
- logger.info("Starting concurrent development mode", {
14252
- framework,
14253
- output: options.output,
14254
- port: options.port,
14255
- withFramework: !options.noFramework
14256
- });
14257
- const { runConcurrentProcesses: runConcurrentProcesses2 } = await Promise.resolve().then(() => (init_concurrent_processes(), concurrent_processes_exports));
14258
- const processes = [];
14259
- if (!options.noFramework && framework !== "generic") {
14260
- const frameworkCommands = {
14261
- nextjs: "npm run dev",
14262
- vite: "npm run dev",
14263
- nuxt: "npm run dev",
14264
- sveltekit: "npm run dev",
14265
- remix: "npm run dev",
14266
- astro: "npm run dev",
14267
- express: "npm run dev",
14268
- "tanstack-start": "npm run dev"
14269
- };
14270
- const frameworkCommand = options.cmd || frameworkCommands[framework];
14271
- if (frameworkCommand) {
14272
- processes.push({
14273
- name: framework === "nextjs" ? "Next.js" : framework === "sveltekit" ? "SvelteKit" : framework === "tanstack-start" ? "TanStack Start" : framework.charAt(0).toUpperCase() + framework.slice(1),
14274
- command: frameworkCommand,
14275
- color: framework === "nextjs" ? "green" : framework === "vite" ? "yellow" : framework === "nuxt" ? "cyan" : framework === "sveltekit" ? "magenta" : framework === "remix" ? "red" : framework === "astro" ? "white" : framework === "express" ? "blue" : framework === "tanstack-start" ? "purple" : "gray",
14276
- cwd: process.cwd(),
14277
- env: {
14278
- PORT: options.port.toString(),
14279
- NODE_ENV: "development"
14280
- }
14281
- });
14282
- }
14283
- }
14284
- processes.push({
14285
- name: "Igniter.js",
14286
- command: `igniter generate --framework ${framework} --output ${options.output}${options.debug ? " --debug" : ""}`,
14287
- color: "blue",
14288
- cwd: process.cwd()
14289
- });
14290
- await runConcurrentProcesses2({
14291
- processes,
14292
- killOthers: true,
14293
- prefixFormat: "name",
14294
- prefixColors: true,
14295
- prefixLength: 10
14296
- });
14550
+ await runConcurrentProcesses2({ processes, killOthers: true });
14297
14551
  }
14298
14552
  });
14299
- program.command("generate").description("Generate client once (useful for CI/CD)").option(
14300
- "--framework <type>",
14301
- `Framework type (${getFrameworkList()}, generic)`
14302
- ).option("--output <dir>", "Output directory", "lib").option("--debug", "Enable debug mode").action(async (options) => {
14553
+ var generate = program.command("generate").description("Scaffold new features or generate client schema");
14554
+ 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) => {
14303
14555
  const startTime = performance.now();
14304
14556
  const detectedFramework = detectFramework();
14305
14557
  const framework = options.framework ? isFrameworkSupported(options.framework) ? options.framework : "generic" : detectedFramework;
14306
- const cmdLogger = createChildLogger({ command: "generate" });
14307
14558
  logger.group("Igniter.js CLI");
14308
- logger.info("Starting client generation", {
14309
- framework,
14310
- output: options.output
14311
- });
14559
+ logger.info(`Starting client schema ${options.watch ? "watching" : "generation"}`, { framework, output: options.output });
14312
14560
  const watcherSpinner = createDetachedSpinner("Loading generator...");
14313
14561
  watcherSpinner.start();
14314
14562
  const { IgniterWatcher: IgniterWatcher2 } = await Promise.resolve().then(() => (init_watcher(), watcher_exports));
@@ -14316,99 +14564,27 @@ program.command("generate").description("Generate client once (useful for CI/CD)
14316
14564
  framework,
14317
14565
  outputDir: options.output,
14318
14566
  debug: options.debug,
14319
- controllerPatterns: ["**/*.controller.{ts,js}"],
14320
- extractTypes: true,
14321
- optimizeClientBundle: true,
14322
- hotReload: false
14567
+ controllerPatterns: ["**/*.controller.{ts,js}"]
14323
14568
  });
14324
14569
  watcherSpinner.success("Generator loaded");
14325
- await watcher.generate();
14326
- const duration = ((performance.now() - startTime) / 1e3).toFixed(2);
14327
- const timestamp = (/* @__PURE__ */ new Date()).toISOString();
14328
- logger.separator();
14329
- logger.success("Generation complete");
14330
- logger.info("Summary", {
14331
- output: options.output,
14332
- framework,
14333
- duration: `${duration}s`,
14334
- timestamp
14335
- });
14336
- logger.separator();
14337
- logger.info("Useful links");
14338
- logger.info(
14339
- "Documentation: https://felipebarcelospro.github.io/igniter-js"
14340
- );
14341
- logger.info(
14342
- "Issues: https://github.com/felipebarcelospro/igniter-js/issues"
14343
- );
14344
- logger.info(
14345
- "Contributing: https://github.com/felipebarcelospro/igniter-js/blob/main/CONTRIBUTING.md"
14346
- );
14347
- logger.groupEnd();
14348
- logger.separator();
14349
- process.exit(0);
14350
- });
14351
- program.command("server").description("Start framework dev server only (no Igniter file watching)").option(
14352
- "--framework <type>",
14353
- `Framework type (${getFrameworkList()}, generic)`
14354
- ).option("--port <number>", "Port for the dev server", "3000").option("--cmd <command>", "Custom command to start dev server").option("--debug", "Enable debug mode").action(async (options) => {
14355
- const detectedFramework = detectFramework();
14356
- const framework = options.framework ? isFrameworkSupported(options.framework) ? options.framework : "generic" : detectedFramework;
14357
- const cmdLogger = createChildLogger({ command: "server" });
14358
- logger.group("Igniter.js CLI");
14359
- logger.info("Server mode", {
14360
- framework,
14361
- port: options.port
14362
- });
14363
- try {
14364
- const devServerProcess = await startDevServer({
14365
- framework,
14366
- command: options.cmd,
14367
- port: parseInt(options.port),
14368
- debug: options.debug
14369
- });
14370
- logger.success("Dev server started");
14371
- logger.info("Press Ctrl+C to stop");
14372
- process.on("SIGINT", () => {
14373
- console.log("\n");
14374
- const shutdownSpinner = createDetachedSpinner("Stopping dev server...");
14375
- shutdownSpinner.start();
14376
- devServerProcess?.kill("SIGTERM");
14377
- setTimeout(() => {
14378
- if (devServerProcess && !devServerProcess.killed) {
14379
- devServerProcess.kill("SIGKILL");
14380
- }
14381
- shutdownSpinner.success("Dev server stopped");
14382
- logger.groupEnd();
14383
- process.exit(0);
14384
- }, 3e3);
14385
- });
14386
- } catch (error) {
14387
- if (error instanceof Error) {
14388
- logger.error("Failed to start dev server", { error: error.message });
14389
- } else {
14390
- logger.error("Failed to start dev server", { error });
14391
- }
14570
+ if (options.watch) {
14571
+ await watcher.start();
14572
+ } else {
14573
+ await watcher.generate();
14574
+ const duration = ((performance.now() - startTime) / 1e3).toFixed(2);
14575
+ logger.success(`Generation complete in ${duration}s`);
14392
14576
  logger.groupEnd();
14393
- process.exit(1);
14577
+ process.exit(0);
14394
14578
  }
14395
14579
  });
14396
- program.on("--help", () => {
14397
- console.log();
14398
- console.log("\u{1F525} Igniter.js - Type-safe API development");
14399
- console.log();
14400
- console.log("Examples:");
14401
- console.log(" igniter init my-api Create new project with setup wizard");
14402
- console.log(" igniter init . Initialize current directory");
14403
- console.log(" igniter dev --interactive Start development with interactive dashboard");
14404
- console.log(" igniter dev --framework nextjs Start with specific framework");
14405
- console.log(" igniter generate --watch Generate and watch for changes");
14406
- console.log();
14407
- console.log("For more help with a specific command:");
14408
- console.log(" igniter init --help");
14409
- console.log(" igniter dev --help");
14410
- console.log(" igniter generate --help");
14411
- console.log();
14580
+ 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) => {
14581
+ await handleGenerateFeature(name, options);
14582
+ });
14583
+ generate.command("controller").description("Scaffold a new controller within a feature").argument("<name>", "The name of the controller (e.g., 'profile')").option("-f, --feature <feature>", "The parent feature name", "").action(async (name, options) => {
14584
+ await handleGenerateController(name, options.feature);
14585
+ });
14586
+ generate.command("procedure").description("Scaffold a new procedure within a feature").argument("<name>", "The name of the procedure (e.g., 'auth', 'logging')").option("-f, --feature <feature>", "The parent feature name", "").action(async (name, options) => {
14587
+ await handleGenerateProcedure(name, options.feature);
14412
14588
  });
14413
14589
  program.parse();
14414
14590
  function validateConfig(config) {