schemantic 0.2.1 → 0.4.0

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.
Files changed (144) hide show
  1. package/CHANGELOG.md +196 -159
  2. package/LICENSE +21 -21
  3. package/README.md +88 -830
  4. package/dist/cli/index.d.ts +4 -1
  5. package/dist/cli/index.d.ts.map +1 -1
  6. package/dist/cli/index.js +186 -62
  7. package/dist/cli/index.js.map +1 -1
  8. package/dist/core/configuration.d.ts +6 -0
  9. package/dist/core/configuration.d.ts.map +1 -0
  10. package/dist/core/configuration.js +64 -0
  11. package/dist/core/configuration.js.map +1 -0
  12. package/dist/core/index.d.ts +1 -0
  13. package/dist/core/index.d.ts.map +1 -1
  14. package/dist/core/index.js +1 -0
  15. package/dist/core/index.js.map +1 -1
  16. package/dist/core/output-writer.d.ts +7 -0
  17. package/dist/core/output-writer.d.ts.map +1 -0
  18. package/dist/core/output-writer.js +255 -0
  19. package/dist/core/output-writer.js.map +1 -0
  20. package/dist/core/schemantic.d.ts +8 -6
  21. package/dist/core/schemantic.d.ts.map +1 -1
  22. package/dist/core/schemantic.js +194 -127
  23. package/dist/core/schemantic.js.map +1 -1
  24. package/dist/generators/api-client-generator.d.ts +9 -20
  25. package/dist/generators/api-client-generator.d.ts.map +1 -1
  26. package/dist/generators/api-client-generator.js +364 -424
  27. package/dist/generators/api-client-generator.js.map +1 -1
  28. package/dist/generators/base.d.ts.map +1 -1
  29. package/dist/generators/base.js +7 -25
  30. package/dist/generators/base.js.map +1 -1
  31. package/dist/generators/enum-generator.d.ts.map +1 -1
  32. package/dist/generators/enum-generator.js +30 -3
  33. package/dist/generators/enum-generator.js.map +1 -1
  34. package/dist/generators/hook-generator.d.ts +3 -17
  35. package/dist/generators/hook-generator.d.ts.map +1 -1
  36. package/dist/generators/hook-generator.js +51 -445
  37. package/dist/generators/hook-generator.js.map +1 -1
  38. package/dist/generators/naming.d.ts +10 -0
  39. package/dist/generators/naming.d.ts.map +1 -0
  40. package/dist/generators/naming.js +331 -0
  41. package/dist/generators/naming.js.map +1 -0
  42. package/dist/generators/object-generator.d.ts +0 -4
  43. package/dist/generators/object-generator.d.ts.map +1 -1
  44. package/dist/generators/object-generator.js +13 -101
  45. package/dist/generators/object-generator.js.map +1 -1
  46. package/dist/generators/primitive-generator.d.ts +1 -13
  47. package/dist/generators/primitive-generator.d.ts.map +1 -1
  48. package/dist/generators/primitive-generator.js +11 -76
  49. package/dist/generators/primitive-generator.js.map +1 -1
  50. package/dist/generators/projections.d.ts +10 -0
  51. package/dist/generators/projections.d.ts.map +1 -0
  52. package/dist/generators/projections.js +65 -0
  53. package/dist/generators/projections.js.map +1 -0
  54. package/dist/generators/schema-type.d.ts +23 -0
  55. package/dist/generators/schema-type.d.ts.map +1 -0
  56. package/dist/generators/schema-type.js +233 -0
  57. package/dist/generators/schema-type.js.map +1 -0
  58. package/dist/parsers/base.d.ts +2 -0
  59. package/dist/parsers/base.d.ts.map +1 -1
  60. package/dist/parsers/base.js +12 -2
  61. package/dist/parsers/base.js.map +1 -1
  62. package/dist/parsers/factory.d.ts.map +1 -1
  63. package/dist/parsers/factory.js +2 -1
  64. package/dist/parsers/factory.js.map +1 -1
  65. package/dist/parsers/openapi.d.ts +4 -0
  66. package/dist/parsers/openapi.d.ts.map +1 -1
  67. package/dist/parsers/openapi.js +107 -20
  68. package/dist/parsers/openapi.js.map +1 -1
  69. package/dist/plugins/branded-types.d.ts.map +1 -1
  70. package/dist/plugins/branded-types.js +231 -365
  71. package/dist/plugins/branded-types.js.map +1 -1
  72. package/dist/plugins/builtin.d.ts.map +1 -1
  73. package/dist/plugins/builtin.js +6 -141
  74. package/dist/plugins/builtin.js.map +1 -1
  75. package/dist/plugins/index.d.ts +4 -0
  76. package/dist/plugins/index.d.ts.map +1 -1
  77. package/dist/plugins/index.js +17 -0
  78. package/dist/plugins/index.js.map +1 -1
  79. package/dist/plugins/loader.d.ts +5 -1
  80. package/dist/plugins/loader.d.ts.map +1 -1
  81. package/dist/plugins/loader.js +42 -5
  82. package/dist/plugins/loader.js.map +1 -1
  83. package/dist/plugins/manager.d.ts.map +1 -1
  84. package/dist/plugins/manager.js +7 -7
  85. package/dist/plugins/manager.js.map +1 -1
  86. package/dist/plugins/performance-monitoring.d.ts +31 -1
  87. package/dist/plugins/performance-monitoring.d.ts.map +1 -1
  88. package/dist/plugins/performance-monitoring.js +146 -207
  89. package/dist/plugins/performance-monitoring.js.map +1 -1
  90. package/dist/plugins/request-deduplication.d.ts.map +1 -1
  91. package/dist/plugins/request-deduplication.js +151 -586
  92. package/dist/plugins/request-deduplication.js.map +1 -1
  93. package/dist/plugins/zod-validation.d.ts +23 -15
  94. package/dist/plugins/zod-validation.d.ts.map +1 -1
  95. package/dist/plugins/zod-validation.js +390 -268
  96. package/dist/plugins/zod-validation.js.map +1 -1
  97. package/dist/types/core.d.ts +20 -4
  98. package/dist/types/core.d.ts.map +1 -1
  99. package/dist/types/core.js +2 -1
  100. package/dist/types/core.js.map +1 -1
  101. package/dist/types/openapi.d.ts +14 -1
  102. package/dist/types/openapi.d.ts.map +1 -1
  103. package/docs/ADVANCED_PLUGINS_IMPLEMENTATION.md +24 -355
  104. package/docs/ADVANCED_PLUGIN_DEVELOPMENT.md +33 -309
  105. package/docs/API.md +80 -600
  106. package/docs/AUDIT.md +49 -0
  107. package/docs/CONFIGURATION.md +81 -722
  108. package/docs/FASTAPI_TUTORIAL.md +109 -1497
  109. package/docs/NAMING_MIGRATION.md +59 -0
  110. package/docs/PLUGINS.md +49 -687
  111. package/docs/PLUGIN_DEVELOPMENT.md +53 -720
  112. package/docs/REAUDIT.md +399 -0
  113. package/docs/RELEASING.md +88 -0
  114. package/docs/REMEDIATION.md +58 -0
  115. package/docs/superpowers/plans/2026-10-10-audit-remediation.md +113 -0
  116. package/package.json +114 -95
  117. package/dist/__tests__/setup.d.ts +0 -6
  118. package/dist/__tests__/setup.d.ts.map +0 -1
  119. package/dist/__tests__/setup.js +0 -30
  120. package/dist/__tests__/setup.js.map +0 -1
  121. package/dist/__tests__/test-config.d.ts +0 -36
  122. package/dist/__tests__/test-config.d.ts.map +0 -1
  123. package/dist/__tests__/test-config.js +0 -563
  124. package/dist/__tests__/test-config.js.map +0 -1
  125. package/dist/lib/api/api-client.d.ts +0 -71
  126. package/dist/lib/api/api-client.d.ts.map +0 -1
  127. package/dist/lib/api/api-client.js +0 -238
  128. package/dist/lib/api/api-client.js.map +0 -1
  129. package/dist/lib/api/barrel.d.ts +0 -4
  130. package/dist/lib/api/barrel.d.ts.map +0 -1
  131. package/dist/lib/api/barrel.js +0 -26
  132. package/dist/lib/api/barrel.js.map +0 -1
  133. package/dist/lib/api/hooks.d.ts +0 -36
  134. package/dist/lib/api/hooks.d.ts.map +0 -1
  135. package/dist/lib/api/hooks.js +0 -120
  136. package/dist/lib/api/hooks.js.map +0 -1
  137. package/dist/lib/api/index.d.ts +0 -4
  138. package/dist/lib/api/index.d.ts.map +0 -1
  139. package/dist/lib/api/index.js +0 -8
  140. package/dist/lib/api/index.js.map +0 -1
  141. package/dist/lib/api/types.d.ts +0 -366
  142. package/dist/lib/api/types.d.ts.map +0 -1
  143. package/dist/lib/api/types.js +0 -315
  144. package/dist/lib/api/types.js.map +0 -1
@@ -8,6 +8,7 @@
8
8
  */
9
9
  export declare class SchemanticCli {
10
10
  private program;
11
+ private pluginDirectory;
11
12
  constructor();
12
13
  /**
13
14
  * Setup the CLI program
@@ -81,6 +82,7 @@ export declare class SchemanticCli {
81
82
  * Handle errors
82
83
  */
83
84
  private handleError;
85
+ private outputEffectiveConfiguration;
84
86
  /**
85
87
  * Run interactive mode
86
88
  */
@@ -97,6 +99,7 @@ export declare class SchemanticCli {
97
99
  * Save configuration to file
98
100
  */
99
101
  private saveConfig;
102
+ private templateConfig;
100
103
  /**
101
104
  * Ask a question and return the answer
102
105
  */
@@ -112,7 +115,7 @@ export declare class SchemanticCli {
112
115
  /**
113
116
  * Run the CLI
114
117
  */
115
- run(): void;
118
+ run(argv?: string[]): Promise<void>;
116
119
  }
117
120
  export declare const cli: SchemanticCli;
118
121
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":";AAEA;;;GAGG;AA0BH;;GAEG;AACH,qBAAa,aAAa;IACxB,OAAO,CAAC,OAAO,CAAU;;IAOzB;;OAEG;IACH,OAAO,CAAC,YAAY;IAoBpB;;OAEG;IACH,OAAO,CAAC,kBAAkB;IAiD1B;;OAEG;IACH,OAAO,CAAC,kBAAkB;IAa1B;;OAEG;IACH,OAAO,CAAC,gBAAgB;IAsBxB;;OAEG;IACH,OAAO,CAAC,cAAc;IAWtB;;OAEG;IACH,OAAO,CAAC,cAAc;IAStB;;OAEG;YACW,qBAAqB;IA+BnC;;OAEG;YACW,qBAAqB;IAqBnC;;OAEG;YACW,iBAAiB;IAiB/B;;OAEG;YACW,uBAAuB;IAerC;;OAEG;YACW,uBAAuB;IAYrC;;OAEG;IACH,OAAO,CAAC,iBAAiB;IAgEzB;;OAEG;YACW,iBAAiB;IA8G/B;;OAEG;YACW,cAAc;IAsB5B;;OAEG;YACW,WAAW;IAsBzB;;OAEG;IACH,OAAO,CAAC,aAAa;IAqBrB;;OAEG;IACH,OAAO,CAAC,uBAAuB;IAoC/B;;OAEG;IACH,OAAO,CAAC,WAAW;IASnB;;OAEG;YACW,kBAAkB;IAuEhC;;OAEG;YACW,kBAAkB;IAoFhC;;OAEG;YACW,mBAAmB;IAsBjC;;OAEG;YACW,UAAU;IAWxB;;OAEG;IACH,OAAO,CAAC,WAAW;IASnB;;OAEG;IACH,OAAO,CAAC,QAAQ;IAahB;;OAEG;IACH,OAAO,CAAC,SAAS;IAmBjB;;OAEG;IACH,GAAG,IAAI,IAAI;CAGZ;AAGD,eAAO,MAAM,GAAG,eAAsB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":";AAEA;;;GAGG;AA8BH;;GAEG;AACH,qBAAa,aAAa;IACxB,OAAO,CAAC,OAAO,CAAU;IACzB,OAAO,CAAC,eAAe,CAAiB;;IAOxC;;OAEG;IACH,OAAO,CAAC,YAAY;IAoBpB;;OAEG;IACH,OAAO,CAAC,kBAAkB;IAgD1B;;OAEG;IACH,OAAO,CAAC,kBAAkB;IAa1B;;OAEG;IACH,OAAO,CAAC,gBAAgB;IAsBxB;;OAEG;IACH,OAAO,CAAC,cAAc;IAYtB;;OAEG;IACH,OAAO,CAAC,cAAc;IAStB;;OAEG;YACW,qBAAqB;IAsDnC;;OAEG;YACW,qBAAqB;IAuBnC;;OAEG;YACW,iBAAiB;IAoB/B;;OAEG;YACW,uBAAuB;IAerC;;OAEG;YACW,uBAAuB;IAarC;;OAEG;IACH,OAAO,CAAC,iBAAiB;IAiEzB;;OAEG;YACW,iBAAiB;IA0I/B;;OAEG;YACW,cAAc;IAyB5B;;OAEG;YACW,WAAW;IAwBzB;;OAEG;IACH,OAAO,CAAC,aAAa;IAqBrB;;OAEG;IACH,OAAO,CAAC,uBAAuB;IAoC/B;;OAEG;IACH,OAAO,CAAC,WAAW;IAKnB,OAAO,CAAC,4BAA4B;IAYpC;;OAEG;YACW,kBAAkB;IAyEhC;;OAEG;YACW,kBAAkB;IAoFhC;;OAEG;YACW,mBAAmB;IAsBjC;;OAEG;YACW,UAAU;IAkBxB,OAAO,CAAC,cAAc;IAItB;;OAEG;IACH,OAAO,CAAC,WAAW;IASnB;;OAEG;IACH,OAAO,CAAC,QAAQ;IAchB;;OAEG;IACH,OAAO,CAAC,SAAS;IAoBjB;;OAEG;IACG,GAAG,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;CAG1C;AAGD,eAAO,MAAM,GAAG,eAAsB,CAAC"}
package/dist/cli/index.js CHANGED
@@ -42,14 +42,19 @@ exports.cli = exports.SchemanticCli = void 0;
42
42
  const commander_1 = require("commander");
43
43
  const core_1 = require("../types/core");
44
44
  const schemantic_1 = require("../core/schemantic");
45
+ const configuration_1 = require("../core/configuration");
45
46
  const plugins_1 = require("../plugins");
46
47
  const builtin_1 = require("../plugins/builtin");
47
48
  const readline = __importStar(require("readline"));
49
+ const path = __importStar(require("path"));
50
+ const chokidar_1 = require("chokidar");
51
+ const module_1 = require("module");
48
52
  /**
49
53
  * Main CLI class
50
54
  */
51
55
  class SchemanticCli {
52
56
  constructor() {
57
+ this.pluginDirectory = process.cwd();
53
58
  this.program = new commander_1.Command();
54
59
  this.setupProgram();
55
60
  }
@@ -60,7 +65,7 @@ class SchemanticCli {
60
65
  this.program
61
66
  .name("schemantic")
62
67
  .description("Generate TypeScript types and API clients from OpenAPI schemas")
63
- .version("1.0.0")
68
+ .version(require('../../package.json').version)
64
69
  .option("-i, --interactive", "Run in interactive mode")
65
70
  .option("-v, --verbose", "Verbose output")
66
71
  .option("-q, --quiet", "Quiet output")
@@ -86,10 +91,11 @@ class SchemanticCli {
86
91
  .option("--types", "Generate types only")
87
92
  .option("--client", "Generate API client only")
88
93
  .option("--hooks", "Generate React hooks")
89
- .option("--strict", "Use strict TypeScript types", true)
90
- .option("--naming <convention>", "Naming convention (camelCase|snake_case|PascalCase)", "camelCase")
91
- .option("--prefix <prefix>", "Type name prefix (leave blank for none)", "")
92
- .option("--suffix <suffix>", "Type name suffix", "")
94
+ .option("--strict", "Use strict TypeScript types")
95
+ .option("--no-strict", "Disable strict TypeScript types")
96
+ .option("--naming <convention>", "Naming convention (camelCase|snake_case|PascalCase)")
97
+ .option("--prefix <prefix>", "Type name prefix (leave blank for none)")
98
+ .option("--suffix <suffix>", "Type name suffix")
93
99
  .option("--exclude-paths <paths>", "Exclude paths (comma-separated)")
94
100
  .option("--include-paths <paths>", "Include paths (comma-separated)")
95
101
  .option("--exclude-schemas <schemas>", "Exclude schemas (comma-separated)")
@@ -101,7 +107,7 @@ class SchemanticCli {
101
107
  .option("-c, --config [file]", "Configuration file path (defaults to ./schemantic.config.json)")
102
108
  .option("--watch", "Watch for changes and regenerate")
103
109
  .action(async (source, options) => {
104
- await this.handleGenerateCommand(source, options);
110
+ await this.handleGenerateCommand(source, { ...this.program.opts(), ...options });
105
111
  });
106
112
  }
107
113
  /**
@@ -114,9 +120,9 @@ class SchemanticCli {
114
120
  .description("Validate OpenAPI schema")
115
121
  .option("-u, --url <url>", "OpenAPI schema URL")
116
122
  .option("-f, --file <file>", "OpenAPI schema file path")
117
- .option("--fix", "Attempt to fix common issues")
123
+ .option("-c, --config [file]", "Configuration file path (otherwise discover schemantic.config.json/.cjs/.js)")
118
124
  .action(async (source, options) => {
119
- await this.handleValidateCommand(source, options);
125
+ await this.handleValidateCommand(source, { ...this.program.opts(), ...options });
120
126
  });
121
127
  }
122
128
  /**
@@ -136,7 +142,7 @@ class SchemanticCli {
136
142
  });
137
143
  pluginCommand
138
144
  .command("load <path>")
139
- .description("Load plugin from file or package")
145
+ .description("Inspect a plugin file or package (does not persist installation)")
140
146
  .action(async (path) => {
141
147
  await this.handlePluginLoadCommand(path);
142
148
  });
@@ -148,10 +154,11 @@ class SchemanticCli {
148
154
  this.program
149
155
  .command("init [directory]")
150
156
  .description("Initialize a new schemantic configuration")
151
- .option("-t, --template <template>", "Configuration template", "default")
157
+ .option("-t, --template <template>", "Configuration template (default|types|react)", "default")
152
158
  .option("--yes", "Skip interactive prompts")
159
+ .option("--force", "Replace an existing schemantic.config.json")
153
160
  .action(async (directory, options) => {
154
- await this.handleInitCommand(directory, options);
161
+ await this.handleInitCommand(directory, { ...this.program.opts(), ...options });
155
162
  });
156
163
  }
157
164
  /**
@@ -178,6 +185,9 @@ class SchemanticCli {
178
185
  }
179
186
  // Load configuration
180
187
  const config = await this.loadConfiguration(source, options);
188
+ this.outputEffectiveConfiguration(config, options);
189
+ if (options.watch && !config.schemaFile)
190
+ throw new Error('--watch requires a local schema file');
181
191
  // Create Schemantic instance
182
192
  const schemanticInstance = new schemantic_1.Schemantic(config);
183
193
  // Load plugins
@@ -186,6 +196,33 @@ class SchemanticCli {
186
196
  const result = await schemanticInstance.generate();
187
197
  // Output results
188
198
  this.outputResults(result, options);
199
+ if (!result.success)
200
+ process.exitCode = 1;
201
+ if (options.watch) {
202
+ const watcher = (0, chokidar_1.watch)(config.schemaFile, { ignoreInitial: true, awaitWriteFinish: { stabilityThreshold: 100 } });
203
+ let generating = false;
204
+ let pending = false;
205
+ watcher.on('change', async () => {
206
+ pending = true;
207
+ if (generating)
208
+ return;
209
+ generating = true;
210
+ try {
211
+ while (pending) {
212
+ pending = false;
213
+ const update = await schemanticInstance.generate();
214
+ this.outputResults(update, options);
215
+ process.exitCode = update.success ? 0 : 1;
216
+ }
217
+ }
218
+ finally {
219
+ generating = false;
220
+ }
221
+ });
222
+ watcher.on('ready', () => { if (!options.quiet)
223
+ console.log(`Watching ${config.schemaFile}`); });
224
+ watcher.on('error', error => this.handleError(error, options));
225
+ }
189
226
  }
190
227
  catch (error) {
191
228
  this.handleError(error, options);
@@ -198,12 +235,15 @@ class SchemanticCli {
198
235
  try {
199
236
  // Load configuration
200
237
  const config = await this.loadConfiguration(source, options);
238
+ this.outputEffectiveConfiguration(config, options);
201
239
  // Create Schemantic instance
202
240
  const schemanticInstance = new schemantic_1.Schemantic(config);
203
241
  // Validate schema
204
242
  const result = await schemanticInstance.validate();
205
243
  // Output validation results
206
244
  this.outputValidationResults(result, options);
245
+ if (!result.isValid)
246
+ process.exitCode = 1;
207
247
  }
208
248
  catch (error) {
209
249
  this.handleError(error, options);
@@ -215,8 +255,11 @@ class SchemanticCli {
215
255
  async handleInitCommand(directory, options = {}) {
216
256
  try {
217
257
  const targetDir = directory || process.cwd();
258
+ if (!['default', 'types', 'react'].includes(String(options.template || 'default'))) {
259
+ throw new Error('Unknown template; choose default, types, or react');
260
+ }
218
261
  if (options.yes) {
219
- await this.createDefaultConfig(targetDir);
262
+ await this.createDefaultConfig(targetDir, options);
220
263
  }
221
264
  else {
222
265
  await this.runInitInteractive(targetDir, options);
@@ -247,9 +290,10 @@ class SchemanticCli {
247
290
  async handlePluginLoadCommand(path) {
248
291
  try {
249
292
  const loader = new plugins_1.PluginLoader();
250
- const plugin = await loader.loadPluginFromFile(path);
293
+ const plugin = await loader.loadPlugin(path);
251
294
  console.log(`Loaded plugin: ${plugin.name} (${plugin.version})`);
252
295
  console.log(`Description: ${plugin.description}`);
296
+ console.log('Use this file/package in generate --plugins or the active config; inspection does not persist registration.');
253
297
  }
254
298
  catch (error) {
255
299
  this.handleError(error, {});
@@ -266,6 +310,7 @@ class SchemanticCli {
266
310
  subCommand.help();
267
311
  }
268
312
  else {
313
+ process.exitCode = 1;
269
314
  console.log(`Unknown command: ${command}`);
270
315
  console.log("Available commands:");
271
316
  this.program.commands.forEach((cmd) => {
@@ -311,18 +356,48 @@ class SchemanticCli {
311
356
  * Load configuration from options
312
357
  */
313
358
  async loadConfiguration(source, options = {}) {
359
+ this.pluginDirectory = process.cwd();
360
+ if (options.url && options.file)
361
+ throw new Error('Choose one schema source: --url or --file');
314
362
  let config = { ...core_1.DEFAULT_CONFIG };
315
- // Load from config file if specified. Commander may set options.config to
316
- // a string (path) or boolean true when the flag is present without value.
317
- if (options.config) {
318
- const configPath = typeof options.config === "string" && options.config.trim().length > 0
319
- ? options.config
320
- : "./schemantic.config.json";
363
+ const fs = await Promise.resolve().then(() => __importStar(require('fs/promises')));
364
+ let configPath = typeof options.config === 'string' ? path.resolve(options.config) : undefined;
365
+ if (!configPath) {
366
+ for (const name of ['schemantic.config.json', 'schemantic.config.cjs', 'schemantic.config.js']) {
367
+ const candidate = path.resolve(name);
368
+ try {
369
+ await fs.access(candidate);
370
+ configPath = candidate;
371
+ break;
372
+ }
373
+ catch (error) {
374
+ if (error.code !== 'ENOENT')
375
+ throw error;
376
+ }
377
+ }
378
+ if (options.config && !configPath)
379
+ throw new Error('No schemantic.config.json/.cjs/.js found in the current directory');
380
+ }
381
+ if (configPath) {
321
382
  const configFile = await this.loadConfigFile(configPath);
383
+ const directory = path.dirname(configPath);
384
+ this.pluginDirectory = directory;
385
+ if (configFile.schemaFile)
386
+ configFile.schemaFile = path.resolve(directory, configFile.schemaFile);
387
+ if (configFile.outputDir)
388
+ configFile.outputDir = path.resolve(directory, configFile.outputDir);
389
+ if (configFile.plugins)
390
+ configFile.plugins = configFile.plugins.map(plugin => ({
391
+ ...plugin,
392
+ name: plugins_1.PluginLoader.isFileSpecifier(plugin.name) ? path.resolve(directory, plugin.name) : plugin.name,
393
+ }));
322
394
  config = { ...config, ...configFile };
323
395
  }
324
396
  // Handle source parameter (URL or file path)
325
397
  if (source) {
398
+ delete config.schemaUrl;
399
+ delete config.schemaFile;
400
+ delete config.schemaData;
326
401
  if (source.startsWith("http://") || source.startsWith("https://")) {
327
402
  config.schemaUrl = source;
328
403
  }
@@ -332,19 +407,25 @@ class SchemanticCli {
332
407
  }
333
408
  // Override with command line options
334
409
  if (options.url) {
410
+ delete config.schemaFile;
411
+ delete config.schemaData;
335
412
  config.schemaUrl = options.url;
336
413
  }
337
414
  if (options.file) {
415
+ delete config.schemaUrl;
416
+ delete config.schemaData;
338
417
  config.schemaFile = options.file;
339
418
  }
340
- if (options.output) {
419
+ if (options.output !== undefined) {
341
420
  config.outputDir = options.output;
342
421
  }
343
422
  if (options.types) {
344
423
  config.generateTypes = true;
424
+ config.generateApiClient = !!options.client;
345
425
  }
346
426
  if (options.client) {
347
427
  config.generateApiClient = true;
428
+ config.generateTypes = true;
348
429
  }
349
430
  if (options.hooks) {
350
431
  config.generateHooks = true;
@@ -355,10 +436,10 @@ class SchemanticCli {
355
436
  if (options.naming) {
356
437
  config.namingConvention = options.naming;
357
438
  }
358
- if (options.prefix) {
439
+ if (options.prefix !== undefined) {
359
440
  config.typePrefix = options.prefix;
360
441
  }
361
- if (options.suffix) {
442
+ if (options.suffix !== undefined) {
362
443
  config.typeSuffix = options.suffix;
363
444
  }
364
445
  if (options.excludePaths) {
@@ -380,6 +461,11 @@ class SchemanticCli {
380
461
  }));
381
462
  }
382
463
  // Validate required fields
464
+ if (!['camelCase', 'snake_case', 'PascalCase'].includes(config.namingConvention)) {
465
+ throw new Error(`Invalid naming convention: ${config.namingConvention}`);
466
+ }
467
+ if (config.generateHooks && !config.generateApiClient)
468
+ throw new Error('React hooks require API client generation');
383
469
  if (!config.schemaUrl && !config.schemaFile && !config.schemaData) {
384
470
  throw new Error("Either source parameter, --url, --file, or schema data must be provided");
385
471
  }
@@ -389,7 +475,7 @@ class SchemanticCli {
389
475
  // ensure we set a concrete string fallback to satisfy strict types
390
476
  config.outputDir = core_1.DEFAULT_CONFIG.outputDir || "./src/generated";
391
477
  }
392
- return config;
478
+ return (0, configuration_1.resolveConfiguration)(config);
393
479
  }
394
480
  /**
395
481
  * Load configuration file
@@ -399,11 +485,14 @@ class SchemanticCli {
399
485
  const fs = await Promise.resolve().then(() => __importStar(require("fs/promises")));
400
486
  const content = await fs.readFile(filePath, "utf-8");
401
487
  if (filePath.endsWith(".json")) {
402
- return JSON.parse(content);
488
+ const parsed = JSON.parse(content);
489
+ return (0, configuration_1.resolveConfiguration)(parsed);
403
490
  }
404
- else if (filePath.endsWith(".js") || filePath.endsWith(".ts")) {
405
- const module = await Promise.resolve(`${filePath}`).then(s => __importStar(require(s)));
406
- return module.default || module;
491
+ else if (filePath.endsWith(".js") || filePath.endsWith(".cjs")) {
492
+ const configModule = (0, module_1.createRequire)(path.resolve(filePath))(path.resolve(filePath));
493
+ const loaded = configModule && typeof configModule === 'object' && 'default' in configModule
494
+ ? configModule.default : configModule;
495
+ return (0, configuration_1.resolveConfiguration)(loaded);
407
496
  }
408
497
  else {
409
498
  throw new Error(`Unsupported configuration file format: ${filePath}`);
@@ -418,15 +507,19 @@ class SchemanticCli {
418
507
  */
419
508
  async loadPlugins(config, Schemantic) {
420
509
  const pluginManager = Schemantic.getPluginManager();
421
- // Load built-in plugins
422
- const builtinPlugins = (0, builtin_1.getBuiltinPlugins)();
423
- for (const plugin of builtinPlugins) {
424
- pluginManager.registerPlugin(plugin);
425
- }
426
510
  // Load configured plugins
511
+ const loader = new plugins_1.PluginLoader();
427
512
  if (config.plugins) {
428
513
  for (const pluginConfig of config.plugins) {
429
514
  if (pluginConfig.enabled) {
515
+ if (!pluginManager.getPlugin(pluginConfig.name)) {
516
+ const plugin = await loader.loadPlugin(pluginConfig.name, this.pluginDirectory);
517
+ if (pluginManager.getPlugin(plugin.name))
518
+ throw new Error(`Duplicate plugin name: ${plugin.name}`);
519
+ pluginManager.registerPlugin(plugin);
520
+ // The config and instance share this entry; generation uses the resolved exported name.
521
+ pluginConfig.name = plugin.name;
522
+ }
430
523
  pluginManager.enablePlugin(pluginConfig.name);
431
524
  }
432
525
  }
@@ -481,11 +574,21 @@ class SchemanticCli {
481
574
  * Handle errors
482
575
  */
483
576
  handleError(error, options) {
484
- if (options.quiet) {
485
- process.exit(1);
486
- }
487
- console.error("āŒ Error:", error);
488
- process.exit(1);
577
+ process.exitCode = 1;
578
+ if (!options.quiet)
579
+ console.error('āŒ Error:', error instanceof Error ? error.message : String(error));
580
+ }
581
+ outputEffectiveConfiguration(config, options) {
582
+ if (!options.verbose || options.quiet)
583
+ return;
584
+ console.log('Effective configuration:', JSON.stringify({
585
+ source: config.schemaFile || config.schemaUrl || '<in-memory schema>',
586
+ output: config.outputDir,
587
+ generateTypes: config.generateTypes,
588
+ generateApiClient: config.generateApiClient,
589
+ generateHooks: config.generateHooks,
590
+ plugins: config.plugins?.filter(plugin => plugin.enabled).map(plugin => plugin.name) || [],
591
+ }, null, 2));
489
592
  }
490
593
  /**
491
594
  * Run interactive mode
@@ -517,16 +620,19 @@ class SchemanticCli {
517
620
  config.generateTypes = generateTypes !== false;
518
621
  const generateClient = await this.askYesNo(rl, "Generate API client? (Y/n): ");
519
622
  config.generateApiClient = generateClient !== false;
520
- const generateHooks = await this.askYesNo(rl, "Generate React hooks? (y/N): ");
623
+ const generateHooks = await this.askYesNo(rl, "Generate React hooks? (y/N): ", false);
521
624
  config.generateHooks = generateHooks === true;
522
625
  const strictTypes = await this.askYesNo(rl, "Use strict TypeScript types? (Y/n): ");
523
626
  config.useStrictTypes = strictTypes !== false;
524
627
  // Create Schemantic instance and generate
525
- const schemanticInstance = new schemantic_1.Schemantic(config);
526
- await this.loadPlugins(config, schemanticInstance);
628
+ const resolvedConfig = (0, configuration_1.resolveConfiguration)(config);
629
+ const schemanticInstance = new schemantic_1.Schemantic(resolvedConfig);
630
+ await this.loadPlugins(resolvedConfig, schemanticInstance);
527
631
  console.log("\nāš™ļø Generating types and API client...");
528
632
  const result = await schemanticInstance.generate();
529
633
  this.outputResults(result, {});
634
+ if (!result.success)
635
+ process.exitCode = 1;
530
636
  }
531
637
  finally {
532
638
  rl.close();
@@ -535,14 +641,14 @@ class SchemanticCli {
535
641
  /**
536
642
  * Run init interactive mode
537
643
  */
538
- async runInitInteractive(directory, _options) {
644
+ async runInitInteractive(directory, options) {
539
645
  console.log("šŸš€ Schemantic Configuration Initialization\n");
540
646
  const rl = readline.createInterface({
541
647
  input: process.stdin,
542
648
  output: process.stdout,
543
649
  });
544
650
  try {
545
- const config = { ...core_1.DEFAULT_CONFIG };
651
+ const config = this.templateConfig(options);
546
652
  // Get schema source
547
653
  const schemaSource = await this.askQuestion(rl, "Enter default OpenAPI schema URL or file path: ");
548
654
  if (schemaSource.startsWith("http")) {
@@ -563,14 +669,14 @@ class SchemanticCli {
563
669
  // Get generation options
564
670
  const generateTypes = await this.askYesNo(rl, "Generate types by default? (Y/n): ");
565
671
  config.generateTypes = generateTypes !== false;
566
- const generateClient = await this.askYesNo(rl, "Generate API client by default? (Y/n): ");
672
+ const generateClient = await this.askYesNo(rl, options.template === 'types' ? "Generate API client by default? (y/N): " : "Generate API client by default? (Y/n): ", options.template !== 'types');
567
673
  config.generateApiClient = generateClient !== false;
568
- const generateHooks = await this.askYesNo(rl, "Generate React hooks by default? (y/N): ");
674
+ const generateHooks = await this.askYesNo(rl, options.template === 'react' ? "Generate React hooks by default? (Y/n): " : "Generate React hooks by default? (y/N): ", options.template === 'react');
569
675
  config.generateHooks = generateHooks === true;
570
676
  const strictTypes = await this.askYesNo(rl, "Use strict TypeScript types by default? (Y/n): ");
571
677
  config.useStrictTypes = strictTypes !== false;
572
678
  // Save configuration
573
- await this.saveConfig(directory, config);
679
+ await this.saveConfig(directory, (0, configuration_1.resolveConfiguration)(config), !!options.force);
574
680
  console.log("\nāœ… Configuration saved to schemantic.config.json");
575
681
  }
576
682
  finally {
@@ -580,34 +686,45 @@ class SchemanticCli {
580
686
  /**
581
687
  * Create default configuration
582
688
  */
583
- async createDefaultConfig(directory) {
689
+ async createDefaultConfig(directory, options) {
584
690
  const config = {
585
- ...core_1.DEFAULT_CONFIG,
691
+ ...this.templateConfig(options),
586
692
  outputDir: "./src/generated",
587
693
  typePrefix: "",
588
694
  generateTypes: true,
589
- generateApiClient: true,
590
- generateHooks: false,
695
+ generateApiClient: options.template !== 'types',
696
+ generateHooks: options.template === 'react',
591
697
  useStrictTypes: true,
592
698
  namingConvention: "camelCase",
593
699
  typeSuffix: "",
594
- generateBarrelExports: true,
700
+ // generateBarrelExports removed - barrel.ts is no longer generated
595
701
  useOptionalChaining: true,
596
702
  useNullishCoalescing: true,
597
703
  preserveComments: true,
598
704
  generateIndexFile: true,
599
705
  };
600
- await this.saveConfig(directory, config);
706
+ await this.saveConfig(directory, config, !!options.force);
601
707
  console.log("āœ… Default configuration created");
602
708
  }
603
709
  /**
604
710
  * Save configuration to file
605
711
  */
606
- async saveConfig(directory, config) {
712
+ async saveConfig(directory, config, force = false) {
607
713
  const fs = await Promise.resolve().then(() => __importStar(require("fs/promises")));
608
714
  const path = await Promise.resolve().then(() => __importStar(require("path")));
609
715
  const configPath = path.join(directory, "schemantic.config.json");
610
- await fs.writeFile(configPath, JSON.stringify(config, null, 2), "utf-8");
716
+ await fs.mkdir(directory, { recursive: true });
717
+ try {
718
+ await fs.writeFile(configPath, JSON.stringify(config, null, 2), { encoding: 'utf-8', flag: force ? 'w' : 'wx' });
719
+ }
720
+ catch (error) {
721
+ if (error.code === 'EEXIST')
722
+ throw new Error(`Configuration already exists: ${configPath}; use init --force to replace it`);
723
+ throw error;
724
+ }
725
+ }
726
+ templateConfig(options) {
727
+ return { ...core_1.DEFAULT_CONFIG, outputDir: './src/generated', generateApiClient: options.template !== 'types', generateHooks: options.template === 'react' };
611
728
  }
612
729
  /**
613
730
  * Ask a question and return the answer
@@ -620,16 +737,20 @@ class SchemanticCli {
620
737
  /**
621
738
  * Ask a yes/no question and return boolean
622
739
  */
623
- askYesNo(rl, question) {
624
- return new Promise((resolve) => {
740
+ askYesNo(rl, question, defaultValue = true) {
741
+ return new Promise((resolve, reject) => {
625
742
  rl.question(question, (answer) => {
626
743
  const normalized = answer.toLowerCase().trim();
627
- if (normalized === "" || normalized === "y" || normalized === "yes") {
744
+ if (normalized === '')
745
+ resolve(defaultValue);
746
+ else if (normalized === "y" || normalized === "yes") {
628
747
  resolve(true);
629
748
  }
630
- else {
749
+ else if (normalized === 'n' || normalized === 'no') {
631
750
  resolve(false);
632
751
  }
752
+ else
753
+ reject(new Error('Answer yes or no'));
633
754
  });
634
755
  });
635
756
  }
@@ -637,7 +758,7 @@ class SchemanticCli {
637
758
  * Ask a choice question and return selected option
638
759
  */
639
760
  askChoice(rl, question, choices, defaultChoice) {
640
- return new Promise((resolve) => {
761
+ return new Promise((resolve, reject) => {
641
762
  rl.question(question, (answer) => {
642
763
  const normalized = answer.toLowerCase().trim();
643
764
  if (normalized === "") {
@@ -645,7 +766,10 @@ class SchemanticCli {
645
766
  }
646
767
  else {
647
768
  const choice = choices.find((c) => c.toLowerCase() === normalized);
648
- resolve(choice || defaultChoice);
769
+ if (choice)
770
+ resolve(choice);
771
+ else
772
+ reject(new Error(`Choose one of: ${choices.join(', ')}`));
649
773
  }
650
774
  });
651
775
  });
@@ -653,8 +777,8 @@ class SchemanticCli {
653
777
  /**
654
778
  * Run the CLI
655
779
  */
656
- run() {
657
- this.program.parse();
780
+ async run(argv) {
781
+ await this.program.parseAsync(argv);
658
782
  }
659
783
  }
660
784
  exports.SchemanticCli = SchemanticCli;
@@ -662,6 +786,6 @@ exports.SchemanticCli = SchemanticCli;
662
786
  exports.cli = new SchemanticCli();
663
787
  // Run CLI if this file is executed directly
664
788
  if (require.main === module) {
665
- exports.cli.run();
789
+ void exports.cli.run();
666
790
  }
667
791
  //# sourceMappingURL=index.js.map