@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 ADDED
@@ -0,0 +1,570 @@
1
+ # OpenAPI to TypeScript Converter
2
+
3
+ Este projeto converte automaticamente schemas OpenAPI/Swagger JSON em definições TypeScript (interfaces e tipos).
4
+
5
+ ## Funcionalidades
6
+
7
+ - ✅ **Interfaces TypeScript**: Converte objetos OpenAPI em interfaces TypeScript
8
+ - ✅ **Tipos Union**: Converte enums OpenAPI em tipos union TypeScript
9
+ - ✅ **Tipos Primitivos**: Suporte completo a string, number, boolean, integer
10
+ - ✅ **Arrays**: Suporte a arrays com tipagem correta
11
+ - ✅ **Propriedades Nullable**: Converte `nullable: true` para `| null`
12
+ - ✅ **Referências ($ref)**: Resolve referências entre schemas
13
+ - ✅ **Datas**: Converte `format: "date-time"` para tipo `Date`
14
+ - ✅ **Propriedades Obrigatórias**: Todas as propriedades definidas são obrigatórias por padrão
15
+ - ✅ **Plugin system para geradores HTTP**: Plugins built-in `fetch`, `angular` e `axios`
16
+ - ✅ **Seleção explícita de plugin**: `--plugin <id>` (aceita múltiplos)
17
+ - ✅ **Compatibilidade retroativa**: `--http` e `--angular` continuam funcionando como aliases
18
+ - ✅ **Relatório de definições ausentes**: Cada plugin HTTP gera relatório com os pontos que viraram `any`
19
+
20
+ ## Como Usar
21
+
22
+ ### Opção 1: Binário Compilado (Recomendado)
23
+
24
+ Após compilar o projeto, você pode usar o binário executável diretamente:
25
+
26
+ ```bash
27
+ # Compilar o projeto
28
+ bun build --compile ./src/index.ts --outfile sauron
29
+
30
+ # Usar o binário
31
+ ./sauron --input swaggerAfEstoque.json --angular --http
32
+ ```
33
+
34
+ ### Opção 2: Desenvolvimento com Bun
35
+
36
+ ```bash
37
+ # Instalar dependências
38
+ bun install
39
+
40
+ # Criar arquivo de configuração inicial
41
+ bun run cli -- init
42
+
43
+ # Executar diretamente
44
+ bun run src/index.ts
45
+
46
+ # Ou usar o CLI wrapper
47
+ bun run cli --input swaggerAfEstoque.json --angular
48
+ ```
49
+
50
+ ### Executar Testes
51
+
52
+ ```bash
53
+ # Executar todos os testes
54
+ bun test
55
+
56
+ # Executar testes em modo watch
57
+ bun test --watch
58
+
59
+ # Executar com cobertura
60
+ bun test --coverage
61
+ ```
62
+
63
+ O comando irá:
64
+
65
+ 1. Ler o arquivo OpenAPI/Swagger especificado
66
+ 2. Validar o schema OpenAPI
67
+ 3. **Por padrão**: Gerar apenas interfaces TypeScript (models)
68
+ 4. **Com `--http`**: Gerar também métodos HTTP (fetch por padrão)
69
+ 5. **Com `--angular --http`**: Detectar projeto Angular e gerar serviço Angular
70
+ 6. **Com `--plugin <id>`**: Escolher explicitamente o plugin (`fetch`, `angular`, `axios`)
71
+ 7. **Com `--plugin angular` fora de projeto Angular**: fallback automático para `fetch`
72
+ 8. Salvar nos diretórios apropriados (`outputs/` ou `src/app/sauron/`)
73
+ 9. Gerar relatório de definições ausentes ao lado do cliente/serviço HTTP gerado
74
+
75
+ ### Flags Disponíveis
76
+
77
+ - **`init`**: Cria `sauron.config.ts` com configurações iniciais
78
+ - **Sem flags**: Apenas models TypeScript
79
+ - **`--http`**: Models + métodos HTTP com plugin padrão (`fetch`)
80
+ - **`--angular --http`**: Models + serviço Angular (alias compatível)
81
+ - **`--plugin <id>`**: Seleciona plugin HTTP explicitamente (`fetch`, `angular`, `axios`)
82
+ - **`--input arquivo.json`**: Especificar arquivo de entrada
83
+ - **`--output diretorio`**: Diretório de saída customizado
84
+ - **`--config arquivo.ts`**: Caminho para arquivo de configuração (padrão: `sauron.config.ts`)
85
+
86
+ Regras de precedência:
87
+
88
+ - Se `--plugin` for informado, ele tem prioridade sobre `--http` e `--angular`.
89
+ - Se `--plugin` não for informado, `--http` usa `fetch`.
90
+ - Se `--plugin` não for informado, `--http --angular` usa `angular`.
91
+
92
+ ### Arquivo de Configuração (`sauron.config.ts`)
93
+
94
+ Você pode centralizar as opções do CLI em um arquivo:
95
+
96
+ ```ts
97
+ import type { SauronConfig } from "@ygorazambuja/sauron";
98
+
99
+ export default {
100
+ input: "swagger.json",
101
+ // url: "https://api.exemplo.com/openapi.json",
102
+ // plugin: ["fetch"], // fetch | angular | axios
103
+ output: "outputs",
104
+ angular: false,
105
+ http: true,
106
+ } satisfies SauronConfig;
107
+ ```
108
+
109
+ As flags da CLI têm prioridade sobre os valores do arquivo de configuração.
110
+
111
+ ### Uso Programático
112
+
113
+ ```typescript
114
+ import {
115
+ readJsonFile,
116
+ verifySwaggerComposition,
117
+ createModels,
118
+ } from "./src/utils";
119
+
120
+ // 1. Ler arquivo JSON
121
+ const swaggerData = await readJsonFile("swagger.json");
122
+
123
+ // 2. Validar schema
124
+ const validatedSchema = verifySwaggerComposition(swaggerData);
125
+
126
+ // 3. Gerar definições TypeScript
127
+ const typeDefinitions = createModels(validatedSchema);
128
+
129
+ // Resultado: array de strings com definições TypeScript
130
+ console.log(typeDefinitions);
131
+ ```
132
+
133
+ ## Estrutura dos Arquivos
134
+
135
+ ```
136
+ src/
137
+ ├── index.ts # Ponto de entrada principal
138
+ ├── utils/
139
+ │ └── index.ts # Funções utilitárias de conversão
140
+ └── schemas/
141
+ └── swagger.ts # Schema Zod para validação OpenAPI
142
+
143
+ outputs/
144
+ ├── models/
145
+ │ └── index.ts # Arquivo gerado com tipos TypeScript
146
+ └── http-client/ # Quando plugin fetch/axios
147
+ ├── sauron-api.client.ts # Cliente fetch (plugin fetch)
148
+ ├── sauron-api.axios-client.ts # Cliente axios (plugin axios)
149
+ ├── missing-swagger-definitions.json # Relatório (plugin fetch)
150
+ ├── type-coverage-report.json # Cobertura de tipos (plugin fetch)
151
+ ├── missing-swagger-definitions.axios.json # Relatório (plugin axios)
152
+ └── type-coverage-report.axios.json # Cobertura de tipos (plugin axios)
153
+
154
+ src/app/sauron/
155
+ ├── models/
156
+ │ └── index.ts # Arquivo gerado com tipos TypeScript
157
+ └── angular-http-client/ # Quando --angular --http
158
+ ├── sauron-api.service.ts # Serviço Angular
159
+ ├── missing-swagger-definitions.json # Relatório de definições ausentes
160
+ └── type-coverage-report.json # Cobertura de tipos
161
+ ```
162
+
163
+ ## Exemplo de Saída
164
+
165
+ **Schema OpenAPI:**
166
+
167
+ ```json
168
+ {
169
+ "MesValorIndexadorDto": {
170
+ "type": "object",
171
+ "properties": {
172
+ "dominioMesID": { "type": "integer" },
173
+ "valor": { "type": "number" }
174
+ }
175
+ }
176
+ }
177
+ ```
178
+
179
+ **Resultado TypeScript:**
180
+
181
+ ```typescript
182
+ export interface MesValorIndexadorDto {
183
+ dominioMesID: number;
184
+ valor: number;
185
+ }
186
+ ```
187
+
188
+ ## API Reference
189
+
190
+ ### `readJsonFile(filePath: string): Promise<unknown>`
191
+
192
+ Lê e faz parse de um arquivo JSON do sistema de arquivos.
193
+
194
+ **Parâmetros:**
195
+
196
+ - `filePath`: Caminho para o arquivo JSON
197
+
198
+ **Retorna:** Conteúdo JSON parseado
199
+
200
+ ### `verifySwaggerComposition(swaggerData: Record<string, unknown>)`
201
+
202
+ Valida dados OpenAPI/Swagger contra o schema esperado.
203
+
204
+ **Parâmetros:**
205
+
206
+ - `swaggerData`: Dados brutos do OpenAPI JSON
207
+
208
+ **Retorna:** Schema validado e tipado
209
+
210
+ **Lança:** Error se a validação falhar
211
+
212
+ ### `createModels(openApiSchema): string[]`
213
+
214
+ Gera definições TypeScript a partir de schemas OpenAPI.
215
+
216
+ **Parâmetros:**
217
+
218
+ - `openApiSchema`: Schema OpenAPI validado
219
+
220
+ **Retorna:** Array de strings com definições TypeScript
221
+
222
+ ## Regras de Conversão
223
+
224
+ ### Propriedades Obrigatórias vs Opcionais
225
+
226
+ - **Por padrão**: Todas as propriedades definidas no schema são obrigatórias
227
+ - **Se houver lista `required`**: Apenas propriedades na lista são obrigatórias
228
+ - **Nullable**: Propriedades com `nullable: true` recebem `| null`
229
+
230
+ ### Tipos Suportados
231
+
232
+ | OpenAPI | TypeScript |
233
+ | ------------------------------------ | ---------- | --- | --- |
234
+ | `string` | `string` |
235
+ | `integer` | `number` |
236
+ | `number` | `number` |
237
+ | `boolean` | `boolean` |
238
+ | `string` + `format: "date-time"` | `Date` |
239
+ | `array` + `items` | `T[]` |
240
+ | `enum: [1, 2, 3]` | `1 | 2 | 3` |
241
+ | `$ref: "#/components/schemas/Model"` | `Model` |
242
+
243
+ ## Desenvolvimento
244
+
245
+ ### Executar Testes
246
+
247
+ ```bash
248
+ bun run src/index.ts
249
+ ```
250
+
251
+ ### Modificar Schemas
252
+
253
+ Os schemas de validação estão em `src/schemas/swagger.ts`. Eles usam a biblioteca Zod para validação robusta.
254
+
255
+ ### Adicionar Novos Conversores
256
+
257
+ Para adicionar suporte a novos tipos OpenAPI, modifique a função `convertSchemaToTypeScript` em `src/utils/index.ts`.
258
+
259
+ ## Serviço Angular Gerado
260
+
261
+ Além dos tipos TypeScript, o projeto também gera um serviço Angular completo com métodos HTTP Client para todas as rotas da API.
262
+
263
+ ### Características do Serviço Gerado
264
+
265
+ - ✅ **HttpClient injection**: Usa o novo sistema de injeção do Angular
266
+ - ✅ **Métodos tipados**: Cada método tem assinatura correta de parâmetros
267
+ - ✅ **Tipos de resposta**: Retorna `Observable<T>` com tipos específicos (quando definido no schema)
268
+ - ✅ **Observables**: Retorna `Observable<any>` quando tipo não é especificado
269
+ - ✅ **Parâmetros de path**: Suporte a parâmetros na URL (`/users/{id}`)
270
+ - ✅ **Query parameters**: Suporte a parâmetros de query
271
+ - ✅ **Request body**: Suporte a POST/PUT/PATCH com body
272
+ - ✅ **Nomes únicos**: Evita conflitos de nomes entre métodos
273
+ - ✅ **Imports automáticos**: Importa apenas os tipos utilizados
274
+
275
+ ### Exemplo de Serviço Gerado
276
+
277
+ ```typescript
278
+ import { Injectable, inject } from "@angular/core";
279
+ import { HttpClient } from "@angular/common/http";
280
+ import { Observable } from "rxjs";
281
+ import { LaboratorioDtoResultPaginateFilterDto, LaboratorioDto } from "../models";
282
+
283
+ @Injectable({
284
+ providedIn: "root"
285
+ })
286
+ export class SauronApiService {
287
+ private readonly httpClient = inject(HttpClient);
288
+
289
+ // Padrão: Get{Resource}{Modifier}
290
+
291
+ // Retorna Observable<any> quando não há schema definido
292
+ GetApiGenerica(): Observable<any> {
293
+ return this.httpClient.get("/api/ApiGenerica/credorDivida");
294
+ }
295
+
296
+ // Retorna tipo específico quando schema está definido
297
+ GetLaboratorioWithParams(Nome?: any, AlteracaoCadastro?: any, Pagina?: any, TamanhoDaPagina?: any, Ordenacao?: any, TipoOrdenacao?: any): Observable<LaboratorioDtoResultPaginateFilterDto> {
298
+ return this.httpClient.get("/api/Laboratorio/ListarTodos", { params: { Nome, AlteracaoCadastro, Pagina, TamanhoDaPagina, Ordenacao, TipoOrdenacao } });
299
+ }
300
+
301
+ // Retorna objeto único
302
+ GetLaboratorioById(id: any): Observable<LaboratorioDto> {
303
+ return this.httpClient.get(\`/api/Laboratorio/\${id}\`);
304
+ }
305
+
306
+ // Operações CREATE/UPDATE/DELETE
307
+ PostLaboratorioCreate(body: any): Observable<LaboratorioDto> {
308
+ return this.httpClient.post("/api/Laboratorio/Incluir", body);
309
+ }
310
+
311
+ PutLaboratorioCreate(body: any): Observable<any> {
312
+ return this.httpClient.put("/api/Laboratorio/Alterar", body);
313
+ }
314
+
315
+ DeleteLaboratorio(id?: any): Observable<any> {
316
+ return this.httpClient.delete("/api/Laboratorio/Excluir", { params: { id } });
317
+ }
318
+
319
+ // ... mais métodos
320
+ }
321
+ ```
322
+
323
+ ## Tipos de Saída
324
+
325
+ ### Apenas Models (Padrão)
326
+
327
+ ```bash
328
+ sauron swagger.json
329
+ ```
330
+
331
+ Gera apenas interfaces TypeScript em `outputs/models/`.
332
+
333
+ ### Cliente Fetch (Vanilla JS/TS)
334
+
335
+ ```bash
336
+ sauron swagger.json --http
337
+ ```
338
+
339
+ Gera models + cliente fetch-based em `outputs/http-client/sauron-api.client.ts`.
340
+ Também gera `outputs/http-client/missing-swagger-definitions.json`.
341
+
342
+ ```typescript
343
+ // Exemplo de uso
344
+ import { SauronApiClient } from "./outputs/http-client/sauron-api.client";
345
+ import type {
346
+ LaboratorioDto,
347
+ LaboratorioDtoResultPaginateFilterDto,
348
+ } from "./outputs/models";
349
+
350
+ // Criar instância com base URL
351
+ const api = new SauronApiClient("https://api.exemplo.com");
352
+
353
+ // Usar métodos assíncronos com tipos específicos
354
+ try {
355
+ const laboratorios: LaboratorioDtoResultPaginateFilterDto =
356
+ await api.GetLaboratorioWithParams("search", 1, 10);
357
+ console.log("Resultados:", laboratorios);
358
+
359
+ const laboratorio: LaboratorioDto = await api.GetLaboratorioById(123);
360
+ console.log("Laboratório específico:", laboratorio);
361
+ } catch (error) {
362
+ console.error("Erro na API:", error);
363
+ }
364
+ ```
365
+
366
+ Ou usar a instância padrão:
367
+
368
+ ```typescript
369
+ import { sauronApi } from "./outputs/http-client/sauron-api.client";
370
+
371
+ // Configura uma única vez para todas as chamadas
372
+ sauronApi.setBaseUrl("https://api.exemplo.com");
373
+
374
+ const result = await sauronApi.GetLaboratorioWithParams("search", 1, 10);
375
+ ```
376
+
377
+ ### Cliente Axios
378
+
379
+ ```bash
380
+ sauron swagger.json --plugin axios
381
+ ```
382
+
383
+ Gera models + cliente axios em `outputs/http-client/sauron-api.axios-client.ts`.
384
+ Também gera `outputs/http-client/missing-swagger-definitions.axios.json`.
385
+
386
+ ```typescript
387
+ import { SauronAxiosApiClient } from "./outputs/http-client/sauron-api.axios-client";
388
+
389
+ const api = new SauronAxiosApiClient("https://api.exemplo.com");
390
+ const result = await api.GetLaboratorioWithParams("search", 1, 10);
391
+ console.log(result);
392
+ ```
393
+
394
+ ### Serviço Angular
395
+
396
+ ```bash
397
+ sauron swagger.json --angular --http
398
+ ```
399
+
400
+ Gera models + serviço Angular em `src/app/sauron/angular-http-client/sauron-api.service.ts`.
401
+ Também gera `src/app/sauron/angular-http-client/missing-swagger-definitions.json`.
402
+
403
+ ```typescript
404
+ // Exemplo de uso
405
+ import { SauronApiService } from './src/app/sauron/angular-http-client/sauron-api.service';
406
+
407
+ constructor(private api: SauronApiService) {}
408
+
409
+ loadData() {
410
+ this.api.GetLaboratorioWithParams('search', 1, 10)
411
+ .subscribe(result => console.log(result));
412
+ }
413
+ ```
414
+
415
+ ### Como Usar o Serviço Gerado
416
+
417
+ ```typescript
418
+ import { Component, inject } from '@angular/core';
419
+ import { SauronApiService } from './outputs/angular-http-client/sauron-api.service';
420
+ import { LaboratorioDtoResultPaginateFilterDto } from './outputs/models';
421
+
422
+ @Component({...})
423
+ export class MyComponent {
424
+ private readonly api = inject(SauronApiService);
425
+
426
+ // Com tipos específicos - IntelliSense e type safety
427
+ loadLaboratorios() {
428
+ this.api.GetLaboratorioWithParams(
429
+ 'search term',
430
+ undefined, // AlteracaoCadastro
431
+ 1, // Pagina
432
+ 10, // TamanhoDaPagina
433
+ 'Nome', // Ordenacao
434
+ 'asc' // TipoOrdenacao
435
+ ).subscribe((response: LaboratorioDtoResultPaginateFilterDto) => {
436
+ // response.dados é tipado como LaboratorioDto[]
437
+ // response.totalRegistros é tipado como number
438
+ console.log('Total:', response.totalRegistros);
439
+ console.log('Dados:', response.dados);
440
+ });
441
+ }
442
+
443
+ // Buscar laboratório específico
444
+ getLaboratorio(id: number) {
445
+ this.api.GetLaboratorioById(id).subscribe(lab => {
446
+ console.log('Laboratório:', lab);
447
+ });
448
+ }
449
+
450
+ // Criar novo laboratório
451
+ createLaboratorio(labData: any) {
452
+ this.api.PostLaboratorioCreate(labData).subscribe(result => {
453
+ console.log('Criado:', result);
454
+ });
455
+ }
456
+
457
+ // Sem tipos específicos - ainda funciona
458
+ loadGenericData() {
459
+ this.api.GetApiGenerica().subscribe((data: any) => {
460
+ console.log(data);
461
+ });
462
+ }
463
+ }
464
+ ```
465
+
466
+ ## Relatório de Definições Ausentes
467
+
468
+ Quando há geração HTTP, o CLI gera automaticamente um relatório ao lado do cliente/serviço.
469
+
470
+ Arquivos atuais:
471
+
472
+ - `missing-swagger-definitions.json` (plugin `fetch` e `angular`)
473
+ - `missing-swagger-definitions.axios.json` (plugin `axios`)
474
+ - `type-coverage-report.json` (plugin `fetch` e `angular`)
475
+ - `type-coverage-report.axios.json` (plugin `axios`)
476
+
477
+ Esse relatório lista os pontos da especificação Swagger/OpenAPI que resultaram em `any` na
478
+ camada HTTP gerada, para facilitar correções no contrato da API.
479
+
480
+ ## Relatório de Cobertura de Tipos
481
+
482
+ Quando há geração HTTP, o CLI também gera um relatório de cobertura de tipos por operação.
483
+
484
+ Esse relatório mostra:
485
+
486
+ - Cobertura total de tipagem (`typed` vs `untyped`)
487
+ - Cobertura por localização (`path.parameter`, `query.parameter`, `request.body`, `response.body`)
488
+ - Resumo por operação (rota + método)
489
+ - Lista de ocorrências não tipadas em `issues`
490
+
491
+ ### Estrutura do arquivo
492
+
493
+ - `generatedAt`: data/hora de geração (ISO string)
494
+ - `totalIssues`: total de ocorrências encontradas
495
+ - `summary.pathParameters`: total de problemas em parâmetros de path
496
+ - `summary.queryParameters`: total de problemas em parâmetros de query
497
+ - `summary.requestBodies`: total de problemas em corpos de requisição
498
+ - `summary.responseBodies`: total de problemas em corpos de resposta
499
+ - `issues`: lista detalhada de ocorrências
500
+
501
+ ### Exemplo de item em `issues`
502
+
503
+ ```json
504
+ {
505
+ "path": "/api/users/{id}",
506
+ "method": "GET",
507
+ "location": "path.parameter",
508
+ "field": "id",
509
+ "reason": "Path parameter schema is missing or unresolved.",
510
+ "recommendedDefinition": "Define parameter.schema with a primitive type, enum, object, array, or valid $ref."
511
+ }
512
+ ```
513
+
514
+ ## Sistema de Plugins
515
+
516
+ Plugins HTTP built-in:
517
+
518
+ - `fetch`
519
+ - `angular`
520
+ - `axios`
521
+
522
+ Exemplos:
523
+
524
+ ```bash
525
+ sauron swagger.json --plugin fetch
526
+ sauron swagger.json --plugin angular
527
+ sauron swagger.json --plugin axios
528
+ ```
529
+
530
+ Para criar novos plugins, veja `docs/plugins.md`.
531
+
532
+ ## Distribuição
533
+
534
+ ### Binário Executável
535
+
536
+ O projeto pode ser compilado em um único arquivo executável que roda em qualquer sistema com suporte ao Bun runtime:
537
+
538
+ ```bash
539
+ # Compilar
540
+ bun build --compile ./src/index.ts --outfile sauron
541
+
542
+ # O arquivo 'sauron' pode ser distribuído e executado diretamente
543
+ ./sauron --input api.json --angular
544
+ ```
545
+
546
+ ### Publicação no NPM com Bun
547
+
548
+ Para validar e publicar no registro npm usando Bun:
549
+
550
+ ```bash
551
+ bun publish --dry-run
552
+ bun publish --access public
553
+ ```
554
+
555
+ Então outros projetos podem instalar e usar:
556
+
557
+ ```bash
558
+ npm install -g @ygorazambuja/sauron
559
+ sauron --input swagger.json --angular
560
+ ```
561
+
562
+ ## Limitações
563
+
564
+ - Não suporta todos os recursos avançados do OpenAPI 3.x
565
+ - Focado principalmente em schemas de componentes
566
+ - Não gera validações em runtime (apenas tipos TypeScript)
567
+
568
+ ## Licença
569
+
570
+ Este projeto é parte do sistema Sauron para conversão de APIs.
package/bin.ts ADDED
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env bun
2
+
3
+ // Execute the main TypeScript file with all arguments passed through
4
+ import { execSync } from "node:child_process";
5
+ import { dirname, join } from "node:path";
6
+ import { fileURLToPath } from "node:url";
7
+
8
+ const __filename = fileURLToPath(import.meta.url);
9
+ const __dirname = dirname(__filename);
10
+ const scriptPath = join(__dirname, "src", "index.ts");
11
+
12
+ execSync(`bun run "${scriptPath}" ${process.argv.slice(2).join(" ")}`, {
13
+ stdio: "inherit",
14
+ cwd: process.cwd(),
15
+ });