@ps-aux/api-client-gen 0.0.3 → 0.0.4
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 +22 -0
- package/dist/bin.js +24 -0
- package/dist/downloadSpec.d.ts +1 -1
- package/dist/generateApiClient.d.ts +12 -0
- package/dist/generateOpenApiModel.d.ts +11 -1
- package/dist/generateSchemas.d.ts +1 -0
- package/dist/index.d.ts +1 -5
- package/dist/index.esm.js +90 -75
- package/dist/index.js +144 -132
- package/package.json +6 -7
- package/rollup.config.mjs +30 -16
- package/src/downloadSpec.ts +10 -12
- package/src/{gen-model.ts → generateApiClient.ts} +28 -16
- package/src/generateOpenApiModel.ts +69 -10
- package/src/generateSchemas.ts +28 -0
- package/src/go.ts +2 -5
- package/src/index.ts +1 -6
- package/templates/http-client.eta +2 -5
- package/dist/foo.d.ts +0 -1
- package/dist/gen-model.d.ts +0 -7
- package/dist/go +0 -4
- package/dist/go.js +0 -151
package/dist/bin.esm.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
#!/bin/env node
|
|
2
|
+
import { generateApiClient } from './index.esm.js';
|
|
3
|
+
import { cosmiconfigSync } from 'cosmiconfig';
|
|
4
|
+
import 'path';
|
|
5
|
+
import 'fs';
|
|
6
|
+
import 'axios';
|
|
7
|
+
import 'fs/promises';
|
|
8
|
+
import 'swagger-typescript-api';
|
|
9
|
+
import 'ts-to-zod';
|
|
10
|
+
|
|
11
|
+
const profile = process.argv[2];
|
|
12
|
+
if (!profile)
|
|
13
|
+
throw new Error(`No profile specified`);
|
|
14
|
+
const conf = cosmiconfigSync('api-client-gen')
|
|
15
|
+
.search();
|
|
16
|
+
// TODO validate config
|
|
17
|
+
if (!conf)
|
|
18
|
+
throw new Error(`No config provided`);
|
|
19
|
+
const profileConf = conf.config[profile];
|
|
20
|
+
if (!profile)
|
|
21
|
+
throw new Error(`Profile ${profile} not present in the configuration`);
|
|
22
|
+
generateApiClient(profileConf).catch(console.error);
|
package/dist/bin.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
#!/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
var index = require('./index.js');
|
|
5
|
+
var cosmiconfig = require('cosmiconfig');
|
|
6
|
+
require('path');
|
|
7
|
+
require('fs');
|
|
8
|
+
require('axios');
|
|
9
|
+
require('fs/promises');
|
|
10
|
+
require('swagger-typescript-api');
|
|
11
|
+
require('ts-to-zod');
|
|
12
|
+
|
|
13
|
+
const profile = process.argv[2];
|
|
14
|
+
if (!profile)
|
|
15
|
+
throw new Error(`No profile specified`);
|
|
16
|
+
const conf = cosmiconfig.cosmiconfigSync('api-client-gen')
|
|
17
|
+
.search();
|
|
18
|
+
// TODO validate config
|
|
19
|
+
if (!conf)
|
|
20
|
+
throw new Error(`No config provided`);
|
|
21
|
+
const profileConf = conf.config[profile];
|
|
22
|
+
if (!profile)
|
|
23
|
+
throw new Error(`Profile ${profile} not present in the configuration`);
|
|
24
|
+
index.generateApiClient(profileConf).catch(console.error);
|
package/dist/downloadSpec.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const downloadSpec: (path: string, url: string) => Promise<
|
|
1
|
+
export declare const downloadSpec: (path: string, url: string) => Promise<void>;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export type Params = {
|
|
2
|
+
srcSpec: string;
|
|
3
|
+
dstDir: string;
|
|
4
|
+
apiName: string;
|
|
5
|
+
env: 'browser' | 'node';
|
|
6
|
+
responseWrapper?: {
|
|
7
|
+
symbol: string;
|
|
8
|
+
import?: string;
|
|
9
|
+
};
|
|
10
|
+
zodSchemas?: boolean;
|
|
11
|
+
};
|
|
12
|
+
export declare const generateApiClient: ({ dstDir, apiName, env, srcSpec, responseWrapper, zodSchemas, }: Params, log?: (msg: string) => void) => Promise<void>;
|
|
@@ -1 +1,11 @@
|
|
|
1
|
-
export
|
|
1
|
+
export type GenerateOpenApiModelCmd = {
|
|
2
|
+
name: string;
|
|
3
|
+
input: string;
|
|
4
|
+
outputDir: string;
|
|
5
|
+
isNode?: boolean;
|
|
6
|
+
responseWrapper?: {
|
|
7
|
+
symbol: string;
|
|
8
|
+
import?: string;
|
|
9
|
+
};
|
|
10
|
+
};
|
|
11
|
+
export declare const generateOpenApiModel: ({ input, isNode, responseWrapper, name, outputDir }: GenerateOpenApiModelCmd, log: (msg: string) => void) => Promise<string>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const generateSchemas: (inputFile: string, outputFile: string) => void;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1 @@
|
|
|
1
|
-
export {
|
|
2
|
-
declare const _default: {
|
|
3
|
-
generateModel: ({ dstDir, apiName, env, srcSpec, }: import("./gen-model").Params, log?: (msg: string) => void) => Promise<void>;
|
|
4
|
-
};
|
|
5
|
-
export default _default;
|
|
1
|
+
export { generateApiClient } from "./generateApiClient";
|
package/dist/index.esm.js
CHANGED
|
@@ -1,70 +1,34 @@
|
|
|
1
1
|
import Path from 'path';
|
|
2
|
-
import fs from 'fs';
|
|
2
|
+
import fs$1 from 'fs';
|
|
3
3
|
import axios from 'axios';
|
|
4
|
+
import fs from 'fs/promises';
|
|
4
5
|
import { generateApi } from 'swagger-typescript-api';
|
|
5
|
-
import
|
|
6
|
+
import { generate } from 'ts-to-zod';
|
|
6
7
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
Permission to use, copy, modify, and/or distribute this software for any
|
|
11
|
-
purpose with or without fee is hereby granted.
|
|
12
|
-
|
|
13
|
-
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
|
14
|
-
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
|
15
|
-
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
|
16
|
-
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
|
17
|
-
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
|
18
|
-
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
|
19
|
-
PERFORMANCE OF THIS SOFTWARE.
|
|
20
|
-
***************************************************************************** */
|
|
21
|
-
/* global Reflect, Promise, SuppressedError, Symbol */
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
function __awaiter(thisArg, _arguments, P, generator) {
|
|
25
|
-
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
26
|
-
return new (P || (P = Promise))(function (resolve, reject) {
|
|
27
|
-
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
28
|
-
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
29
|
-
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
30
|
-
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
31
|
-
});
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
|
|
35
|
-
var e = new Error(message);
|
|
36
|
-
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
37
|
-
};
|
|
38
|
-
|
|
39
|
-
const downloadSpec = (path, url) => __awaiter(void 0, void 0, void 0, function* () {
|
|
40
|
-
const res = yield axios({
|
|
8
|
+
const downloadSpec = async (path, url) => {
|
|
9
|
+
const res = await axios({
|
|
41
10
|
url,
|
|
42
11
|
method: 'GET',
|
|
43
|
-
responseType: '
|
|
12
|
+
responseType: 'arraybuffer',
|
|
44
13
|
}).catch(err => {
|
|
45
14
|
throw err.toString();
|
|
46
15
|
});
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
writer.on('finish', resolve);
|
|
52
|
-
writer.on('error', err => {
|
|
53
|
-
reject(err);
|
|
54
|
-
});
|
|
55
|
-
});
|
|
56
|
-
});
|
|
16
|
+
const content = res.data.toString();
|
|
17
|
+
await fs.writeFile(path, format(content));
|
|
18
|
+
};
|
|
19
|
+
const format = (content) => JSON.stringify(JSON.parse(content), null, 4);
|
|
57
20
|
|
|
58
|
-
const generateOpenApiModel = (
|
|
21
|
+
const generateOpenApiModel = async ({ input, isNode, responseWrapper, name, outputDir }, log) => {
|
|
59
22
|
log(`Will generate API client name=${name} to ${outputDir}, node=${isNode}`);
|
|
60
23
|
const specialMapping = isNode
|
|
61
24
|
? {
|
|
62
25
|
File: '{file: Buffer | stream.Readable, name: string}',
|
|
63
26
|
}
|
|
64
27
|
: {};
|
|
65
|
-
const
|
|
66
|
-
|
|
67
|
-
|
|
28
|
+
const fileName = 'index';
|
|
29
|
+
const dstFile = Path.join(outputDir, `${fileName}.ts`);
|
|
30
|
+
await generateApi({
|
|
31
|
+
name: 'index',
|
|
68
32
|
output: outputDir,
|
|
69
33
|
input: input,
|
|
70
34
|
// modular: true,
|
|
@@ -76,6 +40,7 @@ const generateOpenApiModel = (name, input, outputDir, log, isNode = false) => __
|
|
|
76
40
|
// extractRequestBody: true,
|
|
77
41
|
cleanOutput: true,
|
|
78
42
|
moduleNameFirstTag: true,
|
|
43
|
+
apiClassName: capitalize(name) + 'Api',
|
|
79
44
|
// @ts-ignore
|
|
80
45
|
codeGenConstructs: (struct) => ({
|
|
81
46
|
// @ts-ignore
|
|
@@ -83,19 +48,67 @@ const generateOpenApiModel = (name, input, outputDir, log, isNode = false) => __
|
|
|
83
48
|
}),
|
|
84
49
|
// extractRequestParams: true,
|
|
85
50
|
});
|
|
51
|
+
const addImports = [];
|
|
52
|
+
const replaces = [];
|
|
86
53
|
if (isNode) {
|
|
87
|
-
|
|
88
|
-
// Naive solution
|
|
89
|
-
content = 'import stream from \'node:stream\'\n' + content;
|
|
90
|
-
yield fs$1.writeFile(dstFile, content);
|
|
54
|
+
addImports.push('import stream from \'node:stream\'');
|
|
91
55
|
}
|
|
92
|
-
|
|
56
|
+
if (responseWrapper) {
|
|
57
|
+
log(`Will use response wrapper '${JSON.stringify(responseWrapper)}'`);
|
|
58
|
+
if (responseWrapper.import)
|
|
59
|
+
addImports.push(responseWrapper.import);
|
|
60
|
+
replaces.push(['Promise', responseWrapper.symbol]);
|
|
61
|
+
}
|
|
62
|
+
// Remove unnecessary generics
|
|
63
|
+
replaces.push(['<SecurityDataType extends unknown>', '']);
|
|
64
|
+
replaces.push(['<SecurityDataType>', '']);
|
|
65
|
+
log(`Will modify the outputs ${JSON.stringify({
|
|
66
|
+
addImports,
|
|
67
|
+
replaces,
|
|
68
|
+
})}`);
|
|
69
|
+
await modifyOutput(dstFile, {
|
|
70
|
+
addImports,
|
|
71
|
+
replaces,
|
|
72
|
+
});
|
|
73
|
+
return dstFile;
|
|
74
|
+
};
|
|
75
|
+
const modifyOutput = async (path, cmd) => {
|
|
76
|
+
let content = await fs.readFile(path).then((r) => r.toString());
|
|
77
|
+
if (cmd.addImports.length) {
|
|
78
|
+
content = cmd.addImports.join('\n') + '\n' + content;
|
|
79
|
+
}
|
|
80
|
+
cmd.replaces.forEach(([from, to]) => {
|
|
81
|
+
content = content.replaceAll(from, to);
|
|
82
|
+
});
|
|
83
|
+
await fs.writeFile(path, content);
|
|
84
|
+
};
|
|
93
85
|
const getThisScriptDirname = () => {
|
|
94
86
|
// Might be problem in ESM mode
|
|
95
87
|
return __dirname;
|
|
96
88
|
};
|
|
89
|
+
const capitalize = (str) => {
|
|
90
|
+
return str[0].toUpperCase() + str.substring(1);
|
|
91
|
+
};
|
|
97
92
|
|
|
98
|
-
const
|
|
93
|
+
const removeGenericTypes = (code) => {
|
|
94
|
+
const regex = /export (interface|enum|type) [^<]* \{\n(.*\n)*?}/mg;
|
|
95
|
+
const matches = code.matchAll(regex);
|
|
96
|
+
const types = Array.from(matches).map(m => m[0]);
|
|
97
|
+
return types.join('\n');
|
|
98
|
+
};
|
|
99
|
+
const generateSchemas = (inputFile, outputFile) => {
|
|
100
|
+
const code = fs$1.readFileSync(inputFile).toString();
|
|
101
|
+
const { getZodSchemasFile } = generate({
|
|
102
|
+
sourceText: removeGenericTypes(code),
|
|
103
|
+
});
|
|
104
|
+
const fileName = Path.basename(inputFile);
|
|
105
|
+
let f = getZodSchemasFile(`./${fileName.replace('.ts', '')}`);
|
|
106
|
+
// Backend sends nulls not undefined - should be properly set in the OpenAPI TS types generation!
|
|
107
|
+
f = f.replaceAll('.optional()', '.nullable()');
|
|
108
|
+
fs$1.writeFileSync(outputFile, f);
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
const generateApiClient = async ({ dstDir, apiName, env, srcSpec, responseWrapper, zodSchemas, }, log = console.log) => {
|
|
99
112
|
if (!srcSpec)
|
|
100
113
|
throw new Error(`Url or path ('srcSpec' not specified`);
|
|
101
114
|
if (!apiName)
|
|
@@ -105,34 +118,36 @@ const generateModel = ({ dstDir, apiName, env, srcSpec, }, log = console.log) =>
|
|
|
105
118
|
if (!env)
|
|
106
119
|
throw new Error('env not specified');
|
|
107
120
|
const dir = Path.resolve(dstDir, apiName);
|
|
108
|
-
if (!fs.existsSync(dir)) {
|
|
121
|
+
if (!fs$1.existsSync(dir)) {
|
|
109
122
|
log(`Creating dir ${dir}`);
|
|
110
|
-
fs.mkdirSync(dir, {
|
|
123
|
+
fs$1.mkdirSync(dir, {
|
|
111
124
|
recursive: true,
|
|
112
125
|
});
|
|
113
126
|
}
|
|
114
127
|
const clientDir = Path.resolve(dir, 'client');
|
|
115
|
-
const specPath =
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
});
|
|
123
|
-
|
|
128
|
+
const specPath = await getSpecPath(srcSpec, dir, log);
|
|
129
|
+
const clientFile = await generateOpenApiModel({
|
|
130
|
+
name: apiName,
|
|
131
|
+
input: specPath,
|
|
132
|
+
outputDir: clientDir,
|
|
133
|
+
isNode: env === 'node',
|
|
134
|
+
responseWrapper,
|
|
135
|
+
}, log);
|
|
136
|
+
if (zodSchemas === false)
|
|
137
|
+
return;
|
|
138
|
+
log('Generating Zod schemas');
|
|
139
|
+
generateSchemas(Path.resolve(dir, clientFile), Path.resolve(dir, './zod.ts'));
|
|
140
|
+
};
|
|
141
|
+
const getSpecPath = async (urlOrPath, dir, log) => {
|
|
124
142
|
if (!urlOrPath.startsWith('http')) {
|
|
125
|
-
if (!fs.existsSync(urlOrPath))
|
|
143
|
+
if (!fs$1.existsSync(urlOrPath))
|
|
126
144
|
throw new Error(`Spec file ${urlOrPath} does not exists`);
|
|
127
145
|
return urlOrPath;
|
|
128
146
|
}
|
|
129
147
|
const specPath = Path.resolve(dir, `spec.json`);
|
|
130
148
|
log(`Will download the API spec from ${urlOrPath} to ${specPath}`);
|
|
131
|
-
|
|
149
|
+
await downloadSpec(specPath, urlOrPath);
|
|
132
150
|
return specPath;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
// Bcs of mixing for ESM and CommonJS - todo fix somehow
|
|
136
|
-
var index = { generateModel };
|
|
151
|
+
};
|
|
137
152
|
|
|
138
|
-
export {
|
|
153
|
+
export { generateApiClient };
|
package/dist/index.js
CHANGED
|
@@ -1,143 +1,155 @@
|
|
|
1
|
-
|
|
2
|
-
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('path'), require('fs'), require('axios'), require('swagger-typescript-api'), require('fs/promises')) :
|
|
3
|
-
typeof define === 'function' && define.amd ? define(['exports', 'path', 'fs', 'axios', 'swagger-typescript-api', 'fs/promises'], factory) :
|
|
4
|
-
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.MyLibrary = {}, global.Path, global.fs, global.axios, global.swaggerTypescriptApi, global.fs$1));
|
|
5
|
-
})(this, (function (exports, Path, fs, axios, swaggerTypescriptApi, fs$1) { 'use strict';
|
|
1
|
+
'use strict';
|
|
6
2
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
|
14
|
-
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
|
15
|
-
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
|
16
|
-
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
|
17
|
-
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
|
18
|
-
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
|
19
|
-
PERFORMANCE OF THIS SOFTWARE.
|
|
20
|
-
***************************************************************************** */
|
|
21
|
-
/* global Reflect, Promise, SuppressedError, Symbol */
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
function __awaiter(thisArg, _arguments, P, generator) {
|
|
25
|
-
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
26
|
-
return new (P || (P = Promise))(function (resolve, reject) {
|
|
27
|
-
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
28
|
-
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
29
|
-
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
30
|
-
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
31
|
-
});
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
|
|
35
|
-
var e = new Error(message);
|
|
36
|
-
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
37
|
-
};
|
|
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');
|
|
38
9
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
});
|
|
47
|
-
const writer = fs.createWriteStream(path);
|
|
48
|
-
// TODO Format to readable JSON
|
|
49
|
-
yield res.data.pipe(writer);
|
|
50
|
-
return new Promise((resolve, reject) => {
|
|
51
|
-
writer.on('finish', resolve);
|
|
52
|
-
writer.on('error', err => {
|
|
53
|
-
reject(err);
|
|
54
|
-
});
|
|
55
|
-
});
|
|
10
|
+
const downloadSpec = async (path, url) => {
|
|
11
|
+
const res = await axios({
|
|
12
|
+
url,
|
|
13
|
+
method: 'GET',
|
|
14
|
+
responseType: 'arraybuffer',
|
|
15
|
+
}).catch(err => {
|
|
16
|
+
throw err.toString();
|
|
56
17
|
});
|
|
18
|
+
const content = res.data.toString();
|
|
19
|
+
await fs.writeFile(path, format(content));
|
|
20
|
+
};
|
|
21
|
+
const format = (content) => JSON.stringify(JSON.parse(content), null, 4);
|
|
57
22
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
}
|
|
64
|
-
: {};
|
|
65
|
-
const dstFile = Path.join(outputDir, name + '.ts');
|
|
66
|
-
yield swaggerTypescriptApi.generateApi({
|
|
67
|
-
name,
|
|
68
|
-
output: outputDir,
|
|
69
|
-
input: input,
|
|
70
|
-
// modular: true,
|
|
71
|
-
httpClientType: 'axios',
|
|
72
|
-
templates: Path.resolve(getThisScriptDirname(), '../templates'),
|
|
73
|
-
defaultResponseAsSuccess: true,
|
|
74
|
-
// generateRouteTypes: true,
|
|
75
|
-
singleHttpClient: true,
|
|
76
|
-
// extractRequestBody: true,
|
|
77
|
-
cleanOutput: true,
|
|
78
|
-
moduleNameFirstTag: true,
|
|
79
|
-
// @ts-ignore
|
|
80
|
-
codeGenConstructs: (struct) => ({
|
|
81
|
-
// @ts-ignore
|
|
82
|
-
Keyword: specialMapping,
|
|
83
|
-
}),
|
|
84
|
-
// extractRequestParams: true,
|
|
85
|
-
});
|
|
86
|
-
if (isNode) {
|
|
87
|
-
let content = yield fs$1.readFile(dstFile).then((r) => r.toString());
|
|
88
|
-
// Naive solution
|
|
89
|
-
content = 'import stream from \'node:stream\'\n' + content;
|
|
90
|
-
yield fs$1.writeFile(dstFile, content);
|
|
23
|
+
const generateOpenApiModel = async ({ input, isNode, responseWrapper, name, outputDir }, log) => {
|
|
24
|
+
log(`Will generate API client name=${name} to ${outputDir}, node=${isNode}`);
|
|
25
|
+
const specialMapping = isNode
|
|
26
|
+
? {
|
|
27
|
+
File: '{file: Buffer | stream.Readable, name: string}',
|
|
91
28
|
}
|
|
29
|
+
: {};
|
|
30
|
+
const fileName = 'index';
|
|
31
|
+
const dstFile = Path.join(outputDir, `${fileName}.ts`);
|
|
32
|
+
await swaggerTypescriptApi.generateApi({
|
|
33
|
+
name: 'index',
|
|
34
|
+
output: outputDir,
|
|
35
|
+
input: input,
|
|
36
|
+
// modular: true,
|
|
37
|
+
httpClientType: 'axios',
|
|
38
|
+
templates: Path.resolve(getThisScriptDirname(), '../templates'),
|
|
39
|
+
defaultResponseAsSuccess: true,
|
|
40
|
+
// generateRouteTypes: true,
|
|
41
|
+
singleHttpClient: true,
|
|
42
|
+
// extractRequestBody: true,
|
|
43
|
+
cleanOutput: true,
|
|
44
|
+
moduleNameFirstTag: true,
|
|
45
|
+
apiClassName: capitalize(name) + 'Api',
|
|
46
|
+
// @ts-ignore
|
|
47
|
+
codeGenConstructs: (struct) => ({
|
|
48
|
+
// @ts-ignore
|
|
49
|
+
Keyword: specialMapping,
|
|
50
|
+
}),
|
|
51
|
+
// extractRequestParams: true,
|
|
92
52
|
});
|
|
93
|
-
const
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
const clientDir = Path.resolve(dir, 'client');
|
|
115
|
-
const specPath = yield getSpecPath(srcSpec, dir, log);
|
|
116
|
-
yield generateOpenApiModel(apiName + '-api', specPath, clientDir, log, env === 'node');
|
|
117
|
-
// TODO does not seem to currently generate all schemas
|
|
118
|
-
// generateSchemas(
|
|
119
|
-
// Path.resolve(modelDir, 'data-contracts.ts'),
|
|
120
|
-
// Path.resolve(dir, 'schemas.ts')
|
|
121
|
-
// )
|
|
53
|
+
const addImports = [];
|
|
54
|
+
const replaces = [];
|
|
55
|
+
if (isNode) {
|
|
56
|
+
addImports.push('import stream from \'node:stream\'');
|
|
57
|
+
}
|
|
58
|
+
if (responseWrapper) {
|
|
59
|
+
log(`Will use response wrapper '${JSON.stringify(responseWrapper)}'`);
|
|
60
|
+
if (responseWrapper.import)
|
|
61
|
+
addImports.push(responseWrapper.import);
|
|
62
|
+
replaces.push(['Promise', responseWrapper.symbol]);
|
|
63
|
+
}
|
|
64
|
+
// Remove unnecessary generics
|
|
65
|
+
replaces.push(['<SecurityDataType extends unknown>', '']);
|
|
66
|
+
replaces.push(['<SecurityDataType>', '']);
|
|
67
|
+
log(`Will modify the outputs ${JSON.stringify({
|
|
68
|
+
addImports,
|
|
69
|
+
replaces,
|
|
70
|
+
})}`);
|
|
71
|
+
await modifyOutput(dstFile, {
|
|
72
|
+
addImports,
|
|
73
|
+
replaces,
|
|
122
74
|
});
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
return specPath;
|
|
75
|
+
return dstFile;
|
|
76
|
+
};
|
|
77
|
+
const modifyOutput = async (path, cmd) => {
|
|
78
|
+
let content = await fs.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);
|
|
133
84
|
});
|
|
85
|
+
await fs.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
|
+
};
|
|
134
94
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
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) => {
|
|
102
|
+
const code = fs$1.readFileSync(inputFile).toString();
|
|
103
|
+
const { getZodSchemasFile } = tsToZod.generate({
|
|
104
|
+
sourceText: removeGenericTypes(code),
|
|
105
|
+
});
|
|
106
|
+
const fileName = Path.basename(inputFile);
|
|
107
|
+
let f = getZodSchemasFile(`./${fileName.replace('.ts', '')}`);
|
|
108
|
+
// Backend sends nulls not undefined - should be properly set in the OpenAPI TS types generation!
|
|
109
|
+
f = f.replaceAll('.optional()', '.nullable()');
|
|
110
|
+
fs$1.writeFileSync(outputFile, f);
|
|
111
|
+
};
|
|
140
112
|
|
|
141
|
-
|
|
113
|
+
const generateApiClient = async ({ dstDir, apiName, env, srcSpec, responseWrapper, zodSchemas, }, log = console.log) => {
|
|
114
|
+
if (!srcSpec)
|
|
115
|
+
throw new Error(`Url or path ('srcSpec' not specified`);
|
|
116
|
+
if (!apiName)
|
|
117
|
+
throw new Error('apiName not specified');
|
|
118
|
+
if (!dstDir)
|
|
119
|
+
throw new Error('dstDir not specified');
|
|
120
|
+
if (!env)
|
|
121
|
+
throw new Error('env not specified');
|
|
122
|
+
const dir = Path.resolve(dstDir, apiName);
|
|
123
|
+
if (!fs$1.existsSync(dir)) {
|
|
124
|
+
log(`Creating dir ${dir}`);
|
|
125
|
+
fs$1.mkdirSync(dir, {
|
|
126
|
+
recursive: true,
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
const clientDir = Path.resolve(dir, 'client');
|
|
130
|
+
const specPath = await getSpecPath(srcSpec, dir, log);
|
|
131
|
+
const clientFile = await generateOpenApiModel({
|
|
132
|
+
name: apiName,
|
|
133
|
+
input: specPath,
|
|
134
|
+
outputDir: clientDir,
|
|
135
|
+
isNode: env === 'node',
|
|
136
|
+
responseWrapper,
|
|
137
|
+
}, log);
|
|
138
|
+
if (zodSchemas === false)
|
|
139
|
+
return;
|
|
140
|
+
log('Generating Zod schemas');
|
|
141
|
+
generateSchemas(Path.resolve(dir, clientFile), Path.resolve(dir, './zod.ts'));
|
|
142
|
+
};
|
|
143
|
+
const getSpecPath = async (urlOrPath, dir, log) => {
|
|
144
|
+
if (!urlOrPath.startsWith('http')) {
|
|
145
|
+
if (!fs$1.existsSync(urlOrPath))
|
|
146
|
+
throw new Error(`Spec file ${urlOrPath} does not exists`);
|
|
147
|
+
return urlOrPath;
|
|
148
|
+
}
|
|
149
|
+
const specPath = Path.resolve(dir, `spec.json`);
|
|
150
|
+
log(`Will download the API spec from ${urlOrPath} to ${specPath}`);
|
|
151
|
+
await downloadSpec(specPath, urlOrPath);
|
|
152
|
+
return specPath;
|
|
153
|
+
};
|
|
142
154
|
|
|
143
|
-
|
|
155
|
+
exports.generateApiClient = generateApiClient;
|
package/package.json
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ps-aux/api-client-gen",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.4",
|
|
4
4
|
"main": "dist/index.js",
|
|
5
|
+
"module": "dist/index.esm.js",
|
|
5
6
|
"types": "dist/index.d.ts",
|
|
6
7
|
"bin": {
|
|
7
|
-
"gen-api-client": "dist/
|
|
8
|
+
"gen-api-client": "dist/bin.js"
|
|
8
9
|
},
|
|
9
10
|
"scripts": {
|
|
10
11
|
"build": "rollup -c",
|
|
12
|
+
"dev": "rollup -c --watch",
|
|
11
13
|
"tc": "tsc",
|
|
12
14
|
"eh": "foo"
|
|
13
15
|
},
|
|
@@ -16,10 +18,7 @@
|
|
|
16
18
|
"dependencies": {
|
|
17
19
|
"axios": "^1.5.0",
|
|
18
20
|
"cosmiconfig": "^8.3.6",
|
|
19
|
-
"swagger-typescript-api": "^13.0.3"
|
|
20
|
-
|
|
21
|
-
"devDependencies": {
|
|
22
|
-
"rollup": "^4.1.4",
|
|
23
|
-
"rollup-plugin-typescript2": "^0.36.0"
|
|
21
|
+
"swagger-typescript-api": "^13.0.3",
|
|
22
|
+
"ts-to-zod": "^3.2.0"
|
|
24
23
|
}
|
|
25
24
|
}
|
package/rollup.config.mjs
CHANGED
|
@@ -1,26 +1,40 @@
|
|
|
1
1
|
import typescript from 'rollup-plugin-typescript2'
|
|
2
|
+
import resolve from '@rollup/plugin-node-resolve'
|
|
3
|
+
|
|
4
|
+
const external = ['axios', 'cosmiconfig', 'form-data', 'swagger-typescript-api', 'ts-to-zod']
|
|
2
5
|
|
|
3
6
|
export default [{
|
|
4
|
-
input:
|
|
7
|
+
input:{
|
|
8
|
+
index: 'src/index.ts',
|
|
9
|
+
bin: 'src/go.ts'
|
|
10
|
+
},
|
|
5
11
|
output: [
|
|
6
12
|
{
|
|
7
|
-
file: 'dist/index.js',
|
|
8
|
-
|
|
9
|
-
|
|
13
|
+
// file: 'dist/index.js',
|
|
14
|
+
dir: 'dist',
|
|
15
|
+
entryFileNames: '[name].js',
|
|
16
|
+
format: 'cjs',
|
|
10
17
|
},
|
|
11
18
|
{
|
|
12
|
-
file: 'dist/index.esm.js',
|
|
19
|
+
// file: 'dist/index.esm.js',
|
|
20
|
+
dir: 'dist',
|
|
21
|
+
entryFileNames: '[name].esm.js',
|
|
13
22
|
format: 'es',
|
|
14
23
|
},
|
|
15
24
|
],
|
|
16
|
-
plugins: [typescript()],
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
25
|
+
plugins: [typescript(), resolve()],
|
|
26
|
+
external,
|
|
27
|
+
},
|
|
28
|
+
// {
|
|
29
|
+
// input: 'src/go.ts',
|
|
30
|
+
// output: [
|
|
31
|
+
// {
|
|
32
|
+
// file: 'dist/go.js',
|
|
33
|
+
// format: 'cjs',
|
|
34
|
+
// }],
|
|
35
|
+
//
|
|
36
|
+
// plugins: [typescript(),
|
|
37
|
+
// // resolve(), commonJs(), json()
|
|
38
|
+
// ],
|
|
39
|
+
// }
|
|
40
|
+
]
|
package/src/downloadSpec.ts
CHANGED
|
@@ -1,24 +1,22 @@
|
|
|
1
1
|
import axios from 'axios'
|
|
2
|
-
import fs from 'fs'
|
|
2
|
+
import fs from 'fs/promises'
|
|
3
3
|
|
|
4
4
|
export const downloadSpec = async (path: string, url: string) => {
|
|
5
5
|
const res = await axios({
|
|
6
6
|
url,
|
|
7
7
|
method: 'GET',
|
|
8
|
-
responseType: '
|
|
8
|
+
responseType: 'arraybuffer',
|
|
9
9
|
}).catch(err => {
|
|
10
10
|
throw err.toString()
|
|
11
11
|
})
|
|
12
12
|
|
|
13
|
-
const
|
|
13
|
+
const content = res.data.toString()
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
await res.data.pipe(writer)
|
|
17
|
-
|
|
18
|
-
return new Promise((resolve, reject) => {
|
|
19
|
-
writer.on('finish', resolve)
|
|
20
|
-
writer.on('error', err => {
|
|
21
|
-
reject(err)
|
|
22
|
-
})
|
|
23
|
-
})
|
|
15
|
+
await fs.writeFile(path, format(content))
|
|
24
16
|
}
|
|
17
|
+
|
|
18
|
+
const format = (content: string): string =>
|
|
19
|
+
JSON.stringify(
|
|
20
|
+
JSON.parse(content), null, 4,
|
|
21
|
+
)
|
|
22
|
+
|
|
@@ -2,20 +2,28 @@ import Path from 'path'
|
|
|
2
2
|
import fs from 'fs'
|
|
3
3
|
import { downloadSpec } from './downloadSpec'
|
|
4
4
|
import { generateOpenApiModel } from './generateOpenApiModel'
|
|
5
|
+
import { generateSchemas } from './generateSchemas'
|
|
5
6
|
|
|
6
7
|
export type Params = {
|
|
7
8
|
srcSpec: string, // path or http(s) URL
|
|
8
9
|
dstDir: string
|
|
9
10
|
apiName: string
|
|
10
11
|
env: 'browser' | 'node'
|
|
12
|
+
responseWrapper?: {
|
|
13
|
+
symbol: string
|
|
14
|
+
import?: string
|
|
15
|
+
},
|
|
16
|
+
zodSchemas?: boolean
|
|
11
17
|
}
|
|
12
18
|
|
|
13
|
-
export const
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
+
export const generateApiClient = async ({
|
|
20
|
+
dstDir,
|
|
21
|
+
apiName,
|
|
22
|
+
env,
|
|
23
|
+
srcSpec,
|
|
24
|
+
responseWrapper,
|
|
25
|
+
zodSchemas,
|
|
26
|
+
}: Params, log: (msg: string) => void = console.log): Promise<void> => {
|
|
19
27
|
if (!srcSpec) throw new Error(`Url or path ('srcSpec' not specified`)
|
|
20
28
|
if (!apiName) throw new Error('apiName not specified')
|
|
21
29
|
if (!dstDir) throw new Error('dstDir not specified')
|
|
@@ -32,19 +40,23 @@ export const generateModel = async ({
|
|
|
32
40
|
|
|
33
41
|
const specPath = await getSpecPath(srcSpec, dir, log)
|
|
34
42
|
|
|
35
|
-
await generateOpenApiModel(
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
43
|
+
const clientFile = await generateOpenApiModel(
|
|
44
|
+
{
|
|
45
|
+
name: apiName,
|
|
46
|
+
input: specPath,
|
|
47
|
+
outputDir: clientDir,
|
|
48
|
+
isNode: env === 'node',
|
|
49
|
+
responseWrapper,
|
|
50
|
+
},
|
|
39
51
|
log,
|
|
40
|
-
env === 'node',
|
|
41
52
|
)
|
|
42
53
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
54
|
+
if (zodSchemas === false)
|
|
55
|
+
return
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
log('Generating Zod schemas')
|
|
59
|
+
generateSchemas(Path.resolve(dir, clientFile), Path.resolve(dir, './zod.ts'))
|
|
48
60
|
}
|
|
49
61
|
|
|
50
62
|
const getSpecPath = async (urlOrPath: string, dir: string, log: (str: string) => void): Promise<string> => {
|
|
@@ -2,13 +2,20 @@ import { generateApi } from 'swagger-typescript-api'
|
|
|
2
2
|
import Path from 'path'
|
|
3
3
|
import fs from 'fs/promises'
|
|
4
4
|
|
|
5
|
-
export
|
|
5
|
+
export type GenerateOpenApiModelCmd = {
|
|
6
6
|
name: string,
|
|
7
7
|
input: string,
|
|
8
8
|
outputDir: string,
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
isNode?: boolean
|
|
10
|
+
responseWrapper?: {
|
|
11
|
+
symbol: string
|
|
12
|
+
import?: string
|
|
13
|
+
},
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export const generateOpenApiModel = async ({ input, isNode, responseWrapper, name, outputDir }: GenerateOpenApiModelCmd,
|
|
17
|
+
log: (msg: string) => void,
|
|
18
|
+
): Promise<string> => {
|
|
12
19
|
|
|
13
20
|
log(`Will generate API client name=${name} to ${outputDir}, node=${isNode}`)
|
|
14
21
|
const specialMapping = isNode
|
|
@@ -17,10 +24,11 @@ export const generateOpenApiModel = async (
|
|
|
17
24
|
}
|
|
18
25
|
: {}
|
|
19
26
|
|
|
20
|
-
const
|
|
27
|
+
const fileName = 'index'
|
|
28
|
+
const dstFile = Path.join(outputDir, `${fileName}.ts`)
|
|
21
29
|
|
|
22
30
|
await generateApi({
|
|
23
|
-
name,
|
|
31
|
+
name: 'index',
|
|
24
32
|
output: outputDir,
|
|
25
33
|
input: input,
|
|
26
34
|
// modular: true,
|
|
@@ -32,6 +40,7 @@ export const generateOpenApiModel = async (
|
|
|
32
40
|
// extractRequestBody: true,
|
|
33
41
|
cleanOutput: true,
|
|
34
42
|
moduleNameFirstTag: true,
|
|
43
|
+
apiClassName: capitalize(name) + 'Api',
|
|
35
44
|
// @ts-ignore
|
|
36
45
|
codeGenConstructs: (struct) => ({
|
|
37
46
|
// @ts-ignore
|
|
@@ -40,16 +49,66 @@ export const generateOpenApiModel = async (
|
|
|
40
49
|
// extractRequestParams: true,
|
|
41
50
|
})
|
|
42
51
|
|
|
52
|
+
const addImports: string[] = []
|
|
53
|
+
const replaces: [string, string][] = []
|
|
54
|
+
|
|
43
55
|
if (isNode) {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
content = 'import stream from \'node:stream\'\n' + content
|
|
56
|
+
addImports.push('import stream from \'node:stream\'')
|
|
57
|
+
}
|
|
47
58
|
|
|
48
|
-
|
|
59
|
+
|
|
60
|
+
if (responseWrapper) {
|
|
61
|
+
log(`Will use response wrapper '${JSON.stringify(responseWrapper)}'`)
|
|
62
|
+
if (responseWrapper.import)
|
|
63
|
+
addImports.push(responseWrapper.import)
|
|
64
|
+
|
|
65
|
+
replaces.push(['Promise', responseWrapper.symbol])
|
|
49
66
|
}
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
// Remove unnecessary generics
|
|
70
|
+
replaces.push(['<SecurityDataType extends unknown>', ''])
|
|
71
|
+
replaces.push(['<SecurityDataType>', ''])
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
log(`Will modify the outputs ${JSON.stringify({
|
|
75
|
+
addImports,
|
|
76
|
+
replaces,
|
|
77
|
+
})}`)
|
|
78
|
+
await modifyOutput(dstFile, {
|
|
79
|
+
addImports,
|
|
80
|
+
replaces,
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
return dstFile
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const modifyOutput = async (path: string, cmd: {
|
|
87
|
+
addImports: string[]
|
|
88
|
+
replaces: [string, string][]
|
|
89
|
+
}) => {
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
let content = await fs.readFile(path).then((r) => r.toString())
|
|
93
|
+
|
|
94
|
+
if (cmd.addImports.length) {
|
|
95
|
+
content = cmd.addImports.join('\n') + '\n' + content
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
cmd.replaces.forEach(([from, to]) => {
|
|
99
|
+
content = content.replaceAll(from, to)
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
await fs.writeFile(path, content)
|
|
50
104
|
}
|
|
51
105
|
|
|
52
106
|
const getThisScriptDirname = (): string => {
|
|
53
107
|
// Might be problem in ESM mode
|
|
54
108
|
return __dirname
|
|
55
109
|
}
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
const capitalize = (str: string): string => {
|
|
113
|
+
return str[0].toUpperCase() + str.substring(1)
|
|
114
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import fs from 'fs'
|
|
2
|
+
import {generate} from 'ts-to-zod'
|
|
3
|
+
import Path from 'path'
|
|
4
|
+
|
|
5
|
+
const removeGenericTypes = (code: string): string => {
|
|
6
|
+
const regex = /export (interface|enum|type) [^<]* \{\n(.*\n)*?}/mg
|
|
7
|
+
const matches = code.matchAll(regex)
|
|
8
|
+
const types = Array.from(matches).map(m => m[0])
|
|
9
|
+
|
|
10
|
+
return types.join('\n')
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export const generateSchemas = (inputFile: string, outputFile: string) => {
|
|
14
|
+
const code = fs.readFileSync(inputFile).toString()
|
|
15
|
+
|
|
16
|
+
const {getZodSchemasFile} = generate({
|
|
17
|
+
sourceText: removeGenericTypes(code),
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
const fileName = Path.basename(inputFile)
|
|
21
|
+
|
|
22
|
+
let f = getZodSchemasFile(`./${fileName.replace('.ts', '')}`)
|
|
23
|
+
|
|
24
|
+
// Backend sends nulls not undefined - should be properly set in the OpenAPI TS types generation!
|
|
25
|
+
f = f.replaceAll('.optional()', '.nullable()')
|
|
26
|
+
fs.writeFileSync(outputFile, f)
|
|
27
|
+
|
|
28
|
+
}
|
package/src/go.ts
CHANGED
|
@@ -1,10 +1,7 @@
|
|
|
1
1
|
#!/bin/env node
|
|
2
|
-
import {
|
|
2
|
+
import { generateApiClient } from './generateApiClient'
|
|
3
3
|
import { cosmiconfigSync } from 'cosmiconfig'
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
const arg = process.argv
|
|
7
|
-
|
|
8
5
|
const profile = process.argv[2]
|
|
9
6
|
|
|
10
7
|
if (!profile)
|
|
@@ -23,4 +20,4 @@ const profileConf = conf.config[profile]
|
|
|
23
20
|
if (!profile)
|
|
24
21
|
throw new Error(`Profile ${profile} not present in the configuration`)
|
|
25
22
|
|
|
26
|
-
|
|
23
|
+
generateApiClient(profileConf).catch(console.error)
|
package/src/index.ts
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
// Use this file also as a symlink for http-client.eta file
|
|
2
|
-
|
|
3
1
|
export type ContentType = {}
|
|
4
2
|
|
|
5
3
|
export type RequestParams = {
|
|
@@ -14,14 +12,13 @@ export const ContentType = {
|
|
|
14
12
|
export type Request = {
|
|
15
13
|
path: string
|
|
16
14
|
method: 'GET' | 'POST' | 'PUT' | 'DELETE'
|
|
17
|
-
format?: 'json'
|
|
15
|
+
format?: 'json' | 'document'
|
|
18
16
|
query?: any
|
|
19
17
|
body?: any
|
|
20
18
|
type?: string
|
|
21
19
|
secure?: boolean
|
|
22
|
-
fileDownload?: boolean
|
|
23
20
|
}
|
|
24
21
|
|
|
25
|
-
export type HttpClient
|
|
22
|
+
export type HttpClient = {
|
|
26
23
|
request: <Data, A = any>(req: Request) => Promise<Data>
|
|
27
24
|
}
|
package/dist/foo.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare const foo = "123";
|
package/dist/gen-model.d.ts
DELETED
package/dist/go
DELETED
package/dist/go.js
DELETED
|
@@ -1,151 +0,0 @@
|
|
|
1
|
-
#!/bin/env node
|
|
2
|
-
'use strict';
|
|
3
|
-
|
|
4
|
-
var Path = require('path');
|
|
5
|
-
var fs = require('fs');
|
|
6
|
-
var axios = require('axios');
|
|
7
|
-
var swaggerTypescriptApi = require('swagger-typescript-api');
|
|
8
|
-
var fs$1 = require('fs/promises');
|
|
9
|
-
var cosmiconfig = require('cosmiconfig');
|
|
10
|
-
|
|
11
|
-
/******************************************************************************
|
|
12
|
-
Copyright (c) Microsoft Corporation.
|
|
13
|
-
|
|
14
|
-
Permission to use, copy, modify, and/or distribute this software for any
|
|
15
|
-
purpose with or without fee is hereby granted.
|
|
16
|
-
|
|
17
|
-
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
|
18
|
-
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
|
19
|
-
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
|
20
|
-
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
|
21
|
-
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
|
22
|
-
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
|
23
|
-
PERFORMANCE OF THIS SOFTWARE.
|
|
24
|
-
***************************************************************************** */
|
|
25
|
-
/* global Reflect, Promise, SuppressedError, Symbol */
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
function __awaiter(thisArg, _arguments, P, generator) {
|
|
29
|
-
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
30
|
-
return new (P || (P = Promise))(function (resolve, reject) {
|
|
31
|
-
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
32
|
-
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
33
|
-
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
34
|
-
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
35
|
-
});
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
|
|
39
|
-
var e = new Error(message);
|
|
40
|
-
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
41
|
-
};
|
|
42
|
-
|
|
43
|
-
const downloadSpec = (path, url) => __awaiter(void 0, void 0, void 0, function* () {
|
|
44
|
-
const res = yield axios({
|
|
45
|
-
url,
|
|
46
|
-
method: 'GET',
|
|
47
|
-
responseType: 'stream'
|
|
48
|
-
}).catch(err => {
|
|
49
|
-
throw err.toString();
|
|
50
|
-
});
|
|
51
|
-
const writer = fs.createWriteStream(path);
|
|
52
|
-
// TODO Format to readable JSON
|
|
53
|
-
yield res.data.pipe(writer);
|
|
54
|
-
return new Promise((resolve, reject) => {
|
|
55
|
-
writer.on('finish', resolve);
|
|
56
|
-
writer.on('error', err => {
|
|
57
|
-
reject(err);
|
|
58
|
-
});
|
|
59
|
-
});
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
const generateOpenApiModel = (name, input, outputDir, log, isNode = false) => __awaiter(void 0, void 0, void 0, function* () {
|
|
63
|
-
log(`Will generate API client name=${name} to ${outputDir}, node=${isNode}`);
|
|
64
|
-
const specialMapping = isNode
|
|
65
|
-
? {
|
|
66
|
-
File: '{file: Buffer | stream.Readable, name: string}',
|
|
67
|
-
}
|
|
68
|
-
: {};
|
|
69
|
-
const dstFile = Path.join(outputDir, name + '.ts');
|
|
70
|
-
yield swaggerTypescriptApi.generateApi({
|
|
71
|
-
name,
|
|
72
|
-
output: outputDir,
|
|
73
|
-
input: input,
|
|
74
|
-
// modular: true,
|
|
75
|
-
httpClientType: 'axios',
|
|
76
|
-
templates: Path.resolve(getThisScriptDirname(), '../templates'),
|
|
77
|
-
defaultResponseAsSuccess: true,
|
|
78
|
-
// generateRouteTypes: true,
|
|
79
|
-
singleHttpClient: true,
|
|
80
|
-
// extractRequestBody: true,
|
|
81
|
-
cleanOutput: true,
|
|
82
|
-
moduleNameFirstTag: true,
|
|
83
|
-
// @ts-ignore
|
|
84
|
-
codeGenConstructs: (struct) => ({
|
|
85
|
-
// @ts-ignore
|
|
86
|
-
Keyword: specialMapping,
|
|
87
|
-
}),
|
|
88
|
-
// extractRequestParams: true,
|
|
89
|
-
});
|
|
90
|
-
if (isNode) {
|
|
91
|
-
let content = yield fs$1.readFile(dstFile).then((r) => r.toString());
|
|
92
|
-
// Naive solution
|
|
93
|
-
content = 'import stream from \'node:stream\'\n' + content;
|
|
94
|
-
yield fs$1.writeFile(dstFile, content);
|
|
95
|
-
}
|
|
96
|
-
});
|
|
97
|
-
const getThisScriptDirname = () => {
|
|
98
|
-
// Might be problem in ESM mode
|
|
99
|
-
return __dirname;
|
|
100
|
-
};
|
|
101
|
-
|
|
102
|
-
const generateModel = ({ dstDir, apiName, env, srcSpec, }, log = console.log) => __awaiter(void 0, void 0, void 0, function* () {
|
|
103
|
-
if (!srcSpec)
|
|
104
|
-
throw new Error(`Url or path ('srcSpec' not specified`);
|
|
105
|
-
if (!apiName)
|
|
106
|
-
throw new Error('apiName not specified');
|
|
107
|
-
if (!dstDir)
|
|
108
|
-
throw new Error('dstDir not specified');
|
|
109
|
-
if (!env)
|
|
110
|
-
throw new Error('env not specified');
|
|
111
|
-
const dir = Path.resolve(dstDir, apiName);
|
|
112
|
-
if (!fs.existsSync(dir)) {
|
|
113
|
-
log(`Creating dir ${dir}`);
|
|
114
|
-
fs.mkdirSync(dir, {
|
|
115
|
-
recursive: true,
|
|
116
|
-
});
|
|
117
|
-
}
|
|
118
|
-
const clientDir = Path.resolve(dir, 'client');
|
|
119
|
-
const specPath = yield getSpecPath(srcSpec, dir, log);
|
|
120
|
-
yield generateOpenApiModel(apiName + '-api', specPath, clientDir, log, env === 'node');
|
|
121
|
-
// TODO does not seem to currently generate all schemas
|
|
122
|
-
// generateSchemas(
|
|
123
|
-
// Path.resolve(modelDir, 'data-contracts.ts'),
|
|
124
|
-
// Path.resolve(dir, 'schemas.ts')
|
|
125
|
-
// )
|
|
126
|
-
});
|
|
127
|
-
const getSpecPath = (urlOrPath, dir, log) => __awaiter(void 0, void 0, void 0, function* () {
|
|
128
|
-
if (!urlOrPath.startsWith('http')) {
|
|
129
|
-
if (!fs.existsSync(urlOrPath))
|
|
130
|
-
throw new Error(`Spec file ${urlOrPath} does not exists`);
|
|
131
|
-
return urlOrPath;
|
|
132
|
-
}
|
|
133
|
-
const specPath = Path.resolve(dir, `spec.json`);
|
|
134
|
-
log(`Will download the API spec from ${urlOrPath} to ${specPath}`);
|
|
135
|
-
yield downloadSpec(specPath, urlOrPath);
|
|
136
|
-
return specPath;
|
|
137
|
-
});
|
|
138
|
-
|
|
139
|
-
process.argv;
|
|
140
|
-
const profile = process.argv[2];
|
|
141
|
-
if (!profile)
|
|
142
|
-
throw new Error(`No profile specified`);
|
|
143
|
-
const conf = cosmiconfig.cosmiconfigSync('api-client-gen')
|
|
144
|
-
.search();
|
|
145
|
-
// TODO validate config
|
|
146
|
-
if (!conf)
|
|
147
|
-
throw new Error(`No config provided`);
|
|
148
|
-
const profileConf = conf.config[profile];
|
|
149
|
-
if (!profile)
|
|
150
|
-
throw new Error(`Profile ${profile} not present in the configuration`);
|
|
151
|
-
generateModel(profileConf).catch(console.error);
|