@igniter-js/cli 0.4.92 → 0.4.95

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
@@ -7,7 +7,7 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
7
7
  });
8
8
 
9
9
  // src/index.ts
10
- import { Command as Command9 } from "commander";
10
+ import { Command as Command10 } from "commander";
11
11
 
12
12
  // src/commands/init/index.ts
13
13
  import { Command } from "commander";
@@ -123,11 +123,11 @@ function registerHandlebarsHelpers() {
123
123
  handlebars.registerHelper("capitalizeSlug", function(slug) {
124
124
  return slug.replace(/-/g, " ").replace(/\b\w/g, (char) => char.toUpperCase());
125
125
  });
126
- handlebars.registerHelper("get", function(obj, path24) {
127
- if (!path24 || typeof path24 !== "string") {
126
+ handlebars.registerHelper("get", function(obj, path25) {
127
+ if (!path25 || typeof path25 !== "string") {
128
128
  return void 0;
129
129
  }
130
- const pathArray = path24.split(".");
130
+ const pathArray = path25.split(".");
131
131
  let current = obj;
132
132
  for (let i = 0; i < pathArray.length; i++) {
133
133
  if (current === null || current === void 0) {
@@ -148,7 +148,7 @@ function registerHandlebarsHelpers() {
148
148
  });
149
149
  handlebars.registerHelper("filterPlugins", function(plugins = []) {
150
150
  if (!Array.isArray(plugins)) return [];
151
- return plugins.filter((p13) => p13 !== "next-cookies");
151
+ return plugins.filter((p14) => p14 !== "next-cookies");
152
152
  });
153
153
  handlebars.registerHelper(
154
154
  "generatePluginImports",
@@ -162,7 +162,7 @@ function registerHandlebarsHelpers() {
162
162
  if (str === "open-api") return "openAPI";
163
163
  return str.replace(/-([a-z])/g, (g) => g[1].toUpperCase());
164
164
  };
165
- const regularPlugins = plugins.filter((p13) => p13 !== "next-cookies").map(camelCase);
165
+ const regularPlugins = plugins.filter((p14) => p14 !== "next-cookies").map(camelCase);
166
166
  const hasNextCookies = plugins.includes("next-cookies");
167
167
  const importStatements = [];
168
168
  if (regularPlugins.length > 0) {
@@ -334,9 +334,9 @@ var BaseStarter = class {
334
334
  }
335
335
  const outputPath = join2(projectDir, template.outputPath);
336
336
  const outputDir = join2(outputPath, "..");
337
- await import("fs").then((fs8) => {
338
- if (!fs8.existsSync(outputDir)) {
339
- fs8.mkdirSync(outputDir, { recursive: true });
337
+ await import("fs").then((fs9) => {
338
+ if (!fs9.existsSync(outputDir)) {
339
+ fs9.mkdirSync(outputDir, { recursive: true });
340
340
  }
341
341
  });
342
342
  await writeFile(outputPath, renderedContent);
@@ -971,9 +971,9 @@ var BaseAddOn = class {
971
971
  }
972
972
  const outputPath = join3(projectDir, template.outputPath);
973
973
  const outputDir = join3(outputPath, "..");
974
- await import("fs").then((fs8) => {
975
- if (!fs8.existsSync(outputDir)) {
976
- fs8.mkdirSync(outputDir, { recursive: true });
974
+ await import("fs").then((fs9) => {
975
+ if (!fs9.existsSync(outputDir)) {
976
+ fs9.mkdirSync(outputDir, { recursive: true });
977
977
  }
978
978
  });
979
979
  await writeFile2(outputPath, renderedContent);
@@ -1838,9 +1838,9 @@ var addOnRegistry = AddOnRegistry.create().register(new RedisStoreAddOn()).regis
1838
1838
 
1839
1839
  // src/core/file-system.ts
1840
1840
  import * as fs2 from "fs/promises";
1841
- async function isPathEmpty(path24) {
1841
+ async function isPathEmpty(path25) {
1842
1842
  try {
1843
- const files = await fs2.readdir(path24);
1843
+ const files = await fs2.readdir(path25);
1844
1844
  return files.length === 0;
1845
1845
  } catch (error) {
1846
1846
  if (error.code === "ENOENT") {
@@ -2173,14 +2173,14 @@ var ProjectGenerator = class {
2173
2173
  dockerSetup.message(
2174
2174
  `Checking and stopping currently running containers...`
2175
2175
  );
2176
- const fs8 = await import("fs/promises");
2177
- const path24 = await import("path");
2178
- const yaml = await import("js-yaml");
2179
- const composeFilePath = path24.join(this.targetDir, "docker-compose.yml");
2176
+ const fs9 = await import("fs/promises");
2177
+ const path25 = await import("path");
2178
+ const yaml2 = await import("js-yaml");
2179
+ const composeFilePath = path25.join(this.targetDir, "docker-compose.yml");
2180
2180
  let ports = [];
2181
2181
  try {
2182
- const composeContent = await fs8.readFile(composeFilePath, "utf8");
2183
- const doc = yaml.load(composeContent);
2182
+ const composeContent = await fs9.readFile(composeFilePath, "utf8");
2183
+ const doc = yaml2.load(composeContent);
2184
2184
  const services = doc?.services || {};
2185
2185
  for (const svcKey of Object.keys(services)) {
2186
2186
  const service = services[svcKey];
@@ -2287,7 +2287,7 @@ var initCommand = new Command().command("init").description("Create a new Ignite
2287
2287
  ).option("--no-git", "Skip git repository initialization").option("--no-install", "Skip automatic dependency installation").option("--no-docker", "Skip Docker Compose setup").action(handleInitAction);
2288
2288
 
2289
2289
  // src/commands/generate/index.ts
2290
- import { Command as Command7 } from "commander";
2290
+ import { Command as Command8 } from "commander";
2291
2291
 
2292
2292
  // src/commands/generate/feature/index.ts
2293
2293
  import { Command as Command2 } from "commander";
@@ -2900,8 +2900,8 @@ async function handleGenerateFeatureAction(name, options) {
2900
2900
  p7.outro("Nothing to do.");
2901
2901
  process.exit(1);
2902
2902
  }
2903
- const spinner7 = p7.spinner();
2904
- spinner7.start(`Scaffolding feature '${featureSlug}'...`);
2903
+ const spinner8 = p7.spinner();
2904
+ spinner8.start(`Scaffolding feature '${featureSlug}'...`);
2905
2905
  try {
2906
2906
  if (provider && selection) {
2907
2907
  await FeatureWorkspace.ensureStructure(featureDir);
@@ -2914,13 +2914,13 @@ async function handleGenerateFeatureAction(name, options) {
2914
2914
  await FeatureWorkspace.ensureStructure(featureDir);
2915
2915
  await generateEmptyFeature(featureSlug, featureDir, templateEngine);
2916
2916
  }
2917
- spinner7.stop(`Feature '${featureSlug}' created successfully!`);
2917
+ spinner8.stop(`Feature '${featureSlug}' created successfully!`);
2918
2918
  p7.log.success(
2919
2919
  `Scaffolded feature '${featureSlug}'. Remember to register the controller in your router.`
2920
2920
  );
2921
2921
  p7.outro("Feature generation complete!");
2922
2922
  } catch (error) {
2923
- spinner7.stop("Failed to scaffold the feature.");
2923
+ spinner8.stop("Failed to scaffold the feature.");
2924
2924
  const message = error instanceof Error ? error.message : String(error);
2925
2925
  p7.log.error(`Feature generation failed: ${message}`);
2926
2926
  process.exit(1);
@@ -2929,7 +2929,7 @@ async function handleGenerateFeatureAction(name, options) {
2929
2929
  async function resolveSchemaProvider({
2930
2930
  registry,
2931
2931
  options,
2932
- cancel: cancel4
2932
+ cancel: cancel5
2933
2933
  }) {
2934
2934
  if (options.schema) {
2935
2935
  const provider2 = registry.findBySchemaOption(options.schema);
@@ -2962,7 +2962,7 @@ async function resolveSchemaProvider({
2962
2962
  initialValue: "none"
2963
2963
  });
2964
2964
  if (p7.isCancel(choice)) {
2965
- cancel4("Feature generation cancelled.");
2965
+ cancel5("Feature generation cancelled.");
2966
2966
  }
2967
2967
  if (choice === "none") {
2968
2968
  return { provider: null, selection: null };
@@ -3063,13 +3063,13 @@ var OpenAPIGenerator = class _OpenAPIGenerator {
3063
3063
  for (const [controllerKey, controller] of Object.entries(router.controllers)) {
3064
3064
  for (const [actionKey, action] of Object.entries(controller.actions)) {
3065
3065
  const actionName = action.name || actionKey;
3066
- let path24 = `/${controller.path}/${action.path}`;
3067
- path24 = path24.replace(/\/{2,}/g, "/");
3068
- if (path24.length > 1 && path24.endsWith("/")) {
3069
- path24 = path24.slice(0, -1);
3066
+ let path25 = `/${controller.path}/${action.path}`;
3067
+ path25 = path25.replace(/\/{2,}/g, "/");
3068
+ if (path25.length > 1 && path25.endsWith("/")) {
3069
+ path25 = path25.slice(0, -1);
3070
3070
  }
3071
- if (!paths[path24]) {
3072
- paths[path24] = {};
3071
+ if (!paths[path25]) {
3072
+ paths[path25] = {};
3073
3073
  }
3074
3074
  const operation = {
3075
3075
  summary: action.description || actionName,
@@ -3131,7 +3131,7 @@ var OpenAPIGenerator = class _OpenAPIGenerator {
3131
3131
  if (action.isStream) {
3132
3132
  operation.description = (operation.description ? operation.description + "\n\n" : "") + "This endpoint supports Server-Sent Events (SSE) for real-time updates. It functions as a standard GET request initially, then maintains an open connection for streaming data.";
3133
3133
  }
3134
- paths[path24][action.method.toLowerCase()] = operation;
3134
+ paths[path25][action.method.toLowerCase()] = operation;
3135
3135
  }
3136
3136
  }
3137
3137
  return paths;
@@ -3307,10 +3307,10 @@ async function generateDocsWatchMode(routerPath, outputDir) {
3307
3307
  async function handleGenerateDocsAction(options) {
3308
3308
  try {
3309
3309
  p9.intro("Generate OpenAPI documentation");
3310
- const spinner7 = p9.spinner();
3311
- spinner7.start("Generating OpenAPI documentation...");
3310
+ const spinner8 = p9.spinner();
3311
+ spinner8.start("Generating OpenAPI documentation...");
3312
3312
  const result = await generateDocsWatchMode(options.router, options.output);
3313
- spinner7.stop("OpenAPI documentation generated successfully.");
3313
+ spinner8.stop("OpenAPI documentation generated successfully.");
3314
3314
  p9.log.success(
3315
3315
  `Docs generated in ${(result.durationMs / 1e3).toFixed(2)}s (${result.sizeKb.toFixed(1)} KB)`
3316
3316
  );
@@ -3354,10 +3354,10 @@ async function generateSchemaWatchMode(routerPath, outputPath) {
3354
3354
  async function handleGenerateSchemaAction({ router: routerPath, output: outputPath }) {
3355
3355
  try {
3356
3356
  p10.intro("Generate Igniter.js Client Schema");
3357
- const spinner7 = p10.spinner();
3358
- spinner7.start("Generating schema...");
3357
+ const spinner8 = p10.spinner();
3358
+ spinner8.start("Generating schema...");
3359
3359
  const result = await generateSchemaWatchMode(routerPath, outputPath);
3360
- spinner7.stop("Schema generated successfully.");
3360
+ spinner8.stop("Schema generated successfully.");
3361
3361
  p10.log.success(
3362
3362
  `Schema generated in ${(result.durationMs / 1e3).toFixed(2)}s (${result.controllers} controllers, ${result.actions} actions).`
3363
3363
  );
@@ -3432,8 +3432,8 @@ async function handleGenerateControllerAction(name, options) {
3432
3432
  "feature",
3433
3433
  "empty.controller.hbs"
3434
3434
  );
3435
- const spinner7 = p11.spinner();
3436
- spinner7.start(
3435
+ const spinner8 = p11.spinner();
3436
+ spinner8.start(
3437
3437
  `Creating controller '${controllerSlug}' inside feature '${featureSlug}'...`
3438
3438
  );
3439
3439
  try {
@@ -3446,13 +3446,13 @@ async function handleGenerateControllerAction(name, options) {
3446
3446
  },
3447
3447
  controllerPath
3448
3448
  );
3449
- spinner7.stop("Controller created successfully!");
3449
+ spinner8.stop("Controller created successfully!");
3450
3450
  p11.log.success(
3451
3451
  `Created controller '${controllerSlug}' in feature '${featureSlug}'.`
3452
3452
  );
3453
3453
  p11.outro("Controller generation complete!");
3454
3454
  } catch (error) {
3455
- spinner7.stop("Failed to create the controller.");
3455
+ spinner8.stop("Failed to create the controller.");
3456
3456
  const message = error instanceof Error ? error.message : String(error);
3457
3457
  p11.log.error(`Controller generation failed: ${message}`);
3458
3458
  process.exit(1);
@@ -3510,8 +3510,8 @@ async function handleGenerateProcedureAction(name, options) {
3510
3510
  "feature",
3511
3511
  "procedure.hbs"
3512
3512
  );
3513
- const spinner7 = p12.spinner();
3514
- spinner7.start(
3513
+ const spinner8 = p12.spinner();
3514
+ spinner8.start(
3515
3515
  `Creating procedure '${procedureSlug}' inside feature '${featureSlug}'...`
3516
3516
  );
3517
3517
  if (await FeatureWorkspace.fileExists(path22.join(featureDir, "procedures", ".gitkeep"))) {
@@ -3527,13 +3527,13 @@ async function handleGenerateProcedureAction(name, options) {
3527
3527
  },
3528
3528
  procedurePath
3529
3529
  );
3530
- spinner7.stop("Procedure created successfully!");
3530
+ spinner8.stop("Procedure created successfully!");
3531
3531
  p12.log.success(
3532
3532
  `Created procedure '${procedureSlug}' in feature '${featureSlug}'.`
3533
3533
  );
3534
3534
  p12.outro("Procedure generation complete!");
3535
3535
  } catch (error) {
3536
- spinner7.stop("Failed to create the procedure.");
3536
+ spinner8.stop("Failed to create the procedure.");
3537
3537
  const message = error instanceof Error ? error.message : String(error);
3538
3538
  p12.log.error(`Procedure generation failed: ${message}`);
3539
3539
  process.exit(1);
@@ -3543,15 +3543,699 @@ async function handleGenerateProcedureAction(name, options) {
3543
3543
  // src/commands/generate/procedure/index.ts
3544
3544
  var procedureCommand = new Command6().command("procedure").description("Scaffold a new procedure within a feature").argument("[name]", "Name of the procedure (e.g., 'profile')").option("-f, --feature <feature>", "Target feature name").action(handleGenerateProcedureAction);
3545
3545
 
3546
+ // src/commands/generate/caller/index.ts
3547
+ import { Command as Command7 } from "commander";
3548
+
3549
+ // src/commands/generate/caller/action.ts
3550
+ import { promises as fs7 } from "fs";
3551
+ import path23 from "path";
3552
+ import * as p13 from "@clack/prompts";
3553
+ import yaml from "js-yaml";
3554
+ import SwaggerParser from "@apidevtools/swagger-parser";
3555
+ var SUPPORTED_METHODS = ["get", "post", "put", "patch", "delete", "head"];
3556
+ async function handleGenerateCallerAction(options) {
3557
+ p13.intro("Generate Igniter Caller");
3558
+ try {
3559
+ const callerName = await resolveCallerName(options.name);
3560
+ const source = await resolveSource(options);
3561
+ const spinner8 = p13.spinner();
3562
+ spinner8.start("Loading OpenAPI spec...");
3563
+ const parsedDoc = await loadOpenApiDocument(source);
3564
+ const host = deriveHostname(source, parsedDoc) || "api";
3565
+ const outputDir = path23.resolve(
3566
+ process.cwd(),
3567
+ options.output ?? path23.join("src", "callers", host)
3568
+ );
3569
+ const prefix = Casing.toPascalCase(callerName);
3570
+ const converter = new SchemaConverter(prefix, parsedDoc.components);
3571
+ const componentsCode = converter.renderComponentSchemas();
3572
+ const schemaConstName = `${Casing.toCamelCase(callerName)}CallerSchemas`;
3573
+ const schemaTypeName = `${prefix}CallerSchemas`;
3574
+ const { builderCode, typeAliases } = buildSchemaBuilder(
3575
+ parsedDoc,
3576
+ converter,
3577
+ schemaConstName
3578
+ );
3579
+ const schemaFileContents = [
3580
+ "/* eslint-disable */",
3581
+ "/* prettier-ignore */",
3582
+ "/**",
3583
+ " * @generated by @igniter-js/cli",
3584
+ " * This file was automatically created from your OpenAPI spec.",
3585
+ " * Do not edit manually; regenerate via `igniter generate caller`.",
3586
+ " */",
3587
+ 'import { z } from "zod";',
3588
+ 'import { IgniterCallerSchema } from "@igniter-js/caller";',
3589
+ componentsCode,
3590
+ builderCode,
3591
+ `export type ${schemaTypeName} = typeof ${schemaConstName};`,
3592
+ typeAliases,
3593
+ ""
3594
+ ].filter(Boolean).join("\n");
3595
+ const baseUrl = inferBaseUrl(source, parsedDoc);
3596
+ const callerVar = `${Casing.toCamelCase(callerName)}Caller`;
3597
+ const callerFileContents = [
3598
+ "/**",
3599
+ ` * Preconfigured IgniterCaller for ${prefix}.`,
3600
+ " * Regenerate via `igniter generate caller` when the OpenAPI spec changes.",
3601
+ " */",
3602
+ 'import { IgniterCaller } from "@igniter-js/caller";',
3603
+ `import { ${schemaConstName} } from "./schema";`,
3604
+ "",
3605
+ "export const " + callerVar + " = IgniterCaller.create()",
3606
+ baseUrl ? ` .withBaseUrl("${baseUrl}")` : "",
3607
+ " .withSchemas(" + schemaConstName + ', { mode: "strict" })',
3608
+ " .build();",
3609
+ ""
3610
+ ].filter(Boolean).join("\n");
3611
+ await fs7.mkdir(outputDir, { recursive: true });
3612
+ await fs7.writeFile(path23.join(outputDir, "schema.ts"), schemaFileContents, "utf8");
3613
+ await fs7.writeFile(path23.join(outputDir, "index.ts"), callerFileContents, "utf8");
3614
+ spinner8.stop("Caller generated successfully.");
3615
+ p13.log.success(
3616
+ `Schemas and caller created at ${path23.relative(process.cwd(), outputDir)}`
3617
+ );
3618
+ p13.log.info(`Exported caller: ${callerVar} (schemas: ${schemaConstName})`);
3619
+ } catch (error) {
3620
+ const message = error instanceof Error ? error.message : String(error);
3621
+ p13.log.error(message);
3622
+ process.exit(1);
3623
+ }
3624
+ }
3625
+ async function resolveCallerName(name) {
3626
+ if (name) {
3627
+ return name.trim();
3628
+ }
3629
+ const answer = await p13.text({
3630
+ message: "Caller name (e.g., facebook, billing)",
3631
+ validate: (value) => value && value.trim().length > 0 ? void 0 : "Name is required"
3632
+ });
3633
+ if (p13.isCancel(answer)) {
3634
+ p13.cancel("Caller generation cancelled.");
3635
+ process.exit(0);
3636
+ }
3637
+ return String(answer);
3638
+ }
3639
+ async function resolveSource(options) {
3640
+ if (options.url && options.path) {
3641
+ throw new Error("Use either --url or --path, not both.");
3642
+ }
3643
+ if (options.url) {
3644
+ return { type: "url", url: options.url };
3645
+ }
3646
+ if (options.path) {
3647
+ return { type: "path", path: options.path };
3648
+ }
3649
+ const sourceChoice = await p13.select({
3650
+ message: "Where is your OpenAPI spec?",
3651
+ options: [
3652
+ { label: "Remote URL", value: "url" },
3653
+ { label: "Local file path", value: "path" }
3654
+ ],
3655
+ initialValue: "url"
3656
+ });
3657
+ if (p13.isCancel(sourceChoice)) {
3658
+ p13.cancel("Caller generation cancelled.");
3659
+ process.exit(0);
3660
+ }
3661
+ if (sourceChoice === "url") {
3662
+ const url = await p13.text({
3663
+ message: "OpenAPI URL",
3664
+ placeholder: "https://api.example.com/openapi.json",
3665
+ validate: (value) => !value ? "URL is required" : void 0
3666
+ });
3667
+ if (p13.isCancel(url)) {
3668
+ p13.cancel("Caller generation cancelled.");
3669
+ process.exit(0);
3670
+ }
3671
+ return { type: "url", url: String(url) };
3672
+ }
3673
+ const filePath = await p13.text({
3674
+ message: "Path to OpenAPI file",
3675
+ placeholder: "./openapi.json",
3676
+ validate: (value) => !value ? "Path is required" : void 0
3677
+ });
3678
+ if (p13.isCancel(filePath)) {
3679
+ p13.cancel("Caller generation cancelled.");
3680
+ process.exit(0);
3681
+ }
3682
+ return { type: "path", path: String(filePath) };
3683
+ }
3684
+ async function loadOpenApiDocument(source) {
3685
+ const raw = await loadRawContent(source);
3686
+ const parsed = parseOpenApi(raw);
3687
+ const version = parsed.openapi ?? "";
3688
+ if (typeof version !== "string" || !version.startsWith("3.")) {
3689
+ throw new Error("Only OpenAPI 3.x documents are supported.");
3690
+ }
3691
+ const bundled = await SwaggerParser.bundle(parsed);
3692
+ return bundled;
3693
+ }
3694
+ async function loadRawContent(source) {
3695
+ if (source.type === "url") {
3696
+ const response = await fetch(source.url);
3697
+ if (!response.ok) {
3698
+ throw new Error(
3699
+ `Failed to fetch OpenAPI document: ${response.status} ${response.statusText}`
3700
+ );
3701
+ }
3702
+ return await response.text();
3703
+ }
3704
+ const filePath = path23.resolve(process.cwd(), source.path);
3705
+ return await fs7.readFile(filePath, "utf8");
3706
+ }
3707
+ function parseOpenApi(content) {
3708
+ try {
3709
+ return JSON.parse(content);
3710
+ } catch {
3711
+ const parsed = yaml.load(content);
3712
+ if (!parsed || typeof parsed !== "object") {
3713
+ throw new Error("Could not parse OpenAPI document (JSON/YAML).");
3714
+ }
3715
+ return parsed;
3716
+ }
3717
+ }
3718
+ function deriveHostname(source, doc) {
3719
+ if (source.type === "url") {
3720
+ try {
3721
+ const url = new URL(source.url);
3722
+ return url.hostname.replace(/[^a-zA-Z0-9.-]/g, "") || null;
3723
+ } catch {
3724
+ return null;
3725
+ }
3726
+ }
3727
+ const serverUrl = doc.servers?.[0]?.url;
3728
+ if (serverUrl) {
3729
+ try {
3730
+ const url = new URL(serverUrl);
3731
+ return url.hostname.replace(/[^a-zA-Z0-9.-]/g, "") || null;
3732
+ } catch {
3733
+ return null;
3734
+ }
3735
+ }
3736
+ return null;
3737
+ }
3738
+ function inferBaseUrl(source, doc) {
3739
+ if (source.type === "url") {
3740
+ const serverUrl2 = doc.servers?.[0]?.url;
3741
+ if (serverUrl2) {
3742
+ return serverUrl2;
3743
+ }
3744
+ return new URL(source.url).origin;
3745
+ }
3746
+ const serverUrl = doc.servers?.[0]?.url;
3747
+ return serverUrl ?? null;
3748
+ }
3749
+ var SchemaConverter = class {
3750
+ constructor(prefix, components) {
3751
+ this.generated = /* @__PURE__ */ new Map();
3752
+ this.inProgress = /* @__PURE__ */ new Set();
3753
+ this.prefix = prefix;
3754
+ this.components = components;
3755
+ }
3756
+ renderComponentSchemas() {
3757
+ const entries = [];
3758
+ const names = this.listComponents();
3759
+ for (const name of names) {
3760
+ const identifier = this.componentIdentifier(name);
3761
+ const expression = this.convertWithCache(name);
3762
+ const registryKey = this.registryKey(name);
3763
+ entries.push(
3764
+ [
3765
+ "/**",
3766
+ ` * Schema: ${registryKey}`,
3767
+ ` * Source: openapi#/components/schemas/${name}`,
3768
+ " */",
3769
+ `const ${identifier} = ${expression};`,
3770
+ ""
3771
+ ].join("\n")
3772
+ );
3773
+ }
3774
+ return entries.length ? `${entries.join("\n")}
3775
+ ` : "";
3776
+ }
3777
+ listComponents() {
3778
+ return Object.keys(this.components?.schemas ?? {}).sort();
3779
+ }
3780
+ registryKey(name) {
3781
+ return Casing.toPascalCase(name);
3782
+ }
3783
+ componentName(name) {
3784
+ return this.componentIdentifier(name);
3785
+ }
3786
+ convert(schema) {
3787
+ if ("$ref" in schema) {
3788
+ const refName = this.refName(schema.$ref);
3789
+ const identifier = this.componentIdentifier(refName);
3790
+ this.convertWithCache(refName);
3791
+ return this.wrapLazyIfNeeded(refName, identifier);
3792
+ }
3793
+ const schemaType = Array.isArray(schema.type) ? schema.type[0] : schema.type;
3794
+ const nullable = isNullable(schema);
3795
+ if (schema.oneOf?.length) {
3796
+ const parts = schema.oneOf.map((item) => this.convert(item));
3797
+ return wrapNullable(`z.union([${parts.join(", ")}])`, nullable);
3798
+ }
3799
+ if (schema.anyOf?.length) {
3800
+ const parts = schema.anyOf.map((item) => this.convert(item));
3801
+ return wrapNullable(`z.union([${parts.join(", ")}])`, nullable);
3802
+ }
3803
+ if (schema.allOf?.length) {
3804
+ const [first, ...rest] = schema.allOf;
3805
+ let expr = this.convert(first);
3806
+ for (const part of rest) {
3807
+ expr = `z.intersection(${expr}, ${this.convert(part)})`;
3808
+ }
3809
+ return wrapNullable(expr, nullable);
3810
+ }
3811
+ switch (schemaType) {
3812
+ case "string":
3813
+ if (schema.enum) {
3814
+ const values = schema.enum.map((v) => JSON.stringify(String(v)));
3815
+ return wrapNullable(`z.enum([${values.join(", ")}])`, nullable);
3816
+ }
3817
+ return wrapNullable("z.string()", nullable);
3818
+ case "number":
3819
+ case "integer": {
3820
+ let expr = "z.number()";
3821
+ if (schemaType === "integer") {
3822
+ expr = `${expr}.int()`;
3823
+ }
3824
+ return wrapNullable(expr, nullable);
3825
+ }
3826
+ case "boolean":
3827
+ return wrapNullable("z.boolean()", nullable);
3828
+ case "array": {
3829
+ const arraySchema = schema;
3830
+ const items = arraySchema.items ? this.convert(arraySchema.items) : "z.unknown()";
3831
+ return wrapNullable(`z.array(${items})`, nullable);
3832
+ }
3833
+ case "object": {
3834
+ const properties = schema.properties ?? {};
3835
+ const required = new Set(schema.required ?? []);
3836
+ const entries = Object.entries(properties).map(([key, value]) => {
3837
+ const valueExpr = this.convert(value);
3838
+ return `${JSON.stringify(key)}: ${required.has(key) ? valueExpr : `${valueExpr}.optional()`}`;
3839
+ });
3840
+ let base = `z.object({${entries.length ? ` ${entries.join(", ")} ` : ""}})`;
3841
+ if (schema.additionalProperties) {
3842
+ const apValue = schema.additionalProperties === true ? "z.unknown()" : this.convert(schema.additionalProperties);
3843
+ base = `${base}.catchall(${apValue})`;
3844
+ }
3845
+ return wrapNullable(base, nullable);
3846
+ }
3847
+ default:
3848
+ return "z.unknown()";
3849
+ }
3850
+ }
3851
+ convertWithCache(name) {
3852
+ const identifier = this.componentIdentifier(name);
3853
+ if (this.generated.has(name)) {
3854
+ return this.generated.get(name);
3855
+ }
3856
+ if (this.inProgress.has(name)) {
3857
+ return `z.lazy(() => ${identifier})`;
3858
+ }
3859
+ const schema = this.components?.schemas?.[name];
3860
+ if (!schema) {
3861
+ return "z.unknown()";
3862
+ }
3863
+ this.inProgress.add(name);
3864
+ const expression = this.convert(schema);
3865
+ this.inProgress.delete(name);
3866
+ this.generated.set(name, expression);
3867
+ return expression;
3868
+ }
3869
+ componentIdentifier(name) {
3870
+ return `${this.prefix}${Casing.toPascalCase(name)}Schema`;
3871
+ }
3872
+ refName(ref) {
3873
+ const [, component] = ref.split("#/components/schemas/");
3874
+ if (!component) {
3875
+ return "UnknownSchema";
3876
+ }
3877
+ return component;
3878
+ }
3879
+ wrapLazyIfNeeded(name, identifier) {
3880
+ if (this.inProgress.has(name)) {
3881
+ return `z.lazy(() => ${identifier})`;
3882
+ }
3883
+ return identifier;
3884
+ }
3885
+ };
3886
+ function buildSchemaBuilder(doc, converter, schemaConstName) {
3887
+ const usedNames = /* @__PURE__ */ new Map();
3888
+ const operations = [];
3889
+ const pathBlocks = [];
3890
+ let needsVoid = false;
3891
+ const sortedPaths = Object.keys(doc.paths || {}).sort();
3892
+ for (const rawPath of sortedPaths) {
3893
+ const pathItem = doc.paths?.[rawPath];
3894
+ if (!pathItem) continue;
3895
+ const methodBlocks = [];
3896
+ for (const method of SUPPORTED_METHODS) {
3897
+ const operation = pathItem[method];
3898
+ if (!operation) continue;
3899
+ const normalizedPath2 = normalizePath(rawPath);
3900
+ const requestBody = buildRequestBody(
3901
+ operation.requestBody,
3902
+ converter,
3903
+ doc
3904
+ );
3905
+ const responses = buildResponses(operation.responses, converter, doc);
3906
+ const typeBaseName = ensureUniqueTypeName(
3907
+ buildOperationTypeName(rawPath, method, operation.operationId),
3908
+ usedNames
3909
+ );
3910
+ operations.push({
3911
+ path: normalizedPath2,
3912
+ method: method.toUpperCase(),
3913
+ request: Boolean(requestBody),
3914
+ responses: responses.entries,
3915
+ typeName: typeBaseName
3916
+ });
3917
+ const docBlock = buildMethodDocBlock({
3918
+ method: method.toUpperCase(),
3919
+ path: normalizedPath2,
3920
+ summary: operation.summary ?? operation.description,
3921
+ tags: operation.tags,
3922
+ operationId: operation.operationId,
3923
+ requestLabel: requestBody?.label,
3924
+ responses: responses.entries,
3925
+ source: `openapi#/paths/${encodeJsonPointerPath(rawPath)}/${method}`
3926
+ });
3927
+ const methodCall = buildMethodCall(method, {
3928
+ request: requestBody?.expression,
3929
+ responses: responses.code,
3930
+ doc: operation.summary ?? operation.description,
3931
+ tags: operation.tags,
3932
+ operationId: operation.operationId
3933
+ });
3934
+ methodBlocks.push(indent(`${docBlock}
3935
+ ${methodCall}`, 3));
3936
+ needsVoid = needsVoid || responses.usesVoid;
3937
+ }
3938
+ if (!methodBlocks.length) continue;
3939
+ const normalizedPath = normalizePath(rawPath);
3940
+ const pathLines = [
3941
+ ` .path(${JSON.stringify(normalizedPath)}, (path) =>`,
3942
+ " path",
3943
+ methodBlocks.join("\n"),
3944
+ " )"
3945
+ ];
3946
+ pathBlocks.push(pathLines.join("\n"));
3947
+ }
3948
+ const builderLines = [];
3949
+ builderLines.push(`export const ${schemaConstName} = IgniterCallerSchema.create()`);
3950
+ const componentNames = converter.listComponents();
3951
+ for (const name of componentNames) {
3952
+ const registryKey = converter.registryKey(name);
3953
+ const identifier = converter.componentName(name);
3954
+ builderLines.push(` .schema(${JSON.stringify(registryKey)}, ${identifier})`);
3955
+ }
3956
+ if (needsVoid) {
3957
+ builderLines.push(` .schema("Void", z.void(), { internal: true })`);
3958
+ }
3959
+ if (pathBlocks.length) {
3960
+ builderLines.push(pathBlocks.join("\n"));
3961
+ }
3962
+ builderLines.push(" .build()");
3963
+ return {
3964
+ builderCode: `${builderLines.join("\n")}
3965
+ `,
3966
+ typeAliases: buildTypeAliases(operations, schemaConstName)
3967
+ };
3968
+ }
3969
+ function buildMethodDocBlock(params) {
3970
+ const lines = [];
3971
+ lines.push("/**");
3972
+ lines.push(` * ${params.method} ${params.path}`);
3973
+ if (params.summary) {
3974
+ lines.push(` * Summary: ${params.summary}`);
3975
+ }
3976
+ if (params.tags?.length) {
3977
+ lines.push(` * Tags: ${params.tags.join(", ")}`);
3978
+ }
3979
+ if (params.operationId) {
3980
+ lines.push(` * OperationId: ${params.operationId}`);
3981
+ }
3982
+ if (params.requestLabel) {
3983
+ lines.push(` * Request: ${params.requestLabel}`);
3984
+ }
3985
+ if (params.responses.length) {
3986
+ lines.push(" * Responses:");
3987
+ for (const response of params.responses) {
3988
+ lines.push(` * - ${response.status}: ${response.label}`);
3989
+ }
3990
+ }
3991
+ lines.push(` * Source: ${params.source}`);
3992
+ lines.push(" */");
3993
+ return lines.join("\n");
3994
+ }
3995
+ function buildMethodCall(method, params) {
3996
+ const lines = [];
3997
+ lines.push(`.${method}({`);
3998
+ if (params.request) {
3999
+ lines.push(` request: ${params.request},`);
4000
+ }
4001
+ lines.push(" responses: {");
4002
+ lines.push(params.responses);
4003
+ lines.push(" },");
4004
+ if (params.doc) {
4005
+ lines.push(` doc: ${JSON.stringify(params.doc)},`);
4006
+ }
4007
+ if (params.tags?.length) {
4008
+ lines.push(` tags: ${JSON.stringify(params.tags)},`);
4009
+ }
4010
+ if (params.operationId) {
4011
+ lines.push(` operationId: ${JSON.stringify(params.operationId)},`);
4012
+ }
4013
+ lines.push("})");
4014
+ return lines.join("\n");
4015
+ }
4016
+ function buildResponses(responses, converter, doc) {
4017
+ const builder = new CodeBuilder();
4018
+ const entries = [];
4019
+ let usesVoid = false;
4020
+ if (!responses || !Object.keys(responses).length) {
4021
+ const expression = `path.ref("Void").schema`;
4022
+ builder.line(indent(`200: ${expression},`, 2));
4023
+ entries.push({
4024
+ status: "200",
4025
+ statusLiteral: "200",
4026
+ label: "Void"
4027
+ });
4028
+ return { code: builder.toString(), entries, usesVoid: true };
4029
+ }
4030
+ const sorted = Object.keys(responses).sort();
4031
+ for (const status of sorted) {
4032
+ const responseOrRef = responses[status];
4033
+ if (!responseOrRef) continue;
4034
+ const resolved = resolveResponse(responseOrRef, doc);
4035
+ const schema = resolveSchemaFromResponse(resolved);
4036
+ const resolvedSchema = resolveSchemaExpression(schema, converter);
4037
+ builder.line(indent(`${formatStatusKey(status)}: ${resolvedSchema.expression},`, 2));
4038
+ entries.push({
4039
+ status: String(status),
4040
+ statusLiteral: formatStatusType(status),
4041
+ label: resolvedSchema.label
4042
+ });
4043
+ usesVoid = usesVoid || resolvedSchema.usesVoid;
4044
+ }
4045
+ return { code: builder.toString(), entries, usesVoid };
4046
+ }
4047
+ function buildRequestBody(requestBody, converter, doc) {
4048
+ if (!requestBody) return null;
4049
+ const resolved = "$ref" in requestBody ? resolveRequestBody(requestBody, doc) : requestBody;
4050
+ if (!resolved?.content) return null;
4051
+ const schema = selectJsonSchema(resolved.content);
4052
+ if (!schema) return null;
4053
+ return resolveSchemaExpression(schema, converter);
4054
+ }
4055
+ function resolveSchemaExpression(schema, converter) {
4056
+ if (!schema) {
4057
+ return {
4058
+ expression: `path.ref("Void").schema`,
4059
+ label: "Void",
4060
+ usesVoid: true
4061
+ };
4062
+ }
4063
+ const refName = extractSchemaRef(schema);
4064
+ if (refName) {
4065
+ const registryKey = converter.registryKey(refName);
4066
+ const base = `path.ref(${JSON.stringify(registryKey)}).schema`;
4067
+ return {
4068
+ expression: wrapNullable(base, isNullable(schema)),
4069
+ label: registryKey,
4070
+ usesVoid: false
4071
+ };
4072
+ }
4073
+ const arrayRefName = extractArraySchemaRef(schema);
4074
+ if (arrayRefName) {
4075
+ const registryKey = converter.registryKey(arrayRefName);
4076
+ const base = `path.ref(${JSON.stringify(registryKey)}).array()`;
4077
+ return {
4078
+ expression: wrapNullable(base, isNullable(schema)),
4079
+ label: `${registryKey}[]`,
4080
+ usesVoid: false
4081
+ };
4082
+ }
4083
+ return {
4084
+ expression: converter.convert(schema),
4085
+ label: "InlineSchema",
4086
+ usesVoid: false
4087
+ };
4088
+ }
4089
+ function buildTypeAliases(operations, schemaConstName) {
4090
+ if (!operations.length) return "";
4091
+ const lines = [];
4092
+ lines.push("// Derived types");
4093
+ for (const operation of operations) {
4094
+ const pathLiteral = JSON.stringify(operation.path);
4095
+ const methodLiteral = JSON.stringify(operation.method);
4096
+ if (operation.request) {
4097
+ lines.push(
4098
+ `export type ${operation.typeName}Request = ReturnType<typeof ${schemaConstName}.$Infer.Request<${pathLiteral}, ${methodLiteral}>>;`
4099
+ );
4100
+ }
4101
+ lines.push(
4102
+ `export type ${operation.typeName}Responses = ReturnType<typeof ${schemaConstName}.$Infer.Responses<${pathLiteral}, ${methodLiteral}>>;`
4103
+ );
4104
+ for (const response of operation.responses) {
4105
+ const suffix = formatStatusSuffix(response.status);
4106
+ lines.push(
4107
+ `export type ${operation.typeName}Response${suffix} = ReturnType<typeof ${schemaConstName}.$Infer.Response<${pathLiteral}, ${methodLiteral}, ${response.statusLiteral}>>;`
4108
+ );
4109
+ }
4110
+ lines.push("");
4111
+ }
4112
+ return lines.join("\n");
4113
+ }
4114
+ function buildOperationTypeName(rawPath, method, operationId) {
4115
+ if (operationId) {
4116
+ return Casing.toPascalCase(operationId);
4117
+ }
4118
+ const normalizedPath = normalizePath(rawPath);
4119
+ const segments = normalizedPath.split("/").filter(Boolean).map((segment) => segment.startsWith(":") ? `by-${segment.slice(1)}` : segment);
4120
+ return Casing.toPascalCase([...segments, method].join("-"));
4121
+ }
4122
+ function ensureUniqueTypeName(baseName, used) {
4123
+ const count = used.get(baseName) ?? 0;
4124
+ if (count === 0) {
4125
+ used.set(baseName, 1);
4126
+ return baseName;
4127
+ }
4128
+ const next = `${baseName}${count + 1}`;
4129
+ used.set(baseName, count + 1);
4130
+ return next;
4131
+ }
4132
+ function encodeJsonPointerPath(pathname) {
4133
+ return pathname.replace(/~/g, "~0").replace(/\//g, "~1");
4134
+ }
4135
+ function extractSchemaRef(schema) {
4136
+ if (!schema || typeof schema !== "object") return null;
4137
+ if (!("$ref" in schema)) return null;
4138
+ return extractComponentSchemaName(schema.$ref);
4139
+ }
4140
+ function extractArraySchemaRef(schema) {
4141
+ if (!schema || typeof schema !== "object") return null;
4142
+ if (schema.type !== "array") return null;
4143
+ const items = schema.items;
4144
+ if (!items || typeof items !== "object") return null;
4145
+ if (!("$ref" in items)) return null;
4146
+ return extractComponentSchemaName(items.$ref);
4147
+ }
4148
+ function extractComponentSchemaName(ref) {
4149
+ const match = ref.match(/^#\/components\/schemas\/(.+)$/);
4150
+ return match ? match[1] : null;
4151
+ }
4152
+ function selectJsonSchema(content) {
4153
+ if (content["application/json"]?.schema) {
4154
+ return content["application/json"].schema;
4155
+ }
4156
+ const first = Object.values(content)[0];
4157
+ return first?.schema;
4158
+ }
4159
+ function resolveSchemaFromResponse(response) {
4160
+ if (!response?.content) {
4161
+ return void 0;
4162
+ }
4163
+ return selectJsonSchema(response.content);
4164
+ }
4165
+ function normalizePath(pathname) {
4166
+ return pathname.replace(/{(.*?)}/g, ":$1");
4167
+ }
4168
+ function indent(value, depth) {
4169
+ const pad = " ".repeat(depth);
4170
+ return value.split("\n").map((line) => line ? pad + line : line).join("\n");
4171
+ }
4172
+ function isNullable(schema) {
4173
+ if (!schema || typeof schema !== "object") return false;
4174
+ return Boolean(schema.nullable);
4175
+ }
4176
+ function wrapNullable(expr, nullable) {
4177
+ return nullable ? `${expr}.nullable()` : expr;
4178
+ }
4179
+ function formatStatusKey(status) {
4180
+ const trimmed = String(status).trim();
4181
+ const numeric = Number(trimmed);
4182
+ if (!Number.isNaN(numeric) && `${numeric}` === trimmed) {
4183
+ return trimmed;
4184
+ }
4185
+ return JSON.stringify(trimmed);
4186
+ }
4187
+ function formatStatusType(status) {
4188
+ return formatStatusKey(status);
4189
+ }
4190
+ function formatStatusSuffix(status) {
4191
+ const safe = String(status).replace(/[^a-zA-Z0-9]+/g, "-");
4192
+ const pascal = Casing.toPascalCase(safe);
4193
+ return pascal || "Unknown";
4194
+ }
4195
+ function resolveResponse(response, doc) {
4196
+ if ("$ref" in response) {
4197
+ return getComponent(doc, response.$ref, "responses");
4198
+ }
4199
+ return response;
4200
+ }
4201
+ function resolveRequestBody(requestBody, doc) {
4202
+ return getComponent(doc, requestBody.$ref, "requestBodies");
4203
+ }
4204
+ function getComponent(doc, ref, type) {
4205
+ const match = ref.match(/^#\/components\/([^/]+)\/(.+)$/);
4206
+ if (!match) return void 0;
4207
+ const [, category, name] = match;
4208
+ if (category !== type) return void 0;
4209
+ const component = doc.components?.[type]?.[name];
4210
+ return component;
4211
+ }
4212
+ var CodeBuilder = class {
4213
+ constructor() {
4214
+ this.lines = [];
4215
+ }
4216
+ line(text4) {
4217
+ this.lines.push(text4);
4218
+ }
4219
+ toString() {
4220
+ return this.lines.join("\n");
4221
+ }
4222
+ };
4223
+
4224
+ // src/commands/generate/caller/index.ts
4225
+ var callerCommand = new Command7().command("caller").description("Generate Igniter Caller schemas from an OpenAPI spec").option("--name <name>", "Name used to prefix generated schemas and caller export").option("--url <url>", "URL to the OpenAPI document").option("--path <path>", "Local path to the OpenAPI document").option(
4226
+ "--output <path>",
4227
+ "Output directory (defaults to src/callers/<hostname>)"
4228
+ ).action(handleGenerateCallerAction);
4229
+
3546
4230
  // src/commands/generate/index.ts
3547
- var generateCommand = new Command7().command("generate").description("Scaffold new features or generate client schema").addCommand(featureCommand).addCommand(controllerCommand).addCommand(procedureCommand).addCommand(docsCommand).addCommand(schemaCommand);
4231
+ var generateCommand = new Command8().command("generate").description("Scaffold new features or generate client schema").addCommand(featureCommand).addCommand(controllerCommand).addCommand(procedureCommand).addCommand(docsCommand).addCommand(schemaCommand).addCommand(callerCommand);
3548
4232
 
3549
4233
  // src/commands/dev/index.ts
3550
- import { Command as Command8 } from "commander";
4234
+ import { Command as Command9 } from "commander";
3551
4235
 
3552
4236
  // src/commands/dev/action.ts
3553
- import * as path23 from "path";
3554
- import * as fs7 from "fs";
4237
+ import * as path24 from "path";
4238
+ import * as fs8 from "fs";
3555
4239
  import { spawn as spawn2 } from "child_process";
3556
4240
  import chokidar from "chokidar";
3557
4241
  import { render } from "ink";
@@ -3586,14 +4270,14 @@ function DevUI({ igniterLogs, appLogs, onExit }) {
3586
4270
  };
3587
4271
  const renderLogs = (logs, maxLines = 50) => {
3588
4272
  const visibleLogs = logs.slice(-maxLines);
3589
- return /* @__PURE__ */ React.createElement(Box, { flexDirection: "column" }, visibleLogs.length === 0 ? /* @__PURE__ */ React.createElement(Text, { dimColor: true }, "No logs yet...") : visibleLogs.map((log11, index) => {
4273
+ return /* @__PURE__ */ React.createElement(Box, { flexDirection: "column" }, visibleLogs.length === 0 ? /* @__PURE__ */ React.createElement(Text, { dimColor: true }, "No logs yet...") : visibleLogs.map((log12, index) => {
3590
4274
  const color2 = {
3591
4275
  info: "cyan",
3592
4276
  success: "green",
3593
4277
  error: "red",
3594
4278
  warn: "yellow"
3595
- }[log11.type];
3596
- return /* @__PURE__ */ React.createElement(Text, { key: `${log11.timestamp.getTime()}-${index}`, color: color2 }, formatLogEntry(log11));
4279
+ }[log12.type];
4280
+ return /* @__PURE__ */ React.createElement(Text, { key: `${log12.timestamp.getTime()}-${index}`, color: color2 }, formatLogEntry(log12));
3597
4281
  }));
3598
4282
  };
3599
4283
  return /* @__PURE__ */ React.createElement(Box, { flexDirection: "column" }, /* @__PURE__ */ React.createElement(Box, { marginBottom: 1 }, /* @__PURE__ */ React.createElement(Text, { bold: true }, "Igniter.js Development Mode")), /* @__PURE__ */ React.createElement(Box, { flexDirection: "row" }, /* @__PURE__ */ React.createElement(
@@ -3655,19 +4339,19 @@ async function regenerateSchemaAndDocs(routerPath, outputPath, docsOutputDir, ad
3655
4339
  }
3656
4340
  async function handleDevAction(options) {
3657
4341
  const packageManager = detectPackageManager();
3658
- const routerPath = path23.resolve(process.cwd(), options.router || "src/igniter.router.ts");
4342
+ const routerPath = path24.resolve(process.cwd(), options.router || "src/igniter.router.ts");
3659
4343
  const outputPath = options.output || "src/igniter.schema.ts";
3660
4344
  const docsOutputDir = options.docsOutput || "./src/docs";
3661
4345
  const devCommand2 = options.cmd || getDefaultDevCommand(packageManager);
3662
- if (!fs7.existsSync(routerPath)) {
4346
+ if (!fs8.existsSync(routerPath)) {
3663
4347
  console.error(`Router file not found: ${routerPath}`);
3664
4348
  process.exit(1);
3665
4349
  }
3666
4350
  const igniterLogs = [];
3667
4351
  const appLogs = [];
3668
4352
  let rerenderFn = null;
3669
- const addIgniterLog = (log11) => {
3670
- igniterLogs.push(log11);
4353
+ const addIgniterLog = (log12) => {
4354
+ igniterLogs.push(log12);
3671
4355
  if (igniterLogs.length > 1e3) {
3672
4356
  igniterLogs.shift();
3673
4357
  }
@@ -3675,8 +4359,8 @@ async function handleDevAction(options) {
3675
4359
  rerenderFn();
3676
4360
  }
3677
4361
  };
3678
- const addAppLog = (log11) => {
3679
- appLogs.push(log11);
4362
+ const addAppLog = (log12) => {
4363
+ appLogs.push(log12);
3680
4364
  if (appLogs.length > 1e3) {
3681
4365
  appLogs.shift();
3682
4366
  }
@@ -3706,13 +4390,13 @@ async function handleDevAction(options) {
3706
4390
  });
3707
4391
  process.exit(1);
3708
4392
  }
3709
- const featuresDir = path23.join(process.cwd(), "src", "features");
4393
+ const featuresDir = path24.join(process.cwd(), "src", "features");
3710
4394
  const watchPaths = [
3711
4395
  routerPath,
3712
4396
  // Watch router file directly
3713
4397
  featuresDir
3714
4398
  // Watch features directory recursively
3715
- ].filter((p13) => fs7.existsSync(p13));
4399
+ ].filter((p14) => fs8.existsSync(p14));
3716
4400
  if (watchPaths.length === 0) {
3717
4401
  addIgniterLog({
3718
4402
  type: "warn",
@@ -3723,7 +4407,7 @@ async function handleDevAction(options) {
3723
4407
  }
3724
4408
  addIgniterLog({
3725
4409
  type: "info",
3726
- message: `Watching for changes in: ${watchPaths.map((p13) => path23.relative(process.cwd(), p13)).join(", ")}`,
4410
+ message: `Watching for changes in: ${watchPaths.map((p14) => path24.relative(process.cwd(), p14)).join(", ")}`,
3727
4411
  timestamp: /* @__PURE__ */ new Date()
3728
4412
  });
3729
4413
  let regenerateTimeout = null;
@@ -3743,7 +4427,7 @@ async function handleDevAction(options) {
3743
4427
  ignoreInitial: true
3744
4428
  });
3745
4429
  watcher.on("change", (filePath) => {
3746
- const relativePath = path23.relative(process.cwd(), filePath);
4430
+ const relativePath = path24.relative(process.cwd(), filePath);
3747
4431
  if (regenerateTimeout) {
3748
4432
  clearTimeout(regenerateTimeout);
3749
4433
  }
@@ -3867,10 +4551,10 @@ async function handleDevAction(options) {
3867
4551
  }
3868
4552
 
3869
4553
  // src/commands/dev/index.ts
3870
- var devCommand = new Command8().command("dev").description("Start development mode with automatic schema and docs regeneration").option("--router <path>", "Path to the router file", "src/igniter.router.ts").option("--output <path>", "Output path for the schema file", "src/igniter.schema.ts").option("--docs-output <dir>", "Output directory for the OpenAPI spec", "./src/docs").option("--cmd <command>", "Custom command to start the development server").action(handleDevAction);
4554
+ var devCommand = new Command9().command("dev").description("Start development mode with automatic schema and docs regeneration").option("--router <path>", "Path to the router file", "src/igniter.router.ts").option("--output <path>", "Output path for the schema file", "src/igniter.schema.ts").option("--docs-output <dir>", "Output directory for the OpenAPI spec", "./src/docs").option("--cmd <command>", "Custom command to start the development server").action(handleDevAction);
3871
4555
 
3872
4556
  // src/index.ts
3873
- var program = new Command9();
4557
+ var program = new Command10();
3874
4558
  program.name("igniter").description("The next-generation command-line interface for Igniter.js").version("0.0.1");
3875
4559
  program.addCommand(initCommand);
3876
4560
  program.addCommand(generateCommand);