api-sdk-gen 1.0.4 → 1.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -3,17 +3,15 @@
3
3
 
4
4
  // src/generators/typescript/util.ts
5
5
  function pascalCase(str) {
6
- return str.replace(/(^\w|-\w)/g, clearAndUpper).replace(/_/g, "");
7
- }
8
- function clearAndUpper(text) {
9
- return text.replace(/-/, "").toUpperCase();
6
+ return str.replace(/(^|[-_])(\w)/g, (_, __, c) => c.toUpperCase());
10
7
  }
11
8
  function resolveSchema(ref) {
12
9
  const parts = ref.split("#/components/schemas/");
13
- return pascalCase(parts[1]);
10
+ return pascalCase(parts[1] || parts[0]);
14
11
  }
15
12
 
16
13
  // src/generators/typescript/schema_to_ts.ts
14
+ var import_ts_morph = require("ts-morph");
17
15
  function schemaToTypeLiteral(schema, doc, refPrefix) {
18
16
  return (writer) => {
19
17
  if ("$ref" in schema) {
@@ -22,34 +20,79 @@ function schemaToTypeLiteral(schema, doc, refPrefix) {
22
20
  writer.write(
23
21
  schema.enum.map((value) => JSON.stringify(value)).join(" | ")
24
22
  );
23
+ } else if (schema.allOf && schema.allOf.length > 0) {
24
+ const uniqueTypes = /* @__PURE__ */ new Set();
25
+ for (const subSchema of schema.allOf) {
26
+ const subWriter = new import_ts_morph.CodeBlockWriter();
27
+ schemaToTypeLiteral(subSchema, doc, refPrefix)(subWriter);
28
+ const str = subWriter.toString().trim();
29
+ if (str) uniqueTypes.add(str);
30
+ }
31
+ const typesArray = Array.from(uniqueTypes);
32
+ if (typesArray.length > 1) {
33
+ writer.write("(");
34
+ writer.write(typesArray.join(" & "));
35
+ writer.write(")");
36
+ } else if (typesArray.length === 1) {
37
+ writer.write(typesArray[0]);
38
+ } else {
39
+ writer.write("unknown");
40
+ }
25
41
  } else if (schema.anyOf || schema.oneOf) {
26
- (schema.anyOf || schema.oneOf)?.forEach((subSchema, index) => {
27
- if (index > 0) {
28
- writer.write(" | ");
42
+ const items = schema.anyOf || schema.oneOf;
43
+ if (items && items.length > 0) {
44
+ const uniqueTypes = /* @__PURE__ */ new Set();
45
+ for (const subSchema of items) {
46
+ const subWriter = new import_ts_morph.CodeBlockWriter();
47
+ schemaToTypeLiteral(subSchema, doc, refPrefix)(subWriter);
48
+ const str = subWriter.toString().trim();
49
+ if (str) uniqueTypes.add(str);
29
50
  }
30
- schemaToTypeLiteral(subSchema, doc, refPrefix)(writer);
31
- });
51
+ const typesArray = Array.from(uniqueTypes);
52
+ if (typesArray.length > 1) {
53
+ writer.write("(");
54
+ writer.write(typesArray.join(" | "));
55
+ writer.write(")");
56
+ } else if (typesArray.length === 1) {
57
+ writer.write(typesArray[0]);
58
+ } else {
59
+ writer.write("unknown");
60
+ }
61
+ }
62
+ } else if (schema.type === "object" || !schema.type && schema.properties) {
63
+ const properties = schema.properties || {};
64
+ const propEntries = Object.entries(properties);
65
+ if (propEntries.length === 0) {
66
+ if (schema.additionalProperties && typeof schema.additionalProperties === "object" && Object.keys(schema.additionalProperties).length > 0) {
67
+ writer.write("Record<string, ");
68
+ schemaToTypeLiteral(schema.additionalProperties, doc, refPrefix)(writer);
69
+ writer.write(">");
70
+ } else {
71
+ writer.write("Record<string, unknown>");
72
+ }
73
+ } else {
74
+ const requiredProps = new Set(schema.required || []);
75
+ writer.write("{").newLine();
76
+ for (const [propName, propSchema] of propEntries) {
77
+ writer.indent(() => {
78
+ writer.write(propName);
79
+ if (!requiredProps.has(propName)) {
80
+ writer.write("?");
81
+ }
82
+ writer.write(": ");
83
+ schemaToTypeLiteral(propSchema, doc, refPrefix)(writer);
84
+ writer.write(";").newLine();
85
+ });
86
+ }
87
+ if (schema.additionalProperties) {
88
+ writer.indent(() => {
89
+ writer.write("[k: string]: unknown;").newLine();
90
+ });
91
+ }
92
+ writer.write("}");
93
+ }
32
94
  } else if (schema.type) {
33
95
  switch (schema.type) {
34
- case "object": {
35
- const requiredProps = new Set(schema.required || []);
36
- writer.write("{").newLine();
37
- for (const [propName, propSchema] of Object.entries(
38
- schema.properties || {}
39
- )) {
40
- writer.indent(() => {
41
- writer.write(propName);
42
- if (!requiredProps.has(propName)) {
43
- writer.write("?");
44
- }
45
- writer.write(": ");
46
- schemaToTypeLiteral(propSchema, doc, refPrefix)(writer);
47
- writer.write(";").newLine();
48
- });
49
- }
50
- writer.write("}");
51
- break;
52
- }
53
96
  case "array":
54
97
  if (Array.isArray(schema.items)) {
55
98
  writer.write("[");
@@ -118,12 +161,33 @@ var jsonSchemaTags = [
118
161
  "format",
119
162
  "example"
120
163
  ];
164
+ var standardFormatsWithRedundantPatterns = /* @__PURE__ */ new Set([
165
+ "date",
166
+ "date-time",
167
+ "uuid",
168
+ "email",
169
+ "ipv4",
170
+ "ipv6",
171
+ "uri",
172
+ "url"
173
+ ]);
174
+ function shouldIgnorePattern(schema) {
175
+ const format = schema.format;
176
+ if (format && standardFormatsWithRedundantPatterns.has(format)) {
177
+ return true;
178
+ }
179
+ const subFormats = (schema.oneOf || schema.anyOf)?.map((s) => s?.format).filter(Boolean);
180
+ if (subFormats && subFormats.some((f) => standardFormatsWithRedundantPatterns.has(f))) {
181
+ return true;
182
+ }
183
+ return false;
184
+ }
121
185
  function createInterfaceModel(doc, file, name, schema) {
122
186
  if (!schema.properties || Object.keys(schema.properties).length === 0) {
123
187
  file.addTypeAlias({
124
188
  name: pascalCase(name),
125
189
  isExported: true,
126
- type: "{}"
190
+ type: schema.additionalProperties ? "Record<string, unknown>" : "{}"
127
191
  });
128
192
  return;
129
193
  }
@@ -162,12 +226,23 @@ function generateSchemaDocs(schema, doc) {
162
226
  });
163
227
  }
164
228
  for (const tagName of jsonSchemaTags) {
229
+ if (tagName === "pattern" && shouldIgnorePattern(schema)) {
230
+ continue;
231
+ }
165
232
  const value = schema[tagName];
166
233
  if (value !== void 0) {
167
234
  jsdoc.tags?.push({
168
235
  tagName,
169
236
  text: String(value)
170
237
  });
238
+ } else if (tagName === "format") {
239
+ const subFormats = (schema.oneOf || schema.anyOf)?.map((sub) => sub?.format).filter(Boolean);
240
+ if (subFormats && subFormats.length > 0) {
241
+ jsdoc.tags?.push({
242
+ tagName: "format",
243
+ text: Array.from(new Set(subFormats)).join(" | ")
244
+ });
245
+ }
171
246
  }
172
247
  }
173
248
  if (!jsdoc.tags?.length && !jsdoc.description) {
@@ -184,10 +259,7 @@ function generatePropertySignature(doc, propName, propSchema) {
184
259
  };
185
260
  }
186
261
  function createModel(doc, file, name, schema) {
187
- if ("$ref" in schema) {
188
- return;
189
- }
190
- if (schema.type === "object" && schema.properties) {
262
+ if (!("$ref" in schema) && (schema.type === "object" || !schema.type && schema.properties) && schema.properties) {
191
263
  createInterfaceModel(doc, file, name, schema);
192
264
  return;
193
265
  }
@@ -199,19 +271,49 @@ function createModel(doc, file, name, schema) {
199
271
  docs: jsdoc ? [jsdoc] : []
200
272
  });
201
273
  }
274
+ function findReferencedSchemas(obj, refs = /* @__PURE__ */ new Set()) {
275
+ if (!obj || typeof obj !== "object") return refs;
276
+ for (const [k, v] of Object.entries(obj)) {
277
+ if (k === "$ref" && typeof v === "string") {
278
+ const parts = v.split("#/components/schemas/");
279
+ if (parts.length > 1 && parts[1]) {
280
+ refs.add(parts[1]);
281
+ }
282
+ } else {
283
+ findReferencedSchemas(v, refs);
284
+ }
285
+ }
286
+ return refs;
287
+ }
202
288
  async function generateModels(file, config, doc) {
203
289
  const schemas = doc?.components?.schemas;
204
- if (!schemas) {
205
- return;
290
+ if (schemas) {
291
+ for (const [schemaName, schema] of Object.entries(schemas)) {
292
+ createModel(doc, file, schemaName, schema);
293
+ }
206
294
  }
207
- for (const [schemaName, schema] of Object.entries(schemas)) {
208
- createModel(doc, file, schemaName, schema);
295
+ const referencedSchemas = findReferencedSchemas(doc);
296
+ for (const refName of referencedSchemas) {
297
+ const pascalName = pascalCase(refName);
298
+ if (!file.getTypeAlias(pascalName) && !file.getInterface(pascalName)) {
299
+ file.addTypeAlias({
300
+ name: pascalName,
301
+ isExported: true,
302
+ type: "Record<string, unknown>",
303
+ docs: [
304
+ {
305
+ description: `Aviso: Este schema n\xE3o existe ou n\xE3o foi declarado na especifica\xE7\xE3o OpenAPI ('#/components/schemas/${refName}').
306
+ Gerado automaticamente como fallback para evitar quebras de refer\xEAncia e erros de compila\xE7\xE3o.`
307
+ }
308
+ ]
309
+ });
310
+ }
209
311
  }
210
312
  }
211
313
 
212
314
  // src/index.ts
213
315
  var import_promises = require("fs/promises");
214
- var import_ts_morph = require("ts-morph");
316
+ var import_ts_morph2 = require("ts-morph");
215
317
  var import_node_path = require("path");
216
318
 
217
319
  // src/generators/typescript/api_generator.ts
@@ -339,28 +441,50 @@ async function generateApiType(file, config, doc) {
339
441
  // src/index.ts
340
442
  var import_commander = require("commander");
341
443
  var program = new import_commander.Command();
342
- program.name("api-sdk-gen").description("Generate TypeScript SDK from OpenAPI specification").version("1.0.0").requiredOption("-u, --url <url>", "URL of the OpenAPI JSON specification").requiredOption("-b, --base <url>", "Base URL for the API calls").option("-o, --output <dir>", "Output directory", "./output").action(async (options) => {
444
+ program.name("api-sdk-gen").description("Generate TypeScript SDK from OpenAPI specification").version("1.0.0").requiredOption("-u, --url <url>", "URL or file path of the OpenAPI JSON specification").option("-b, --base <url>", "Base URL for the API calls", "http://localhost").option("-o, --output <dir>", "Output directory", "./output").action(async (options) => {
343
445
  const config = {
344
446
  url: options.url,
345
447
  api_base: options.base,
346
448
  outputDir: options.output
347
449
  };
348
450
  try {
349
- console.log(`Fetching OpenAPI spec from: ${config.url}...`);
350
- const res = await fetch(config.url);
351
- if (!res.ok) {
352
- throw new Error(`Failed to fetch OpenAPI spec: ${res.statusText}`);
451
+ let openapi;
452
+ if (config.url.startsWith("http://") || config.url.startsWith("https://")) {
453
+ console.log(`Fetching OpenAPI spec from: ${config.url}...`);
454
+ const res = await fetch(config.url);
455
+ if (!res.ok) {
456
+ throw new Error(`Failed to fetch OpenAPI spec: ${res.statusText}`);
457
+ }
458
+ openapi = await res.json();
459
+ } else {
460
+ console.log(`Reading OpenAPI spec from file: ${config.url}...`);
461
+ const content = await (0, import_promises.readFile)(config.url, "utf-8");
462
+ openapi = JSON.parse(content);
353
463
  }
354
- const openapi = await res.json();
355
464
  console.log(`Generating SDK to: ${config.outputDir}...`);
356
465
  await (0, import_promises.mkdir)(config.outputDir, { recursive: true });
357
- const project = new import_ts_morph.Project();
466
+ const project = new import_ts_morph2.Project();
358
467
  const modelsFile = project.createSourceFile((0, import_node_path.join)(config.outputDir, "models.ts"), "", { overwrite: true });
359
468
  const apiFile = project.createSourceFile((0, import_node_path.join)(config.outputDir, "api.ts"), "", { overwrite: true });
360
- const templatePath = (0, import_node_path.join)(__dirname, "../templates", "core.ts");
469
+ const templatePathCandidates = [
470
+ (0, import_node_path.join)(__dirname, "../templates", "core.ts"),
471
+ (0, import_node_path.join)(__dirname, "templates", "core.ts"),
472
+ (0, import_node_path.join)(process.cwd(), "templates", "core.ts")
473
+ ];
474
+ let templateContent = "";
475
+ for (const candidate of templatePathCandidates) {
476
+ try {
477
+ templateContent = await (0, import_promises.readFile)(candidate, "utf-8");
478
+ break;
479
+ } catch {
480
+ }
481
+ }
482
+ if (!templateContent) {
483
+ throw new Error("Template core.ts not found");
484
+ }
361
485
  const coreFile = project.createSourceFile(
362
486
  (0, import_node_path.join)(config.outputDir, "core.ts"),
363
- await (0, import_promises.readFile)(templatePath, "utf-8"),
487
+ templateContent,
364
488
  { overwrite: true }
365
489
  );
366
490
  await generateModels(modelsFile, config, openapi);
package/dist/index.mjs CHANGED
@@ -9,17 +9,15 @@ var __dirname = /* @__PURE__ */ getDirname();
9
9
 
10
10
  // src/generators/typescript/util.ts
11
11
  function pascalCase(str) {
12
- return str.replace(/(^\w|-\w)/g, clearAndUpper).replace(/_/g, "");
13
- }
14
- function clearAndUpper(text) {
15
- return text.replace(/-/, "").toUpperCase();
12
+ return str.replace(/(^|[-_])(\w)/g, (_, __, c) => c.toUpperCase());
16
13
  }
17
14
  function resolveSchema(ref) {
18
15
  const parts = ref.split("#/components/schemas/");
19
- return pascalCase(parts[1]);
16
+ return pascalCase(parts[1] || parts[0]);
20
17
  }
21
18
 
22
19
  // src/generators/typescript/schema_to_ts.ts
20
+ import { CodeBlockWriter } from "ts-morph";
23
21
  function schemaToTypeLiteral(schema, doc, refPrefix) {
24
22
  return (writer) => {
25
23
  if ("$ref" in schema) {
@@ -28,34 +26,79 @@ function schemaToTypeLiteral(schema, doc, refPrefix) {
28
26
  writer.write(
29
27
  schema.enum.map((value) => JSON.stringify(value)).join(" | ")
30
28
  );
29
+ } else if (schema.allOf && schema.allOf.length > 0) {
30
+ const uniqueTypes = /* @__PURE__ */ new Set();
31
+ for (const subSchema of schema.allOf) {
32
+ const subWriter = new CodeBlockWriter();
33
+ schemaToTypeLiteral(subSchema, doc, refPrefix)(subWriter);
34
+ const str = subWriter.toString().trim();
35
+ if (str) uniqueTypes.add(str);
36
+ }
37
+ const typesArray = Array.from(uniqueTypes);
38
+ if (typesArray.length > 1) {
39
+ writer.write("(");
40
+ writer.write(typesArray.join(" & "));
41
+ writer.write(")");
42
+ } else if (typesArray.length === 1) {
43
+ writer.write(typesArray[0]);
44
+ } else {
45
+ writer.write("unknown");
46
+ }
31
47
  } else if (schema.anyOf || schema.oneOf) {
32
- (schema.anyOf || schema.oneOf)?.forEach((subSchema, index) => {
33
- if (index > 0) {
34
- writer.write(" | ");
48
+ const items = schema.anyOf || schema.oneOf;
49
+ if (items && items.length > 0) {
50
+ const uniqueTypes = /* @__PURE__ */ new Set();
51
+ for (const subSchema of items) {
52
+ const subWriter = new CodeBlockWriter();
53
+ schemaToTypeLiteral(subSchema, doc, refPrefix)(subWriter);
54
+ const str = subWriter.toString().trim();
55
+ if (str) uniqueTypes.add(str);
35
56
  }
36
- schemaToTypeLiteral(subSchema, doc, refPrefix)(writer);
37
- });
57
+ const typesArray = Array.from(uniqueTypes);
58
+ if (typesArray.length > 1) {
59
+ writer.write("(");
60
+ writer.write(typesArray.join(" | "));
61
+ writer.write(")");
62
+ } else if (typesArray.length === 1) {
63
+ writer.write(typesArray[0]);
64
+ } else {
65
+ writer.write("unknown");
66
+ }
67
+ }
68
+ } else if (schema.type === "object" || !schema.type && schema.properties) {
69
+ const properties = schema.properties || {};
70
+ const propEntries = Object.entries(properties);
71
+ if (propEntries.length === 0) {
72
+ if (schema.additionalProperties && typeof schema.additionalProperties === "object" && Object.keys(schema.additionalProperties).length > 0) {
73
+ writer.write("Record<string, ");
74
+ schemaToTypeLiteral(schema.additionalProperties, doc, refPrefix)(writer);
75
+ writer.write(">");
76
+ } else {
77
+ writer.write("Record<string, unknown>");
78
+ }
79
+ } else {
80
+ const requiredProps = new Set(schema.required || []);
81
+ writer.write("{").newLine();
82
+ for (const [propName, propSchema] of propEntries) {
83
+ writer.indent(() => {
84
+ writer.write(propName);
85
+ if (!requiredProps.has(propName)) {
86
+ writer.write("?");
87
+ }
88
+ writer.write(": ");
89
+ schemaToTypeLiteral(propSchema, doc, refPrefix)(writer);
90
+ writer.write(";").newLine();
91
+ });
92
+ }
93
+ if (schema.additionalProperties) {
94
+ writer.indent(() => {
95
+ writer.write("[k: string]: unknown;").newLine();
96
+ });
97
+ }
98
+ writer.write("}");
99
+ }
38
100
  } else if (schema.type) {
39
101
  switch (schema.type) {
40
- case "object": {
41
- const requiredProps = new Set(schema.required || []);
42
- writer.write("{").newLine();
43
- for (const [propName, propSchema] of Object.entries(
44
- schema.properties || {}
45
- )) {
46
- writer.indent(() => {
47
- writer.write(propName);
48
- if (!requiredProps.has(propName)) {
49
- writer.write("?");
50
- }
51
- writer.write(": ");
52
- schemaToTypeLiteral(propSchema, doc, refPrefix)(writer);
53
- writer.write(";").newLine();
54
- });
55
- }
56
- writer.write("}");
57
- break;
58
- }
59
102
  case "array":
60
103
  if (Array.isArray(schema.items)) {
61
104
  writer.write("[");
@@ -124,12 +167,33 @@ var jsonSchemaTags = [
124
167
  "format",
125
168
  "example"
126
169
  ];
170
+ var standardFormatsWithRedundantPatterns = /* @__PURE__ */ new Set([
171
+ "date",
172
+ "date-time",
173
+ "uuid",
174
+ "email",
175
+ "ipv4",
176
+ "ipv6",
177
+ "uri",
178
+ "url"
179
+ ]);
180
+ function shouldIgnorePattern(schema) {
181
+ const format = schema.format;
182
+ if (format && standardFormatsWithRedundantPatterns.has(format)) {
183
+ return true;
184
+ }
185
+ const subFormats = (schema.oneOf || schema.anyOf)?.map((s) => s?.format).filter(Boolean);
186
+ if (subFormats && subFormats.some((f) => standardFormatsWithRedundantPatterns.has(f))) {
187
+ return true;
188
+ }
189
+ return false;
190
+ }
127
191
  function createInterfaceModel(doc, file, name, schema) {
128
192
  if (!schema.properties || Object.keys(schema.properties).length === 0) {
129
193
  file.addTypeAlias({
130
194
  name: pascalCase(name),
131
195
  isExported: true,
132
- type: "{}"
196
+ type: schema.additionalProperties ? "Record<string, unknown>" : "{}"
133
197
  });
134
198
  return;
135
199
  }
@@ -168,12 +232,23 @@ function generateSchemaDocs(schema, doc) {
168
232
  });
169
233
  }
170
234
  for (const tagName of jsonSchemaTags) {
235
+ if (tagName === "pattern" && shouldIgnorePattern(schema)) {
236
+ continue;
237
+ }
171
238
  const value = schema[tagName];
172
239
  if (value !== void 0) {
173
240
  jsdoc.tags?.push({
174
241
  tagName,
175
242
  text: String(value)
176
243
  });
244
+ } else if (tagName === "format") {
245
+ const subFormats = (schema.oneOf || schema.anyOf)?.map((sub) => sub?.format).filter(Boolean);
246
+ if (subFormats && subFormats.length > 0) {
247
+ jsdoc.tags?.push({
248
+ tagName: "format",
249
+ text: Array.from(new Set(subFormats)).join(" | ")
250
+ });
251
+ }
177
252
  }
178
253
  }
179
254
  if (!jsdoc.tags?.length && !jsdoc.description) {
@@ -190,10 +265,7 @@ function generatePropertySignature(doc, propName, propSchema) {
190
265
  };
191
266
  }
192
267
  function createModel(doc, file, name, schema) {
193
- if ("$ref" in schema) {
194
- return;
195
- }
196
- if (schema.type === "object" && schema.properties) {
268
+ if (!("$ref" in schema) && (schema.type === "object" || !schema.type && schema.properties) && schema.properties) {
197
269
  createInterfaceModel(doc, file, name, schema);
198
270
  return;
199
271
  }
@@ -205,13 +277,43 @@ function createModel(doc, file, name, schema) {
205
277
  docs: jsdoc ? [jsdoc] : []
206
278
  });
207
279
  }
280
+ function findReferencedSchemas(obj, refs = /* @__PURE__ */ new Set()) {
281
+ if (!obj || typeof obj !== "object") return refs;
282
+ for (const [k, v] of Object.entries(obj)) {
283
+ if (k === "$ref" && typeof v === "string") {
284
+ const parts = v.split("#/components/schemas/");
285
+ if (parts.length > 1 && parts[1]) {
286
+ refs.add(parts[1]);
287
+ }
288
+ } else {
289
+ findReferencedSchemas(v, refs);
290
+ }
291
+ }
292
+ return refs;
293
+ }
208
294
  async function generateModels(file, config, doc) {
209
295
  const schemas = doc?.components?.schemas;
210
- if (!schemas) {
211
- return;
296
+ if (schemas) {
297
+ for (const [schemaName, schema] of Object.entries(schemas)) {
298
+ createModel(doc, file, schemaName, schema);
299
+ }
212
300
  }
213
- for (const [schemaName, schema] of Object.entries(schemas)) {
214
- createModel(doc, file, schemaName, schema);
301
+ const referencedSchemas = findReferencedSchemas(doc);
302
+ for (const refName of referencedSchemas) {
303
+ const pascalName = pascalCase(refName);
304
+ if (!file.getTypeAlias(pascalName) && !file.getInterface(pascalName)) {
305
+ file.addTypeAlias({
306
+ name: pascalName,
307
+ isExported: true,
308
+ type: "Record<string, unknown>",
309
+ docs: [
310
+ {
311
+ description: `Aviso: Este schema n\xE3o existe ou n\xE3o foi declarado na especifica\xE7\xE3o OpenAPI ('#/components/schemas/${refName}').
312
+ Gerado automaticamente como fallback para evitar quebras de refer\xEAncia e erros de compila\xE7\xE3o.`
313
+ }
314
+ ]
315
+ });
316
+ }
215
317
  }
216
318
  }
217
319
 
@@ -345,28 +447,50 @@ async function generateApiType(file, config, doc) {
345
447
  // src/index.ts
346
448
  import { Command } from "commander";
347
449
  var program = new Command();
348
- program.name("api-sdk-gen").description("Generate TypeScript SDK from OpenAPI specification").version("1.0.0").requiredOption("-u, --url <url>", "URL of the OpenAPI JSON specification").requiredOption("-b, --base <url>", "Base URL for the API calls").option("-o, --output <dir>", "Output directory", "./output").action(async (options) => {
450
+ program.name("api-sdk-gen").description("Generate TypeScript SDK from OpenAPI specification").version("1.0.0").requiredOption("-u, --url <url>", "URL or file path of the OpenAPI JSON specification").option("-b, --base <url>", "Base URL for the API calls", "http://localhost").option("-o, --output <dir>", "Output directory", "./output").action(async (options) => {
349
451
  const config = {
350
452
  url: options.url,
351
453
  api_base: options.base,
352
454
  outputDir: options.output
353
455
  };
354
456
  try {
355
- console.log(`Fetching OpenAPI spec from: ${config.url}...`);
356
- const res = await fetch(config.url);
357
- if (!res.ok) {
358
- throw new Error(`Failed to fetch OpenAPI spec: ${res.statusText}`);
457
+ let openapi;
458
+ if (config.url.startsWith("http://") || config.url.startsWith("https://")) {
459
+ console.log(`Fetching OpenAPI spec from: ${config.url}...`);
460
+ const res = await fetch(config.url);
461
+ if (!res.ok) {
462
+ throw new Error(`Failed to fetch OpenAPI spec: ${res.statusText}`);
463
+ }
464
+ openapi = await res.json();
465
+ } else {
466
+ console.log(`Reading OpenAPI spec from file: ${config.url}...`);
467
+ const content = await readFile(config.url, "utf-8");
468
+ openapi = JSON.parse(content);
359
469
  }
360
- const openapi = await res.json();
361
470
  console.log(`Generating SDK to: ${config.outputDir}...`);
362
471
  await mkdir(config.outputDir, { recursive: true });
363
472
  const project = new Project();
364
473
  const modelsFile = project.createSourceFile(join(config.outputDir, "models.ts"), "", { overwrite: true });
365
474
  const apiFile = project.createSourceFile(join(config.outputDir, "api.ts"), "", { overwrite: true });
366
- const templatePath = join(__dirname, "../templates", "core.ts");
475
+ const templatePathCandidates = [
476
+ join(__dirname, "../templates", "core.ts"),
477
+ join(__dirname, "templates", "core.ts"),
478
+ join(process.cwd(), "templates", "core.ts")
479
+ ];
480
+ let templateContent = "";
481
+ for (const candidate of templatePathCandidates) {
482
+ try {
483
+ templateContent = await readFile(candidate, "utf-8");
484
+ break;
485
+ } catch {
486
+ }
487
+ }
488
+ if (!templateContent) {
489
+ throw new Error("Template core.ts not found");
490
+ }
367
491
  const coreFile = project.createSourceFile(
368
492
  join(config.outputDir, "core.ts"),
369
- await readFile(templatePath, "utf-8"),
493
+ templateContent,
370
494
  { overwrite: true }
371
495
  );
372
496
  await generateModels(modelsFile, config, openapi);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "api-sdk-gen",
3
- "version": "1.0.4",
3
+ "version": "1.0.5",
4
4
  "description": "",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",
package/templates/core.ts CHANGED
@@ -27,5 +27,16 @@ type Pretty<T> = {
27
27
  [K in keyof T]: T[K];
28
28
  };
29
29
 
30
- export type QueryParams<Api extends ApiDoc, Path extends keyof Api, Method extends keyof Api[Path]> = Api[Path][Method] extends { parameters: infer P } ? P : never
31
- export type ResponseStatus<Api extends ApiDoc, Path extends keyof Api, Method extends keyof Api[Path], Status extends keyof Api[Path][Method]['responses']> = Api[Path][Method]['responses'][Status]
30
+ export type QueryParams<Api extends ApiDoc, Path extends keyof Api, Method extends keyof Api[Path]> = Api[Path][Method] extends { parameters: infer P } ? P : never;
31
+ export type ResponseStatus<
32
+ Api extends ApiDoc,
33
+ Path extends keyof Api,
34
+ Method extends keyof Api[Path],
35
+ Status extends Api[Path][Method] extends { responses: Record<string | number, any> }
36
+ ? keyof Api[Path][Method]['responses']
37
+ : never
38
+ > = Api[Path][Method] extends { responses: infer R }
39
+ ? Status extends keyof R
40
+ ? R[Status]
41
+ : never
42
+ : never;