@ps-aux/api-client-gen 0.0.7-rc1 → 0.1.0-rc1

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/bin.esm.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/bin/env node
2
- import { g as generateApiClient } from './generateApiClient-f8NyPBtp.js';
2
+ import { g as generateApiClient } from './generateApiClient-PeRLjZk9.js';
3
3
  import { cosmiconfigSync } from 'cosmiconfig';
4
4
  import 'path';
5
5
  import 'fs';
package/dist/bin.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/bin/env node
2
2
  'use strict';
3
3
 
4
- var generateApiClient = require('./generateApiClient-CJ2UZFXk.js');
4
+ var generateApiClient = require('./generateApiClient-d_X--WFl.js');
5
5
  var cosmiconfig = require('cosmiconfig');
6
6
  require('path');
7
7
  require('fs');
@@ -0,0 +1,188 @@
1
+ 'use strict';
2
+
3
+ var Path = require('path');
4
+ var fs$1 = require('fs');
5
+ var axios = require('axios');
6
+ var fs = require('fs/promises');
7
+ var swaggerTypescriptApi = require('swagger-typescript-api');
8
+ var tsToZod = require('ts-to-zod');
9
+
10
+ function _interopNamespaceDefault(e) {
11
+ var n = Object.create(null);
12
+ if (e) {
13
+ Object.keys(e).forEach(function (k) {
14
+ if (k !== 'default') {
15
+ var d = Object.getOwnPropertyDescriptor(e, k);
16
+ Object.defineProperty(n, k, d.get ? d : {
17
+ enumerable: true,
18
+ get: function () { return e[k]; }
19
+ });
20
+ }
21
+ });
22
+ }
23
+ n.default = e;
24
+ return Object.freeze(n);
25
+ }
26
+
27
+ var fs__namespace = /*#__PURE__*/_interopNamespaceDefault(fs$1);
28
+
29
+ const downloadSpec = async (path, url) => {
30
+ const res = await axios({
31
+ url,
32
+ method: 'GET',
33
+ responseType: 'arraybuffer',
34
+ }).catch(err => {
35
+ throw err.toString();
36
+ });
37
+ const content = res.data.toString();
38
+ await fs.writeFile(path, format(content));
39
+ };
40
+ const format = (content) => JSON.stringify(JSON.parse(content), null, 4);
41
+
42
+ const generateOpenApiModel = async ({ input, isNode, responseWrapper, name, outputDir }, log) => {
43
+ log(`Will generate API client name=${name} to ${outputDir}, node=${isNode}`);
44
+ const specialMapping = isNode
45
+ ? {
46
+ File: '{file: Buffer | stream.Readable, name: string}',
47
+ }
48
+ : {};
49
+ const fileName = 'index';
50
+ const dstFile = Path.join(outputDir, `${fileName}.ts`);
51
+ await swaggerTypescriptApi.generateApi({
52
+ name: 'index',
53
+ output: outputDir,
54
+ input: input,
55
+ // modular: true,
56
+ httpClientType: 'axios',
57
+ templates: Path.resolve(getThisScriptDirname(), '../templates'),
58
+ defaultResponseAsSuccess: true,
59
+ // generateRouteTypes: true,
60
+ singleHttpClient: true,
61
+ // extractRequestBody: true,
62
+ cleanOutput: true,
63
+ moduleNameFirstTag: true,
64
+ apiClassName: capitalize(name) + 'Api',
65
+ // @ts-ignore
66
+ codeGenConstructs: (struct) => ({
67
+ // @ts-ignore
68
+ Keyword: specialMapping,
69
+ }),
70
+ // extractRequestParams: true,
71
+ });
72
+ const addImports = [];
73
+ const replaces = [];
74
+ if (isNode) {
75
+ addImports.push('import stream from \'node:stream\'');
76
+ }
77
+ if (responseWrapper) {
78
+ log(`Will use response wrapper '${JSON.stringify(responseWrapper)}'`);
79
+ if (responseWrapper.import)
80
+ addImports.push(responseWrapper.import);
81
+ replaces.push(['Promise', responseWrapper.symbol]);
82
+ }
83
+ // Remove unnecessary generics
84
+ replaces.push(['<SecurityDataType extends unknown>', '']);
85
+ replaces.push(['<SecurityDataType>', '']);
86
+ log(`Will modify the outputs ${JSON.stringify({
87
+ addImports,
88
+ replaces,
89
+ })}`);
90
+ await modifyOutput(dstFile, {
91
+ addImports,
92
+ replaces,
93
+ });
94
+ return dstFile;
95
+ };
96
+ const modifyOutput = async (path, cmd) => {
97
+ let content = await fs.readFile(path).then((r) => r.toString());
98
+ if (cmd.addImports.length) {
99
+ content = cmd.addImports.join('\n') + '\n' + content;
100
+ }
101
+ cmd.replaces.forEach(([from, to]) => {
102
+ content = content.replaceAll(from, to);
103
+ });
104
+ await fs.writeFile(path, content);
105
+ };
106
+ const getThisScriptDirname = () => {
107
+ // Might be problem in ESM mode
108
+ return __dirname;
109
+ };
110
+ const capitalize = (str) => {
111
+ return str[0].toUpperCase() + str.substring(1);
112
+ };
113
+
114
+ // TODO needed?
115
+ const removeGenericTypes = (code) => {
116
+ const regex = /export (interface|enum|type) [^<]* \{\n(.*\n)*?}/mg;
117
+ const matches = code.matchAll(regex);
118
+ const types = Array.from(matches).map(m => m[0]);
119
+ return types.join('\n');
120
+ };
121
+ const generateSchemas = (inputFile, outputFile, opts = {}) => {
122
+ const code = fs$1.readFileSync(inputFile).toString();
123
+ const { getZodSchemasFile } = tsToZod.generate({
124
+ sourceText: removeGenericTypes(code),
125
+ // inputOutputMappings: {
126
+ //
127
+ // },
128
+ customJSDocFormatTypes: {
129
+ // Custom mapping
130
+ // 'date-time': 'blablaj' - regex
131
+ }
132
+ });
133
+ const fileName = Path.basename(inputFile);
134
+ let f = getZodSchemasFile(`./${fileName.replace('.ts', '')}`);
135
+ // Add import from the api model - TODO find a way how to define on generate
136
+ f = f.replaceAll('"./index";', '"./client"');
137
+ // Backend sends nulls not undefined - should be properly set in the OpenAPI TS types generation!
138
+ f = f.replaceAll('.optional()', '.nullable()');
139
+ if (opts.localDateTimes) {
140
+ f = f.replaceAll('z.string().datetime()', 'z.string().datetime({local: true})');
141
+ }
142
+ fs$1.writeFileSync(outputFile, f);
143
+ };
144
+
145
+ const generateApiClient = async ({ dstDir, apiName, env, srcSpec, responseWrapper, zodSchemas, }, log = console.log) => {
146
+ if (!srcSpec)
147
+ throw new Error(`Url or path ('srcSpec' not specified`);
148
+ if (!apiName)
149
+ throw new Error('apiName not specified');
150
+ if (!dstDir)
151
+ throw new Error('dstDir not specified');
152
+ if (!env)
153
+ throw new Error('env not specified');
154
+ const dir = Path.resolve(dstDir, apiName);
155
+ if (!fs__namespace.existsSync(dir)) {
156
+ log(`Creating dir ${dir}`);
157
+ fs__namespace.mkdirSync(dir, {
158
+ recursive: true,
159
+ });
160
+ }
161
+ const clientDir = Path.resolve(dir, 'client');
162
+ const specPath = await getSpecPath(srcSpec, dir, log);
163
+ const clientFile = await generateOpenApiModel({
164
+ name: apiName,
165
+ input: specPath,
166
+ outputDir: clientDir,
167
+ isNode: env === 'node',
168
+ responseWrapper,
169
+ }, log);
170
+ console.log('client file', clientFile);
171
+ if (zodSchemas?.enabled === false)
172
+ return;
173
+ log('Generating Zod schemas');
174
+ generateSchemas(Path.resolve(dir, clientFile), Path.resolve(dir, './zod.ts'), zodSchemas);
175
+ };
176
+ const getSpecPath = async (urlOrPath, dir, log) => {
177
+ if (!urlOrPath.startsWith('http')) {
178
+ if (!fs__namespace.existsSync(urlOrPath))
179
+ throw new Error(`Spec file ${urlOrPath} does not exists`);
180
+ return urlOrPath;
181
+ }
182
+ const specPath = Path.resolve(dir, `spec.json`);
183
+ log(`Will download the API spec from ${urlOrPath} to ${specPath}`);
184
+ await downloadSpec(specPath, urlOrPath);
185
+ return specPath;
186
+ };
187
+
188
+ exports.generateApiClient = generateApiClient;
@@ -0,0 +1,168 @@
1
+ import Path from 'path';
2
+ import * as fs from 'fs';
3
+ import fs__default from 'fs';
4
+ import axios from 'axios';
5
+ import fs$1 from 'fs/promises';
6
+ import { generateApi } from 'swagger-typescript-api';
7
+ import { generate } from 'ts-to-zod';
8
+
9
+ const downloadSpec = async (path, url) => {
10
+ const res = await axios({
11
+ url,
12
+ method: 'GET',
13
+ responseType: 'arraybuffer',
14
+ }).catch(err => {
15
+ throw err.toString();
16
+ });
17
+ const content = res.data.toString();
18
+ await fs$1.writeFile(path, format(content));
19
+ };
20
+ const format = (content) => JSON.stringify(JSON.parse(content), null, 4);
21
+
22
+ const generateOpenApiModel = async ({ input, isNode, responseWrapper, name, outputDir }, log) => {
23
+ log(`Will generate API client name=${name} to ${outputDir}, node=${isNode}`);
24
+ const specialMapping = isNode
25
+ ? {
26
+ File: '{file: Buffer | stream.Readable, name: string}',
27
+ }
28
+ : {};
29
+ const fileName = 'index';
30
+ const dstFile = Path.join(outputDir, `${fileName}.ts`);
31
+ await generateApi({
32
+ name: 'index',
33
+ output: outputDir,
34
+ input: input,
35
+ // modular: true,
36
+ httpClientType: 'axios',
37
+ templates: Path.resolve(getThisScriptDirname(), '../templates'),
38
+ defaultResponseAsSuccess: true,
39
+ // generateRouteTypes: true,
40
+ singleHttpClient: true,
41
+ // extractRequestBody: true,
42
+ cleanOutput: true,
43
+ moduleNameFirstTag: true,
44
+ apiClassName: capitalize(name) + 'Api',
45
+ // @ts-ignore
46
+ codeGenConstructs: (struct) => ({
47
+ // @ts-ignore
48
+ Keyword: specialMapping,
49
+ }),
50
+ // extractRequestParams: true,
51
+ });
52
+ const addImports = [];
53
+ const replaces = [];
54
+ if (isNode) {
55
+ addImports.push('import stream from \'node:stream\'');
56
+ }
57
+ if (responseWrapper) {
58
+ log(`Will use response wrapper '${JSON.stringify(responseWrapper)}'`);
59
+ if (responseWrapper.import)
60
+ addImports.push(responseWrapper.import);
61
+ replaces.push(['Promise', responseWrapper.symbol]);
62
+ }
63
+ // Remove unnecessary generics
64
+ replaces.push(['<SecurityDataType extends unknown>', '']);
65
+ replaces.push(['<SecurityDataType>', '']);
66
+ log(`Will modify the outputs ${JSON.stringify({
67
+ addImports,
68
+ replaces,
69
+ })}`);
70
+ await modifyOutput(dstFile, {
71
+ addImports,
72
+ replaces,
73
+ });
74
+ return dstFile;
75
+ };
76
+ const modifyOutput = async (path, cmd) => {
77
+ let content = await fs$1.readFile(path).then((r) => r.toString());
78
+ if (cmd.addImports.length) {
79
+ content = cmd.addImports.join('\n') + '\n' + content;
80
+ }
81
+ cmd.replaces.forEach(([from, to]) => {
82
+ content = content.replaceAll(from, to);
83
+ });
84
+ await fs$1.writeFile(path, content);
85
+ };
86
+ const getThisScriptDirname = () => {
87
+ // Might be problem in ESM mode
88
+ return __dirname;
89
+ };
90
+ const capitalize = (str) => {
91
+ return str[0].toUpperCase() + str.substring(1);
92
+ };
93
+
94
+ // TODO needed?
95
+ const removeGenericTypes = (code) => {
96
+ const regex = /export (interface|enum|type) [^<]* \{\n(.*\n)*?}/mg;
97
+ const matches = code.matchAll(regex);
98
+ const types = Array.from(matches).map(m => m[0]);
99
+ return types.join('\n');
100
+ };
101
+ const generateSchemas = (inputFile, outputFile, opts = {}) => {
102
+ const code = fs__default.readFileSync(inputFile).toString();
103
+ const { getZodSchemasFile } = generate({
104
+ sourceText: removeGenericTypes(code),
105
+ // inputOutputMappings: {
106
+ //
107
+ // },
108
+ customJSDocFormatTypes: {
109
+ // Custom mapping
110
+ // 'date-time': 'blablaj' - regex
111
+ }
112
+ });
113
+ const fileName = Path.basename(inputFile);
114
+ let f = getZodSchemasFile(`./${fileName.replace('.ts', '')}`);
115
+ // Add import from the api model - TODO find a way how to define on generate
116
+ f = f.replaceAll('"./index";', '"./client"');
117
+ // Backend sends nulls not undefined - should be properly set in the OpenAPI TS types generation!
118
+ f = f.replaceAll('.optional()', '.nullable()');
119
+ if (opts.localDateTimes) {
120
+ f = f.replaceAll('z.string().datetime()', 'z.string().datetime({local: true})');
121
+ }
122
+ fs__default.writeFileSync(outputFile, f);
123
+ };
124
+
125
+ const generateApiClient = async ({ dstDir, apiName, env, srcSpec, responseWrapper, zodSchemas, }, log = console.log) => {
126
+ if (!srcSpec)
127
+ throw new Error(`Url or path ('srcSpec' not specified`);
128
+ if (!apiName)
129
+ throw new Error('apiName not specified');
130
+ if (!dstDir)
131
+ throw new Error('dstDir not specified');
132
+ if (!env)
133
+ throw new Error('env not specified');
134
+ const dir = Path.resolve(dstDir, apiName);
135
+ if (!fs.existsSync(dir)) {
136
+ log(`Creating dir ${dir}`);
137
+ fs.mkdirSync(dir, {
138
+ recursive: true,
139
+ });
140
+ }
141
+ const clientDir = Path.resolve(dir, 'client');
142
+ const specPath = await getSpecPath(srcSpec, dir, log);
143
+ const clientFile = await generateOpenApiModel({
144
+ name: apiName,
145
+ input: specPath,
146
+ outputDir: clientDir,
147
+ isNode: env === 'node',
148
+ responseWrapper,
149
+ }, log);
150
+ console.log('client file', clientFile);
151
+ if (zodSchemas?.enabled === false)
152
+ return;
153
+ log('Generating Zod schemas');
154
+ generateSchemas(Path.resolve(dir, clientFile), Path.resolve(dir, './zod.ts'), zodSchemas);
155
+ };
156
+ const getSpecPath = async (urlOrPath, dir, log) => {
157
+ if (!urlOrPath.startsWith('http')) {
158
+ if (!fs.existsSync(urlOrPath))
159
+ throw new Error(`Spec file ${urlOrPath} does not exists`);
160
+ return urlOrPath;
161
+ }
162
+ const specPath = Path.resolve(dir, `spec.json`);
163
+ log(`Will download the API spec from ${urlOrPath} to ${specPath}`);
164
+ await downloadSpec(specPath, urlOrPath);
165
+ return specPath;
166
+ };
167
+
168
+ export { generateApiClient as g };
@@ -0,0 +1,168 @@
1
+ import Path from 'path';
2
+ import * as fs from 'fs';
3
+ import fs__default from 'fs';
4
+ import axios from 'axios';
5
+ import fs$1 from 'fs/promises';
6
+ import { generateApi } from 'swagger-typescript-api';
7
+ import { generate } from 'ts-to-zod';
8
+
9
+ const downloadSpec = async (path, url) => {
10
+ const res = await axios({
11
+ url,
12
+ method: 'GET',
13
+ responseType: 'arraybuffer',
14
+ }).catch(err => {
15
+ throw err.toString();
16
+ });
17
+ const content = res.data.toString();
18
+ await fs$1.writeFile(path, format(content));
19
+ };
20
+ const format = (content) => JSON.stringify(JSON.parse(content), null, 4);
21
+
22
+ const generateOpenApiModel = async ({ input, isNode, responseWrapper, name, outputDir }, log) => {
23
+ log(`Will generate API client name=${name} to ${outputDir}, node=${isNode}`);
24
+ const specialMapping = isNode
25
+ ? {
26
+ File: '{file: Buffer | stream.Readable, name: string}',
27
+ }
28
+ : {};
29
+ const fileName = 'index';
30
+ const dstFile = Path.join(outputDir, `${fileName}.ts`);
31
+ await generateApi({
32
+ name: 'index',
33
+ output: outputDir,
34
+ input: input,
35
+ // modular: true,
36
+ httpClientType: 'axios',
37
+ templates: Path.resolve(getThisScriptDirname(), '../templates'),
38
+ defaultResponseAsSuccess: true,
39
+ // generateRouteTypes: true,
40
+ singleHttpClient: true,
41
+ // extractRequestBody: true,
42
+ cleanOutput: true,
43
+ moduleNameFirstTag: true,
44
+ apiClassName: capitalize(name) + 'Api',
45
+ // @ts-ignore
46
+ codeGenConstructs: (struct) => ({
47
+ // @ts-ignore
48
+ Keyword: specialMapping,
49
+ }),
50
+ // extractRequestParams: true,
51
+ });
52
+ const addImports = [];
53
+ const replaces = [];
54
+ if (isNode) {
55
+ addImports.push('import stream from \'node:stream\'');
56
+ }
57
+ if (responseWrapper) {
58
+ log(`Will use response wrapper '${JSON.stringify(responseWrapper)}'`);
59
+ if (responseWrapper.import)
60
+ addImports.push(responseWrapper.import);
61
+ replaces.push(['Promise', responseWrapper.symbol]);
62
+ }
63
+ // Remove unnecessary generics
64
+ // replaces.push(['<SecurityDataType extends unknown>', ''])
65
+ // replaces.push(['<SecurityDataType>', ''])
66
+ // replaces.push(['request: <Data, A = any>', 'request: <Data>'])
67
+ log(`Will modify the outputs ${JSON.stringify({
68
+ addImports,
69
+ replaces,
70
+ })}`);
71
+ await modifyOutput(dstFile, {
72
+ addImports,
73
+ replaces,
74
+ });
75
+ return dstFile;
76
+ };
77
+ const modifyOutput = async (path, cmd) => {
78
+ let content = await fs$1.readFile(path).then((r) => r.toString());
79
+ if (cmd.addImports.length) {
80
+ content = cmd.addImports.join('\n') + '\n' + content;
81
+ }
82
+ cmd.replaces.forEach(([from, to]) => {
83
+ content = content.replaceAll(from, to);
84
+ });
85
+ await fs$1.writeFile(path, content);
86
+ };
87
+ const getThisScriptDirname = () => {
88
+ // Might be problem in ESM mode
89
+ return __dirname;
90
+ };
91
+ const capitalize = (str) => {
92
+ return str[0].toUpperCase() + str.substring(1);
93
+ };
94
+
95
+ // TODO needed?
96
+ const removeGenericTypes = (code) => {
97
+ const regex = /export (interface|enum|type) [^<]* \{\n(.*\n)*?}/mg;
98
+ const matches = code.matchAll(regex);
99
+ const types = Array.from(matches).map(m => m[0]);
100
+ return types.join('\n');
101
+ };
102
+ const generateSchemas = (inputFile, outputFile, opts = {}) => {
103
+ const code = fs__default.readFileSync(inputFile).toString();
104
+ const { getZodSchemasFile } = generate({
105
+ sourceText: removeGenericTypes(code),
106
+ // inputOutputMappings: {
107
+ //
108
+ // },
109
+ customJSDocFormatTypes: {
110
+ // Custom mapping
111
+ // 'date-time': 'blablaj' - regex
112
+ }
113
+ });
114
+ const fileName = Path.basename(inputFile);
115
+ let f = getZodSchemasFile(`./${fileName.replace('.ts', '')}`);
116
+ // Add import from the api model - TODO find a way how to define on generate
117
+ f = f.replaceAll('"./index";', '"./client"');
118
+ // Backend sends nulls not undefined - should be properly set in the OpenAPI TS types generation!
119
+ f = f.replaceAll('.optional()', '.nullable()');
120
+ if (opts.localDateTimes) {
121
+ f = f.replaceAll('z.string().datetime()', 'z.string().datetime({local: true})');
122
+ }
123
+ fs__default.writeFileSync(outputFile, f);
124
+ };
125
+
126
+ const generateApiClient = async ({ dstDir, apiName, env, srcSpec, responseWrapper, zodSchemas }, log = console.log) => {
127
+ if (!srcSpec)
128
+ throw new Error(`Url or path ('srcSpec' not specified`);
129
+ if (!apiName)
130
+ throw new Error('apiName not specified');
131
+ if (!dstDir)
132
+ throw new Error('dstDir not specified');
133
+ if (!env)
134
+ throw new Error('env not specified');
135
+ const dir = Path.resolve(dstDir, apiName);
136
+ if (!fs.existsSync(dir)) {
137
+ log(`Creating dir ${dir}`);
138
+ fs.mkdirSync(dir, {
139
+ recursive: true
140
+ });
141
+ }
142
+ const clientDir = Path.resolve(dir, 'client');
143
+ const specPath = await getSpecPath(srcSpec, dir, log);
144
+ const clientFile = await generateOpenApiModel({
145
+ name: apiName,
146
+ input: specPath,
147
+ outputDir: clientDir,
148
+ isNode: env === 'node',
149
+ responseWrapper
150
+ }, log);
151
+ if (zodSchemas?.enabled === false)
152
+ return;
153
+ log('Generating Zod schemas');
154
+ generateSchemas(Path.resolve(dir, clientFile), Path.resolve(dir, './zod.ts'), zodSchemas);
155
+ };
156
+ const getSpecPath = async (urlOrPath, dir, log) => {
157
+ if (!urlOrPath.startsWith('http')) {
158
+ if (!fs.existsSync(urlOrPath))
159
+ throw new Error(`Spec file ${urlOrPath} does not exists`);
160
+ return urlOrPath;
161
+ }
162
+ const specPath = Path.resolve(dir, `spec.json`);
163
+ log(`Will download the API spec from ${urlOrPath} to ${specPath}`);
164
+ await downloadSpec(specPath, urlOrPath);
165
+ return specPath;
166
+ };
167
+
168
+ export { generateApiClient as g };
@@ -0,0 +1,188 @@
1
+ 'use strict';
2
+
3
+ var Path = require('path');
4
+ var fs$1 = require('fs');
5
+ var axios = require('axios');
6
+ var fs = require('fs/promises');
7
+ var swaggerTypescriptApi = require('swagger-typescript-api');
8
+ var tsToZod = require('ts-to-zod');
9
+
10
+ function _interopNamespaceDefault(e) {
11
+ var n = Object.create(null);
12
+ if (e) {
13
+ Object.keys(e).forEach(function (k) {
14
+ if (k !== 'default') {
15
+ var d = Object.getOwnPropertyDescriptor(e, k);
16
+ Object.defineProperty(n, k, d.get ? d : {
17
+ enumerable: true,
18
+ get: function () { return e[k]; }
19
+ });
20
+ }
21
+ });
22
+ }
23
+ n.default = e;
24
+ return Object.freeze(n);
25
+ }
26
+
27
+ var fs__namespace = /*#__PURE__*/_interopNamespaceDefault(fs$1);
28
+
29
+ const downloadSpec = async (path, url) => {
30
+ const res = await axios({
31
+ url,
32
+ method: 'GET',
33
+ responseType: 'arraybuffer',
34
+ }).catch(err => {
35
+ throw err.toString();
36
+ });
37
+ const content = res.data.toString();
38
+ await fs.writeFile(path, format(content));
39
+ };
40
+ const format = (content) => JSON.stringify(JSON.parse(content), null, 4);
41
+
42
+ const generateOpenApiModel = async ({ input, isNode, responseWrapper, name, outputDir }, log) => {
43
+ log(`Will generate API client name=${name} to ${outputDir}, node=${isNode}`);
44
+ const specialMapping = isNode
45
+ ? {
46
+ File: '{file: Buffer | stream.Readable, name: string}',
47
+ }
48
+ : {};
49
+ const fileName = 'index';
50
+ const dstFile = Path.join(outputDir, `${fileName}.ts`);
51
+ await swaggerTypescriptApi.generateApi({
52
+ name: 'index',
53
+ output: outputDir,
54
+ input: input,
55
+ // modular: true,
56
+ httpClientType: 'axios',
57
+ templates: Path.resolve(getThisScriptDirname(), '../templates'),
58
+ defaultResponseAsSuccess: true,
59
+ // generateRouteTypes: true,
60
+ singleHttpClient: true,
61
+ // extractRequestBody: true,
62
+ cleanOutput: true,
63
+ moduleNameFirstTag: true,
64
+ apiClassName: capitalize(name) + 'Api',
65
+ // @ts-ignore
66
+ codeGenConstructs: (struct) => ({
67
+ // @ts-ignore
68
+ Keyword: specialMapping,
69
+ }),
70
+ // extractRequestParams: true,
71
+ });
72
+ const addImports = [];
73
+ const replaces = [];
74
+ if (isNode) {
75
+ addImports.push('import stream from \'node:stream\'');
76
+ }
77
+ if (responseWrapper) {
78
+ log(`Will use response wrapper '${JSON.stringify(responseWrapper)}'`);
79
+ if (responseWrapper.import)
80
+ addImports.push(responseWrapper.import);
81
+ replaces.push(['Promise', responseWrapper.symbol]);
82
+ }
83
+ // Remove unnecessary generics
84
+ // replaces.push(['<SecurityDataType extends unknown>', ''])
85
+ // replaces.push(['<SecurityDataType>', ''])
86
+ // replaces.push(['request: <Data, A = any>', 'request: <Data>'])
87
+ log(`Will modify the outputs ${JSON.stringify({
88
+ addImports,
89
+ replaces,
90
+ })}`);
91
+ await modifyOutput(dstFile, {
92
+ addImports,
93
+ replaces,
94
+ });
95
+ return dstFile;
96
+ };
97
+ const modifyOutput = async (path, cmd) => {
98
+ let content = await fs.readFile(path).then((r) => r.toString());
99
+ if (cmd.addImports.length) {
100
+ content = cmd.addImports.join('\n') + '\n' + content;
101
+ }
102
+ cmd.replaces.forEach(([from, to]) => {
103
+ content = content.replaceAll(from, to);
104
+ });
105
+ await fs.writeFile(path, content);
106
+ };
107
+ const getThisScriptDirname = () => {
108
+ // Might be problem in ESM mode
109
+ return __dirname;
110
+ };
111
+ const capitalize = (str) => {
112
+ return str[0].toUpperCase() + str.substring(1);
113
+ };
114
+
115
+ // TODO needed?
116
+ const removeGenericTypes = (code) => {
117
+ const regex = /export (interface|enum|type) [^<]* \{\n(.*\n)*?}/mg;
118
+ const matches = code.matchAll(regex);
119
+ const types = Array.from(matches).map(m => m[0]);
120
+ return types.join('\n');
121
+ };
122
+ const generateSchemas = (inputFile, outputFile, opts = {}) => {
123
+ const code = fs$1.readFileSync(inputFile).toString();
124
+ const { getZodSchemasFile } = tsToZod.generate({
125
+ sourceText: removeGenericTypes(code),
126
+ // inputOutputMappings: {
127
+ //
128
+ // },
129
+ customJSDocFormatTypes: {
130
+ // Custom mapping
131
+ // 'date-time': 'blablaj' - regex
132
+ }
133
+ });
134
+ const fileName = Path.basename(inputFile);
135
+ let f = getZodSchemasFile(`./${fileName.replace('.ts', '')}`);
136
+ // Add import from the api model - TODO find a way how to define on generate
137
+ f = f.replaceAll('"./index";', '"./client"');
138
+ // Backend sends nulls not undefined - should be properly set in the OpenAPI TS types generation!
139
+ f = f.replaceAll('.optional()', '.nullable()');
140
+ if (opts.localDateTimes) {
141
+ f = f.replaceAll('z.string().datetime()', 'z.string().datetime({local: true})');
142
+ }
143
+ fs$1.writeFileSync(outputFile, f);
144
+ };
145
+
146
+ const generateApiClient = async ({ dstDir, apiName, env, srcSpec, responseWrapper, zodSchemas }, log = console.log) => {
147
+ if (!srcSpec)
148
+ throw new Error(`Url or path ('srcSpec' not specified`);
149
+ if (!apiName)
150
+ throw new Error('apiName not specified');
151
+ if (!dstDir)
152
+ throw new Error('dstDir not specified');
153
+ if (!env)
154
+ throw new Error('env not specified');
155
+ const dir = Path.resolve(dstDir, apiName);
156
+ if (!fs__namespace.existsSync(dir)) {
157
+ log(`Creating dir ${dir}`);
158
+ fs__namespace.mkdirSync(dir, {
159
+ recursive: true
160
+ });
161
+ }
162
+ const clientDir = Path.resolve(dir, 'client');
163
+ const specPath = await getSpecPath(srcSpec, dir, log);
164
+ const clientFile = await generateOpenApiModel({
165
+ name: apiName,
166
+ input: specPath,
167
+ outputDir: clientDir,
168
+ isNode: env === 'node',
169
+ responseWrapper
170
+ }, log);
171
+ if (zodSchemas?.enabled === false)
172
+ return;
173
+ log('Generating Zod schemas');
174
+ generateSchemas(Path.resolve(dir, clientFile), Path.resolve(dir, './zod.ts'), zodSchemas);
175
+ };
176
+ const getSpecPath = async (urlOrPath, dir, log) => {
177
+ if (!urlOrPath.startsWith('http')) {
178
+ if (!fs__namespace.existsSync(urlOrPath))
179
+ throw new Error(`Spec file ${urlOrPath} does not exists`);
180
+ return urlOrPath;
181
+ }
182
+ const specPath = Path.resolve(dir, `spec.json`);
183
+ log(`Will download the API spec from ${urlOrPath} to ${specPath}`);
184
+ await downloadSpec(specPath, urlOrPath);
185
+ return specPath;
186
+ };
187
+
188
+ exports.generateApiClient = generateApiClient;
@@ -12,4 +12,4 @@ export type Params = {
12
12
  enabled?: boolean;
13
13
  };
14
14
  };
15
- export declare const generateApiClient: ({ dstDir, apiName, env, srcSpec, responseWrapper, zodSchemas, }: Params, log?: (msg: string) => void) => Promise<void>;
15
+ export declare const generateApiClient: ({ dstDir, apiName, env, srcSpec, responseWrapper, zodSchemas }: Params, log?: (msg: string) => void) => Promise<void>;
package/dist/index.esm.js CHANGED
@@ -1,4 +1,4 @@
1
- export { g as generateApiClient } from './generateApiClient-f8NyPBtp.js';
1
+ export { g as generateApiClient } from './generateApiClient-PeRLjZk9.js';
2
2
  import 'path';
3
3
  import 'fs';
4
4
  import 'axios';
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- var generateApiClient = require('./generateApiClient-CJ2UZFXk.js');
3
+ var generateApiClient = require('./generateApiClient-d_X--WFl.js');
4
4
  require('path');
5
5
  require('fs');
6
6
  require('axios');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ps-aux/api-client-gen",
3
- "version": "0.0.7-rc1",
3
+ "version": "0.1.0-rc1",
4
4
  "main": "dist/index.js",
5
5
  "module": "dist/index.esm.js",
6
6
  "types": "dist/index.d.ts",
@@ -18,10 +18,10 @@
18
18
  "author": "",
19
19
  "license": "ISC",
20
20
  "dependencies": {
21
- "axios": "^1.5.0",
22
- "cosmiconfig": "^8.3.6",
23
- "swagger-typescript-api": "^13.0.23",
24
- "ts-to-zod": "^3.15.0"
21
+ "axios": "1.5.0",
22
+ "cosmiconfig": "8.3.6",
23
+ "swagger-typescript-api": "13.0.23",
24
+ "ts-to-zod": "3.15.0"
25
25
  },
26
26
  "devDependencies": {
27
27
  "vitest": "^3.1.1"
@@ -0,0 +1,56 @@
1
+ <%
2
+ const { apiConfig, routes, utils, config } = it;
3
+ const { info, servers, externalDocs } = apiConfig;
4
+ const { _, require, formatDescription } = utils;
5
+
6
+ const server = (servers && servers[0]) || { url: "" };
7
+
8
+ const descriptionLines = _.compact([
9
+ `@title ${info.title || "No title"}`,
10
+ info.version && `@version ${info.version}`,
11
+ info.license && `@license ${_.compact([
12
+ info.license.name,
13
+ info.license.url && `(${info.license.url})`,
14
+ ]).join(" ")}`,
15
+ info.termsOfService && `@termsOfService ${info.termsOfService}`,
16
+ server.url && `@baseUrl ${server.url}`,
17
+ externalDocs.url && `@externalDocs ${externalDocs.url}`,
18
+ info.contact && `@contact ${_.compact([
19
+ info.contact.name,
20
+ info.contact.email && `<${info.contact.email}>`,
21
+ info.contact.url && `(${info.contact.url})`,
22
+ ]).join(" ")}`,
23
+ info.description && " ",
24
+ info.description && _.replace(formatDescription(info.description), /\n/g, "\n * "),
25
+ ]);
26
+
27
+ %>
28
+
29
+ <% if (config.httpClientType === config.constants.HTTP_CLIENT.AXIOS) { %> import type { AxiosRequestConfig, AxiosResponse } from "axios"; <% } %>
30
+
31
+ <% if (descriptionLines.length) { %>
32
+ /**
33
+ <% descriptionLines.forEach((descriptionLine) => { %>
34
+ * <%~ descriptionLine %>
35
+
36
+ <% }) %>
37
+ */
38
+ <% } %>
39
+ export class <%~ config.apiClassName %><RequestParams>{
40
+
41
+ http: HttpClient<RequestParams>;
42
+
43
+ constructor (http: HttpClient<RequestParams>) {
44
+ this.http = http;
45
+ }
46
+
47
+ <% for (const { routes: combinedRoutes = [], moduleName } of routes.combined) { %>
48
+ <%~ moduleName %> = {
49
+ <% for (const route of combinedRoutes) { %>
50
+
51
+ <%~ includeFile('./procedure-call.ejs', { ...it, route }) %>
52
+
53
+ <% } %>
54
+ }
55
+ <% } %>
56
+ }
@@ -1,8 +1,5 @@
1
1
  export type ContentType = {}
2
2
 
3
- export type RequestParams = {
4
- }
5
-
6
3
  export const ContentType = {
7
4
  Json: 'application/json',
8
5
  FormData: 'multipart/form-data',
@@ -15,9 +12,8 @@ export type Request = {
15
12
  query?: any
16
13
  body?: any
17
14
  type?: string
18
- secure?: boolean
19
15
  }
20
16
 
21
- export type HttpClient = {
22
- request: <Data, A = any>(req: Request) => Promise<Data>
17
+ export type HttpClient<RequestParams> = {
18
+ request: <Data>(req: Request, params?: RequestParams) => Promise<Data>
23
19
  }
@@ -0,0 +1,98 @@
1
+ <%
2
+ const { utils, route, config } = it;
3
+ const { requestBodyInfo, responseBodyInfo, specificArgNameResolver } = route;
4
+ const { _, getInlineParseContent, getParseContent, parseSchema, getComponentByRef, require } = utils;
5
+ const { parameters, path, method, payload, query, formData, security, requestParams } = route.request;
6
+ const { type, errorType, contentTypes } = route.response;
7
+ const { HTTP_CLIENT, RESERVED_REQ_PARAMS_ARG_NAMES } = config.constants;
8
+ const routeDocs = includeFile("@base/route-docs", { config, route, utils });
9
+ const queryName = (query && query.name) || "query";
10
+ const pathParams = _.values(parameters);
11
+ const pathParamsNames = _.map(pathParams, "name");
12
+
13
+ const isFetchTemplate = config.httpClientType === HTTP_CLIENT.FETCH;
14
+
15
+ const requestConfigParam = {
16
+ name: specificArgNameResolver.resolve(RESERVED_REQ_PARAMS_ARG_NAMES),
17
+ optional: true,
18
+ type: "RequestParams"
19
+ }
20
+
21
+ const argToTmpl = ({ name, optional, type, defaultValue }) => `${name}${!defaultValue && optional ? '?' : ''}: ${type}${defaultValue ? ` = ${defaultValue}` : ''}`;
22
+
23
+ const rawWrapperArgs = config.extractRequestParams ?
24
+ _.compact([
25
+ requestParams && {
26
+ name: pathParams.length ? `{ ${_.join(pathParamsNames, ", ")}, ...${queryName} }` : queryName,
27
+ optional: false,
28
+ type: getInlineParseContent(requestParams),
29
+ },
30
+ ...(!requestParams ? pathParams : []),
31
+ payload,
32
+ requestConfigParam,
33
+ ]) :
34
+ _.compact([
35
+ ...pathParams,
36
+ query,
37
+ payload,
38
+ requestConfigParam,
39
+ ])
40
+
41
+ const wrapperArgs = _
42
+ // Sort by optionality
43
+ .sortBy(rawWrapperArgs, [o => o.optional])
44
+ .map(argToTmpl)
45
+ .join(', ')
46
+
47
+ // RequestParams["type"]
48
+ const requestContentKind = {
49
+ "JSON": "ContentType.Json",
50
+ "URL_ENCODED": "ContentType.UrlEncoded",
51
+ "FORM_DATA": "ContentType.FormData",
52
+ "TEXT": "ContentType.Text",
53
+ }
54
+ // RequestParams["format"]
55
+ const responseContentKind = {
56
+ "JSON": '"json"',
57
+ "IMAGE": '"blob"',
58
+ "FORM_DATA": isFetchTemplate ? '"formData"' : '"document"'
59
+ }
60
+
61
+ const bodyTmpl = _.get(payload, "name") || null;
62
+ const queryTmpl = (query != null && queryName) || null;
63
+ const bodyContentKindTmpl = requestContentKind[requestBodyInfo.contentKind] || null;
64
+ const responseFormatTmpl = responseContentKind[responseBodyInfo.success && responseBodyInfo.success.schema && responseBodyInfo.success.schema.contentKind] || null;
65
+ const securityTmpl = security ? 'true' : null;
66
+
67
+ const describeReturnType = () => {
68
+ if (!config.toJS) return "";
69
+
70
+ switch(config.httpClientType) {
71
+ case HTTP_CLIENT.AXIOS: {
72
+ return `Promise<AxiosResponse<${type}>>`
73
+ }
74
+ default: {
75
+ return `Promise<HttpResponse<${type}, ${errorType}>`
76
+ }
77
+ }
78
+ }
79
+
80
+ %>
81
+ /**
82
+ <%~ routeDocs.description %>
83
+
84
+ *<% /* Here you can add some other JSDoc tags */ %>
85
+
86
+ <%~ routeDocs.lines %>
87
+
88
+ */
89
+ <%~ route.routeName.usage %><%~ route.namespace ? ': ' : ' = ' %>(<%~ wrapperArgs %>)<%~ config.toJS ? `: ${describeReturnType()}` : "" %> =>
90
+ <%~ config.singleHttpClient ? 'this.http.request' : 'this.request' %><<%~ type %>>({
91
+ path: `<%~ path %>`,
92
+ method: '<%~ _.upperCase(method) %>',
93
+ <%~ queryTmpl ? `query: ${queryTmpl},` : '' %>
94
+ <%~ bodyTmpl ? `body: ${bodyTmpl},` : '' %>
95
+ <%~ securityTmpl ? `secure: ${securityTmpl},` : '' %>
96
+ <%~ bodyContentKindTmpl ? `type: ${bodyContentKindTmpl},` : '' %>
97
+ <%~ responseFormatTmpl ? `format: ${responseFormatTmpl},` : '' %>
98
+ }, params)<%~ route.namespace ? ',' : '' %>