ng-openapi 0.0.38 → 0.0.40-pr-8-feature-url-support.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 (4) hide show
  1. package/cli.cjs +268 -67
  2. package/index.d.ts +16 -2
  3. package/index.js +228 -52
  4. package/package.json +3 -2
package/cli.cjs CHANGED
@@ -26,19 +26,25 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
26
26
 
27
27
  // src/lib/cli.ts
28
28
  var import_commander = require("commander");
29
- var path9 = __toESM(require("path"));
29
+ var path10 = __toESM(require("path"));
30
30
  var fs4 = __toESM(require("fs"));
31
31
 
32
32
  // src/lib/core/swagger-parser.ts
33
33
  var fs = __toESM(require("fs"));
34
- var SwaggerParser = class {
34
+ var path = __toESM(require("path"));
35
+ var yaml = __toESM(require("js-yaml"));
36
+ var SwaggerParser = class _SwaggerParser {
35
37
  static {
36
38
  __name(this, "SwaggerParser");
37
39
  }
38
40
  spec;
39
- constructor(swaggerPath) {
40
- const swaggerContent = fs.readFileSync(swaggerPath, "utf8");
41
- this.spec = JSON.parse(swaggerContent);
41
+ constructor(spec) {
42
+ this.spec = spec;
43
+ }
44
+ static async create(swaggerPathOrUrl) {
45
+ const swaggerContent = await _SwaggerParser.loadContent(swaggerPathOrUrl);
46
+ const spec = _SwaggerParser.parseSpecContent(swaggerContent, swaggerPathOrUrl);
47
+ return new _SwaggerParser(spec);
42
48
  }
43
49
  getDefinitions() {
44
50
  return this.spec.definitions || this.spec.components?.schemas || {};
@@ -55,6 +61,125 @@ var SwaggerParser = class {
55
61
  getAllDefinitionNames() {
56
62
  return Object.keys(this.getDefinitions());
57
63
  }
64
+ getSpec() {
65
+ return this.spec;
66
+ }
67
+ getPaths() {
68
+ return this.spec.paths || {};
69
+ }
70
+ isValidSpec() {
71
+ return !!(this.spec.swagger && this.spec.swagger.startsWith("2.") || this.spec.openapi && this.spec.openapi.startsWith("3."));
72
+ }
73
+ getSpecVersion() {
74
+ if (this.spec.swagger) {
75
+ return {
76
+ type: "swagger",
77
+ version: this.spec.swagger
78
+ };
79
+ }
80
+ if (this.spec.openapi) {
81
+ return {
82
+ type: "openapi",
83
+ version: this.spec.openapi
84
+ };
85
+ }
86
+ return null;
87
+ }
88
+ static async loadContent(pathOrUrl) {
89
+ if (_SwaggerParser.isUrl(pathOrUrl)) {
90
+ return await _SwaggerParser.fetchUrlContent(pathOrUrl);
91
+ } else {
92
+ return fs.readFileSync(pathOrUrl, "utf8");
93
+ }
94
+ }
95
+ static isUrl(input) {
96
+ try {
97
+ new URL(input);
98
+ return true;
99
+ } catch {
100
+ return false;
101
+ }
102
+ }
103
+ static async fetchUrlContent(url) {
104
+ try {
105
+ const response = await fetch(url, {
106
+ method: "GET",
107
+ headers: {
108
+ "Accept": "application/json, application/yaml, text/yaml, text/plain, */*",
109
+ "User-Agent": "ng-openapi"
110
+ },
111
+ // 30 second timeout
112
+ signal: AbortSignal.timeout(3e4)
113
+ });
114
+ if (!response.ok) {
115
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
116
+ }
117
+ const content = await response.text();
118
+ if (!content || content.trim() === "") {
119
+ throw new Error(`Empty response from URL: ${url}`);
120
+ }
121
+ return content;
122
+ } catch (error) {
123
+ let errorMessage = `Failed to fetch content from URL: ${url}`;
124
+ if (error.name === "AbortError") {
125
+ errorMessage += " - Request timeout (30s)";
126
+ } else if (error.message) {
127
+ errorMessage += ` - ${error.message}`;
128
+ }
129
+ throw new Error(errorMessage);
130
+ }
131
+ }
132
+ static parseSpecContent(content, pathOrUrl) {
133
+ let format;
134
+ if (_SwaggerParser.isUrl(pathOrUrl)) {
135
+ const urlPath = new URL(pathOrUrl).pathname.toLowerCase();
136
+ if (urlPath.endsWith(".json")) {
137
+ format = "json";
138
+ } else if (urlPath.endsWith(".yaml") || urlPath.endsWith(".yml")) {
139
+ format = "yaml";
140
+ } else {
141
+ format = _SwaggerParser.detectFormat(content);
142
+ }
143
+ } else {
144
+ const extension = path.extname(pathOrUrl).toLowerCase();
145
+ switch (extension) {
146
+ case ".json":
147
+ format = "json";
148
+ break;
149
+ case ".yaml":
150
+ format = "yaml";
151
+ break;
152
+ case ".yml":
153
+ format = "yml";
154
+ break;
155
+ default:
156
+ format = _SwaggerParser.detectFormat(content);
157
+ }
158
+ }
159
+ try {
160
+ switch (format) {
161
+ case "json":
162
+ return JSON.parse(content);
163
+ case "yaml":
164
+ case "yml":
165
+ return yaml.load(content);
166
+ default:
167
+ throw new Error(`Unable to determine format for: ${pathOrUrl}`);
168
+ }
169
+ } catch (error) {
170
+ throw new Error(`Failed to parse ${format.toUpperCase()} content from: ${pathOrUrl}. Error: ${error instanceof Error ? error.message : error}`);
171
+ }
172
+ }
173
+ static detectFormat(content) {
174
+ const trimmed = content.trim();
175
+ if (trimmed.startsWith("{") || trimmed.startsWith("[")) {
176
+ return "json";
177
+ }
178
+ if (trimmed.includes("openapi:") || trimmed.includes("swagger:") || trimmed.includes("---") || /^[a-zA-Z][a-zA-Z0-9_]*\s*:/.test(trimmed)) {
179
+ return "yaml";
180
+ }
181
+ return "json";
182
+ }
58
183
  };
59
184
 
60
185
  // src/lib/core/generator.ts
@@ -99,7 +224,7 @@ var BASE_INTERCEPTOR_HEADER_COMMENT = /* @__PURE__ */ __name((clientName) => def
99
224
  `, "BASE_INTERCEPTOR_HEADER_COMMENT");
100
225
 
101
226
  // src/lib/generators/type/type.generator.ts
102
- var TypeGenerator = class {
227
+ var TypeGenerator = class _TypeGenerator {
103
228
  static {
104
229
  __name(this, "TypeGenerator");
105
230
  }
@@ -108,7 +233,7 @@ var TypeGenerator = class {
108
233
  sourceFile;
109
234
  generatedTypes = /* @__PURE__ */ new Set();
110
235
  config;
111
- constructor(swaggerPath, outputRoot, config) {
236
+ constructor(parser, outputRoot, config) {
112
237
  this.config = config;
113
238
  const outputPath = outputRoot + "/models/index.ts";
114
239
  this.project = new import_ts_morph.Project({
@@ -120,15 +245,14 @@ var TypeGenerator = class {
120
245
  ...this.config.compilerOptions
121
246
  }
122
247
  });
123
- try {
124
- this.parser = new SwaggerParser(swaggerPath);
125
- this.sourceFile = this.project.createSourceFile(outputPath, "", {
126
- overwrite: true
127
- });
128
- } catch (error) {
129
- console.error("Error initializing TypeGenerator:", error);
130
- throw error;
131
- }
248
+ this.parser = parser;
249
+ this.sourceFile = this.project.createSourceFile(outputPath, "", {
250
+ overwrite: true
251
+ });
252
+ }
253
+ static async create(swaggerPathOrUrl, outputRoot, config) {
254
+ const parser = await SwaggerParser.create(swaggerPathOrUrl);
255
+ return new _TypeGenerator(parser, outputRoot, config);
132
256
  }
133
257
  generate() {
134
258
  try {
@@ -400,7 +524,7 @@ var TypeGenerator = class {
400
524
 
401
525
  // src/lib/generators/utility/token.generator.ts
402
526
  var import_ts_morph2 = require("ts-morph");
403
- var path = __toESM(require("path"));
527
+ var path2 = __toESM(require("path"));
404
528
  var TokenGenerator = class {
405
529
  static {
406
530
  __name(this, "TokenGenerator");
@@ -412,8 +536,8 @@ var TokenGenerator = class {
412
536
  this.clientName = clientName;
413
537
  }
414
538
  generate(outputDir) {
415
- const tokensDir = path.join(outputDir, "tokens");
416
- const filePath = path.join(tokensDir, "index.ts");
539
+ const tokensDir = path2.join(outputDir, "tokens");
540
+ const filePath = path2.join(tokensDir, "index.ts");
417
541
  const sourceFile = this.project.createSourceFile(filePath, "", {
418
542
  overwrite: true
419
543
  });
@@ -528,7 +652,7 @@ var TokenGenerator = class {
528
652
  };
529
653
 
530
654
  // src/lib/generators/utility/file-download.generator.ts
531
- var path2 = __toESM(require("path"));
655
+ var path3 = __toESM(require("path"));
532
656
  var FileDownloadGenerator = class {
533
657
  static {
534
658
  __name(this, "FileDownloadGenerator");
@@ -538,8 +662,8 @@ var FileDownloadGenerator = class {
538
662
  this.project = project;
539
663
  }
540
664
  generate(outputDir) {
541
- const utilsDir = path2.join(outputDir, "utils");
542
- const filePath = path2.join(utilsDir, "file-download.ts");
665
+ const utilsDir = path3.join(outputDir, "utils");
666
+ const filePath = path3.join(utilsDir, "file-download.ts");
543
667
  const sourceFile = this.project.createSourceFile(filePath, "", {
544
668
  overwrite: true
545
669
  });
@@ -670,7 +794,7 @@ var FileDownloadGenerator = class {
670
794
 
671
795
  // src/lib/generators/utility/date-transformer.generator.ts
672
796
  var import_ts_morph3 = require("ts-morph");
673
- var path3 = __toESM(require("path"));
797
+ var path4 = __toESM(require("path"));
674
798
  var DateTransformerGenerator = class {
675
799
  static {
676
800
  __name(this, "DateTransformerGenerator");
@@ -680,8 +804,8 @@ var DateTransformerGenerator = class {
680
804
  this.project = project;
681
805
  }
682
806
  generate(outputDir) {
683
- const utilsDir = path3.join(outputDir, "utils");
684
- const filePath = path3.join(utilsDir, "date-transformer.ts");
807
+ const utilsDir = path4.join(outputDir, "utils");
808
+ const filePath = path4.join(utilsDir, "date-transformer.ts");
685
809
  const sourceFile = this.project.createSourceFile(filePath, "", {
686
810
  overwrite: true
687
811
  });
@@ -804,7 +928,7 @@ var DateTransformerGenerator = class {
804
928
  };
805
929
 
806
930
  // src/lib/generators/utility/main-index.generator.ts
807
- var path4 = __toESM(require("path"));
931
+ var path5 = __toESM(require("path"));
808
932
  var MainIndexGenerator = class {
809
933
  static {
810
934
  __name(this, "MainIndexGenerator");
@@ -816,7 +940,7 @@ var MainIndexGenerator = class {
816
940
  this.config = config;
817
941
  }
818
942
  generateMainIndex(outputRoot) {
819
- const indexPath = path4.join(outputRoot, "index.ts");
943
+ const indexPath = path5.join(outputRoot, "index.ts");
820
944
  const sourceFile = this.project.createSourceFile(indexPath, "", {
821
945
  overwrite: true
822
946
  });
@@ -848,7 +972,7 @@ var MainIndexGenerator = class {
848
972
  };
849
973
 
850
974
  // src/lib/generators/utility/provider.generator.ts
851
- var path5 = __toESM(require("path"));
975
+ var path6 = __toESM(require("path"));
852
976
  var ProviderGenerator = class {
853
977
  static {
854
978
  __name(this, "ProviderGenerator");
@@ -862,7 +986,7 @@ var ProviderGenerator = class {
862
986
  this.clientName = config.clientName || "default";
863
987
  }
864
988
  generate(outputDir) {
865
- const filePath = path5.join(outputDir, "providers.ts");
989
+ const filePath = path6.join(outputDir, "providers.ts");
866
990
  const sourceFile = this.project.createSourceFile(filePath, "", {
867
991
  overwrite: true
868
992
  });
@@ -1055,7 +1179,7 @@ return makeEnvironmentProviders(providers);`;
1055
1179
 
1056
1180
  // src/lib/generators/utility/base-interceptor.generator.ts
1057
1181
  var import_ts_morph4 = require("ts-morph");
1058
- var path6 = __toESM(require("path"));
1182
+ var path7 = __toESM(require("path"));
1059
1183
  var BaseInterceptorGenerator = class {
1060
1184
  static {
1061
1185
  __name(this, "BaseInterceptorGenerator");
@@ -1067,8 +1191,8 @@ var BaseInterceptorGenerator = class {
1067
1191
  this.#clientName = clientName;
1068
1192
  }
1069
1193
  generate(outputDir) {
1070
- const utilsDir = path6.join(outputDir, "utils");
1071
- const filePath = path6.join(utilsDir, "base-interceptor.ts");
1194
+ const utilsDir = path7.join(outputDir, "utils");
1195
+ const filePath = path7.join(utilsDir, "base-interceptor.ts");
1072
1196
  const sourceFile = this.#project.createSourceFile(filePath, "", {
1073
1197
  overwrite: true
1074
1198
  });
@@ -1187,7 +1311,7 @@ var BaseInterceptorGenerator = class {
1187
1311
 
1188
1312
  // src/lib/generators/service/service.generator.ts
1189
1313
  var import_ts_morph5 = require("ts-morph");
1190
- var path7 = __toESM(require("path"));
1314
+ var path8 = __toESM(require("path"));
1191
1315
 
1192
1316
  // src/lib/utils/string.utils.ts
1193
1317
  function camelCase(str) {
@@ -1970,7 +2094,7 @@ var ServiceMethodGenerator = class {
1970
2094
  };
1971
2095
 
1972
2096
  // src/lib/generators/service/service.generator.ts
1973
- var ServiceGenerator = class {
2097
+ var ServiceGenerator = class _ServiceGenerator {
1974
2098
  static {
1975
2099
  __name(this, "ServiceGenerator");
1976
2100
  }
@@ -1979,16 +2103,28 @@ var ServiceGenerator = class {
1979
2103
  spec;
1980
2104
  config;
1981
2105
  methodGenerator;
1982
- constructor(swaggerPath, project, config) {
2106
+ constructor(parser, project, config) {
1983
2107
  this.config = config;
1984
2108
  this.project = project;
1985
- this.parser = new SwaggerParser(swaggerPath);
1986
- this.spec = JSON.parse(require("fs").readFileSync(swaggerPath, "utf8"));
2109
+ this.parser = parser;
2110
+ this.spec = this.parser.getSpec();
2111
+ if (!this.parser.isValidSpec()) {
2112
+ const versionInfo = this.parser.getSpecVersion();
2113
+ throw new Error(`Invalid or unsupported specification format. Expected OpenAPI 3.x or Swagger 2.x. ${versionInfo ? `Found: ${versionInfo.type} ${versionInfo.version}` : "No version info found"}`);
2114
+ }
1987
2115
  this.methodGenerator = new ServiceMethodGenerator(config);
1988
2116
  }
2117
+ static async create(swaggerPathOrUrl, project, config) {
2118
+ const parser = await SwaggerParser.create(swaggerPathOrUrl);
2119
+ return new _ServiceGenerator(parser, project, config);
2120
+ }
1989
2121
  generate(outputRoot) {
1990
- const outputDir = path7.join(outputRoot, "services");
2122
+ const outputDir = path8.join(outputRoot, "services");
1991
2123
  const paths = this.extractPaths();
2124
+ if (paths.length === 0) {
2125
+ console.warn("No API paths found in the specification");
2126
+ return;
2127
+ }
1992
2128
  const controllerGroups = this.groupPathsByController(paths);
1993
2129
  Object.entries(controllerGroups).forEach(([controllerName, operations]) => {
1994
2130
  this.generateServiceFile(controllerName, operations, outputDir);
@@ -1997,7 +2133,7 @@ var ServiceGenerator = class {
1997
2133
  extractPaths() {
1998
2134
  const paths = [];
1999
2135
  const swaggerPaths = this.spec.paths || {};
2000
- Object.entries(swaggerPaths).forEach(([path10, pathItem]) => {
2136
+ Object.entries(swaggerPaths).forEach(([path11, pathItem]) => {
2001
2137
  const methods = [
2002
2138
  "get",
2003
2139
  "post",
@@ -2011,7 +2147,7 @@ var ServiceGenerator = class {
2011
2147
  if (pathItem[method]) {
2012
2148
  const operation = pathItem[method];
2013
2149
  paths.push({
2014
- path: path10,
2150
+ path: path11,
2015
2151
  method: method.toUpperCase(),
2016
2152
  operationId: operation.operationId,
2017
2153
  summary: operation.summary,
@@ -2043,12 +2179,12 @@ var ServiceGenerator = class {
2043
2179
  }
2044
2180
  groupPathsByController(paths) {
2045
2181
  const groups = {};
2046
- paths.forEach((path10) => {
2182
+ paths.forEach((path11) => {
2047
2183
  let controllerName = "Default";
2048
- if (path10.tags && path10.tags.length > 0) {
2049
- controllerName = path10.tags[0];
2184
+ if (path11.tags && path11.tags.length > 0) {
2185
+ controllerName = path11.tags[0];
2050
2186
  } else {
2051
- const pathParts = path10.path.split("/").filter((p) => p && !p.startsWith("{"));
2187
+ const pathParts = path11.path.split("/").filter((p) => p && !p.startsWith("{"));
2052
2188
  if (pathParts.length > 1) {
2053
2189
  controllerName = pascalCase(pathParts[1]);
2054
2190
  }
@@ -2057,13 +2193,13 @@ var ServiceGenerator = class {
2057
2193
  if (!groups[controllerName]) {
2058
2194
  groups[controllerName] = [];
2059
2195
  }
2060
- groups[controllerName].push(path10);
2196
+ groups[controllerName].push(path11);
2061
2197
  });
2062
2198
  return groups;
2063
2199
  }
2064
2200
  generateServiceFile(controllerName, operations, outputDir) {
2065
2201
  const fileName = `${camelCase(controllerName)}.service.ts`;
2066
- const filePath = path7.join(outputDir, fileName);
2202
+ const filePath = path8.join(outputDir, fileName);
2067
2203
  const sourceFile = this.project.createSourceFile(filePath, "", {
2068
2204
  overwrite: true
2069
2205
  });
@@ -2257,7 +2393,7 @@ return context.set(this.clientContextToken, '${this.config.clientName || "defaul
2257
2393
 
2258
2394
  // src/lib/generators/service/service-index.generator.ts
2259
2395
  var fs2 = __toESM(require("fs"));
2260
- var path8 = __toESM(require("path"));
2396
+ var path9 = __toESM(require("path"));
2261
2397
  var ServiceIndexGenerator = class {
2262
2398
  static {
2263
2399
  __name(this, "ServiceIndexGenerator");
@@ -2267,8 +2403,8 @@ var ServiceIndexGenerator = class {
2267
2403
  this.project = project;
2268
2404
  }
2269
2405
  generateIndex(outputRoot) {
2270
- const servicesDir = path8.join(outputRoot, "services");
2271
- const indexPath = path8.join(servicesDir, "index.ts");
2406
+ const servicesDir = path9.join(outputRoot, "services");
2407
+ const indexPath = path9.join(servicesDir, "index.ts");
2272
2408
  const sourceFile = this.project.createSourceFile(indexPath, "", {
2273
2409
  overwrite: true
2274
2410
  });
@@ -2289,12 +2425,36 @@ var ServiceIndexGenerator = class {
2289
2425
 
2290
2426
  // src/lib/core/generator.ts
2291
2427
  var fs3 = __toESM(require("fs"));
2292
- async function generateFromConfig(config) {
2293
- if (!fs3.existsSync(config.input)) {
2294
- throw new Error(`Input file not found: ${config.input}`);
2428
+ function isUrl(input) {
2429
+ try {
2430
+ new URL(input);
2431
+ return true;
2432
+ } catch {
2433
+ return false;
2434
+ }
2435
+ }
2436
+ __name(isUrl, "isUrl");
2437
+ function validateInput(input) {
2438
+ if (isUrl(input)) {
2439
+ const url = new URL(input);
2440
+ if (![
2441
+ "http:",
2442
+ "https:"
2443
+ ].includes(url.protocol)) {
2444
+ throw new Error(`Unsupported URL protocol: ${url.protocol}. Only HTTP and HTTPS are supported.`);
2445
+ }
2446
+ } else {
2447
+ if (!fs3.existsSync(input)) {
2448
+ throw new Error(`Input file not found: ${input}`);
2449
+ }
2295
2450
  }
2451
+ }
2452
+ __name(validateInput, "validateInput");
2453
+ async function generateFromConfig(config) {
2454
+ validateInput(config.input);
2296
2455
  const outputPath = config.output;
2297
2456
  const generateServices = config.options.generateServices ?? true;
2457
+ const inputType = isUrl(config.input) ? "URL" : "file";
2298
2458
  if (!fs3.existsSync(outputPath)) {
2299
2459
  fs3.mkdirSync(outputPath, {
2300
2460
  recursive: true
@@ -2310,7 +2470,8 @@ async function generateFromConfig(config) {
2310
2470
  ...config.compilerOptions
2311
2471
  }
2312
2472
  });
2313
- const typeGenerator = new TypeGenerator(config.input, outputPath, config);
2473
+ console.log(`\u{1F4E1} Processing OpenAPI specification from ${inputType}: ${config.input}`);
2474
+ const typeGenerator = await TypeGenerator.create(config.input, outputPath, config);
2314
2475
  typeGenerator.generate();
2315
2476
  console.log(`\u2705 TypeScript interfaces generated`);
2316
2477
  if (generateServices) {
@@ -2322,7 +2483,7 @@ async function generateFromConfig(config) {
2322
2483
  }
2323
2484
  const fileDownloadHelper = new FileDownloadGenerator(project);
2324
2485
  fileDownloadHelper.generate(outputPath);
2325
- const serviceGenerator = new ServiceGenerator(config.input, project, config);
2486
+ const serviceGenerator = await ServiceGenerator.create(config.input, project, config);
2326
2487
  serviceGenerator.generate(outputPath);
2327
2488
  const indexGenerator = new ServiceIndexGenerator(project);
2328
2489
  indexGenerator.generateIndex(outputPath);
@@ -2334,14 +2495,19 @@ async function generateFromConfig(config) {
2334
2495
  }
2335
2496
  const mainIndexGenerator = new MainIndexGenerator(project, config);
2336
2497
  mainIndexGenerator.generateMainIndex(outputPath);
2498
+ const sourceInfo = `from ${inputType}: ${config.input}`;
2337
2499
  if (config.clientName) {
2338
- console.log(`\u{1F389} ${config.clientName} Generation completed successfully at: ${outputPath}`);
2500
+ console.log(`\u{1F389} ${config.clientName} Generation completed successfully ${sourceInfo} -> ${outputPath}`);
2339
2501
  } else {
2340
- console.log("\u{1F389} Generation completed successfully at:", outputPath);
2502
+ console.log(`\u{1F389} Generation completed successfully ${sourceInfo} -> ${outputPath}`);
2341
2503
  }
2342
2504
  } catch (error) {
2343
2505
  if (error instanceof Error) {
2344
2506
  console.error("\u274C Error during generation:", error.message);
2507
+ if (error.message.includes("fetch") || error.message.includes("Failed to fetch")) {
2508
+ console.error("\u{1F4A1} Tip: Make sure the URL is accessible and returns a valid OpenAPI/Swagger specification");
2509
+ console.error("\u{1F4A1} Alternative: Download the specification file locally and use the file path instead");
2510
+ }
2345
2511
  } else {
2346
2512
  console.error("\u274C Unknown error during generation:", error);
2347
2513
  }
@@ -2351,12 +2517,12 @@ async function generateFromConfig(config) {
2351
2517
  __name(generateFromConfig, "generateFromConfig");
2352
2518
 
2353
2519
  // package.json
2354
- var version = "0.0.37";
2520
+ var version = "0.0.39";
2355
2521
 
2356
2522
  // src/lib/cli.ts
2357
2523
  var program = new import_commander.Command();
2358
2524
  async function loadConfigFile(configPath) {
2359
- const resolvedPath = path9.resolve(configPath);
2525
+ const resolvedPath = path10.resolve(configPath);
2360
2526
  if (!fs4.existsSync(resolvedPath)) {
2361
2527
  throw new Error(`Configuration file not found: ${resolvedPath}`);
2362
2528
  }
@@ -2376,19 +2542,50 @@ async function loadConfigFile(configPath) {
2376
2542
  }
2377
2543
  }
2378
2544
  __name(loadConfigFile, "loadConfigFile");
2545
+ function isUrl2(input) {
2546
+ try {
2547
+ new URL(input);
2548
+ return true;
2549
+ } catch {
2550
+ return false;
2551
+ }
2552
+ }
2553
+ __name(isUrl2, "isUrl");
2554
+ function validateInput2(inputPath) {
2555
+ if (isUrl2(inputPath)) {
2556
+ const url = new URL(inputPath);
2557
+ if (![
2558
+ "http:",
2559
+ "https:"
2560
+ ].includes(url.protocol)) {
2561
+ throw new Error(`Unsupported URL protocol: ${url.protocol}. Only HTTP and HTTPS are supported.`);
2562
+ }
2563
+ return;
2564
+ }
2565
+ if (!fs4.existsSync(inputPath)) {
2566
+ throw new Error(`Input file not found: ${inputPath}`);
2567
+ }
2568
+ const extension = path10.extname(inputPath).toLowerCase();
2569
+ const supportedExtensions = [
2570
+ ".json",
2571
+ ".yaml",
2572
+ ".yml"
2573
+ ];
2574
+ if (!supportedExtensions.includes(extension)) {
2575
+ throw new Error(`Failed to parse ${extension || "specification"}. Supported formats are .json, .yaml, and .yml.`);
2576
+ }
2577
+ }
2578
+ __name(validateInput2, "validateInput");
2379
2579
  async function generateFromOptions(options) {
2380
2580
  try {
2381
2581
  if (options.config) {
2382
2582
  const config = await loadConfigFile(options.config);
2583
+ validateInput2(config.input);
2383
2584
  await generateFromConfig(config);
2384
2585
  } else if (options.input) {
2385
- const inputPath = path9.resolve(options.input);
2386
- if (!fs4.existsSync(inputPath)) {
2387
- console.error(`Error: Input file not found: ${inputPath}`);
2388
- process.exit(1);
2389
- }
2586
+ validateInput2(options.input);
2390
2587
  const config = {
2391
- input: inputPath,
2588
+ input: options.input,
2392
2589
  output: options.output || "./src/generated",
2393
2590
  options: {
2394
2591
  dateType: options.dateType || "Date",
@@ -2410,10 +2607,10 @@ async function generateFromOptions(options) {
2410
2607
  }
2411
2608
  }
2412
2609
  __name(generateFromOptions, "generateFromOptions");
2413
- program.name("ng-openapi").description("Generate Angular services and types from Swagger/OpenAPI spec").version(version).option("-c, --config <path>", "Path to configuration file").option("-i, --input <path>", "Path to Swagger/OpenAPI specification file").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) => {
2610
+ 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) => {
2414
2611
  await generateFromOptions(options);
2415
2612
  });
2416
- program.command("generate").alias("gen").description("Generate code from Swagger specification").option("-c, --config <path>", "Path to configuration file").option("-i, --input <path>", "Path to Swagger/OpenAPI specification file").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) => {
2613
+ 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) => {
2417
2614
  await generateFromOptions(options);
2418
2615
  });
2419
2616
  program.on("--help", () => {
@@ -2421,8 +2618,12 @@ program.on("--help", () => {
2421
2618
  console.log("Examples:");
2422
2619
  console.log(" $ ng-openapi -c ./openapi.config.ts");
2423
2620
  console.log(" $ ng-openapi -i ./swagger.json -o ./src/api");
2621
+ console.log(" $ ng-openapi -i ./openapi.yaml -o ./src/api");
2622
+ console.log(" $ ng-openapi -i ./api-spec.yml -o ./src/api");
2623
+ console.log(" $ ng-openapi -i https://api.example.com/openapi.json -o ./src/api");
2624
+ console.log(" $ ng-openapi -i https://petstore.swagger.io/v2/swagger.json -o ./src/api");
2424
2625
  console.log(" $ ng-openapi generate -c ./openapi.config.ts");
2425
- console.log(" $ ng-openapi generate -i ./api.yaml --types-only");
2626
+ console.log(" $ ng-openapi generate -i https://api.example.com/swagger.yaml --types-only");
2426
2627
  });
2427
2628
  program.parse();
2428
2629
  //# sourceMappingURL=cli.cjs.map
package/index.d.ts CHANGED
@@ -125,6 +125,7 @@ interface SwaggerDefinition {
125
125
  anyOf?: SwaggerDefinition[];
126
126
  }
127
127
  interface SwaggerSpec {
128
+ openapi: string;
128
129
  swagger: string;
129
130
  info: Info;
130
131
  externalDocs?: ExternalDocs | undefined;
@@ -162,12 +163,25 @@ type EnumValueObject = {
162
163
  };
163
164
 
164
165
  declare class SwaggerParser {
165
- private spec;
166
- constructor(swaggerPath: string);
166
+ private readonly spec;
167
+ private constructor();
168
+ static create(swaggerPathOrUrl: string): Promise<SwaggerParser>;
167
169
  getDefinitions(): Record<string, SwaggerDefinition>;
168
170
  getDefinition(name: string): SwaggerDefinition | undefined;
169
171
  resolveReference(ref: string): SwaggerDefinition | undefined;
170
172
  getAllDefinitionNames(): string[];
173
+ getSpec(): SwaggerSpec;
174
+ getPaths(): Record<string, any>;
175
+ isValidSpec(): boolean;
176
+ getSpecVersion(): {
177
+ type: "swagger" | "openapi";
178
+ version: string;
179
+ } | null;
180
+ private static loadContent;
181
+ private static isUrl;
182
+ private static fetchUrlContent;
183
+ private static parseSpecContent;
184
+ private static detectFormat;
171
185
  }
172
186
 
173
187
  /**
package/index.js CHANGED
@@ -80,11 +80,19 @@ module.exports = __toCommonJS(index_exports);
80
80
 
81
81
  // src/lib/core/swagger-parser.ts
82
82
  var fs = __toESM(require("fs"));
83
+ var path = __toESM(require("path"));
84
+ var yaml = __toESM(require("js-yaml"));
83
85
  var _SwaggerParser = class _SwaggerParser {
84
- constructor(swaggerPath) {
86
+ constructor(spec) {
85
87
  __publicField(this, "spec");
86
- const swaggerContent = fs.readFileSync(swaggerPath, "utf8");
87
- this.spec = JSON.parse(swaggerContent);
88
+ this.spec = spec;
89
+ }
90
+ static create(swaggerPathOrUrl) {
91
+ return __async(this, null, function* () {
92
+ const swaggerContent = yield _SwaggerParser.loadContent(swaggerPathOrUrl);
93
+ const spec = _SwaggerParser.parseSpecContent(swaggerContent, swaggerPathOrUrl);
94
+ return new _SwaggerParser(spec);
95
+ });
88
96
  }
89
97
  getDefinitions() {
90
98
  var _a;
@@ -102,6 +110,129 @@ var _SwaggerParser = class _SwaggerParser {
102
110
  getAllDefinitionNames() {
103
111
  return Object.keys(this.getDefinitions());
104
112
  }
113
+ getSpec() {
114
+ return this.spec;
115
+ }
116
+ getPaths() {
117
+ return this.spec.paths || {};
118
+ }
119
+ isValidSpec() {
120
+ return !!(this.spec.swagger && this.spec.swagger.startsWith("2.") || this.spec.openapi && this.spec.openapi.startsWith("3."));
121
+ }
122
+ getSpecVersion() {
123
+ if (this.spec.swagger) {
124
+ return {
125
+ type: "swagger",
126
+ version: this.spec.swagger
127
+ };
128
+ }
129
+ if (this.spec.openapi) {
130
+ return {
131
+ type: "openapi",
132
+ version: this.spec.openapi
133
+ };
134
+ }
135
+ return null;
136
+ }
137
+ static loadContent(pathOrUrl) {
138
+ return __async(this, null, function* () {
139
+ if (_SwaggerParser.isUrl(pathOrUrl)) {
140
+ return yield _SwaggerParser.fetchUrlContent(pathOrUrl);
141
+ } else {
142
+ return fs.readFileSync(pathOrUrl, "utf8");
143
+ }
144
+ });
145
+ }
146
+ static isUrl(input) {
147
+ try {
148
+ new URL(input);
149
+ return true;
150
+ } catch (e) {
151
+ return false;
152
+ }
153
+ }
154
+ static fetchUrlContent(url) {
155
+ return __async(this, null, function* () {
156
+ try {
157
+ const response = yield fetch(url, {
158
+ method: "GET",
159
+ headers: {
160
+ "Accept": "application/json, application/yaml, text/yaml, text/plain, */*",
161
+ "User-Agent": "ng-openapi"
162
+ },
163
+ // 30 second timeout
164
+ signal: AbortSignal.timeout(3e4)
165
+ });
166
+ if (!response.ok) {
167
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
168
+ }
169
+ const content = yield response.text();
170
+ if (!content || content.trim() === "") {
171
+ throw new Error(`Empty response from URL: ${url}`);
172
+ }
173
+ return content;
174
+ } catch (error) {
175
+ let errorMessage = `Failed to fetch content from URL: ${url}`;
176
+ if (error.name === "AbortError") {
177
+ errorMessage += " - Request timeout (30s)";
178
+ } else if (error.message) {
179
+ errorMessage += ` - ${error.message}`;
180
+ }
181
+ throw new Error(errorMessage);
182
+ }
183
+ });
184
+ }
185
+ static parseSpecContent(content, pathOrUrl) {
186
+ let format;
187
+ if (_SwaggerParser.isUrl(pathOrUrl)) {
188
+ const urlPath = new URL(pathOrUrl).pathname.toLowerCase();
189
+ if (urlPath.endsWith(".json")) {
190
+ format = "json";
191
+ } else if (urlPath.endsWith(".yaml") || urlPath.endsWith(".yml")) {
192
+ format = "yaml";
193
+ } else {
194
+ format = _SwaggerParser.detectFormat(content);
195
+ }
196
+ } else {
197
+ const extension = path.extname(pathOrUrl).toLowerCase();
198
+ switch (extension) {
199
+ case ".json":
200
+ format = "json";
201
+ break;
202
+ case ".yaml":
203
+ format = "yaml";
204
+ break;
205
+ case ".yml":
206
+ format = "yml";
207
+ break;
208
+ default:
209
+ format = _SwaggerParser.detectFormat(content);
210
+ }
211
+ }
212
+ try {
213
+ switch (format) {
214
+ case "json":
215
+ return JSON.parse(content);
216
+ case "yaml":
217
+ case "yml":
218
+ return yaml.load(content);
219
+ default:
220
+ throw new Error(`Unable to determine format for: ${pathOrUrl}`);
221
+ }
222
+ } catch (error) {
223
+ throw new Error(`Failed to parse ${format.toUpperCase()} content from: ${pathOrUrl}. Error: ${error instanceof Error ? error.message : error}`);
224
+ }
225
+ }
226
+ static detectFormat(content) {
227
+ const trimmed = content.trim();
228
+ if (trimmed.startsWith("{") || trimmed.startsWith("[")) {
229
+ return "json";
230
+ }
231
+ if (trimmed.includes("openapi:") || trimmed.includes("swagger:") || trimmed.includes("---") || /^[a-zA-Z][a-zA-Z0-9_]*\s*:/.test(trimmed)) {
232
+ return "yaml";
233
+ }
234
+ return "json";
235
+ }
105
236
  };
106
237
  __name(_SwaggerParser, "SwaggerParser");
107
238
  var SwaggerParser = _SwaggerParser;
@@ -149,7 +280,7 @@ var BASE_INTERCEPTOR_HEADER_COMMENT = /* @__PURE__ */ __name((clientName) => def
149
280
 
150
281
  // src/lib/generators/type/type.generator.ts
151
282
  var _TypeGenerator = class _TypeGenerator {
152
- constructor(swaggerPath, outputRoot, config) {
283
+ constructor(parser, outputRoot, config) {
153
284
  __publicField(this, "project");
154
285
  __publicField(this, "parser");
155
286
  __publicField(this, "sourceFile");
@@ -165,15 +296,16 @@ var _TypeGenerator = class _TypeGenerator {
165
296
  strict: true
166
297
  }, this.config.compilerOptions)
167
298
  });
168
- try {
169
- this.parser = new SwaggerParser(swaggerPath);
170
- this.sourceFile = this.project.createSourceFile(outputPath, "", {
171
- overwrite: true
172
- });
173
- } catch (error) {
174
- console.error("Error initializing TypeGenerator:", error);
175
- throw error;
176
- }
299
+ this.parser = parser;
300
+ this.sourceFile = this.project.createSourceFile(outputPath, "", {
301
+ overwrite: true
302
+ });
303
+ }
304
+ static create(swaggerPathOrUrl, outputRoot, config) {
305
+ return __async(this, null, function* () {
306
+ const parser = yield SwaggerParser.create(swaggerPathOrUrl);
307
+ return new _TypeGenerator(parser, outputRoot, config);
308
+ });
177
309
  }
178
310
  generate() {
179
311
  try {
@@ -450,7 +582,7 @@ var TypeGenerator = _TypeGenerator;
450
582
 
451
583
  // src/lib/generators/utility/token.generator.ts
452
584
  var import_ts_morph2 = require("ts-morph");
453
- var path = __toESM(require("path"));
585
+ var path2 = __toESM(require("path"));
454
586
  var _TokenGenerator = class _TokenGenerator {
455
587
  constructor(project, clientName = "default") {
456
588
  __publicField(this, "project");
@@ -459,8 +591,8 @@ var _TokenGenerator = class _TokenGenerator {
459
591
  this.clientName = clientName;
460
592
  }
461
593
  generate(outputDir) {
462
- const tokensDir = path.join(outputDir, "tokens");
463
- const filePath = path.join(tokensDir, "index.ts");
594
+ const tokensDir = path2.join(outputDir, "tokens");
595
+ const filePath = path2.join(tokensDir, "index.ts");
464
596
  const sourceFile = this.project.createSourceFile(filePath, "", {
465
597
  overwrite: true
466
598
  });
@@ -577,15 +709,15 @@ __name(_TokenGenerator, "TokenGenerator");
577
709
  var TokenGenerator = _TokenGenerator;
578
710
 
579
711
  // src/lib/generators/utility/file-download.generator.ts
580
- var path2 = __toESM(require("path"));
712
+ var path3 = __toESM(require("path"));
581
713
  var _FileDownloadGenerator = class _FileDownloadGenerator {
582
714
  constructor(project) {
583
715
  __publicField(this, "project");
584
716
  this.project = project;
585
717
  }
586
718
  generate(outputDir) {
587
- const utilsDir = path2.join(outputDir, "utils");
588
- const filePath = path2.join(utilsDir, "file-download.ts");
719
+ const utilsDir = path3.join(outputDir, "utils");
720
+ const filePath = path3.join(utilsDir, "file-download.ts");
589
721
  const sourceFile = this.project.createSourceFile(filePath, "", {
590
722
  overwrite: true
591
723
  });
@@ -718,15 +850,15 @@ var FileDownloadGenerator = _FileDownloadGenerator;
718
850
 
719
851
  // src/lib/generators/utility/date-transformer.generator.ts
720
852
  var import_ts_morph3 = require("ts-morph");
721
- var path3 = __toESM(require("path"));
853
+ var path4 = __toESM(require("path"));
722
854
  var _DateTransformerGenerator = class _DateTransformerGenerator {
723
855
  constructor(project) {
724
856
  __publicField(this, "project");
725
857
  this.project = project;
726
858
  }
727
859
  generate(outputDir) {
728
- const utilsDir = path3.join(outputDir, "utils");
729
- const filePath = path3.join(utilsDir, "date-transformer.ts");
860
+ const utilsDir = path4.join(outputDir, "utils");
861
+ const filePath = path4.join(utilsDir, "date-transformer.ts");
730
862
  const sourceFile = this.project.createSourceFile(filePath, "", {
731
863
  overwrite: true
732
864
  });
@@ -851,7 +983,7 @@ __name(_DateTransformerGenerator, "DateTransformerGenerator");
851
983
  var DateTransformerGenerator = _DateTransformerGenerator;
852
984
 
853
985
  // src/lib/generators/utility/main-index.generator.ts
854
- var path4 = __toESM(require("path"));
986
+ var path5 = __toESM(require("path"));
855
987
  var _MainIndexGenerator = class _MainIndexGenerator {
856
988
  constructor(project, config) {
857
989
  __publicField(this, "project");
@@ -860,7 +992,7 @@ var _MainIndexGenerator = class _MainIndexGenerator {
860
992
  this.config = config;
861
993
  }
862
994
  generateMainIndex(outputRoot) {
863
- const indexPath = path4.join(outputRoot, "index.ts");
995
+ const indexPath = path5.join(outputRoot, "index.ts");
864
996
  const sourceFile = this.project.createSourceFile(indexPath, "", {
865
997
  overwrite: true
866
998
  });
@@ -894,7 +1026,7 @@ __name(_MainIndexGenerator, "MainIndexGenerator");
894
1026
  var MainIndexGenerator = _MainIndexGenerator;
895
1027
 
896
1028
  // src/lib/generators/utility/provider.generator.ts
897
- var path5 = __toESM(require("path"));
1029
+ var path6 = __toESM(require("path"));
898
1030
  var _ProviderGenerator = class _ProviderGenerator {
899
1031
  constructor(project, config) {
900
1032
  __publicField(this, "project");
@@ -905,7 +1037,7 @@ var _ProviderGenerator = class _ProviderGenerator {
905
1037
  this.clientName = config.clientName || "default";
906
1038
  }
907
1039
  generate(outputDir) {
908
- const filePath = path5.join(outputDir, "providers.ts");
1040
+ const filePath = path6.join(outputDir, "providers.ts");
909
1041
  const sourceFile = this.project.createSourceFile(filePath, "", {
910
1042
  overwrite: true
911
1043
  });
@@ -1100,7 +1232,7 @@ var ProviderGenerator = _ProviderGenerator;
1100
1232
 
1101
1233
  // src/lib/generators/utility/base-interceptor.generator.ts
1102
1234
  var import_ts_morph4 = require("ts-morph");
1103
- var path6 = __toESM(require("path"));
1235
+ var path7 = __toESM(require("path"));
1104
1236
  var _project, _clientName;
1105
1237
  var _BaseInterceptorGenerator = class _BaseInterceptorGenerator {
1106
1238
  constructor(project, clientName = "default") {
@@ -1110,8 +1242,8 @@ var _BaseInterceptorGenerator = class _BaseInterceptorGenerator {
1110
1242
  __privateSet(this, _clientName, clientName);
1111
1243
  }
1112
1244
  generate(outputDir) {
1113
- const utilsDir = path6.join(outputDir, "utils");
1114
- const filePath = path6.join(utilsDir, "base-interceptor.ts");
1245
+ const utilsDir = path7.join(outputDir, "utils");
1246
+ const filePath = path7.join(utilsDir, "base-interceptor.ts");
1115
1247
  const sourceFile = __privateGet(this, _project).createSourceFile(filePath, "", {
1116
1248
  overwrite: true
1117
1249
  });
@@ -1234,7 +1366,7 @@ var BaseInterceptorGenerator = _BaseInterceptorGenerator;
1234
1366
 
1235
1367
  // src/lib/generators/service/service.generator.ts
1236
1368
  var import_ts_morph5 = require("ts-morph");
1237
- var path7 = __toESM(require("path"));
1369
+ var path8 = __toESM(require("path"));
1238
1370
 
1239
1371
  // src/lib/utils/string.utils.ts
1240
1372
  function camelCase(str) {
@@ -2025,7 +2157,7 @@ var ServiceMethodGenerator = _ServiceMethodGenerator;
2025
2157
 
2026
2158
  // src/lib/generators/service/service.generator.ts
2027
2159
  var _ServiceGenerator = class _ServiceGenerator {
2028
- constructor(swaggerPath, project, config) {
2160
+ constructor(parser, project, config) {
2029
2161
  __publicField(this, "project");
2030
2162
  __publicField(this, "parser");
2031
2163
  __publicField(this, "spec");
@@ -2033,13 +2165,27 @@ var _ServiceGenerator = class _ServiceGenerator {
2033
2165
  __publicField(this, "methodGenerator");
2034
2166
  this.config = config;
2035
2167
  this.project = project;
2036
- this.parser = new SwaggerParser(swaggerPath);
2037
- this.spec = JSON.parse(require("fs").readFileSync(swaggerPath, "utf8"));
2168
+ this.parser = parser;
2169
+ this.spec = this.parser.getSpec();
2170
+ if (!this.parser.isValidSpec()) {
2171
+ const versionInfo = this.parser.getSpecVersion();
2172
+ throw new Error(`Invalid or unsupported specification format. Expected OpenAPI 3.x or Swagger 2.x. ${versionInfo ? `Found: ${versionInfo.type} ${versionInfo.version}` : "No version info found"}`);
2173
+ }
2038
2174
  this.methodGenerator = new ServiceMethodGenerator(config);
2039
2175
  }
2176
+ static create(swaggerPathOrUrl, project, config) {
2177
+ return __async(this, null, function* () {
2178
+ const parser = yield SwaggerParser.create(swaggerPathOrUrl);
2179
+ return new _ServiceGenerator(parser, project, config);
2180
+ });
2181
+ }
2040
2182
  generate(outputRoot) {
2041
- const outputDir = path7.join(outputRoot, "services");
2183
+ const outputDir = path8.join(outputRoot, "services");
2042
2184
  const paths = this.extractPaths();
2185
+ if (paths.length === 0) {
2186
+ console.warn("No API paths found in the specification");
2187
+ return;
2188
+ }
2043
2189
  const controllerGroups = this.groupPathsByController(paths);
2044
2190
  Object.entries(controllerGroups).forEach(([controllerName, operations]) => {
2045
2191
  this.generateServiceFile(controllerName, operations, outputDir);
@@ -2048,7 +2194,7 @@ var _ServiceGenerator = class _ServiceGenerator {
2048
2194
  extractPaths() {
2049
2195
  const paths = [];
2050
2196
  const swaggerPaths = this.spec.paths || {};
2051
- Object.entries(swaggerPaths).forEach(([path9, pathItem]) => {
2197
+ Object.entries(swaggerPaths).forEach(([path10, pathItem]) => {
2052
2198
  const methods = [
2053
2199
  "get",
2054
2200
  "post",
@@ -2062,7 +2208,7 @@ var _ServiceGenerator = class _ServiceGenerator {
2062
2208
  if (pathItem[method]) {
2063
2209
  const operation = pathItem[method];
2064
2210
  paths.push({
2065
- path: path9,
2211
+ path: path10,
2066
2212
  method: method.toUpperCase(),
2067
2213
  operationId: operation.operationId,
2068
2214
  summary: operation.summary,
@@ -2094,12 +2240,12 @@ var _ServiceGenerator = class _ServiceGenerator {
2094
2240
  }
2095
2241
  groupPathsByController(paths) {
2096
2242
  const groups = {};
2097
- paths.forEach((path9) => {
2243
+ paths.forEach((path10) => {
2098
2244
  let controllerName = "Default";
2099
- if (path9.tags && path9.tags.length > 0) {
2100
- controllerName = path9.tags[0];
2245
+ if (path10.tags && path10.tags.length > 0) {
2246
+ controllerName = path10.tags[0];
2101
2247
  } else {
2102
- const pathParts = path9.path.split("/").filter((p) => p && !p.startsWith("{"));
2248
+ const pathParts = path10.path.split("/").filter((p) => p && !p.startsWith("{"));
2103
2249
  if (pathParts.length > 1) {
2104
2250
  controllerName = pascalCase(pathParts[1]);
2105
2251
  }
@@ -2108,13 +2254,13 @@ var _ServiceGenerator = class _ServiceGenerator {
2108
2254
  if (!groups[controllerName]) {
2109
2255
  groups[controllerName] = [];
2110
2256
  }
2111
- groups[controllerName].push(path9);
2257
+ groups[controllerName].push(path10);
2112
2258
  });
2113
2259
  return groups;
2114
2260
  }
2115
2261
  generateServiceFile(controllerName, operations, outputDir) {
2116
2262
  const fileName = `${camelCase(controllerName)}.service.ts`;
2117
- const filePath = path7.join(outputDir, fileName);
2263
+ const filePath = path8.join(outputDir, fileName);
2118
2264
  const sourceFile = this.project.createSourceFile(filePath, "", {
2119
2265
  overwrite: true
2120
2266
  });
@@ -2311,15 +2457,15 @@ var ServiceGenerator = _ServiceGenerator;
2311
2457
 
2312
2458
  // src/lib/generators/service/service-index.generator.ts
2313
2459
  var fs2 = __toESM(require("fs"));
2314
- var path8 = __toESM(require("path"));
2460
+ var path9 = __toESM(require("path"));
2315
2461
  var _ServiceIndexGenerator = class _ServiceIndexGenerator {
2316
2462
  constructor(project) {
2317
2463
  __publicField(this, "project");
2318
2464
  this.project = project;
2319
2465
  }
2320
2466
  generateIndex(outputRoot) {
2321
- const servicesDir = path8.join(outputRoot, "services");
2322
- const indexPath = path8.join(servicesDir, "index.ts");
2467
+ const servicesDir = path9.join(outputRoot, "services");
2468
+ const indexPath = path9.join(servicesDir, "index.ts");
2323
2469
  const sourceFile = this.project.createSourceFile(indexPath, "", {
2324
2470
  overwrite: true
2325
2471
  });
@@ -2342,14 +2488,38 @@ var ServiceIndexGenerator = _ServiceIndexGenerator;
2342
2488
 
2343
2489
  // src/lib/core/generator.ts
2344
2490
  var fs3 = __toESM(require("fs"));
2491
+ function isUrl(input) {
2492
+ try {
2493
+ new URL(input);
2494
+ return true;
2495
+ } catch (e) {
2496
+ return false;
2497
+ }
2498
+ }
2499
+ __name(isUrl, "isUrl");
2500
+ function validateInput(input) {
2501
+ if (isUrl(input)) {
2502
+ const url = new URL(input);
2503
+ if (![
2504
+ "http:",
2505
+ "https:"
2506
+ ].includes(url.protocol)) {
2507
+ throw new Error(`Unsupported URL protocol: ${url.protocol}. Only HTTP and HTTPS are supported.`);
2508
+ }
2509
+ } else {
2510
+ if (!fs3.existsSync(input)) {
2511
+ throw new Error(`Input file not found: ${input}`);
2512
+ }
2513
+ }
2514
+ }
2515
+ __name(validateInput, "validateInput");
2345
2516
  function generateFromConfig(config) {
2346
2517
  return __async(this, null, function* () {
2347
2518
  var _a;
2348
- if (!fs3.existsSync(config.input)) {
2349
- throw new Error(`Input file not found: ${config.input}`);
2350
- }
2519
+ validateInput(config.input);
2351
2520
  const outputPath = config.output;
2352
2521
  const generateServices = (_a = config.options.generateServices) != null ? _a : true;
2522
+ const inputType = isUrl(config.input) ? "URL" : "file";
2353
2523
  if (!fs3.existsSync(outputPath)) {
2354
2524
  fs3.mkdirSync(outputPath, {
2355
2525
  recursive: true
@@ -2364,7 +2534,8 @@ function generateFromConfig(config) {
2364
2534
  strict: true
2365
2535
  }, config.compilerOptions)
2366
2536
  });
2367
- const typeGenerator = new TypeGenerator(config.input, outputPath, config);
2537
+ console.log(`\u{1F4E1} Processing OpenAPI specification from ${inputType}: ${config.input}`);
2538
+ const typeGenerator = yield TypeGenerator.create(config.input, outputPath, config);
2368
2539
  typeGenerator.generate();
2369
2540
  console.log(`\u2705 TypeScript interfaces generated`);
2370
2541
  if (generateServices) {
@@ -2376,7 +2547,7 @@ function generateFromConfig(config) {
2376
2547
  }
2377
2548
  const fileDownloadHelper = new FileDownloadGenerator(project);
2378
2549
  fileDownloadHelper.generate(outputPath);
2379
- const serviceGenerator = new ServiceGenerator(config.input, project, config);
2550
+ const serviceGenerator = yield ServiceGenerator.create(config.input, project, config);
2380
2551
  serviceGenerator.generate(outputPath);
2381
2552
  const indexGenerator = new ServiceIndexGenerator(project);
2382
2553
  indexGenerator.generateIndex(outputPath);
@@ -2388,14 +2559,19 @@ function generateFromConfig(config) {
2388
2559
  }
2389
2560
  const mainIndexGenerator = new MainIndexGenerator(project, config);
2390
2561
  mainIndexGenerator.generateMainIndex(outputPath);
2562
+ const sourceInfo = `from ${inputType}: ${config.input}`;
2391
2563
  if (config.clientName) {
2392
- console.log(`\u{1F389} ${config.clientName} Generation completed successfully at: ${outputPath}`);
2564
+ console.log(`\u{1F389} ${config.clientName} Generation completed successfully ${sourceInfo} -> ${outputPath}`);
2393
2565
  } else {
2394
- console.log("\u{1F389} Generation completed successfully at:", outputPath);
2566
+ console.log(`\u{1F389} Generation completed successfully ${sourceInfo} -> ${outputPath}`);
2395
2567
  }
2396
2568
  } catch (error) {
2397
2569
  if (error instanceof Error) {
2398
2570
  console.error("\u274C Error during generation:", error.message);
2571
+ if (error.message.includes("fetch") || error.message.includes("Failed to fetch")) {
2572
+ console.error("\u{1F4A1} Tip: Make sure the URL is accessible and returns a valid OpenAPI/Swagger specification");
2573
+ console.error("\u{1F4A1} Alternative: Download the specification file locally and use the file path instead");
2574
+ }
2399
2575
  } else {
2400
2576
  console.error("\u274C Unknown error during generation:", error);
2401
2577
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ng-openapi",
3
- "version": "0.0.38",
3
+ "version": "0.0.40-pr-8-feature-url-support.0",
4
4
  "description": "Generate Angular services and TypeScript types from OpenAPI/Swagger specifications",
5
5
  "keywords": [
6
6
  "angular",
@@ -61,7 +61,8 @@
61
61
  "ts-morph": "^26.0.0",
62
62
  "ts-node": "^10.9.2",
63
63
  "typescript": "^5.8.3",
64
- "@types/swagger-schema-official": "^2.0.25"
64
+ "@types/swagger-schema-official": "^2.0.25",
65
+ "js-yaml": "^4.1.0"
65
66
  },
66
67
  "peerDependencies": {
67
68
  "@angular/core": ">=15",