openapi-sync 3.0.2 β†’ 4.0.1

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 CHANGED
@@ -2,93 +2,39 @@
2
2
  [![License](https://img.shields.io/npm/l/openapi-sync.svg)](https://github.com/akintomiwa-fisayo/openapi-sync/blob/main/LICENSE)
3
3
  [![Tests](https://img.shields.io/badge/tests-passing-brightgreen.svg)](https://github.com/akintomiwa-fisayo/openapi-sync)
4
4
 
5
- **OpenAPI Sync** is a powerful developer tool that automates the synchronization of your API documentation with your codebase using OpenAPI (formerly Swagger) specifications. It generates TypeScript types, endpoint definitions, and comprehensive documentation from your OpenAPI schema, ensuring your API documentation stays up-to-date with your code.
5
+ # OpenAPI Sync
6
6
 
7
- ## Table of Contents
7
+ **OpenAPI Sync** is a powerful developer tool that automates the synchronization of your API documentation with your codebase using OpenAPI (formerly Swagger) specifications. It generates TypeScript types, endpoint definitions, runtime validation schemas (Zod, Yup, Joi), and comprehensive documentation from your OpenAPI schemaβ€”ensuring type safety from API specification to runtime validation.
8
8
 
9
- - [Features](#features)
10
- - [Installation](#installation)
11
- - [Quick Start](#quick-start)
12
- - [Configuration](#configuration)
13
- - [Usage](#usage)
14
- - [Generated Output](#generated-output)
15
- - [Custom Code Injection](#custom-code-injection)
16
- - [API Reference](#api-reference)
17
- - [Advanced Examples](#advanced-examples)
18
- - [Troubleshooting](#troubleshooting)
9
+ > πŸ“˜ **[Full documentation available at openapi-sync.com](https://openapi-sync.com)**
19
10
 
20
11
  ## Features
21
12
 
22
- ### πŸ”„ **Real-time API Synchronization**
13
+ - πŸ”„ **Real-time API Synchronization** - Automatically syncs OpenAPI specs from remote URLs
14
+ - πŸ“ **Automatic Type Generation** - Generates TypeScript interfaces for all endpoints
15
+ - πŸ”§ **Highly Configurable** - Customizable naming, filtering, and folder organization
16
+ - πŸ›‘οΈ **Enterprise Ready** - Error handling, validation, and state persistence
17
+ - πŸ” **Runtime Validation** - Generate Zod, Yup, or Joi schemas from OpenAPI specs
18
+ - πŸ“š **Rich Documentation** - JSDoc comments with cURL examples
19
+ - πŸ”„ **Custom Code Injection** - Preserve your custom code between regenerations
23
20
 
24
- - Automatically fetches and syncs OpenAPI specifications from remote URLs
25
- - Supports both JSON and YAML formats
26
- - Configurable refetch intervals for development environments
27
- - Smart caching to avoid unnecessary regeneration
28
-
29
- ### πŸ“ **Automatic Type Generation**
30
-
31
- - Generates TypeScript interfaces for all API endpoints
32
- - Creates type definitions for request/response bodies
33
- - Supports complex nested objects, arrays, and unions
34
- - Handles nullable types and optional properties
35
- - Generates shared component types from OpenAPI components
36
-
37
- ### πŸ”§ **Highly Configurable**
38
-
39
- - Customizable naming conventions for types and endpoints
40
- - Exclude/include endpoints by exact path or regex patterns
41
- - Tag-based filtering, method-specific filtering, and pattern matching
42
- - Folder splitting configuration for organized code generation
43
- - OperationId-based naming for better type and endpoint names
44
- - Flexible output directory structure with custom folder organization
45
- - URL transformation and text replacement rules
46
- - Configurable documentation generation
47
- - Support for multiple API specifications
48
- - Custom code injection, preserve your custom code between regenerations
49
-
50
- ### πŸ›‘οΈ **Enterprise Ready**
51
-
52
- - Network error handling with exponential backoff retry
53
- - Schema validation using Redocly OpenAPI Core
54
- - State persistence to track changes
55
- - Environment-aware (disables auto-sync in production)
56
- - TypeScript support with full type safety
57
-
58
- ### πŸ“š **Rich Documentation**
59
-
60
- - Generates comprehensive JSDoc comments
61
- - Includes cURL examples for each endpoint
62
- - Security scheme documentation
63
- - Request/response type documentation
64
- - Markdown-formatted type references
21
+ [View all features β†’](https://openapi-sync.com/docs#features)
65
22
 
66
23
  ## Installation
67
24
 
68
- ### NPM Package
69
-
70
25
  ```bash
71
26
  npm install openapi-sync
72
- ```
73
-
74
- ### Global Installation
75
-
76
- ```bash
27
+ # or
77
28
  npm install -g openapi-sync
78
- ```
79
-
80
- ### Direct Usage (No Installation)
81
-
82
- ```bash
29
+ # or use directly
83
30
  npx openapi-sync
84
31
  ```
85
32
 
86
33
  ## Quick Start
87
34
 
88
- 1. **Create a configuration file** in your project root:
35
+ **1. Create `openapi.sync.json` in your project root:**
89
36
 
90
37
  ```json
91
- // openapi.sync.json
92
38
  {
93
39
  "refetchInterval": 5000,
94
40
  "folder": "./src/api",
@@ -98,1331 +44,88 @@ npx openapi-sync
98
44
  }
99
45
  ```
100
46
 
101
- 2. **Run the sync command**:
47
+ **2. Run the sync command:**
102
48
 
103
49
  ```bash
104
50
  npx openapi-sync
105
51
  ```
106
52
 
107
- 3. **Use the generated types and endpoints**:
53
+ **3. Use generated types and endpoints:**
108
54
 
109
55
  ```typescript
110
56
  import { getPetById } from "./src/api/petstore/endpoints";
111
- import { IPet, IGetPetByIdResponse } from "./src/api/petstore/types";
57
+ import { IPet } from "./src/api/petstore/types";
112
58
 
113
- // Use the endpoint URL
114
59
  const petUrl = getPetById("123"); // Returns: "/pet/123"
115
-
116
- // Use the generated types
117
- const pet: IPet = {
118
- id: 1,
119
- name: "Fluffy",
120
- status: "available",
121
- };
122
60
  ```
123
61
 
124
- ## Configuration
62
+ [View detailed quick start guide β†’](https://openapi-sync.com/docs#quick-start)
125
63
 
126
- OpenAPI Sync supports multiple configuration file formats:
64
+ ## Configuration
127
65
 
128
- - `openapi.sync.json` (JSON format)
129
- - `openapi.sync.ts` (TypeScript format)
130
- - `openapi.sync.js` (JavaScript format)
66
+ Supports multiple configuration formats: `openapi.sync.json`, `openapi.sync.ts`, or `openapi.sync.js`
131
67
 
132
- ### Basic Configuration
68
+ **Basic Example:**
133
69
 
134
70
  ```json
135
71
  {
136
72
  "refetchInterval": 5000,
137
73
  "folder": "./src/api",
138
74
  "api": {
139
- "petstore": "https://petstore3.swagger.io/api/v3/openapi.json",
140
- "jsonplaceholder": "https://jsonplaceholder.typicode.com/openapi.yaml"
141
- },
142
- "server": 0
143
- }
144
- ```
145
-
146
- ### Advanced Configuration
147
-
148
- ```typescript
149
- // openapi.sync.ts
150
- import { IConfig } from "openapi-sync/types";
151
-
152
- const config: IConfig = {
153
- refetchInterval: 10000,
154
- folder: "./src/generated/api",
155
- api: {
156
- "main-api": "https://api.example.com/openapi.json",
157
- "auth-api": "https://auth.example.com/openapi.yaml",
158
- },
159
- server: "https://api.example.com", // Override server URL
160
-
161
- // NEW: Folder splitting configuration
162
- folderSplit: {
163
- byTags: true, // Create folders based on endpoint tags
164
- customFolder: ({ method, path, tags, operationId }) => {
165
- // Custom logic to determine folder structure
166
- if (tags?.includes("admin")) return "admin";
167
- if (tags?.includes("public")) return "public";
168
- if (path.startsWith("/api/v1/")) return "v1";
169
- return null; // Use default folder structure
170
- },
171
- },
172
-
173
- // Type generation configuration
174
- types: {
175
- name: {
176
- prefix: "I", // Prefix for interface names
177
- format: (source, data, defaultName) => {
178
- if (source === "shared") {
179
- return `${data.name}Type`;
180
- } else if (source === "endpoint") {
181
- return `${data.method?.toUpperCase()}${data.path?.replace(
182
- /\//g,
183
- "_"
184
- )}${data.type}`;
185
- }
186
- return defaultName;
187
- },
188
- },
189
- doc: {
190
- disable: false, // Enable/disable JSDoc generation
191
- },
192
- },
193
-
194
- // Endpoint generation configuration
195
- endpoints: {
196
- value: {
197
- replaceWords: [
198
- {
199
- replace: "/api/v\\d+/", // Remove version from URLs
200
- with: "/",
201
- },
202
- {
203
- replace: "/internal/",
204
- with: "/",
205
- type: "endpoint",
206
- },
207
- ],
208
- includeServer: true, // Include server URL in endpoints
209
- type: "object", // Generate as objects instead of strings
210
- },
211
- name: {
212
- prefix: "API_",
213
- useOperationId: true, // Use operationId from OpenAPI spec
214
- format: ({ method, path, summary, operationId }, defaultName) => {
215
- if (operationId) return operationId;
216
- return path.replace(/\//g, "_").replace(/{|}/g, "");
217
- },
218
- },
219
- doc: {
220
- disable: false,
221
- showCurl: true, // Include cURL examples in documentation
222
- },
223
- exclude: {
224
- // Exclude endpoints by tags
225
- tags: ["deprecated", "internal"],
226
- // Exclude individual endpoints by path and method
227
- endpoints: [
228
- // Exact path match
229
- { path: "/admin/users", method: "DELETE" },
230
- // Regex pattern match
231
- { regex: "^/internal/.*", method: "GET" },
232
- { regex: ".*/debug$", method: "GET" },
233
- // Don't specify method to exclude all methods
234
- { path: "/debug/logs" },
235
- ],
236
- },
237
- include: {
238
- // Include endpoints by tags
239
- tags: ["public"],
240
- // Include individual endpoints by path and method
241
- endpoints: [
242
- // Exact path match
243
- { path: "/public/users", method: "GET" },
244
- // Regex pattern match
245
- { regex: "^/public/.*", method: "GET" },
246
- { regex: ".*/logs$", method: "GET" },
247
- // Don't specify method to include all methods
248
- { path: "/public/logs" },
249
- ],
250
- },
251
- },
252
- };
253
-
254
- export default config;
255
- ```
256
-
257
- ### Configuration Options Reference
258
-
259
- #### Root Configuration
260
-
261
- | Property | Type | Description | Default |
262
- | ----------------- | ------------------------ | --------------------------------------------- | -------- |
263
- | `refetchInterval` | `number` | Milliseconds between API refetches (dev only) | - |
264
- | `folder` | `string` | Output directory for generated files | `""` |
265
- | `api` | `Record<string, string>` | Map of API names to OpenAPI spec URLs | Required |
266
- | `server` | `number \| string` | Server index or custom server URL | `0` |
267
- | `folderSplit` | `IConfigFolderSplit` | Configuration for folder splitting | - |
268
-
269
- #### Type Configuration (`types`)
270
-
271
- | Property | Type | Description |
272
- | --------------------- | ---------- | ------------------------------------------------------- |
273
- | `name.prefix` | `string` | Prefix for generated type names |
274
- | `name.useOperationId` | `boolean` | Use OpenAPI operationId for type naming when available |
275
- | `name.format` | `function` | Custom naming function with source context and metadata |
276
- | `doc.disable` | `boolean` | Disable JSDoc generation for types |
277
-
278
- **OperationId-based Type Naming:**
279
-
280
- When `useOperationId` is set to `true`, the system will use the OpenAPI `operationId` for type naming:
281
-
282
- - **Query Types**: `{operationId}Query` (e.g., `getUserByIdQuery`)
283
- - **DTO Types**: `{operationId}DTO` (e.g., `createUserDTO`)
284
- - **Response Types**: `{operationId}{code}Response` (e.g., `getUserById200Response`)
285
-
286
- If `operationId` is not available, the system falls back to the default path-based naming convention.
287
-
288
- #### Endpoint Configuration (`endpoints`)
289
-
290
- | Property | Type | Description |
291
- | --------------------- | --------------------------------------------------------- | --------------------------------------------------------- |
292
- | `value.replaceWords` | `IConfigReplaceWord[]` | URL transformation rules |
293
- | `value.includeServer` | `boolean` | Include server URL in endpoints |
294
- | `value.type` | `"string" \| "object"` | Output format for endpoints |
295
- | `name.prefix` | `string` | Prefix for endpoint names |
296
- | `name.useOperationId` | `boolean` | Use OpenAPI operationId for naming |
297
- | `name.format` | `function` | Custom naming function for endpoints |
298
- | `doc.disable` | `boolean` | Disable JSDoc generation for endpoints |
299
- | `doc.showCurl` | `boolean` | Include cURL examples in documentation |
300
- | `exclude.tags` | `string[]` | Exclude endpoints by tags |
301
- | `exclude.endpoints` | `Array<{path?: string, regex?: string, method?: Method}>` | Exclude specific endpoints by exact path or regex pattern |
302
- | `include.tags` | `string[]` | Include endpoints by tags |
303
- | `include.endpoints` | `Array<{path?: string, regex?: string, method?: Method}>` | Include specific endpoints by exact path or regex pattern |
304
-
305
- #### Folder Splitting Configuration (`folderSplit`)
306
-
307
- | Property | Type | Description |
308
- | -------------- | ---------- | --------------------------------------------- |
309
- | `byTags` | `boolean` | Create folders based on endpoint tags |
310
- | `customFolder` | `function` | Custom function to determine folder structure |
311
-
312
- **Folder Splitting Examples:**
313
-
314
- ```typescript
315
- // Split by tags - creates folders like "admin/", "public/", "user/"
316
- folderSplit: {
317
- byTags: true,
318
- }
319
-
320
- // Custom folder logic
321
- folderSplit: {
322
- customFolder: ({ method, path, tags, operationId }) => {
323
- // Admin endpoints go to admin folder
324
- if (tags?.includes("admin")) return "admin";
325
-
326
- // Public endpoints go to public folder
327
- if (tags?.includes("public")) return "public";
328
-
329
- // API versioning
330
- if (path.startsWith("/api/v1/")) return "v1";
331
- if (path.startsWith("/api/v2/")) return "v2";
332
-
333
- // Method-based organization
334
- const method = data.method.toLowerCase();
335
- if (method === "get") return "read";
336
- if (method === "post" || method === "PUT") return "write";
337
-
338
- return null; // Use default structure
339
- },
340
- }
341
- ```
342
-
343
- ## Usage
344
-
345
- ### CLI Usage
346
-
347
- #### Basic Commands
348
-
349
- ```bash
350
- # Run with default configuration
351
- npx openapi-sync
352
-
353
- # Run with custom refetch interval
354
- npx openapi-sync --refreshinterval 30000
355
- npx openapi-sync -ri 30000
356
-
357
- # Get help
358
- npx openapi-sync --help
359
- ```
360
-
361
- #### CLI Options
362
-
363
- | Option | Alias | Type | Description |
364
- | ------------------- | ----- | -------- | ------------------------------------- |
365
- | `--refreshinterval` | `-ri` | `number` | Override refetch interval from config |
366
-
367
- ### Programmatic Usage
368
-
369
- #### Basic Usage
370
-
371
- ```typescript
372
- import { Init } from "openapi-sync";
373
-
374
- // Initialize with default config
375
- await Init();
376
-
377
- // Initialize with custom options
378
- await Init({
379
- refetchInterval: 30000,
380
- });
381
- ```
382
-
383
- #### Advanced Integration
384
-
385
- ```typescript
386
- import { Init } from "openapi-sync";
387
-
388
- // In your application startup
389
- export const initializeAPI = async () => {
390
- try {
391
- await Init({
392
- refetchInterval: process.env.NODE_ENV === "development" ? 5000 : 0,
393
- });
394
- console.log("API types synchronized successfully");
395
- } catch (error) {
396
- console.error("Failed to sync API types:", error);
397
- throw error;
398
- }
399
- };
400
-
401
- // Call during app initialization
402
- initializeAPI();
403
- ```
404
-
405
- #### Function-based Configuration
406
-
407
- ```typescript
408
- // openapi.sync.ts
409
- import { IConfig } from "openapi-sync/types";
410
-
411
- export default (): IConfig => {
412
- const baseConfig = {
413
- refetchInterval: 5000,
414
- folder: "./src/api",
415
- api: {},
416
- };
417
-
418
- // Dynamic configuration based on environment
419
- if (process.env.NODE_ENV === "development") {
420
- baseConfig.api = {
421
- "local-api": "http://localhost:3000/openapi.json",
422
- };
423
- } else {
424
- baseConfig.api = {
425
- "prod-api": "https://api.production.com/openapi.json",
426
- };
75
+ "petstore": "https://petstore3.swagger.io/api/v3/openapi.json"
427
76
  }
428
-
429
- return baseConfig;
430
- };
431
- ```
432
-
433
- ## Generated Output
434
-
435
- OpenAPI Sync generates a structured output in your specified folder:
436
-
437
- ### Default Structure
438
-
439
- ```
440
- src/api/
441
- β”œβ”€β”€ petstore/
442
- β”‚ β”œβ”€β”€ endpoints.ts # Endpoint URLs and metadata
443
- β”‚ └── types.ts # Endpoint-specific types
444
- β”œβ”€β”€ auth-api/
445
- β”‚ β”œβ”€β”€ endpoints.ts # Endpoint URLs and metadata
446
- β”‚ └── types.ts # Endpoint-specific types
447
- └── shared.ts # Shared component types
448
- ```
449
-
450
- ### Folder Splitting Structure
451
-
452
- When `folderSplit.byTags` is enabled or custom folder logic is used:
453
-
454
- ```
455
- src/api/
456
- β”œβ”€β”€ petstore/
457
- β”‚ β”œβ”€β”€ admin/ # Endpoints with "admin" tag
458
- β”‚ β”‚ β”œβ”€β”€ endpoints.ts
459
- β”‚ β”‚ └── types.ts
460
- β”‚ β”œβ”€β”€ public/ # Endpoints with "public" tag
461
- β”‚ β”‚ β”œβ”€β”€ endpoints.ts
462
- β”‚ β”‚ └── types.ts
463
- β”‚ └── user/ # Endpoints with "user" tag
464
- β”‚ β”œβ”€β”€ endpoints.ts
465
- β”‚ └── types.ts
466
- │── auth-api/
467
- β”‚ β”œβ”€β”€ v1/ # Custom folder logic
468
- β”‚ β”‚ β”œβ”€β”€ endpoints.ts
469
- β”‚ β”‚ └── types.ts
470
- β”‚ └── v2/
471
- β”‚ β”œβ”€β”€ endpoints.ts
472
- β”‚ └── types.ts
473
- └── shared.ts # Shared component types
474
- ```
475
-
476
- ### Generated Endpoints
477
-
478
- When `endpoints.value.type` is set to `"string"`
479
-
480
- #### String Format (Default)
481
-
482
- ````typescript
483
- // endpoints.ts
484
- /**
485
- * **Method**: `GET`
486
- * **Summary**: Find pet by ID
487
- * **Tags**: [pet]
488
- * **OperationId**: getPetById
489
- * **Response**:
490
- * - **200**:
491
- * ```typescript
492
- * {
493
- * "id": number;
494
- * "name": string;
495
- * "status": ("available"|"pending"|"sold");
496
- * }
497
- * ```
498
- *
499
- * ```bash
500
- * curl -X GET "https://petstore3.swagger.io/api/v3/pet/123" \
501
- * -H "accept: application/json"
502
- * ```
503
- */
504
- export const getPetById = (petId: string) => `/pet/${petId}`;
505
-
506
- /**
507
- * **Method**: `POST`
508
- * **Summary**: Add a new pet to the store
509
- */
510
- export const addPet = "/pet";
511
- ````
512
-
513
- #### Object Format
514
-
515
- When `endpoints.value.type` is set to `"object"`, each endpoint is generated as an object containing metadata:
516
-
517
- ````typescript
518
- // endpoints.ts (with type: "object")
519
- /**
520
- * **Method**: `POST`
521
- * **Summary**: Add a new pet to the store
522
- * **Tags**: [pet]
523
- * **DTO**:
524
- * ```typescript
525
- * {
526
- * "name": string;
527
- * "photoUrls": string[];
528
- * "status": ("available"|"pending"|"sold");
529
- * }
530
- * ```
531
- */
532
- export const addPet = {
533
- method: "POST",
534
- operationId: "addPet",
535
- url: "/pet",
536
- tags: ["pet"],
537
- };
538
-
539
- export const getPetById = {
540
- method: "GET",
541
- operationId: "getPetById",
542
- url: (petId: string) => `/pet/${petId}`,
543
- tags: ["pet"],
544
- };
545
- ````
546
-
547
- ### Generated Types
548
-
549
- #### Endpoint Types
550
-
551
- ```typescript
552
- // types/index.ts
553
- import * as Shared from "./shared";
554
-
555
- // Query parameter types
556
- export type IFindPetsByStatusQuery = {
557
- status?: "available" | "pending" | "sold";
558
- };
559
-
560
- // Request body types (DTO)
561
- export type IAddPetDTO = {
562
- id?: number;
563
- name: string;
564
- category?: Shared.ICategory;
565
- photoUrls: string[];
566
- tags?: Shared.ITag[];
567
- status?: "available" | "pending" | "sold";
568
- };
569
-
570
- // Response types
571
- export type IGetPetById200Response = {
572
- id?: number;
573
- name: string;
574
- category?: Shared.ICategory;
575
- photoUrls: string[];
576
- tags?: Shared.ITag[];
577
- status?: "available" | "pending" | "sold";
578
- };
579
- ```
580
-
581
- #### Shared Component Types
582
-
583
- ```typescript
584
- // types/shared.ts
585
- /**
586
- * A category for a pet
587
- */
588
- export type ICategory = {
589
- id?: number;
590
- name?: string;
591
- };
592
-
593
- /**
594
- * A tag for a pet
595
- */
596
- export type ITag = {
597
- id?: number;
598
- name?: string;
599
- };
600
-
601
- export type IPet = {
602
- id?: number;
603
- name: string;
604
- category?: ICategory;
605
- photoUrls: string[];
606
- tags?: ITag[];
607
- status?: "available" | "pending" | "sold";
608
- };
609
- ```
610
-
611
- ## Custom Code Injection
612
-
613
- OpenAPI Sync supports preserving custom code between regenerations using special comment markers. This allows you to add your own custom endpoints, types, or utility functions that will survive when the generated code is updated.
614
-
615
- ### How It Works
616
-
617
- Custom code is preserved using special comment markers in the generated files. Any code you add between these markers will be preserved when the files are regenerated.
618
-
619
- ### Configuration
620
-
621
- Add the `customCode` configuration to your `openapi.sync.ts` file:
622
-
623
- ```typescript
624
- import { IConfig } from "openapi-sync/types";
625
-
626
- export default {
627
- refetchInterval: 5000,
628
- folder: "./src/api",
629
- api: {
630
- petstore: "https://petstore3.swagger.io/api/v3/openapi.json",
631
- },
632
- customCode: {
633
- enabled: true, // Enable custom code preservation (default: true)
634
- position: "bottom", // Position of custom code: "top", "bottom", or "both"
635
- markerText: "CUSTOM CODE", // Custom marker text (default: "CUSTOM CODE")
636
- includeInstructions: true, // Include helpful instructions (default: true)
637
- },
638
- } as IConfig;
639
- ```
640
-
641
- ### Configuration Options
642
-
643
- | Option | Type | Default | Description |
644
- | --------------------- | ----------------------------- | --------------- | ------------------------------------------ |
645
- | `enabled` | `boolean` | `true` | Enable or disable custom code preservation |
646
- | `position` | `"top" \| "bottom" \| "both"` | `"bottom"` | Where to place custom code markers |
647
- | `markerText` | `string` | `"CUSTOM CODE"` | Custom text for markers |
648
- | `includeInstructions` | `boolean` | `true` | Include helpful instructions in markers |
649
-
650
- ### Usage Example
651
-
652
- After running OpenAPI Sync for the first time, your generated files will include custom code markers:
653
-
654
- **endpoints.ts**
655
-
656
- ```typescript
657
- // AUTO-GENERATED FILE - DO NOT EDIT OUTSIDE CUSTOM CODE MARKERS
658
- export const getPet = (petId: string) => `/pet/${petId}`;
659
- export const createPet = "/pet";
660
- export const updatePet = (petId: string) => `/pet/${petId}`;
661
-
662
- // ============================================================
663
- // πŸ”’ CUSTOM CODE START
664
- // Add your custom code above this line
665
- // This section will be preserved during regeneration
666
- // ============================================================
667
-
668
- // ============================================================
669
- // πŸ”’ CUSTOM CODE END
670
- // ============================================================
671
- ```
672
-
673
- ### Adding Custom Code
674
-
675
- Simply add your custom code between the markers:
676
-
677
- **endpoints.ts**
678
-
679
- ```typescript
680
- export const getPet = (petId: string) => `/pet/${petId}`;
681
- export const createPet = "/pet";
682
-
683
- // ============================================================
684
- // πŸ”’ CUSTOM CODE START
685
- // ============================================================
686
-
687
- // Custom endpoints for legacy API
688
- export const legacyGetPet = (petId: string) => `/api/v1/pet/${petId}`;
689
- export const customSearch = "/api/search";
690
-
691
- // Custom utility function
692
- export const buildPetUrl = (petId: string, includePhotos: boolean) => {
693
- const base = getPet(petId);
694
- return includePhotos ? `${base}?include=photos` : base;
695
- };
696
-
697
- // ============================================================
698
- // πŸ”’ CUSTOM CODE END
699
- // ============================================================
700
-
701
- export const updatePet = (petId: string) => `/pet/${petId}`;
702
- ```
703
-
704
- ### Custom Types
705
-
706
- You can also add custom types in the `types.ts` and `shared.ts` files:
707
-
708
- **types.ts**
709
-
710
- ```typescript
711
- import * as Shared from "./shared";
712
-
713
- export type IGetPetByIdResponse = Shared.IPet;
714
- export type ICreatePetDTO = {
715
- name: string;
716
- status?: "available" | "pending" | "sold";
717
- };
718
-
719
- // ============================================================
720
- // πŸ”’ CUSTOM CODE START
721
- // ============================================================
722
-
723
- // Custom type extending generated types
724
- export interface IPetWithMetadata extends Shared.IPet {
725
- fetchedAt: Date;
726
- cached: boolean;
727
- }
728
-
729
- // Custom utility type
730
- export type PartialPet = Partial<Shared.IPet>;
731
-
732
- // Custom enum
733
- export enum PetStatus {
734
- Available = "available",
735
- Pending = "pending",
736
- Sold = "sold",
737
- }
738
-
739
- // ============================================================
740
- // πŸ”’ CUSTOM CODE END
741
- // ============================================================
742
- ```
743
-
744
- ### Position Options
745
-
746
- #### Bottom Position (Default)
747
-
748
- Custom code markers appear at the bottom of the file:
749
-
750
- ```typescript
751
- // Generated code...
752
- export const endpoint1 = "/api/v1";
753
-
754
- // πŸ”’ CUSTOM CODE START
755
- // Your custom code here
756
- // πŸ”’ CUSTOM CODE END
757
- ```
758
-
759
- #### Top Position
760
-
761
- Custom code markers appear at the top of the file:
762
-
763
- ```typescript
764
- // πŸ”’ CUSTOM CODE START
765
- // Your custom code here
766
- // πŸ”’ CUSTOM CODE END
767
-
768
- // Generated code...
769
- export const endpoint1 = "/api/v1";
770
- ```
771
-
772
- #### Both Positions
773
-
774
- Custom code markers appear at both top and bottom:
775
-
776
- ```typescript
777
- // πŸ”’ CUSTOM CODE START (TOP)
778
- // Top custom code
779
- // πŸ”’ CUSTOM CODE END
780
-
781
- // Generated code...
782
-
783
- // πŸ”’ CUSTOM CODE START (BOTTOM)
784
- // Bottom custom code
785
- // πŸ”’ CUSTOM CODE END
786
- ```
787
-
788
- ### Best Practices
789
-
790
- 1. **Don't Edit Outside Markers**: Only add code between the custom code markers. Code outside these markers will be overwritten.
791
-
792
- 2. **Use Descriptive Names**: Use clear, descriptive names for your custom code to avoid conflicts with generated code.
793
-
794
- 3. **Keep It Organized**: Group related custom code together and add comments explaining its purpose.
795
-
796
- 4. **Test After Regeneration**: After regenerating, verify your custom code is still present and working correctly.
797
-
798
- 5. **Version Control**: Commit your custom code changes separately from regeneration to track what's custom vs generated.
799
-
800
- ### Use Cases
801
-
802
- #### Legacy API Support
803
-
804
- ```typescript
805
- // πŸ”’ CUSTOM CODE START
806
- // Support for legacy v1 API that's not in OpenAPI spec
807
- export const legacyLogin = "/api/v1/auth/login";
808
- export const legacyLogout = "/api/v1/auth/logout";
809
- // πŸ”’ CUSTOM CODE END
810
- ```
811
-
812
- #### Custom Utilities
813
-
814
- ```typescript
815
- // πŸ”’ CUSTOM CODE START
816
- // Utility functions for working with generated endpoints
817
- export const isPublicEndpoint = (endpoint: string): boolean => {
818
- return endpoint.startsWith("/public/");
819
- };
820
-
821
- export const requiresAuth = (endpoint: string): boolean => {
822
- return !isPublicEndpoint(endpoint);
823
- };
824
- // πŸ”’ CUSTOM CODE END
825
- ```
826
-
827
- #### Type Extensions
828
-
829
- ```typescript
830
- // πŸ”’ CUSTOM CODE START
831
- // Extended types with additional client-side fields
832
- export interface IUserWithUI extends Shared.IUser {
833
- isLoading?: boolean;
834
- hasError?: boolean;
835
- lastFetched?: Date;
836
77
  }
837
- // πŸ”’ CUSTOM CODE END
838
- ```
839
-
840
- ### Disabling Custom Code Preservation
841
-
842
- If you want to disable custom code preservation (not recommended for most use cases):
843
-
844
- ```typescript
845
- export default {
846
- // ... other config
847
- customCode: {
848
- enabled: false, // Disables custom code preservation
849
- },
850
- } as IConfig;
851
- ```
852
-
853
- ⚠️ **Warning**: When disabled, all files will be completely overwritten on each regeneration.
854
-
855
- ## API Reference
856
-
857
- ### Exported Functions
858
-
859
- #### `Init(options?: { refetchInterval?: number }): Promise<void>`
860
-
861
- Initializes OpenAPI sync with the specified configuration.
862
-
863
- **Parameters:**
864
-
865
- - `options.refetchInterval` - Override the refetch interval from config file
866
-
867
- **Example:**
868
-
869
- ```typescript
870
- import { Init } from "openapi-sync";
871
-
872
- await Init({ refetchInterval: 10000 });
873
78
  ```
874
79
 
875
- ### Exported Types
876
-
877
- All types from the `types.ts` file are available for import:
80
+ **Advanced TypeScript Example:**
878
81
 
879
82
  ```typescript
880
- import {
881
- IConfig,
882
- IOpenApiSpec,
883
- IOpenApSchemaSpec,
884
- IConfigReplaceWord,
885
- IConfigExclude,
886
- IConfigInclude,
887
- IConfigDoc,
888
- } from "openapi-sync/types";
889
- ```
890
-
891
- ## Advanced Examples
892
-
893
- ### Advanced Folder Splitting Configuration
894
-
895
- ```typescript
896
- // openapi.sync.ts
897
83
  import { IConfig } from "openapi-sync/types";
898
84
 
899
85
  const config: IConfig = {
900
- refetchInterval: 5000,
86
+ refetchInterval: 10000,
901
87
  folder: "./src/api",
902
88
  api: {
903
89
  "main-api": "https://api.example.com/openapi.json",
904
90
  },
905
-
906
- // Advanced folder splitting with multiple strategies
907
- folderSplit: {
908
- byTags: true, // Enable tag-based splitting
909
- customFolder: ({ method, path, tags, operationId }) => {
910
- // Priority-based folder assignment
911
-
912
- // 1. Admin endpoints always go to admin folder
913
- if (tags?.includes("admin")) return "admin";
914
-
915
- // 2. Public API endpoints
916
- if (tags?.includes("public")) return "public";
917
-
918
- // 3. Version-based splitting
919
- if (path.startsWith("/api/v1/")) return "v1";
920
- if (path.startsWith("/api/v2/")) return "v2";
921
-
922
- // 4. Method-based organization for remaining endpoints
923
- if (method === "GET") return "read";
924
- if (method === "POST" || method === "PUT" || method === "PATCH")
925
- return "write";
926
- if (method === "DELETE") return "delete";
927
-
928
- // 5. OperationId-based splitting for specific operations
929
- if (operationId?.includes("Auth")) return "auth";
930
- if (operationId?.includes("User")) return "user";
931
-
932
- return null; // Use default structure
933
- },
934
- },
935
-
936
- // Enhanced type naming with operationId support
937
- types: {
938
- name: {
939
- prefix: "I",
940
- useOperationId: true, // Use operationId when available
941
- format: (source, data, defaultName) => {
942
- if (source === "endpoint" && data.operationId) {
943
- // Use operationId for better naming
944
- switch (data.type) {
945
- case "query":
946
- return `${data.operationId}Query`;
947
- case "dto":
948
- return `${data.operationId}DTO`;
949
- case "response":
950
- return `${data.operationId}${data.code}Response`;
951
- }
952
- }
953
- return defaultName;
954
- },
955
- },
956
- },
957
-
958
- // Enhanced endpoint configuration
91
+ folderSplit: { byTags: true },
92
+ types: { name: { prefix: "I", useOperationId: true } },
959
93
  endpoints: {
960
- name: {
961
- useOperationId: true, // Use operationId for endpoint names
962
- format: ({ operationId, method, path }, defaultName) => {
963
- if (operationId) return operationId;
964
- return defaultName;
965
- },
966
- },
967
- exclude: {
968
- tags: ["deprecated", "internal"],
969
- endpoints: [
970
- { regex: "^/internal/.*" },
971
- { path: "/debug", method: "GET" },
972
- ],
973
- },
94
+ exclude: { tags: ["deprecated"] },
95
+ doc: { showCurl: true },
974
96
  },
97
+ validations: { library: "zod" },
975
98
  };
976
99
 
977
100
  export default config;
978
101
  ```
979
102
 
980
- ### Multi-Environment Configuration
981
-
982
- ```typescript
983
- // openapi.sync.ts
984
- import { IConfig } from "openapi-sync/types";
985
-
986
- const getConfig = (): IConfig => {
987
- const env = process.env.NODE_ENV || "development";
988
-
989
- const baseConfig: IConfig = {
990
- refetchInterval: env === "development" ? 5000 : 0,
991
- folder: "./src/api",
992
- api: {},
993
- types: {
994
- name: {
995
- prefix: "I",
996
- format: (source, data, defaultName) => {
997
- if (source === "endpoint" && data.type === "response") {
998
- return `${defaultName.replace(/Response$/, "")}Data`;
999
- }
1000
- return defaultName;
1001
- },
1002
- },
1003
- },
1004
- };
1005
-
1006
- switch (env) {
1007
- case "development":
1008
- baseConfig.api = {
1009
- "local-api": "http://localhost:3000/api/openapi.json",
1010
- };
1011
- break;
1012
- case "staging":
1013
- baseConfig.api = {
1014
- "staging-api": "https://staging-api.example.com/openapi.json",
1015
- };
1016
- break;
1017
- case "production":
1018
- baseConfig.api = {
1019
- "prod-api": "https://api.example.com/openapi.json",
1020
- };
1021
- break;
1022
- }
1023
-
1024
- return baseConfig;
1025
- };
1026
-
1027
- export default getConfig;
1028
- ```
1029
-
1030
- ### Custom Type Formatting
1031
-
1032
- ```typescript
1033
- // Advanced type name formatting with operationId support
1034
- const config: IConfig = {
1035
- // ... other config
1036
- types: {
1037
- name: {
1038
- prefix: "",
1039
- useOperationId: true, // Use operationId when available
1040
- format: (source, data, defaultName) => {
1041
- if (source === "shared") {
1042
- // Shared types: UserProfile, OrderStatus, etc.
1043
- return `${data.name}`;
1044
- } else if (source === "endpoint") {
1045
- // Use operationId if available and configured
1046
- if (data.operationId) {
1047
- switch (data.type) {
1048
- case "query":
1049
- return `${data.operationId}Query`;
1050
- case "dto":
1051
- return `${data.operationId}DTO`;
1052
- case "response":
1053
- return `${data.operationId}${data.code}Response`;
1054
- default:
1055
- return defaultName;
1056
- }
1057
- }
1058
-
1059
- // Fallback to path-based naming
1060
- const method = data.method?.toUpperCase();
1061
- const cleanPath = data.path
1062
- ?.replace(/[{}\/]/g, "_")
1063
- .replace(/_+/g, "_");
1064
-
1065
- switch (data.type) {
1066
- case "query":
1067
- return `${method}${cleanPath}Query`;
1068
- case "dto":
1069
- return `${method}${cleanPath}Request`;
1070
- case "response":
1071
- return `${method}${cleanPath}${data.code}Response`;
1072
- default:
1073
- return defaultName;
1074
- }
1075
- }
1076
- return defaultName;
1077
- },
1078
- },
1079
- },
1080
- };
1081
- ```
1082
-
1083
- ### Endpoint Filtering and Selection
1084
-
1085
- ```typescript
1086
- // Advanced endpoint filtering configuration
1087
- const config: IConfig = {
1088
- // ... other config
1089
- endpoints: {
1090
- // ... other endpoint config
1091
- exclude: {
1092
- // Exclude endpoints by tags
1093
- tags: ["deprecated", "internal", "admin"],
1094
- // Exclude specific endpoints by exact path or regex pattern
1095
- endpoints: [
1096
- // Exact path matches
1097
- { path: "/admin/users", method: "DELETE" },
1098
- { path: "/admin/settings", method: "PUT" },
1099
- // Regex pattern matches
1100
- { regex: "^/internal/.*", method: "GET" },
1101
- { regex: ".*/debug$", method: "POST" },
1102
- // Exclude all methods for a specific path
1103
- { path: "/debug/logs" },
1104
- ],
1105
- },
1106
- include: {
1107
- // Include only public endpoints
1108
- tags: ["public", "user"],
1109
- // Include specific endpoints by exact path or regex pattern
1110
- endpoints: [
1111
- // Exact path matches
1112
- { path: "/public/users", method: "GET" },
1113
- { path: "/public/profile", method: "PUT" },
1114
- // Regex pattern matches
1115
- { regex: "^/public/.*", method: "GET" },
1116
- { regex: ".*/health$", method: "GET" },
1117
- ],
1118
- },
1119
- },
1120
- };
1121
- ```
1122
-
1123
- ### Path vs Regex Filtering
1124
-
1125
- ```typescript
1126
- // Demonstrating the difference between path and regex filtering
1127
- const config: IConfig = {
1128
- // ... other config
1129
- endpoints: {
1130
- exclude: {
1131
- endpoints: [
1132
- // Exact path match - only excludes exactly "/api/users"
1133
- { path: "/api/users", method: "GET" },
1134
-
1135
- // Regex match - excludes all paths starting with "/api/users"
1136
- { regex: "^/api/users.*", method: "GET" },
1137
-
1138
- // Regex match - excludes all paths ending with "/debug"
1139
- { regex: ".*/debug$", method: "GET" },
1140
-
1141
- // Regex match - excludes paths with specific pattern
1142
- { regex: "^/internal/.*/admin$", method: "POST" },
1143
- ],
1144
- },
1145
- },
1146
- };
1147
- ```
1148
-
1149
- ### URL Transformation Rules
1150
-
1151
- ```typescript
1152
- const config: IConfig = {
1153
- // ... other config
1154
- endpoints: {
1155
- value: {
1156
- replaceWords: [
1157
- // Remove API versioning from URLs
1158
- {
1159
- replace: "/api/v[0-9]+/",
1160
- with: "/",
1161
- },
1162
- // Remove internal prefixes
1163
- {
1164
- replace: "/internal/",
1165
- with: "/",
1166
- },
1167
- // Transform specific paths
1168
- {
1169
- replace: "/users/profile",
1170
- with: "/profile",
1171
- },
1172
- ],
1173
- includeServer: true,
1174
- type: "object",
1175
- },
1176
- name: {
1177
- format: ({ method, path, operationId }, defaultName) => {
1178
- // Use operationId if available, otherwise generate from path
1179
- if (operationId) {
1180
- return operationId.replace(/[^a-zA-Z0-9]/g, "");
1181
- }
1182
-
1183
- // Generate meaningful names from path and method
1184
- const cleanPath = path
1185
- .replace(/^\//, "")
1186
- .replace(/\//g, "_")
1187
- .replace(/{([^}]+)}/g, "By$1")
1188
- .replace(/[^a-zA-Z0-9_]/g, "");
1189
-
1190
- return `${method.toLowerCase()}${cleanPath}`;
1191
- },
1192
- },
1193
- },
1194
- };
1195
- ```
1196
-
1197
- ### Integration with Build Process
1198
-
1199
- #### Package.json Scripts
1200
-
1201
- ```json
1202
- {
1203
- "scripts": {
1204
- "api:sync": "openapi-sync",
1205
- "api:sync:watch": "openapi-sync --refreshinterval 3000",
1206
- "prebuild": "npm run api:sync",
1207
- "build": "tsc",
1208
- "dev": "concurrently \"npm run api:sync:watch\" \"npm run dev:server\""
1209
- }
1210
- }
1211
- ```
1212
-
1213
- #### Pre-commit Hook
1214
-
1215
- ```bash
1216
- #!/bin/sh
1217
- # .husky/pre-commit
1218
-
1219
- # Sync API types before commit
1220
- npm run api:sync
1221
-
1222
- # Add generated files to commit
1223
- git add src/api/
1224
- ```
1225
-
1226
- #### CI/CD Integration
1227
-
1228
- ```yaml
1229
- # .github/workflows/build.yml
1230
- name: Build
1231
- on: [push, pull_request]
103
+ [View full configuration options β†’](https://openapi-sync.com/docs#configuration)
1232
104
 
1233
- jobs:
1234
- build:
1235
- runs-on: ubuntu-latest
1236
- steps:
1237
- - uses: actions/checkout@v2
1238
- - uses: actions/setup-node@v2
1239
- with:
1240
- node-version: "16"
105
+ ## Documentation
1241
106
 
1242
- - name: Install dependencies
1243
- run: npm ci
1244
-
1245
- - name: Sync API types
1246
- run: npm run api:sync
1247
- env:
1248
- NODE_ENV: production
1249
-
1250
- - name: Build
1251
- run: npm run build
1252
- ```
1253
-
1254
- ### Error Handling and Monitoring
1255
-
1256
- ```typescript
1257
- import { Init } from "openapi-sync";
1258
-
1259
- const initializeAPIWithRetry = async (maxRetries = 3) => {
1260
- for (let attempt = 1; attempt <= maxRetries; attempt++) {
1261
- try {
1262
- await Init({
1263
- refetchInterval: process.env.NODE_ENV === "development" ? 5000 : 0,
1264
- });
1265
-
1266
- console.log("βœ… API types synchronized successfully");
1267
- return;
1268
- } catch (error) {
1269
- console.error(`❌ Attempt ${attempt} failed:`, error.message);
1270
-
1271
- if (attempt === maxRetries) {
1272
- console.error("🚨 Failed to sync API types after all retries");
1273
- throw error;
1274
- }
1275
-
1276
- // Wait before retry
1277
- await new Promise((resolve) => setTimeout(resolve, 2000 * attempt));
1278
- }
1279
- }
1280
- };
1281
-
1282
- // Usage in your app
1283
- initializeAPIWithRetry().catch((error) => {
1284
- // Handle critical error - maybe use cached types or exit
1285
- console.error("Critical: Could not sync API types", error);
1286
- process.exit(1);
1287
- });
1288
- ```
1289
-
1290
- ## Troubleshooting
1291
-
1292
- ### Common Issues
1293
-
1294
- #### 1. Configuration File Not Found
1295
-
1296
- ```
1297
- Error: No config found
1298
- ```
1299
-
1300
- **Solution:** Ensure you have one of these files in your project root:
1301
-
1302
- - `openapi.sync.json`
1303
- - `openapi.sync.ts`
1304
- - `openapi.sync.js`
1305
-
1306
- #### 2. Network Timeout Errors
1307
-
1308
- ```
1309
- Error: timeout of 60000ms exceeded
1310
- ```
107
+ For complete documentation including:
1311
108
 
1312
- **Solution:** The tool includes automatic retry with exponential backoff. If issues persist:
109
+ - **Configuration Options** - All available settings and customization
110
+ - **Generated Output** - Understanding generated files and structure
111
+ - **Custom Code Injection** - Preserve your code between regenerations
112
+ - **Validation Schemas** - Runtime validation with Zod, Yup, or Joi
113
+ - **Advanced Examples** - Complex configurations and use cases
114
+ - **API Reference** - Programmatic usage and type definitions
115
+ - **Troubleshooting** - Common issues and solutions
1313
116
 
1314
- - Check your internet connection
1315
- - Verify the OpenAPI spec URL is accessible
1316
- - Consider increasing timeout in your configuration
1317
-
1318
- #### 3. TypeScript Compilation Errors
1319
-
1320
- ```
1321
- Error: Cannot find module './src/api/petstore/types'
1322
- ```
1323
-
1324
- **Solution:**
1325
-
1326
- - Ensure the sync process completed successfully
1327
- - Check that the `folder` path in config is correct
1328
- - Verify TypeScript can resolve the generated paths
1329
-
1330
- #### 4. Invalid OpenAPI Specification
1331
-
1332
- ```
1333
- Error: Schema validation failed
1334
- ```
1335
-
1336
- **Solution:**
1337
-
1338
- - Validate your OpenAPI spec using online validators
1339
- - Check for syntax errors in YAML/JSON
1340
- - Ensure the spec follows OpenAPI 3.0+ standards
1341
-
1342
- ### Performance Optimization
1343
-
1344
- #### 1. Reduce Refetch Frequency
1345
-
1346
- ```json
1347
- {
1348
- "refetchInterval": 30000 // Increase to 30 seconds
1349
- }
1350
- ```
1351
-
1352
- #### 2. Disable Documentation Generation
1353
-
1354
- ```json
1355
- {
1356
- "types": { "doc": { "disable": true } },
1357
- "endpoints": { "doc": { "disable": true } }
1358
- }
1359
- ```
1360
-
1361
- #### 3. Use Specific Output Folders
1362
-
1363
- ```json
1364
- {
1365
- "folder": "./src/api" // Specific path instead of root
1366
- }
1367
- ```
1368
-
1369
- ### Debugging
1370
-
1371
- #### Enable Verbose Logging
1372
-
1373
- ```typescript
1374
- // Set environment variable for debugging
1375
- process.env.DEBUG = "openapi-sync:*";
1376
-
1377
- import { Init } from "openapi-sync";
1378
- await Init();
1379
- ```
1380
-
1381
- #### Check Generated State
1382
-
1383
- The tool maintains state in `db.json` to track changes:
1384
-
1385
- ```json
1386
- // db.json
1387
- {
1388
- "petstore": {
1389
- "openapi": "3.0.0",
1390
- "info": { "title": "Petstore API" }
1391
- // ... full OpenAPI spec
1392
- }
1393
- }
1394
- ```
1395
-
1396
- ### Getting Help
1397
-
1398
- 1. **Check the Issues**: [GitHub Issues](https://github.com/akintomiwa-fisayo/openapi-sync/issues)
1399
- 2. **Create a Bug Report**: Include your configuration and error logs
1400
- 3. **Feature Requests**: Describe your use case and expected behavior
117
+ **Visit [openapi-sync.com](https://openapi-sync.com)**
1401
118
 
1402
119
  ---
1403
120
 
1404
- ## Changelog
1405
-
1406
- - v2.1.13: Fix dts type fixes and Clean up tsup build config and introduction of unit testing
1407
- - v2.1.12: Add automatic sync support for function-based config, improved handling of missing OpenAPI urls
1408
- - v2.1.11: Folder splitting configuration for organized code generation
1409
- - v2.1.10: OperationId-based naming for types and endpoints, enhanced filtering and tag support
1410
- - v2.1.9: Enhanced JSONStringify function improvements
1411
- - v2.1.8: File extension corrections and path handling
1412
- - v2.1.7: Endpoint tags support in API documentation
1413
- - v2.1.6: Improved handling of nullable fields in generated types
1414
- - v2.1.5: Fixed bug with recursive schema references
1415
- - v2.1.4: Enhanced error messages on invalid config
1416
- - v2.1.3: Add more informative debugging logs
1417
- - v2.1.2: Support enum descriptions in output
1418
- - v2.1.1: Update dependencies, minor TypeScript type fixes
1419
- - v2.1.0: Initial v2 major release with new sync engine
1420
- - v2.0.0: Major refactor and breaking changes for v2
1421
-
1422
121
  ## License
1423
122
 
1424
- This project is licensed under the ISC License - see the [LICENSE](LICENSE) file for details.
123
+ ISC License - see [LICENSE](LICENSE) file for details.
1425
124
 
1426
125
  ## Contributing
1427
126
 
1428
- Contributions are welcome! Please read our contributing guidelines and submit pull requests to our GitHub repository.
127
+ Contributions welcome! Submit pull requests to our [GitHub repository](https://github.com/akintomiwa-fisayo/openapi-sync).
128
+
129
+ ---
130
+
131
+ **[πŸ“˜ Full Documentation](https://openapi-sync.com) | [GitHub](https://github.com/akintomiwa-fisayo/openapi-sync) | [npm](https://www.npmjs.com/package/openapi-sync)**