openapi-sync 1.0.0 → 1.0.2
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/Openapi-sync/components/helpers.ts +150 -0
- package/Openapi-sync/components/regex.ts +2 -0
- package/Openapi-sync/index.ts +252 -0
- package/Openapi-sync/state.ts +15 -0
- package/Openapi-sync/types.ts +43 -0
- package/README.md +14 -0
- package/bin/cli +4 -0
- package/dist/Openapi-sync/components/helpers.js +159 -0
- package/dist/Openapi-sync/components/regex.js +5 -0
- package/dist/Openapi-sync/index.js +182 -0
- package/dist/Openapi-sync/state.js +16 -0
- package/dist/Openapi-sync/types.js +2 -0
- package/dist/index.js +30 -4
- package/index.d.ts +1 -0
- package/index.ts +24 -3
- package/openapi.sync.sample.json +8 -0
- package/package.json +19 -2
- package/tsconfig.json +1 -1
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { IOpenApiSpec, IOpenApSchemaSpec } from "../types";
|
|
2
|
+
import { variableNameChar } from "./regex";
|
|
3
|
+
import propertyExpr from "property-expr";
|
|
4
|
+
import * as yaml from "js-yaml";
|
|
5
|
+
|
|
6
|
+
export const isJson = (value: any) => {
|
|
7
|
+
return ["object"].includes(typeof value) && !(value instanceof Blob);
|
|
8
|
+
};
|
|
9
|
+
export const isYamlString = (fileContent: string) => {
|
|
10
|
+
try {
|
|
11
|
+
yaml.load(fileContent);
|
|
12
|
+
return true;
|
|
13
|
+
} catch (en) {
|
|
14
|
+
const e = en as any;
|
|
15
|
+
if (e instanceof yaml.YAMLException) {
|
|
16
|
+
return false;
|
|
17
|
+
} else {
|
|
18
|
+
throw e;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export const yamlStringToJson = (fileContent: string) => {
|
|
24
|
+
if (isYamlString(fileContent)) {
|
|
25
|
+
const content = yaml.load(fileContent);
|
|
26
|
+
|
|
27
|
+
const jsonString = JSON.stringify(content, null, 2);
|
|
28
|
+
const json = JSON.parse(jsonString);
|
|
29
|
+
return json;
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export const capitalize = (text: string) => {
|
|
34
|
+
const capitalizedWord =
|
|
35
|
+
text.substring(0, 1).toUpperCase() + text.substring(1);
|
|
36
|
+
return capitalizedWord;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export const getSharedComponentName = (componentName: string) =>
|
|
40
|
+
`IApi${capitalize(componentName)}`;
|
|
41
|
+
|
|
42
|
+
export const getEndpointDetails = (path: string, method: string) => {
|
|
43
|
+
const pathParts = path.split("/");
|
|
44
|
+
let name = `${capitalize(method)}`;
|
|
45
|
+
const variables: string[] = [];
|
|
46
|
+
pathParts.forEach((part) => {
|
|
47
|
+
// check if part is a variable
|
|
48
|
+
if (part[0] === "{" && part[part.length - 1] === "}") {
|
|
49
|
+
const s = part.replace(/{/, "").replace(/}/, "");
|
|
50
|
+
variables.push(s);
|
|
51
|
+
part = `$${s}`;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// parse to variable name
|
|
55
|
+
let partVal = "";
|
|
56
|
+
part.split("").forEach((char) => {
|
|
57
|
+
let c = char;
|
|
58
|
+
if (!variableNameChar.test(char)) c = "/";
|
|
59
|
+
partVal += c;
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
partVal.split("/").forEach((val) => {
|
|
63
|
+
name += capitalize(val);
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
return { name, variables, pathParts };
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
export const parseSchemaToType = (
|
|
71
|
+
apiDoc: IOpenApiSpec,
|
|
72
|
+
schema: IOpenApSchemaSpec,
|
|
73
|
+
name: string,
|
|
74
|
+
isRequired?: boolean,
|
|
75
|
+
options?: {
|
|
76
|
+
noSharedImport?: boolean;
|
|
77
|
+
}
|
|
78
|
+
) => {
|
|
79
|
+
let typeName = name ? `\t${name}${isRequired ? "" : "?"}: ` : "";
|
|
80
|
+
let type = "";
|
|
81
|
+
if (schema.$ref) {
|
|
82
|
+
if (schema.$ref[0] === "#") {
|
|
83
|
+
let pathToComponentParts = (schema.$ref || "").split("/");
|
|
84
|
+
pathToComponentParts.shift();
|
|
85
|
+
const pathToComponent = pathToComponentParts.join(".");
|
|
86
|
+
const component = propertyExpr.getter(pathToComponent)(
|
|
87
|
+
apiDoc
|
|
88
|
+
) as IOpenApSchemaSpec;
|
|
89
|
+
|
|
90
|
+
if (component) {
|
|
91
|
+
const componentName =
|
|
92
|
+
pathToComponentParts[pathToComponentParts.length - 1];
|
|
93
|
+
// Reference component via import instead of parsing
|
|
94
|
+
type += `${
|
|
95
|
+
options?.noSharedImport ? "" : "Shared."
|
|
96
|
+
}${getSharedComponentName(componentName)}`;
|
|
97
|
+
// type += `${parseSchemaToType(apiDoc, component, "", isRequired)}`;
|
|
98
|
+
}
|
|
99
|
+
} else {
|
|
100
|
+
type += "";
|
|
101
|
+
//TODO $ref is a uri - use axios to fetch doc
|
|
102
|
+
}
|
|
103
|
+
} else if (schema.type) {
|
|
104
|
+
if (schema.enum && schema.enum.length > 0) {
|
|
105
|
+
if (schema.enum.length > 1) type += "(";
|
|
106
|
+
type += schema.enum
|
|
107
|
+
.map((v) => `"${v}"`)
|
|
108
|
+
.join("|")
|
|
109
|
+
.toString();
|
|
110
|
+
if (schema.enum.length > 1) type += ")";
|
|
111
|
+
} else if (["string", "integer", "number", "array"].includes(schema.type)) {
|
|
112
|
+
if (schema.type === "string") {
|
|
113
|
+
type += `string`;
|
|
114
|
+
} else if (["integer", "number"].includes(schema.type)) {
|
|
115
|
+
type += `number`;
|
|
116
|
+
} else if (schema.type === "array") {
|
|
117
|
+
if (schema.items) {
|
|
118
|
+
type += `${parseSchemaToType(
|
|
119
|
+
apiDoc,
|
|
120
|
+
schema.items,
|
|
121
|
+
"",
|
|
122
|
+
false,
|
|
123
|
+
options
|
|
124
|
+
)}[]`;
|
|
125
|
+
} else {
|
|
126
|
+
type += "any[]";
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
} else if (schema.type === "object") {
|
|
130
|
+
if (schema.properties) {
|
|
131
|
+
//parse object key one at a time
|
|
132
|
+
const objKeys = Object.keys(schema.properties);
|
|
133
|
+
const requiredKeys = schema.required || [];
|
|
134
|
+
type += "{\n";
|
|
135
|
+
objKeys.forEach((key) => {
|
|
136
|
+
type += `${parseSchemaToType(
|
|
137
|
+
apiDoc,
|
|
138
|
+
schema.properties?.[key] as IOpenApSchemaSpec,
|
|
139
|
+
key,
|
|
140
|
+
requiredKeys.includes(key),
|
|
141
|
+
options
|
|
142
|
+
)}`;
|
|
143
|
+
});
|
|
144
|
+
type += "}";
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return type.length > 0 ? `${typeName}${type}${name ? ";\n" : ""}` : "";
|
|
150
|
+
};
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import {
|
|
4
|
+
getEndpointDetails,
|
|
5
|
+
getSharedComponentName,
|
|
6
|
+
isJson,
|
|
7
|
+
isYamlString,
|
|
8
|
+
parseSchemaToType,
|
|
9
|
+
yamlStringToJson,
|
|
10
|
+
} from "./components/helpers";
|
|
11
|
+
import {
|
|
12
|
+
IOpenApiMediaTypeSpec,
|
|
13
|
+
IOpenApiParameterSpec,
|
|
14
|
+
IOpenApiRequestBodySpec,
|
|
15
|
+
IOpenApiResponseSpec,
|
|
16
|
+
IOpenApiSpec,
|
|
17
|
+
IOpenApSchemaSpec,
|
|
18
|
+
} from "./types";
|
|
19
|
+
import { isEqual } from "lodash";
|
|
20
|
+
import axios from "axios";
|
|
21
|
+
import axiosRetry from "axios-retry";
|
|
22
|
+
import { bundleFromString, createConfig } from "@redocly/openapi-core";
|
|
23
|
+
import { getState, setState } from "./state";
|
|
24
|
+
|
|
25
|
+
const rootUsingCwd = process.cwd();
|
|
26
|
+
let fetchTimeout: null | NodeJS.Timeout = null;
|
|
27
|
+
|
|
28
|
+
// Create an Axios instance
|
|
29
|
+
const apiClient = axios.create({
|
|
30
|
+
timeout: 30000, // Timeout after 30 seconds
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
// Configure axios-retry
|
|
34
|
+
axiosRetry(apiClient, {
|
|
35
|
+
retries: 20, // Number of retry attempts
|
|
36
|
+
retryCondition: (error) => {
|
|
37
|
+
// Retry on network error
|
|
38
|
+
return (
|
|
39
|
+
error.code === "ECONNABORTED" || error.message.includes("Network Error")
|
|
40
|
+
);
|
|
41
|
+
},
|
|
42
|
+
retryDelay: (retryCount) => {
|
|
43
|
+
return retryCount * 1000; // Exponential back-off: 1s, 2s, 3s, etc.
|
|
44
|
+
},
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
const OpenapiSync = async (apiUrl: string, apiName: string) => {
|
|
48
|
+
const specResponse = await apiClient.get(apiUrl);
|
|
49
|
+
|
|
50
|
+
const redoclyConfig = await createConfig({
|
|
51
|
+
extends: ["minimal"],
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
const source = JSON.stringify(
|
|
55
|
+
isJson(specResponse.data)
|
|
56
|
+
? specResponse.data
|
|
57
|
+
: yamlStringToJson(specResponse.data)
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
const lintResults = await bundleFromString({
|
|
61
|
+
source,
|
|
62
|
+
config: redoclyConfig,
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
// Load config file
|
|
66
|
+
const config = require(path.join(rootUsingCwd, "openapi.sync.json"));
|
|
67
|
+
const folderPath = path.join(config.folder || "", apiName);
|
|
68
|
+
|
|
69
|
+
const spec: IOpenApiSpec = lintResults.bundle.parsed;
|
|
70
|
+
// auto update only on dev
|
|
71
|
+
if (
|
|
72
|
+
!(
|
|
73
|
+
process.env.NODE_ENV &&
|
|
74
|
+
["production", "prod", "test", "staging"].includes(process.env.NODE_ENV)
|
|
75
|
+
)
|
|
76
|
+
) {
|
|
77
|
+
// compare new spec with old spec, continuing only if spec it different
|
|
78
|
+
// auto sync at interval
|
|
79
|
+
if (fetchTimeout) clearTimeout(fetchTimeout);
|
|
80
|
+
|
|
81
|
+
if (!isNaN(config.refetchInterval) && config.refetchInterval) {
|
|
82
|
+
// use config interval or 1 hour
|
|
83
|
+
fetchTimeout = setTimeout(
|
|
84
|
+
() => OpenapiSync(apiUrl, apiName),
|
|
85
|
+
config.refetchInterval || 60000
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const prevSpec = getState(apiName);
|
|
90
|
+
if (isEqual(prevSpec, spec)) return;
|
|
91
|
+
|
|
92
|
+
setState(apiName, spec);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
let endpointsFileContent = "";
|
|
96
|
+
let typesFileContent = "";
|
|
97
|
+
let sharedTypesFileContent = "";
|
|
98
|
+
|
|
99
|
+
if (spec.components && spec.components.schemas) {
|
|
100
|
+
// Create components (shared) types
|
|
101
|
+
const components: Record<string, IOpenApiMediaTypeSpec> =
|
|
102
|
+
spec.components.schemas;
|
|
103
|
+
const contentKeys = Object.keys(components);
|
|
104
|
+
// only need 1 schema so will us the first schema provided
|
|
105
|
+
contentKeys.forEach((key) => {
|
|
106
|
+
const typeCnt = `${parseSchemaToType(
|
|
107
|
+
spec,
|
|
108
|
+
components[key] as IOpenApSchemaSpec,
|
|
109
|
+
"",
|
|
110
|
+
true,
|
|
111
|
+
{
|
|
112
|
+
noSharedImport: true,
|
|
113
|
+
}
|
|
114
|
+
)}`;
|
|
115
|
+
if (typeCnt) {
|
|
116
|
+
sharedTypesFileContent += `export type ${getSharedComponentName(
|
|
117
|
+
key
|
|
118
|
+
)} = ${typeCnt};\n`;
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const getBodySchemaType = (requestBody: IOpenApiRequestBodySpec) => {
|
|
124
|
+
let typeCnt = "";
|
|
125
|
+
if (requestBody.content) {
|
|
126
|
+
const contentKeys = Object.keys(requestBody.content);
|
|
127
|
+
// only need 1 schema so will us the first schema provided
|
|
128
|
+
if (contentKeys[0] && requestBody.content[contentKeys[0]].schema) {
|
|
129
|
+
typeCnt += `${parseSchemaToType(
|
|
130
|
+
spec,
|
|
131
|
+
requestBody.content[contentKeys[0]].schema as IOpenApSchemaSpec,
|
|
132
|
+
""
|
|
133
|
+
)}`;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
return typeCnt;
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
Object.keys(spec.paths || {}).forEach((endpointPath) => {
|
|
140
|
+
const endpointSpec = spec.paths[endpointPath];
|
|
141
|
+
const endpointMethods = Object.keys(endpointSpec);
|
|
142
|
+
endpointMethods.forEach((method: string) => {
|
|
143
|
+
const endpoint = getEndpointDetails(endpointPath, method);
|
|
144
|
+
|
|
145
|
+
const endpointUrlTxt = endpoint.pathParts
|
|
146
|
+
.map((part) => {
|
|
147
|
+
// check if part is a variable
|
|
148
|
+
if (part[0] === "{" && part[part.length - 1] === "}") {
|
|
149
|
+
const s = part.replace(/{/, "").replace(/}/, "");
|
|
150
|
+
part = `\${${s}}`;
|
|
151
|
+
}
|
|
152
|
+
return part;
|
|
153
|
+
})
|
|
154
|
+
.join("/");
|
|
155
|
+
|
|
156
|
+
let endpointUrl = `"${endpointUrlTxt}"`;
|
|
157
|
+
if (endpoint.variables.length > 0) {
|
|
158
|
+
const params = endpoint.variables.map((v) => `${v}:string`).join(",");
|
|
159
|
+
endpointUrl = `(${params})=> \`${endpointUrlTxt}\``;
|
|
160
|
+
}
|
|
161
|
+
// Add the endpoint url
|
|
162
|
+
endpointsFileContent += `export const ${endpoint.name} = ${endpointUrl};
|
|
163
|
+
`;
|
|
164
|
+
|
|
165
|
+
if (endpointSpec[method]?.parameters) {
|
|
166
|
+
// create query parameters types
|
|
167
|
+
const parameters: IOpenApiParameterSpec[] =
|
|
168
|
+
endpointSpec[method]?.parameters;
|
|
169
|
+
let typeCnt = "";
|
|
170
|
+
parameters.forEach((param) => {
|
|
171
|
+
if (param.in === "query" && param.name) {
|
|
172
|
+
typeCnt += `${parseSchemaToType(
|
|
173
|
+
spec,
|
|
174
|
+
param.schema as any,
|
|
175
|
+
param.name,
|
|
176
|
+
param.required
|
|
177
|
+
)}`;
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
if (typeCnt) {
|
|
182
|
+
typesFileContent += `export type I${endpoint.name}Query = {\n${typeCnt}};\n`;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (endpointSpec[method]?.requestBody) {
|
|
187
|
+
//create requestBody types
|
|
188
|
+
const requestBody: IOpenApiRequestBodySpec =
|
|
189
|
+
endpointSpec[method]?.requestBody;
|
|
190
|
+
|
|
191
|
+
let typeCnt = getBodySchemaType(requestBody);
|
|
192
|
+
if (typeCnt) {
|
|
193
|
+
typesFileContent += `export type I${endpoint.name}DTO = ${typeCnt};\n`;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
if (endpointSpec[method]?.responses) {
|
|
198
|
+
// create request response types
|
|
199
|
+
const responses: IOpenApiResponseSpec = endpointSpec[method]?.responses;
|
|
200
|
+
const resCodes = Object.keys(responses);
|
|
201
|
+
resCodes.forEach((code) => {
|
|
202
|
+
let typeCnt = getBodySchemaType(responses[code]);
|
|
203
|
+
if (typeCnt) {
|
|
204
|
+
typesFileContent += `export type I${endpoint.name}${code}Response = ${typeCnt};\n`;
|
|
205
|
+
}
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
});
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
// Create the necessary directories
|
|
212
|
+
const endpointsFilePath = path.join(rootUsingCwd, folderPath, "endpoints.ts");
|
|
213
|
+
await fs.promises.mkdir(path.dirname(endpointsFilePath), { recursive: true });
|
|
214
|
+
// Create the file asynchronously
|
|
215
|
+
await fs.promises.writeFile(endpointsFilePath, endpointsFileContent);
|
|
216
|
+
|
|
217
|
+
if (sharedTypesFileContent.length > 0) {
|
|
218
|
+
// Create the necessary directories
|
|
219
|
+
const sharedTypesFilePath = path.join(
|
|
220
|
+
rootUsingCwd,
|
|
221
|
+
folderPath,
|
|
222
|
+
"types",
|
|
223
|
+
"shared.ts"
|
|
224
|
+
);
|
|
225
|
+
await fs.promises.mkdir(path.dirname(sharedTypesFilePath), {
|
|
226
|
+
recursive: true,
|
|
227
|
+
});
|
|
228
|
+
// Create the file asynchronously
|
|
229
|
+
await fs.promises.writeFile(sharedTypesFilePath, sharedTypesFileContent);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
if (typesFileContent.length > 0) {
|
|
233
|
+
// Create the necessary directories
|
|
234
|
+
const typesFilePath = path.join(
|
|
235
|
+
rootUsingCwd,
|
|
236
|
+
folderPath,
|
|
237
|
+
"types",
|
|
238
|
+
"index.ts"
|
|
239
|
+
);
|
|
240
|
+
await fs.promises.mkdir(path.dirname(typesFilePath), { recursive: true });
|
|
241
|
+
// Create the file asynchronously
|
|
242
|
+
await fs.promises.writeFile(
|
|
243
|
+
typesFilePath,
|
|
244
|
+
`${
|
|
245
|
+
sharedTypesFileContent.length > 0
|
|
246
|
+
? `import * as Shared from "./shared";\n\n`
|
|
247
|
+
: ""
|
|
248
|
+
}${typesFileContent}`
|
|
249
|
+
);
|
|
250
|
+
}
|
|
251
|
+
};
|
|
252
|
+
export default OpenapiSync;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { IOpenApiSpec } from "./types";
|
|
2
|
+
|
|
3
|
+
let state: Record<string, IOpenApiSpec> = {};
|
|
4
|
+
|
|
5
|
+
export const setState = (key: string, value: IOpenApiSpec) => {
|
|
6
|
+
state[key] = value;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export const getState = (key: string): IOpenApiSpec | undefined => {
|
|
10
|
+
return state[key];
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export const resetState = () => {
|
|
14
|
+
state = {};
|
|
15
|
+
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
export type IOpenApiSpec = Record<"openapi", string> & Record<string, any>;
|
|
2
|
+
|
|
3
|
+
export type IOpenApSchemaSpec = {
|
|
4
|
+
type: "string" | "integer" | "number" | "array" | "object";
|
|
5
|
+
example?: any;
|
|
6
|
+
enum?: string[];
|
|
7
|
+
format?: string;
|
|
8
|
+
items?: IOpenApSchemaSpec;
|
|
9
|
+
required?: string[];
|
|
10
|
+
$ref?: string;
|
|
11
|
+
properties?: Record<string, IOpenApSchemaSpec>;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export type IOpenApiParameterSpec = {
|
|
15
|
+
name: string;
|
|
16
|
+
in: string;
|
|
17
|
+
enum?: string[];
|
|
18
|
+
description?: string;
|
|
19
|
+
required?: boolean;
|
|
20
|
+
deprecated?: boolean;
|
|
21
|
+
allowEmptyValue?: boolean;
|
|
22
|
+
style?: string;
|
|
23
|
+
explode?: boolean;
|
|
24
|
+
allowReserved?: boolean;
|
|
25
|
+
schema?: IOpenApSchemaSpec;
|
|
26
|
+
example?: any;
|
|
27
|
+
examples?: any[];
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export type IOpenApiMediaTypeSpec = {
|
|
31
|
+
schema?: IOpenApSchemaSpec;
|
|
32
|
+
example?: any;
|
|
33
|
+
examples?: any[];
|
|
34
|
+
encoding?: any;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export type IOpenApiRequestBodySpec = {
|
|
38
|
+
description?: string;
|
|
39
|
+
required?: boolean;
|
|
40
|
+
content: Record<string, IOpenApiMediaTypeSpec>;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export type IOpenApiResponseSpec = Record<string, IOpenApiRequestBodySpec>;
|
package/README.md
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
## Install Openapi-sync
|
|
2
|
+
|
|
3
|
+
`npm i openapi-sync`
|
|
4
|
+
|
|
5
|
+
## Add a Openapi-sync config file
|
|
6
|
+
|
|
7
|
+
Add a `openapi.sync.json` file at the root of your project (check out `openapi.sync.sample.json`)
|
|
8
|
+
|
|
9
|
+
## import `openapi-sync`
|
|
10
|
+
|
|
11
|
+
import openapi-sync anywhere in your project (preferably the entry point)
|
|
12
|
+
`import 'openapi-sync'`
|
|
13
|
+
|
|
14
|
+
## Run your app
|
package/bin/cli
ADDED
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
19
|
+
if (mod && mod.__esModule) return mod;
|
|
20
|
+
var result = {};
|
|
21
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
+
__setModuleDefault(result, mod);
|
|
23
|
+
return result;
|
|
24
|
+
};
|
|
25
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
26
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
27
|
+
};
|
|
28
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
29
|
+
exports.parseSchemaToType = exports.getEndpointDetails = exports.getSharedComponentName = exports.capitalize = exports.yamlStringToJson = exports.isYamlString = exports.isJson = void 0;
|
|
30
|
+
const regex_1 = require("./regex");
|
|
31
|
+
const property_expr_1 = __importDefault(require("property-expr"));
|
|
32
|
+
const yaml = __importStar(require("js-yaml"));
|
|
33
|
+
const isJson = (value) => {
|
|
34
|
+
return ["object"].includes(typeof value) && !(value instanceof Blob);
|
|
35
|
+
};
|
|
36
|
+
exports.isJson = isJson;
|
|
37
|
+
const isYamlString = (fileContent) => {
|
|
38
|
+
try {
|
|
39
|
+
yaml.load(fileContent);
|
|
40
|
+
return true;
|
|
41
|
+
}
|
|
42
|
+
catch (en) {
|
|
43
|
+
const e = en;
|
|
44
|
+
if (e instanceof yaml.YAMLException) {
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
throw e;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
exports.isYamlString = isYamlString;
|
|
53
|
+
const yamlStringToJson = (fileContent) => {
|
|
54
|
+
if ((0, exports.isYamlString)(fileContent)) {
|
|
55
|
+
const content = yaml.load(fileContent);
|
|
56
|
+
const jsonString = JSON.stringify(content, null, 2);
|
|
57
|
+
const json = JSON.parse(jsonString);
|
|
58
|
+
return json;
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
exports.yamlStringToJson = yamlStringToJson;
|
|
62
|
+
const capitalize = (text) => {
|
|
63
|
+
const capitalizedWord = text.substring(0, 1).toUpperCase() + text.substring(1);
|
|
64
|
+
return capitalizedWord;
|
|
65
|
+
};
|
|
66
|
+
exports.capitalize = capitalize;
|
|
67
|
+
const getSharedComponentName = (componentName) => `IApi${(0, exports.capitalize)(componentName)}`;
|
|
68
|
+
exports.getSharedComponentName = getSharedComponentName;
|
|
69
|
+
const getEndpointDetails = (path, method) => {
|
|
70
|
+
const pathParts = path.split("/");
|
|
71
|
+
let name = `${(0, exports.capitalize)(method)}`;
|
|
72
|
+
const variables = [];
|
|
73
|
+
pathParts.forEach((part) => {
|
|
74
|
+
// check if part is a variable
|
|
75
|
+
if (part[0] === "{" && part[part.length - 1] === "}") {
|
|
76
|
+
const s = part.replace(/{/, "").replace(/}/, "");
|
|
77
|
+
variables.push(s);
|
|
78
|
+
part = `$${s}`;
|
|
79
|
+
}
|
|
80
|
+
// parse to variable name
|
|
81
|
+
let partVal = "";
|
|
82
|
+
part.split("").forEach((char) => {
|
|
83
|
+
let c = char;
|
|
84
|
+
if (!regex_1.variableNameChar.test(char))
|
|
85
|
+
c = "/";
|
|
86
|
+
partVal += c;
|
|
87
|
+
});
|
|
88
|
+
partVal.split("/").forEach((val) => {
|
|
89
|
+
name += (0, exports.capitalize)(val);
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
return { name, variables, pathParts };
|
|
93
|
+
};
|
|
94
|
+
exports.getEndpointDetails = getEndpointDetails;
|
|
95
|
+
const parseSchemaToType = (apiDoc, schema, name, isRequired, options) => {
|
|
96
|
+
let typeName = name ? `\t${name}${isRequired ? "" : "?"}: ` : "";
|
|
97
|
+
let type = "";
|
|
98
|
+
if (schema.$ref) {
|
|
99
|
+
if (schema.$ref[0] === "#") {
|
|
100
|
+
let pathToComponentParts = (schema.$ref || "").split("/");
|
|
101
|
+
pathToComponentParts.shift();
|
|
102
|
+
const pathToComponent = pathToComponentParts.join(".");
|
|
103
|
+
const component = property_expr_1.default.getter(pathToComponent)(apiDoc);
|
|
104
|
+
if (component) {
|
|
105
|
+
const componentName = pathToComponentParts[pathToComponentParts.length - 1];
|
|
106
|
+
// Reference component via import instead of parsing
|
|
107
|
+
type += `${(options === null || options === void 0 ? void 0 : options.noSharedImport) ? "" : "Shared."}${(0, exports.getSharedComponentName)(componentName)}`;
|
|
108
|
+
// type += `${parseSchemaToType(apiDoc, component, "", isRequired)}`;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
type += "";
|
|
113
|
+
//TODO $ref is a uri - use axios to fetch doc
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
else if (schema.type) {
|
|
117
|
+
if (schema.enum && schema.enum.length > 0) {
|
|
118
|
+
if (schema.enum.length > 1)
|
|
119
|
+
type += "(";
|
|
120
|
+
type += schema.enum
|
|
121
|
+
.map((v) => `"${v}"`)
|
|
122
|
+
.join("|")
|
|
123
|
+
.toString();
|
|
124
|
+
if (schema.enum.length > 1)
|
|
125
|
+
type += ")";
|
|
126
|
+
}
|
|
127
|
+
else if (["string", "integer", "number", "array"].includes(schema.type)) {
|
|
128
|
+
if (schema.type === "string") {
|
|
129
|
+
type += `string`;
|
|
130
|
+
}
|
|
131
|
+
else if (["integer", "number"].includes(schema.type)) {
|
|
132
|
+
type += `number`;
|
|
133
|
+
}
|
|
134
|
+
else if (schema.type === "array") {
|
|
135
|
+
if (schema.items) {
|
|
136
|
+
type += `${(0, exports.parseSchemaToType)(apiDoc, schema.items, "", false, options)}[]`;
|
|
137
|
+
}
|
|
138
|
+
else {
|
|
139
|
+
type += "any[]";
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
else if (schema.type === "object") {
|
|
144
|
+
if (schema.properties) {
|
|
145
|
+
//parse object key one at a time
|
|
146
|
+
const objKeys = Object.keys(schema.properties);
|
|
147
|
+
const requiredKeys = schema.required || [];
|
|
148
|
+
type += "{\n";
|
|
149
|
+
objKeys.forEach((key) => {
|
|
150
|
+
var _a;
|
|
151
|
+
type += `${(0, exports.parseSchemaToType)(apiDoc, (_a = schema.properties) === null || _a === void 0 ? void 0 : _a[key], key, requiredKeys.includes(key), options)}`;
|
|
152
|
+
});
|
|
153
|
+
type += "}";
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return type.length > 0 ? `${typeName}${type}${name ? ";\n" : ""}` : "";
|
|
158
|
+
};
|
|
159
|
+
exports.parseSchemaToType = parseSchemaToType;
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
12
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
|
+
};
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
const fs_1 = __importDefault(require("fs"));
|
|
16
|
+
const path_1 = __importDefault(require("path"));
|
|
17
|
+
const helpers_1 = require("./components/helpers");
|
|
18
|
+
const lodash_1 = require("lodash");
|
|
19
|
+
const axios_1 = __importDefault(require("axios"));
|
|
20
|
+
const axios_retry_1 = __importDefault(require("axios-retry"));
|
|
21
|
+
const openapi_core_1 = require("@redocly/openapi-core");
|
|
22
|
+
const state_1 = require("./state");
|
|
23
|
+
const rootUsingCwd = process.cwd();
|
|
24
|
+
let fetchTimeout = null;
|
|
25
|
+
// Create an Axios instance
|
|
26
|
+
const apiClient = axios_1.default.create({
|
|
27
|
+
timeout: 30000, // Timeout after 30 seconds
|
|
28
|
+
});
|
|
29
|
+
// Configure axios-retry
|
|
30
|
+
(0, axios_retry_1.default)(apiClient, {
|
|
31
|
+
retries: 20, // Number of retry attempts
|
|
32
|
+
retryCondition: (error) => {
|
|
33
|
+
// Retry on network error
|
|
34
|
+
return (error.code === "ECONNABORTED" || error.message.includes("Network Error"));
|
|
35
|
+
},
|
|
36
|
+
retryDelay: (retryCount) => {
|
|
37
|
+
return retryCount * 1000; // Exponential back-off: 1s, 2s, 3s, etc.
|
|
38
|
+
},
|
|
39
|
+
});
|
|
40
|
+
const OpenapiSync = (apiUrl, apiName) => __awaiter(void 0, void 0, void 0, function* () {
|
|
41
|
+
const specResponse = yield apiClient.get(apiUrl);
|
|
42
|
+
const redoclyConfig = yield (0, openapi_core_1.createConfig)({
|
|
43
|
+
extends: ["minimal"],
|
|
44
|
+
});
|
|
45
|
+
const source = JSON.stringify((0, helpers_1.isJson)(specResponse.data)
|
|
46
|
+
? specResponse.data
|
|
47
|
+
: (0, helpers_1.yamlStringToJson)(specResponse.data));
|
|
48
|
+
const lintResults = yield (0, openapi_core_1.bundleFromString)({
|
|
49
|
+
source,
|
|
50
|
+
config: redoclyConfig,
|
|
51
|
+
});
|
|
52
|
+
// Load config file
|
|
53
|
+
const config = require(path_1.default.join(rootUsingCwd, "openapi.sync.json"));
|
|
54
|
+
const folderPath = path_1.default.join(config.folder || "", apiName);
|
|
55
|
+
const spec = lintResults.bundle.parsed;
|
|
56
|
+
// auto update only on dev
|
|
57
|
+
if (!(process.env.NODE_ENV &&
|
|
58
|
+
["production", "prod", "test", "staging"].includes(process.env.NODE_ENV))) {
|
|
59
|
+
// compare new spec with old spec, continuing only if spec it different
|
|
60
|
+
// auto sync at interval
|
|
61
|
+
if (fetchTimeout)
|
|
62
|
+
clearTimeout(fetchTimeout);
|
|
63
|
+
if (!isNaN(config.refetchInterval) && config.refetchInterval) {
|
|
64
|
+
// use config interval or 1 hour
|
|
65
|
+
fetchTimeout = setTimeout(() => OpenapiSync(apiUrl, apiName), config.refetchInterval || 60000);
|
|
66
|
+
}
|
|
67
|
+
const prevSpec = (0, state_1.getState)(apiName);
|
|
68
|
+
if ((0, lodash_1.isEqual)(prevSpec, spec))
|
|
69
|
+
return;
|
|
70
|
+
(0, state_1.setState)(apiName, spec);
|
|
71
|
+
}
|
|
72
|
+
let endpointsFileContent = "";
|
|
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`;
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
const getBodySchemaType = (requestBody) => {
|
|
90
|
+
let typeCnt = "";
|
|
91
|
+
if (requestBody.content) {
|
|
92
|
+
const contentKeys = Object.keys(requestBody.content);
|
|
93
|
+
// only need 1 schema so will us the first schema provided
|
|
94
|
+
if (contentKeys[0] && requestBody.content[contentKeys[0]].schema) {
|
|
95
|
+
typeCnt += `${(0, helpers_1.parseSchemaToType)(spec, requestBody.content[contentKeys[0]].schema, "")}`;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return typeCnt;
|
|
99
|
+
};
|
|
100
|
+
Object.keys(spec.paths || {}).forEach((endpointPath) => {
|
|
101
|
+
const endpointSpec = spec.paths[endpointPath];
|
|
102
|
+
const endpointMethods = Object.keys(endpointSpec);
|
|
103
|
+
endpointMethods.forEach((method) => {
|
|
104
|
+
var _a, _b, _c, _d, _e, _f;
|
|
105
|
+
const endpoint = (0, helpers_1.getEndpointDetails)(endpointPath, method);
|
|
106
|
+
const endpointUrlTxt = endpoint.pathParts
|
|
107
|
+
.map((part) => {
|
|
108
|
+
// check if part is a variable
|
|
109
|
+
if (part[0] === "{" && part[part.length - 1] === "}") {
|
|
110
|
+
const s = part.replace(/{/, "").replace(/}/, "");
|
|
111
|
+
part = `\${${s}}`;
|
|
112
|
+
}
|
|
113
|
+
return part;
|
|
114
|
+
})
|
|
115
|
+
.join("/");
|
|
116
|
+
let endpointUrl = `"${endpointUrlTxt}"`;
|
|
117
|
+
if (endpoint.variables.length > 0) {
|
|
118
|
+
const params = endpoint.variables.map((v) => `${v}:string`).join(",");
|
|
119
|
+
endpointUrl = `(${params})=> \`${endpointUrlTxt}\``;
|
|
120
|
+
}
|
|
121
|
+
// Add the endpoint url
|
|
122
|
+
endpointsFileContent += `export const ${endpoint.name} = ${endpointUrl};
|
|
123
|
+
`;
|
|
124
|
+
if ((_a = endpointSpec[method]) === null || _a === void 0 ? void 0 : _a.parameters) {
|
|
125
|
+
// create query parameters types
|
|
126
|
+
const parameters = (_b = endpointSpec[method]) === null || _b === void 0 ? void 0 : _b.parameters;
|
|
127
|
+
let typeCnt = "";
|
|
128
|
+
parameters.forEach((param) => {
|
|
129
|
+
if (param.in === "query" && param.name) {
|
|
130
|
+
typeCnt += `${(0, helpers_1.parseSchemaToType)(spec, param.schema, param.name, param.required)}`;
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
if (typeCnt) {
|
|
134
|
+
typesFileContent += `export type I${endpoint.name}Query = {\n${typeCnt}};\n`;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
if ((_c = endpointSpec[method]) === null || _c === void 0 ? void 0 : _c.requestBody) {
|
|
138
|
+
//create requestBody types
|
|
139
|
+
const requestBody = (_d = endpointSpec[method]) === null || _d === void 0 ? void 0 : _d.requestBody;
|
|
140
|
+
let typeCnt = getBodySchemaType(requestBody);
|
|
141
|
+
if (typeCnt) {
|
|
142
|
+
typesFileContent += `export type I${endpoint.name}DTO = ${typeCnt};\n`;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
if ((_e = endpointSpec[method]) === null || _e === void 0 ? void 0 : _e.responses) {
|
|
146
|
+
// create request response types
|
|
147
|
+
const responses = (_f = endpointSpec[method]) === null || _f === void 0 ? void 0 : _f.responses;
|
|
148
|
+
const resCodes = Object.keys(responses);
|
|
149
|
+
resCodes.forEach((code) => {
|
|
150
|
+
let typeCnt = getBodySchemaType(responses[code]);
|
|
151
|
+
if (typeCnt) {
|
|
152
|
+
typesFileContent += `export type I${endpoint.name}${code}Response = ${typeCnt};\n`;
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
// Create the necessary directories
|
|
159
|
+
const endpointsFilePath = path_1.default.join(rootUsingCwd, folderPath, "endpoints.ts");
|
|
160
|
+
yield fs_1.default.promises.mkdir(path_1.default.dirname(endpointsFilePath), { recursive: true });
|
|
161
|
+
// Create the file asynchronously
|
|
162
|
+
yield fs_1.default.promises.writeFile(endpointsFilePath, endpointsFileContent);
|
|
163
|
+
if (sharedTypesFileContent.length > 0) {
|
|
164
|
+
// Create the necessary directories
|
|
165
|
+
const sharedTypesFilePath = path_1.default.join(rootUsingCwd, folderPath, "types", "shared.ts");
|
|
166
|
+
yield fs_1.default.promises.mkdir(path_1.default.dirname(sharedTypesFilePath), {
|
|
167
|
+
recursive: true,
|
|
168
|
+
});
|
|
169
|
+
// Create the file asynchronously
|
|
170
|
+
yield fs_1.default.promises.writeFile(sharedTypesFilePath, sharedTypesFileContent);
|
|
171
|
+
}
|
|
172
|
+
if (typesFileContent.length > 0) {
|
|
173
|
+
// Create the necessary directories
|
|
174
|
+
const typesFilePath = path_1.default.join(rootUsingCwd, folderPath, "types", "index.ts");
|
|
175
|
+
yield fs_1.default.promises.mkdir(path_1.default.dirname(typesFilePath), { recursive: true });
|
|
176
|
+
// Create the file asynchronously
|
|
177
|
+
yield fs_1.default.promises.writeFile(typesFilePath, `${sharedTypesFileContent.length > 0
|
|
178
|
+
? `import * as Shared from "./shared";\n\n`
|
|
179
|
+
: ""}${typesFileContent}`);
|
|
180
|
+
}
|
|
181
|
+
});
|
|
182
|
+
exports.default = OpenapiSync;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.resetState = exports.getState = exports.setState = void 0;
|
|
4
|
+
let state = {};
|
|
5
|
+
const setState = (key, value) => {
|
|
6
|
+
state[key] = value;
|
|
7
|
+
};
|
|
8
|
+
exports.setState = setState;
|
|
9
|
+
const getState = (key) => {
|
|
10
|
+
return state[key];
|
|
11
|
+
};
|
|
12
|
+
exports.getState = getState;
|
|
13
|
+
const resetState = () => {
|
|
14
|
+
state = {};
|
|
15
|
+
};
|
|
16
|
+
exports.resetState = resetState;
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,32 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
12
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
|
+
};
|
|
2
14
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
15
|
+
const Openapi_sync_1 = __importDefault(require("./Openapi-sync"));
|
|
16
|
+
const dotenv_1 = __importDefault(require("dotenv"));
|
|
17
|
+
const path_1 = __importDefault(require("path"));
|
|
18
|
+
const state_1 = require("./Openapi-sync/state");
|
|
19
|
+
dotenv_1.default.config();
|
|
20
|
+
const rootUsingCwd = process.cwd();
|
|
21
|
+
const Init = () => __awaiter(void 0, void 0, void 0, function* () {
|
|
22
|
+
// Load config file
|
|
23
|
+
const config = require(path_1.default.join(rootUsingCwd, "openapi.sync.json"));
|
|
24
|
+
const apiNames = Object.keys(config.api);
|
|
25
|
+
(0, state_1.resetState)();
|
|
26
|
+
for (let i = 0; i < apiNames.length; i += 1) {
|
|
27
|
+
const apiName = apiNames[i];
|
|
28
|
+
const apiUrl = config.api[apiName];
|
|
29
|
+
(0, Openapi_sync_1.default)(apiUrl, apiName);
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
Init();
|
package/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./Openapi-sync/types";
|
package/index.ts
CHANGED
|
@@ -1,3 +1,24 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import OpenapiSync from "./Openapi-sync";
|
|
2
|
+
import dotenv from "dotenv";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import { resetState } from "./Openapi-sync/state";
|
|
5
|
+
|
|
6
|
+
dotenv.config();
|
|
7
|
+
|
|
8
|
+
const rootUsingCwd = process.cwd();
|
|
9
|
+
|
|
10
|
+
const Init = async () => {
|
|
11
|
+
// Load config file
|
|
12
|
+
const config = require(path.join(rootUsingCwd, "openapi.sync.json"));
|
|
13
|
+
const apiNames = Object.keys(config.api);
|
|
14
|
+
|
|
15
|
+
resetState();
|
|
16
|
+
for (let i = 0; i < apiNames.length; i += 1) {
|
|
17
|
+
const apiName = apiNames[i];
|
|
18
|
+
const apiUrl = config.api[apiName];
|
|
19
|
+
|
|
20
|
+
OpenapiSync(apiUrl, apiName);
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
Init();
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
{
|
|
2
|
+
"refetchInterval": 5000,
|
|
3
|
+
"folder": "inputed/path",
|
|
4
|
+
"api": {
|
|
5
|
+
"example1": "https://raw.githubusercontent.com/OAI/OpenAPI-Specification/main/examples/v3.0/petstore.json",
|
|
6
|
+
"example2": "https://raw.githubusercontent.com/OAI/OpenAPI-Specification/main/examples/v3.0/petstore.yaml"
|
|
7
|
+
}
|
|
8
|
+
}
|
package/package.json
CHANGED
|
@@ -1,17 +1,34 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "openapi-sync",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"description": "sync openapi variables",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
7
|
+
"bin": {
|
|
8
|
+
"openapi-sync": "./bin/cli"
|
|
9
|
+
},
|
|
7
10
|
"scripts": {
|
|
8
11
|
"test": "echo \"Error: no test specified\" && exit 1",
|
|
9
|
-
"build": "tsc"
|
|
12
|
+
"build": "tsc",
|
|
13
|
+
"publish": "npm run build && npm version patch && npm publish",
|
|
14
|
+
"start": "ts-node index.ts"
|
|
10
15
|
},
|
|
11
16
|
"author": "P-Technologies",
|
|
12
17
|
"license": "ISC",
|
|
13
18
|
"devDependencies": {
|
|
19
|
+
"@types/js-yaml": "^4.0.9",
|
|
20
|
+
"@types/lodash": "^4.17.7",
|
|
14
21
|
"@types/node": "^22.1.0",
|
|
22
|
+
"ts-node": "^10.9.2",
|
|
15
23
|
"typescript": "^5.5.4"
|
|
24
|
+
},
|
|
25
|
+
"dependencies": {
|
|
26
|
+
"@redocly/openapi-core": "^1.19.0",
|
|
27
|
+
"axios": "^1.7.3",
|
|
28
|
+
"axios-retry": "^4.5.0",
|
|
29
|
+
"dotenv": "^16.4.5",
|
|
30
|
+
"js-yaml": "^4.1.0",
|
|
31
|
+
"lodash": "^4.17.21",
|
|
32
|
+
"property-expr": "^2.0.6"
|
|
16
33
|
}
|
|
17
34
|
}
|
package/tsconfig.json
CHANGED
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
// "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */
|
|
40
40
|
// "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */
|
|
41
41
|
// "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */
|
|
42
|
-
|
|
42
|
+
"resolveJsonModule": true, /* Enable importing .json files. */
|
|
43
43
|
// "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */
|
|
44
44
|
// "noResolve": true, /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */
|
|
45
45
|
|