@ygorazambuja/sauron 1.0.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/README.md +570 -0
- package/bin.ts +15 -0
- package/docs/plugins.md +154 -0
- package/package.json +59 -0
- package/src/cli/args.ts +196 -0
- package/src/cli/config.ts +228 -0
- package/src/cli/main.ts +276 -0
- package/src/cli/project.ts +113 -0
- package/src/cli/types.ts +22 -0
- package/src/generators/angular.ts +76 -0
- package/src/generators/fetch.ts +994 -0
- package/src/generators/missing-definitions.ts +776 -0
- package/src/generators/type-coverage.ts +938 -0
- package/src/index.ts +47 -0
- package/src/plugins/builtin/angular.ts +146 -0
- package/src/plugins/builtin/axios.ts +307 -0
- package/src/plugins/builtin/fetch.ts +140 -0
- package/src/plugins/registry.ts +84 -0
- package/src/plugins/runner.ts +174 -0
- package/src/plugins/types.ts +97 -0
- package/src/schemas/swagger.ts +134 -0
- package/src/utils/index.ts +1599 -0
package/src/index.ts
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { main } from "./cli/main";
|
|
2
|
+
|
|
3
|
+
export {
|
|
4
|
+
parseArgs,
|
|
5
|
+
parseCommand,
|
|
6
|
+
showHelp,
|
|
7
|
+
} from "./cli/args";
|
|
8
|
+
|
|
9
|
+
export {
|
|
10
|
+
createGeneratedFileHeader,
|
|
11
|
+
initConfigFile,
|
|
12
|
+
loadSauronConfig,
|
|
13
|
+
mergeOptionsWithConfig,
|
|
14
|
+
} from "./cli/config";
|
|
15
|
+
|
|
16
|
+
export { getOutputPaths, isAngularProject } from "./cli/project";
|
|
17
|
+
export type { CliOptions, SauronConfig } from "./cli/types";
|
|
18
|
+
|
|
19
|
+
export { generateAngularService } from "./generators/angular";
|
|
20
|
+
export {
|
|
21
|
+
createFetchHttpMethods,
|
|
22
|
+
extractMethodParameters,
|
|
23
|
+
extractResponseType,
|
|
24
|
+
generateFetchService,
|
|
25
|
+
generateMethodName,
|
|
26
|
+
} from "./generators/fetch";
|
|
27
|
+
export {
|
|
28
|
+
createMissingSwaggerDefinitionsReport,
|
|
29
|
+
generateMissingSwaggerDefinitionsFile,
|
|
30
|
+
} from "./generators/missing-definitions";
|
|
31
|
+
export {
|
|
32
|
+
createTypeCoverageReport,
|
|
33
|
+
generateTypeCoverageReportFile,
|
|
34
|
+
} from "./generators/type-coverage";
|
|
35
|
+
export {
|
|
36
|
+
BUILTIN_PLUGIN_IDS,
|
|
37
|
+
createDefaultPluginRegistry,
|
|
38
|
+
createPluginRegistry,
|
|
39
|
+
} from "./plugins/registry";
|
|
40
|
+
export { runHttpPlugins } from "./plugins/runner";
|
|
41
|
+
export type { PluginContext, SauronPlugin } from "./plugins/types";
|
|
42
|
+
|
|
43
|
+
export { main };
|
|
44
|
+
|
|
45
|
+
if (import.meta.main) {
|
|
46
|
+
main();
|
|
47
|
+
}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
import { generateAngularService } from "../../generators/angular";
|
|
3
|
+
import {
|
|
4
|
+
createMissingSwaggerDefinitionsReport,
|
|
5
|
+
generateMissingSwaggerDefinitionsFile,
|
|
6
|
+
} from "../../generators/missing-definitions";
|
|
7
|
+
import {
|
|
8
|
+
createTypeCoverageReport,
|
|
9
|
+
generateTypeCoverageReportFile,
|
|
10
|
+
} from "../../generators/type-coverage";
|
|
11
|
+
import { createAngularHttpClientMethods } from "../../utils";
|
|
12
|
+
import type {
|
|
13
|
+
PluginCanRunResult,
|
|
14
|
+
PluginContext,
|
|
15
|
+
PluginGenerateResult,
|
|
16
|
+
PluginOutputPaths,
|
|
17
|
+
SauronPlugin,
|
|
18
|
+
} from "../types";
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Create angular plugin.
|
|
22
|
+
* @returns Create angular plugin output as `SauronPlugin`.
|
|
23
|
+
* @example
|
|
24
|
+
* ```ts
|
|
25
|
+
* const result = createAngularPlugin();
|
|
26
|
+
* // result: SauronPlugin
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
export function createAngularPlugin(): SauronPlugin {
|
|
30
|
+
return {
|
|
31
|
+
id: "angular",
|
|
32
|
+
aliases: ["ng"],
|
|
33
|
+
kind: "http-client",
|
|
34
|
+
canRun,
|
|
35
|
+
resolveOutputs,
|
|
36
|
+
generate,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Can run angular plugin.
|
|
42
|
+
* @param context Input parameter `context`.
|
|
43
|
+
* @returns Can run angular plugin output as `PluginCanRunResult`.
|
|
44
|
+
* @example
|
|
45
|
+
* ```ts
|
|
46
|
+
* const result = canRun({ isAngularProject: true } as PluginContext);
|
|
47
|
+
* // result: PluginCanRunResult
|
|
48
|
+
* ```
|
|
49
|
+
*/
|
|
50
|
+
function canRun(context: PluginContext): PluginCanRunResult {
|
|
51
|
+
if (context.isAngularProject) {
|
|
52
|
+
return { ok: true };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return {
|
|
56
|
+
ok: false,
|
|
57
|
+
reason:
|
|
58
|
+
"⚠️ Angular plugin requested but Angular project not detected. Falling back to fetch plugin.",
|
|
59
|
+
fallbackPluginId: "fetch",
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Resolve angular plugin outputs.
|
|
65
|
+
* @param context Input parameter `context`.
|
|
66
|
+
* @returns Resolve angular plugin outputs output as `PluginOutputPaths`.
|
|
67
|
+
* @example
|
|
68
|
+
* ```ts
|
|
69
|
+
* const result = resolveOutputs({ baseOutputPath: "src/app/sauron" } as PluginContext);
|
|
70
|
+
* // result: PluginOutputPaths
|
|
71
|
+
* ```
|
|
72
|
+
*/
|
|
73
|
+
function resolveOutputs(context: PluginContext): PluginOutputPaths {
|
|
74
|
+
const serviceDirectory = join(context.baseOutputPath, "angular-http-client");
|
|
75
|
+
return {
|
|
76
|
+
servicePath: join(serviceDirectory, "sauron-api.service.ts"),
|
|
77
|
+
reportPath: join(serviceDirectory, "missing-swagger-definitions.json"),
|
|
78
|
+
typeCoverageReportPath: join(serviceDirectory, "type-coverage-report.json"),
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Generate angular plugin files.
|
|
84
|
+
* @param context Input parameter `context`.
|
|
85
|
+
* @returns Generate angular plugin files output as `Promise<PluginGenerateResult>`.
|
|
86
|
+
* @example
|
|
87
|
+
* ```ts
|
|
88
|
+
* const result = await generate({} as PluginContext);
|
|
89
|
+
* // result: PluginGenerateResult
|
|
90
|
+
* ```
|
|
91
|
+
*/
|
|
92
|
+
async function generate(
|
|
93
|
+
context: PluginContext,
|
|
94
|
+
): Promise<PluginGenerateResult> {
|
|
95
|
+
const {
|
|
96
|
+
methods: angularMethods,
|
|
97
|
+
imports: angularImports,
|
|
98
|
+
paramsInterfaces: angularParamsInterfaces,
|
|
99
|
+
} = createAngularHttpClientMethods(
|
|
100
|
+
context.schema,
|
|
101
|
+
context.operationTypes,
|
|
102
|
+
context.typeNameMap,
|
|
103
|
+
);
|
|
104
|
+
const angularService = generateAngularService(
|
|
105
|
+
angularMethods,
|
|
106
|
+
angularImports,
|
|
107
|
+
true,
|
|
108
|
+
angularParamsInterfaces,
|
|
109
|
+
);
|
|
110
|
+
const outputPaths = resolveOutputs(context);
|
|
111
|
+
|
|
112
|
+
const missingDefinitionsReport = createMissingSwaggerDefinitionsReport(
|
|
113
|
+
context.schema,
|
|
114
|
+
context.operationTypes,
|
|
115
|
+
);
|
|
116
|
+
const reportFileContent = generateMissingSwaggerDefinitionsFile(
|
|
117
|
+
missingDefinitionsReport,
|
|
118
|
+
);
|
|
119
|
+
const typeCoverageReport = createTypeCoverageReport(
|
|
120
|
+
context.schema,
|
|
121
|
+
context.operationTypes,
|
|
122
|
+
);
|
|
123
|
+
const typeCoverageFileContent =
|
|
124
|
+
generateTypeCoverageReportFile(typeCoverageReport);
|
|
125
|
+
const files = [
|
|
126
|
+
{
|
|
127
|
+
path: outputPaths.servicePath,
|
|
128
|
+
content: `${context.fileHeader}\n${angularService}`,
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
path: outputPaths.reportPath,
|
|
132
|
+
content: reportFileContent,
|
|
133
|
+
},
|
|
134
|
+
];
|
|
135
|
+
if (outputPaths.typeCoverageReportPath) {
|
|
136
|
+
files.push({
|
|
137
|
+
path: outputPaths.typeCoverageReportPath,
|
|
138
|
+
content: typeCoverageFileContent,
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return {
|
|
143
|
+
files,
|
|
144
|
+
methodCount: angularMethods.length,
|
|
145
|
+
};
|
|
146
|
+
}
|
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
import {
|
|
3
|
+
createMissingSwaggerDefinitionsReport,
|
|
4
|
+
generateMissingSwaggerDefinitionsFile,
|
|
5
|
+
} from "../../generators/missing-definitions";
|
|
6
|
+
import { createFetchHttpMethods } from "../../generators/fetch";
|
|
7
|
+
import {
|
|
8
|
+
createTypeCoverageReport,
|
|
9
|
+
generateTypeCoverageReportFile,
|
|
10
|
+
} from "../../generators/type-coverage";
|
|
11
|
+
import type {
|
|
12
|
+
PluginCanRunResult,
|
|
13
|
+
PluginContext,
|
|
14
|
+
PluginGenerateResult,
|
|
15
|
+
PluginOutputPaths,
|
|
16
|
+
SauronPlugin,
|
|
17
|
+
} from "../types";
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Create axios plugin.
|
|
21
|
+
* @returns Create axios plugin output as `SauronPlugin`.
|
|
22
|
+
* @example
|
|
23
|
+
* ```ts
|
|
24
|
+
* const result = createAxiosPlugin();
|
|
25
|
+
* // result: SauronPlugin
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
export function createAxiosPlugin(): SauronPlugin {
|
|
29
|
+
return {
|
|
30
|
+
id: "axios",
|
|
31
|
+
aliases: ["ax"],
|
|
32
|
+
kind: "http-client",
|
|
33
|
+
canRun,
|
|
34
|
+
resolveOutputs,
|
|
35
|
+
generate,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Can run axios plugin.
|
|
41
|
+
* @param _context Input parameter `_context`.
|
|
42
|
+
* @returns Can run axios plugin output as `PluginCanRunResult`.
|
|
43
|
+
* @example
|
|
44
|
+
* ```ts
|
|
45
|
+
* const result = canRun({} as PluginContext);
|
|
46
|
+
* // result: PluginCanRunResult
|
|
47
|
+
* ```
|
|
48
|
+
*/
|
|
49
|
+
function canRun(_context: PluginContext): PluginCanRunResult {
|
|
50
|
+
return { ok: true };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Resolve axios plugin outputs.
|
|
55
|
+
* @param context Input parameter `context`.
|
|
56
|
+
* @returns Resolve axios plugin outputs output as `PluginOutputPaths`.
|
|
57
|
+
* @example
|
|
58
|
+
* ```ts
|
|
59
|
+
* const result = resolveOutputs({ baseOutputPath: "outputs" } as PluginContext);
|
|
60
|
+
* // result: PluginOutputPaths
|
|
61
|
+
* ```
|
|
62
|
+
*/
|
|
63
|
+
function resolveOutputs(context: PluginContext): PluginOutputPaths {
|
|
64
|
+
const serviceDirectory = join(context.baseOutputPath, "http-client");
|
|
65
|
+
return {
|
|
66
|
+
servicePath: join(serviceDirectory, "sauron-api.axios-client.ts"),
|
|
67
|
+
reportPath: join(
|
|
68
|
+
serviceDirectory,
|
|
69
|
+
"missing-swagger-definitions.axios.json",
|
|
70
|
+
),
|
|
71
|
+
typeCoverageReportPath: join(
|
|
72
|
+
serviceDirectory,
|
|
73
|
+
"type-coverage-report.axios.json",
|
|
74
|
+
),
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Generate axios plugin files.
|
|
80
|
+
* @param context Input parameter `context`.
|
|
81
|
+
* @returns Generate axios plugin files output as `Promise<PluginGenerateResult>`.
|
|
82
|
+
* @example
|
|
83
|
+
* ```ts
|
|
84
|
+
* const result = await generate({} as PluginContext);
|
|
85
|
+
* // result: PluginGenerateResult
|
|
86
|
+
* ```
|
|
87
|
+
*/
|
|
88
|
+
async function generate(
|
|
89
|
+
context: PluginContext,
|
|
90
|
+
): Promise<PluginGenerateResult> {
|
|
91
|
+
const usedTypes = new Set<string>();
|
|
92
|
+
const {
|
|
93
|
+
methods: fetchMethods,
|
|
94
|
+
paramsInterfaces,
|
|
95
|
+
} = createFetchHttpMethods(
|
|
96
|
+
context.schema,
|
|
97
|
+
usedTypes,
|
|
98
|
+
context.operationTypes,
|
|
99
|
+
context.typeNameMap,
|
|
100
|
+
);
|
|
101
|
+
const axiosMethods = fetchMethods.map((method) =>
|
|
102
|
+
convertFetchMethodToAxios(method),
|
|
103
|
+
);
|
|
104
|
+
const axiosService = generateAxiosService(
|
|
105
|
+
axiosMethods,
|
|
106
|
+
usedTypes,
|
|
107
|
+
paramsInterfaces,
|
|
108
|
+
);
|
|
109
|
+
const outputPaths = resolveOutputs(context);
|
|
110
|
+
|
|
111
|
+
const missingDefinitionsReport = createMissingSwaggerDefinitionsReport(
|
|
112
|
+
context.schema,
|
|
113
|
+
context.operationTypes,
|
|
114
|
+
);
|
|
115
|
+
const reportFileContent = generateMissingSwaggerDefinitionsFile(
|
|
116
|
+
missingDefinitionsReport,
|
|
117
|
+
);
|
|
118
|
+
const typeCoverageReport = createTypeCoverageReport(
|
|
119
|
+
context.schema,
|
|
120
|
+
context.operationTypes,
|
|
121
|
+
);
|
|
122
|
+
const typeCoverageFileContent =
|
|
123
|
+
generateTypeCoverageReportFile(typeCoverageReport);
|
|
124
|
+
const files = [
|
|
125
|
+
{
|
|
126
|
+
path: outputPaths.servicePath,
|
|
127
|
+
content: `${context.fileHeader}\n${axiosService}`,
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
path: outputPaths.reportPath,
|
|
131
|
+
content: reportFileContent,
|
|
132
|
+
},
|
|
133
|
+
];
|
|
134
|
+
if (outputPaths.typeCoverageReportPath) {
|
|
135
|
+
files.push({
|
|
136
|
+
path: outputPaths.typeCoverageReportPath,
|
|
137
|
+
content: typeCoverageFileContent,
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return {
|
|
142
|
+
files,
|
|
143
|
+
methodCount: axiosMethods.length,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Convert fetch method to axios adapter method.
|
|
149
|
+
* @param fetchMethod Input parameter `fetchMethod`.
|
|
150
|
+
* @returns Convert fetch method to axios adapter method output as `string`.
|
|
151
|
+
* @example
|
|
152
|
+
* ```ts
|
|
153
|
+
* const result = convertFetchMethodToAxios("const response = await fetch(url, {});");
|
|
154
|
+
* // result: string
|
|
155
|
+
* ```
|
|
156
|
+
*/
|
|
157
|
+
function convertFetchMethodToAxios(fetchMethod: string): string {
|
|
158
|
+
return fetchMethod.replace(
|
|
159
|
+
"const response = await fetch(",
|
|
160
|
+
"const response = await this.fetchWithAxios(",
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Generate axios service.
|
|
166
|
+
* @param methods Input parameter `methods`.
|
|
167
|
+
* @param usedTypes Input parameter `usedTypes`.
|
|
168
|
+
* @param paramsInterfaces Input parameter `paramsInterfaces`.
|
|
169
|
+
* @returns Generate axios service output as `string`.
|
|
170
|
+
* @example
|
|
171
|
+
* ```ts
|
|
172
|
+
* const result = generateAxiosService([], new Set<string>(), []);
|
|
173
|
+
* // result: string
|
|
174
|
+
* ```
|
|
175
|
+
*/
|
|
176
|
+
function generateAxiosService(
|
|
177
|
+
methods: string[],
|
|
178
|
+
usedTypes: Set<string>,
|
|
179
|
+
paramsInterfaces: string[],
|
|
180
|
+
): string {
|
|
181
|
+
const importStatement = buildModelImportStatement(usedTypes);
|
|
182
|
+
const interfacesBlock = buildInterfacesBlock(paramsInterfaces);
|
|
183
|
+
|
|
184
|
+
return `// Generated axios-based HTTP client
|
|
185
|
+
import axios, { type AxiosError, type AxiosInstance } from "axios";
|
|
186
|
+
import qs from "query-string";
|
|
187
|
+
${importStatement}
|
|
188
|
+
${interfacesBlock}
|
|
189
|
+
|
|
190
|
+
type AxiosFetchConfig = {
|
|
191
|
+
method?: string;
|
|
192
|
+
headers?: Record<string, string>;
|
|
193
|
+
body?: string;
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
type AxiosLikeResponse = {
|
|
197
|
+
ok: boolean;
|
|
198
|
+
status: number;
|
|
199
|
+
json(): Promise<unknown>;
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
function createAxiosLikeResponse(status: number, data: unknown, ok: boolean): AxiosLikeResponse {
|
|
203
|
+
return {
|
|
204
|
+
ok,
|
|
205
|
+
status,
|
|
206
|
+
async json() {
|
|
207
|
+
return data;
|
|
208
|
+
},
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
export class SauronAxiosApiClient {
|
|
213
|
+
private baseUrl = "";
|
|
214
|
+
private readonly httpClient: AxiosInstance;
|
|
215
|
+
|
|
216
|
+
constructor(baseUrl?: string, httpClient: AxiosInstance = axios.create()) {
|
|
217
|
+
this.httpClient = httpClient;
|
|
218
|
+
if (baseUrl) {
|
|
219
|
+
this.baseUrl = baseUrl;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
setBaseUrl(baseUrl: string): void {
|
|
224
|
+
this.baseUrl = baseUrl;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
private buildUrl(path: string): string {
|
|
228
|
+
if (/^(https?:)?\\/\\//i.test(path)) {
|
|
229
|
+
return path;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const normalizedBase = this.baseUrl.replace(/\\/+$/, "");
|
|
233
|
+
const normalizedPath = path.startsWith("/") ? path : \`/\${path}\`;
|
|
234
|
+
|
|
235
|
+
if (!normalizedBase) {
|
|
236
|
+
return normalizedPath;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
return \`\${normalizedBase}\${normalizedPath}\`;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
private async fetchWithAxios(
|
|
243
|
+
url: string,
|
|
244
|
+
config: AxiosFetchConfig,
|
|
245
|
+
): Promise<AxiosLikeResponse> {
|
|
246
|
+
try {
|
|
247
|
+
const response = await this.httpClient.request({
|
|
248
|
+
url,
|
|
249
|
+
method: config.method,
|
|
250
|
+
headers: config.headers,
|
|
251
|
+
data: config.body,
|
|
252
|
+
});
|
|
253
|
+
return createAxiosLikeResponse(response.status, response.data, true);
|
|
254
|
+
} catch (error) {
|
|
255
|
+
const axiosError = error as AxiosError;
|
|
256
|
+
const response = axiosError.response;
|
|
257
|
+
if (response) {
|
|
258
|
+
return createAxiosLikeResponse(response.status, response.data, false);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
throw error;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
${methods.join("\n\n")}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
export const sauronAxiosApi = new SauronAxiosApiClient();
|
|
269
|
+
`;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Build model import statement.
|
|
274
|
+
* @param usedTypes Input parameter `usedTypes`.
|
|
275
|
+
* @returns Build model import statement output as `string`.
|
|
276
|
+
* @example
|
|
277
|
+
* ```ts
|
|
278
|
+
* const result = buildModelImportStatement(new Set<string>());
|
|
279
|
+
* // result: string
|
|
280
|
+
* ```
|
|
281
|
+
*/
|
|
282
|
+
function buildModelImportStatement(usedTypes: Set<string>): string {
|
|
283
|
+
if (usedTypes.size === 0) {
|
|
284
|
+
return "";
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
const importList = Array.from(usedTypes).join(", ");
|
|
288
|
+
return `import { ${importList} } from "../models";`;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* Build interfaces block.
|
|
293
|
+
* @param paramsInterfaces Input parameter `paramsInterfaces`.
|
|
294
|
+
* @returns Build interfaces block output as `string`.
|
|
295
|
+
* @example
|
|
296
|
+
* ```ts
|
|
297
|
+
* const result = buildInterfacesBlock([]);
|
|
298
|
+
* // result: string
|
|
299
|
+
* ```
|
|
300
|
+
*/
|
|
301
|
+
function buildInterfacesBlock(paramsInterfaces: string[]): string {
|
|
302
|
+
if (paramsInterfaces.length === 0) {
|
|
303
|
+
return "";
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
return `${paramsInterfaces.join("\n\n")}\n`;
|
|
307
|
+
}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
import {
|
|
3
|
+
createMissingSwaggerDefinitionsReport,
|
|
4
|
+
generateMissingSwaggerDefinitionsFile,
|
|
5
|
+
} from "../../generators/missing-definitions";
|
|
6
|
+
import {
|
|
7
|
+
createTypeCoverageReport,
|
|
8
|
+
generateTypeCoverageReportFile,
|
|
9
|
+
} from "../../generators/type-coverage";
|
|
10
|
+
import {
|
|
11
|
+
createFetchHttpMethods,
|
|
12
|
+
generateFetchService,
|
|
13
|
+
} from "../../generators/fetch";
|
|
14
|
+
import type {
|
|
15
|
+
PluginCanRunResult,
|
|
16
|
+
PluginContext,
|
|
17
|
+
PluginGenerateResult,
|
|
18
|
+
PluginOutputPaths,
|
|
19
|
+
SauronPlugin,
|
|
20
|
+
} from "../types";
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Create fetch plugin.
|
|
24
|
+
* @returns Create fetch plugin output as `SauronPlugin`.
|
|
25
|
+
* @example
|
|
26
|
+
* ```ts
|
|
27
|
+
* const result = createFetchPlugin();
|
|
28
|
+
* // result: SauronPlugin
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
export function createFetchPlugin(): SauronPlugin {
|
|
32
|
+
return {
|
|
33
|
+
id: "fetch",
|
|
34
|
+
aliases: ["http", "http-client"],
|
|
35
|
+
kind: "http-client",
|
|
36
|
+
canRun,
|
|
37
|
+
resolveOutputs,
|
|
38
|
+
generate,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Can run fetch plugin.
|
|
44
|
+
* @param _context Input parameter `_context`.
|
|
45
|
+
* @returns Can run fetch plugin output as `PluginCanRunResult`.
|
|
46
|
+
* @example
|
|
47
|
+
* ```ts
|
|
48
|
+
* const result = canRun({} as PluginContext);
|
|
49
|
+
* // result: PluginCanRunResult
|
|
50
|
+
* ```
|
|
51
|
+
*/
|
|
52
|
+
function canRun(_context: PluginContext): PluginCanRunResult {
|
|
53
|
+
return { ok: true };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Resolve fetch plugin outputs.
|
|
58
|
+
* @param context Input parameter `context`.
|
|
59
|
+
* @returns Resolve fetch plugin outputs output as `PluginOutputPaths`.
|
|
60
|
+
* @example
|
|
61
|
+
* ```ts
|
|
62
|
+
* const result = resolveOutputs({ baseOutputPath: "outputs" } as PluginContext);
|
|
63
|
+
* // result: PluginOutputPaths
|
|
64
|
+
* ```
|
|
65
|
+
*/
|
|
66
|
+
function resolveOutputs(context: PluginContext): PluginOutputPaths {
|
|
67
|
+
const serviceDirectory = join(context.baseOutputPath, "http-client");
|
|
68
|
+
return {
|
|
69
|
+
servicePath: join(serviceDirectory, "sauron-api.client.ts"),
|
|
70
|
+
reportPath: join(serviceDirectory, "missing-swagger-definitions.json"),
|
|
71
|
+
typeCoverageReportPath: join(serviceDirectory, "type-coverage-report.json"),
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Generate fetch plugin files.
|
|
77
|
+
* @param context Input parameter `context`.
|
|
78
|
+
* @returns Generate fetch plugin files output as `Promise<PluginGenerateResult>`.
|
|
79
|
+
* @example
|
|
80
|
+
* ```ts
|
|
81
|
+
* const result = await generate({} as PluginContext);
|
|
82
|
+
* // result: PluginGenerateResult
|
|
83
|
+
* ```
|
|
84
|
+
*/
|
|
85
|
+
async function generate(
|
|
86
|
+
context: PluginContext,
|
|
87
|
+
): Promise<PluginGenerateResult> {
|
|
88
|
+
const usedTypes = new Set<string>();
|
|
89
|
+
const {
|
|
90
|
+
methods: fetchMethods,
|
|
91
|
+
paramsInterfaces: fetchParamsInterfaces,
|
|
92
|
+
} = createFetchHttpMethods(
|
|
93
|
+
context.schema,
|
|
94
|
+
usedTypes,
|
|
95
|
+
context.operationTypes,
|
|
96
|
+
context.typeNameMap,
|
|
97
|
+
);
|
|
98
|
+
const fetchService = generateFetchService(
|
|
99
|
+
fetchMethods,
|
|
100
|
+
context.modelsPath,
|
|
101
|
+
usedTypes,
|
|
102
|
+
fetchParamsInterfaces,
|
|
103
|
+
);
|
|
104
|
+
const outputPaths = resolveOutputs(context);
|
|
105
|
+
|
|
106
|
+
const missingDefinitionsReport = createMissingSwaggerDefinitionsReport(
|
|
107
|
+
context.schema,
|
|
108
|
+
context.operationTypes,
|
|
109
|
+
);
|
|
110
|
+
const reportFileContent = generateMissingSwaggerDefinitionsFile(
|
|
111
|
+
missingDefinitionsReport,
|
|
112
|
+
);
|
|
113
|
+
const typeCoverageReport = createTypeCoverageReport(
|
|
114
|
+
context.schema,
|
|
115
|
+
context.operationTypes,
|
|
116
|
+
);
|
|
117
|
+
const typeCoverageFileContent =
|
|
118
|
+
generateTypeCoverageReportFile(typeCoverageReport);
|
|
119
|
+
const files = [
|
|
120
|
+
{
|
|
121
|
+
path: outputPaths.servicePath,
|
|
122
|
+
content: `${context.fileHeader}\n${fetchService}`,
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
path: outputPaths.reportPath,
|
|
126
|
+
content: reportFileContent,
|
|
127
|
+
},
|
|
128
|
+
];
|
|
129
|
+
if (outputPaths.typeCoverageReportPath) {
|
|
130
|
+
files.push({
|
|
131
|
+
path: outputPaths.typeCoverageReportPath,
|
|
132
|
+
content: typeCoverageFileContent,
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return {
|
|
137
|
+
files,
|
|
138
|
+
methodCount: fetchMethods.length,
|
|
139
|
+
};
|
|
140
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { createAngularPlugin } from "./builtin/angular";
|
|
2
|
+
import { createAxiosPlugin } from "./builtin/axios";
|
|
3
|
+
import { createFetchPlugin } from "./builtin/fetch";
|
|
4
|
+
import type { SauronPlugin } from "./types";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Built-in plugin IDs.
|
|
8
|
+
*/
|
|
9
|
+
export const BUILTIN_PLUGIN_IDS = ["fetch", "angular", "axios"] as const;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Plugin registry.
|
|
13
|
+
*/
|
|
14
|
+
export type PluginRegistry = {
|
|
15
|
+
getAll(): SauronPlugin[];
|
|
16
|
+
resolve(idOrAlias: string): SauronPlugin | undefined;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Create plugin registry.
|
|
21
|
+
* @param plugins Input parameter `plugins`.
|
|
22
|
+
* @returns Create plugin registry output as `PluginRegistry`.
|
|
23
|
+
* @example
|
|
24
|
+
* ```ts
|
|
25
|
+
* const result = createPluginRegistry([]);
|
|
26
|
+
* // result: PluginRegistry
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
export function createPluginRegistry(plugins: SauronPlugin[]): PluginRegistry {
|
|
30
|
+
const allPlugins = [...plugins];
|
|
31
|
+
const idMap = createPluginIdMap(allPlugins);
|
|
32
|
+
|
|
33
|
+
return {
|
|
34
|
+
getAll: () => [...allPlugins],
|
|
35
|
+
resolve: (idOrAlias: string) => {
|
|
36
|
+
const normalized = idOrAlias.trim().toLowerCase();
|
|
37
|
+
if (!normalized) {
|
|
38
|
+
return undefined;
|
|
39
|
+
}
|
|
40
|
+
return idMap.get(normalized);
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Create default plugin registry.
|
|
47
|
+
* @returns Create default plugin registry output as `PluginRegistry`.
|
|
48
|
+
* @example
|
|
49
|
+
* ```ts
|
|
50
|
+
* const result = createDefaultPluginRegistry();
|
|
51
|
+
* // result: PluginRegistry
|
|
52
|
+
* ```
|
|
53
|
+
*/
|
|
54
|
+
export function createDefaultPluginRegistry(): PluginRegistry {
|
|
55
|
+
return createPluginRegistry([
|
|
56
|
+
createFetchPlugin(),
|
|
57
|
+
createAngularPlugin(),
|
|
58
|
+
createAxiosPlugin(),
|
|
59
|
+
]);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Create plugin ID map.
|
|
64
|
+
* @param plugins Input parameter `plugins`.
|
|
65
|
+
* @returns Create plugin ID map output as `Map<string, SauronPlugin>`.
|
|
66
|
+
* @example
|
|
67
|
+
* ```ts
|
|
68
|
+
* const result = createPluginIdMap([]);
|
|
69
|
+
* // result: Map<string, SauronPlugin>
|
|
70
|
+
* ```
|
|
71
|
+
*/
|
|
72
|
+
function createPluginIdMap(plugins: SauronPlugin[]): Map<string, SauronPlugin> {
|
|
73
|
+
const idMap = new Map<string, SauronPlugin>();
|
|
74
|
+
|
|
75
|
+
for (const plugin of plugins) {
|
|
76
|
+
idMap.set(plugin.id.toLowerCase(), plugin);
|
|
77
|
+
const aliases = plugin.aliases ?? [];
|
|
78
|
+
for (const alias of aliases) {
|
|
79
|
+
idMap.set(alias.toLowerCase(), plugin);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return idMap;
|
|
84
|
+
}
|