apigen-ts 1.2.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/_template.ts CHANGED
@@ -69,7 +69,7 @@ export class ApiClient {
69
69
  const headers = new Headers({ ...configHeaders, ...opts.headers })
70
70
  const ct = headers.get("content-type") ?? "application/json"
71
71
 
72
- let body: FormData | URLSearchParams | string | undefined = undefined
72
+ let body: FormData | URLSearchParams | string | null = null
73
73
 
74
74
  if (ct === "multipart/form-data" || ct === "application/x-www-form-urlencoded") {
75
75
  // https://stackoverflow.com/a/61053359/3664464
package/dist/cli.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { a as apigen, g as getCliConfig } from './main-l0LIDQ3K.js';
2
+ import { a as apigen, g as getCliConfig } from './main-B8jUVaQq.js';
3
3
  import 'fs/promises';
4
4
  import 'path';
5
5
  import 'url';
@@ -2,7 +2,7 @@ import fs from 'fs/promises';
2
2
  import { join, dirname } from 'path';
3
3
  import { fileURLToPath } from 'url';
4
4
  import { cli } from 'cleye';
5
- import redocly, { BaseResolver } from '@redocly/openapi-core';
5
+ import { bundle, BaseResolver, createConfig } from '@redocly/openapi-core';
6
6
  import { filterEmpty, filterNullable } from 'array-utils-ts';
7
7
  import { get, uniq, upperFirst, isArray, isBoolean, sortBy, isObject, lowerFirst, uniqBy } from 'lodash-es';
8
8
  import { convertObj } from 'swagger2openapi';
@@ -10,7 +10,7 @@ import ts from 'typescript';
10
10
  import path from 'node:path';
11
11
 
12
12
  var name = "apigen-ts";
13
- var version = "1.2.0";
13
+ var version = "1.3.0";
14
14
 
15
15
  const initCtx = (config) => {
16
16
  return {
@@ -20,6 +20,7 @@ const initCtx = (config) => {
20
20
  doc: { openapi: "3.1.0" },
21
21
  parseDates: false,
22
22
  inlineEnums: false,
23
+ fetchOptions: false,
23
24
  headers: {},
24
25
  ...config,
25
26
  logTag: "",
@@ -55,21 +56,51 @@ const getCliConfig = () => {
55
56
  description: "Use inline enums instead of enum types",
56
57
  default: false
57
58
  },
59
+ fetchOptions: {
60
+ type: Boolean,
61
+ description: "Add fetch options (e.g. AbortSignal) as last argument to each method",
62
+ default: false
63
+ },
58
64
  header: {
59
65
  type: [String],
60
66
  alias: "H",
61
67
  description: 'HTTP header as key=value (e.g., -H "x-api-key: your-key"). Used only when generating code.',
62
68
  default: []
69
+ },
70
+ filterPaths: {
71
+ type: String,
72
+ description: "Filter endpoints by path regex (e.g., --filter-paths '^/accounts')"
73
+ },
74
+ includeTags: {
75
+ type: [String],
76
+ description: "Only include operations with these tags (comma-separated or repeated flag)",
77
+ default: []
78
+ },
79
+ excludeTags: {
80
+ type: [String],
81
+ description: "Exclude operations with these tags (comma-separated or repeated flag)",
82
+ default: []
63
83
  }
64
84
  }
65
85
  });
86
+ const parseTags = (items) => {
87
+ const tags = items.flatMap((x) => x.split(",")).map((x) => x.trim()).filter(Boolean);
88
+ return tags.length ? tags : void 0;
89
+ };
90
+ const filterPaths = argv.flags.filterPaths ? new RegExp(argv.flags.filterPaths) : void 0;
91
+ const includeTags = parseTags(argv.flags.includeTags);
92
+ const excludeTags = parseTags(argv.flags.excludeTags);
66
93
  const config = {
67
94
  source: argv._.source,
68
95
  output: argv._.output ?? null,
69
96
  name: argv.flags.name,
70
97
  parseDates: argv.flags.parseDates,
71
98
  inlineEnums: argv.flags.inlineEnums,
72
- headers: parseHeaders(argv.flags.header)
99
+ fetchOptions: argv.flags.fetchOptions,
100
+ headers: parseHeaders(argv.flags.header),
101
+ ...filterPaths ? { filterPaths } : {},
102
+ ...includeTags ? { includeTags } : {},
103
+ ...excludeTags ? { excludeTags } : {}
73
104
  };
74
105
  return config;
75
106
  };
@@ -108,8 +139,10 @@ const getReqSchema = (ctx, config) => {
108
139
  return void 0;
109
140
  };
110
141
  const getRepSchema = (ctx, config) => {
111
- const successCodes = Object.keys(config.responses ?? {}).filter((x) => x.startsWith("2")).filter((x) => get(config, ["responses", x, "content"]));
112
- const cts = Object.entries(get(config, ["responses", successCodes[0], "content"], {})).filter((x) => x[1].schema);
142
+ const successCodes = Object.keys(config.responses ?? {}).filter((x) => x.startsWith("2")).filter((x) => get(config, ["responses", x]));
143
+ const firstSuccess = unref(ctx, get(config, ["responses", successCodes[0]], {}));
144
+ if (!firstSuccess || !("content" in firstSuccess)) return void 0;
145
+ const cts = Object.entries(firstSuccess.content ?? {}).filter((x) => x[1].schema);
113
146
  if (cts.length === 0) return void 0;
114
147
  const ctJson = cts.find((x) => x[0].startsWith("application/json"));
115
148
  if (ctJson) return ctJson[1].schema;
@@ -205,8 +238,16 @@ const makeInlineEnum = (s) => {
205
238
  return f$2.createUnionTypeNode(tokens.map((x) => f$2.createLiteralTypeNode(x)));
206
239
  }
207
240
  if (s.type === "number") {
208
- const tokens = uniq(values).map((x) => f$2.createNumericLiteral(x));
209
- return f$2.createUnionTypeNode(tokens.map((x) => f$2.createLiteralTypeNode(x)));
241
+ const tokens = uniq(values).map((x) => {
242
+ const n = x;
243
+ if (n < 0) {
244
+ return f$2.createLiteralTypeNode(
245
+ f$2.createPrefixUnaryExpression(ts.SyntaxKind.MinusToken, f$2.createNumericLiteral(-n))
246
+ );
247
+ }
248
+ return f$2.createLiteralTypeNode(f$2.createNumericLiteral(n));
249
+ });
250
+ return f$2.createUnionTypeNode(tokens);
210
251
  }
211
252
  if (s.type === "boolean") {
212
253
  const tokens = [];
@@ -256,22 +297,29 @@ const makeType = (ctx, s) => {
256
297
  if (t) return isArray2 ? f$2.createArrayTypeNode(t) : t;
257
298
  }
258
299
  if ("properties" in s && s.properties) {
259
- return f$2.createTypeLiteralNode(
300
+ let t = f$2.createTypeLiteralNode(
260
301
  Object.entries(s.properties).map(([k, v]) => {
261
302
  const r = s.required ?? [];
262
303
  const q = r.includes(k) ? void 0 : f$2.createToken(ts.SyntaxKind.QuestionToken);
263
304
  return f$2.createPropertySignature(void 0, f$2.createStringLiteral(k), q, mk(v));
264
305
  })
265
306
  );
307
+ if ("nullable" in s && s.nullable) {
308
+ return f$2.createUnionTypeNode([t, f$2.createLiteralTypeNode(f$2.createNull())]);
309
+ }
310
+ return t;
266
311
  }
267
312
  if ("type" in s) {
268
313
  let t;
269
314
  if (s.type === "object") t = makeObject(ctx, s);
270
315
  else if (s.type === "boolean") t = f$2.createKeywordTypeNode(ts.SyntaxKind.BooleanKeyword);
271
316
  else if (s.type === "number") t = f$2.createKeywordTypeNode(ts.SyntaxKind.NumberKeyword);
317
+ else if (isConstantString(s)) t = f$2.createLiteralTypeNode(f$2.createStringLiteral(s.const));
272
318
  else if (s.type === "string") t = f$2.createKeywordTypeNode(ts.SyntaxKind.StringKeyword);
273
319
  else if (s.type === "null") t = f$2.createLiteralTypeNode(f$2.createNull());
274
320
  else if (isArray(s.type)) t = makeLiteralUnion(ctx, s.type);
321
+ else if (s.type === "array" && isPrefixItems(s) && s.prefixItems && !s.items)
322
+ t = f$2.createTupleTypeNode(s.prefixItems.map(mk));
275
323
  else if (s.type === "array" && !isBoolean(s.items)) t = f$2.createArrayTypeNode(mk(s.items));
276
324
  else {
277
325
  console.warn(`makeType: unknown type "${s.type}"`);
@@ -294,6 +342,12 @@ const isStringEnum = (s) => {
294
342
  }
295
343
  return false;
296
344
  };
345
+ const isPrefixItems = (s) => {
346
+ return s.prefixItems !== void 0;
347
+ };
348
+ const isConstantString = (s) => {
349
+ return s.type === "string" && s.const !== void 0;
350
+ };
297
351
  const makeTypeAlias = (ctx, name, s) => {
298
352
  if (isStringEnum(s) && !ctx.inlineEnums) {
299
353
  const tokens1 = uniq(s.enum);
@@ -385,7 +439,19 @@ const prepareOp = (ctx, cfg, opName) => {
385
439
  urlReplacements[x.name] = name;
386
440
  return f$1.createParameterDeclaration(void 0, void 0, name, void 0, type);
387
441
  });
442
+ if (ctx.fetchOptions) {
443
+ fnArgs.push(
444
+ f$1.createParameterDeclaration(
445
+ void 0,
446
+ void 0,
447
+ "opts",
448
+ f$1.createToken(ts.SyntaxKind.QuestionToken),
449
+ f$1.createTypeReferenceNode(f$1.createIdentifier("ApigenRequest"), void 0)
450
+ )
451
+ );
452
+ }
388
453
  const cbArgs = filterNullable([
454
+ ctx.fetchOptions ? f$1.createSpreadAssignment(f$1.createIdentifier("opts")) : void 0,
389
455
  search.length ? f$1.createShorthandPropertyAssignment("search") : void 0,
390
456
  reqSchema && f$1.createShorthandPropertyAssignment("body"),
391
457
  reqSchema && reqSchema[0] !== "application/json" ? f$1.createPropertyAssignment(
@@ -439,6 +505,10 @@ const prepareRoutes = async (ctx) => {
439
505
  for (const [path, pathConfig] of Object.entries(ctx.doc.paths ?? {})) {
440
506
  ctx.logTag = `${"[ALL]".toUpperCase().padEnd(6, " ")} ${path}`;
441
507
  if (!isObject(pathConfig)) continue;
508
+ if (ctx.filterPaths) {
509
+ const match = ctx.filterPaths instanceof RegExp ? ctx.filterPaths.test(path) : ctx.filterPaths(path);
510
+ if (!match) continue;
511
+ }
442
512
  if ("$ref" in pathConfig) {
443
513
  console.warn(`${ctx.logTag} $ref should be resolved before (skipping)`);
444
514
  continue;
@@ -450,6 +520,9 @@ const prepareRoutes = async (ctx) => {
450
520
  if (pathConfig.parameters) {
451
521
  config.parameters = [...config.parameters ?? [], ...pathConfig.parameters];
452
522
  }
523
+ const opTags = config.tags ?? [];
524
+ if (ctx.includeTags?.length && !opTags.some((t) => ctx.includeTags.includes(t))) continue;
525
+ if (ctx.excludeTags?.length && opTags.some((t) => ctx.excludeTags.includes(t))) continue;
453
526
  const [ns, op] = getOpName(ctx, { ...config, method, path });
454
527
  if (!routes[ns]) routes[ns] = [];
455
528
  const joined = [ns, op].join(".");
@@ -496,9 +569,9 @@ const loadSchema = async ({
496
569
  headers = {}
497
570
  }) => {
498
571
  if (url.startsWith("file://")) url = url.substring(7);
499
- const { bundle } = await redocly.bundle({
572
+ const { bundle: bundle$1 } = await bundle({
500
573
  ref: url,
501
- config: await redocly.createConfig({}),
574
+ config: await createConfig({}),
502
575
  removeUnusedComponents: false,
503
576
  externalRefResolver: new BaseResolver({
504
577
  http: {
@@ -508,11 +581,12 @@ const loadSchema = async ({
508
581
  }
509
582
  })
510
583
  });
511
- if (bundle.parsed.swagger && upgrade) {
512
- const { openapi } = await convertObj(bundle.parsed, { patch: true });
584
+ const parsed = bundle$1.parsed;
585
+ if ("swagger" in parsed && upgrade) {
586
+ const { openapi } = await convertObj(parsed, { patch: true });
513
587
  return openapi;
514
588
  }
515
- return bundle.parsed;
589
+ return parsed;
516
590
  };
517
591
 
518
592
  const f = ts.factory;
@@ -544,7 +618,10 @@ const formatCode = async (code) => {
544
618
  };
545
619
 
546
620
  const apigen = async (config) => {
547
- const doc = await loadSchema({ url: config.source, headers: config.headers });
621
+ const doc = await loadSchema({
622
+ url: config.source,
623
+ ...config.headers ? { headers: config.headers } : {}
624
+ });
548
625
  const ctx = initCtx({ ...config, doc });
549
626
  const { modules, types } = await generateAst(ctx);
550
627
  const filepath = join(dirname(fileURLToPath(import.meta.url)), "_template.ts");