apigen-ts 1.0.1 → 1.2.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/cli.cjs +1 -1
- package/dist/cli.js +1 -1
- package/dist/cli.mjs +1 -1
- package/dist/{main-Cic4LkJH.cjs → main-C0qK6dZX.cjs} +56 -21
- package/dist/{main-CRZklgfk.js → main-l0LIDQ3K.js} +57 -22
- package/dist/{main-CRZklgfk.mjs → main-l0LIDQ3K.mjs} +57 -22
- package/dist/main.cjs +1 -1
- package/dist/main.d.cts +2 -0
- package/dist/main.d.mts +2 -0
- package/dist/main.js +1 -1
- package/dist/main.mjs +1 -1
- package/package.json +10 -10
- package/readme.md +37 -9
package/dist/cli.cjs
CHANGED
package/dist/cli.js
CHANGED
package/dist/cli.mjs
CHANGED
|
@@ -13,20 +13,30 @@ var path = require('node:path');
|
|
|
13
13
|
|
|
14
14
|
var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
|
|
15
15
|
var name = "apigen-ts";
|
|
16
|
-
var version = "1.0
|
|
16
|
+
var version = "1.2.0";
|
|
17
17
|
|
|
18
18
|
const initCtx = (config) => {
|
|
19
19
|
return {
|
|
20
20
|
source: "",
|
|
21
21
|
output: "",
|
|
22
22
|
name: "ApiClient",
|
|
23
|
-
parseDates: false,
|
|
24
23
|
doc: { openapi: "3.1.0" },
|
|
24
|
+
parseDates: false,
|
|
25
|
+
inlineEnums: false,
|
|
26
|
+
headers: {},
|
|
25
27
|
...config,
|
|
26
28
|
logTag: "",
|
|
27
29
|
usedNames: /* @__PURE__ */ new Set()
|
|
28
30
|
};
|
|
29
31
|
};
|
|
32
|
+
const parseHeaders = (items) => {
|
|
33
|
+
const headers = {};
|
|
34
|
+
for (const item of items) {
|
|
35
|
+
const [key, val] = item.split(":");
|
|
36
|
+
if (key && val) headers[key.trim()] = val.trim();
|
|
37
|
+
}
|
|
38
|
+
return headers;
|
|
39
|
+
};
|
|
30
40
|
const getCliConfig = () => {
|
|
31
41
|
const argv = cleye.cli({
|
|
32
42
|
name,
|
|
@@ -35,13 +45,24 @@ const getCliConfig = () => {
|
|
|
35
45
|
flags: {
|
|
36
46
|
name: {
|
|
37
47
|
type: String,
|
|
38
|
-
description: "
|
|
48
|
+
description: "API class name to export",
|
|
39
49
|
default: "ApiClient"
|
|
40
50
|
},
|
|
41
51
|
parseDates: {
|
|
42
52
|
type: Boolean,
|
|
43
|
-
description: "
|
|
53
|
+
description: "Parse dates as Date objects",
|
|
54
|
+
default: false
|
|
55
|
+
},
|
|
56
|
+
inlineEnums: {
|
|
57
|
+
type: Boolean,
|
|
58
|
+
description: "Use inline enums instead of enum types",
|
|
44
59
|
default: false
|
|
60
|
+
},
|
|
61
|
+
header: {
|
|
62
|
+
type: [String],
|
|
63
|
+
alias: "H",
|
|
64
|
+
description: 'HTTP header as key=value (e.g., -H "x-api-key: your-key"). Used only when generating code.',
|
|
65
|
+
default: []
|
|
45
66
|
}
|
|
46
67
|
}
|
|
47
68
|
});
|
|
@@ -49,7 +70,9 @@ const getCliConfig = () => {
|
|
|
49
70
|
source: argv._.source,
|
|
50
71
|
output: argv._.output ?? null,
|
|
51
72
|
name: argv.flags.name,
|
|
52
|
-
parseDates: argv.flags.parseDates
|
|
73
|
+
parseDates: argv.flags.parseDates,
|
|
74
|
+
inlineEnums: argv.flags.inlineEnums,
|
|
75
|
+
headers: parseHeaders(argv.flags.header)
|
|
53
76
|
};
|
|
54
77
|
return config;
|
|
55
78
|
};
|
|
@@ -207,6 +230,12 @@ const makeObject = (ctx, s) => {
|
|
|
207
230
|
}
|
|
208
231
|
return f$2.createKeywordTypeNode(ts.SyntaxKind.ObjectKeyword);
|
|
209
232
|
};
|
|
233
|
+
const makeLiteralUnion = (ctx, types) => {
|
|
234
|
+
const tokens = types.map((x) => makeType(ctx, { type: x }));
|
|
235
|
+
const hasUnknown = tokens.some((x) => x.kind === ts.SyntaxKind.UnknownKeyword);
|
|
236
|
+
if (hasUnknown) return f$2.createKeywordTypeNode(ts.SyntaxKind.UnknownKeyword);
|
|
237
|
+
return f$2.createUnionTypeNode(tokens);
|
|
238
|
+
};
|
|
210
239
|
const makeType = (ctx, s) => {
|
|
211
240
|
const mk = makeType.bind(null, ctx);
|
|
212
241
|
if (s === void 0) return f$2.createKeywordTypeNode(ts.SyntaxKind.VoidKeyword);
|
|
@@ -239,22 +268,14 @@ const makeType = (ctx, s) => {
|
|
|
239
268
|
);
|
|
240
269
|
}
|
|
241
270
|
if ("type" in s) {
|
|
242
|
-
if (Array.isArray(s.type)) {
|
|
243
|
-
const types = [];
|
|
244
|
-
for (const type of s.type) {
|
|
245
|
-
if (type === "null") types.push({ type: "null" });
|
|
246
|
-
else types.push({ ...s, type });
|
|
247
|
-
}
|
|
248
|
-
return mk({ oneOf: types });
|
|
249
|
-
}
|
|
250
271
|
let t;
|
|
251
272
|
if (s.type === "object") t = makeObject(ctx, s);
|
|
252
273
|
else if (s.type === "boolean") t = f$2.createKeywordTypeNode(ts.SyntaxKind.BooleanKeyword);
|
|
253
274
|
else if (s.type === "number") t = f$2.createKeywordTypeNode(ts.SyntaxKind.NumberKeyword);
|
|
254
275
|
else if (s.type === "string") t = f$2.createKeywordTypeNode(ts.SyntaxKind.StringKeyword);
|
|
255
|
-
else if (s.type === "array") t = f$2.createArrayTypeNode(mk(s.items));
|
|
256
276
|
else if (s.type === "null") t = f$2.createLiteralTypeNode(f$2.createNull());
|
|
257
|
-
else if (lodashEs.isArray(s.type)) t =
|
|
277
|
+
else if (lodashEs.isArray(s.type)) t = makeLiteralUnion(ctx, s.type);
|
|
278
|
+
else if (s.type === "array" && !lodashEs.isBoolean(s.items)) t = f$2.createArrayTypeNode(mk(s.items));
|
|
258
279
|
else {
|
|
259
280
|
console.warn(`makeType: unknown type "${s.type}"`);
|
|
260
281
|
return f$2.createKeywordTypeNode(ts.SyntaxKind.UnknownKeyword);
|
|
@@ -263,7 +284,10 @@ const makeType = (ctx, s) => {
|
|
|
263
284
|
if (s.format === "binary") t = f$2.createTypeReferenceNode("File");
|
|
264
285
|
if (s.format === "date-time" && ctx.parseDates) t = f$2.createTypeReferenceNode("Date");
|
|
265
286
|
}
|
|
266
|
-
|
|
287
|
+
if ("nullable" in s && s.nullable) {
|
|
288
|
+
return f$2.createUnionTypeNode([t, f$2.createLiteralTypeNode(f$2.createNull())]);
|
|
289
|
+
}
|
|
290
|
+
return t;
|
|
267
291
|
}
|
|
268
292
|
return f$2.createKeywordTypeNode(ts.SyntaxKind.UnknownKeyword);
|
|
269
293
|
};
|
|
@@ -274,7 +298,7 @@ const isStringEnum = (s) => {
|
|
|
274
298
|
return false;
|
|
275
299
|
};
|
|
276
300
|
const makeTypeAlias = (ctx, name, s) => {
|
|
277
|
-
if (isStringEnum(s)) {
|
|
301
|
+
if (isStringEnum(s) && !ctx.inlineEnums) {
|
|
278
302
|
const tokens1 = lodashEs.uniq(s.enum);
|
|
279
303
|
const tokens2 = arrayUtilsTs.filterEmpty(tokens1);
|
|
280
304
|
if (tokens1.length !== tokens2.length) {
|
|
@@ -469,12 +493,23 @@ const generateAst = async (ctx) => {
|
|
|
469
493
|
}
|
|
470
494
|
return { modules, types };
|
|
471
495
|
};
|
|
472
|
-
const loadSchema = async (
|
|
496
|
+
const loadSchema = async ({
|
|
497
|
+
url,
|
|
498
|
+
upgrade = true,
|
|
499
|
+
headers = {}
|
|
500
|
+
}) => {
|
|
473
501
|
if (url.startsWith("file://")) url = url.substring(7);
|
|
474
502
|
const { bundle } = await redocly.bundle({
|
|
475
503
|
ref: url,
|
|
476
504
|
config: await redocly.createConfig({}),
|
|
477
|
-
removeUnusedComponents: false
|
|
505
|
+
removeUnusedComponents: false,
|
|
506
|
+
externalRefResolver: new redocly.BaseResolver({
|
|
507
|
+
http: {
|
|
508
|
+
headers: Object.entries(headers).map(([name, value]) => {
|
|
509
|
+
return { name, value, matches: "**" };
|
|
510
|
+
})
|
|
511
|
+
}
|
|
512
|
+
})
|
|
478
513
|
});
|
|
479
514
|
if (bundle.parsed.swagger && upgrade) {
|
|
480
515
|
const { openapi } = await swagger2openapi.convertObj(bundle.parsed, { patch: true });
|
|
@@ -512,10 +547,10 @@ const formatCode = async (code) => {
|
|
|
512
547
|
};
|
|
513
548
|
|
|
514
549
|
const apigen = async (config) => {
|
|
515
|
-
const doc = await loadSchema(config.source);
|
|
550
|
+
const doc = await loadSchema({ url: config.source, headers: config.headers });
|
|
516
551
|
const ctx = initCtx({ ...config, doc });
|
|
517
552
|
const { modules, types } = await generateAst(ctx);
|
|
518
|
-
const filepath = path$1.join(path$1.dirname(url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.src || new URL('main-
|
|
553
|
+
const filepath = path$1.join(path$1.dirname(url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('main-C0qK6dZX.cjs', document.baseURI).href)))), "_template.ts");
|
|
519
554
|
const file = await fs.readFile(filepath, "utf-8");
|
|
520
555
|
let code = [
|
|
521
556
|
`// Auto-generated by https://github.com/vladkens/apigen-ts`,
|
|
@@ -2,28 +2,38 @@ 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 from '@redocly/openapi-core';
|
|
5
|
+
import redocly, { BaseResolver } from '@redocly/openapi-core';
|
|
6
6
|
import { filterEmpty, filterNullable } from 'array-utils-ts';
|
|
7
|
-
import { get, uniq, upperFirst, isArray, isBoolean,
|
|
7
|
+
import { get, uniq, upperFirst, isArray, isBoolean, sortBy, isObject, lowerFirst, uniqBy } from 'lodash-es';
|
|
8
8
|
import { convertObj } from 'swagger2openapi';
|
|
9
9
|
import ts from 'typescript';
|
|
10
10
|
import path from 'node:path';
|
|
11
11
|
|
|
12
12
|
var name = "apigen-ts";
|
|
13
|
-
var version = "1.0
|
|
13
|
+
var version = "1.2.0";
|
|
14
14
|
|
|
15
15
|
const initCtx = (config) => {
|
|
16
16
|
return {
|
|
17
17
|
source: "",
|
|
18
18
|
output: "",
|
|
19
19
|
name: "ApiClient",
|
|
20
|
-
parseDates: false,
|
|
21
20
|
doc: { openapi: "3.1.0" },
|
|
21
|
+
parseDates: false,
|
|
22
|
+
inlineEnums: false,
|
|
23
|
+
headers: {},
|
|
22
24
|
...config,
|
|
23
25
|
logTag: "",
|
|
24
26
|
usedNames: /* @__PURE__ */ new Set()
|
|
25
27
|
};
|
|
26
28
|
};
|
|
29
|
+
const parseHeaders = (items) => {
|
|
30
|
+
const headers = {};
|
|
31
|
+
for (const item of items) {
|
|
32
|
+
const [key, val] = item.split(":");
|
|
33
|
+
if (key && val) headers[key.trim()] = val.trim();
|
|
34
|
+
}
|
|
35
|
+
return headers;
|
|
36
|
+
};
|
|
27
37
|
const getCliConfig = () => {
|
|
28
38
|
const argv = cli({
|
|
29
39
|
name,
|
|
@@ -32,13 +42,24 @@ const getCliConfig = () => {
|
|
|
32
42
|
flags: {
|
|
33
43
|
name: {
|
|
34
44
|
type: String,
|
|
35
|
-
description: "
|
|
45
|
+
description: "API class name to export",
|
|
36
46
|
default: "ApiClient"
|
|
37
47
|
},
|
|
38
48
|
parseDates: {
|
|
39
49
|
type: Boolean,
|
|
40
|
-
description: "
|
|
50
|
+
description: "Parse dates as Date objects",
|
|
51
|
+
default: false
|
|
52
|
+
},
|
|
53
|
+
inlineEnums: {
|
|
54
|
+
type: Boolean,
|
|
55
|
+
description: "Use inline enums instead of enum types",
|
|
41
56
|
default: false
|
|
57
|
+
},
|
|
58
|
+
header: {
|
|
59
|
+
type: [String],
|
|
60
|
+
alias: "H",
|
|
61
|
+
description: 'HTTP header as key=value (e.g., -H "x-api-key: your-key"). Used only when generating code.',
|
|
62
|
+
default: []
|
|
42
63
|
}
|
|
43
64
|
}
|
|
44
65
|
});
|
|
@@ -46,7 +67,9 @@ const getCliConfig = () => {
|
|
|
46
67
|
source: argv._.source,
|
|
47
68
|
output: argv._.output ?? null,
|
|
48
69
|
name: argv.flags.name,
|
|
49
|
-
parseDates: argv.flags.parseDates
|
|
70
|
+
parseDates: argv.flags.parseDates,
|
|
71
|
+
inlineEnums: argv.flags.inlineEnums,
|
|
72
|
+
headers: parseHeaders(argv.flags.header)
|
|
50
73
|
};
|
|
51
74
|
return config;
|
|
52
75
|
};
|
|
@@ -204,6 +227,12 @@ const makeObject = (ctx, s) => {
|
|
|
204
227
|
}
|
|
205
228
|
return f$2.createKeywordTypeNode(ts.SyntaxKind.ObjectKeyword);
|
|
206
229
|
};
|
|
230
|
+
const makeLiteralUnion = (ctx, types) => {
|
|
231
|
+
const tokens = types.map((x) => makeType(ctx, { type: x }));
|
|
232
|
+
const hasUnknown = tokens.some((x) => x.kind === ts.SyntaxKind.UnknownKeyword);
|
|
233
|
+
if (hasUnknown) return f$2.createKeywordTypeNode(ts.SyntaxKind.UnknownKeyword);
|
|
234
|
+
return f$2.createUnionTypeNode(tokens);
|
|
235
|
+
};
|
|
207
236
|
const makeType = (ctx, s) => {
|
|
208
237
|
const mk = makeType.bind(null, ctx);
|
|
209
238
|
if (s === void 0) return f$2.createKeywordTypeNode(ts.SyntaxKind.VoidKeyword);
|
|
@@ -236,22 +265,14 @@ const makeType = (ctx, s) => {
|
|
|
236
265
|
);
|
|
237
266
|
}
|
|
238
267
|
if ("type" in s) {
|
|
239
|
-
if (Array.isArray(s.type)) {
|
|
240
|
-
const types = [];
|
|
241
|
-
for (const type of s.type) {
|
|
242
|
-
if (type === "null") types.push({ type: "null" });
|
|
243
|
-
else types.push({ ...s, type });
|
|
244
|
-
}
|
|
245
|
-
return mk({ oneOf: types });
|
|
246
|
-
}
|
|
247
268
|
let t;
|
|
248
269
|
if (s.type === "object") t = makeObject(ctx, s);
|
|
249
270
|
else if (s.type === "boolean") t = f$2.createKeywordTypeNode(ts.SyntaxKind.BooleanKeyword);
|
|
250
271
|
else if (s.type === "number") t = f$2.createKeywordTypeNode(ts.SyntaxKind.NumberKeyword);
|
|
251
272
|
else if (s.type === "string") t = f$2.createKeywordTypeNode(ts.SyntaxKind.StringKeyword);
|
|
252
|
-
else if (s.type === "array") t = f$2.createArrayTypeNode(mk(s.items));
|
|
253
273
|
else if (s.type === "null") t = f$2.createLiteralTypeNode(f$2.createNull());
|
|
254
|
-
else if (isArray(s.type)) t =
|
|
274
|
+
else if (isArray(s.type)) t = makeLiteralUnion(ctx, s.type);
|
|
275
|
+
else if (s.type === "array" && !isBoolean(s.items)) t = f$2.createArrayTypeNode(mk(s.items));
|
|
255
276
|
else {
|
|
256
277
|
console.warn(`makeType: unknown type "${s.type}"`);
|
|
257
278
|
return f$2.createKeywordTypeNode(ts.SyntaxKind.UnknownKeyword);
|
|
@@ -260,7 +281,10 @@ const makeType = (ctx, s) => {
|
|
|
260
281
|
if (s.format === "binary") t = f$2.createTypeReferenceNode("File");
|
|
261
282
|
if (s.format === "date-time" && ctx.parseDates) t = f$2.createTypeReferenceNode("Date");
|
|
262
283
|
}
|
|
263
|
-
|
|
284
|
+
if ("nullable" in s && s.nullable) {
|
|
285
|
+
return f$2.createUnionTypeNode([t, f$2.createLiteralTypeNode(f$2.createNull())]);
|
|
286
|
+
}
|
|
287
|
+
return t;
|
|
264
288
|
}
|
|
265
289
|
return f$2.createKeywordTypeNode(ts.SyntaxKind.UnknownKeyword);
|
|
266
290
|
};
|
|
@@ -271,7 +295,7 @@ const isStringEnum = (s) => {
|
|
|
271
295
|
return false;
|
|
272
296
|
};
|
|
273
297
|
const makeTypeAlias = (ctx, name, s) => {
|
|
274
|
-
if (isStringEnum(s)) {
|
|
298
|
+
if (isStringEnum(s) && !ctx.inlineEnums) {
|
|
275
299
|
const tokens1 = uniq(s.enum);
|
|
276
300
|
const tokens2 = filterEmpty(tokens1);
|
|
277
301
|
if (tokens1.length !== tokens2.length) {
|
|
@@ -466,12 +490,23 @@ const generateAst = async (ctx) => {
|
|
|
466
490
|
}
|
|
467
491
|
return { modules, types };
|
|
468
492
|
};
|
|
469
|
-
const loadSchema = async (
|
|
493
|
+
const loadSchema = async ({
|
|
494
|
+
url,
|
|
495
|
+
upgrade = true,
|
|
496
|
+
headers = {}
|
|
497
|
+
}) => {
|
|
470
498
|
if (url.startsWith("file://")) url = url.substring(7);
|
|
471
499
|
const { bundle } = await redocly.bundle({
|
|
472
500
|
ref: url,
|
|
473
501
|
config: await redocly.createConfig({}),
|
|
474
|
-
removeUnusedComponents: false
|
|
502
|
+
removeUnusedComponents: false,
|
|
503
|
+
externalRefResolver: new BaseResolver({
|
|
504
|
+
http: {
|
|
505
|
+
headers: Object.entries(headers).map(([name, value]) => {
|
|
506
|
+
return { name, value, matches: "**" };
|
|
507
|
+
})
|
|
508
|
+
}
|
|
509
|
+
})
|
|
475
510
|
});
|
|
476
511
|
if (bundle.parsed.swagger && upgrade) {
|
|
477
512
|
const { openapi } = await convertObj(bundle.parsed, { patch: true });
|
|
@@ -509,7 +544,7 @@ const formatCode = async (code) => {
|
|
|
509
544
|
};
|
|
510
545
|
|
|
511
546
|
const apigen = async (config) => {
|
|
512
|
-
const doc = await loadSchema(config.source);
|
|
547
|
+
const doc = await loadSchema({ url: config.source, headers: config.headers });
|
|
513
548
|
const ctx = initCtx({ ...config, doc });
|
|
514
549
|
const { modules, types } = await generateAst(ctx);
|
|
515
550
|
const filepath = join(dirname(fileURLToPath(import.meta.url)), "_template.ts");
|
|
@@ -2,28 +2,38 @@ 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 from '@redocly/openapi-core';
|
|
5
|
+
import redocly, { BaseResolver } from '@redocly/openapi-core';
|
|
6
6
|
import { filterEmpty, filterNullable } from 'array-utils-ts';
|
|
7
|
-
import { get, uniq, upperFirst, isArray, isBoolean,
|
|
7
|
+
import { get, uniq, upperFirst, isArray, isBoolean, sortBy, isObject, lowerFirst, uniqBy } from 'lodash-es';
|
|
8
8
|
import { convertObj } from 'swagger2openapi';
|
|
9
9
|
import ts from 'typescript';
|
|
10
10
|
import path from 'node:path';
|
|
11
11
|
|
|
12
12
|
var name = "apigen-ts";
|
|
13
|
-
var version = "1.0
|
|
13
|
+
var version = "1.2.0";
|
|
14
14
|
|
|
15
15
|
const initCtx = (config) => {
|
|
16
16
|
return {
|
|
17
17
|
source: "",
|
|
18
18
|
output: "",
|
|
19
19
|
name: "ApiClient",
|
|
20
|
-
parseDates: false,
|
|
21
20
|
doc: { openapi: "3.1.0" },
|
|
21
|
+
parseDates: false,
|
|
22
|
+
inlineEnums: false,
|
|
23
|
+
headers: {},
|
|
22
24
|
...config,
|
|
23
25
|
logTag: "",
|
|
24
26
|
usedNames: /* @__PURE__ */ new Set()
|
|
25
27
|
};
|
|
26
28
|
};
|
|
29
|
+
const parseHeaders = (items) => {
|
|
30
|
+
const headers = {};
|
|
31
|
+
for (const item of items) {
|
|
32
|
+
const [key, val] = item.split(":");
|
|
33
|
+
if (key && val) headers[key.trim()] = val.trim();
|
|
34
|
+
}
|
|
35
|
+
return headers;
|
|
36
|
+
};
|
|
27
37
|
const getCliConfig = () => {
|
|
28
38
|
const argv = cli({
|
|
29
39
|
name,
|
|
@@ -32,13 +42,24 @@ const getCliConfig = () => {
|
|
|
32
42
|
flags: {
|
|
33
43
|
name: {
|
|
34
44
|
type: String,
|
|
35
|
-
description: "
|
|
45
|
+
description: "API class name to export",
|
|
36
46
|
default: "ApiClient"
|
|
37
47
|
},
|
|
38
48
|
parseDates: {
|
|
39
49
|
type: Boolean,
|
|
40
|
-
description: "
|
|
50
|
+
description: "Parse dates as Date objects",
|
|
51
|
+
default: false
|
|
52
|
+
},
|
|
53
|
+
inlineEnums: {
|
|
54
|
+
type: Boolean,
|
|
55
|
+
description: "Use inline enums instead of enum types",
|
|
41
56
|
default: false
|
|
57
|
+
},
|
|
58
|
+
header: {
|
|
59
|
+
type: [String],
|
|
60
|
+
alias: "H",
|
|
61
|
+
description: 'HTTP header as key=value (e.g., -H "x-api-key: your-key"). Used only when generating code.',
|
|
62
|
+
default: []
|
|
42
63
|
}
|
|
43
64
|
}
|
|
44
65
|
});
|
|
@@ -46,7 +67,9 @@ const getCliConfig = () => {
|
|
|
46
67
|
source: argv._.source,
|
|
47
68
|
output: argv._.output ?? null,
|
|
48
69
|
name: argv.flags.name,
|
|
49
|
-
parseDates: argv.flags.parseDates
|
|
70
|
+
parseDates: argv.flags.parseDates,
|
|
71
|
+
inlineEnums: argv.flags.inlineEnums,
|
|
72
|
+
headers: parseHeaders(argv.flags.header)
|
|
50
73
|
};
|
|
51
74
|
return config;
|
|
52
75
|
};
|
|
@@ -204,6 +227,12 @@ const makeObject = (ctx, s) => {
|
|
|
204
227
|
}
|
|
205
228
|
return f$2.createKeywordTypeNode(ts.SyntaxKind.ObjectKeyword);
|
|
206
229
|
};
|
|
230
|
+
const makeLiteralUnion = (ctx, types) => {
|
|
231
|
+
const tokens = types.map((x) => makeType(ctx, { type: x }));
|
|
232
|
+
const hasUnknown = tokens.some((x) => x.kind === ts.SyntaxKind.UnknownKeyword);
|
|
233
|
+
if (hasUnknown) return f$2.createKeywordTypeNode(ts.SyntaxKind.UnknownKeyword);
|
|
234
|
+
return f$2.createUnionTypeNode(tokens);
|
|
235
|
+
};
|
|
207
236
|
const makeType = (ctx, s) => {
|
|
208
237
|
const mk = makeType.bind(null, ctx);
|
|
209
238
|
if (s === void 0) return f$2.createKeywordTypeNode(ts.SyntaxKind.VoidKeyword);
|
|
@@ -236,22 +265,14 @@ const makeType = (ctx, s) => {
|
|
|
236
265
|
);
|
|
237
266
|
}
|
|
238
267
|
if ("type" in s) {
|
|
239
|
-
if (Array.isArray(s.type)) {
|
|
240
|
-
const types = [];
|
|
241
|
-
for (const type of s.type) {
|
|
242
|
-
if (type === "null") types.push({ type: "null" });
|
|
243
|
-
else types.push({ ...s, type });
|
|
244
|
-
}
|
|
245
|
-
return mk({ oneOf: types });
|
|
246
|
-
}
|
|
247
268
|
let t;
|
|
248
269
|
if (s.type === "object") t = makeObject(ctx, s);
|
|
249
270
|
else if (s.type === "boolean") t = f$2.createKeywordTypeNode(ts.SyntaxKind.BooleanKeyword);
|
|
250
271
|
else if (s.type === "number") t = f$2.createKeywordTypeNode(ts.SyntaxKind.NumberKeyword);
|
|
251
272
|
else if (s.type === "string") t = f$2.createKeywordTypeNode(ts.SyntaxKind.StringKeyword);
|
|
252
|
-
else if (s.type === "array") t = f$2.createArrayTypeNode(mk(s.items));
|
|
253
273
|
else if (s.type === "null") t = f$2.createLiteralTypeNode(f$2.createNull());
|
|
254
|
-
else if (isArray(s.type)) t =
|
|
274
|
+
else if (isArray(s.type)) t = makeLiteralUnion(ctx, s.type);
|
|
275
|
+
else if (s.type === "array" && !isBoolean(s.items)) t = f$2.createArrayTypeNode(mk(s.items));
|
|
255
276
|
else {
|
|
256
277
|
console.warn(`makeType: unknown type "${s.type}"`);
|
|
257
278
|
return f$2.createKeywordTypeNode(ts.SyntaxKind.UnknownKeyword);
|
|
@@ -260,7 +281,10 @@ const makeType = (ctx, s) => {
|
|
|
260
281
|
if (s.format === "binary") t = f$2.createTypeReferenceNode("File");
|
|
261
282
|
if (s.format === "date-time" && ctx.parseDates) t = f$2.createTypeReferenceNode("Date");
|
|
262
283
|
}
|
|
263
|
-
|
|
284
|
+
if ("nullable" in s && s.nullable) {
|
|
285
|
+
return f$2.createUnionTypeNode([t, f$2.createLiteralTypeNode(f$2.createNull())]);
|
|
286
|
+
}
|
|
287
|
+
return t;
|
|
264
288
|
}
|
|
265
289
|
return f$2.createKeywordTypeNode(ts.SyntaxKind.UnknownKeyword);
|
|
266
290
|
};
|
|
@@ -271,7 +295,7 @@ const isStringEnum = (s) => {
|
|
|
271
295
|
return false;
|
|
272
296
|
};
|
|
273
297
|
const makeTypeAlias = (ctx, name, s) => {
|
|
274
|
-
if (isStringEnum(s)) {
|
|
298
|
+
if (isStringEnum(s) && !ctx.inlineEnums) {
|
|
275
299
|
const tokens1 = uniq(s.enum);
|
|
276
300
|
const tokens2 = filterEmpty(tokens1);
|
|
277
301
|
if (tokens1.length !== tokens2.length) {
|
|
@@ -466,12 +490,23 @@ const generateAst = async (ctx) => {
|
|
|
466
490
|
}
|
|
467
491
|
return { modules, types };
|
|
468
492
|
};
|
|
469
|
-
const loadSchema = async (
|
|
493
|
+
const loadSchema = async ({
|
|
494
|
+
url,
|
|
495
|
+
upgrade = true,
|
|
496
|
+
headers = {}
|
|
497
|
+
}) => {
|
|
470
498
|
if (url.startsWith("file://")) url = url.substring(7);
|
|
471
499
|
const { bundle } = await redocly.bundle({
|
|
472
500
|
ref: url,
|
|
473
501
|
config: await redocly.createConfig({}),
|
|
474
|
-
removeUnusedComponents: false
|
|
502
|
+
removeUnusedComponents: false,
|
|
503
|
+
externalRefResolver: new BaseResolver({
|
|
504
|
+
http: {
|
|
505
|
+
headers: Object.entries(headers).map(([name, value]) => {
|
|
506
|
+
return { name, value, matches: "**" };
|
|
507
|
+
})
|
|
508
|
+
}
|
|
509
|
+
})
|
|
475
510
|
});
|
|
476
511
|
if (bundle.parsed.swagger && upgrade) {
|
|
477
512
|
const { openapi } = await convertObj(bundle.parsed, { patch: true });
|
|
@@ -509,7 +544,7 @@ const formatCode = async (code) => {
|
|
|
509
544
|
};
|
|
510
545
|
|
|
511
546
|
const apigen = async (config) => {
|
|
512
|
-
const doc = await loadSchema(config.source);
|
|
547
|
+
const doc = await loadSchema({ url: config.source, headers: config.headers });
|
|
513
548
|
const ctx = initCtx({ ...config, doc });
|
|
514
549
|
const { modules, types } = await generateAst(ctx);
|
|
515
550
|
const filepath = join(dirname(fileURLToPath(import.meta.url)), "_template.ts");
|
package/dist/main.cjs
CHANGED
package/dist/main.d.cts
CHANGED
|
@@ -11,7 +11,9 @@ type Config = {
|
|
|
11
11
|
output: string | null;
|
|
12
12
|
name: string;
|
|
13
13
|
parseDates: boolean;
|
|
14
|
+
inlineEnums: boolean;
|
|
14
15
|
resolveName?: (ctx: Context, op: OpConfig, proposal: OpName) => OpName | undefined;
|
|
16
|
+
headers: Record<string, string>;
|
|
15
17
|
};
|
|
16
18
|
type Context = Config & {
|
|
17
19
|
doc: Oas3Definition;
|
package/dist/main.d.mts
CHANGED
|
@@ -11,7 +11,9 @@ type Config = {
|
|
|
11
11
|
output: string | null;
|
|
12
12
|
name: string;
|
|
13
13
|
parseDates: boolean;
|
|
14
|
+
inlineEnums: boolean;
|
|
14
15
|
resolveName?: (ctx: Context, op: OpConfig, proposal: OpName) => OpName | undefined;
|
|
16
|
+
headers: Record<string, string>;
|
|
15
17
|
};
|
|
16
18
|
type Context = Config & {
|
|
17
19
|
doc: Oas3Definition;
|
package/dist/main.js
CHANGED
package/dist/main.mjs
CHANGED
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "apigen-ts",
|
|
4
|
-
"version": "1.0
|
|
4
|
+
"version": "1.2.0",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "vladkens <v.pronsky@gmail.com>",
|
|
7
7
|
"repository": "vladkens/apigen-ts",
|
|
@@ -22,23 +22,23 @@
|
|
|
22
22
|
"ci": "tsc --noEmit && yarn test-cov && yarn build"
|
|
23
23
|
},
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"@redocly/openapi-core": "1.
|
|
25
|
+
"@redocly/openapi-core": "1.34.1",
|
|
26
26
|
"@types/lodash-es": "4.17.12",
|
|
27
27
|
"@types/swagger2openapi": "7.0.4",
|
|
28
|
-
"array-utils-ts": "0.
|
|
29
|
-
"cleye": "1.3.
|
|
28
|
+
"array-utils-ts": "1.0.2",
|
|
29
|
+
"cleye": "1.3.4",
|
|
30
30
|
"lodash-es": "4.17.21",
|
|
31
31
|
"swagger2openapi": "7.0.8"
|
|
32
32
|
},
|
|
33
33
|
"devDependencies": {
|
|
34
|
-
"@types/node": "22.
|
|
35
|
-
"c8": "10.1.
|
|
36
|
-
"fetch-mock": "
|
|
37
|
-
"pkgroll": "2.
|
|
38
|
-
"prettier": "3.
|
|
34
|
+
"@types/node": "22.14.0",
|
|
35
|
+
"c8": "10.1.3",
|
|
36
|
+
"fetch-mock": "12.5.2",
|
|
37
|
+
"pkgroll": "2.12.1",
|
|
38
|
+
"prettier": "3.5.3",
|
|
39
39
|
"prettier-plugin-organize-imports": "4.1.0",
|
|
40
40
|
"tsm": "2.3.0",
|
|
41
|
-
"typescript": "5.
|
|
41
|
+
"typescript": "5.8.3",
|
|
42
42
|
"uvu": "0.5.6"
|
|
43
43
|
},
|
|
44
44
|
"peerDependencies": {
|
package/readme.md
CHANGED
|
@@ -2,17 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
<div align="center">
|
|
4
4
|
|
|
5
|
-
[<img src="https://
|
|
6
|
-
[<img src="https://
|
|
7
|
-
[<img src="https://
|
|
8
|
-
[<img src="https://
|
|
9
|
-
[<img src="https://
|
|
5
|
+
[<img src="https://badges.ws/npm/v/apigen-ts" alt="version" />](https://npmjs.org/package/apigen-ts)
|
|
6
|
+
[<img src="https://badges.ws/packagephobia/publish/apigen-ts" alt="size" />](https://packagephobia.now.sh/result?p=apigen-ts)
|
|
7
|
+
[<img src="https://badges.ws/npm/dm/apigen-ts" alt="downloads" />](https://npmjs.org/package/apigen-ts)
|
|
8
|
+
[<img src="https://badges.ws/github/license/vladkens/apigen-ts" alt="license" />](https://github.com/vladkens/apigen-ts/blob/main/LICENSE)
|
|
9
|
+
[<img src="https://badges.ws/badge/-/buy%20me%20a%20coffee/ff813f?icon=buymeacoffee&label" alt="donate" />](https://buymeacoffee.com/vladkens)
|
|
10
10
|
|
|
11
11
|
</div>
|
|
12
12
|
|
|
13
13
|
<div align="center">
|
|
14
14
|
<img src="./logo.svg" alt="apigen-ts logo" height="80" />
|
|
15
|
-
<div>Simple typed OpenAPI client generator</div>
|
|
16
15
|
</div>
|
|
17
16
|
|
|
18
17
|
## Features
|
|
@@ -29,7 +28,11 @@
|
|
|
29
28
|
## Install
|
|
30
29
|
|
|
31
30
|
```sh
|
|
32
|
-
|
|
31
|
+
npm install apigen-ts --save-dev
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
```sh
|
|
35
|
+
yarn add -D apigen-ts
|
|
33
36
|
```
|
|
34
37
|
|
|
35
38
|
## Usage
|
|
@@ -37,11 +40,14 @@ yarn install -D apigen-ts
|
|
|
37
40
|
### 1. Generate
|
|
38
41
|
|
|
39
42
|
```sh
|
|
43
|
+
# From file
|
|
44
|
+
yarn apigen-ts ./openapi.json ./api-client.ts
|
|
45
|
+
|
|
40
46
|
# From url
|
|
41
47
|
yarn apigen-ts https://petstore3.swagger.io/api/v3/openapi.json ./api-client.ts
|
|
42
48
|
|
|
43
|
-
# From
|
|
44
|
-
yarn apigen-ts
|
|
49
|
+
# From protected url
|
|
50
|
+
yarn apigen-ts https://secret-api.example.com ./api-client.ts -H "x-api-key: secret-key"
|
|
45
51
|
```
|
|
46
52
|
|
|
47
53
|
Run `yarn apigen-ts --help` for more options. Examples of generated clients [here](./examples/).
|
|
@@ -92,6 +98,26 @@ const pet = await api.pet.getPetById(1)
|
|
|
92
98
|
const createdAt: Date = pet.createdAt // date parsed from string with format=date-time
|
|
93
99
|
```
|
|
94
100
|
|
|
101
|
+
### String union as enums
|
|
102
|
+
|
|
103
|
+
You can generate string literal union instead of native enums in case you want to run in Node.js environment with [type-stripping](https://nodejs.org/api/typescript.html#type-stripping). To achive this pass `--inline-enums` command line argument or use `inlineEnums: true` in Node.js API.
|
|
104
|
+
|
|
105
|
+
```sh
|
|
106
|
+
yarn apigen-ts ./openapi.json ./api-client.ts --inline-enums
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
This will generate:
|
|
110
|
+
|
|
111
|
+
```ts
|
|
112
|
+
type MyEnum = "OptionA" | "OptionB"
|
|
113
|
+
|
|
114
|
+
// instead of
|
|
115
|
+
enum MyEnum = {
|
|
116
|
+
OptionA = "OptionA",
|
|
117
|
+
OptionB = "OptionB"
|
|
118
|
+
}
|
|
119
|
+
```
|
|
120
|
+
|
|
95
121
|
### Errors handling
|
|
96
122
|
|
|
97
123
|
An exception will be thrown for all unsuccessful return codes.
|
|
@@ -156,6 +182,8 @@ await apigen({
|
|
|
156
182
|
// everything below is optional
|
|
157
183
|
name: "MyApiClient", // default "ApiClient"
|
|
158
184
|
parseDates: true, // default false
|
|
185
|
+
inlineEnums: false, // default false, use string literal union instead of enum
|
|
186
|
+
headers: { "x-api-key": "secret-key" }, // Custom HTTP headers to use when fetching schema
|
|
159
187
|
resolveName(ctx, op, proposal) {
|
|
160
188
|
// proposal is [string, string] which represents module.funcName
|
|
161
189
|
if (proposal[0] === "users") return // will use default proposal
|