openapi-sync 1.0.24 → 1.0.25
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 +113 -12
- package/dist/Openapi-sync/components/helpers.js +15 -4
- package/dist/Openapi-sync/index.js +46 -18
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,37 +1,138 @@
|
|
|
1
1
|
# Openapi-sync
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://www.npmjs.com/package/openapi-sync)
|
|
4
|
+
[](https://github.com/akintomiwa-fisayo/openapi-sync/blob/main/LICENSE)
|
|
5
|
+
|
|
6
|
+
**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 and endpoint definitions from your OpenAPI schema, ensuring your API documentation stays up-to-date with your code.
|
|
7
|
+
|
|
8
|
+
## Features
|
|
9
|
+
|
|
10
|
+
- 🔄 Real-time API Synchronization
|
|
11
|
+
- 📝 Automatic Type Generation
|
|
12
|
+
- 🔄 Periodic API Refetching
|
|
13
|
+
- 📁 Configurable Output Directory
|
|
14
|
+
- 🔄 Customizable Naming Conventions
|
|
15
|
+
- 🔄 Endpoint URL Transformation
|
|
16
|
+
- 🔄 Schema Validation
|
|
17
|
+
- 🔄 CLI Integration
|
|
18
|
+
- 🔄 TypeScript Support
|
|
19
|
+
- 🔄 YAML and JSON Support
|
|
4
20
|
|
|
5
21
|
## Installation
|
|
6
22
|
|
|
7
|
-
|
|
23
|
+
Install the package using npm:
|
|
8
24
|
|
|
9
25
|
```bash
|
|
10
26
|
npm install openapi-sync
|
|
11
27
|
```
|
|
12
28
|
|
|
29
|
+
Or use it directly via npx:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
npx openapi-sync
|
|
33
|
+
```
|
|
34
|
+
|
|
13
35
|
## Configuration
|
|
14
36
|
|
|
15
|
-
Create
|
|
37
|
+
Create a `openapi.sync.json` file in your project root with the following structure:
|
|
38
|
+
|
|
39
|
+
```json
|
|
40
|
+
{
|
|
41
|
+
"refetchInterval": 5000, // milliseconds between API refetches
|
|
42
|
+
"folder": "/path/to/output", // output directory for generated files
|
|
43
|
+
"api": {
|
|
44
|
+
"example1": "https://api.example.com/openapi.json",
|
|
45
|
+
"example2": "https://api.example.com/openapi.yaml"
|
|
46
|
+
},
|
|
47
|
+
"naming": {
|
|
48
|
+
"replaceWords": [
|
|
49
|
+
{
|
|
50
|
+
"replace": "Api",
|
|
51
|
+
"with": "",
|
|
52
|
+
"type": "endpoint"
|
|
53
|
+
}
|
|
54
|
+
]
|
|
55
|
+
},
|
|
56
|
+
"endpoints": {
|
|
57
|
+
"value": {
|
|
58
|
+
"replaceWords": [
|
|
59
|
+
{
|
|
60
|
+
"replace": "/api/v\\d/",
|
|
61
|
+
"with": ""
|
|
62
|
+
}
|
|
63
|
+
]
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
```
|
|
16
68
|
|
|
17
69
|
## Usage
|
|
18
70
|
|
|
19
|
-
|
|
71
|
+
### CLI Commands
|
|
20
72
|
|
|
21
73
|
```bash
|
|
74
|
+
# Basic usage
|
|
22
75
|
npx openapi-sync
|
|
76
|
+
|
|
77
|
+
# With custom refetch interval
|
|
78
|
+
npx openapi-sync --refreshinterval 30000
|
|
23
79
|
```
|
|
24
80
|
|
|
25
|
-
|
|
81
|
+
### Programmatic Usage
|
|
26
82
|
|
|
27
|
-
```
|
|
28
|
-
"
|
|
29
|
-
|
|
30
|
-
|
|
83
|
+
```typescript
|
|
84
|
+
import { Init } from "openapi-sync";
|
|
85
|
+
|
|
86
|
+
// Initialize with custom options
|
|
87
|
+
await Init({
|
|
88
|
+
refetchInterval: 30000, // optional, defaults to config value
|
|
89
|
+
});
|
|
31
90
|
```
|
|
32
91
|
|
|
33
|
-
##
|
|
92
|
+
## Output Generation
|
|
93
|
+
|
|
94
|
+
The tool generates:
|
|
95
|
+
|
|
96
|
+
1. TypeScript interfaces for API endpoints
|
|
97
|
+
2. Type definitions for request/response bodies
|
|
98
|
+
3. Shared component types
|
|
99
|
+
4. Endpoint URL constants
|
|
100
|
+
|
|
101
|
+
## Type Generation
|
|
102
|
+
|
|
103
|
+
The tool supports:
|
|
104
|
+
|
|
105
|
+
- Primitive types (string, number, boolean, etc.)
|
|
106
|
+
- Complex types (objects, arrays)
|
|
107
|
+
- Enums
|
|
108
|
+
- Nullable types
|
|
109
|
+
- Any types
|
|
110
|
+
- Shared components
|
|
111
|
+
- Request/response bodies
|
|
112
|
+
|
|
113
|
+
## Error Handling
|
|
114
|
+
|
|
115
|
+
The tool includes:
|
|
116
|
+
|
|
117
|
+
- Network error retries
|
|
118
|
+
- Schema validation
|
|
119
|
+
- Type generation error handling
|
|
120
|
+
- State persistence
|
|
121
|
+
|
|
122
|
+
## API Documentation
|
|
123
|
+
|
|
124
|
+
For detailed API documentation, please refer to the [OpenAPI specification](https://spec.openapis.org/oas/v3.0.3).
|
|
125
|
+
|
|
126
|
+
## License
|
|
127
|
+
|
|
128
|
+
This project is licensed under the ISC License - see the [LICENSE](LICENSE) file for details.
|
|
129
|
+
|
|
130
|
+
## Support
|
|
131
|
+
|
|
132
|
+
For support, please open an issue in the GitHub repository.
|
|
133
|
+
|
|
134
|
+
## Acknowledgments
|
|
34
135
|
|
|
35
|
-
-
|
|
36
|
-
-
|
|
136
|
+
- Thanks to the OpenAPI Initiative for the OpenAPI specification
|
|
137
|
+
- Thanks to all contributors and users of this package
|
|
37
138
|
- Flexible CLI Commands: Sync your API at any point in the development process on app start, pre-commit, or via manual triggers.
|
|
@@ -64,7 +64,7 @@ const capitalize = (text) => {
|
|
|
64
64
|
return capitalizedWord;
|
|
65
65
|
};
|
|
66
66
|
exports.capitalize = capitalize;
|
|
67
|
-
const getSharedComponentName = (componentName) => `IApi${(0, exports.capitalize)(componentName)}`;
|
|
67
|
+
const getSharedComponentName = (componentName, componentType) => `IApi${(0, exports.capitalize)(componentName)}`;
|
|
68
68
|
exports.getSharedComponentName = getSharedComponentName;
|
|
69
69
|
const getEndpointDetails = (path, method) => {
|
|
70
70
|
const pathParts = path.split("/");
|
|
@@ -106,17 +106,23 @@ const getEndpointDetails = (path, method) => {
|
|
|
106
106
|
};
|
|
107
107
|
exports.getEndpointDetails = getEndpointDetails;
|
|
108
108
|
const parseSchemaToType = (apiDoc, schema, name, isRequired, options) => {
|
|
109
|
-
let
|
|
109
|
+
let overrideName = "";
|
|
110
|
+
let componentName = "";
|
|
110
111
|
let type = "";
|
|
111
112
|
if (schema) {
|
|
112
113
|
if (schema.$ref) {
|
|
114
|
+
// console.log("Type schema 2");
|
|
113
115
|
if (schema.$ref[0] === "#") {
|
|
114
116
|
let pathToComponentParts = (schema.$ref || "").split("/");
|
|
115
117
|
pathToComponentParts.shift();
|
|
116
118
|
const pathToComponent = pathToComponentParts.join(".");
|
|
117
119
|
const component = lodash_1.default.get(apiDoc, pathToComponent, null);
|
|
120
|
+
// console.log("Type schema 3", pathToComponentParts);
|
|
118
121
|
if (component) {
|
|
119
|
-
|
|
122
|
+
if (component === null || component === void 0 ? void 0 : component.name) {
|
|
123
|
+
overrideName = component.name;
|
|
124
|
+
}
|
|
125
|
+
componentName = pathToComponentParts[pathToComponentParts.length - 1];
|
|
120
126
|
// Reference component via import instead of parsing
|
|
121
127
|
type += `${(options === null || options === void 0 ? void 0 : options.noSharedImport) ? "" : "Shared."}${(0, exports.getSharedComponentName)(componentName)}`;
|
|
122
128
|
// type += `${parseSchemaToType(apiDoc, component, "", isRequired)}`;
|
|
@@ -210,9 +216,14 @@ const parseSchemaToType = (apiDoc, schema, name, isRequired, options) => {
|
|
|
210
216
|
//Default type to string if no schema provided
|
|
211
217
|
type = "string";
|
|
212
218
|
}
|
|
219
|
+
let _name = overrideName || name;
|
|
220
|
+
if ((options === null || options === void 0 ? void 0 : options.useComponentName) && !_name) {
|
|
221
|
+
_name = componentName;
|
|
222
|
+
}
|
|
223
|
+
let typeName = _name ? `\t"${_name}"${isRequired ? "" : "?"}: ` : "";
|
|
213
224
|
const nullable = (schema === null || schema === void 0 ? void 0 : schema.nullable) ? " | null" : "";
|
|
214
225
|
return type.length > 0
|
|
215
|
-
? `${typeName}${type}${nullable}${
|
|
226
|
+
? `${typeName}${type}${nullable}${_name ? ";\n" : ""}`
|
|
216
227
|
: "";
|
|
217
228
|
};
|
|
218
229
|
exports.parseSchemaToType = parseSchemaToType;
|
|
@@ -71,18 +71,46 @@ const OpenapiSync = (apiUrl, apiName, refetchInterval) => __awaiter(void 0, void
|
|
|
71
71
|
(0, state_1.setState)(apiName, spec);
|
|
72
72
|
let endpointsFileContent = "";
|
|
73
73
|
let typesFileContent = "";
|
|
74
|
-
let sharedTypesFileContent =
|
|
75
|
-
if (spec.components
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
74
|
+
let sharedTypesFileContent = {};
|
|
75
|
+
if (spec.components) {
|
|
76
|
+
Object.keys(spec.components).forEach((key) => {
|
|
77
|
+
if ([
|
|
78
|
+
"schemas",
|
|
79
|
+
"responses",
|
|
80
|
+
"parameters",
|
|
81
|
+
"examples",
|
|
82
|
+
"requestBodies",
|
|
83
|
+
"headers",
|
|
84
|
+
"links",
|
|
85
|
+
"callbacks",
|
|
86
|
+
].includes(key)) {
|
|
87
|
+
// Create components (shared) types
|
|
88
|
+
const components = spec.components[key];
|
|
89
|
+
const contentKeys = Object.keys(components);
|
|
90
|
+
// only need 1 schema so will us the first schema provided
|
|
91
|
+
contentKeys.forEach((contentKey) => {
|
|
92
|
+
var _a, _b;
|
|
93
|
+
/* const schema = (() => {
|
|
94
|
+
switch (key) {
|
|
95
|
+
case "parameters":
|
|
96
|
+
return components[contentKey].schema;
|
|
97
|
+
default:
|
|
98
|
+
return components[contentKey];
|
|
99
|
+
}
|
|
100
|
+
})() as IOpenApSchemaSpec; */
|
|
101
|
+
const schema = (((_a = components[contentKey]) === null || _a === void 0 ? void 0 : _a.schema)
|
|
102
|
+
? components[contentKey].schema
|
|
103
|
+
: components[contentKey]);
|
|
104
|
+
const typeCnt = `${(0, helpers_1.parseSchemaToType)(spec, schema, "", true, {
|
|
105
|
+
noSharedImport: true,
|
|
106
|
+
useComponentName: ["parameters"].includes(key),
|
|
107
|
+
})}`;
|
|
108
|
+
if (typeCnt) {
|
|
109
|
+
sharedTypesFileContent[key] =
|
|
110
|
+
((_b = sharedTypesFileContent[key]) !== null && _b !== void 0 ? _b : "") +
|
|
111
|
+
`export type ${(0, helpers_1.getSharedComponentName)(contentKey)} = ${typeCnt};\n`;
|
|
112
|
+
}
|
|
113
|
+
});
|
|
86
114
|
}
|
|
87
115
|
});
|
|
88
116
|
}
|
|
@@ -152,9 +180,9 @@ const OpenapiSync = (apiUrl, apiName, refetchInterval) => __awaiter(void 0, void
|
|
|
152
180
|
// create query parameters types
|
|
153
181
|
const parameters = (_b = endpointSpec[method]) === null || _b === void 0 ? void 0 : _b.parameters;
|
|
154
182
|
let typeCnt = "";
|
|
155
|
-
parameters.forEach((param) => {
|
|
156
|
-
if (param.in === "query" && param.name) {
|
|
157
|
-
typeCnt += `${(0, helpers_1.parseSchemaToType)(spec, param.schema, param.name, param.required)}`;
|
|
183
|
+
parameters.forEach((param, i) => {
|
|
184
|
+
if (param.$ref || (param.in === "query" && param.name)) {
|
|
185
|
+
typeCnt += `${(0, helpers_1.parseSchemaToType)(spec, param.$ref ? param : param.schema, param.name || "", param.required)}`;
|
|
158
186
|
}
|
|
159
187
|
});
|
|
160
188
|
if (typeCnt) {
|
|
@@ -187,21 +215,21 @@ const OpenapiSync = (apiUrl, apiName, refetchInterval) => __awaiter(void 0, void
|
|
|
187
215
|
yield fs_1.default.promises.mkdir(path_1.default.dirname(endpointsFilePath), { recursive: true });
|
|
188
216
|
// Create the file asynchronously
|
|
189
217
|
yield fs_1.default.promises.writeFile(endpointsFilePath, endpointsFileContent);
|
|
190
|
-
if (sharedTypesFileContent.length > 0) {
|
|
218
|
+
if (Object.values(sharedTypesFileContent).length > 0) {
|
|
191
219
|
// Create the necessary directories
|
|
192
220
|
const sharedTypesFilePath = path_1.default.join(rootUsingCwd, folderPath, "types", "shared.ts");
|
|
193
221
|
yield fs_1.default.promises.mkdir(path_1.default.dirname(sharedTypesFilePath), {
|
|
194
222
|
recursive: true,
|
|
195
223
|
});
|
|
196
224
|
// Create the file asynchronously
|
|
197
|
-
yield fs_1.default.promises.writeFile(sharedTypesFilePath, sharedTypesFileContent);
|
|
225
|
+
yield fs_1.default.promises.writeFile(sharedTypesFilePath, Object.values(sharedTypesFileContent).join("\n"));
|
|
198
226
|
}
|
|
199
227
|
if (typesFileContent.length > 0) {
|
|
200
228
|
// Create the necessary directories
|
|
201
229
|
const typesFilePath = path_1.default.join(rootUsingCwd, folderPath, "types", "index.ts");
|
|
202
230
|
yield fs_1.default.promises.mkdir(path_1.default.dirname(typesFilePath), { recursive: true });
|
|
203
231
|
// Create the file asynchronously
|
|
204
|
-
yield fs_1.default.promises.writeFile(typesFilePath, `${sharedTypesFileContent.length > 0
|
|
232
|
+
yield fs_1.default.promises.writeFile(typesFilePath, `${Object.values(sharedTypesFileContent).length > 0
|
|
205
233
|
? `import * as Shared from "./shared";\n\n`
|
|
206
234
|
: ""}${typesFileContent}`);
|
|
207
235
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "openapi-sync",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.25",
|
|
4
4
|
"description": "A developer-friendly tool designed to keep your API up-to-date by leveraging OpenAPI schemas. It automates the generation of endpoint URIs and type definitions, including shared types, directly from your OpenAPI specification.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|