ng-openapi 0.0.40 → 0.0.41-pr-8-feature-url-support-7d492ee.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.
- package/cli.cjs +191 -47
- package/index.d.ts +38 -32
- package/index.js +169 -34
- package/package.json +1 -1
package/cli.cjs
CHANGED
|
@@ -33,14 +33,117 @@ var fs4 = __toESM(require("fs"));
|
|
|
33
33
|
var fs = __toESM(require("fs"));
|
|
34
34
|
var path = __toESM(require("path"));
|
|
35
35
|
var yaml = __toESM(require("js-yaml"));
|
|
36
|
-
var SwaggerParser = class {
|
|
36
|
+
var SwaggerParser = class _SwaggerParser {
|
|
37
37
|
static {
|
|
38
38
|
__name(this, "SwaggerParser");
|
|
39
39
|
}
|
|
40
40
|
spec;
|
|
41
|
-
constructor(
|
|
42
|
-
const
|
|
43
|
-
|
|
41
|
+
constructor(spec, config) {
|
|
42
|
+
const generateClient = config.generateClientIf?.(spec) ?? true;
|
|
43
|
+
if (!generateClient) {
|
|
44
|
+
throw new Error("Client generation is disabled by configuration. Check your `generateClientIf` condition.");
|
|
45
|
+
}
|
|
46
|
+
this.spec = spec;
|
|
47
|
+
}
|
|
48
|
+
static async create(swaggerPathOrUrl, config) {
|
|
49
|
+
const swaggerContent = await _SwaggerParser.loadContent(swaggerPathOrUrl);
|
|
50
|
+
const spec = _SwaggerParser.parseSpecContent(swaggerContent, swaggerPathOrUrl);
|
|
51
|
+
return new _SwaggerParser(spec, config);
|
|
52
|
+
}
|
|
53
|
+
static async loadContent(pathOrUrl) {
|
|
54
|
+
if (_SwaggerParser.isUrl(pathOrUrl)) {
|
|
55
|
+
return await _SwaggerParser.fetchUrlContent(pathOrUrl);
|
|
56
|
+
} else {
|
|
57
|
+
return fs.readFileSync(pathOrUrl, "utf8");
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
static isUrl(input) {
|
|
61
|
+
try {
|
|
62
|
+
new URL(input);
|
|
63
|
+
return true;
|
|
64
|
+
} catch {
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
static async fetchUrlContent(url) {
|
|
69
|
+
try {
|
|
70
|
+
const response = await fetch(url, {
|
|
71
|
+
method: "GET",
|
|
72
|
+
headers: {
|
|
73
|
+
"Accept": "application/json, application/yaml, text/yaml, text/plain, */*",
|
|
74
|
+
"User-Agent": "ng-openapi"
|
|
75
|
+
},
|
|
76
|
+
// 30 second timeout
|
|
77
|
+
signal: AbortSignal.timeout(3e4)
|
|
78
|
+
});
|
|
79
|
+
if (!response.ok) {
|
|
80
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
81
|
+
}
|
|
82
|
+
const content = await response.text();
|
|
83
|
+
if (!content || content.trim() === "") {
|
|
84
|
+
throw new Error(`Empty response from URL: ${url}`);
|
|
85
|
+
}
|
|
86
|
+
return content;
|
|
87
|
+
} catch (error) {
|
|
88
|
+
let errorMessage = `Failed to fetch content from URL: ${url}`;
|
|
89
|
+
if (error.name === "AbortError") {
|
|
90
|
+
errorMessage += " - Request timeout (30s)";
|
|
91
|
+
} else if (error.message) {
|
|
92
|
+
errorMessage += ` - ${error.message}`;
|
|
93
|
+
}
|
|
94
|
+
throw new Error(errorMessage);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
static parseSpecContent(content, pathOrUrl) {
|
|
98
|
+
let format;
|
|
99
|
+
if (_SwaggerParser.isUrl(pathOrUrl)) {
|
|
100
|
+
const urlPath = new URL(pathOrUrl).pathname.toLowerCase();
|
|
101
|
+
if (urlPath.endsWith(".json")) {
|
|
102
|
+
format = "json";
|
|
103
|
+
} else if (urlPath.endsWith(".yaml") || urlPath.endsWith(".yml")) {
|
|
104
|
+
format = "yaml";
|
|
105
|
+
} else {
|
|
106
|
+
format = _SwaggerParser.detectFormat(content);
|
|
107
|
+
}
|
|
108
|
+
} else {
|
|
109
|
+
const extension = path.extname(pathOrUrl).toLowerCase();
|
|
110
|
+
switch (extension) {
|
|
111
|
+
case ".json":
|
|
112
|
+
format = "json";
|
|
113
|
+
break;
|
|
114
|
+
case ".yaml":
|
|
115
|
+
format = "yaml";
|
|
116
|
+
break;
|
|
117
|
+
case ".yml":
|
|
118
|
+
format = "yml";
|
|
119
|
+
break;
|
|
120
|
+
default:
|
|
121
|
+
format = _SwaggerParser.detectFormat(content);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
try {
|
|
125
|
+
switch (format) {
|
|
126
|
+
case "json":
|
|
127
|
+
return JSON.parse(content);
|
|
128
|
+
case "yaml":
|
|
129
|
+
case "yml":
|
|
130
|
+
return yaml.load(content);
|
|
131
|
+
default:
|
|
132
|
+
throw new Error(`Unable to determine format for: ${pathOrUrl}`);
|
|
133
|
+
}
|
|
134
|
+
} catch (error) {
|
|
135
|
+
throw new Error(`Failed to parse ${format.toUpperCase()} content from: ${pathOrUrl}. Error: ${error instanceof Error ? error.message : error}`);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
static detectFormat(content) {
|
|
139
|
+
const trimmed = content.trim();
|
|
140
|
+
if (trimmed.startsWith("{") || trimmed.startsWith("[")) {
|
|
141
|
+
return "json";
|
|
142
|
+
}
|
|
143
|
+
if (trimmed.includes("openapi:") || trimmed.includes("swagger:") || trimmed.includes("---") || /^[a-zA-Z][a-zA-Z0-9_]*\s*:/.test(trimmed)) {
|
|
144
|
+
return "yaml";
|
|
145
|
+
}
|
|
146
|
+
return "json";
|
|
44
147
|
}
|
|
45
148
|
getDefinitions() {
|
|
46
149
|
return this.spec.definitions || this.spec.components?.schemas || {};
|
|
@@ -81,18 +184,6 @@ var SwaggerParser = class {
|
|
|
81
184
|
}
|
|
82
185
|
return null;
|
|
83
186
|
}
|
|
84
|
-
parseSpecFile(content, filePath) {
|
|
85
|
-
const extension = path.extname(filePath).toLowerCase();
|
|
86
|
-
switch (extension) {
|
|
87
|
-
case ".json":
|
|
88
|
-
return JSON.parse(content);
|
|
89
|
-
case ".yaml":
|
|
90
|
-
case ".yml":
|
|
91
|
-
return yaml.load(content);
|
|
92
|
-
default:
|
|
93
|
-
throw new Error(`Failed to parse ${extension || "specification"} file: ${filePath}. Supported formats are .json, .yaml, and .yml.`);
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
187
|
};
|
|
97
188
|
|
|
98
189
|
// src/lib/core/generator.ts
|
|
@@ -137,7 +228,7 @@ var BASE_INTERCEPTOR_HEADER_COMMENT = /* @__PURE__ */ __name((clientName) => def
|
|
|
137
228
|
`, "BASE_INTERCEPTOR_HEADER_COMMENT");
|
|
138
229
|
|
|
139
230
|
// src/lib/generators/type/type.generator.ts
|
|
140
|
-
var TypeGenerator = class {
|
|
231
|
+
var TypeGenerator = class _TypeGenerator {
|
|
141
232
|
static {
|
|
142
233
|
__name(this, "TypeGenerator");
|
|
143
234
|
}
|
|
@@ -146,7 +237,7 @@ var TypeGenerator = class {
|
|
|
146
237
|
sourceFile;
|
|
147
238
|
generatedTypes = /* @__PURE__ */ new Set();
|
|
148
239
|
config;
|
|
149
|
-
constructor(
|
|
240
|
+
constructor(parser, outputRoot, config) {
|
|
150
241
|
this.config = config;
|
|
151
242
|
const outputPath = outputRoot + "/models/index.ts";
|
|
152
243
|
this.project = new import_ts_morph.Project({
|
|
@@ -158,15 +249,14 @@ var TypeGenerator = class {
|
|
|
158
249
|
...this.config.compilerOptions
|
|
159
250
|
}
|
|
160
251
|
});
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
}
|
|
252
|
+
this.parser = parser;
|
|
253
|
+
this.sourceFile = this.project.createSourceFile(outputPath, "", {
|
|
254
|
+
overwrite: true
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
static async create(swaggerPathOrUrl, outputRoot, config) {
|
|
258
|
+
const parser = await SwaggerParser.create(swaggerPathOrUrl, config);
|
|
259
|
+
return new _TypeGenerator(parser, outputRoot, config);
|
|
170
260
|
}
|
|
171
261
|
generate() {
|
|
172
262
|
try {
|
|
@@ -2008,7 +2098,7 @@ var ServiceMethodGenerator = class {
|
|
|
2008
2098
|
};
|
|
2009
2099
|
|
|
2010
2100
|
// src/lib/generators/service/service.generator.ts
|
|
2011
|
-
var ServiceGenerator = class {
|
|
2101
|
+
var ServiceGenerator = class _ServiceGenerator {
|
|
2012
2102
|
static {
|
|
2013
2103
|
__name(this, "ServiceGenerator");
|
|
2014
2104
|
}
|
|
@@ -2017,10 +2107,10 @@ var ServiceGenerator = class {
|
|
|
2017
2107
|
spec;
|
|
2018
2108
|
config;
|
|
2019
2109
|
methodGenerator;
|
|
2020
|
-
constructor(
|
|
2110
|
+
constructor(parser, project, config) {
|
|
2021
2111
|
this.config = config;
|
|
2022
2112
|
this.project = project;
|
|
2023
|
-
this.parser =
|
|
2113
|
+
this.parser = parser;
|
|
2024
2114
|
this.spec = this.parser.getSpec();
|
|
2025
2115
|
if (!this.parser.isValidSpec()) {
|
|
2026
2116
|
const versionInfo = this.parser.getSpecVersion();
|
|
@@ -2028,6 +2118,10 @@ var ServiceGenerator = class {
|
|
|
2028
2118
|
}
|
|
2029
2119
|
this.methodGenerator = new ServiceMethodGenerator(config);
|
|
2030
2120
|
}
|
|
2121
|
+
static async create(swaggerPathOrUrl, project, config) {
|
|
2122
|
+
const parser = await SwaggerParser.create(swaggerPathOrUrl, config);
|
|
2123
|
+
return new _ServiceGenerator(parser, project, config);
|
|
2124
|
+
}
|
|
2031
2125
|
generate(outputRoot) {
|
|
2032
2126
|
const outputDir = path8.join(outputRoot, "services");
|
|
2033
2127
|
const paths = this.extractPaths();
|
|
@@ -2335,12 +2429,36 @@ var ServiceIndexGenerator = class {
|
|
|
2335
2429
|
|
|
2336
2430
|
// src/lib/core/generator.ts
|
|
2337
2431
|
var fs3 = __toESM(require("fs"));
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2432
|
+
function isUrl(input) {
|
|
2433
|
+
try {
|
|
2434
|
+
new URL(input);
|
|
2435
|
+
return true;
|
|
2436
|
+
} catch {
|
|
2437
|
+
return false;
|
|
2438
|
+
}
|
|
2439
|
+
}
|
|
2440
|
+
__name(isUrl, "isUrl");
|
|
2441
|
+
function validateInput(input) {
|
|
2442
|
+
if (isUrl(input)) {
|
|
2443
|
+
const url = new URL(input);
|
|
2444
|
+
if (![
|
|
2445
|
+
"http:",
|
|
2446
|
+
"https:"
|
|
2447
|
+
].includes(url.protocol)) {
|
|
2448
|
+
throw new Error(`Unsupported URL protocol: ${url.protocol}. Only HTTP and HTTPS are supported.`);
|
|
2449
|
+
}
|
|
2450
|
+
} else {
|
|
2451
|
+
if (!fs3.existsSync(input)) {
|
|
2452
|
+
throw new Error(`Input file not found: ${input}`);
|
|
2453
|
+
}
|
|
2341
2454
|
}
|
|
2455
|
+
}
|
|
2456
|
+
__name(validateInput, "validateInput");
|
|
2457
|
+
async function generateFromConfig(config) {
|
|
2458
|
+
validateInput(config.input);
|
|
2342
2459
|
const outputPath = config.output;
|
|
2343
2460
|
const generateServices = config.options.generateServices ?? true;
|
|
2461
|
+
const inputType = isUrl(config.input) ? "URL" : "file";
|
|
2344
2462
|
if (!fs3.existsSync(outputPath)) {
|
|
2345
2463
|
fs3.mkdirSync(outputPath, {
|
|
2346
2464
|
recursive: true
|
|
@@ -2356,7 +2474,8 @@ async function generateFromConfig(config) {
|
|
|
2356
2474
|
...config.compilerOptions
|
|
2357
2475
|
}
|
|
2358
2476
|
});
|
|
2359
|
-
|
|
2477
|
+
console.log(`\u{1F4E1} Processing OpenAPI specification from ${inputType}: ${config.input}`);
|
|
2478
|
+
const typeGenerator = await TypeGenerator.create(config.input, outputPath, config);
|
|
2360
2479
|
typeGenerator.generate();
|
|
2361
2480
|
console.log(`\u2705 TypeScript interfaces generated`);
|
|
2362
2481
|
if (generateServices) {
|
|
@@ -2368,7 +2487,7 @@ async function generateFromConfig(config) {
|
|
|
2368
2487
|
}
|
|
2369
2488
|
const fileDownloadHelper = new FileDownloadGenerator(project);
|
|
2370
2489
|
fileDownloadHelper.generate(outputPath);
|
|
2371
|
-
const serviceGenerator =
|
|
2490
|
+
const serviceGenerator = await ServiceGenerator.create(config.input, project, config);
|
|
2372
2491
|
serviceGenerator.generate(outputPath);
|
|
2373
2492
|
const indexGenerator = new ServiceIndexGenerator(project);
|
|
2374
2493
|
indexGenerator.generateIndex(outputPath);
|
|
@@ -2380,14 +2499,19 @@ async function generateFromConfig(config) {
|
|
|
2380
2499
|
}
|
|
2381
2500
|
const mainIndexGenerator = new MainIndexGenerator(project, config);
|
|
2382
2501
|
mainIndexGenerator.generateMainIndex(outputPath);
|
|
2502
|
+
const sourceInfo = `from ${inputType}: ${config.input}`;
|
|
2383
2503
|
if (config.clientName) {
|
|
2384
|
-
console.log(`\u{1F389} ${config.clientName} Generation completed successfully
|
|
2504
|
+
console.log(`\u{1F389} ${config.clientName} Generation completed successfully ${sourceInfo} -> ${outputPath}`);
|
|
2385
2505
|
} else {
|
|
2386
|
-
console.log(
|
|
2506
|
+
console.log(`\u{1F389} Generation completed successfully ${sourceInfo} -> ${outputPath}`);
|
|
2387
2507
|
}
|
|
2388
2508
|
} catch (error) {
|
|
2389
2509
|
if (error instanceof Error) {
|
|
2390
2510
|
console.error("\u274C Error during generation:", error.message);
|
|
2511
|
+
if (error.message.includes("fetch") || error.message.includes("Failed to fetch")) {
|
|
2512
|
+
console.error("\u{1F4A1} Tip: Make sure the URL is accessible and returns a valid OpenAPI/Swagger specification");
|
|
2513
|
+
console.error("\u{1F4A1} Alternative: Download the specification file locally and use the file path instead");
|
|
2514
|
+
}
|
|
2391
2515
|
} else {
|
|
2392
2516
|
console.error("\u274C Unknown error during generation:", error);
|
|
2393
2517
|
}
|
|
@@ -2397,7 +2521,7 @@ async function generateFromConfig(config) {
|
|
|
2397
2521
|
__name(generateFromConfig, "generateFromConfig");
|
|
2398
2522
|
|
|
2399
2523
|
// package.json
|
|
2400
|
-
var version = "0.0.
|
|
2524
|
+
var version = "0.0.40";
|
|
2401
2525
|
|
|
2402
2526
|
// src/lib/cli.ts
|
|
2403
2527
|
var program = new import_commander.Command();
|
|
@@ -2422,7 +2546,26 @@ async function loadConfigFile(configPath) {
|
|
|
2422
2546
|
}
|
|
2423
2547
|
}
|
|
2424
2548
|
__name(loadConfigFile, "loadConfigFile");
|
|
2425
|
-
function
|
|
2549
|
+
function isUrl2(input) {
|
|
2550
|
+
try {
|
|
2551
|
+
new URL(input);
|
|
2552
|
+
return true;
|
|
2553
|
+
} catch {
|
|
2554
|
+
return false;
|
|
2555
|
+
}
|
|
2556
|
+
}
|
|
2557
|
+
__name(isUrl2, "isUrl");
|
|
2558
|
+
function validateInput2(inputPath) {
|
|
2559
|
+
if (isUrl2(inputPath)) {
|
|
2560
|
+
const url = new URL(inputPath);
|
|
2561
|
+
if (![
|
|
2562
|
+
"http:",
|
|
2563
|
+
"https:"
|
|
2564
|
+
].includes(url.protocol)) {
|
|
2565
|
+
throw new Error(`Unsupported URL protocol: ${url.protocol}. Only HTTP and HTTPS are supported.`);
|
|
2566
|
+
}
|
|
2567
|
+
return;
|
|
2568
|
+
}
|
|
2426
2569
|
if (!fs4.existsSync(inputPath)) {
|
|
2427
2570
|
throw new Error(`Input file not found: ${inputPath}`);
|
|
2428
2571
|
}
|
|
@@ -2436,18 +2579,17 @@ function validateInputFile(inputPath) {
|
|
|
2436
2579
|
throw new Error(`Failed to parse ${extension || "specification"}. Supported formats are .json, .yaml, and .yml.`);
|
|
2437
2580
|
}
|
|
2438
2581
|
}
|
|
2439
|
-
__name(
|
|
2582
|
+
__name(validateInput2, "validateInput");
|
|
2440
2583
|
async function generateFromOptions(options) {
|
|
2441
2584
|
try {
|
|
2442
2585
|
if (options.config) {
|
|
2443
2586
|
const config = await loadConfigFile(options.config);
|
|
2444
|
-
|
|
2587
|
+
validateInput2(config.input);
|
|
2445
2588
|
await generateFromConfig(config);
|
|
2446
2589
|
} else if (options.input) {
|
|
2447
|
-
|
|
2448
|
-
validateInputFile(inputPath);
|
|
2590
|
+
validateInput2(options.input);
|
|
2449
2591
|
const config = {
|
|
2450
|
-
input:
|
|
2592
|
+
input: options.input,
|
|
2451
2593
|
output: options.output || "./src/generated",
|
|
2452
2594
|
options: {
|
|
2453
2595
|
dateType: options.dateType || "Date",
|
|
@@ -2469,10 +2611,10 @@ async function generateFromOptions(options) {
|
|
|
2469
2611
|
}
|
|
2470
2612
|
}
|
|
2471
2613
|
__name(generateFromOptions, "generateFromOptions");
|
|
2472
|
-
program.name("ng-openapi").description("Generate Angular services and types from OpenAPI/Swagger specifications (JSON, YAML, YML)").version(version).option("-c, --config <path>", "Path to configuration file").option("-i, --input <path>", "Path to OpenAPI/Swagger specification
|
|
2614
|
+
program.name("ng-openapi").description("Generate Angular services and types from OpenAPI/Swagger specifications (JSON, YAML, YML) from files or URLs").version(version).option("-c, --config <path>", "Path to configuration file").option("-i, --input <path>", "Path or URL to OpenAPI/Swagger specification (.json, .yaml, .yml)").option("-o, --output <path>", "Output directory", "./src/generated").option("--types-only", "Generate only TypeScript interfaces").option("--date-type <type>", "Date type to use (string | Date)", "Date").action(async (options) => {
|
|
2473
2615
|
await generateFromOptions(options);
|
|
2474
2616
|
});
|
|
2475
|
-
program.command("generate").alias("gen").description("Generate code from OpenAPI/Swagger specification").option("-c, --config <path>", "Path to configuration file").option("-i, --input <path>", "Path to OpenAPI/Swagger specification
|
|
2617
|
+
program.command("generate").alias("gen").description("Generate code from OpenAPI/Swagger specification").option("-c, --config <path>", "Path to configuration file").option("-i, --input <path>", "Path or URL to OpenAPI/Swagger specification (.json, .yaml, .yml)").option("-o, --output <path>", "Output directory", "./src/generated").option("--types-only", "Generate only TypeScript interfaces").option("--date-type <type>", "Date type to use (string | Date)", "Date").action(async (options) => {
|
|
2476
2618
|
await generateFromOptions(options);
|
|
2477
2619
|
});
|
|
2478
2620
|
program.on("--help", () => {
|
|
@@ -2482,8 +2624,10 @@ program.on("--help", () => {
|
|
|
2482
2624
|
console.log(" $ ng-openapi -i ./swagger.json -o ./src/api");
|
|
2483
2625
|
console.log(" $ ng-openapi -i ./openapi.yaml -o ./src/api");
|
|
2484
2626
|
console.log(" $ ng-openapi -i ./api-spec.yml -o ./src/api");
|
|
2627
|
+
console.log(" $ ng-openapi -i https://api.example.com/openapi.json -o ./src/api");
|
|
2628
|
+
console.log(" $ ng-openapi -i https://petstore.swagger.io/v2/swagger.json -o ./src/api");
|
|
2485
2629
|
console.log(" $ ng-openapi generate -c ./openapi.config.ts");
|
|
2486
|
-
console.log(" $ ng-openapi generate -i
|
|
2630
|
+
console.log(" $ ng-openapi generate -i https://api.example.com/swagger.yaml --types-only");
|
|
2487
2631
|
});
|
|
2488
2632
|
program.parse();
|
|
2489
2633
|
//# sourceMappingURL=cli.cjs.map
|
package/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { ParameterType, XML, ExternalDocs, Info, Path, BodyParameter, QueryParameter, Security, Tag } from 'swagger-schema-official';
|
|
2
1
|
import { ScriptTarget, ModuleKind } from 'ts-morph';
|
|
3
2
|
import { HttpInterceptor } from '@angular/common/http';
|
|
3
|
+
import { Info, ExternalDocs, Path, ParameterType, XML, BodyParameter, QueryParameter, Security, Tag } from 'swagger-schema-official';
|
|
4
4
|
|
|
5
5
|
interface MethodGenerationContext {
|
|
6
6
|
pathParams: Array<{
|
|
@@ -26,35 +26,6 @@ interface TypeSchema {
|
|
|
26
26
|
[key: string]: any;
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
interface GeneratorConfig {
|
|
30
|
-
input: string;
|
|
31
|
-
output: string;
|
|
32
|
-
clientName?: string;
|
|
33
|
-
options: {
|
|
34
|
-
dateType: "string" | "Date";
|
|
35
|
-
enumStyle: "enum" | "union";
|
|
36
|
-
generateServices?: boolean;
|
|
37
|
-
generateEnumBasedOnDescription?: boolean;
|
|
38
|
-
customHeaders?: Record<string, string>;
|
|
39
|
-
responseTypeMapping?: {
|
|
40
|
-
[contentType: string]: "json" | "blob" | "arraybuffer" | "text";
|
|
41
|
-
};
|
|
42
|
-
customizeMethodName?: (operationId: string) => string;
|
|
43
|
-
};
|
|
44
|
-
compilerOptions?: {
|
|
45
|
-
declaration?: boolean;
|
|
46
|
-
target?: ScriptTarget;
|
|
47
|
-
module?: ModuleKind;
|
|
48
|
-
strict?: boolean;
|
|
49
|
-
};
|
|
50
|
-
}
|
|
51
|
-
interface NgOpenapiClientConfig {
|
|
52
|
-
clientName: string;
|
|
53
|
-
basePath: string;
|
|
54
|
-
enableDateTransform?: boolean;
|
|
55
|
-
interceptors?: (new (...args: HttpInterceptor[]) => HttpInterceptor)[];
|
|
56
|
-
}
|
|
57
|
-
|
|
58
29
|
interface Parameter {
|
|
59
30
|
name: string;
|
|
60
31
|
in: "query" | "path" | "header" | "cookie";
|
|
@@ -162,9 +133,45 @@ type EnumValueObject = {
|
|
|
162
133
|
Value: number;
|
|
163
134
|
};
|
|
164
135
|
|
|
136
|
+
interface GeneratorConfig {
|
|
137
|
+
input: string;
|
|
138
|
+
output: string;
|
|
139
|
+
clientName?: string;
|
|
140
|
+
generateClientIf?: (swaggerSpec: SwaggerSpec) => boolean;
|
|
141
|
+
options: {
|
|
142
|
+
dateType: "string" | "Date";
|
|
143
|
+
enumStyle: "enum" | "union";
|
|
144
|
+
generateServices?: boolean;
|
|
145
|
+
generateEnumBasedOnDescription?: boolean;
|
|
146
|
+
customHeaders?: Record<string, string>;
|
|
147
|
+
responseTypeMapping?: {
|
|
148
|
+
[contentType: string]: "json" | "blob" | "arraybuffer" | "text";
|
|
149
|
+
};
|
|
150
|
+
customizeMethodName?: (operationId: string) => string;
|
|
151
|
+
};
|
|
152
|
+
compilerOptions?: {
|
|
153
|
+
declaration?: boolean;
|
|
154
|
+
target?: ScriptTarget;
|
|
155
|
+
module?: ModuleKind;
|
|
156
|
+
strict?: boolean;
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
interface NgOpenapiClientConfig {
|
|
160
|
+
clientName: string;
|
|
161
|
+
basePath: string;
|
|
162
|
+
enableDateTransform?: boolean;
|
|
163
|
+
interceptors?: (new (...args: HttpInterceptor[]) => HttpInterceptor)[];
|
|
164
|
+
}
|
|
165
|
+
|
|
165
166
|
declare class SwaggerParser {
|
|
166
167
|
private readonly spec;
|
|
167
|
-
constructor(
|
|
168
|
+
private constructor();
|
|
169
|
+
static create(swaggerPathOrUrl: string, config: GeneratorConfig): Promise<SwaggerParser>;
|
|
170
|
+
private static loadContent;
|
|
171
|
+
private static isUrl;
|
|
172
|
+
private static fetchUrlContent;
|
|
173
|
+
private static parseSpecContent;
|
|
174
|
+
private static detectFormat;
|
|
168
175
|
getDefinitions(): Record<string, SwaggerDefinition>;
|
|
169
176
|
getDefinition(name: string): SwaggerDefinition | undefined;
|
|
170
177
|
resolveReference(ref: string): SwaggerDefinition | undefined;
|
|
@@ -176,7 +183,6 @@ declare class SwaggerParser {
|
|
|
176
183
|
type: "swagger" | "openapi";
|
|
177
184
|
version: string;
|
|
178
185
|
} | null;
|
|
179
|
-
private parseSpecFile;
|
|
180
186
|
}
|
|
181
187
|
|
|
182
188
|
/**
|
package/index.js
CHANGED
|
@@ -83,10 +83,120 @@ var fs = __toESM(require("fs"));
|
|
|
83
83
|
var path = __toESM(require("path"));
|
|
84
84
|
var yaml = __toESM(require("js-yaml"));
|
|
85
85
|
var _SwaggerParser = class _SwaggerParser {
|
|
86
|
-
constructor(
|
|
86
|
+
constructor(spec, config) {
|
|
87
87
|
__publicField(this, "spec");
|
|
88
|
-
|
|
89
|
-
|
|
88
|
+
var _a, _b;
|
|
89
|
+
const generateClient = (_b = (_a = config.generateClientIf) == null ? void 0 : _a.call(config, spec)) != null ? _b : true;
|
|
90
|
+
if (!generateClient) {
|
|
91
|
+
throw new Error("Client generation is disabled by configuration. Check your `generateClientIf` condition.");
|
|
92
|
+
}
|
|
93
|
+
this.spec = spec;
|
|
94
|
+
}
|
|
95
|
+
static create(swaggerPathOrUrl, config) {
|
|
96
|
+
return __async(this, null, function* () {
|
|
97
|
+
const swaggerContent = yield _SwaggerParser.loadContent(swaggerPathOrUrl);
|
|
98
|
+
const spec = _SwaggerParser.parseSpecContent(swaggerContent, swaggerPathOrUrl);
|
|
99
|
+
return new _SwaggerParser(spec, config);
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
static loadContent(pathOrUrl) {
|
|
103
|
+
return __async(this, null, function* () {
|
|
104
|
+
if (_SwaggerParser.isUrl(pathOrUrl)) {
|
|
105
|
+
return yield _SwaggerParser.fetchUrlContent(pathOrUrl);
|
|
106
|
+
} else {
|
|
107
|
+
return fs.readFileSync(pathOrUrl, "utf8");
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
static isUrl(input) {
|
|
112
|
+
try {
|
|
113
|
+
new URL(input);
|
|
114
|
+
return true;
|
|
115
|
+
} catch (e) {
|
|
116
|
+
return false;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
static fetchUrlContent(url) {
|
|
120
|
+
return __async(this, null, function* () {
|
|
121
|
+
try {
|
|
122
|
+
const response = yield fetch(url, {
|
|
123
|
+
method: "GET",
|
|
124
|
+
headers: {
|
|
125
|
+
"Accept": "application/json, application/yaml, text/yaml, text/plain, */*",
|
|
126
|
+
"User-Agent": "ng-openapi"
|
|
127
|
+
},
|
|
128
|
+
// 30 second timeout
|
|
129
|
+
signal: AbortSignal.timeout(3e4)
|
|
130
|
+
});
|
|
131
|
+
if (!response.ok) {
|
|
132
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
133
|
+
}
|
|
134
|
+
const content = yield response.text();
|
|
135
|
+
if (!content || content.trim() === "") {
|
|
136
|
+
throw new Error(`Empty response from URL: ${url}`);
|
|
137
|
+
}
|
|
138
|
+
return content;
|
|
139
|
+
} catch (error) {
|
|
140
|
+
let errorMessage = `Failed to fetch content from URL: ${url}`;
|
|
141
|
+
if (error.name === "AbortError") {
|
|
142
|
+
errorMessage += " - Request timeout (30s)";
|
|
143
|
+
} else if (error.message) {
|
|
144
|
+
errorMessage += ` - ${error.message}`;
|
|
145
|
+
}
|
|
146
|
+
throw new Error(errorMessage);
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
static parseSpecContent(content, pathOrUrl) {
|
|
151
|
+
let format;
|
|
152
|
+
if (_SwaggerParser.isUrl(pathOrUrl)) {
|
|
153
|
+
const urlPath = new URL(pathOrUrl).pathname.toLowerCase();
|
|
154
|
+
if (urlPath.endsWith(".json")) {
|
|
155
|
+
format = "json";
|
|
156
|
+
} else if (urlPath.endsWith(".yaml") || urlPath.endsWith(".yml")) {
|
|
157
|
+
format = "yaml";
|
|
158
|
+
} else {
|
|
159
|
+
format = _SwaggerParser.detectFormat(content);
|
|
160
|
+
}
|
|
161
|
+
} else {
|
|
162
|
+
const extension = path.extname(pathOrUrl).toLowerCase();
|
|
163
|
+
switch (extension) {
|
|
164
|
+
case ".json":
|
|
165
|
+
format = "json";
|
|
166
|
+
break;
|
|
167
|
+
case ".yaml":
|
|
168
|
+
format = "yaml";
|
|
169
|
+
break;
|
|
170
|
+
case ".yml":
|
|
171
|
+
format = "yml";
|
|
172
|
+
break;
|
|
173
|
+
default:
|
|
174
|
+
format = _SwaggerParser.detectFormat(content);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
try {
|
|
178
|
+
switch (format) {
|
|
179
|
+
case "json":
|
|
180
|
+
return JSON.parse(content);
|
|
181
|
+
case "yaml":
|
|
182
|
+
case "yml":
|
|
183
|
+
return yaml.load(content);
|
|
184
|
+
default:
|
|
185
|
+
throw new Error(`Unable to determine format for: ${pathOrUrl}`);
|
|
186
|
+
}
|
|
187
|
+
} catch (error) {
|
|
188
|
+
throw new Error(`Failed to parse ${format.toUpperCase()} content from: ${pathOrUrl}. Error: ${error instanceof Error ? error.message : error}`);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
static detectFormat(content) {
|
|
192
|
+
const trimmed = content.trim();
|
|
193
|
+
if (trimmed.startsWith("{") || trimmed.startsWith("[")) {
|
|
194
|
+
return "json";
|
|
195
|
+
}
|
|
196
|
+
if (trimmed.includes("openapi:") || trimmed.includes("swagger:") || trimmed.includes("---") || /^[a-zA-Z][a-zA-Z0-9_]*\s*:/.test(trimmed)) {
|
|
197
|
+
return "yaml";
|
|
198
|
+
}
|
|
199
|
+
return "json";
|
|
90
200
|
}
|
|
91
201
|
getDefinitions() {
|
|
92
202
|
var _a;
|
|
@@ -128,18 +238,6 @@ var _SwaggerParser = class _SwaggerParser {
|
|
|
128
238
|
}
|
|
129
239
|
return null;
|
|
130
240
|
}
|
|
131
|
-
parseSpecFile(content, filePath) {
|
|
132
|
-
const extension = path.extname(filePath).toLowerCase();
|
|
133
|
-
switch (extension) {
|
|
134
|
-
case ".json":
|
|
135
|
-
return JSON.parse(content);
|
|
136
|
-
case ".yaml":
|
|
137
|
-
case ".yml":
|
|
138
|
-
return yaml.load(content);
|
|
139
|
-
default:
|
|
140
|
-
throw new Error(`Failed to parse ${extension || "specification"} file: ${filePath}. Supported formats are .json, .yaml, and .yml.`);
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
241
|
};
|
|
144
242
|
__name(_SwaggerParser, "SwaggerParser");
|
|
145
243
|
var SwaggerParser = _SwaggerParser;
|
|
@@ -187,7 +285,7 @@ var BASE_INTERCEPTOR_HEADER_COMMENT = /* @__PURE__ */ __name((clientName) => def
|
|
|
187
285
|
|
|
188
286
|
// src/lib/generators/type/type.generator.ts
|
|
189
287
|
var _TypeGenerator = class _TypeGenerator {
|
|
190
|
-
constructor(
|
|
288
|
+
constructor(parser, outputRoot, config) {
|
|
191
289
|
__publicField(this, "project");
|
|
192
290
|
__publicField(this, "parser");
|
|
193
291
|
__publicField(this, "sourceFile");
|
|
@@ -203,15 +301,16 @@ var _TypeGenerator = class _TypeGenerator {
|
|
|
203
301
|
strict: true
|
|
204
302
|
}, this.config.compilerOptions)
|
|
205
303
|
});
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
304
|
+
this.parser = parser;
|
|
305
|
+
this.sourceFile = this.project.createSourceFile(outputPath, "", {
|
|
306
|
+
overwrite: true
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
static create(swaggerPathOrUrl, outputRoot, config) {
|
|
310
|
+
return __async(this, null, function* () {
|
|
311
|
+
const parser = yield SwaggerParser.create(swaggerPathOrUrl, config);
|
|
312
|
+
return new _TypeGenerator(parser, outputRoot, config);
|
|
313
|
+
});
|
|
215
314
|
}
|
|
216
315
|
generate() {
|
|
217
316
|
try {
|
|
@@ -2063,7 +2162,7 @@ var ServiceMethodGenerator = _ServiceMethodGenerator;
|
|
|
2063
2162
|
|
|
2064
2163
|
// src/lib/generators/service/service.generator.ts
|
|
2065
2164
|
var _ServiceGenerator = class _ServiceGenerator {
|
|
2066
|
-
constructor(
|
|
2165
|
+
constructor(parser, project, config) {
|
|
2067
2166
|
__publicField(this, "project");
|
|
2068
2167
|
__publicField(this, "parser");
|
|
2069
2168
|
__publicField(this, "spec");
|
|
@@ -2071,7 +2170,7 @@ var _ServiceGenerator = class _ServiceGenerator {
|
|
|
2071
2170
|
__publicField(this, "methodGenerator");
|
|
2072
2171
|
this.config = config;
|
|
2073
2172
|
this.project = project;
|
|
2074
|
-
this.parser =
|
|
2173
|
+
this.parser = parser;
|
|
2075
2174
|
this.spec = this.parser.getSpec();
|
|
2076
2175
|
if (!this.parser.isValidSpec()) {
|
|
2077
2176
|
const versionInfo = this.parser.getSpecVersion();
|
|
@@ -2079,6 +2178,12 @@ var _ServiceGenerator = class _ServiceGenerator {
|
|
|
2079
2178
|
}
|
|
2080
2179
|
this.methodGenerator = new ServiceMethodGenerator(config);
|
|
2081
2180
|
}
|
|
2181
|
+
static create(swaggerPathOrUrl, project, config) {
|
|
2182
|
+
return __async(this, null, function* () {
|
|
2183
|
+
const parser = yield SwaggerParser.create(swaggerPathOrUrl, config);
|
|
2184
|
+
return new _ServiceGenerator(parser, project, config);
|
|
2185
|
+
});
|
|
2186
|
+
}
|
|
2082
2187
|
generate(outputRoot) {
|
|
2083
2188
|
const outputDir = path8.join(outputRoot, "services");
|
|
2084
2189
|
const paths = this.extractPaths();
|
|
@@ -2388,14 +2493,38 @@ var ServiceIndexGenerator = _ServiceIndexGenerator;
|
|
|
2388
2493
|
|
|
2389
2494
|
// src/lib/core/generator.ts
|
|
2390
2495
|
var fs3 = __toESM(require("fs"));
|
|
2496
|
+
function isUrl(input) {
|
|
2497
|
+
try {
|
|
2498
|
+
new URL(input);
|
|
2499
|
+
return true;
|
|
2500
|
+
} catch (e) {
|
|
2501
|
+
return false;
|
|
2502
|
+
}
|
|
2503
|
+
}
|
|
2504
|
+
__name(isUrl, "isUrl");
|
|
2505
|
+
function validateInput(input) {
|
|
2506
|
+
if (isUrl(input)) {
|
|
2507
|
+
const url = new URL(input);
|
|
2508
|
+
if (![
|
|
2509
|
+
"http:",
|
|
2510
|
+
"https:"
|
|
2511
|
+
].includes(url.protocol)) {
|
|
2512
|
+
throw new Error(`Unsupported URL protocol: ${url.protocol}. Only HTTP and HTTPS are supported.`);
|
|
2513
|
+
}
|
|
2514
|
+
} else {
|
|
2515
|
+
if (!fs3.existsSync(input)) {
|
|
2516
|
+
throw new Error(`Input file not found: ${input}`);
|
|
2517
|
+
}
|
|
2518
|
+
}
|
|
2519
|
+
}
|
|
2520
|
+
__name(validateInput, "validateInput");
|
|
2391
2521
|
function generateFromConfig(config) {
|
|
2392
2522
|
return __async(this, null, function* () {
|
|
2393
2523
|
var _a;
|
|
2394
|
-
|
|
2395
|
-
throw new Error(`Input file not found: ${config.input}`);
|
|
2396
|
-
}
|
|
2524
|
+
validateInput(config.input);
|
|
2397
2525
|
const outputPath = config.output;
|
|
2398
2526
|
const generateServices = (_a = config.options.generateServices) != null ? _a : true;
|
|
2527
|
+
const inputType = isUrl(config.input) ? "URL" : "file";
|
|
2399
2528
|
if (!fs3.existsSync(outputPath)) {
|
|
2400
2529
|
fs3.mkdirSync(outputPath, {
|
|
2401
2530
|
recursive: true
|
|
@@ -2410,7 +2539,8 @@ function generateFromConfig(config) {
|
|
|
2410
2539
|
strict: true
|
|
2411
2540
|
}, config.compilerOptions)
|
|
2412
2541
|
});
|
|
2413
|
-
|
|
2542
|
+
console.log(`\u{1F4E1} Processing OpenAPI specification from ${inputType}: ${config.input}`);
|
|
2543
|
+
const typeGenerator = yield TypeGenerator.create(config.input, outputPath, config);
|
|
2414
2544
|
typeGenerator.generate();
|
|
2415
2545
|
console.log(`\u2705 TypeScript interfaces generated`);
|
|
2416
2546
|
if (generateServices) {
|
|
@@ -2422,7 +2552,7 @@ function generateFromConfig(config) {
|
|
|
2422
2552
|
}
|
|
2423
2553
|
const fileDownloadHelper = new FileDownloadGenerator(project);
|
|
2424
2554
|
fileDownloadHelper.generate(outputPath);
|
|
2425
|
-
const serviceGenerator =
|
|
2555
|
+
const serviceGenerator = yield ServiceGenerator.create(config.input, project, config);
|
|
2426
2556
|
serviceGenerator.generate(outputPath);
|
|
2427
2557
|
const indexGenerator = new ServiceIndexGenerator(project);
|
|
2428
2558
|
indexGenerator.generateIndex(outputPath);
|
|
@@ -2434,14 +2564,19 @@ function generateFromConfig(config) {
|
|
|
2434
2564
|
}
|
|
2435
2565
|
const mainIndexGenerator = new MainIndexGenerator(project, config);
|
|
2436
2566
|
mainIndexGenerator.generateMainIndex(outputPath);
|
|
2567
|
+
const sourceInfo = `from ${inputType}: ${config.input}`;
|
|
2437
2568
|
if (config.clientName) {
|
|
2438
|
-
console.log(`\u{1F389} ${config.clientName} Generation completed successfully
|
|
2569
|
+
console.log(`\u{1F389} ${config.clientName} Generation completed successfully ${sourceInfo} -> ${outputPath}`);
|
|
2439
2570
|
} else {
|
|
2440
|
-
console.log(
|
|
2571
|
+
console.log(`\u{1F389} Generation completed successfully ${sourceInfo} -> ${outputPath}`);
|
|
2441
2572
|
}
|
|
2442
2573
|
} catch (error) {
|
|
2443
2574
|
if (error instanceof Error) {
|
|
2444
2575
|
console.error("\u274C Error during generation:", error.message);
|
|
2576
|
+
if (error.message.includes("fetch") || error.message.includes("Failed to fetch")) {
|
|
2577
|
+
console.error("\u{1F4A1} Tip: Make sure the URL is accessible and returns a valid OpenAPI/Swagger specification");
|
|
2578
|
+
console.error("\u{1F4A1} Alternative: Download the specification file locally and use the file path instead");
|
|
2579
|
+
}
|
|
2445
2580
|
} else {
|
|
2446
2581
|
console.error("\u274C Unknown error during generation:", error);
|
|
2447
2582
|
}
|