@softeria/ms-365-mcp-server 0.11.5 → 0.12.0
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/bin/modules/simplified-openapi.mjs +465 -274
- package/dist/generated/client.js +4394 -8142
- package/package.json +3 -1
|
@@ -2,7 +2,8 @@ import fs from 'fs';
|
|
|
2
2
|
import yaml from 'js-yaml';
|
|
3
3
|
|
|
4
4
|
export function createAndSaveSimplifiedOpenAPI(endpointsFile, openapiFile, openapiTrimmedFile) {
|
|
5
|
-
const
|
|
5
|
+
const allEndpoints = JSON.parse(fs.readFileSync(endpointsFile, 'utf8'));
|
|
6
|
+
const endpoints = allEndpoints.filter((endpoint) => !endpoint.disabled);
|
|
6
7
|
|
|
7
8
|
const spec = fs.readFileSync(openapiFile, 'utf8');
|
|
8
9
|
const openApiSpec = yaml.load(spec);
|
|
@@ -39,6 +40,10 @@ export function createAndSaveSimplifiedOpenAPI(endpointsFile, openapiFile, opena
|
|
|
39
40
|
simplifyAnyOfInPaths(openApiSpec.paths);
|
|
40
41
|
}
|
|
41
42
|
|
|
43
|
+
console.log('🧹 Pruning unused schemas...');
|
|
44
|
+
const usedSchemas = findUsedSchemas(openApiSpec);
|
|
45
|
+
pruneUnusedSchemas(openApiSpec, usedSchemas);
|
|
46
|
+
|
|
42
47
|
fs.writeFileSync(openapiTrimmedFile, yaml.dump(openApiSpec));
|
|
43
48
|
}
|
|
44
49
|
|
|
@@ -50,327 +55,256 @@ function removeODataTypeRecursively(obj) {
|
|
|
50
55
|
return;
|
|
51
56
|
}
|
|
52
57
|
|
|
53
|
-
if (obj.properties && obj.properties['@odata.type']) {
|
|
54
|
-
delete obj.properties['@odata.type'];
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
if (obj.required && Array.isArray(obj.required)) {
|
|
58
|
-
const typeIndex = obj.required.indexOf('@odata.type');
|
|
59
|
-
if (typeIndex !== -1) {
|
|
60
|
-
obj.required.splice(typeIndex, 1);
|
|
61
|
-
if (obj.required.length === 0) {
|
|
62
|
-
delete obj.required;
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
if (obj.properties) {
|
|
68
|
-
removeODataTypeRecursively(obj.properties);
|
|
69
|
-
Object.values(obj.properties).forEach((prop) => removeODataTypeRecursively(prop));
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
if (obj.additionalProperties && typeof obj.additionalProperties === 'object') {
|
|
73
|
-
removeODataTypeRecursively(obj.additionalProperties);
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
if (obj.items) {
|
|
77
|
-
removeODataTypeRecursively(obj.items);
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
['allOf', 'anyOf', 'oneOf'].forEach((key) => {
|
|
81
|
-
if (obj[key] && Array.isArray(obj[key])) {
|
|
82
|
-
obj[key].forEach((item) => removeODataTypeRecursively(item));
|
|
83
|
-
}
|
|
84
|
-
});
|
|
85
|
-
|
|
86
58
|
Object.keys(obj).forEach((key) => {
|
|
87
|
-
if (
|
|
59
|
+
if (key === '@odata.type') {
|
|
60
|
+
delete obj[key];
|
|
61
|
+
} else {
|
|
88
62
|
removeODataTypeRecursively(obj[key]);
|
|
89
63
|
}
|
|
90
64
|
});
|
|
91
65
|
}
|
|
92
66
|
|
|
93
|
-
function
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
let flattenedCount = 0;
|
|
67
|
+
function simplifyAnyOfInPaths(paths) {
|
|
68
|
+
Object.entries(paths).forEach(([pathKey, pathItem]) => {
|
|
69
|
+
if (!pathItem || typeof pathItem !== 'object') return;
|
|
97
70
|
|
|
98
|
-
|
|
99
|
-
|
|
71
|
+
Object.entries(pathItem).forEach(([method, operation]) => {
|
|
72
|
+
if (!operation || typeof operation !== 'object') return;
|
|
100
73
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
for (const subSchema of schema.allOf) {
|
|
107
|
-
if (subSchema.$ref && subSchema.$ref.startsWith('#/components/schemas/')) {
|
|
108
|
-
const refName = subSchema.$ref.replace('#/components/schemas/', '');
|
|
109
|
-
if (schemaName === 'microsoft.graph.attendee') {
|
|
110
|
-
console.log(
|
|
111
|
-
`Processing ref ${refName} for attendee, exists: ${!!schemas[refName]}, has properties: ${!!schemas[refName]?.properties}`
|
|
112
|
-
);
|
|
113
|
-
if (schemas[refName]?.properties) {
|
|
114
|
-
console.log(`Properties in ${refName}:`, Object.keys(schemas[refName].properties));
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
if (schemas[refName] && schemas[refName].properties) {
|
|
118
|
-
Object.assign(flattened.properties, schemas[refName].properties);
|
|
119
|
-
if (schemas[refName].required) {
|
|
120
|
-
schemas[refName].required.forEach((req) => required.add(req));
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
} else if (subSchema.properties) {
|
|
124
|
-
Object.assign(flattened.properties, subSchema.properties);
|
|
125
|
-
if (subSchema.required) {
|
|
126
|
-
subSchema.required.forEach((req) => required.add(req));
|
|
127
|
-
}
|
|
74
|
+
if (operation.parameters && Array.isArray(operation.parameters)) {
|
|
75
|
+
operation.parameters.forEach((param) => {
|
|
76
|
+
if (param.schema && param.schema.anyOf) {
|
|
77
|
+
simplifyAnyOfSchema(param.schema, `Path ${pathKey} ${method} parameter`);
|
|
128
78
|
}
|
|
79
|
+
});
|
|
80
|
+
}
|
|
129
81
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
if (schema.properties) {
|
|
138
|
-
Object.assign(flattened.properties, schema.properties);
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
if (schema.required) {
|
|
142
|
-
schema.required.forEach((req) => required.add(req));
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
Object.keys(schema).forEach((key) => {
|
|
146
|
-
if (!['allOf', 'properties', 'required'].includes(key) && !flattened[key]) {
|
|
147
|
-
flattened[key] = schema[key];
|
|
82
|
+
if (operation.requestBody && operation.requestBody.content) {
|
|
83
|
+
Object.entries(operation.requestBody.content).forEach(([mediaType, mediaTypeObj]) => {
|
|
84
|
+
if (mediaTypeObj.schema && mediaTypeObj.schema.anyOf) {
|
|
85
|
+
simplifyAnyOfSchema(
|
|
86
|
+
mediaTypeObj.schema,
|
|
87
|
+
`Path ${pathKey} ${method} requestBody ${mediaType}`
|
|
88
|
+
);
|
|
148
89
|
}
|
|
149
90
|
});
|
|
91
|
+
}
|
|
150
92
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
if (!flattened.properties.emailAddress && attendeeBase.properties.emailAddress) {
|
|
163
|
-
flattened.properties.emailAddress = attendeeBase.properties.emailAddress;
|
|
164
|
-
console.log('Added emailAddress property to attendee');
|
|
165
|
-
}
|
|
166
|
-
if (!flattened.properties.type && attendeeBase.properties.type) {
|
|
167
|
-
flattened.properties.type = attendeeBase.properties.type;
|
|
168
|
-
console.log('Added type property to attendee');
|
|
169
|
-
}
|
|
93
|
+
if (operation.responses) {
|
|
94
|
+
Object.entries(operation.responses).forEach(([statusCode, response]) => {
|
|
95
|
+
if (response.content) {
|
|
96
|
+
Object.entries(response.content).forEach(([mediaType, mediaTypeObj]) => {
|
|
97
|
+
if (mediaTypeObj.schema && mediaTypeObj.schema.anyOf) {
|
|
98
|
+
simplifyAnyOfSchema(
|
|
99
|
+
mediaTypeObj.schema,
|
|
100
|
+
`Path ${pathKey} ${method} response ${statusCode} ${mediaType}`
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
});
|
|
170
104
|
}
|
|
171
|
-
}
|
|
172
|
-
} catch (error) {
|
|
173
|
-
console.warn(`Warning: Could not flatten schema ${schemaName}:`, error.message);
|
|
105
|
+
});
|
|
174
106
|
}
|
|
175
|
-
}
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
}
|
|
176
110
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
const hasRef = schema.anyOf.some((item) => item.$ref);
|
|
180
|
-
const hasNullableObject = schema.anyOf.some(
|
|
181
|
-
(item) =>
|
|
182
|
-
item.type === 'object' && item.nullable === true && Object.keys(item).length <= 2
|
|
183
|
-
);
|
|
111
|
+
function simplifyAnyOfSchema(schema, context) {
|
|
112
|
+
if (!schema.anyOf || !Array.isArray(schema.anyOf)) return;
|
|
184
113
|
|
|
185
|
-
|
|
186
|
-
console.log(`Simplifying anyOf in ${schemaName} (ref + nullable object pattern)`);
|
|
187
|
-
const refItem = schema.anyOf.find((item) => item.$ref);
|
|
188
|
-
const simplified = { ...refItem };
|
|
189
|
-
simplified.nullable = true;
|
|
190
|
-
Object.keys(schema).forEach((key) => {
|
|
191
|
-
if (!['anyOf'].includes(key) && !simplified[key]) {
|
|
192
|
-
simplified[key] = schema[key];
|
|
193
|
-
}
|
|
194
|
-
});
|
|
195
|
-
schemas[schemaName] = simplified;
|
|
196
|
-
flattenedCount++;
|
|
197
|
-
}
|
|
198
|
-
} else if (schema.anyOf.length > 2) {
|
|
199
|
-
console.log(`Simplifying anyOf in ${schemaName} (${schema.anyOf.length} -> 1 option)`);
|
|
200
|
-
const simplified = { ...schema.anyOf[0] };
|
|
201
|
-
simplified.nullable = true;
|
|
202
|
-
simplified.description = `Simplified from ${schema.anyOf.length} anyOf options`;
|
|
203
|
-
schemas[schemaName] = simplified;
|
|
204
|
-
flattenedCount++;
|
|
205
|
-
}
|
|
206
|
-
}
|
|
114
|
+
const anyOfItems = schema.anyOf;
|
|
207
115
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
schemas[schemaName] = simplified;
|
|
214
|
-
flattenedCount++;
|
|
215
|
-
}
|
|
116
|
+
if (anyOfItems.length === 2) {
|
|
117
|
+
const hasRef = anyOfItems.some((item) => item.$ref);
|
|
118
|
+
const hasNullableObject = anyOfItems.some(
|
|
119
|
+
(item) => item.type === 'object' && item.nullable === true && Object.keys(item).length <= 2
|
|
120
|
+
);
|
|
216
121
|
|
|
217
|
-
if (
|
|
218
|
-
console.log(
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
122
|
+
if (hasRef && hasNullableObject) {
|
|
123
|
+
console.log(`Simplifying anyOf in ${context} (ref + nullable object pattern)`);
|
|
124
|
+
const refItem = anyOfItems.find((item) => item.$ref);
|
|
125
|
+
delete schema.anyOf;
|
|
126
|
+
schema.$ref = refItem.$ref;
|
|
127
|
+
schema.nullable = true;
|
|
128
|
+
}
|
|
129
|
+
} else if (anyOfItems.length > 2) {
|
|
130
|
+
console.log(`Simplifying anyOf in ${context} (multiple options)`);
|
|
131
|
+
schema.type = anyOfItems[0].type || 'object';
|
|
132
|
+
schema.nullable = true;
|
|
133
|
+
schema.description = `${schema.description || ''} [Simplified from ${
|
|
134
|
+
anyOfItems.length
|
|
135
|
+
} options]`.trim();
|
|
136
|
+
delete schema.anyOf;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
223
139
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
priorityProperties[key] = schema.properties[key];
|
|
228
|
-
}
|
|
229
|
-
});
|
|
230
|
-
}
|
|
140
|
+
function flattenComplexSchemasRecursively(schemas) {
|
|
141
|
+
Object.entries(schemas).forEach(([schemaName, schema]) => {
|
|
142
|
+
if (!schema || typeof schema !== 'object') return;
|
|
231
143
|
|
|
232
|
-
|
|
233
|
-
allKeys.slice(0, remainingSlots).forEach((key) => {
|
|
234
|
-
if (!priorityProperties[key]) {
|
|
235
|
-
priorityProperties[key] = schema.properties[key];
|
|
236
|
-
}
|
|
237
|
-
});
|
|
144
|
+
flattenComplexSchema(schema, schemaName);
|
|
238
145
|
|
|
239
|
-
|
|
240
|
-
schema.
|
|
241
|
-
|
|
242
|
-
|
|
146
|
+
if (schema.allOf) {
|
|
147
|
+
const flattenedSchema = mergeAllOfSchemas(schema.allOf, schemas);
|
|
148
|
+
Object.assign(schema, flattenedSchema);
|
|
149
|
+
delete schema.allOf;
|
|
243
150
|
}
|
|
244
151
|
|
|
245
|
-
if (schema.properties) {
|
|
246
|
-
|
|
152
|
+
if (schema.properties && shouldReduceProperties(schema)) {
|
|
153
|
+
reduceProperties(schema, schemaName);
|
|
247
154
|
}
|
|
248
|
-
});
|
|
249
155
|
|
|
250
|
-
Object.keys(schemas).forEach((schemaName) => {
|
|
251
|
-
const schema = schemas[schemaName];
|
|
252
156
|
if (schema.properties) {
|
|
253
|
-
|
|
254
|
-
const prop = schema.properties[propName];
|
|
255
|
-
if (prop && prop.anyOf && Array.isArray(prop.anyOf) && prop.anyOf.length === 2) {
|
|
256
|
-
const hasRef = prop.anyOf.some((item) => item.$ref);
|
|
257
|
-
const hasNullableObject = prop.anyOf.some(
|
|
258
|
-
(item) =>
|
|
259
|
-
item.type === 'object' && item.nullable === true && Object.keys(item).length <= 2
|
|
260
|
-
);
|
|
261
|
-
|
|
262
|
-
if (hasRef && hasNullableObject) {
|
|
263
|
-
console.log(
|
|
264
|
-
`Simplifying anyOf in ${schemaName}.${propName} (ref + nullable object pattern)`
|
|
265
|
-
);
|
|
266
|
-
const refItem = prop.anyOf.find((item) => item.$ref);
|
|
267
|
-
delete prop.anyOf;
|
|
268
|
-
prop.$ref = refItem.$ref;
|
|
269
|
-
prop.nullable = true;
|
|
270
|
-
flattenedCount++;
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
});
|
|
157
|
+
simplifyNestedPropertiesRecursively(schema.properties);
|
|
274
158
|
}
|
|
275
159
|
});
|
|
160
|
+
}
|
|
276
161
|
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
162
|
+
function flattenComplexSchema(schema, schemaName) {
|
|
163
|
+
if (schema.anyOf && Array.isArray(schema.anyOf)) {
|
|
164
|
+
if (schema.anyOf.length === 2) {
|
|
165
|
+
const hasRef = schema.anyOf.some((item) => item.$ref);
|
|
166
|
+
const hasNullableObject = schema.anyOf.some(
|
|
167
|
+
(item) => item.type === 'object' && item.nullable === true && Object.keys(item).length <= 2
|
|
168
|
+
);
|
|
283
169
|
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
170
|
+
if (hasRef && hasNullableObject) {
|
|
171
|
+
console.log(`Simplifying anyOf in ${schemaName} (ref + nullable object pattern)`);
|
|
172
|
+
const refItem = schema.anyOf.find((item) => item.$ref);
|
|
173
|
+
delete schema.anyOf;
|
|
174
|
+
schema.$ref = refItem.$ref;
|
|
175
|
+
schema.nullable = true;
|
|
176
|
+
}
|
|
177
|
+
} else if (schema.anyOf.length > 2) {
|
|
178
|
+
console.log(`Simplifying anyOf in ${schemaName} (${schema.anyOf.length} options)`);
|
|
179
|
+
const firstOption = schema.anyOf[0];
|
|
180
|
+
schema.type = firstOption.type || 'object';
|
|
181
|
+
schema.nullable = true;
|
|
182
|
+
schema.description = `${schema.description || ''} [Simplified from ${
|
|
183
|
+
schema.anyOf.length
|
|
184
|
+
} options]`.trim();
|
|
185
|
+
delete schema.anyOf;
|
|
291
186
|
}
|
|
292
187
|
}
|
|
188
|
+
|
|
189
|
+
if (schema.oneOf && Array.isArray(schema.oneOf) && schema.oneOf.length > 2) {
|
|
190
|
+
console.log(`Simplifying oneOf in ${schemaName} (${schema.oneOf.length} options)`);
|
|
191
|
+
const firstOption = schema.oneOf[0];
|
|
192
|
+
schema.type = firstOption.type || 'object';
|
|
193
|
+
schema.nullable = true;
|
|
194
|
+
schema.description = `${schema.description || ''} [Simplified from ${
|
|
195
|
+
schema.oneOf.length
|
|
196
|
+
} options]`.trim();
|
|
197
|
+
delete schema.oneOf;
|
|
198
|
+
}
|
|
293
199
|
}
|
|
294
200
|
|
|
295
|
-
function
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
const pathItem = paths[path];
|
|
301
|
-
Object.keys(pathItem).forEach((method) => {
|
|
302
|
-
const operation = pathItem[method];
|
|
303
|
-
if (operation && typeof operation === 'object') {
|
|
304
|
-
if (operation.responses) {
|
|
305
|
-
Object.keys(operation.responses).forEach((statusCode) => {
|
|
306
|
-
const response = operation.responses[statusCode];
|
|
307
|
-
if (response && response.content) {
|
|
308
|
-
Object.keys(response.content).forEach((contentType) => {
|
|
309
|
-
const mediaType = response.content[contentType];
|
|
310
|
-
if (mediaType && mediaType.schema) {
|
|
311
|
-
simplifiedCount += simplifyAnyOfPattern(
|
|
312
|
-
mediaType.schema,
|
|
313
|
-
`${path}.${method}.${statusCode}`
|
|
314
|
-
);
|
|
315
|
-
}
|
|
316
|
-
});
|
|
317
|
-
}
|
|
318
|
-
});
|
|
319
|
-
}
|
|
201
|
+
function shouldReduceProperties(schema) {
|
|
202
|
+
if (!schema.properties) return false;
|
|
203
|
+
const propertyCount = Object.keys(schema.properties).length;
|
|
204
|
+
return propertyCount > 25;
|
|
205
|
+
}
|
|
320
206
|
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
207
|
+
function reduceProperties(schema, schemaName) {
|
|
208
|
+
const properties = schema.properties;
|
|
209
|
+
const propertyCount = Object.keys(properties).length;
|
|
210
|
+
|
|
211
|
+
if (propertyCount > 25) {
|
|
212
|
+
console.log(`Reducing properties in ${schemaName} (${propertyCount} -> 25)`);
|
|
213
|
+
|
|
214
|
+
const priorityProperties = [
|
|
215
|
+
'id',
|
|
216
|
+
'name',
|
|
217
|
+
'displayName',
|
|
218
|
+
'description',
|
|
219
|
+
'createdDateTime',
|
|
220
|
+
'lastModifiedDateTime',
|
|
221
|
+
'status',
|
|
222
|
+
'state',
|
|
223
|
+
'type',
|
|
224
|
+
'value',
|
|
225
|
+
'email',
|
|
226
|
+
'userPrincipalName',
|
|
227
|
+
'title',
|
|
228
|
+
'content',
|
|
229
|
+
'body',
|
|
230
|
+
'subject',
|
|
231
|
+
'message',
|
|
232
|
+
'error',
|
|
233
|
+
'code',
|
|
234
|
+
'details',
|
|
235
|
+
'url',
|
|
236
|
+
'href',
|
|
237
|
+
'path',
|
|
238
|
+
'method',
|
|
239
|
+
'enabled',
|
|
240
|
+
];
|
|
241
|
+
|
|
242
|
+
const keptProperties = {};
|
|
243
|
+
const propertyKeys = Object.keys(properties);
|
|
244
|
+
|
|
245
|
+
priorityProperties.forEach((key) => {
|
|
246
|
+
if (properties[key]) {
|
|
247
|
+
keptProperties[key] = properties[key];
|
|
332
248
|
}
|
|
333
249
|
});
|
|
334
|
-
});
|
|
335
|
-
|
|
336
|
-
console.log(`Simplified ${simplifiedCount} anyOf patterns in paths`);
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
function simplifyAnyOfPattern(obj, context = '') {
|
|
340
|
-
let count = 0;
|
|
341
250
|
|
|
342
|
-
|
|
251
|
+
const remainingSlots = 25 - Object.keys(keptProperties).length;
|
|
252
|
+
const otherKeys = propertyKeys.filter((key) => !keptProperties[key]);
|
|
343
253
|
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
(item) => item.type === 'object' && item.nullable === true && Object.keys(item).length <= 2
|
|
348
|
-
);
|
|
254
|
+
otherKeys.slice(0, remainingSlots).forEach((key) => {
|
|
255
|
+
keptProperties[key] = properties[key];
|
|
256
|
+
});
|
|
349
257
|
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
});
|
|
356
|
-
Object.assign(obj, refItem);
|
|
357
|
-
obj.nullable = true;
|
|
358
|
-
delete obj.anyOf;
|
|
359
|
-
count++;
|
|
360
|
-
}
|
|
258
|
+
schema.properties = keptProperties;
|
|
259
|
+
schema.additionalProperties = true;
|
|
260
|
+
schema.description = `${
|
|
261
|
+
schema.description || ''
|
|
262
|
+
} [Note: Simplified from ${propertyCount} properties to 25 most common ones]`.trim();
|
|
361
263
|
}
|
|
264
|
+
}
|
|
362
265
|
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
266
|
+
function mergeAllOfSchemas(allOfArray, allSchemas) {
|
|
267
|
+
const merged = {
|
|
268
|
+
type: 'object',
|
|
269
|
+
properties: {},
|
|
270
|
+
};
|
|
271
|
+
|
|
272
|
+
allOfArray.forEach((item) => {
|
|
273
|
+
if (item.$ref) {
|
|
274
|
+
const refSchemaName = item.$ref.replace('#/components/schemas/', '');
|
|
275
|
+
const refSchema = allSchemas[refSchemaName];
|
|
276
|
+
if (refSchema) {
|
|
277
|
+
console.log(
|
|
278
|
+
`Processing ref ${refSchemaName} for ${item.title}, exists: true, has properties: ${!!refSchema.properties}`
|
|
279
|
+
);
|
|
280
|
+
if (refSchema.properties) {
|
|
281
|
+
console.log(`Ensuring ${item.title} has all required properties from ${refSchemaName}`);
|
|
282
|
+
Object.assign(merged.properties, refSchema.properties);
|
|
283
|
+
}
|
|
284
|
+
if (refSchema.required) {
|
|
285
|
+
merged.required = [...(merged.required || []), ...refSchema.required];
|
|
286
|
+
}
|
|
287
|
+
if (refSchema.description && !merged.description) {
|
|
288
|
+
merged.description = refSchema.description;
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
} else if (item.properties) {
|
|
292
|
+
Object.assign(merged.properties, item.properties);
|
|
293
|
+
if (item.required) {
|
|
294
|
+
merged.required = [...(merged.required || []), ...item.required];
|
|
295
|
+
}
|
|
366
296
|
}
|
|
367
297
|
});
|
|
368
298
|
|
|
369
|
-
|
|
299
|
+
if (merged.required) {
|
|
300
|
+
merged.required = [...new Set(merged.required)];
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
return merged;
|
|
370
304
|
}
|
|
371
305
|
|
|
372
|
-
function simplifyNestedPropertiesRecursively(properties, currentDepth, maxDepth) {
|
|
373
|
-
if (currentDepth >= maxDepth) {
|
|
306
|
+
function simplifyNestedPropertiesRecursively(properties, currentDepth = 0, maxDepth = 3) {
|
|
307
|
+
if (!properties || typeof properties !== 'object' || currentDepth >= maxDepth) {
|
|
374
308
|
return;
|
|
375
309
|
}
|
|
376
310
|
|
|
@@ -422,3 +356,260 @@ function simplifyNestedPropertiesRecursively(properties, currentDepth, maxDepth)
|
|
|
422
356
|
}
|
|
423
357
|
});
|
|
424
358
|
}
|
|
359
|
+
|
|
360
|
+
function findUsedSchemas(openApiSpec) {
|
|
361
|
+
const usedSchemas = new Set();
|
|
362
|
+
const schemasToProcess = [];
|
|
363
|
+
const schemas = openApiSpec.components?.schemas || {};
|
|
364
|
+
const responses = openApiSpec.components?.responses || {};
|
|
365
|
+
const paths = openApiSpec.paths || {};
|
|
366
|
+
|
|
367
|
+
Object.entries(paths).forEach(([, pathItem]) => {
|
|
368
|
+
Object.entries(pathItem).forEach(([, operation]) => {
|
|
369
|
+
if (typeof operation !== 'object') return;
|
|
370
|
+
|
|
371
|
+
if (operation.requestBody?.content) {
|
|
372
|
+
Object.values(operation.requestBody.content).forEach((content) => {
|
|
373
|
+
if (content.schema?.$ref) {
|
|
374
|
+
const schemaName = content.schema.$ref.replace('#/components/schemas/', '');
|
|
375
|
+
schemasToProcess.push(schemaName);
|
|
376
|
+
}
|
|
377
|
+
});
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
if (operation.responses) {
|
|
381
|
+
Object.entries(operation.responses).forEach(([, response]) => {
|
|
382
|
+
if (response.$ref) {
|
|
383
|
+
const responseName = response.$ref.replace('#/components/responses/', '');
|
|
384
|
+
const responseDefinition = responses[responseName];
|
|
385
|
+
if (responseDefinition?.content) {
|
|
386
|
+
Object.values(responseDefinition.content).forEach((content) => {
|
|
387
|
+
if (content.schema?.$ref) {
|
|
388
|
+
const schemaName = content.schema.$ref.replace('#/components/schemas/', '');
|
|
389
|
+
schemasToProcess.push(schemaName);
|
|
390
|
+
}
|
|
391
|
+
});
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
if (response.content) {
|
|
396
|
+
Object.values(response.content).forEach((content) => {
|
|
397
|
+
if (content.schema?.$ref) {
|
|
398
|
+
const schemaName = content.schema.$ref.replace('#/components/schemas/', '');
|
|
399
|
+
schemasToProcess.push(schemaName);
|
|
400
|
+
}
|
|
401
|
+
});
|
|
402
|
+
}
|
|
403
|
+
});
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
if (operation.parameters) {
|
|
407
|
+
operation.parameters.forEach((param) => {
|
|
408
|
+
if (param.schema?.$ref) {
|
|
409
|
+
const schemaName = param.schema.$ref.replace('#/components/schemas/', '');
|
|
410
|
+
schemasToProcess.push(schemaName);
|
|
411
|
+
}
|
|
412
|
+
});
|
|
413
|
+
}
|
|
414
|
+
});
|
|
415
|
+
});
|
|
416
|
+
|
|
417
|
+
const visited = new Set();
|
|
418
|
+
|
|
419
|
+
function processSchema(schemaName) {
|
|
420
|
+
if (visited.has(schemaName)) return;
|
|
421
|
+
visited.add(schemaName);
|
|
422
|
+
|
|
423
|
+
const schema = schemas[schemaName];
|
|
424
|
+
if (!schema) {
|
|
425
|
+
console.log(`⚠️ Warning: Schema ${schemaName} not found`);
|
|
426
|
+
return;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
usedSchemas.add(schemaName);
|
|
430
|
+
|
|
431
|
+
findRefsInObject(schema, (ref) => {
|
|
432
|
+
const refSchemaName = ref.replace('#/components/schemas/', '');
|
|
433
|
+
if (schemas[refSchemaName]) {
|
|
434
|
+
processSchema(refSchemaName);
|
|
435
|
+
} else {
|
|
436
|
+
console.log(`⚠️ Schema ${schemaName} references missing schema: ${refSchemaName}`);
|
|
437
|
+
}
|
|
438
|
+
});
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
schemasToProcess.forEach((schemaName) => processSchema(schemaName));
|
|
442
|
+
|
|
443
|
+
[
|
|
444
|
+
'microsoft.graph.ODataErrors.ODataError',
|
|
445
|
+
'microsoft.graph.ODataErrors.MainError',
|
|
446
|
+
'microsoft.graph.ODataErrors.ErrorDetails',
|
|
447
|
+
'microsoft.graph.ODataErrors.InnerError',
|
|
448
|
+
].forEach((errorSchema) => {
|
|
449
|
+
if (schemas[errorSchema]) {
|
|
450
|
+
processSchema(errorSchema);
|
|
451
|
+
}
|
|
452
|
+
});
|
|
453
|
+
|
|
454
|
+
console.log(
|
|
455
|
+
` Found ${usedSchemas.size} used schemas out of ${Object.keys(schemas).length} total schemas`
|
|
456
|
+
);
|
|
457
|
+
|
|
458
|
+
return usedSchemas;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
function findRefsInObject(obj, callback, visited = new Set()) {
|
|
462
|
+
if (!obj || typeof obj !== 'object' || visited.has(obj)) return;
|
|
463
|
+
visited.add(obj);
|
|
464
|
+
|
|
465
|
+
if (Array.isArray(obj)) {
|
|
466
|
+
obj.forEach((item) => findRefsInObject(item, callback, visited));
|
|
467
|
+
return;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
Object.entries(obj).forEach(([key, value]) => {
|
|
471
|
+
if (key === '$ref' && typeof value === 'string' && value.startsWith('#/components/schemas/')) {
|
|
472
|
+
callback(value);
|
|
473
|
+
} else if (typeof value === 'object') {
|
|
474
|
+
findRefsInObject(value, callback, visited);
|
|
475
|
+
}
|
|
476
|
+
});
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
function cleanBrokenRefs(obj, availableSchemas, visited = new Set()) {
|
|
480
|
+
if (!obj || typeof obj !== 'object' || visited.has(obj)) return;
|
|
481
|
+
visited.add(obj);
|
|
482
|
+
|
|
483
|
+
if (Array.isArray(obj)) {
|
|
484
|
+
for (let i = obj.length - 1; i >= 0; i--) {
|
|
485
|
+
const item = obj[i];
|
|
486
|
+
if (item && typeof item === 'object' && item.$ref) {
|
|
487
|
+
const refSchemaName = item.$ref.replace('#/components/schemas/', '');
|
|
488
|
+
if (!availableSchemas[refSchemaName]) {
|
|
489
|
+
console.log(` Removing broken reference: ${refSchemaName}`);
|
|
490
|
+
obj.splice(i, 1);
|
|
491
|
+
}
|
|
492
|
+
} else if (typeof item === 'object') {
|
|
493
|
+
cleanBrokenRefs(item, availableSchemas, visited);
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
return;
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
Object.entries(obj).forEach(([key, value]) => {
|
|
500
|
+
if (key === '$ref' && typeof value === 'string' && value.startsWith('#/components/schemas/')) {
|
|
501
|
+
const refSchemaName = value.replace('#/components/schemas/', '');
|
|
502
|
+
if (!availableSchemas[refSchemaName]) {
|
|
503
|
+
console.log(` Removing broken $ref: ${refSchemaName}`);
|
|
504
|
+
delete obj[key];
|
|
505
|
+
if (Object.keys(obj).length === 0) {
|
|
506
|
+
obj.type = 'object';
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
} else if (typeof value === 'object') {
|
|
510
|
+
cleanBrokenRefs(value, availableSchemas, visited);
|
|
511
|
+
}
|
|
512
|
+
});
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
function pruneUnusedSchemas(openApiSpec, usedSchemas) {
|
|
516
|
+
const schemas = openApiSpec.components?.schemas || {};
|
|
517
|
+
const originalCount = Object.keys(schemas).length;
|
|
518
|
+
|
|
519
|
+
Object.keys(schemas).forEach((schemaName) => {
|
|
520
|
+
if (!usedSchemas.has(schemaName)) {
|
|
521
|
+
delete schemas[schemaName];
|
|
522
|
+
}
|
|
523
|
+
});
|
|
524
|
+
|
|
525
|
+
Object.values(schemas).forEach((schema) => {
|
|
526
|
+
if (schema) {
|
|
527
|
+
cleanBrokenRefs(schema, schemas);
|
|
528
|
+
}
|
|
529
|
+
});
|
|
530
|
+
|
|
531
|
+
if (openApiSpec.components?.responses) {
|
|
532
|
+
Object.values(openApiSpec.components.responses).forEach((response) => {
|
|
533
|
+
if (response) {
|
|
534
|
+
cleanBrokenRefs(response, schemas);
|
|
535
|
+
}
|
|
536
|
+
});
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
if (openApiSpec.paths) {
|
|
540
|
+
Object.values(openApiSpec.paths).forEach((pathItem) => {
|
|
541
|
+
if (pathItem) {
|
|
542
|
+
cleanBrokenRefs(pathItem, schemas);
|
|
543
|
+
}
|
|
544
|
+
});
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
const newCount = Object.keys(schemas).length;
|
|
548
|
+
const reduction = (((originalCount - newCount) / originalCount) * 100).toFixed(1);
|
|
549
|
+
|
|
550
|
+
console.log(` Removed ${originalCount - newCount} unused schemas (${reduction}% reduction)`);
|
|
551
|
+
console.log(` Final schema count: ${newCount} (from ${originalCount})`);
|
|
552
|
+
|
|
553
|
+
if (openApiSpec.components?.responses) {
|
|
554
|
+
const usedResponses = new Set();
|
|
555
|
+
|
|
556
|
+
Object.values(openApiSpec.paths || {}).forEach((pathItem) => {
|
|
557
|
+
Object.values(pathItem).forEach((operation) => {
|
|
558
|
+
if (operation.responses) {
|
|
559
|
+
Object.values(operation.responses).forEach((response) => {
|
|
560
|
+
if (response.$ref) {
|
|
561
|
+
const responseName = response.$ref.replace('#/components/responses/', '');
|
|
562
|
+
usedResponses.add(responseName);
|
|
563
|
+
}
|
|
564
|
+
});
|
|
565
|
+
}
|
|
566
|
+
});
|
|
567
|
+
});
|
|
568
|
+
|
|
569
|
+
usedResponses.add('error');
|
|
570
|
+
|
|
571
|
+
const responses = openApiSpec.components.responses;
|
|
572
|
+
const originalResponseCount = Object.keys(responses).length;
|
|
573
|
+
|
|
574
|
+
Object.keys(responses).forEach((responseName) => {
|
|
575
|
+
if (!usedResponses.has(responseName)) {
|
|
576
|
+
delete responses[responseName];
|
|
577
|
+
}
|
|
578
|
+
});
|
|
579
|
+
|
|
580
|
+
const newResponseCount = Object.keys(responses).length;
|
|
581
|
+
console.log(
|
|
582
|
+
` Removed ${originalResponseCount - newResponseCount} unused responses (from ${originalResponseCount} to ${newResponseCount})`
|
|
583
|
+
);
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
if (openApiSpec.components?.requestBodies) {
|
|
587
|
+
const usedRequestBodies = new Set();
|
|
588
|
+
|
|
589
|
+
Object.values(openApiSpec.paths || {}).forEach((pathItem) => {
|
|
590
|
+
Object.values(pathItem).forEach((operation) => {
|
|
591
|
+
if (operation.requestBody?.$ref) {
|
|
592
|
+
const requestBodyName = operation.requestBody.$ref.replace(
|
|
593
|
+
'#/components/requestBodies/',
|
|
594
|
+
''
|
|
595
|
+
);
|
|
596
|
+
usedRequestBodies.add(requestBodyName);
|
|
597
|
+
}
|
|
598
|
+
});
|
|
599
|
+
});
|
|
600
|
+
|
|
601
|
+
const requestBodies = openApiSpec.components.requestBodies;
|
|
602
|
+
const originalRequestBodyCount = Object.keys(requestBodies).length;
|
|
603
|
+
|
|
604
|
+
Object.keys(requestBodies).forEach((requestBodyName) => {
|
|
605
|
+
if (!usedRequestBodies.has(requestBodyName)) {
|
|
606
|
+
delete requestBodies[requestBodyName];
|
|
607
|
+
}
|
|
608
|
+
});
|
|
609
|
+
|
|
610
|
+
const newRequestBodyCount = Object.keys(requestBodies).length;
|
|
611
|
+
console.log(
|
|
612
|
+
` Removed ${originalRequestBodyCount - newRequestBodyCount} unused request bodies (from ${originalRequestBodyCount} to ${newRequestBodyCount})`
|
|
613
|
+
);
|
|
614
|
+
}
|
|
615
|
+
}
|