@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/docs/plugins.md
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
# Plugin Development Guide
|
|
2
|
+
|
|
3
|
+
Este guia descreve como criar um novo plugin HTTP no Sauron.
|
|
4
|
+
|
|
5
|
+
## Visao geral
|
|
6
|
+
|
|
7
|
+
O plugin system atual cobre geracao HTTP. O fluxo principal esta em:
|
|
8
|
+
|
|
9
|
+
- `src/plugins/types.ts`
|
|
10
|
+
- `src/plugins/registry.ts`
|
|
11
|
+
- `src/plugins/runner.ts`
|
|
12
|
+
- `src/plugins/builtin/`
|
|
13
|
+
|
|
14
|
+
O `main` gera models no core e delega clientes HTTP ao `runHttpPlugins`.
|
|
15
|
+
|
|
16
|
+
## Contrato do plugin
|
|
17
|
+
|
|
18
|
+
Todo plugin deve implementar `SauronPlugin` (em `src/plugins/types.ts`):
|
|
19
|
+
|
|
20
|
+
- `id: string`
|
|
21
|
+
- `aliases?: string[]`
|
|
22
|
+
- `kind: "http-client"`
|
|
23
|
+
- `canRun(context): { ok: true } | { ok: false; reason: string; fallbackPluginId?: string }`
|
|
24
|
+
- `resolveOutputs(context): { servicePath: string; reportPath: string }`
|
|
25
|
+
- `generate(context): Promise<{ files: Array<{ path: string; content: string }>; methodCount: number }>`
|
|
26
|
+
|
|
27
|
+
`PluginContext` oferece os dados necessarios:
|
|
28
|
+
|
|
29
|
+
- schema OpenAPI validado (`schema`)
|
|
30
|
+
- tipos derivados por operacao (`operationTypes`, `typeNameMap`)
|
|
31
|
+
- opcoes resolvidas (`options`)
|
|
32
|
+
- caminhos de saida (`baseOutputPath`, `modelsPath`)
|
|
33
|
+
- cabecalho padrao (`fileHeader`)
|
|
34
|
+
- status de projeto Angular (`isAngularProject`)
|
|
35
|
+
- escritor formatado (`writeFormattedFile`)
|
|
36
|
+
|
|
37
|
+
## Passo a passo para criar um plugin
|
|
38
|
+
|
|
39
|
+
1. Criar arquivo em `src/plugins/builtin/<nome>.ts`.
|
|
40
|
+
2. Implementar `create<Nome>Plugin(): SauronPlugin`.
|
|
41
|
+
3. Definir `canRun`.
|
|
42
|
+
4. Definir `resolveOutputs` com paths estaveis do plugin.
|
|
43
|
+
5. Definir `generate` para retornar todos os arquivos do plugin.
|
|
44
|
+
6. Incluir relatorio de definicoes ausentes no `generate`.
|
|
45
|
+
7. Registrar plugin em `src/plugins/registry.ts`.
|
|
46
|
+
8. Atualizar ajuda de CLI em `src/cli/args.ts` (opcoes de `--plugin`).
|
|
47
|
+
9. Adicionar/atualizar testes de registry e main.
|
|
48
|
+
|
|
49
|
+
## Template minimo
|
|
50
|
+
|
|
51
|
+
```ts
|
|
52
|
+
import { join } from "node:path";
|
|
53
|
+
import {
|
|
54
|
+
createMissingSwaggerDefinitionsReport,
|
|
55
|
+
generateMissingSwaggerDefinitionsFile,
|
|
56
|
+
} from "../../generators/missing-definitions";
|
|
57
|
+
import type {
|
|
58
|
+
PluginCanRunResult,
|
|
59
|
+
PluginContext,
|
|
60
|
+
PluginGenerateResult,
|
|
61
|
+
PluginOutputPaths,
|
|
62
|
+
SauronPlugin,
|
|
63
|
+
} from "../types";
|
|
64
|
+
|
|
65
|
+
export function createExamplePlugin(): SauronPlugin {
|
|
66
|
+
return {
|
|
67
|
+
id: "example",
|
|
68
|
+
aliases: ["ex"],
|
|
69
|
+
kind: "http-client",
|
|
70
|
+
canRun,
|
|
71
|
+
resolveOutputs,
|
|
72
|
+
generate,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function canRun(_context: PluginContext): PluginCanRunResult {
|
|
77
|
+
return { ok: true };
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function resolveOutputs(context: PluginContext): PluginOutputPaths {
|
|
81
|
+
const serviceDirectory = join(context.baseOutputPath, "http-client");
|
|
82
|
+
return {
|
|
83
|
+
servicePath: join(serviceDirectory, "sauron-api.example-client.ts"),
|
|
84
|
+
reportPath: join(serviceDirectory, "missing-swagger-definitions.example.json"),
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async function generate(context: PluginContext): Promise<PluginGenerateResult> {
|
|
89
|
+
const serviceSource = `export class ExampleClient {}`;
|
|
90
|
+
const outputPaths = resolveOutputs(context);
|
|
91
|
+
|
|
92
|
+
const missingDefinitionsReport = createMissingSwaggerDefinitionsReport(
|
|
93
|
+
context.schema,
|
|
94
|
+
context.operationTypes,
|
|
95
|
+
);
|
|
96
|
+
const reportFileContent = generateMissingSwaggerDefinitionsFile(
|
|
97
|
+
missingDefinitionsReport,
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
return {
|
|
101
|
+
files: [
|
|
102
|
+
{
|
|
103
|
+
path: outputPaths.servicePath,
|
|
104
|
+
content: `${context.fileHeader}\n${serviceSource}`,
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
path: outputPaths.reportPath,
|
|
108
|
+
content: reportFileContent,
|
|
109
|
+
},
|
|
110
|
+
],
|
|
111
|
+
methodCount: 0,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## Registro do plugin
|
|
117
|
+
|
|
118
|
+
Em `src/plugins/registry.ts`:
|
|
119
|
+
|
|
120
|
+
1. Importar `createExamplePlugin`.
|
|
121
|
+
2. Adicionar o id em `BUILTIN_PLUGIN_IDS`.
|
|
122
|
+
3. Incluir instancia no `createDefaultPluginRegistry`.
|
|
123
|
+
|
|
124
|
+
## Fallbacks
|
|
125
|
+
|
|
126
|
+
Use fallback quando o plugin nao puder rodar no contexto atual.
|
|
127
|
+
|
|
128
|
+
Exemplo (plugin angular):
|
|
129
|
+
|
|
130
|
+
- `canRun` retorna `{ ok: false, reason: "...", fallbackPluginId: "fetch" }`
|
|
131
|
+
- runner resolve automaticamente o fallback
|
|
132
|
+
|
|
133
|
+
## Testes recomendados
|
|
134
|
+
|
|
135
|
+
- `src/plugins/registry.spec.ts`
|
|
136
|
+
- `src/plugins/runner.spec.ts`
|
|
137
|
+
- `src/cli/main.spec.ts`
|
|
138
|
+
|
|
139
|
+
Cenarios minimos:
|
|
140
|
+
|
|
141
|
+
- resolve por `id`
|
|
142
|
+
- resolve por `alias`
|
|
143
|
+
- erro para plugin desconhecido
|
|
144
|
+
- geracao de arquivo do novo plugin
|
|
145
|
+
- fallback (se aplicavel)
|
|
146
|
+
- compatibilidade com aliases de CLI (`--http`, `--angular`)
|
|
147
|
+
|
|
148
|
+
## Boas praticas
|
|
149
|
+
|
|
150
|
+
- Reaproveitar geradores existentes sempre que possivel.
|
|
151
|
+
- Manter o plugin focado em uma responsabilidade (geracao HTTP).
|
|
152
|
+
- Gerar caminho de output previsivel e estavel.
|
|
153
|
+
- Incluir o relatorio de definicoes ausentes em todos os plugins HTTP.
|
|
154
|
+
- Evitar quebrar comportamento legado sem migracao clara.
|
package/package.json
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ygorazambuja/sauron",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "OpenAPI to TypeScript/Angular Converter - Generate TypeScript interfaces and Angular HTTP services from Swagger/OpenAPI specs",
|
|
5
|
+
"main": "src/index.ts",
|
|
6
|
+
"files": [
|
|
7
|
+
"bin.ts",
|
|
8
|
+
"src/**/*.ts",
|
|
9
|
+
"!src/**/*.spec.ts",
|
|
10
|
+
"!src/**/*.test.ts",
|
|
11
|
+
"!src/app/sauron/**",
|
|
12
|
+
"README.md",
|
|
13
|
+
"docs/plugins.md"
|
|
14
|
+
],
|
|
15
|
+
"bin": {
|
|
16
|
+
"sauron": "./bin.ts"
|
|
17
|
+
},
|
|
18
|
+
"engines": {
|
|
19
|
+
"bun": ">=1.3.0"
|
|
20
|
+
},
|
|
21
|
+
"publishConfig": {
|
|
22
|
+
"access": "public"
|
|
23
|
+
},
|
|
24
|
+
"scripts": {
|
|
25
|
+
"start": "bun run src/index.ts",
|
|
26
|
+
"dev": "bun run src/index.ts",
|
|
27
|
+
"test": "bun test",
|
|
28
|
+
"test:watch": "bun test --watch",
|
|
29
|
+
"test:coverage": "bun test --coverage",
|
|
30
|
+
"build": "bun build --compile ./src/index.ts --outfile sauron",
|
|
31
|
+
"cli": "bun run ./bin.ts",
|
|
32
|
+
"format": "biome check --fix"
|
|
33
|
+
},
|
|
34
|
+
"keywords": [
|
|
35
|
+
"openapi",
|
|
36
|
+
"swagger",
|
|
37
|
+
"typescript",
|
|
38
|
+
"angular",
|
|
39
|
+
"http-client",
|
|
40
|
+
"api-client",
|
|
41
|
+
"code-generator"
|
|
42
|
+
],
|
|
43
|
+
"author": "Ygor Correa Azambuja",
|
|
44
|
+
"license": "MIT",
|
|
45
|
+
"repository": {
|
|
46
|
+
"type": "git",
|
|
47
|
+
"url": "https://github.com/ygorazambuja/sauron"
|
|
48
|
+
},
|
|
49
|
+
"devDependencies": {
|
|
50
|
+
"@biomejs/biome": "^2.3.15",
|
|
51
|
+
"@types/bun": "^1.3.9",
|
|
52
|
+
"bun-types": "^1.3.9"
|
|
53
|
+
},
|
|
54
|
+
"dependencies": {
|
|
55
|
+
"prettier": "^3.8.1",
|
|
56
|
+
"query-string": "^9.3.1",
|
|
57
|
+
"zod": "^4.3.6"
|
|
58
|
+
}
|
|
59
|
+
}
|
package/src/cli/args.ts
ADDED
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import { parseArgs as parseCliArgs } from "node:util";
|
|
2
|
+
import type { CliOptions } from "./types";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Parse command.
|
|
6
|
+
* @returns Parse command output as `"generate" | "init"`.
|
|
7
|
+
* @example
|
|
8
|
+
* ```ts
|
|
9
|
+
* const result = parseCommand();
|
|
10
|
+
* // result: "generate" | "init"
|
|
11
|
+
* ```
|
|
12
|
+
*/
|
|
13
|
+
export function parseCommand(): "generate" | "init" {
|
|
14
|
+
const { positionals } = parseCliArgs({
|
|
15
|
+
args: Bun.argv,
|
|
16
|
+
options: {},
|
|
17
|
+
strict: false,
|
|
18
|
+
allowPositionals: true,
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
const command = positionals.slice(2)[0];
|
|
22
|
+
return command === "init" ? "init" : "generate";
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Parse args.
|
|
27
|
+
* @returns Parse args output as `CliOptions`.
|
|
28
|
+
* @example
|
|
29
|
+
* ```ts
|
|
30
|
+
* const result = parseArgs();
|
|
31
|
+
* // result: CliOptions
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
34
|
+
export function parseArgs(): CliOptions {
|
|
35
|
+
const { values, positionals } = parseCliArgs({
|
|
36
|
+
args: Bun.argv,
|
|
37
|
+
options: {
|
|
38
|
+
input: {
|
|
39
|
+
type: "string",
|
|
40
|
+
short: "i",
|
|
41
|
+
},
|
|
42
|
+
url: {
|
|
43
|
+
type: "string",
|
|
44
|
+
short: "u",
|
|
45
|
+
},
|
|
46
|
+
angular: {
|
|
47
|
+
type: "boolean",
|
|
48
|
+
short: "a",
|
|
49
|
+
},
|
|
50
|
+
http: {
|
|
51
|
+
type: "boolean",
|
|
52
|
+
short: "t",
|
|
53
|
+
},
|
|
54
|
+
plugin: {
|
|
55
|
+
type: "string",
|
|
56
|
+
short: "p",
|
|
57
|
+
multiple: true,
|
|
58
|
+
},
|
|
59
|
+
output: {
|
|
60
|
+
type: "string",
|
|
61
|
+
short: "o",
|
|
62
|
+
},
|
|
63
|
+
config: {
|
|
64
|
+
type: "string",
|
|
65
|
+
short: "c",
|
|
66
|
+
},
|
|
67
|
+
help: {
|
|
68
|
+
type: "boolean",
|
|
69
|
+
short: "h",
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
strict: true,
|
|
73
|
+
allowPositionals: true,
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
const options: CliOptions = {
|
|
77
|
+
input: "swagger.json",
|
|
78
|
+
angular: false,
|
|
79
|
+
http: false,
|
|
80
|
+
help: false,
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
if (values.input) {
|
|
84
|
+
options.input = values.input;
|
|
85
|
+
}
|
|
86
|
+
if (values.url) {
|
|
87
|
+
options.url = values.url;
|
|
88
|
+
}
|
|
89
|
+
if (values.angular) {
|
|
90
|
+
options.angular = values.angular;
|
|
91
|
+
}
|
|
92
|
+
if (values.http) {
|
|
93
|
+
options.http = values.http;
|
|
94
|
+
}
|
|
95
|
+
if (values.plugin) {
|
|
96
|
+
options.plugin = normalizePluginValues(values.plugin);
|
|
97
|
+
}
|
|
98
|
+
if (values.output) {
|
|
99
|
+
options.output = values.output;
|
|
100
|
+
}
|
|
101
|
+
if (values.config) {
|
|
102
|
+
options.config = values.config;
|
|
103
|
+
}
|
|
104
|
+
if (values.help) {
|
|
105
|
+
options.help = values.help;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
for (const positional of positionals.slice(2)) {
|
|
109
|
+
if (positional === "init") {
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
if (positional.endsWith(".json")) {
|
|
113
|
+
options.input = positional;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return options;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Show help.
|
|
122
|
+
* @example
|
|
123
|
+
* ```ts
|
|
124
|
+
* showHelp();
|
|
125
|
+
* ```
|
|
126
|
+
*/
|
|
127
|
+
export function showHelp(): void {
|
|
128
|
+
console.log(`
|
|
129
|
+
Sauron - OpenAPI to TypeScript/Angular Converter
|
|
130
|
+
|
|
131
|
+
USAGE:
|
|
132
|
+
sauron [COMMAND] [OPTIONS] [INPUT_FILE]
|
|
133
|
+
|
|
134
|
+
OPTIONS:
|
|
135
|
+
-i, --input <file> Input OpenAPI/Swagger JSON file (default: swagger.json)
|
|
136
|
+
-u, --url <url> Download OpenAPI/Swagger JSON from URL
|
|
137
|
+
-a, --angular Generate Angular service in src/app/sauron (requires Angular project)
|
|
138
|
+
-t, --http Generate HTTP client/service methods
|
|
139
|
+
-p, --plugin <id> HTTP plugin to run (repeatable: fetch, angular, axios)
|
|
140
|
+
-o, --output <dir> Output directory (default: outputs or src/app/sauron)
|
|
141
|
+
-c, --config <file> Config file path (default: sauron.config.ts)
|
|
142
|
+
-h, --help Show this help message
|
|
143
|
+
|
|
144
|
+
COMMANDS:
|
|
145
|
+
init Create sauron.config.ts with default settings
|
|
146
|
+
|
|
147
|
+
EXAMPLES:
|
|
148
|
+
sauron init
|
|
149
|
+
sauron --config ./sauron.config.ts
|
|
150
|
+
sauron swagger.json
|
|
151
|
+
sauron --input swaggerAfEstoque.json --angular --http
|
|
152
|
+
sauron --url https://api.example.com/swagger.json --http
|
|
153
|
+
sauron --http -i api.json -o ./generated
|
|
154
|
+
sauron --plugin fetch -i api.json
|
|
155
|
+
sauron --plugin axios -i api.json
|
|
156
|
+
sauron --plugin angular --plugin fetch -i api.json
|
|
157
|
+
|
|
158
|
+
When --angular flag is used, the tool will:
|
|
159
|
+
1. Detect if current directory is an Angular project
|
|
160
|
+
2. Generate models in src/app/sauron/models/
|
|
161
|
+
3. Generate Angular service in src/app/sauron/sauron-api.service.ts
|
|
162
|
+
|
|
163
|
+
When --http flag is used without --angular:
|
|
164
|
+
1. Generate fetch-based HTTP methods in outputs/http-client/
|
|
165
|
+
2. Generate models in outputs/models/
|
|
166
|
+
|
|
167
|
+
Without flags, generates only TypeScript models.
|
|
168
|
+
`);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Normalize plugin values.
|
|
173
|
+
* @param pluginValues Input parameter `pluginValues`.
|
|
174
|
+
* @returns Normalize plugin values output as `string[]`.
|
|
175
|
+
* @example
|
|
176
|
+
* ```ts
|
|
177
|
+
* const result = normalizePluginValues(["fetch", "angular"]);
|
|
178
|
+
* // result: string[]
|
|
179
|
+
* ```
|
|
180
|
+
*/
|
|
181
|
+
function normalizePluginValues(
|
|
182
|
+
pluginValues: string | string[],
|
|
183
|
+
): string[] {
|
|
184
|
+
if (Array.isArray(pluginValues)) {
|
|
185
|
+
return pluginValues
|
|
186
|
+
.map((plugin) => plugin.trim())
|
|
187
|
+
.filter((plugin) => plugin.length > 0);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const normalizedPlugin = pluginValues.trim();
|
|
191
|
+
if (!normalizedPlugin) {
|
|
192
|
+
return [];
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
return [normalizedPlugin];
|
|
196
|
+
}
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
|
+
import { pathToFileURL } from "node:url";
|
|
4
|
+
import type { z } from "zod";
|
|
5
|
+
import type { SwaggerOrOpenAPISchema } from "../schemas/swagger";
|
|
6
|
+
import { isAngularProject } from "./project";
|
|
7
|
+
import {
|
|
8
|
+
type CliOptions,
|
|
9
|
+
DEFAULT_CONFIG_FILE,
|
|
10
|
+
DEFAULT_SAURON_VERSION,
|
|
11
|
+
type SauronConfig,
|
|
12
|
+
} from "./types";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Format generated file.
|
|
16
|
+
* @param content Input parameter `content`.
|
|
17
|
+
* @param filePath Input parameter `filePath`.
|
|
18
|
+
* @returns Format generated file output as `Promise<string>`.
|
|
19
|
+
* @example
|
|
20
|
+
* ```ts
|
|
21
|
+
* const result = await formatGeneratedFile("value", "value");
|
|
22
|
+
* // result: string
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
export async function formatGeneratedFile(
|
|
26
|
+
content: string,
|
|
27
|
+
filePath: string,
|
|
28
|
+
): Promise<string> {
|
|
29
|
+
try {
|
|
30
|
+
const prettier = await import("prettier");
|
|
31
|
+
return await prettier.format(content, { filepath: filePath });
|
|
32
|
+
} catch (error) {
|
|
33
|
+
console.warn(
|
|
34
|
+
`⚠️ Could not format ${filePath} with Prettier. Writing unformatted output.`,
|
|
35
|
+
error,
|
|
36
|
+
);
|
|
37
|
+
return content;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Get sauron version.
|
|
43
|
+
* @returns Get sauron version output as `string`.
|
|
44
|
+
* @example
|
|
45
|
+
* ```ts
|
|
46
|
+
* const result = getSauronVersion();
|
|
47
|
+
* // result: string
|
|
48
|
+
* ```
|
|
49
|
+
*/
|
|
50
|
+
function getSauronVersion(): string {
|
|
51
|
+
try {
|
|
52
|
+
const packageJsonPath = resolve(
|
|
53
|
+
import.meta.dir,
|
|
54
|
+
"..",
|
|
55
|
+
"..",
|
|
56
|
+
"package.json",
|
|
57
|
+
);
|
|
58
|
+
const packageJsonContent = readFileSync(packageJsonPath, "utf-8");
|
|
59
|
+
const packageJson = JSON.parse(packageJsonContent) as { version?: unknown };
|
|
60
|
+
if (
|
|
61
|
+
typeof packageJson.version === "string" &&
|
|
62
|
+
packageJson.version.length > 0
|
|
63
|
+
) {
|
|
64
|
+
return packageJson.version;
|
|
65
|
+
}
|
|
66
|
+
} catch {
|
|
67
|
+
// Fallback to default version when package metadata cannot be read.
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return DEFAULT_SAURON_VERSION;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Create generated file header.
|
|
75
|
+
* @param schema Input parameter `schema`.
|
|
76
|
+
* @param generatedAt Input parameter `generatedAt`.
|
|
77
|
+
* @returns Create generated file header output as `string`.
|
|
78
|
+
* @example
|
|
79
|
+
* ```ts
|
|
80
|
+
* const result = createGeneratedFileHeader({}, {});
|
|
81
|
+
* // result: string
|
|
82
|
+
* ```
|
|
83
|
+
*/
|
|
84
|
+
export function createGeneratedFileHeader(
|
|
85
|
+
schema: z.infer<typeof SwaggerOrOpenAPISchema>,
|
|
86
|
+
generatedAt = new Date().toISOString(),
|
|
87
|
+
): string {
|
|
88
|
+
return `/**
|
|
89
|
+
* Gerado por Sauron v${getSauronVersion()}
|
|
90
|
+
* Timestamp: ${generatedAt}
|
|
91
|
+
* Nao edite manualmente.
|
|
92
|
+
* ${schema.info.title}
|
|
93
|
+
* OpenAPI spec version: ${schema.info.version}
|
|
94
|
+
*/
|
|
95
|
+
`;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Init config file.
|
|
100
|
+
* @param configFilePath Input parameter `configFilePath`.
|
|
101
|
+
* @returns Init config file output as `Promise<void>`.
|
|
102
|
+
* @example
|
|
103
|
+
* ```ts
|
|
104
|
+
* const result = await initConfigFile({});
|
|
105
|
+
* // result: void
|
|
106
|
+
* ```
|
|
107
|
+
*/
|
|
108
|
+
export async function initConfigFile(
|
|
109
|
+
configFilePath = DEFAULT_CONFIG_FILE,
|
|
110
|
+
): Promise<void> {
|
|
111
|
+
const resolvedConfigPath = resolve(configFilePath);
|
|
112
|
+
if (existsSync(resolvedConfigPath)) {
|
|
113
|
+
console.warn(`⚠️ Config file already exists: ${configFilePath}`);
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
const angularProjectDetected = isAngularProject();
|
|
117
|
+
const defaultOutput = angularProjectDetected ? "src/app/sauron" : "outputs";
|
|
118
|
+
|
|
119
|
+
const template = `import type { SauronConfig } from "@ygorazambuja/sauron";
|
|
120
|
+
|
|
121
|
+
export default {
|
|
122
|
+
// Use either "input" or "url". If both are set, "url" takes precedence.
|
|
123
|
+
input: "swagger.json",
|
|
124
|
+
// url: "https://example.com/openapi.json",
|
|
125
|
+
// plugin: ["fetch"],
|
|
126
|
+
output: "${defaultOutput}",
|
|
127
|
+
angular: ${angularProjectDetected},
|
|
128
|
+
http: true,
|
|
129
|
+
} satisfies SauronConfig;
|
|
130
|
+
`;
|
|
131
|
+
|
|
132
|
+
const formattedTemplate = await formatGeneratedFile(
|
|
133
|
+
template,
|
|
134
|
+
resolvedConfigPath,
|
|
135
|
+
);
|
|
136
|
+
writeFileSync(resolvedConfigPath, formattedTemplate);
|
|
137
|
+
console.log(`✅ Created config file: ${configFilePath}`);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Load sauron config.
|
|
142
|
+
* @param configFilePath Input parameter `configFilePath`.
|
|
143
|
+
* @returns Load sauron config output as `Promise<SauronConfig | null>`.
|
|
144
|
+
* @example
|
|
145
|
+
* ```ts
|
|
146
|
+
* const result = await loadSauronConfig({});
|
|
147
|
+
* // result: SauronConfig | null
|
|
148
|
+
* ```
|
|
149
|
+
*/
|
|
150
|
+
export async function loadSauronConfig(
|
|
151
|
+
configFilePath = DEFAULT_CONFIG_FILE,
|
|
152
|
+
): Promise<SauronConfig | null> {
|
|
153
|
+
const resolvedConfigPath = resolve(configFilePath);
|
|
154
|
+
if (!existsSync(resolvedConfigPath)) {
|
|
155
|
+
return null;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const configModule = await import(
|
|
159
|
+
`${pathToFileURL(resolvedConfigPath).href}?t=${Date.now()}`
|
|
160
|
+
);
|
|
161
|
+
const loadedConfig = configModule.default;
|
|
162
|
+
|
|
163
|
+
if (!loadedConfig || typeof loadedConfig !== "object") {
|
|
164
|
+
throw new Error(
|
|
165
|
+
`Invalid config file format in ${configFilePath}. Expected a default exported object.`,
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return loadedConfig as SauronConfig;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Merge options with config.
|
|
174
|
+
* @param options Input parameter `options`.
|
|
175
|
+
* @param config Input parameter `config`.
|
|
176
|
+
* @returns Merge options with config output as `CliOptions`.
|
|
177
|
+
* @example
|
|
178
|
+
* ```ts
|
|
179
|
+
* const result = mergeOptionsWithConfig({}, {});
|
|
180
|
+
* // result: CliOptions
|
|
181
|
+
* ```
|
|
182
|
+
*/
|
|
183
|
+
export function mergeOptionsWithConfig(
|
|
184
|
+
options: CliOptions,
|
|
185
|
+
config: SauronConfig,
|
|
186
|
+
): CliOptions {
|
|
187
|
+
const mergedPlugins = resolveMergedPlugins(options.plugin, config.plugin);
|
|
188
|
+
|
|
189
|
+
return {
|
|
190
|
+
input:
|
|
191
|
+
options.input !== "swagger.json"
|
|
192
|
+
? options.input
|
|
193
|
+
: (config.input ?? "swagger.json"),
|
|
194
|
+
url: options.url ?? config.url,
|
|
195
|
+
angular: options.angular || !!config.angular,
|
|
196
|
+
http: options.http || !!config.http,
|
|
197
|
+
plugin: mergedPlugins,
|
|
198
|
+
output: options.output ?? config.output,
|
|
199
|
+
config: options.config,
|
|
200
|
+
help: options.help,
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Resolve merged plugins.
|
|
206
|
+
* @param cliPlugins Input parameter `cliPlugins`.
|
|
207
|
+
* @param configPlugins Input parameter `configPlugins`.
|
|
208
|
+
* @returns Resolve merged plugins output as `string[] | undefined`.
|
|
209
|
+
* @example
|
|
210
|
+
* ```ts
|
|
211
|
+
* const result = resolveMergedPlugins(["fetch"], ["angular"]);
|
|
212
|
+
* // result: string[] | undefined
|
|
213
|
+
* ```
|
|
214
|
+
*/
|
|
215
|
+
function resolveMergedPlugins(
|
|
216
|
+
cliPlugins?: string[],
|
|
217
|
+
configPlugins?: string[],
|
|
218
|
+
): string[] | undefined {
|
|
219
|
+
if (Array.isArray(cliPlugins) && cliPlugins.length > 0) {
|
|
220
|
+
return [...cliPlugins];
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
if (Array.isArray(configPlugins) && configPlugins.length > 0) {
|
|
224
|
+
return [...configPlugins];
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
return undefined;
|
|
228
|
+
}
|