schemantic 0.3.0 ā 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +204 -174
- package/LICENSE +21 -21
- package/README.md +88 -830
- package/dist/cli/index.d.ts +4 -1
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +185 -61
- package/dist/cli/index.js.map +1 -1
- package/dist/core/configuration.d.ts +6 -0
- package/dist/core/configuration.d.ts.map +1 -0
- package/dist/core/configuration.js +64 -0
- package/dist/core/configuration.js.map +1 -0
- package/dist/core/index.d.ts +1 -0
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +1 -0
- package/dist/core/index.js.map +1 -1
- package/dist/core/output-writer.d.ts +7 -0
- package/dist/core/output-writer.d.ts.map +1 -0
- package/dist/core/output-writer.js +255 -0
- package/dist/core/output-writer.js.map +1 -0
- package/dist/core/schemantic.d.ts +8 -2
- package/dist/core/schemantic.d.ts.map +1 -1
- package/dist/core/schemantic.js +196 -71
- package/dist/core/schemantic.js.map +1 -1
- package/dist/generators/api-client-generator.d.ts +10 -20
- package/dist/generators/api-client-generator.d.ts.map +1 -1
- package/dist/generators/api-client-generator.js +374 -424
- package/dist/generators/api-client-generator.js.map +1 -1
- package/dist/generators/base.d.ts.map +1 -1
- package/dist/generators/base.js +7 -25
- package/dist/generators/base.js.map +1 -1
- package/dist/generators/enum-generator.d.ts.map +1 -1
- package/dist/generators/enum-generator.js +30 -3
- package/dist/generators/enum-generator.js.map +1 -1
- package/dist/generators/hook-generator.d.ts +3 -17
- package/dist/generators/hook-generator.d.ts.map +1 -1
- package/dist/generators/hook-generator.js +51 -445
- package/dist/generators/hook-generator.js.map +1 -1
- package/dist/generators/naming.d.ts +10 -0
- package/dist/generators/naming.d.ts.map +1 -0
- package/dist/generators/naming.js +331 -0
- package/dist/generators/naming.js.map +1 -0
- package/dist/generators/object-generator.d.ts +0 -4
- package/dist/generators/object-generator.d.ts.map +1 -1
- package/dist/generators/object-generator.js +13 -101
- package/dist/generators/object-generator.js.map +1 -1
- package/dist/generators/primitive-generator.d.ts +1 -13
- package/dist/generators/primitive-generator.d.ts.map +1 -1
- package/dist/generators/primitive-generator.js +11 -76
- package/dist/generators/primitive-generator.js.map +1 -1
- package/dist/generators/projections.d.ts +10 -0
- package/dist/generators/projections.d.ts.map +1 -0
- package/dist/generators/projections.js +65 -0
- package/dist/generators/projections.js.map +1 -0
- package/dist/generators/schema-type.d.ts +23 -0
- package/dist/generators/schema-type.d.ts.map +1 -0
- package/dist/generators/schema-type.js +233 -0
- package/dist/generators/schema-type.js.map +1 -0
- package/dist/parsers/base.d.ts +2 -0
- package/dist/parsers/base.d.ts.map +1 -1
- package/dist/parsers/base.js +12 -2
- package/dist/parsers/base.js.map +1 -1
- package/dist/parsers/factory.d.ts.map +1 -1
- package/dist/parsers/factory.js +2 -1
- package/dist/parsers/factory.js.map +1 -1
- package/dist/parsers/openapi.d.ts +4 -0
- package/dist/parsers/openapi.d.ts.map +1 -1
- package/dist/parsers/openapi.js +107 -20
- package/dist/parsers/openapi.js.map +1 -1
- package/dist/plugins/branded-types.d.ts.map +1 -1
- package/dist/plugins/branded-types.js +231 -365
- package/dist/plugins/branded-types.js.map +1 -1
- package/dist/plugins/builtin.d.ts.map +1 -1
- package/dist/plugins/builtin.js +6 -141
- package/dist/plugins/builtin.js.map +1 -1
- package/dist/plugins/index.d.ts +4 -0
- package/dist/plugins/index.d.ts.map +1 -1
- package/dist/plugins/index.js +17 -0
- package/dist/plugins/index.js.map +1 -1
- package/dist/plugins/loader.d.ts +5 -1
- package/dist/plugins/loader.d.ts.map +1 -1
- package/dist/plugins/loader.js +42 -5
- package/dist/plugins/loader.js.map +1 -1
- package/dist/plugins/manager.d.ts.map +1 -1
- package/dist/plugins/manager.js +7 -7
- package/dist/plugins/manager.js.map +1 -1
- package/dist/plugins/performance-monitoring.d.ts +31 -1
- package/dist/plugins/performance-monitoring.d.ts.map +1 -1
- package/dist/plugins/performance-monitoring.js +146 -207
- package/dist/plugins/performance-monitoring.js.map +1 -1
- package/dist/plugins/request-deduplication.d.ts.map +1 -1
- package/dist/plugins/request-deduplication.js +151 -586
- package/dist/plugins/request-deduplication.js.map +1 -1
- package/dist/plugins/zod-validation.d.ts +23 -15
- package/dist/plugins/zod-validation.d.ts.map +1 -1
- package/dist/plugins/zod-validation.js +392 -268
- package/dist/plugins/zod-validation.js.map +1 -1
- package/dist/types/core.d.ts +20 -3
- package/dist/types/core.d.ts.map +1 -1
- package/dist/types/core.js +2 -0
- package/dist/types/core.js.map +1 -1
- package/dist/types/openapi.d.ts +14 -1
- package/dist/types/openapi.d.ts.map +1 -1
- package/docs/ADVANCED_PLUGINS_IMPLEMENTATION.md +24 -355
- package/docs/ADVANCED_PLUGIN_DEVELOPMENT.md +33 -309
- package/docs/API.md +80 -601
- package/docs/AUDIT.md +49 -0
- package/docs/CONFIGURATION.md +81 -715
- package/docs/FASTAPI_TUTORIAL.md +109 -1497
- package/docs/NAMING_MIGRATION.md +59 -0
- package/docs/PLUGINS.md +49 -687
- package/docs/PLUGIN_DEVELOPMENT.md +53 -720
- package/docs/REAUDIT.md +399 -0
- package/docs/RELEASING.md +88 -0
- package/docs/REMEDIATION.md +58 -0
- package/docs/superpowers/plans/2026-10-10-audit-remediation.md +113 -0
- package/package.json +114 -95
- package/dist/__tests__/setup.d.ts +0 -6
- package/dist/__tests__/setup.d.ts.map +0 -1
- package/dist/__tests__/setup.js +0 -30
- package/dist/__tests__/setup.js.map +0 -1
- package/dist/__tests__/test-config.d.ts +0 -36
- package/dist/__tests__/test-config.d.ts.map +0 -1
- package/dist/__tests__/test-config.js +0 -562
- package/dist/__tests__/test-config.js.map +0 -1
package/dist/cli/index.d.ts
CHANGED
|
@@ -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
|
package/dist/cli/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":";AAEA;;;GAGG;
|
|
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(
|
|
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"
|
|
90
|
-
.option("--
|
|
91
|
-
.option("--
|
|
92
|
-
.option("--
|
|
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("--
|
|
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("
|
|
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.
|
|
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
|
-
|
|
316
|
-
|
|
317
|
-
if (
|
|
318
|
-
const
|
|
319
|
-
|
|
320
|
-
|
|
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
|
-
|
|
488
|
+
const parsed = JSON.parse(content);
|
|
489
|
+
return (0, configuration_1.resolveConfiguration)(parsed);
|
|
403
490
|
}
|
|
404
|
-
else if (filePath.endsWith(".js") || filePath.endsWith(".
|
|
405
|
-
const
|
|
406
|
-
|
|
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
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
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
|
|
526
|
-
|
|
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,
|
|
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 =
|
|
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,14 +686,14 @@ 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
|
-
...
|
|
691
|
+
...this.templateConfig(options),
|
|
586
692
|
outputDir: "./src/generated",
|
|
587
693
|
typePrefix: "",
|
|
588
694
|
generateTypes: true,
|
|
589
|
-
generateApiClient:
|
|
590
|
-
generateHooks:
|
|
695
|
+
generateApiClient: options.template !== 'types',
|
|
696
|
+
generateHooks: options.template === 'react',
|
|
591
697
|
useStrictTypes: true,
|
|
592
698
|
namingConvention: "camelCase",
|
|
593
699
|
typeSuffix: "",
|
|
@@ -597,17 +703,28 @@ class SchemanticCli {
|
|
|
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.
|
|
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 ===
|
|
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
|
-
|
|
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.
|
|
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
|