@igniter-js/cli 0.4.91 → 0.4.93

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";
@@ -94,10 +94,10 @@ import { execa } from "execa";
94
94
  import { existsSync } from "fs";
95
95
  import { readFile, writeFile } from "fs/promises";
96
96
  import { join as join2 } from "path";
97
- import * as handlebars2 from "handlebars";
97
+ import handlebars2 from "handlebars";
98
98
 
99
99
  // src/core/handlebars-helpers.ts
100
- import * as handlebars from "handlebars";
100
+ import handlebars from "handlebars";
101
101
  function registerHandlebarsHelpers() {
102
102
  handlebars.registerHelper("includes", function(array, value) {
103
103
  if (!Array.isArray(array)) {
@@ -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);
@@ -676,7 +676,7 @@ import path4 from "path";
676
676
  import { readFile as readFile2, writeFile as writeFile2 } from "fs/promises";
677
677
  import { existsSync as existsSync2 } from "fs";
678
678
  import { join as join3 } from "path";
679
- import * as handlebars3 from "handlebars";
679
+ import handlebars3 from "handlebars";
680
680
  var BaseAddOn = class {
681
681
  async runSetup(projectDir, config) {
682
682
  await this.addToPackageJson(projectDir, config);
@@ -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";
@@ -2302,7 +2302,7 @@ import { writeFile as writeFile5 } from "fs/promises";
2302
2302
  import { mkdirSync, existsSync as existsSync3 } from "fs";
2303
2303
  import { readFile as readFile3, writeFile as writeFile3 } from "fs/promises";
2304
2304
  import * as path14 from "path";
2305
- import * as handlebars4 from "handlebars";
2305
+ import handlebars4 from "handlebars";
2306
2306
  import { fileURLToPath } from "url";
2307
2307
  var __filename = fileURLToPath(import.meta?.url);
2308
2308
  var __dirname = path14.dirname(__filename);
@@ -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,434 @@ 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"];
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 pathsCode = buildSchemasObject(parsedDoc, converter);
3572
+ const componentsCode = converter.renderComponentSchemas();
3573
+ const schemaConstName = `${prefix}Schema`;
3574
+ const schemaTypeName = `${prefix}SchemaType`;
3575
+ const schemaFileContents = [
3576
+ "/* eslint-disable */",
3577
+ "/* prettier-ignore */",
3578
+ "",
3579
+ "/**",
3580
+ " * @generated by @igniter-js/cli",
3581
+ " * This file was automatically created from your OpenAPI spec.",
3582
+ " * Do not edit manually; regenerate via `igniter generate caller`.",
3583
+ " */",
3584
+ "",
3585
+ 'import { z } from "zod";',
3586
+ componentsCode,
3587
+ `const schemas = ${pathsCode};`,
3588
+ "",
3589
+ `export const ${schemaConstName} = schemas;`,
3590
+ `export type ${schemaTypeName} = typeof ${schemaConstName};`,
3591
+ ""
3592
+ ].filter(Boolean).join("\n");
3593
+ const baseUrl = inferBaseUrl(source, parsedDoc);
3594
+ const callerVar = `${Casing.toCamelCase(callerName)}Caller`;
3595
+ const callerFileContents = [
3596
+ "/**",
3597
+ ` * Preconfigured IgniterCaller for ${prefix}.`,
3598
+ " * Regenerate via `igniter generate caller` when the OpenAPI spec changes.",
3599
+ " */",
3600
+ 'import { IgniterCaller } from "@igniter-js/caller";',
3601
+ `import { ${schemaConstName} } from "./schema";`,
3602
+ "",
3603
+ "export const " + callerVar + " = IgniterCaller.create()",
3604
+ baseUrl ? ` .withBaseUrl("${baseUrl}")` : "",
3605
+ " .withSchemas(" + schemaConstName + ', { mode: "strict" })',
3606
+ " .build();",
3607
+ ""
3608
+ ].filter(Boolean).join("\n");
3609
+ await fs7.mkdir(outputDir, { recursive: true });
3610
+ await fs7.writeFile(path23.join(outputDir, "schema.ts"), schemaFileContents, "utf8");
3611
+ await fs7.writeFile(path23.join(outputDir, "index.ts"), callerFileContents, "utf8");
3612
+ spinner8.stop("Caller generated successfully.");
3613
+ p13.log.success(
3614
+ `Schemas and caller created at ${path23.relative(process.cwd(), outputDir)}`
3615
+ );
3616
+ p13.log.info(
3617
+ `Exported caller: ${callerVar} (schemas: ${schemaConstName})`
3618
+ );
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
+ if (!parsed.openapi || !parsed.openapi.startsWith("3.")) {
3688
+ throw new Error("Only OpenAPI 3.x documents are supported.");
3689
+ }
3690
+ const dereferenced = await SwaggerParser.dereference(
3691
+ parsed
3692
+ );
3693
+ return dereferenced;
3694
+ }
3695
+ async function loadRawContent(source) {
3696
+ if (source.type === "url") {
3697
+ const response = await fetch(source.url);
3698
+ if (!response.ok) {
3699
+ throw new Error(
3700
+ `Failed to fetch OpenAPI document: ${response.status} ${response.statusText}`
3701
+ );
3702
+ }
3703
+ return await response.text();
3704
+ }
3705
+ const filePath = path23.resolve(process.cwd(), source.path);
3706
+ return await fs7.readFile(filePath, "utf8");
3707
+ }
3708
+ function parseOpenApi(content) {
3709
+ try {
3710
+ return JSON.parse(content);
3711
+ } catch {
3712
+ const parsed = yaml.load(content);
3713
+ if (!parsed || typeof parsed !== "object") {
3714
+ throw new Error("Could not parse OpenAPI document (JSON/YAML).");
3715
+ }
3716
+ return parsed;
3717
+ }
3718
+ }
3719
+ function deriveHostname(source, doc) {
3720
+ if (source.type === "url") {
3721
+ try {
3722
+ const url = new URL(source.url);
3723
+ return url.hostname.replace(/[^a-zA-Z0-9.-]/g, "") || null;
3724
+ } catch {
3725
+ return null;
3726
+ }
3727
+ }
3728
+ const serverUrl = doc.servers?.[0]?.url;
3729
+ if (serverUrl) {
3730
+ try {
3731
+ const url = new URL(serverUrl);
3732
+ return url.hostname.replace(/[^a-zA-Z0-9.-]/g, "") || null;
3733
+ } catch {
3734
+ return null;
3735
+ }
3736
+ }
3737
+ return null;
3738
+ }
3739
+ function inferBaseUrl(source, doc) {
3740
+ if (source.type === "url") {
3741
+ const serverUrl2 = doc.servers?.[0]?.url;
3742
+ if (serverUrl2) {
3743
+ return serverUrl2;
3744
+ }
3745
+ return new URL(source.url).origin;
3746
+ }
3747
+ const serverUrl = doc.servers?.[0]?.url;
3748
+ return serverUrl ?? null;
3749
+ }
3750
+ var SchemaConverter = class {
3751
+ constructor(prefix, components) {
3752
+ this.generated = /* @__PURE__ */ new Map();
3753
+ this.inProgress = /* @__PURE__ */ new Set();
3754
+ this.prefix = prefix;
3755
+ this.components = components;
3756
+ }
3757
+ renderComponentSchemas() {
3758
+ const entries = [];
3759
+ const names = Object.keys(this.components?.schemas ?? {}).sort();
3760
+ for (const name of names) {
3761
+ const identifier = this.componentIdentifier(name);
3762
+ const expression = this.convertWithCache(name);
3763
+ entries.push(`const ${identifier} = ${expression};`);
3764
+ }
3765
+ return entries.length ? `${entries.join("\n")}
3766
+ ` : "";
3767
+ }
3768
+ convert(schema) {
3769
+ if ("$ref" in schema) {
3770
+ const refName = this.refName(schema.$ref);
3771
+ const identifier = this.componentIdentifier(refName);
3772
+ this.convertWithCache(refName);
3773
+ return this.wrapLazyIfNeeded(refName, identifier);
3774
+ }
3775
+ if (schema.oneOf?.length) {
3776
+ const parts = schema.oneOf.map((item) => this.convert(item));
3777
+ return `z.union([${parts.join(", ")}])${schema.nullable ? ".nullable()" : ""}`;
3778
+ }
3779
+ if (schema.anyOf?.length) {
3780
+ const parts = schema.anyOf.map((item) => this.convert(item));
3781
+ return `z.union([${parts.join(", ")}])${schema.nullable ? ".nullable()" : ""}`;
3782
+ }
3783
+ if (schema.allOf?.length) {
3784
+ const [first, ...rest] = schema.allOf;
3785
+ let expr = this.convert(first);
3786
+ for (const part of rest) {
3787
+ expr = `z.intersection(${expr}, ${this.convert(part)})`;
3788
+ }
3789
+ return schema.nullable ? `${expr}.nullable()` : expr;
3790
+ }
3791
+ switch (schema.type) {
3792
+ case "string":
3793
+ if (schema.enum) {
3794
+ const values = schema.enum.map((v) => JSON.stringify(String(v)));
3795
+ return `z.enum([${values.join(", ")}])${schema.nullable ? ".nullable()" : ""}`;
3796
+ }
3797
+ return `z.string()${schema.nullable ? ".nullable()" : ""}`;
3798
+ case "number":
3799
+ case "integer": {
3800
+ let expr = "z.number()";
3801
+ if (schema.type === "integer") {
3802
+ expr = `${expr}.int()`;
3803
+ }
3804
+ if (schema.nullable) {
3805
+ expr = `${expr}.nullable()`;
3806
+ }
3807
+ return expr;
3808
+ }
3809
+ case "boolean":
3810
+ return `z.boolean()${schema.nullable ? ".nullable()" : ""}`;
3811
+ case "array": {
3812
+ const items = schema.items ? this.convert(schema.items) : "z.unknown()";
3813
+ return `z.array(${items})${schema.nullable ? ".nullable()" : ""}`;
3814
+ }
3815
+ case "object": {
3816
+ const properties = schema.properties ?? {};
3817
+ const required = new Set(schema.required ?? []);
3818
+ const entries = Object.entries(properties).map(([key, value]) => {
3819
+ const valueExpr = this.convert(value);
3820
+ return `${JSON.stringify(key)}: ${required.has(key) ? valueExpr : `${valueExpr}.optional()`}`;
3821
+ });
3822
+ let base = `z.object({${entries.length ? ` ${entries.join(", ")} ` : ""}})`;
3823
+ if (schema.additionalProperties) {
3824
+ const apValue = schema.additionalProperties === true ? "z.unknown()" : this.convert(schema.additionalProperties);
3825
+ base = `${base}.catchall(${apValue})`;
3826
+ }
3827
+ if (schema.nullable) {
3828
+ base = `${base}.nullable()`;
3829
+ }
3830
+ return base;
3831
+ }
3832
+ default:
3833
+ return "z.unknown()";
3834
+ }
3835
+ }
3836
+ convertWithCache(name) {
3837
+ const identifier = this.componentIdentifier(name);
3838
+ if (this.generated.has(name)) {
3839
+ return this.generated.get(name);
3840
+ }
3841
+ if (this.inProgress.has(name)) {
3842
+ return `z.lazy(() => ${identifier})`;
3843
+ }
3844
+ const schema = this.components?.schemas?.[name];
3845
+ if (!schema) {
3846
+ return "z.unknown()";
3847
+ }
3848
+ this.inProgress.add(name);
3849
+ const expression = this.convert(schema);
3850
+ this.inProgress.delete(name);
3851
+ const finalized = expression.startsWith("z.lazy(") ? expression : expression;
3852
+ this.generated.set(name, finalized);
3853
+ return finalized;
3854
+ }
3855
+ componentIdentifier(name) {
3856
+ return `${this.prefix}${Casing.toPascalCase(name)}Schema`;
3857
+ }
3858
+ refName(ref) {
3859
+ const [, component] = ref.split("#/components/schemas/");
3860
+ if (!component) {
3861
+ return "UnknownSchema";
3862
+ }
3863
+ return component;
3864
+ }
3865
+ wrapLazyIfNeeded(name, identifier) {
3866
+ if (this.inProgress.has(name)) {
3867
+ return `z.lazy(() => ${identifier})`;
3868
+ }
3869
+ return identifier;
3870
+ }
3871
+ };
3872
+ function buildSchemasObject(doc, converter) {
3873
+ const pathEntries = [];
3874
+ const sortedPaths = Object.keys(doc.paths || {}).sort();
3875
+ for (const rawPath of sortedPaths) {
3876
+ const pathItem = doc.paths?.[rawPath];
3877
+ if (!pathItem) continue;
3878
+ const methodEntries = [];
3879
+ for (const method of SUPPORTED_METHODS) {
3880
+ const operation = pathItem[method];
3881
+ if (!operation) continue;
3882
+ const responses = buildResponses(operation.responses, converter);
3883
+ const requestBody = buildRequestBody(operation.requestBody, converter);
3884
+ const segments = [];
3885
+ if (requestBody) {
3886
+ segments.push(`request: ${requestBody}`);
3887
+ }
3888
+ segments.push(`responses: ${responses}`);
3889
+ const methodBlock = [
3890
+ `${method.toUpperCase()}: {`,
3891
+ indentLines(segments.join(",\n"), 2),
3892
+ "}"
3893
+ ].join("\n");
3894
+ methodEntries.push(indentLines(methodBlock, 2));
3895
+ }
3896
+ if (!methodEntries.length) continue;
3897
+ const normalizedPath = normalizePath(rawPath);
3898
+ const pathBlock = [
3899
+ `${JSON.stringify(normalizedPath)}: {`,
3900
+ methodEntries.join(",\n"),
3901
+ "}"
3902
+ ].join("\n");
3903
+ pathEntries.push(indentLines(pathBlock, 1));
3904
+ }
3905
+ return `{
3906
+ ${pathEntries.join(",\n")}
3907
+ }`;
3908
+ }
3909
+ function buildResponses(responses, converter) {
3910
+ if (!responses || !Object.keys(responses).length) {
3911
+ return "{ }";
3912
+ }
3913
+ const entries = [];
3914
+ const sorted = Object.keys(responses).sort();
3915
+ for (const status of sorted) {
3916
+ const responseOrRef = responses[status];
3917
+ if (!responseOrRef) continue;
3918
+ const response = "$ref" in responseOrRef ? void 0 : responseOrRef;
3919
+ const schema = resolveSchemaFromResponse(response);
3920
+ const expression = schema ? converter.convert(schema) : "z.unknown()";
3921
+ entries.push(`${status}: ${expression}`);
3922
+ }
3923
+ if (!entries.length) {
3924
+ return "{ }";
3925
+ }
3926
+ return `{
3927
+ ${indentLines(entries.join(",\n"), 2)}
3928
+ }`;
3929
+ }
3930
+ function buildRequestBody(requestBody, converter) {
3931
+ if (!requestBody) return null;
3932
+ const resolved = "$ref" in requestBody ? null : requestBody;
3933
+ if (!resolved?.content) return null;
3934
+ const schema = selectJsonSchema(resolved.content);
3935
+ if (!schema) return null;
3936
+ return converter.convert(schema);
3937
+ }
3938
+ function selectJsonSchema(content) {
3939
+ if (content["application/json"]?.schema) {
3940
+ return content["application/json"].schema;
3941
+ }
3942
+ const first = Object.values(content)[0];
3943
+ return first?.schema;
3944
+ }
3945
+ function resolveSchemaFromResponse(response) {
3946
+ if (!response?.content) {
3947
+ return void 0;
3948
+ }
3949
+ return selectJsonSchema(response.content);
3950
+ }
3951
+ function normalizePath(pathname) {
3952
+ return pathname.replace(/{(.*?)}/g, ":$1");
3953
+ }
3954
+ function indentLines(value, depth) {
3955
+ const pad = " ".repeat(depth);
3956
+ return value.split("\n").map((line) => line ? pad + line : line).join("\n");
3957
+ }
3958
+
3959
+ // src/commands/generate/caller/index.ts
3960
+ 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(
3961
+ "--output <path>",
3962
+ "Output directory (defaults to src/callers/<hostname>)"
3963
+ ).action(handleGenerateCallerAction);
3964
+
3546
3965
  // 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);
3966
+ 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
3967
 
3549
3968
  // src/commands/dev/index.ts
3550
- import { Command as Command8 } from "commander";
3969
+ import { Command as Command9 } from "commander";
3551
3970
 
3552
3971
  // src/commands/dev/action.ts
3553
- import * as path23 from "path";
3554
- import * as fs7 from "fs";
3972
+ import * as path24 from "path";
3973
+ import * as fs8 from "fs";
3555
3974
  import { spawn as spawn2 } from "child_process";
3556
3975
  import chokidar from "chokidar";
3557
3976
  import { render } from "ink";
@@ -3586,14 +4005,14 @@ function DevUI({ igniterLogs, appLogs, onExit }) {
3586
4005
  };
3587
4006
  const renderLogs = (logs, maxLines = 50) => {
3588
4007
  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) => {
4008
+ return /* @__PURE__ */ React.createElement(Box, { flexDirection: "column" }, visibleLogs.length === 0 ? /* @__PURE__ */ React.createElement(Text, { dimColor: true }, "No logs yet...") : visibleLogs.map((log12, index) => {
3590
4009
  const color2 = {
3591
4010
  info: "cyan",
3592
4011
  success: "green",
3593
4012
  error: "red",
3594
4013
  warn: "yellow"
3595
- }[log11.type];
3596
- return /* @__PURE__ */ React.createElement(Text, { key: `${log11.timestamp.getTime()}-${index}`, color: color2 }, formatLogEntry(log11));
4014
+ }[log12.type];
4015
+ return /* @__PURE__ */ React.createElement(Text, { key: `${log12.timestamp.getTime()}-${index}`, color: color2 }, formatLogEntry(log12));
3597
4016
  }));
3598
4017
  };
3599
4018
  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 +4074,19 @@ async function regenerateSchemaAndDocs(routerPath, outputPath, docsOutputDir, ad
3655
4074
  }
3656
4075
  async function handleDevAction(options) {
3657
4076
  const packageManager = detectPackageManager();
3658
- const routerPath = path23.resolve(process.cwd(), options.router || "src/igniter.router.ts");
4077
+ const routerPath = path24.resolve(process.cwd(), options.router || "src/igniter.router.ts");
3659
4078
  const outputPath = options.output || "src/igniter.schema.ts";
3660
4079
  const docsOutputDir = options.docsOutput || "./src/docs";
3661
4080
  const devCommand2 = options.cmd || getDefaultDevCommand(packageManager);
3662
- if (!fs7.existsSync(routerPath)) {
4081
+ if (!fs8.existsSync(routerPath)) {
3663
4082
  console.error(`Router file not found: ${routerPath}`);
3664
4083
  process.exit(1);
3665
4084
  }
3666
4085
  const igniterLogs = [];
3667
4086
  const appLogs = [];
3668
4087
  let rerenderFn = null;
3669
- const addIgniterLog = (log11) => {
3670
- igniterLogs.push(log11);
4088
+ const addIgniterLog = (log12) => {
4089
+ igniterLogs.push(log12);
3671
4090
  if (igniterLogs.length > 1e3) {
3672
4091
  igniterLogs.shift();
3673
4092
  }
@@ -3675,8 +4094,8 @@ async function handleDevAction(options) {
3675
4094
  rerenderFn();
3676
4095
  }
3677
4096
  };
3678
- const addAppLog = (log11) => {
3679
- appLogs.push(log11);
4097
+ const addAppLog = (log12) => {
4098
+ appLogs.push(log12);
3680
4099
  if (appLogs.length > 1e3) {
3681
4100
  appLogs.shift();
3682
4101
  }
@@ -3706,13 +4125,13 @@ async function handleDevAction(options) {
3706
4125
  });
3707
4126
  process.exit(1);
3708
4127
  }
3709
- const featuresDir = path23.join(process.cwd(), "src", "features");
4128
+ const featuresDir = path24.join(process.cwd(), "src", "features");
3710
4129
  const watchPaths = [
3711
4130
  routerPath,
3712
4131
  // Watch router file directly
3713
4132
  featuresDir
3714
4133
  // Watch features directory recursively
3715
- ].filter((p13) => fs7.existsSync(p13));
4134
+ ].filter((p14) => fs8.existsSync(p14));
3716
4135
  if (watchPaths.length === 0) {
3717
4136
  addIgniterLog({
3718
4137
  type: "warn",
@@ -3723,7 +4142,7 @@ async function handleDevAction(options) {
3723
4142
  }
3724
4143
  addIgniterLog({
3725
4144
  type: "info",
3726
- message: `Watching for changes in: ${watchPaths.map((p13) => path23.relative(process.cwd(), p13)).join(", ")}`,
4145
+ message: `Watching for changes in: ${watchPaths.map((p14) => path24.relative(process.cwd(), p14)).join(", ")}`,
3727
4146
  timestamp: /* @__PURE__ */ new Date()
3728
4147
  });
3729
4148
  let regenerateTimeout = null;
@@ -3743,7 +4162,7 @@ async function handleDevAction(options) {
3743
4162
  ignoreInitial: true
3744
4163
  });
3745
4164
  watcher.on("change", (filePath) => {
3746
- const relativePath = path23.relative(process.cwd(), filePath);
4165
+ const relativePath = path24.relative(process.cwd(), filePath);
3747
4166
  if (regenerateTimeout) {
3748
4167
  clearTimeout(regenerateTimeout);
3749
4168
  }
@@ -3867,10 +4286,10 @@ async function handleDevAction(options) {
3867
4286
  }
3868
4287
 
3869
4288
  // 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);
4289
+ 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
4290
 
3872
4291
  // src/index.ts
3873
- var program = new Command9();
4292
+ var program = new Command10();
3874
4293
  program.name("igniter").description("The next-generation command-line interface for Igniter.js").version("0.0.1");
3875
4294
  program.addCommand(initCommand);
3876
4295
  program.addCommand(generateCommand);