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 CHANGED
@@ -1,37 +1,138 @@
1
1
  # Openapi-sync
2
2
 
3
- **Openapi-sync** is 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. Whether you need real-time synchronization before commits or periodic updates, openapi-sync ensures your API structure is always current and consistent. With an easy-to-use CLI, this tool integrates seamlessly into your development workflow, making API maintenance simpler and more reliable.
3
+ [![NPM Version](https://img.shields.io/npm/v/openapi-sync.svg)](https://www.npmjs.com/package/openapi-sync)
4
+ [![License](https://img.shields.io/npm/l/openapi-sync.svg)](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
- To install `openapi-sync`, run the following command:
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 an `openapi.sync.json` file at the root of your project to configure openapi-sync. You can use the provided [`openapi.sync.sample.json`](https://github.com/akintomiwa-fisayo/openapi-sync/blob/master/openapi.sync.sample.json) as reference.
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
- To start using openapi-sync, simply run the following command in your terminal:
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
- You can also add it as a script in your package.json for easy access:
81
+ ### Programmatic Usage
26
82
 
27
- ```json
28
- "scripts": {
29
- "api-sync": "npx openapi-sync",
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
- ## Features
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
- - Automated Endpoint URI Generation: Effortlessly generate endpoint URIs from your OpenAPI schema.
36
- - Type Generation: Automatically create all types defined in your API schema, including shared types, for better code consistency.
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 typeName = name ? `\t"${name}"${isRequired ? "" : "?"}: ` : "";
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
- const componentName = pathToComponentParts[pathToComponentParts.length - 1];
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}${name ? ";\n" : ""}`
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 && spec.components.schemas) {
76
- // Create components (shared) types
77
- const components = spec.components.schemas;
78
- const contentKeys = Object.keys(components);
79
- // only need 1 schema so will us the first schema provided
80
- contentKeys.forEach((key) => {
81
- const typeCnt = `${(0, helpers_1.parseSchemaToType)(spec, components[key], "", true, {
82
- noSharedImport: true,
83
- })}`;
84
- if (typeCnt) {
85
- sharedTypesFileContent += `export type ${(0, helpers_1.getSharedComponentName)(key)} = ${typeCnt};\n`;
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.24",
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",