enlace-openapi 0.0.1-beta.1 → 0.0.1-beta.3

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
@@ -1,4 +1,3 @@
1
- #!/usr/bin/env node
2
1
  "use strict";
3
2
  var __create = Object.create;
4
3
  var __defProp = Object.defineProperty;
@@ -35,8 +34,6 @@ __export(src_exports, {
35
34
  parseSchema: () => parseSchema
36
35
  });
37
36
  module.exports = __toCommonJS(src_exports);
38
- var import_commander = require("commander");
39
- var import_fs = __toESM(require("fs"));
40
37
 
41
38
  // src/parser.ts
42
39
  var import_typescript2 = __toESM(require("typescript"));
@@ -223,9 +220,9 @@ function parseSchema(schemaFilePath, typeName) {
223
220
  compilerOptions = { ...compilerOptions, ...parsed.options };
224
221
  }
225
222
  }
226
- const program2 = import_typescript2.default.createProgram([absolutePath], compilerOptions);
227
- const checker = program2.getTypeChecker();
228
- const sourceFile = program2.getSourceFile(absolutePath);
223
+ const program = import_typescript2.default.createProgram([absolutePath], compilerOptions);
224
+ const checker = program.getTypeChecker();
225
+ const sourceFile = program.getSourceFile(absolutePath);
229
226
  if (!sourceFile) {
230
227
  throw new Error(`Could not find source file: ${absolutePath}`);
231
228
  }
@@ -310,16 +307,20 @@ function isEndpointStructure(type) {
310
307
  propNames.delete("data");
311
308
  propNames.delete("body");
312
309
  propNames.delete("error");
310
+ propNames.delete("query");
311
+ propNames.delete("formData");
313
312
  const remainingProps = [...propNames].filter(
314
313
  (name) => !name.startsWith("__@") && !name.includes("Brand")
315
314
  );
316
315
  return remainingProps.length === 0;
317
316
  }
318
- function parseEndpointType(type, path2, method, pathParams, ctx) {
317
+ function parseEndpointType(type, pathStr, method, pathParams, ctx) {
319
318
  const { checker } = ctx;
320
319
  let dataType;
321
320
  let bodyType;
322
321
  let errorType;
322
+ let queryType;
323
+ let formDataType;
323
324
  const typesToCheck = type.isIntersection() ? type.types : [type];
324
325
  for (const t of typesToCheck) {
325
326
  const props = t.getProperties();
@@ -331,32 +332,93 @@ function parseEndpointType(type, path2, method, pathParams, ctx) {
331
332
  bodyType = checker.getTypeOfSymbol(prop);
332
333
  } else if (name === "error") {
333
334
  errorType = checker.getTypeOfSymbol(prop);
335
+ } else if (name === "query") {
336
+ queryType = checker.getTypeOfSymbol(prop);
337
+ } else if (name === "formData") {
338
+ formDataType = checker.getTypeOfSymbol(prop);
334
339
  }
335
340
  }
336
341
  }
337
342
  const endpoint = {
338
- path: path2,
343
+ path: pathStr,
339
344
  method,
340
345
  responseSchema: dataType ? typeToSchema(dataType, ctx) : {},
341
346
  pathParams
342
347
  };
343
- if (bodyType && !(bodyType.flags & import_typescript2.default.TypeFlags.Never)) {
348
+ if (formDataType && !(formDataType.flags & import_typescript2.default.TypeFlags.Never)) {
349
+ endpoint.requestBodySchema = formDataTypeToSchema(formDataType, ctx);
350
+ endpoint.requestBodyContentType = "multipart/form-data";
351
+ } else if (bodyType && !(bodyType.flags & import_typescript2.default.TypeFlags.Never)) {
344
352
  endpoint.requestBodySchema = typeToSchema(bodyType, ctx);
353
+ endpoint.requestBodyContentType = "application/json";
354
+ }
355
+ if (queryType && !(queryType.flags & import_typescript2.default.TypeFlags.Never)) {
356
+ endpoint.queryParams = queryTypeToParams(queryType, ctx);
345
357
  }
346
358
  if (errorType && !(errorType.flags & import_typescript2.default.TypeFlags.Never) && !(errorType.flags & import_typescript2.default.TypeFlags.Unknown)) {
347
359
  endpoint.errorSchema = typeToSchema(errorType, ctx);
348
360
  }
349
361
  return endpoint;
350
362
  }
363
+ function queryTypeToParams(queryType, ctx) {
364
+ const { checker } = ctx;
365
+ const params = [];
366
+ const props = queryType.getProperties();
367
+ for (const prop of props) {
368
+ const propName = prop.getName();
369
+ const propType = checker.getTypeOfSymbol(prop);
370
+ const isOptional = !!(prop.flags & import_typescript2.default.SymbolFlags.Optional);
371
+ params.push({
372
+ name: propName,
373
+ in: "query",
374
+ required: !isOptional,
375
+ schema: typeToSchema(propType, ctx)
376
+ });
377
+ }
378
+ return params;
379
+ }
380
+ function formDataTypeToSchema(formDataType, ctx) {
381
+ const { checker } = ctx;
382
+ const properties = {};
383
+ const required = [];
384
+ const props = formDataType.getProperties();
385
+ for (const prop of props) {
386
+ const propName = prop.getName();
387
+ const propType = checker.getTypeOfSymbol(prop);
388
+ const isOptional = !!(prop.flags & import_typescript2.default.SymbolFlags.Optional);
389
+ const typeName = checker.typeToString(propType);
390
+ if (typeName.includes("File") || typeName.includes("Blob")) {
391
+ properties[propName] = { type: "string", format: "binary" };
392
+ } else if (propType.isUnion()) {
393
+ const hasFile = propType.types.some((t) => {
394
+ const name = checker.typeToString(t);
395
+ return name.includes("File") || name.includes("Blob");
396
+ });
397
+ if (hasFile) {
398
+ properties[propName] = { type: "string", format: "binary" };
399
+ } else {
400
+ properties[propName] = typeToSchema(propType, ctx);
401
+ }
402
+ } else {
403
+ properties[propName] = typeToSchema(propType, ctx);
404
+ }
405
+ if (!isOptional) {
406
+ required.push(propName);
407
+ }
408
+ }
409
+ const schema = {
410
+ type: "object",
411
+ properties
412
+ };
413
+ if (required.length > 0) {
414
+ schema.required = required;
415
+ }
416
+ return schema;
417
+ }
351
418
 
352
419
  // src/generator.ts
353
420
  function generateOpenAPISpec(endpoints, schemas, options = {}) {
354
- const {
355
- title = "API",
356
- version = "1.0.0",
357
- description,
358
- baseUrl
359
- } = options;
421
+ const { title = "API", version = "1.0.0", description, baseUrl } = options;
360
422
  const paths = {};
361
423
  for (const endpoint of endpoints) {
362
424
  if (!paths[endpoint.path]) {
@@ -410,11 +472,15 @@ function createOperation(endpoint) {
410
472
  }
411
473
  };
412
474
  }
475
+ if (endpoint.queryParams && endpoint.queryParams.length > 0) {
476
+ operation.parameters = endpoint.queryParams;
477
+ }
413
478
  if (endpoint.requestBodySchema && hasContent(endpoint.requestBodySchema)) {
479
+ const contentType = endpoint.requestBodyContentType || "application/json";
414
480
  operation.requestBody = {
415
481
  required: true,
416
482
  content: {
417
- "application/json": {
483
+ [contentType]: {
418
484
  schema: endpoint.requestBodySchema
419
485
  }
420
486
  }
@@ -435,29 +501,6 @@ function createOperation(endpoint) {
435
501
  function hasContent(schema) {
436
502
  return Object.keys(schema).length > 0;
437
503
  }
438
-
439
- // src/index.ts
440
- import_commander.program.name("enlace-openapi").description("Generate OpenAPI spec from TypeScript API schema").requiredOption("-s, --schema <path>", "Path to TypeScript file containing the schema type").option("-t, --type <name>", "Name of the schema type to use", "ApiSchema").option("-o, --output <path>", "Output file path (default: stdout)").option("--title <title>", "API title for OpenAPI info").option("--version <version>", "API version for OpenAPI info", "1.0.0").option("--base-url <url>", "Base URL for servers array").action((options) => {
441
- try {
442
- const { endpoints, schemas } = parseSchema(options.schema, options.type);
443
- const spec = generateOpenAPISpec(endpoints, schemas, {
444
- title: options.title,
445
- version: options.version,
446
- baseUrl: options.baseUrl
447
- });
448
- const output = JSON.stringify(spec, null, 2);
449
- if (options.output) {
450
- import_fs.default.writeFileSync(options.output, output);
451
- console.log(`OpenAPI spec written to ${options.output}`);
452
- } else {
453
- console.log(output);
454
- }
455
- } catch (error) {
456
- console.error("Error:", error instanceof Error ? error.message : error);
457
- process.exit(1);
458
- }
459
- });
460
- import_commander.program.parse();
461
504
  // Annotate the CommonJS export names for ESM import in node:
462
505
  0 && (module.exports = {
463
506
  generateOpenAPISpec,
package/dist/index.mjs CHANGED
@@ -1,9 +1,3 @@
1
- #!/usr/bin/env node
2
-
3
- // src/index.ts
4
- import { program } from "commander";
5
- import fs from "fs";
6
-
7
1
  // src/parser.ts
8
2
  import ts2 from "typescript";
9
3
  import path from "path";
@@ -189,9 +183,9 @@ function parseSchema(schemaFilePath, typeName) {
189
183
  compilerOptions = { ...compilerOptions, ...parsed.options };
190
184
  }
191
185
  }
192
- const program2 = ts2.createProgram([absolutePath], compilerOptions);
193
- const checker = program2.getTypeChecker();
194
- const sourceFile = program2.getSourceFile(absolutePath);
186
+ const program = ts2.createProgram([absolutePath], compilerOptions);
187
+ const checker = program.getTypeChecker();
188
+ const sourceFile = program.getSourceFile(absolutePath);
195
189
  if (!sourceFile) {
196
190
  throw new Error(`Could not find source file: ${absolutePath}`);
197
191
  }
@@ -276,16 +270,20 @@ function isEndpointStructure(type) {
276
270
  propNames.delete("data");
277
271
  propNames.delete("body");
278
272
  propNames.delete("error");
273
+ propNames.delete("query");
274
+ propNames.delete("formData");
279
275
  const remainingProps = [...propNames].filter(
280
276
  (name) => !name.startsWith("__@") && !name.includes("Brand")
281
277
  );
282
278
  return remainingProps.length === 0;
283
279
  }
284
- function parseEndpointType(type, path2, method, pathParams, ctx) {
280
+ function parseEndpointType(type, pathStr, method, pathParams, ctx) {
285
281
  const { checker } = ctx;
286
282
  let dataType;
287
283
  let bodyType;
288
284
  let errorType;
285
+ let queryType;
286
+ let formDataType;
289
287
  const typesToCheck = type.isIntersection() ? type.types : [type];
290
288
  for (const t of typesToCheck) {
291
289
  const props = t.getProperties();
@@ -297,32 +295,93 @@ function parseEndpointType(type, path2, method, pathParams, ctx) {
297
295
  bodyType = checker.getTypeOfSymbol(prop);
298
296
  } else if (name === "error") {
299
297
  errorType = checker.getTypeOfSymbol(prop);
298
+ } else if (name === "query") {
299
+ queryType = checker.getTypeOfSymbol(prop);
300
+ } else if (name === "formData") {
301
+ formDataType = checker.getTypeOfSymbol(prop);
300
302
  }
301
303
  }
302
304
  }
303
305
  const endpoint = {
304
- path: path2,
306
+ path: pathStr,
305
307
  method,
306
308
  responseSchema: dataType ? typeToSchema(dataType, ctx) : {},
307
309
  pathParams
308
310
  };
309
- if (bodyType && !(bodyType.flags & ts2.TypeFlags.Never)) {
311
+ if (formDataType && !(formDataType.flags & ts2.TypeFlags.Never)) {
312
+ endpoint.requestBodySchema = formDataTypeToSchema(formDataType, ctx);
313
+ endpoint.requestBodyContentType = "multipart/form-data";
314
+ } else if (bodyType && !(bodyType.flags & ts2.TypeFlags.Never)) {
310
315
  endpoint.requestBodySchema = typeToSchema(bodyType, ctx);
316
+ endpoint.requestBodyContentType = "application/json";
317
+ }
318
+ if (queryType && !(queryType.flags & ts2.TypeFlags.Never)) {
319
+ endpoint.queryParams = queryTypeToParams(queryType, ctx);
311
320
  }
312
321
  if (errorType && !(errorType.flags & ts2.TypeFlags.Never) && !(errorType.flags & ts2.TypeFlags.Unknown)) {
313
322
  endpoint.errorSchema = typeToSchema(errorType, ctx);
314
323
  }
315
324
  return endpoint;
316
325
  }
326
+ function queryTypeToParams(queryType, ctx) {
327
+ const { checker } = ctx;
328
+ const params = [];
329
+ const props = queryType.getProperties();
330
+ for (const prop of props) {
331
+ const propName = prop.getName();
332
+ const propType = checker.getTypeOfSymbol(prop);
333
+ const isOptional = !!(prop.flags & ts2.SymbolFlags.Optional);
334
+ params.push({
335
+ name: propName,
336
+ in: "query",
337
+ required: !isOptional,
338
+ schema: typeToSchema(propType, ctx)
339
+ });
340
+ }
341
+ return params;
342
+ }
343
+ function formDataTypeToSchema(formDataType, ctx) {
344
+ const { checker } = ctx;
345
+ const properties = {};
346
+ const required = [];
347
+ const props = formDataType.getProperties();
348
+ for (const prop of props) {
349
+ const propName = prop.getName();
350
+ const propType = checker.getTypeOfSymbol(prop);
351
+ const isOptional = !!(prop.flags & ts2.SymbolFlags.Optional);
352
+ const typeName = checker.typeToString(propType);
353
+ if (typeName.includes("File") || typeName.includes("Blob")) {
354
+ properties[propName] = { type: "string", format: "binary" };
355
+ } else if (propType.isUnion()) {
356
+ const hasFile = propType.types.some((t) => {
357
+ const name = checker.typeToString(t);
358
+ return name.includes("File") || name.includes("Blob");
359
+ });
360
+ if (hasFile) {
361
+ properties[propName] = { type: "string", format: "binary" };
362
+ } else {
363
+ properties[propName] = typeToSchema(propType, ctx);
364
+ }
365
+ } else {
366
+ properties[propName] = typeToSchema(propType, ctx);
367
+ }
368
+ if (!isOptional) {
369
+ required.push(propName);
370
+ }
371
+ }
372
+ const schema = {
373
+ type: "object",
374
+ properties
375
+ };
376
+ if (required.length > 0) {
377
+ schema.required = required;
378
+ }
379
+ return schema;
380
+ }
317
381
 
318
382
  // src/generator.ts
319
383
  function generateOpenAPISpec(endpoints, schemas, options = {}) {
320
- const {
321
- title = "API",
322
- version = "1.0.0",
323
- description,
324
- baseUrl
325
- } = options;
384
+ const { title = "API", version = "1.0.0", description, baseUrl } = options;
326
385
  const paths = {};
327
386
  for (const endpoint of endpoints) {
328
387
  if (!paths[endpoint.path]) {
@@ -376,11 +435,15 @@ function createOperation(endpoint) {
376
435
  }
377
436
  };
378
437
  }
438
+ if (endpoint.queryParams && endpoint.queryParams.length > 0) {
439
+ operation.parameters = endpoint.queryParams;
440
+ }
379
441
  if (endpoint.requestBodySchema && hasContent(endpoint.requestBodySchema)) {
442
+ const contentType = endpoint.requestBodyContentType || "application/json";
380
443
  operation.requestBody = {
381
444
  required: true,
382
445
  content: {
383
- "application/json": {
446
+ [contentType]: {
384
447
  schema: endpoint.requestBodySchema
385
448
  }
386
449
  }
@@ -401,29 +464,6 @@ function createOperation(endpoint) {
401
464
  function hasContent(schema) {
402
465
  return Object.keys(schema).length > 0;
403
466
  }
404
-
405
- // src/index.ts
406
- program.name("enlace-openapi").description("Generate OpenAPI spec from TypeScript API schema").requiredOption("-s, --schema <path>", "Path to TypeScript file containing the schema type").option("-t, --type <name>", "Name of the schema type to use", "ApiSchema").option("-o, --output <path>", "Output file path (default: stdout)").option("--title <title>", "API title for OpenAPI info").option("--version <version>", "API version for OpenAPI info", "1.0.0").option("--base-url <url>", "Base URL for servers array").action((options) => {
407
- try {
408
- const { endpoints, schemas } = parseSchema(options.schema, options.type);
409
- const spec = generateOpenAPISpec(endpoints, schemas, {
410
- title: options.title,
411
- version: options.version,
412
- baseUrl: options.baseUrl
413
- });
414
- const output = JSON.stringify(spec, null, 2);
415
- if (options.output) {
416
- fs.writeFileSync(options.output, output);
417
- console.log(`OpenAPI spec written to ${options.output}`);
418
- } else {
419
- console.log(output);
420
- }
421
- } catch (error) {
422
- console.error("Error:", error instanceof Error ? error.message : error);
423
- process.exit(1);
424
- }
425
- });
426
- program.parse();
427
467
  export {
428
468
  generateOpenAPISpec,
429
469
  parseSchema
package/package.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "enlace-openapi",
3
- "version": "0.0.1-beta.1",
3
+ "version": "0.0.1-beta.3",
4
4
  "license": "MIT",
5
5
  "bin": {
6
- "enlace-openapi": "./dist/index.mjs"
6
+ "enlace-openapi": "./dist/cli.mjs"
7
7
  },
8
8
  "files": [
9
9
  "dist"
@@ -15,18 +15,17 @@
15
15
  "require": "./dist/index.js"
16
16
  }
17
17
  },
18
- "scripts": {
19
- "dev": "tsup --watch",
20
- "build": "tsup",
21
- "typecheck": "tsc --noEmit",
22
- "lint": "eslint src --max-warnings 0",
23
- "prepublishOnly": "npm run build && npm run typecheck && npm run lint"
24
- },
25
18
  "dependencies": {
26
19
  "commander": "^12.1.0",
27
20
  "typescript": "^5.9.2"
28
21
  },
29
22
  "devDependencies": {
30
23
  "@types/node": "^22.19.2"
24
+ },
25
+ "scripts": {
26
+ "dev": "tsup --watch",
27
+ "build": "tsup",
28
+ "typecheck": "tsc --noEmit",
29
+ "lint": "eslint src --max-warnings 0"
31
30
  }
32
- }
31
+ }