@igniter-js/cli 0.2.0 → 0.2.2

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