@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/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
+ }