@devlearning/swagger-generator 1.1.9 โ†’ 1.1.10

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-OLD.md ADDED
@@ -0,0 +1,210 @@
1
+ # Swagger Generator for Angular, Next.js and Flutter
2
+
3
+ This tool automates the generation of API clients for Angular, Next.js and Flutter using a Swagger (OpenAPI) specification. It creates TypeScript models and service classes, making API integration seamless and type-safe.
4
+
5
+ ## Features
6
+
7
+ - ๐Ÿš€ **Automatic Model Generation** โ€“ Converts Swagger schemas into TypeScript interfaces or classes.
8
+ - ๐Ÿ”ฅ **API Client Generation** โ€“ Creates service methods for making API calls with authentication, headers, and request parameters.
9
+ - ๐ŸŽฏ **Framework-Specific Output**:
10
+ - **Angular** โ€“ Generates injectable services using `HttpClient`.
11
+ - **Next.js** โ€“ Provides fetch-based or Axios-based API functions, optimized for both server-side and client-side usage.
12
+ - **Flutter** โ€“ Provides Flutter with Dio API functions, optimized for both server-side and client-side usage.
13
+ - โœ… **Strong Typing** โ€“ Ensures type safety for API requests and responses.
14
+ - โšก **Customization** โ€“ Configurable options for method naming, error handling, and request structure.
15
+ - ๐Ÿ”„ **Auto-Sync with Backend** โ€“ Keeps API clients up-to-date with backend changes.
16
+
17
+ ## Installation
18
+
19
+ ```sh
20
+ npm i @devlearning/swagger-generator --save-dev
21
+ ```
22
+
23
+
24
+ add npm script:
25
+
26
+ "swgen": "swgen http://localhost:7550/swagger/ApiGateway/swagger.json src/app/core/autogenerated angular moment"
27
+
28
+ params:
29
+ - url of swagger.json: not https
30
+ - output path for autogenerated files: src/app/core/autogenerated
31
+ - target framework/library: angular/next
32
+ - type of date management:
33
+ - angular only support momentjs, sorry :(
34
+ - nextjs only support date-fns
35
+ - dart native date management
36
+
37
+ create output path like this: src/app/core/autogenerated
38
+
39
+ ## Angular configuration
40
+
41
+ create ApiService like this
42
+ ```ts
43
+ import { HttpClient, HttpErrorResponse } from '@angular/common/http';
44
+ import { Injectable } from '@angular/core';
45
+ import { ApiAutogeneratedService } from '../autogenerated/api.autogenerated';
46
+ import { environment } from 'src/environments/environment';
47
+ import { ResponseError } from '../models/response-error';
48
+ import { throwError } from 'rxjs';
49
+ import * as moment from 'moment-timezone';
50
+
51
+ @Injectable({
52
+ providedIn: 'root',
53
+ })
54
+ export class ApiService extends ApiAutogeneratedService {
55
+
56
+ private _dateTimeFormat = /^((19|20)[0-9][0-9])[-](0[1-9]|1[012])[-](0[1-9]|[12][0-9]|3[01])[T]([01]?[0-9]|[2][0-3])[:]([0-5][0-9])[:]([0-5][0-9])[.]([0-9][0-9][0-9])([+|-]([01][0-9]|[2][0-3])[:]([0-5][0-9])){0,1}$/;
57
+ private _timeFormat = /^([01]?[0-9]|[2][0-3])[:]([0-5][0-9])[:]([0-5][0-9])[.]([0-9][0-9][0-9])$/;
58
+ private _ianaName: string = Intl.DateTimeFormat().resolvedOptions().timeZone;
59
+ private _dateTimeFormatString = "YYYY-MM-DDTHH:mm:ss.SSSZ";
60
+ private _timeFormatString = "HH:mm:ss.SSS";
61
+
62
+ public get ianaName() { return this._ianaName; }
63
+
64
+ constructor(
65
+ public override _http: HttpClient,
66
+ ) {
67
+ super(_http, environment.BASE_URL);
68
+ }
69
+
70
+ protected override _momentToString(moment: moment.Moment) {
71
+ return (<moment.Moment>moment).format(this._dateTimeFormatString);
72
+ }
73
+
74
+ protected override _handleRequest<T>(request: T) {
75
+ if (request === null || request === undefined) {
76
+ return request;
77
+ }
78
+ if (typeof request !== 'object') {
79
+ return request;
80
+ }
81
+
82
+ var clonedRequest = { ...request };
83
+
84
+ for (const key of Object.keys(clonedRequest)) {
85
+ const value = (<any>clonedRequest)[key];
86
+ if (moment.isMoment(value)) {
87
+ (<any>clonedRequest)[key] = this._momentToString(value);
88
+ } else if (typeof value === 'object') {
89
+ this._handleRequest(value);
90
+ }
91
+ }
92
+
93
+ return clonedRequest;
94
+ }
95
+
96
+ protected override _handleMultipart<T>(request: T): FormData {
97
+ const formData = new FormData();
98
+ if (request === null || request === undefined) {
99
+ return formData;
100
+ }
101
+
102
+ for (const key of Object.keys(request)) {
103
+ const value = (<any>request)[key];
104
+ if (value instanceof File) {
105
+ formData.append(key, value, value.name);
106
+ } else {
107
+ formData.append(key, value.toString());
108
+ }
109
+ }
110
+
111
+ return formData;
112
+ }
113
+
114
+ public override _handleResponse<T>(response: T) {
115
+ if (response === null || response === undefined) {
116
+ return response;
117
+ }
118
+ if (typeof response !== 'object') {
119
+ return response;
120
+ }
121
+
122
+ for (const key of Object.keys(response)) {
123
+ const value = (<any>response)[key];
124
+ if (this._isDateString(value)) {
125
+ (<any>response)[key] = moment.tz(value, this._dateTimeFormatString, this._ianaName);
126
+ } else if (this._isTimeString(value)) {
127
+ (<any>response)[key] = moment.tz(value, this._timeFormatString, this._ianaName);
128
+ } else if (typeof value === 'object') {
129
+ this._handleResponse(value);
130
+ }
131
+ }
132
+
133
+ return response;
134
+ }
135
+
136
+ protected override _handleError(error: any, obs: any) {
137
+ let responseError = new ResponseError();
138
+ if (error.error instanceof Error) {
139
+ console.error('Api - an error occurred:', error.error.message);
140
+ responseError.message = error.error.message;
141
+ } else if (error instanceof HttpErrorResponse) {
142
+ responseError = ResponseError.CreateFromHttpErrorResponse(error);
143
+ }
144
+
145
+ console.error(`Api - error code ${error.status} message ${responseError.message} ${responseError.stacktrace}`);
146
+
147
+ return throwError(() => responseError);
148
+ }
149
+
150
+ private _isDateString(value: string) {
151
+ if (value === null || value === undefined) {
152
+ return false;
153
+ }
154
+ if (typeof value === 'string') {
155
+ return this._dateTimeFormat.test(value);
156
+ }
157
+ return false;
158
+ }
159
+
160
+ private _isTimeString(value: string) {
161
+ if (value === null || value === undefined) {
162
+ return false;
163
+ }
164
+ if (typeof value === 'string') {
165
+ return this._timeFormat.test(value);
166
+ }
167
+ return false;
168
+ }
169
+ }
170
+ ```
171
+
172
+
173
+ ```ts
174
+ import { HttpErrorResponse } from '@angular/common/http';
175
+
176
+ export class ResponseError {
177
+ public url: string | null;
178
+ public status: number | null;
179
+ public message: string | null;
180
+ public stacktrace: string | null;
181
+
182
+ constructor() {
183
+ this.url = null;
184
+ this.status = null;
185
+ this.message = null;
186
+ this.stacktrace = null;
187
+ }
188
+
189
+ static CreateFromHttpErrorResponse(httpErrorResponse: HttpErrorResponse) {
190
+ let responseError = new ResponseError();
191
+
192
+ if (httpErrorResponse.error != null && !(httpErrorResponse.error instanceof ProgressEvent)) {
193
+ if (httpErrorResponse.error.hasOwnProperty('message')) {
194
+ responseError.message = httpErrorResponse.error.message;
195
+ }
196
+
197
+ if (httpErrorResponse.error.hasOwnProperty('stacktrace')) {
198
+ responseError.stacktrace = httpErrorResponse.error.stacktrace;
199
+ }
200
+ } else {
201
+ responseError.message = httpErrorResponse.message;
202
+ }
203
+
204
+ responseError.status = httpErrorResponse.status;
205
+ responseError.url = httpErrorResponse.url;
206
+
207
+ return responseError;
208
+ }
209
+ }
210
+ ```
package/README.md CHANGED
@@ -1,210 +1,223 @@
1
- # Swagger Generator for Angular, Next.js and Flutter
1
+ # Swagger API Generator
2
2
 
3
- This tool automates the generation of API clients for Angular, Next.js and Flutter using a Swagger (OpenAPI) specification. It creates TypeScript models and service classes, making API integration seamless and type-safe.
3
+ Generatore automatico di client API e modelli per **Angular**, **Next.js** e **Flutter/Dart** da specifiche Swagger/OpenAPI.
4
4
 
5
- ## Features
5
+ [![npm version](https://img.shields.io/npm/v/@devlearning/swagger-generator.svg)](https://www.npmjs.com/package/@devlearning/swagger-generator)
6
+ [![License: ISC](https://img.shields.io/badge/License-ISC-blue.svg)](https://opensource.org/licenses/ISC)
6
7
 
7
- - ๐Ÿš€ **Automatic Model Generation** โ€“ Converts Swagger schemas into TypeScript interfaces or classes.
8
- - ๐Ÿ”ฅ **API Client Generation** โ€“ Creates service methods for making API calls with authentication, headers, and request parameters.
9
- - ๐ŸŽฏ **Framework-Specific Output**:
10
- - **Angular** โ€“ Generates injectable services using `HttpClient`.
11
- - **Next.js** โ€“ Provides fetch-based or Axios-based API functions, optimized for both server-side and client-side usage.
12
- - **Flutter** โ€“ Provides Flutter with Dio API functions, optimized for both server-side and client-side usage.
13
- - โœ… **Strong Typing** โ€“ Ensures type safety for API requests and responses.
14
- - โšก **Customization** โ€“ Configurable options for method naming, error handling, and request structure.
15
- - ๐Ÿ”„ **Auto-Sync with Backend** โ€“ Keeps API clients up-to-date with backend changes.
8
+ ## ๐ŸŽฏ Caratteristiche
16
9
 
17
- ## Installation
10
+ - โœ… **Generazione automatica** di modelli e API da Swagger JSON
11
+ - ๐ŸŽจ **Multi-framework**: Angular, Next.js, Flutter/Dart
12
+ - ๐Ÿ”’ **Type-safe**: Tipizzazione forte per TypeScript e Dart
13
+ - ๐Ÿ“ฆ **Zero dipendenze** nei file generati (configurabile)
14
+ - ๐Ÿš€ **CLI semplice** da usare
15
+ - ๐Ÿ”„ **Validazione** del documento Swagger prima della generazione
16
+ - ๐Ÿ“ **Logging strutturato** per debugging
18
17
 
19
- ```sh
20
- npm i @devlearning/swagger-generator --save-dev
18
+ ## ๐Ÿ“ฆ Installazione
19
+
20
+ ```bash
21
+ npm install @devlearning/swagger-generator --save-dev
22
+ ```
23
+
24
+ ## ๐Ÿš€ Utilizzo
25
+
26
+ ### Da linea di comando
27
+
28
+ ```bash
29
+ npx swgen --url <swagger-url> --output <output-dir> --target <angular|next|flutter> [opzioni]
30
+ ```
31
+
32
+ ### Esempi
33
+
34
+ #### Angular
35
+ ```bash
36
+ npx swgen \
37
+ --url http://localhost:5000/swagger/v1/swagger.json \
38
+ --output src/app/core/autogen \
39
+ --target angular \
40
+ --dateTimeLibrary moment
21
41
  ```
22
42
 
43
+ #### Next.js
44
+ ```bash
45
+ npx swgen \
46
+ --url https://api.example.com/swagger.json \
47
+ --output src/lib/api \
48
+ --target next \
49
+ --dateTimeLibrary date-fns
50
+ ```
23
51
 
24
- add npm script:
52
+ #### Flutter/Dart
53
+ ```bash
54
+ npx swgen \
55
+ --url http://localhost:5000/swagger/v1/swagger.json \
56
+ --output lib/core/api \
57
+ --target flutter \
58
+ --package my_app_name
59
+ ```
25
60
 
26
- "swgen": "swgen http://localhost:7550/swagger/ApiGateway/swagger.json src/app/core/autogenerated angular moment"
61
+ ### Opzioni
27
62
 
28
- params:
29
- - url of swagger.json: not https
30
- - output path for autogenerated files: src/app/core/autogenerated
31
- - target framework/library: angular/next
32
- - type of date management:
33
- - angular only support momentjs, sorry :(
34
- - nextjs only support date-fns
35
- - dart native date management
63
+ | Opzione | Alias | Descrizione | Obbligatorio | Default |
64
+ |---------|-------|-------------|--------------|---------|
65
+ | `--url` | `-u` | URL del file swagger.json | โœ… | - |
66
+ | `--output` | `-o` | Directory di output | โœ… | - |
67
+ | `--target` | `-t` | Framework target (`angular`, `next`, `flutter`) | โœ… | - |
68
+ | `--dateTimeLibrary` | `-d` | Libreria date (`moment`, `date-fns`) | โŒ | `date-fns` |
69
+ | `--package` | `-p` | Nome package (solo Dart/Flutter) | โŒ | - |
36
70
 
37
- create output path like this: src/app/core/autogenerated
71
+ ## ๐Ÿ“ Struttura File Generati
38
72
 
39
- ## Angular configuration
73
+ ### Angular
74
+ ```
75
+ output/
76
+ โ”œโ”€โ”€ models/
77
+ โ”‚ โ”œโ”€โ”€ user.model.ts
78
+ โ”‚ โ”œโ”€โ”€ product.model.ts
79
+ โ”‚ โ””โ”€โ”€ ...
80
+ โ””โ”€โ”€ api/
81
+ โ”œโ”€โ”€ user.api.ts
82
+ โ”œโ”€โ”€ product.api.ts
83
+ โ””โ”€โ”€ ...
84
+ ```
40
85
 
41
- create ApiService like this
42
- ```ts
43
- import { HttpClient, HttpErrorResponse } from '@angular/common/http';
86
+ ### Next.js
87
+ ```
88
+ output/
89
+ โ”œโ”€โ”€ models/
90
+ โ”‚ โ”œโ”€โ”€ user.ts
91
+ โ”‚ โ”œโ”€โ”€ product.ts
92
+ โ”‚ โ””โ”€โ”€ ...
93
+ โ””โ”€โ”€ api/
94
+ โ”œโ”€โ”€ user-api.ts
95
+ โ”œโ”€โ”€ product-api.ts
96
+ โ””โ”€โ”€ ...
97
+ ```
98
+
99
+ ### Flutter/Dart
100
+ ```
101
+ lib/output/
102
+ โ”œโ”€โ”€ user/
103
+ โ”‚ โ”œโ”€โ”€ models/
104
+ โ”‚ โ”‚ โ””โ”€โ”€ user.dart
105
+ โ”‚ โ””โ”€โ”€ api/
106
+ โ”‚ โ””โ”€โ”€ user_api.dart
107
+ โ””โ”€โ”€ product/
108
+ โ”œโ”€โ”€ models/
109
+ โ”‚ โ””โ”€โ”€ product.dart
110
+ โ””โ”€โ”€ api/
111
+ โ””โ”€โ”€ product_api.dart
112
+ ```
113
+
114
+ ## ๐Ÿ”ง Integrazione nel Progetto
115
+
116
+ ### Angular
117
+
118
+ 1. Crea un service wrapper:
119
+
120
+ ```typescript
44
121
  import { Injectable } from '@angular/core';
45
- import { ApiAutogeneratedService } from '../autogenerated/api.autogenerated';
46
- import { environment } from 'src/environments/environment';
47
- import { ResponseError } from '../models/response-error';
48
- import { throwError } from 'rxjs';
49
- import * as moment from 'moment-timezone';
50
-
51
- @Injectable({
52
- providedIn: 'root',
53
- })
54
- export class ApiService extends ApiAutogeneratedService {
55
-
56
- private _dateTimeFormat = /^((19|20)[0-9][0-9])[-](0[1-9]|1[012])[-](0[1-9]|[12][0-9]|3[01])[T]([01]?[0-9]|[2][0-3])[:]([0-5][0-9])[:]([0-5][0-9])[.]([0-9][0-9][0-9])([+|-]([01][0-9]|[2][0-3])[:]([0-5][0-9])){0,1}$/;
57
- private _timeFormat = /^([01]?[0-9]|[2][0-3])[:]([0-5][0-9])[:]([0-5][0-9])[.]([0-9][0-9][0-9])$/;
58
- private _ianaName: string = Intl.DateTimeFormat().resolvedOptions().timeZone;
59
- private _dateTimeFormatString = "YYYY-MM-DDTHH:mm:ss.SSSZ";
60
- private _timeFormatString = "HH:mm:ss.SSS";
61
-
62
- public get ianaName() { return this._ianaName; }
63
-
64
- constructor(
65
- public override _http: HttpClient,
66
- ) {
67
- super(_http, environment.BASE_URL);
68
- }
69
-
70
- protected override _momentToString(moment: moment.Moment) {
71
- return (<moment.Moment>moment).format(this._dateTimeFormatString);
72
- }
73
-
74
- protected override _handleRequest<T>(request: T) {
75
- if (request === null || request === undefined) {
76
- return request;
77
- }
78
- if (typeof request !== 'object') {
79
- return request;
80
- }
81
-
82
- var clonedRequest = { ...request };
83
-
84
- for (const key of Object.keys(clonedRequest)) {
85
- const value = (<any>clonedRequest)[key];
86
- if (moment.isMoment(value)) {
87
- (<any>clonedRequest)[key] = this._momentToString(value);
88
- } else if (typeof value === 'object') {
89
- this._handleRequest(value);
90
- }
91
- }
92
-
93
- return clonedRequest;
94
- }
95
-
96
- protected override _handleMultipart<T>(request: T): FormData {
97
- const formData = new FormData();
98
- if (request === null || request === undefined) {
99
- return formData;
100
- }
101
-
102
- for (const key of Object.keys(request)) {
103
- const value = (<any>request)[key];
104
- if (value instanceof File) {
105
- formData.append(key, value, value.name);
106
- } else {
107
- formData.append(key, value.toString());
108
- }
109
- }
110
-
111
- return formData;
112
- }
113
-
114
- public override _handleResponse<T>(response: T) {
115
- if (response === null || response === undefined) {
116
- return response;
117
- }
118
- if (typeof response !== 'object') {
119
- return response;
120
- }
121
-
122
- for (const key of Object.keys(response)) {
123
- const value = (<any>response)[key];
124
- if (this._isDateString(value)) {
125
- (<any>response)[key] = moment.tz(value, this._dateTimeFormatString, this._ianaName);
126
- } else if (this._isTimeString(value)) {
127
- (<any>response)[key] = moment.tz(value, this._timeFormatString, this._ianaName);
128
- } else if (typeof value === 'object') {
129
- this._handleResponse(value);
130
- }
131
- }
132
-
133
- return response;
134
- }
135
-
136
- protected override _handleError(error: any, obs: any) {
137
- let responseError = new ResponseError();
138
- if (error.error instanceof Error) {
139
- console.error('Api - an error occurred:', error.error.message);
140
- responseError.message = error.error.message;
141
- } else if (error instanceof HttpErrorResponse) {
142
- responseError = ResponseError.CreateFromHttpErrorResponse(error);
143
- }
144
-
145
- console.error(`Api - error code ${error.status} message ${responseError.message} ${responseError.stacktrace}`);
146
-
147
- return throwError(() => responseError);
148
- }
149
-
150
- private _isDateString(value: string) {
151
- if (value === null || value === undefined) {
152
- return false;
153
- }
154
- if (typeof value === 'string') {
155
- return this._dateTimeFormat.test(value);
156
- }
157
- return false;
158
- }
159
-
160
- private _isTimeString(value: string) {
161
- if (value === null || value === undefined) {
162
- return false;
163
- }
164
- if (typeof value === 'string') {
165
- return this._timeFormat.test(value);
166
- }
167
- return false;
168
- }
122
+ import { HttpClient } from '@angular/common/http';
123
+ import { environment } from '@env/environment';
124
+
125
+ @Injectable({ providedIn: 'root' })
126
+ export class ApiService {
127
+ constructor(private http: HttpClient) {}
128
+
129
+ // I tuoi metodi personalizzati qui
169
130
  }
170
131
  ```
171
132
 
133
+ 2. Usa i modelli generati:
172
134
 
173
- ```ts
174
- import { HttpErrorResponse } from '@angular/common/http';
135
+ ```typescript
136
+ import { User } from './autogen/models/user.model';
137
+ import { UserApi } from './autogen/api/user.api';
138
+ ```
175
139
 
176
- export class ResponseError {
177
- public url: string | null;
178
- public status: number | null;
179
- public message: string | null;
180
- public stacktrace: string | null;
140
+ ### Next.js
181
141
 
182
- constructor() {
183
- this.url = null;
184
- this.status = null;
185
- this.message = null;
186
- this.stacktrace = null;
187
- }
142
+ ```typescript
143
+ import { getUsers } from '@/lib/api/user-api';
144
+ import { User } from '@/lib/api/models/user';
188
145
 
189
- static CreateFromHttpErrorResponse(httpErrorResponse: HttpErrorResponse) {
190
- let responseError = new ResponseError();
146
+ export async function UsersPage() {
147
+ const users = await getUsers();
148
+ // ...
149
+ }
150
+ ```
191
151
 
192
- if (httpErrorResponse.error != null && !(httpErrorResponse.error instanceof ProgressEvent)) {
193
- if (httpErrorResponse.error.hasOwnProperty('message')) {
194
- responseError.message = httpErrorResponse.error.message;
195
- }
152
+ ### Flutter/Dart
196
153
 
197
- if (httpErrorResponse.error.hasOwnProperty('stacktrace')) {
198
- responseError.stacktrace = httpErrorResponse.error.stacktrace;
199
- }
200
- } else {
201
- responseError.message = httpErrorResponse.message;
202
- }
154
+ ```dart
155
+ import 'package:my_app/core/api/user/api/user_api.dart';
156
+ import 'package:my_app/core/api/user/models/user.dart';
203
157
 
204
- responseError.status = httpErrorResponse.status;
205
- responseError.url = httpErrorResponse.url;
158
+ final userApi = UserApi();
159
+ final users = await userApi.getUsers();
160
+ ```
206
161
 
207
- return responseError;
208
- }
209
- }
210
- ```
162
+ ## ๐Ÿ› Risoluzione Problemi
163
+
164
+ ### Errore: "Swagger validation failed"
165
+ Il documento Swagger non รจ valido. Verifica:
166
+ - Presenza di `openApi` version
167
+ - Presenza di `paths` object
168
+ - Struttura corretta delle definizioni
169
+
170
+ ### Errore: "Cannot parse null data"
171
+ L'API restituisce null per un tipo primitivo. Verifica che il backend ritorni sempre un valore valido.
172
+
173
+ ### Tipi primitivi non funzionano (Dart)
174
+ โœ… **RISOLTO** - Ora il generatore gestisce correttamente String, int, bool, double con conversioni automatiche.
175
+
176
+ ## ๐Ÿ› ๏ธ Sviluppo
177
+
178
+ ```bash
179
+ # Clone repository
180
+ git clone <repo-url>
181
+
182
+ # Install dependencies
183
+ npm install
184
+
185
+ # Build
186
+ npm run build
187
+
188
+ # Test local
189
+ npm run debug-angular
190
+ npm run debug-nextjs
191
+ npm run debug-flutter
192
+ ```
193
+
194
+ ## ๐Ÿ“ Script NPM
195
+
196
+ | Script | Descrizione |
197
+ |--------|-------------|
198
+ | `npm run build` | Compila TypeScript e copia templates |
199
+ | `npm run debug-angular` | Test con target Angular |
200
+ | `npm run debug-nextjs` | Test con target Next.js |
201
+ | `npm run debug-flutter` | Test con target Flutter |
202
+ | `npm run deploy` | Pubblica su npm |
203
+
204
+ ## ๐Ÿค Contribuire
205
+
206
+ Contributi benvenuti! Per favore:
207
+ 1. Fai fork del progetto
208
+ 2. Crea un branch per la tua feature (`git checkout -b feature/amazing-feature`)
209
+ 3. Commit dei cambiamenti (`git commit -m 'Add amazing feature'`)
210
+ 4. Push del branch (`git push origin feature/amazing-feature`)
211
+ 5. Apri una Pull Request
212
+
213
+ ## ๐Ÿ“„ Licenza
214
+
215
+ ISC License - vedi file LICENSE per dettagli
216
+
217
+ ## ๐Ÿ™ Crediti
218
+
219
+ Sviluppato da DeVLearninG Team
220
+
221
+ ---
222
+
223
+ **Note**: Questo tool รจ in sviluppo attivo. Per bug o feature request, apri una issue su GitHub.